From b40f81592bfc6f624e5f9dde8c2a54f8be678888 Mon Sep 17 00:00:00 2001 From: danceratopz Date: Tue, 9 Jun 2026 10:35:47 +0200 Subject: [PATCH 001/233] feat(spec-tool): enforce `@final` on leaf dataclasses Add a `FinalDecoratorHygiene` lint rule that flags any leaf dataclass (a `@dataclass` or `@slotted_freezable` class never used as a base) that is missing `@final`. Marking leaf dataclasses `@final` lets `mypyc` bypass the vtable for method calls and property accessors. The rule scans the whole specification once at the first fork position: every fork's modules plus the shared modules such as `ethereum.state` and `ethereum.trace`, including each package's `__init__.py`. Register the rule in `vulture_whitelist.py` since lints are discovered dynamically. --- .../lint/lints/final_decorator.py | 147 ++++++++++++++++++ vulture_whitelist.py | 6 + 2 files changed, 153 insertions(+) create mode 100644 src/ethereum_spec_tools/lint/lints/final_decorator.py diff --git a/src/ethereum_spec_tools/lint/lints/final_decorator.py b/src/ethereum_spec_tools/lint/lints/final_decorator.py new file mode 100644 index 00000000000..182500835aa --- /dev/null +++ b/src/ethereum_spec_tools/lint/lints/final_decorator.py @@ -0,0 +1,147 @@ +""" +Final Decorator Lint. + +Ensures that leaf dataclasses are decorated with `@final`. +""" + +import ast +import importlib +import inspect +import pkgutil +from typing import Generator, List, Optional, Sequence, Set, Tuple + +from ethereum_spec_tools.forks import Hardfork +from ethereum_spec_tools.lint import Diagnostic, Lint + +DATACLASS_DECORATORS = {"dataclass", "slotted_freezable"} +FINAL_DECORATOR = "final" + + +def _spec_sources( + forks: List[Hardfork], +) -> Generator[Tuple[str, str], None, None]: + """ + Yield the name and source of every module in the specification. + + This spans each fork's modules as well as the shared, fork-independent + modules such as `ethereum.state` and `ethereum.trace`. The fork packages + are walked individually because `ethereum.forks` is a namespace package + that `walk_packages` does not descend into from the `ethereum` root. The + package roots are listed explicitly because `walk_packages` never yields + the package it is walking, so their `__init__.py` files would otherwise + be skipped. + """ + names: List[str] = ["ethereum"] + for fork in forks: + names.append(fork.name) + names += [mod_info.name for mod_info in fork.walk_packages()] + + root = importlib.import_module("ethereum") + names += [ + mod_info.name + for mod_info in pkgutil.walk_packages(root.__path__, "ethereum.") + ] + + for name in names: + mod = importlib.import_module(name) + yield mod.__name__, inspect.getsource(mod) + + +class FinalDecoratorHygiene(Lint): + """ + Ensure that every leaf dataclass is decorated with `@final`. + + A *leaf* class is one that is never used as a base class anywhere in the + specification. Marking such classes `@final` lets `mypyc` bypass the + vtable for method calls and property accessors. Classes that are + subclassed are skipped (they are not leaves), as are non-dataclass types + such as enums, protocols, exceptions, and constant namespaces. + """ + + def lint( + self, forks: List[Hardfork], position: int + ) -> Sequence[Diagnostic]: + """ + Flag leaf dataclasses that are missing `@final`. + + The check spans every fork and the shared modules at once, so it only + does work at the first position. + """ + if position != 0: + return [] + + bases: Set[str] = set() + candidates: List[Tuple[str, int, str]] = [] + for name, source in _spec_sources(forks): + visitor = self._parse(source, _Visitor()) + bases |= visitor.bases + for lineno, class_name in visitor.undecorated: + candidates.append((name, lineno, class_name)) + + diagnostics: List[Diagnostic] = [] + for name, lineno, class_name in candidates: + if class_name in bases: + # The class is subclassed somewhere, so it is not a leaf. + continue + diagnostics.append( + Diagnostic( + message=( + f"`{class_name}` at line {lineno} in `{name}` is a " + "leaf dataclass and should be decorated with `@final`" + ) + ) + ) + + return diagnostics + + +def _name_of(node: ast.expr) -> Optional[str]: + """ + Return the bare name of a decorator or base class expression. + + Handles plain names (`final`), dotted names (`typing.final`), and calls + (`dataclass(frozen=True)`). Returns `None` for anything else, such as a + subscripted generic base. + """ + target = node.func if isinstance(node, ast.Call) else node + if isinstance(target, ast.Name): + return target.id + if isinstance(target, ast.Attribute): + return target.attr + return None + + +class _Visitor(ast.NodeVisitor): + """ + Collect base class names and dataclasses that lack `@final`. + """ + + bases: Set[str] + undecorated: List[Tuple[int, str]] + + def __init__(self) -> None: + self.bases = set() + self.undecorated = [] + + def visit_ClassDef(self, klass: ast.ClassDef) -> None: + """ + Visit a class definition. + """ + for base in klass.bases: + base_name = _name_of(base) + if base_name is not None: + self.bases.add(base_name) + + decorators: Set[str] = set() + for decorator in klass.decorator_list: + decorator_name = _name_of(decorator) + if decorator_name is not None: + decorators.add(decorator_name) + + if ( + decorators & DATACLASS_DECORATORS + and FINAL_DECORATOR not in decorators + ): + self.undecorated.append((klass.lineno, klass.name)) + + self.generic_visit(klass) diff --git a/vulture_whitelist.py b/vulture_whitelist.py index 6b045fb9e8b..1a663e745e5 100644 --- a/vulture_whitelist.py +++ b/vulture_whitelist.py @@ -24,6 +24,9 @@ FinalTrace, Trace, ) +from ethereum_spec_tools.lint.lints.final_decorator import ( + FinalDecoratorHygiene, +) from ethereum_spec_tools.lint.lints.glacier_forks_hygiene import ( GlacierForksHygiene, ) @@ -127,6 +130,9 @@ Trace.opName FinalTrace.gasUsed +# src/ethereum_spec_tools/lint/lints/final_decorator.py +FinalDecoratorHygiene + # src/ethereum_spec_tools/lint/lints/uint_len.py UintLenHygiene From 8ea50be3c640c2ce2be108fc634dc48a3d039cbd Mon Sep 17 00:00:00 2001 From: spencer Date: Tue, 9 Jun 2026 17:42:14 +0100 Subject: [PATCH 002/233] feat(spec-specs, tests): merge EIP-8037 to `forks/amsterdam` (#2901) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ben Adams Co-authored-by: felix Co-authored-by: fselmo Co-authored-by: Stefan <22667037+qu0b@users.noreply.github.com> Co-authored-by: Mario Vega Co-authored-by: kclowes Co-authored-by: carsons-eels Co-authored-by: Leo Lara Co-authored-by: danceratopz Co-authored-by: Paweł Bylica Co-authored-by: Sam Wilson <57262657+SamWilsn@users.noreply.github.com> Co-authored-by: marioevz <11726710+marioevz@users.noreply.github.com> Co-authored-by: kclowes <6540608+kclowes@users.noreply.github.com> --- .../plugins/execute/contracts.py | 1 + .../plugins/execute/execute_recover.py | 2 +- .../plugins/execute/pre_alloc.py | 114 +- .../pytest_commands/plugins/execute/sender.py | 2 +- .../pytest_commands/plugins/filler/filler.py | 38 + .../plugins/shared/transaction_fixtures.py | 2 +- .../client_clis/cli_types.py | 1 + .../src/execution_testing/forks/base_fork.py | 100 + .../forks/forks/eips/amsterdam/eip_8037.py | 439 +++ .../forks/forks/eips/cancun/eip_4788.py | 5 + .../execution_testing/forks/forks/forks.py | 94 +- .../forks/tests/test_forks.py | 33 + .../tools/utility/generators.py | 7 +- .../testing/src/execution_testing/vm/bases.py | 16 + .../src/execution_testing/vm/bytecode.py | 47 + .../src/execution_testing/vm/opcodes.py | 14 +- src/ethereum/forks/amsterdam/fork.py | 101 +- src/ethereum/forks/amsterdam/transactions.py | 120 +- src/ethereum/forks/amsterdam/utils/message.py | 1 + src/ethereum/forks/amsterdam/vm/__init__.py | 56 +- .../forks/amsterdam/vm/eoa_delegation.py | 41 +- src/ethereum/forks/amsterdam/vm/gas.py | 62 +- .../amsterdam/vm/instructions/storage.py | 44 +- .../forks/amsterdam/vm/instructions/system.py | 290 +- .../forks/amsterdam/vm/interpreter.py | 59 +- .../arrow_glacier/vm/instructions/system.py | 168 +- .../forks/berlin/vm/instructions/system.py | 168 +- src/ethereum/forks/bpo1/fork.py | 6 +- src/ethereum/forks/bpo1/transactions.py | 29 +- .../forks/bpo1/vm/instructions/system.py | 190 +- src/ethereum/forks/bpo2/fork.py | 6 +- src/ethereum/forks/bpo2/transactions.py | 29 +- .../forks/bpo2/vm/instructions/system.py | 190 +- src/ethereum/forks/bpo3/fork.py | 6 +- src/ethereum/forks/bpo3/transactions.py | 29 +- .../forks/bpo3/vm/instructions/system.py | 190 +- src/ethereum/forks/bpo4/fork.py | 6 +- src/ethereum/forks/bpo4/transactions.py | 29 +- .../forks/bpo4/vm/instructions/system.py | 190 +- src/ethereum/forks/bpo5/fork.py | 6 +- src/ethereum/forks/bpo5/transactions.py | 29 +- .../forks/bpo5/vm/instructions/system.py | 190 +- .../forks/byzantium/vm/instructions/system.py | 170 +- .../forks/cancun/vm/instructions/system.py | 170 +- .../constantinople/vm/instructions/system.py | 170 +- .../forks/dao_fork/vm/instructions/system.py | 132 +- .../forks/frontier/vm/instructions/system.py | 102 +- .../gray_glacier/vm/instructions/system.py | 168 +- .../forks/homestead/vm/instructions/system.py | 132 +- .../forks/istanbul/vm/instructions/system.py | 170 +- .../forks/london/vm/instructions/system.py | 168 +- .../muir_glacier/vm/instructions/system.py | 170 +- src/ethereum/forks/osaka/fork.py | 6 +- src/ethereum/forks/osaka/transactions.py | 33 +- .../forks/osaka/vm/instructions/system.py | 190 +- .../forks/paris/vm/instructions/system.py | 170 +- src/ethereum/forks/prague/fork.py | 6 +- src/ethereum/forks/prague/transactions.py | 32 +- .../forks/prague/vm/instructions/system.py | 190 +- .../forks/shanghai/vm/instructions/system.py | 170 +- .../spurious_dragon/vm/instructions/system.py | 136 +- .../vm/instructions/system.py | 136 +- src/ethereum/trace.py | 14 + .../evm_tools/t8n/evm_trace/eip3155.py | 35 +- .../evm_tools/t8n/evm_trace/protocols.py | 9 + .../evm_tools/t8n/t8n_types.py | 3 + .../eip7708_eth_transfer_logs/spec.py | 2 +- .../test_burn_logs.py | 26 +- .../test_fork_transition.py | 23 +- .../test_transfer_logs.py | 21 +- .../test_gas_accounting.py | 166 +- .../eip7843_slotnum/test_fork_transition.py | 9 +- .../amsterdam/eip7843_slotnum/test_slotnum.py | 68 +- .../test_block_access_lists.py | 109 +- .../test_block_access_lists_cross_index.py | 52 +- .../test_block_access_lists_eip7002.py | 25 +- .../test_block_access_lists_eip7702.py | 4 + .../test_block_access_lists_opcodes.py | 53 +- .../test_fork_transition.py | 33 +- .../test_max_code_size.py | 18 +- .../test_refunds.py | 78 +- .../eip8024_dupn_swapn_exchange/test_swapn.py | 52 +- .../__init__.py | 1 + .../eip_checklist_external_coverage.txt | 3 + .../eip_checklist_not_applicable.txt | 11 + .../spec.py | 54 + .../test_block_2d_gas_accounting.py | 667 +++++ .../test_eip_mainnet.py | 98 + .../test_state_gas_call.py | 1650 +++++++++++ .../test_state_gas_calldata_floor.py | 240 ++ .../test_state_gas_create.py | 2512 +++++++++++++++++ .../test_state_gas_delegation_pointer.py | 167 ++ .../test_state_gas_fork_transition.py | 236 ++ .../test_state_gas_multi_block.py | 319 +++ .../test_state_gas_ordering.py | 414 +++ .../test_state_gas_pricing.py | 617 ++++ .../test_state_gas_reservoir.py | 1964 +++++++++++++ .../test_state_gas_selfdestruct.py | 840 ++++++ .../test_state_gas_set_code.py | 1463 ++++++++++ .../test_state_gas_sstore.py | 1330 +++++++++ .../eip2929_gas_cost_increases/test_call.py | 42 +- tests/berlin/eip2930_access_list/test_acl.py | 5 +- .../eip214_staticcall/test_staticcall.py | 25 +- .../test_create_oog_from_eoa_refunds.py | 23 +- .../test_tstorage_clear_after_tx.py | 14 +- .../test_tstorage_create_contexts.py | 12 +- .../test_beacon_root_contract.py | 32 +- .../eip4844_blobs/test_blobhash_opcode.py | 19 +- .../test_blobhash_opcode_contexts.py | 24 +- .../eip4844_blobs/test_excess_blob_gas.py | 4 +- tests/cancun/eip5656_mcopy/test_mcopy.py | 15 +- .../eip5656_mcopy/test_mcopy_contexts.py | 8 +- .../test_mcopy_memory_expansion.py | 7 +- ..._dynamic_create2_selfdestruct_collision.py | 39 +- .../test_reentrancy_selfdestruct_revert.py | 5 +- .../eip6780_selfdestruct/test_selfdestruct.py | 80 +- .../test_selfdestruct_revert.py | 11 +- tests/common/precompile_fixtures.py | 5 +- .../eip1014_create2/test_create2_revert.py | 13 +- .../test_deterministic_deployment.py | 16 +- .../eip1052_extcodehash/test_extcodehash.py | 176 +- .../test_shift_combinations.py | 17 +- tests/frontier/create/test_create_one_byte.py | 23 +- .../create/test_create_preimage_layout.py | 12 +- .../frontier/identity_precompile/conftest.py | 8 +- .../test_identity_returndatasize.py | 4 +- tests/frontier/opcodes/test_all_opcodes.py | 6 +- tests/frontier/opcodes/test_blockhash.py | 10 +- .../test_call_and_callcode_gas_calculation.py | 10 +- tests/frontier/opcodes/test_calldatacopy.py | 6 +- tests/frontier/opcodes/test_calldataload.py | 34 +- tests/frontier/opcodes/test_calldatasize.py | 51 +- tests/frontier/opcodes/test_dup.py | 7 +- tests/frontier/opcodes/test_swap.py | 18 +- .../precompiles/test_precompile_absence.py | 9 +- tests/frontier/scenarios/test_scenarios.py | 5 + .../identity_precompile/test_identity.py | 17 +- .../istanbul/eip1344_chainid/test_chainid.py | 20 +- tests/istanbul/eip152_blake2/common.py | 3 - tests/istanbul/eip152_blake2/test_blake2.py | 11 +- .../test_tx_gas_limit.py | 47 +- .../eip7883_modexp_gas_increase/conftest.py | 40 +- .../test_modexp_thresholds.py | 3 +- .../test_blob_base_fee.py | 37 +- .../test_count_leading_zeros.py | 109 +- .../conftest.py | 2 + .../test_p256verify.py | 2 +- .../test_collision_selfdestruct.py | 18 +- .../test_initcollision.py | 4 +- .../test_revert_in_create.py | 8 +- .../security/test_selfdestruct_balance_bug.py | 42 +- tests/ported_static/amsterdam_skip_list.txt | 569 ++++ tests/ported_static/conftest.py | 58 + .../test_contract_creation_spam.py | 14 +- .../stCallCodes/test_callcall_00.py | 25 +- .../test_callcall_00_suicide_end.py | 19 +- .../stCallCodes/test_callcallcall_000.py | 29 +- .../test_callcallcall_000_suicide_end.py | 23 +- .../test_callcallcall_abcb_recursive.py | 6 +- .../stCallCodes/test_callcallcallcode_001.py | 29 +- .../test_callcallcallcode_001_suicide_end.py | 23 +- .../test_callcallcallcode_abcb_recursive.py | 13 +- .../stCallCodes/test_callcallcode_01.py | 25 +- .../stCallCodes/test_callcallcodecall_010.py | 29 +- .../test_callcallcodecall_010_suicide_end.py | 23 +- .../test_callcallcodecall_abcb_recursive.py | 6 +- .../test_callcallcodecallcode_011.py | 29 +- ...est_callcallcodecallcode_abcb_recursive.py | 6 +- .../stCallCodes/test_callcode_check_pc.py | 1 - .../stCallCodes/test_callcode_dynamic_code.py | 39 +- .../test_callcode_dynamic_code2_self_call.py | 25 +- ..._callcode_in_initcode_to_empty_contract.py | 15 +- .../stCallCodes/test_callcodecall_10.py | 25 +- .../test_callcodecall_10_suicide_end.py | 19 +- .../stCallCodes/test_callcodecallcall_100.py | 29 +- .../test_callcodecallcall_100_suicide_end.py | 23 +- .../test_callcodecallcall_abcb_recursive.py | 6 +- .../test_callcodecallcallcode_101.py | 29 +- ...st_callcodecallcallcode_101_suicide_end.py | 23 +- ...est_callcodecallcallcode_abcb_recursive.py | 13 +- .../stCallCodes/test_callcodecallcode_11.py | 25 +- .../test_callcodecallcodecall_110.py | 29 +- ...st_callcodecallcodecall_110_suicide_end.py | 23 +- ...est_callcodecallcodecall_abcb_recursive.py | 15 +- .../test_callcodecallcodecallcode_111.py | 29 +- ...allcodecallcodecallcode_111_suicide_end.py | 23 +- ...callcodecallcodecallcode_abcb_recursive.py | 8 +- .../test_call_lose_gas_oog.py | 6 +- .../test_call_with_high_value_oo_gin_call.py | 1 - .../test_create_fail_balance_too_low.py | 11 +- ..._create_init_fail_undefined_instruction.py | 14 +- .../test_create_js_no_collision.py | 7 +- .../test_create_name_registrator_per_txs.py | 1 - .../test_callcallcallcode_001.py | 29 +- .../test_callcallcallcode_001_suicide_end.py | 23 +- .../test_callcallcallcode_abcb_recursive.py | 8 +- .../test_callcallcode_01.py | 25 +- .../test_callcallcode_01_suicide_end.py | 19 +- .../test_callcallcodecall_010.py | 29 +- .../test_callcallcodecall_010_suicide_end.py | 23 +- .../test_callcallcodecall_abcb_recursive.py | 8 +- .../test_callcallcodecallcode_011.py | 29 +- ...st_callcallcodecallcode_011_suicide_end.py | 23 +- ...est_callcallcodecallcode_abcb_recursive.py | 8 +- .../test_callcodecall_10.py | 25 +- .../test_callcodecall_10_suicide_end.py | 19 +- .../test_callcodecallcall_100.py | 29 +- .../test_callcodecallcall_100_suicide_end.py | 23 +- .../test_callcodecallcall_abcb_recursive.py | 6 +- .../test_callcodecallcallcode_101.py | 29 +- ...st_callcodecallcallcode_101_suicide_end.py | 23 +- ...est_callcodecallcallcode_abcb_recursive.py | 8 +- .../test_callcodecallcode_11.py | 25 +- .../test_callcodecallcode_11_suicide_end.py | 19 +- .../test_callcodecallcodecall_110.py | 29 +- ...st_callcodecallcodecall_110_suicide_end.py | 23 +- ...est_callcodecallcodecall_abcb_recursive.py | 8 +- .../test_callcodecallcodecallcode_111.py | 29 +- ...allcodecallcodecallcode_111_suicide_end.py | 29 +- ...callcodecallcodecallcode_abcb_recursive.py | 8 +- .../test_callcallcallcode_001.py | 29 +- .../test_callcallcallcode_001_suicide_end.py | 23 +- .../test_callcallcallcode_abcb_recursive.py | 13 +- .../test_callcallcode_01.py | 25 +- .../test_callcallcode_01_suicide_end.py | 19 +- .../test_callcallcodecall_010.py | 29 +- .../test_callcallcodecall_010_suicide_end.py | 23 +- .../test_callcallcodecall_abcb_recursive.py | 6 +- .../test_callcallcodecallcode_011.py | 29 +- ...st_callcallcodecallcode_011_suicide_end.py | 23 +- ...est_callcallcodecallcode_abcb_recursive.py | 6 +- .../test_callcodecall_10.py | 25 +- .../test_callcodecall_10_suicide_end.py | 19 +- .../test_callcodecallcall_100.py | 29 +- .../test_callcodecallcall_100_suicide_end.py | 23 +- .../test_callcodecallcall_abcb_recursive.py | 6 +- .../test_callcodecallcallcode_101.py | 29 +- ...st_callcodecallcallcode_101_suicide_end.py | 23 +- ...est_callcodecallcallcode_abcb_recursive.py | 13 +- .../test_callcodecallcode_11.py | 25 +- .../test_callcodecallcode_11_suicide_end.py | 19 +- .../test_callcodecallcodecall_110.py | 29 +- ...st_callcodecallcodecall_110_suicide_end.py | 23 +- ...est_callcodecallcodecall_abcb_recursive.py | 15 +- .../test_callcodecallcodecallcode_111.py | 29 +- ...allcodecallcodecallcode_111_suicide_end.py | 23 +- ...callcodecallcodecallcode_abcb_recursive.py | 8 +- ...opy_target_range_longer_than_code_tests.py | 1 - .../test_ext_code_copy_tests_paris.py | 1 - .../test_codesize_oog_invalid_size.py | 16 +- .../stCodeSizeLimit/test_codesize_valid.py | 13 +- .../test_create2_code_size_limit.py | 33 +- .../test_create_code_size_limit.py | 9 +- ..._create2_successful_then_returndatasize.py | 6 +- ..._create2_successful_then_returndatasize.py | 6 +- ...cide_during_init_then_store_then_return.py | 20 +- .../stCreate2/test_create2_first_byte_loop.py | 11 +- .../test_create2_oo_gafter_init_code.py | 19 +- ...create2_oo_gafter_init_code_returndata2.py | 11 +- ...te2_oo_gafter_init_code_returndata_size.py | 5 +- ...test_create2_oo_gafter_init_code_revert.py | 5 +- .../test_create2_oog_from_call_refunds.py | 33 +- .../stCreate2/test_create2_smart_init_code.py | 11 +- .../stCreate2/test_create2_suicide.py | 29 +- .../stCreate2/test_create2call_precompiles.py | 1 - .../test_create2collision_balance.py | 13 +- .../stCreate2/test_create2collision_code.py | 13 +- .../stCreate2/test_create2collision_code2.py | 13 +- .../stCreate2/test_create2collision_nonce.py | 13 +- .../test_create2collision_selfdestructed.py | 24 +- .../test_create2collision_selfdestructed2.py | 22 +- .../stCreate2/test_create_message_reverted.py | 12 +- ...atacopy_0_0_following_successful_create.py | 6 +- ...est_returndatacopy_after_failing_create.py | 6 +- ...turndatacopy_following_revert_in_create.py | 6 +- ...urndatasize_following_successful_create.py | 6 +- .../test_revert_depth_create2_oog.py | 1 - .../test_revert_depth_create2_oog_berlin.py | 1 - ...t_revert_depth_create_address_collision.py | 11 +- ...t_depth_create_address_collision_berlin.py | 11 +- .../stCreate2/test_revert_opcode_create.py | 1 - ...revert_opcode_in_create_returns_create2.py | 6 +- .../stCreateTest/test_code_in_constructor.py | 1 - .../stCreateTest/test_create2_call_data.py | 7 +- .../test_create_address_warm_after_fail.py | 7 +- .../test_create_collision_results.py | 1 - .../test_create_collision_to_empty2.py | 9 +- ...test_create_contract_sstore_during_init.py | 5 +- ...e_contract_create_e_contract_in_init_tr.py | 17 +- ..._contract_create_ne_contract_in_init_tr.py | 17 +- ...empty000_createin_init_code_transaction.py | 17 +- .../test_create_oo_gafter_init_code.py | 19 +- ..._create_oo_gafter_init_code_returndata2.py | 11 +- ...test_create_oo_gafter_init_code_revert2.py | 58 +- .../test_create_oog_from_call_refunds.py | 33 +- .../stCreateTest/test_create_results.py | 1 - .../test_create_transaction_call_data.py | 11 +- .../test_create_transaction_high_nonce.py | 16 +- .../test_create_transaction_refund_ef.py | 7 +- ...transaction_collision_to_empty_but_code.py | 27 +- ...ransaction_collision_to_empty_but_nonce.py | 27 +- .../test_call_lose_gas_oog.py | 6 +- .../test_callcode_lose_gas_oog.py | 1 - ...ll_in_initcode_to_existing_contract_oog.py | 7 +- .../test_delegatecall_oo_gin_call.py | 1 - ...est_10_revert_undoes_store_after_return.py | 1 - .../test_14_revert_after_nested_staticcall.py | 1 - ..._that_ask_fore_gas_then_trabsaction_has.py | 5 +- .../test_base_fee_diff_places_osaka.py | 1 - .../test_gas_price_diff_places_osaka.py | 1 - .../stEIP2930/test_address_opcodes.py | 1 - .../stEIP2930/test_coinbase_t01.py | 1 - .../stEIP2930/test_coinbase_t2.py | 1 - .../stEIP2930/test_manual_create.py | 29 +- .../stEIP2930/test_storage_costs.py | 71 +- .../stEIP2930/test_varied_context.py | 202 +- ...t_init_colliding_with_non_empty_account.py | 28 +- ...iding_with_non_empty_account_init_paris.py | 1 - ...est_coinbase_warm_account_call_gas_fail.py | 9 +- .../stEIP3855_push0/test_push0.py | 13 +- .../test_create_blobhash_tx.py | 1 - .../stEIP5656_MCOPY/test_mcopy_copy_cost.py | 1 - tests/ported_static/stExample/test_add11.py | 1 - .../ported_static/stExample/test_add11_yml.py | 1 - .../stExample/test_basefee_example.py | 1 - .../stExample/test_indexes_omit_example.py | 1 - .../stExample/test_labels_example.py | 1 - .../stExample/test_ranges_example.py | 1 - ..._creation_oo_gdont_leave_empty_contract.py | 1 - ...eate_contract_via_transaction_cost53000.py | 20 +- ...ract_to_create_contract_and_call_it_oog.py | 5 +- ...ntract_to_create_contract_oog_bonus_gas.py | 6 +- ...t_which_would_create_contract_if_called.py | 17 +- ...hich_would_create_contract_in_init_code.py | 6 +- .../test_call_recursive_contract.py | 95 +- ...l_the_contract_to_create_empty_contract.py | 13 +- .../stInitCodeTest/test_return_test2.py | 1 - ...test_stack_under_flow_contract_creation.py | 6 +- ...ransaction_create_auto_suicide_contract.py | 17 +- ...est_transaction_create_random_init_code.py | 6 +- ...est_transaction_create_stop_in_initcode.py | 16 +- ..._transaction_create_suicide_in_initcode.py | 5 +- ...n_second_level_with_mem_expanding_calls.py | 1 - ...ransaction_has_with_mem_expanding_calls.py | 5 +- .../stMemoryStressTest/test_return_bounds.py | 1 - .../stMemoryStressTest/test_sstore_bounds.py | 1 - .../stMemoryTest/test_calldatacopy_dejavu2.py | 1 - .../stMemoryTest/test_mem0b_single_byte.py | 13 +- .../stMemoryTest/test_mem31b_single_byte.py | 13 +- .../stMemoryTest/test_mem32b_single_byte.py | 13 +- .../stMemoryTest/test_mem32kb.py | 6 +- .../stMemoryTest/test_mem32kb_minus_1.py | 6 +- .../stMemoryTest/test_mem32kb_minus_31.py | 6 +- .../stMemoryTest/test_mem32kb_minus_32.py | 6 +- .../stMemoryTest/test_mem32kb_minus_33.py | 6 +- .../stMemoryTest/test_mem32kb_plus_1.py | 6 +- .../stMemoryTest/test_mem32kb_plus_31.py | 6 +- .../stMemoryTest/test_mem32kb_plus_32.py | 6 +- .../stMemoryTest/test_mem32kb_plus_33.py | 6 +- .../stMemoryTest/test_mem32kb_single_byte.py | 13 +- .../test_mem32kb_single_byte_minus_1.py | 13 +- .../test_mem32kb_single_byte_minus_31.py | 13 +- .../test_mem32kb_single_byte_minus_32.py | 13 +- .../test_mem32kb_single_byte_minus_33.py | 13 +- .../test_mem32kb_single_byte_plus_1.py | 13 +- .../test_mem32kb_single_byte_plus_31.py | 13 +- .../test_mem32kb_single_byte_plus_32.py | 13 +- .../test_mem32kb_single_byte_plus_33.py | 13 +- .../stMemoryTest/test_mem33b_single_byte.py | 13 +- .../stMemoryTest/test_mem64kb.py | 6 +- .../stMemoryTest/test_mem64kb_minus_1.py | 6 +- .../stMemoryTest/test_mem64kb_minus_31.py | 6 +- .../stMemoryTest/test_mem64kb_minus_32.py | 6 +- .../stMemoryTest/test_mem64kb_minus_33.py | 6 +- .../stMemoryTest/test_mem64kb_plus_1.py | 6 +- .../stMemoryTest/test_mem64kb_plus_31.py | 6 +- .../stMemoryTest/test_mem64kb_plus_32.py | 6 +- .../stMemoryTest/test_mem64kb_plus_33.py | 6 +- .../stMemoryTest/test_mem64kb_single_byte.py | 13 +- .../test_mem64kb_single_byte_minus_1.py | 13 +- .../test_mem64kb_single_byte_minus_31.py | 13 +- .../test_mem64kb_single_byte_minus_32.py | 13 +- .../test_mem64kb_single_byte_minus_33.py | 13 +- .../test_mem64kb_single_byte_plus_1.py | 13 +- .../test_mem64kb_single_byte_plus_31.py | 13 +- .../test_mem64kb_single_byte_plus_32.py | 13 +- .../test_mem64kb_single_byte_plus_33.py | 13 +- .../test_precomps_eip2929_cancun.py | 27 +- .../test_call_ecrecover_overflow.py | 18 +- .../test_modexp_0_0_0_20500.py | 6 + .../test_modexp_0_0_0_22000.py | 6 + .../test_modexp_0_0_0_25000.py | 6 + .../test_modexp_0_0_0_35000.py | 6 + .../test_call20_kbytes_contract50_1.py | 1 - .../test_return50000.py | 1 - .../test_return50000_2.py | 1 - .../stRandom/test_random_statetest100.py | 1 - .../stRandom/test_random_statetest102.py | 13 +- .../stRandom/test_random_statetest104.py | 13 +- .../stRandom/test_random_statetest105.py | 13 +- .../stRandom/test_random_statetest106.py | 13 +- .../stRandom/test_random_statetest107.py | 13 +- .../stRandom/test_random_statetest11.py | 13 +- .../stRandom/test_random_statetest110.py | 13 +- .../stRandom/test_random_statetest112.py | 13 +- .../stRandom/test_random_statetest114.py | 13 +- .../stRandom/test_random_statetest115.py | 1 - .../stRandom/test_random_statetest116.py | 13 +- .../stRandom/test_random_statetest117.py | 13 +- .../stRandom/test_random_statetest118.py | 13 +- .../stRandom/test_random_statetest119.py | 13 +- .../stRandom/test_random_statetest12.py | 13 +- .../stRandom/test_random_statetest120.py | 13 +- .../stRandom/test_random_statetest121.py | 13 +- .../stRandom/test_random_statetest122.py | 13 +- .../stRandom/test_random_statetest124.py | 13 +- .../stRandom/test_random_statetest129.py | 13 +- .../stRandom/test_random_statetest130.py | 13 +- .../stRandom/test_random_statetest131.py | 13 +- .../stRandom/test_random_statetest137.py | 13 +- .../stRandom/test_random_statetest138.py | 6 +- .../stRandom/test_random_statetest139.py | 13 +- .../stRandom/test_random_statetest14.py | 6 +- .../stRandom/test_random_statetest142.py | 13 +- .../stRandom/test_random_statetest143.py | 1 - .../stRandom/test_random_statetest145.py | 13 +- .../stRandom/test_random_statetest147.py | 6 +- .../stRandom/test_random_statetest148.py | 13 +- .../stRandom/test_random_statetest15.py | 13 +- .../stRandom/test_random_statetest153.py | 1 - .../stRandom/test_random_statetest155.py | 13 +- .../stRandom/test_random_statetest156.py | 13 +- .../stRandom/test_random_statetest158.py | 13 +- .../stRandom/test_random_statetest161.py | 13 +- .../stRandom/test_random_statetest162.py | 13 +- .../stRandom/test_random_statetest164.py | 6 +- .../stRandom/test_random_statetest166.py | 13 +- .../stRandom/test_random_statetest167.py | 13 +- .../stRandom/test_random_statetest169.py | 13 +- .../stRandom/test_random_statetest17.py | 6 +- .../stRandom/test_random_statetest173.py | 6 +- .../stRandom/test_random_statetest174.py | 1 - .../stRandom/test_random_statetest175.py | 13 +- .../stRandom/test_random_statetest179.py | 13 +- .../stRandom/test_random_statetest180.py | 13 +- .../stRandom/test_random_statetest183.py | 13 +- .../stRandom/test_random_statetest184.py | 13 +- .../stRandom/test_random_statetest187.py | 13 +- .../stRandom/test_random_statetest188.py | 13 +- .../stRandom/test_random_statetest19.py | 13 +- .../stRandom/test_random_statetest191.py | 13 +- .../stRandom/test_random_statetest192.py | 13 +- .../stRandom/test_random_statetest194.py | 15 +- .../stRandom/test_random_statetest195.py | 13 +- .../stRandom/test_random_statetest196.py | 13 +- .../stRandom/test_random_statetest198.py | 6 +- .../stRandom/test_random_statetest199.py | 1 - .../stRandom/test_random_statetest2.py | 13 +- .../stRandom/test_random_statetest200.py | 13 +- .../stRandom/test_random_statetest201.py | 6 +- .../stRandom/test_random_statetest202.py | 13 +- .../stRandom/test_random_statetest204.py | 13 +- .../stRandom/test_random_statetest206.py | 13 +- .../stRandom/test_random_statetest207.py | 1 - .../stRandom/test_random_statetest208.py | 13 +- .../stRandom/test_random_statetest210.py | 13 +- .../stRandom/test_random_statetest212.py | 6 +- .../stRandom/test_random_statetest214.py | 13 +- .../stRandom/test_random_statetest215.py | 13 +- .../stRandom/test_random_statetest216.py | 13 +- .../stRandom/test_random_statetest217.py | 13 +- .../stRandom/test_random_statetest219.py | 13 +- .../stRandom/test_random_statetest22.py | 6 +- .../stRandom/test_random_statetest220.py | 13 +- .../stRandom/test_random_statetest221.py | 13 +- .../stRandom/test_random_statetest222.py | 13 +- .../stRandom/test_random_statetest225.py | 13 +- .../stRandom/test_random_statetest227.py | 13 +- .../stRandom/test_random_statetest228.py | 1 - .../stRandom/test_random_statetest23.py | 13 +- .../stRandom/test_random_statetest231.py | 13 +- .../stRandom/test_random_statetest232.py | 6 +- .../stRandom/test_random_statetest236.py | 6 +- .../stRandom/test_random_statetest237.py | 6 +- .../stRandom/test_random_statetest238.py | 13 +- .../stRandom/test_random_statetest242.py | 13 +- .../stRandom/test_random_statetest243.py | 13 +- .../stRandom/test_random_statetest244.py | 1 - .../stRandom/test_random_statetest245.py | 6 +- .../stRandom/test_random_statetest246.py | 1 - .../stRandom/test_random_statetest247.py | 13 +- .../stRandom/test_random_statetest248.py | 13 +- .../stRandom/test_random_statetest249.py | 15 +- .../stRandom/test_random_statetest254.py | 13 +- .../stRandom/test_random_statetest259.py | 13 +- .../stRandom/test_random_statetest26.py | 1 - .../stRandom/test_random_statetest264.py | 15 +- .../stRandom/test_random_statetest267.py | 13 +- .../stRandom/test_random_statetest268.py | 13 +- .../stRandom/test_random_statetest269.py | 13 +- .../stRandom/test_random_statetest27.py | 13 +- .../stRandom/test_random_statetest270.py | 6 +- .../stRandom/test_random_statetest273.py | 1 - .../stRandom/test_random_statetest276.py | 13 +- .../stRandom/test_random_statetest278.py | 13 +- .../stRandom/test_random_statetest279.py | 13 +- .../stRandom/test_random_statetest28.py | 13 +- .../stRandom/test_random_statetest280.py | 13 +- .../stRandom/test_random_statetest281.py | 13 +- .../stRandom/test_random_statetest283.py | 13 +- .../stRandom/test_random_statetest29.py | 13 +- .../stRandom/test_random_statetest290.py | 13 +- .../stRandom/test_random_statetest291.py | 6 +- .../stRandom/test_random_statetest293.py | 6 +- .../stRandom/test_random_statetest297.py | 13 +- .../stRandom/test_random_statetest298.py | 13 +- .../stRandom/test_random_statetest299.py | 13 +- .../stRandom/test_random_statetest3.py | 13 +- .../stRandom/test_random_statetest30.py | 1 - .../stRandom/test_random_statetest301.py | 13 +- .../stRandom/test_random_statetest305.py | 13 +- .../stRandom/test_random_statetest31.py | 6 +- .../stRandom/test_random_statetest310.py | 13 +- .../stRandom/test_random_statetest311.py | 13 +- .../stRandom/test_random_statetest315.py | 13 +- .../stRandom/test_random_statetest316.py | 15 +- .../stRandom/test_random_statetest318.py | 13 +- .../stRandom/test_random_statetest322.py | 13 +- .../stRandom/test_random_statetest325.py | 13 +- .../stRandom/test_random_statetest329.py | 13 +- .../stRandom/test_random_statetest332.py | 13 +- .../stRandom/test_random_statetest333.py | 13 +- .../stRandom/test_random_statetest334.py | 13 +- .../stRandom/test_random_statetest337.py | 6 +- .../stRandom/test_random_statetest338.py | 6 +- .../stRandom/test_random_statetest339.py | 13 +- .../stRandom/test_random_statetest342.py | 13 +- .../stRandom/test_random_statetest343.py | 6 +- .../stRandom/test_random_statetest348.py | 13 +- .../stRandom/test_random_statetest349.py | 6 +- .../stRandom/test_random_statetest351.py | 13 +- .../stRandom/test_random_statetest354.py | 13 +- .../stRandom/test_random_statetest356.py | 13 +- .../stRandom/test_random_statetest358.py | 13 +- .../stRandom/test_random_statetest360.py | 13 +- .../stRandom/test_random_statetest361.py | 13 +- .../stRandom/test_random_statetest362.py | 13 +- .../stRandom/test_random_statetest363.py | 13 +- .../stRandom/test_random_statetest364.py | 13 +- .../stRandom/test_random_statetest365.py | 13 +- .../stRandom/test_random_statetest366.py | 13 +- .../stRandom/test_random_statetest367.py | 13 +- .../stRandom/test_random_statetest368.py | 6 +- .../stRandom/test_random_statetest369.py | 13 +- .../stRandom/test_random_statetest37.py | 13 +- .../stRandom/test_random_statetest371.py | 6 +- .../stRandom/test_random_statetest372.py | 13 +- .../stRandom/test_random_statetest376.py | 6 +- .../stRandom/test_random_statetest379.py | 1 - .../stRandom/test_random_statetest380.py | 13 +- .../stRandom/test_random_statetest381.py | 13 +- .../stRandom/test_random_statetest382.py | 13 +- .../stRandom/test_random_statetest383.py | 13 +- .../stRandom/test_random_statetest39.py | 6 +- .../stRandom/test_random_statetest41.py | 13 +- .../stRandom/test_random_statetest43.py | 6 +- .../stRandom/test_random_statetest47.py | 13 +- .../stRandom/test_random_statetest49.py | 13 +- .../stRandom/test_random_statetest52.py | 13 +- .../stRandom/test_random_statetest58.py | 13 +- .../stRandom/test_random_statetest59.py | 13 +- .../stRandom/test_random_statetest6.py | 13 +- .../stRandom/test_random_statetest60.py | 13 +- .../stRandom/test_random_statetest62.py | 13 +- .../stRandom/test_random_statetest63.py | 13 +- .../stRandom/test_random_statetest64.py | 6 +- .../stRandom/test_random_statetest66.py | 13 +- .../stRandom/test_random_statetest67.py | 13 +- .../stRandom/test_random_statetest69.py | 13 +- .../stRandom/test_random_statetest73.py | 13 +- .../stRandom/test_random_statetest74.py | 13 +- .../stRandom/test_random_statetest75.py | 13 +- .../stRandom/test_random_statetest77.py | 13 +- .../stRandom/test_random_statetest80.py | 13 +- .../stRandom/test_random_statetest81.py | 13 +- .../stRandom/test_random_statetest83.py | 13 +- .../stRandom/test_random_statetest85.py | 13 +- .../stRandom/test_random_statetest87.py | 13 +- .../stRandom/test_random_statetest88.py | 13 +- .../stRandom/test_random_statetest89.py | 13 +- .../stRandom/test_random_statetest9.py | 13 +- .../stRandom/test_random_statetest90.py | 13 +- .../stRandom/test_random_statetest92.py | 13 +- .../stRandom/test_random_statetest95.py | 13 +- .../stRandom/test_random_statetest96.py | 13 +- .../stRandom/test_random_statetest98.py | 6 +- .../stRandom2/test_random_statetest.py | 13 +- .../stRandom2/test_random_statetest384.py | 13 +- .../stRandom2/test_random_statetest385.py | 13 +- .../stRandom2/test_random_statetest386.py | 15 +- .../stRandom2/test_random_statetest388.py | 13 +- .../stRandom2/test_random_statetest389.py | 13 +- .../stRandom2/test_random_statetest395.py | 13 +- .../stRandom2/test_random_statetest398.py | 13 +- .../stRandom2/test_random_statetest399.py | 13 +- .../stRandom2/test_random_statetest402.py | 13 +- .../stRandom2/test_random_statetest405.py | 13 +- .../stRandom2/test_random_statetest406.py | 6 +- .../stRandom2/test_random_statetest407.py | 13 +- .../stRandom2/test_random_statetest408.py | 13 +- .../stRandom2/test_random_statetest409.py | 6 +- .../stRandom2/test_random_statetest411.py | 13 +- .../stRandom2/test_random_statetest412.py | 13 +- .../stRandom2/test_random_statetest413.py | 13 +- .../stRandom2/test_random_statetest416.py | 13 +- .../stRandom2/test_random_statetest419.py | 13 +- .../stRandom2/test_random_statetest421.py | 13 +- .../stRandom2/test_random_statetest424.py | 13 +- .../stRandom2/test_random_statetest425.py | 13 +- .../stRandom2/test_random_statetest426.py | 13 +- .../stRandom2/test_random_statetest429.py | 13 +- .../stRandom2/test_random_statetest430.py | 13 +- .../stRandom2/test_random_statetest435.py | 6 +- .../stRandom2/test_random_statetest436.py | 13 +- .../stRandom2/test_random_statetest437.py | 6 +- .../stRandom2/test_random_statetest438.py | 13 +- .../stRandom2/test_random_statetest439.py | 13 +- .../stRandom2/test_random_statetest440.py | 13 +- .../stRandom2/test_random_statetest442.py | 6 +- .../stRandom2/test_random_statetest446.py | 13 +- .../stRandom2/test_random_statetest447.py | 13 +- .../stRandom2/test_random_statetest450.py | 13 +- .../stRandom2/test_random_statetest451.py | 13 +- .../stRandom2/test_random_statetest452.py | 13 +- .../stRandom2/test_random_statetest455.py | 13 +- .../stRandom2/test_random_statetest457.py | 13 +- .../stRandom2/test_random_statetest460.py | 13 +- .../stRandom2/test_random_statetest461.py | 13 +- .../stRandom2/test_random_statetest462.py | 13 +- .../stRandom2/test_random_statetest464.py | 13 +- .../stRandom2/test_random_statetest465.py | 15 +- .../stRandom2/test_random_statetest466.py | 1 - .../stRandom2/test_random_statetest470.py | 13 +- .../stRandom2/test_random_statetest471.py | 13 +- .../stRandom2/test_random_statetest473.py | 13 +- .../stRandom2/test_random_statetest474.py | 13 +- .../stRandom2/test_random_statetest475.py | 13 +- .../stRandom2/test_random_statetest477.py | 13 +- .../stRandom2/test_random_statetest480.py | 13 +- .../stRandom2/test_random_statetest482.py | 13 +- .../stRandom2/test_random_statetest483.py | 15 +- .../stRandom2/test_random_statetest487.py | 6 +- .../stRandom2/test_random_statetest488.py | 13 +- .../stRandom2/test_random_statetest489.py | 13 +- .../stRandom2/test_random_statetest491.py | 13 +- .../stRandom2/test_random_statetest493.py | 6 +- .../stRandom2/test_random_statetest495.py | 6 +- .../stRandom2/test_random_statetest497.py | 13 +- .../stRandom2/test_random_statetest500.py | 13 +- .../stRandom2/test_random_statetest501.py | 6 +- .../stRandom2/test_random_statetest502.py | 13 +- .../stRandom2/test_random_statetest503.py | 13 +- .../stRandom2/test_random_statetest505.py | 13 +- .../stRandom2/test_random_statetest506.py | 13 +- .../stRandom2/test_random_statetest511.py | 13 +- .../stRandom2/test_random_statetest512.py | 13 +- .../stRandom2/test_random_statetest514.py | 13 +- .../stRandom2/test_random_statetest516.py | 13 +- .../stRandom2/test_random_statetest517.py | 6 +- .../stRandom2/test_random_statetest518.py | 13 +- .../stRandom2/test_random_statetest519.py | 13 +- .../stRandom2/test_random_statetest520.py | 13 +- .../stRandom2/test_random_statetest521.py | 6 +- .../stRandom2/test_random_statetest526.py | 13 +- .../stRandom2/test_random_statetest532.py | 13 +- .../stRandom2/test_random_statetest533.py | 13 +- .../stRandom2/test_random_statetest534.py | 13 +- .../stRandom2/test_random_statetest535.py | 13 +- .../stRandom2/test_random_statetest537.py | 13 +- .../stRandom2/test_random_statetest539.py | 13 +- .../stRandom2/test_random_statetest541.py | 15 +- .../stRandom2/test_random_statetest542.py | 6 +- .../stRandom2/test_random_statetest544.py | 13 +- .../stRandom2/test_random_statetest545.py | 13 +- .../stRandom2/test_random_statetest546.py | 13 +- .../stRandom2/test_random_statetest548.py | 13 +- .../stRandom2/test_random_statetest550.py | 13 +- .../stRandom2/test_random_statetest552.py | 13 +- .../stRandom2/test_random_statetest553.py | 13 +- .../stRandom2/test_random_statetest555.py | 13 +- .../stRandom2/test_random_statetest556.py | 13 +- .../stRandom2/test_random_statetest559.py | 6 +- .../stRandom2/test_random_statetest564.py | 13 +- .../stRandom2/test_random_statetest565.py | 13 +- .../stRandom2/test_random_statetest571.py | 13 +- .../stRandom2/test_random_statetest574.py | 13 +- .../stRandom2/test_random_statetest577.py | 1 - .../stRandom2/test_random_statetest578.py | 13 +- .../stRandom2/test_random_statetest580.py | 13 +- .../stRandom2/test_random_statetest581.py | 6 +- .../stRandom2/test_random_statetest584.py | 6 +- .../stRandom2/test_random_statetest585.py | 13 +- .../stRandom2/test_random_statetest586.py | 13 +- .../stRandom2/test_random_statetest587.py | 13 +- .../stRandom2/test_random_statetest588.py | 15 +- .../stRandom2/test_random_statetest592.py | 13 +- .../stRandom2/test_random_statetest596.py | 13 +- .../stRandom2/test_random_statetest599.py | 13 +- .../stRandom2/test_random_statetest600.py | 13 +- .../stRandom2/test_random_statetest602.py | 13 +- .../stRandom2/test_random_statetest603.py | 13 +- .../stRandom2/test_random_statetest605.py | 13 +- .../stRandom2/test_random_statetest607.py | 13 +- .../stRandom2/test_random_statetest608.py | 13 +- .../stRandom2/test_random_statetest610.py | 13 +- .../stRandom2/test_random_statetest612.py | 6 +- .../stRandom2/test_random_statetest615.py | 13 +- .../stRandom2/test_random_statetest616.py | 13 +- .../stRandom2/test_random_statetest620.py | 13 +- .../stRandom2/test_random_statetest621.py | 13 +- .../stRandom2/test_random_statetest627.py | 1 - .../stRandom2/test_random_statetest628.py | 1 - .../stRandom2/test_random_statetest629.py | 13 +- .../stRandom2/test_random_statetest630.py | 13 +- .../stRandom2/test_random_statetest633.py | 13 +- .../stRandom2/test_random_statetest635.py | 6 +- .../stRandom2/test_random_statetest637.py | 13 +- .../stRandom2/test_random_statetest638.py | 13 +- .../stRandom2/test_random_statetest641.py | 13 +- .../stRandom2/test_random_statetest643.py | 1 - ...n_create_successful_then_returndatasize.py | 6 +- ...n_create_successful_then_returndatasize.py | 6 +- ...st_create_callprecompile_returndatasize.py | 6 +- .../test_modexp_modsize0_returndatasize.py | 1 - ...atacopy_0_0_following_successful_create.py | 6 +- ...est_returndatacopy_after_failing_create.py | 6 +- ...turndatacopy_following_revert_in_create.py | 6 +- ...eturndatasize_after_successful_callcode.py | 1 - ...urndatasize_following_successful_create.py | 6 +- .../test_too_long_return_data_copy.py | 1 - .../stRevertTest/test_revert_depth2.py | 1 - ...t_revert_depth_create_address_collision.py | 1 - .../test_revert_depth_create_oog.py | 1 - .../stRevertTest/test_revert_in_call_code.py | 7 +- .../test_revert_in_create_in_init_paris.py | 14 +- .../test_revert_in_delegate_call.py | 7 +- .../stRevertTest/test_revert_opcode_calls.py | 26 +- .../stRevertTest/test_revert_opcode_create.py | 1 - .../test_revert_opcode_direct_call.py | 1 - ...pcode_in_calls_on_non_empty_return_data.py | 26 +- .../test_revert_opcode_in_create_returns.py | 6 +- .../test_revert_opcode_in_init.py | 11 +- .../test_revert_opcode_multiple_sub_calls.py | 1 - .../stRevertTest/test_revert_opcode_return.py | 6 + ...evert_precompiled_touch_exact_oog_paris.py | 21 +- .../test_revert_sub_call_storage_oog.py | 6 +- .../test_revert_sub_call_storage_oog2.py | 6 +- .../stSStoreTest/test_sstore_0to0.py | 1 - .../stSStoreTest/test_sstore_0to0to0.py | 1 - .../stSStoreTest/test_sstore_0to0to_x.py | 1 - .../stSStoreTest/test_sstore_0to_x.py | 1 - .../stSStoreTest/test_sstore_0to_xto0.py | 1 - .../stSStoreTest/test_sstore_0to_xto0to_x.py | 1 - .../stSStoreTest/test_sstore_0to_xto_x.py | 1 - .../stSStoreTest/test_sstore_0to_xto_y.py | 1 - ..._change_from_external_call_in_init_code.py | 71 +- .../stSStoreTest/test_sstore_gas_left.py | 70 +- .../stSStoreTest/test_sstore_xto0.py | 1 - .../stSStoreTest/test_sstore_xto0to0.py | 1 - .../stSStoreTest/test_sstore_xto0to_x.py | 1 - .../stSStoreTest/test_sstore_xto0to_xto0.py | 1 - .../stSStoreTest/test_sstore_xto0to_y.py | 1 - .../stSStoreTest/test_sstore_xto_x.py | 1 - .../stSStoreTest/test_sstore_xto_xto0.py | 1 - .../stSStoreTest/test_sstore_xto_xto_x.py | 1 - .../stSStoreTest/test_sstore_xto_xto_y.py | 1 - .../stSStoreTest/test_sstore_xto_y.py | 1 - .../stSStoreTest/test_sstore_xto_yto0.py | 1 - .../stSStoreTest/test_sstore_xto_yto_x.py | 1 - .../stSStoreTest/test_sstore_xto_yto_y.py | 1 - .../stSStoreTest/test_sstore_xto_yto_z.py | 1 - .../stSelfBalance/test_self_balance.py | 1 - .../test_self_balance_call_types.py | 1 - .../test_self_balance_equals_balance.py | 1 - .../test_self_balance_gas_cost.py | 1 - .../stSelfBalance/test_self_balance_update.py | 6 +- .../test_call_low_level_creates_solidity.py | 5 +- ...sive_create_contracts_create4_contracts.py | 5 +- .../stSolidityTest/test_test_overflow.py | 1 - .../test_test_structures_and_variabless.py | 1 - .../stSpecialTest/test_deployment_error.py | 6 +- ...st_failed_create_reverts_deletion_paris.py | 6 +- .../test_selfdestruct_eip2929.py | 1 - .../stStackTests/test_shallow_stack.py | 1 - .../stStackTests/test_stack_overflow.py | 1 - .../stStackTests/test_stack_overflow_dup.py | 1 - .../stStackTests/test_stack_overflow_m1.py | 1 - .../test_stack_overflow_m1_dup.py | 1 - .../stStackTests/test_stack_overflow_swap.py | 1 - .../stStackTests/test_stacksanity_swap.py | 1 - .../stStaticCall/test_static_ab_acalls3.py | 1 - .../stStaticCall/test_static_call10.py | 3 +- .../stStaticCall/test_static_call1024_oog.py | 1 - ...ic_call_contract_to_create_contract_oog.py | 4 + ...t_which_would_create_contract_if_called.py | 9 +- .../test_static_call_lose_gas_oog.py | 7 +- ..._static_callcallcodecall_abcb_recursive.py | 1 - ...static_callcallcodecall_abcb_recursive2.py | 1 - ...tic_callcallcodecallcode_abcb_recursive.py | 1 - ...ic_callcallcodecallcode_abcb_recursive2.py | 1 - .../test_static_callcode_check_pc.py | 1 - ..._static_callcodecallcall_abcb_recursive.py | 1 - ...static_callcodecallcall_abcb_recursive2.py | 1 - ...c_callcodecallcallcode_101_oogm_after_3.py | 2 + ...tic_callcodecallcallcode_abcb_recursive.py | 1 - ...ic_callcodecallcallcode_abcb_recursive2.py | 1 - ...ic_callcodecallcodecall_110_suicide_end.py | 7 +- ...c_callcodecallcodecall_110_suicide_end2.py | 54 +- ...tic_callcodecallcodecall_abcb_recursive.py | 1 - ...ic_callcodecallcodecall_abcb_recursive2.py | 1 - .../stStaticCall/test_static_check_opcodes.py | 2 + .../test_static_check_opcodes5.py | 1 - ..._ask_more_gas_then_transaction_provided.py | 12 +- ...nt_leave_empty_contract_via_transaction.py | 13 +- ...tic_create_contract_suicide_during_init.py | 4 + ...contract_suicide_during_init_with_value.py | 4 + ..._create_empty_contract_and_call_it_0wei.py | 22 +- ..._contract_with_storage_and_call_it_0wei.py | 22 +- .../stStaticCall/test_static_return50000_2.py | 7 +- .../stStaticCall/test_static_return_bounds.py | 1 - .../test_static_return_bounds_oog.py | 1 - .../stStaticCall/test_static_return_test2.py | 1 - ...call_to_precompile_from_called_contract.py | 7 +- ...precompile_from_contract_initialization.py | 10 +- ...aticcall_to_precompile_from_transaction.py | 7 +- ...code_to_precompile_from_called_contract.py | 5 +- ...precompile_from_contract_initialization.py | 5 +- ...callcode_to_precompile_from_transaction.py | 5 +- .../stSystemOperationsTest/test_call10.py | 1 - .../test_call_to_name_registrator0.py | 13 +- ...ame_registrator_zeor_size_mem_expansion.py | 1 - ...e_to_name_registrator_zero_mem_expanion.py | 1 - .../test_callcode_to_return1.py | 13 +- .../test_create_name_registrator.py | 12 +- .../test_create_name_registrator_zero_mem.py | 12 +- .../test_create_name_registrator_zero_mem2.py | 12 +- ...ate_name_registrator_zero_mem_expansion.py | 12 +- .../test_double_selfdestruct_test.py | 1 - .../test_extcodecopy.py | 5 +- .../test_multi_selfdestruct.py | 1 - .../test_test_random_test.py | 7 +- .../test_create_message_success.py | 6 +- .../test_create_transaction_success.py | 6 +- .../test_empty_transaction3.py | 7 +- .../test_internal_call_hitting_gas_limit2.py | 1 - ...internal_call_hitting_gas_limit_success.py | 21 +- ...suicides_and_internal_call_suicides_oog.py | 1 - ...ides_and_internal_call_suicides_success.py | 1 - .../test_transaction_data_costs652.py | 15 +- .../test_transaction_sending_to_empty.py | 7 +- ...t_create_name_registrator_per_txs_after.py | 6 +- ...test_create_name_registrator_per_txs_at.py | 6 +- ..._create_name_registrator_per_txs_before.py | 6 +- .../test_day_limit_construction.py | 13 +- ...ned_construction_not_enough_gas_partial.py | 15 +- .../stWalletTest/test_wallet_construction.py | 13 +- .../test_wallet_construction_oog.py | 17 +- .../test_zero_value_call_oog_revert.py | 1 - ...ro_value_call_to_empty_oog_revert_paris.py | 1 - ...lue_call_to_non_zero_balance_oog_revert.py | 1 - ...all_to_one_storage_key_oog_revert_paris.py | 1 - .../test_zero_value_callcode_oog_revert.py | 1 - ...alue_callcode_to_empty_oog_revert_paris.py | 1 - ...callcode_to_non_zero_balance_oog_revert.py | 1 - ...ode_to_one_storage_key_oog_revert_paris.py | 1 - ...test_zero_value_delegatecall_oog_revert.py | 1 - ..._delegatecall_to_empty_oog_revert_paris.py | 1 - ...gatecall_to_non_zero_balance_oog_revert.py | 1 - ...all_to_one_storage_key_oog_revert_paris.py | 1 - .../test_zero_value_suicide_oog_revert.py | 1 - ...value_suicide_to_empty_oog_revert_paris.py | 1 - ..._suicide_to_non_zero_balance_oog_revert.py | 1 - ...ide_to_one_storage_key_oog_revert_paris.py | 1 - .../stZeroKnowledge/test_point_mul_add.py | 1 - .../stZeroKnowledge/test_point_mul_add2.py | 1 - ...t_bls12_variable_length_input_contracts.py | 13 +- tests/prague/eip6110_deposits/conftest.py | 19 +- .../prague/eip6110_deposits/test_deposits.py | 2 + .../conftest.py | 16 +- .../helpers.py | 37 +- .../test_withdrawal_requests.py | 6 + .../prague/eip7251_consolidations/conftest.py | 12 +- .../prague/eip7251_consolidations/helpers.py | 34 +- .../test_consolidations.py | 10 + .../test_modified_consolidation_contract.py | 4 +- .../conftest.py | 7 +- .../test_execution_gas.py | 7 +- .../test_refunds.py | 85 +- .../test_transaction_validity.py | 4 + .../prague/eip7702_set_code_tx/test_calls.py | 28 +- tests/prague/eip7702_set_code_tx/test_gas.py | 30 +- .../eip7702_set_code_tx/test_invalid_tx.py | 6 +- .../eip7702_set_code_tx/test_set_code_txs.py | 171 +- .../test_set_code_txs_2.py | 123 +- .../test_warm_coinbase.py | 22 +- tests/shanghai/eip3855_push0/test_push0.py | 63 +- .../eip3860_initcode/test_initcode.py | 7 + .../eip4895_withdrawals/test_withdrawals.py | 52 +- vulture_whitelist.py | 2 + whitelist.txt | 2 + 910 files changed, 25910 insertions(+), 4094 deletions(-) create mode 100644 packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8037.py create mode 100644 tests/amsterdam/eip8037_state_creation_gas_cost_increase/__init__.py create mode 100644 tests/amsterdam/eip8037_state_creation_gas_cost_increase/eip_checklist_external_coverage.txt create mode 100644 tests/amsterdam/eip8037_state_creation_gas_cost_increase/eip_checklist_not_applicable.txt create mode 100644 tests/amsterdam/eip8037_state_creation_gas_cost_increase/spec.py create mode 100644 tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py create mode 100644 tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_eip_mainnet.py create mode 100644 tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py create mode 100644 tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_calldata_floor.py create mode 100644 tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py create mode 100644 tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_delegation_pointer.py create mode 100644 tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_fork_transition.py create mode 100644 tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_multi_block.py create mode 100644 tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_ordering.py create mode 100644 tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_pricing.py create mode 100644 tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_reservoir.py create mode 100644 tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_selfdestruct.py create mode 100644 tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py create mode 100644 tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_sstore.py create mode 100644 tests/ported_static/amsterdam_skip_list.txt create mode 100644 tests/ported_static/conftest.py diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/contracts.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/contracts.py index 4be3f8aa36c..8a552f5d33c 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/contracts.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/contracts.py @@ -109,6 +109,7 @@ def deploy_deterministic_factory_contract( fund_tx = Transaction( to=deploy_tx_sender, value=fund_amount, + gas_limit=200_000, gas_price=gas_price, sender=seed_key, ) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/execute_recover.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/execute_recover.py index fa1653dfabf..d901f692c66 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/execute_recover.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/execute_recover.py @@ -24,7 +24,7 @@ def test_recover_funds( del index remaining_balance = eth_rpc.get_balance(eoa) - refund_gas_limit = 21_000 + refund_gas_limit = 200_000 tx_cost = refund_gas_limit * gas_price if remaining_balance < tx_cost: pytest.skip( diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py index 5cb131b3ba9..27a887b43d3 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py @@ -220,6 +220,58 @@ class _DeferredFundAddress: minimum_balance: bool +def _compute_deploy_gas_limit( + fork: Fork, + *, + deploy_code_size: int, + initcode: Bytes | Initcode, + storage_slots: int = 0, +) -> Tuple[int, int]: + """ + Compute the deploy transaction gas limit, returning both the regular + gas portion bound by the EIP 7825 cap and the total regular plus + state gas used as the transaction gas field. Under EIP 8037 the cap + binds only the regular portion while state gas comes from the block + reservoir and may push the total above the cap, and before Amsterdam + the state gas is zero so the total equals the regular gas. The regular + portion is doubled as a safety buffer since gas estimation is + approximate while the state portion is exact. + """ + gas_costs = fork.gas_costs() + memory_expansion_gas_calculator = fork.memory_expansion_gas_calculator() + intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator() + + sstore = Op.SSTORE(new_value=1) + sstore_state_gas = sstore.state_cost(fork) + sstore_regular_gas = sstore.gas_cost(fork) - sstore_state_gas + + # Back out the state gas folded into TX_CREATE. + intrinsic_state_gas = fork.transaction_intrinsic_state_gas( + contract_creation=True + ) + intrinsic_regular_gas = ( + intrinsic_gas_calculator(calldata=initcode, contract_creation=True) + - intrinsic_state_gas + ) + + # Regular portion, bound by the gas cap. + regular_gas = intrinsic_regular_gas + regular_gas += deploy_code_size * gas_costs.CODE_DEPOSIT_PER_BYTE + regular_gas += memory_expansion_gas_calculator( + new_bytes=len(bytes(initcode)) + ) + regular_gas += storage_slots * sstore_regular_gas + regular_gas *= 2 + + # State portion, from the block reservoir. + state_gas = intrinsic_state_gas + state_gas += fork.code_deposit_state_gas(code_size=deploy_code_size) + state_gas += storage_slots * sstore_state_gas + + deploy_gas_limit = regular_gas + state_gas + return regular_gas, deploy_gas_limit + + class Alloc(SharedAlloc): """A custom class that inherits from the original Alloc class.""" @@ -256,6 +308,7 @@ def __init__( address_stubs: AddressStubs | None = None, block_number: int = 0, timestamp: int = 0, + funding_gas_limit: int = 200_000, **kwargs: Any, ) -> None: """Initialize the pre-alloc with the given parameters.""" @@ -268,6 +321,7 @@ def __init__( self._address_stubs = address_stubs or AddressStubs(root={}) self._block_number = block_number self._timestamp = timestamp + self._funding_gas_limit = funding_gas_limit def code_pre_processor(self, code: Bytecode) -> Bytecode: """Pre-processes the code before setting it.""" @@ -325,11 +379,6 @@ def _deterministic_deploy_contract( fork = self._fork.fork_at( block_number=self._block_number, timestamp=self._timestamp ) - gas_costs = fork.gas_costs() - memory_expansion_gas_calculator = ( - fork.memory_expansion_gas_calculator() - ) - calldata_gas_calculator = fork.calldata_gas_calculator() if not isinstance(deploy_code, Bytes): deploy_code = Bytes(deploy_code) if initcode is None: @@ -352,18 +401,18 @@ def _deterministic_deploy_contract( raise ValueError( f"initcode too large {len(initcode)} > {max_initcode_size}" ) - deploy_gas_limit = gas_costs.TX_BASE + gas_costs.TX_CREATE - deploy_gas_limit += len(deploy_code) * gas_costs.CODE_DEPOSIT_PER_BYTE - deploy_gas_limit += memory_expansion_gas_calculator( - new_bytes=len(initcode) + regular_gas, deploy_gas_limit = _compute_deploy_gas_limit( + fork, + deploy_code_size=len(deploy_code), + initcode=initcode, ) - deploy_gas_limit += calldata_gas_calculator(data=initcode) - deploy_gas_limit = deploy_gas_limit * 2 + # Per EIP-8037, the per-tx 2^24 cap (EIP-7825) binds only the + # regular-gas portion; state gas is drawn from the block reservoir. tx_gas_limit_cap = fork.transaction_gas_limit_cap() - if tx_gas_limit_cap and deploy_gas_limit > tx_gas_limit_cap: + if tx_gas_limit_cap and regular_gas > tx_gas_limit_cap: raise ValueError( - f"deterministic deploy gas limit exceeds the transaction " - f"gas limit cap: {deploy_gas_limit} > {tx_gas_limit_cap}" + f"deterministic deploy regular gas exceeds the transaction " + f"gas limit cap: {regular_gas} > {tx_gas_limit_cap}" ) # Defer the on-chain check; the deploy tx (if needed) and the @@ -407,11 +456,6 @@ def _deploy_contract( fork = self._fork.fork_at( block_number=self._block_number, timestamp=self._timestamp ) - gas_costs = fork.gas_costs() - memory_expansion_gas_calculator = ( - fork.memory_expansion_gas_calculator() - ) - calldata_gas_calculator = fork.calldata_gas_calculator() if not isinstance(storage, Storage): storage = Storage(storage) # type: ignore @@ -447,13 +491,10 @@ def _deploy_contract( initcode_prefix = Bytecode() - deploy_gas_limit = gas_costs.TX_BASE + gas_costs.TX_CREATE - if len(storage.root) > 0: initcode_prefix += sum( Op.SSTORE(key, value) for key, value in storage.root.items() ) - deploy_gas_limit += len(storage.root) * 22_600 assert isinstance(code, Bytecode), ( f"incompatible code type: {type(code)}" @@ -464,14 +505,9 @@ def _deploy_contract( if len(code) > max_code_size: raise ValueError(f"code too large: {len(code)} > {max_code_size}") - deploy_gas_limit += len(code) * gas_costs.CODE_DEPOSIT_PER_BYTE - prepared_initcode = Initcode( deploy_code=code, initcode_prefix=initcode_prefix ) - deploy_gas_limit += memory_expansion_gas_calculator( - new_bytes=len(bytes(prepared_initcode)) - ) max_initcode_size = fork.max_initcode_size() initcode_len = len(prepared_initcode) @@ -480,14 +516,19 @@ def _deploy_contract( f"initcode too large {initcode_len} > {max_initcode_size}" ) - deploy_gas_limit += calldata_gas_calculator(data=prepared_initcode) - - deploy_gas_limit = deploy_gas_limit * 2 + regular_gas, deploy_gas_limit = _compute_deploy_gas_limit( + fork, + deploy_code_size=len(code), + initcode=prepared_initcode, + storage_slots=len(storage.root), + ) + # Per EIP-8037, the per-tx 2^24 cap (EIP-7825) binds only the + # regular-gas portion; state gas is drawn from the block reservoir. tx_gas_limit_cap = fork.transaction_gas_limit_cap() - if tx_gas_limit_cap and deploy_gas_limit > tx_gas_limit_cap: + if tx_gas_limit_cap and regular_gas > tx_gas_limit_cap: raise ValueError( - f"deploy gas limit exceeds the transaction gas limit cap: " - f"{deploy_gas_limit} > {tx_gas_limit_cap}" + f"deploy regular gas exceeds the transaction gas limit cap: " + f"{regular_gas} > {tx_gas_limit_cap}" ) deploy_tx = self._add_pending_tx( @@ -649,6 +690,7 @@ def _fund_eoa( target=label, to=eoa, value=amount, + gas_limit=self._funding_gas_limit, ) if fund_tx is not None: @@ -866,6 +908,7 @@ def _resolve_fund_addresses(self) -> None: target=d.address.label, to=d.address, value=d.amount - current_balance, + gas_limit=self._funding_gas_limit, ) new_balance = d.amount else: @@ -880,6 +923,7 @@ def _resolve_fund_addresses(self) -> None: target=d.address.label, to=d.address, value=d.amount, + gas_limit=self._funding_gas_limit, ) new_balance = current_balance + d.amount @@ -1006,6 +1050,7 @@ def pre( max_fee_per_gas: int, max_priority_fee_per_gas: int, dry_run: bool, + sender_fund_refund_gas_limit: int, request: pytest.FixtureRequest, ) -> Generator[Alloc, None, None]: """Return default pre allocation for all tests (Empty alloc).""" @@ -1030,6 +1075,7 @@ def pre( chain_id=chain_config.chain_id, node_id=request.node.nodeid, address_stubs=address_stubs, + funding_gas_limit=sender_fund_refund_gas_limit, ) # Yield the pre-alloc for usage during the test @@ -1055,7 +1101,7 @@ def pre( # Build refund transactions refund_txs: List[Transaction] = [] skipped_refunds = 0 - refund_gas_limit = 21_000 + refund_gas_limit = sender_fund_refund_gas_limit tx_cost = refund_gas_limit * max_fee_per_gas for idx, eoa in enumerate(funded_eoas): account = eth_rpc.get_account(eoa, skip_code=True) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/sender.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/sender.py index d59342918ad..40db82a1eb2 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/sender.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/sender.py @@ -56,7 +56,7 @@ def pytest_addoption(parser: pytest.Parser) -> None: action="store", dest="sender_fund_refund_gas_limit", type=Wei, - default=21_000, + default=200_000, help=( "Gas limit set for the funding transactions of each worker's sender key." # noqa: E501 ), diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py index fb7fac5f239..639631de181 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py @@ -1031,6 +1031,44 @@ def pytest_html_results_table_row(report: Any, cells: Any) -> None: del cells[-1] # Remove the "Links" column +@pytest.hookimpl(hookwrapper=True) +def pytest_runtest_setup(item: Any) -> Generator[None, None, None]: + """ + Snapshot parametrize values before fixture setup to detect unintended + mutations of shared pytest parameter objects across fixture format runs. + """ + if hasattr(item, "callspec"): + item._param_repr_snapshot = { + key: repr(value) for key, value in item.callspec.params.items() + } + yield + + +def pytest_runtest_teardown(item: Any) -> None: + """ + Compare parametrize values after test teardown to the pre-setup snapshot. + + Warn if any fixture mutated shared parameter objects — these mutations + persist across fixture format runs and can cause subtle bugs (e.g. + block hash mismatches between blockchain_test and blockchain_engine_test). + """ + snapshot = getattr(item, "_param_repr_snapshot", None) + if snapshot is None: + return + for key, original_repr in snapshot.items(): + current_repr = repr(item.callspec.params[key]) + if current_repr != original_repr: + warnings.warn( + f"Shared pytest parameter '{key}' was mutated during " + f"test '{item.nodeid}'. Mutations on parametrize values " + f"persist across fixture format runs and can cause " + f"divergent test results. Avoid mutating these objects " + f"in fixtures; compute derived values locally instead.", + stacklevel=1, + ) + del item._param_repr_snapshot + + @pytest.hookimpl(hookwrapper=True) def pytest_runtest_makereport( item: Any, call: Any diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/transaction_fixtures.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/transaction_fixtures.py index 8930a6e35a5..33dd3693cf4 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/transaction_fixtures.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/transaction_fixtures.py @@ -106,7 +106,7 @@ def type_4_default_transaction(sender: EOA, pre: Alloc) -> Transaction: sender=sender, max_fee_per_gas=10**10, max_priority_fee_per_gas=10**9, - gas_limit=150_000, + gas_limit=500_000, data=b"\x00" * 200, access_list=[ AccessList(address=0x4567, storage_keys=[1000, 2000, 3000]), diff --git a/packages/testing/src/execution_testing/client_clis/cli_types.py b/packages/testing/src/execution_testing/client_clis/cli_types.py index 11b7e92aa15..85479f4cae6 100644 --- a/packages/testing/src/execution_testing/client_clis/cli_types.py +++ b/packages/testing/src/execution_testing/client_clis/cli_types.py @@ -158,6 +158,7 @@ class TransactionTraces(CamelModel): traces: List[TraceLine] output: str | None = None gas_used: HexNumber | None = None + error: str | None = None @classmethod def from_file(cls, trace_file_path: Path) -> Self: diff --git a/packages/testing/src/execution_testing/forks/base_fork.py b/packages/testing/src/execution_testing/forks/base_fork.py index f6d5edb2bf9..0182fbd6252 100644 --- a/packages/testing/src/execution_testing/forks/base_fork.py +++ b/packages/testing/src/execution_testing/forks/base_fork.py @@ -470,6 +470,47 @@ def opcode_gas_map( """ pass + @classmethod + @abstractmethod + def opcode_state_map( + cls, + ) -> Dict[OpcodeBase, int | Callable[[OpcodeBase], int]]: + """ + Return a mapping of opcodes to their state gas costs. + + An int value is a multiplier of `cost_per_state_byte`. A + callable takes the opcode instance with metadata and returns + the full state gas cost. + """ + pass + + @classmethod + @abstractmethod + def opcode_refund_map( + cls, + ) -> Dict[OpcodeBase, int | Callable[[OpcodeBase], int]]: + """ + Return a mapping of opcodes to their gas refunds. + + An int value is a direct gas refund. A callable takes the + opcode instance with metadata and returns the gas refund. + """ + pass + + @classmethod + @abstractmethod + def opcode_state_refund_map( + cls, + ) -> Dict[OpcodeBase, int | Callable[[OpcodeBase], int]]: + """ + Return a mapping of opcodes to their state refunds. + + An int value is a multiplier of `cost_per_state_byte`. A + callable takes the opcode instance with metadata and returns + the state refund. + """ + pass + # Gas calculation helpers @classmethod @abstractmethod @@ -597,6 +638,14 @@ def base_fee_change_calculator(cls) -> BaseFeeChangeCalculator: """ pass + @classmethod + @abstractmethod + def cost_per_state_byte(cls) -> int: + """ + Return the cost per state byte for this fork. + """ + pass + # Fee helpers @classmethod @abstractmethod @@ -639,6 +688,25 @@ def transaction_intrinsic_cost_calculator( """ pass + @classmethod + def transaction_intrinsic_state_gas( + cls, + *, + contract_creation: bool = False, + authorization_count: int = 0, + ) -> int: + """Return intrinsic state gas (zero pre-Amsterdam).""" + del contract_creation, authorization_count + return 0 + + @classmethod + def system_call_gas_limit(cls) -> int: + """ + Return the total gas budget the system transaction grants the + target contract. + """ + return 0 + @classmethod @abstractmethod def blob_gas_price_calculator(cls) -> BlobGasPriceCalculator: @@ -774,6 +842,38 @@ def transaction_gas_limit_cap(cls) -> int | None: """ pass + @classmethod + @abstractmethod + def code_deposit_state_gas(cls, *, code_size: int) -> int: + """Return state gas for code deposit of the given size.""" + pass + + @classmethod + @abstractmethod + def create_state_gas(cls, *, code_size: int = 0) -> int: + """Return total state gas for CREATE.""" + pass + + @classmethod + def oog_budget_lift( + cls, + *, + sstores_before_oog: int = 0, + creates_before_oog: int = 0, + deploy_code_size: int = 0, + ) -> int: + """ + Return the extra regular gas an out of gas budget needs to + stop at the same point on this fork: the state gas EIP-8037 + spills into regular gas for the given SSTOREs, CREATEs, and + deployed bytes. Zero before EIP-8037, so no fork guard needed. + """ + return ( + sstores_before_oog * Opcodes.SSTORE(new_value=1).state_cost(cls) + + creates_before_oog * cls.create_state_gas() + + cls.code_deposit_state_gas(code_size=deploy_code_size) + ) + @classmethod @abstractmethod def block_rlp_size_limit(cls) -> int | None: diff --git a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8037.py b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8037.py new file mode 100644 index 00000000000..bfc33e6c342 --- /dev/null +++ b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8037.py @@ -0,0 +1,439 @@ +""" +EIP-8037: State Creation Gas Cost Increase. + +Harmonization, increase and separate metering of state creation gas costs to +mitigate state growth and unblock scaling. + +https://eips.ethereum.org/EIPS/eip-8037 +""" + +from dataclasses import replace +from typing import Callable, Dict + +from execution_testing.vm import ( + OpcodeBase, + OpcodeGasCalculator, + Opcodes, +) + +from ....base_fork import BaseFork +from ....gas_costs import GasCosts + +STATE_BYTES_PER_NEW_ACCOUNT = 120 +STATE_BYTES_PER_STORAGE_SET = 64 +STATE_BYTES_PER_AUTH_BASE = 23 + +PER_AUTH_BASE_COST = 7_500 +REGULAR_GAS_CREATE = 9_000 + +SYSTEM_MAX_SSTORES_PER_CALL = 16 + + +class EIP8037(BaseFork): + """EIP-8037 class.""" + + @classmethod + def cost_per_state_byte(cls) -> int: + """ + Return the fixed cost per state byte for EIP-8037. + """ + return 1530 + + @classmethod + def system_call_gas_limit(cls) -> int: + """ + Bump the inherited limit so state gas cost changes cannot + OOG a system call. + """ + sstore_state_gas = ( + STATE_BYTES_PER_STORAGE_SET * cls.cost_per_state_byte() + ) + extra = sstore_state_gas * SYSTEM_MAX_SSTORES_PER_CALL + return super(EIP8037, cls).system_call_gas_limit() + extra + + @classmethod + def code_deposit_state_gas(cls, *, code_size: int) -> int: + """Return state gas for code deposit (EIP-8037).""" + return code_size * cls.cost_per_state_byte() + + @classmethod + def create_state_gas(cls, *, code_size: int = 0) -> int: + """Return total state gas for CREATE (EIP-8037).""" + gas_costs = cls.gas_costs() + return gas_costs.NEW_ACCOUNT + cls.code_deposit_state_gas( + code_size=code_size + ) + + @classmethod + def gas_costs(cls) -> GasCosts: + """ + Return gas costs updated for two-dimensional gas metering, + with state gas folded into the relevant totals. + """ + cpsb = cls.cost_per_state_byte() + parent = super(EIP8037, cls).gas_costs() + new_acct = STATE_BYTES_PER_NEW_ACCOUNT * cpsb + return replace( + parent, + BLOCK_ACCESS_LIST_ITEM=2000, + STORAGE_SET=( + parent.COLD_STORAGE_WRITE + - parent.COLD_STORAGE_ACCESS + + STATE_BYTES_PER_STORAGE_SET * cpsb + ), + NEW_ACCOUNT=new_acct, + OPCODE_CREATE_BASE=REGULAR_GAS_CREATE, + TX_CREATE=(REGULAR_GAS_CREATE + new_acct), + AUTH_PER_EMPTY_ACCOUNT=( + PER_AUTH_BASE_COST + + (STATE_BYTES_PER_NEW_ACCOUNT + STATE_BYTES_PER_AUTH_BASE) + * cpsb + ), + REFUND_AUTH_PER_EXISTING_ACCOUNT=new_acct, + ) + + @classmethod + def opcode_gas_calculator(cls) -> OpcodeGasCalculator: + """ + Return callable that calculates the gas cost of a single opcode. + """ + opcode_gas_map = cls.opcode_gas_map() + opcode_state_calculator = cls.opcode_state_calculator() + + def fn(opcode: OpcodeBase) -> int: + if opcode not in opcode_gas_map: + raise ValueError( + f"No gas cost defined for opcode: {opcode._name_}" + ) + gas_cost_or_calculator = opcode_gas_map[opcode] + + if callable(gas_cost_or_calculator): + regular_gas = gas_cost_or_calculator(opcode) + else: + regular_gas = gas_cost_or_calculator + + return regular_gas + opcode_state_calculator(opcode) + + return fn + + @classmethod + def opcode_state_map( + cls, + ) -> Dict[OpcodeBase, int | Callable[[OpcodeBase], int]]: + """ + Return a mapping of opcodes to their state gas costs. + """ + gas_costs = cls.gas_costs() + return { + Opcodes.SSTORE: lambda op: cls._calculate_sstore_state_gas( + op, gas_costs + ), + Opcodes.RETURN: lambda op: cls._calculate_return_state_gas( + op, gas_costs + ), + Opcodes.CREATE: lambda op: cls._calculate_create_state_gas( + op, gas_costs + ), + Opcodes.CREATE2: lambda op: cls._calculate_create_state_gas( + op, gas_costs + ), + } + + @classmethod + def opcode_state_calculator(cls) -> OpcodeGasCalculator: + """ + Return callable that calculates the state gas of a single opcode. + """ + opcode_state_map = cls.opcode_state_map() + + def fn(opcode: OpcodeBase) -> int: + if opcode not in opcode_state_map: + return 0 + state_or_calculator = opcode_state_map[opcode] + + if callable(state_or_calculator): + return state_or_calculator(opcode) + + return state_or_calculator * cls.cost_per_state_byte() + + return fn + + @classmethod + def opcode_refund_calculator(cls) -> OpcodeGasCalculator: + """ + Return callable that calculates the gas refund of a single opcode. + """ + opcode_refund_map = cls.opcode_refund_map() + opcode_state_refund_calculator = cls.opcode_state_refund_calculator() + + def fn(opcode: OpcodeBase) -> int: + state_refund = opcode_state_refund_calculator(opcode) + if opcode not in opcode_refund_map: + return state_refund + refund_or_calculator = opcode_refund_map[opcode] + + if callable(refund_or_calculator): + regular_refund = refund_or_calculator(opcode) + else: + regular_refund = refund_or_calculator + + return regular_refund + state_refund + + return fn + + @classmethod + def opcode_state_refund_map( + cls, + ) -> Dict[OpcodeBase, int | Callable[[OpcodeBase], int]]: + """ + Return a mapping of opcodes to their state refunds. + """ + gas_costs = cls.gas_costs() + return { + Opcodes.SSTORE: lambda op: cls._calculate_sstore_state_refund( + op, gas_costs + ), + Opcodes.SELFDESTRUCT: ( + lambda op: cls._calculate_selfdestruct_state_refund( + op, gas_costs + ) + ), + } + + @classmethod + def opcode_state_refund_calculator(cls) -> OpcodeGasCalculator: + """ + Return callable that calculates the state refund of a single opcode. + """ + opcode_state_refund_map = cls.opcode_state_refund_map() + + def fn(opcode: OpcodeBase) -> int: + if opcode not in opcode_state_refund_map: + return 0 + state_refund_or_calculator = opcode_state_refund_map[opcode] + + if callable(state_refund_or_calculator): + return state_refund_or_calculator(opcode) + + return state_refund_or_calculator * cls.cost_per_state_byte() + + return fn + + @classmethod + def transaction_intrinsic_state_gas( + cls, + *, + contract_creation: bool = False, + authorization_count: int = 0, + ) -> int: + """ + Return the intrinsic state gas for a transaction. Creation + adds `STATE_BYTES_PER_NEW_ACCOUNT * cpsb`, and each + authorization adds + `(STATE_BYTES_PER_NEW_ACCOUNT + STATE_BYTES_PER_AUTH_BASE) * cpsb`. + """ + cpsb = cls.cost_per_state_byte() + state_gas = 0 + if contract_creation: + state_gas += STATE_BYTES_PER_NEW_ACCOUNT * cpsb + state_gas += ( + (STATE_BYTES_PER_NEW_ACCOUNT + STATE_BYTES_PER_AUTH_BASE) + * cpsb + * authorization_count + ) + return state_gas + + @classmethod + def _calculate_sstore_gas( + cls, opcode: OpcodeBase, gas_costs: GasCosts + ) -> int: + """ + Calculate the regular SSTORE gas cost. The state portion is + returned separately by `_calculate_sstore_state_gas`. A cold + slot adds `COLD_STORAGE_ACCESS`, a write to an unchanged + original adds `COLD_STORAGE_WRITE` minus `COLD_STORAGE_ACCESS`, + and every other case adds `WARM_SLOAD`. + """ + metadata = opcode.metadata + + original_value = metadata["original_value"] + current_value = metadata["current_value"] + if current_value is None: + current_value = original_value + new_value = metadata["new_value"] + + gas_cost = 0 if metadata["key_warm"] else gas_costs.COLD_STORAGE_ACCESS + + if original_value == current_value and current_value != new_value: + gas_cost += ( + gas_costs.COLD_STORAGE_WRITE - gas_costs.COLD_STORAGE_ACCESS + ) + else: + gas_cost += gas_costs.WARM_SLOAD + + return gas_cost + + @classmethod + def _calculate_sstore_state_gas( + cls, opcode: OpcodeBase, gas_costs: GasCosts + ) -> int: + """ + Calculate the SSTORE state gas cost. Return + `STATE_BYTES_PER_STORAGE_SET * cpsb` when a slot is first set + from zero, otherwise return 0. + """ + del gas_costs + metadata = opcode.metadata + cpsb = cls.cost_per_state_byte() + + original_value = metadata["original_value"] + current_value = metadata["current_value"] + if current_value is None: + current_value = original_value + new_value = metadata["new_value"] + + if ( + original_value == current_value + and current_value != new_value + and original_value == 0 + ): + return STATE_BYTES_PER_STORAGE_SET * cpsb + return 0 + + @classmethod + def _calculate_sstore_refund( + cls, opcode: OpcodeBase, gas_costs: GasCosts + ) -> int: + """ + Calculate the regular SSTORE gas refund. The state portion is + returned separately by `_calculate_sstore_state_refund`. + """ + metadata = opcode.metadata + + original_value = metadata["original_value"] + current_value = metadata["current_value"] + if current_value is None: + current_value = original_value + new_value = metadata["new_value"] + + refund = 0 + if current_value != new_value: + if original_value != 0 and current_value != 0 and new_value == 0: + refund += gas_costs.REFUND_STORAGE_CLEAR + + if original_value != 0 and current_value == 0: + refund -= gas_costs.REFUND_STORAGE_CLEAR + + if original_value == new_value: + refund += ( + gas_costs.COLD_STORAGE_WRITE + - gas_costs.COLD_STORAGE_ACCESS + - gas_costs.WARM_SLOAD + ) + + return refund + + @classmethod + def _calculate_sstore_state_refund( + cls, opcode: OpcodeBase, gas_costs: GasCosts + ) -> int: + """ + Calculate the SSTORE state gas refund. Return + `STATE_BYTES_PER_STORAGE_SET * cpsb` when a slot that was + originally empty is restored back to zero within the + transaction, otherwise return 0. + """ + del gas_costs + metadata = opcode.metadata + cpsb = cls.cost_per_state_byte() + + original_value = metadata["original_value"] + current_value = metadata["current_value"] + if current_value is None: + current_value = original_value + new_value = metadata["new_value"] + if current_value != new_value: + if original_value == new_value: + if original_value == 0: + return STATE_BYTES_PER_STORAGE_SET * cpsb + return 0 + + @classmethod + def _calculate_selfdestruct_state_refund( + cls, opcode: OpcodeBase, gas_costs: GasCosts + ) -> int: + """ + Calculate the SELFDESTRUCT state gas refund. Refund + `STATE_BYTES_PER_NEW_ACCOUNT * cpsb` for the destroyed account, + `STATE_BYTES_PER_STORAGE_SET * cpsb` for each populated storage + slot, and `cpsb` per byte of deposited code. + """ + del gas_costs + metadata = opcode.metadata + cpsb = cls.cost_per_state_byte() + + self_destructed_account = metadata["self_destructed_account"] + self_destructed_account_storage_slot_count = metadata[ + "self_destructed_account_storage_slot_count" + ] + self_destructed_account_code_deposit = metadata[ + "self_destructed_account_code_deposit" + ] + state_refund = 0 + if self_destructed_account: + state_refund = STATE_BYTES_PER_NEW_ACCOUNT * cpsb + state_refund += ( + STATE_BYTES_PER_STORAGE_SET + * cpsb + * self_destructed_account_storage_slot_count + ) + state_refund += cpsb * self_destructed_account_code_deposit + return state_refund + + @classmethod + def _calculate_return_gas( + cls, opcode: OpcodeBase, gas_costs: GasCosts + ) -> int: + """ + Calculate the regular RETURN gas cost: the code hash gas + (keccak256 of the deployed bytecode). The per byte code deposit + cost moves to state gas, returned by `_calculate_return_state_gas`. + """ + metadata = opcode.metadata + code_deposit_size = metadata["code_deposit_size"] + if code_deposit_size > 0: + code_words = (code_deposit_size + 31) // 32 + hash_gas = gas_costs.OPCODE_KECCAK256_PER_WORD * code_words + return hash_gas + return 0 + + @classmethod + def _calculate_return_state_gas( + cls, opcode: OpcodeBase, gas_costs: GasCosts + ) -> int: + """ + Calculate the RETURN state gas cost: `cpsb` per deposited code + byte, the state portion replacing the per byte code deposit + cost. The code hash gas is accounted for separately in + `_calculate_return_gas`. + """ + del gas_costs + metadata = opcode.metadata + code_deposit_size = metadata["code_deposit_size"] + if code_deposit_size > 0: + return code_deposit_size * cls.cost_per_state_byte() + return 0 + + @classmethod + def _calculate_create_state_gas( + cls, opcode: OpcodeBase, gas_costs: GasCosts + ) -> int: + """ + Calculate the CREATE and CREATE2 state gas cost, which is + `NEW_ACCOUNT`. Before EIP-8037 this was folded into + `OPCODE_CREATE_BASE`. Under EIP-8037 it is exposed here so that + `OPCODE_CREATE_BASE` stays regular only and matches the spec + EVM constant. + """ + del opcode + return gas_costs.NEW_ACCOUNT diff --git a/packages/testing/src/execution_testing/forks/forks/eips/cancun/eip_4788.py b/packages/testing/src/execution_testing/forks/forks/eips/cancun/eip_4788.py index 29db5a9b93d..c247691b1f5 100644 --- a/packages/testing/src/execution_testing/forks/forks/eips/cancun/eip_4788.py +++ b/packages/testing/src/execution_testing/forks/forks/eips/cancun/eip_4788.py @@ -23,6 +23,11 @@ def header_beacon_root_required(cls) -> bool: """Parent beacon block root is required.""" return True + @classmethod + def system_call_gas_limit(cls) -> int: + """Gas budget for the system-call mechanism (30M).""" + return 30_000_000 + @classmethod def system_contracts(cls) -> List[Address]: """Add the beacon roots system contract.""" diff --git a/packages/testing/src/execution_testing/forks/forks/forks.py b/packages/testing/src/execution_testing/forks/forks/forks.py index 9e56f4e2ae1..acaa4403176 100644 --- a/packages/testing/src/execution_testing/forks/forks/forks.py +++ b/packages/testing/src/execution_testing/forks/forks/forks.py @@ -329,21 +329,11 @@ def opcode_gas_map( ) -> Dict[OpcodeBase, int | Callable[[OpcodeBase], int]]: """ Return a mapping of opcodes to their gas costs. - - Each entry is either: - - Constants (int): Direct gas cost values from gas_costs() - - Callables: Functions that take the opcode instance with metadata and - return gas cost """ gas_costs = cls.gas_costs() memory_expansion_calculator = cls.memory_expansion_gas_calculator() - # Define the opcode gas cost mapping - # Each entry is either: - # - an int (constant cost) - # - a callable(opcode) -> int return { - # Stop and arithmetic operations Opcodes.STOP: 0, Opcodes.ADD: gas_costs.OPCODE_ADD, Opcodes.MUL: gas_costs.OPCODE_MUL, @@ -360,7 +350,6 @@ def opcode_gas_map( * ((op.metadata["exponent"].bit_length() + 7) // 8) ), Opcodes.SIGNEXTEND: gas_costs.OPCODE_SIGNEXTEND, - # Comparison & bitwise logic operations Opcodes.LT: gas_costs.OPCODE_LT, Opcodes.GT: gas_costs.OPCODE_GT, Opcodes.SLT: gas_costs.OPCODE_SLT, @@ -372,7 +361,6 @@ def opcode_gas_map( Opcodes.XOR: gas_costs.OPCODE_XOR, Opcodes.NOT: gas_costs.OPCODE_NOT, Opcodes.BYTE: gas_costs.OPCODE_BYTE, - # SHA3 Opcodes.SHA3: cls._with_memory_expansion( lambda op: ( gas_costs.OPCODE_KECCAK256_BASE @@ -381,7 +369,6 @@ def opcode_gas_map( ), memory_expansion_calculator, ), - # Environmental information Opcodes.ADDRESS: gas_costs.BASE, Opcodes.BALANCE: cls._with_account_access(0, gas_costs), Opcodes.ORIGIN: gas_costs.BASE, @@ -409,14 +396,12 @@ def opcode_gas_map( ), memory_expansion_calculator, ), - # Block information Opcodes.BLOCKHASH: gas_costs.OPCODE_BLOCKHASH, Opcodes.COINBASE: gas_costs.OPCODE_COINBASE, Opcodes.TIMESTAMP: gas_costs.BASE, Opcodes.NUMBER: gas_costs.BASE, Opcodes.PREVRANDAO: gas_costs.BASE, Opcodes.GASLIMIT: gas_costs.BASE, - # Stack, memory, storage and flow operations Opcodes.POP: gas_costs.BASE, Opcodes.MLOAD: cls._with_memory_expansion( gas_costs.OPCODE_MLOAD_BASE, @@ -444,22 +429,18 @@ def opcode_gas_map( Opcodes.MSIZE: gas_costs.BASE, Opcodes.GAS: gas_costs.BASE, Opcodes.JUMPDEST: gas_costs.OPCODE_JUMPDEST, - # Push operations (PUSH1 through PUSH32) **{ getattr(Opcodes, f"PUSH{i}"): gas_costs.OPCODE_PUSH for i in range(1, 33) }, - # Dup operations (DUP1 through DUP16) **{ getattr(Opcodes, f"DUP{i}"): gas_costs.OPCODE_DUP for i in range(1, 17) }, - # Swap operations (SWAP1 through SWAP16) **{ getattr(Opcodes, f"SWAP{i}"): gas_costs.OPCODE_SWAP for i in range(1, 17) }, - # Logging operations Opcodes.LOG0: cls._with_memory_expansion( lambda op: ( gas_costs.OPCODE_LOG_BASE @@ -504,7 +485,6 @@ def opcode_gas_map( ), memory_expansion_calculator, ), - # System operations Opcodes.CREATE: cls._with_memory_expansion( lambda op: cls._calculate_create_gas(op, gas_costs), memory_expansion_calculator, @@ -535,37 +515,49 @@ def opcode_gas_calculator(cls) -> OpcodeGasCalculator: opcode_gas_map = cls.opcode_gas_map() def fn(opcode: OpcodeBase) -> int: - # Get the gas cost or calculator if opcode not in opcode_gas_map: raise ValueError( f"No gas cost defined for opcode: {opcode._name_}" ) gas_cost_or_calculator = opcode_gas_map[opcode] - # If it's a callable, call it with the opcode if callable(gas_cost_or_calculator): return gas_cost_or_calculator(opcode) - # Otherwise it's a constant return gas_cost_or_calculator return fn + @classmethod + def opcode_state_map( + cls, + ) -> Dict[OpcodeBase, int | Callable[[OpcodeBase], int]]: + """ + Return a mapping of opcodes to their state gas costs. + """ + return {} + + @classmethod + def opcode_state_calculator(cls) -> OpcodeGasCalculator: + """ + Return callable that calculates the state gas of a single opcode. + """ + + def fn(opcode: OpcodeBase) -> int: + del opcode + return 0 + + return fn + @classmethod def opcode_refund_map( cls, ) -> Dict[OpcodeBase, int | Callable[[OpcodeBase], int]]: """ Return a mapping of opcodes to their gas refunds. - - Each entry is either: - - Constants (int): Direct gas refund values - - Callables: Functions that take the opcode instance with metadata and - return gas refund """ gas_costs = cls.gas_costs() - # Only SSTORE provides refunds return { Opcodes.SSTORE: lambda op: cls._calculate_sstore_refund( op, gas_costs @@ -580,21 +572,38 @@ def opcode_refund_calculator(cls) -> OpcodeGasCalculator: opcode_refund_map = cls.opcode_refund_map() def fn(opcode: OpcodeBase) -> int: - # Get the gas refund or calculator if opcode not in opcode_refund_map: - # Most opcodes don't provide refunds return 0 refund_or_calculator = opcode_refund_map[opcode] - # If it's a callable, call it with the opcode if callable(refund_or_calculator): return refund_or_calculator(opcode) - # Otherwise it's a constant return refund_or_calculator return fn + @classmethod + def opcode_state_refund_map( + cls, + ) -> Dict[OpcodeBase, int | Callable[[OpcodeBase], int]]: + """ + Return a mapping of opcodes to their state refunds. + """ + return {} + + @classmethod + def opcode_state_refund_calculator(cls) -> OpcodeGasCalculator: + """ + Return callable that calculates the state refund of a single opcode. + """ + + def fn(opcode: OpcodeBase) -> int: + del opcode + return 0 + + return fn + @classmethod def _calculate_sstore_refund( cls, opcode: OpcodeBase, gas_costs: GasCosts @@ -792,6 +801,13 @@ def base_fee_change_calculator(cls) -> BaseFeeChangeCalculator: f"Base fee change calculator is not supported in {cls.name()}" ) + @classmethod + def cost_per_state_byte(cls) -> int: + """ + Return the cost per state byte, 0 before state gas applies. + """ + return 0 + @classmethod def base_fee_max_change_denominator(cls) -> int: """Return the base fee max change denominator at a given fork.""" @@ -1009,6 +1025,18 @@ def transaction_gas_limit_cap(cls) -> int | None: """At Genesis, no transaction gas limit cap is imposed.""" return None + @classmethod + def code_deposit_state_gas(cls, *, code_size: int) -> int: + """Return the state gas for code deposit of the given size.""" + del code_size + return 0 + + @classmethod + def create_state_gas(cls, *, code_size: int = 0) -> int: + """Return total state gas for CREATE (new account + code deposit).""" + del code_size + return 0 + @classmethod def block_rlp_size_limit(cls) -> int | None: """At Genesis, no RLP block size limit is imposed.""" diff --git a/packages/testing/src/execution_testing/forks/tests/test_forks.py b/packages/testing/src/execution_testing/forks/tests/test_forks.py index 8d9cec7882c..3251541e0dd 100644 --- a/packages/testing/src/execution_testing/forks/tests/test_forks.py +++ b/packages/testing/src/execution_testing/forks/tests/test_forks.py @@ -6,6 +6,7 @@ from pydantic import BaseModel from execution_testing.base_types import BlobSchedule +from execution_testing.vm import Opcodes from ..forks.eips.paris.eip_3675 import EIP3675 from ..forks.forks import ( @@ -731,3 +732,35 @@ def test_eips() -> None: # noqa: D103 assert not Paris.is_eip_enabled(3675, 3855) assert not Paris.is_eip_enabled(3855, 3675) assert Shanghai.is_eip_enabled(3855) + + +def test_oog_budget_lift() -> None: + """ + `Fork.oog_budget_lift` returns zero pre-EIP-8037 and the cumulative + SSTORE-set + CREATE + code-deposit state-gas spill on Amsterdam. + """ + # Pre-EIP-8037: state_gas helpers are 0, so any lift is 0. + assert Cancun.oog_budget_lift(sstores_before_oog=1) == 0 + assert Cancun.oog_budget_lift(creates_before_oog=1) == 0 + assert ( + Cancun.oog_budget_lift( + sstores_before_oog=3, creates_before_oog=2, deploy_code_size=64 + ) + == 0 + ) + # Amsterdam: lift composes the three state-gas helpers. + sstore = Opcodes.SSTORE(new_value=1).state_cost(Amsterdam) + create = Amsterdam.create_state_gas() + code_64 = Amsterdam.code_deposit_state_gas(code_size=64) + assert Amsterdam.oog_budget_lift() == 0 + assert Amsterdam.oog_budget_lift(sstores_before_oog=1) == sstore + assert Amsterdam.oog_budget_lift(creates_before_oog=1) == create + assert Amsterdam.oog_budget_lift(deploy_code_size=64) == code_64 + assert ( + Amsterdam.oog_budget_lift( + sstores_before_oog=3, + creates_before_oog=2, + deploy_code_size=64, + ) + == 3 * sstore + 2 * create + code_64 + ) diff --git a/packages/testing/src/execution_testing/tools/utility/generators.py b/packages/testing/src/execution_testing/tools/utility/generators.py index ca28a5b9fe1..233c84bb19a 100644 --- a/packages/testing/src/execution_testing/tools/utility/generators.py +++ b/packages/testing/src/execution_testing/tools/utility/generators.py @@ -362,9 +362,12 @@ def wrapper( + gas_costs.COLD_STORAGE_ACCESS + (gas_costs.VERY_LOW * 2) ) + effective_max_gas = max( + max_gas_limit, fork.system_call_gas_limit() + ) modified_system_contract_code += sum( Op.SSTORE(i, 1) - for i in range(max_gas_limit // gas_used_per_storage) + for i in range(effective_max_gas // gas_used_per_storage) ) # If the gas limit is not divisible by the gas used per # storage, we need to add some NO-OP (JUMPDEST) to the code @@ -376,7 +379,7 @@ def wrapper( ) modified_system_contract_code += sum( Op.JUMPDEST - for _ in range(max_gas_limit % gas_used_per_storage) + for _ in range(effective_max_gas % gas_used_per_storage) ) if test_type == SystemContractTestType.OUT_OF_GAS_ERROR: diff --git a/packages/testing/src/execution_testing/vm/bases.py b/packages/testing/src/execution_testing/vm/bases.py index 7fb76bf5c3e..645d9357551 100644 --- a/packages/testing/src/execution_testing/vm/bases.py +++ b/packages/testing/src/execution_testing/vm/bases.py @@ -39,6 +39,14 @@ def opcode_gas_calculator(cls) -> OpcodeGasCalculator: """ pass + @classmethod + @abstractmethod + def opcode_state_calculator(cls) -> OpcodeGasCalculator: + """ + Return callable that calculates the state gas cost of a single opcode. + """ + pass + @classmethod @abstractmethod def opcode_refund_calculator(cls) -> OpcodeGasCalculator: @@ -46,3 +54,11 @@ def opcode_refund_calculator(cls) -> OpcodeGasCalculator: Return callable that calculates the gas refund of a single opcode. """ pass + + @classmethod + @abstractmethod + def opcode_state_refund_calculator(cls) -> OpcodeGasCalculator: + """ + Return callable that calculates the gas refund of a single opcode. + """ + pass diff --git a/packages/testing/src/execution_testing/vm/bytecode.py b/packages/testing/src/execution_testing/vm/bytecode.py index 74b2ede512f..1a28cd18f09 100644 --- a/packages/testing/src/execution_testing/vm/bytecode.py +++ b/packages/testing/src/execution_testing/vm/bytecode.py @@ -36,8 +36,14 @@ class Bytecode: _keccak_256_: Hash | None = None _gas_cost_: int | None = None _gas_cost_fork_: Type[ForkOpcodeInterface] | None = None + _state_cost_: int | None = None + _state_cost_fork_: Type[ForkOpcodeInterface] | None = None + _regular_cost_: int | None = None + _regular_cost_fork_: Type[ForkOpcodeInterface] | None = None _refund_: int | None = None _refund_fork_: Type[ForkOpcodeInterface] | None = None + _state_refund_: int | None = None + _state_refund_fork_: Type[ForkOpcodeInterface] | None = None popped_stack_items: int pushed_stack_items: int @@ -302,6 +308,33 @@ def gas_cost(self, fork: Type[ForkOpcodeInterface]) -> int: self._gas_cost_ += opcode_gas_calculator(opcode) return self._gas_cost_ + def state_cost(self, fork: Type[ForkOpcodeInterface]) -> int: + """ + Use a fork object to calculate the state gas used by this + bytecode. + """ + if self._state_cost_ is None or self._state_cost_fork_ != fork: + self._state_cost_fork_ = fork + opcode_state_calculator = fork.opcode_state_calculator() + self._state_cost_ = 0 + for opcode in self.opcode_list: + self._state_cost_ += opcode_state_calculator(opcode) + return self._state_cost_ + + def regular_cost(self, fork: Type[ForkOpcodeInterface]) -> int: + """ + Use a fork object to calculate the regular gas used by this + bytecode (i.e. excluding the state-gas portion under EIP-8037). + + Useful for OOG-boundary tests that need to land at the regular + gas charge of an opcode rather than its combined regular + state + cost. + """ + if self._regular_cost_ is None or self._regular_cost_fork_ != fork: + self._regular_cost_fork_ = fork + self._regular_cost_ = self.gas_cost(fork) - self.state_cost(fork) + return self._regular_cost_ + def refund(self, fork: Type[ForkOpcodeInterface]) -> int: """Use a fork object to calculate the gas refund from this bytecode.""" if self._refund_ is None or self._refund_fork_ != fork: @@ -312,6 +345,20 @@ def refund(self, fork: Type[ForkOpcodeInterface]) -> int: self._refund_ += opcode_refund_calculator(opcode) return self._refund_ + def state_refund(self, fork: Type[ForkOpcodeInterface]) -> int: + """ + Use a fork object to calculate the state refund from this bytecode. + """ + if self._state_refund_ is None or self._state_refund_fork_ != fork: + self._state_refund_fork_ = fork + opcode_state_refund_calculator = ( + fork.opcode_state_refund_calculator() + ) + self._state_refund_ = 0 + for opcode in self.opcode_list: + self._state_refund_ += opcode_state_refund_calculator(opcode) + return self._state_refund_ + @classmethod def __get_pydantic_core_schema__( cls, source_type: Any, handler: GetCoreSchemaHandler diff --git a/packages/testing/src/execution_testing/vm/opcodes.py b/packages/testing/src/execution_testing/vm/opcodes.py index 49563887cb7..cfe8c4d52b1 100644 --- a/packages/testing/src/execution_testing/vm/opcodes.py +++ b/packages/testing/src/execution_testing/vm/opcodes.py @@ -5909,7 +5909,13 @@ class Opcodes(Opcode, Enum): 0xFF, popped_stack_items=1, kwargs=["address"], - metadata={"address_warm": False, "account_new": False}, + metadata={ + "address_warm": False, + "account_new": False, + "self_destructed_account": False, + "self_destructed_account_storage_slot_count": 0, + "self_destructed_account_code_deposit": 0, + }, ) """ SELFDESTRUCT(address) @@ -5937,6 +5943,12 @@ class Opcodes(Opcode, Enum): (default: False) - account_new: whether creating a new beneficiary account, requires non-zero balance in the source account (default: False) + - self_destructed_account: whether the execution results in an account + self-destructing (default: False) + - self_destructed_account_storage_slot_count: amount of storage slots that + were created in the self-destructing account (default: 0) + - self_destructed_account_code_deposit: amount of bytes that comprised the + code of the self-destructing account (default: 0) Source: [evm.codes/#FF](https://www.evm.codes/#FF) """ diff --git a/src/ethereum/forks/amsterdam/fork.py b/src/ethereum/forks/amsterdam/fork.py index d50ae63f193..50761a89047 100644 --- a/src/ethereum/forks/amsterdam/fork.py +++ b/src/ethereum/forks/amsterdam/fork.py @@ -15,7 +15,7 @@ from typing import Final, List, Optional, Tuple, final from ethereum_rlp import rlp -from ethereum_types.bytes import Bytes +from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U64, U256, Uint, ulen @@ -80,8 +80,10 @@ set_account_balance, ) from .transactions import ( + TX_MAX_GAS_LIMIT, BlobTransaction, FeeMarketTransaction, + IntrinsicGasCost, LegacyTransaction, SetCodeTransaction, Transaction, @@ -98,6 +100,7 @@ from .vm.eoa_delegation import is_valid_delegation from .vm.gas import ( GasCosts, + StateGasCosts, calculate_blob_gas_price, calculate_data_fee, calculate_excess_blob_gas, @@ -113,6 +116,11 @@ "0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02" ) SYSTEM_TRANSACTION_GAS = Uint(30000000) +SYSTEM_MAX_SSTORES_PER_CALL = Uint(16) +""" +Upper bound on the number of new storage slots a single system call is +expected to write. +""" MAX_BLOB_GAS_PER_BLOCK: Final[U64] = ( GasCosts.BLOB_SCHEDULE_MAX * GasCosts.PER_BLOB ) @@ -337,10 +345,12 @@ def execute_block( block_output.block_access_list ) - if block_output.block_gas_used != block.header.gas_used: - raise InvalidBlock( - f"{block_output.block_gas_used} != {block.header.gas_used}" - ) + block_gas_used = max( + block_output.block_gas_used, + block_output.block_state_gas_used, + ) + if block_gas_used != block.header.gas_used: + raise InvalidBlock(f"{block_gas_used} != {block.header.gas_used}") if transactions_root != block.header.transactions_root: raise InvalidBlock if block_state_root != block.header.state_root: @@ -486,6 +496,7 @@ def check_transaction( block_output: vm.BlockOutput, tx: Transaction, tx_state: TransactionState, + intrinsic: IntrinsicGasCost, ) -> Tuple[Address, Uint, Tuple[VersionedHash, ...], U64]: """ Check if the transaction is includable in the block. @@ -500,6 +511,9 @@ def check_transaction( The transaction. tx_state : The transaction state tracker. + intrinsic : + The transaction's intrinsic gas cost, split into regular and + state components. Returns ------- @@ -547,11 +561,25 @@ def check_transaction( is empty. """ - gas_available = block_env.block_gas_limit - block_output.block_gas_used + regular_gas_available = ( + block_env.block_gas_limit - block_output.block_gas_used + ) + state_gas_available = ( + block_env.block_gas_limit - block_output.block_state_gas_used + ) blob_gas_available = MAX_BLOB_GAS_PER_BLOCK - block_output.blob_gas_used - if tx.gas > gas_available: - raise GasUsedExceedsLimitError("gas used exceeds limit") + # Worst-case regular contribution: tx.gas minus the portion that + # must go to intrinsic state gas, capped at TX_MAX_GAS_LIMIT. + worst_case_regular = min(TX_MAX_GAS_LIMIT, tx.gas - intrinsic.state) + if worst_case_regular > regular_gas_available: + raise GasUsedExceedsLimitError("regular gas used exceeds limit") + + # Worst-case state contribution: tx.gas minus the portion that + # must go to intrinsic regular gas. + worst_case_state = tx.gas - intrinsic.regular + if worst_case_state > state_gas_available: + raise GasUsedExceedsLimitError("state gas used exceeds limit") tx_blob_gas_used = calculate_total_blob_gas(tx) if tx_blob_gas_used > blob_gas_available: @@ -770,6 +798,9 @@ def process_unchecked_system_transaction( origin=SYSTEM_ADDRESS, gas_price=block_env.base_fee_per_gas, gas=SYSTEM_TRANSACTION_GAS, + state_gas_reservoir=( + StateGasCosts.STORAGE_SET * SYSTEM_MAX_SSTORES_PER_CALL + ), access_list_addresses=set(), access_list_storage_keys=set(), state=system_tx_state, @@ -777,6 +808,8 @@ def process_unchecked_system_transaction( authorizations=(), index_in_block=None, tx_hash=None, + intrinsic_regular_gas=Uint(0), + intrinsic_state_gas=Uint(0), ) system_tx_message = Message( @@ -785,6 +818,9 @@ def process_unchecked_system_transaction( caller=SYSTEM_ADDRESS, target=target_address, gas=SYSTEM_TRANSACTION_GAS, + state_gas_reservoir=( + StateGasCosts.STORAGE_SET * SYSTEM_MAX_SSTORES_PER_CALL + ), value=U256(0), data=data, code=system_contract_code, @@ -966,7 +1002,9 @@ def process_transaction( encode_transaction(tx), ) - intrinsic_gas, calldata_floor_gas_cost = validate_transaction(tx) + intrinsic = validate_transaction(tx) + + intrinsic_gas = intrinsic.regular + intrinsic.state ( sender, @@ -978,6 +1016,7 @@ def process_transaction( block_output=block_output, tx=tx, tx_state=tx_state, + intrinsic=intrinsic, ) sender_account = get_account(tx_state, sender) @@ -989,7 +1028,12 @@ def process_transaction( effective_gas_fee = tx.gas * effective_gas_price - gas = tx.gas - intrinsic_gas + # Split execution gas into gas_left (capped by remaining regular gas + # budget) and state_gas_reservoir. + execution_gas = tx.gas - intrinsic_gas + regular_gas_budget = TX_MAX_GAS_LIMIT - intrinsic.regular + gas = min(regular_gas_budget, execution_gas) + state_gas_reservoir = Uint(execution_gas - gas) increment_nonce(tx_state, sender) @@ -1015,6 +1059,7 @@ def process_transaction( origin=sender, gas_price=effective_gas_price, gas=gas, + state_gas_reservoir=state_gas_reservoir, access_list_addresses=access_list_addresses, access_list_storage_keys=access_list_storage_keys, state=tx_state, @@ -1022,6 +1067,8 @@ def process_transaction( authorizations=authorizations, index_in_block=index, tx_hash=get_transaction_hash(encode_transaction(tx)), + intrinsic_regular_gas=intrinsic.regular, + intrinsic_state_gas=intrinsic.state, ) message = prepare_message( @@ -1032,9 +1079,19 @@ def process_transaction( tx_output = process_message_call(message) - # For EIP-7623 we first calculate the execution_gas_used, which includes - # the execution gas refund. - tx_gas_used_before_refund = tx.gas - tx_output.gas_left + if tx_output.error is not None: + tx_output.state_gas_left = Uint( + int(tx_output.state_gas_left) + tx_output.state_gas_used + ) + tx_output.state_gas_used = 0 + if isinstance(tx.to, Bytes0): + new_account_refund = StateGasCosts.NEW_ACCOUNT + tx_output.state_gas_left += new_account_refund + tx_output.state_refund += new_account_refund + + tx_gas_used_before_refund = ( + tx.gas - tx_output.gas_left - tx_output.state_gas_left + ) tx_gas_refund = min( tx_gas_used_before_refund // Uint(5), Uint(tx_output.refund_counter) ) @@ -1042,10 +1099,7 @@ def process_transaction( # Transactions with less execution_gas_used than the floor pay at the # floor cost. - tx_gas_used = max(tx_gas_used_after_refund, calldata_floor_gas_cost) - block_gas_used_in_tx = max( - tx_gas_used_before_refund, calldata_floor_gas_cost - ) + tx_gas_used = max(tx_gas_used_after_refund, intrinsic.calldata_floor) tx_gas_left = tx.gas - tx_gas_used gas_refund_amount = tx_gas_left * effective_gas_price @@ -1080,10 +1134,19 @@ def process_transaction( all_logs = tx_output.logs + tuple(finalization_logs) - block_output.cumulative_gas_used += tx_gas_used - block_output.block_gas_used += block_gas_used_in_tx + tx_regular_gas = tx_env.intrinsic_regular_gas + tx_output.regular_gas_used + tx_state_gas = ( + int(tx_env.intrinsic_state_gas) + + tx_output.state_gas_used + - int(tx_output.state_refund) + ) + block_output.block_gas_used += max( + tx_regular_gas, intrinsic.calldata_floor + ) + block_output.block_state_gas_used += Uint(max(0, tx_state_gas)) block_output.blob_gas_used += tx_blob_gas_used + block_output.cumulative_gas_used += tx_gas_used receipt = make_receipt( tx, tx_output.error, diff --git a/src/ethereum/forks/amsterdam/transactions.py b/src/ethereum/forks/amsterdam/transactions.py index f757be7d15d..320922cce46 100644 --- a/src/ethereum/forks/amsterdam/transactions.py +++ b/src/ethereum/forks/amsterdam/transactions.py @@ -23,11 +23,35 @@ from .exceptions import ( InitCodeTooLargeError, - TransactionGasLimitExceededError, TransactionTypeError, ) from .fork_types import Authorization, VersionedHash + +@final +@dataclass +class IntrinsicGasCost: + """Intrinsic gas costs for a transaction, split by gas type.""" + + regular: Uint + """Regular execution gas (calldata, base cost, access list, etc.).""" + + state: Uint + """ + State growth gas (account creation, storage set, authorization) per + [EIP-8037]. + + [EIP-8037]: https://eips.ethereum.org/EIPS/eip-8037 + """ + + calldata_floor: Uint + """ + Minimum gas cost based on calldata size per [EIP-7623]. + + [EIP-7623]: https://eips.ethereum.org/EIPS/eip-7623 + """ + + TX_MAX_GAS_LIMIT = Uint(16_777_216) ACCESS_LIST_ADDRESS_FLOOR_TOKENS = Uint(80) @@ -535,7 +559,7 @@ def decode_transaction(tx: LegacyTransaction | Bytes) -> Transaction: return tx -def validate_transaction(tx: Transaction) -> Tuple[Uint, Uint]: +def validate_transaction(tx: Transaction) -> IntrinsicGasCost: """ Verifies a transaction. @@ -553,33 +577,43 @@ def validate_transaction(tx: Transaction) -> Tuple[Uint, Uint]: Also, the code size of a contract creation transaction must be within limits of the protocol. - This function takes a transaction as a parameter and returns the intrinsic - gas cost and the minimum calldata gas cost for the transaction after - validation. It throws an `InsufficientTransactionGasError` exception if - the transaction does not provide enough gas to cover the intrinsic cost, - and a `NonceOverflowError` exception if the nonce is greater than - `2**64 - 2`. It also raises an `InitCodeTooLargeError` if the code size of - a contract creation transaction exceeds the maximum allowed size. + This function takes a transaction and gas_limit as parameters and + returns the intrinsic gas costs for the transaction after validation. + It throws an `InsufficientTransactionGasError` exception if the + transaction does not provide enough gas to cover the intrinsic cost, + and a `NonceOverflowError` exception if the nonce overflows. + It also raises an `InitCodeTooLargeError` if the code + size of a contract creation transaction exceeds the maximum allowed + size. [EIP-2681]: https://eips.ethereum.org/EIPS/eip-2681 [EIP-7623]: https://eips.ethereum.org/EIPS/eip-7623 """ from .vm.interpreter import MAX_INIT_CODE_SIZE - intrinsic_gas, data_floor_gas_cost = calculate_intrinsic_cost(tx) - if max(intrinsic_gas, data_floor_gas_cost) > tx.gas: - raise InsufficientTransactionGasError("Insufficient gas") + intrinsic = calculate_intrinsic_cost(tx) + intrinsic_gas = intrinsic.regular + intrinsic.state + if intrinsic_gas > tx.gas: + raise InsufficientTransactionGasError("Insufficient intrinsic gas") + if intrinsic.calldata_floor > tx.gas: + raise InsufficientTransactionGasError("Insufficient calldata floor") + if intrinsic.regular > TX_MAX_GAS_LIMIT: + raise InsufficientTransactionGasError( + "Intrinsic regular gas exceeds TX_MAX_GAS_LIMIT" + ) + if intrinsic.calldata_floor > TX_MAX_GAS_LIMIT: + raise InsufficientTransactionGasError( + "Intrinsic calldata floor exceeds TX_MAX_GAS_LIMIT" + ) if U256(tx.nonce) >= U256(U64.MAX_VALUE): raise NonceOverflowError("Nonce too high") if tx.to == Bytes0(b"") and len(tx.data) > MAX_INIT_CODE_SIZE: raise InitCodeTooLargeError("Code size too large") - if tx.gas > TX_MAX_GAS_LIMIT: - raise TransactionGasLimitExceededError("Gas limit too high") - return intrinsic_gas, data_floor_gas_cost + return intrinsic -def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: +def calculate_intrinsic_cost(tx: Transaction) -> IntrinsicGasCost: """ Calculates the gas that is charged before execution is started. @@ -600,20 +634,27 @@ def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: 5. Cost for authorizations (if applicable) - This function takes a transaction as a parameter and returns the intrinsic - gas cost of the transaction and the minimum gas cost used by the - transaction based on the calldata size. + This function takes a transaction and gas_limit as parameters and + returns the intrinsic regular gas cost, intrinsic state gas cost, and the + minimum gas cost used by the transaction based on the calldata size. """ - from .vm.gas import GasCosts, init_code_cost + from .vm.gas import ( + GasCosts, + StateGasCosts, + init_code_cost, + ) tokens_in_calldata = count_tokens_in_data(tx.data) data_cost = tokens_in_calldata * GasCosts.TX_DATA_TOKEN_STANDARD + create_regular_gas = Uint(0) + create_state_gas = Uint(0) if tx.to == Bytes0(b""): - create_cost = GasCosts.TX_CREATE + init_code_cost(ulen(tx.data)) - else: - create_cost = Uint(0) + create_state_gas = StateGasCosts.NEW_ACCOUNT + create_regular_gas = GasCosts.REGULAR_GAS_CREATE + init_code_cost( + ulen(tx.data) + ) access_list_cost = Uint(0) tokens_in_access_list = Uint(0) @@ -631,11 +672,15 @@ def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: # Data token floor cost for access list bytes. access_list_cost += tokens_in_access_list * GasCosts.TX_DATA_TOKEN_FLOOR - auth_cost = Uint(0) + auth_regular_gas = Uint(0) + auth_state_gas = Uint(0) if isinstance(tx, SetCodeTransaction): - auth_cost += Uint( - GasCosts.AUTH_PER_EMPTY_ACCOUNT * len(tx.authorizations) + auth_regular_gas = GasCosts.PER_AUTH_BASE_COST * ulen( + tx.authorizations ) + auth_state_gas = ( + StateGasCosts.NEW_ACCOUNT + StateGasCosts.AUTH_BASE + ) * ulen(tx.authorizations) # EIP-7976 floor tokens: all calldata bytes count uniformly. floor_tokens_in_calldata = ulen(tx.data) * GasCosts.TX_DATA_TOKEN_STANDARD @@ -648,15 +693,20 @@ def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: total_floor_tokens * GasCosts.TX_DATA_TOKEN_FLOOR + GasCosts.TX_BASE ) - return ( - Uint( - GasCosts.TX_BASE - + data_cost - + create_cost - + access_list_cost - + auth_cost - ), - data_floor_gas_cost, + intrinsic_regular_gas = ( + GasCosts.TX_BASE + + data_cost + + create_regular_gas + + access_list_cost + + auth_regular_gas + ) + + intrinsic_state_gas = create_state_gas + auth_state_gas + + return IntrinsicGasCost( + regular=intrinsic_regular_gas, + state=intrinsic_state_gas, + calldata_floor=data_floor_gas_cost, ) diff --git a/src/ethereum/forks/amsterdam/utils/message.py b/src/ethereum/forks/amsterdam/utils/message.py index ee29f60f942..0c442e007d5 100644 --- a/src/ethereum/forks/amsterdam/utils/message.py +++ b/src/ethereum/forks/amsterdam/utils/message.py @@ -78,6 +78,7 @@ def prepare_message( caller=tx_env.origin, target=tx.to, gas=tx_env.gas, + state_gas_reservoir=tx_env.state_gas_reservoir, value=tx.value, data=msg_data, code=code, diff --git a/src/ethereum/forks/amsterdam/vm/__init__.py b/src/ethereum/forks/amsterdam/vm/__init__.py index 1370b0c1516..3c12b81e474 100644 --- a/src/ethereum/forks/amsterdam/vm/__init__.py +++ b/src/ethereum/forks/amsterdam/vm/__init__.py @@ -71,6 +71,10 @@ class BlockOutput: block_gas_used : `ethereum.base_types.Uint` Gas used for executing all transactions. + block_state_gas_used : `ethereum.base_types.Uint` + State gas used for executing all transactions. + cumulative_gas_used : `ethereum.base_types.Uint` + Cumulative gas paid by users (post-refund, post-floor). transactions_trie : `ethereum.fork_types.Root` Trie of all the transactions in the block. receipts_trie : `ethereum.fork_types.Root` @@ -91,6 +95,7 @@ class BlockOutput: """ block_gas_used: Uint = Uint(0) + block_state_gas_used: Uint = Uint(0) cumulative_gas_used: Uint = Uint(0) transactions_trie: Trie[Bytes, Optional[Bytes | LegacyTransaction]] = ( field(default_factory=lambda: Trie(secured=False, default=None)) @@ -118,6 +123,7 @@ class TransactionEnvironment: origin: Address gas_price: Uint gas: Uint + state_gas_reservoir: Uint access_list_addresses: Set[Address] access_list_storage_keys: Set[Tuple[Address, Bytes32]] state: TransactionState @@ -125,6 +131,8 @@ class TransactionEnvironment: authorizations: Tuple[Authorization, ...] index_in_block: Optional[Uint] tx_hash: Optional[Hash32] + intrinsic_regular_gas: Uint + intrinsic_state_gas: Uint @final @@ -140,6 +148,7 @@ class Message: target: Bytes0 | Address current_target: Address gas: Uint + state_gas_reservoir: Uint value: U256 data: Bytes code_address: Optional[Address] @@ -163,6 +172,7 @@ class Evm: memory: bytearray code: Bytes gas_left: Uint + state_gas_left: Uint valid_jump_destinations: Set[Uint] logs: Tuple[Log, ...] refund_counter: int @@ -174,6 +184,31 @@ class Evm: error: Optional[EthereumException] accessed_addresses: Set[Address] accessed_storage_keys: Set[Tuple[Address, Bytes32]] + regular_gas_used: Uint = Uint(0) + state_gas_used: int = 0 + """ + State gas that has been consumed by this execution frame and its + children. + + `state_gas_used` may go negative when the refund matches an + ancestor's charge (e.g. an `SSTORE` clearing a slot a parent set). + """ + + +def credit_state_gas_refund(evm: Evm, amount: Uint) -> None: + """ + Credit an inline state gas refund to the local frame's reservoir. + + Parameters + ---------- + evm : + The frame crediting the refund. + amount : + The refund amount to credit. + + """ + evm.state_gas_left += amount + evm.state_gas_used -= int(amount) def incorporate_child_on_success(evm: Evm, child_evm: Evm) -> None: @@ -189,17 +224,30 @@ def incorporate_child_on_success(evm: Evm, child_evm: Evm) -> None: """ evm.gas_left += child_evm.gas_left + evm.state_gas_left += child_evm.state_gas_left evm.logs += child_evm.logs evm.refund_counter += child_evm.refund_counter evm.accounts_to_delete.update(child_evm.accounts_to_delete) evm.accessed_addresses.update(child_evm.accessed_addresses) evm.accessed_storage_keys.update(child_evm.accessed_storage_keys) + evm.regular_gas_used += child_evm.regular_gas_used + evm.state_gas_used += child_evm.state_gas_used -def incorporate_child_on_error(evm: Evm, child_evm: Evm) -> None: +def incorporate_child_on_error( + evm: Evm, + child_evm: Evm, +) -> None: """ Incorporate the state of an unsuccessful `child_evm` into the parent `evm`. + State is rolled back, restoring all state gas to the parent's + reservoir via the `state_gas_left + state_gas_used` invariant. The + child's `state_gas_used` is not inherited (only the success path + propagates it), satisfying the EIP-8037 revert rule that + `execution_state_gas_used` decreases by the child's charged state + gas. Inline refunds roll back with their matching charges. + Parameters ---------- evm : @@ -209,6 +257,12 @@ def incorporate_child_on_error(evm: Evm, child_evm: Evm) -> None: """ evm.gas_left += child_evm.gas_left + evm.state_gas_left = Uint( + int(evm.state_gas_left) + + child_evm.state_gas_used + + int(child_evm.state_gas_left) + ) + evm.regular_gas_used += child_evm.regular_gas_used def emit_transfer_log( diff --git a/src/ethereum/forks/amsterdam/vm/eoa_delegation.py b/src/ethereum/forks/amsterdam/vm/eoa_delegation.py index 6262f42a0bc..8f2a3e81609 100644 --- a/src/ethereum/forks/amsterdam/vm/eoa_delegation.py +++ b/src/ethereum/forks/amsterdam/vm/eoa_delegation.py @@ -10,7 +10,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import keccak256 from ethereum.exceptions import InvalidBlock, InvalidSignatureError -from ethereum.state import Address +from ethereum.state import EMPTY_CODE_HASH, Address from ..fork_types import Authorization from ..state_tracker import ( @@ -21,14 +21,16 @@ set_code, ) from ..utils.hexadecimal import hex_to_address -from ..vm.gas import GasCosts +from ..vm.gas import ( + GasCosts, + StateGasCosts, +) from . import Evm, Message SET_CODE_TX_MAGIC = b"\x05" EOA_DELEGATION_MARKER = b"\xef\x01\x00" EOA_DELEGATION_MARKER_LENGTH = len(EOA_DELEGATION_MARKER) EOA_DELEGATED_CODE_LENGTH = 23 -REFUND_AUTH_PER_EXISTING_ACCOUNT = 12500 NULL_ADDRESS = hex_to_address("0x0000000000000000000000000000000000000000") @@ -155,10 +157,15 @@ def calculate_delegation_cost( return True, delegated_address, delegation_gas_cost -def set_delegation(message: Message) -> U256: +def set_delegation(message: Message) -> Uint: """ Set the delegation code for the authorities in the message. + Refills `StateGasCosts.NEW_ACCOUNT` when the authority's account + leaf already exists, and `StateGasCosts.AUTH_BASE` when its code + slot already holds a delegation indicator. The total is returned + so block accounting can subtract it from `tx_state_gas`. + Parameters ---------- message : @@ -166,12 +173,12 @@ def set_delegation(message: Message) -> U256: Returns ------- - refund_counter: `U256` - Refund from authority which already exists in state. + state_refund : `Uint` + Total state gas refunded across all processed authorizations. """ tx_state = message.tx_env.state - refund_counter = U256(0) + state_refund = Uint(0) for auth in message.tx_env.authorizations: if auth.chain_id not in (message.block_env.chain_id, U256(0)): continue @@ -197,10 +204,20 @@ def set_delegation(message: Message) -> U256: continue if account_exists(tx_state, authority): - refund_counter += U256( - GasCosts.AUTH_PER_EMPTY_ACCOUNT - - REFUND_AUTH_PER_EXISTING_ACCOUNT - ) + refund = StateGasCosts.NEW_ACCOUNT + message.state_gas_reservoir += refund + state_refund += refund + + # No new delegation indicator bytes are written: either the + # authority already has one (overwrite in place / clear) or + # this auth clears against an authority with no prior code. + if ( + authority_account.code_hash != EMPTY_CODE_HASH + or auth.address == NULL_ADDRESS + ): + refund = StateGasCosts.AUTH_BASE + message.state_gas_reservoir += refund + state_refund += refund if auth.address == NULL_ADDRESS: code_to_set = b"" @@ -218,4 +235,4 @@ def set_delegation(message: Message) -> U256: get_account(tx_state, message.code_address).code_hash, ) - return refund_counter + return state_refund diff --git a/src/ethereum/forks/amsterdam/vm/gas.py b/src/ethereum/forks/amsterdam/vm/gas.py index 8b0dbae14fc..7ba692a9f51 100644 --- a/src/ethereum/forks/amsterdam/vm/gas.py +++ b/src/ethereum/forks/amsterdam/vm/gas.py @@ -17,7 +17,7 @@ from ethereum_types.numeric import U64, U256, Uint, ulen from ethereum.forks.bpo5.blocks import Header as PreviousHeader -from ethereum.trace import GasAndRefund, evm_trace +from ethereum.trace import GasAndRefund, StateGasAndRefund, evm_trace from ethereum.utils.numeric import ceil32, taylor_exponential from ..blocks import Header @@ -26,6 +26,29 @@ from .exceptions import OutOfGasError +# These may be patched at runtime by a future gas repricing utility to +# fast-iterate on state-byte costs. +class StateGasCosts: + """ + EIP-8037 state-gas constants. + + Kept separate from `GasCosts` because these carry a different unit: + state-byte counts that convert into gas via `COST_PER_STATE_BYTE`. + """ + + COST_PER_STATE_BYTE: Final[Uint] = Uint(1530) + STATE_BYTES_PER_NEW_ACCOUNT: Final[Uint] = Uint(120) + STATE_BYTES_PER_STORAGE_SET: Final[Uint] = Uint(64) + STATE_BYTES_PER_AUTH_BASE: Final[Uint] = Uint(23) + STORAGE_SET: Final[Uint] = ( + STATE_BYTES_PER_STORAGE_SET * COST_PER_STATE_BYTE + ) + NEW_ACCOUNT: Final[Uint] = ( + STATE_BYTES_PER_NEW_ACCOUNT * COST_PER_STATE_BYTE + ) + AUTH_BASE: Final[Uint] = STATE_BYTES_PER_AUTH_BASE * COST_PER_STATE_BYTE + + # These values may be patched at runtime by a future gas repricing utility class GasCosts: """ @@ -56,9 +79,11 @@ class GasCosts: # Contract Creation CODE_DEPOSIT_PER_BYTE: Final[Uint] = Uint(200) CODE_INIT_PER_WORD: Final[Uint] = Uint(2) + REGULAR_GAS_CREATE: Final[Uint] = Uint(9000) # Authorization AUTH_PER_EMPTY_ACCOUNT: Final[int] = 25000 + PER_AUTH_BASE_COST: Final[Uint] = Uint(7500) # Utility ZERO: Final[Uint] = Uint(0) @@ -185,7 +210,6 @@ class GasCosts: OPCODE_MSTORE_BASE: Final[Uint] = VERY_LOW OPCODE_MSTORE8_BASE: Final[Uint] = VERY_LOW OPCODE_COPY_PER_WORD: Final[Uint] = Uint(3) - OPCODE_CREATE_BASE: Final[Uint] = Uint(32000) OPCODE_EXP_BASE: Final[Uint] = Uint(10) OPCODE_EXP_PER_BYTE: Final[Uint] = Uint(50) OPCODE_KECCAK256_BASE: Final[Uint] = Uint(30) @@ -251,22 +275,50 @@ def check_gas(evm: Evm, amount: Uint) -> None: def charge_gas(evm: Evm, amount: Uint) -> None: """ - Subtracts `amount` from `evm.gas_left`. + Subtracts `amount` from `evm.gas_left` (regular gas) and records usage. Parameters ---------- evm : The current EVM. amount : - The amount of gas the current operation requires. + The amount of regular gas the current operation requires. """ evm_trace(evm, GasAndRefund(int(amount))) if evm.gas_left < amount: raise OutOfGasError + evm.gas_left -= amount + + evm.regular_gas_used += amount + + +def charge_state_gas(evm: Evm, amount: Uint) -> None: + """ + Subtracts `amount` from the state gas reservoir, then from + `evm.gas_left` when the reservoir is empty. Records state gas usage. + + Parameters + ---------- + evm : + The current EVM. + amount : + The amount of state gas the current operation requires. + + """ + evm_trace(evm, StateGasAndRefund(int(amount))) + + if evm.state_gas_left >= amount: + evm.state_gas_left -= amount + elif evm.state_gas_left + evm.gas_left >= amount: + remainder = amount - evm.state_gas_left + evm.state_gas_left = Uint(0) + evm.gas_left -= remainder else: - evm.gas_left -= amount + raise OutOfGasError + + evm.state_gas_used += int(amount) def calculate_memory_gas_cost(size_in_bytes: Uint) -> Uint: diff --git a/src/ethereum/forks/amsterdam/vm/instructions/storage.py b/src/ethereum/forks/amsterdam/vm/instructions/storage.py index 56e1ed28d06..aa8d869a456 100644 --- a/src/ethereum/forks/amsterdam/vm/instructions/storage.py +++ b/src/ethereum/forks/amsterdam/vm/instructions/storage.py @@ -20,11 +20,13 @@ set_storage, set_transient_storage, ) -from .. import Evm +from .. import Evm, credit_state_gas_refund from ..exceptions import WriteInStaticContext from ..gas import ( GasCosts, + StateGasCosts, charge_gas, + charge_state_gas, check_gas, ) from ..stack import pop, push @@ -88,18 +90,16 @@ def sstore(evm: Evm) -> None: current_value = get_storage(tx_state, evm.message.current_target, key) gas_cost = Uint(0) + state_gas = Uint(0) if (evm.message.current_target, key) not in evm.accessed_storage_keys: evm.accessed_storage_keys.add((evm.message.current_target, key)) gas_cost += GasCosts.COLD_STORAGE_ACCESS if original_value == current_value and current_value != new_value: - if original_value == 0: - gas_cost += GasCosts.STORAGE_SET - else: - gas_cost += ( - GasCosts.COLD_STORAGE_WRITE - GasCosts.COLD_STORAGE_ACCESS - ) + # charge regular cost for the operation, even when we + # already charge state gas for state creation + gas_cost += GasCosts.COLD_STORAGE_WRITE - GasCosts.COLD_STORAGE_ACCESS else: gas_cost += GasCosts.WARM_ACCESS @@ -115,20 +115,26 @@ def sstore(evm: Evm) -> None: if original_value == new_value: # Storage slot being restored to its original value - if original_value == 0: - # Slot was originally empty and was SET earlier - evm.refund_counter += int( - GasCosts.STORAGE_SET - GasCosts.WARM_ACCESS - ) - else: - # Slot was originally non-empty and was UPDATED earlier - evm.refund_counter += int( - GasCosts.COLD_STORAGE_WRITE - - GasCosts.COLD_STORAGE_ACCESS - - GasCosts.WARM_ACCESS - ) + evm.refund_counter += int( + GasCosts.COLD_STORAGE_WRITE + - GasCosts.COLD_STORAGE_ACCESS + - GasCosts.WARM_ACCESS + ) + + if original_value == current_value and current_value != new_value: + if original_value == 0: + state_gas = StateGasCosts.STORAGE_SET + + if current_value != new_value and original_value == new_value: + if original_value == 0: + # Slot set then cleared: refund the state gas charge. + credit_state_gas_refund(evm, StateGasCosts.STORAGE_SET) + # Charge regular gas before state gas so that a regular-gas OOG + # does not consume state gas that would inflate the parent's + # reservoir on frame failure. charge_gas(evm, gas_cost) + charge_state_gas(evm, state_gas) set_storage(tx_state, evm.message.current_target, key, new_value) # PROGRAM COUNTER diff --git a/src/ethereum/forks/amsterdam/vm/instructions/system.py b/src/ethereum/forks/amsterdam/vm/instructions/system.py index 5e526279471..a9b450e398d 100644 --- a/src/ethereum/forks/amsterdam/vm/instructions/system.py +++ b/src/ethereum/forks/amsterdam/vm/instructions/system.py @@ -11,6 +11,9 @@ Implementations of the EVM system related instructions. """ +from dataclasses import dataclass +from typing import final + from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.numeric import U256, Uint @@ -39,6 +42,7 @@ CALL_SUCCESS, Evm, Message, + credit_state_gas_refund, emit_burn_log, emit_transfer_log, incorporate_child_on_error, @@ -47,9 +51,11 @@ from ..exceptions import OutOfGasError, Revert, WriteInStaticContext from ..gas import ( GasCosts, + StateGasCosts, calculate_gas_extend_memory, calculate_message_call_gas, charge_gas, + charge_state_gas, check_gas, init_code_cost, max_message_call_gas, @@ -76,14 +82,14 @@ def generic_create( process_create_message, ) - # Check static context first - if evm.message.is_static: - raise WriteInStaticContext - # Check max init code size early before memory read if memory_size > U256(MAX_INIT_CODE_SIZE): raise OutOfGasError + # Charge state gas for account creation (pay-before-execute). + # Refunded to the reservoir on any failure path below. + charge_state_gas(evm, StateGasCosts.NEW_ACCOUNT) + tx_state = evm.message.tx_env.state call_data = memory_read_bytes( @@ -92,6 +98,15 @@ def generic_create( create_message_gas = max_message_call_gas(Uint(evm.gas_left)) evm.gas_left -= create_message_gas + + if evm.message.is_static: + raise WriteInStaticContext + + # Move full reservoir to child (no 63/64 rule for state gas). Parent's + # `state_gas_left` is zeroed and restored when the child returns. + create_message_state_gas_reservoir = evm.state_gas_left + evm.state_gas_left = Uint(0) + evm.return_data = b"" sender_address = evm.message.current_target @@ -103,6 +118,8 @@ def generic_create( or evm.message.depth + Uint(1) > STACK_DEPTH_LIMIT ): evm.gas_left += create_message_gas + evm.state_gas_left += create_message_state_gas_reservoir + credit_state_gas_refund(evm, StateGasCosts.NEW_ACCOUNT) push(evm.stack, U256(0)) return @@ -112,6 +129,10 @@ def generic_create( tx_state, contract_address ) or account_has_storage(tx_state, contract_address): increment_nonce(tx_state, evm.message.current_target) + evm.regular_gas_used += create_message_gas + evm.state_gas_left += create_message_state_gas_reservoir + # Address collision — no account created, refund state gas. + credit_state_gas_refund(evm, StateGasCosts.NEW_ACCOUNT) push(evm.stack, U256(0)) return @@ -123,6 +144,7 @@ def generic_create( caller=evm.message.current_target, target=Bytes0(), gas=create_message_gas, + state_gas_reservoir=create_message_state_gas_reservoir, value=endowment, data=b"", code=call_data, @@ -140,6 +162,8 @@ def generic_create( if child_evm.error: incorporate_child_on_error(evm, child_evm) + # No account created, refund parent's CREATE state gas. + credit_state_gas_refund(evm, StateGasCosts.NEW_ACCOUNT) evm.return_data = child_evm.output push(evm.stack, U256(0)) else: @@ -168,9 +192,9 @@ def create(evm: Evm) -> None: evm.memory, [(memory_start_position, memory_size)] ) init_code_gas = init_code_cost(Uint(memory_size)) - charge_gas( - evm, GasCosts.OPCODE_CREATE_BASE + extend_memory.cost + init_code_gas + evm, + GasCosts.REGULAR_GAS_CREATE + extend_memory.cost + init_code_gas, ) # OPERATION @@ -221,7 +245,7 @@ def create2(evm: Evm) -> None: init_code_gas = init_code_cost(Uint(memory_size)) charge_gas( evm, - GasCosts.OPCODE_CREATE_BASE + GasCosts.REGULAR_GAS_CREATE + GasCosts.OPCODE_KECCAK256_PER_WORD * call_data_words + extend_memory.cost + init_code_gas, @@ -280,21 +304,30 @@ def return_(evm: Evm) -> None: pass -def generic_call( - evm: Evm, - gas: Uint, - value: U256, - caller: Address, - to: Address, - code_address: Address, - should_transfer_value: bool, - is_staticcall: bool, - memory_input_start_position: U256, - memory_input_size: U256, - memory_output_start_position: U256, - memory_output_size: U256, - disable_precompiles: bool, -) -> None: +@final +@dataclass +class GenericCall: + """ + Parameters for the core logic of the `CALL*` family of opcodes. + """ + + gas: Uint + state_gas_reservoir: Uint + value: U256 + caller: Address + to: Address + code_address: Address + should_transfer_value: bool + is_staticcall: bool + memory_input_start_position: U256 + memory_input_size: U256 + memory_output_start_position: U256 + memory_output_size: U256 + code: Bytes + disable_precompiles: bool + + +def generic_call(evm: Evm, params: GenericCall) -> None: """ Perform the core logic of the `CALL*` family of opcodes. """ @@ -303,35 +336,35 @@ def generic_call( evm.return_data = b"" if evm.message.depth + Uint(1) > STACK_DEPTH_LIMIT: - evm.gas_left += gas + evm.gas_left += params.gas + evm.state_gas_left += params.state_gas_reservoir push(evm.stack, U256(0)) return - tx_state = evm.message.tx_env.state - code_hash = get_account(tx_state, code_address).code_hash - code = get_code(tx_state, code_hash) - call_data = memory_read_bytes( - evm.memory, memory_input_start_position, memory_input_size + evm.memory, + params.memory_input_start_position, + params.memory_input_size, ) child_message = Message( block_env=evm.message.block_env, tx_env=evm.message.tx_env, - caller=caller, - target=to, - gas=gas, - value=value, + caller=params.caller, + target=params.to, + gas=params.gas, + state_gas_reservoir=params.state_gas_reservoir, + value=params.value, data=call_data, - code=code, - current_target=to, + code=params.code, + current_target=params.to, depth=evm.message.depth + Uint(1), - code_address=code_address, - should_transfer_value=should_transfer_value, - is_static=True if is_staticcall else evm.message.is_static, + code_address=params.code_address, + should_transfer_value=params.should_transfer_value, + is_static=params.is_staticcall or evm.message.is_static, accessed_addresses=evm.accessed_addresses.copy(), accessed_storage_keys=evm.accessed_storage_keys.copy(), - disable_precompiles=disable_precompiles, + disable_precompiles=params.disable_precompiles, parent_evm=evm, ) @@ -346,10 +379,12 @@ def generic_call( evm.return_data = child_evm.output push(evm.stack, CALL_SUCCESS) - actual_output_size = min(memory_output_size, U256(len(child_evm.output))) + actual_output_size = min( + params.memory_output_size, U256(len(child_evm.output)) + ) memory_write( evm.memory, - memory_output_start_position, + params.memory_output_start_position, child_evm.output[:actual_output_size], ) @@ -404,11 +439,7 @@ def call(evm: Evm) -> None: if is_cold_access: evm.accessed_addresses.add(to) - create_gas_cost = GasCosts.NEW_ACCOUNT - if value == 0 or is_account_alive(tx_state, to): - create_gas_cost = Uint(0) - - extra_gas = access_gas_cost + transfer_gas_cost + create_gas_cost + extra_gas = access_gas_cost + transfer_gas_cost ( is_delegated, code_address, @@ -422,37 +453,55 @@ def call(evm: Evm) -> None: if code_address not in evm.accessed_addresses: evm.accessed_addresses.add(code_address) + code_hash = get_account(tx_state, code_address).code_hash + code = get_code(tx_state, code_hash) + + charge_gas(evm, extra_gas + extend_memory.cost) + if value != 0 and not is_account_alive(tx_state, to): + charge_state_gas(evm, StateGasCosts.NEW_ACCOUNT) + message_call_gas = calculate_message_call_gas( value, gas, Uint(evm.gas_left), - extend_memory.cost, - extra_gas, + memory_cost=Uint(0), + extra_gas=Uint(0), ) - charge_gas(evm, message_call_gas.cost + extend_memory.cost) + charge_gas(evm, message_call_gas.cost) + evm.regular_gas_used -= message_call_gas.sub_call # OPERATION evm.memory += b"\x00" * extend_memory.expand_by + + # Pass full reservoir to child (no 63/64 rule for state gas) + call_state_gas_reservoir = evm.state_gas_left + evm.state_gas_left = Uint(0) + sender_balance = get_account(tx_state, evm.message.current_target).balance if sender_balance < value: push(evm.stack, U256(0)) evm.return_data = b"" evm.gas_left += message_call_gas.sub_call + evm.state_gas_left += call_state_gas_reservoir else: generic_call( evm, - message_call_gas.sub_call, - value, - evm.message.current_target, - to, - code_address, - True, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, - is_delegated, + GenericCall( + gas=message_call_gas.sub_call, + state_gas_reservoir=call_state_gas_reservoir, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + code=code, + disable_precompiles=is_delegated, + ), ) # PROGRAM COUNTER @@ -522,6 +571,9 @@ def callcode(evm: Evm) -> None: if code_address not in evm.accessed_addresses: evm.accessed_addresses.add(code_address) + code_hash = get_account(tx_state, code_address).code_hash + code = get_code(tx_state, code_hash) + message_call_gas = calculate_message_call_gas( value, gas, @@ -530,30 +582,41 @@ def callcode(evm: Evm) -> None: extra_gas, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) + evm.regular_gas_used -= message_call_gas.sub_call # OPERATION evm.memory += b"\x00" * extend_memory.expand_by + + # Pass full reservoir to child (no 63/64 rule for state gas) + call_state_gas_reservoir = evm.state_gas_left + evm.state_gas_left = Uint(0) + sender_balance = get_account(tx_state, evm.message.current_target).balance if sender_balance < value: push(evm.stack, U256(0)) evm.return_data = b"" evm.gas_left += message_call_gas.sub_call + evm.state_gas_left += call_state_gas_reservoir else: generic_call( evm, - message_call_gas.sub_call, - value, - evm.message.current_target, - to, - code_address, - True, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, - is_delegated, + GenericCall( + gas=message_call_gas.sub_call, + state_gas_reservoir=call_state_gas_reservoir, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + code=code, + disable_precompiles=is_delegated, + ), ) # PROGRAM COUNTER @@ -591,13 +654,18 @@ def selfdestruct(evm: Evm) -> None: if is_cold_access: evm.accessed_addresses.add(beneficiary) + state_gas = Uint(0) if ( not is_account_alive(tx_state, beneficiary) and get_account(tx_state, evm.message.current_target).balance != 0 ): - gas_cost += GasCosts.OPCODE_SELFDESTRUCT_NEW_ACCOUNT + state_gas = StateGasCosts.NEW_ACCOUNT + # Charge regular gas before state gas so that a regular-gas OOG + # does not consume state gas that would inflate the parent's + # reservoir on frame failure. charge_gas(evm, gas_cost) + charge_state_gas(evm, state_gas) originator = evm.message.current_target originator_balance = get_account(tx_state, originator).balance @@ -678,6 +746,10 @@ def delegatecall(evm: Evm) -> None: if code_address not in evm.accessed_addresses: evm.accessed_addresses.add(code_address) + tx_state = evm.message.tx_env.state + code_hash = get_account(tx_state, code_address).code_hash + code = get_code(tx_state, code_hash) + message_call_gas = calculate_message_call_gas( U256(0), gas, @@ -686,23 +758,33 @@ def delegatecall(evm: Evm) -> None: extra_gas, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) + evm.regular_gas_used -= message_call_gas.sub_call # OPERATION evm.memory += b"\x00" * extend_memory.expand_by + + # Pass full reservoir to child (no 63/64 rule for state gas) + call_state_gas_reservoir = evm.state_gas_left + evm.state_gas_left = Uint(0) + generic_call( evm, - message_call_gas.sub_call, - evm.message.value, - evm.message.caller, - evm.message.current_target, - code_address, - False, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, - is_delegated, + GenericCall( + gas=message_call_gas.sub_call, + state_gas_reservoir=call_state_gas_reservoir, + value=evm.message.value, + caller=evm.message.caller, + to=evm.message.current_target, + code_address=code_address, + should_transfer_value=False, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + code=code, + disable_precompiles=is_delegated, + ), ) # PROGRAM COUNTER @@ -763,6 +845,10 @@ def staticcall(evm: Evm) -> None: if code_address not in evm.accessed_addresses: evm.accessed_addresses.add(code_address) + tx_state = evm.message.tx_env.state + code_hash = get_account(tx_state, code_address).code_hash + code = get_code(tx_state, code_hash) + message_call_gas = calculate_message_call_gas( U256(0), gas, @@ -771,23 +857,33 @@ def staticcall(evm: Evm) -> None: extra_gas, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) + evm.regular_gas_used -= message_call_gas.sub_call # OPERATION evm.memory += b"\x00" * extend_memory.expand_by + + # Pass full reservoir to child (no 63/64 rule for state gas) + call_state_gas_reservoir = evm.state_gas_left + evm.state_gas_left = Uint(0) + generic_call( evm, - message_call_gas.sub_call, - U256(0), - evm.message.current_target, - to, - code_address, - True, - True, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, - is_delegated, + GenericCall( + gas=message_call_gas.sub_call, + state_gas_reservoir=call_state_gas_reservoir, + value=U256(0), + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=True, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + code=code, + disable_precompiles=is_delegated, + ), ) # PROGRAM COUNTER diff --git a/src/ethereum/forks/amsterdam/vm/interpreter.py b/src/ethereum/forks/amsterdam/vm/interpreter.py index 78d276673da..b8eb6b968db 100644 --- a/src/ethereum/forks/amsterdam/vm/interpreter.py +++ b/src/ethereum/forks/amsterdam/vm/interpreter.py @@ -29,6 +29,7 @@ TransactionEnd, evm_trace, ) +from ethereum.utils.numeric import ceil32 from ..blocks import Log from ..state_tracker import ( @@ -46,7 +47,12 @@ ) from ..vm import Message from ..vm.eoa_delegation import get_delegated_code_address, set_delegation -from ..vm.gas import GasCosts, charge_gas +from ..vm.gas import ( + GasCosts, + StateGasCosts, + charge_gas, + charge_state_gas, +) from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS from . import Evm, emit_transfer_log from .exceptions import ( @@ -80,6 +86,12 @@ class MessageCallOutput: 4. `accounts_to_delete`: Contracts which have self-destructed. 5. `error`: The error from the execution if any. 6. `return_data`: The output of the execution. + 7. `regular_gas_used`: Regular gas used during execution. + 8. `state_gas_used`: State gas used during execution. + 9. `state_refund`: State gas refunded by `set_delegation` for + authorities that already existed in state. Subtracted from + `tx_state_gas` in block accounting so `block.gas_used` + matches the receipt `cumulative_gas_used`. """ gas_left: Uint @@ -88,6 +100,10 @@ class MessageCallOutput: accounts_to_delete: Set[Address] error: Optional[EthereumException] return_data: Bytes + state_gas_left: Uint + regular_gas_used: Uint + state_gas_used: int + state_refund: Uint def process_message_call(message: Message) -> MessageCallOutput: @@ -108,24 +124,29 @@ def process_message_call(message: Message) -> MessageCallOutput: """ tx_state = message.tx_env.state refund_counter = U256(0) + state_refund = Uint(0) if message.target == Bytes0(b""): is_collision = account_has_code_or_nonce( tx_state, message.current_target ) or account_has_storage(tx_state, message.current_target) if is_collision: return MessageCallOutput( - Uint(0), - U256(0), - tuple(), - set(), - AddressCollision(), - Bytes(b""), + gas_left=Uint(0), + refund_counter=U256(0), + logs=tuple(), + accounts_to_delete=set(), + error=AddressCollision(), + return_data=Bytes(b""), + state_gas_left=message.state_gas_reservoir, + regular_gas_used=message.gas, + state_gas_used=0, + state_refund=Uint(0), ) else: evm = process_create_message(message) else: if message.tx_env.authorizations != (): - refund_counter += set_delegation(message) + state_refund += set_delegation(message) delegated_address = get_delegated_code_address(message.code) if delegated_address is not None: @@ -159,6 +180,10 @@ def process_message_call(message: Message) -> MessageCallOutput: accounts_to_delete=accounts_to_delete, error=evm.error, return_data=evm.output, + state_gas_left=evm.state_gas_left, + regular_gas_used=evm.regular_gas_used, + state_gas_used=evm.state_gas_used, + state_refund=state_refund, ) @@ -201,18 +226,26 @@ def process_create_message(message: Message) -> Evm: evm = process_message(message) if not evm.error: contract_code = evm.output - contract_code_gas = ( - ulen(contract_code) * GasCosts.CODE_DEPOSIT_PER_BYTE - ) try: if len(contract_code) > 0: if contract_code[0] == 0xEF: raise InvalidContractPrefix - charge_gas(evm, contract_code_gas) if len(contract_code) > MAX_CODE_SIZE: raise OutOfGasError + # Hash cost for computing keccak256 of deployed bytecode + code_hash_gas = ( + GasCosts.OPCODE_KECCAK256_PER_WORD + * ceil32(ulen(contract_code)) + // Uint(32) + ) + charge_gas(evm, code_hash_gas) + code_deposit_state_gas = ( + ulen(contract_code) * StateGasCosts.COST_PER_STATE_BYTE + ) + charge_state_gas(evm, code_deposit_state_gas) except ExceptionalHalt as error: restore_tx_state(tx_state, snapshot) + evm.regular_gas_used += evm.gas_left evm.gas_left = Uint(0) evm.output = b"" evm.error = error @@ -250,6 +283,7 @@ def process_message(message: Message) -> Evm: memory=bytearray(), code=code, gas_left=message.gas, + state_gas_left=message.state_gas_reservoir, valid_jump_destinations=valid_jump_destinations, logs=(), refund_counter=0, @@ -299,6 +333,7 @@ def process_message(message: Message) -> Evm: except ExceptionalHalt as error: evm_trace(evm, OpException(error)) + evm.regular_gas_used += evm.gas_left evm.gas_left = Uint(0) evm.output = b"" evm.error = error diff --git a/src/ethereum/forks/arrow_glacier/vm/instructions/system.py b/src/ethereum/forks/arrow_glacier/vm/instructions/system.py index cbc799007a6..b3870ddb774 100644 --- a/src/ethereum/forks/arrow_glacier/vm/instructions/system.py +++ b/src/ethereum/forks/arrow_glacier/vm/instructions/system.py @@ -11,6 +11,9 @@ Implementations of the EVM system related instructions. """ +from dataclasses import dataclass +from typing import final + from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.numeric import U256, Uint @@ -246,20 +249,27 @@ def return_(evm: Evm) -> None: pass -def generic_call( - evm: Evm, - gas: Uint, - value: U256, - caller: Address, - to: Address, - code_address: Address, - should_transfer_value: bool, - is_staticcall: bool, - memory_input_start_position: U256, - memory_input_size: U256, - memory_output_start_position: U256, - memory_output_size: U256, -) -> None: +@final +@dataclass +class GenericCall: + """ + Parameters for the core logic of the `CALL*` family of opcodes. + """ + + gas: Uint + value: U256 + caller: Address + to: Address + code_address: Address + should_transfer_value: bool + is_staticcall: bool + memory_input_start_position: U256 + memory_input_size: U256 + memory_output_start_position: U256 + memory_output_size: U256 + + +def generic_call(evm: Evm, params: GenericCall) -> None: """ Perform the core logic of the `CALL*` family of opcodes. """ @@ -268,29 +278,31 @@ def generic_call( evm.return_data = b"" if evm.message.depth + Uint(1) > STACK_DEPTH_LIMIT: - evm.gas_left += gas + evm.gas_left += params.gas push(evm.stack, U256(0)) return call_data = memory_read_bytes( - evm.memory, memory_input_start_position, memory_input_size + evm.memory, + params.memory_input_start_position, + params.memory_input_size, ) - account = get_account(evm.message.tx_env.state, code_address) + account = get_account(evm.message.tx_env.state, params.code_address) code = get_code(evm.message.tx_env.state, account.code_hash) child_message = Message( block_env=evm.message.block_env, tx_env=evm.message.tx_env, - caller=caller, - target=to, - gas=gas, - value=value, + caller=params.caller, + target=params.to, + gas=params.gas, + value=params.value, data=call_data, code=code, - current_target=to, + current_target=params.to, depth=evm.message.depth + Uint(1), - code_address=code_address, - should_transfer_value=should_transfer_value, - is_static=True if is_staticcall else evm.message.is_static, + code_address=params.code_address, + should_transfer_value=params.should_transfer_value, + is_static=params.is_staticcall or evm.message.is_static, accessed_addresses=evm.accessed_addresses.copy(), accessed_storage_keys=evm.accessed_storage_keys.copy(), parent_evm=evm, @@ -306,10 +318,12 @@ def generic_call( evm.return_data = child_evm.output push(evm.stack, U256(1)) - actual_output_size = min(memory_output_size, U256(len(child_evm.output))) + actual_output_size = min( + params.memory_output_size, U256(len(child_evm.output)) + ) memory_write( evm.memory, - memory_output_start_position, + params.memory_output_start_position, child_evm.output[:actual_output_size], ) @@ -358,8 +372,8 @@ def call(evm: Evm) -> None: value, gas, Uint(evm.gas_left), - extend_memory.cost, - access_gas_cost + create_gas_cost + transfer_gas_cost, + memory_cost=extend_memory.cost, + extra_gas=access_gas_cost + create_gas_cost + transfer_gas_cost, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) if evm.message.is_static and value != U256(0): @@ -375,17 +389,19 @@ def call(evm: Evm) -> None: else: generic_call( evm, - message_call_gas.sub_call, - value, - evm.message.current_target, - to, - code_address, - True, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER @@ -450,17 +466,19 @@ def callcode(evm: Evm) -> None: else: generic_call( evm, - message_call_gas.sub_call, - value, - evm.message.current_target, - to, - code_address, - True, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER @@ -578,17 +596,19 @@ def delegatecall(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by generic_call( evm, - message_call_gas.sub_call, - evm.message.value, - evm.message.caller, - evm.message.current_target, - code_address, - False, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=evm.message.value, + caller=evm.message.caller, + to=evm.message.current_target, + code_address=code_address, + should_transfer_value=False, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER @@ -643,17 +663,19 @@ def staticcall(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by generic_call( evm, - message_call_gas.sub_call, - U256(0), - evm.message.current_target, - to, - code_address, - True, - True, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=U256(0), + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=True, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER diff --git a/src/ethereum/forks/berlin/vm/instructions/system.py b/src/ethereum/forks/berlin/vm/instructions/system.py index e1e409cc8b8..b94c7ebfb4b 100644 --- a/src/ethereum/forks/berlin/vm/instructions/system.py +++ b/src/ethereum/forks/berlin/vm/instructions/system.py @@ -11,6 +11,9 @@ Implementations of the EVM system related instructions. """ +from dataclasses import dataclass +from typing import final + from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.numeric import U256, Uint @@ -246,20 +249,27 @@ def return_(evm: Evm) -> None: pass -def generic_call( - evm: Evm, - gas: Uint, - value: U256, - caller: Address, - to: Address, - code_address: Address, - should_transfer_value: bool, - is_staticcall: bool, - memory_input_start_position: U256, - memory_input_size: U256, - memory_output_start_position: U256, - memory_output_size: U256, -) -> None: +@final +@dataclass +class GenericCall: + """ + Parameters for the core logic of the `CALL*` family of opcodes. + """ + + gas: Uint + value: U256 + caller: Address + to: Address + code_address: Address + should_transfer_value: bool + is_staticcall: bool + memory_input_start_position: U256 + memory_input_size: U256 + memory_output_start_position: U256 + memory_output_size: U256 + + +def generic_call(evm: Evm, params: GenericCall) -> None: """ Perform the core logic of the `CALL*` family of opcodes. """ @@ -268,29 +278,31 @@ def generic_call( evm.return_data = b"" if evm.message.depth + Uint(1) > STACK_DEPTH_LIMIT: - evm.gas_left += gas + evm.gas_left += params.gas push(evm.stack, U256(0)) return call_data = memory_read_bytes( - evm.memory, memory_input_start_position, memory_input_size + evm.memory, + params.memory_input_start_position, + params.memory_input_size, ) - _code_account = get_account(evm.message.tx_env.state, code_address) + _code_account = get_account(evm.message.tx_env.state, params.code_address) code = get_code(evm.message.tx_env.state, _code_account.code_hash) child_message = Message( block_env=evm.message.block_env, tx_env=evm.message.tx_env, - caller=caller, - target=to, - gas=gas, - value=value, + caller=params.caller, + target=params.to, + gas=params.gas, + value=params.value, data=call_data, code=code, - current_target=to, + current_target=params.to, depth=evm.message.depth + Uint(1), - code_address=code_address, - should_transfer_value=should_transfer_value, - is_static=True if is_staticcall else evm.message.is_static, + code_address=params.code_address, + should_transfer_value=params.should_transfer_value, + is_static=params.is_staticcall or evm.message.is_static, accessed_addresses=evm.accessed_addresses.copy(), accessed_storage_keys=evm.accessed_storage_keys.copy(), parent_evm=evm, @@ -306,10 +318,12 @@ def generic_call( evm.return_data = child_evm.output push(evm.stack, U256(1)) - actual_output_size = min(memory_output_size, U256(len(child_evm.output))) + actual_output_size = min( + params.memory_output_size, U256(len(child_evm.output)) + ) memory_write( evm.memory, - memory_output_start_position, + params.memory_output_start_position, child_evm.output[:actual_output_size], ) @@ -358,8 +372,8 @@ def call(evm: Evm) -> None: value, gas, Uint(evm.gas_left), - extend_memory.cost, - access_gas_cost + create_gas_cost + transfer_gas_cost, + memory_cost=extend_memory.cost, + extra_gas=access_gas_cost + create_gas_cost + transfer_gas_cost, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) if evm.message.is_static and value != U256(0): @@ -375,17 +389,19 @@ def call(evm: Evm) -> None: else: generic_call( evm, - message_call_gas.sub_call, - value, - evm.message.current_target, - to, - code_address, - True, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER @@ -450,17 +466,19 @@ def callcode(evm: Evm) -> None: else: generic_call( evm, - message_call_gas.sub_call, - value, - evm.message.current_target, - to, - code_address, - True, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER @@ -585,17 +603,19 @@ def delegatecall(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by generic_call( evm, - message_call_gas.sub_call, - evm.message.value, - evm.message.caller, - evm.message.current_target, - code_address, - False, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=evm.message.value, + caller=evm.message.caller, + to=evm.message.current_target, + code_address=code_address, + should_transfer_value=False, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER @@ -650,17 +670,19 @@ def staticcall(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by generic_call( evm, - message_call_gas.sub_call, - U256(0), - evm.message.current_target, - to, - code_address, - True, - True, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=U256(0), + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=True, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER diff --git a/src/ethereum/forks/bpo1/fork.py b/src/ethereum/forks/bpo1/fork.py index fcdff89d045..69b586fde1d 100644 --- a/src/ethereum/forks/bpo1/fork.py +++ b/src/ethereum/forks/bpo1/fork.py @@ -872,7 +872,7 @@ def process_transaction( encode_transaction(tx), ) - intrinsic_gas, calldata_floor_gas_cost = validate_transaction(tx) + intrinsic = validate_transaction(tx) ( sender, @@ -895,7 +895,7 @@ def process_transaction( effective_gas_fee = tx.gas * effective_gas_price - gas = tx.gas - intrinsic_gas + gas = tx.gas - intrinsic.regular increment_nonce(tx_state, sender) sender_balance_after_gas_fee = ( @@ -944,7 +944,7 @@ def process_transaction( # Transactions with less execution_gas_used than the floor pay at the # floor cost. tx_gas_used_after_refund = max( - tx_gas_used_after_refund, calldata_floor_gas_cost + tx_gas_used_after_refund, intrinsic.calldata_floor ) tx_gas_left = tx.gas - tx_gas_used_after_refund diff --git a/src/ethereum/forks/bpo1/transactions.py b/src/ethereum/forks/bpo1/transactions.py index 18953538456..3d8cdf3754d 100644 --- a/src/ethereum/forks/bpo1/transactions.py +++ b/src/ethereum/forks/bpo1/transactions.py @@ -28,6 +28,19 @@ ) from .fork_types import Authorization, VersionedHash + +@final +@dataclass +class IntrinsicGasCost: + """Intrinsic gas costs for a transaction, split by gas type.""" + + regular: Uint + """Regular execution gas (calldata, base cost, access list, etc.).""" + + calldata_floor: Uint + """Minimum gas cost based on calldata size per [EIP-7623].""" + + TX_MAX_GAS_LIMIT = Uint(16_777_216) @@ -512,7 +525,7 @@ def decode_transaction(tx: LegacyTransaction | Bytes) -> Transaction: return tx -def validate_transaction(tx: Transaction) -> Tuple[Uint, Uint]: +def validate_transaction(tx: Transaction) -> IntrinsicGasCost: """ Verifies a transaction. @@ -543,8 +556,8 @@ def validate_transaction(tx: Transaction) -> Tuple[Uint, Uint]: """ from .vm.interpreter import MAX_INIT_CODE_SIZE - intrinsic_gas, calldata_floor_gas_cost = calculate_intrinsic_cost(tx) - if max(intrinsic_gas, calldata_floor_gas_cost) > tx.gas: + intrinsic = calculate_intrinsic_cost(tx) + if max(intrinsic.regular, intrinsic.calldata_floor) > tx.gas: raise InsufficientTransactionGasError("Insufficient gas") if U256(tx.nonce) >= U256(U64.MAX_VALUE): raise NonceOverflowError("Nonce too high") @@ -553,10 +566,10 @@ def validate_transaction(tx: Transaction) -> Tuple[Uint, Uint]: if tx.gas > TX_MAX_GAS_LIMIT: raise TransactionGasLimitExceededError("Gas limit too high") - return intrinsic_gas, calldata_floor_gas_cost + return intrinsic -def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: +def calculate_intrinsic_cost(tx: Transaction) -> IntrinsicGasCost: """ Calculates the gas that is charged before execution is started. @@ -613,15 +626,15 @@ def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: GasCosts.AUTH_PER_EMPTY_ACCOUNT * len(tx.authorizations) ) - return ( - Uint( + return IntrinsicGasCost( + regular=Uint( GasCosts.TX_BASE + data_cost + create_cost + access_list_cost + auth_cost ), - calldata_floor_gas_cost, + calldata_floor=calldata_floor_gas_cost, ) diff --git a/src/ethereum/forks/bpo1/vm/instructions/system.py b/src/ethereum/forks/bpo1/vm/instructions/system.py index 092c6fb68c2..f8f8f29e100 100644 --- a/src/ethereum/forks/bpo1/vm/instructions/system.py +++ b/src/ethereum/forks/bpo1/vm/instructions/system.py @@ -11,6 +11,9 @@ Implementations of the EVM system related instructions. """ +from dataclasses import dataclass +from typing import final + from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.numeric import U256, Uint @@ -268,22 +271,29 @@ def return_(evm: Evm) -> None: pass -def generic_call( - evm: Evm, - gas: Uint, - value: U256, - caller: Address, - to: Address, - code_address: Address, - should_transfer_value: bool, - is_staticcall: bool, - memory_input_start_position: U256, - memory_input_size: U256, - memory_output_start_position: U256, - memory_output_size: U256, - code: Bytes, - disable_precompiles: bool, -) -> None: +@final +@dataclass +class GenericCall: + """ + Parameters for the core logic of the `CALL*` family of opcodes. + """ + + gas: Uint + value: U256 + caller: Address + to: Address + code_address: Address + should_transfer_value: bool + is_staticcall: bool + memory_input_start_position: U256 + memory_input_size: U256 + memory_output_start_position: U256 + memory_output_size: U256 + code: Bytes + disable_precompiles: bool + + +def generic_call(evm: Evm, params: GenericCall) -> None: """ Perform the core logic of the `CALL*` family of opcodes. """ @@ -292,31 +302,33 @@ def generic_call( evm.return_data = b"" if evm.message.depth + Uint(1) > STACK_DEPTH_LIMIT: - evm.gas_left += gas + evm.gas_left += params.gas push(evm.stack, U256(0)) return call_data = memory_read_bytes( - evm.memory, memory_input_start_position, memory_input_size + evm.memory, + params.memory_input_start_position, + params.memory_input_size, ) child_message = Message( block_env=evm.message.block_env, tx_env=evm.message.tx_env, - caller=caller, - target=to, - gas=gas, - value=value, + caller=params.caller, + target=params.to, + gas=params.gas, + value=params.value, data=call_data, - code=code, - current_target=to, + code=params.code, + current_target=params.to, depth=evm.message.depth + Uint(1), - code_address=code_address, - should_transfer_value=should_transfer_value, - is_static=True if is_staticcall else evm.message.is_static, + code_address=params.code_address, + should_transfer_value=params.should_transfer_value, + is_static=params.is_staticcall or evm.message.is_static, accessed_addresses=evm.accessed_addresses.copy(), accessed_storage_keys=evm.accessed_storage_keys.copy(), - disable_precompiles=disable_precompiles, + disable_precompiles=params.disable_precompiles, parent_evm=evm, ) child_evm = process_message(child_message) @@ -330,10 +342,12 @@ def generic_call( evm.return_data = child_evm.output push(evm.stack, U256(1)) - actual_output_size = min(memory_output_size, U256(len(child_evm.output))) + actual_output_size = min( + params.memory_output_size, U256(len(child_evm.output)) + ) memory_write( evm.memory, - memory_output_start_position, + params.memory_output_start_position, child_evm.output[:actual_output_size], ) @@ -389,8 +403,8 @@ def call(evm: Evm) -> None: value, gas, Uint(evm.gas_left), - extend_memory.cost, - access_gas_cost + create_gas_cost + transfer_gas_cost, + memory_cost=extend_memory.cost, + extra_gas=access_gas_cost + create_gas_cost + transfer_gas_cost, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) if evm.message.is_static and value != U256(0): @@ -406,19 +420,21 @@ def call(evm: Evm) -> None: else: generic_call( evm, - message_call_gas.sub_call, - value, - evm.message.current_target, - to, - code_address, - True, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, - code, - disable_precompiles, + GenericCall( + gas=message_call_gas.sub_call, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + code=code, + disable_precompiles=disable_precompiles, + ), ) # PROGRAM COUNTER @@ -491,19 +507,21 @@ def callcode(evm: Evm) -> None: else: generic_call( evm, - message_call_gas.sub_call, - value, - evm.message.current_target, - to, - code_address, - True, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, - code, - disable_precompiles, + GenericCall( + gas=message_call_gas.sub_call, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + code=code, + disable_precompiles=disable_precompiles, + ), ) # PROGRAM COUNTER @@ -619,19 +637,21 @@ def delegatecall(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by generic_call( evm, - message_call_gas.sub_call, - evm.message.value, - evm.message.caller, - evm.message.current_target, - code_address, - False, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, - code, - disable_precompiles, + GenericCall( + gas=message_call_gas.sub_call, + value=evm.message.value, + caller=evm.message.caller, + to=evm.message.current_target, + code_address=code_address, + should_transfer_value=False, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + code=code, + disable_precompiles=disable_precompiles, + ), ) # PROGRAM COUNTER @@ -693,19 +713,21 @@ def staticcall(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by generic_call( evm, - message_call_gas.sub_call, - U256(0), - evm.message.current_target, - to, - code_address, - True, - True, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, - code, - disable_precompiles, + GenericCall( + gas=message_call_gas.sub_call, + value=U256(0), + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=True, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + code=code, + disable_precompiles=disable_precompiles, + ), ) # PROGRAM COUNTER diff --git a/src/ethereum/forks/bpo2/fork.py b/src/ethereum/forks/bpo2/fork.py index fcdff89d045..69b586fde1d 100644 --- a/src/ethereum/forks/bpo2/fork.py +++ b/src/ethereum/forks/bpo2/fork.py @@ -872,7 +872,7 @@ def process_transaction( encode_transaction(tx), ) - intrinsic_gas, calldata_floor_gas_cost = validate_transaction(tx) + intrinsic = validate_transaction(tx) ( sender, @@ -895,7 +895,7 @@ def process_transaction( effective_gas_fee = tx.gas * effective_gas_price - gas = tx.gas - intrinsic_gas + gas = tx.gas - intrinsic.regular increment_nonce(tx_state, sender) sender_balance_after_gas_fee = ( @@ -944,7 +944,7 @@ def process_transaction( # Transactions with less execution_gas_used than the floor pay at the # floor cost. tx_gas_used_after_refund = max( - tx_gas_used_after_refund, calldata_floor_gas_cost + tx_gas_used_after_refund, intrinsic.calldata_floor ) tx_gas_left = tx.gas - tx_gas_used_after_refund diff --git a/src/ethereum/forks/bpo2/transactions.py b/src/ethereum/forks/bpo2/transactions.py index 18953538456..3d8cdf3754d 100644 --- a/src/ethereum/forks/bpo2/transactions.py +++ b/src/ethereum/forks/bpo2/transactions.py @@ -28,6 +28,19 @@ ) from .fork_types import Authorization, VersionedHash + +@final +@dataclass +class IntrinsicGasCost: + """Intrinsic gas costs for a transaction, split by gas type.""" + + regular: Uint + """Regular execution gas (calldata, base cost, access list, etc.).""" + + calldata_floor: Uint + """Minimum gas cost based on calldata size per [EIP-7623].""" + + TX_MAX_GAS_LIMIT = Uint(16_777_216) @@ -512,7 +525,7 @@ def decode_transaction(tx: LegacyTransaction | Bytes) -> Transaction: return tx -def validate_transaction(tx: Transaction) -> Tuple[Uint, Uint]: +def validate_transaction(tx: Transaction) -> IntrinsicGasCost: """ Verifies a transaction. @@ -543,8 +556,8 @@ def validate_transaction(tx: Transaction) -> Tuple[Uint, Uint]: """ from .vm.interpreter import MAX_INIT_CODE_SIZE - intrinsic_gas, calldata_floor_gas_cost = calculate_intrinsic_cost(tx) - if max(intrinsic_gas, calldata_floor_gas_cost) > tx.gas: + intrinsic = calculate_intrinsic_cost(tx) + if max(intrinsic.regular, intrinsic.calldata_floor) > tx.gas: raise InsufficientTransactionGasError("Insufficient gas") if U256(tx.nonce) >= U256(U64.MAX_VALUE): raise NonceOverflowError("Nonce too high") @@ -553,10 +566,10 @@ def validate_transaction(tx: Transaction) -> Tuple[Uint, Uint]: if tx.gas > TX_MAX_GAS_LIMIT: raise TransactionGasLimitExceededError("Gas limit too high") - return intrinsic_gas, calldata_floor_gas_cost + return intrinsic -def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: +def calculate_intrinsic_cost(tx: Transaction) -> IntrinsicGasCost: """ Calculates the gas that is charged before execution is started. @@ -613,15 +626,15 @@ def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: GasCosts.AUTH_PER_EMPTY_ACCOUNT * len(tx.authorizations) ) - return ( - Uint( + return IntrinsicGasCost( + regular=Uint( GasCosts.TX_BASE + data_cost + create_cost + access_list_cost + auth_cost ), - calldata_floor_gas_cost, + calldata_floor=calldata_floor_gas_cost, ) diff --git a/src/ethereum/forks/bpo2/vm/instructions/system.py b/src/ethereum/forks/bpo2/vm/instructions/system.py index 3fdfe3e4386..30db9d8309f 100644 --- a/src/ethereum/forks/bpo2/vm/instructions/system.py +++ b/src/ethereum/forks/bpo2/vm/instructions/system.py @@ -11,6 +11,9 @@ Implementations of the EVM system related instructions. """ +from dataclasses import dataclass +from typing import final + from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.numeric import U256, Uint @@ -267,22 +270,29 @@ def return_(evm: Evm) -> None: pass -def generic_call( - evm: Evm, - gas: Uint, - value: U256, - caller: Address, - to: Address, - code_address: Address, - should_transfer_value: bool, - is_staticcall: bool, - memory_input_start_position: U256, - memory_input_size: U256, - memory_output_start_position: U256, - memory_output_size: U256, - code: Bytes, - disable_precompiles: bool, -) -> None: +@final +@dataclass +class GenericCall: + """ + Parameters for the core logic of the `CALL*` family of opcodes. + """ + + gas: Uint + value: U256 + caller: Address + to: Address + code_address: Address + should_transfer_value: bool + is_staticcall: bool + memory_input_start_position: U256 + memory_input_size: U256 + memory_output_start_position: U256 + memory_output_size: U256 + code: Bytes + disable_precompiles: bool + + +def generic_call(evm: Evm, params: GenericCall) -> None: """ Perform the core logic of the `CALL*` family of opcodes. """ @@ -291,31 +301,33 @@ def generic_call( evm.return_data = b"" if evm.message.depth + Uint(1) > STACK_DEPTH_LIMIT: - evm.gas_left += gas + evm.gas_left += params.gas push(evm.stack, U256(0)) return call_data = memory_read_bytes( - evm.memory, memory_input_start_position, memory_input_size + evm.memory, + params.memory_input_start_position, + params.memory_input_size, ) child_message = Message( block_env=evm.message.block_env, tx_env=evm.message.tx_env, - caller=caller, - target=to, - gas=gas, - value=value, + caller=params.caller, + target=params.to, + gas=params.gas, + value=params.value, data=call_data, - code=code, - current_target=to, + code=params.code, + current_target=params.to, depth=evm.message.depth + Uint(1), - code_address=code_address, - should_transfer_value=should_transfer_value, - is_static=True if is_staticcall else evm.message.is_static, + code_address=params.code_address, + should_transfer_value=params.should_transfer_value, + is_static=params.is_staticcall or evm.message.is_static, accessed_addresses=evm.accessed_addresses.copy(), accessed_storage_keys=evm.accessed_storage_keys.copy(), - disable_precompiles=disable_precompiles, + disable_precompiles=params.disable_precompiles, parent_evm=evm, ) child_evm = process_message(child_message) @@ -329,10 +341,12 @@ def generic_call( evm.return_data = child_evm.output push(evm.stack, U256(1)) - actual_output_size = min(memory_output_size, U256(len(child_evm.output))) + actual_output_size = min( + params.memory_output_size, U256(len(child_evm.output)) + ) memory_write( evm.memory, - memory_output_start_position, + params.memory_output_start_position, child_evm.output[:actual_output_size], ) @@ -388,8 +402,8 @@ def call(evm: Evm) -> None: value, gas, Uint(evm.gas_left), - extend_memory.cost, - access_gas_cost + create_gas_cost + transfer_gas_cost, + memory_cost=extend_memory.cost, + extra_gas=access_gas_cost + create_gas_cost + transfer_gas_cost, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) if evm.message.is_static and value != U256(0): @@ -405,19 +419,21 @@ def call(evm: Evm) -> None: else: generic_call( evm, - message_call_gas.sub_call, - value, - evm.message.current_target, - to, - code_address, - True, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, - code, - disable_precompiles, + GenericCall( + gas=message_call_gas.sub_call, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + code=code, + disable_precompiles=disable_precompiles, + ), ) # PROGRAM COUNTER @@ -490,19 +506,21 @@ def callcode(evm: Evm) -> None: else: generic_call( evm, - message_call_gas.sub_call, - value, - evm.message.current_target, - to, - code_address, - True, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, - code, - disable_precompiles, + GenericCall( + gas=message_call_gas.sub_call, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + code=code, + disable_precompiles=disable_precompiles, + ), ) # PROGRAM COUNTER @@ -618,19 +636,21 @@ def delegatecall(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by generic_call( evm, - message_call_gas.sub_call, - evm.message.value, - evm.message.caller, - evm.message.current_target, - code_address, - False, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, - code, - disable_precompiles, + GenericCall( + gas=message_call_gas.sub_call, + value=evm.message.value, + caller=evm.message.caller, + to=evm.message.current_target, + code_address=code_address, + should_transfer_value=False, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + code=code, + disable_precompiles=disable_precompiles, + ), ) # PROGRAM COUNTER @@ -692,19 +712,21 @@ def staticcall(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by generic_call( evm, - message_call_gas.sub_call, - U256(0), - evm.message.current_target, - to, - code_address, - True, - True, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, - code, - disable_precompiles, + GenericCall( + gas=message_call_gas.sub_call, + value=U256(0), + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=True, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + code=code, + disable_precompiles=disable_precompiles, + ), ) # PROGRAM COUNTER diff --git a/src/ethereum/forks/bpo3/fork.py b/src/ethereum/forks/bpo3/fork.py index fcdff89d045..69b586fde1d 100644 --- a/src/ethereum/forks/bpo3/fork.py +++ b/src/ethereum/forks/bpo3/fork.py @@ -872,7 +872,7 @@ def process_transaction( encode_transaction(tx), ) - intrinsic_gas, calldata_floor_gas_cost = validate_transaction(tx) + intrinsic = validate_transaction(tx) ( sender, @@ -895,7 +895,7 @@ def process_transaction( effective_gas_fee = tx.gas * effective_gas_price - gas = tx.gas - intrinsic_gas + gas = tx.gas - intrinsic.regular increment_nonce(tx_state, sender) sender_balance_after_gas_fee = ( @@ -944,7 +944,7 @@ def process_transaction( # Transactions with less execution_gas_used than the floor pay at the # floor cost. tx_gas_used_after_refund = max( - tx_gas_used_after_refund, calldata_floor_gas_cost + tx_gas_used_after_refund, intrinsic.calldata_floor ) tx_gas_left = tx.gas - tx_gas_used_after_refund diff --git a/src/ethereum/forks/bpo3/transactions.py b/src/ethereum/forks/bpo3/transactions.py index 18953538456..3d8cdf3754d 100644 --- a/src/ethereum/forks/bpo3/transactions.py +++ b/src/ethereum/forks/bpo3/transactions.py @@ -28,6 +28,19 @@ ) from .fork_types import Authorization, VersionedHash + +@final +@dataclass +class IntrinsicGasCost: + """Intrinsic gas costs for a transaction, split by gas type.""" + + regular: Uint + """Regular execution gas (calldata, base cost, access list, etc.).""" + + calldata_floor: Uint + """Minimum gas cost based on calldata size per [EIP-7623].""" + + TX_MAX_GAS_LIMIT = Uint(16_777_216) @@ -512,7 +525,7 @@ def decode_transaction(tx: LegacyTransaction | Bytes) -> Transaction: return tx -def validate_transaction(tx: Transaction) -> Tuple[Uint, Uint]: +def validate_transaction(tx: Transaction) -> IntrinsicGasCost: """ Verifies a transaction. @@ -543,8 +556,8 @@ def validate_transaction(tx: Transaction) -> Tuple[Uint, Uint]: """ from .vm.interpreter import MAX_INIT_CODE_SIZE - intrinsic_gas, calldata_floor_gas_cost = calculate_intrinsic_cost(tx) - if max(intrinsic_gas, calldata_floor_gas_cost) > tx.gas: + intrinsic = calculate_intrinsic_cost(tx) + if max(intrinsic.regular, intrinsic.calldata_floor) > tx.gas: raise InsufficientTransactionGasError("Insufficient gas") if U256(tx.nonce) >= U256(U64.MAX_VALUE): raise NonceOverflowError("Nonce too high") @@ -553,10 +566,10 @@ def validate_transaction(tx: Transaction) -> Tuple[Uint, Uint]: if tx.gas > TX_MAX_GAS_LIMIT: raise TransactionGasLimitExceededError("Gas limit too high") - return intrinsic_gas, calldata_floor_gas_cost + return intrinsic -def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: +def calculate_intrinsic_cost(tx: Transaction) -> IntrinsicGasCost: """ Calculates the gas that is charged before execution is started. @@ -613,15 +626,15 @@ def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: GasCosts.AUTH_PER_EMPTY_ACCOUNT * len(tx.authorizations) ) - return ( - Uint( + return IntrinsicGasCost( + regular=Uint( GasCosts.TX_BASE + data_cost + create_cost + access_list_cost + auth_cost ), - calldata_floor_gas_cost, + calldata_floor=calldata_floor_gas_cost, ) diff --git a/src/ethereum/forks/bpo3/vm/instructions/system.py b/src/ethereum/forks/bpo3/vm/instructions/system.py index 3fdfe3e4386..30db9d8309f 100644 --- a/src/ethereum/forks/bpo3/vm/instructions/system.py +++ b/src/ethereum/forks/bpo3/vm/instructions/system.py @@ -11,6 +11,9 @@ Implementations of the EVM system related instructions. """ +from dataclasses import dataclass +from typing import final + from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.numeric import U256, Uint @@ -267,22 +270,29 @@ def return_(evm: Evm) -> None: pass -def generic_call( - evm: Evm, - gas: Uint, - value: U256, - caller: Address, - to: Address, - code_address: Address, - should_transfer_value: bool, - is_staticcall: bool, - memory_input_start_position: U256, - memory_input_size: U256, - memory_output_start_position: U256, - memory_output_size: U256, - code: Bytes, - disable_precompiles: bool, -) -> None: +@final +@dataclass +class GenericCall: + """ + Parameters for the core logic of the `CALL*` family of opcodes. + """ + + gas: Uint + value: U256 + caller: Address + to: Address + code_address: Address + should_transfer_value: bool + is_staticcall: bool + memory_input_start_position: U256 + memory_input_size: U256 + memory_output_start_position: U256 + memory_output_size: U256 + code: Bytes + disable_precompiles: bool + + +def generic_call(evm: Evm, params: GenericCall) -> None: """ Perform the core logic of the `CALL*` family of opcodes. """ @@ -291,31 +301,33 @@ def generic_call( evm.return_data = b"" if evm.message.depth + Uint(1) > STACK_DEPTH_LIMIT: - evm.gas_left += gas + evm.gas_left += params.gas push(evm.stack, U256(0)) return call_data = memory_read_bytes( - evm.memory, memory_input_start_position, memory_input_size + evm.memory, + params.memory_input_start_position, + params.memory_input_size, ) child_message = Message( block_env=evm.message.block_env, tx_env=evm.message.tx_env, - caller=caller, - target=to, - gas=gas, - value=value, + caller=params.caller, + target=params.to, + gas=params.gas, + value=params.value, data=call_data, - code=code, - current_target=to, + code=params.code, + current_target=params.to, depth=evm.message.depth + Uint(1), - code_address=code_address, - should_transfer_value=should_transfer_value, - is_static=True if is_staticcall else evm.message.is_static, + code_address=params.code_address, + should_transfer_value=params.should_transfer_value, + is_static=params.is_staticcall or evm.message.is_static, accessed_addresses=evm.accessed_addresses.copy(), accessed_storage_keys=evm.accessed_storage_keys.copy(), - disable_precompiles=disable_precompiles, + disable_precompiles=params.disable_precompiles, parent_evm=evm, ) child_evm = process_message(child_message) @@ -329,10 +341,12 @@ def generic_call( evm.return_data = child_evm.output push(evm.stack, U256(1)) - actual_output_size = min(memory_output_size, U256(len(child_evm.output))) + actual_output_size = min( + params.memory_output_size, U256(len(child_evm.output)) + ) memory_write( evm.memory, - memory_output_start_position, + params.memory_output_start_position, child_evm.output[:actual_output_size], ) @@ -388,8 +402,8 @@ def call(evm: Evm) -> None: value, gas, Uint(evm.gas_left), - extend_memory.cost, - access_gas_cost + create_gas_cost + transfer_gas_cost, + memory_cost=extend_memory.cost, + extra_gas=access_gas_cost + create_gas_cost + transfer_gas_cost, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) if evm.message.is_static and value != U256(0): @@ -405,19 +419,21 @@ def call(evm: Evm) -> None: else: generic_call( evm, - message_call_gas.sub_call, - value, - evm.message.current_target, - to, - code_address, - True, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, - code, - disable_precompiles, + GenericCall( + gas=message_call_gas.sub_call, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + code=code, + disable_precompiles=disable_precompiles, + ), ) # PROGRAM COUNTER @@ -490,19 +506,21 @@ def callcode(evm: Evm) -> None: else: generic_call( evm, - message_call_gas.sub_call, - value, - evm.message.current_target, - to, - code_address, - True, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, - code, - disable_precompiles, + GenericCall( + gas=message_call_gas.sub_call, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + code=code, + disable_precompiles=disable_precompiles, + ), ) # PROGRAM COUNTER @@ -618,19 +636,21 @@ def delegatecall(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by generic_call( evm, - message_call_gas.sub_call, - evm.message.value, - evm.message.caller, - evm.message.current_target, - code_address, - False, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, - code, - disable_precompiles, + GenericCall( + gas=message_call_gas.sub_call, + value=evm.message.value, + caller=evm.message.caller, + to=evm.message.current_target, + code_address=code_address, + should_transfer_value=False, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + code=code, + disable_precompiles=disable_precompiles, + ), ) # PROGRAM COUNTER @@ -692,19 +712,21 @@ def staticcall(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by generic_call( evm, - message_call_gas.sub_call, - U256(0), - evm.message.current_target, - to, - code_address, - True, - True, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, - code, - disable_precompiles, + GenericCall( + gas=message_call_gas.sub_call, + value=U256(0), + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=True, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + code=code, + disable_precompiles=disable_precompiles, + ), ) # PROGRAM COUNTER diff --git a/src/ethereum/forks/bpo4/fork.py b/src/ethereum/forks/bpo4/fork.py index fcdff89d045..69b586fde1d 100644 --- a/src/ethereum/forks/bpo4/fork.py +++ b/src/ethereum/forks/bpo4/fork.py @@ -872,7 +872,7 @@ def process_transaction( encode_transaction(tx), ) - intrinsic_gas, calldata_floor_gas_cost = validate_transaction(tx) + intrinsic = validate_transaction(tx) ( sender, @@ -895,7 +895,7 @@ def process_transaction( effective_gas_fee = tx.gas * effective_gas_price - gas = tx.gas - intrinsic_gas + gas = tx.gas - intrinsic.regular increment_nonce(tx_state, sender) sender_balance_after_gas_fee = ( @@ -944,7 +944,7 @@ def process_transaction( # Transactions with less execution_gas_used than the floor pay at the # floor cost. tx_gas_used_after_refund = max( - tx_gas_used_after_refund, calldata_floor_gas_cost + tx_gas_used_after_refund, intrinsic.calldata_floor ) tx_gas_left = tx.gas - tx_gas_used_after_refund diff --git a/src/ethereum/forks/bpo4/transactions.py b/src/ethereum/forks/bpo4/transactions.py index 18953538456..3d8cdf3754d 100644 --- a/src/ethereum/forks/bpo4/transactions.py +++ b/src/ethereum/forks/bpo4/transactions.py @@ -28,6 +28,19 @@ ) from .fork_types import Authorization, VersionedHash + +@final +@dataclass +class IntrinsicGasCost: + """Intrinsic gas costs for a transaction, split by gas type.""" + + regular: Uint + """Regular execution gas (calldata, base cost, access list, etc.).""" + + calldata_floor: Uint + """Minimum gas cost based on calldata size per [EIP-7623].""" + + TX_MAX_GAS_LIMIT = Uint(16_777_216) @@ -512,7 +525,7 @@ def decode_transaction(tx: LegacyTransaction | Bytes) -> Transaction: return tx -def validate_transaction(tx: Transaction) -> Tuple[Uint, Uint]: +def validate_transaction(tx: Transaction) -> IntrinsicGasCost: """ Verifies a transaction. @@ -543,8 +556,8 @@ def validate_transaction(tx: Transaction) -> Tuple[Uint, Uint]: """ from .vm.interpreter import MAX_INIT_CODE_SIZE - intrinsic_gas, calldata_floor_gas_cost = calculate_intrinsic_cost(tx) - if max(intrinsic_gas, calldata_floor_gas_cost) > tx.gas: + intrinsic = calculate_intrinsic_cost(tx) + if max(intrinsic.regular, intrinsic.calldata_floor) > tx.gas: raise InsufficientTransactionGasError("Insufficient gas") if U256(tx.nonce) >= U256(U64.MAX_VALUE): raise NonceOverflowError("Nonce too high") @@ -553,10 +566,10 @@ def validate_transaction(tx: Transaction) -> Tuple[Uint, Uint]: if tx.gas > TX_MAX_GAS_LIMIT: raise TransactionGasLimitExceededError("Gas limit too high") - return intrinsic_gas, calldata_floor_gas_cost + return intrinsic -def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: +def calculate_intrinsic_cost(tx: Transaction) -> IntrinsicGasCost: """ Calculates the gas that is charged before execution is started. @@ -613,15 +626,15 @@ def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: GasCosts.AUTH_PER_EMPTY_ACCOUNT * len(tx.authorizations) ) - return ( - Uint( + return IntrinsicGasCost( + regular=Uint( GasCosts.TX_BASE + data_cost + create_cost + access_list_cost + auth_cost ), - calldata_floor_gas_cost, + calldata_floor=calldata_floor_gas_cost, ) diff --git a/src/ethereum/forks/bpo4/vm/instructions/system.py b/src/ethereum/forks/bpo4/vm/instructions/system.py index 092c6fb68c2..f8f8f29e100 100644 --- a/src/ethereum/forks/bpo4/vm/instructions/system.py +++ b/src/ethereum/forks/bpo4/vm/instructions/system.py @@ -11,6 +11,9 @@ Implementations of the EVM system related instructions. """ +from dataclasses import dataclass +from typing import final + from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.numeric import U256, Uint @@ -268,22 +271,29 @@ def return_(evm: Evm) -> None: pass -def generic_call( - evm: Evm, - gas: Uint, - value: U256, - caller: Address, - to: Address, - code_address: Address, - should_transfer_value: bool, - is_staticcall: bool, - memory_input_start_position: U256, - memory_input_size: U256, - memory_output_start_position: U256, - memory_output_size: U256, - code: Bytes, - disable_precompiles: bool, -) -> None: +@final +@dataclass +class GenericCall: + """ + Parameters for the core logic of the `CALL*` family of opcodes. + """ + + gas: Uint + value: U256 + caller: Address + to: Address + code_address: Address + should_transfer_value: bool + is_staticcall: bool + memory_input_start_position: U256 + memory_input_size: U256 + memory_output_start_position: U256 + memory_output_size: U256 + code: Bytes + disable_precompiles: bool + + +def generic_call(evm: Evm, params: GenericCall) -> None: """ Perform the core logic of the `CALL*` family of opcodes. """ @@ -292,31 +302,33 @@ def generic_call( evm.return_data = b"" if evm.message.depth + Uint(1) > STACK_DEPTH_LIMIT: - evm.gas_left += gas + evm.gas_left += params.gas push(evm.stack, U256(0)) return call_data = memory_read_bytes( - evm.memory, memory_input_start_position, memory_input_size + evm.memory, + params.memory_input_start_position, + params.memory_input_size, ) child_message = Message( block_env=evm.message.block_env, tx_env=evm.message.tx_env, - caller=caller, - target=to, - gas=gas, - value=value, + caller=params.caller, + target=params.to, + gas=params.gas, + value=params.value, data=call_data, - code=code, - current_target=to, + code=params.code, + current_target=params.to, depth=evm.message.depth + Uint(1), - code_address=code_address, - should_transfer_value=should_transfer_value, - is_static=True if is_staticcall else evm.message.is_static, + code_address=params.code_address, + should_transfer_value=params.should_transfer_value, + is_static=params.is_staticcall or evm.message.is_static, accessed_addresses=evm.accessed_addresses.copy(), accessed_storage_keys=evm.accessed_storage_keys.copy(), - disable_precompiles=disable_precompiles, + disable_precompiles=params.disable_precompiles, parent_evm=evm, ) child_evm = process_message(child_message) @@ -330,10 +342,12 @@ def generic_call( evm.return_data = child_evm.output push(evm.stack, U256(1)) - actual_output_size = min(memory_output_size, U256(len(child_evm.output))) + actual_output_size = min( + params.memory_output_size, U256(len(child_evm.output)) + ) memory_write( evm.memory, - memory_output_start_position, + params.memory_output_start_position, child_evm.output[:actual_output_size], ) @@ -389,8 +403,8 @@ def call(evm: Evm) -> None: value, gas, Uint(evm.gas_left), - extend_memory.cost, - access_gas_cost + create_gas_cost + transfer_gas_cost, + memory_cost=extend_memory.cost, + extra_gas=access_gas_cost + create_gas_cost + transfer_gas_cost, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) if evm.message.is_static and value != U256(0): @@ -406,19 +420,21 @@ def call(evm: Evm) -> None: else: generic_call( evm, - message_call_gas.sub_call, - value, - evm.message.current_target, - to, - code_address, - True, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, - code, - disable_precompiles, + GenericCall( + gas=message_call_gas.sub_call, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + code=code, + disable_precompiles=disable_precompiles, + ), ) # PROGRAM COUNTER @@ -491,19 +507,21 @@ def callcode(evm: Evm) -> None: else: generic_call( evm, - message_call_gas.sub_call, - value, - evm.message.current_target, - to, - code_address, - True, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, - code, - disable_precompiles, + GenericCall( + gas=message_call_gas.sub_call, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + code=code, + disable_precompiles=disable_precompiles, + ), ) # PROGRAM COUNTER @@ -619,19 +637,21 @@ def delegatecall(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by generic_call( evm, - message_call_gas.sub_call, - evm.message.value, - evm.message.caller, - evm.message.current_target, - code_address, - False, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, - code, - disable_precompiles, + GenericCall( + gas=message_call_gas.sub_call, + value=evm.message.value, + caller=evm.message.caller, + to=evm.message.current_target, + code_address=code_address, + should_transfer_value=False, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + code=code, + disable_precompiles=disable_precompiles, + ), ) # PROGRAM COUNTER @@ -693,19 +713,21 @@ def staticcall(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by generic_call( evm, - message_call_gas.sub_call, - U256(0), - evm.message.current_target, - to, - code_address, - True, - True, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, - code, - disable_precompiles, + GenericCall( + gas=message_call_gas.sub_call, + value=U256(0), + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=True, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + code=code, + disable_precompiles=disable_precompiles, + ), ) # PROGRAM COUNTER diff --git a/src/ethereum/forks/bpo5/fork.py b/src/ethereum/forks/bpo5/fork.py index fcdff89d045..69b586fde1d 100644 --- a/src/ethereum/forks/bpo5/fork.py +++ b/src/ethereum/forks/bpo5/fork.py @@ -872,7 +872,7 @@ def process_transaction( encode_transaction(tx), ) - intrinsic_gas, calldata_floor_gas_cost = validate_transaction(tx) + intrinsic = validate_transaction(tx) ( sender, @@ -895,7 +895,7 @@ def process_transaction( effective_gas_fee = tx.gas * effective_gas_price - gas = tx.gas - intrinsic_gas + gas = tx.gas - intrinsic.regular increment_nonce(tx_state, sender) sender_balance_after_gas_fee = ( @@ -944,7 +944,7 @@ def process_transaction( # Transactions with less execution_gas_used than the floor pay at the # floor cost. tx_gas_used_after_refund = max( - tx_gas_used_after_refund, calldata_floor_gas_cost + tx_gas_used_after_refund, intrinsic.calldata_floor ) tx_gas_left = tx.gas - tx_gas_used_after_refund diff --git a/src/ethereum/forks/bpo5/transactions.py b/src/ethereum/forks/bpo5/transactions.py index 18953538456..3d8cdf3754d 100644 --- a/src/ethereum/forks/bpo5/transactions.py +++ b/src/ethereum/forks/bpo5/transactions.py @@ -28,6 +28,19 @@ ) from .fork_types import Authorization, VersionedHash + +@final +@dataclass +class IntrinsicGasCost: + """Intrinsic gas costs for a transaction, split by gas type.""" + + regular: Uint + """Regular execution gas (calldata, base cost, access list, etc.).""" + + calldata_floor: Uint + """Minimum gas cost based on calldata size per [EIP-7623].""" + + TX_MAX_GAS_LIMIT = Uint(16_777_216) @@ -512,7 +525,7 @@ def decode_transaction(tx: LegacyTransaction | Bytes) -> Transaction: return tx -def validate_transaction(tx: Transaction) -> Tuple[Uint, Uint]: +def validate_transaction(tx: Transaction) -> IntrinsicGasCost: """ Verifies a transaction. @@ -543,8 +556,8 @@ def validate_transaction(tx: Transaction) -> Tuple[Uint, Uint]: """ from .vm.interpreter import MAX_INIT_CODE_SIZE - intrinsic_gas, calldata_floor_gas_cost = calculate_intrinsic_cost(tx) - if max(intrinsic_gas, calldata_floor_gas_cost) > tx.gas: + intrinsic = calculate_intrinsic_cost(tx) + if max(intrinsic.regular, intrinsic.calldata_floor) > tx.gas: raise InsufficientTransactionGasError("Insufficient gas") if U256(tx.nonce) >= U256(U64.MAX_VALUE): raise NonceOverflowError("Nonce too high") @@ -553,10 +566,10 @@ def validate_transaction(tx: Transaction) -> Tuple[Uint, Uint]: if tx.gas > TX_MAX_GAS_LIMIT: raise TransactionGasLimitExceededError("Gas limit too high") - return intrinsic_gas, calldata_floor_gas_cost + return intrinsic -def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: +def calculate_intrinsic_cost(tx: Transaction) -> IntrinsicGasCost: """ Calculates the gas that is charged before execution is started. @@ -613,15 +626,15 @@ def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: GasCosts.AUTH_PER_EMPTY_ACCOUNT * len(tx.authorizations) ) - return ( - Uint( + return IntrinsicGasCost( + regular=Uint( GasCosts.TX_BASE + data_cost + create_cost + access_list_cost + auth_cost ), - calldata_floor_gas_cost, + calldata_floor=calldata_floor_gas_cost, ) diff --git a/src/ethereum/forks/bpo5/vm/instructions/system.py b/src/ethereum/forks/bpo5/vm/instructions/system.py index 092c6fb68c2..f8f8f29e100 100644 --- a/src/ethereum/forks/bpo5/vm/instructions/system.py +++ b/src/ethereum/forks/bpo5/vm/instructions/system.py @@ -11,6 +11,9 @@ Implementations of the EVM system related instructions. """ +from dataclasses import dataclass +from typing import final + from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.numeric import U256, Uint @@ -268,22 +271,29 @@ def return_(evm: Evm) -> None: pass -def generic_call( - evm: Evm, - gas: Uint, - value: U256, - caller: Address, - to: Address, - code_address: Address, - should_transfer_value: bool, - is_staticcall: bool, - memory_input_start_position: U256, - memory_input_size: U256, - memory_output_start_position: U256, - memory_output_size: U256, - code: Bytes, - disable_precompiles: bool, -) -> None: +@final +@dataclass +class GenericCall: + """ + Parameters for the core logic of the `CALL*` family of opcodes. + """ + + gas: Uint + value: U256 + caller: Address + to: Address + code_address: Address + should_transfer_value: bool + is_staticcall: bool + memory_input_start_position: U256 + memory_input_size: U256 + memory_output_start_position: U256 + memory_output_size: U256 + code: Bytes + disable_precompiles: bool + + +def generic_call(evm: Evm, params: GenericCall) -> None: """ Perform the core logic of the `CALL*` family of opcodes. """ @@ -292,31 +302,33 @@ def generic_call( evm.return_data = b"" if evm.message.depth + Uint(1) > STACK_DEPTH_LIMIT: - evm.gas_left += gas + evm.gas_left += params.gas push(evm.stack, U256(0)) return call_data = memory_read_bytes( - evm.memory, memory_input_start_position, memory_input_size + evm.memory, + params.memory_input_start_position, + params.memory_input_size, ) child_message = Message( block_env=evm.message.block_env, tx_env=evm.message.tx_env, - caller=caller, - target=to, - gas=gas, - value=value, + caller=params.caller, + target=params.to, + gas=params.gas, + value=params.value, data=call_data, - code=code, - current_target=to, + code=params.code, + current_target=params.to, depth=evm.message.depth + Uint(1), - code_address=code_address, - should_transfer_value=should_transfer_value, - is_static=True if is_staticcall else evm.message.is_static, + code_address=params.code_address, + should_transfer_value=params.should_transfer_value, + is_static=params.is_staticcall or evm.message.is_static, accessed_addresses=evm.accessed_addresses.copy(), accessed_storage_keys=evm.accessed_storage_keys.copy(), - disable_precompiles=disable_precompiles, + disable_precompiles=params.disable_precompiles, parent_evm=evm, ) child_evm = process_message(child_message) @@ -330,10 +342,12 @@ def generic_call( evm.return_data = child_evm.output push(evm.stack, U256(1)) - actual_output_size = min(memory_output_size, U256(len(child_evm.output))) + actual_output_size = min( + params.memory_output_size, U256(len(child_evm.output)) + ) memory_write( evm.memory, - memory_output_start_position, + params.memory_output_start_position, child_evm.output[:actual_output_size], ) @@ -389,8 +403,8 @@ def call(evm: Evm) -> None: value, gas, Uint(evm.gas_left), - extend_memory.cost, - access_gas_cost + create_gas_cost + transfer_gas_cost, + memory_cost=extend_memory.cost, + extra_gas=access_gas_cost + create_gas_cost + transfer_gas_cost, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) if evm.message.is_static and value != U256(0): @@ -406,19 +420,21 @@ def call(evm: Evm) -> None: else: generic_call( evm, - message_call_gas.sub_call, - value, - evm.message.current_target, - to, - code_address, - True, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, - code, - disable_precompiles, + GenericCall( + gas=message_call_gas.sub_call, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + code=code, + disable_precompiles=disable_precompiles, + ), ) # PROGRAM COUNTER @@ -491,19 +507,21 @@ def callcode(evm: Evm) -> None: else: generic_call( evm, - message_call_gas.sub_call, - value, - evm.message.current_target, - to, - code_address, - True, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, - code, - disable_precompiles, + GenericCall( + gas=message_call_gas.sub_call, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + code=code, + disable_precompiles=disable_precompiles, + ), ) # PROGRAM COUNTER @@ -619,19 +637,21 @@ def delegatecall(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by generic_call( evm, - message_call_gas.sub_call, - evm.message.value, - evm.message.caller, - evm.message.current_target, - code_address, - False, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, - code, - disable_precompiles, + GenericCall( + gas=message_call_gas.sub_call, + value=evm.message.value, + caller=evm.message.caller, + to=evm.message.current_target, + code_address=code_address, + should_transfer_value=False, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + code=code, + disable_precompiles=disable_precompiles, + ), ) # PROGRAM COUNTER @@ -693,19 +713,21 @@ def staticcall(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by generic_call( evm, - message_call_gas.sub_call, - U256(0), - evm.message.current_target, - to, - code_address, - True, - True, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, - code, - disable_precompiles, + GenericCall( + gas=message_call_gas.sub_call, + value=U256(0), + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=True, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + code=code, + disable_precompiles=disable_precompiles, + ), ) # PROGRAM COUNTER diff --git a/src/ethereum/forks/byzantium/vm/instructions/system.py b/src/ethereum/forks/byzantium/vm/instructions/system.py index 1f4dc02194b..477d466d325 100644 --- a/src/ethereum/forks/byzantium/vm/instructions/system.py +++ b/src/ethereum/forks/byzantium/vm/instructions/system.py @@ -11,6 +11,9 @@ Implementations of the EVM system related instructions. """ +from dataclasses import dataclass +from typing import final + from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.numeric import U256, Uint @@ -173,20 +176,27 @@ def return_(evm: Evm) -> None: pass -def generic_call( - evm: Evm, - gas: Uint, - value: U256, - caller: Address, - to: Address, - code_address: Address, - should_transfer_value: bool, - is_staticcall: bool, - memory_input_start_position: U256, - memory_input_size: U256, - memory_output_start_position: U256, - memory_output_size: U256, -) -> None: +@final +@dataclass +class GenericCall: + """ + Parameters for the core logic of the `CALL*` family of opcodes. + """ + + gas: Uint + value: U256 + caller: Address + to: Address + code_address: Address + should_transfer_value: bool + is_staticcall: bool + memory_input_start_position: U256 + memory_input_size: U256 + memory_output_start_position: U256 + memory_output_size: U256 + + +def generic_call(evm: Evm, params: GenericCall) -> None: """ Perform the core logic of the `CALL*` family of opcodes. """ @@ -195,29 +205,31 @@ def generic_call( evm.return_data = b"" if evm.message.depth + Uint(1) > STACK_DEPTH_LIMIT: - evm.gas_left += gas + evm.gas_left += params.gas push(evm.stack, U256(0)) return call_data = memory_read_bytes( - evm.memory, memory_input_start_position, memory_input_size + evm.memory, + params.memory_input_start_position, + params.memory_input_size, ) - account = get_account(evm.message.tx_env.state, code_address) + account = get_account(evm.message.tx_env.state, params.code_address) code = get_code(evm.message.tx_env.state, account.code_hash) child_message = Message( block_env=evm.message.block_env, tx_env=evm.message.tx_env, - caller=caller, - target=to, - gas=gas, - value=value, + caller=params.caller, + target=params.to, + gas=params.gas, + value=params.value, data=call_data, code=code, - current_target=to, + current_target=params.to, depth=evm.message.depth + Uint(1), - code_address=code_address, - should_transfer_value=should_transfer_value, - is_static=True if is_staticcall else evm.message.is_static, + code_address=params.code_address, + should_transfer_value=params.should_transfer_value, + is_static=params.is_staticcall or evm.message.is_static, parent_evm=evm, ) child_evm = process_message(child_message) @@ -231,10 +243,12 @@ def generic_call( evm.return_data = child_evm.output push(evm.stack, U256(1)) - actual_output_size = min(memory_output_size, U256(len(child_evm.output))) + actual_output_size = min( + params.memory_output_size, U256(len(child_evm.output)) + ) memory_write( evm.memory, - memory_output_start_position, + params.memory_output_start_position, child_evm.output[:actual_output_size], ) @@ -277,8 +291,10 @@ def call(evm: Evm) -> None: value, gas, Uint(evm.gas_left), - extend_memory.cost, - GasCosts.OPCODE_CALL_BASE + create_gas_cost + transfer_gas_cost, + memory_cost=extend_memory.cost, + extra_gas=GasCosts.OPCODE_CALL_BASE + + create_gas_cost + + transfer_gas_cost, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) if evm.message.is_static and value != U256(0): @@ -294,17 +310,19 @@ def call(evm: Evm) -> None: else: generic_call( evm, - message_call_gas.sub_call, - value, - evm.message.current_target, - to, - code_address, - True, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER @@ -362,17 +380,19 @@ def callcode(evm: Evm) -> None: else: generic_call( evm, - message_call_gas.sub_call, - value, - evm.message.current_target, - to, - code_address, - True, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER @@ -489,17 +509,19 @@ def delegatecall(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by generic_call( evm, - message_call_gas.sub_call, - evm.message.value, - evm.message.caller, - evm.message.current_target, - code_address, - False, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=evm.message.value, + caller=evm.message.caller, + to=evm.message.current_target, + code_address=code_address, + should_transfer_value=False, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER @@ -548,17 +570,19 @@ def staticcall(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by generic_call( evm, - message_call_gas.sub_call, - U256(0), - evm.message.current_target, - to, - code_address, - True, - True, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=U256(0), + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=True, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER diff --git a/src/ethereum/forks/cancun/vm/instructions/system.py b/src/ethereum/forks/cancun/vm/instructions/system.py index 3f7b1be3676..c2d0c4c7f3e 100644 --- a/src/ethereum/forks/cancun/vm/instructions/system.py +++ b/src/ethereum/forks/cancun/vm/instructions/system.py @@ -11,6 +11,9 @@ Implementations of the EVM system related instructions. """ +from dataclasses import dataclass +from typing import final + from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.numeric import U256, Uint @@ -266,20 +269,27 @@ def return_(evm: Evm) -> None: pass -def generic_call( - evm: Evm, - gas: Uint, - value: U256, - caller: Address, - to: Address, - code_address: Address, - should_transfer_value: bool, - is_staticcall: bool, - memory_input_start_position: U256, - memory_input_size: U256, - memory_output_start_position: U256, - memory_output_size: U256, -) -> None: +@final +@dataclass +class GenericCall: + """ + Parameters for the core logic of the `CALL*` family of opcodes. + """ + + gas: Uint + value: U256 + caller: Address + to: Address + code_address: Address + should_transfer_value: bool + is_staticcall: bool + memory_input_start_position: U256 + memory_input_size: U256 + memory_output_start_position: U256 + memory_output_size: U256 + + +def generic_call(evm: Evm, params: GenericCall) -> None: """ Perform the core logic of the `CALL*` family of opcodes. """ @@ -288,29 +298,33 @@ def generic_call( evm.return_data = b"" if evm.message.depth + Uint(1) > STACK_DEPTH_LIMIT: - evm.gas_left += gas + evm.gas_left += params.gas push(evm.stack, U256(0)) return call_data = memory_read_bytes( - evm.memory, memory_input_start_position, memory_input_size + evm.memory, + params.memory_input_start_position, + params.memory_input_size, ) tx_state = evm.message.tx_env.state - code = get_code(tx_state, get_account(tx_state, code_address).code_hash) + code = get_code( + tx_state, get_account(tx_state, params.code_address).code_hash + ) child_message = Message( block_env=evm.message.block_env, tx_env=evm.message.tx_env, - caller=caller, - target=to, - gas=gas, - value=value, + caller=params.caller, + target=params.to, + gas=params.gas, + value=params.value, data=call_data, code=code, - current_target=to, + current_target=params.to, depth=evm.message.depth + Uint(1), - code_address=code_address, - should_transfer_value=should_transfer_value, - is_static=True if is_staticcall else evm.message.is_static, + code_address=params.code_address, + should_transfer_value=params.should_transfer_value, + is_static=params.is_staticcall or evm.message.is_static, accessed_addresses=evm.accessed_addresses.copy(), accessed_storage_keys=evm.accessed_storage_keys.copy(), parent_evm=evm, @@ -326,10 +340,12 @@ def generic_call( evm.return_data = child_evm.output push(evm.stack, U256(1)) - actual_output_size = min(memory_output_size, U256(len(child_evm.output))) + actual_output_size = min( + params.memory_output_size, U256(len(child_evm.output)) + ) memory_write( evm.memory, - memory_output_start_position, + params.memory_output_start_position, child_evm.output[:actual_output_size], ) @@ -378,8 +394,8 @@ def call(evm: Evm) -> None: value, gas, Uint(evm.gas_left), - extend_memory.cost, - access_gas_cost + create_gas_cost + transfer_gas_cost, + memory_cost=extend_memory.cost, + extra_gas=access_gas_cost + create_gas_cost + transfer_gas_cost, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) if evm.message.is_static and value != U256(0): @@ -395,17 +411,19 @@ def call(evm: Evm) -> None: else: generic_call( evm, - message_call_gas.sub_call, - value, - evm.message.current_target, - to, - code_address, - True, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER @@ -470,17 +488,19 @@ def callcode(evm: Evm) -> None: else: generic_call( evm, - message_call_gas.sub_call, - value, - evm.message.current_target, - to, - code_address, - True, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER @@ -588,17 +608,19 @@ def delegatecall(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by generic_call( evm, - message_call_gas.sub_call, - evm.message.value, - evm.message.caller, - evm.message.current_target, - code_address, - False, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=evm.message.value, + caller=evm.message.caller, + to=evm.message.current_target, + code_address=code_address, + should_transfer_value=False, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER @@ -653,17 +675,19 @@ def staticcall(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by generic_call( evm, - message_call_gas.sub_call, - U256(0), - evm.message.current_target, - to, - code_address, - True, - True, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=U256(0), + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=True, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER diff --git a/src/ethereum/forks/constantinople/vm/instructions/system.py b/src/ethereum/forks/constantinople/vm/instructions/system.py index cf3f54c2c8d..1f4a1741743 100644 --- a/src/ethereum/forks/constantinople/vm/instructions/system.py +++ b/src/ethereum/forks/constantinople/vm/instructions/system.py @@ -11,6 +11,9 @@ Implementations of the EVM system related instructions. """ +from dataclasses import dataclass +from typing import final + from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.numeric import U256, Uint @@ -246,20 +249,27 @@ def return_(evm: Evm) -> None: pass -def generic_call( - evm: Evm, - gas: Uint, - value: U256, - caller: Address, - to: Address, - code_address: Address, - should_transfer_value: bool, - is_staticcall: bool, - memory_input_start_position: U256, - memory_input_size: U256, - memory_output_start_position: U256, - memory_output_size: U256, -) -> None: +@final +@dataclass +class GenericCall: + """ + Parameters for the core logic of the `CALL*` family of opcodes. + """ + + gas: Uint + value: U256 + caller: Address + to: Address + code_address: Address + should_transfer_value: bool + is_staticcall: bool + memory_input_start_position: U256 + memory_input_size: U256 + memory_output_start_position: U256 + memory_output_size: U256 + + +def generic_call(evm: Evm, params: GenericCall) -> None: """ Perform the core logic of the `CALL*` family of opcodes. """ @@ -268,29 +278,31 @@ def generic_call( evm.return_data = b"" if evm.message.depth + Uint(1) > STACK_DEPTH_LIMIT: - evm.gas_left += gas + evm.gas_left += params.gas push(evm.stack, U256(0)) return call_data = memory_read_bytes( - evm.memory, memory_input_start_position, memory_input_size + evm.memory, + params.memory_input_start_position, + params.memory_input_size, ) - account = get_account(evm.message.tx_env.state, code_address) + account = get_account(evm.message.tx_env.state, params.code_address) code = get_code(evm.message.tx_env.state, account.code_hash) child_message = Message( block_env=evm.message.block_env, tx_env=evm.message.tx_env, - caller=caller, - target=to, - gas=gas, - value=value, + caller=params.caller, + target=params.to, + gas=params.gas, + value=params.value, data=call_data, code=code, - current_target=to, + current_target=params.to, depth=evm.message.depth + Uint(1), - code_address=code_address, - should_transfer_value=should_transfer_value, - is_static=True if is_staticcall else evm.message.is_static, + code_address=params.code_address, + should_transfer_value=params.should_transfer_value, + is_static=params.is_staticcall or evm.message.is_static, parent_evm=evm, ) child_evm = process_message(child_message) @@ -304,10 +316,12 @@ def generic_call( evm.return_data = child_evm.output push(evm.stack, U256(1)) - actual_output_size = min(memory_output_size, U256(len(child_evm.output))) + actual_output_size = min( + params.memory_output_size, U256(len(child_evm.output)) + ) memory_write( evm.memory, - memory_output_start_position, + params.memory_output_start_position, child_evm.output[:actual_output_size], ) @@ -350,8 +364,10 @@ def call(evm: Evm) -> None: value, gas, Uint(evm.gas_left), - extend_memory.cost, - GasCosts.OPCODE_CALL_BASE + create_gas_cost + transfer_gas_cost, + memory_cost=extend_memory.cost, + extra_gas=GasCosts.OPCODE_CALL_BASE + + create_gas_cost + + transfer_gas_cost, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) if evm.message.is_static and value != U256(0): @@ -367,17 +383,19 @@ def call(evm: Evm) -> None: else: generic_call( evm, - message_call_gas.sub_call, - value, - evm.message.current_target, - to, - code_address, - True, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER @@ -435,17 +453,19 @@ def callcode(evm: Evm) -> None: else: generic_call( evm, - message_call_gas.sub_call, - value, - evm.message.current_target, - to, - code_address, - True, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER @@ -563,17 +583,19 @@ def delegatecall(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by generic_call( evm, - message_call_gas.sub_call, - evm.message.value, - evm.message.caller, - evm.message.current_target, - code_address, - False, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=evm.message.value, + caller=evm.message.caller, + to=evm.message.current_target, + code_address=code_address, + should_transfer_value=False, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER @@ -622,17 +644,19 @@ def staticcall(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by generic_call( evm, - message_call_gas.sub_call, - U256(0), - evm.message.current_target, - to, - code_address, - True, - True, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=U256(0), + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=True, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER diff --git a/src/ethereum/forks/dao_fork/vm/instructions/system.py b/src/ethereum/forks/dao_fork/vm/instructions/system.py index 7fe3234231f..b13f23b1be8 100644 --- a/src/ethereum/forks/dao_fork/vm/instructions/system.py +++ b/src/ethereum/forks/dao_fork/vm/instructions/system.py @@ -11,6 +11,9 @@ Implementations of the EVM system related instructions. """ +from dataclasses import dataclass +from typing import final + from ethereum_types.bytes import Bytes0 from ethereum_types.numeric import U256, Uint @@ -164,47 +167,58 @@ def return_(evm: Evm) -> None: pass -def generic_call( - evm: Evm, - gas: Uint, - value: U256, - caller: Address, - to: Address, - code_address: Address, - should_transfer_value: bool, - memory_input_start_position: U256, - memory_input_size: U256, - memory_output_start_position: U256, - memory_output_size: U256, -) -> None: +@final +@dataclass +class GenericCall: + """ + Parameters for the core logic of the `CALL*` family of opcodes. + """ + + gas: Uint + value: U256 + caller: Address + to: Address + code_address: Address + should_transfer_value: bool + memory_input_start_position: U256 + memory_input_size: U256 + memory_output_start_position: U256 + memory_output_size: U256 + + +def generic_call(evm: Evm, params: GenericCall) -> None: """ Perform the core logic of the `CALL*` family of opcodes. """ from ...vm.interpreter import STACK_DEPTH_LIMIT, process_message if evm.message.depth + Uint(1) > STACK_DEPTH_LIMIT: - evm.gas_left += gas + evm.gas_left += params.gas push(evm.stack, U256(0)) return call_data = memory_read_bytes( - evm.memory, memory_input_start_position, memory_input_size + evm.memory, + params.memory_input_start_position, + params.memory_input_size, + ) + account_to_call = get_account( + evm.message.tx_env.state, params.code_address ) - account_to_call = get_account(evm.message.tx_env.state, code_address) code = get_code(evm.message.tx_env.state, account_to_call.code_hash) child_message = Message( block_env=evm.message.block_env, tx_env=evm.message.tx_env, - caller=caller, - target=to, - gas=gas, - value=value, + caller=params.caller, + target=params.to, + gas=params.gas, + value=params.value, data=call_data, code=code, - current_target=to, + current_target=params.to, depth=evm.message.depth + Uint(1), - code_address=code_address, - should_transfer_value=should_transfer_value, + code_address=params.code_address, + should_transfer_value=params.should_transfer_value, parent_evm=evm, ) child_evm = process_message(child_message) @@ -216,10 +230,12 @@ def generic_call( incorporate_child_on_success(evm, child_evm) push(evm.stack, U256(1)) - actual_output_size = min(memory_output_size, U256(len(child_evm.output))) + actual_output_size = min( + params.memory_output_size, U256(len(child_evm.output)) + ) memory_write( evm.memory, - memory_output_start_position, + params.memory_output_start_position, child_evm.output[:actual_output_size], ) @@ -270,16 +286,18 @@ def call(evm: Evm) -> None: else: generic_call( evm, - message_call_gas.sub_call, - value, - evm.message.current_target, - to, - code_address, - True, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER @@ -331,16 +349,18 @@ def callcode(evm: Evm) -> None: else: generic_call( evm, - message_call_gas.sub_call, - value, - evm.message.current_target, - to, - code_address, - True, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER @@ -437,16 +457,18 @@ def delegatecall(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by generic_call( evm, - gas, - evm.message.value, - evm.message.caller, - evm.message.current_target, - code_address, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=gas, + value=evm.message.value, + caller=evm.message.caller, + to=evm.message.current_target, + code_address=code_address, + should_transfer_value=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER diff --git a/src/ethereum/forks/frontier/vm/instructions/system.py b/src/ethereum/forks/frontier/vm/instructions/system.py index cf978c5a295..47dd034fc16 100644 --- a/src/ethereum/forks/frontier/vm/instructions/system.py +++ b/src/ethereum/forks/frontier/vm/instructions/system.py @@ -11,6 +11,9 @@ Implementations of the EVM system related instructions. """ +from dataclasses import dataclass +from typing import final + from ethereum_types.bytes import Bytes0 from ethereum_types.numeric import U256, Uint @@ -163,45 +166,56 @@ def return_(evm: Evm) -> None: pass -def generic_call( - evm: Evm, - gas: Uint, - value: U256, - caller: Address, - to: Address, - code_address: Address, - memory_input_start_position: U256, - memory_input_size: U256, - memory_output_start_position: U256, - memory_output_size: U256, -) -> None: +@final +@dataclass +class GenericCall: + """ + Parameters for the core logic of the `CALL*` family of opcodes. + """ + + gas: Uint + value: U256 + caller: Address + to: Address + code_address: Address + memory_input_start_position: U256 + memory_input_size: U256 + memory_output_start_position: U256 + memory_output_size: U256 + + +def generic_call(evm: Evm, params: GenericCall) -> None: """ Perform the core logic of the `CALL*` family of opcodes. """ from ...vm.interpreter import STACK_DEPTH_LIMIT, process_message if evm.message.depth + Uint(1) > STACK_DEPTH_LIMIT: - evm.gas_left += gas + evm.gas_left += params.gas push(evm.stack, U256(0)) return call_data = memory_read_bytes( - evm.memory, memory_input_start_position, memory_input_size + evm.memory, + params.memory_input_start_position, + params.memory_input_size, + ) + account_to_call = get_account( + evm.message.tx_env.state, params.code_address ) - account_to_call = get_account(evm.message.tx_env.state, code_address) code = get_code(evm.message.tx_env.state, account_to_call.code_hash) child_message = Message( block_env=evm.message.block_env, tx_env=evm.message.tx_env, - caller=caller, - target=to, - gas=gas, - value=value, + caller=params.caller, + target=params.to, + gas=params.gas, + value=params.value, data=call_data, code=code, - current_target=to, + current_target=params.to, depth=evm.message.depth + Uint(1), - code_address=code_address, + code_address=params.code_address, parent_evm=evm, ) child_evm = process_message(child_message) @@ -213,10 +227,12 @@ def generic_call( incorporate_child_on_success(evm, child_evm) push(evm.stack, U256(1)) - actual_output_size = min(memory_output_size, U256(len(child_evm.output))) + actual_output_size = min( + params.memory_output_size, U256(len(child_evm.output)) + ) memory_write( evm.memory, - memory_output_start_position, + params.memory_output_start_position, child_evm.output[:actual_output_size], ) @@ -267,15 +283,17 @@ def call(evm: Evm) -> None: else: generic_call( evm, - message_call_gas.sub_call, - value, - evm.message.current_target, - to, - code_address, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER @@ -327,15 +345,17 @@ def callcode(evm: Evm) -> None: else: generic_call( evm, - message_call_gas.sub_call, - value, - evm.message.current_target, - to, - code_address, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER diff --git a/src/ethereum/forks/gray_glacier/vm/instructions/system.py b/src/ethereum/forks/gray_glacier/vm/instructions/system.py index 89a0d310f51..05d4957faa8 100644 --- a/src/ethereum/forks/gray_glacier/vm/instructions/system.py +++ b/src/ethereum/forks/gray_glacier/vm/instructions/system.py @@ -11,6 +11,9 @@ Implementations of the EVM system related instructions. """ +from dataclasses import dataclass +from typing import final + from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.numeric import U256, Uint @@ -246,20 +249,27 @@ def return_(evm: Evm) -> None: pass -def generic_call( - evm: Evm, - gas: Uint, - value: U256, - caller: Address, - to: Address, - code_address: Address, - should_transfer_value: bool, - is_staticcall: bool, - memory_input_start_position: U256, - memory_input_size: U256, - memory_output_start_position: U256, - memory_output_size: U256, -) -> None: +@final +@dataclass +class GenericCall: + """ + Parameters for the core logic of the `CALL*` family of opcodes. + """ + + gas: Uint + value: U256 + caller: Address + to: Address + code_address: Address + should_transfer_value: bool + is_staticcall: bool + memory_input_start_position: U256 + memory_input_size: U256 + memory_output_start_position: U256 + memory_output_size: U256 + + +def generic_call(evm: Evm, params: GenericCall) -> None: """ Perform the core logic of the `CALL*` family of opcodes. """ @@ -268,29 +278,31 @@ def generic_call( evm.return_data = b"" if evm.message.depth + Uint(1) > STACK_DEPTH_LIMIT: - evm.gas_left += gas + evm.gas_left += params.gas push(evm.stack, U256(0)) return call_data = memory_read_bytes( - evm.memory, memory_input_start_position, memory_input_size + evm.memory, + params.memory_input_start_position, + params.memory_input_size, ) - account = get_account(evm.message.tx_env.state, code_address) + account = get_account(evm.message.tx_env.state, params.code_address) code = get_code(evm.message.tx_env.state, account.code_hash) child_message = Message( block_env=evm.message.block_env, tx_env=evm.message.tx_env, - caller=caller, - target=to, - gas=gas, - value=value, + caller=params.caller, + target=params.to, + gas=params.gas, + value=params.value, data=call_data, code=code, - current_target=to, + current_target=params.to, depth=evm.message.depth + Uint(1), - code_address=code_address, - should_transfer_value=should_transfer_value, - is_static=True if is_staticcall else evm.message.is_static, + code_address=params.code_address, + should_transfer_value=params.should_transfer_value, + is_static=params.is_staticcall or evm.message.is_static, accessed_addresses=evm.accessed_addresses.copy(), accessed_storage_keys=evm.accessed_storage_keys.copy(), parent_evm=evm, @@ -306,10 +318,12 @@ def generic_call( evm.return_data = child_evm.output push(evm.stack, U256(1)) - actual_output_size = min(memory_output_size, U256(len(child_evm.output))) + actual_output_size = min( + params.memory_output_size, U256(len(child_evm.output)) + ) memory_write( evm.memory, - memory_output_start_position, + params.memory_output_start_position, child_evm.output[:actual_output_size], ) @@ -358,8 +372,8 @@ def call(evm: Evm) -> None: value, gas, Uint(evm.gas_left), - extend_memory.cost, - access_gas_cost + create_gas_cost + transfer_gas_cost, + memory_cost=extend_memory.cost, + extra_gas=access_gas_cost + create_gas_cost + transfer_gas_cost, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) if evm.message.is_static and value != U256(0): @@ -375,17 +389,19 @@ def call(evm: Evm) -> None: else: generic_call( evm, - message_call_gas.sub_call, - value, - evm.message.current_target, - to, - code_address, - True, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER @@ -450,17 +466,19 @@ def callcode(evm: Evm) -> None: else: generic_call( evm, - message_call_gas.sub_call, - value, - evm.message.current_target, - to, - code_address, - True, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER @@ -574,17 +592,19 @@ def delegatecall(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by generic_call( evm, - message_call_gas.sub_call, - evm.message.value, - evm.message.caller, - evm.message.current_target, - code_address, - False, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=evm.message.value, + caller=evm.message.caller, + to=evm.message.current_target, + code_address=code_address, + should_transfer_value=False, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER @@ -639,17 +659,19 @@ def staticcall(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by generic_call( evm, - message_call_gas.sub_call, - U256(0), - evm.message.current_target, - to, - code_address, - True, - True, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=U256(0), + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=True, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER diff --git a/src/ethereum/forks/homestead/vm/instructions/system.py b/src/ethereum/forks/homestead/vm/instructions/system.py index 7fe3234231f..b13f23b1be8 100644 --- a/src/ethereum/forks/homestead/vm/instructions/system.py +++ b/src/ethereum/forks/homestead/vm/instructions/system.py @@ -11,6 +11,9 @@ Implementations of the EVM system related instructions. """ +from dataclasses import dataclass +from typing import final + from ethereum_types.bytes import Bytes0 from ethereum_types.numeric import U256, Uint @@ -164,47 +167,58 @@ def return_(evm: Evm) -> None: pass -def generic_call( - evm: Evm, - gas: Uint, - value: U256, - caller: Address, - to: Address, - code_address: Address, - should_transfer_value: bool, - memory_input_start_position: U256, - memory_input_size: U256, - memory_output_start_position: U256, - memory_output_size: U256, -) -> None: +@final +@dataclass +class GenericCall: + """ + Parameters for the core logic of the `CALL*` family of opcodes. + """ + + gas: Uint + value: U256 + caller: Address + to: Address + code_address: Address + should_transfer_value: bool + memory_input_start_position: U256 + memory_input_size: U256 + memory_output_start_position: U256 + memory_output_size: U256 + + +def generic_call(evm: Evm, params: GenericCall) -> None: """ Perform the core logic of the `CALL*` family of opcodes. """ from ...vm.interpreter import STACK_DEPTH_LIMIT, process_message if evm.message.depth + Uint(1) > STACK_DEPTH_LIMIT: - evm.gas_left += gas + evm.gas_left += params.gas push(evm.stack, U256(0)) return call_data = memory_read_bytes( - evm.memory, memory_input_start_position, memory_input_size + evm.memory, + params.memory_input_start_position, + params.memory_input_size, + ) + account_to_call = get_account( + evm.message.tx_env.state, params.code_address ) - account_to_call = get_account(evm.message.tx_env.state, code_address) code = get_code(evm.message.tx_env.state, account_to_call.code_hash) child_message = Message( block_env=evm.message.block_env, tx_env=evm.message.tx_env, - caller=caller, - target=to, - gas=gas, - value=value, + caller=params.caller, + target=params.to, + gas=params.gas, + value=params.value, data=call_data, code=code, - current_target=to, + current_target=params.to, depth=evm.message.depth + Uint(1), - code_address=code_address, - should_transfer_value=should_transfer_value, + code_address=params.code_address, + should_transfer_value=params.should_transfer_value, parent_evm=evm, ) child_evm = process_message(child_message) @@ -216,10 +230,12 @@ def generic_call( incorporate_child_on_success(evm, child_evm) push(evm.stack, U256(1)) - actual_output_size = min(memory_output_size, U256(len(child_evm.output))) + actual_output_size = min( + params.memory_output_size, U256(len(child_evm.output)) + ) memory_write( evm.memory, - memory_output_start_position, + params.memory_output_start_position, child_evm.output[:actual_output_size], ) @@ -270,16 +286,18 @@ def call(evm: Evm) -> None: else: generic_call( evm, - message_call_gas.sub_call, - value, - evm.message.current_target, - to, - code_address, - True, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER @@ -331,16 +349,18 @@ def callcode(evm: Evm) -> None: else: generic_call( evm, - message_call_gas.sub_call, - value, - evm.message.current_target, - to, - code_address, - True, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER @@ -437,16 +457,18 @@ def delegatecall(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by generic_call( evm, - gas, - evm.message.value, - evm.message.caller, - evm.message.current_target, - code_address, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=gas, + value=evm.message.value, + caller=evm.message.caller, + to=evm.message.current_target, + code_address=code_address, + should_transfer_value=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER diff --git a/src/ethereum/forks/istanbul/vm/instructions/system.py b/src/ethereum/forks/istanbul/vm/instructions/system.py index 8002d48698a..3ecdf779332 100644 --- a/src/ethereum/forks/istanbul/vm/instructions/system.py +++ b/src/ethereum/forks/istanbul/vm/instructions/system.py @@ -11,6 +11,9 @@ Implementations of the EVM system related instructions. """ +from dataclasses import dataclass +from typing import final + from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.numeric import U256, Uint @@ -246,20 +249,27 @@ def return_(evm: Evm) -> None: pass -def generic_call( - evm: Evm, - gas: Uint, - value: U256, - caller: Address, - to: Address, - code_address: Address, - should_transfer_value: bool, - is_staticcall: bool, - memory_input_start_position: U256, - memory_input_size: U256, - memory_output_start_position: U256, - memory_output_size: U256, -) -> None: +@final +@dataclass +class GenericCall: + """ + Parameters for the core logic of the `CALL*` family of opcodes. + """ + + gas: Uint + value: U256 + caller: Address + to: Address + code_address: Address + should_transfer_value: bool + is_staticcall: bool + memory_input_start_position: U256 + memory_input_size: U256 + memory_output_start_position: U256 + memory_output_size: U256 + + +def generic_call(evm: Evm, params: GenericCall) -> None: """ Perform the core logic of the `CALL*` family of opcodes. """ @@ -268,29 +278,31 @@ def generic_call( evm.return_data = b"" if evm.message.depth + Uint(1) > STACK_DEPTH_LIMIT: - evm.gas_left += gas + evm.gas_left += params.gas push(evm.stack, U256(0)) return call_data = memory_read_bytes( - evm.memory, memory_input_start_position, memory_input_size + evm.memory, + params.memory_input_start_position, + params.memory_input_size, ) - code_account = get_account(evm.message.tx_env.state, code_address) + code_account = get_account(evm.message.tx_env.state, params.code_address) code = get_code(evm.message.tx_env.state, code_account.code_hash) child_message = Message( block_env=evm.message.block_env, tx_env=evm.message.tx_env, - caller=caller, - target=to, - gas=gas, - value=value, + caller=params.caller, + target=params.to, + gas=params.gas, + value=params.value, data=call_data, code=code, - current_target=to, + current_target=params.to, depth=evm.message.depth + Uint(1), - code_address=code_address, - should_transfer_value=should_transfer_value, - is_static=True if is_staticcall else evm.message.is_static, + code_address=params.code_address, + should_transfer_value=params.should_transfer_value, + is_static=params.is_staticcall or evm.message.is_static, parent_evm=evm, ) child_evm = process_message(child_message) @@ -304,10 +316,12 @@ def generic_call( evm.return_data = child_evm.output push(evm.stack, U256(1)) - actual_output_size = min(memory_output_size, U256(len(child_evm.output))) + actual_output_size = min( + params.memory_output_size, U256(len(child_evm.output)) + ) memory_write( evm.memory, - memory_output_start_position, + params.memory_output_start_position, child_evm.output[:actual_output_size], ) @@ -350,8 +364,10 @@ def call(evm: Evm) -> None: value, gas, Uint(evm.gas_left), - extend_memory.cost, - GasCosts.OPCODE_CALL_BASE + create_gas_cost + transfer_gas_cost, + memory_cost=extend_memory.cost, + extra_gas=GasCosts.OPCODE_CALL_BASE + + create_gas_cost + + transfer_gas_cost, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) if evm.message.is_static and value != U256(0): @@ -367,17 +383,19 @@ def call(evm: Evm) -> None: else: generic_call( evm, - message_call_gas.sub_call, - value, - evm.message.current_target, - to, - code_address, - True, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER @@ -435,17 +453,19 @@ def callcode(evm: Evm) -> None: else: generic_call( evm, - message_call_gas.sub_call, - value, - evm.message.current_target, - to, - code_address, - True, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER @@ -563,17 +583,19 @@ def delegatecall(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by generic_call( evm, - message_call_gas.sub_call, - evm.message.value, - evm.message.caller, - evm.message.current_target, - code_address, - False, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=evm.message.value, + caller=evm.message.caller, + to=evm.message.current_target, + code_address=code_address, + should_transfer_value=False, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER @@ -622,17 +644,19 @@ def staticcall(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by generic_call( evm, - message_call_gas.sub_call, - U256(0), - evm.message.current_target, - to, - code_address, - True, - True, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=U256(0), + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=True, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER diff --git a/src/ethereum/forks/london/vm/instructions/system.py b/src/ethereum/forks/london/vm/instructions/system.py index 89a0d310f51..05d4957faa8 100644 --- a/src/ethereum/forks/london/vm/instructions/system.py +++ b/src/ethereum/forks/london/vm/instructions/system.py @@ -11,6 +11,9 @@ Implementations of the EVM system related instructions. """ +from dataclasses import dataclass +from typing import final + from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.numeric import U256, Uint @@ -246,20 +249,27 @@ def return_(evm: Evm) -> None: pass -def generic_call( - evm: Evm, - gas: Uint, - value: U256, - caller: Address, - to: Address, - code_address: Address, - should_transfer_value: bool, - is_staticcall: bool, - memory_input_start_position: U256, - memory_input_size: U256, - memory_output_start_position: U256, - memory_output_size: U256, -) -> None: +@final +@dataclass +class GenericCall: + """ + Parameters for the core logic of the `CALL*` family of opcodes. + """ + + gas: Uint + value: U256 + caller: Address + to: Address + code_address: Address + should_transfer_value: bool + is_staticcall: bool + memory_input_start_position: U256 + memory_input_size: U256 + memory_output_start_position: U256 + memory_output_size: U256 + + +def generic_call(evm: Evm, params: GenericCall) -> None: """ Perform the core logic of the `CALL*` family of opcodes. """ @@ -268,29 +278,31 @@ def generic_call( evm.return_data = b"" if evm.message.depth + Uint(1) > STACK_DEPTH_LIMIT: - evm.gas_left += gas + evm.gas_left += params.gas push(evm.stack, U256(0)) return call_data = memory_read_bytes( - evm.memory, memory_input_start_position, memory_input_size + evm.memory, + params.memory_input_start_position, + params.memory_input_size, ) - account = get_account(evm.message.tx_env.state, code_address) + account = get_account(evm.message.tx_env.state, params.code_address) code = get_code(evm.message.tx_env.state, account.code_hash) child_message = Message( block_env=evm.message.block_env, tx_env=evm.message.tx_env, - caller=caller, - target=to, - gas=gas, - value=value, + caller=params.caller, + target=params.to, + gas=params.gas, + value=params.value, data=call_data, code=code, - current_target=to, + current_target=params.to, depth=evm.message.depth + Uint(1), - code_address=code_address, - should_transfer_value=should_transfer_value, - is_static=True if is_staticcall else evm.message.is_static, + code_address=params.code_address, + should_transfer_value=params.should_transfer_value, + is_static=params.is_staticcall or evm.message.is_static, accessed_addresses=evm.accessed_addresses.copy(), accessed_storage_keys=evm.accessed_storage_keys.copy(), parent_evm=evm, @@ -306,10 +318,12 @@ def generic_call( evm.return_data = child_evm.output push(evm.stack, U256(1)) - actual_output_size = min(memory_output_size, U256(len(child_evm.output))) + actual_output_size = min( + params.memory_output_size, U256(len(child_evm.output)) + ) memory_write( evm.memory, - memory_output_start_position, + params.memory_output_start_position, child_evm.output[:actual_output_size], ) @@ -358,8 +372,8 @@ def call(evm: Evm) -> None: value, gas, Uint(evm.gas_left), - extend_memory.cost, - access_gas_cost + create_gas_cost + transfer_gas_cost, + memory_cost=extend_memory.cost, + extra_gas=access_gas_cost + create_gas_cost + transfer_gas_cost, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) if evm.message.is_static and value != U256(0): @@ -375,17 +389,19 @@ def call(evm: Evm) -> None: else: generic_call( evm, - message_call_gas.sub_call, - value, - evm.message.current_target, - to, - code_address, - True, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER @@ -450,17 +466,19 @@ def callcode(evm: Evm) -> None: else: generic_call( evm, - message_call_gas.sub_call, - value, - evm.message.current_target, - to, - code_address, - True, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER @@ -574,17 +592,19 @@ def delegatecall(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by generic_call( evm, - message_call_gas.sub_call, - evm.message.value, - evm.message.caller, - evm.message.current_target, - code_address, - False, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=evm.message.value, + caller=evm.message.caller, + to=evm.message.current_target, + code_address=code_address, + should_transfer_value=False, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER @@ -639,17 +659,19 @@ def staticcall(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by generic_call( evm, - message_call_gas.sub_call, - U256(0), - evm.message.current_target, - to, - code_address, - True, - True, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=U256(0), + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=True, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER diff --git a/src/ethereum/forks/muir_glacier/vm/instructions/system.py b/src/ethereum/forks/muir_glacier/vm/instructions/system.py index 8002d48698a..3ecdf779332 100644 --- a/src/ethereum/forks/muir_glacier/vm/instructions/system.py +++ b/src/ethereum/forks/muir_glacier/vm/instructions/system.py @@ -11,6 +11,9 @@ Implementations of the EVM system related instructions. """ +from dataclasses import dataclass +from typing import final + from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.numeric import U256, Uint @@ -246,20 +249,27 @@ def return_(evm: Evm) -> None: pass -def generic_call( - evm: Evm, - gas: Uint, - value: U256, - caller: Address, - to: Address, - code_address: Address, - should_transfer_value: bool, - is_staticcall: bool, - memory_input_start_position: U256, - memory_input_size: U256, - memory_output_start_position: U256, - memory_output_size: U256, -) -> None: +@final +@dataclass +class GenericCall: + """ + Parameters for the core logic of the `CALL*` family of opcodes. + """ + + gas: Uint + value: U256 + caller: Address + to: Address + code_address: Address + should_transfer_value: bool + is_staticcall: bool + memory_input_start_position: U256 + memory_input_size: U256 + memory_output_start_position: U256 + memory_output_size: U256 + + +def generic_call(evm: Evm, params: GenericCall) -> None: """ Perform the core logic of the `CALL*` family of opcodes. """ @@ -268,29 +278,31 @@ def generic_call( evm.return_data = b"" if evm.message.depth + Uint(1) > STACK_DEPTH_LIMIT: - evm.gas_left += gas + evm.gas_left += params.gas push(evm.stack, U256(0)) return call_data = memory_read_bytes( - evm.memory, memory_input_start_position, memory_input_size + evm.memory, + params.memory_input_start_position, + params.memory_input_size, ) - code_account = get_account(evm.message.tx_env.state, code_address) + code_account = get_account(evm.message.tx_env.state, params.code_address) code = get_code(evm.message.tx_env.state, code_account.code_hash) child_message = Message( block_env=evm.message.block_env, tx_env=evm.message.tx_env, - caller=caller, - target=to, - gas=gas, - value=value, + caller=params.caller, + target=params.to, + gas=params.gas, + value=params.value, data=call_data, code=code, - current_target=to, + current_target=params.to, depth=evm.message.depth + Uint(1), - code_address=code_address, - should_transfer_value=should_transfer_value, - is_static=True if is_staticcall else evm.message.is_static, + code_address=params.code_address, + should_transfer_value=params.should_transfer_value, + is_static=params.is_staticcall or evm.message.is_static, parent_evm=evm, ) child_evm = process_message(child_message) @@ -304,10 +316,12 @@ def generic_call( evm.return_data = child_evm.output push(evm.stack, U256(1)) - actual_output_size = min(memory_output_size, U256(len(child_evm.output))) + actual_output_size = min( + params.memory_output_size, U256(len(child_evm.output)) + ) memory_write( evm.memory, - memory_output_start_position, + params.memory_output_start_position, child_evm.output[:actual_output_size], ) @@ -350,8 +364,10 @@ def call(evm: Evm) -> None: value, gas, Uint(evm.gas_left), - extend_memory.cost, - GasCosts.OPCODE_CALL_BASE + create_gas_cost + transfer_gas_cost, + memory_cost=extend_memory.cost, + extra_gas=GasCosts.OPCODE_CALL_BASE + + create_gas_cost + + transfer_gas_cost, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) if evm.message.is_static and value != U256(0): @@ -367,17 +383,19 @@ def call(evm: Evm) -> None: else: generic_call( evm, - message_call_gas.sub_call, - value, - evm.message.current_target, - to, - code_address, - True, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER @@ -435,17 +453,19 @@ def callcode(evm: Evm) -> None: else: generic_call( evm, - message_call_gas.sub_call, - value, - evm.message.current_target, - to, - code_address, - True, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER @@ -563,17 +583,19 @@ def delegatecall(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by generic_call( evm, - message_call_gas.sub_call, - evm.message.value, - evm.message.caller, - evm.message.current_target, - code_address, - False, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=evm.message.value, + caller=evm.message.caller, + to=evm.message.current_target, + code_address=code_address, + should_transfer_value=False, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER @@ -622,17 +644,19 @@ def staticcall(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by generic_call( evm, - message_call_gas.sub_call, - U256(0), - evm.message.current_target, - to, - code_address, - True, - True, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=U256(0), + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=True, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER diff --git a/src/ethereum/forks/osaka/fork.py b/src/ethereum/forks/osaka/fork.py index fcdff89d045..69b586fde1d 100644 --- a/src/ethereum/forks/osaka/fork.py +++ b/src/ethereum/forks/osaka/fork.py @@ -872,7 +872,7 @@ def process_transaction( encode_transaction(tx), ) - intrinsic_gas, calldata_floor_gas_cost = validate_transaction(tx) + intrinsic = validate_transaction(tx) ( sender, @@ -895,7 +895,7 @@ def process_transaction( effective_gas_fee = tx.gas * effective_gas_price - gas = tx.gas - intrinsic_gas + gas = tx.gas - intrinsic.regular increment_nonce(tx_state, sender) sender_balance_after_gas_fee = ( @@ -944,7 +944,7 @@ def process_transaction( # Transactions with less execution_gas_used than the floor pay at the # floor cost. tx_gas_used_after_refund = max( - tx_gas_used_after_refund, calldata_floor_gas_cost + tx_gas_used_after_refund, intrinsic.calldata_floor ) tx_gas_left = tx.gas - tx_gas_used_after_refund diff --git a/src/ethereum/forks/osaka/transactions.py b/src/ethereum/forks/osaka/transactions.py index 18953538456..3e24a84f079 100644 --- a/src/ethereum/forks/osaka/transactions.py +++ b/src/ethereum/forks/osaka/transactions.py @@ -28,6 +28,23 @@ ) from .fork_types import Authorization, VersionedHash + +@final +@dataclass +class IntrinsicGasCost: + """Intrinsic gas costs for a transaction, split by gas type.""" + + regular: Uint + """Regular execution gas (calldata, base cost, access list, etc.).""" + + calldata_floor: Uint + """ + Minimum gas cost based on calldata size per [EIP-7623]. + + [EIP-7623]: https://eips.ethereum.org/EIPS/eip-7623 + """ + + TX_MAX_GAS_LIMIT = Uint(16_777_216) @@ -512,7 +529,7 @@ def decode_transaction(tx: LegacyTransaction | Bytes) -> Transaction: return tx -def validate_transaction(tx: Transaction) -> Tuple[Uint, Uint]: +def validate_transaction(tx: Transaction) -> IntrinsicGasCost: """ Verifies a transaction. @@ -543,8 +560,8 @@ def validate_transaction(tx: Transaction) -> Tuple[Uint, Uint]: """ from .vm.interpreter import MAX_INIT_CODE_SIZE - intrinsic_gas, calldata_floor_gas_cost = calculate_intrinsic_cost(tx) - if max(intrinsic_gas, calldata_floor_gas_cost) > tx.gas: + intrinsic = calculate_intrinsic_cost(tx) + if max(intrinsic.regular, intrinsic.calldata_floor) > tx.gas: raise InsufficientTransactionGasError("Insufficient gas") if U256(tx.nonce) >= U256(U64.MAX_VALUE): raise NonceOverflowError("Nonce too high") @@ -553,10 +570,10 @@ def validate_transaction(tx: Transaction) -> Tuple[Uint, Uint]: if tx.gas > TX_MAX_GAS_LIMIT: raise TransactionGasLimitExceededError("Gas limit too high") - return intrinsic_gas, calldata_floor_gas_cost + return intrinsic -def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: +def calculate_intrinsic_cost(tx: Transaction) -> IntrinsicGasCost: """ Calculates the gas that is charged before execution is started. @@ -613,15 +630,15 @@ def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: GasCosts.AUTH_PER_EMPTY_ACCOUNT * len(tx.authorizations) ) - return ( - Uint( + return IntrinsicGasCost( + regular=Uint( GasCosts.TX_BASE + data_cost + create_cost + access_list_cost + auth_cost ), - calldata_floor_gas_cost, + calldata_floor=calldata_floor_gas_cost, ) diff --git a/src/ethereum/forks/osaka/vm/instructions/system.py b/src/ethereum/forks/osaka/vm/instructions/system.py index cb4058774e2..5774be6002c 100644 --- a/src/ethereum/forks/osaka/vm/instructions/system.py +++ b/src/ethereum/forks/osaka/vm/instructions/system.py @@ -11,6 +11,9 @@ Implementations of the EVM system related instructions. """ +from dataclasses import dataclass +from typing import final + from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.numeric import U256, Uint @@ -268,22 +271,29 @@ def return_(evm: Evm) -> None: pass -def generic_call( - evm: Evm, - gas: Uint, - value: U256, - caller: Address, - to: Address, - code_address: Address, - should_transfer_value: bool, - is_staticcall: bool, - memory_input_start_position: U256, - memory_input_size: U256, - memory_output_start_position: U256, - memory_output_size: U256, - code: Bytes, - disable_precompiles: bool, -) -> None: +@final +@dataclass +class GenericCall: + """ + Parameters for the core logic of the `CALL*` family of opcodes. + """ + + gas: Uint + value: U256 + caller: Address + to: Address + code_address: Address + should_transfer_value: bool + is_staticcall: bool + memory_input_start_position: U256 + memory_input_size: U256 + memory_output_start_position: U256 + memory_output_size: U256 + code: Bytes + disable_precompiles: bool + + +def generic_call(evm: Evm, params: GenericCall) -> None: """ Perform the core logic of the `CALL*` family of opcodes. """ @@ -292,31 +302,33 @@ def generic_call( evm.return_data = b"" if evm.message.depth + Uint(1) > STACK_DEPTH_LIMIT: - evm.gas_left += gas + evm.gas_left += params.gas push(evm.stack, U256(0)) return call_data = memory_read_bytes( - evm.memory, memory_input_start_position, memory_input_size + evm.memory, + params.memory_input_start_position, + params.memory_input_size, ) child_message = Message( block_env=evm.message.block_env, tx_env=evm.message.tx_env, - caller=caller, - target=to, - gas=gas, - value=value, + caller=params.caller, + target=params.to, + gas=params.gas, + value=params.value, data=call_data, - code=code, - current_target=to, + code=params.code, + current_target=params.to, depth=evm.message.depth + Uint(1), - code_address=code_address, - should_transfer_value=should_transfer_value, - is_static=True if is_staticcall else evm.message.is_static, + code_address=params.code_address, + should_transfer_value=params.should_transfer_value, + is_static=params.is_staticcall or evm.message.is_static, accessed_addresses=evm.accessed_addresses.copy(), accessed_storage_keys=evm.accessed_storage_keys.copy(), - disable_precompiles=disable_precompiles, + disable_precompiles=params.disable_precompiles, parent_evm=evm, ) child_evm = process_message(child_message) @@ -330,10 +342,12 @@ def generic_call( evm.return_data = child_evm.output push(evm.stack, U256(1)) - actual_output_size = min(memory_output_size, U256(len(child_evm.output))) + actual_output_size = min( + params.memory_output_size, U256(len(child_evm.output)) + ) memory_write( evm.memory, - memory_output_start_position, + params.memory_output_start_position, child_evm.output[:actual_output_size], ) @@ -389,8 +403,8 @@ def call(evm: Evm) -> None: value, gas, Uint(evm.gas_left), - extend_memory.cost, - access_gas_cost + create_gas_cost + transfer_gas_cost, + memory_cost=extend_memory.cost, + extra_gas=access_gas_cost + create_gas_cost + transfer_gas_cost, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) if evm.message.is_static and value != U256(0): @@ -406,19 +420,21 @@ def call(evm: Evm) -> None: else: generic_call( evm, - message_call_gas.sub_call, - value, - evm.message.current_target, - to, - code_address, - True, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, - code, - disable_precompiles, + GenericCall( + gas=message_call_gas.sub_call, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + code=code, + disable_precompiles=disable_precompiles, + ), ) # PROGRAM COUNTER @@ -492,19 +508,21 @@ def callcode(evm: Evm) -> None: else: generic_call( evm, - message_call_gas.sub_call, - value, - evm.message.current_target, - to, - code_address, - True, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, - code, - disable_precompiles, + GenericCall( + gas=message_call_gas.sub_call, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + code=code, + disable_precompiles=disable_precompiles, + ), ) # PROGRAM COUNTER @@ -625,19 +643,21 @@ def delegatecall(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by generic_call( evm, - message_call_gas.sub_call, - evm.message.value, - evm.message.caller, - evm.message.current_target, - code_address, - False, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, - code, - disable_precompiles, + GenericCall( + gas=message_call_gas.sub_call, + value=evm.message.value, + caller=evm.message.caller, + to=evm.message.current_target, + code_address=code_address, + should_transfer_value=False, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + code=code, + disable_precompiles=disable_precompiles, + ), ) # PROGRAM COUNTER @@ -699,19 +719,21 @@ def staticcall(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by generic_call( evm, - message_call_gas.sub_call, - U256(0), - evm.message.current_target, - to, - code_address, - True, - True, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, - code, - disable_precompiles, + GenericCall( + gas=message_call_gas.sub_call, + value=U256(0), + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=True, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + code=code, + disable_precompiles=disable_precompiles, + ), ) # PROGRAM COUNTER diff --git a/src/ethereum/forks/paris/vm/instructions/system.py b/src/ethereum/forks/paris/vm/instructions/system.py index 447cf6410e2..3bff288ecf3 100644 --- a/src/ethereum/forks/paris/vm/instructions/system.py +++ b/src/ethereum/forks/paris/vm/instructions/system.py @@ -11,6 +11,9 @@ Implementations of the EVM system related instructions. """ +from dataclasses import dataclass +from typing import final + from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.numeric import U256, Uint @@ -245,20 +248,27 @@ def return_(evm: Evm) -> None: pass -def generic_call( - evm: Evm, - gas: Uint, - value: U256, - caller: Address, - to: Address, - code_address: Address, - should_transfer_value: bool, - is_staticcall: bool, - memory_input_start_position: U256, - memory_input_size: U256, - memory_output_start_position: U256, - memory_output_size: U256, -) -> None: +@final +@dataclass +class GenericCall: + """ + Parameters for the core logic of the `CALL*` family of opcodes. + """ + + gas: Uint + value: U256 + caller: Address + to: Address + code_address: Address + should_transfer_value: bool + is_staticcall: bool + memory_input_start_position: U256 + memory_input_size: U256 + memory_output_start_position: U256 + memory_output_size: U256 + + +def generic_call(evm: Evm, params: GenericCall) -> None: """ Perform the core logic of the `CALL*` family of opcodes. """ @@ -267,29 +277,33 @@ def generic_call( evm.return_data = b"" if evm.message.depth + Uint(1) > STACK_DEPTH_LIMIT: - evm.gas_left += gas + evm.gas_left += params.gas push(evm.stack, U256(0)) return call_data = memory_read_bytes( - evm.memory, memory_input_start_position, memory_input_size + evm.memory, + params.memory_input_start_position, + params.memory_input_size, ) tx_state = evm.message.tx_env.state - code = get_code(tx_state, get_account(tx_state, code_address).code_hash) + code = get_code( + tx_state, get_account(tx_state, params.code_address).code_hash + ) child_message = Message( block_env=evm.message.block_env, tx_env=evm.message.tx_env, - caller=caller, - target=to, - gas=gas, - value=value, + caller=params.caller, + target=params.to, + gas=params.gas, + value=params.value, data=call_data, code=code, - current_target=to, + current_target=params.to, depth=evm.message.depth + Uint(1), - code_address=code_address, - should_transfer_value=should_transfer_value, - is_static=True if is_staticcall else evm.message.is_static, + code_address=params.code_address, + should_transfer_value=params.should_transfer_value, + is_static=params.is_staticcall or evm.message.is_static, accessed_addresses=evm.accessed_addresses.copy(), accessed_storage_keys=evm.accessed_storage_keys.copy(), parent_evm=evm, @@ -305,10 +319,12 @@ def generic_call( evm.return_data = child_evm.output push(evm.stack, U256(1)) - actual_output_size = min(memory_output_size, U256(len(child_evm.output))) + actual_output_size = min( + params.memory_output_size, U256(len(child_evm.output)) + ) memory_write( evm.memory, - memory_output_start_position, + params.memory_output_start_position, child_evm.output[:actual_output_size], ) @@ -357,8 +373,8 @@ def call(evm: Evm) -> None: value, gas, Uint(evm.gas_left), - extend_memory.cost, - access_gas_cost + create_gas_cost + transfer_gas_cost, + memory_cost=extend_memory.cost, + extra_gas=access_gas_cost + create_gas_cost + transfer_gas_cost, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) if evm.message.is_static and value != U256(0): @@ -374,17 +390,19 @@ def call(evm: Evm) -> None: else: generic_call( evm, - message_call_gas.sub_call, - value, - evm.message.current_target, - to, - code_address, - True, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER @@ -449,17 +467,19 @@ def callcode(evm: Evm) -> None: else: generic_call( evm, - message_call_gas.sub_call, - value, - evm.message.current_target, - to, - code_address, - True, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER @@ -569,17 +589,19 @@ def delegatecall(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by generic_call( evm, - message_call_gas.sub_call, - evm.message.value, - evm.message.caller, - evm.message.current_target, - code_address, - False, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=evm.message.value, + caller=evm.message.caller, + to=evm.message.current_target, + code_address=code_address, + should_transfer_value=False, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER @@ -634,17 +656,19 @@ def staticcall(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by generic_call( evm, - message_call_gas.sub_call, - U256(0), - evm.message.current_target, - to, - code_address, - True, - True, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=U256(0), + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=True, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER diff --git a/src/ethereum/forks/prague/fork.py b/src/ethereum/forks/prague/fork.py index 19e8534db9f..34f67ce6177 100644 --- a/src/ethereum/forks/prague/fork.py +++ b/src/ethereum/forks/prague/fork.py @@ -855,7 +855,7 @@ def process_transaction( encode_transaction(tx), ) - intrinsic_gas, calldata_floor_gas_cost = validate_transaction(tx) + intrinsic = validate_transaction(tx) ( sender, @@ -878,7 +878,7 @@ def process_transaction( effective_gas_fee = tx.gas * effective_gas_price - gas = tx.gas - intrinsic_gas + gas = tx.gas - intrinsic.regular increment_nonce(tx_state, sender) sender_balance_after_gas_fee = ( @@ -927,7 +927,7 @@ def process_transaction( # Transactions with less execution_gas_used than the floor pay at the # floor cost. tx_gas_used_after_refund = max( - tx_gas_used_after_refund, calldata_floor_gas_cost + tx_gas_used_after_refund, intrinsic.calldata_floor ) tx_gas_left = tx.gas - tx_gas_used_after_refund diff --git a/src/ethereum/forks/prague/transactions.py b/src/ethereum/forks/prague/transactions.py index b85dfd6536f..30af712a4fd 100644 --- a/src/ethereum/forks/prague/transactions.py +++ b/src/ethereum/forks/prague/transactions.py @@ -25,6 +25,22 @@ from .fork_types import Authorization, VersionedHash +@final +@dataclass +class IntrinsicGasCost: + """Intrinsic gas costs for a transaction, split by gas type.""" + + regular: Uint + """Regular execution gas (calldata, base cost, access list, etc.).""" + + calldata_floor: Uint + """ + Minimum gas cost based on calldata size per [EIP-7623]. + + [EIP-7623]: https://eips.ethereum.org/EIPS/eip-7623 + """ + + @final @slotted_freezable @dataclass @@ -506,7 +522,7 @@ def decode_transaction(tx: LegacyTransaction | Bytes) -> Transaction: return tx -def validate_transaction(tx: Transaction) -> Tuple[Uint, Uint]: +def validate_transaction(tx: Transaction) -> IntrinsicGasCost: """ Verifies a transaction. @@ -537,18 +553,18 @@ def validate_transaction(tx: Transaction) -> Tuple[Uint, Uint]: """ from .vm.interpreter import MAX_INIT_CODE_SIZE - intrinsic_gas, calldata_floor_gas_cost = calculate_intrinsic_cost(tx) - if max(intrinsic_gas, calldata_floor_gas_cost) > tx.gas: + intrinsic = calculate_intrinsic_cost(tx) + if max(intrinsic.regular, intrinsic.calldata_floor) > tx.gas: raise InsufficientTransactionGasError("Insufficient gas") if U256(tx.nonce) >= U256(U64.MAX_VALUE): raise NonceOverflowError("Nonce too high") if tx.to == Bytes0(b"") and len(tx.data) > MAX_INIT_CODE_SIZE: raise InitCodeTooLargeError("Code size too large") - return intrinsic_gas, calldata_floor_gas_cost + return intrinsic -def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: +def calculate_intrinsic_cost(tx: Transaction) -> IntrinsicGasCost: """ Calculates the gas that is charged before execution is started. @@ -605,15 +621,15 @@ def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: GasCosts.AUTH_PER_EMPTY_ACCOUNT * len(tx.authorizations) ) - return ( - Uint( + return IntrinsicGasCost( + regular=Uint( GasCosts.TX_BASE + data_cost + create_cost + access_list_cost + auth_cost ), - calldata_floor_gas_cost, + calldata_floor=calldata_floor_gas_cost, ) diff --git a/src/ethereum/forks/prague/vm/instructions/system.py b/src/ethereum/forks/prague/vm/instructions/system.py index 3fdfe3e4386..30db9d8309f 100644 --- a/src/ethereum/forks/prague/vm/instructions/system.py +++ b/src/ethereum/forks/prague/vm/instructions/system.py @@ -11,6 +11,9 @@ Implementations of the EVM system related instructions. """ +from dataclasses import dataclass +from typing import final + from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.numeric import U256, Uint @@ -267,22 +270,29 @@ def return_(evm: Evm) -> None: pass -def generic_call( - evm: Evm, - gas: Uint, - value: U256, - caller: Address, - to: Address, - code_address: Address, - should_transfer_value: bool, - is_staticcall: bool, - memory_input_start_position: U256, - memory_input_size: U256, - memory_output_start_position: U256, - memory_output_size: U256, - code: Bytes, - disable_precompiles: bool, -) -> None: +@final +@dataclass +class GenericCall: + """ + Parameters for the core logic of the `CALL*` family of opcodes. + """ + + gas: Uint + value: U256 + caller: Address + to: Address + code_address: Address + should_transfer_value: bool + is_staticcall: bool + memory_input_start_position: U256 + memory_input_size: U256 + memory_output_start_position: U256 + memory_output_size: U256 + code: Bytes + disable_precompiles: bool + + +def generic_call(evm: Evm, params: GenericCall) -> None: """ Perform the core logic of the `CALL*` family of opcodes. """ @@ -291,31 +301,33 @@ def generic_call( evm.return_data = b"" if evm.message.depth + Uint(1) > STACK_DEPTH_LIMIT: - evm.gas_left += gas + evm.gas_left += params.gas push(evm.stack, U256(0)) return call_data = memory_read_bytes( - evm.memory, memory_input_start_position, memory_input_size + evm.memory, + params.memory_input_start_position, + params.memory_input_size, ) child_message = Message( block_env=evm.message.block_env, tx_env=evm.message.tx_env, - caller=caller, - target=to, - gas=gas, - value=value, + caller=params.caller, + target=params.to, + gas=params.gas, + value=params.value, data=call_data, - code=code, - current_target=to, + code=params.code, + current_target=params.to, depth=evm.message.depth + Uint(1), - code_address=code_address, - should_transfer_value=should_transfer_value, - is_static=True if is_staticcall else evm.message.is_static, + code_address=params.code_address, + should_transfer_value=params.should_transfer_value, + is_static=params.is_staticcall or evm.message.is_static, accessed_addresses=evm.accessed_addresses.copy(), accessed_storage_keys=evm.accessed_storage_keys.copy(), - disable_precompiles=disable_precompiles, + disable_precompiles=params.disable_precompiles, parent_evm=evm, ) child_evm = process_message(child_message) @@ -329,10 +341,12 @@ def generic_call( evm.return_data = child_evm.output push(evm.stack, U256(1)) - actual_output_size = min(memory_output_size, U256(len(child_evm.output))) + actual_output_size = min( + params.memory_output_size, U256(len(child_evm.output)) + ) memory_write( evm.memory, - memory_output_start_position, + params.memory_output_start_position, child_evm.output[:actual_output_size], ) @@ -388,8 +402,8 @@ def call(evm: Evm) -> None: value, gas, Uint(evm.gas_left), - extend_memory.cost, - access_gas_cost + create_gas_cost + transfer_gas_cost, + memory_cost=extend_memory.cost, + extra_gas=access_gas_cost + create_gas_cost + transfer_gas_cost, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) if evm.message.is_static and value != U256(0): @@ -405,19 +419,21 @@ def call(evm: Evm) -> None: else: generic_call( evm, - message_call_gas.sub_call, - value, - evm.message.current_target, - to, - code_address, - True, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, - code, - disable_precompiles, + GenericCall( + gas=message_call_gas.sub_call, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + code=code, + disable_precompiles=disable_precompiles, + ), ) # PROGRAM COUNTER @@ -490,19 +506,21 @@ def callcode(evm: Evm) -> None: else: generic_call( evm, - message_call_gas.sub_call, - value, - evm.message.current_target, - to, - code_address, - True, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, - code, - disable_precompiles, + GenericCall( + gas=message_call_gas.sub_call, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + code=code, + disable_precompiles=disable_precompiles, + ), ) # PROGRAM COUNTER @@ -618,19 +636,21 @@ def delegatecall(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by generic_call( evm, - message_call_gas.sub_call, - evm.message.value, - evm.message.caller, - evm.message.current_target, - code_address, - False, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, - code, - disable_precompiles, + GenericCall( + gas=message_call_gas.sub_call, + value=evm.message.value, + caller=evm.message.caller, + to=evm.message.current_target, + code_address=code_address, + should_transfer_value=False, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + code=code, + disable_precompiles=disable_precompiles, + ), ) # PROGRAM COUNTER @@ -692,19 +712,21 @@ def staticcall(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by generic_call( evm, - message_call_gas.sub_call, - U256(0), - evm.message.current_target, - to, - code_address, - True, - True, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, - code, - disable_precompiles, + GenericCall( + gas=message_call_gas.sub_call, + value=U256(0), + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=True, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + code=code, + disable_precompiles=disable_precompiles, + ), ) # PROGRAM COUNTER diff --git a/src/ethereum/forks/shanghai/vm/instructions/system.py b/src/ethereum/forks/shanghai/vm/instructions/system.py index 2527f02f12c..c703c775df0 100644 --- a/src/ethereum/forks/shanghai/vm/instructions/system.py +++ b/src/ethereum/forks/shanghai/vm/instructions/system.py @@ -11,6 +11,9 @@ Implementations of the EVM system related instructions. """ +from dataclasses import dataclass +from typing import final + from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.numeric import U256, Uint @@ -265,20 +268,27 @@ def return_(evm: Evm) -> None: pass -def generic_call( - evm: Evm, - gas: Uint, - value: U256, - caller: Address, - to: Address, - code_address: Address, - should_transfer_value: bool, - is_staticcall: bool, - memory_input_start_position: U256, - memory_input_size: U256, - memory_output_start_position: U256, - memory_output_size: U256, -) -> None: +@final +@dataclass +class GenericCall: + """ + Parameters for the core logic of the `CALL*` family of opcodes. + """ + + gas: Uint + value: U256 + caller: Address + to: Address + code_address: Address + should_transfer_value: bool + is_staticcall: bool + memory_input_start_position: U256 + memory_input_size: U256 + memory_output_start_position: U256 + memory_output_size: U256 + + +def generic_call(evm: Evm, params: GenericCall) -> None: """ Perform the core logic of the `CALL*` family of opcodes. """ @@ -287,29 +297,33 @@ def generic_call( evm.return_data = b"" if evm.message.depth + Uint(1) > STACK_DEPTH_LIMIT: - evm.gas_left += gas + evm.gas_left += params.gas push(evm.stack, U256(0)) return call_data = memory_read_bytes( - evm.memory, memory_input_start_position, memory_input_size + evm.memory, + params.memory_input_start_position, + params.memory_input_size, ) tx_state = evm.message.tx_env.state - code = get_code(tx_state, get_account(tx_state, code_address).code_hash) + code = get_code( + tx_state, get_account(tx_state, params.code_address).code_hash + ) child_message = Message( block_env=evm.message.block_env, tx_env=evm.message.tx_env, - caller=caller, - target=to, - gas=gas, - value=value, + caller=params.caller, + target=params.to, + gas=params.gas, + value=params.value, data=call_data, code=code, - current_target=to, + current_target=params.to, depth=evm.message.depth + Uint(1), - code_address=code_address, - should_transfer_value=should_transfer_value, - is_static=True if is_staticcall else evm.message.is_static, + code_address=params.code_address, + should_transfer_value=params.should_transfer_value, + is_static=params.is_staticcall or evm.message.is_static, accessed_addresses=evm.accessed_addresses.copy(), accessed_storage_keys=evm.accessed_storage_keys.copy(), parent_evm=evm, @@ -325,10 +339,12 @@ def generic_call( evm.return_data = child_evm.output push(evm.stack, U256(1)) - actual_output_size = min(memory_output_size, U256(len(child_evm.output))) + actual_output_size = min( + params.memory_output_size, U256(len(child_evm.output)) + ) memory_write( evm.memory, - memory_output_start_position, + params.memory_output_start_position, child_evm.output[:actual_output_size], ) @@ -377,8 +393,8 @@ def call(evm: Evm) -> None: value, gas, Uint(evm.gas_left), - extend_memory.cost, - access_gas_cost + create_gas_cost + transfer_gas_cost, + memory_cost=extend_memory.cost, + extra_gas=access_gas_cost + create_gas_cost + transfer_gas_cost, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) if evm.message.is_static and value != U256(0): @@ -394,17 +410,19 @@ def call(evm: Evm) -> None: else: generic_call( evm, - message_call_gas.sub_call, - value, - evm.message.current_target, - to, - code_address, - True, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER @@ -469,17 +487,19 @@ def callcode(evm: Evm) -> None: else: generic_call( evm, - message_call_gas.sub_call, - value, - evm.message.current_target, - to, - code_address, - True, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER @@ -589,17 +609,19 @@ def delegatecall(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by generic_call( evm, - message_call_gas.sub_call, - evm.message.value, - evm.message.caller, - evm.message.current_target, - code_address, - False, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=evm.message.value, + caller=evm.message.caller, + to=evm.message.current_target, + code_address=code_address, + should_transfer_value=False, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER @@ -654,17 +676,19 @@ def staticcall(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by generic_call( evm, - message_call_gas.sub_call, - U256(0), - evm.message.current_target, - to, - code_address, - True, - True, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=U256(0), + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=True, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER diff --git a/src/ethereum/forks/spurious_dragon/vm/instructions/system.py b/src/ethereum/forks/spurious_dragon/vm/instructions/system.py index b5ff4bb23bc..d12feed9990 100644 --- a/src/ethereum/forks/spurious_dragon/vm/instructions/system.py +++ b/src/ethereum/forks/spurious_dragon/vm/instructions/system.py @@ -11,6 +11,9 @@ Implementations of the EVM system related instructions. """ +from dataclasses import dataclass +from typing import final + from ethereum_types.bytes import Bytes0 from ethereum_types.numeric import U256, Uint @@ -167,47 +170,56 @@ def return_(evm: Evm) -> None: pass -def generic_call( - evm: Evm, - gas: Uint, - value: U256, - caller: Address, - to: Address, - code_address: Address, - should_transfer_value: bool, - memory_input_start_position: U256, - memory_input_size: U256, - memory_output_start_position: U256, - memory_output_size: U256, -) -> None: +@final +@dataclass +class GenericCall: + """ + Parameters for the core logic of the `CALL*` family of opcodes. + """ + + gas: Uint + value: U256 + caller: Address + to: Address + code_address: Address + should_transfer_value: bool + memory_input_start_position: U256 + memory_input_size: U256 + memory_output_start_position: U256 + memory_output_size: U256 + + +def generic_call(evm: Evm, params: GenericCall) -> None: """ Perform the core logic of the `CALL*` family of opcodes. """ from ...vm.interpreter import STACK_DEPTH_LIMIT, process_message if evm.message.depth + Uint(1) > STACK_DEPTH_LIMIT: - evm.gas_left += gas + evm.gas_left += params.gas push(evm.stack, U256(0)) return call_data = memory_read_bytes( - evm.memory, memory_input_start_position, memory_input_size + evm.memory, + params.memory_input_start_position, + params.memory_input_size, ) - account = get_account(evm.message.tx_env.state, code_address) + account = get_account(evm.message.tx_env.state, params.code_address) code = get_code(evm.message.tx_env.state, account.code_hash) child_message = Message( block_env=evm.message.block_env, tx_env=evm.message.tx_env, - caller=caller, - target=to, - gas=gas, - value=value, + caller=params.caller, + target=params.to, + gas=params.gas, + value=params.value, data=call_data, code=code, - current_target=to, + current_target=params.to, depth=evm.message.depth + Uint(1), - code_address=code_address, - should_transfer_value=should_transfer_value, + code_address=params.code_address, + should_transfer_value=params.should_transfer_value, parent_evm=evm, ) child_evm = process_message(child_message) @@ -219,10 +231,12 @@ def generic_call( incorporate_child_on_success(evm, child_evm) push(evm.stack, U256(1)) - actual_output_size = min(memory_output_size, U256(len(child_evm.output))) + actual_output_size = min( + params.memory_output_size, U256(len(child_evm.output)) + ) memory_write( evm.memory, - memory_output_start_position, + params.memory_output_start_position, child_evm.output[:actual_output_size], ) @@ -265,8 +279,10 @@ def call(evm: Evm) -> None: value, gas, Uint(evm.gas_left), - extend_memory.cost, - GasCosts.OPCODE_CALL_BASE + create_gas_cost + transfer_gas_cost, + memory_cost=extend_memory.cost, + extra_gas=GasCosts.OPCODE_CALL_BASE + + create_gas_cost + + transfer_gas_cost, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) @@ -281,16 +297,18 @@ def call(evm: Evm) -> None: else: generic_call( evm, - message_call_gas.sub_call, - value, - evm.message.current_target, - to, - code_address, - True, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER @@ -347,16 +365,18 @@ def callcode(evm: Evm) -> None: else: generic_call( evm, - message_call_gas.sub_call, - value, - evm.message.current_target, - to, - code_address, - True, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER @@ -471,16 +491,18 @@ def delegatecall(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by generic_call( evm, - message_call_gas.sub_call, - evm.message.value, - evm.message.caller, - evm.message.current_target, - code_address, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=evm.message.value, + caller=evm.message.caller, + to=evm.message.current_target, + code_address=code_address, + should_transfer_value=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER diff --git a/src/ethereum/forks/tangerine_whistle/vm/instructions/system.py b/src/ethereum/forks/tangerine_whistle/vm/instructions/system.py index f09665e1701..8f30a357288 100644 --- a/src/ethereum/forks/tangerine_whistle/vm/instructions/system.py +++ b/src/ethereum/forks/tangerine_whistle/vm/instructions/system.py @@ -11,6 +11,9 @@ Implementations of the EVM system related instructions. """ +from dataclasses import dataclass +from typing import final + from ethereum_types.bytes import Bytes0 from ethereum_types.numeric import U256, Uint @@ -166,47 +169,56 @@ def return_(evm: Evm) -> None: pass -def generic_call( - evm: Evm, - gas: Uint, - value: U256, - caller: Address, - to: Address, - code_address: Address, - should_transfer_value: bool, - memory_input_start_position: U256, - memory_input_size: U256, - memory_output_start_position: U256, - memory_output_size: U256, -) -> None: +@final +@dataclass +class GenericCall: + """ + Parameters for the core logic of the `CALL*` family of opcodes. + """ + + gas: Uint + value: U256 + caller: Address + to: Address + code_address: Address + should_transfer_value: bool + memory_input_start_position: U256 + memory_input_size: U256 + memory_output_start_position: U256 + memory_output_size: U256 + + +def generic_call(evm: Evm, params: GenericCall) -> None: """ Perform the core logic of the `CALL*` family of opcodes. """ from ...vm.interpreter import STACK_DEPTH_LIMIT, process_message if evm.message.depth + Uint(1) > STACK_DEPTH_LIMIT: - evm.gas_left += gas + evm.gas_left += params.gas push(evm.stack, U256(0)) return call_data = memory_read_bytes( - evm.memory, memory_input_start_position, memory_input_size + evm.memory, + params.memory_input_start_position, + params.memory_input_size, ) - account = get_account(evm.message.tx_env.state, code_address) + account = get_account(evm.message.tx_env.state, params.code_address) code = get_code(evm.message.tx_env.state, account.code_hash) child_message = Message( block_env=evm.message.block_env, tx_env=evm.message.tx_env, - caller=caller, - target=to, - gas=gas, - value=value, + caller=params.caller, + target=params.to, + gas=params.gas, + value=params.value, data=call_data, code=code, - current_target=to, + current_target=params.to, depth=evm.message.depth + Uint(1), - code_address=code_address, - should_transfer_value=should_transfer_value, + code_address=params.code_address, + should_transfer_value=params.should_transfer_value, parent_evm=evm, ) child_evm = process_message(child_message) @@ -218,10 +230,12 @@ def generic_call( incorporate_child_on_success(evm, child_evm) push(evm.stack, U256(1)) - actual_output_size = min(memory_output_size, U256(len(child_evm.output))) + actual_output_size = min( + params.memory_output_size, U256(len(child_evm.output)) + ) memory_write( evm.memory, - memory_output_start_position, + params.memory_output_start_position, child_evm.output[:actual_output_size], ) @@ -263,8 +277,10 @@ def call(evm: Evm) -> None: value, gas, Uint(evm.gas_left), - extend_memory.cost, - GasCosts.OPCODE_CALL_BASE + create_gas_cost + transfer_gas_cost, + memory_cost=extend_memory.cost, + extra_gas=GasCosts.OPCODE_CALL_BASE + + create_gas_cost + + transfer_gas_cost, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) @@ -279,16 +295,18 @@ def call(evm: Evm) -> None: else: generic_call( evm, - message_call_gas.sub_call, - value, - evm.message.current_target, - to, - code_address, - True, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER @@ -345,16 +363,18 @@ def callcode(evm: Evm) -> None: else: generic_call( evm, - message_call_gas.sub_call, - value, - evm.message.current_target, - to, - code_address, - True, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER @@ -460,16 +480,18 @@ def delegatecall(evm: Evm) -> None: evm.memory += b"\x00" * extend_memory.expand_by generic_call( evm, - message_call_gas.sub_call, - evm.message.value, - evm.message.caller, - evm.message.current_target, - code_address, - False, - memory_input_start_position, - memory_input_size, - memory_output_start_position, - memory_output_size, + GenericCall( + gas=message_call_gas.sub_call, + value=evm.message.value, + caller=evm.message.caller, + to=evm.message.current_target, + code_address=code_address, + should_transfer_value=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + ), ) # PROGRAM COUNTER diff --git a/src/ethereum/trace.py b/src/ethereum/trace.py index f40644dcc54..72d40afe44f 100644 --- a/src/ethereum/trace.py +++ b/src/ethereum/trace.py @@ -160,6 +160,19 @@ class GasAndRefund: """ +@final +@dataclass +class StateGasAndRefund: + """ + Trace event that is triggered when state gas is deducted. + """ + + state_gas_cost: int + """ + Amount of state gas charged. + """ + + TraceEvent = ( TransactionStart | TransactionEnd @@ -170,6 +183,7 @@ class GasAndRefund: | OpException | EvmStop | GasAndRefund + | StateGasAndRefund ) """ All possible types of events that an [`EvmTracer`] is expected to handle. diff --git a/src/ethereum_spec_tools/evm_tools/t8n/evm_trace/eip3155.py b/src/ethereum_spec_tools/evm_tools/t8n/evm_trace/eip3155.py index 9e89598532a..9f503c85154 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/evm_trace/eip3155.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/evm_trace/eip3155.py @@ -18,14 +18,25 @@ OpStart, PrecompileEnd, PrecompileStart, + StateGasAndRefund, TraceEvent, TransactionEnd, TransactionStart, ) -from .protocols import Evm, EvmWithReturnData, TransactionEnvironment +from .protocols import ( + Evm, + EvmWithReturnData, + EvmWithStateGas, + TransactionEnvironment, +) -EXCLUDE_FROM_OUTPUT = ["gasCostTraced", "errorTraced", "precompile"] +EXCLUDE_FROM_OUTPUT = [ + "gasCostTraced", + "stateGasCostTraced", + "errorTraced", + "precompile", +] @dataclass @@ -45,7 +56,10 @@ class Trace: depth: int refund: int opName: str + stateGas: Optional[str] = None + stateGasCost: Optional[str] = None gasCostTraced: bool = False + stateGasCostTraced: bool = False errorTraced: bool = False precompile: bool = False error: Optional[str] = None @@ -171,11 +185,17 @@ def __call__(self, evm: Any, event: TraceEvent) -> None: assert isinstance(last_trace, Trace) last_trace.gasCostTraced = True + last_trace.stateGasCostTraced = True last_trace.errorTraced = True elif isinstance(event, OpStart): op = event.op.value if op == "InvalidOpcode": op = "Invalid" + + state_gas = None + if isinstance(evm, EvmWithStateGas): + state_gas = hex(evm.state_gas_left) + new_trace = Trace( pc=int(evm.pc), op=op, @@ -188,6 +208,7 @@ def __call__(self, evm: Any, event: TraceEvent) -> None: depth=int(evm.message.depth) + 1, refund=refund_counter, opName=str(event.op).split(".")[-1], + stateGas=state_gas, ) self.active_traces.append(new_trace) @@ -195,6 +216,7 @@ def __call__(self, evm: Any, event: TraceEvent) -> None: assert isinstance(last_trace, Trace) last_trace.gasCostTraced = True + last_trace.stateGasCostTraced = True last_trace.errorTraced = True elif isinstance(event, OpException): if last_trace is not None: @@ -264,6 +286,15 @@ def __call__(self, evm: Any, event: TraceEvent) -> None: last_trace.gasCost = hex(event.gas_cost) last_trace.refund = refund_counter last_trace.gasCostTraced = True + elif isinstance(event, StateGasAndRefund): + if len(self.active_traces) == 0: + return + + assert isinstance(last_trace, Trace) + + if not last_trace.stateGasCostTraced: + last_trace.stateGasCost = hex(event.state_gas_cost) + last_trace.stateGasCostTraced = True class _TraceJsonEncoder(json.JSONEncoder): diff --git a/src/ethereum_spec_tools/evm_tools/t8n/evm_trace/protocols.py b/src/ethereum_spec_tools/evm_tools/t8n/evm_trace/protocols.py index d57fd1f9214..74ec4cb0cb3 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/evm_trace/protocols.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/evm_trace/protocols.py @@ -52,3 +52,12 @@ class EvmWithReturnData(Evm, Protocol): """ return_data: Bytes + + +@runtime_checkable +class EvmWithStateGas(EvmWithReturnData, Protocol): + """ + The class describes the EVM interface for forks with state gas (EIP-8037). + """ + + state_gas_left: Uint diff --git a/src/ethereum_spec_tools/evm_tools/t8n/t8n_types.py b/src/ethereum_spec_tools/evm_tools/t8n/t8n_types.py index fb3139c7b5c..9a5a824e76b 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/t8n_types.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/t8n_types.py @@ -309,6 +309,9 @@ def update(self, t8n: "T8N", block_env: Any, block_output: Any) -> None: Update the result after processing the inputs. """ self.gas_used = block_output.block_gas_used + if hasattr(block_output, "block_state_gas_used"): + if block_output.block_state_gas_used > self.gas_used: + self.gas_used = block_output.block_state_gas_used self.tx_root = root(block_output.transactions_trie) self.receipt_root = root(block_output.receipts_trie) self.bloom = t8n.fork.logs_bloom(block_output.block_logs) diff --git a/tests/amsterdam/eip7708_eth_transfer_logs/spec.py b/tests/amsterdam/eip7708_eth_transfer_logs/spec.py index f95378a6380..9d08cb17eaf 100644 --- a/tests/amsterdam/eip7708_eth_transfer_logs/spec.py +++ b/tests/amsterdam/eip7708_eth_transfer_logs/spec.py @@ -14,7 +14,7 @@ class ReferenceSpec: ref_spec_7708 = ReferenceSpec( - "EIPS/eip-7708.md", "43a7f15cd1105f308086bed6a61e3155039271fc" + "EIPS/eip-7708.md", "172188d7b090ed1afb876140f45e19ac00cba4bb" ) diff --git a/tests/amsterdam/eip7708_eth_transfer_logs/test_burn_logs.py b/tests/amsterdam/eip7708_eth_transfer_logs/test_burn_logs.py index f8f8c5eeeff..1776fde3276 100644 --- a/tests/amsterdam/eip7708_eth_transfer_logs/test_burn_logs.py +++ b/tests/amsterdam/eip7708_eth_transfer_logs/test_burn_logs.py @@ -84,6 +84,7 @@ def test_selfdestruct_to_self_same_tx( state_test: StateTestFiller, env: Environment, pre: Alloc, + fork: Fork, sender: EOA, contract_balance: int, create_opcode: Op, @@ -126,7 +127,9 @@ def test_selfdestruct_to_self_same_tx( sender=sender, to=factory, value=contract_balance, - gas_limit=200_000, + # Same-tx CREATE+SELFDESTRUCT charges NEW_ACCOUNT state gas + # under EIP-8037 (0 otherwise). + gas_limit=200_000 + fork.gas_costs().NEW_ACCOUNT, expected_receipt=TransactionReceipt(logs=expected_logs), ) @@ -145,6 +148,7 @@ def test_selfdestruct_to_different_address_same_tx( state_test: StateTestFiller, env: Environment, pre: Alloc, + fork: Fork, sender: EOA, contract_balance: int, create_opcode: Op, @@ -190,7 +194,9 @@ def test_selfdestruct_to_different_address_same_tx( sender=sender, to=factory, value=contract_balance, - gas_limit=200_000, + # Same-tx CREATE+SELFDESTRUCT charges NEW_ACCOUNT state gas + # under EIP-8037 (0 otherwise). + gas_limit=200_000 + fork.gas_costs().NEW_ACCOUNT, expected_receipt=TransactionReceipt(logs=expected_logs), ) @@ -223,6 +229,7 @@ def test_selfdestruct_same_tx_via_call( state_test: StateTestFiller, env: Environment, pre: Alloc, + fork: Fork, sender: EOA, to_self: bool, call_twice: bool, @@ -316,7 +323,11 @@ def test_selfdestruct_same_tx_via_call( sender=sender, to=factory, value=0, - gas_limit=300_000, + # Same-tx CREATE+CALL+SELFDESTRUCT with SSTOREs for verification. + # Under EIP-8037 the SSTORE state writes and the SELFDESTRUCT + # NEW_ACCOUNT charge are paid from the shared limit; bump to + # 1_000_000 plus NEW_ACCOUNT to cover both dimensions. + gas_limit=1_000_000 + fork.gas_costs().NEW_ACCOUNT, expected_receipt=TransactionReceipt(logs=expected_logs), ) @@ -514,7 +525,7 @@ def test_finalization_burn_logs( to=None, value=0, data=factory_code, - gas_limit=1_000_000, + gas_limit=2_000_000, expected_receipt=TransactionReceipt( logs=execution_logs + finalization_logs ), @@ -891,15 +902,12 @@ def test_selfdestruct_finalization_after_priority_fee( # finalization burn log if fork.is_eip_enabled(8037): - raise Exception( - "Test needs update: recompute exact gas usage with 8037" - ) - + # TODO: Fix calculation of the exact expected gas usage + finalization_balance = None expected_logs.append(burn_log(created_address, finalization_balance)) gas_limit = 500_000 if fork.is_eip_enabled(8037): gas_limit = 2_000_000 - tx = Transaction( sender=sender, to=None, diff --git a/tests/amsterdam/eip7708_eth_transfer_logs/test_fork_transition.py b/tests/amsterdam/eip7708_eth_transfer_logs/test_fork_transition.py index 4125f1a8273..3c95d3b6628 100644 --- a/tests/amsterdam/eip7708_eth_transfer_logs/test_fork_transition.py +++ b/tests/amsterdam/eip7708_eth_transfer_logs/test_fork_transition.py @@ -11,6 +11,7 @@ Alloc, Block, BlockchainTestFiller, + Fork, Op, Transaction, TransactionReceipt, @@ -35,6 +36,7 @@ def test_burn_log_at_fork_transition( blockchain_test: BlockchainTestFiller, pre: Alloc, + fork: Fork, same_tx: bool, to_self: bool, ) -> None: @@ -117,6 +119,17 @@ def test_burn_log_at_fork_transition( beneficiary: Account(balance=contract_balance * 3), } + # `fork` is a TransitionFork here; resolve to the post-transition + # fork (where the larger NEW_ACCOUNT applies) so the gas budget + # covers the same-tx CREATE+SELFDESTRUCT on the post-transition + # block. The pre-transition block has plenty of headroom. + pre_transition_timestamp = 14_999 + transition_timestamp = 15_000 + post_transition_timestamp = 15_001 + post_fork = fork.fork_at(timestamp=post_transition_timestamp) + gas_limit = 200_000 + if post_fork.is_eip_enabled(8037): + gas_limit += post_fork.gas_costs().NEW_ACCOUNT blocks = [ Block( timestamp=ts, @@ -124,12 +137,18 @@ def test_burn_log_at_fork_transition( Transaction( to=targets[i], sender=sender, - gas_limit=200_000, + gas_limit=gas_limit, expected_receipt=TransactionReceipt(logs=expected_logs[i]), ) ], ) - for i, ts in enumerate([14_999, 15_000, 15_001]) + for i, ts in enumerate( + [ + pre_transition_timestamp, + transition_timestamp, + post_transition_timestamp, + ] + ) ] blockchain_test(pre=pre, blocks=blocks, post=post) diff --git a/tests/amsterdam/eip7708_eth_transfer_logs/test_transfer_logs.py b/tests/amsterdam/eip7708_eth_transfer_logs/test_transfer_logs.py index 61c216406f8..ddceefb56dc 100644 --- a/tests/amsterdam/eip7708_eth_transfer_logs/test_transfer_logs.py +++ b/tests/amsterdam/eip7708_eth_transfer_logs/test_transfer_logs.py @@ -180,6 +180,7 @@ def test_contract_creation_tx_collision( state_test: StateTestFiller, env: Environment, pre: Alloc, + fork: Fork, collision_nonce: int, collision_code: bytes, ) -> None: @@ -192,11 +193,17 @@ def test_contract_creation_tx_collision( value transfer, so EIP-7708 emits no Transfer log. """ sender = pre.fund_eoa() + # EIP-8037: a contract-creating tx charges intrinsic state gas for the + # new account, so the gas limit must cover it on top of the regular + # intrinsic cost. + gas_limit = 200_000 + if fork.is_eip_enabled(8037): + gas_limit += fork.create_state_gas() tx = Transaction( sender=sender, to=None, value=1000, - gas_limit=200_000, + gas_limit=gas_limit, data=bytes(Op.RETURN(0, 0)), expected_receipt=TransactionReceipt(logs=[]), ) @@ -1081,6 +1088,7 @@ def test_nested_calls_log_order( state_test: StateTestFiller, env: Environment, pre: Alloc, + fork: Fork, sender: EOA, call_depth: int, ) -> None: @@ -1091,11 +1099,14 @@ def test_nested_calls_log_order( # Build the chain from innermost outward by prepending each new caller. # Once finished, accounts[0] is the entry contract (the tx target) and # accounts[-1] is the final recipient. + # Forward all gas (`Op.GAS`) rather than a fixed amount: under EIP-8037 + # each frame's per-frame `SSTORE` and the deepest `NEW_ACCOUNT` charge + # make a fixed forward too small to reach the chain depth. accounts: list[Address] = [pre.nonexistent_account()] for _ in range(call_depth): contract_code = Op.SSTORE( 0, - Op.CALL(gas=500_000, address=accounts[0], value=transfer_value), + Op.CALL(gas=Op.GAS, address=accounts[0], value=transfer_value), ) accounts.insert( 0, pre.deploy_contract(contract_code, balance=transfer_value) @@ -1116,7 +1127,7 @@ def test_nested_calls_log_order( sender=sender, to=entry_contract, value=tx_value, - gas_limit=1_000_000, + gas_limit=fork.transaction_gas_limit_cap(), expected_receipt=TransactionReceipt(logs=expected_logs), ) @@ -1258,6 +1269,7 @@ def test_transfer_with_all_tx_types( state_test: StateTestFiller, env: Environment, pre: Alloc, + fork: Fork, sender: EOA, typed_transaction: Transaction, ) -> None: @@ -1265,9 +1277,12 @@ def test_transfer_with_all_tx_types( recipient = pre.nonexistent_account() transfer_amount = 1000 + # Sending value to a nonexistent recipient charges NEW_ACCOUNT + # state gas under EIP-8037 (0 otherwise). tx = typed_transaction.copy( to=recipient, value=transfer_amount, + gas_limit=typed_transaction.gas_limit + fork.gas_costs().NEW_ACCOUNT, expected_receipt=TransactionReceipt( logs=[transfer_log(sender, recipient, transfer_amount)] ), diff --git a/tests/amsterdam/eip7778_block_gas_accounting_without_refunds/test_gas_accounting.py b/tests/amsterdam/eip7778_block_gas_accounting_without_refunds/test_gas_accounting.py index dfead7f5006..fff2265d819 100644 --- a/tests/amsterdam/eip7778_block_gas_accounting_without_refunds/test_gas_accounting.py +++ b/tests/amsterdam/eip7778_block_gas_accounting_without_refunds/test_gas_accounting.py @@ -40,7 +40,7 @@ def build_refund_tx( call_data: bytes = b"", refund_tx_has_extra_gas_limit: bool = False, exceed_block_gas_limit: bool = False, -) -> Tuple[int, int, int, Transaction]: +) -> Tuple[int, int, int, int, Transaction]: """Build a transaction that has different refund types from a fork.""" # All essential calc functions intrinsic_cost_calc = fork.transaction_intrinsic_cost_calculator() @@ -61,7 +61,12 @@ def build_refund_tx( empty_storage_on_success = False refund_tx_extra_gas = 1 if refund_tx_has_extra_gas_limit else 0 - for refund_type in sorted(refund_types, key=lambda r: r.value): + # EIP-8037: existing authority "refund" adjusts intrinsic_state_gas, + # not the standard refund counter. + auth_state_gas = 0 + auth_state_refund = 0 + + for refund_type in refund_types: match refund_type: case RefundTypes.STORAGE_CLEAR: for slot in storage_slots: @@ -77,15 +82,24 @@ def build_refund_tx( case RefundTypes.AUTHORIZATION_EXISTING_AUTHORITY: code += Op.PUSH0 delegated_contract = pre.deploy_contract(code=Bytecode()) + authority_signers = [ + pre.fund_eoa(amount=1) for _ in range(refunds_count) + ] authorization_list = [ AuthorizationTuple( address=delegated_contract, nonce=0, - signer=pre.fund_eoa(amount=1), + signer=signer, ) - for _ in range(refunds_count) + for signer in authority_signers ] - refund_counter += ( + post[delegated_contract] = Account(code=Bytecode()) + for signer in authority_signers: + post[signer] = Account(balance=1) + auth_state_gas = fork.transaction_intrinsic_state_gas( + authorization_count=refunds_count, + ) + auth_state_refund = ( gsc.REFUND_AUTH_PER_EXISTING_ACCOUNT * refunds_count ) case _: @@ -101,30 +115,50 @@ def build_refund_tx( storage=dict.fromkeys(storage_slots, 1), ) - gas_used_pre_refund = intrinsic_cost_calc( + # Combined gas (regular + state) from intrinsic cost calculator + combined_gas_used = intrinsic_cost_calc( calldata=call_data, return_cost_deducted_prior_execution=True, authorization_list_or_count=authorization_list, ) + code.gas_cost(fork) + # EIP-8037: block gas_used only counts regular gas + gas_used_pre_refund = combined_gas_used - auth_state_gas + # Calculate refund (still applied to user's balance) if not refund_tx_reverts: refund_counter += code.refund(fork) + # EIP-8037: remaining state gas = intrinsic state gas - state gas + # returned to reservoir for existing authorities + remaining_state_gas = auth_state_gas - auth_state_refund + + # In the spec, the refund cap uses tx_gas_used_before_refund which is + # tx.gas - gas_left - state_gas_left (combined regular + remaining + # state). + combined_before_refund = gas_used_pre_refund + remaining_state_gas + effective_refund = min( - refund_counter, gas_used_pre_refund // max_refund_quotient + refund_counter, combined_before_refund // max_refund_quotient ) - gas_used_post_refund = gas_used_pre_refund - effective_refund + receipt_gas_used = combined_before_refund - effective_refund call_data_floor_cost = data_floor_calc(data=call_data) - refund_tx_block_gas_used = max(call_data_floor_cost, gas_used_pre_refund) + # gas_used_post_refund is the "combined after refund" value used for + # calldata floor comparisons and balance computation + gas_used_post_refund = receipt_gas_used refund_tx_gas_used = max(call_data_floor_cost, gas_used_post_refund) + # gas_limit must cover combined gas (regular + state) + refund_tx_gas_limit = ( + max(call_data_floor_cost, combined_gas_used) + refund_tx_extra_gas + ) + # Build refund transaction refund_tx = Transaction( to=contract_address, data=call_data, - gas_limit=refund_tx_block_gas_used + refund_tx_extra_gas, + gas_limit=refund_tx_gas_limit, sender=refund_tx_sender, authorization_list=authorization_list, expected_receipt={ @@ -160,9 +194,14 @@ def build_refund_tx( if not exceed_block_gas_limit: post[refund_tx_sender] = Account(balance=expected_balance) + # block_state_gas_used reflects intrinsic_state minus the + # existing-authority auth refund (state_refund), since + # `process_transaction` deducts it from `tx_state_gas` before + # accumulating into `block_state_gas_used`. return ( - gas_used_post_refund, + receipt_gas_used, gas_used_pre_refund, + remaining_state_gas, call_data_floor_cost, refund_tx, ) @@ -190,18 +229,24 @@ def test_simple_gas_accounting( post = Alloc() - (_, gas_used_pre_refund, call_data_floor_cost, refund_tx) = ( - build_refund_tx( - fork=fork, - pre=pre, - post=post, - refund_types={refund_type}, - refunds_count=refunds_count, - refund_tx_reverts=refund_tx_reverts, - ) + ( + _, + gas_used_pre_refund, + tx_state_gas, + call_data_floor_cost, + refund_tx, + ) = build_refund_tx( + fork=fork, + pre=pre, + post=post, + refund_types={refund_type}, + refunds_count=refunds_count, + refund_tx_reverts=refund_tx_reverts, ) - refund_tx_block_gas_used = max(gas_used_pre_refund, call_data_floor_cost) + # EIP-8037: block gas_used = max(block_regular_gas, block_state_gas) + block_regular = max(gas_used_pre_refund, call_data_floor_cost) + refund_tx_block_gas_used = max(block_regular, tx_state_gas) blockchain_test( pre=pre, @@ -267,6 +312,18 @@ def test_multi_transaction_gas_accounting( This tests that clients correctly use pre-refund gas for block accounting. """ + # TODO[EIP-8037]: this test's exceed_block_gas_limit branch builds + # `environment_gas_limit = total - 1` from a single combined + # `total_block_gas_used`, but post-fix the auth refund splits the + # regular vs state dimensions further. Reworking the per-dimension + # budget math is out of scope for the auth-refund spec fix; until + # then, skip the AUTHORIZATION_EXISTING_AUTHORITY case here. + if refund_type == RefundTypes.AUTHORIZATION_EXISTING_AUTHORITY: + pytest.skip( + "AUTHORIZATION_EXISTING_AUTHORITY not yet adapted to the " + "two-dimensional block budget post EIP-8037 auth-refund fix" + ) + intrinsic_cost_calc = fork.transaction_intrinsic_cost_calculator() refunds_count = 10 @@ -277,6 +334,7 @@ def test_multi_transaction_gas_accounting( ( gas_used_post_refund, gas_used_pre_refund, + tx_state_gas, call_data_floor_cost, refund_tx, ) = build_refund_tx( @@ -291,7 +349,7 @@ def test_multi_transaction_gas_accounting( exceed_block_gas_limit=exceed_block_gas_limit, ) refund_tx_gas_used = max(gas_used_post_refund, call_data_floor_cost) - refund_tx_block_gas_used = max(gas_used_pre_refund, call_data_floor_cost) + refund_tx_block_regular = max(gas_used_pre_refund, call_data_floor_cost) extra_tx_sender = pre.fund_eoa() extra_tx_calldata = b"\xff" if extra_tx_data_floor else b"" @@ -312,9 +370,11 @@ def test_multi_transaction_gas_accounting( else None, ) - total_block_gas_used = ( - refund_tx_block_gas_used + extra_tx_intrinsic_gas_cost - ) + # EIP-8037: block_gas_used = max(sum_regular, sum_state) + # Extra tx has no state gas, so its state gas contribution = 0 + block_regular = refund_tx_block_regular + extra_tx_intrinsic_gas_cost + block_state = tx_state_gas + total_block_gas_used = max(block_regular, block_state) if exceed_block_gas_limit: environment_gas_limit = total_block_gas_used - 1 else: @@ -405,6 +465,18 @@ def test_varying_calldata_costs( 2. tx_gas_after_refund < calldata_floor < tx_gas_before_refund 3. calldata_floor > tx_gas_before_refund """ + if refund_type == RefundTypes.AUTHORIZATION_EXISTING_AUTHORITY: + if calldata_test_type == ( + CallDataTestType.DATA_FLOOR_BETWEEN_TX_GAS_BEFORE_AND_AFTER + ): + pytest.skip( + "EIP-7702 auth refund routes through state_gas_reservoir " + "and state_refund (deducted from tx_state_gas); it does " + "not feed refund_counter, so receipt gas_used_pre_refund " + "== gas_used_post_refund and no calldata floor can land " + "strictly between them" + ) + match refund_type: case RefundTypes.STORAGE_CLEAR: bytes_to_add_per_iteration = b"00" * 2 @@ -430,6 +502,7 @@ def test_varying_calldata_costs( ( gas_used_post_refund, gas_used_pre_refund, + tx_state_gas, call_data_floor_cost, refund_tx, ) = build_refund_tx( @@ -476,7 +549,9 @@ def test_varying_calldata_costs( f"Could not find the call_data with {num_iterations} iterations." ) - refund_tx_block_gas_used = max(call_data_floor_cost, gas_used_pre_refund) + # EIP-8037: block gas_used = max(block_regular_gas, block_state_gas) + block_regular = max(call_data_floor_cost, gas_used_pre_refund) + refund_tx_block_gas_used = max(block_regular, tx_state_gas) blockchain_test( pre=pre, @@ -511,18 +586,24 @@ def test_multiple_refund_types_in_one_tx( post = Alloc() refund_types = set(fork.refund_types()) - (_, gas_used_pre_refund, call_data_floor_cost, refund_tx) = ( - build_refund_tx( - fork=fork, - pre=pre, - post=post, - refund_types=refund_types, - refunds_count=refunds_count, - refund_tx_reverts=refund_tx_reverts, - ) + ( + _, + gas_used_pre_refund, + tx_state_gas, + call_data_floor_cost, + refund_tx, + ) = build_refund_tx( + fork=fork, + pre=pre, + post=post, + refund_types=refund_types, + refunds_count=refunds_count, + refund_tx_reverts=refund_tx_reverts, ) - refund_tx_block_gas_used = max(gas_used_pre_refund, call_data_floor_cost) + # EIP-8037: block gas_used = max(block_regular_gas, block_state_gas) + block_regular = max(gas_used_pre_refund, call_data_floor_cost) + refund_tx_block_gas_used = max(block_regular, tx_state_gas) blockchain_test( pre=pre, @@ -568,6 +649,8 @@ def test_mixed_gas_regimes( tx1_target = pre.deploy_contract(code=tx1_code) tx1_sender = pre.fund_eoa(initial_fund) tx1_data = b"" + # Full intrinsic + execution gas (regular + state) sizes the gas limit + # and the balance charged to the sender. tx1_pre_refund = intrinsic_cost_calc( calldata=tx1_data, return_cost_deducted_prior_execution=True, @@ -575,6 +658,12 @@ def test_mixed_gas_regimes( tx1_floor = data_floor_calc(data=tx1_data) assert tx1_pre_refund > tx1_floor, "tx1: pre_refund must exceed floor" tx1_contribution = max(tx1_pre_refund, tx1_floor) + # EIP-8037: block gas_used counts only regular gas; the SSTORE-set + # state gas lives in the separate state dimension, so the block-level + # contribution excludes it. + tx1_block_contribution = max( + tx1_pre_refund - Op.SSTORE(new_value=1).state_cost(fork), tx1_floor + ) tx1 = Transaction( to=tx1_target, gas_limit=tx1_contribution, @@ -596,6 +685,7 @@ def test_mixed_gas_regimes( ( tx2_post_refund, tx2_pre_refund, + _, tx2_floor, tx2, ) = build_refund_tx( @@ -638,7 +728,9 @@ def test_mixed_gas_regimes( balance=initial_fund - tx3_contribution * tx3_gas_price ) - total_gas_used = tx1_contribution + tx2_contribution + tx3_contribution + total_gas_used = ( + tx1_block_contribution + tx2_contribution + tx3_contribution + ) blockchain_test( pre=pre, diff --git a/tests/amsterdam/eip7843_slotnum/test_fork_transition.py b/tests/amsterdam/eip7843_slotnum/test_fork_transition.py index 210e3a5001b..2660249284e 100644 --- a/tests/amsterdam/eip7843_slotnum/test_fork_transition.py +++ b/tests/amsterdam/eip7843_slotnum/test_fork_transition.py @@ -6,6 +6,7 @@ Alloc, Block, BlockchainTestFiller, + Fork, Op, Transaction, ) @@ -20,6 +21,7 @@ def test_slotnum_at_fork_transition( blockchain_test: BlockchainTestFiller, pre: Alloc, + fork: Fork, ) -> None: """ Test SLOTNUM behavior across the EIP-7843 fork transition. @@ -38,16 +40,19 @@ def test_slotnum_at_fork_transition( * block 3 (post-fork): slot 3 == ``post_fork_slot``. """ sender = pre.fund_eoa() - contract = pre.deploy_contract(Op.SSTORE(Op.NUMBER, Op.SLOTNUM) + Op.STOP) + code = Op.SSTORE(Op.NUMBER, Op.SLOTNUM, new_value=1) + Op.STOP + contract = pre.deploy_contract(code) at_fork_slot = 200 post_fork_slot = 201 + gas_limit = 100_000 + code.gas_cost(fork.transitions_to()) + blocks = [ Block( timestamp=ts, slot_number=slot, - txs=[Transaction(sender=sender, to=contract, gas_limit=100_000)], + txs=[Transaction(sender=sender, to=contract, gas_limit=gas_limit)], ) for ts, slot in [ (14_999, None), diff --git a/tests/amsterdam/eip7843_slotnum/test_slotnum.py b/tests/amsterdam/eip7843_slotnum/test_slotnum.py index c3387c957e4..1fd9856ef41 100644 --- a/tests/amsterdam/eip7843_slotnum/test_slotnum.py +++ b/tests/amsterdam/eip7843_slotnum/test_slotnum.py @@ -34,6 +34,7 @@ def test_slotnum_value( state_test: StateTestFiller, pre: Alloc, + fork: Fork, slot_number: int, ) -> None: """ @@ -42,27 +43,32 @@ def test_slotnum_value( The slot number is provided by the consensus layer and should be accessible via the SLOTNUM opcode (0x4B). """ - # Store SLOTNUM result at storage key 0 - code = Op.SSTORE(0, Op.SLOTNUM) + # Store SLOTNUM result at storage key 0. Metadata pins the + # storage transition (0->slot_number) so `code.gas_cost(fork)` + # picks the right SSTORE branch under EIP-8037's 2D gas model. + code = Op.SSTORE( + key=0, + value=Op.SLOTNUM, + key_warm=False, + original_value=0, + new_value=slot_number, + ) code_address = pre.deploy_contract(code) + intrinsic_cost = fork.transaction_intrinsic_cost_calculator()() + code_regular = code.gas_cost(fork) + tx = Transaction( sender=pre.fund_eoa(), - gas_limit=100_000, + gas_limit=intrinsic_cost + code_regular, to=code_address, ) - post = { - code_address: Account( - storage={0: slot_number}, - ), - } - state_test( env=Environment(slot_number=slot_number), pre=pre, tx=tx, - post=post, + post={code_address: Account(storage={0: slot_number})}, ) @@ -90,33 +96,47 @@ def test_slotnum_gas_cost( callee_code = Op.SLOTNUM + Op.STOP callee_address = pre.deterministic_deploy_contract(deploy_code=callee_code) - # Caller calls the callee with limited gas and stores result - caller_code = Op.SSTORE(0, Op.CALL(gas=call_gas, address=callee_address)) + # Caller calls the callee with `call_gas`; SSTOREs the call's + # success bit (1 if SLOTNUM had enough gas, 0 if it OOG'd). + sstore_value = 1 if call_succeeds else 0 + caller_code = Op.SSTORE( + key=0, + value=Op.CALL( + gas=call_gas, + address=callee_address, + address_warm=False, + ), + key_warm=False, + original_value=0, + new_value=sstore_value, + ) caller_address = pre.deploy_contract(caller_code) + intrinsic_cost = fork.transaction_intrinsic_cost_calculator()() + # Static opcode-metadata calc misses the gas burned in the inner + # CALL frame; add it back. `call_gas` is the full forwarded amount + # — for `enough_gas` SLOTNUM consumes it all; for `out_of_gas` + # the OOG burns the entire forwarded budget. + code_regular = caller_code.gas_cost(fork) + call_gas + tx = Transaction( sender=pre.fund_eoa(), - gas_limit=100_000, + gas_limit=intrinsic_cost + code_regular, to=caller_address, ) - post = { - caller_address: Account( - storage={0: 1 if call_succeeds else 0}, - ), - } - state_test( env=Environment(slot_number=12345), pre=pre, tx=tx, - post=post, + post={caller_address: Account(storage={0: sstore_value})}, ) def test_slotnum_distinct_per_block( blockchain_test: BlockchainTestFiller, pre: Alloc, + fork: Fork, ) -> None: """ Test that SLOTNUM returns each block's own slot number. @@ -128,15 +148,19 @@ def test_slotnum_distinct_per_block( in the final post-state. """ sender = pre.fund_eoa() - contract = pre.deploy_contract(Op.SSTORE(Op.NUMBER, Op.SLOTNUM) + Op.STOP) + code = Op.SSTORE(Op.NUMBER, Op.SLOTNUM, new_value=1) + Op.STOP + contract = pre.deploy_contract(code) # Non-monotonic on purpose: decrease, increase, jump to large value. slot_numbers = [100, 42, 7, 2**32] + intrinsic_cost = fork.transaction_intrinsic_cost_calculator()() + gas_limit = intrinsic_cost + code.gas_cost(fork) + blocks = [ Block( slot_number=slot, - txs=[Transaction(sender=sender, to=contract, gas_limit=100_000)], + txs=[Transaction(sender=sender, to=contract, gas_limit=gas_limit)], ) for slot in slot_numbers ] diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py index b276d04f74a..5901e2903ba 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py @@ -314,6 +314,7 @@ def test_bal_account_access_target( def test_bal_callcode_nested_value_transfer( pre: Alloc, blockchain_test: BlockchainTestFiller, + fork: Fork, ) -> None: """ Ensure BAL captures balance changes from nested value transfers @@ -326,8 +327,11 @@ def test_bal_callcode_nested_value_transfer( target_code = Op.CALL(0, bob, 100, 0, 0, 0, 0) target_contract = pre.deploy_contract(code=target_code) + callcode_gas = 50_000 + if fork.is_eip_enabled(8037): + callcode_gas = 500_000 # Oracle contract that uses CALLCODE to execute TargetContract's code - oracle_code = Op.CALLCODE(50_000, target_contract, 100, 0, 0, 0, 0) + oracle_code = Op.CALLCODE(callcode_gas, target_contract, 100, 0, 0, 0, 0) oracle_contract = pre.deploy_contract(code=oracle_code, balance=200) tx = Transaction( @@ -369,13 +373,15 @@ def test_bal_callcode_nested_value_transfer( "delegated_opcode", [ pytest.param( - lambda target_addr: Op.DELEGATECALL( - 50000, target_addr, 0, 0, 0, 0 + lambda target_addr, inner_gas: Op.DELEGATECALL( + inner_gas, target_addr, 0, 0, 0, 0 ), id="delegatecall", ), pytest.param( - lambda target_addr: Op.CALLCODE(50000, target_addr, 0, 0, 0, 0, 0), + lambda target_addr, inner_gas: Op.CALLCODE( + inner_gas, target_addr, 0, 0, 0, 0, 0 + ), id="callcode", ), ], @@ -383,7 +389,8 @@ def test_bal_callcode_nested_value_transfer( def test_bal_delegated_storage_writes( pre: Alloc, blockchain_test: BlockchainTestFiller, - delegated_opcode: Callable[[Address], Op], + delegated_opcode: Callable[[Address, int], Op], + fork: Fork, ) -> None: """ Ensure BAL captures delegated storage writes via @@ -391,13 +398,26 @@ def test_bal_delegated_storage_writes( """ alice = pre.fund_eoa() - # TargetContract that writes 0x42 to slot 0x01 - target_code = Op.SSTORE(0x01, 0x42) + # TargetContract that writes 0x42 to slot 0x01. + # Metadata pins the 0->0x42 transition so the gas calculator + # accounts for SSTORE state gas under EIP-8037. + target_code = Op.SSTORE.with_metadata( + key_warm=False, + original_value=0, + current_value=0, + new_value=0x42, + )(0x01, 0x42) target_contract = pre.deploy_contract(code=target_code) + # Forward enough inner gas to cover both the regular and (under + # EIP-8037) the spilled state-gas portion of the SSTORE — the + # oracle frame inherits `state_gas_reservoir=0` since the outer + # tx_gas stays below TX_MAX_GAS_LIMIT. + inner_gas = target_code.gas_cost(fork) + 100 # small buffer + # Oracle contract that uses delegated opcode to execute # TargetContract's code - oracle_code = delegated_opcode(target_contract) + oracle_code = delegated_opcode(target_contract, inner_gas) oracle_contract = pre.deploy_contract(code=oracle_code) tx = Transaction( @@ -824,13 +844,16 @@ def test_bal_2930_slot_listed_and_unlisted_writes( ) intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator() + gas_buffer = 50_000 + if fork.is_eip_enabled(8037): + gas_buffer = 500_000 gas_limit = ( intrinsic_gas_calculator( calldata=b"", contract_creation=False, access_list=[access_list], ) - + 50000 + + gas_buffer ) # intrinsic + buffer for storage writes tx = Transaction( @@ -2373,6 +2396,7 @@ def test_bal_nested_delegatecall_storage_writes_net_zero( def test_bal_create_transaction_empty_code( pre: Alloc, blockchain_test: BlockchainTestFiller, + fork: Fork, ) -> None: """ Ensure BAL does not record spurious code changes when a CREATE transaction @@ -2381,11 +2405,15 @@ def test_bal_create_transaction_empty_code( alice = pre.fund_eoa() contract_address = compute_create_address(address=alice, nonce=0) + gas_limit = 100_000 + if fork.is_eip_enabled(8037): + gas_limit = 500_000 + tx = Transaction( sender=alice, to=None, data=b"", - gas_limit=100_000, + gas_limit=gas_limit, ) account_expectations = { @@ -2426,6 +2454,7 @@ def test_bal_cross_tx_storage_write( pre: Alloc, blockchain_test: BlockchainTestFiller, tx2_value: int, + fork: Fork, ) -> None: """ Tx1's storage_change must be preserved regardless of tx2's write. @@ -2440,18 +2469,40 @@ def test_bal_cross_tx_storage_write( contract = pre.deploy_contract(code=Op.SSTORE(0, Op.CALLDATALOAD(0))) + # Size each tx_gas_limit precisely against its SSTORE transition + # under EIP-8037's 2D gas model (regular + state). + intrinsic_calc = fork.transaction_intrinsic_cost_calculator() + tx1_data = Hash(tx1_value) + tx2_data = Hash(tx2_value) + tx1_code = Op.SSTORE.with_metadata( + key_warm=False, + original_value=0, + current_value=0, + new_value=tx1_value, + )(0, Op.CALLDATALOAD(0)) + tx2_code = Op.SSTORE.with_metadata( + key_warm=False, + original_value=tx1_value, + current_value=tx1_value, + new_value=tx2_value, + )(0, Op.CALLDATALOAD(0)) + tx1 = Transaction( sender=alice, to=contract, - data=Hash(tx1_value), - gas_limit=100_000, + data=tx1_data, + gas_limit=( + intrinsic_calc(calldata=tx1_data) + tx1_code.gas_cost(fork) + ), ) tx2 = Transaction( sender=alice, to=contract, - data=Hash(tx2_value), - gas_limit=100_000, + data=tx2_data, + gas_limit=( + intrinsic_calc(calldata=tx2_data) + tx2_code.gas_cost(fork) + ), ) slot_changes = [ @@ -2497,6 +2548,7 @@ def test_bal_cross_tx_storage_write( def test_bal_cross_tx_storage_chain( pre: Alloc, blockchain_test: BlockchainTestFiller, + fork: Fork, ) -> None: """ Verify clients apply BAL state changes from prior transactions before @@ -2538,7 +2590,7 @@ def test_bal_cross_tx_storage_chain( sender=sender, to=contract, data=Hash(i), - gas_limit=100_000, + gas_limit=fork.transaction_gas_limit_cap(), ) ) @@ -2591,6 +2643,7 @@ def test_bal_cross_tx_deploy_then_call( pre: Alloc, blockchain_test: BlockchainTestFiller, create_opcode: Op, + fork: Fork, ) -> None: """ Verify clients apply Tx1's CREATE to their state view before @@ -2634,12 +2687,12 @@ def test_bal_cross_tx_deploy_then_call( sender=alice, to=factory, data=initcode_bytes, - gas_limit=500_000, + gas_limit=fork.transaction_gas_limit_cap(), ) tx_call = Transaction( sender=bob, to=target, - gas_limit=100_000, + gas_limit=fork.transaction_gas_limit_cap(), ) account_expectations = { @@ -2850,6 +2903,7 @@ def test_bal_cross_tx_balance_dependency( pre: Alloc, blockchain_test: BlockchainTestFiller, funding_method: str, + fork: Fork, ) -> None: """ Verify clients apply Tx1's balance change before executing Tx2 in @@ -2881,7 +2935,7 @@ def test_bal_cross_tx_balance_dependency( sender=alice, to=contract, value=transferred, - gas_limit=100_000, + gas_limit=fork.transaction_gas_limit_cap(), ) send_expectations: dict = {} elif funding_method == "selfdestruct": @@ -2892,7 +2946,7 @@ def test_bal_cross_tx_balance_dependency( tx_send = Transaction( sender=alice, to=killer, - gas_limit=100_000, + gas_limit=fork.transaction_gas_limit_cap(), ) send_expectations = { killer: BalAccountExpectation( @@ -2908,7 +2962,7 @@ def test_bal_cross_tx_balance_dependency( sender=bob, to=contract, data=b"\x01", - gas_limit=100_000, + gas_limit=fork.transaction_gas_limit_cap(), ) account_expectations = { @@ -3274,6 +3328,7 @@ def test_bal_cross_block_ripemd160_state_leak( def test_bal_all_transaction_types( pre: Alloc, blockchain_test: BlockchainTestFiller, + fork: Fork, ) -> None: """ Test BAL with all 5 tx types in single block. @@ -3290,6 +3345,10 @@ def test_bal_all_transaction_types( """ from tests.prague.eip7702_set_code_tx.spec import Spec as Spec7702 + gas_limit = 100_000 + if fork.is_eip_enabled(8037): + gas_limit = 500_000 + # Create senders for each transaction type sender_0 = pre.fund_eoa() # Type 0 - Legacy sender_1 = pre.fund_eoa() # Type 1 - Access List @@ -3316,7 +3375,7 @@ def test_bal_all_transaction_types( ty=0, sender=sender_0, to=contract_0, - gas_limit=100_000, + gas_limit=gas_limit, gas_price=10, data=Hash(0x01), # Value to store ) @@ -3326,7 +3385,7 @@ def test_bal_all_transaction_types( ty=1, sender=sender_1, to=contract_1, - gas_limit=100_000, + gas_limit=gas_limit, gas_price=10, data=Hash(0x02), access_list=[ @@ -3342,7 +3401,7 @@ def test_bal_all_transaction_types( ty=2, sender=sender_2, to=contract_2, - gas_limit=100_000, + gas_limit=gas_limit, max_fee_per_gas=50, max_priority_fee_per_gas=5, data=Hash(0x03), @@ -3355,7 +3414,7 @@ def test_bal_all_transaction_types( ty=3, sender=sender_3, to=contract_3, - gas_limit=100_000, + gas_limit=gas_limit, max_fee_per_gas=50, max_priority_fee_per_gas=5, max_fee_per_blob_gas=10, @@ -3368,7 +3427,7 @@ def test_bal_all_transaction_types( ty=4, sender=sender_4, to=alice, - gas_limit=100_000, + gas_limit=gas_limit, max_fee_per_gas=50, max_priority_fee_per_gas=5, authorization_list=[ diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_cross_index.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_cross_index.py index 557bd98b420..90715853114 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_cross_index.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_cross_index.py @@ -24,6 +24,8 @@ BlockAccessListExpectation, BlockchainTestFiller, Bytecode, + Fork, + Header, Op, Transaction, ) @@ -197,6 +199,7 @@ def test_bal_consolidation_contract_cross_index( def test_bal_noop_write_filtering( pre: Alloc, blockchain_test: BlockchainTestFiller, + fork: Fork, ) -> None: """ Test that NOOP writes (writing same value or 0 to empty) are filtered. @@ -206,15 +209,37 @@ def test_bal_noop_write_filtering( 2. Writing the same value to a slot doesn't appear in BAL 3. Only actual changes are tracked """ + # Metadata pins each SSTORE's actual transition so the gas + # calculator picks the right branch under EIP-8037's 2D model. test_code = Bytecode( # Write 0 to uninitialized slot 1 (noop) - Op.SSTORE(1, 0) - # Write 42 to slot 2 - + Op.SSTORE(2, 42) - # Write 100 to slot 3 (will be same as pre-state, should be filtered) - + Op.SSTORE(3, 100) - # Write 200 to slot 4 (different from pre-state 150, should appear) - + Op.SSTORE(4, 200) + Op.SSTORE.with_metadata( + key_warm=False, + original_value=0, + current_value=0, + new_value=0, + )(1, 0) + # Write 42 to slot 2 (0->42, charges sstore_state_gas) + + Op.SSTORE.with_metadata( + key_warm=False, + original_value=0, + current_value=0, + new_value=42, + )(2, 42) + # Write 100 to slot 3 (same as pre-state, should be filtered) + + Op.SSTORE.with_metadata( + key_warm=False, + original_value=100, + current_value=100, + new_value=100, + )(3, 100) + # Write 200 to slot 4 (150->200, regular update) + + Op.SSTORE.with_metadata( + key_warm=False, + original_value=150, + current_value=150, + new_value=200, + )(4, 200) ) sender = pre.fund_eoa() @@ -223,10 +248,11 @@ def test_bal_noop_write_filtering( storage={3: 100, 4: 150}, ) + intrinsic_cost = fork.transaction_intrinsic_cost_calculator()() tx = Transaction( sender=sender, to=test_address, - gas_limit=100_000, + gas_limit=intrinsic_cost + test_code.gas_cost(fork), ) # Expected BAL should only show actual changes @@ -255,9 +281,17 @@ def test_bal_noop_write_filtering( } ) + # Header `gas_used = max(regular, state)` for the single tx; the + # SSTORE metadata pins each transition so `regular_cost`/`state_cost` + # return the actual fork-priced amount. + expected_regular = intrinsic_cost + test_code.regular_cost(fork) + expected_state = test_code.state_cost(fork) block = Block( txs=[tx], expected_block_access_list=expected_block_access_list, + header_verify=Header( + gas_used=max(expected_regular, expected_state), + ), ) blockchain_test( @@ -467,7 +501,7 @@ def test_bal_withdrawal_predeploy_balance_observed_cross_tx( tx_read_balance = Transaction( sender=sender_1, to=reader, - gas_limit=100_000, + gas_limit=200_000, ) expected_block_access_list = BlockAccessListExpectation( diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip7002.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip7002.py index 907dc6ce099..05f7c0574ae 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip7002.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip7002.py @@ -15,6 +15,7 @@ Block, BlockAccessListExpectation, BlockchainTestFiller, + Fork, Op, Transaction, ) @@ -176,6 +177,7 @@ def _build_incremental_changes( def test_bal_7002_clean_sweep( pre: Alloc, blockchain_test: BlockchainTestFiller, + fork: Fork, pubkey: bytes, amount: int, ) -> None: @@ -195,13 +197,18 @@ def test_bal_7002_clean_sweep( fee=Spec7002.get_fee(0), ) + # Predeploy sweep performs first-time SSTOREs for queue, count, and + # tail slots. `sstore_state_gas()` is 0 pre-EIP-8037 and scales with + # cpsb on Amsterdam, keeping this budget CPSB-agnostic. + gas_limit = 200_000 + 5 * Op.SSTORE(new_value=1).state_cost(fork) + # Transaction to system contract tx = Transaction( sender=alice, to=Address(Spec7002.WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS), value=withdrawal_request.fee, data=withdrawal_request.calldata, - gas_limit=200_000, + gas_limit=gas_limit, ) # Build queue writes and reads based on pubkey @@ -283,6 +290,7 @@ def test_bal_7002_clean_sweep( def test_bal_7002_partial_sweep( pre: Alloc, blockchain_test: BlockchainTestFiller, + fork: Fork, ) -> None: """ Ensure BAL correctly tracks queue overflow when requests exceed MAX. @@ -293,6 +301,11 @@ def test_bal_7002_partial_sweep( fee = Spec7002.get_fee(0) senders = [pre.fund_eoa() for _ in range(num_requests)] + # Predeploy sweep performs first-time SSTOREs for queue, count, and + # tail slots. `sstore_state_gas()` is 0 pre-EIP-8037 and scales with + # cpsb on Amsterdam, keeping this budget CPSB-agnostic. + gas_limit = 200_000 + 5 * Op.SSTORE(new_value=1).state_cost(fork) + # Block 1: 20 withdrawal requests withdrawal_requests = [ WithdrawalRequest(validator_pubkey=i + 1, amount=0, fee=fee) @@ -307,7 +320,7 @@ def test_bal_7002_partial_sweep( to=eip7002_address, value=withdrawal_request.fee, data=withdrawal_request.calldata, - gas_limit=200_000, + gas_limit=gas_limit, ) for sender, withdrawal_request in zip( senders, withdrawal_requests, strict=True @@ -455,6 +468,7 @@ def test_bal_7002_partial_sweep( def test_bal_7002_no_withdrawal_requests( pre: Alloc, blockchain_test: BlockchainTestFiller, + fork: Fork, ) -> None: """ Ensure BAL captures EIP-7002 system contract dequeue operation even @@ -469,11 +483,16 @@ def test_bal_7002_no_withdrawal_requests( value = 10 + # Predeploy sweep performs first-time SSTOREs for queue, count, and + # tail slots. `sstore_state_gas()` is 0 pre-EIP-8037 and scales with + # cpsb on Amsterdam, keeping this budget CPSB-agnostic. + gas_limit = 200_000 + 5 * Op.SSTORE(new_value=1).state_cost(fork) + tx = Transaction( sender=alice, to=bob, value=value, - gas_limit=200_000, + gas_limit=gas_limit, ) block = Block( diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip7702.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip7702.py index 08ab12a9e79..68fd84b1f97 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip7702.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip7702.py @@ -1447,6 +1447,10 @@ def test_bal_withdrawal_to_7702_delegation( ) +# TODO[EIP-8037]: Balance calculation needs update for two-dimensional gas +# (state gas reservoir credits from authorization refunds change the effective +# gas cost). +@pytest.mark.skip(reason="EIP-8037 state gas reservoir changes gas accounting") @pytest.mark.with_all_create_opcodes def test_bal_7702_delegated_create( fork: Fork, diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py index 52b06e9ff7f..a445801805d 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py @@ -2051,10 +2051,11 @@ def test_bal_create_oog_code_deposit( access_list=[], ) + # NEW_ACCOUNT keeps the budget CPSB-agnostic but short of the deposit. tx = Transaction( sender=alice, to=factory, - gas_limit=intrinsic_gas + 500_000, # insufficient for deposit + gas_limit=(intrinsic_gas + 500_000 + fork.gas_costs().NEW_ACCOUNT), ) # BAL expectations: @@ -2556,6 +2557,7 @@ def test_bal_create_contract_init_revert( def test_bal_call_revert_insufficient_funds( pre: Alloc, blockchain_test: BlockchainTestFiller, + fork: Fork, call_opcode: Op, delegated: bool, target_is_warm: bool, @@ -2567,12 +2569,10 @@ def test_bal_call_revert_insufficient_funds( Caller (balance=100): SLOAD(0x01) → call_opcode(target, value=1000) → SSTORE(0x02, result). The call fails because 1000 > 100. The - failure happens after delegation resolution. However, the delegation - target's account has not been read yet. - So when the target is a 7702-delegated EOA, the target itself appears in - the BAL since it is already read. The delegation target however, - does not appear in the BAL, since it does not need to be read - for verifying sufficient balance. + failure happens after delegation resolution. Under EIP-8037 the + call family reads the delegation target's code before the balance + check fails, so both the target and the delegation target appear in + the BAL. Pre-8037 forks defer that read, so only the target appears. Access-list warming does NOT add to BAL on its own — only EVM access does — so the BAL is identical across warm/cold variants. @@ -2644,10 +2644,17 @@ def test_bal_call_revert_insufficient_funds( if delegated: assert delegation_target is not None - # Delegation target must NOT appear in the BAL — get_account - # for code_address only runs inside generic_call, which is - # never invoked when the balance check fails. - account_expectations[delegation_target] = None + # Under EIP-8037 the call family reads the delegation target's + # code before the balance check fails, so it appears in the + # BAL. Pre-8037 forks defer that read and it stays out. + # TODO: drop this fork split once #2473 (defer get_code into + # generic_call) is consolidated into amsterdam. + if fork.is_eip_enabled(8037): + account_expectations[delegation_target] = ( + BalAccountExpectation.empty() + ) + else: + account_expectations[delegation_target] = None block = Block( txs=[tx], @@ -2673,6 +2680,7 @@ def test_bal_call_revert_insufficient_funds( def test_bal_create_selfdestruct_to_self_with_call( pre: Alloc, blockchain_test: BlockchainTestFiller, + fork: Fork, ) -> None: """ Test BAL with init code that CALLs Oracle, writes storage, then @@ -2700,9 +2708,12 @@ def test_bal_create_selfdestruct_to_self_with_call( # 1. Calls Oracle (which writes to its slot 0x01) # 2. Writes 0x42 to own slot 0x01 # 3. Selfdestructs to self + # + # Forward enough gas for Oracle's first-time SSTORE + # (regular base + state gas, CPSB-agnostic). + oracle_call_gas = 100_000 + Op.SSTORE(new_value=1).state_cost(fork) initcode_runtime = ( - # CALL(gas, Oracle, value=0, ...) - Op.CALL(100_000, oracle, 0, 0, 0, 0, 0) + Op.CALL(oracle_call_gas, oracle, 0, 0, 0, 0, 0) + Op.POP # Write to own storage slot 0x01 + Op.SSTORE(0x01, 0x42) @@ -2763,10 +2774,17 @@ def test_bal_create_selfdestruct_to_self_with_call( opcode=Op.CREATE2, ) + # Budget for CREATE2 + 3 first-time SSTOREs, CPSB-agnostic via state gas. + gas_limit = ( + 1_000_000 + + fork.gas_costs().NEW_ACCOUNT + + 3 * Op.SSTORE(new_value=1).state_cost(fork) + ) + tx = Transaction( sender=alice, to=factory, - gas_limit=1_000_000, + gas_limit=gas_limit, ) block = Block( @@ -3600,6 +3618,7 @@ def test_bal_create_storage_op_then_selfdestruct_same_tx( def test_bal_create2_selfdestruct_then_recreate_same_block( pre: Alloc, blockchain_test: BlockchainTestFiller, + fork: Fork, pre_balance: int, ) -> None: """ @@ -3654,17 +3673,19 @@ def test_bal_create2_selfdestruct_then_recreate_same_block( if pre_balance > 0: pre.fund_address(target_a, pre_balance) + # Headroom for the self-destruct to fund a fresh beneficiary. + gas_limit = (fork.transaction_gas_limit_cap() or 0) + 2_000_000 tx1 = Transaction( sender=alice, to=factory, data=initcode_bytes, - gas_limit=500_000, + gas_limit=gas_limit, ) tx2 = Transaction( sender=alice, to=factory, data=initcode_bytes, - gas_limit=500_000, + gas_limit=gas_limit, ) target_a_balance_changes = [] 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 4000ed7df4a..ca41e6b9308 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 @@ -38,7 +38,8 @@ def test_max_code_size_fork_transition( fork: TransitionFork, ) -> None: """Ensure the new max code size limit activates at the fork boundary.""" - code_size = fork.transitions_to().max_code_size() + post_fork = fork.transitions_to() + code_size = post_fork.max_code_size() deploy_code = Op.JUMPDEST * code_size initcode = Initcode(deploy_code=deploy_code) @@ -48,6 +49,9 @@ def test_max_code_size_fork_transition( create_address_pre = compute_create_address(address=alice, nonce=0) create_address_post = compute_create_address(address=bob, nonce=0) + post_fork_gas_limit = ( + post_fork.transaction_gas_limit_cap() or 0 + ) + post_fork.create_state_gas(code_size=code_size) blocks = [ Block( timestamp=14_999, @@ -67,7 +71,7 @@ def test_max_code_size_fork_transition( sender=bob, to=None, data=initcode, - gas_limit=fork.transitions_to().transaction_gas_limit_cap(), + gas_limit=post_fork_gas_limit, ) ], ), @@ -89,7 +93,8 @@ def test_max_code_size_via_create_fork_transition( create_opcode: Op, ) -> None: """Ensure the new max code size limit activates at the fork via opcodes.""" - code_size = fork.transitions_to().max_code_size() + post_fork = fork.transitions_to() + code_size = post_fork.max_code_size() deploy_code = Op.JUMPDEST * code_size initcode = Initcode(deploy_code=deploy_code) initcode_bytes = bytes(initcode) @@ -148,7 +153,10 @@ def test_max_code_size_via_create_fork_transition( sender=bob, to=factory_post, data=initcode_bytes, - gas_limit=fork.transitions_to().transaction_gas_limit_cap(), + gas_limit=( + (post_fork.transaction_gas_limit_cap() or 0) + + post_fork.create_state_gas(code_size=code_size) + ), ) ], ), @@ -311,10 +319,12 @@ def test_max_code_size_with_max_initcode_fork_transition( fork: TransitionFork, ) -> None: """Ensure max code + max initcode activates at the fork boundary.""" - deploy_code = Op.JUMPDEST * fork.transitions_to().max_code_size() + post_fork = fork.transitions_to() + code_size = post_fork.max_code_size() + deploy_code = Op.JUMPDEST * code_size initcode = Initcode( deploy_code=deploy_code, - initcode_length=fork.transitions_to().max_initcode_size(), + initcode_length=post_fork.max_initcode_size(), ) alice = pre.fund_eoa() @@ -345,7 +355,10 @@ def test_max_code_size_with_max_initcode_fork_transition( sender=bob, to=None, data=initcode, - gas_limit=fork.transitions_to().transaction_gas_limit_cap(), + gas_limit=( + (post_fork.transaction_gas_limit_cap() or 0) + + post_fork.create_state_gas(code_size=code_size) + ), ) ], ), @@ -367,6 +380,7 @@ def test_parent_max_code_size_across_fork( parent = fork.transitions_from() assert parent is not None, "Parent fork must be defined for this test" + post_fork = fork.transitions_to() code_size = parent.max_code_size() deploy_code = Op.JUMPDEST * code_size initcode = Initcode(deploy_code=deploy_code) @@ -396,7 +410,10 @@ def test_parent_max_code_size_across_fork( sender=bob, to=None, data=initcode, - gas_limit=fork.transitions_to().transaction_gas_limit_cap(), + gas_limit=( + (post_fork.transaction_gas_limit_cap() or 0) + + post_fork.create_state_gas(code_size=code_size) + ), ) ], ), 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 85ed98451c9..d49848e9492 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 @@ -52,7 +52,10 @@ def test_max_code_size( sender=alice, to=None, data=initcode, - gas_limit=fork.transaction_gas_limit_cap(), + gas_limit=( + (fork.transaction_gas_limit_cap() or 0) + + fork.create_state_gas(code_size=code_size) + ), ) post: dict[Any, Account | None] = {} @@ -109,7 +112,10 @@ def test_max_code_size_via_create( sender=alice, to=factory, data=initcode_bytes, - gas_limit=fork.transaction_gas_limit_cap(), + gas_limit=( + (fork.transaction_gas_limit_cap() or 0) + + fork.create_state_gas(code_size=code_size) + ), ) created = code_size <= fork.max_code_size() @@ -178,7 +184,8 @@ def test_max_code_size_with_max_initcode( fork: Fork, ) -> None: """Ensure max-size code deploys when initcode is also at max size.""" - deploy_code = Op.JUMPDEST * fork.max_code_size() + code_size = fork.max_code_size() + deploy_code = Op.JUMPDEST * code_size initcode = Initcode( deploy_code=deploy_code, initcode_length=fork.max_initcode_size(), @@ -191,7 +198,10 @@ def test_max_code_size_with_max_initcode( sender=alice, to=None, data=initcode, - gas_limit=fork.transaction_gas_limit_cap(), + gas_limit=( + (fork.transaction_gas_limit_cap() or 0) + + fork.create_state_gas(code_size=code_size) + ), ) post = {create_address: Account(code=deploy_code)} diff --git a/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_refunds.py b/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_refunds.py index 4fcddcd316c..7463c6b66ef 100644 --- a/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_refunds.py +++ b/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_refunds.py @@ -77,6 +77,17 @@ def ty(refund_type: RefundTypes) -> int: raise ValueError(f"Unknown refund type: {refund_type}") +@pytest.fixture +def state_gas_refund(fork: Fork, refund_type: RefundTypes) -> int: + """Return the EIP-8037 auth state-gas refund (not subject to 1/5 cap).""" + if ( + fork.is_eip_enabled(8037) + and refund_type == RefundTypes.AUTHORIZATION_EXISTING_AUTHORITY + ): + return fork.gas_costs().REFUND_AUTH_PER_EXISTING_ACCOUNT + return 0 + + @pytest.fixture def max_refund(fork: Fork, refund_type: RefundTypes) -> int: """Return the max refund gas of the transaction.""" @@ -86,11 +97,11 @@ def max_refund(fork: Fork, refund_type: RefundTypes) -> int: if refund_type == RefundTypes.STORAGE_CLEAR else 0 ) - max_refund += ( - gas_costs.REFUND_AUTH_PER_EXISTING_ACCOUNT - if refund_type == RefundTypes.AUTHORIZATION_EXISTING_AUTHORITY - else 0 - ) + if ( + not fork.is_eip_enabled(8037) + and refund_type == RefundTypes.AUTHORIZATION_EXISTING_AUTHORITY + ): + max_refund += gas_costs.REFUND_AUTH_PER_EXISTING_ACCOUNT return max_refund @@ -161,6 +172,7 @@ def execution_gas_used( tx_intrinsic_gas_cost_before_execution: int, tx_floor_data_cost: int, max_refund: int, + state_gas_refund: int, prefix_code_gas: int, refund_test_type: RefundTestType, ) -> int: @@ -178,8 +190,9 @@ def execution_gas_used( def execution_gas_cost(execution_gas: int) -> int: total_gas_used = tx_intrinsic_gas_cost_before_execution + execution_gas - return total_gas_used - min( - max_refund, total_gas_used // fork.max_refund_quotient() + effective_gas = total_gas_used - state_gas_refund + return effective_gas - min( + max_refund, effective_gas // fork.max_refund_quotient() ) execution_gas = prefix_code_gas @@ -223,16 +236,19 @@ def refund( tx_intrinsic_gas_cost_before_execution: int, execution_gas_used: int, max_refund: int, + state_gas_refund: int, ) -> int: """Return the refund gas of the transaction.""" total_gas_used = ( tx_intrinsic_gas_cost_before_execution + execution_gas_used ) - return min(max_refund, total_gas_used // fork.max_refund_quotient()) + effective_gas = total_gas_used - state_gas_refund + return min(max_refund, effective_gas // fork.max_refund_quotient()) @pytest.fixture def to( + fork: Fork, pre: Alloc, execution_gas_used: int, prefix_code: Bytecode, @@ -248,10 +264,44 @@ def to( Ideally, we can use memory expansion to consume gas. """ extra_gas = execution_gas_used - prefix_code_gas - return pre.deploy_contract( - prefix_code + (Op.JUMPDEST * extra_gas) + Op.STOP, - storage=code_storage, + code = prefix_code + (Op.JUMPDEST * extra_gas) + Op.STOP + if len(code) <= fork.max_code_size(): + return pre.deploy_contract(code, storage=code_storage) + + loop_target = len(prefix_code) + len(Op.PUSH2(0)) + setup = Op.PUSH2(0) + loop_body = ( + Op.JUMPDEST + + Op.PUSH1(1) + + Op.SWAP1 + + Op.SUB + + Op.DUP1 + + Op.PUSH1(loop_target) + + Op.JUMPI + ) + teardown = Op.POP + overhead = setup.gas_cost(fork) + teardown.gas_cost(fork) + gas_per_iter = loop_body.gas_cost(fork) + + available = extra_gas - overhead + iterations = available // gas_per_iter + remaining = available % gas_per_iter + + code = ( + prefix_code + + Op.PUSH2(iterations) + + Op.JUMPDEST + + Op.PUSH1(1) + + Op.SWAP1 + + Op.SUB + + Op.DUP1 + + Op.PUSH1(loop_target) + + Op.JUMPI + + Op.POP + + (Op.JUMPDEST * remaining) + + Op.STOP ) + return pre.deploy_contract(code, storage=code_storage) @pytest.fixture @@ -288,6 +338,7 @@ def test_gas_refunds_from_data_floor( tx_intrinsic_gas_cost_before_execution: int, execution_gas_used: int, refund: int, + state_gas_refund: int, refund_test_type: RefundTestType, ) -> None: """ @@ -295,7 +346,10 @@ def test_gas_refunds_from_data_floor( floor. """ gas_used = ( - tx_intrinsic_gas_cost_before_execution + execution_gas_used - refund + tx_intrinsic_gas_cost_before_execution + + execution_gas_used + - state_gas_refund + - refund ) if ( refund_test_type diff --git a/tests/amsterdam/eip8024_dupn_swapn_exchange/test_swapn.py b/tests/amsterdam/eip8024_dupn_swapn_exchange/test_swapn.py index e238b39a5ff..7103c75f6dc 100644 --- a/tests/amsterdam/eip8024_dupn_swapn_exchange/test_swapn.py +++ b/tests/amsterdam/eip8024_dupn_swapn_exchange/test_swapn.py @@ -12,6 +12,7 @@ Bytecode, EIPChecklist, Fork, + Header, Op, StateTestFiller, Transaction, @@ -144,6 +145,7 @@ def test_swapn_valid_immediates( def test_swapn_preserves_other_stack_items( pre: Alloc, state_test: StateTestFiller, + fork: Fork, ) -> None: """Test SWAPN only swaps the specified items, leaving others unchanged.""" sender = pre.fund_eoa() @@ -153,6 +155,16 @@ def test_swapn_preserves_other_stack_items( stack_index = 17 stack_height = stack_index + 1 # Need 18 items + # Compute expected storage values (post-swap stack reads). + expected_storage: dict = {} + for i in range(stack_height): + if i == 0: + expected_storage[i] = 0x1000 # Was at bottom, now at top + elif i == stack_height - 1: + expected_storage[i] = 0x1011 # Was at top, now at bottom + else: + expected_storage[i] = 0x1000 + (stack_height - 1 - i) + # Create a stack with 18 distinct values code = Bytecode() for i in range(stack_height): @@ -162,31 +174,39 @@ def test_swapn_preserves_other_stack_items( # Pass stack index directly - encoder will handle encoding code += Op.SWAPN[stack_index] - # Store all values to verify only the swapped ones changed + # Store all values; metadata pins each slot's 0->non-zero + # transition so `code.gas_cost(fork)` accounts for SSTORE state + # gas under EIP-8037. for i in range(stack_height): - code += Op.PUSH1(i) + Op.SSTORE + code += Op.PUSH1(i) + Op.SSTORE.with_metadata( + key_warm=False, + original_value=0, + current_value=0, + new_value=expected_storage[i], + ) code += Op.STOP contract_address = pre.deploy_contract(code=code) - tx = Transaction(to=contract_address, sender=sender, gas_limit=1_000_000) + intrinsic_cost = fork.transaction_intrinsic_cost_calculator()() + code_state = code.state_cost(fork) + code_regular = code.gas_cost(fork) - code_state - # After swap: position 1 and position 18 are swapped - # Original stack (top to bottom): 0x1011, 0x1010, ..., 0x1001, 0x1000 - # After SWAPN[0]: 0x1000, 0x1010, ..., 0x1001, 0x1011 - expected_storage = {} - for i in range(stack_height): - if i == 0: - expected_storage[i] = 0x1000 # Was at bottom, now at top - elif i == stack_height - 1: - expected_storage[i] = 0x1011 # Was at top, now at bottom - else: - expected_storage[i] = 0x1000 + (stack_height - 1 - i) + tx = Transaction( + to=contract_address, + sender=sender, + gas_limit=intrinsic_cost + code_regular + code_state, + ) - post = {contract_address: Account(storage=expected_storage)} + expected_gas_used = max(intrinsic_cost + code_regular, code_state) - state_test(pre=pre, post=post, tx=tx) + state_test( + pre=pre, + post={contract_address: Account(storage=expected_storage)}, + tx=tx, + blockchain_test_header_verify=Header(gas_used=expected_gas_used), + ) def test_swapn_stack_underflow( diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/__init__.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/__init__.py new file mode 100644 index 00000000000..1542336c33b --- /dev/null +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/__init__.py @@ -0,0 +1 @@ +"""EIP-8037 State Creation Gas Cost Increase tests.""" diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/eip_checklist_external_coverage.txt b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/eip_checklist_external_coverage.txt new file mode 100644 index 00000000000..2525cc455d5 --- /dev/null +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/eip_checklist_external_coverage.txt @@ -0,0 +1,3 @@ +general/code_coverage/eels = TODO: re-run coverage after spec stabilizes. Preliminary: vm/__init__.py 95%, utils/message.py 94%, vm/gas.py 89%, transactions.py 87%, vm/interpreter.py 85%, state.py 84%, vm/instructions/storage.py 77%, vm/instructions/system.py 67%, fork.py 56% +general/code_coverage/test_coverage = 236 tests pass with --cov; key state gas paths (reservoir, gas splitting, SSTORE/CREATE/CALL/SELFDESTRUCT/SET_CODE state gas charging) are covered +general/code_coverage/missed_lines = Missed lines are mostly non-EIP-8037 code: fork.py header validation and PoW functions, system.py EXTCALL/EXTDELEGATECALL paths, storage.py TSTORE/TLOAD, eoa_delegation.py edge-case auth branches diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/eip_checklist_not_applicable.txt b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/eip_checklist_not_applicable.txt new file mode 100644 index 00000000000..8de0802000c --- /dev/null +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/eip_checklist_not_applicable.txt @@ -0,0 +1,11 @@ +opcode = EIP does not introduce a new opcode +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 +block_header_field = EIP does not add any new block header fields +block_body_field = EIP does not add any new block body fields +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 does not introduce a new transaction validity constraint +block_level_constraint = EIP does not introduce a block-level validation constraint diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/spec.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/spec.py new file mode 100644 index 00000000000..f11954e2c2f --- /dev/null +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/spec.py @@ -0,0 +1,54 @@ +"""Defines EIP-8037 specification constants and functions.""" + +from dataclasses import dataclass + +from execution_testing.vm import Bytecode, Op + + +def init_code_at_high_bytes( + init_code: Op | Bytecode | bytes, +) -> tuple[int, int]: + """Return (mstore_value, size) to place init_code at memory[0:size].""" + code_bytes = bytes(init_code) + size = len(code_bytes) + return int.from_bytes(code_bytes, "big") << (256 - 8 * size), size + + +@dataclass(frozen=True) +class ReferenceSpec: + """Defines the reference spec version and git path.""" + + git_path: str + version: str + + +# TODO: update version once +# https://github.com/ethereum/EIPs/pull/11328 is merged +ref_spec_8037 = ReferenceSpec( + "EIPS/eip-8037.md", "a12902ae1b811c45a81b51bfce671cf7a1fb27f3" +) + + +@dataclass(frozen=True) +class Spec: + """ + Constants and helpers for the EIP-8037 State Creation Gas Cost + Increase tests. + """ + + # EIP-7825 transaction gas limit cap + TX_MAX_GAS_LIMIT = 2**24 # 16,777,216 + + # CPSB is a fixed parameter derived from a 150M reference block + # gas limit and a 120 GiB/year target state growth. + COST_PER_STATE_BYTE = 1530 + + # State bytes per operation + STATE_BYTES_PER_NEW_ACCOUNT = 120 + STATE_BYTES_PER_STORAGE_SET = 64 + STATE_BYTES_PER_AUTH_BASE = 23 + + # Regular gas constants (EIP-8037 replaces old combined costs) + REGULAR_GAS_CREATE = 9000 + PER_AUTH_BASE_COST = 7500 + GAS_COLD_STORAGE_WRITE = 5000 diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py new file mode 100644 index 00000000000..0299ce309ba --- /dev/null +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py @@ -0,0 +1,667 @@ +""" +Test block-level two-dimensional gas accounting under EIP-8037. + +Verify that the block header gas_used equals +max(block_regular_gas_used, block_state_gas_used) across +single-block, multi-block, and mixed-transaction scenarios. + +Tests for [EIP-8037: State Creation Gas Cost Increase] +(https://eips.ethereum.org/EIPS/eip-8037). +""" + +import pytest +from execution_testing import ( + Account, + Alloc, + Block, + BlockchainTestFiller, + Bytecode, + Environment, + Fork, + Header, + Op, + Storage, + Transaction, + TransactionException, + TransactionReceipt, +) + +from .spec import ref_spec_8037 + +REFERENCE_SPEC_GIT_PATH = ref_spec_8037.git_path +REFERENCE_SPEC_VERSION = ref_spec_8037.version + + +def sstore_tx_gas(fork: Fork, num_sstores: int = 1) -> tuple[int, int]: + """Return (regular, state) gas for a tx with N cold SSTOREs.""" + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() + evm_total = num_sstores * Op.SSTORE(0, 1).gas_cost(fork) + state = num_sstores * Op.SSTORE(new_value=1).state_cost(fork) + return intrinsic_gas + evm_total - state, state + + +def sstore_txs( + pre: Alloc, + fork: Fork, + n: int, + num_sstores: int = 1, + tx_gas_limit: int | None = None, +) -> tuple[list[Transaction], dict]: + """Build n txs each doing num_sstores zero-to-nonzero SSTOREs.""" + if tx_gas_limit is None: + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + tx_gas_limit = gas_limit_cap + num_sstores * Op.SSTORE( + new_value=1 + ).state_cost(fork) + txs, post = [], {} + for _ in range(n): + storage = Storage() + code = Bytecode(Op.STOP) + for _ in range(num_sstores): + code = Op.SSTORE(storage.store_next(1), 1) + code + contract = pre.deploy_contract(code=code) + txs.append( + Transaction( + to=contract, + gas_limit=tx_gas_limit, + sender=pre.fund_eoa(), + ) + ) + post[contract] = Account(storage=storage) + return txs, post + + +def stop_txs(pre: Alloc, fork: Fork, n: int) -> list[Transaction]: + """Build n STOP transactions.""" + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() + txs = [] + for _ in range(n): + contract = pre.deploy_contract(code=Op.STOP) + txs.append( + Transaction( + to=contract, + gas_limit=intrinsic_gas, + sender=pre.fund_eoa(), + ) + ) + return txs + + +@pytest.mark.parametrize( + "num_txs,num_sstores", + [ + pytest.param(5, 1, id="single_sstore"), + pytest.param(20, 1, id="single_sstore_many_txs"), + pytest.param(2, 3, id="multi_sstore_spillover"), + pytest.param(10, 5, id="multi_sstore_many_txs"), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_block_gas_used_state_dominates( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, + num_txs: int, + num_sstores: int, +) -> None: + """ + Verify block.gas_used = block_state_gas when state > regular. + + Each tx performs zero-to-nonzero SSTOREs. Since state gas per + SSTORE exceeds regular gas, block_state_gas exceeds + block_regular_gas and becomes the header gas_used. + + The spillover variant provides reservoir for only one SSTORE + per tx; the remaining state gas spills into gas_left. + Block-level accounting must still separate the two dimensions. + """ + tx_regular, tx_state = sstore_tx_gas(fork, num_sstores) + block_regular = num_txs * tx_regular + block_state = num_txs * tx_state + assert block_state > block_regular + + txs, post = sstore_txs( + pre, + fork, + num_txs, + num_sstores=num_sstores, + ) + blockchain_test( + pre=pre, + blocks=[ + Block( + txs=txs, + header_verify=Header(gas_used=block_state), + ) + ], + post=post, + ) + + +@pytest.mark.valid_from("EIP8037") +def test_block_gas_used_regular_dominates( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify block.gas_used = block_regular_gas when state gas is zero. + + A block containing only STOP transactions to existing contracts + produces no state gas. The block header gas_used must equal the + sum of regular gas across all transactions, since + max(regular, 0) = regular. + """ + num_txs = 3 + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() + txs = stop_txs(pre, fork, num_txs) + + blockchain_test( + pre=pre, + blocks=[ + Block( + txs=txs, + header_verify=Header(gas_used=num_txs * intrinsic_gas), + ) + ], + post={}, + ) + + +@pytest.mark.parametrize( + "num_stop,num_sstore,interleaved", + [ + pytest.param(2, 3, False, id="grouped"), + pytest.param(10, 10, True, id="interleaved"), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_block_gas_used_mixed_txs( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, + num_stop: int, + num_sstore: int, + interleaved: bool, +) -> None: + """ + Verify block.gas_used with mixed STOP and SSTORE transactions. + + STOP txs contribute only regular gas; SSTORE txs contribute both. + The interleaved variant alternates SSTORE/STOP to test that + non-contiguous state gas contributions accumulate correctly. + """ + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() + tx_regular_sstore, tx_state_sstore = sstore_tx_gas(fork) + + block_regular = num_stop * intrinsic_gas + num_sstore * tx_regular_sstore + block_state = num_sstore * tx_state_sstore + expected = max(block_regular, block_state) + + txs_sstore, post = sstore_txs(pre, fork, num_sstore) + txs_stop = stop_txs(pre, fork, num_stop) + + if interleaved: + txs = [] + for i in range(max(num_sstore, num_stop)): + if i < num_sstore: + txs.append(txs_sstore[i]) + if i < num_stop: + txs.append(txs_stop[i]) + else: + txs = txs_stop + txs_sstore + + blockchain_test( + pre=pre, + blocks=[ + Block( + txs=txs, + header_verify=Header(gas_used=expected), + ) + ], + post=post, + ) + + +@pytest.mark.valid_from("EIP8037") +def test_block_gas_refund_eip7778_no_block_reduction( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify block gas accounting for SSTORE 0→x→0 refund paths. + + Regular gas refund via `refund_counter` does NOT reduce block gas + (EIP-7778). State gas refund goes to the reservoir and DOES reduce + `block_state_gas_used` (net zero state growth). + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() + + num_txs = 3 + # Set then restore: second SSTORE is warm with current_value=1 + code = Op.SSTORE(0, 1) + Op.SSTORE.with_metadata( + key_warm=True, + original_value=0, + current_value=1, + new_value=0, + )(0, 0) + tx_regular = intrinsic_gas + code.gas_cost(fork) - sstore_state_gas + expected = num_txs * tx_regular + txs = [] + for _ in range(num_txs): + contract = pre.deploy_contract(code=code) + txs.append( + Transaction( + to=contract, + gas_limit=gas_limit_cap + sstore_state_gas, + sender=pre.fund_eoa(), + ) + ) + + blockchain_test( + pre=pre, + blocks=[ + Block( + txs=txs, + header_verify=Header(gas_used=expected), + ) + ], + post={}, + ) + + +@pytest.mark.parametrize( + "num_txs,num_sstores", + [ + pytest.param(1, 1, id="single_sstore_single_tx"), + pytest.param(5, 1, id="single_sstore"), + pytest.param(20, 1, id="single_sstore_many_txs"), + pytest.param(10, 5, id="multi_sstore_many_txs"), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_block_2d_gas_boundary_exact_fit( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, + num_txs: int, + num_sstores: int, +) -> None: + """ + Verify a block is valid when state gas dominates regular gas. + + Clients that sum regular + state will reject this valid block. + """ + block_gas_limit = 30_000_000 + while True: + # We have a circular dependency to calculate the block gas limit based + # on the transactions required gas (tx gas increments as we increase + # the block gas limit to fit). This loops tries incrementing the + # block gas limit by consistent steps in order to find the minimum gas + # allows the transactions required to fit. + env = Environment( + gas_limit=block_gas_limit, + ) + tx_regular, tx_state = sstore_tx_gas(fork, num_sstores) + intrinsic_regular = fork.transaction_intrinsic_cost_calculator()() + + tx_limit = tx_regular + tx_state + tx_regular // 10 + + # Per-tx worst-case state contribution: tx.gas - intrinsic_regular. + # The block_gas_limit must leave enough state budget for every tx. + worst_state_per_tx = tx_limit - intrinsic_regular + minimum_block_gas_limit = max( + # Regular dimension: last tx must fit. + (num_txs - 1) * tx_regular + tx_limit, + # State dimension: cumulative worst-case must fit. + num_txs * worst_state_per_tx, + ) + if block_gas_limit >= minimum_block_gas_limit: + break + block_gas_limit += 1_000_000 + + block_regular = num_txs * tx_regular + block_state = num_txs * tx_state + expected_gas_used = max(block_regular, block_state) + + txs, post = sstore_txs( + pre, + fork, + num_txs, + num_sstores=num_sstores, + tx_gas_limit=tx_limit, + ) + + blockchain_test( + genesis_environment=env, + pre=pre, + blocks=[ + Block( + txs=txs, + gas_limit=block_gas_limit, + header_verify=Header(gas_used=expected_gas_used), + ) + ], + post=post, + ) + + +@pytest.mark.valid_from("EIP8037") +def test_block_gas_used_call_new_account( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify block.gas_used includes state gas from CALL creating accounts. + + A contract does CALL(value=1) to a non-existent address (charges + GAS_NEW_ACCOUNT state gas) then SSTORE. Combined with a STOP tx, + the 2D max must reflect state gas from account creation. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + new_account_state_gas = fork.gas_costs().NEW_ACCOUNT + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + + target = pre.fund_eoa(amount=0) + + parent_storage = Storage() + parent = pre.deploy_contract( + code=( + Op.CALL(gas=100_000, address=target, value=1) + + Op.SSTORE(parent_storage.store_next(1), 1) + ), + balance=10**18, + ) + + txs = [ + Transaction( + to=parent, + gas_limit=( + gas_limit_cap + new_account_state_gas + sstore_state_gas + ), + sender=pre.fund_eoa(), + ), + ] + stop_txs(pre, fork, 1) + + blockchain_test( + pre=pre, + blocks=[Block(txs=txs)], + post={parent: Account(storage=parent_storage)}, + ) + + +@pytest.mark.valid_from("EIP8037") +def test_block_gas_used_create_tx( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify block.gas_used includes intrinsic state gas from CREATE txs. + + Contract creation charges GAS_NEW_ACCOUNT as intrinsic state gas. + Combined with a STOP tx, verify the 2D max is correct. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + intrinsic_calc = fork.transaction_intrinsic_cost_calculator() + create_state_gas = fork.create_state_gas(code_size=0) + + init_code = bytes(Op.STOP) + create_regular = ( + intrinsic_calc( + calldata=init_code, + contract_creation=True, + ) + - create_state_gas + ) + stop_regular = intrinsic_calc() + + expected = max(create_regular + stop_regular, create_state_gas) + + txs = [ + Transaction( + to=None, + data=init_code, + gas_limit=gas_limit_cap + create_state_gas, + sender=pre.fund_eoa(), + ), + ] + stop_txs(pre, fork, 1) + + blockchain_test( + pre=pre, + blocks=[ + Block( + txs=txs, + header_verify=Header(gas_used=expected), + ) + ], + post={}, + ) + + +@pytest.mark.valid_from("EIP8037") +def test_multi_block_dimension_flip( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify gas_used across blocks where dominant dimension flips. + + Block 1: STOP txs only (regular dominates). + Block 2: SSTORE txs only (state dominates). + Each block independently computes its own 2D max. + """ + n = 3 + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() + tx_regular, tx_state = sstore_tx_gas(fork) + + block_1 = stop_txs(pre, fork, n) + block_2, post_2 = sstore_txs(pre, fork, n) + + blockchain_test( + pre=pre, + blocks=[ + Block( + txs=block_1, + header_verify=Header(gas_used=n * intrinsic_gas), + ), + Block( + txs=block_2, + header_verify=Header( + gas_used=max(n * tx_regular, n * tx_state), + ), + ), + ], + post=post_2, + ) + + +@pytest.mark.parametrize( + "delta", + [ + pytest.param(0, id="exactly_fits"), + pytest.param(1, id="exceeds", marks=pytest.mark.exception_test), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_tx_inclusion_at_regular_gas_block_limit_small( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, + delta: int, +) -> None: + """ + Probe the regular-gas inclusion boundary with a small-gas tx. + + The second tx's ``gas_limit`` is the remaining regular budget + plus ``delta``. The inclusion check uses strict ``>``, so + ``delta=0`` must pass and ``delta=1`` must reject with + ``GAS_ALLOWANCE_EXCEEDED``. Catches an off-by-one ``>=`` bug. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() + + block_gas_limit = intrinsic_gas * 2 + + filler = pre.deploy_contract(code=Op.STOP) + filler_tx = Transaction( + to=filler, + gas_limit=intrinsic_gas, + sender=pre.fund_eoa(), + ) + + second_gas_limit = intrinsic_gas + delta + assert second_gas_limit < gas_limit_cap + error = TransactionException.GAS_ALLOWANCE_EXCEEDED if delta else None + second = pre.deploy_contract(code=Op.STOP) + second_tx = Transaction( + to=second, + gas_limit=second_gas_limit, + sender=pre.fund_eoa(), + error=error, + ) + + blockchain_test( + genesis_environment=Environment(gas_limit=block_gas_limit), + pre=pre, + blocks=[ + Block( + txs=[filler_tx, second_tx], + gas_limit=block_gas_limit, + exception=error, + ) + ], + post={}, + ) + + +@pytest.mark.parametrize( + "tx2_gas_limit_equals_block_gas_limit", + [ + pytest.param(True, id="tx_gas_limit_equals_block_limit"), + pytest.param(False, id="tx_gas_limit_just_above_remaining"), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_block_2d_gas_tx_gas_limit_exceeds_regular_remaining( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, + tx2_gas_limit_equals_block_gas_limit: bool, +) -> None: + """ + Verify a block is valid when a later tx's gas_limit exceeds the + regular budget remaining but its capped regular contribution fits. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() + env = Environment() + block_gas_limit = int(env.gas_limit) + + if tx2_gas_limit_equals_block_gas_limit: + tx2_gas_limit = block_gas_limit + else: + tx2_gas_limit = block_gas_limit - intrinsic_gas + 1 + + assert tx2_gas_limit > gas_limit_cap + assert tx2_gas_limit > block_gas_limit - intrinsic_gas + + stop_contract = pre.deploy_contract(code=Op.STOP) + + storage = Storage() + sstore_contract = pre.deploy_contract( + code=Op.SSTORE(storage.store_next(1), 1), + ) + + tx1_regular = intrinsic_gas + tx2_regular, tx2_state = sstore_tx_gas(fork) + expected_gas_used = max(tx1_regular + tx2_regular, tx2_state) + + blockchain_test( + pre=pre, + blocks=[ + Block( + txs=[ + Transaction( + to=stop_contract, + gas_limit=intrinsic_gas, + sender=pre.fund_eoa(), + ), + Transaction( + to=sstore_contract, + gas_limit=tx2_gas_limit, + sender=pre.fund_eoa(), + ), + ], + header_verify=Header(gas_used=expected_gas_used), + ), + ], + post={sstore_contract: Account(storage=storage)}, + ) + + +@pytest.mark.valid_from("EIP8037") +def test_receipt_cumulative_differs_from_header_gas_used( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify receipt cumulative_gas_used can diverge from header + gas_used under 2D accounting when state gas dominates. + """ + tx_regular, tx_state = sstore_tx_gas(fork) + num_txs = 3 + + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + tx_gas_limit = gas_limit_cap + Op.SSTORE(new_value=1).state_cost(fork) + per_tx_gas_used = tx_regular + tx_state + + txs: list[Transaction] = [] + post: dict = {} + for i in range(num_txs): + storage = Storage() + contract = pre.deploy_contract( + code=Op.SSTORE(storage.store_next(1), 1) + Op.STOP, + ) + txs.append( + Transaction( + to=contract, + gas_limit=tx_gas_limit, + sender=pre.fund_eoa(), + expected_receipt=TransactionReceipt( + cumulative_gas_used=(i + 1) * per_tx_gas_used, + ), + ) + ) + post[contract] = Account(storage=storage) + + block_regular = num_txs * tx_regular + block_state = num_txs * tx_state + header_gas_used = max(block_regular, block_state) + + assert block_state > block_regular + assert header_gas_used < num_txs * per_tx_gas_used + + blockchain_test( + pre=pre, + blocks=[ + Block( + txs=txs, + header_verify=Header(gas_used=header_gas_used), + ), + ], + post=post, + ) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_eip_mainnet.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_eip_mainnet.py new file mode 100644 index 00000000000..bee5ed11565 --- /dev/null +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_eip_mainnet.py @@ -0,0 +1,98 @@ +""" +Mainnet marked execute checklist tests for +[EIP-8037: State Creation Gas Cost Increase](https://eips.ethereum.org/EIPS/eip-8037). +""" + +import pytest +from execution_testing import ( + Account, + Alloc, + Fork, + Op, + StateTestFiller, + Storage, + Transaction, +) + +from .spec import ref_spec_8037 + +REFERENCE_SPEC_GIT_PATH = ref_spec_8037.git_path +REFERENCE_SPEC_VERSION = ref_spec_8037.version + +pytestmark = [pytest.mark.valid_at("EIP8037"), pytest.mark.mainnet] + + +def test_sstore_zero_to_nonzero( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """Test SSTORE zero-to-nonzero charges state gas and succeeds.""" + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + storage = Storage() + contract = pre.deploy_contract( + code=Op.SSTORE(storage.store_next(1), 1), + ) + + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap, + sender=pre.fund_eoa(), + ) + + post = {contract: Account(storage=storage)} + state_test(pre=pre, post=post, tx=tx) + + +def test_create_charges_state_gas( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """Test CREATE charges state gas for new account creation.""" + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + init_code = Op.STOP + + storage = Storage() + contract = pre.deploy_contract( + code=( + Op.MSTORE( + 0, + int.from_bytes(bytes(init_code), "big") + << (256 - 8 * len(init_code)), + ) + + Op.SSTORE( + storage.store_next(True), + Op.GT(Op.CREATE(0, 0, len(init_code)), 0), + ) + ), + ) + + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap, + sender=pre.fund_eoa(), + ) + + post = {contract: Account(storage=storage)} + state_test(pre=pre, post=post, tx=tx) + + +def test_create_tx_deploys_contract( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """Test contract creation transaction succeeds with state gas.""" + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + tx = Transaction( + to=None, + data=Op.STOP, + gas_limit=gas_limit_cap, + sender=pre.fund_eoa(), + ) + + state_test(pre=pre, post={}, tx=tx) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py new file mode 100644 index 00000000000..a31c1cdfd54 --- /dev/null +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py @@ -0,0 +1,1650 @@ +""" +Test CALL state gas reservoir passing under EIP-8037. + +The full state gas reservoir is passed to child call frames with no +63/64 rule. On child success, remaining state gas returns to the +parent. On child revert or exceptional halt, all state gas, both +reservoir and any that spilled into `gas_left`, is restored to the +parent's reservoir (only CPU gas is consumed for the failed frame). + +All CALL-family opcodes (CALL, DELEGATECALL, STATICCALL) pass the +full reservoir to child frames. + +Tests for [EIP-8037: State Creation Gas Cost Increase] +(https://eips.ethereum.org/EIPS/eip-8037). +""" + +import pytest +from execution_testing import ( + Account, + Address, + Alloc, + Block, + BlockchainTestFiller, + Bytecode, + Environment, + Fork, + Header, + Op, + StateTestFiller, + Storage, + Transaction, + compute_create2_address, + compute_create_address, +) +from execution_testing.checklists import EIPChecklist + +from .spec import init_code_at_high_bytes, ref_spec_8037 + +REFERENCE_SPEC_GIT_PATH = ref_spec_8037.git_path +REFERENCE_SPEC_VERSION = ref_spec_8037.version + + +@pytest.mark.valid_from("EIP8037") +def test_child_call_uses_reservoir( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test child call can use parent's state gas reservoir. + + The parent calls a child contract that performs an SSTORE + (zero-to-nonzero). The state gas for the SSTORE is drawn from + the reservoir passed from the parent. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment() + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + + child_storage = Storage() + child = pre.deploy_contract( + code=Op.SSTORE(child_storage.store_next(1), 1), + ) + + parent_storage = Storage() + parent = pre.deploy_contract( + code=( + Op.SSTORE( + parent_storage.store_next(1), + Op.CALL(gas=100_000, address=child), + ) + ), + ) + + tx = Transaction( + to=parent, + gas_limit=gas_limit_cap + sstore_state_gas, + sender=pre.fund_eoa(), + ) + + post = { + parent: Account(storage=parent_storage), + child: Account(storage=child_storage), + } + state_test(env=env, pre=pre, post=post, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_delegatecall_child_spill_not_double_charged( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test DELEGATECALL child state gas paid from `gas_left` is not recharged. + + With gas below the Amsterdam tx gas cap, the top-level frame starts with + no state gas reservoir and the child pays for SSTOREs by spilling from + `gas_left`. The parent frame must not charge the same state growth again + at frame end. + """ + env = Environment() + + child_code = sum(Op.SSTORE(i, i + 1) for i in range(6)) + Op.STOP + child = pre.deploy_contract(code=child_code) + + caller = pre.deploy_contract( + code=Op.POP( + Op.DELEGATECALL( + gas=Op.GAS, + address=child, + args_offset=0, + args_size=0, + ret_offset=0, + ret_size=0, + ) + ) + ) + + tx = Transaction( + to=caller, + gas_limit=700_000, + sender=pre.fund_eoa(), + ) + + post = { + caller: Account(storage={i: i + 1 for i in range(6)}), + } + state_test(env=env, pre=pre, post=post, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_reservoir_returned_on_revert( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test state gas reservoir is returned to parent on child revert. + + The child contract reverts. The parent should recover the + reservoir and be able to use it for its own SSTORE. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment() + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + + child = pre.deploy_contract(code=Op.REVERT(0, 0)) + + parent_storage = Storage() + parent = pre.deploy_contract( + code=( + # Call child that reverts (returns 0) + Op.POP(Op.CALL(gas=100_000, address=child)) + # Parent can still use reservoir for its own SSTORE + + Op.SSTORE(parent_storage.store_next(1), 1) + ), + ) + + tx = Transaction( + to=parent, + gas_limit=gas_limit_cap + sstore_state_gas, + sender=pre.fund_eoa(), + ) + + post = {parent: Account(storage=parent_storage)} + state_test(env=env, pre=pre, post=post, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_reservoir_returned_on_oog( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test state gas reservoir is returned to parent on child OOG. + + The child runs out of regular gas. The parent recovers the + reservoir and can use it for its own state operations. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment() + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + + # Child that consumes all gas + child = pre.deploy_contract(code=Op.INVALID) + + parent_storage = Storage() + parent = pre.deploy_contract( + code=( + # Call child with minimal gas — it will OOG (returns 0) + Op.POP(Op.CALL(gas=100, address=child)) + # Parent can still use reservoir for SSTORE + + Op.SSTORE(parent_storage.store_next(1), 1) + ), + ) + + tx = Transaction( + to=parent, + gas_limit=gas_limit_cap + sstore_state_gas, + sender=pre.fund_eoa(), + ) + + post = {parent: Account(storage=parent_storage)} + state_test(env=env, pre=pre, post=post, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_reservoir_restored_after_child_spill_and_revert( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test all state gas recovered when child spills then reverts. + + The child performs two SSTOREs (zero-to-nonzero) but only one + SSTORE's worth of state gas fits in the reservoir — the second + spills into `gas_left`. The child then REVERTs. Because state + changes are rolled back, all state gas (reservoir + spill) is + restored to the parent's reservoir. The parent can then perform + two SSTOREs using only the recovered reservoir. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment() + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + + # Child does two SSTOREs then reverts — the second SSTORE's + # state gas spills from the reservoir into `gas_left` + child = pre.deploy_contract( + code=(Op.SSTORE(0, 1) + Op.SSTORE(1, 1) + Op.REVERT(0, 0)), + ) + + parent_storage = Storage() + parent = pre.deploy_contract( + code=( + Op.POP(Op.CALL(gas=500_000, address=child)) + # All state gas recovered (reservoir + spill), parent + # can perform two SSTOREs from the recovered reservoir + + Op.SSTORE(parent_storage.store_next(1), 1) + + Op.SSTORE(parent_storage.store_next(1), 1) + ), + ) + + # Reservoir = 1 SSTORE's worth of state gas — child will spill + tx = Transaction( + to=parent, + gas_limit=gas_limit_cap + sstore_state_gas, + sender=pre.fund_eoa(), + ) + + post = {parent: Account(storage=parent_storage)} + state_test(env=env, pre=pre, post=post, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_reservoir_restored_after_child_spill_and_halt( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test parent gets reservoir back after child spill + halt. + + The child performs two SSTOREs (zero-to-nonzero), exhausting the + reservoir and spilling into `gas_left`, then hits INVALID causing + an exceptional halt. The child's halt resets its frame to (0, + R0_child) — only the reservoir-portion is returned to the + parent; the spilled gas stays burned (re-classified as regular). + The parent does two SSTOREs: the first drains the recovered + reservoir, the second spills from the parent's own `gas_left`. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment() + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + + # Child does two SSTOREs then halts + child = pre.deploy_contract( + code=(Op.SSTORE(0, 1) + Op.SSTORE(1, 1) + Op.INVALID), + ) + + parent_storage = Storage() + parent = pre.deploy_contract( + code=( + Op.POP(Op.CALL(gas=500_000, address=child)) + # First SSTORE drains the recovered reservoir; second + # SSTORE spills from parent's gas_left (gas_limit_cap is + # large enough to absorb it). + + Op.SSTORE(parent_storage.store_next(1), 1) + + Op.SSTORE(parent_storage.store_next(1), 1) + ), + ) + + # Reservoir = 1 SSTORE's worth of state gas — child will spill + tx = Transaction( + to=parent, + gas_limit=gas_limit_cap + sstore_state_gas, + sender=pre.fund_eoa(), + ) + + post = {parent: Account(storage=parent_storage)} + state_test(env=env, pre=pre, post=post, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_reservoir_restored_after_child_full_drain_and_revert( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test reservoir restored when child exactly exhausts it then reverts. + + The child performs exactly one SSTORE consuming the entire reservoir + (no spill into gas_left), then REVERTs. The full reservoir is + returned to the parent. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment() + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + + child = pre.deploy_contract( + code=(Op.SSTORE(0, 1) + Op.REVERT(0, 0)), + ) + + parent_storage = Storage() + parent = pre.deploy_contract( + code=( + Op.POP(Op.CALL(gas=500_000, address=child)) + + Op.SSTORE(parent_storage.store_next(1), 1) + ), + ) + + tx = Transaction( + to=parent, + gas_limit=gas_limit_cap + sstore_state_gas, + sender=pre.fund_eoa(), + ) + + post = {parent: Account(storage=parent_storage)} + state_test(env=env, pre=pre, post=post, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_sequential_calls_reservoir_restored_between_reverts( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test reservoir restored across sequential child reverts. + + Parent calls child1 which spills and reverts, then calls child2 + which also uses state gas from the restored reservoir. Both + child failures restore the reservoir, so the parent can use it + for its own SSTORE at the end. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment() + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + + child = pre.deploy_contract( + code=(Op.SSTORE(0, 1) + Op.REVERT(0, 0)), + ) + + parent_storage = Storage() + parent = pre.deploy_contract( + code=( + # First child: uses reservoir, reverts — reservoir restored + Op.POP(Op.CALL(gas=500_000, address=child)) + # Second child: uses restored reservoir, reverts — restored again + + Op.POP(Op.CALL(gas=500_000, address=child)) + # Parent SSTORE succeeds with restored reservoir + + Op.SSTORE(parent_storage.store_next(1), 1) + ), + ) + + tx = Transaction( + to=parent, + gas_limit=gas_limit_cap + sstore_state_gas, + sender=pre.fund_eoa(), + ) + + post = {parent: Account(storage=parent_storage)} + state_test(env=env, pre=pre, post=post, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_nested_calls_reservoir_passing( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test reservoir passes through nested calls. + + The reservoir is passed from A to B to C. C performs an SSTORE + using the reservoir gas. After all calls return, A verifies + success. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment() + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + + c_storage = Storage() + c = pre.deploy_contract( + code=Op.SSTORE(c_storage.store_next(1), 1), + ) + + b = pre.deploy_contract( + code=Op.CALL(gas=200_000, address=c), + ) + + a_storage = Storage() + a = pre.deploy_contract( + code=( + Op.SSTORE( + a_storage.store_next(1), + Op.CALL(gas=300_000, address=b), + ) + ), + ) + + tx = Transaction( + to=a, + gas_limit=gas_limit_cap + sstore_state_gas, + sender=pre.fund_eoa(), + ) + + post = { + a: Account(storage=a_storage), + c: Account(storage=c_storage), + } + state_test(env=env, pre=pre, post=post, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_call_value_transfer_new_account( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test CALL with value to non-existent account charges state gas. + + A CALL that transfers value to a non-existent account creates a + new account, charging new-account state gas of state gas. + """ + gas_costs = fork.gas_costs() + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment() + new_account_state_gas = gas_costs.NEW_ACCOUNT + + # Target address that doesn't exist in pre-state + target = 0xDEAD + + parent_storage = Storage() + parent = pre.deploy_contract( + code=( + Op.SSTORE( + parent_storage.store_next(1), + Op.CALL(gas=100_000, address=target, value=1), + ) + ), + balance=1, + ) + + tx = Transaction( + to=parent, + gas_limit=gas_limit_cap + new_account_state_gas, + sender=pre.fund_eoa(), + ) + + post = {parent: Account(storage=parent_storage)} + state_test(env=env, pre=pre, post=post, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_call_value_transfer_existing_account_no_state_gas( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test CALL with value to existing account charges no state gas. + + A CALL that transfers value to an already-alive account does not + create new state, so no state gas is charged. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + # Existing target account + target = pre.fund_eoa(amount=0) + + parent_storage = Storage() + parent = pre.deploy_contract( + code=( + Op.SSTORE( + parent_storage.store_next(1), + Op.CALL(gas=100_000, address=target, value=1), + ) + ), + balance=1, + ) + + tx = Transaction( + to=parent, + gas_limit=gas_limit_cap, + sender=pre.fund_eoa(), + ) + + post = {parent: Account(storage=parent_storage)} + state_test(pre=pre, post=post, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_child_state_gas_tracked_in_parent( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test state gas used by child is accumulated in parent. + + Both parent and child perform SSTOREs. The total state gas used + should reflect both operations. This is verified by the test + succeeding with enough total gas but would OOG if state gas + wasn't tracked across frames. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment() + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + + child_storage = Storage() + child = pre.deploy_contract( + code=Op.SSTORE(child_storage.store_next(1), 1), + ) + + parent_storage = Storage() + parent = pre.deploy_contract( + code=( + # Parent SSTORE + Op.SSTORE(parent_storage.store_next(1), 1) + # Child SSTORE + + Op.SSTORE( + parent_storage.store_next(1), + Op.CALL(gas=100_000, address=child), + ) + ), + ) + + # Provide enough reservoir for both SSTOREs + tx = Transaction( + to=parent, + gas_limit=gas_limit_cap + sstore_state_gas * 2, + sender=pre.fund_eoa(), + ) + + post = { + parent: Account(storage=parent_storage), + child: Account(storage=child_storage), + } + state_test(env=env, pre=pre, post=post, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_delegatecall_reservoir_passing( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test DELEGATECALL passes full reservoir to child. + + DELEGATECALL runs child code in the caller's storage context. + The child's SSTORE writes to the parent's storage using state + gas from the reservoir. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment() + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + + # Library code that writes to slot 0 — runs in parent's context + library = pre.deploy_contract( + code=Op.SSTORE(0, 1), + ) + + parent_storage = Storage() + parent_storage[0] = 1 # Expect slot 0 = 1 after delegatecall + parent = pre.deploy_contract( + code=(Op.DELEGATECALL(gas=100_000, address=library)), + ) + + tx = Transaction( + to=parent, + gas_limit=gas_limit_cap + sstore_state_gas, + sender=pre.fund_eoa(), + ) + + post = {parent: Account(storage=parent_storage)} + state_test(env=env, pre=pre, post=post, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_staticcall_passes_reservoir( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test STATICCALL passes reservoir but cannot use it for state ops. + + STATICCALL forbids state-modifying operations. The reservoir is + passed to the child but cannot be consumed. After the STATICCALL + returns, the parent can still use the reservoir for its own SSTORE. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment() + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + + # Child does a read-only operation + child = pre.deploy_contract( + code=Op.MSTORE(0, Op.ADDRESS), + ) + + parent_storage = Storage() + parent = pre.deploy_contract( + code=( + Op.POP(Op.STATICCALL(gas=100_000, address=child)) + # Reservoir should still be available for parent's SSTORE + + Op.SSTORE(parent_storage.store_next(1), 1) + ), + ) + + tx = Transaction( + to=parent, + gas_limit=gas_limit_cap + sstore_state_gas, + sender=pre.fund_eoa(), + ) + + post = {parent: Account(storage=parent_storage)} + state_test(env=env, pre=pre, post=post, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_gas_opcode_excludes_reservoir( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test GAS opcode returns gas_left only, excluding the reservoir. + + The spec states the GAS opcode reports only gas_left. When the + reservoir is non-empty, the GAS return value should be less than + the total remaining gas (gas_left + reservoir). + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment() + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + + storage = Storage() + contract = pre.deploy_contract( + code=( + # Store GAS opcode result — should only reflect gas_left + Op.SSTORE(0, Op.GAS) + # Store 1 to prove execution reached this point + + Op.SSTORE(storage.store_next(1), 1) + ), + ) + + # Provide large reservoir — GAS should NOT include it + reservoir_gas = sstore_state_gas * 100 + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap + reservoir_gas, + sender=pre.fund_eoa(), + ) + + # Verify: slot 0 should hold a value <= TX_MAX_GAS_LIMIT + # (gas_left is capped by TX_MAX_GAS_LIMIT - intrinsic.regular) + # We can't check the exact value, but we verify the SSTORE + # succeeded and the contract executed correctly + post = {contract: Account(storage=storage)} + state_test(env=env, pre=pre, post=post, tx=tx) + + +@pytest.mark.parametrize( + "target_exists", + [ + pytest.param(True, id="existing_account"), + pytest.param(False, id="new_account"), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_call_insufficient_balance_returns_reservoir( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + target_exists: bool, +) -> None: + """ + Test CALL with insufficient balance returns reservoir to parent. + + When a CALL transfers value but the caller has insufficient balance, + the call fails before any state gas is charged for the target + account. Both gas_left and state_gas_left are returned to the + parent frame. The parent can still use the reservoir for a + subsequent SSTORE. + """ + gas_costs = fork.gas_costs() + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment() + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + + target: int | Address + if target_exists: + target = pre.deploy_contract(code=Op.STOP) + reservoir = sstore_state_gas + else: + target = 0xDEAD + # New account needs new-account state gas too + reservoir = sstore_state_gas + gas_costs.NEW_ACCOUNT + + storage = Storage() + contract = pre.deploy_contract( + code=( + # CALL with 1 wei — fails (contract has 0 balance) + Op.SSTORE( + storage.store_next(0, "call_fails"), + Op.CALL(100_000, target, 1, 0, 0, 0, 0), + ) + # Reservoir should be returned — SSTORE still works + + Op.SSTORE(storage.store_next(1, "sstore_after"), 1) + ), + ) + + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap + reservoir, + sender=pre.fund_eoa(), + ) + + post = {contract: Account(storage=storage)} + state_test(env=env, pre=pre, post=post, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_create_insufficient_balance_returns_reservoir( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test CREATE with insufficient balance returns reservoir to parent. + + When CREATE is called but the sender doesn't have enough balance + for the endowment, the operation fails and both gas and state gas + reservoir are returned to the parent frame. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment() + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + + storage = Storage() + contract = pre.deploy_contract( + code=( + Op.MSTORE(0, int.from_bytes(bytes(Op.STOP), "big") << 248) + # CREATE with 1 wei endowment — fails (contract has 0 balance) + + Op.SSTORE( + storage.store_next(0, "create_fails"), + Op.CREATE(1, 0, 1), + ) + # Reservoir returned — SSTORE still works + + Op.SSTORE(storage.store_next(1, "sstore_after"), 1) + ), + ) + + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap + sstore_state_gas, + sender=pre.fund_eoa(), + ) + + post = {contract: Account(storage=storage)} + state_test(env=env, pre=pre, post=post, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_call_stack_depth_returns_reservoir( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test CALL at stack depth limit returns reservoir. + + When a CALL exceeds the 1024 stack depth limit, the call fails + and gas and state gas reservoir are returned. The parent can still + use the reservoir for state operations. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment() + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + + # Contract that recursively calls itself until depth exhausted, + # then does an SSTORE using the reservoir + storage = Storage() + recursive = pre.deploy_contract( + code=( + # Try recursive call (will eventually hit depth 1024) + Op.POP(Op.CALL(Op.GAS, Op.ADDRESS, 0, 0, 0, 0, 0)) + # After recursion unwinds, only the outermost frame + # reaches this SSTORE + + Op.SSTORE(storage.store_next(1, "after_recursion"), 1) + ), + ) + + tx = Transaction( + to=recursive, + gas_limit=gas_limit_cap + sstore_state_gas, + sender=pre.fund_eoa(), + ) + + post = {recursive: Account(storage=storage)} + state_test(env=env, pre=pre, post=post, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_call_pre_charged_costs_excluded_from_forwarding( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify pre-charged CALL costs do not reduce the 63/64 forwarding budget. + + CALL charges access gas and memory expansion up front, before + computing the 63/64 sub-call gas. Those costs must not be + subtracted again during the forwarding calculation. + + A wrapper contract receives a precise gas budget and calls a child + with maximum gas and a large ret_size (triggering memory expansion). + The child does a cold zero-to-nonzero SSTORE as proof of execution. + The gas budget is tight enough that any double-counting of the + pre-charged costs (access gas, memory expansion, or both) causes + the child to OOG and the SSTORE to revert. + """ + gas_costs = fork.gas_costs() + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + + # Child: SSTORE(0, 1) as proof of execution + child_storage = Storage() + child_code = Op.SSTORE(child_storage.store_next(1, "child_ran"), 1) + child = pre.deploy_contract(child_code) + + child_regular_gas = 2 * gas_costs.VERY_LOW + gas_costs.COLD_STORAGE_WRITE + + # Memory expansion triggered by ret_size on the wrapper's CALL + ret_size = 512 * 32 # 512 words + memory_cost = fork.memory_expansion_gas_calculator()(new_bytes=ret_size) + + extra_gas = gas_costs.COLD_ACCOUNT_ACCESS # cold call, value=0 + + # Wrapper: CALL child requesting max gas with memory expansion + wrapper_code = Op.CALL( + gas=0xFFFFFFFF, + address=child, + value=0, + args_offset=0, + args_size=0, + ret_offset=0, + ret_size=ret_size, + ) + wrapper = pre.deploy_contract(wrapper_code) + + wrapper_pushes = 7 * gas_costs.VERY_LOW # 7 CALL args + + # After the pre-charge of extra_gas + memory_cost, the wrapper has + # gas_remaining left. The 63/64 rule should forward + # gas_remaining * 63/64 to the child — just enough for its SSTORE. + gas_remaining = child_regular_gas * 64 // 63 + memory_cost // 2 + + wrapper_gas = wrapper_pushes + extra_gas + memory_cost + gas_remaining + + caller = pre.deploy_contract( + Op.POP(Op.CALL(gas=wrapper_gas, address=wrapper)) + ) + + sender = pre.fund_eoa() + tx = Transaction( + sender=sender, + to=caller, + gas_limit=gas_limit_cap + sstore_state_gas, + ) + + post = { + child: Account(storage=child_storage), + } + + state_test(pre=pre, tx=tx, post=post) + + +@pytest.mark.valid_from("EIP8037") +def test_call_new_account_header_gas_used( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify block gas accounting for CALL creating a new account. + + A contract CALLs a non-existent address with value, charging + GAS_NEW_ACCOUNT state gas. The block must be accepted with + correct 2D max(regular, state) accounting in the header. + """ + gas_costs = fork.gas_costs() + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + new_account_state_gas = gas_costs.NEW_ACCOUNT + + target = pre.fund_eoa(amount=0) + + storage = Storage() + contract = pre.deploy_contract( + code=( + Op.SSTORE( + storage.store_next(1, "call_succeeds"), + Op.CALL(gas=100_000, address=target, value=1), + ) + ), + balance=1, + ) + + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap + new_account_state_gas, + sender=pre.fund_eoa(), + ) + + blockchain_test( + pre=pre, + blocks=[ + Block(txs=[tx]), + ], + post={contract: Account(storage=storage)}, + ) + + +@pytest.mark.parametrize( + "create_opcode", + [ + pytest.param(Op.CREATE, id="create"), + pytest.param(Op.CREATE2, id="create2"), + ], +) +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +@pytest.mark.valid_from("EIP8037") +def test_call_value_to_self_destructed_same_tx_account( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + create_opcode: Op, +) -> None: + """ + Smoke test for CALL with value to a same transaction + selfdestructed account. + + Confirms the happy path runs to completion. The account still + has its CREATE nonce when the CALL runs, so it is neither empty + nor nonexistent and the new account creation gate does not fire; + end of the transaction destruction removes the account regardless + and the value transferred is burned. Strict discrimination of + the no charge behavior lives in + `test_call_value_to_self_destructed_header_gas_used`. + """ + env = Environment() + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + new_account_state_gas = fork.gas_costs().NEW_ACCOUNT + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + + inner_code = Op.SELFDESTRUCT(Op.ADDRESS) + mstore_value, size = init_code_at_high_bytes(inner_code) + + storage = Storage() + orchestrator = pre.deploy_contract( + code=( + Op.MSTORE(0, mstore_value) + + ( + Op.CREATE2(1, 0, size, 0) + if create_opcode == Op.CREATE2 + else Op.CREATE(1, 0, size) + ) + + Op.MSTORE(0x20, Op.DUP1) + + Op.POP + + Op.SSTORE( + storage.store_next(1, "call_succeeds"), + Op.CALL(gas=Op.GAS, address=Op.MLOAD(0x20), value=1), + ) + ), + balance=3, + ) + + tx = Transaction( + to=orchestrator, + gas_limit=gas_limit_cap + new_account_state_gas + sstore_state_gas, + sender=pre.fund_eoa(), + ) + + post = {orchestrator: Account(storage=storage)} + state_test(env=env, pre=pre, post=post, tx=tx) + + +@pytest.mark.parametrize( + "selfdestruct_beneficiary", + [ + pytest.param("self", id="self_beneficiary"), + pytest.param("external", id="external_beneficiary"), + ], +) +@pytest.mark.parametrize( + "create_opcode", + [ + pytest.param(Op.CREATE, id="create"), + pytest.param(Op.CREATE2, id="create2"), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_call_value_to_self_destructed_header_gas_used( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, + create_opcode: Op, + selfdestruct_beneficiary: str, +) -> None: + """ + Verify block gas accounting for CALL with value to a same + transaction selfdestructed account. + + Reservoir is sized for the CREATE's state charge only. Under + the spec no new account charge fires on the CALL, so block + state gas used equals exactly the single account creation + charge and the header reports that value. The created account + is queued for destruction regardless of whether SELFDESTRUCT + targeted itself or an external beneficiary, so the no charge + behavior holds across both cases. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + new_account_state_gas = fork.gas_costs().NEW_ACCOUNT + + if selfdestruct_beneficiary == "self": + inner_code = Op.SELFDESTRUCT(Op.ADDRESS) + else: + # Alive EOA so the SELFDESTRUCT itself does not charge a + # new account state gas for the beneficiary. + alive_beneficiary = pre.fund_eoa(amount=1) + inner_code = Op.SELFDESTRUCT(alive_beneficiary) + mstore_value, size = init_code_at_high_bytes(inner_code) + + orchestrator = pre.deploy_contract( + code=( + Op.MSTORE(0, mstore_value) + + ( + Op.CREATE2(1, 0, size, 0) + if create_opcode == Op.CREATE2 + else Op.CREATE(1, 0, size) + ) + + Op.MSTORE(0x20, Op.DUP1) + + Op.POP + + Op.POP(Op.CALL(gas=Op.GAS, address=Op.MLOAD(0x20), value=1)) + ), + balance=3, + ) + + tx = Transaction( + to=orchestrator, + gas_limit=gas_limit_cap + new_account_state_gas, + sender=pre.fund_eoa(), + ) + + blockchain_test( + pre=pre, + blocks=[Block(txs=[tx])], + post={}, + ) + + +@pytest.mark.parametrize( + "call_value", + [ + pytest.param(1, id="one_wei"), + pytest.param(10**18, id="one_ether"), + ], +) +@pytest.mark.parametrize( + "create_opcode", + [ + pytest.param(Op.CREATE, id="create"), + pytest.param(Op.CREATE2, id="create2"), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_call_value_to_self_destructed_burns_value( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, + create_opcode: Op, + call_value: int, +) -> None: + """ + Verify value transferred to a same transaction selfdestructed + account is burned when end of the transaction destruction runs. + + The orchestrator funds the inner contract via CREATE, the + initcode immediately selfdestructs, and then the orchestrator + transfers more value into the now queued for destruction + address. At the end of the transaction the account is removed + and the accumulated balance is lost. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + new_account_state_gas = fork.gas_costs().NEW_ACCOUNT + + inner_code = Op.SELFDESTRUCT(Op.ADDRESS) + mstore_value, size = init_code_at_high_bytes(inner_code) + + initial_balance = 2 * call_value + orchestrator = pre.deploy_contract( + code=( + Op.MSTORE(0, mstore_value) + + ( + Op.CREATE2(call_value, 0, size, 0) + if create_opcode == Op.CREATE2 + else Op.CREATE(call_value, 0, size) + ) + + Op.MSTORE(0x20, Op.DUP1) + + Op.POP + + Op.POP( + Op.CALL( + gas=Op.GAS, + address=Op.MLOAD(0x20), + value=call_value, + ) + ) + ), + balance=initial_balance, + ) + # CREATE/CREATE2 address depends on the opcode, but for both the + # orchestrator's nonce after the deploy is 1 at the time of the + # CREATE. Using compute_create_address for CREATE is correct; for + # CREATE2 the deterministic address depends on salt and initcode. + # Use a salt of 0 and the initcode built above for CREATE2. + if create_opcode == Op.CREATE2: + created_address = compute_create2_address( + address=orchestrator, + salt=0, + initcode=bytes(inner_code), + ) + else: + created_address = compute_create_address(address=orchestrator, nonce=1) + + tx = Transaction( + to=orchestrator, + gas_limit=gas_limit_cap + new_account_state_gas, + sender=pre.fund_eoa(), + ) + + blockchain_test( + pre=pre, + blocks=[Block(txs=[tx])], + post={ + created_address: Account.NONEXISTENT, + orchestrator: Account(balance=0), + }, + ) + + +@pytest.mark.parametrize( + "create_opcode", + [ + pytest.param(Op.CREATE, id="create"), + pytest.param(Op.CREATE2, id="create2"), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_call_zero_value_to_self_destructed_same_tx_account( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, + create_opcode: Op, +) -> None: + """ + Verify CALL with zero value to a same transaction selfdestructed + account charges no new account state gas. + + Value transfer gates the new account creation charge. Under the + correct spec the block header reflects only the CREATE's single + new account state gas charge. A spurious charge on the zero + value CALL (value gate broken) would double the state gas + component. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + new_account_state_gas = fork.gas_costs().NEW_ACCOUNT + + inner_code = Op.SELFDESTRUCT(Op.ADDRESS) + mstore_value, size = init_code_at_high_bytes(inner_code) + + orchestrator = pre.deploy_contract( + code=( + Op.MSTORE(0, mstore_value) + + ( + Op.CREATE2(1, 0, size, 0) + if create_opcode == Op.CREATE2 + else Op.CREATE(1, 0, size) + ) + + Op.MSTORE(0x20, Op.DUP1) + + Op.POP + + Op.POP(Op.CALL(gas=Op.GAS, address=Op.MLOAD(0x20), value=0)) + ), + balance=3, + ) + + tx = Transaction( + to=orchestrator, + gas_limit=gas_limit_cap + new_account_state_gas, + sender=pre.fund_eoa(), + ) + + blockchain_test( + pre=pre, + blocks=[Block(txs=[tx])], + post={}, + ) + + +@pytest.mark.parametrize( + "beneficiary_type", + [ + pytest.param("eoa", id="eoa_beneficiary"), + pytest.param("contract", id="contract_beneficiary"), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_call_value_to_pre_existing_selfdestructed_account( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, + beneficiary_type: str, +) -> None: + """ + Verify CALL with value to a pre existing contract that ran + SELFDESTRUCT charges no new account state gas. + + Per EIP-6780 a pre existing contract that executes SELFDESTRUCT + is not queued for end of the transaction destruction, so a + subsequent CALL sees an existing, code carrying account and the + new account creation gate does not fire. + + Several cold SSTOREs after the CALLs make block state gas + dominate the block regular gas component, so the block header + reflects exactly `num_probes * sstore_state_gas`. A spurious + new account charge on the value bearing CALL would push the + header up by that charge, breaking the assertion. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + + # Enough probes that the combined probe state gas dominates the + # transaction's regular gas component and the header reflects + # block state gas alone. + num_probes = 6 + probe_state_gas = num_probes * sstore_state_gas + + # Beneficiary must be alive so the target's SELFDESTRUCT itself + # does not charge for creating a new beneficiary. + beneficiary: Address = ( + pre.fund_eoa(amount=1) + if beneficiary_type == "eoa" + else pre.deploy_contract(code=Op.STOP) + ) + target = pre.deploy_contract( + code=Op.SELFDESTRUCT(beneficiary), + balance=1, + ) + + probes = Bytecode() + for slot in range(num_probes): + probes += Op.SSTORE(slot, 1) + orchestrator = pre.deploy_contract( + code=( + Op.POP(Op.CALL(gas=Op.GAS, address=target)) + + Op.POP(Op.CALL(gas=Op.GAS, address=target, value=1)) + + probes + ), + balance=3, + ) + + tx = Transaction( + to=orchestrator, + gas_limit=gas_limit_cap + probe_state_gas, + sender=pre.fund_eoa(), + ) + + blockchain_test( + pre=pre, + blocks=[ + Block( + txs=[tx], + header_verify=Header(gas_used=probe_state_gas), + ), + ], + post={}, + ) + + +@pytest.mark.parametrize( + "reservoir_delta", + [ + pytest.param(-1, id="reservoir_one_short"), + pytest.param(0, id="reservoir_exact"), + pytest.param(1, id="reservoir_one_over"), + ], +) +@pytest.mark.parametrize( + "child_termination", + [ + pytest.param("revert", id="child_revert"), + pytest.param("halt", id="child_halt"), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_top_level_halt_refunds_total_state_gas( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, + child_termination: str, + reservoir_delta: int, +) -> None: + """ + Verify a top-level halt refunds the total state-gas consumed + (reservoir-portion + spilled-portion) regardless of child failure + mode. The parent calls a child that either reverts or halts, then + INVALIDs at the top level. + + Per the updated EIP, both child failure modes propagate the full + `state_gas_used` back through `incorporate_child_on_error`, and + the top-level halt no longer overrides it. The tx-level error + handler then folds the residual into the reservoir, so + `state_gas_left_end = max(reservoir, child_charge)` and + `tx_gas_used = tx.gas - state_gas_left_end`: + + - `reservoir < child_charge` (one_short): spill is refunded too, + `tx_gas_used = gas_limit_cap - (child_charge - reservoir)`. + - `reservoir >= child_charge`: no spill, `tx_gas_used = + gas_limit_cap`. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + + if child_termination == "revert": + child_code: Bytecode = Op.SSTORE(0, 1) + Op.REVERT(0, 0) + else: + child_code = Op.SSTORE(0, 1) + Op.INVALID + + child = pre.deploy_contract(code=child_code) + + parent = pre.deploy_contract( + code=(Op.POP(Op.CALL(gas=500_000, address=child)) + Op.INVALID), + ) + + reservoir = sstore_state_gas + reservoir_delta + tx_gas = gas_limit_cap + reservoir + + tx = Transaction( + to=parent, + gas_limit=tx_gas, + sender=pre.fund_eoa(), + ) + + # Policy A halt: state_gas counters preserved through the child + # halt/revert, parent halt, and tx-level fold. + # state_gas_left_end = max(reservoir, sstore_state_gas). + state_gas_left_end = max(reservoir, sstore_state_gas) + expected_gas_used = tx_gas - state_gas_left_end + + blockchain_test( + pre=pre, + blocks=[ + Block( + txs=[tx], + header_verify=Header(gas_used=expected_gas_used), + ), + ], + post={child: Account(storage={0: 0})}, + ) + + +@pytest.mark.valid_from("EIP8037") +def test_callcode_value_no_new_account_state_gas( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify CALLCODE with value does not charge new-account state + gas, since the value stays with the caller. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + + target = pre.fund_eoa(amount=0) + + storage = Storage() + contract = pre.deploy_contract( + code=( + Op.POP( + Op.CALLCODE( + gas=Op.GAS, + address=target, + value=1, + ) + ) + + Op.SSTORE(storage.store_next(1, "reservoir_ok"), 1) + ), + balance=10**18, + ) + + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap + sstore_state_gas, + sender=pre.fund_eoa(), + ) + + post = { + contract: Account(storage=storage), + target: Account.NONEXISTENT, + } + state_test(pre=pre, post=post, tx=tx) + + +@pytest.mark.with_all_create_opcodes() +@pytest.mark.valid_from("EIP8037") +def test_create_oog_during_state_gas_charge( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + create_opcode: Op, +) -> None: + """ + Verify the parent reservoir is refunded when a child's CREATE + OOGs while charging account-creation state gas. The grandchild + SSTORE is forwarded only its regular stipend, so it succeeds + only if the refund landed in the reservoir (not in `gas_left`). + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + gas_costs = fork.gas_costs() + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + + init_code = Op.STOP + inner_create_call = ( + create_opcode(value=0, offset=31, size=1, salt=0) + if create_opcode == Op.CREATE2 + else create_opcode(value=0, offset=31, size=1) + ) + + inner = pre.deploy_contract( + code=( + Op.MSTORE( + 0, + int.from_bytes(bytes(init_code), "big") << 248, + ) + + Op.POP(inner_create_call) + ), + ) + + grandchild = pre.deploy_contract(code=Op.SSTORE(0, 1)) + + push_cost = 2 * gas_costs.VERY_LOW + sstore_regular = gas_costs.COLD_STORAGE_WRITE + grandchild_stipend = push_cost + sstore_regular + + parent = pre.deploy_contract( + code=( + Op.POP(Op.CALL(gas=20_000, address=inner)) + + Op.POP(Op.CALL(gas=grandchild_stipend, address=grandchild)) + ), + ) + + tx = Transaction( + to=parent, + gas_limit=gas_limit_cap + sstore_state_gas, + sender=pre.fund_eoa(), + ) + + state_test( + pre=pre, + post={grandchild: Account(storage={0: 1})}, + tx=tx, + ) + + +@pytest.mark.valid_from("EIP8037") +def test_call_new_account_no_regular_account_creation_cost( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify CALL with value to a non-existent account does not + charge a regular account-creation cost on top of state gas. + """ + gas_costs = fork.gas_costs() + new_account_state_gas = gas_costs.NEW_ACCOUNT + + target = pre.fund_eoa(amount=0) + + caller_code = Op.POP(Op.CALL(gas=0, address=target, value=1)) + Op.STOP + caller = pre.deploy_contract(code=caller_code, balance=1) + + # Tight budget: slack is less than the old pre-Amsterdam regular + # account-creation cost, so any extra regular draw would OOG. + intrinsic = fork.transaction_intrinsic_cost_calculator()() + tx = Transaction( + to=caller, + gas_limit=( + intrinsic + + caller_code.gas_cost(fork) + + gas_costs.CALL_VALUE + + new_account_state_gas + + 20_000 + ), + sender=pre.fund_eoa(), + ) + + state_test(pre=pre, post={target: Account(balance=1)}, tx=tx) + + +@pytest.mark.parametrize( + "call_opcode,charge_via", + [ + pytest.param(Op.CALL, "sstore", id="call_sstore_charge"), + pytest.param( + Op.DELEGATECALL, "sstore", id="delegatecall_sstore_charge" + ), + pytest.param( + Op.CALL, + "call_value_new_account", + id="call_call_value_new_account_charge", + ), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_child_failure_refunds_state_gas_to_reservoir_not_gas_left( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + call_opcode: Op, + charge_via: str, +) -> None: + """ + Verify state gas from a failing child is restored to the + reservoir, so a sibling probe SSTORE can draw from it under a + tight regular stipend. Covers SSTORE and CALL-value (new + account) state-gas charge paths. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + gas_costs = fork.gas_costs() + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + + probe = pre.deploy_contract(code=Op.SSTORE(0, 1)) + + if charge_via == "sstore": + child_code: Bytecode = Op.SSTORE(0, 1) + Op.REVERT(0, 0) + child_balance = 0 + child_state_charge = sstore_state_gas + else: + fresh_target = pre.fund_eoa(amount=0) + child_code = Op.POP( + Op.CALL(gas=Op.GAS, address=fresh_target, value=1) + ) + Op.REVERT(0, 0) + child_balance = 1 + child_state_charge = gas_costs.NEW_ACCOUNT + + child = pre.deploy_contract(code=child_code, balance=child_balance) + + # Tight stipend: just enough regular gas for the probe's SSTORE + # opcode plus its two stack pushes, leaving no slack to absorb a + # state-gas spill. + push_cost = 2 * gas_costs.VERY_LOW + sstore_regular = gas_costs.COLD_STORAGE_WRITE + probe_stipend = push_cost + sstore_regular + + parent = pre.deploy_contract( + code=( + Op.POP(call_opcode(gas=Op.GAS, address=child)) + + Op.POP(call_opcode(gas=probe_stipend, address=probe)) + ), + ) + + # Reservoir must cover the child's state charge (refunded on + # REVERT) so the probe SSTORE can draw from it afterwards. + reservoir = max(child_state_charge, sstore_state_gas) + + tx = Transaction( + to=parent, + gas_limit=gas_limit_cap + reservoir, + sender=pre.fund_eoa(), + ) + + # DELEGATECALL executes the callee in the caller's storage + # context, so the probe's SSTORE lands on `parent` instead of + # `probe`. + if call_opcode == Op.DELEGATECALL: + post: dict = {parent: Account(storage={0: 1})} + else: + post = {probe: Account(storage={0: 1})} + + state_test( + pre=pre, + post=post, + tx=tx, + blockchain_test_header_verify=Header(gas_used=sstore_state_gas), + ) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_calldata_floor.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_calldata_floor.py new file mode 100644 index 00000000000..8dc98e0d063 --- /dev/null +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_calldata_floor.py @@ -0,0 +1,240 @@ +""" +Test EIP-7623 calldata floor interaction with EIP-8037 state gas. + +The calldata floor applies to the regular gas dimension only. It +does not affect state gas. Block gas accounting uses +max(tx_regular_gas, calldata_floor) for regular gas and tracks +state gas separately. + +Tests for [EIP-8037: State Creation Gas Cost Increase] +(https://eips.ethereum.org/EIPS/eip-8037). +""" + +import pytest +from execution_testing import ( + Account, + Alloc, + Block, + BlockchainTestFiller, + Environment, + Fork, + Op, + StateTestFiller, + Storage, + Transaction, + TransactionException, +) +from execution_testing.checklists import EIPChecklist + +from .spec import ref_spec_8037 + +REFERENCE_SPEC_GIT_PATH = ref_spec_8037.git_path +REFERENCE_SPEC_VERSION = ref_spec_8037.version + + +@EIPChecklist.GasRefundsChanges.Test.CrossFunctional.CalldataCost() +@pytest.mark.valid_from("EIP8037") +def test_calldata_floor_with_sstore( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test calldata floor does not affect state gas charging. + + A transaction with large calldata triggers the calldata floor for + regular gas, but state gas for SSTORE is charged independently. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + storage = Storage() + contract = pre.deploy_contract( + code=Op.SSTORE(storage.store_next(1), 1), + ) + + # Large calldata to trigger the calldata floor + calldata = b"\x01" * 256 + + tx = Transaction( + to=contract, + data=calldata, + gas_limit=gas_limit_cap, + sender=pre.fund_eoa(), + ) + + post = {contract: Account(storage=storage)} + state_test(pre=pre, post=post, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_calldata_floor_independent_of_state_gas( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test calldata floor applies only to regular gas dimension. + + The calldata floor inflates regular gas used for block accounting + but does not affect the state gas dimension. A transaction with + high calldata and no state operations should succeed even when + the floor exceeds actual execution gas. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + contract = pre.deploy_contract(code=Op.STOP) + + # Large calldata so the floor exceeds actual execution gas + calldata = b"\xff" * 512 + + tx = Transaction( + to=contract, + data=calldata, + gas_limit=gas_limit_cap, + sender=pre.fund_eoa(), + ) + + state_test(pre=pre, post={}, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_calldata_floor_higher_than_execution_with_state_ops( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test state gas is tracked separately when calldata floor dominates. + + Even when calldata floor > actual regular gas used, state gas for + SSTORE is charged normally from the reservoir or gas_left. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment() + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + + storage = Storage() + contract = pre.deploy_contract( + code=Op.SSTORE(storage.store_next(1), 1), + ) + + # Large calldata so floor dominates regular gas + calldata = b"\x01" * 1024 + + tx = Transaction( + to=contract, + data=calldata, + gas_limit=gas_limit_cap + sstore_state_gas, + sender=pre.fund_eoa(), + ) + + post = {contract: Account(storage=storage)} + state_test(env=env, pre=pre, post=post, tx=tx) + + +@pytest.mark.parametrize( + "exceeds_cap", + [ + pytest.param(False, id="at_cap"), + pytest.param(True, id="exceeds_cap", marks=pytest.mark.exception_test), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_calldata_floor_exceeding_tx_gas_limit_cap( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + exceeds_cap: bool, +) -> None: + """ + Verify calldata floor > TX_MAX_GAS_LIMIT rejects the transaction. + + When the EIP-7623 calldata floor cost exceeds the EIP-7825 transaction + gas limit cap, the transaction must be rejected at validation — + even though the regular intrinsic gas may be within the cap. + + at_cap: tightest calldata floor that fits within the cap — + transaction accepted. + exceeds_cap: one zero byte more tips the floor over the cap — + transaction rejected. + """ + gas_costs = fork.gas_costs() + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + + floor_token = gas_costs.TX_DATA_TOKEN_FLOOR + tx_base = gas_costs.TX_BASE + max_tokens = (gas_limit_cap - tx_base) // floor_token + + if fork.is_eip_enabled(7976): + # EIP-7976: all bytes contribute 4 floor tokens regardless of + # value, so the token count is len(data) * 4. + tokens_per_byte = 4 + max_bytes = max_tokens // tokens_per_byte + if exceeds_cap: + max_bytes += 1 + calldata = b"\x01" * max_bytes + else: + # EIP-7623: non-zero bytes contribute 4 tokens, zero bytes 1. + tokens_per_nonzero = 4 + nonzero_bytes = max_tokens // tokens_per_nonzero + zero_bytes = max_tokens - nonzero_bytes * tokens_per_nonzero + if exceeds_cap: + zero_bytes += 1 + calldata = b"\x01" * nonzero_bytes + b"\x00" * zero_bytes + contract = pre.deploy_contract(Op.STOP) + + tx = Transaction( + to=contract, + data=calldata, + gas_limit=gas_limit_cap, + sender=pre.fund_eoa(), + error=TransactionException.INTRINSIC_GAS_TOO_LOW + if exceeds_cap + else None, + ) + + post = {contract: Account(code=Op.STOP)} if not exceeds_cap else {} + state_test(pre=pre, post=post, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_calldata_floor_applied_to_sender_refund( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify the calldata floor is applied to the sender gas refund. + + With a STOP callee and large all-nonzero calldata, execution gas + falls below the calldata floor. The sender must be charged + `calldata_floor * gas_price`, so the final balance reflects the + floor-applied value, not the pre-floor execution cost. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + calldata = b"\xff" * 1024 + calldata_floor = fork.transaction_intrinsic_cost_calculator()( + calldata=calldata, + ) + gas_price = 10**9 + initial = gas_limit_cap * gas_price + + contract = pre.deploy_contract(code=Op.STOP) + sender = pre.fund_eoa(amount=initial) + + tx = Transaction( + to=contract, + data=calldata, + gas_limit=gas_limit_cap, + gas_price=gas_price, + sender=sender, + ) + + blockchain_test( + pre=pre, + blocks=[Block(txs=[tx])], + post={sender: Account(balance=initial - calldata_floor * gas_price)}, + ) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py new file mode 100644 index 00000000000..dcd4663b51c --- /dev/null +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py @@ -0,0 +1,2512 @@ +""" +Test CREATE and CREATE2 state gas charging under EIP-8037. + +Contract creation charges state gas for the new account and for +code deposit. Regular gas for CREATE is charged separately. + +Tests for [EIP-8037: State Creation Gas Cost Increase] +(https://eips.ethereum.org/EIPS/eip-8037). +""" + +from typing import Union + +import pytest +from execution_testing import ( + Account, + Alloc, + Block, + BlockchainTestFiller, + Bytecode, + Environment, + Fork, + Header, + Initcode, + Op, + StateTestFiller, + Storage, + Transaction, + TransactionException, + TransactionReceipt, + compute_create2_address, + compute_create_address, +) +from execution_testing.checklists import EIPChecklist + +from .spec import init_code_at_high_bytes, ref_spec_8037 + +REFERENCE_SPEC_GIT_PATH = ref_spec_8037.git_path +REFERENCE_SPEC_VERSION = ref_spec_8037.version + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +@pytest.mark.valid_from("EIP8037") +def test_create_charges_state_gas( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test CREATE charges state gas for new account and code deposit. + + A successful CREATE charges new-account state gas plus code + deposit state gas proportional to the deployed code size. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + init_code = Op.STOP + + storage = Storage() + contract = pre.deploy_contract( + code=( + Op.MSTORE( + 0, + int.from_bytes(bytes(init_code), "big") + << (256 - 8 * len(init_code)), + ) + + Op.SSTORE( + storage.store_next(True), + Op.GT(Op.CREATE(0, 0, len(init_code)), 0), + ) + ), + ) + + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap, + sender=pre.fund_eoa(), + ) + + post = {contract: Account(storage=storage)} + state_test(pre=pre, post=post, tx=tx) + + +@pytest.mark.parametrize( + "opcode", + [ + pytest.param(Op.CREATE, id="create"), + pytest.param(Op.CREATE2, id="create2"), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_create_with_reservoir( + state_test: StateTestFiller, + pre: Alloc, + opcode: Op, + fork: Fork, +) -> None: + """ + Test CREATE/CREATE2 with state gas funded from the reservoir. + + Provide gas above TX_MAX_GAS_LIMIT so the new account state gas + is drawn from the reservoir rather than gas_left. + """ + gas_costs = fork.gas_costs() + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment() + create_state_gas = gas_costs.NEW_ACCOUNT + + storage = Storage() + init_code = Op.STOP + + if opcode == Op.CREATE: + create_call = Op.CREATE(0, 0, len(init_code)) + else: + create_call = Op.CREATE2(0, 0, len(init_code), 0) + + contract = pre.deploy_contract( + code=( + Op.MSTORE( + 0, + int.from_bytes(bytes(init_code), "big") + << (256 - 8 * len(init_code)), + ) + + Op.SSTORE( + storage.store_next(True), + Op.GT(create_call, 0), + ) + ), + ) + + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap + create_state_gas, + sender=pre.fund_eoa(), + ) + + post = {contract: Account(storage=storage)} + state_test(env=env, pre=pre, post=post, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_create2_child_spill_not_double_charged( + state_test: StateTestFiller, + pre: Alloc, +) -> None: + """ + Test CREATE2 child state gas paid from `gas_left` is not recharged. + + The factory executes below the Amsterdam tx gas cap, so the CREATE2 child + pays new-account and storage state gas by spilling from `gas_left`. The + factory must not charge the same state growth again at frame end. + """ + env = Environment() + + init_code = sum(Op.SSTORE(i, i + 1) for i in range(6)) + Op.STOP + mstore_value, initcode_size = init_code_at_high_bytes(init_code) + + factory = pre.deploy_contract( + code=( + Op.MSTORE(0, mstore_value) + + Op.POP( + Op.CREATE2( + value=0, + offset=0, + size=initcode_size, + salt=0, + ) + ) + ) + ) + created = compute_create2_address( + address=factory, + salt=0, + initcode=bytes(init_code), + ) + + tx = Transaction( + to=factory, + gas_limit=1_000_000, + sender=pre.fund_eoa(), + ) + + post = { + created: Account(nonce=1, storage={i: i + 1 for i in range(6)}), + } + state_test(env=env, pre=pre, post=post, tx=tx) + + +@pytest.mark.parametrize( + "code_size", + [ + pytest.param(1, id="tiny_code"), + pytest.param(32, id="one_word"), + pytest.param(256, id="small_contract"), + pytest.param(1024, id="medium_contract"), + pytest.param("max", id="max_code_size"), + pytest.param("max+1", id="over_max_code_size"), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_code_deposit_state_gas_scales_with_size( + state_test: StateTestFiller, + pre: Alloc, + code_size: Union[int, str], + fork: Fork, +) -> None: + """ + Test code deposit state gas scales linearly with code size. + + The code deposit charges len(code) * cost_per_state_byte of state + gas. Larger deployed code requires proportionally more state gas. + When code exceeds MAX_CODE_SIZE, the size check rejects before + any gas is charged and the contract is not deployed. + """ + if code_size == "max": + code_size = fork.max_code_size() + elif code_size == "max+1": + code_size = fork.max_code_size() + 1 + assert isinstance(code_size, int) + + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment() + # State gas: new account + code deposit + total_state_gas = fork.create_state_gas(code_size=code_size) + + # Build init code that returns `code_size` bytes of 0x00 + # PUSH2 code_size, PUSH1 0, RETURN + init_code = Op.RETURN(0, code_size) + + sender = pre.fund_eoa() + tx = Transaction( + to=None, + data=init_code, + gas_limit=gas_limit_cap + total_state_gas, + sender=sender, + ) + + if code_size > fork.max_code_size(): + create_address = compute_create_address(address=sender, nonce=0) + post = {create_address: Account.NONEXISTENT} + else: + post = {} + + state_test(env=env, pre=pre, post=post, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_repeated_create_same_code_charges_each_account( + state_test: StateTestFiller, + pre: Alloc, +) -> None: + """ + Test code deposit is charged per-account, not per code hash. + + Two CREATEs with identical init code deploy identical bytecode + and so share a single ``code_hash``. The factory snapshots + ``gas_left`` around each CREATE via ``Op.GAS`` and stores + ``(g0 - g1) - (g1 - g2)`` in slot 0. Identical work must cost + the same — so the difference must be zero. + + Runtime measurement is required: the bug manifests as a + child-frame state-gas spillover into ``gas_left`` (a runtime + quantity), which static helpers like ``bytecode.gas_cost()`` + do not model. + + A non-zero result indicates ``compute_state_byte_diff`` is + keying code-deposit accounting by hash via ``code_writes``, + silently dropping the second CREATE's ``len(code) × CPSB`` + charge. + """ + # Y init code returns memory[0:1] = 0x00 to deploy a 1-byte STOP. + y_init = Op.PUSH1(1) + Op.PUSH1(0) + Op.RETURN + y_size = len(bytes(y_init)) + + # Memory layout: + # [ 0: 32) — Y init code (right-aligned PUSH32 padding) + # [32: 64) — g0 (gas before first CREATE) + # [64: 96) — g1 (gas between the two CREATEs) + # [96:128) — g2 (gas after second CREATE) + factory_code = ( + Op.MSTORE(0, Op.PUSH32(bytes(y_init))) + + Op.MSTORE(32, Op.GAS) + + Op.POP(Op.CREATE(value=0, offset=32 - y_size, size=y_size)) + + Op.MSTORE(64, Op.GAS) + + Op.POP(Op.CREATE(value=0, offset=32 - y_size, size=y_size)) + + Op.MSTORE(96, Op.GAS) + + Op.SSTORE( + 0, + Op.SUB( + Op.SUB(Op.MLOAD(32), Op.MLOAD(64)), # cost of CREATE 1 + Op.SUB(Op.MLOAD(64), Op.MLOAD(96)), # cost of CREATE 2 + ), + ) + + Op.STOP + ) + + factory_storage = Storage() + factory_storage[0] = 0 + factory = pre.deploy_contract(code=factory_code, storage=factory_storage) + + tx = Transaction( + to=factory, + sender=pre.fund_eoa(), + gas_limit=2_000_000, + ) + + state_test( + pre=pre, + post={factory: Account(storage=factory_storage)}, + tx=tx, + ) + + +@pytest.mark.valid_from("EIP8037") +def test_create_tx_state_gas( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test contract creation transaction charges intrinsic state gas. + + A create transaction (to=None) charges new-account state gas + as intrinsic state gas for the new account, plus code deposit state + gas for the deployed bytecode. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + tx = Transaction( + to=None, + data=Op.STOP, + gas_limit=gas_limit_cap, + sender=pre.fund_eoa(), + ) + + state_test(pre=pre, post={}, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_create_revert_no_code_deposit_state_gas( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test reverted CREATE does not charge code deposit state gas. + + When CREATE fails during init code execution (REVERT), the new + account state gas is consumed but no code deposit state gas is + charged because no code was deployed. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + init_code = Op.REVERT(0, 0) + + storage = Storage() + contract = pre.deploy_contract( + code=( + Op.MSTORE( + 0, + int.from_bytes(bytes(init_code), "big") + << (256 - 8 * len(init_code)), + ) + + Op.SSTORE( + storage.store_next(0), # CREATE returns 0 on failure + Op.CREATE(0, 0, len(init_code)), + ) + ), + ) + + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap, + sender=pre.fund_eoa(), + ) + + post = {contract: Account(storage=storage)} + state_test(pre=pre, post=post, tx=tx) + + +@EIPChecklist.GasCostChanges.Test.OutOfGas() +@pytest.mark.valid_from("EIP8037") +def test_create_insufficient_state_gas( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test CREATE OOGs when state gas is insufficient. + + Provide enough gas for CREATE's regular gas cost but not enough + to cover the new-account state gas. The CREATE should fail, + returning 0. + """ + init_code = Op.STOP + + storage = Storage() + contract = pre.deploy_contract( + code=( + Op.MSTORE( + 0, + int.from_bytes(bytes(init_code), "big") + << (256 - 8 * len(init_code)), + ) + + Op.SSTORE( + storage.store_next(0), # CREATE returns 0 on OOG + Op.CREATE(0, 0, len(init_code)), + ) + ), + ) + + # Tight gas — enough for intrinsic + CREATE regular gas but not + # enough for the new account state gas + gas_costs = fork.gas_costs() + intrinsic_cost = fork.transaction_intrinsic_cost_calculator() + regular_create_gas = gas_costs.OPCODE_CREATE_BASE + gas_limit = intrinsic_cost() + regular_create_gas + 10_000 + + tx = Transaction( + to=contract, + gas_limit=gas_limit, + sender=pre.fund_eoa(), + ) + + post = {contract: Account(storage=storage)} + state_test(pre=pre, post=post, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_create2_address_collision( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test CREATE2 returns zero on address collision. + + When CREATE2 targets an address that already has code or storage, + the collision is detected early and returns zero without charging + state gas. The existing account is left unchanged. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + init_code = Op.STOP + salt = 0 + + storage = Storage() + contract = pre.deploy_contract( + code=( + Op.MSTORE( + 0, + int.from_bytes(bytes(init_code), "big") + << (256 - 8 * len(init_code)), + ) + # First CREATE2 succeeds + + Op.SSTORE( + storage.store_next(1, "first_create2"), + Op.ISZERO(Op.ISZERO(Op.CREATE2(0, 0, len(init_code), salt))), + ) + # Second CREATE2 with same salt collides + + Op.SSTORE( + storage.store_next(0, "collision_create2"), + Op.CREATE2(0, 0, len(init_code), salt), + ) + ), + ) + + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap * 2, + sender=pre.fund_eoa(), + ) + + post = {contract: Account(storage=storage)} + state_test(pre=pre, post=post, tx=tx) + + +@pytest.mark.parametrize( + "gas_delta", + [ + pytest.param( + -1, + id="below_intrinsic", + marks=pytest.mark.exception_test, + ), + pytest.param(0, id="at_intrinsic"), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_create_tx_intrinsic_gas_boundary( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + gas_delta: int, +) -> None: + """ + Test CREATE tx intrinsic gas boundary includes state component. + + The intrinsic gas for a contract-creating transaction includes + both regular gas and state gas. A transaction with gas_limit + exactly at the boundary succeeds; one gas below is rejected. + """ + intrinsic_cost = fork.transaction_intrinsic_cost_calculator() + gas_limit = intrinsic_cost( + contract_creation=True, + ) + + tx = Transaction( + to=None, + gas_limit=gas_limit + gas_delta, + sender=pre.fund_eoa(), + error=( + TransactionException.INTRINSIC_GAS_TOO_LOW + if gas_delta < 0 + else None + ), + ) + + state_test(pre=pre, post={}, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_code_deposit_oog_preserves_parent_reservoir( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test parent reservoir preserved after child code deposit OOG. + + A caller contract invokes the factory via CALL with limited gas. + The child CREATE returns enough bytes that code deposit state gas + exceeds the child frame's available gas (reservoir spillover plus + the limited gas_left). The factory's SSTORE after the failed + CREATE proves the reservoir was not inflated by a spill-then-halt + refund. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + gas_costs = fork.gas_costs() + new_account_state_gas = gas_costs.NEW_ACCOUNT + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + + # Small deploy size; code deposit state gas will exceed the + # limited gas available in the CREATE child frame. + deploy_size = 4096 + init_code = Op.RETURN(0, deploy_size) + + # Limited regular gas forwarded to the factory. After CREATE + # takes 63/64, the factory retains ~15 K for its SSTOREs. + child_gas = 1_000_000 + + factory_storage = Storage() + factory = pre.deploy_contract( + code=( + Op.MSTORE(0, Op.PUSH32(bytes(init_code))) + + Op.SSTORE( + factory_storage.store_next(0, "create_fails"), + Op.CREATE( + value=0, + offset=32 - len(init_code), + size=len(init_code), + ), + ) + # Reservoir must be fully preserved after failed CREATE; + # parent can still perform its own SSTORE. + + Op.SSTORE( + factory_storage.store_next(1, "parent_sstore"), + 1, + ) + ), + ) + + # Caller invokes factory with limited gas via CALL. + caller = pre.deploy_contract( + code=Op.CALL(gas=child_gas, address=factory), + ) + + # Reservoir = new-account state gas + one SSTORE's state gas. + # Code deposit draws from the reservoir first then spills into + # gas_left, which the limited CALL gas cannot cover. + tx = Transaction( + to=caller, + gas_limit=(gas_limit_cap + new_account_state_gas + sstore_state_gas), + sender=pre.fund_eoa(), + ) + + post = {factory: Account(storage=factory_storage)} + state_test(pre=pre, post=post, tx=tx) + + +@pytest.mark.parametrize( + ("with_reservoir", "failure_op"), + [ + pytest.param(True, Op.REVERT(0, 0), id="with_reservoir-revert"), + pytest.param(True, Op.INVALID, id="with_reservoir-halt"), + pytest.param(False, Op.REVERT(0, 0), id="no_reservoir-revert"), + pytest.param(False, Op.INVALID, id="no_reservoir-halt"), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_parent_state_gas_after_child_failure( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + with_reservoir: bool, + failure_op: Bytecode, +) -> None: + """ + Test parent state-gas pools after CREATE child failure. + + A factory invokes CREATE whose initcode performs an SSTORE + (charging state gas) then either REVERTs or hits INVALID. The + factory's own SSTORE after the failed CREATE acts as the + discriminator that the parent's state-gas accounting (reservoir + and gas_left) is in the expected state. + + Four scenarios cover the gas-pool state space: + + - `with_reservoir x revert`: child state gas (new account + + initcode SSTORE) is fully refunded to the parent reservoir on + REVERT. + - `with_reservoir x halt`: HALT resets the child frame to + `(0, R0_child)`; only the reservoir-portion entering the + initcode is returned, any spilled gas stays burned. + - `no_reservoir x revert`: child state gas refunded forms a + fresh reservoir even though `R0_parent` started at 0. + - `no_reservoir x halt`: no phantom reservoir may form; the + factory's post-CREATE SSTORE must spill from gas_left. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + gas_costs = fork.gas_costs() + intrinsic_cost = fork.transaction_intrinsic_cost_calculator()() + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + new_account_state_gas = gas_costs.NEW_ACCOUNT + + initcode = Op.SSTORE(0, 1, original_value=0, new_value=1) + failure_op + + factory_storage = Storage() + factory_code = ( + Op.MSTORE(0, Op.PUSH32(bytes(initcode))) + + Op.SSTORE( + factory_storage.store_next(0, "create_fails"), + Op.CREATE( + value=0, + offset=32 - len(initcode), + size=len(initcode), + ), + original_value=0, + new_value=0, + ) + + Op.SSTORE( + factory_storage.store_next(1, "post_create"), + 1, + original_value=0, + new_value=1, + ) + ) + factory = pre.deploy_contract(code=factory_code) + + gas_limit = ( + gas_limit_cap + new_account_state_gas + sstore_state_gas * 2 + if with_reservoir + else 5_000_000 + ) + + # `bytecode.gas_cost(fork)` accounts for opcode base costs and + # state-gas charges, but does NOT track memory-expansion or CREATE + # init-code word costs. Add those back to recover runtime regular + # gas consumption. + init_code_word_count = (len(initcode) + 31) // 32 + init_code_word_cost = gas_costs.CODE_INIT_PER_WORD * init_code_word_count + mstore_memory_expansion = gas_costs.MEMORY_PER_WORD # 1 word + gas_cost_helper_extras = init_code_word_cost + mstore_memory_expansion + + # Factory bytecode shape costs, derived from fork.gas_costs(): + # pre-CREATE: PUSH32 + PUSH1 + MSTORE (with 1-word expansion) + # + 3 PUSHes for CREATE inputs + # post-CREATE: PUSH key + SSTORE (no-op) + 2 PUSHes + SSTORE + # (zero-to-nonzero regular) + factory_pre_create_regular = ( + gas_costs.VERY_LOW * 2 + + gas_costs.OPCODE_MSTORE_BASE + + mstore_memory_expansion + + gas_costs.VERY_LOW * 3 + ) + factory_post_create_regular = ( + gas_costs.VERY_LOW + + gas_costs.COLD_STORAGE_ACCESS + + gas_costs.WARM_ACCESS + + gas_costs.VERY_LOW * 2 + + gas_costs.COLD_STORAGE_WRITE + ) + + factory_regular = ( + factory_code.gas_cost(fork) + - new_account_state_gas + - sstore_state_gas + + gas_cost_helper_extras + ) + initcode_regular_revert = initcode.gas_cost(fork) - sstore_state_gas + + if failure_op == Op.INVALID: + # Simulate runtime gas accounting for HALT using fork helpers: + # 1. Initial regular pool capped by transaction_gas_limit_cap; + # remainder forms the state reservoir. + # 2. CREATE op charges new_account state gas (from reservoir + # first, spilled to gas_left otherwise). + # 3. 63/64 retention rule: parent retains gas_left // 64. + # 4. INVALID burns all forwarded regular gas in the child. + # Per the updated EIP, child halt preserves its state-gas + # counters and `incorporate_child_on_error` refunds the + # full child charge — including any spilled portion — to + # the parent's reservoir. + # 5. CREATE failure refunds new_account state gas to the + # parent's state pool (account creation rolled back). + # 6. Factory's post-CREATE SSTORE charges sstore_state_gas + # (state pool first, spilled to gas_left otherwise). + execution_gas = gas_limit - intrinsic_cost + regular_budget = gas_limit_cap - intrinsic_cost + sim_gas_left = min(regular_budget, execution_gas) + sim_state_gas_left = execution_gas - sim_gas_left + + sim_gas_left -= factory_pre_create_regular + sim_gas_left -= gas_costs.OPCODE_CREATE_BASE + init_code_word_cost + + if sim_state_gas_left >= new_account_state_gas: + sim_state_gas_left -= new_account_state_gas + else: + sim_gas_left -= new_account_state_gas - sim_state_gas_left + sim_state_gas_left = 0 + + # `child_reservoir` is what the parent forwards to the child. + # Under Policy A halt, incorporate refunds child.state_gas_used + # + child.state_gas_left = max(sstore, child_reservoir) back to + # the parent. The simulator already implicitly retains + # `child_reservoir` in `sim_state_gas_left`, so the additional + # Policy A refund versus the Policy B "burn the spill" rule is + # `max(0, sstore_state_gas - child_reservoir)`. + child_reservoir = sim_state_gas_left + sim_gas_left = sim_gas_left // 64 + sim_state_gas_left += max(0, sstore_state_gas - child_reservoir) + sim_state_gas_left += new_account_state_gas + + sim_gas_left -= factory_post_create_regular + + if sim_state_gas_left >= sstore_state_gas: + sim_state_gas_left -= sstore_state_gas + else: + sim_gas_left -= sstore_state_gas - sim_state_gas_left + sim_state_gas_left = 0 + + expected_cumulative = gas_limit - sim_gas_left - sim_state_gas_left + else: + # REVERT preserves gas_left and refunds the child frame's + # state gas (initcode SSTORE + new account). Only the + # factory's own post-CREATE SSTORE consumes net state gas. + expected_cumulative = ( + intrinsic_cost + + factory_regular + + initcode_regular_revert + + sstore_state_gas + ) + + tx = Transaction( + to=factory, + gas_limit=gas_limit, + sender=pre.fund_eoa(), + expected_receipt=TransactionReceipt( + cumulative_gas_used=expected_cumulative, + ), + ) + + state_test( + pre=pre, + post={factory: Account(storage=factory_storage)}, + tx=tx, + ) + + +@pytest.mark.valid_from("EIP8037") +def test_nested_create_code_deposit_cannot_borrow_parent_gas( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test nested CREATE code deposit does not borrow parent gas. + + Provide just enough gas for CREATE to start (new account state + gas + regular gas) but not enough for the child frame to cover + code deposit after init code runs. The CREATE increments the + factory nonce but code deposit fails, so no contract is deployed. + """ + init_code = Op.RETURN(0, 1) + gas_costs = fork.gas_costs() + code_deposit_state = fork.code_deposit_state_gas(code_size=1) + + factory_mstore = Op.MSTORE( + 0, Op.PUSH32(bytes(init_code)), new_memory_size=32 + ) + factory_create = Op.CREATE( + value=0, + offset=32 - len(init_code), + size=len(init_code), + init_code_size=len(init_code), + ) + factory = pre.deploy_contract( + code=factory_mstore + Op.POP(factory_create), + ) + created = compute_create_address(address=factory, nonce=1) + + # Init code child execution: PUSH1 + PUSH1 + RETURN's mem_exp. + # Code deposit (keccak + state) is charged AFTER the child returns. + init_cost = 2 * gas_costs.VERY_LOW + gas_costs.MEMORY_PER_WORD + # Target child: enough for init, not enough for code deposit state. + target_child = (init_cost + code_deposit_state) // 2 + # Invert EIP-150 63/64ths rule: ceil(target_child * 64 / 63). + factory_remaining = (target_child * 64 + 62) // 63 + + # NEW_ACCOUNT state gas spills into gas_left (no reservoir at the + # top level), so it must be funded out of the regular budget. + intrinsic_cost = fork.transaction_intrinsic_cost_calculator()() + gas_limit = ( + intrinsic_cost + + factory_mstore.regular_cost(fork) + + factory_create.regular_cost(fork) + + gas_costs.NEW_ACCOUNT + + factory_remaining + ) + + tx = Transaction( + to=factory, + gas_limit=gas_limit, + sender=pre.fund_eoa(), + ) + + post = { + factory: Account(nonce=2), + created: Account.NONEXISTENT, + } + state_test(pre=pre, post=post, tx=tx) + + +@pytest.mark.parametrize( + "gas_shortfall", + [ + pytest.param(0, id="exact_gas"), + pytest.param(1, id="short_one_gas"), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_sstore_oog_no_reservoir_inflation( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + gas_shortfall: int, +) -> None: + """ + Verify SSTORE state gas is not charged when regular gas OOGs. + + With zero reservoir, all state gas spills into gas_left. A child + frame does CREATE (charging state gas from gas_left) followed by + SSTORE. When the factory is 1 gas short, SSTORE OOGs. If state + gas is incorrectly charged before regular gas, the extra state gas + inflates the parent's reservoir on frame failure, changing the + transaction's effective gas consumption. + + Regression test for SSTORE gas ordering: regular gas must be + checked before state gas. + """ + initcode = Initcode(deploy_code=Op.STOP) + initcode_len = len(initcode) + + factory_code = Op.CALLDATACOPY( + 0, + 0, + Op.CALLDATASIZE, + data_size=initcode_len, + new_memory_size=initcode_len, + ) + Op.SSTORE( + 0, + Op.CREATE( + value=0, + offset=0, + size=Op.CALLDATASIZE, + init_code_size=initcode_len, + ), + ) + factory = pre.deploy_contract(factory_code) + create_address = compute_create_address(address=factory, nonce=1) + + # Total gas includes both regular and state components since + # reservoir is zero — all state gas comes from gas_left. + factory_gas = ( + factory_code.gas_cost(fork) + + initcode.execution_gas(fork) + + initcode.deployment_gas(fork) + ) + + # Caller forwards total gas (regular + state) through CALL. + # With zero reservoir, the CALL gas parameter is the only source. + 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, + ) + ) + + sender = pre.fund_eoa() + # gas_limit = cap, reservoir = 0 + tx = Transaction( + sender=sender, + to=caller, + data=bytes(initcode), + gas_limit=fork.transaction_gas_limit_cap(), + ) + + 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) + + +@pytest.mark.parametrize( + "gas_shortfall", + [ + pytest.param(0, id="exact_gas"), + pytest.param(1, id="short_one_gas"), + ], +) +@pytest.mark.with_all_create_opcodes() +@pytest.mark.valid_from("EIP8037") +def test_max_initcode_size_gas_metering_via_create( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + gas_shortfall: int, + create_opcode: Op, +) -> None: + """ + Verify 2D gas metering for CREATE with max initcode size. + + A caller contract forwards exact regular gas to a factory via CALL. + State gas is supplied through the reservoir (tx.gas_limit above the + cap). With short_one_gas, the factory is 1 regular gas short and + all state changes revert. + """ + 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=0xC0FFEE, + 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_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=0xC0FFEE, + initcode=initcode, + opcode=create_opcode, + ) + + # Split gas into regular and state components. + # CALL gas only feeds gas_left; state gas must come from the reservoir. + factory_gas = ( + factory_code.gas_cost(fork) + + initcode.execution_gas(fork) + + initcode.deployment_gas(fork) + ) + factory_state_gas = fork.create_state_gas( + code_size=len(initcode.deploy_code) + ) + Op.SSTORE(new_value=1).state_cost(fork) + factory_regular_gas = factory_gas - factory_state_gas + + caller = pre.deploy_contract( + Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) + + Op.CALL( + gas=factory_regular_gas - gas_shortfall, + address=factory, + value=0, + args_offset=0, + args_size=Op.CALLDATASIZE, + ret_offset=0, + ret_size=0, + ) + + Op.STOP + ) + + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + tx = Transaction( + sender=alice, + to=caller, + data=bytes(initcode), + gas_limit=gas_limit_cap + factory_state_gas, + ) + + 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) + + +@pytest.mark.valid_from("EIP8037") +def test_create_no_double_charge_new_account( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify CREATE does not double-charge new-account gas. + + CREATE charges REGULAR_GAS_CREATE as regular gas and new-account + state gas separately. Provide exactly enough gas for both — if + GAS_NEW_ACCOUNT were charged twice (once in regular, once in + state), the CREATE would OOG. + """ + create_state_gas = fork.create_state_gas(code_size=0) + + # Child: just does CREATE(value=0, offset=0, size=0) and stores result. + # This creates an empty account (no code deposit). + child_code = Op.SSTORE(0, Op.CREATE(value=0, offset=0, size=0)) + child = pre.deploy_contract(child_code) + + # Compute exact gas: child bytecode + CREATE child frame. + # The child frame is empty (size=0) so only the CREATE opcode + # charges matter: regular (REGULAR_GAS_CREATE) + state (new account). + child_total = child_code.gas_cost(fork) + + create_address = compute_create_address(address=child, nonce=1) + + # Caller forwards exact regular gas via CALL. State gas for + # new account comes from the reservoir (gas_limit above the cap). + caller_storage = Storage() + regular_gas = child_total - create_state_gas + caller = pre.deploy_contract( + Op.SSTORE( + caller_storage.store_next(1, "create_succeeds"), + Op.CALL(gas=regular_gas, address=child), + ) + ) + + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + tx = Transaction( + sender=pre.fund_eoa(), + to=caller, + gas_limit=gas_limit_cap + create_state_gas, + ) + + post = { + caller: Account(storage=caller_storage), + child: Account(storage={0: create_address}), + create_address: Account(nonce=1), + } + state_test(pre=pre, tx=tx, post=post) + + +# TODO: Review for bal-devnet-4. If EIP-8037 adopts top-level state gas +# refund (https://github.com/ethereum/EIPs/pull/11476), the expected block +# gas accounting in these tests will change and may need updating. +@pytest.mark.parametrize( + "state_opcode", + [ + pytest.param(Op.CALL, id="call_new_account"), + pytest.param(Op.CREATE, id="inner_create"), + ], +) +@pytest.mark.parametrize( + "deposit_fail_mode", + [ + pytest.param("oversized_code", id="oversized_code"), + pytest.param("oog_deposit", id="oog_deposit"), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_code_deposit_halt_discards_initcode_state_gas( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, + state_opcode: Op, + deposit_fail_mode: str, +) -> None: + """ + Verify initcode state gas excluded from block on deposit halt. + + A CREATE tx runs initcode that first performs a state-creating + operation (charging GAS_NEW_ACCOUNT state gas), then returns + code that triggers a deposit failure (oversized or OOG). The + exceptional halt reverts all initcode state changes including + the new account. The reverted GAS_NEW_ACCOUNT must NOT count + in block_state_gas_used, which determines the block header + gas_used via max(block_regular_gas, block_state_gas). + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + + subcall_forwarded_value = 1 + entry_account_value = 1 + if state_opcode == Op.CALL: + state_op = Op.POP( + Op.CALL( + address=pre.nonexistent_account(), + value=subcall_forwarded_value, + ) + ) + else: + state_op = Op.POP(Op.CREATE(value=0, offset=0, size=1)) + + if deposit_fail_mode == "oversized_code": + deposit_fail = Op.RETURN(0, fork.max_code_size() + 1) + else: + # Return code at max size — passes the size check but code + # deposit state gas (max_code_size * cost_per_state_byte) + # exceeds available state gas in the child frame, causing OOG. + deposit_fail = Op.RETURN(0, fork.max_code_size()) + + initcode = state_op + deposit_fail + + blockchain_test( + pre=pre, + blocks=[ + Block( + txs=[ + Transaction( + to=None, + data=initcode, + value=entry_account_value + subcall_forwarded_value, + gas_limit=gas_limit_cap, + sender=pre.fund_eoa(), + ), + ], + ), + ], + post={}, + ) + + +@pytest.mark.valid_from("EIP8037") +def test_create_tx_header_gas_used( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify block header gas_used for a successful CREATE transaction. + + A contract creation tx (to=None) with known gas costs. Compute + exact gas_used from first principles and verify against the block + header. Catches bugs where clients report gas_limit instead of + actual consumed gas. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + + gas_costs = fork.gas_costs() + initcode = Op.STOP + create_state_gas = fork.create_state_gas(code_size=1) + + tx = Transaction( + to=None, + data=initcode, + gas_limit=gas_limit_cap + create_state_gas, + sender=pre.fund_eoa(), + ) + + # block_gas_used = max(block_regular, block_state) + # For a minimal CREATE tx deploying Op.STOP (1 byte), + # state gas (new account) dominates regular gas. + expected_gas_used = gas_costs.NEW_ACCOUNT + + blockchain_test( + pre=pre, + blocks=[ + Block( + txs=[tx], + header_verify=Header(gas_used=expected_gas_used), + ), + ], + post={}, + ) + + +@pytest.mark.valid_from("EIP8037") +def test_create_initcode_halt_no_code_deposit_state_gas( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify initcode exceptional halt excludes code deposit state gas. + + A CREATE tx runs initcode that hits INVALID (exceptional halt) + before returning any code. Code deposit never happens, so code + deposit state gas must NOT be charged. Only the intrinsic state + gas (new account creation) should count. + + Complements test_create_revert_no_code_deposit_state_gas which + covers the REVERT path. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + + # Initcode that immediately halts, no code returned + initcode = Op.INVALID + + # State gas = new account only (no code deposit on halt) + intrinsic_state_gas = fork.create_state_gas(code_size=0) + + gas_limit = gas_limit_cap + intrinsic_state_gas + + tx = Transaction( + to=None, + data=initcode, + gas_limit=gas_limit, + sender=pre.fund_eoa(), + ) + + # On exceptional halt all gas_left is consumed. + # block_gas_used = max(block_regular, block_state) + # block_state = intrinsic_state_gas (new account only, no deposit) + # block_regular = gas_limit - intrinsic_state_gas (all remaining) + tx_regular = gas_limit - intrinsic_state_gas + tx_state = intrinsic_state_gas + expected_gas_used = max(tx_regular, tx_state) + + blockchain_test( + pre=pre, + blocks=[ + Block( + txs=[tx], + header_verify=Header(gas_used=expected_gas_used), + ), + ], + post={}, + ) + + +@pytest.mark.valid_from("EIP8037") +def test_state_gas_spill_header_gas_used( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify header gas_used when state gas spills into gas_left. + + A transaction performs an SSTORE with state gas partially from + the reservoir and partially spilling into gas_left. Verify the + block header gas_used reflects the correct 2D max accounting. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + + # SSTORE zero-to-nonzero with small reservoir + sstore_code = Op.SSTORE(0, 1) + Op.STOP + contract = pre.deploy_contract(code=sstore_code) + + intrinsic_cost = fork.transaction_intrinsic_cost_calculator() + intrinsic_gas = intrinsic_cost() + + sstore_state_gas = sstore_code.state_cost(fork) + evm_regular = sstore_code.regular_cost(fork) + + # Reservoir = half the SSTORE state gas, rest spills to gas_left + reservoir = sstore_state_gas // 2 + gas_limit = gas_limit_cap + reservoir + + tx = Transaction( + to=contract, + gas_limit=gas_limit, + sender=pre.fund_eoa(), + ) + + tx_regular = intrinsic_gas + evm_regular + tx_state = sstore_state_gas + expected_gas_used = max(tx_regular, tx_state) + + blockchain_test( + pre=pre, + blocks=[ + Block( + txs=[tx], + header_verify=Header(gas_used=expected_gas_used), + ), + ], + post={contract: Account(storage={0: 1})}, + ) + + +@pytest.mark.parametrize( + "failure_mode", + [ + pytest.param("revert", id="revert"), + pytest.param("halt", id="halt"), + ], +) +@pytest.mark.with_all_create_opcodes() +@pytest.mark.valid_from("EIP8037") +def test_failed_create_header_gas_used( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, + create_opcode: Op, + failure_mode: str, +) -> None: + """ + Verify block header gas_used for failed CREATE/CREATE2 via opcode. + + A factory contract calls CREATE/CREATE2 which fails (revert or + halt). Verify the block is accepted with correct gas accounting. + Parametrized across failure modes and create opcodes. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + create_state_gas = fork.create_state_gas(code_size=0) + + if failure_mode == "revert": + init_code = Op.REVERT(0, 0) + else: + init_code = Op.INVALID + + create_call = ( + create_opcode(value=0, offset=0, size=len(init_code), salt=0) + if create_opcode == Op.CREATE2 + else create_opcode(value=0, offset=0, size=len(init_code)) + ) + + storage = Storage() + factory_code = Op.MSTORE( + 0, + int.from_bytes(bytes(init_code), "big") << (256 - 8 * len(init_code)), + ) + Op.SSTORE( + storage.store_next(0, "create_fails"), + create_call, + ) + + factory = pre.deploy_contract(factory_code) + + tx = Transaction( + to=factory, + gas_limit=gas_limit_cap + create_state_gas, + sender=pre.fund_eoa(), + ) + + blockchain_test( + pre=pre, + blocks=[ + Block(txs=[tx]), + ], + post={factory: Account(storage=storage)}, + ) + + +@pytest.mark.parametrize( + "failure_mode", + [ + pytest.param("nonce_overflow", id="nonce_overflow"), + pytest.param("insufficient_balance", id="insufficient_balance"), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_create_silent_failure_refunds_state_gas( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, + failure_mode: str, +) -> None: + """ + Verify CREATE silent failure refunds account state gas. + + Failures that skip child spawning (nonce overflow, insufficient + balance) refund `GAS_NEW_ACCOUNT` to the reservoir. Block state + gas reflects only the probe SSTORE, not the refunded CREATE. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + gas_costs = fork.gas_costs() + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + intrinsic_cost = fork.transaction_intrinsic_cost_calculator()() + + mstore_value, size = init_code_at_high_bytes(Op.STOP) + value = 1 if failure_mode == "insufficient_balance" else 0 + + storage = Storage() + factory_code = ( + Op.MSTORE(0, mstore_value) + + Op.POP(Op.CREATE(value=value, offset=0, size=size)) + + Op.SSTORE(storage.store_next(1, "reservoir_ok"), 1) + ) + if failure_mode == "nonce_overflow": + factory = pre.deploy_contract(code=factory_code, nonce=2**64 - 1) + else: + factory = pre.deploy_contract(code=factory_code) + + tx = Transaction( + to=factory, + gas_limit=gas_limit_cap + sstore_state_gas, + sender=pre.fund_eoa(), + ) + + # CREATE's GAS_NEW_ACCOUNT is refunded (silent failure, no child + # spawned). SSTORE's state portion is tracked separately in + # tx_state. + tx_regular = ( + intrinsic_cost + + factory_code.gas_cost(fork) + - gas_costs.NEW_ACCOUNT + - sstore_state_gas + ) + tx_state = sstore_state_gas + expected = max(tx_regular, tx_state) + blockchain_test( + pre=pre, + blocks=[Block(txs=[tx], header_verify=Header(gas_used=expected))], + post={factory: Account(storage=storage)}, + ) + + +@pytest.mark.parametrize( + "gas_limit_mode", + [ + pytest.param("reservoir", id="with_reservoir"), + pytest.param("spillover", id="spillover"), + ], +) +@pytest.mark.with_all_create_opcodes() +@pytest.mark.valid_from("EIP8037") +def test_create_child_revert_refunds_state_gas( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, + create_opcode: Op, + gas_limit_mode: str, +) -> None: + """ + Verify CREATE/CREATE2 child REVERT refunds parent's account gas. + + On REVERT the parent's `GAS_NEW_ACCOUNT` charge is refunded to + the reservoir (on top of the child's state gas returned via + `incorporate_child_on_error`). Block state gas reflects only the + probe SSTORE. The spillover variant runs with tx.gas at the cap + (reservoir zero), so the state gas charge spills into `gas_left` + and the refund returns to the reservoir (not back to `gas_left`). + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + gas_costs = fork.gas_costs() + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + intrinsic_cost = fork.transaction_intrinsic_cost_calculator()() + + init_code = Op.REVERT(0, 0) + mstore_value, size = init_code_at_high_bytes(init_code) + + create_call = ( + create_opcode(value=0, offset=0, size=size, salt=0) + if create_opcode == Op.CREATE2 + else create_opcode(value=0, offset=0, size=size) + ) + + storage = Storage() + factory_code = ( + Op.MSTORE(0, mstore_value) + + Op.POP(create_call) + + Op.SSTORE(storage.store_next(1, "reservoir_ok"), 1) + ) + factory = pre.deploy_contract(code=factory_code) + + gas_limit = ( + gas_limit_cap + if gas_limit_mode == "spillover" + else gas_limit_cap + sstore_state_gas + ) + tx = Transaction( + to=factory, + gas_limit=gas_limit, + sender=pre.fund_eoa(), + ) + + # CREATE's GAS_NEW_ACCOUNT is refunded on child REVERT. SSTORE's + # state portion is tracked separately. Child REVERT regular + # (init_code execution) is propagated via + # incorporate_child_on_error. + tx_regular = ( + intrinsic_cost + + factory_code.gas_cost(fork) + - gas_costs.NEW_ACCOUNT + - sstore_state_gas + + init_code.gas_cost(fork) + ) + tx_state = sstore_state_gas + expected = max(tx_regular, tx_state) + blockchain_test( + pre=pre, + blocks=[Block(txs=[tx], header_verify=Header(gas_used=expected))], + post={factory: Account(storage=storage)}, + ) + + +@pytest.mark.parametrize( + "failure_mode", + [ + pytest.param("initcode_halt", id="initcode_halt"), + pytest.param("invalid_prefix", id="invalid_prefix"), + ], +) +@pytest.mark.with_all_create_opcodes() +@pytest.mark.valid_from("EIP8037") +def test_create_child_halt_refunds_state_gas( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + create_opcode: Op, + failure_mode: str, +) -> None: + """ + Verify CREATE/CREATE2 child halt refunds parent's account gas. + + Exceptional halts (invalid opcode, EIP-3541 invalid prefix) + consume all forwarded gas as `regular_gas_used`, so block + accounting cannot strictly discriminate via header gas. Tight + gas tuning via a caller wrapper leaves the factory with just + enough `gas_left` to pay the probe SSTORE's regular portion + but not enough to spill the state portion, so the probe SSTORE + can only succeed via the refunded reservoir. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + gas_costs = fork.gas_costs() + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + new_account_state_gas = gas_costs.NEW_ACCOUNT + + init_code: Op | Bytecode + if failure_mode == "initcode_halt": + init_code = Op.INVALID + elif failure_mode == "invalid_prefix": + # Return code starting with 0xEF (EIP-3541 invalid prefix). + init_code = Op.MSTORE8(0, 0xEF) + Op.RETURN(0, 1) + + mstore_value, size = init_code_at_high_bytes(init_code) + + create_call = ( + create_opcode(value=0, offset=0, size=size, salt=0) + if create_opcode == Op.CREATE2 + else create_opcode(value=0, offset=0, size=size) + ) + + storage = Storage() + factory = pre.deploy_contract( + code=( + Op.MSTORE(0, mstore_value) + + Op.POP(create_call) + + Op.SSTORE(storage.store_next(1, "reservoir_ok"), 1) + ), + ) + + # Tight gas tuning: child halt consumes all forwarded gas as + # regular_gas_used. Factory retains + # ~(forwarded - pre_sstore_regular) / 64 after CREATE. Target + # the discrimination window `(probe_regular, + # probe_regular + sstore_state_gas)` so the probe SSTORE + # regular fits but state gas spillover from `gas_left` under + # the old behavior OOGs. + pre_sstore_code = Op.MSTORE(0, mstore_value) + Op.POP(create_call) + pre_sstore_regular = pre_sstore_code.gas_cost(fork) - new_account_state_gas + probe_code = Op.SSTORE(0, 1) + probe_regular = probe_code.gas_cost(fork) - sstore_state_gas + target_gas_left = probe_regular + sstore_state_gas // 2 + forwarded_gas = target_gas_left * 64 + pre_sstore_regular + # Reservoir sized for CREATE charge only — SSTORE must pull + # from the refunded reservoir, not from spill. + caller = pre.deploy_contract( + code=Op.CALL(gas=forwarded_gas, address=factory) + ) + tx = Transaction( + to=caller, + gas_limit=gas_limit_cap + new_account_state_gas, + sender=pre.fund_eoa(), + ) + + state_test(pre=pre, post={factory: Account(storage=storage)}, tx=tx) + + +@pytest.mark.with_all_create_opcodes() +@pytest.mark.valid_from("EIP8037") +def test_create_mixed_success_and_failure_block_accounting( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, + create_opcode: Op, +) -> None: + """ + Verify block state gas excludes refunded charges from failed CREATE. + + One successful CREATE plus one failed CREATE (REVERT): block + state gas reflects only the successful charges. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() + create_account_state_gas = fork.create_state_gas(code_size=0) + + success_value, success_size = init_code_at_high_bytes(Op.STOP) + fail_value, fail_size = init_code_at_high_bytes(Op.REVERT(0, 0)) + + def call(size: int, salt: int) -> Bytecode: + if create_opcode == Op.CREATE2: + return create_opcode(value=0, offset=0, size=size, salt=salt) + return create_opcode(value=0, offset=0, size=size) + + factory_code = ( + Op.MSTORE(0, success_value) + + Op.POP(call(size=success_size, salt=0)) + + Op.MSTORE(0, fail_value) + + Op.POP(call(size=fail_size, salt=1)) + ) + factory = pre.deploy_contract(code=factory_code) + + # STOP deploys empty code, so only GAS_NEW_ACCOUNT counts for + # the successful CREATE, and the failed CREATE is refunded. + block_state = create_account_state_gas + tx_regular = ( + intrinsic_gas + + factory_code.gas_cost(fork) + - 2 * create_account_state_gas + ) + expected = max(tx_regular, block_state) + + tx = Transaction( + to=factory, + gas_limit=gas_limit_cap + 2 * create_account_state_gas, + sender=pre.fund_eoa(), + ) + + blockchain_test( + pre=pre, + blocks=[Block(txs=[tx], header_verify=Header(gas_used=expected))], + post={}, + ) + + +@pytest.mark.pre_alloc_mutable() +@pytest.mark.with_all_create_opcodes() +@pytest.mark.valid_from("EIP8037") +def test_create_collision_refunds_state_gas( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + create_opcode: Op, +) -> None: + """ + Verify CREATE/CREATE2 address collision refunds account state gas. + + The collision path increments the factory nonce and burns the + forwarded regular gas (consumed by the never-spawned child), but + still refunds `GAS_NEW_ACCOUNT` to the reservoir. Tight gas + tuning limits the factory's post-collision `gas_left` so the + probe SSTORE can only succeed via the refunded reservoir, not + by spilling state gas from `gas_left`. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + gas_costs = fork.gas_costs() + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + new_account_state_gas = gas_costs.NEW_ACCOUNT + + init_code = Op.STOP + mstore_value, size = init_code_at_high_bytes(init_code) + salt = 0 + + storage = Storage() + create_call = ( + create_opcode(value=0, offset=0, size=size, salt=salt) + if create_opcode == Op.CREATE2 + else create_opcode(value=0, offset=0, size=size) + ) + factory_code = ( + Op.MSTORE(0, mstore_value) + + Op.POP(create_call) + + Op.SSTORE(storage.store_next(1, "reservoir_ok"), 1) + ) + factory = pre.deploy_contract(code=factory_code) + + collision_target = compute_create_address( + address=factory, + nonce=1, + salt=salt, + initcode=bytes(init_code), + opcode=create_opcode, + ) + pre.deploy_contract(code=Op.STOP, address=collision_target) + + # Tight gas tuning: factory retains + # ~(forwarded - pre_sstore_regular) / 64 after collision burns + # `max_message_call_gas` as regular. Target the discrimination + # window `(probe_regular, probe_regular + sstore_state_gas)` so + # the probe SSTORE regular fits but state gas spillover from + # `gas_left` under the old behavior OOGs. + pre_sstore_code = Op.MSTORE(0, mstore_value) + Op.POP(create_call) + pre_sstore_regular = pre_sstore_code.gas_cost(fork) - new_account_state_gas + probe_code = Op.SSTORE(0, 1) + probe_regular = probe_code.gas_cost(fork) - sstore_state_gas + target_gas_left = probe_regular + sstore_state_gas // 2 + forwarded_gas = target_gas_left * 64 + pre_sstore_regular + # Reservoir sized for CREATE charge only — SSTORE must pull from + # the refunded reservoir, not from spill. + caller = pre.deploy_contract( + code=Op.CALL(gas=forwarded_gas, address=factory) + ) + tx = Transaction( + to=caller, + gas_limit=gas_limit_cap + new_account_state_gas, + sender=pre.fund_eoa(), + ) + + state_test(pre=pre, post={factory: Account(storage=storage)}, tx=tx) + + +@pytest.mark.with_all_create_opcodes() +@pytest.mark.valid_from("EIP8037") +def test_create_code_deposit_oog_refunds_state_gas( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + create_opcode: Op, +) -> None: + """ + Verify CREATE/CREATE2 code-deposit OOG refunds account state gas. + + The initcode executes successfully and returns code longer than + `MAX_CODE_SIZE`, triggering an exceptional halt during code + deposit. Tight gas tuning limits the factory's post-halt + `gas_left` so the probe SSTORE can only succeed via the + refunded reservoir, not by spilling state gas from `gas_left`. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + gas_costs = fork.gas_costs() + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + new_account_state_gas = gas_costs.NEW_ACCOUNT + max_code_size = fork.max_code_size() + + # Init code returns (max_code_size + 1) bytes, triggering the + # OOG path in process_create_message code deposit. + init_code = Op.RETURN(0, max_code_size + 1) + mstore_value, size = init_code_at_high_bytes(init_code) + + create_call = ( + create_opcode(value=0, offset=0, size=size, salt=0) + if create_opcode == Op.CREATE2 + else create_opcode(value=0, offset=0, size=size) + ) + + storage = Storage() + factory = pre.deploy_contract( + code=( + Op.MSTORE(0, mstore_value) + + Op.POP(create_call) + + Op.SSTORE(storage.store_next(1, "reservoir_ok"), 1) + ), + ) + + # Child halt consumes all forwarded gas; factory retains only + # ~(forwarded - pre_sstore_regular) / 64. Target the + # discrimination window so SSTORE regular fits but state gas + # spillover fails. + pre_sstore_code = Op.MSTORE(0, mstore_value) + Op.POP(create_call) + pre_sstore_regular = pre_sstore_code.gas_cost(fork) - new_account_state_gas + probe_code = Op.SSTORE(0, 1) + probe_regular = probe_code.gas_cost(fork) - sstore_state_gas + target_gas_left = probe_regular + sstore_state_gas // 2 + forwarded_gas = target_gas_left * 64 + pre_sstore_regular + caller = pre.deploy_contract( + code=Op.CALL(gas=forwarded_gas, address=factory) + ) + tx = Transaction( + to=caller, + gas_limit=gas_limit_cap + new_account_state_gas, + sender=pre.fund_eoa(), + ) + + state_test(pre=pre, post={factory: Account(storage=storage)}, tx=tx) + + +@pytest.mark.parametrize( + "init_code", + [ + pytest.param(Op.REVERT(0, 0), id="revert"), + pytest.param(Op.INVALID, id="halt"), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_failed_create_tx_refunds_intrinsic_new_account( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + init_code: Bytecode, +) -> None: + """ + Verify the NEW_ACCOUNT × CPSB portion of intrinsic_state_gas is + refunded on creation-tx revert/halt. Block state-gas excludes it + so header gas_used reflects only the regular component, and the + sender's receipt reflects the same refund via cumulative_gas_used. + """ + intrinsic_calc = fork.transaction_intrinsic_cost_calculator() + create_state_gas = fork.create_state_gas(code_size=0) + + intrinsic_total = intrinsic_calc( + calldata=bytes(init_code), contract_creation=True + ) + intrinsic_regular = intrinsic_total - create_state_gas + gas_limit = intrinsic_total + 1000 + + if init_code == Op.INVALID: + regular_consumed = gas_limit - intrinsic_total + else: + regular_consumed = init_code.regular_cost(fork) + + expected_gas_used = intrinsic_regular + regular_consumed + expected_cumulative = intrinsic_total + regular_consumed - create_state_gas + + tx = Transaction( + to=None, + data=init_code, + gas_limit=gas_limit, + sender=pre.fund_eoa(), + expected_receipt=TransactionReceipt( + cumulative_gas_used=expected_cumulative, + ), + ) + + state_test( + pre=pre, + post={}, + tx=tx, + blockchain_test_header_verify=Header(gas_used=expected_gas_used), + ) + + +@pytest.mark.pre_alloc_mutable() +@pytest.mark.valid_from("EIP8037") +def test_create_tx_collision_refunds_intrinsic_new_account( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify the NEW_ACCOUNT × CPSB portion of intrinsic_state_gas is + refunded on creation-tx address collision, so block state-gas + excludes it and header gas_used reflects only the regular + consumption (full forwarded gas, no initcode runs). + """ + intrinsic_calc = fork.transaction_intrinsic_cost_calculator() + create_state_gas = fork.create_state_gas(code_size=0) + + init_code = Op.STOP + intrinsic_total = intrinsic_calc( + calldata=bytes(init_code), contract_creation=True + ) + gas_limit = intrinsic_total + 1000 + + sender = pre.fund_eoa() + collision_target = compute_create_address(address=sender, nonce=0) + pre[collision_target] = Account(nonce=1) + + expected_gas_used = gas_limit - create_state_gas + + tx = Transaction( + to=None, + data=init_code, + gas_limit=gas_limit, + sender=sender, + ) + + blockchain_test( + pre=pre, + blocks=[ + Block( + txs=[tx], + header_verify=Header(gas_used=expected_gas_used), + ), + ], + post={}, + ) + + +@pytest.mark.parametrize( + "initcode_size_delta", + [ + pytest.param(0, id="at_max"), + pytest.param(1, id="over_max", marks=pytest.mark.exception_test), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_oversized_initcode_tx_no_state_gas( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, + initcode_size_delta: int, +) -> None: + """ + Verify a creation tx with oversized initcode is rejected before + any state gas is charged. + """ + max_size = fork.max_initcode_size() + size = max_size + initcode_size_delta + initcode = Initcode(deploy_code=Op.STOP, initcode_length=size) + + sender = pre.fund_eoa() + create_address = compute_create_address(address=sender, nonce=0) + + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + create_state_gas = fork.create_state_gas(code_size=len(Op.STOP)) + + tx = Transaction( + sender=sender, + to=None, + data=initcode, + gas_limit=gas_limit_cap + create_state_gas, + ) + + if initcode_size_delta > 0: + tx.error = TransactionException.INITCODE_SIZE_EXCEEDED + post: dict = {create_address: Account.NONEXISTENT} + else: + post = {create_address: Account(code=Op.STOP)} + + blockchain_test( + pre=pre, + blocks=[ + Block( + txs=[tx], + exception=( + TransactionException.INITCODE_SIZE_EXCEEDED + if initcode_size_delta > 0 + else None + ), + ), + ], + post=post, + ) + + +@pytest.mark.parametrize( + "initcode_size_delta", + [ + pytest.param(0, id="at_max"), + pytest.param(1, id="over_max"), + ], +) +@pytest.mark.with_all_create_opcodes() +@pytest.mark.valid_from("EIP8037") +def test_oversized_initcode_opcode_no_state_gas( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, + create_opcode: Op, + initcode_size_delta: int, +) -> None: + """ + Verify CREATE/CREATE2 with oversized initcode fails the size + check before any state gas is charged. + """ + max_size = fork.max_initcode_size() + size = max_size + initcode_size_delta + initcode = Initcode(deploy_code=Op.STOP, initcode_length=size) + initcode_bytes = bytes(initcode) + + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + gas_costs = fork.gas_costs() + create_state_gas = gas_costs.NEW_ACCOUNT + + create_call = ( + create_opcode( + value=0, + offset=0, + size=Op.CALLDATASIZE, + salt=0, + init_code_size=len(initcode_bytes), + ) + if create_opcode == Op.CREATE2 + else create_opcode(value=0, offset=0, size=Op.CALLDATASIZE) + ) + + factory = pre.deploy_contract( + Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) + Op.SSTORE(0, create_call) + ) + + create_address = compute_create_address( + address=factory, + nonce=1, + salt=0, + initcode=initcode, + opcode=create_opcode, + ) + + storage = Storage() + storage[0] = create_address if initcode_size_delta == 0 else 0 + + tx = Transaction( + sender=pre.fund_eoa(), + to=factory, + data=initcode_bytes, + gas_limit=gas_limit_cap + create_state_gas, + ) + + post: dict = {factory: Account(storage=storage)} + if initcode_size_delta == 0: + post[create_address] = Account(code=Op.STOP) + else: + post[create_address] = Account.NONEXISTENT + + blockchain_test( + pre=pre, + blocks=[Block(txs=[tx])], + post=post, + ) + + +@pytest.mark.valid_from("EIP8037") +def test_selfdestruct_in_create_tx_initcode( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify state gas accounting when a creation tx's initcode + immediately SELFDESTRUCTs to a new beneficiary. + """ + gas_costs = fork.gas_costs() + create_state_gas = fork.create_state_gas(code_size=0) + + beneficiary = 0xDEAD + initcode = Op.SELFDESTRUCT(beneficiary) + + sender = pre.fund_eoa() + intrinsic_calc = fork.transaction_intrinsic_cost_calculator() + intrinsic_total = intrinsic_calc( + calldata=bytes(initcode), contract_creation=True + ) + + expected_state = create_state_gas + gas_costs.NEW_ACCOUNT + + initcode_gas = initcode.gas_cost(fork) + gas_limit = intrinsic_total + initcode_gas + gas_costs.NEW_ACCOUNT + 1000 + + tx = Transaction( + sender=sender, + to=None, + data=initcode, + value=1, + gas_limit=gas_limit, + ) + + blockchain_test( + pre=pre, + blocks=[ + Block( + txs=[tx], + header_verify=Header(gas_used=expected_state), + ), + ], + post={}, + ) + + +@pytest.mark.parametrize( + "outer_outcome", + [ + pytest.param("succeeds", id="outer_succeeds"), + pytest.param("reverts", id="outer_reverts"), + pytest.param("halts", id="outer_halts"), + ], +) +@pytest.mark.with_all_create_opcodes() +@pytest.mark.valid_from("EIP8037") +def test_inner_create_succeeds_code_deposit_state_gas( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, + create_opcode: Op, + outer_outcome: str, +) -> None: + """ + Verify state gas accumulation and top-level failure refund in a + creation tx whose initcode runs a successful inner CREATE. + """ + gas_costs = fork.gas_costs() + outer_state_gas = fork.create_state_gas(code_size=0) + inner_code_deposit = fork.code_deposit_state_gas(code_size=1) + inner_state_gas = gas_costs.NEW_ACCOUNT + inner_code_deposit + + deploy_code = Op.STOP + inner_initcode = Op.MSTORE( + 0, + int.from_bytes(bytes(deploy_code), "big") << 248, + ) + Op.RETURN(31, 1) + inner_bytes = bytes(inner_initcode) + + setup = Op.MSTORE( + 0, + int.from_bytes(inner_bytes, "big") << (256 - 8 * len(inner_bytes)), + ) + if create_opcode == Op.CREATE2: + inner_create = Op.POP(Op.CREATE2(0, 0, len(inner_bytes), 0)) + else: + inner_create = Op.POP(Op.CREATE(0, 0, len(inner_bytes))) + + if outer_outcome == "succeeds": + termination = Op.RETURN(0, 0) + elif outer_outcome == "reverts": + termination = Op.REVERT(0, 0) + else: + termination = Op.INVALID + + initcode = setup + inner_create + termination + + sender = pre.fund_eoa() + intrinsic_calc = fork.transaction_intrinsic_cost_calculator() + intrinsic_total = intrinsic_calc( + calldata=bytes(initcode), contract_creation=True + ) + + if outer_outcome == "halts": + initcode_gas = initcode.regular_cost(fork) + else: + initcode_gas = initcode.gas_cost(fork) + gas_limit = intrinsic_total + initcode_gas + inner_code_deposit + 1000 + + create_address = compute_create_address(address=sender, nonce=0) + + tx = Transaction( + sender=sender, + to=None, + data=initcode, + gas_limit=gas_limit, + ) + + if outer_outcome == "succeeds": + post: dict = {create_address: Account(code=b"")} + block = Block( + txs=[tx], + header_verify=Header(gas_used=outer_state_gas + inner_state_gas), + ) + else: + post = {create_address: Account.NONEXISTENT} + block = Block(txs=[tx]) + + blockchain_test(pre=pre, blocks=[block], post=post) + + +@pytest.mark.parametrize( + "parent_reverts", + [ + pytest.param(True, id="parent_reverts"), + pytest.param(False, id="parent_succeeds"), + ], +) +@pytest.mark.parametrize( + "child_failure", + [ + pytest.param("revert", id="child_revert"), + pytest.param("halt", id="child_halt"), + ], +) +@pytest.mark.with_all_create_opcodes() +@pytest.mark.valid_from("EIP8037") +def test_nested_create_fail_parent_revert_state_gas( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, + parent_reverts: bool, + child_failure: str, + create_opcode: Op, +) -> None: + """ + Verify factory nonce is rolled back when the factory reverts after + a failed inner CREATE, and preserved when the factory returns. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + gas_costs = fork.gas_costs() + create_state_gas = gas_costs.NEW_ACCOUNT + + if child_failure == "revert": + init_code = Op.REVERT(0, 0) + else: + init_code = Op.INVALID + + create_call = ( + create_opcode(value=0, offset=0, size=len(init_code), salt=0) + if create_opcode == Op.CREATE2 + else create_opcode(value=0, offset=0, size=len(init_code)) + ) + + factory = pre.deploy_contract( + code=( + Op.MSTORE( + 0, + int.from_bytes(bytes(init_code), "big") + << (256 - 8 * len(init_code)), + ) + + Op.POP(create_call) + + (Op.REVERT(0, 0) if parent_reverts else Op.STOP) + ), + ) + + # Nested CALL required so the child-error path has a parent + # frame to receive the restored state gas. + caller = pre.deploy_contract( + code=Op.POP(Op.CALL(gas=500_000, address=factory)), + ) + + tx = Transaction( + to=caller, + gas_limit=gas_limit_cap + create_state_gas, + sender=pre.fund_eoa(), + ) + + inner_address = compute_create_address( + address=factory, + nonce=1, + salt=0, + initcode=bytes(init_code), + opcode=create_opcode, + ) + + if parent_reverts: + post = { + factory: Account(nonce=1), + inner_address: Account.NONEXISTENT, + } + else: + post = { + factory: Account(nonce=2), + inner_address: Account.NONEXISTENT, + } + + blockchain_test( + pre=pre, + blocks=[Block(txs=[tx])], + post=post, + ) + + +@pytest.mark.valid_from("EIP8037") +def test_create_stack_depth_state_gas_consumed( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify the state gas reservoir survives a deep recursion of + nested CALLs that silently fail on gas or depth exhaustion. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + + storage = Storage() + recursive = pre.deploy_contract( + code=( + Op.POP(Op.CALL(Op.GAS, Op.ADDRESS, 0, 0, 0, 0, 0)) + + Op.SSTORE(storage.store_next(1, "reservoir_ok"), 1) + ), + ) + + tx = Transaction( + to=recursive, + gas_limit=gas_limit_cap + sstore_state_gas, + sender=pre.fund_eoa(), + ) + + post = {recursive: Account(storage=storage)} + state_test(pre=pre, post=post, tx=tx) + + +@pytest.mark.parametrize( + "num_inner_ops", + [ + pytest.param(1, id="single"), + pytest.param(3, id="accumulate"), + ], +) +@pytest.mark.parametrize( + "outer_outcome", + [ + pytest.param("succeeds", id="outer_succeeds"), + pytest.param("reverts", id="outer_reverts"), + ], +) +@pytest.mark.with_all_create_opcodes() +@pytest.mark.valid_from("EIP8037") +def test_inner_create_fail_refunds_in_creation_tx( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, + create_opcode: Op, + outer_outcome: str, + num_inner_ops: int, +) -> None: + """ + Verify failed inner CREATEs inside a creation tx refund state + gas so only the outer intrinsic state gas remains. + """ + gas_costs = fork.gas_costs() + outer_state_gas = fork.create_state_gas(code_size=0) + + inner_initcode = bytes(Op.REVERT(0, 0)) + + setup = Op.MSTORE( + 0, + int.from_bytes(inner_initcode, "big") + << (256 - 8 * len(inner_initcode)), + ) + + inner_ops = Bytecode() + for i in range(num_inner_ops): + if create_opcode == Op.CREATE2: + inner_ops += Op.POP(Op.CREATE2(0, 0, len(inner_initcode), i)) + else: + inner_ops += Op.POP(Op.CREATE(0, 0, len(inner_initcode))) + + if outer_outcome == "succeeds": + termination = Op.RETURN(0, 0) + else: + termination = Op.REVERT(0, 0) + + initcode = setup + inner_ops + termination + + sender = pre.fund_eoa() + intrinsic_calc = fork.transaction_intrinsic_cost_calculator() + intrinsic_total = intrinsic_calc( + calldata=bytes(initcode), contract_creation=True + ) + + initcode_gas = initcode.gas_cost(fork) + per_inner_slack = 2_000 + gas_limit = ( + intrinsic_total + + initcode_gas + + num_inner_ops * (gas_costs.NEW_ACCOUNT + per_inner_slack) + ) + + create_address = compute_create_address(address=sender, nonce=0) + + tx = Transaction( + sender=sender, + to=None, + data=initcode, + gas_limit=gas_limit, + ) + + if outer_outcome == "succeeds": + post: dict = {create_address: Account(code=b"")} + block = Block( + txs=[tx], + header_verify=Header(gas_used=outer_state_gas), + ) + else: + post = {create_address: Account.NONEXISTENT} + block = Block(txs=[tx]) + + blockchain_test(pre=pre, blocks=[block], post=post) + + +@pytest.mark.pre_alloc_mutable +@pytest.mark.with_all_create_opcodes() +@pytest.mark.valid_from("EIP8037") +def test_create_collision_burned_gas_counted_in_block_regular( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, + create_opcode: Op, +) -> None: + """ + Verify gas burned by a CREATE/CREATE2 address collision counts + toward block regular gas used in the header. + """ + init_code = Op.STOP + mstore_value, size = init_code_at_high_bytes(init_code) + salt = 0 + + create_call = ( + create_opcode(value=0, offset=0, size=size, salt=salt) + if create_opcode == Op.CREATE2 + else create_opcode(value=0, offset=0, size=size) + ) + factory_code = Op.MSTORE(0, mstore_value) + Op.POP(create_call) + Op.STOP + factory = pre.deploy_contract(code=factory_code) + + collision_target = compute_create_address( + address=factory, + nonce=1, + salt=salt, + initcode=bytes(init_code), + opcode=create_opcode, + ) + pre.deploy_contract(code=Op.STOP, address=collision_target) + + # Fixed-size budget so the forwarded create_message_gas is + # deterministic and the baseline below is reproducible. + gas_limit = 250_000 + + tx = Transaction( + to=factory, + gas_limit=gas_limit, + sender=pre.fund_eoa(), + ) + + # CPSB-agnostic baseline: block_state_gas is zero for this tx (the + # collision refunds the NEW_ACCOUNT state charge), so header.gas_used + # equals the regular-gas total. Decompose the parent + inner frame + # accounting from fork APIs so the baseline tracks future cost + # changes automatically. + intrinsic = fork.transaction_intrinsic_cost_calculator()() + new_account = fork.gas_costs().NEW_ACCOUNT + create_base = fork.gas_costs().OPCODE_CREATE_BASE + # POP + STOP run in the parent frame after CREATE returns; their + # cost comes out of the 1/64 retained gas. + post_create_static = (Op.POP + Op.STOP).gas_cost(fork) + # factory_code.gas_cost(fork) folds NEW_ACCOUNT into the CREATE op + # (state gas is treated as part of the opcode total). Strip it + # back out and split off the post-CREATE tail to isolate the + # pre-CREATE static gas. + factory_pre_create = ( + factory_code.gas_cost(fork) + - new_account + - create_base + - post_create_static + ) + # MSTORE writes the initcode at memory[0:32] (one word). + memory_expansion = fork.memory_expansion_gas_calculator()(new_bytes=32) + # gas_left at the moment NEW_ACCOUNT spills into the regular pool + # (reservoir is empty for tx_gas_limit < TX_MAX_GAS_LIMIT). + gas_at_create_after_state = ( + gas_limit + - intrinsic + - factory_pre_create + - memory_expansion + - create_base + - new_account + ) + # Inner burns 63/64 of the available gas on collision; the parent + # retains 1/64. The state-spill of NEW_ACCOUNT is refunded back to + # gas_left on collision (nets zero). Post-CREATE consumes from the + # retained pool. A mutation that drops the burned forwarded gas + # from regular accounting would reduce this baseline. + retained = gas_at_create_after_state // 64 + baseline_gas_used = gas_limit - retained - new_account + post_create_static + + blockchain_test( + pre=pre, + blocks=[ + Block( + txs=[tx], + header_verify=Header(gas_used=baseline_gas_used), + ), + ], + post={}, + ) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_delegation_pointer.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_delegation_pointer.py new file mode 100644 index 00000000000..b6c79007c1d --- /dev/null +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_delegation_pointer.py @@ -0,0 +1,167 @@ +""" +Test state gas behavior when calling via 7702 delegation pointer vs direct. + +Under EIP-8037, calling a contract that has a 7702 delegation pointer +should charge the same state gas as calling the target directly. The +delegation resolution is transparent to gas accounting. + +Tests for [EIP-8037: State Creation Gas Cost Increase] +(https://eips.ethereum.org/EIPS/eip-8037). +""" + +import pytest +from execution_testing import ( + Account, + Alloc, + AuthorizationTuple, + Environment, + Fork, + Op, + StateTestFiller, + Storage, + Transaction, +) + +from .spec import ref_spec_8037 + +REFERENCE_SPEC_GIT_PATH = ref_spec_8037.git_path +REFERENCE_SPEC_VERSION = ref_spec_8037.version + + +@pytest.mark.valid_from("EIP8037") +def test_sstore_via_delegation_pointer( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test SSTORE state gas charged when called via delegation pointer. + + A contract performs an SSTORE. An EOA delegates to that contract + via EIP-7702. Calling the EOA (delegation pointer) executes the + contract code in the EOA's context. The SSTORE state gas should + be charged from the reservoir just as it would for a direct call. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment() + auth_state_gas = fork.transaction_intrinsic_state_gas( + authorization_count=1, + ) + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + + storage = Storage() + contract = pre.deploy_contract( + code=Op.SSTORE(storage.store_next(1), 1), + ) + + # EOA with pre-existing delegation to the contract + delegator = pre.fund_eoa(delegation=contract) + + sender = pre.fund_eoa() + tx = Transaction( + to=delegator, + gas_limit=(gas_limit_cap + auth_state_gas + sstore_state_gas), + authorization_list=[ + AuthorizationTuple( + address=contract, + nonce=0, + signer=delegator, + ), + ], + sender=sender, + ) + + # SSTORE writes to the delegator's storage context + post = {delegator: Account(storage=storage)} + state_test(env=env, pre=pre, post=post, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_sstore_direct_call_same_contract( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test SSTORE state gas charged when calling the contract directly. + + Baseline comparison: calling the contract directly (not via a + delegation pointer) charges SSTORE state gas identically. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment() + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + + storage = Storage() + contract = pre.deploy_contract( + code=Op.SSTORE(storage.store_next(1), 1), + ) + + sender = pre.fund_eoa() + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap + sstore_state_gas, + sender=sender, + ) + + post = {contract: Account(storage=storage)} + state_test(env=env, pre=pre, post=post, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_delegation_pointer_new_account_state_gas( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test delegation pointer CALL to empty account charges new-account gas. + + A contract CALLs with value to a non-existent address. When executed + via a delegation pointer, the new-account state gas + is charged identically to a direct call. + """ + gas_costs = fork.gas_costs() + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment() + auth_state_gas = fork.transaction_intrinsic_state_gas( + authorization_count=1, + ) + new_account_state_gas = gas_costs.NEW_ACCOUNT + + target = 0xDEAD + + parent_storage = Storage() + contract = pre.deploy_contract( + code=( + Op.SSTORE( + parent_storage.store_next(1), + Op.CALL(gas=100_000, address=target, value=1), + ) + ), + balance=1, + ) + + # EOA delegates to the contract + delegator = pre.fund_eoa(delegation=contract, amount=1) + + sender = pre.fund_eoa() + tx = Transaction( + to=delegator, + gas_limit=(gas_limit_cap + auth_state_gas + new_account_state_gas), + authorization_list=[ + AuthorizationTuple( + address=contract, + nonce=0, + signer=delegator, + ), + ], + sender=sender, + ) + + # CALL success stored in delegator's storage context + post = {delegator: Account(storage=parent_storage)} + state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_fork_transition.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_fork_transition.py new file mode 100644 index 00000000000..5438eaba8ce --- /dev/null +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_fork_transition.py @@ -0,0 +1,236 @@ +""" +State gas fork transition tests for EIP-8037. + +Verify that state gas pricing and the modified transaction validity +constraint (tx.gas can exceed TX_MAX_GAS_LIMIT) activate correctly at +the EIP-8037 fork boundary. + +Before EIP-8037: no state gas dimension, tx.gas capped at +TX_MAX_GAS_LIMIT (EIP-7825). + +At/after EIP-8037: state gas charges apply, tx.gas above +TX_MAX_GAS_LIMIT is valid (excess feeds the reservoir). + +Tests for [EIP-8037: State Creation Gas Cost Increase] +(https://eips.ethereum.org/EIPS/eip-8037). +""" + +import pytest +from execution_testing import ( + Account, + Alloc, + Block, + BlockchainTestFiller, + EIPChecklist, + Fork, + Op, + Storage, + Transaction, + TransactionException, +) + +from .spec import ref_spec_8037 + +REFERENCE_SPEC_GIT_PATH = ref_spec_8037.git_path +REFERENCE_SPEC_VERSION = ref_spec_8037.version + +pytestmark = pytest.mark.valid_at_transition_to("EIP8037") + + +@EIPChecklist.GasCostChanges.Test.ForkTransition.Before() +@EIPChecklist.GasCostChanges.Test.ForkTransition.After() +def test_sstore_state_gas_at_transition( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test SSTORE state gas activates at the EIP-8037 fork boundary. + + Before the fork, an SSTORE zero-to-nonzero succeeds with only + regular gas (no state gas dimension). After the fork, the same + operation requires state gas. Both blocks use TX_MAX_GAS_LIMIT + which provides enough gas in either regime. + """ + after_fork = fork.fork_at(timestamp=15_000) + gas_limit_cap = after_fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + contract_before = pre.deploy_contract( + code=Op.SSTORE(0, 1), + ) + contract_after = pre.deploy_contract( + code=Op.SSTORE(0, 1), + ) + + blocks = [ + # Before fork: SSTORE succeeds with regular gas only + Block( + timestamp=14_999, + txs=[ + Transaction( + to=contract_before, + gas_limit=gas_limit_cap, + sender=pre.fund_eoa(), + ), + ], + ), + # After fork: SSTORE succeeds — state gas drawn from gas_left + Block( + timestamp=15_000, + txs=[ + Transaction( + to=contract_after, + gas_limit=gas_limit_cap, + sender=pre.fund_eoa(), + ), + ], + ), + ] + + post = { + contract_before: Account(storage={0: 1}), + contract_after: Account(storage={0: 1}), + } + + blockchain_test(pre=pre, blocks=blocks, post=post) + + +@EIPChecklist.ModifiedTransactionValidityConstraint.Test.ForkTransition.AcceptedBeforeFork() +@EIPChecklist.ModifiedTransactionValidityConstraint.Test.ForkTransition.RejectedBeforeFork() +@EIPChecklist.ModifiedTransactionValidityConstraint.Test.ForkTransition.AcceptedAfterFork() +@EIPChecklist.ModifiedTransactionValidityConstraint.Test.ForkTransition.RejectedAfterFork() +@pytest.mark.parametrize( + "gas_above_cap", + [ + pytest.param(False, id="at_cap"), + pytest.param( + True, + id="above_cap", + marks=pytest.mark.exception_test, + ), + ], +) +def test_tx_gas_above_cap_at_transition( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + gas_above_cap: bool, + fork: Fork, +) -> None: + """ + Test tx.gas > TX_MAX_GAS_LIMIT validity at the EIP-8037 transition. + + Before EIP-8037, EIP-7825 rejects any tx with gas > TX_MAX_GAS_LIMIT. + After EIP-8037 it's allowed — the excess feeds the state gas + reservoir. This test sends a tx at the cap (always valid) and one + above the cap (rejected before, accepted after). + """ + after_fork = fork.fork_at(timestamp=15_000) + gas_limit_cap = after_fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + storage_before = Storage() + contract_before = pre.deploy_contract( + code=(Op.SSTORE(storage_before.store_next(1), 1)), + ) + + storage_after = Storage() + contract_after = pre.deploy_contract( + code=(Op.SSTORE(storage_after.store_next(1), 1)), + ) + + gas_limit = gas_limit_cap + 1 if gas_above_cap else gas_limit_cap + + # Before fork: above-cap tx is rejected by EIP-7825 + before_error = ( + TransactionException.GAS_LIMIT_EXCEEDS_MAXIMUM + if gas_above_cap + else None + ) + + blocks = [ + Block( + timestamp=14_999, + txs=[ + Transaction( + to=contract_before, + gas_limit=gas_limit, + sender=pre.fund_eoa(), + error=before_error, + ), + ], + exception=before_error, + ), + # After fork: above-cap tx is now valid (excess feeds reservoir) + Block( + timestamp=15_000, + txs=[ + Transaction( + to=contract_after, + gas_limit=gas_limit, + sender=pre.fund_eoa(), + ), + ], + ), + ] + + post = { + contract_before: Account( + storage=storage_before if not gas_above_cap else {0: 0}, + ), + contract_after: Account(storage=storage_after), + } + + blockchain_test(pre=pre, blocks=blocks, post=post) + + +@EIPChecklist.GasCostChanges.Test.ForkTransition.After() +def test_reservoir_available_after_transition( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test reservoir is available for state ops after the fork. + + Before the fork, tx.gas is capped at TX_MAX_GAS_LIMIT and there is + no reservoir. After the fork, gas above the cap feeds the reservoir, + which child calls can draw from for state operations. + """ + after_fork = fork.fork_at(timestamp=15_000) + gas_limit_cap = after_fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(after_fork) + + child_storage = Storage() + child = pre.deploy_contract( + code=Op.SSTORE(child_storage.store_next(1), 1), + ) + + parent_storage = Storage() + parent = pre.deploy_contract( + code=( + Op.SSTORE( + parent_storage.store_next(1), + Op.CALL(gas=100_000, address=child), + ) + ), + ) + + blocks = [ + Block( + timestamp=15_000, + txs=[ + Transaction( + to=parent, + gas_limit=gas_limit_cap + sstore_state_gas, + sender=pre.fund_eoa(), + ), + ], + ), + ] + + post = { + parent: Account(storage=parent_storage), + child: Account(storage=child_storage), + } + + blockchain_test(pre=pre, blocks=blocks, post=post) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_multi_block.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_multi_block.py new file mode 100644 index 00000000000..2398717d9c9 --- /dev/null +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_multi_block.py @@ -0,0 +1,319 @@ +""" +Multi-block tests for EIP-8037 state gas receipt accounting and +coinbase fee accumulation. + +Verify that `receipt_gas_used` is computed correctly across multiple +blocks under two-dimensional gas accounting. These tests exercise: + +- Receipt gas accounting over multi-block sequences with diverse + state gas paths (reservoir, spill+revert, spill+halt) +- Observable coinbase balance between state-creating transactions + +Any disagreement in `receipt_gas_used` between clients causes the +coinbase balance to diverge, producing a different state root. + +Tests for [EIP-8037: State Creation Gas Cost Increase] +(https://eips.ethereum.org/EIPS/eip-8037). +""" + +import pytest +from execution_testing import ( + Account, + Alloc, + Block, + BlockchainTestFiller, + Fork, + Op, + Storage, + Transaction, +) + +from .spec import ref_spec_8037 + +REFERENCE_SPEC_GIT_PATH = ref_spec_8037.git_path +REFERENCE_SPEC_VERSION = ref_spec_8037.version + + +@pytest.mark.valid_from("EIP8037") +def test_exact_coinbase_fee_simple_sstore( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Assert exact coinbase balance from a single SSTORE transaction. + + Compute `tx_gas_used` from first principles and verify the + reporter contract reads exactly `tx_gas_used` as the coinbase + balance (priority fee is 1 wei). Any error in `state_gas_left` or + `refund_counter` will produce a different coinbase balance, + causing the state root to diverge. + + Motivated by BAL devnet-3 ethrex/besu coinbase balance mismatch + where clients diverged on cumulative `receipt_gas_used`. + """ + gas_costs = fork.gas_costs() + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + + # Gas breakdown for tx 1 (SSTORE zero-to-nonzero, no calldata): + # PUSH1(1) + PUSH1(0) + SSTORE(cold, zero-to-nonzero) + STOP + intrinsic_regular = gas_costs.TX_BASE + evm_regular = ( + 2 * gas_costs.VERY_LOW # PUSH1 + PUSH1 + + gas_costs.COLD_STORAGE_WRITE # SSTORE cold zero-to-nonzero + ) + tx1_gas_used = intrinsic_regular + evm_regular + sstore_state_gas + expected_coinbase = tx1_gas_used + + # Tx 1: single SSTORE zero-to-nonzero + sstore_storage = Storage() + sstore_contract = pre.deploy_contract( + code=(Op.SSTORE(sstore_storage.store_next(1), 1)), + ) + + # Tx 2: reporter reads BALANCE(COINBASE) into slot 0 + reporter = pre.deploy_contract( + code=(Op.SSTORE(0, Op.BALANCE(Op.COINBASE)) + Op.SSTORE(1, 1)), + ) + + blocks = [ + Block( + txs=[ + Transaction( + to=sstore_contract, + gas_limit=(gas_limit_cap + sstore_state_gas), + max_priority_fee_per_gas=1, + max_fee_per_gas=8, + sender=pre.fund_eoa(), + ), + Transaction( + to=reporter, + gas_limit=gas_limit_cap, + max_priority_fee_per_gas=1, + max_fee_per_gas=8, + sender=pre.fund_eoa(), + ), + ] + ), + ] + + post = { + sstore_contract: Account(storage=sstore_storage), + reporter: Account(storage={0: expected_coinbase, 1: 1}), + } + blockchain_test(pre=pre, blocks=blocks, post=post) + + +@pytest.mark.valid_from("EIP8037") +def test_multi_block_mixed_state_operations( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify coinbase fee across blocks with diverse state operations. + + Block 1: Simple SSTORE transactions (state gas from reservoir). + Block 2: Child spill + revert transactions (reservoir recovery). + Block 3: Child spill + halt transactions (halt recovery). + + This mixed scenario tests that `receipt_gas_used` is consistent + across different state gas paths within a multi-block chain. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + + reverting_child = pre.deploy_contract( + code=(Op.SSTORE(0, 1) + Op.SSTORE(1, 1) + Op.REVERT(0, 0)), + ) + halting_child = pre.deploy_contract( + code=(Op.SSTORE(0, 1) + Op.SSTORE(1, 1) + Op.INVALID), + ) + + all_contracts = [] + all_storages = [] + + # Simple SSTOREs from reservoir + block1_txs = [] + for _ in range(2): + storage = Storage() + contract = pre.deploy_contract( + code=(Op.SSTORE(storage.store_next(1), 1)), + ) + all_contracts.append(contract) + all_storages.append(storage) + block1_txs.append( + Transaction( + to=contract, + gas_limit=(gas_limit_cap + sstore_state_gas), + max_priority_fee_per_gas=1, + max_fee_per_gas=8, + sender=pre.fund_eoa(), + ) + ) + + # Child spill + revert + block2_txs = [] + for _ in range(2): + storage = Storage() + parent = pre.deploy_contract( + code=( + Op.POP( + Op.CALL( + gas=500_000, + address=reverting_child, + ) + ) + + Op.SSTORE(storage.store_next(1), 1) + ), + ) + all_contracts.append(parent) + all_storages.append(storage) + block2_txs.append( + Transaction( + to=parent, + gas_limit=(gas_limit_cap + sstore_state_gas), + max_priority_fee_per_gas=1, + max_fee_per_gas=8, + sender=pre.fund_eoa(), + ) + ) + + # Child spill + exceptional halt + block3_txs = [] + for _ in range(2): + storage = Storage() + parent = pre.deploy_contract( + code=( + Op.POP( + Op.CALL( + gas=500_000, + address=halting_child, + ) + ) + + Op.SSTORE(storage.store_next(1), 1) + ), + ) + all_contracts.append(parent) + all_storages.append(storage) + block3_txs.append( + Transaction( + to=parent, + gas_limit=(gas_limit_cap + sstore_state_gas), + max_priority_fee_per_gas=1, + max_fee_per_gas=8, + sender=pre.fund_eoa(), + ) + ) + + blocks = [ + Block(txs=block1_txs), + Block(txs=block2_txs), + Block(txs=block3_txs), + ] + post = { + c: Account(storage=s) + for c, s in zip(all_contracts, all_storages, strict=False) + } + blockchain_test(pre=pre, blocks=blocks, post=post) + + +@pytest.mark.valid_from("EIP8037") +def test_multi_block_observed_coinbase_balance( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Observe coinbase balance between state-creating transactions. + + A reporter contract reads `BALANCE(COINBASE)` and stores it. + This makes `receipt_gas_used` directly observable: if a client + computes a different `receipt_gas_used` for prior transactions, + the stored balance will differ and the state root will not match. + + Block 1: + Tx 1: SSTORE zero-to-nonzero (coinbase earns fee). + Tx 2: Store `BALANCE(COINBASE)` in slot 0. + + Block 2: + Tx 3: Child spills state gas then reverts; parent SSTOREs + (coinbase earns fee through different code path). + Tx 4: Store `BALANCE(COINBASE)` in slot 0. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + + reporter1 = pre.deploy_contract( + code=(Op.SSTORE(0, Op.BALANCE(Op.COINBASE))), + ) + reporter2 = pre.deploy_contract( + code=(Op.SSTORE(0, Op.BALANCE(Op.COINBASE))), + ) + + # Block 1 tx 1: simple SSTORE + sstore_storage = Storage() + sstore_contract = pre.deploy_contract( + code=(Op.SSTORE(sstore_storage.store_next(1), 1)), + ) + + # Block 2 tx 3: child spill + revert, parent SSTORE + reverting_child = pre.deploy_contract( + code=(Op.SSTORE(0, 1) + Op.SSTORE(1, 1) + Op.REVERT(0, 0)), + ) + spill_storage = Storage() + spill_parent = pre.deploy_contract( + code=( + Op.POP(Op.CALL(gas=500_000, address=reverting_child)) + + Op.SSTORE(spill_storage.store_next(1), 1) + ), + ) + + blocks = [ + Block( + txs=[ + Transaction( + to=sstore_contract, + gas_limit=(gas_limit_cap + sstore_state_gas), + max_priority_fee_per_gas=1, + max_fee_per_gas=8, + sender=pre.fund_eoa(), + ), + Transaction( + to=reporter1, + gas_limit=gas_limit_cap, + max_priority_fee_per_gas=1, + max_fee_per_gas=8, + sender=pre.fund_eoa(), + ), + ] + ), + Block( + txs=[ + Transaction( + to=spill_parent, + gas_limit=(gas_limit_cap + sstore_state_gas), + max_priority_fee_per_gas=1, + max_fee_per_gas=8, + sender=pre.fund_eoa(), + ), + Transaction( + to=reporter2, + gas_limit=gas_limit_cap, + max_priority_fee_per_gas=1, + max_fee_per_gas=8, + sender=pre.fund_eoa(), + ), + ] + ), + ] + + post = { + sstore_contract: Account(storage=sstore_storage), + spill_parent: Account(storage=spill_storage), + } + blockchain_test(pre=pre, blocks=blocks, post=post) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_ordering.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_ordering.py new file mode 100644 index 00000000000..70661c498ec --- /dev/null +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_ordering.py @@ -0,0 +1,414 @@ +""" +Test state gas consumption ordering under EIP-8037. + +When an opcode charges both regular gas and state gas, regular gas MUST +be charged first. If regular gas OOGs, state gas is not consumed. This +prevents the parent's reservoir from being inflated on frame failure. + +Each test gives a child frame exactly 1 gas less than needed, then uses +a probe contract to detect whether the parent's reservoir was inflated +by incorrectly consumed state gas. + +Tests for [EIP-8037: State Creation Gas Cost Increase] +(https://eips.ethereum.org/EIPS/eip-8037). +""" + +import pytest +from execution_testing import ( + Account, + Alloc, + Block, + BlockchainTestFiller, + Fork, + Header, + Initcode, + Op, + StateTestFiller, + Storage, + Transaction, +) + +from .spec import ref_spec_8037 + +REFERENCE_SPEC_GIT_PATH = ref_spec_8037.git_path +REFERENCE_SPEC_VERSION = ref_spec_8037.version + +WORD_SIZE = 32 + + +def _single_sstore_probe_gas(fork: Fork) -> int: + """ + Return the gas for a single-SSTORE probe that OOGs by 1 when the + reservoir is 0 but succeeds when the reservoir holds any state gas. + + The probe bytecode is Op.SSTORE(0, 1): two pushes + SSTORE. + """ + gas_costs = fork.gas_costs() + sstore_regular = gas_costs.COLD_STORAGE_WRITE + sstore_state = Op.SSTORE(new_value=1).state_cost(fork) + push_gas = 2 * gas_costs.VERY_LOW + return push_gas + sstore_regular + sstore_state - 1 + + +@pytest.mark.valid_from("EIP8037") +def test_sstore_oog_reservoir_inflation_detection( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Detect SSTORE state gas ordering via reservoir inflation. + + A factory does CREATE + SSTORE where SSTORE OOGs (1 gas short). + After factory failure, the parent's reservoir should contain only + CREATE's state gas. A probe contract tests this by doing 4 SSTOREs + that need more total state gas than the correct reservoir but less + than the inflated one. + + With correct ordering (regular gas first): probe OOGs on 4th SSTORE. + With wrong ordering (state gas first): reservoir is inflated, + probe succeeds. + """ + gas_costs = fork.gas_costs() + initcode = Initcode(deploy_code=Op.STOP) + initcode_len = len(initcode) + + factory_code = Op.CALLDATACOPY( + 0, + 0, + Op.CALLDATASIZE, + data_size=initcode_len, + new_memory_size=initcode_len, + ) + Op.SSTORE( + 0, + Op.CREATE( + value=0, + offset=0, + size=Op.CALLDATASIZE, + init_code_size=initcode_len, + ), + ) + factory = pre.deploy_contract(factory_code) + + factory_gas = ( + factory_code.gas_cost(fork) + + initcode.execution_gas(fork) + + initcode.deployment_gas(fork) + ) + + # Probe: 4 SSTOREs to cold slots. Total state gas exceeds the + # correct reservoir (CREATE state gas only) but fits within the + # inflated reservoir (CREATE + SSTORE state gas). + probe = pre.deploy_contract( + Op.SSTORE(0, 1) + Op.SSTORE(1, 1) + Op.SSTORE(2, 1) + Op.SSTORE(3, 1) + ) + + # Compute probe gas: enough for 4 SSTOREs' regular gas + pushes, + # but after 4th regular charge, gas_left < the state gas spill. + sstore_regular = gas_costs.COLD_STORAGE_WRITE + sstore_state = Op.SSTORE(new_value=1).state_cost(fork) + push_per_sstore = 2 * gas_costs.VERY_LOW + create_state_gas = fork.create_state_gas( + code_size=len(initcode.deploy_code) + ) + spill = 4 * sstore_state - create_state_gas + probe_gas = 4 * (push_per_sstore + sstore_regular) + spill // 2 + + caller_storage = Storage() + caller = pre.deploy_contract( + Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) + + Op.POP( + Op.CALL( + gas=factory_gas - 1, + address=factory, + value=0, + args_offset=0, + args_size=Op.CALLDATASIZE, + ret_offset=0, + ret_size=0, + ) + ) + + Op.SSTORE( + caller_storage.store_next(0, "probe_must_fail"), + Op.CALL(gas=probe_gas, address=probe), + ) + ) + + sender = pre.fund_eoa() + tx = Transaction( + sender=sender, + to=caller, + data=bytes(initcode), + gas_limit=fork.transaction_gas_limit_cap(), + ) + + post = { + caller: Account(storage=caller_storage), + } + + state_test(pre=pre, tx=tx, post=post) + + +@pytest.mark.valid_from("EIP8037") +def test_call_oog_reservoir_inflation_detection( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Detect CALL state gas ordering via reservoir inflation. + + A child does CALL(value=1) to a dead address with gas tuned so + the regular gas charge OOGs by 1. If state gas (new account) is + incorrectly charged first, the parent's reservoir is inflated. + + A single-SSTORE probe detects the inflation: with correct reservoir + (0) it OOGs; with inflated reservoir it succeeds. + """ + gas_costs = fork.gas_costs() + new_account_state_gas = gas_costs.NEW_ACCOUNT + + dead_address = 0xDEAD + child_code = Op.CALL( + gas=0, + address=dead_address, + value=1, + args_offset=0, + args_size=0, + ret_offset=0, + ret_size=0, + ) + pushes_gas = 7 * gas_costs.VERY_LOW + call_regular_gas = gas_costs.COLD_ACCOUNT_ACCESS + gas_costs.CALL_VALUE + child_gas = pushes_gas + call_regular_gas + new_account_state_gas - 1 + child = pre.deploy_contract(child_code) + + probe = pre.deploy_contract(Op.SSTORE(0, 1)) + probe_gas = _single_sstore_probe_gas(fork) + + caller_storage = Storage() + caller = pre.deploy_contract( + Op.POP(Op.CALL(gas=child_gas, address=child)) + + Op.SSTORE( + caller_storage.store_next(0, "probe_must_fail"), + Op.CALL(gas=probe_gas, address=probe), + ) + ) + + sender = pre.fund_eoa() + tx = Transaction( + sender=sender, + to=caller, + gas_limit=fork.transaction_gas_limit_cap(), + ) + + post = {caller: Account(storage=caller_storage)} + state_test(pre=pre, tx=tx, post=post) + + +@pytest.mark.valid_from("EIP8037") +def test_selfdestruct_oog_reservoir_inflation_detection( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Detect SELFDESTRUCT state gas ordering via reservoir inflation. + + A child with non-zero balance does SELFDESTRUCT(dead_beneficiary) + with gas tuned so the regular gas charge OOGs by 1. If state gas + is incorrectly charged first, the parent's reservoir is inflated. + + Single-SSTORE probe detects the inflation. + """ + gas_costs = fork.gas_costs() + new_account_state_gas = gas_costs.NEW_ACCOUNT + + dead_beneficiary = 0xBEEF + child_code = Op.SELFDESTRUCT(dead_beneficiary) + pushes_gas = gas_costs.VERY_LOW + selfdestruct_regular_gas = ( + gas_costs.OPCODE_SELFDESTRUCT_BASE + gas_costs.COLD_ACCOUNT_ACCESS + ) + child_gas = ( + pushes_gas + selfdestruct_regular_gas + new_account_state_gas - 1 + ) + child = pre.deploy_contract(child_code, balance=1) + + probe = pre.deploy_contract(Op.SSTORE(0, 1)) + probe_gas = _single_sstore_probe_gas(fork) + + caller_storage = Storage() + caller = pre.deploy_contract( + Op.POP(Op.CALL(gas=child_gas, address=child)) + + Op.SSTORE( + caller_storage.store_next(0, "probe_must_fail"), + Op.CALL(gas=probe_gas, address=probe), + ) + ) + + sender = pre.fund_eoa() + tx = Transaction( + sender=sender, + to=caller, + gas_limit=fork.transaction_gas_limit_cap(), + ) + + post = {caller: Account(storage=caller_storage)} + state_test(pre=pre, tx=tx, post=post) + + +@pytest.mark.parametrize( + "oog_step", + [ + pytest.param("create_base", id="oog_on_create_base"), + pytest.param("init_code_word_cost", id="oog_on_init_code_word_cost"), + ], +) +@pytest.mark.with_all_create_opcodes() +@pytest.mark.valid_from("EIP8037") +def test_create_oog_reservoir_inflation_detection( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + create_opcode: Op, + oog_step: str, +) -> None: + """ + Detect CREATE/CREATE2 state-gas ordering via parent-reservoir + inflation. Two OOG boundaries are exercised: `oog_on_create_base` + (empty initcode) and `oog_on_init_code_word_cost` (32-byte + initcode). + """ + gas_costs = fork.gas_costs() + new_account_state_gas = gas_costs.NEW_ACCOUNT + + if oog_step == "create_base": + initcode_size = 0 + setup_gas = 0 + init_code_word_cost = 0 + else: + initcode_size = WORD_SIZE + setup_gas = ( + Op.MSTORE.popped_stack_items * gas_costs.VERY_LOW + + gas_costs.OPCODE_MSTORE_BASE + + gas_costs.MEMORY_PER_WORD + ) + init_code_word_cost = gas_costs.CODE_INIT_PER_WORD + + if create_opcode == Op.CREATE: + create_op = create_opcode(value=0, offset=0, size=initcode_size) + else: + create_op = create_opcode( + value=0, offset=0, size=initcode_size, salt=0 + ) + pushes_gas = create_opcode.popped_stack_items * gas_costs.VERY_LOW + + if oog_step == "create_base": + child_code = create_op + else: + child_code = Op.MSTORE(0, 0) + create_op + + create_regular_gas = gas_costs.OPCODE_CREATE_BASE + init_code_word_cost + child_gas = ( + setup_gas + pushes_gas + create_regular_gas + new_account_state_gas - 1 + ) + child = pre.deploy_contract(child_code) + + probe = pre.deploy_contract(Op.SSTORE(0, 1)) + probe_gas = _single_sstore_probe_gas(fork) + + caller_storage = Storage() + caller = pre.deploy_contract( + Op.POP(Op.CALL(gas=child_gas, address=child)) + + Op.SSTORE( + caller_storage.store_next(0, "probe_must_fail"), + Op.CALL(gas=probe_gas, address=probe), + ) + ) + + sender = pre.fund_eoa() + tx = Transaction( + sender=sender, + to=caller, + gas_limit=fork.transaction_gas_limit_cap(), + ) + + post = {caller: Account(storage=caller_storage)} + state_test(pre=pre, tx=tx, post=post) + + +@pytest.mark.parametrize( + "oog_step", + [ + pytest.param("create_base", id="oog_on_create_base"), + pytest.param("init_code_word_cost", id="oog_on_init_code_word_cost"), + ], +) +@pytest.mark.with_all_create_opcodes() +@pytest.mark.valid_from("EIP8037") +def test_create_oog_full_burn_no_state_credit( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, + create_opcode: Op, + oog_step: str, +) -> None: + """ + Verify a CREATE OOG inside a non-creation tx burns the whole + tx gas_limit — no state-gas leftover is credited at tx-end. + """ + gas_costs = fork.gas_costs() + new_account_state_gas = gas_costs.NEW_ACCOUNT + + if oog_step == "create_base": + initcode_size = 0 + setup_gas = 0 + init_code_word_cost = 0 + else: + initcode_size = WORD_SIZE + setup_gas = ( + 2 * gas_costs.VERY_LOW + + gas_costs.OPCODE_MSTORE_BASE + + gas_costs.MEMORY_PER_WORD + ) + init_code_word_cost = gas_costs.CODE_INIT_PER_WORD + + if create_opcode == Op.CREATE: + create_op = create_opcode(value=0, offset=0, size=initcode_size) + else: + create_op = create_opcode( + value=0, offset=0, size=initcode_size, salt=0 + ) + pushes_gas = create_opcode.popped_stack_items * gas_costs.VERY_LOW + + if oog_step == "create_base": + factory_code = create_op + else: + factory_code = Op.MSTORE(0, 0) + create_op + factory = pre.deploy_contract(factory_code) + + create_regular_gas = gas_costs.OPCODE_CREATE_BASE + init_code_word_cost + body_gas = ( + setup_gas + pushes_gas + create_regular_gas + new_account_state_gas - 1 + ) + + intrinsic_calc = fork.transaction_intrinsic_cost_calculator() + tx_gas_limit = intrinsic_calc() + body_gas + + tx = Transaction( + sender=pre.fund_eoa(), + to=factory, + gas_limit=tx_gas_limit, + ) + + blockchain_test( + pre=pre, + blocks=[ + Block( + txs=[tx], + header_verify=Header(gas_used=tx_gas_limit), + ), + ], + post={}, + ) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_pricing.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_pricing.py new file mode 100644 index 00000000000..64ec9c00946 --- /dev/null +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_pricing.py @@ -0,0 +1,617 @@ +""" +Test the core EIP-8037 state gas pricing and charge mechanism. + +`cost_per_state_byte` is a fixed parameter (CPSB = 1530) derived from +a 150M reference block gas limit and a 120 GiB/year target state +growth. The state gas cost of any operation is its byte footprint +multiplied by CPSB. + +The `charge_state_gas()` function draws from the state gas reservoir +first, then spills into gas_left. If both pools are insufficient, the +transaction runs out of gas. + +Tests for [EIP-8037: State Creation Gas Cost Increase] +(https://eips.ethereum.org/EIPS/eip-8037). +""" + +import pytest +from execution_testing import ( + Account, + Alloc, + AuthorizationTuple, + Environment, + Fork, + Op, + StateTestFiller, + Storage, + Transaction, + TransactionException, +) +from execution_testing.checklists import EIPChecklist + +from .spec import Spec, ref_spec_8037 + +REFERENCE_SPEC_GIT_PATH = ref_spec_8037.git_path +REFERENCE_SPEC_VERSION = ref_spec_8037.version + +BLOCK_GAS_LIMITS = [ + pytest.param(1_000_000, id="1M"), + pytest.param(30_000_000, id="30M"), + pytest.param(36_000_000, id="36M"), + pytest.param(60_000_000, id="60M"), + pytest.param(100_000_000, id="100M"), + pytest.param(120_000_000, id="120M"), + pytest.param(200_000_000, id="200M"), + pytest.param(300_000_000, id="300M"), + pytest.param(500_000_000, id="500M"), + pytest.param(1_000_000_000, id="1G"), +] + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +@pytest.mark.parametrize("block_gas_limit", BLOCK_GAS_LIMITS) +@pytest.mark.valid_from("EIP8037") +def test_pricing_at_various_gas_limits( + state_test: StateTestFiller, + pre: Alloc, + block_gas_limit: int, + fork: Fork, +) -> None: + """ + Test SSTORE succeeds at various block gas limits. + + EIP-8037 prices state gas at a constant `cost_per_state_byte`, + independent of block gas limit. At each block size, an SSTORE + zero-to-nonzero should succeed when given sufficient total gas. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment(gas_limit=block_gas_limit) + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + tx_gas = min(gas_limit_cap + sstore_state_gas, block_gas_limit) + + storage = Storage() + contract = pre.deploy_contract( + code=Op.SSTORE(storage.store_next(1), 1), + ) + + tx = Transaction( + to=contract, + gas_limit=tx_gas, + sender=pre.fund_eoa(), + ) + + post = {contract: Account(storage=storage)} + state_test(env=env, pre=pre, post=post, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_charge_draws_entirely_from_reservoir( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test state gas is drawn entirely from the reservoir. + + When the reservoir has enough gas for the SSTORE state cost, + gas_left should not be reduced by the state charge. Verify by + performing a regular-gas-heavy computation after the SSTORE. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment() + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + + storage = Storage() + contract = pre.deploy_contract( + code=( + # SSTORE draws state gas from reservoir + Op.SSTORE(storage.store_next(1), 1) + # Remaining gas_left is available for regular ops + + Op.SSTORE( + storage.store_next(1), + Op.ADD(1, 0), # Cheap regular-gas op + ) + ), + ) + + # Provide exact state gas in the reservoir + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap + sstore_state_gas * 2, + sender=pre.fund_eoa(), + ) + + post = {contract: Account(storage=storage)} + state_test(env=env, pre=pre, post=post, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_charge_spills_to_gas_left( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test state gas spills from reservoir to gas_left. + + When the reservoir has some gas but not enough to cover the full + state charge, the remainder is taken from gas_left. The SSTORE + should still succeed. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment() + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + + storage = Storage() + contract = pre.deploy_contract( + code=Op.SSTORE(storage.store_next(1), 1), + ) + + # Provide half the state gas in the reservoir, rest from gas_left + half_state_gas = sstore_state_gas // 2 + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap + half_state_gas, + sender=pre.fund_eoa(), + ) + + post = {contract: Account(storage=storage)} + state_test(env=env, pre=pre, post=post, tx=tx) + + +@EIPChecklist.GasCostChanges.Test.OutOfGas() +@pytest.mark.valid_from("EIP8037") +def test_charge_oog_both_pools_insufficient( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test OOG when both reservoir and gas_left are insufficient. + + Provide just enough gas for intrinsic + SSTORE regular gas but + not enough for the state gas charge. Neither the reservoir (empty + at TX_MAX_GAS_LIMIT) nor gas_left can cover the cost. + """ + gas_costs = fork.gas_costs() + contract = pre.deploy_contract( + code=Op.SSTORE(0, 1), + ) + + # Tight gas: intrinsic + SSTORE regular gas only + intrinsic_cost = fork.transaction_intrinsic_cost_calculator() + gas_limit = intrinsic_cost() + gas_costs.COLD_STORAGE_WRITE + + tx = Transaction( + to=contract, + gas_limit=gas_limit, + sender=pre.fund_eoa(), + ) + + # OOG — storage unchanged + post = {contract: Account(storage={0: 0})} + state_test(pre=pre, post=post, tx=tx) + + +@EIPChecklist.GasRefundsChanges.Test.RefundCalculation() +@pytest.mark.valid_from("EIP8037") +def test_refund_cap_includes_state_gas( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test the 1/5 refund cap includes state gas used from gas_left. + + When state gas is drawn from gas_left (no reservoir), it counts + toward tx_gas_used_before_refund. The 1/5 refund cap applies to + the combined total of regular + state gas consumed. This test + performs an SSTORE zero-to-nonzero-to-zero sequence to generate + a refund and verifies the transaction succeeds. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + contract = pre.deploy_contract( + code=(Op.SSTORE(0, 1) + Op.SSTORE(0, 0)), + ) + + # No reservoir — all gas from gas_left, refund cap applies + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap, + sender=pre.fund_eoa(), + ) + + # Slot 0 restored to zero + post = {contract: Account(storage={0: 0})} + state_test(pre=pre, post=post, tx=tx) + + +@EIPChecklist.GasRefundsChanges.Test.RefundCalculation() +@pytest.mark.valid_from("EIP8037") +def test_refund_with_reservoir_state_gas( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test refund when state gas is drawn from reservoir. + + When state gas comes from the reservoir, the refund still applies. + The refund_counter accumulates state + regular gas refunds, and + the 1/5 cap uses tx_gas_used_before_refund which accounts for + both dimensions. An SSTORE zero-to-nonzero-to-zero sequence + should refund correctly. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment() + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + + contract = pre.deploy_contract( + code=(Op.SSTORE(0, 1) + Op.SSTORE(0, 0)), + ) + + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap + sstore_state_gas, + sender=pre.fund_eoa(), + ) + + # Slot 0 restored to zero + post = {contract: Account(storage={0: 0})} + state_test(env=env, pre=pre, post=post, tx=tx) + + +@pytest.mark.exception_test +@pytest.mark.valid_from("EIP8037") +def test_intrinsic_regular_gas_exceeds_cap( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test that tx is rejected when intrinsic regular gas exceeds cap. + + validate_transaction checks that the intrinsic regular gas (or + calldata floor) does not exceed the transaction gas limit cap. + A transaction with enough calldata to push intrinsic cost above + the cap is invalid even with a high gas_limit. + """ + gas_costs = fork.gas_costs() + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + # One more non-zero byte than needed to exceed the cap + calldata_len = gas_limit_cap // gas_costs.TX_DATA_PER_NON_ZERO + 1 + calldata = b"\x01" * calldata_len + + contract = pre.deploy_contract(code=Op.STOP) + + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap * 2, + data=calldata, + sender=pre.fund_eoa(), + error=TransactionException.INTRINSIC_GAS_TOO_LOW, + ) + + state_test(pre=pre, post={}, tx=tx) + + +@pytest.mark.exception_test +@pytest.mark.valid_from("EIP8037") +def test_intrinsic_regular_gas_exceeds_cap_with_floor_below_cap( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test rejection when intrinsic regular gas exceeds the per-tx gas + cap while the calldata floor stays below the cap. + + EIP-7825/8037 applies the cap to both intrinsic dimensions + independently. The companion `test_intrinsic_regular_gas_exceeds_cap` + pushes both dimensions above the cap with non-zero calldata, so an + implementation that only checks `max(regular, floor)` against the + cap would still pass. This test isolates the regular-only case via + a large EIP-7702 authorization list and minimal calldata. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + + # Authorizations contribute to regular intrinsic only (not floor). + # Pick enough to push regular > cap by a comfortable margin. + auth_count = (gas_limit_cap // Spec.PER_AUTH_BASE_COST) + 1 + calldata = b"\x01" * 4 # tiny: floor stays << cap. + + target = pre.deploy_contract(code=Op.STOP) + authorizations = [ + AuthorizationTuple( + address=target, + nonce=0, + signer=pre.fund_eoa(), + ) + for _ in range(auth_count) + ] + + tx = Transaction( + ty=4, + to=target, + gas_limit=gas_limit_cap * 2, + data=calldata, + authorization_list=authorizations, + sender=pre.fund_eoa(), + error=TransactionException.INTRINSIC_GAS_TOO_LOW, + ) + state_test(pre=pre, post={}, tx=tx) + + +@pytest.mark.parametrize( + "above_floor", + [ + pytest.param( + False, + id="below_floor", + marks=pytest.mark.exception_test, + ), + pytest.param(True, id="at_floor"), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_calldata_floor_enforced_with_state_gas( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + above_floor: bool, +) -> None: + """ + Test EIP-7623 calldata floor is enforced when EIP-8037 is active. + + Send 100 non-zero calldata bytes to a call transaction so the + regular intrinsic cost is below the calldata floor. A gas_limit + at the floor succeeds; one below the floor is rejected. + """ + calldata = b"\x01" * 100 + intrinsic_cost = fork.transaction_intrinsic_cost_calculator() + floor_cost = fork.transaction_data_floor_cost_calculator() + + regular_gas = intrinsic_cost( + calldata=calldata, + return_cost_deducted_prior_execution=True, + ) + floor_gas = floor_cost(data=calldata) + assert floor_gas > regular_gas, "floor must exceed regular for test" + + if above_floor: + gas_limit = floor_gas + error = None + else: + # Between regular and floor: satisfies regular but not floor + gas_limit = (regular_gas + floor_gas) // 2 + error = TransactionException.INTRINSIC_GAS_BELOW_FLOOR_GAS_COST + + tx = Transaction( + to=pre.fund_eoa(0), + data=calldata, + gas_limit=gas_limit, + sender=pre.fund_eoa(), + error=error, + ) + + state_test(pre=pre, post={}, tx=tx) + + +@pytest.mark.parametrize("block_gas_limit", BLOCK_GAS_LIMITS) +@pytest.mark.valid_from("EIP8037") +def test_create_state_gas_scales_with_cpsb( + state_test: StateTestFiller, + pre: Alloc, + block_gas_limit: int, + fork: Fork, +) -> None: + """ + Test CREATE new-account state gas scales with block gas limit. + + State gas for a CREATE is 120 * cpsb (new account) plus + code_size * cpsb (code deposit). + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment(gas_limit=block_gas_limit) + create_state_gas = fork.create_state_gas(code_size=1) + + storage = Storage() + contract = pre.deploy_contract( + code=( + Op.SSTORE( + storage.store_next(1, "create_success"), + Op.GT(Op.CREATE(0, 0, 1), 0), + ) + ), + ) + + tx_gas = min(gas_limit_cap + create_state_gas, block_gas_limit) + tx = Transaction( + to=contract, + gas_limit=tx_gas, + sender=pre.fund_eoa(), + ) + + post = {contract: Account(storage=storage)} + state_test(env=env, pre=pre, post=post, tx=tx) + + +@pytest.mark.parametrize("block_gas_limit", BLOCK_GAS_LIMITS) +@pytest.mark.valid_from("EIP8037") +def test_call_new_account_state_gas_scales_with_cpsb( + state_test: StateTestFiller, + pre: Alloc, + block_gas_limit: int, + fork: Fork, +) -> None: + """ + Test CALL value transfer to empty account scales with block gas limit. + + Sending value to a non-existent account charges 120 * cpsb + of state gas for account creation. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment(gas_limit=block_gas_limit) + gas_costs = fork.gas_costs() + new_account_state_gas = gas_costs.NEW_ACCOUNT + + empty = pre.fund_eoa(0) + storage = Storage() + contract = pre.deploy_contract( + code=( + Op.SSTORE( + storage.store_next(1, "call_success"), + Op.CALL(gas=100_000, address=empty, value=1), + ) + ), + balance=1, + ) + + tx_gas = min(gas_limit_cap + new_account_state_gas, block_gas_limit) + tx = Transaction( + to=contract, + gas_limit=tx_gas, + sender=pre.fund_eoa(), + ) + + post = {contract: Account(storage=storage)} + state_test(env=env, pre=pre, post=post, tx=tx) + + +@pytest.mark.parametrize("block_gas_limit", BLOCK_GAS_LIMITS) +@pytest.mark.valid_from("EIP8037") +def test_selfdestruct_new_beneficiary_scales_with_cpsb( + state_test: StateTestFiller, + pre: Alloc, + block_gas_limit: int, + fork: Fork, +) -> None: + """ + Test SELFDESTRUCT to new beneficiary scales with block gas limit. + + Destructing to a non-existent address with balance charges + 120 * cpsb of state gas for the new beneficiary account. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment(gas_limit=block_gas_limit) + gas_costs = fork.gas_costs() + new_account_state_gas = gas_costs.NEW_ACCOUNT + + beneficiary = pre.fund_eoa(0) + storage = Storage() + caller = pre.deploy_contract( + code=( + Op.SSTORE( + storage.store_next(1, "selfdestruct_ran"), + 1, + ) + + Op.SELFDESTRUCT(beneficiary) + ), + balance=1, + ) + + tx_gas = min(gas_limit_cap + new_account_state_gas, block_gas_limit) + tx = Transaction( + to=caller, + gas_limit=tx_gas, + sender=pre.fund_eoa(), + ) + + post = {caller: Account(storage=storage)} + state_test(env=env, pre=pre, post=post, tx=tx) + + +@pytest.mark.parametrize("block_gas_limit", BLOCK_GAS_LIMITS) +@pytest.mark.valid_from("EIP8037") +def test_sstore_refund_scales_with_cpsb( + state_test: StateTestFiller, + pre: Alloc, + block_gas_limit: int, + fork: Fork, +) -> None: + """ + Test SSTORE restoration refund scales with block gas limit. + + Zero-to-nonzero-to-zero in the same tx refunds the state gas + (64 * cpsb) via refund_counter. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment(gas_limit=block_gas_limit) + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + + contract = pre.deploy_contract( + code=(Op.SSTORE(0, 1) + Op.SSTORE(0, 0)), + ) + + tx_gas = min(gas_limit_cap + sstore_state_gas, block_gas_limit) + tx = Transaction( + to=contract, + gas_limit=tx_gas, + sender=pre.fund_eoa(), + ) + + post = {contract: Account(storage={0: 0})} + state_test(env=env, pre=pre, post=post, tx=tx) + + +@pytest.mark.parametrize("block_gas_limit", BLOCK_GAS_LIMITS) +@pytest.mark.valid_from("EIP8037") +def test_auth_state_gas_scales_with_cpsb( + state_test: StateTestFiller, + pre: Alloc, + block_gas_limit: int, + fork: Fork, +) -> None: + """ + Test SetCode authorization state gas scales with block gas limit. + + A type-4 tx with one authorization charges + (STATE_BYTES_PER_NEW_ACCOUNT + STATE_BYTES_PER_AUTH_BASE) * cpsb + of intrinsic state gas for the new account delegation. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment(gas_limit=block_gas_limit) + auth_state_gas = fork.transaction_intrinsic_state_gas( + authorization_count=1, + ) + + delegate = pre.deploy_contract(code=Op.SSTORE(0, 1)) + signer = pre.fund_eoa() + + storage = Storage() + target = pre.deploy_contract( + code=Op.SSTORE( + storage.store_next(1, "delegated_call_success"), + Op.CALL(gas=100_000, address=signer), + ), + ) + + tx_gas = min(gas_limit_cap + auth_state_gas, block_gas_limit) + tx = Transaction( + ty=4, + to=target, + gas_limit=tx_gas, + sender=pre.fund_eoa(), + authorization_list=[ + AuthorizationTuple( + address=delegate, + nonce=0, + signer=signer, + ), + ], + ) + + post = {target: Account(storage=storage)} + state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_reservoir.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_reservoir.py new file mode 100644 index 00000000000..e50d38e73ea --- /dev/null +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_reservoir.py @@ -0,0 +1,1964 @@ +""" +Test cases for the EIP-8037 state gas reservoir and its interaction with the +EIP-7825 TX_MAX_GAS_LIMIT cap. + +EIP-8037 splits execution gas into two pools: +- `gas_left` (regular gas): capped at `TX_MAX_GAS_LIMIT - intrinsic.regular` +- `state_gas_reservoir`: the overflow beyond the regular gas cap + +State gas charges draw from the reservoir first, then spill into gas_left. +Regular gas charges draw only from gas_left. + +Tests for [EIP-8037: State Creation Gas Cost Increase] +(https://eips.ethereum.org/EIPS/eip-8037). +""" + +import pytest +from execution_testing import ( + AccessList, + Account, + Address, + Alloc, + AuthorizationTuple, + Block, + BlockchainTestFiller, + Bytecode, + Environment, + Fork, + Header, + Op, + StateTestFiller, + Storage, + Transaction, + TransactionException, + TransactionReceipt, + compute_create_address, +) +from execution_testing import ( + Macros as Om, +) +from execution_testing.checklists import EIPChecklist + +from tests.prague.eip7702_set_code_tx.spec import Spec as Spec7702 + +from .spec import ref_spec_8037 + +REFERENCE_SPEC_GIT_PATH = ref_spec_8037.git_path +REFERENCE_SPEC_VERSION = ref_spec_8037.version + + +@pytest.mark.parametrize( + "gas_limit_delta", + [ + pytest.param(-1, id="below_cap"), + pytest.param(0, id="at_cap"), + pytest.param(1, id="above_cap"), + ], +) +@EIPChecklist.ModifiedTransactionValidityConstraint.Test() +@pytest.mark.valid_from("EIP8037") +def test_reservoir_allocation_boundary( + state_test: StateTestFiller, + pre: Alloc, + gas_limit_delta: int, + fork: Fork, +) -> None: + """ + Test state gas reservoir allocation at TX_MAX_GAS_LIMIT boundary. + + When tx.gas <= TX_MAX_GAS_LIMIT, all execution gas fits in gas_left + and the reservoir is zero. When tx.gas > TX_MAX_GAS_LIMIT, the + excess goes to the reservoir. In all cases, an SSTORE should + succeed because state gas can spill from gas_left. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + storage = Storage() + contract = pre.deploy_contract( + code=Op.SSTORE(storage.store_next(1), 1), + ) + + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap + gas_limit_delta, + sender=pre.fund_eoa(), + ) + + post = {contract: Account(storage=storage)} + state_test(pre=pre, post=post, tx=tx) + + +@pytest.mark.parametrize( + "num_sstores,reservoir_covers_state_gas", + [ + pytest.param(1, True, id="single_sstore_from_reservoir"), + pytest.param(5, True, id="multiple_sstores_from_reservoir"), + pytest.param(1, False, id="single_sstore_spill_to_gas_left"), + pytest.param(5, False, id="multiple_sstores_spill_to_gas_left"), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_sstore_state_gas_source( + state_test: StateTestFiller, + pre: Alloc, + num_sstores: int, + reservoir_covers_state_gas: bool, + fork: Fork, +) -> None: + """ + Test SSTORE zero-to-nonzero drawing state gas from different sources. + + When reservoir_covers_state_gas is True, enough gas is provided above + TX_MAX_GAS_LIMIT to cover all SSTORE state gas from the reservoir. + When False, the reservoir is minimal (1 gas unit) and state gas must + spill into gas_left. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment() + + storage = Storage() + code = Bytecode() + for _ in range(num_sstores): + code += Op.SSTORE(storage.store_next(1), 1) + contract = pre.deploy_contract(code=code) + + if reservoir_covers_state_gas: + extra_gas = code.state_cost(fork) + else: + extra_gas = 1 # Minimal reservoir, rest spills to gas_left + + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap + extra_gas, + sender=pre.fund_eoa(), + ) + + post = {contract: Account(storage=storage)} + state_test(env=env, pre=pre, post=post, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_sstore_state_gas_entirely_from_gas_left( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test SSTORE state gas charged entirely from gas_left (no reservoir). + + When tx.gas <= TX_MAX_GAS_LIMIT, the reservoir is zero. All state + gas must come from gas_left. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + storage = Storage() + contract = pre.deploy_contract( + code=Op.SSTORE(storage.store_next(1), 1), + ) + + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap, + sender=pre.fund_eoa(), + ) + + post = {contract: Account(storage=storage)} + state_test(pre=pre, post=post, tx=tx) + + +@EIPChecklist.GasCostChanges.Test.OutOfGas() +@pytest.mark.valid_from("EIP8037") +def test_insufficient_gas_for_sstore_state_cost( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test that execution OOGs when gas is insufficient for SSTORE state cost. + + Provide just enough gas for intrinsic costs plus the SSTORE regular + gas, but not enough to also cover the SSTORE state gas. The SSTORE + should OOG, leaving storage slot 0 unchanged at zero. + """ + gas_costs = fork.gas_costs() + contract = pre.deploy_contract( + code=Op.SSTORE(0, 1), + ) + + # Enough for intrinsic + warm SSTORE regular gas, but not the + # state gas cost for zero-to-nonzero transition + intrinsic_cost = fork.transaction_intrinsic_cost_calculator() + gas_limit = intrinsic_cost() + gas_costs.COLD_STORAGE_WRITE + + tx = Transaction( + to=contract, + gas_limit=gas_limit, + sender=pre.fund_eoa(), + ) + + # Execution OOGs — storage slot 0 remains at default (zero) + post = {contract: Account(storage={0: 0})} + state_test(pre=pre, post=post, tx=tx) + + +@pytest.mark.parametrize( + "exceed_block_gas_limit", + [ + pytest.param(True, marks=pytest.mark.exception_test), + pytest.param(False), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_block_regular_gas_limit( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + exceed_block_gas_limit: bool, + fork: Fork, +) -> None: + """ + Test check_transaction enforcement of regular gas against block limit. + + The regular gas check uses min(TX_MAX_GAS_LIMIT, tx.gas). + Fill the block with transactions at TX_MAX_GAS_LIMIT and verify + the last one is accepted or rejected based on remaining capacity. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment() + tx_count = env.gas_limit // gas_limit_cap + + gas_spender = pre.deploy_contract(code=Op.INVALID) + + total_txs = tx_count + int(exceed_block_gas_limit) + block = Block( + txs=[ + Transaction( + to=gas_spender, + sender=pre.fund_eoa(), + gas_limit=gas_limit_cap, + error=( + TransactionException.GAS_ALLOWANCE_EXCEEDED + if i >= tx_count + else None + ), + ) + for i in range(total_txs) + ], + exception=( + TransactionException.GAS_ALLOWANCE_EXCEEDED + if exceed_block_gas_limit + else None + ), + ) + + blockchain_test(pre=pre, post={}, blocks=[block]) + + +@pytest.mark.parametrize( + "delta", + [ + pytest.param(0, id="exact_fit"), + pytest.param(1, id="exceeded", marks=pytest.mark.exception_test), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_block_state_gas_limit_boundary( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, + delta: int, +) -> None: + """ + Verify the per-tx state check at the strict-greater-than boundary. + + tx1 consumes `tx1_state` via cold SSTOREs. tx2 is sized so that + its worst-case state contribution `tx.gas - intrinsic_regular` + equals `state_available` (delta=0, accepted because the check is + strict `>`) or exceeds it by 1 (delta=1, rejected with + `GAS_ALLOWANCE_EXCEEDED`). + + The regular check is asserted to pass so rejection on delta=1 is + pinned to the state dimension. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + + block_gas_limit = 100_000_000 + + intrinsic_cost = fork.transaction_intrinsic_cost_calculator() + + num_sstores = 50 + tx1_code = Bytecode() + for i in range(num_sstores): + tx1_code = tx1_code + Op.SSTORE(i, 1) + tx1_contract = pre.deploy_contract(code=tx1_code) + + tx1_state = tx1_code.state_cost(fork) + tx1_regular = intrinsic_cost() + tx1_code.gas_cost(fork) - tx1_state + tx1_gas = gas_limit_cap + tx1_state + + # tx2: worst-case state contribution = tx.gas - intrinsic_regular. + # Plain call, so intrinsic_state is zero. + tx2_intrinsic_regular = intrinsic_cost() + state_available = block_gas_limit - tx1_state + tx2_gas = tx2_intrinsic_regular + state_available + delta + + # Pin the rejection (when delta > 0) to the state check: the + # regular check must not fire. + regular_available = block_gas_limit - tx1_regular + assert min(gas_limit_cap, tx2_gas) < regular_available, ( + "tx2 would fail the regular check instead of the state check" + ) + + tx2_error = ( + TransactionException.GAS_ALLOWANCE_EXCEEDED if delta > 0 else None + ) + block_exception = tx2_error + + tx1 = Transaction( + to=tx1_contract, + gas_limit=tx1_gas, + sender=pre.fund_eoa(), + ) + tx2 = Transaction( + to=pre.deploy_contract(code=Op.STOP), + gas_limit=tx2_gas, + sender=pre.fund_eoa(), + error=tx2_error, + ) + + blockchain_test( + genesis_environment=Environment(gas_limit=block_gas_limit), + pre=pre, + blocks=[ + Block( + txs=[tx1, tx2], + gas_limit=block_gas_limit, + exception=block_exception, + ) + ], + post={}, + ) + + +@pytest.mark.valid_from("EIP8037") +def test_creation_tx_regular_check_subtracts_intrinsic_state( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify the regular check subtracts `intrinsic.state` from tx.gas. + + The EIP regular check is + `min(TX_MAX, tx.gas - intrinsic.state) > regular_available`. For a + creation tx, `intrinsic.state = GAS_NEW_ACCOUNT`. This test sizes a + creation tx whose raw `tx.gas` exceeds `regular_available` but + `tx.gas - intrinsic.state` fits; it must be accepted. The old + formula `min(TX_MAX, tx.gas)` would reject the same tx, proving + the subtraction is honored. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + + # `intrinsic_regular` for a creation tx is cpsb-free + # (GAS_TX_BASE + REGULAR_GAS_CREATE + init_code_cost), so + # reading it at the current cpsb and using it to size the block + # gives a stable `block_gas_limit` independent of cpsb. + intrinsic_regular = fork.transaction_intrinsic_cost_calculator()( + contract_creation=True + ) - fork.transaction_intrinsic_state_gas(contract_creation=True) + + # Tight boundary: after the filler consumes gas_limit_cap, the + # remaining regular is exactly intrinsic_regular + 1. The old + # formula `min(TX_MAX, tx.gas)` rejects (tx.gas = intrinsic_total + # > intrinsic_regular + 1); the new formula `min(TX_MAX, tx.gas + # - intrinsic.state)` accepts (equals intrinsic_regular). + block_gas_limit = gas_limit_cap + intrinsic_regular + 1 + + intrinsic_state = fork.transaction_intrinsic_state_gas( + contract_creation=True, + ) + create_tx_gas = fork.transaction_intrinsic_cost_calculator()( + contract_creation=True, + ) + + # Filler consumes the full regular cap (OOG on INVALID). + filler = pre.deploy_contract(code=Op.INVALID) + + remaining_regular = block_gas_limit - gas_limit_cap + + assert create_tx_gas > remaining_regular, ( + "old formula must reject to prove new formula differs" + ) + assert create_tx_gas - intrinsic_state <= remaining_regular, ( + "new formula must accept" + ) + + filler_tx = Transaction( + to=filler, + gas_limit=gas_limit_cap, + sender=pre.fund_eoa(), + ) + create_tx = Transaction( + to=None, + gas_limit=create_tx_gas, + sender=pre.fund_eoa(), + ) + + blockchain_test( + genesis_environment=Environment(gas_limit=block_gas_limit), + pre=pre, + blocks=[ + Block( + txs=[filler_tx, create_tx], + gas_limit=block_gas_limit, + ) + ], + post={}, + ) + + +@pytest.mark.exception_test +@pytest.mark.valid_from("EIP8037") +def test_single_tx_state_check_exceeds_block_limit( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify a single tx is rejected when its state contribution exceeds + the entire block gas limit. + + No prior txs needed. A tx whose tx.gas - intrinsic_regular exceeds + block_gas_limit must be rejected at inclusion. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + intrinsic_cost = fork.transaction_intrinsic_cost_calculator() + intrinsic_regular = intrinsic_cost() + + block_gas_limit = gas_limit_cap + 100 + tx_gas = block_gas_limit + intrinsic_regular + 1 + + tx = Transaction( + to=pre.deploy_contract(code=Op.STOP), + gas_limit=tx_gas, + sender=pre.fund_eoa(), + error=TransactionException.GAS_ALLOWANCE_EXCEEDED, + ) + + blockchain_test( + genesis_environment=Environment(gas_limit=block_gas_limit), + pre=pre, + blocks=[ + Block( + txs=[tx], + gas_limit=block_gas_limit, + exception=TransactionException.GAS_ALLOWANCE_EXCEEDED, + ) + ], + post={}, + ) + + +@pytest.mark.exception_test +@pytest.mark.valid_from("EIP8037") +def test_creation_tx_state_check_exceeded( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify a creation tx is rejected by the state check. + + A creation tx has non-zero intrinsic_state (new account) AND + intrinsic_regular (base + CREATE cost). Both formulas are + exercised: the regular check subtracts intrinsic_state, the state + check subtracts intrinsic_regular. + + A filler tx consumes state budget. The creation tx's state + contribution (tx.gas - intrinsic_regular) exceeds the remaining + state budget while its regular contribution + (tx.gas - intrinsic_state) fits the regular budget. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + + block_gas_limit = 100_000_000 + + intrinsic_cost = fork.transaction_intrinsic_cost_calculator() + create_intrinsic_total = intrinsic_cost(contract_creation=True) + create_intrinsic_state = fork.transaction_intrinsic_state_gas( + contract_creation=True, + ) + create_intrinsic_regular = create_intrinsic_total - create_intrinsic_state + + num_sstores = 50 + tx1_code = Bytecode() + for i in range(num_sstores): + tx1_code = tx1_code + Op.SSTORE(i, 1) + tx1_contract = pre.deploy_contract(code=tx1_code) + + tx1_state = tx1_code.state_cost(fork) + tx1_regular = intrinsic_cost() + tx1_code.gas_cost(fork) - tx1_state + tx1_gas = gas_limit_cap + tx1_state + state_available = block_gas_limit - tx1_state + + # tx2 state contribution = state_available + 1 → rejected + tx2_gas = create_intrinsic_regular + state_available + 1 + + # Regular check must pass so rejection is pinned to state. + regular_available = block_gas_limit - tx1_regular + assert min(gas_limit_cap, tx2_gas - create_intrinsic_state) < ( + regular_available + ) + + tx1 = Transaction( + to=tx1_contract, + gas_limit=tx1_gas, + sender=pre.fund_eoa(), + ) + tx2 = Transaction( + to=None, + gas_limit=tx2_gas, + sender=pre.fund_eoa(), + error=TransactionException.GAS_ALLOWANCE_EXCEEDED, + ) + + blockchain_test( + genesis_environment=Environment(gas_limit=block_gas_limit), + pre=pre, + blocks=[ + Block( + txs=[tx1, tx2], + gas_limit=block_gas_limit, + exception=TransactionException.GAS_ALLOWANCE_EXCEEDED, + ) + ], + post={}, + ) + + +@pytest.mark.valid_from("EIP8037") +def test_block_gas_used_no_state_ops( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test block gas_used when regular gas dominates (no state operations). + + With no state-creating operations, state gas is 0 and block gas_used + should equal regular gas used. + """ + contract = pre.deploy_contract(code=Op.STOP) + + intrinsic_cost = fork.transaction_intrinsic_cost_calculator() + gas_needed = intrinsic_cost() + + tx = Transaction( + to=contract, + gas_limit=gas_needed, + sender=pre.fund_eoa(), + ) + + blockchain_test( + pre=pre, + blocks=[Block(txs=[tx], header_verify=Header(gas_used=gas_needed))], + post={}, + ) + + +@pytest.mark.valid_from("EIP8037") +def test_block_gas_used_with_state_ops( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test block gas_used includes state gas contribution. + + A transaction performing SSTORE zero-to-nonzero contributes to both + block_gas_used and block_state_gas_used. The block header gas_used + is max(block_gas_used, block_state_gas_used). + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + storage = Storage() + code = Op.SSTORE(storage.store_next(1), 1) + contract = pre.deploy_contract(code=code) + + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap, + sender=pre.fund_eoa(), + ) + + intrinsic_cost = fork.transaction_intrinsic_cost_calculator() + block_regular_gas = intrinsic_cost() + code.regular_cost(fork) + block_state_gas = code.state_cost(fork) + assert block_state_gas > block_regular_gas + + blockchain_test( + pre=pre, + blocks=[ + Block( + txs=[tx], + header_verify=Header( + gas_used=block_state_gas, + ), + ), + ], + post={contract: Account(storage=storage)}, + ) + + +@pytest.mark.valid_from("EIP8037") +def test_block_2d_gas_valid_when_cumulative_exceeds_limit( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify block validity under 2D gas when sum(txGasUsed) > gas_limit. + + EIP-8037 block validity: max(regular, state) <= gas_limit. + Receipt cumulative_gas_used sums both dimensions per-tx, so it + can legitimately exceed gas_limit. Clients must not use the 1D + cumulative check for block validation. + """ + block_gas_limit = 100_000_000 + + sstore_code = Op.SSTORE(0, 1, new_value=1) + sstore_state_gas = sstore_code.state_cost(fork) + + tx_regular = ( + sstore_code.regular_cost(fork) + + fork.transaction_intrinsic_cost_calculator()() + ) + tx_state = sstore_state_gas + tx_gas_used = tx_regular + tx_state + + assert tx_state > tx_regular + block_gas_used = tx_state + + # num_txs sized so `one_d_bound > block_gas_limit > two_d_bound`: + # per-dimension maxes fit (accepted under 2D-max) but the 1D sum + # exceeds the limit (would be rejected by a summing client). + num_txs = block_gas_limit // block_gas_used + two_d_bound = num_txs * block_gas_used + one_d_bound = num_txs * tx_gas_used + assert two_d_bound <= block_gas_limit < one_d_bound + + env = Environment(gas_limit=block_gas_limit) + tx_limit = tx_gas_used + 1000 + + txs = [] + post = {} + for _ in range(num_txs): + storage = Storage() + contract = pre.deploy_contract( + code=Op.SSTORE(storage.store_next(1), 1), + ) + txs.append( + Transaction( + to=contract, + gas_limit=tx_limit, + sender=pre.fund_eoa(), + ), + ) + post[contract] = Account(storage=storage) + + blockchain_test( + genesis_environment=env, + pre=pre, + blocks=[ + Block( + txs=txs, + gas_limit=block_gas_limit, + header_verify=Header( + gas_used=num_txs * block_gas_used, + ), + ), + ], + post=post, + ) + + +@pytest.mark.parametrize( + "gas_above_cap", + [ + pytest.param(True, id="state_gas_from_reservoir"), + pytest.param(False, id="state_gas_from_gas_left"), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_create_tx_reservoir( + state_test: StateTestFiller, + pre: Alloc, + gas_above_cap: bool, + fork: Fork, +) -> None: + """ + Test contract creation with state gas from reservoir or gas_left. + + Contract creation charges intrinsic state gas for the new account + (new-account state gas). When gas_above_cap is True, extra gas + beyond TX_MAX_GAS_LIMIT feeds the reservoir. When False, all state + gas comes from gas_left (reservoir is zero). + """ + gas_costs = fork.gas_costs() + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + init_code = Op.STOP + + env = Environment() + create_state_gas = gas_costs.NEW_ACCOUNT + + if gas_above_cap: + gas_limit = gas_limit_cap + create_state_gas + else: + gas_limit = gas_limit_cap + + tx = Transaction( + to=None, + data=init_code, + gas_limit=gas_limit, + sender=pre.fund_eoa(), + ) + + state_test(env=env, pre=pre, post={}, tx=tx) + + +@pytest.mark.parametrize( + "failure_mode", + [ + pytest.param("revert", id="revert"), + pytest.param("halt", id="halt"), + pytest.param("oog", id="oog"), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_top_level_failure_refunds_execution_state_gas( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + failure_mode: str, +) -> None: + """ + Verify top level tx failure returns execution state gas to the + reservoir across revert, exceptional halt, and out of gas paths. + + On top level failure no state was created, so execution state gas + is credited back to the reservoir and `state_gas_used` is zeroed. + The billing formula `tx.gas - gas_left - state_gas_left` sees a + restored reservoir and refunds the sender. Without the refund the + receipt would bill the consumed state gas despite the failure. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + intrinsic_cost = fork.transaction_intrinsic_cost_calculator()() + + if failure_mode == "revert": + code = Op.SSTORE(0, 1) + Op.REVERT(0, 0) + elif failure_mode == "halt": + code = Op.SSTORE(0, 1) + Op.INVALID + else: + # OOG: perform the SSTORE then spin with JUMPDEST loop until + # gas runs out. + code = Op.SSTORE(0, 1) + Op.JUMPDEST + Op.JUMP(0x5) + contract = pre.deploy_contract(code=code) + + tx_gas = gas_limit_cap + sstore_state_gas + + if failure_mode == "revert": + # REVERT preserves unused gas_left. + expected_cumulative = ( + intrinsic_cost + code.gas_cost(fork) - sstore_state_gas + ) + else: + # Exceptional halt and out of gas zero gas_left. + expected_cumulative = tx_gas - sstore_state_gas + + tx = Transaction( + to=contract, + gas_limit=tx_gas, + sender=pre.fund_eoa(), + expected_receipt=TransactionReceipt( + cumulative_gas_used=expected_cumulative, + ), + ) + + state_test(pre=pre, post={contract: Account(storage={})}, tx=tx) + + +@pytest.mark.parametrize( + "failure_mode", + [ + pytest.param("revert", id="revert"), + pytest.param("halt", id="halt"), + pytest.param("oog", id="oog"), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_top_level_failure_zeros_block_state_gas( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, + failure_mode: str, +) -> None: + """ + Verify the block header reflects zero execution state gas after a + top level failure. + + With `state_gas_used` zeroed on failure, `block_state_gas_used` + excludes any state gas consumed during the failed transaction and + the block header `gas_used` falls back to the regular gas + component alone. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + intrinsic_cost = fork.transaction_intrinsic_cost_calculator()() + + if failure_mode == "revert": + code = Op.SSTORE(0, 1) + Op.REVERT(0, 0) + elif failure_mode == "halt": + code = Op.SSTORE(0, 1) + Op.INVALID + else: + code = Op.SSTORE(0, 1) + Op.JUMPDEST + Op.JUMP(0x5) + contract = pre.deploy_contract(code=code) + + tx_gas = gas_limit_cap + sstore_state_gas + tx = Transaction( + to=contract, + gas_limit=tx_gas, + sender=pre.fund_eoa(), + ) + + if failure_mode == "revert": + expected_block_regular = ( + intrinsic_cost + code.gas_cost(fork) - sstore_state_gas + ) + else: + # Exceptional halt and out of gas zero gas_left. + expected_block_regular = tx_gas - sstore_state_gas + + blockchain_test( + pre=pre, + blocks=[ + Block( + txs=[tx], + header_verify=Header(gas_used=expected_block_regular), + ), + ], + post={contract: Account(storage={})}, + ) + + +@pytest.mark.valid_from("EIP8037") +def test_creation_tx_failure_preserves_intrinsic_state_gas( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Regression test for the creation tx failure path. + + A creation tx (to=None) whose initcode halts exercises both the + intrinsic state gas for the new account and the top level failure + refund of execution state gas. The test asserts the block header + `gas_used` equals `max(block_regular, intrinsic_state_gas)`, + guarding that the failure path does not raise and that block + accounting does not underflow when the refund is applied. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + + create_intrinsic_state = fork.transaction_intrinsic_state_gas( + contract_creation=True, + ) + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + tx_gas = gas_limit_cap + create_intrinsic_state + sstore_state_gas + + tx = Transaction( + to=None, + data=Op.SSTORE(0, 1) + Op.INVALID, + gas_limit=tx_gas, + sender=pre.fund_eoa(), + ) + + block_regular = tx_gas - create_intrinsic_state - sstore_state_gas + expected_gas_used = max(block_regular, create_intrinsic_state) + + blockchain_test( + pre=pre, + blocks=[ + Block( + txs=[tx], + header_verify=Header(gas_used=expected_gas_used), + ), + ], + post={}, + ) + + +@pytest.mark.valid_from("EIP8037") +def test_subcall_failure_does_not_zero_top_level_state_gas( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify a subcall failure does not zero the top level execution + state gas. + + The top level tx succeeds end to end even though a subcall + reverts, so the top level failure refund does not apply. The + parent's own SSTORE contributes state gas that appears in + `block_state_gas_used`. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + + child = pre.deploy_contract(code=Op.REVERT(0, 0)) + parent_storage = Storage() + parent = pre.deploy_contract( + code=( + Op.POP(Op.CALL(gas=Op.GAS, address=child)) + + Op.SSTORE(parent_storage.store_next(1, "parent_sstore"), 1) + ), + ) + + tx = Transaction( + to=parent, + gas_limit=gas_limit_cap + sstore_state_gas, + sender=pre.fund_eoa(), + ) + + # Parent's SSTORE state gas dominates tx_regular and surfaces in + # the block header, proving the top level refund is scoped to + # top level failures and not child reverts. + blockchain_test( + pre=pre, + blocks=[ + Block( + txs=[tx], + header_verify=Header(gas_used=sstore_state_gas), + ), + ], + post={parent: Account(storage=parent_storage)}, + ) + + +@pytest.mark.parametrize( + "failure_mode", + [ + pytest.param("revert", id="revert"), + pytest.param("halt", id="halt"), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_top_level_failure_spilled_state_gas( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + failure_mode: str, +) -> None: + """ + Verify the top-level failure handling for state gas that spilled + from the reservoir into `gas_left`. + + When the reservoir is smaller than the state gas charge, the + overflow spills and is drawn from `gas_left`. Both failure + modes refund the full `state_gas_used` (reservoir-portion + + spilled-portion) to the reservoir per the updated EIP. They + differ only in `gas_left` handling: + + - REVERT preserves `gas_left`; sender billed only the regular + component. + - Exceptional halt zeros `gas_left` (existing EVM rule); sender + pays for everything except the state-gas refund. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + intrinsic_cost = fork.transaction_intrinsic_cost_calculator()() + + if failure_mode == "revert": + code = Op.SSTORE(0, 1) + Op.REVERT(0, 0) + else: + code = Op.SSTORE(0, 1) + Op.INVALID + contract = pre.deploy_contract(code=code) + + # Reservoir sized to cover only half the SSTORE state gas; the + # other half spills into gas_left. + tx_gas = gas_limit_cap + sstore_state_gas // 2 + + if failure_mode == "revert": + # gas_left preserved; full state_gas_used refunded to + # reservoir → sender billed only the regular component. + expected_cumulative = ( + intrinsic_cost + code.gas_cost(fork) - sstore_state_gas + ) + else: + # gas_left burned; full state_gas_used (reservoir-portion + + # spilled-portion) refunded via reservoir. + # tx_gas_used = tx_gas - 0 - sstore_state_gas. + expected_cumulative = tx_gas - sstore_state_gas + + tx = Transaction( + to=contract, + gas_limit=tx_gas, + sender=pre.fund_eoa(), + expected_receipt=TransactionReceipt( + cumulative_gas_used=expected_cumulative, + ), + ) + + state_test(pre=pre, post={contract: Account(storage={})}, tx=tx) + + +@pytest.mark.parametrize( + "failure_mode", + [ + pytest.param("revert", id="revert"), + pytest.param("halt", id="halt"), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_top_level_failure_propagated_state_gas( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + failure_mode: str, +) -> None: + """ + Verify the top-level failure handling for state gas propagated + from a successful subcall. + + The parent calls a child that runs SSTORE and returns. The + child's `state_gas_used` is folded into the parent frame via the + success path so the parent's reservoir is empty and its + `state_gas_used` carries the SSTORE charge. + + Per the updated EIP both failure modes refund the full propagated + `state_gas_used` (reservoir-portion + spilled-portion) to the + reservoir. They differ only in `gas_left` handling: + + - REVERT preserves `gas_left`; sender billed only the regular + component. + - Exceptional halt zeros `gas_left`; sender pays for everything + except the state-gas refund. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + intrinsic_cost = fork.transaction_intrinsic_cost_calculator()() + + child_code = Op.SSTORE(0, 1) + child = pre.deploy_contract(code=child_code) + if failure_mode == "revert": + parent_code = Op.POP(Op.CALL(gas=Op.GAS, address=child)) + Op.REVERT( + 0, 0 + ) + else: + parent_code = Op.POP(Op.CALL(gas=Op.GAS, address=child)) + Op.INVALID + parent = pre.deploy_contract(code=parent_code) + + # Reservoir sized to half the SSTORE state gas so the child's + # charge drains the reservoir AND spills into gas_left. The halt + # path then exercises a non-trivial spill case rather than the + # degenerate no-spill case. + tx_gas = gas_limit_cap + sstore_state_gas // 2 + + if failure_mode == "revert": + # gas_left preserved; full propagated state_gas_used refunded + # → sender billed only the regular component. + expected_cumulative = ( + intrinsic_cost + + parent_code.gas_cost(fork) + + child_code.gas_cost(fork) + - sstore_state_gas + ) + else: + # gas_left burned; full propagated state_gas_used (reservoir + # + spill) refunded via reservoir. + # tx_gas_used = tx_gas - 0 - sstore_state_gas. + expected_cumulative = tx_gas - sstore_state_gas + + tx = Transaction( + to=parent, + gas_limit=tx_gas, + sender=pre.fund_eoa(), + expected_receipt=TransactionReceipt( + cumulative_gas_used=expected_cumulative, + ), + ) + + state_test(pre=pre, post={child: Account(storage={})}, tx=tx) + + +def _build_call_chain( + pre: Alloc, + frame_bodies: list[Bytecode], + terminator: Bytecode, +) -> tuple[Address, list[Bytecode]]: + """ + Build a chain of CALL-nested frames. + + Each non-deepest frame executes its body, CALLs the next frame, + then terminates with `terminator`. The deepest frame just + executes its body and terminates. + """ + remaining_frame_bodies = frame_bodies[:] + deepest_code = remaining_frame_bodies.pop() + terminator + frame_codes: list[Bytecode] = [deepest_code] + inner_addr = pre.deploy_contract(code=deepest_code) + while remaining_frame_bodies: + code = ( + remaining_frame_bodies.pop() + + Op.POP(Op.CALL(gas=Op.GAS, address=inner_addr)) + + terminator + ) + inner_addr = pre.deploy_contract(code=code) + frame_codes.insert(0, code) + return inner_addr, frame_codes + + +def _build_create_chain( + pre: Alloc, + frame_bodies: list[Bytecode], + terminator: Bytecode, +) -> tuple[Address, list[Bytecode]]: + """ + Build a chain of CREATE-nested frames. + + Top frame is a deployed contract; each non-deepest frame executes + its body, places the next-level initcode in memory, CREATEs it, + then terminates with `terminator`. The deepest level's initcode + just executes its body and terminates. + + Each CREATE pre-charges `STATE_NEW × cpsb` of state-gas on the + parent frame, which is what makes this chain exercise the + credit-on-failure path that distinguishes Policy A from Policy B + for top-level halt. + """ + remaining_frame_bodies = frame_bodies[:] + # Deepest level is just body + terminator (runs as initcode of + # the depth-(N-2) frame's CREATE). + inner_initcode = remaining_frame_bodies.pop() + terminator + frame_codes: list[Bytecode] = [inner_initcode] + + while remaining_frame_bodies: + inner_bytes = bytes(inner_initcode) + inner_size = len(inner_bytes) + # Pad to 32-byte alignment so Om.MSTORE uses the cheap + # PUSH32+MSTORE path on the trailing chunk; CREATE reads + # only `size` bytes so the trailing zeros are ignored. + padded = inner_bytes + b"\x00" * ((-inner_size) % 32) + code = ( + remaining_frame_bodies.pop() + + Om.MSTORE(padded, 0) + + Op.POP( + Op.CREATE( + value=0, + offset=0, + size=inner_size, + init_code_size=inner_size, + ) + ) + + terminator + ) + frame_codes.insert(0, code) + inner_initcode = code + + top = pre.deploy_contract(code=frame_codes[0]) + return top, frame_codes + + +@pytest.mark.parametrize( + "frame_bodies", + [ + pytest.param( + [ + Op.SSTORE(0, 1), + Op.SSTORE(1, 1), + Op.SSTORE(2, 1), + Op.SSTORE(3, 1), + ], + id="depth_4_sstore_each", + ), + pytest.param( + [ + Op.SSTORE(0, 1), + Bytecode(), + Op.SSTORE(2, 1), + Bytecode(), + ], + id="depth_4_alternating_state", + ), + pytest.param( + [Bytecode(), Bytecode(), Bytecode(), Bytecode()], + id="depth_4_no_state", + ), + pytest.param( + [ + Op.SSTORE(0, 1) + Op.SSTORE(1, 1), + Op.SSTORE(2, 1) + Op.SSTORE(3, 1), + Op.SSTORE(4, 1) + Op.SSTORE(5, 1), + ], + id="depth_3_two_sstores_each", + ), + pytest.param( + [ + Bytecode(), + Bytecode(), + Op.SSTORE(0, 1) + + Op.SSTORE( + 0, + 0, + key_warm=True, + original_value=0, + current_value=1, + new_value=0, + ), + ], + id="depth_3_deepest_0_to_x_to_0", + ), + pytest.param( + [ + Bytecode(), + Bytecode(), + Op.SSTORE(0, 1) + + Op.SSTORE( + 0, + 2, + key_warm=True, + original_value=0, + current_value=1, + new_value=2, + ) + + Op.SSTORE( + 0, + 0, + key_warm=True, + original_value=0, + current_value=2, + new_value=0, + ), + ], + id="depth_3_deepest_0_to_x_to_y_to_0", + ), + ], +) +@pytest.mark.parametrize( + "failure_mode", + [ + pytest.param("revert", id="revert"), + pytest.param("halt", id="halt"), + ], +) +@pytest.mark.parametrize( + "spill_mode", + [ + pytest.param("no_spill", id="no_spill"), + pytest.param("spill", id="spill"), + ], +) +@pytest.mark.parametrize( + "frame_op", + [ + pytest.param("call", id="call_chain"), + pytest.param("create", id="create_chain"), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_nested_failure_resets_to_tx_reservoir( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, + failure_mode: str, + frame_bodies: list[Bytecode], + spill_mode: str, + frame_op: str, +) -> None: + """ + Verify failure cascade refunds state-gas to the top reservoir. + + Each frame runs its parametrized body, then calls or CREATEs the + next frame, terminating with the failure mode. Every level fails + so the cascade reaches the top. + + Axes: + - `failure_mode`: REVERT vs HALT (top-level gas_left semantics + differ; state-gas refund must agree per the updated EIP). + - `spill_mode`: `no_spill` sizes the reservoir to cover all + state-gas charges. `spill` shrinks it so charges drain into + gas_left, exercising the spill-refund-on-halt rule. + - `frame_op`: `call` chains via CALL (no per-frame pre-charge). + `create` chains via CREATE (each level pre-charges + `STATE_BYTES_PER_NEW_ACCOUNT × cpsb`, exercising + credit-on-failure interleaved with the spill). + + Per the updated EIP, every state-gas charge — body charges, + spilled portions, and CREATE pre-charges — is refunded to the + top-level reservoir on either revert or halt. So the user pays + `tx_gas - max(reservoir, total_state_charges)` on halt and only + regular charges + intrinsic on revert, regardless of axes. + + Two assertions cross-check the gas accounting: + - `cumulative_gas_used` (receipt) pins `tx.gas - gas_left - + state_gas_left`, catching bugs in the leftover split. + - `header.gas_used` pins `max(block_regular, block_state)` via + the block accumulators. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + new_account_state_gas = fork.gas_costs().NEW_ACCOUNT + intrinsic_cost = fork.transaction_intrinsic_cost_calculator()() + + body_state_total = sum(b.state_cost(fork) for b in frame_bodies) + n_creates = (len(frame_bodies) - 1) if frame_op == "create" else 0 + total_state_charges = body_state_total + n_creates * new_account_state_gas + + if spill_mode == "no_spill": + # Reservoir comfortably covers all state-gas charges. + reservoir = max( + total_state_charges + sstore_state_gas, sstore_state_gas + ) + else: + # Reservoir is small; charges spill into gas_left. + reservoir = sstore_state_gas + tx_gas = gas_limit_cap + reservoir + + terminator = Op.REVERT(0, 0) if failure_mode == "revert" else Op.INVALID + + if frame_op == "call": + top, frame_codes = _build_call_chain(pre, frame_bodies, terminator) + else: + top, frame_codes = _build_create_chain(pre, frame_bodies, terminator) + + sum_regular = sum(code.regular_cost(fork) for code in frame_codes) + spill = max(0, total_state_charges - reservoir) + if failure_mode == "halt": + # Policy A (updated EIP): all state-gas — body charges, spilled + # portions, and CREATE pre-charges (returned via credit) — folds + # into state_gas_left at tx end. gas_left is zeroed by halt. + state_gas_at_end = max(reservoir, total_state_charges) + expected_cumulative = tx_gas - state_gas_at_end + # Header: block_regular = gas_limit_cap - spill (spilled + # state-gas drained gas_left but is no longer reclassified to + # regular under Policy A); block_state ≈ 0 for plain CALLs. + expected_header_gas_used = gas_limit_cap - spill + elif failure_mode == "revert": + # Revert preserves gas_left; full state-gas refund. + # User pays only regular costs + intrinsic. + expected_cumulative = intrinsic_cost + sum_regular + # Header reflects the regular-vs-state attribution directly: + # state_gas_used is zeroed by the tx error handler, so only + # regular gas usage shows up. + expected_header_gas_used = intrinsic_cost + sum_regular + else: + raise ValueError("Invariant, unreachable code.") + + tx = Transaction( + to=top, + gas_limit=tx_gas, + sender=pre.fund_eoa(), + expected_receipt=TransactionReceipt( + cumulative_gas_used=expected_cumulative, + ), + ) + + blockchain_test( + pre=pre, + blocks=[ + Block( + txs=[tx], + header_verify=Header(gas_used=expected_header_gas_used), + ) + ], + post={}, + ) + + +@pytest.mark.parametrize( + "refund_scenario", + [ + pytest.param("sstore_restoration", id="sstore_restoration"), + pytest.param("create_collision", id="create_collision"), + pytest.param("create_initcode_revert", id="create_initcode_revert"), + pytest.param("auth_existing_leaf", id="auth_existing_leaf"), + ], +) +@pytest.mark.parametrize( + "depth", + [1, 3, 10], +) +@pytest.mark.parametrize( + "consume_at", + [ + pytest.param("deepest", id="consume_deepest"), + pytest.param("top", id="consume_top"), + ], +) +@pytest.mark.pre_alloc_mutable +@pytest.mark.valid_from("EIP8037") +def test_nested_state_gas_refund_consumed_at_depth( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + refund_scenario: str, + depth: int, + consume_at: str, +) -> None: + """ + Verify state-gas refund credits propagate through a CALL chain so + they can be consumed at any depth. + + Refund sources: SSTORE `0→1→0`, CREATE collision, CREATE initcode + revert (all credit deepest's reservoir), and a SetCode auth on an + `existing_leaf` authority (credits the top reservoir at message + entry). + + A probe CALL sized one short of covering an SSTORE on full spill + runs either at the refund-source frame or back at the top after + the chain returns; it succeeds only when its frame holds enough + reservoir, so a missing or mis-propagated credit OOGs it. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + + is_auth_scenario = refund_scenario == "auth_existing_leaf" + + probe_address = pre.deploy_contract(code=Op.SSTORE(0, 1)) + probe_gas = Op.SSTORE(0, 1).gas_cost(fork) - 1 + consumer_storage = Storage() + consume_op = Op.SSTORE( + consumer_storage.store_next(1, "probe_must_succeed"), + Op.CALL(gas=probe_gas, address=probe_address), + ) + + if refund_scenario == "sstore_restoration": + refund_body = Op.SSTORE(0, 1) + Op.SSTORE.with_metadata( + key_warm=True, + original_value=0, + current_value=1, + new_value=0, + )(0, 0) + elif refund_scenario == "create_collision": + refund_body = Op.POP(Op.CREATE(0, 0, 0)) + elif refund_scenario == "create_initcode_revert": + revert_initcode = bytes(Op.REVERT(0, 0)) + refund_body = Om.MSTORE(revert_initcode, 0) + Op.POP( + Op.CREATE(0, 0, len(revert_initcode)) + ) + elif is_auth_scenario: + refund_body = Bytecode() + else: + raise ValueError(f"unknown refund_scenario: {refund_scenario!r}") + + deepest_body = refund_body + if consume_at == "deepest": + deepest_body = deepest_body + consume_op + elif consume_at != "top": + raise ValueError(f"unknown consume_at: {consume_at!r}") + + deepest_address = pre.deploy_contract(code=deepest_body + Op.STOP) + if refund_scenario == "create_collision": + # Deepest is reached via plain CALL, so the CREATE's sender is + # deepest itself with nonce 1 (fresh `deploy_contract` default). + collision_target = compute_create_address( + address=deepest_address, nonce=1 + ) + pre.deploy_contract(code=Op.STOP, address=collision_target) + + chain_inner = deepest_address + for _ in range(depth): + chain_inner = pre.deploy_contract( + code=Op.POP(Op.CALL(gas=Op.GAS, address=chain_inner)) + Op.STOP + ) + + top_body = Op.POP(Op.CALL(gas=Op.GAS, address=chain_inner)) + if consume_at == "top": + top_body = top_body + consume_op + top = pre.deploy_contract(code=top_body + Op.STOP) + + authorization_list = None + extra_post: dict = {} + if is_auth_scenario: + signer = pre.fund_eoa() + auth_target = pre.deploy_contract(code=Op.STOP) + authorization_list = [ + AuthorizationTuple( + address=auth_target, + nonce=0, + signer=signer, + ), + ] + extra_post[signer] = Account( + nonce=1, + code=Spec7702.delegation_designation(auth_target), + ) + + tx = Transaction( + to=top, + gas_limit=gas_limit_cap, + authorization_list=authorization_list, + sender=pre.fund_eoa(), + ) + + consumer_address = deepest_address if consume_at == "deepest" else top + post: dict = {consumer_address: Account(storage=consumer_storage)} + post.update(extra_post) + state_test(pre=pre, post=post, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_top_level_opcode_oog_before_frame_end_does_not_refund_state_gas( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify an opcode OOG before frame-end settlement does not refund + unsettled state gas. + + The transaction has enough gas for the SSTORE and all preceding + regular work, but is one gas short of the MCOPY regular cost. The + frame halts before frame-end settlement runs, so the earlier SSTORE + never contributes execution state gas to refund. + """ + intrinsic_cost = fork.transaction_intrinsic_cost_calculator()() + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + + code = Op.SSTORE(0, 1) + Op.MCOPY( + 0x1000, + 0, + 1, + old_memory_size=0, + new_memory_size=0x1001, + data_size=1, + ) + contract = pre.deploy_contract(code=code) + + # One gas short of the regular-gas portion of successful execution. + tx_gas = intrinsic_cost + code.gas_cost(fork) - sstore_state_gas - 1 + + tx = Transaction( + to=contract, + gas_limit=tx_gas, + sender=pre.fund_eoa(), + expected_receipt=TransactionReceipt( + cumulative_gas_used=tx_gas, + ), + ) + + state_test(pre=pre, post={contract: Account(storage={})}, tx=tx) + + +@pytest.mark.parametrize( + "num_access_list_entries", + [ + pytest.param(1, id="one_entry"), + pytest.param(10, id="ten_entries"), + ], +) +@pytest.mark.parametrize( + "slots_per_entry", + [ + pytest.param(0, id="addresses_only"), + pytest.param(3, id="with_storage_keys"), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_access_list_gas_is_regular_not_state( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, + num_access_list_entries: int, + slots_per_entry: int, +) -> None: + """Verify EIP-2930 access list gas counts as regular, not state.""" + contract = pre.deploy_contract(code=Op.STOP) + + access_list = [] + for _ in range(num_access_list_entries): + target = pre.fund_eoa(amount=0) + storage_keys = list(range(slots_per_entry)) + access_list.append( + AccessList(address=target, storage_keys=storage_keys) + ) + + intrinsic_calc = fork.transaction_intrinsic_cost_calculator() + gas_needed = intrinsic_calc(access_list=access_list) + + tx = Transaction( + to=contract, + gas_limit=gas_needed, + sender=pre.fund_eoa(), + access_list=access_list, + ) + + blockchain_test( + pre=pre, + blocks=[ + Block( + txs=[tx], + header_verify=Header(gas_used=gas_needed), + ), + ], + post={}, + ) + + +@pytest.mark.valid_from("EIP8037") +def test_access_list_warm_savings_stay_regular( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """Verify access-list warm savings stay in regular gas.""" + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + + contract = pre.deploy_contract( + code=Op.SSTORE(0, Op.SLOAD(0)), + storage={0: 1}, + ) + + access_list = [AccessList(address=contract, storage_keys=[0])] + + intrinsic_calc = fork.transaction_intrinsic_cost_calculator() + intrinsic_gas = intrinsic_calc(access_list=access_list) + + contract_code = Op.SSTORE.with_metadata( + key_warm=True, + original_value=1, + current_value=1, + new_value=1, + )(0, Op.SLOAD.with_metadata(key_warm=True)(0)) + evm_gas = contract_code.gas_cost(fork) + + expected_gas_used = intrinsic_gas + evm_gas + gas_limit = gas_limit_cap + sstore_state_gas + + tx = Transaction( + to=contract, + gas_limit=gas_limit, + sender=pre.fund_eoa(), + access_list=access_list, + ) + + blockchain_test( + pre=pre, + blocks=[ + Block( + txs=[tx], + header_verify=Header(gas_used=expected_gas_used), + ), + ], + post={contract: Account(storage={0: 1})}, + ) + + +@pytest.mark.valid_from("EIP8037") +def test_subcall_revert_does_not_leak_grandchild_storage_clear_credit( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify a grandchild's storage-clear reservoir credit cannot leak + past a reverting parent into the top frame's reservoir. + + Three-frame DELEGATECALL chain so all SSTOREs target the top + contract's storage: + + - top: SSTOREs slots[0..4]=1, DELEGATECALLs `mid`, then + SSTOREs slots[10..14]=1. + - mid: DELEGATECALLs `inner`, then REVERTs. + - inner: SSTOREs slots[0..4]=0, clearing what top set. + + Inner's frame-end sees byte_delta=-160 against its own snapshot + (slots non-zero at frame entry, zero at tx start, zero at exit) + and credits its reservoir by 5 * sstore_state_gas. On mid's + revert that storage clear is rolled back, but the credit lives + on inside mid's reservoir from the prior + `incorporate_child_on_success`. The credit must not propagate + out of mid via `incorporate_child_on_error`, because the + underlying state transition no longer exists. + + The reservoir is sized to the legitimate state cost + (10 * sstore_state_gas: 5 setup writes + 5 phantom writes). Top + drains the reservoir at frame-end and the receipt charges the + full legitimate cost. If the credit leaks, an extra + 5 * sstore_state_gas remains in `state_gas_reservoir` at tx end + and the receipt formula `tx.gas - gas_left - + state_gas_reservoir` would charge the sender 5 * sstore_state_gas + less. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + intrinsic_cost = fork.transaction_intrinsic_cost_calculator()() + + num_slots = 5 + phantom_base = 10 + + # `inner` clears slots [0..num_slots-1] in the caller's storage + # context, which under the DELEGATECALL chain is `top`. The + # slots are warm because top accessed them during setup and + # `accessed_storage_keys` propagated through the DELEGATECALLs. + inner_code = Bytecode() + for i in range(num_slots): + inner_code += Op.SSTORE.with_metadata( + key_warm=True, + original_value=0, + current_value=1, + new_value=0, + )(i, 0) + inner = pre.deploy_contract(code=inner_code) + + mid_code = Op.POP(Op.DELEGATECALL(gas=Op.GAS, address=inner)) + Op.REVERT( + 0, 0 + ) + mid = pre.deploy_contract(code=mid_code) + + setup_code = Bytecode() + for i in range(num_slots): + setup_code += Op.SSTORE(i, 1) + delegatecall_step = Op.POP(Op.DELEGATECALL(gas=Op.GAS, address=mid)) + phantom_code = Bytecode() + for i in range(num_slots): + phantom_code += Op.SSTORE(phantom_base + i, 1) + top_code = setup_code + delegatecall_step + phantom_code + top = pre.deploy_contract(code=top_code) + + # Reservoir sized to the legitimate state cost only; any + # phantom credit surfaces as residual reservoir at tx end. + legit_state_cost = 2 * num_slots * sstore_state_gas + tx_gas = gas_limit_cap + legit_state_cost + + # `bytecode.gas_cost(fork)` sums each opcode's regular and state + # contributions. Setup/phantom SSTOREs predict +sstore_state_gas + # each; inner's clears predict 0 (the negative byte_delta is a + # frame-level effect, not per-opcode). The frame-end byte_delta + # at top is +320 (10 set slots persist, the inner clear is rolled + # back), so the predicted state total of 10 * sstore_state_gas + # matches the actual charge. + expected_cumulative = ( + intrinsic_cost + + top_code.gas_cost(fork) + + mid_code.gas_cost(fork) + + inner_code.gas_cost(fork) + ) + + tx = Transaction( + to=top, + gas_limit=tx_gas, + sender=pre.fund_eoa(), + expected_receipt=TransactionReceipt( + cumulative_gas_used=expected_cumulative, + ), + ) + expected_storage = dict.fromkeys(range(num_slots), 1) | { + phantom_base + i: 1 for i in range(num_slots) + } + + state_test( + pre=pre, + post={top: Account(storage=expected_storage)}, + tx=tx, + ) + + +@pytest.mark.parametrize( + "intermediate_depth", + [ + pytest.param(0, id="direct"), + pytest.param(1, id="depth_1"), + pytest.param(3, id="depth_3"), + pytest.param(10, id="depth_10"), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_revert_discards_descendant_storage_clear_credit_through_depth( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + intermediate_depth: int, +) -> None: + """ + A reverted ancestor must discard a clear-credit regardless of + how many successful frames sit between the X→0 source and the + revert. + + top → reverter (REVERT) + → pass_1 → … → pass_k → inner (X→0) + + Each pass frame returns successfully, so the inner credit walks + up through `incorporate_child_on_success` at every layer before + landing in the reverter, where it must be dropped on + `incorporate_child_on_error`. The receipt invariant holds for + every `k`. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + intrinsic_cost = fork.transaction_intrinsic_cost_calculator()() + + num_slots = 5 + phantom_base = 10 + + # Slots are warm at inner: top's setup populates the access list + # and DELEGATECALL preserves it down the chain. + inner_code = Bytecode() + for i in range(num_slots): + inner_code += Op.SSTORE.with_metadata( + key_warm=True, + original_value=0, + current_value=1, + new_value=0, + )(i, 0) + inner = pre.deploy_contract(code=inner_code) + + # Build the pass-through chain bottom-up so each frame can encode + # the next address. Each pass_i DELEGATECALLs into the next frame + # and STOPs successfully, propagating inner's credit upward. + pass_codes = [] + next_addr = inner + for _ in range(intermediate_depth): + pass_code = ( + Op.POP(Op.DELEGATECALL(gas=Op.GAS, address=next_addr)) + Op.STOP + ) + pass_codes.append(pass_code) + next_addr = pre.deploy_contract(code=pass_code) + + # Reverter sits between top and the chain: enters, then REVERTs. + reverter_code = Op.POP(Op.DELEGATECALL(gas=Op.GAS, address=next_addr)) + ( + Op.REVERT(0, 0) + ) + reverter = pre.deploy_contract(code=reverter_code) + + setup_code = Bytecode() + for i in range(num_slots): + setup_code += Op.SSTORE(i, 1) + delegatecall_step = Op.POP(Op.DELEGATECALL(gas=Op.GAS, address=reverter)) + phantom_code = Bytecode() + for i in range(num_slots): + phantom_code += Op.SSTORE(phantom_base + i, 1) + top_code = setup_code + delegatecall_step + phantom_code + top = pre.deploy_contract(code=top_code) + + legit_state_cost = 2 * num_slots * sstore_state_gas + tx_gas = gas_limit_cap + legit_state_cost + + expected_cumulative = ( + intrinsic_cost + + top_code.gas_cost(fork) + + reverter_code.gas_cost(fork) + + sum(c.gas_cost(fork) for c in pass_codes) + + inner_code.gas_cost(fork) + ) + + tx = Transaction( + to=top, + gas_limit=tx_gas, + sender=pre.fund_eoa(), + expected_receipt=TransactionReceipt( + cumulative_gas_used=expected_cumulative, + ), + ) + expected_storage = dict.fromkeys(range(num_slots), 1) | { + phantom_base + i: 1 for i in range(num_slots) + } + + state_test( + pre=pre, + post={top: Account(storage=expected_storage)}, + tx=tx, + ) + + +@pytest.mark.parametrize( + "spill_mode", + [ + pytest.param("no_spill", id="no_spill"), + pytest.param("spill", id="spill"), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_subcall_set_clear_revert_pays_no_state_gas( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + spill_mode: str, +) -> None: + """ + A child frame doing SSTORE 0 to x to 0 then REVERT must bill the + sender only intrinsic + regular costs. + + Both SSTOREs roll back with the REVERT, so the matching + state-gas charge and refund cancel cleanly. The receipt's + `cumulative_gas_used` equals the regular baseline; a leftover + `sstore_state_gas` would surface a double-charge at the failure + boundary. + + `spill_mode` toggles whether the set draws from the reservoir + directly (`no_spill`, reservoir sized to `sstore_state_gas`) or + spills into `gas_left` (`spill`, reservoir = 0). + """ + intrinsic_cost = fork.transaction_intrinsic_cost_calculator()() + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + + set_op = Op.SSTORE.with_metadata( + key_warm=False, + original_value=0, + current_value=0, + new_value=1, + )(0, 1) + clear_op = Op.SSTORE.with_metadata( + key_warm=True, + original_value=0, + current_value=1, + new_value=0, + )(0, 0) + inner_code = set_op + clear_op + Op.REVERT(0, 0) + inner = pre.deploy_contract(code=inner_code) + + top_code = Op.POP(Op.CALL(gas=Op.GAS, address=inner)) + Op.STOP + top = pre.deploy_contract(code=top_code) + + reservoir = 0 if spill_mode == "spill" else sstore_state_gas + tx_gas = gas_limit_cap + reservoir + + expected_cumulative = ( + intrinsic_cost + + top_code.regular_cost(fork) + + inner_code.regular_cost(fork) + ) + + tx = Transaction( + to=top, + gas_limit=tx_gas, + sender=pre.fund_eoa(), + expected_receipt=TransactionReceipt( + cumulative_gas_used=expected_cumulative, + ), + ) + state_test( + pre=pre, + post={top: Account(), inner: Account(storage={0: 0})}, + tx=tx, + blockchain_test_header_verify=Header(gas_used=expected_cumulative), + ) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_selfdestruct.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_selfdestruct.py new file mode 100644 index 00000000000..88df5dbefaa --- /dev/null +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_selfdestruct.py @@ -0,0 +1,840 @@ +""" +Test SELFDESTRUCT state gas charging under EIP-8037. + +SELFDESTRUCT charges new-account state gas of state gas when the +beneficiary account does not exist AND the originating contract has +a nonzero balance. No state gas is charged when the beneficiary +already exists or the originator has zero balance. + +Tests for [EIP-8037: State Creation Gas Cost Increase] +(https://eips.ethereum.org/EIPS/eip-8037). +""" + +import pytest +from execution_testing import ( + Account, + Alloc, + Block, + BlockchainTestFiller, + Bytecode, + Environment, + Fork, + Header, + Initcode, + Op, + StateTestFiller, + Storage, + Transaction, + compute_create_address, +) + +from .spec import init_code_at_high_bytes, ref_spec_8037 + +REFERENCE_SPEC_GIT_PATH = ref_spec_8037.git_path +REFERENCE_SPEC_VERSION = ref_spec_8037.version + + +@pytest.mark.valid_from("EIP8037") +def test_selfdestruct_new_beneficiary_charges_state_gas( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test SELFDESTRUCT to non-existent beneficiary charges state gas. + + When the beneficiary does not exist and the originator has nonzero + balance, SELFDESTRUCT charges new-account state gas for + creating the new beneficiary account. + """ + gas_costs = fork.gas_costs() + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment() + new_account_state_gas = gas_costs.NEW_ACCOUNT + + # Non-existent beneficiary + beneficiary = 0xDEAD + + contract = pre.deploy_contract( + code=Op.SELFDESTRUCT(beneficiary), + balance=1, + ) + + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap + new_account_state_gas, + sender=pre.fund_eoa(), + ) + + state_test(env=env, pre=pre, post={}, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_selfdestruct_existing_beneficiary_no_state_gas( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test SELFDESTRUCT to existing beneficiary charges no state gas. + + When the beneficiary already exists, no new account is created + and no state gas is charged. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + beneficiary = pre.fund_eoa(amount=0) + + contract = pre.deploy_contract( + code=Op.SELFDESTRUCT(beneficiary), + balance=1, + ) + + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap, + sender=pre.fund_eoa(), + ) + + state_test(pre=pre, post={}, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_selfdestruct_zero_balance_no_state_gas( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test SELFDESTRUCT with zero balance charges no state gas. + + When the originating contract has zero balance, no value is + transferred, so no new account is created even if the beneficiary + does not exist. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + # Non-existent beneficiary but contract has zero balance + beneficiary = 0xDEAD + + contract = pre.deploy_contract( + code=Op.SELFDESTRUCT(beneficiary), + balance=0, + ) + + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap, + sender=pre.fund_eoa(), + ) + + state_test(pre=pre, post={}, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_selfdestruct_state_gas_from_reservoir( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test SELFDESTRUCT state gas drawn from reservoir. + + Provide gas above TX_MAX_GAS_LIMIT so the new account state gas + for the non-existent beneficiary is drawn from the reservoir. + """ + gas_costs = fork.gas_costs() + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment() + new_account_state_gas = gas_costs.NEW_ACCOUNT + + beneficiary = 0xDEAD + + contract = pre.deploy_contract( + code=Op.SELFDESTRUCT(beneficiary), + balance=1, + ) + + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap + new_account_state_gas, + sender=pre.fund_eoa(), + ) + + state_test(env=env, pre=pre, post={}, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_selfdestruct_to_self_in_create_tx( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test SELFDESTRUCT to self in the transaction the contract was created. + + When a contract created in the current transaction SELFDESTRUCTs + to itself, the balance is burned and the account is deleted. No + new account state gas is charged since the beneficiary already + exists. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment() + + inner_code = Op.SELFDESTRUCT(Op.ADDRESS) + + contract = pre.deploy_contract( + code=( + Op.MSTORE( + 0, + int.from_bytes(bytes(inner_code), "big") + << (256 - 8 * len(inner_code)), + ) + + Op.POP(Op.CREATE(1, 0, len(inner_code))) + ), + balance=1, + ) + + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap * 2, + sender=pre.fund_eoa(), + ) + + state_test(env=env, pre=pre, post={}, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_selfdestruct_new_beneficiary_header_gas_used( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify block gas accounting for SELFDESTRUCT to new beneficiary. + + A contract with nonzero balance SELFDESTRUCTs to a non-existent + beneficiary, charging GAS_NEW_ACCOUNT state gas. The block must + be accepted with correct 2D gas accounting in the header. + """ + gas_costs = fork.gas_costs() + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + new_account_state_gas = gas_costs.NEW_ACCOUNT + + beneficiary = pre.fund_eoa(amount=0) + + storage = Storage() + inner = pre.deploy_contract( + code=Op.SELFDESTRUCT(beneficiary), + balance=1, + ) + caller = pre.deploy_contract( + code=( + Op.CALL(gas=100_000, address=inner) + + Op.SSTORE(storage.store_next(1, "completed"), 1) + ), + ) + + tx = Transaction( + to=caller, + gas_limit=gas_limit_cap + new_account_state_gas, + sender=pre.fund_eoa(), + ) + + blockchain_test( + pre=pre, + blocks=[ + Block(txs=[tx]), + ], + post={caller: Account(storage=storage)}, + ) + + +@pytest.mark.parametrize( + "num_slots", + [ + pytest.param(0, id="no_storage"), + pytest.param(1, id="one_slot"), + pytest.param(5, id="five_slots"), + ], +) +@pytest.mark.with_all_create_opcodes() +@pytest.mark.valid_from("EIP8037") +def test_create_selfdestruct_no_refund_account_and_storage( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, + create_opcode: Op, + num_slots: int, +) -> None: + """Verify same tx CREATE+SELFDESTRUCT does not refund state gas.""" + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + new_account_state_gas = fork.gas_costs().NEW_ACCOUNT + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() + + init_code = Bytecode() + for i in range(num_slots): + init_code += Op.SSTORE.with_metadata( + key_warm=False, + original_value=0, + current_value=0, + new_value=1, + )(i, 1) + init_code += Op.SELFDESTRUCT.with_metadata(address_warm=True)(Op.ADDRESS) + mstore_value, size = init_code_at_high_bytes(init_code) + + # Metadata so `.gas_cost(fork)` matches runtime charges. + mstore = Op.MSTORE.with_metadata(new_memory_size=32, old_memory_size=0)( + 0, mstore_value + ) + create_metadata = create_opcode.with_metadata(init_code_size=size) + create_call = ( + create_metadata(value=0, offset=0, size=size, salt=0) + if create_opcode == Op.CREATE2 + else create_metadata(value=0, offset=0, size=size) + ) + factory_code = mstore + Op.POP(create_call) + factory = pre.deploy_contract(code=factory_code) + + total_state_gas = new_account_state_gas + num_slots * sstore_state_gas + regular_used = ( + intrinsic_gas + + factory_code.gas_cost(fork) + + init_code.gas_cost(fork) + - total_state_gas + ) + expected_gas_used = max(regular_used, total_state_gas) + + tx = Transaction( + to=factory, + gas_limit=gas_limit_cap + total_state_gas, + sender=pre.fund_eoa(), + ) + + blockchain_test( + pre=pre, + blocks=[ + Block(txs=[tx], header_verify=Header(gas_used=expected_gas_used)), + ], + post={}, + ) + + +@pytest.mark.parametrize( + "beneficiary_type,code_size", + [ + pytest.param("self", 2, id="self_tiny"), + pytest.param("self", 100, id="self_medium"), + pytest.param("external", 100, id="external_medium"), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_create_selfdestruct_no_refund_code_deposit_state_gas( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, + code_size: int, + beneficiary_type: str, +) -> None: + """ + Verify same tx CREATE+SELFDESTRUCT does not refund code deposit + state gas. + """ + assert code_size >= 2 + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + new_account_state_gas = fork.gas_costs().NEW_ACCOUNT + code_deposit_state_gas = fork.code_deposit_state_gas(code_size=code_size) + + if beneficiary_type == "self": + selfdestruct = Op.SELFDESTRUCT(Op.ADDRESS) + else: + beneficiary = pre.deploy_contract(code=Op.STOP) + selfdestruct = Op.SELFDESTRUCT(beneficiary) + sd_len = len(bytes(selfdestruct)) + assert code_size >= sd_len + deployed = bytes(selfdestruct) + b"\x00" * (code_size - sd_len) + initcode = Initcode(deploy_code=deployed) + initcode_len = len(initcode) + + # Nest CREATE directly as the address argument to CALL so the + # deployed contract's address flows via the stack, avoiding a + # magic memory slot for address storage and an arbitrary gas + # budget. + factory_code = Op.CALLDATACOPY( + 0, + 0, + Op.CALLDATASIZE, + data_size=initcode_len, + new_memory_size=initcode_len, + ) + Op.POP( + Op.CALL( + gas=Op.GAS, + address=Op.CREATE( + value=0, + offset=0, + size=Op.CALLDATASIZE, + init_code_size=initcode_len, + ), + ) + ) + factory = pre.deploy_contract(code=factory_code) + created_address = compute_create_address(address=factory, nonce=1) + + total_state_gas = new_account_state_gas + code_deposit_state_gas + tx = Transaction( + to=factory, + data=bytes(initcode), + gas_limit=gas_limit_cap + total_state_gas, + sender=pre.fund_eoa(), + ) + + blockchain_test( + pre=pre, + blocks=[Block(txs=[tx])], + post={created_address: Account.NONEXISTENT}, + ) + + +@pytest.mark.valid_from("EIP8037") +def test_create_selfdestruct_code_deposit_no_refund_header_check( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify block header gas reflects the full account plus code-deposit + state-gas charge on a same-tx CREATE+SELFDESTRUCT. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + gas_costs = fork.gas_costs() + new_account_state_gas = gas_costs.NEW_ACCOUNT + + selfdestruct = Op.SELFDESTRUCT(Op.ADDRESS) + sd_len = len(bytes(selfdestruct)) + code_size = 256 + assert code_size >= sd_len + deployed = bytes(selfdestruct) + b"\x00" * (code_size - sd_len) + initcode = Initcode(deploy_code=deployed) + initcode_len = len(initcode) + code_deposit_state_gas = fork.code_deposit_state_gas(code_size=code_size) + + factory_code = Op.CALLDATACOPY( + 0, + 0, + Op.CALLDATASIZE, + data_size=initcode_len, + new_memory_size=initcode_len, + ) + Op.POP( + Op.CALL( + gas=Op.GAS, + address=Op.CREATE( + value=0, + offset=0, + size=Op.CALLDATASIZE, + init_code_size=initcode_len, + ), + ) + ) + factory = pre.deploy_contract(code=factory_code) + created_address = compute_create_address(address=factory, nonce=1) + + total_state_gas = new_account_state_gas + code_deposit_state_gas + tx = Transaction( + to=factory, + data=bytes(initcode), + gas_limit=gas_limit_cap + total_state_gas, + sender=pre.fund_eoa(), + ) + + baseline_block_regular = 0x94C8 + expected_gas_used = max(baseline_block_regular, total_state_gas) + + blockchain_test( + pre=pre, + blocks=[ + Block( + txs=[tx], + header_verify=Header(gas_used=expected_gas_used), + ), + ], + post={created_address: Account.NONEXISTENT}, + ) + + +@pytest.mark.valid_from("EIP8037") +def test_create_selfdestruct_sstore_restoration_refund( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify SSTORE restoration still refunds its slot state gas when + the surrounding contract SELFDESTRUCTs. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + new_account_state_gas = fork.gas_costs().NEW_ACCOUNT + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() + + init_code = ( + Op.SSTORE.with_metadata( + key_warm=False, + original_value=0, + current_value=0, + new_value=1, + )(0, 1) + + Op.SSTORE.with_metadata( + key_warm=True, + original_value=0, + current_value=1, + new_value=0, + )(0, 0) + + Op.SELFDESTRUCT.with_metadata(address_warm=True)(Op.ADDRESS) + ) + mstore_value, size = init_code_at_high_bytes(init_code) + + mstore = Op.MSTORE.with_metadata(new_memory_size=32, old_memory_size=0)( + 0, mstore_value + ) + create_call = Op.CREATE.with_metadata(init_code_size=size)(0, 0, size) + factory_code = mstore + Op.POP(create_call) + factory = pre.deploy_contract(code=factory_code) + + state_used = new_account_state_gas + regular_used = ( + intrinsic_gas + + factory_code.gas_cost(fork) + + init_code.gas_cost(fork) + - new_account_state_gas + - sstore_state_gas + ) + expected_gas_used = max(regular_used, state_used) + + tx = Transaction( + to=factory, + gas_limit=gas_limit_cap + new_account_state_gas + sstore_state_gas, + sender=pre.fund_eoa(), + ) + + blockchain_test( + pre=pre, + blocks=[ + Block(txs=[tx], header_verify=Header(gas_used=expected_gas_used)), + ], + post={}, + ) + + +@pytest.mark.valid_from("EIP8037") +def test_selfdestruct_pre_existing_account_no_refund( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify SELFDESTRUCT of a pre-existing account earns no refund. + + The same-tx-create guard (`address in tx_state.created_accounts`) + is load-bearing: without it, destroying any account would leak + state gas back into the reservoir. A contract deployed in `pre` + is destroyed by the tx; `accounts_to_delete` contains it but + `created_accounts` does not, so no refund is applied. The block + header `gas_used` reflects the full regular-gas tx cost (no + state-gas refund offset). + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() + + # Victim deployed in `pre` (NOT same-tx-created). SELFDESTRUCTs + # to self so no new-account state gas is charged to the tx. + victim_code = Op.SELFDESTRUCT.with_metadata(address_warm=True)(Op.ADDRESS) + victim = pre.deploy_contract(code=victim_code) + + caller_code = Op.POP(Op.CALL(gas=Op.GAS, address=victim)) + caller = pre.deploy_contract(code=caller_code) + + # No refund offset: both caller_code and victim_code are pure + # regular gas (SELFDESTRUCT to self, no value-to-new-account). + tx_regular = ( + intrinsic_gas + caller_code.gas_cost(fork) + victim_code.gas_cost(fork) + ) + + tx = Transaction( + to=caller, + gas_limit=gas_limit_cap, + sender=pre.fund_eoa(), + ) + + # Per EIP-6780, SELFDESTRUCT on a not-same-tx-created account + # does not delete it — the account still exists after the tx. + blockchain_test( + pre=pre, + blocks=[Block(txs=[tx], header_verify=Header(gas_used=tx_regular))], + post={victim: Account(code=victim_code)}, + ) + + +@pytest.mark.parametrize( + "num_hops", + [ + pytest.param(1, id="single_hop"), + pytest.param(2, id="two_hops"), + ], +) +@pytest.mark.with_all_call_opcodes( + selector=lambda call_opcode: call_opcode in (Op.DELEGATECALL, Op.CALLCODE) +) +@pytest.mark.valid_from("EIP8037") +def test_selfdestruct_via_delegatecall_chain_no_refund( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, + num_hops: int, + call_opcode: Op, +) -> None: + """ + Verify SELFDESTRUCT in a nested DELEGATECALL/CALLCODE frame below + a same-tx-created contract does not refund state gas. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + new_account_state_gas = fork.gas_costs().NEW_ACCOUNT + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() + + # Bottom of the chain does the SELFDESTRUCT; intermediate helpers + # just delegate further down. Track each frame's bytecode so we + # can sum its regular gas into `expected_gas_used` below. + sd_code = Op.SELFDESTRUCT.with_metadata(address_warm=True)(Op.ADDRESS) + chain_regular_gas = sd_code.gas_cost(fork) + delegate_target = pre.deploy_contract(code=sd_code) + for _ in range(num_hops - 1): + hop_code = ( + Op.POP( + call_opcode.with_metadata(address_warm=False)( + gas=Op.GAS, address=delegate_target + ) + ) + + Op.STOP + ) + chain_regular_gas += hop_code.gas_cost(fork) + delegate_target = pre.deploy_contract(code=hop_code) + + # A's deployed runtime: one delegation into the top of the chain. + deployed_code = ( + Op.POP( + call_opcode.with_metadata(address_warm=False)( + gas=Op.GAS, address=delegate_target + ) + ) + + Op.STOP + ) + deployed = bytes(deployed_code) + code_deposit_state_gas = fork.code_deposit_state_gas( + code_size=len(deployed) + ) + initcode = Initcode(deploy_code=deployed) + initcode_len = len(initcode) + + # Slots 0 and 1 guard against a vacuously-NONEXISTENT A: slot 0 + # fails if CREATE silently returned 0, slot 1 fails if the factory + # OOGed before completing the nested CALL. TSTORE caches the + # CREATE return so both can reuse it. + factory_storage = Storage() + factory_code = ( + Op.CALLDATACOPY( + 0, + 0, + Op.CALLDATASIZE, + data_size=initcode_len, + new_memory_size=initcode_len, + ) + + Op.TSTORE( + 0, + Op.CREATE.with_metadata(init_code_size=initcode_len)( + value=0, + offset=0, + size=Op.CALLDATASIZE, + ), + ) + + Op.SSTORE.with_metadata( + key_warm=False, + original_value=0, + current_value=0, + new_value=1, + )( + factory_storage.store_next(1, "create_returned_nonzero"), + Op.ISZERO(Op.ISZERO(Op.TLOAD(0))), + ) + + Op.SSTORE.with_metadata( + key_warm=False, + original_value=0, + current_value=0, + new_value=1, + )( + factory_storage.store_next(1, "call_returned_success"), + Op.CALL.with_metadata(address_warm=True)( + gas=Op.GAS, address=Op.TLOAD(0) + ), + ) + ) + factory = pre.deploy_contract(code=factory_code) + created_address = compute_create_address(address=factory, nonce=1) + + total_state_gas = ( + new_account_state_gas + code_deposit_state_gas + 2 * sstore_state_gas + ) + regular_used = ( + intrinsic_gas + + factory_code.gas_cost(fork) + + initcode.gas_cost(fork) + + deployed_code.gas_cost(fork) + + chain_regular_gas + - new_account_state_gas + - code_deposit_state_gas + - 2 * sstore_state_gas + ) + expected_gas_used = max(regular_used, total_state_gas) + + tx = Transaction( + to=factory, + data=bytes(initcode), + gas_limit=gas_limit_cap + total_state_gas, + sender=pre.fund_eoa(), + ) + + blockchain_test( + pre=pre, + blocks=[ + Block( + txs=[tx], + header_verify=Header(gas_used=expected_gas_used), + ) + ], + post={ + created_address: Account.NONEXISTENT, + factory: Account(storage=factory_storage), + }, + ) + + +@pytest.mark.valid_from("EIP8037") +def test_selfdestruct_new_beneficiary_no_regular_account_creation_cost( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify SELFDESTRUCT to a new beneficiary does not charge a + regular account-creation cost on top of state gas. + """ + gas_costs = fork.gas_costs() + new_account_state_gas = gas_costs.NEW_ACCOUNT + + beneficiary = pre.fund_eoa(amount=0) + + victim_code = Op.SELFDESTRUCT(beneficiary) + victim = pre.deploy_contract(code=victim_code, balance=1) + + # Tight budget: slack is less than the old pre-Amsterdam regular + # account-creation cost, so any extra regular draw would OOG. + intrinsic = fork.transaction_intrinsic_cost_calculator()() + tx = Transaction( + to=victim, + gas_limit=( + intrinsic + + victim_code.gas_cost(fork) + + new_account_state_gas + + 20_000 + ), + sender=pre.fund_eoa(), + ) + + state_test(pre=pre, post={beneficiary: Account(balance=1)}, tx=tx) + + +@pytest.mark.parametrize( + "tx_value,beneficiary_kind", + [ + pytest.param(0, "self", id="value0_to_self"), + pytest.param(0, "existing", id="value0_to_existing"), + pytest.param(0, "empty", id="value0_to_empty"), + pytest.param(1, "self", id="value1_to_self"), + pytest.param(1, "existing", id="value1_to_existing"), + pytest.param(1, "empty", id="value1_to_empty"), + ], +) +@pytest.mark.pre_alloc_mutable() +@pytest.mark.valid_from("EIP8037") +def test_create_tx_selfdestruct_initcode_state_gas( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, + tx_value: int, + beneficiary_kind: str, +) -> None: + """ + Verify a creation tx whose initcode SELFDESTRUCTs the new contract + still pays the intrinsic NEW_ACCOUNT state gas. + """ + new_account_state_gas = fork.gas_costs().NEW_ACCOUNT + intrinsic_calc = fork.transaction_intrinsic_cost_calculator() + + sender = pre.fund_eoa(amount=10**18) + contract_addr = compute_create_address(address=sender, nonce=0) + + if beneficiary_kind == "self": + beneficiary = contract_addr + elif beneficiary_kind == "existing": + beneficiary = pre.deploy_contract(code=Op.STOP) + else: + beneficiary = pre.fund_eoa(amount=0) + + # `current_target` is added to `accessed_addresses` at message + # entry, so SELFDESTRUCT to self skips the cold-access surcharge. + if beneficiary_kind == "self": + init_code = Op.SELFDESTRUCT.with_metadata(address_warm=True)( + beneficiary + ) + else: + init_code = Op.SELFDESTRUCT(beneficiary) + intrinsic_total = intrinsic_calc( + calldata=bytes(init_code), contract_creation=True + ) + intrinsic_regular = intrinsic_total - new_account_state_gas + + creates_new_beneficiary = beneficiary_kind == "empty" and tx_value > 0 + expected_state = new_account_state_gas + ( + new_account_state_gas if creates_new_beneficiary else 0 + ) + expected_regular = intrinsic_regular + init_code.regular_cost(fork) + expected_gas_used = max(expected_regular, expected_state) + + tx = Transaction( + to=None, + data=init_code, + gas_limit=intrinsic_total + 100_000 + expected_state, + sender=sender, + value=tx_value, + ) + + blockchain_test( + pre=pre, + blocks=[ + Block( + txs=[tx], + header_verify=Header(gas_used=expected_gas_used), + ), + ], + post={}, + ) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py new file mode 100644 index 00000000000..07a00196c24 --- /dev/null +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py @@ -0,0 +1,1463 @@ +""" +Test EIP-7702 SetCode authorization state gas under EIP-8037. + +Each authorization charges intrinsic state gas for the new account +plus auth base bytes, and intrinsic regular gas. When the authority +account already exists, the new-account state gas is refunded to the +state gas reservoir. + +Tests for [EIP-8037: State Creation Gas Cost Increase] +(https://eips.ethereum.org/EIPS/eip-8037). +""" + +import pytest +from execution_testing import ( + Account, + Alloc, + AuthorizationTuple, + Block, + BlockchainTestFiller, + Bytecode, + Environment, + Fork, + Header, + Op, + StateTestFiller, + Storage, + Transaction, + TransactionException, + TransactionReceipt, +) + +from tests.prague.eip7702_set_code_tx.spec import Spec as Spec7702 + +from .spec import ref_spec_8037 + +REFERENCE_SPEC_GIT_PATH = ref_spec_8037.git_path +REFERENCE_SPEC_VERSION = ref_spec_8037.version + + +@pytest.mark.parametrize( + "num_auths", + [ + pytest.param(1, id="single_auth"), + pytest.param(3, id="three_auths"), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_authorization_state_gas_scaling( + state_test: StateTestFiller, + pre: Alloc, + num_auths: int, + fork: Fork, +) -> None: + """ + Test authorization intrinsic state gas scales with count. + + Each authorization adds + (STATE_BYTES_PER_NEW_ACCOUNT + STATE_BYTES_PER_AUTH_BASE) * + cost_per_state_byte of intrinsic state gas. The transaction + should succeed with enough total gas. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment() + auth_state_gas = fork.transaction_intrinsic_state_gas( + authorization_count=1, + ) + + contract = pre.deploy_contract(code=Op.STOP) + + authorization_list = [] + for _ in range(num_auths): + signer = pre.fund_eoa() + authorization_list.append( + AuthorizationTuple( + address=contract, + nonce=1, + signer=signer, + ), + ) + + sender = pre.fund_eoa() + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap + auth_state_gas * num_auths, + authorization_list=authorization_list, + sender=sender, + ) + + state_test(env=env, pre=pre, post={}, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_existing_account_refund( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test authorization targeting existing account refunds state gas. + + When the authority account already exists, new-account state gas + is refunded to the state gas reservoir and subtracted from + intrinsic_state_gas. Only 23 * cost_per_state_byte is effectively + charged. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment() + + contract = pre.deploy_contract(code=Op.STOP) + + # Signer is an existing funded EOA (account_exists = True) + signer = pre.fund_eoa() + + authorization_list = [ + AuthorizationTuple( + address=contract, + nonce=0, + signer=signer, + ), + ] + + # Only need enough state gas for STATE_BYTES_PER_AUTH_BASE, not + # the full (STATE_BYTES_PER_NEW_ACCOUNT + STATE_BYTES_PER_AUTH_BASE), + # because existing account refunds STATE_BYTES_PER_NEW_ACCOUNT + sender = pre.fund_eoa() + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap, + authorization_list=authorization_list, + sender=sender, + ) + + state_test(env=env, pre=pre, post={}, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_mixed_new_and_existing_auths( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test mixed new and existing account authorizations. + + One authorization targets an existing account (gets refund), + another targets a new account (no refund). The total state gas + should reflect the mixed charges. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment() + full_auth_state_gas = fork.transaction_intrinsic_state_gas( + authorization_count=1, + ) + + contract = pre.deploy_contract(code=Op.STOP) + + # Existing account (gets new-account state gas refund) + existing_signer = pre.fund_eoa() + + # New account — fund_eoa creates it in pre-state, so we need + # an address that doesn't exist. Use fund_eoa with amount=0 + # Actually fund_eoa always creates the account. For a "new" + # authorization, we need the nonce to be wrong so it's treated + # as a new account entry, or we accept that both are existing. + # In practice, all signers from fund_eoa are existing accounts. + # The key difference is whether account_exists returns True. + # Since fund_eoa creates the account, both are existing. + # This test verifies both auths succeed with appropriate gas. + second_signer = pre.fund_eoa() + + authorization_list = [ + AuthorizationTuple( + address=contract, + nonce=0, + signer=existing_signer, + ), + AuthorizationTuple( + address=contract, + nonce=0, + signer=second_signer, + ), + ] + + # Both are existing accounts, so both get the new-account state gas refund + sender = pre.fund_eoa() + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap + full_auth_state_gas * 2, + authorization_list=authorization_list, + sender=sender, + ) + + state_test(env=env, pre=pre, post={}, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_authorization_with_sstore( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test SetCode authorization combined with SSTORE. + + A SetCode transaction authorizes delegation and then the called + contract performs an SSTORE. Both the authorization state gas and + the SSTORE state gas are charged. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment() + auth_state_gas = fork.transaction_intrinsic_state_gas( + authorization_count=1, + ) + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + + storage = Storage() + contract = pre.deploy_contract( + code=Op.SSTORE(storage.store_next(1), 1), + ) + + signer = pre.fund_eoa() + authorization_list = [ + AuthorizationTuple( + address=contract, + nonce=0, + signer=signer, + ), + ] + + sender = pre.fund_eoa() + tx = Transaction( + to=contract, + gas_limit=(gas_limit_cap + auth_state_gas + sstore_state_gas), + authorization_list=authorization_list, + sender=sender, + ) + + post = {contract: Account(storage=storage)} + state_test(env=env, pre=pre, post=post, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_existing_account_refund_enables_sstore( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test auth refund to reservoir enables subsequent state ops. + + When an authorization targets an existing account, the + new-account state gas refund goes to state_gas_reservoir. + This refunded gas should then be available for SSTORE state + gas in the execution phase. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment() + auth_state_gas = fork.transaction_intrinsic_state_gas( + authorization_count=1, + ) + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + + storage = Storage() + contract = pre.deploy_contract( + code=Op.SSTORE(storage.store_next(1), 1), + ) + + # Existing signer — gets new-account state gas refunded to reservoir + signer = pre.fund_eoa() + authorization_list = [ + AuthorizationTuple( + address=contract, + nonce=0, + signer=signer, + ), + ] + + # Provide enough for auth intrinsic state gas, but rely on the + # existing-account refund to cover the SSTORE state gas + sender = pre.fund_eoa() + tx = Transaction( + to=contract, + gas_limit=(gas_limit_cap + auth_state_gas + sstore_state_gas), + authorization_list=authorization_list, + sender=sender, + ) + + post = {contract: Account(storage=storage)} + state_test(env=env, pre=pre, post=post, tx=tx) + + +@pytest.mark.parametrize( + "signer_pre_state,authorize_to_null", + [ + pytest.param("nonexistent", False, id="nonexistent_authority"), + pytest.param("nonexistent", True, id="nonexistent_clear"), + pytest.param("existing_leaf", False, id="existing_leaf_empty_code"), + pytest.param("existing_leaf", True, id="existing_leaf_clear"), + pytest.param( + "existing_delegation", + False, + id="existing_delegation_overwrite", + ), + pytest.param( + "existing_delegation", + True, + id="existing_delegation_clear", + ), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_auth_refund_block_gas_accounting( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + signer_pre_state: str, + authorize_to_null: bool, +) -> None: + """ + Verify block + receipt gas accounting against per-authorization + state-gas refunds from `set_delegation`. + + Four signer pre-states span every refund branch: + + * `nonexistent` — no account leaf; no refund; + * `existing_leaf` — leaf, empty code; `NEW_ACCOUNT × CPSB` refilled; + * `existing_delegation` overwrite — leaf + delegation; full refill + (`NEW_ACCOUNT + AUTH_BASE`) as the 23 delegation bytes overwrite + in place; + * `existing_delegation` clear — `auth.address` = + `RESET_DELEGATION_ADDRESS`; same full refill, since the refill + keys off the *pre-state* code slot, not what we're writing. + + Verified via header `gas_used`, receipt `cumulative_gas_used`, and + the authority post-state (catches a silently-skipped auth). + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + intrinsic_state_gas = fork.transaction_intrinsic_state_gas( + authorization_count=1, + ) + total_intrinsic = fork.transaction_intrinsic_cost_calculator()( + authorization_list_or_count=1, + ) + intrinsic_regular = total_intrinsic - intrinsic_state_gas + new_account_refund = fork.gas_costs().REFUND_AUTH_PER_EXISTING_ACCOUNT + # Per-auth intrinsic state gas covers NEW_ACCOUNT + AUTH_BASE; the + # AUTH_BASE portion is what's left after stripping NEW_ACCOUNT. + auth_base_refund = intrinsic_state_gas - new_account_refund + + contract_old = pre.deploy_contract(code=Op.STOP) + contract_new = pre.deploy_contract(code=Op.STOP) + + # AUTH_BASE is refunded when no new delegation-indicator bytes are + # written: either the authority already has an indicator (overwrite + # in place / clear) or `auth.address` is zero (no indicator written). + if signer_pre_state == "nonexistent": + signer = pre.fund_eoa(amount=0) + pre_nonce = 0 + auth_refund = auth_base_refund if authorize_to_null else 0 + elif signer_pre_state == "existing_leaf": + signer = pre.fund_eoa() + pre_nonce = 0 + auth_refund = new_account_refund + ( + auth_base_refund if authorize_to_null else 0 + ) + elif signer_pre_state == "existing_delegation": + # `fund_eoa(delegation=...)` sets the authority's nonce to 1. + signer = pre.fund_eoa(delegation=contract_old) + pre_nonce = 1 + auth_refund = new_account_refund + auth_base_refund + else: + raise ValueError(f"unknown signer_pre_state: {signer_pre_state!r}") + + auth_target = ( + Spec7702.RESET_DELEGATION_ADDRESS + if authorize_to_null + else contract_new + ) + authorization_list = [ + AuthorizationTuple( + address=auth_target, + nonce=pre_nonce, + signer=signer, + ), + ] + + post_signer = Account( + nonce=pre_nonce + 1, + code=( + b"" + if authorize_to_null + else Spec7702.delegation_designation(auth_target) + ), + ) + header_gas_used = max( + intrinsic_regular, + intrinsic_state_gas - auth_refund, + ) + receipt_cumulative_gas_used = total_intrinsic - auth_refund + + tx = Transaction( + to=contract_new, + gas_limit=gas_limit_cap + intrinsic_state_gas, + authorization_list=authorization_list, + sender=pre.fund_eoa(), + expected_receipt=TransactionReceipt( + cumulative_gas_used=receipt_cumulative_gas_used, + ), + ) + + state_test( + pre=pre, + post={signer: post_signer}, + tx=tx, + blockchain_test_header_verify=Header(gas_used=header_gas_used), + ) + + +@pytest.mark.valid_from("EIP8037") +def test_invalid_nonce_auth_still_charges_intrinsic_state_gas( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test invalid-nonce authorization still charges intrinsic state gas. + + An authorization with a wrong nonce is skipped during processing, + but its intrinsic state gas (135 * cpsb) is still charged upfront + as part of the transaction's intrinsic gas. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment() + auth_state_gas = fork.transaction_intrinsic_state_gas( + authorization_count=1, + ) + + contract = pre.deploy_contract(code=Op.STOP) + + signer = pre.fund_eoa() + authorization_list = [ + AuthorizationTuple( + address=contract, + nonce=99, # Wrong nonce — auth will be skipped + signer=signer, + ), + ] + + sender = pre.fund_eoa() + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap + auth_state_gas, + authorization_list=authorization_list, + sender=sender, + ) + + state_test(env=env, pre=pre, post={}, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_invalid_chain_id_auth_still_charges_intrinsic_state_gas( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test invalid-chain-id authorization still charges intrinsic state gas. + + An authorization with a mismatched chain ID is skipped during + processing, but intrinsic state gas is still charged upfront. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment() + auth_state_gas = fork.transaction_intrinsic_state_gas( + authorization_count=1, + ) + + contract = pre.deploy_contract(code=Op.STOP) + + signer = pre.fund_eoa() + authorization_list = [ + AuthorizationTuple( + address=contract, + nonce=0, + chain_id=9999, # Wrong chain ID — auth will be skipped + signer=signer, + ), + ] + + sender = pre.fund_eoa() + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap + auth_state_gas, + authorization_list=authorization_list, + sender=sender, + ) + + state_test(env=env, pre=pre, post={}, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_self_sponsored_authorization( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test self-sponsored authorization where sender is also the signer. + + The sender authorizes delegation to a contract and is also the + authority. The intrinsic state gas for the authorization is still + charged. Since the sender account already exists, the + new-account state gas refund applies. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment() + auth_state_gas = fork.transaction_intrinsic_state_gas( + authorization_count=1, + ) + + storage = Storage() + contract = pre.deploy_contract( + code=Op.SSTORE(storage.store_next(1), 1), + ) + + # Sender is also the signer (self-sponsored) + sender = pre.fund_eoa() + authorization_list = [ + AuthorizationTuple( + address=contract, + nonce=0, + signer=sender, + ), + ] + + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap + auth_state_gas, + authorization_list=authorization_list, + sender=sender, + ) + + post = {contract: Account(storage=storage)} + state_test(env=env, pre=pre, post=post, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_duplicate_signer_authorizations( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test multiple authorizations from the same signer. + + When the same signer appears multiple times in the authorization + list, each authorization charges intrinsic state gas independently. + Only the last valid authorization takes effect, but all contribute + to intrinsic state gas. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment() + auth_state_gas = fork.transaction_intrinsic_state_gas( + authorization_count=1, + ) + + contract_a = pre.deploy_contract(code=Op.STOP) + contract_b = pre.deploy_contract(code=Op.STOP) + + # Same signer, two authorizations + signer = pre.fund_eoa() + authorization_list = [ + AuthorizationTuple( + address=contract_a, + nonce=0, + signer=signer, + ), + AuthorizationTuple( + address=contract_b, + nonce=0, + signer=signer, + ), + ] + + # Both auths charge intrinsic state gas (2x) + sender = pre.fund_eoa() + tx = Transaction( + to=contract_a, + gas_limit=gas_limit_cap + auth_state_gas * 2, + authorization_list=authorization_list, + sender=sender, + ) + + state_test(env=env, pre=pre, post={}, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_auth_with_calldata_and_access_list( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test authorization combined with calldata and access list. + + Intrinsic gas includes calldata cost, access list cost, and + authorization state gas. All components contribute to the total + intrinsic gas requirement. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment() + auth_state_gas = fork.transaction_intrinsic_state_gas( + authorization_count=1, + ) + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + + storage = Storage() + # Contract that reads calldata and stores it + contract = pre.deploy_contract( + code=(Op.SSTORE(storage.store_next(0x42), Op.CALLDATALOAD(0))), + ) + + signer = pre.fund_eoa() + authorization_list = [ + AuthorizationTuple( + address=contract, + nonce=0, + signer=signer, + ), + ] + + sender = pre.fund_eoa() + tx = Transaction( + to=contract, + gas_limit=(gas_limit_cap + auth_state_gas + sstore_state_gas), + data=b"\x00" * 31 + b"\x42", # Calldata adds to intrinsic gas + authorization_list=authorization_list, + sender=sender, + ) + + post = {contract: Account(storage=storage)} + state_test(env=env, pre=pre, post=post, tx=tx) + + +@pytest.mark.parametrize( + "num_valid,num_invalid", + [ + pytest.param(1, 1, id="one_valid_one_invalid"), + pytest.param(2, 1, id="two_valid_one_invalid"), + pytest.param(1, 2, id="one_valid_two_invalid"), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_mixed_valid_and_invalid_auths( + state_test: StateTestFiller, + pre: Alloc, + num_valid: int, + num_invalid: int, + fork: Fork, +) -> None: + """ + Test mixed valid and invalid authorizations state gas charging. + + Both valid and invalid authorizations charge intrinsic state gas. + Invalid auths (wrong nonce) are skipped during processing but their + state gas is still consumed. The total intrinsic state gas equals + (num_valid + num_invalid) * 135 * cpsb. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment() + auth_state_gas = fork.transaction_intrinsic_state_gas( + authorization_count=1, + ) + + contract = pre.deploy_contract(code=Op.STOP) + + authorization_list = [] + + # Valid authorizations + for _ in range(num_valid): + signer = pre.fund_eoa() + authorization_list.append( + AuthorizationTuple( + address=contract, + nonce=0, + signer=signer, + ), + ) + + # Invalid authorizations (wrong nonce) + for _ in range(num_invalid): + signer = pre.fund_eoa() + authorization_list.append( + AuthorizationTuple( + address=contract, + nonce=99, # Wrong nonce + signer=signer, + ), + ) + + total_auths = num_valid + num_invalid + sender = pre.fund_eoa() + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap + auth_state_gas * total_auths, + authorization_list=authorization_list, + sender=sender, + ) + + state_test(env=env, pre=pre, post={}, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_many_authorizations_state_gas( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test many authorizations with state gas from reservoir. + + Ten authorizations each charge 135 * cpsb intrinsic state gas. + The total state gas is drawn from the reservoir. Verifies that + large authorization lists scale correctly. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment() + auth_state_gas = fork.transaction_intrinsic_state_gas( + authorization_count=1, + ) + num_auths = 10 + + contract = pre.deploy_contract(code=Op.STOP) + + authorization_list = [] + for _ in range(num_auths): + signer = pre.fund_eoa() + authorization_list.append( + AuthorizationTuple( + address=contract, + nonce=0, + signer=signer, + ), + ) + + sender = pre.fund_eoa() + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap + auth_state_gas * num_auths, + authorization_list=authorization_list, + sender=sender, + ) + + state_test(env=env, pre=pre, post={}, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_auth_with_multiple_sstores( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test authorization combined with multiple SSTOREs. + + Authorization intrinsic state gas plus multiple SSTORE state gas + charges all draw from the same reservoir. Verifies combined state + gas accounting across intrinsic and execution phases. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment() + auth_state_gas = fork.transaction_intrinsic_state_gas( + authorization_count=1, + ) + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + num_sstores = 5 + + storage = Storage() + code = Bytecode() + for _ in range(num_sstores): + code += Op.SSTORE(storage.store_next(1), 1) + + contract = pre.deploy_contract(code=code) + + signer = pre.fund_eoa() + authorization_list = [ + AuthorizationTuple( + address=contract, + nonce=0, + signer=signer, + ), + ] + + total_state_gas = auth_state_gas + sstore_state_gas * num_sstores + sender = pre.fund_eoa() + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap + total_state_gas, + authorization_list=authorization_list, + sender=sender, + ) + + post = {contract: Account(storage=storage)} + state_test(env=env, pre=pre, post=post, tx=tx) + + +@pytest.mark.parametrize( + "gas_delta", + [ + pytest.param(0, id="exact_gas"), + pytest.param( + -1, + id="one_short", + marks=pytest.mark.exception_test, + ), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_authorization_exact_state_gas_boundary( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, + gas_delta: int, +) -> None: + """ + Test exact intrinsic gas boundary including auth state gas. + + The intrinsic cost includes regular gas (G_TRANSACTION + G_AUTHORIZATION + per auth) and state gas + ((STATE_BYTES_PER_NEW_ACCOUNT + STATE_BYTES_PER_AUTH_BASE) * cpsb + per auth). With gas_delta=0 the tx has exactly enough and succeeds. + With gas_delta=-1 the tx is 1 gas short and is rejected as + intrinsic-gas-too-low. + """ + contract = pre.deploy_contract(code=Op.STOP) + + signer = pre.fund_eoa() + authorization_list = [ + AuthorizationTuple( + address=contract, + nonce=0, + signer=signer, + ), + ] + + intrinsic_cost_calculator = fork.transaction_intrinsic_cost_calculator() + intrinsic_cost = intrinsic_cost_calculator( + authorization_list_or_count=authorization_list, + ) + + is_oog = gas_delta < 0 + sender = pre.fund_eoa() + tx = Transaction( + to=contract, + gas_limit=intrinsic_cost + gas_delta, + authorization_list=authorization_list, + sender=sender, + error=TransactionException.INTRINSIC_GAS_TOO_LOW if is_oog else None, + ) + + blockchain_test( + pre=pre, + blocks=[ + Block( + txs=[tx], + exception=( + TransactionException.INTRINSIC_GAS_TOO_LOW + if is_oog + else None + ), + ) + ], + post={}, + ) + + +@pytest.mark.valid_from("EIP8037") +def test_authorization_to_precompile_address( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test authorization targeting a precompile address charges state gas. + + Authorizing delegation to a precompile address (e.g., ecrecover at + 0x01) charges the same intrinsic state gas as any other target. + The authorization is processed and the signer's code is set to + the precompile address delegation designator. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment() + auth_state_gas = fork.transaction_intrinsic_state_gas( + authorization_count=1, + ) + + # ecrecover precompile at 0x01 + precompile_addr = 0x01 + + signer = pre.fund_eoa() + authorization_list = [ + AuthorizationTuple( + address=precompile_addr, + nonce=0, + signer=signer, + ), + ] + + sender = pre.fund_eoa() + tx = Transaction( + to=signer, + gas_limit=gas_limit_cap + auth_state_gas, + authorization_list=authorization_list, + sender=sender, + ) + + state_test(env=env, pre=pre, post={}, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_multi_tx_block_auth_refund_and_sstore( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test multi-transaction block with auth refund and SSTORE state gas. + + Two transactions in one block: + 1. A SetCode tx authorizing an existing account (gets new-account state gas + refund to reservoir). The refund reduces intrinsic_state_gas. + 2. A regular tx performing an SSTORE (charges + STATE_BYTES_PER_STORAGE_SET * cpsb state gas). + + Verifies block-level state gas accounting correctly handles both + the auth refund from tx1 and the SSTORE charge from tx2. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + auth_state_gas = fork.transaction_intrinsic_state_gas( + authorization_count=1, + ) + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + + contract = pre.deploy_contract(code=Op.STOP) + + # TX 1: auth targeting existing account (gets refund) + signer = pre.fund_eoa() + authorization_list = [ + AuthorizationTuple( + address=contract, + nonce=0, + signer=signer, + ), + ] + sender_1 = pre.fund_eoa() + tx_1 = Transaction( + to=contract, + gas_limit=gas_limit_cap + auth_state_gas, + authorization_list=authorization_list, + sender=sender_1, + ) + + # TX 2: SSTORE zero-to-nonzero (charges state gas) + storage = Storage() + sstore_contract = pre.deploy_contract( + code=Op.SSTORE(storage.store_next(1), 1), + ) + sender_2 = pre.fund_eoa() + tx_2 = Transaction( + to=sstore_contract, + gas_limit=gas_limit_cap + sstore_state_gas, + sender=sender_2, + ) + + blockchain_test( + pre=pre, + blocks=[Block(txs=[tx_1, tx_2])], + post={sstore_contract: Account(storage=storage)}, + ) + + +@pytest.mark.valid_from("EIP8037") +def test_auth_refund_bypasses_one_fifth_cap( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test auth refund to reservoir bypasses the 1/5 refund cap. + + The existing-account auth refund (new-account state gas) goes directly to + state_gas_reservoir, NOT to refund_counter. This means it is not + subject to the 1/5 refund cap. The test provides just enough gas + for the auth intrinsic state gas and multiple SSTOREs whose state + gas can only be funded from the reservoir if the full auth refund + is available (i.e. not capped at 1/5). + + If the auth refund went through refund_counter with the 1/5 cap, + the SSTOREs would OOG. By succeeding, this test proves the refund + bypasses the cap. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment() + auth_state_gas = fork.transaction_intrinsic_state_gas( + authorization_count=1, + ) + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + # Auth refund for existing account = new-account state gas + # (documents the expected value for reasoning about gas budgets). + + # Use 3 SSTOREs: 3 * 64 * cpsb = 192 * cpsb state gas needed. + # Auth refund gives new-account state gas to reservoir for all 3. + # If it were 1/5 capped: refund would be at most + # (143 * cpsb) / 5 ≈ 28 * cpsb, which can only fund 0 SSTOREs. + num_sstores = 3 + + storage = Storage() + code = Bytecode() + for _ in range(num_sstores): + code += Op.SSTORE(storage.store_next(1), 1) + + contract = pre.deploy_contract(code=code) + + # Existing signer — gets auth_refund to reservoir + signer = pre.fund_eoa() + authorization_list = [ + AuthorizationTuple( + address=contract, + nonce=0, + signer=signer, + ), + ] + + # Provide auth intrinsic state gas + SSTORE state gas. + # After the auth refund (new-account state gas) returns to the reservoir, + # the reservoir holds auth_refund which covers 3 SSTOREs (96*cpsb). + sender = pre.fund_eoa() + tx = Transaction( + to=contract, + gas_limit=( + gas_limit_cap + auth_state_gas + sstore_state_gas * num_sstores + ), + authorization_list=authorization_list, + sender=sender, + ) + + post = {contract: Account(storage=storage)} + state_test(env=env, pre=pre, post=post, tx=tx) + + +@pytest.mark.parametrize( + "num_auths", + [ + pytest.param(1, id="one_auth"), + pytest.param(3, id="three_auths"), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_existing_account_auth_header_gas_used_reflects_refund( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + num_auths: int, +) -> None: + """ + Verify the block header gas_used reflects the existing-authority + auth refund (deducted from `tx_state_gas`) when every authority + is an existing account. + + `set_delegation` credits `state_gas_reservoir` and accumulates + `state_refund`, which `process_transaction` subtracts from + `tx_state_gas` before adding it to `block_state_gas_used`. With + STOP execution there is no extra regular or state gas used, so + header gas_used equals + `max(intrinsic_regular, intrinsic_state - N * auth_refund)`. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + intrinsic_state_gas = fork.transaction_intrinsic_state_gas( + authorization_count=num_auths, + ) + total_intrinsic = fork.transaction_intrinsic_cost_calculator()( + authorization_list_or_count=num_auths, + ) + intrinsic_regular = total_intrinsic - intrinsic_state_gas + auth_refund = fork.gas_costs().REFUND_AUTH_PER_EXISTING_ACCOUNT * num_auths + + contract = pre.deploy_contract(code=Op.STOP) + + authorization_list = [ + AuthorizationTuple(address=contract, nonce=0, signer=pre.fund_eoa()) + for _ in range(num_auths) + ] + + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap + intrinsic_state_gas, + authorization_list=authorization_list, + sender=pre.fund_eoa(), + ) + + expected_gas_used = max( + intrinsic_regular, + intrinsic_state_gas - auth_refund, + ) + + state_test( + pre=pre, + post={}, + tx=tx, + blockchain_test_header_verify=Header(gas_used=expected_gas_used), + ) + + +@pytest.mark.parametrize( + "num_existing,num_new", + [ + pytest.param(1, 1, id="one_existing_one_new"), + pytest.param(2, 2, id="two_existing_two_new"), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_mixed_auths_header_gas_used_reflects_existing_refunds( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + num_existing: int, + num_new: int, +) -> None: + """ + Verify the block header gas_used deducts only the existing-authority + auth refunds across a mix of existing and new account + authorizations. + + Each existing authority contributes + `REFUND_AUTH_PER_EXISTING_ACCOUNT` to `state_refund`; new + authorities contribute none. Header gas_used is + `max(intrinsic_regular, intrinsic_state - num_existing * refund)`. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + num_auths = num_existing + num_new + intrinsic_state_gas = fork.transaction_intrinsic_state_gas( + authorization_count=num_auths, + ) + total_intrinsic = fork.transaction_intrinsic_cost_calculator()( + authorization_list_or_count=num_auths, + ) + intrinsic_regular = total_intrinsic - intrinsic_state_gas + auth_refund = ( + fork.gas_costs().REFUND_AUTH_PER_EXISTING_ACCOUNT * num_existing + ) + + contract = pre.deploy_contract(code=Op.STOP) + + authorization_list = [] + for _ in range(num_existing): + authorization_list.append( + AuthorizationTuple( + address=contract, + nonce=0, + signer=pre.fund_eoa(), + ) + ) + for _ in range(num_new): + authorization_list.append( + AuthorizationTuple( + address=contract, + nonce=0, + signer=pre.fund_eoa(amount=0), + ) + ) + + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap + intrinsic_state_gas, + authorization_list=authorization_list, + sender=pre.fund_eoa(), + ) + + expected_gas_used = max( + intrinsic_regular, + intrinsic_state_gas - auth_refund, + ) + + state_test( + pre=pre, + post={}, + tx=tx, + blockchain_test_header_verify=Header(gas_used=expected_gas_used), + ) + + +@pytest.mark.valid_from("EIP8037") +def test_existing_auth_refund_survives_top_level_revert( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify the existing-authority auth refund still flows through + `state_refund` when execution REVERTs at the top level. + + `set_delegation` runs before EVM execution and accumulates the + refund into `MessageCallOutput.state_refund`. A subsequent + top-level REVERT discards the SSTORE state changes (and resets + `state_gas_used` to 0), but it does not unwind the auth refund — + `process_transaction` still subtracts the refund from + `tx_state_gas`. The header gas_used therefore reflects: + + `max(intrinsic_regular + execution_regular, + intrinsic_state - auth_refund)` + + with `execution_state` netting to 0 because of the revert. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + intrinsic_state_gas = fork.transaction_intrinsic_state_gas( + authorization_count=1, + ) + total_intrinsic = fork.transaction_intrinsic_cost_calculator()( + authorization_list_or_count=1, + ) + intrinsic_regular = total_intrinsic - intrinsic_state_gas + auth_refund = fork.gas_costs().REFUND_AUTH_PER_EXISTING_ACCOUNT + + sstore_op = Op.SSTORE( + key=0, + value=1, + key_warm=False, + original_value=0, + new_value=1, + ) + code = sstore_op + Op.REVERT(0, 0) + contract = pre.deploy_contract(code=code) + + # bytecode.gas_cost(fork) returns the combined (regular + state) + # cost; subtract the SSTORE state portion to isolate the regular + # gas burned before REVERT. + execution_regular = code.gas_cost(fork) - Op.SSTORE( + new_value=1 + ).state_cost(fork) + + signer = pre.fund_eoa() + authorization_list = [ + AuthorizationTuple(address=contract, nonce=0, signer=signer), + ] + + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap + intrinsic_state_gas, + authorization_list=authorization_list, + sender=pre.fund_eoa(), + ) + + expected_gas_used = max( + intrinsic_regular + execution_regular, + intrinsic_state_gas - auth_refund, + ) + + state_test( + pre=pre, + post={contract: Account(storage={})}, + tx=tx, + blockchain_test_header_verify=Header(gas_used=expected_gas_used), + ) + + +@pytest.mark.parametrize( + "failure_mode", + [ + pytest.param("revert", id="revert"), + pytest.param("halt", id="halt"), + pytest.param("oog", id="oog"), + ], +) +@pytest.mark.parametrize( + "authority_exists", + [ + pytest.param(False, id="new_account"), + pytest.param(True, id="existing_account"), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_auth_state_gas_in_header_after_failure( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + failure_mode: str, + authority_exists: bool, +) -> None: + """ + Verify block header reflects intrinsic state gas from a 7702 + authorization when the top-level tx fails. + + Execution state gas is zeroed on failure but intrinsic state gas + is preserved. For existing-account auths the spec subtracts the + auth refund from `tx_state_gas`, reducing the state component. + The delegation indicator persists (set before the execution + snapshot). Parametrized across all failure modes (revert/halt/oog) + and authority states (new vs existing). + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + + auth_intrinsic_state = fork.transaction_intrinsic_state_gas( + authorization_count=1, + ) + intrinsic_cost = fork.transaction_intrinsic_cost_calculator() + intrinsic_total = intrinsic_cost(authorization_list_or_count=1) + intrinsic_regular = intrinsic_total - auth_intrinsic_state + auth_refund = ( + fork.gas_costs().REFUND_AUTH_PER_EXISTING_ACCOUNT + if authority_exists + else 0 + ) + + delegate = pre.deploy_contract(code=Op.STOP) + + if failure_mode == "revert": + revert_code = Op.REVERT(0, 0) + target = pre.deploy_contract(code=revert_code) + elif failure_mode == "halt": + target = pre.deploy_contract(code=Op.INVALID) + else: + target = pre.deploy_contract(code=Op.JUMPDEST + Op.JUMP(0x0)) + + if authority_exists: + signer = pre.fund_eoa() + else: + signer = pre.fund_eoa(0) + + tx_gas = gas_limit_cap + auth_intrinsic_state + + tx = Transaction( + ty=4, + to=target, + gas_limit=tx_gas, + sender=pre.fund_eoa(), + authorization_list=[ + AuthorizationTuple( + address=delegate, + nonce=0, + signer=signer, + ), + ], + ) + + if failure_mode == "revert": + block_regular = intrinsic_regular + revert_code.gas_cost(fork) + else: + block_regular = tx_gas - auth_intrinsic_state + + expected_gas_used = max(block_regular, auth_intrinsic_state - auth_refund) + + state_test( + pre=pre, + post={ + signer: Account( + code=Spec7702.delegation_designation(delegate), + ), + }, + tx=tx, + blockchain_test_header_verify=Header(gas_used=expected_gas_used), + ) + + +@pytest.mark.parametrize( + "authority_exists", + [ + pytest.param(False, id="new_account"), + pytest.param(True, id="existing_account"), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_auth_sender_billing_after_failure( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + authority_exists: bool, +) -> None: + """ + Verify sender billing distinguishes new vs existing account auth + on top-level failure. + + For existing accounts, set_delegation refunds new-account state + gas to the reservoir. On REVERT, the restored reservoir reduces + the sender's bill via the billing formula. The sender pays less + than in the new-account case by exactly the refund amount. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + + auth_intrinsic_state = fork.transaction_intrinsic_state_gas( + authorization_count=1, + ) + intrinsic_cost = fork.transaction_intrinsic_cost_calculator() + intrinsic_total = intrinsic_cost(authorization_list_or_count=1) + intrinsic_regular = intrinsic_total - auth_intrinsic_state + new_account_refund = fork.gas_costs().REFUND_AUTH_PER_EXISTING_ACCOUNT + + delegate = pre.deploy_contract(code=Op.STOP) + target = pre.deploy_contract(code=Op.REVERT(0, 0)) + + if authority_exists: + signer = pre.fund_eoa() + else: + signer = pre.fund_eoa(0) + + tx_gas = gas_limit_cap + auth_intrinsic_state + + revert_gas = (Op.REVERT(0, 0)).gas_cost(fork) + auth_refund = new_account_refund if authority_exists else 0 + expected_cumulative = intrinsic_total + revert_gas - auth_refund + expected_gas_used = max( + intrinsic_regular + revert_gas, + auth_intrinsic_state - auth_refund, + ) + + tx = Transaction( + ty=4, + to=target, + gas_limit=tx_gas, + sender=pre.fund_eoa(), + authorization_list=[ + AuthorizationTuple( + address=delegate, + nonce=0, + signer=signer, + ), + ], + expected_receipt=TransactionReceipt( + cumulative_gas_used=expected_cumulative, + ), + ) + + state_test( + pre=pre, + post={ + signer: Account( + code=Spec7702.delegation_designation(delegate), + ), + }, + tx=tx, + blockchain_test_header_verify=Header(gas_used=expected_gas_used), + ) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_sstore.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_sstore.py new file mode 100644 index 00000000000..9ae878adea6 --- /dev/null +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_sstore.py @@ -0,0 +1,1330 @@ +""" +Test SSTORE state gas charging under EIP-8037. + +Zero-to-nonzero storage writes charge +`STATE_BYTES_PER_STORAGE_SET * cost_per_state_byte` of state gas. +Nonzero-to-nonzero writes charge no state gas. 0 to x to 0 restoration +in the same tx refunds state gas directly to `state_gas_reservoir` +(inline at x to 0) and the regular write-cost portion to +`refund_counter`. + +Tests for [EIP-8037: State Creation Gas Cost Increase] +(https://eips.ethereum.org/EIPS/eip-8037). +""" + +import pytest +from execution_testing import ( + Account, + Address, + Alloc, + Block, + BlockchainTestFiller, + Bytecode, + Environment, + Fork, + Header, + Op, + StateTestFiller, + Storage, + Transaction, +) +from execution_testing.checklists import EIPChecklist + +from .spec import ref_spec_8037 + +REFERENCE_SPEC_GIT_PATH = ref_spec_8037.git_path +REFERENCE_SPEC_VERSION = ref_spec_8037.version + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +@pytest.mark.valid_from("EIP8037") +def test_sstore_zero_to_nonzero( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test SSTORE zero-to-nonzero charges state gas. + + Writing a nonzero value to a previously-zero slot charges + STATE_BYTES_PER_STORAGE_SET * cost_per_state_byte of state gas + in addition to regular gas. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + storage = Storage() + contract = pre.deploy_contract( + code=Op.SSTORE(storage.store_next(1), 1), + ) + + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap, + sender=pre.fund_eoa(), + ) + + post = {contract: Account(storage=storage)} + state_test(pre=pre, post=post, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_sstore_nonzero_to_nonzero( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test SSTORE nonzero-to-nonzero charges no state gas. + + Updating a slot that already holds a nonzero value to a different + nonzero value does not create new state, so no state gas is charged. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + storage = Storage() + contract = pre.deploy_contract( + code=Op.SSTORE(storage.store_next(2), 2), + storage={0: 1}, + ) + + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap, + sender=pre.fund_eoa(), + ) + + post = {contract: Account(storage=storage)} + state_test(pre=pre, post=post, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_sstore_nonzero_to_zero( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test SSTORE nonzero-to-zero charges no state gas. + + Clearing a storage slot (setting to zero) does not grow state and + earns a regular gas refund (GAS_STORAGE_CLEAR_REFUND). + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + storage = Storage() + contract = pre.deploy_contract( + code=Op.SSTORE(storage.store_next(0), 0), + storage={0: 1}, + ) + + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap, + sender=pre.fund_eoa(), + ) + + post = {contract: Account(storage=storage)} + state_test(pre=pre, post=post, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_sstore_zero_to_zero( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test SSTORE zero-to-zero charges no state gas. + + Writing zero to an already-zero slot creates no new state. Only + the warm access regular gas cost is charged. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + storage = Storage() + contract = pre.deploy_contract( + code=Op.SSTORE(storage.store_next(0), 0), + ) + + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap, + sender=pre.fund_eoa(), + ) + + post = {contract: Account(storage=storage)} + state_test(pre=pre, post=post, tx=tx) + + +@pytest.mark.parametrize( + "refund_sufficient", + [ + pytest.param(True, id="refund_funds_create"), + pytest.param(False, id="no_refund_create_oogs"), + ], +) +@pytest.mark.parametrize( + "delegatecall_depth", + [ + pytest.param(1, id="depth_1"), + pytest.param(3, id="depth_3"), + pytest.param(10, id="depth_10"), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_sstore_restoration_refund_credits_local_reservoir( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + delegatecall_depth: int, + refund_sufficient: bool, +) -> None: + """ + Verify a same transaction SSTORE restoration refund credits the + clearing frame's own reservoir immediately so later state gas in + that frame is funded. Parametrized to pin the refund as necessary + and sufficient. + """ + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + create_state_gas = fork.create_state_gas() + # Premise: the two restoration refunds must be able to cover the + # CREATE's new-account state gas for the funded path to exist. + # Drift-proof relationship (vs. hardcoding the constants). + assert 2 * sstore_state_gas >= create_state_gas + + # Sentinel written only if the CREATE returned (frame did not OOG). + sentinel_slot = 2 + # refund: clear (1→0, restoration refund). no refund: modify + # (1→2, no state growth, no refund) — same regular shape. + cleared_value = 0 if refund_sufficient else 2 + clearing = pre.deploy_contract( + code=( + Op.SSTORE(0, cleared_value) + + Op.SSTORE(1, cleared_value) + + Op.POP(Op.CREATE(0, 0, 0)) + + Op.SSTORE(sentinel_slot, 1) + + Op.STOP + ) + ) + inner: Address = clearing + for _ in range(delegatecall_depth): + inner = pre.deploy_contract( + code=(Op.POP(Op.DELEGATECALL(gas=Op.GAS, address=inner)) + Op.STOP) + ) + parent = pre.deploy_contract( + code=( + Op.SSTORE(0, 1) + + Op.SSTORE(1, 1) + + Op.POP(Op.DELEGATECALL(gas=Op.GAS, address=inner)) + + Op.STOP + ) + ) + + # The two parent `0→1` sets spill their state gas into `gas_left` + # (tx is far below the per-tx cap, so no state-gas reservoir). + # Budget regular headroom for the call chain plus that spill, then + # sit mid-window: short of also spill-funding `create_state_gas`, + # so only a refund-credited reservoir can cover the CREATE. + regular_headroom = 200_000 + gas_limit = regular_headroom + 2 * sstore_state_gas + create_state_gas // 2 + + if refund_sufficient: + post = {parent: Account(storage={0: 0, 1: 0, sentinel_slot: 1})} + else: + # CREATE OOGs in the clearing frame; its writes (the 1→2 + # modifications and the sentinel) revert, leaving the parent's + # original sets intact. + post = {parent: Account(storage={0: 1, 1: 1})} + + tx = Transaction( + to=parent, + gas_limit=gas_limit, + sender=pre.fund_eoa(), + ) + + state_test(pre=pre, post=post, tx=tx) + + +@EIPChecklist.GasRefundsChanges.Test.RefundCalculation() +@pytest.mark.valid_from("EIP8037") +def test_sstore_restoration_refund( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test SSTORE zero-to-nonzero-to-zero restoration refunds state gas. + + When a slot is written from zero to nonzero and then restored to + zero in the same transaction, the state gas charge + (STATE_BYTES_PER_STORAGE_SET * cost_per_state_byte) is refunded + via refund_counter along with the regular gas write cost. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + contract = pre.deploy_contract( + code=(Op.SSTORE(0, 1) + Op.SSTORE(0, 0)), + ) + + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap, + sender=pre.fund_eoa(), + ) + + # Slot 0 restored to zero — state gas refunded + post = {contract: Account(storage={0: 0})} + state_test(pre=pre, post=post, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_sstore_restoration_nonzero_no_state_refund( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test nonzero-to-nonzero-to-original restoration has no state gas refund. + + When a slot holds a nonzero original value, changing it and + restoring it never involves state gas (no state growth occurred), + so only regular gas refunds apply. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + contract = pre.deploy_contract( + code=(Op.SSTORE(0, 2) + Op.SSTORE(0, 1)), + storage={0: 1}, + ) + + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap, + sender=pre.fund_eoa(), + ) + + post = {contract: Account(storage={0: 1})} + state_test(pre=pre, post=post, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_sstore_clear_refund_reversal( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test clearing a nonzero slot then un-clearing reverses the refund. + + When a slot with a nonzero original value is cleared (set to zero), + the clear refund is granted. If the slot is then set back to a + nonzero value, the clear refund is reversed via refund_counter. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + contract = pre.deploy_contract( + code=(Op.SSTORE(0, 0) + Op.SSTORE(0, 2)), + storage={0: 1}, + ) + + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap, + sender=pre.fund_eoa(), + ) + + post = {contract: Account(storage={0: 2})} + state_test(pre=pre, post=post, tx=tx) + + +@pytest.mark.parametrize( + "num_slots", + [ + pytest.param(1, id="single_slot"), + pytest.param(5, id="five_slots"), + pytest.param(10, id="ten_slots"), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_sstore_multiple_slots( + state_test: StateTestFiller, + pre: Alloc, + num_slots: int, + fork: Fork, +) -> None: + """ + Test multiple zero-to-nonzero SSTOREs each charge state gas. + + Each slot written from zero to nonzero independently charges + STATE_BYTES_PER_STORAGE_SET * cost_per_state_byte of state gas. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + storage = Storage() + code = Bytecode() + for _ in range(num_slots): + code += Op.SSTORE(storage.store_next(1), 1) + contract = pre.deploy_contract(code=code) + + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap, + sender=pre.fund_eoa(), + ) + + post = {contract: Account(storage=storage)} + state_test(pre=pre, post=post, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_sstore_state_gas_drawn_from_reservoir( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test SSTORE state gas drawn from reservoir before gas_left. + + Provide enough gas above TX_MAX_GAS_LIMIT to fully cover the + SSTORE state gas from the reservoir, leaving gas_left untouched + by the state gas charge. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + env = Environment() + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + + storage = Storage() + contract = pre.deploy_contract( + code=Op.SSTORE(storage.store_next(1), 1), + ) + + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap + sstore_state_gas, + sender=pre.fund_eoa(), + ) + + post = {contract: Account(storage=storage)} + state_test(env=env, pre=pre, post=post, tx=tx) + + +@pytest.mark.with_all_typed_transactions +@pytest.mark.valid_from("EIP8037") +def test_sstore_state_gas_all_tx_types( + state_test: StateTestFiller, + pre: Alloc, + typed_transaction: Transaction, + fork: Fork, +) -> None: + """ + Test SSTORE state gas works across all transaction types. + + Different tx types (legacy, access list, EIP-1559, blob, SetCode) + have different intrinsic costs, which affects the gas split between + gas_left and state_gas_reservoir. Verify SSTORE succeeds with + each type. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + storage = Storage() + contract = pre.deploy_contract( + code=Op.SSTORE(storage.store_next(1), 1), + ) + + tx = typed_transaction.copy( + to=contract, + gas_limit=gas_limit_cap, + ) + + post = {contract: Account(storage=storage)} + state_test(pre=pre, post=post, tx=tx) + + +@pytest.mark.parametrize( + "gas_above_stipend", + [ + pytest.param(-1, id="below_stipend"), + pytest.param(0, id="at_stipend"), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_sstore_stipend_check_excludes_reservoir( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + gas_above_stipend: int, +) -> None: + """ + Verify SSTORE stipend check uses gas_left only, not the reservoir. + + A child frame has gas_left at or just below the stipend threshold + (GAS_CALL_STIPEND + 1) while the reservoir holds ample state gas. + The stipend check must fail when gas_left < stipend, regardless + of the reservoir balance. + + With below_stipend: SSTORE fails (gas_left < 2301, reservoir ignored). + With at_stipend: SSTORE passes the stipend check and proceeds. + """ + gas_costs = fork.gas_costs() + stipend = gas_costs.CALL_STIPEND + 1 + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + + # Child: Op.SSTORE(0, 1) = 2 pushes + SSTORE opcode. + child_code = Op.SSTORE(0, 1) + child = pre.deploy_contract(child_code) + + # Full regular gas for the child (pushes + SSTORE regular cost). + # State gas comes from the reservoir so it doesn't affect gas_left. + child_full_regular = child_code.gas_cost(fork) - sstore_state_gas + + # below_stipend: give 1 less than stipend after pushes, fails check. + # at_stipend: give full regular gas, passes check and completes. + if gas_above_stipend < 0: + push_gas = 2 * gas_costs.VERY_LOW + child_gas = push_gas + stipend - 1 + else: + child_gas = child_full_regular + + # Caller forwards limited regular gas via CALL. State gas comes + # from the reservoir (gas_limit above the cap). + caller_storage = Storage() + sstore_succeeds = gas_above_stipend >= 0 + caller = pre.deploy_contract( + Op.SSTORE( + caller_storage.store_next( + 1 if sstore_succeeds else 0, + "sstore_succeeds" + if sstore_succeeds + else "sstore_fails_stipend", + ), + Op.CALL(gas=child_gas, address=child), + ) + ) + + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + tx = Transaction( + sender=pre.fund_eoa(), + to=caller, + gas_limit=gas_limit_cap + sstore_state_gas, + ) + + post = {caller: Account(storage=caller_storage)} + state_test(pre=pre, tx=tx, post=post) + + +@pytest.mark.parametrize( + "num_cycles", + [ + pytest.param(1, id="single_cycle"), + pytest.param(50, id="fifty_cycles"), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_sstore_restoration_block_state_gas_zero( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, + num_cycles: int, +) -> None: + """ + Verify 0 to x to 0 cycles contribute zero to block state gas. + + Net state growth is zero. State gas goes directly to + `state_gas_reservoir` rather than `refund_counter`, so block + state gas is not inflated by the charges. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() + + code = Bytecode() + for i in range(num_cycles): + code += Op.SSTORE(i, 1) + Op.SSTORE.with_metadata( + key_warm=True, + original_value=0, + current_value=1, + new_value=0, + )(i, 0) + tx_regular = ( + intrinsic_gas + code.gas_cost(fork) - num_cycles * sstore_state_gas + ) + + contract = pre.deploy_contract(code=code) + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap + num_cycles * sstore_state_gas, + sender=pre.fund_eoa(), + ) + + blockchain_test( + pre=pre, + blocks=[Block(txs=[tx], header_verify=Header(gas_used=tx_regular))], + post={contract: Account(storage=dict.fromkeys(range(num_cycles), 0))}, + ) + + +@pytest.mark.parametrize( + "num_cycles", + [ + pytest.param(1, id="one_cycle"), + pytest.param(10, id="ten_cycles"), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_sstore_restoration_mixed_with_genuine_sstore( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, + num_cycles: int, +) -> None: + """ + Verify restoration cycles plus a genuine 0 to x SSTORE. + + `num_cycles` of 0 to x to 0 refund; one genuine 0 to x on slot 99 + persists, contributing exactly one `sstore_state_gas` to block + state gas. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() + + code = Bytecode() + for i in range(num_cycles): + code += Op.SSTORE(i, 1) + Op.SSTORE.with_metadata( + key_warm=True, + original_value=0, + current_value=1, + new_value=0, + )(i, 0) + code += Op.SSTORE(99, 1) + + num_0_to_1 = num_cycles + 1 + tx_regular = ( + intrinsic_gas + code.gas_cost(fork) - num_0_to_1 * sstore_state_gas + ) + expected = max(tx_regular, sstore_state_gas) + + contract = pre.deploy_contract(code=code) + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap + num_0_to_1 * sstore_state_gas, + sender=pre.fund_eoa(), + ) + + post_storage = dict.fromkeys(range(num_cycles), 0) + post_storage[99] = 1 + blockchain_test( + pre=pre, + blocks=[Block(txs=[tx], header_verify=Header(gas_used=expected))], + post={contract: Account(storage=post_storage)}, + ) + + +@pytest.mark.valid_from("EIP8037") +def test_sstore_restoration_intermediate_values( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify restoration refund triggers for 0 to x to y to 0. + + The refund condition is `original_value == new_value == 0`, + independent of intermediate values. One state gas charge at the + first 0 to x; no charge for nonzero-to-nonzero; refund to reservoir + at y to 0. Net block state gas is zero. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() + + code = ( + Op.SSTORE(0, 1) + + Op.SSTORE.with_metadata( + key_warm=True, + original_value=0, + current_value=1, + new_value=2, + )(0, 2) + + Op.SSTORE.with_metadata( + key_warm=True, + original_value=0, + current_value=2, + new_value=0, + )(0, 0) + ) + tx_regular = intrinsic_gas + code.gas_cost(fork) - sstore_state_gas + + contract = pre.deploy_contract(code=code) + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap + sstore_state_gas, + sender=pre.fund_eoa(), + ) + + blockchain_test( + pre=pre, + blocks=[Block(txs=[tx], header_verify=Header(gas_used=tx_regular))], + post={contract: Account(storage={0: 0})}, + ) + + +@pytest.mark.valid_from("EIP8037") +def test_sstore_restoration_then_reset( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify accounting across 0 to 1 to 0 to 1 (restore then re-set). + + The refund applied at 1 to 0 returns state gas to the reservoir; + the subsequent 0 to 1 re-charges state gas. Net: one charge + remains, one state gas worth counted in block state gas. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() + + code = ( + Op.SSTORE(0, 1) + + Op.SSTORE.with_metadata( + key_warm=True, + original_value=0, + current_value=1, + new_value=0, + )(0, 0) + + Op.SSTORE.with_metadata( + key_warm=True, + original_value=0, + current_value=0, + new_value=1, + )(0, 1) + ) + tx_regular = intrinsic_gas + code.gas_cost(fork) - 2 * sstore_state_gas + expected = max(tx_regular, sstore_state_gas) + + contract = pre.deploy_contract(code=code) + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap + sstore_state_gas, + sender=pre.fund_eoa(), + ) + + blockchain_test( + pre=pre, + blocks=[Block(txs=[tx], header_verify=Header(gas_used=expected))], + post={contract: Account(storage={0: 1})}, + ) + + +@pytest.mark.valid_from("EIP8037") +def test_sstore_restoration_reservoir_replenished_inline( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify the reservoir is replenished inline at x to 0. + + Reservoir sized for exactly one slot. After the 0 to 1 to 0 pair + on slot 0, the reservoir refill allows a second 0 to 1 on slot 1 + to draw from it. Block state gas reflects only slot 1. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() + + code = ( + Op.SSTORE(0, 1) + + Op.SSTORE.with_metadata( + key_warm=True, + original_value=0, + current_value=1, + new_value=0, + )(0, 0) + + Op.SSTORE(1, 1) + ) + tx_regular = intrinsic_gas + code.gas_cost(fork) - 2 * sstore_state_gas + expected = max(tx_regular, sstore_state_gas) + + contract = pre.deploy_contract(code=code) + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap + sstore_state_gas, + sender=pre.fund_eoa(), + ) + + blockchain_test( + pre=pre, + blocks=[Block(txs=[tx], header_verify=Header(gas_used=expected))], + post={contract: Account(storage={0: 0, 1: 1})}, + ) + + +@pytest.mark.with_all_call_opcodes( + selector=lambda call_opcode: call_opcode != Op.STATICCALL +) +@pytest.mark.valid_from("EIP8037") +def test_sstore_restoration_cross_frame( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, + call_opcode: Op, +) -> None: + """ + Verify restoration refund across frames for CALL / CALLCODE / DELEGATECALL. + + Callee performs the full 0 to x to 0 cycle within its call. For + CALL the slot lives in callee's storage; for CALLCODE/DELEGATECALL + it lives in caller's. The reservoir is tx-level, so the refund + applies regardless of storage ownership. Net block state gas is + zero. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() + + child_code = ( + Op.SSTORE(0, 1) + + Op.SSTORE.with_metadata( + key_warm=True, + original_value=0, + current_value=1, + new_value=0, + )(0, 0) + + Op.STOP + ) + # Callee's regular gas excludes the state gas (refunded at x to 0). + child_regular = child_code.gas_cost(fork) - sstore_state_gas + child = pre.deploy_contract(code=child_code) + + parent_code = Op.POP(call_opcode(gas=child_regular, address=child)) + parent = pre.deploy_contract(code=parent_code) + + tx_regular = intrinsic_gas + parent_code.gas_cost(fork) + child_regular + + tx = Transaction( + to=parent, + gas_limit=gas_limit_cap + sstore_state_gas, + sender=pre.fund_eoa(), + ) + + # CALL targets callee's storage; CALLCODE/DELEGATECALL target caller's. + slot_owner = child if call_opcode == Op.CALL else parent + blockchain_test( + pre=pre, + blocks=[Block(txs=[tx], header_verify=Header(gas_used=tx_regular))], + post={slot_owner: Account(storage={0: 0})}, + ) + + +@pytest.mark.parametrize( + "num_hops", + [ + pytest.param(1, id="single_hop"), + pytest.param(2, id="two_hops"), + pytest.param(3, id="three_hops"), + pytest.param(10, id="ten_hops"), + ], +) +@pytest.mark.with_all_call_opcodes( + selector=lambda call_opcode: call_opcode in (Op.DELEGATECALL, Op.CALLCODE) +) +@pytest.mark.valid_from("EIP8037") +def test_sstore_restoration_charge_in_ancestor( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + call_opcode: Op, + num_hops: int, +) -> None: + """ + Verify 0 to x to 0 refund when the 0 to x charge is in the parent + and x to 0 runs `num_hops` DELEGATECALL/CALLCODE frames below, + each sharing storage with the parent. + + Every intermediate frame has zero local `state_gas_used`, so the + refund must propagate up the chain to the ancestor that charged + the 0 to x. A probe SSTORE sized to OOG by 1 detects any loss. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + gas_costs = fork.gas_costs() + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + probe_gas = ( + 2 * gas_costs.VERY_LOW + + gas_costs.COLD_STORAGE_WRITE + + sstore_state_gas + - 1 + ) + + # Innermost frame does x to 0; each hop above delegates down. + delegate_target = pre.deploy_contract( + code=( + Op.SSTORE.with_metadata( + key_warm=True, + original_value=0, + current_value=1, + new_value=0, + )(0, 0) + + Op.STOP + ) + ) + for _ in range(num_hops - 1): + delegate_target = pre.deploy_contract( + code=Op.POP(call_opcode(gas=Op.GAS, address=delegate_target)) + + Op.STOP, + ) + + probe = pre.deploy_contract(code=Op.SSTORE(0, 1)) + + parent_storage = Storage() + parent_code = ( + Op.SSTORE(parent_storage.store_next(0, "cycle_restored"), 1) + + Op.POP(call_opcode(gas=Op.GAS, address=delegate_target)) + + Op.SSTORE( + parent_storage.store_next(1, "probe_must_succeed"), + Op.CALL(gas=probe_gas, address=probe), + ) + ) + parent = pre.deploy_contract(code=parent_code) + + # Reservoir starts at exactly sstore_state_gas; the parent's 0 to 1 + # drains it to zero before entering the delegation chain. + tx = Transaction( + sender=pre.fund_eoa(), + to=parent, + gas_limit=gas_limit_cap + sstore_state_gas, + ) + + post = {parent: Account(storage=parent_storage)} + state_test(pre=pre, tx=tx, post=post) + + +@pytest.mark.with_all_call_opcodes( + selector=lambda call_opcode: call_opcode != Op.STATICCALL +) +@pytest.mark.valid_from("EIP8037") +def test_sstore_restoration_sub_frame_revert( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + call_opcode: Op, +) -> None: + """ + Verify 0 to x to 0 reservoir refund returns to the caller on + sub-frame REVERT. + + The sub-call performs 0 to x to 0 then REVERTs. Since both the + set-charge and its refund roll back together, the + `state_gas_used + state_gas_left` sum reflects the unconsumed + reservoir and is returned to the caller via + `incorporate_child_on_error`. A single-SSTORE probe sized to OOG + by 1 succeeds, confirming the caller's reservoir was replenished. + """ + gas_costs = fork.gas_costs() + # Probe SSTORE(0, 1): 2 pushes + cold storage write + state gas - 1, + # so it OOGs by 1 when the reservoir is 0 and succeeds otherwise. + probe_gas = ( + 2 * gas_costs.VERY_LOW + + gas_costs.COLD_STORAGE_WRITE + + Op.SSTORE(new_value=1).state_cost(fork) + - 1 + ) + + child_code = Op.SSTORE(0, 1) + Op.SSTORE(0, 0) + Op.REVERT(0, 0) + child = pre.deploy_contract(code=child_code) + probe = pre.deploy_contract(code=Op.SSTORE(0, 1)) + + # Forward all remaining gas so the child completes both SSTOREs + # and REVERT without a hard-coded budget. + caller_storage = Storage() + caller_code = Op.POP(call_opcode(gas=Op.GAS, address=child)) + Op.SSTORE( + caller_storage.store_next(1, "probe_must_succeed"), + Op.CALL(gas=probe_gas, address=probe), + ) + caller = pre.deploy_contract(code=caller_code) + + # gas_limit at the cap means reservoir starts at 0 pre-call. + tx = Transaction( + sender=pre.fund_eoa(), + to=caller, + gas_limit=fork.transaction_gas_limit_cap(), + ) + + post = {caller: Account(storage=caller_storage)} + state_test(pre=pre, tx=tx, post=post) + + +@pytest.mark.with_all_call_opcodes( + selector=lambda call_opcode: call_opcode != Op.STATICCALL +) +@pytest.mark.valid_from("EIP8037") +def test_sstore_restoration_ancestor_revert( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + call_opcode: Op, +) -> None: + """ + Verify the SSTORE 0 to x to 0 refund returns to the caller when an + ancestor frame (not the applying frame itself) reverts. + + Inner frame applies the refund and returns successfully; its + `state_gas_left` (inflated by the refund) propagates to middle + via `incorporate_child_on_success`. Middle then REVERTs; the + refunded reservoir flows back to the caller via + `incorporate_child_on_error`, so the caller's reservoir is + replenished by `sstore_state_gas`. + """ + gas_costs = fork.gas_costs() + intrinsic_cost = fork.transaction_intrinsic_cost_calculator()() + # Probe SSTORE(0, 1): 2 pushes + cold storage write + state gas - 1, + # so it OOGs by 1 when the reservoir is 0 and succeeds otherwise. + probe_gas = ( + 2 * gas_costs.VERY_LOW + + gas_costs.COLD_STORAGE_WRITE + + Op.SSTORE(new_value=1).state_cost(fork) + - 1 + ) + + set_op = Op.SSTORE.with_metadata( + key_warm=False, + original_value=0, + current_value=0, + new_value=1, + )(0, 1) + clear_op = Op.SSTORE.with_metadata( + key_warm=True, + original_value=0, + current_value=1, + new_value=0, + )(0, 0) + inner_code = set_op + clear_op + Op.STOP + inner = pre.deploy_contract(code=inner_code) + + middle_code = Op.POP(Op.CALL(gas=Op.GAS, address=inner)) + Op.REVERT(0, 0) + middle = pre.deploy_contract(code=middle_code) + + probe_code = Op.SSTORE(0, 1) + probe = pre.deploy_contract(code=probe_code) + + caller_storage = Storage() + caller_code = Op.POP(call_opcode(gas=Op.GAS, address=middle)) + Op.SSTORE( + caller_storage.store_next(1, "probe_must_succeed"), + Op.CALL(gas=probe_gas, address=probe), + ) + caller = pre.deploy_contract(code=caller_code) + + # Block state gas commits: probe's SSTORE-set and caller's outer + # SSTORE-set; inner's set+clear cancel before middle reverts and + # don't propagate. Header gas_used is max(regular, state). + expected_regular = ( + intrinsic_cost + + caller_code.regular_cost(fork) + + middle_code.regular_cost(fork) + + inner_code.regular_cost(fork) + + probe_code.regular_cost(fork) + ) + expected_state = 2 * Op.SSTORE(new_value=1).state_cost(fork) + expected_gas_used = max(expected_regular, expected_state) + + # gas_limit at the cap means the caller's reservoir starts at 0. + tx = Transaction( + sender=pre.fund_eoa(), + to=caller, + gas_limit=fork.transaction_gas_limit_cap(), + ) + + state_test( + pre=pre, + tx=tx, + post={caller: Account(storage=caller_storage)}, + blockchain_test_header_verify=Header(gas_used=expected_gas_used), + ) + + +@pytest.mark.with_all_call_opcodes( + selector=lambda call_opcode: call_opcode in (Op.DELEGATECALL, Op.CALLCODE) +) +@pytest.mark.valid_from("EIP8037") +def test_sstore_restoration_charge_in_ancestor_intermediate_revert( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + call_opcode: Op, +) -> None: + """ + Verify a deferred refund applied in an intermediate frame still + flows back to the caller when that frame REVERTs. + + Caller's SSTORE charges; the matching clear in inner is deferred + through the chain and lands on middle's own SSTORE-set during + `incorporate_child_on_success`. Middle REVERTs; the applied + amount must reach the caller via `incorporate_child_on_error`. + A probe SSTORE sized to OOG by 1 detects loss. + """ + gas_costs = fork.gas_costs() + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + intrinsic_cost = fork.transaction_intrinsic_cost_calculator()() + # Probe SSTORE(0, 1): 2 pushes + cold storage write + state gas - 1, + # so it OOGs by 1 when the reservoir is 0 and succeeds otherwise. + probe_gas = ( + 2 * gas_costs.VERY_LOW + + gas_costs.COLD_STORAGE_WRITE + + sstore_state_gas + - 1 + ) + + inner_code = ( + Op.SSTORE.with_metadata( + key_warm=True, + original_value=0, + current_value=1, + new_value=0, + )(0, 0) + + Op.STOP + ) + inner = pre.deploy_contract(code=inner_code) + + # Middle's own SSTORE on slot 1 supplies the `state_gas_used` + # that inner's deferred credit lands on, then middle REVERTs. + middle_code = ( + Op.SSTORE(1, 1) + + Op.POP(call_opcode(gas=Op.GAS, address=inner)) + + Op.REVERT(0, 0) + ) + middle = pre.deploy_contract(code=middle_code) + + probe_code = Op.SSTORE(0, 1) + probe = pre.deploy_contract(code=probe_code) + + caller_storage = Storage() + caller_code = ( + Op.SSTORE(caller_storage.store_next(1, "caller_set_persists"), 1) + + Op.POP(call_opcode(gas=Op.GAS, address=middle)) + + Op.SSTORE( + caller_storage.store_next(1, "probe_must_succeed"), + Op.CALL(gas=probe_gas, address=probe), + ) + ) + caller = pre.deploy_contract(code=caller_code) + + # Block state gas commits: caller's slot-0 set + probe's + # SSTORE-set + caller's outer SSTORE-set on slot 1. Middle's + # own slot-1 set is washed by inner's deferred credit before + # middle reverts, so it does not propagate. Header gas_used + # is max(regular, state). + expected_regular = ( + intrinsic_cost + + caller_code.regular_cost(fork) + + middle_code.regular_cost(fork) + + inner_code.regular_cost(fork) + + probe_code.regular_cost(fork) + ) + expected_state = 3 * sstore_state_gas + expected_gas_used = max(expected_regular, expected_state) + + # Reservoir = 2 * sstore_state_gas covers caller's and middle's + # sets; the deferred credit refills middle by sstore_state_gas, + # which flows to the caller on revert. + tx = Transaction( + sender=pre.fund_eoa(), + to=caller, + gas_limit=gas_limit_cap + 2 * sstore_state_gas, + ) + + state_test( + pre=pre, + tx=tx, + post={caller: Account(storage=caller_storage)}, + blockchain_test_header_verify=Header(gas_used=expected_gas_used), + ) + + +@pytest.mark.with_all_create_opcodes +@pytest.mark.valid_from("EIP8037") +def test_sstore_restoration_create_init_revert( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + create_opcode: Op, +) -> None: + """ + Verify reservoir refunds return to the caller when CREATE init + code REVERTs inside a sub-frame that also REVERTs. + + Wrapping the CREATE in an outer reverting frame isolates the + rollback concern from the legitimate CREATE silent-failure refund + (`create_account_state_gas` credited to the frame executing the + CREATE opcode). When the outer frame reverts, the refunded + reservoir flows back to the caller via + `incorporate_child_on_error`, replenishing the caller's + reservoir by at least `sstore_state_gas`. A single-SSTORE probe + sized to OOG by 1 succeeds, confirming the propagation. + """ + gas_costs = fork.gas_costs() + # Probe SSTORE(0, 1): 2 pushes + cold storage write + state gas - 1, + # so it OOGs by 1 when the reservoir is 0 and succeeds otherwise. + probe_gas = ( + 2 * gas_costs.VERY_LOW + + gas_costs.COLD_STORAGE_WRITE + + Op.SSTORE(new_value=1).state_cost(fork) + - 1 + ) + + init_code = Op.SSTORE(0, 1) + Op.SSTORE(0, 0) + Op.REVERT(0, 0) + probe = pre.deploy_contract(code=Op.SSTORE(0, 1)) + + if create_opcode == Op.CREATE: + create_call = Op.CREATE(0, 0, len(init_code)) + else: + create_call = Op.CREATE2(0, 0, len(init_code), 0) + + # Inner contract performs the CREATE then REVERTs. + inner = pre.deploy_contract( + code=( + Op.MSTORE( + 0, + int.from_bytes(bytes(init_code), "big") + << (256 - 8 * len(init_code)), + ) + + Op.POP(create_call) + + Op.REVERT(0, 0) + ), + ) + + caller_storage = Storage() + caller = pre.deploy_contract( + code=( + Op.POP(Op.CALL(gas=Op.GAS, address=inner)) + + Op.SSTORE( + caller_storage.store_next(1, "probe_must_succeed"), + Op.CALL(gas=probe_gas, address=probe), + ) + ), + ) + + # gas_limit at the cap means the caller's reservoir starts at 0. + tx = Transaction( + to=caller, + gas_limit=fork.transaction_gas_limit_cap(), + sender=pre.fund_eoa(), + ) + + post = {caller: Account(storage=caller_storage)} + state_test(pre=pre, tx=tx, post=post) + + +@pytest.mark.with_all_create_opcodes +@pytest.mark.valid_from("EIP8037") +def test_sstore_restoration_create_init_success( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + create_opcode: Op, +) -> None: + """ + Verify 0 to x to 0 reservoir refund applies across CREATE init. + + Init code writes and clears slot 0, then returns empty runtime. + The CREATE succeeds (returns a nonzero address), confirming the + restoration path works inside init and the refund doesn't disturb + deployment. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + create_state_gas = fork.create_state_gas(code_size=0) + + init_code = ( + Op.SSTORE(0, 1) + + Op.SSTORE.with_metadata( + key_warm=True, + original_value=0, + current_value=1, + new_value=0, + )(0, 0) + + Op.RETURN(0, 0) + ) + + if create_opcode == Op.CREATE: + create_call = Op.CREATE(0, 0, len(init_code)) + else: + create_call = Op.CREATE2(0, 0, len(init_code), 0) + + caller_storage = Storage() + caller = pre.deploy_contract( + code=( + Op.MSTORE( + 0, + int.from_bytes(bytes(init_code), "big") + << (256 - 8 * len(init_code)), + ) + + Op.SSTORE( + caller_storage.store_next(True, "create_succeeded"), + Op.GT(create_call, 0), + ) + ), + ) + + tx = Transaction( + to=caller, + gas_limit=gas_limit_cap + create_state_gas + sstore_state_gas, + sender=pre.fund_eoa(), + ) + + post = {caller: Account(storage=caller_storage)} + state_test(pre=pre, tx=tx, post=post) + + +@pytest.mark.valid_from("EIP8037") +def test_sstore_restoration_reservoir_spillover( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify restoration refund when state gas spilled into gas_left. + + With tx.gas at the cap, reservoir is zero. SSTORE 0 to 1 state + gas comes from gas_left. At x to 0 the refund goes to + `state_gas_reservoir` (not back to gas_left), moving gas between + buckets. Block state gas is zero. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() + + code = Op.SSTORE(0, 1) + Op.SSTORE.with_metadata( + key_warm=True, + original_value=0, + current_value=1, + new_value=0, + )(0, 0) + tx_regular = intrinsic_gas + code.gas_cost(fork) - sstore_state_gas + + contract = pre.deploy_contract(code=code) + tx = Transaction( + to=contract, + gas_limit=gas_limit_cap, + sender=pre.fund_eoa(), + ) + + blockchain_test( + pre=pre, + blocks=[Block(txs=[tx], header_verify=Header(gas_used=tx_regular))], + post={contract: Account(storage={0: 0})}, + ) diff --git a/tests/berlin/eip2929_gas_cost_increases/test_call.py b/tests/berlin/eip2929_gas_cost_increases/test_call.py index 7322e1b1d2f..ab3e21d1d03 100644 --- a/tests/berlin/eip2929_gas_cost_increases/test_call.py +++ b/tests/berlin/eip2929_gas_cost_increases/test_call.py @@ -27,32 +27,32 @@ def test_call_insufficient_balance( """ destination = pre.fund_eoa(1) warm_code = Op.BALANCE(destination, address_warm=True) - contract_address = pre.deploy_contract( - # Perform the aborted external calls - Op.SSTORE( - 0, - Op.CALL( - gas=Op.GAS, - address=destination, - value=1, - args_offset=0, - args_size=0, - ret_offset=0, - ret_size=0, - ), - ) - # Measure the gas cost for BALANCE operation - + CodeGasMeasure( - code=warm_code, - extra_stack_items=1, # BALANCE puts balance on stack - sstore_key=1, + contract_code = Op.SSTORE( + 0, + Op.CALL( + gas=Op.GAS, + address=destination, + value=1, + args_offset=0, + args_size=0, + ret_offset=0, + ret_size=0, ), - balance=0, + ) + CodeGasMeasure( + code=warm_code, + extra_stack_items=1, # BALANCE puts balance on stack + sstore_key=1, ) + contract_address = pre.deploy_contract(contract_code, balance=0) + intrinsic_calc = fork.transaction_intrinsic_cost_calculator() tx = Transaction( to=contract_address, - gas_limit=100_000, + gas_limit=( + intrinsic_calc() + + contract_code.gas_cost(fork) + + Op.SSTORE(new_value=1).state_cost(fork) + ), sender=pre.fund_eoa(), ) diff --git a/tests/berlin/eip2930_access_list/test_acl.py b/tests/berlin/eip2930_access_list/test_acl.py index 7f0f3a82498..0564b93ad17 100644 --- a/tests/berlin/eip2930_access_list/test_acl.py +++ b/tests/berlin/eip2930_access_list/test_acl.py @@ -90,6 +90,8 @@ def test_account_storage_warm_cold_state( intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator() + # CodeGasMeasure SSTOREs the measured cost; budget for one + # first-time SSTORE whose state gas scales with cpsb on Amsterdam. tx_gas_limit = ( intrinsic_gas_calculator( calldata=tx_data, @@ -97,6 +99,7 @@ def test_account_storage_warm_cold_state( access_list=access_lists, ) + 100_000 + + Op.SSTORE(new_value=1).state_cost(fork) ) tx = Transaction( @@ -227,7 +230,7 @@ def test_transaction_intrinsic_gas_cost( access_lists: List[AccessList], enough_gas: bool, ) -> None: - """Test type 1 transaction.""" + """Test type 1 transaction intrinsic gas cost with access lists.""" env = Environment() contract_start_balance = 3 diff --git a/tests/byzantium/eip214_staticcall/test_staticcall.py b/tests/byzantium/eip214_staticcall/test_staticcall.py index 5c0999a6fc5..caf1f15c5f1 100644 --- a/tests/byzantium/eip214_staticcall/test_staticcall.py +++ b/tests/byzantium/eip214_staticcall/test_staticcall.py @@ -143,10 +143,21 @@ def test_staticcall_reentrant_call_to_precompile( target = pre.deploy_contract(code=target_code, balance=target_balance) tx_value = 100 + # The outer SSTORE (slot 0 = STATICCALL result) needs state work even + # though STATICCALL forwards 63/64 of remaining gas to the reentrant + # frame. Lift past the EIP-7825 cap so the EIP-8037 reservoir hosts + # the SSTORE state. + gas_cap = fork.transaction_gas_limit_cap() + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + if gas_cap is not None and sstore_state_gas > 0: + gas_limit = gas_cap + sstore_state_gas + else: + gas_limit = 1_000_000 + tx = Transaction( sender=alice, to=target, - gas_limit=1_000_000, + gas_limit=gas_limit, value=tx_value, protected=True, ) @@ -442,12 +453,22 @@ def test_staticcall_nested_call_to_precompile( account_expectations=account_expectations ) + # Six SSTOREs across A and B, plus CALL/STATICCALL forwarding 63/64 + # at each frame. Lift past the EIP-7825 cap so the EIP-8037 reservoir + # holds the SSTORE state work for both contracts. + gas_cap = fork.transaction_gas_limit_cap() + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + if gas_cap is not None and sstore_state_gas > 0: + gas_limit = gas_cap + 6 * sstore_state_gas + else: + gas_limit = 500_000 + state_test( pre=pre, tx=Transaction( sender=alice, to=contract_b, - gas_limit=500_000, + gas_limit=gas_limit, value=tx_value, protected=True, ), diff --git a/tests/cancun/create/test_create_oog_from_eoa_refunds.py b/tests/cancun/create/test_create_oog_from_eoa_refunds.py index 179efb0f22d..720cd7149f2 100644 --- a/tests/cancun/create/test_create_oog_from_eoa_refunds.py +++ b/tests/cancun/create/test_create_oog_from_eoa_refunds.py @@ -262,7 +262,10 @@ def test_create_oog_from_eoa_refunds( the CREATE failed and all state changes were reverted """ helpers = deploy_helper_contracts(pre) - sender = pre.fund_eoa(amount=4_000_000) + extra_gas = ( + fork.is_eip_enabled(8037) and oog_scenario == OogScenario.NO_OOG + ) + sender = pre.fund_eoa(amount=500_000_000 if extra_gas else 4_000_000) init_code = build_init_code(refund_type, oog_scenario, helpers) created_address = compute_create_address(address=sender, nonce=0) @@ -270,7 +273,9 @@ def test_create_oog_from_eoa_refunds( sender=sender, to=None, data=init_code, - gas_limit=400_000, + gas_limit=5_000_000 + if extra_gas and oog_scenario == OogScenario.NO_OOG + else 400_000, ) post: Dict[Address, Account | None] = { @@ -321,12 +326,16 @@ def test_create_oog_from_eoa_refunds( ) post[sender] = Account(nonce=1) else: - # OOG case: contract not created, sender balance is fully consumed + # OOG case: contract not created post[created_address] = Account.NONEXISTENT - post[sender] = Account( - nonce=1, - balance=0, - ) + if fork.is_eip_enabled(8037): + # EIP-8037: execution state gas is returned to the + # reservoir on top-level failure, so the sender retains + # some balance (the refunded state gas × gas_price). + post[sender] = Account(nonce=1) + else: + # Pre-EIP-8037: sender balance is fully consumed + post[sender] = Account(nonce=1, balance=0) if refund_type == RefundType.SELFDESTRUCT: selfdestruct_code = Op.SELFDESTRUCT(Op.ORIGIN) + Op.STOP diff --git a/tests/cancun/eip1153_tstore/test_tstorage_clear_after_tx.py b/tests/cancun/eip1153_tstore/test_tstorage_clear_after_tx.py index 9c59480a507..77b6bc20d00 100644 --- a/tests/cancun/eip1153_tstore/test_tstorage_clear_after_tx.py +++ b/tests/cancun/eip1153_tstore/test_tstorage_clear_after_tx.py @@ -7,11 +7,11 @@ Block, BlockchainTestFiller, Environment, + Fork, Initcode, Op, Transaction, ) -from execution_testing.forks.helpers import Fork from .spec import ref_spec_1153 @@ -22,8 +22,8 @@ @pytest.mark.valid_from("Cancun") def test_tstore_clear_after_deployment_tx( blockchain_test: BlockchainTestFiller, - pre: Alloc, fork: Fork, + pre: Alloc, ) -> None: """ First creates a contract, which TSTOREs a value 1 in slot 1. After creating @@ -40,8 +40,12 @@ def test_tstore_clear_after_deployment_tx( sender = pre.fund_eoa() + gas_limit = 100_000 + if fork.is_eip_enabled(8037): + gas_limit = 500_000 + deployment_tx = Transaction( - gas_limit=100000, + gas_limit=gas_limit, data=code, to=None, sender=sender, @@ -50,7 +54,9 @@ def test_tstore_clear_after_deployment_tx( address = deployment_tx.created_contract invoke_contract_tx = Transaction( - gas_limit=100000, to=address, sender=sender + gas_limit=gas_limit, + to=address, + sender=sender, ) txs = [deployment_tx, invoke_contract_tx] diff --git a/tests/cancun/eip1153_tstore/test_tstorage_create_contexts.py b/tests/cancun/eip1153_tstore/test_tstorage_create_contexts.py index 6ee241be0fc..f382fcaba45 100644 --- a/tests/cancun/eip1153_tstore/test_tstorage_create_contexts.py +++ b/tests/cancun/eip1153_tstore/test_tstorage_create_contexts.py @@ -328,11 +328,21 @@ def test_tstore_rollback_on_failed_create( ) caller_address = pre.deploy_contract(caller_code, storage={0: 1, 1: 1}) + gas_limit = 16_000_000 + if fork.is_eip_enabled(8037): + gas_limit_cap = fork.transaction_gas_limit_cap() or gas_limit + code_deposit_state = fork.code_deposit_state_gas( + code_size=max_code_size + 0x0A + ) + new_account_state = fork.gas_costs().NEW_ACCOUNT + state_gas = 2 * (code_deposit_state + new_account_state) + gas_limit = gas_limit_cap + state_gas + sender = pre.fund_eoa() tx = Transaction( sender=sender, to=caller_address, - gas_limit=16_000_000, + gas_limit=gas_limit, access_list=[ AccessList(address=caller_address, storage_keys=[0, 1]), ], diff --git a/tests/cancun/eip4788_beacon_root/test_beacon_root_contract.py b/tests/cancun/eip4788_beacon_root/test_beacon_root_contract.py index 2462bbde4e7..dc4888003f8 100644 --- a/tests/cancun/eip4788_beacon_root/test_beacon_root_contract.py +++ b/tests/cancun/eip4788_beacon_root/test_beacon_root_contract.py @@ -27,6 +27,7 @@ Block, BlockchainTestFiller, Bytecode, + Fork, Hash, Op, Storage, @@ -318,6 +319,7 @@ def test_beacon_root_selfdestruct( beacon_root: bytes, timestamp: int, pre: Alloc, + fork: Fork, tx: Transaction, post: Dict, ) -> None: @@ -331,15 +333,20 @@ def test_beacon_root_selfdestruct( balance=0xBA1, ) # self destruct caller + selfdestruct_call_forwarded_gas = 100_000 + self_destruct_caller_code = Op.CALL( + gas=selfdestruct_call_forwarded_gas, + address=self_destruct_actor_address, + ) + Op.SSTORE(0, Op.BALANCE(Spec.BEACON_ROOTS_ADDRESS)) self_destruct_caller_address = pre.deploy_contract( - Op.CALL(gas=100_000, address=self_destruct_actor_address) - + Op.SSTORE(0, Op.BALANCE(Spec.BEACON_ROOTS_ADDRESS)) + self_destruct_caller_code ) post = { self_destruct_caller_address: Account( storage=Storage({0: 0xBA1}), # type: ignore ) } + intrinsic_calc = fork.transaction_intrinsic_cost_calculator() blockchain_test( pre=pre, blocks=[ @@ -348,7 +355,14 @@ def test_beacon_root_selfdestruct( Transaction( sender=pre.fund_eoa(), to=self_destruct_caller_address, - gas_limit=100_000, + # Caller's static cost + forwarded inner gas + EIP-1706 + # stipend slack on the trailing SSTORE. + gas_limit=( + intrinsic_calc() + + self_destruct_caller_code.gas_cost(fork) + + selfdestruct_call_forwarded_gas + + Op.SSTORE(new_value=1).state_cost(fork) + ), ) ] ) @@ -402,6 +416,7 @@ def test_beacon_root_selfdestruct( def test_multi_block_beacon_root_timestamp_calls( blockchain_test: BlockchainTestFiller, pre: Alloc, + fork: Fork, timestamps_factory: Callable[[], Iterator[int]], beacon_roots: Iterator[bytes], block_count: int, @@ -436,6 +451,7 @@ def test_multi_block_beacon_root_timestamp_calls( all_timestamps: List[int] = [] sender = pre.fund_eoa() + intrinsic_calc = fork.transaction_intrinsic_cost_calculator() for timestamp, beacon_root, _i in zip( timestamps, @@ -494,6 +510,14 @@ def test_multi_block_beacon_root_timestamp_calls( post[current_call_account_address] = Account( storage=current_call_account_expected_storage, ) + # Bytecode's regular+state cost + N forwarded call_gas envelopes + # (one per `t` in all_timestamps) + EIP-1706 stipend slack. + block_gas_limit = ( + intrinsic_calc(calldata=Hash(timestamp)) + + current_call_account_code.gas_cost(fork) + + len(all_timestamps) * call_gas + + Op.SSTORE(new_value=1).state_cost(fork) + ) blocks.append( Block( txs=[ @@ -501,7 +525,7 @@ def test_multi_block_beacon_root_timestamp_calls( sender=sender, to=current_call_account_address, data=Hash(timestamp), - gas_limit=1_000_000, + gas_limit=block_gas_limit, ) ], parent_beacon_block_root=beacon_root, diff --git a/tests/cancun/eip4844_blobs/test_blobhash_opcode.py b/tests/cancun/eip4844_blobs/test_blobhash_opcode.py index fdf1d87e1b7..b8b5733a4c6 100644 --- a/tests/cancun/eip4844_blobs/test_blobhash_opcode.py +++ b/tests/cancun/eip4844_blobs/test_blobhash_opcode.py @@ -265,6 +265,10 @@ def test_blobhash_scenarios( ) sender = pre.fund_eoa() + gas_limit = 500_000 + if fork.is_eip_enabled(8037): + gas_limit = 5_000_000 + blocks: List[Block] = [] post = {} for i in range(total_blocks): @@ -277,7 +281,7 @@ def test_blobhash_scenarios( sender=sender, to=address, data=Hash(0), - gas_limit=500_000, + gas_limit=gas_limit, access_list=[], max_fee_per_blob_gas=( fork.min_base_fee_per_blob_gas() * 10 @@ -329,6 +333,11 @@ def test_blobhash_invalid_blob_index( scenario_name=scenario, max_blobs_per_tx=max_blobs_per_tx ) sender = pre.fund_eoa() + + gas_limit = 500_000 + if fork.is_eip_enabled(8037): + gas_limit = 5_000_000 + blocks: List[Block] = [] post = {} for i in range(total_blocks): @@ -342,7 +351,7 @@ def test_blobhash_invalid_blob_index( ty=Spec.BLOB_TX_TYPE, sender=sender, to=address, - gas_limit=500_000, + gas_limit=gas_limit, data=Hash(0), access_list=[], max_fee_per_blob_gas=( @@ -390,13 +399,17 @@ def test_blobhash_multiple_txs_in_block( addresses = [pre.deploy_contract(blobhash_bytecode) for _ in range(4)] sender = pre.fund_eoa() + gas_limit = 500_000 + if fork.is_eip_enabled(8037): + gas_limit = 5_000_000 + def blob_tx(address: Address, tx_type: int) -> Transaction: return Transaction( ty=tx_type, sender=sender, to=address, data=Hash(0), - gas_limit=500_000, + gas_limit=gas_limit, access_list=[] if tx_type >= 1 else None, max_fee_per_blob_gas=(fork.min_base_fee_per_blob_gas() * 10) if tx_type >= 3 diff --git a/tests/cancun/eip4844_blobs/test_blobhash_opcode_contexts.py b/tests/cancun/eip4844_blobs/test_blobhash_opcode_contexts.py index 02235e4edd1..8c9ab56b99f 100644 --- a/tests/cancun/eip4844_blobs/test_blobhash_opcode_contexts.py +++ b/tests/cancun/eip4844_blobs/test_blobhash_opcode_contexts.py @@ -90,6 +90,7 @@ def deploy_contract( indexes: The indexes to request using the BLOBHASH opcode """ + indexes = list(indexes) match self: case ( BlobhashContext.BLOBHASH_SSTORE @@ -312,12 +313,19 @@ def test_blobhash_opcode_contexts( case _: raise Exception(f"Unknown test case {test_case}") + # Budget covers all branches (simple SSTOREs, CREATE / CREATE2 + # initcode + deploy) plus per-blob SSTOREs whose state cost + # scales with cpsb under EIP-8037 (`sstore_state_gas()` is 0 + # otherwise). + gas_limit = 500_000 + max_blobs_per_tx * Op.SSTORE(new_value=1).state_cost( + fork + ) state_test( pre=pre, tx=Transaction( ty=Spec.BLOB_TX_TYPE, to=tx_to, - gas_limit=500_000, + gas_limit=gas_limit, max_fee_per_blob_gas=fork.min_base_fee_per_blob_gas() * 10, blob_versioned_hashes=simple_blob_hashes, sender=pre.fund_eoa(), @@ -333,16 +341,12 @@ def test_blobhash_opcode_contexts_tx_types( state_test: StateTestFiller, ) -> None: """ - Tests that the `BLOBHASH` opcode functions correctly when called in - different contexts. + Test that the `BLOBHASH` opcode returns zero in non-blob transaction + types. - - `BLOBHASH` opcode on the top level of the call stack. - - `BLOBHASH` opcode on the max value. - - `BLOBHASH` opcode on `CALL`, `DELEGATECALL`, `STATICCALL`, and - `CALLCODE`. - - `BLOBHASH` opcode on Initcode. - - `BLOBHASH` opcode on `CREATE` and `CREATE2`. - - `BLOBHASH` opcode on transaction types 0, 1 and 2. + Verify BLOBHASH behavior across transaction types 0, 1, and 2 in + various calling contexts including top-level, CALL, DELEGATECALL, + STATICCALL, CALLCODE, initcode, CREATE, and CREATE2. """ blobhash_sstore_address = BlobhashContext.BLOBHASH_SSTORE.deploy_contract( pre=pre, indexes=[0] diff --git a/tests/cancun/eip4844_blobs/test_excess_blob_gas.py b/tests/cancun/eip4844_blobs/test_excess_blob_gas.py index 5d6c14a10e8..f34cf973971 100644 --- a/tests/cancun/eip4844_blobs/test_excess_blob_gas.py +++ b/tests/cancun/eip4844_blobs/test_excess_blob_gas.py @@ -106,7 +106,9 @@ def tx_blob_data_cost( @pytest.fixture -def tx_gas_limit() -> int: # noqa: D103 +def tx_gas_limit(fork: Fork) -> int: # noqa: D103 + if fork.is_eip_enabled(8037): + return 500_000 return 45000 diff --git a/tests/cancun/eip5656_mcopy/test_mcopy.py b/tests/cancun/eip5656_mcopy/test_mcopy.py index 57177d8b819..93cd8b4d34f 100644 --- a/tests/cancun/eip5656_mcopy/test_mcopy.py +++ b/tests/cancun/eip5656_mcopy/test_mcopy.py @@ -11,6 +11,7 @@ Alloc, Bytecode, Environment, + Fork, Hash, Op, StateTestFiller, @@ -115,13 +116,20 @@ def code_address(pre: Alloc, code_bytecode: Bytecode) -> Address: @pytest.fixture def tx( # noqa: D103 - pre: Alloc, code_address: Address, dest: int, src: int, length: int + pre: Alloc, + fork: Fork, + code_address: Address, + dest: int, + src: int, + length: int, ) -> Transaction: + # The test SSTOREs each memory word it reads, so budget for ~10 + # first-time SSTOREs whose state gas scales with cpsb on Amsterdam. return Transaction( sender=pre.fund_eoa(), to=code_address, data=Hash(dest) + Hash(src) + Hash(length), - gas_limit=1_000_000, + gas_limit=1_000_000 + 10 * Op.SSTORE(new_value=1).state_cost(fork), ) @@ -231,6 +239,7 @@ def test_valid_mcopy_operations( def test_mcopy_repeated( state_test: StateTestFiller, pre: Alloc, + fork: Fork, dest: int, src: int, length: int, @@ -294,7 +303,7 @@ def test_mcopy_repeated( sender=pre.fund_eoa(), to=contract, data=Hash(dest) + Hash(src) + Hash(length), - gas_limit=1_000_000, + gas_limit=1_000_000 + 2 * Op.SSTORE(new_value=1).state_cost(fork), ), ) diff --git a/tests/cancun/eip5656_mcopy/test_mcopy_contexts.py b/tests/cancun/eip5656_mcopy/test_mcopy_contexts.py index 1bacbb0a9c5..ff7d369e96c 100644 --- a/tests/cancun/eip5656_mcopy/test_mcopy_contexts.py +++ b/tests/cancun/eip5656_mcopy/test_mcopy_contexts.py @@ -14,6 +14,7 @@ Alloc, Bytecode, Environment, + Fork, Op, StateTestFiller, Storage, @@ -138,11 +139,14 @@ def callee_address(pre: Alloc, callee_bytecode: Bytecode) -> Address: # noqa: D @pytest.fixture -def tx(pre: Alloc, caller_address: Address) -> Transaction: # noqa: D103 +def tx(pre: Alloc, fork: Fork, caller_address: Address) -> Transaction: # noqa: D103 + gas_limit = 1_000_000 + if fork.is_eip_enabled(8037): + gas_limit = 5_000_000 return Transaction( sender=pre.fund_eoa(), to=caller_address, - gas_limit=1_000_000, + gas_limit=gas_limit, ) diff --git a/tests/cancun/eip5656_mcopy/test_mcopy_memory_expansion.py b/tests/cancun/eip5656_mcopy/test_mcopy_memory_expansion.py index 23f32896ed1..0502a645563 100644 --- a/tests/cancun/eip5656_mcopy/test_mcopy_memory_expansion.py +++ b/tests/cancun/eip5656_mcopy/test_mcopy_memory_expansion.py @@ -128,14 +128,19 @@ def tx( # noqa: D103 initial_memory: bytes, tx_gas_limit: int, tx_access_list: List[AccessList], + successful: bool, + fork: Fork, ) -> Transaction: + expected_gas = tx_gas_limit + if not successful and fork.is_eip_enabled(8037): + expected_gas -= Op.SSTORE(new_value=1).state_cost(fork) return Transaction( sender=sender, to=caller_address, access_list=tx_access_list, data=initial_memory, gas_limit=tx_gas_limit, - expected_receipt=TransactionReceipt(cumulative_gas_used=tx_gas_limit), + expected_receipt=TransactionReceipt(cumulative_gas_used=expected_gas), ) diff --git a/tests/cancun/eip6780_selfdestruct/test_dynamic_create2_selfdestruct_collision.py b/tests/cancun/eip6780_selfdestruct/test_dynamic_create2_selfdestruct_collision.py index 1dd3f72bd68..4fc6232dc95 100644 --- a/tests/cancun/eip6780_selfdestruct/test_dynamic_create2_selfdestruct_collision.py +++ b/tests/cancun/eip6780_selfdestruct/test_dynamic_create2_selfdestruct_collision.py @@ -88,6 +88,9 @@ def test_dynamic_create2_selfdestruct_collision( # Constants address_zero = Address(0x00) create2_salt = 1 + subcall_gas = 100_000 + if fork.is_eip_enabled(8037): + subcall_gas = 500_000 # Create EOA for sendall destination (receives selfdestruct funds) sendall_destination = pre.fund_eoa(0) # Will be funded by selfdestruct @@ -141,7 +144,7 @@ def test_dynamic_create2_selfdestruct_collision( # Make a subcall that do CREATE2 and returns its the result + Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE()) + Op.CALL( - 100000, + subcall_gas, address_code, first_create2_value, 0, @@ -154,16 +157,16 @@ def test_dynamic_create2_selfdestruct_collision( Op.MLOAD(0), ) # In case the create2 didn't work, flush account balance - + Op.CALL(100000, address_code, 0, 0, 0, 0, 0) + + Op.CALL(subcall_gas, address_code, 0, 0, 0, 0, 0) # Call to the created account to trigger selfdestruct + Op.CALL( - 100000, call_address_in_between, first_call_value, 0, 0, 0, 0 + subcall_gas, call_address_in_between, first_call_value, 0, 0, 0, 0 ) # Make a subcall that do CREATE2 collision and returns its address as # the result + Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE()) + Op.CALL( - 100000, + subcall_gas, address_code, second_create2_value, 0, @@ -177,7 +180,7 @@ def test_dynamic_create2_selfdestruct_collision( ) # Call to the created account to trigger selfdestruct + Op.CALL( - 100000, call_address_in_the_end, second_call_value, 0, 0, 0, 0 + subcall_gas, call_address_in_the_end, second_call_value, 0, 0, 0, 0 ) + Op.SSTORE(code_worked, 1), balance=100000000, @@ -313,6 +316,9 @@ def test_dynamic_create2_selfdestruct_collision_two_different_transactions( # Constants address_zero = Address(0x00) create2_salt = 1 + subcall_gas = 100_000 + if fork.is_eip_enabled(8037): + subcall_gas = 500_000 # Create EOA for sendall destination (receives selfdestruct funds) sendall_destination = pre.fund_eoa(0) # Will be funded by selfdestruct @@ -363,7 +369,7 @@ def test_dynamic_create2_selfdestruct_collision_two_different_transactions( # Make a subcall that do CREATE2 and returns its the result + Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE()) + Op.CALL( - 100000, + subcall_gas, address_code, first_create2_value, 0, @@ -376,9 +382,9 @@ def test_dynamic_create2_selfdestruct_collision_two_different_transactions( Op.MLOAD(0), ) # In case the create2 didn't work, flush account balance - + Op.CALL(100000, address_code, 0, 0, 0, 0, 0) + + Op.CALL(subcall_gas, address_code, 0, 0, 0, 0, 0) # Call to the created account to trigger selfdestruct - + Op.CALL(100000, create2_address, first_call_value, 0, 0, 0, 0) + + Op.CALL(subcall_gas, create2_address, first_call_value, 0, 0, 0, 0) + Op.SSTORE(code_worked, 1), balance=100000000, storage={first_create2_result: 0xFF}, @@ -391,7 +397,7 @@ def test_dynamic_create2_selfdestruct_collision_two_different_transactions( # the result + Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE()) + Op.CALL( - 100000, + subcall_gas, address_code, second_create2_value, 0, @@ -587,6 +593,9 @@ def test_dynamic_create2_selfdestruct_collision_multi_tx( # Constants create2_salt = 1 + subcall_gas = 100_000 + if fork.is_eip_enabled(8037): + subcall_gas = 500_000 # Create EOA for sendall destination (receives selfdestruct funds) sendall_destination = pre.fund_eoa(0) # Will be funded by selfdestruct @@ -636,7 +645,7 @@ def test_dynamic_create2_selfdestruct_collision_multi_tx( # Make a subcall that do CREATE2 and returns its the result + Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE()) + Op.CALL( - 100000, + subcall_gas, address_code, first_create2_value, 0, @@ -653,12 +662,12 @@ def test_dynamic_create2_selfdestruct_collision_multi_tx( if selfdestruct_on_first_tx: first_tx_code += ( # Call to the created account to trigger selfdestruct - Op.CALL(100000, create2_address, first_call_value, 0, 0, 0, 0) + Op.CALL(subcall_gas, create2_address, first_call_value, 0, 0, 0, 0) ) else: second_tx_code += ( # Call to the created account to trigger selfdestruct - Op.CALL(100000, create2_address, first_call_value, 0, 0, 0, 0) + Op.CALL(subcall_gas, create2_address, first_call_value, 0, 0, 0, 0) ) if recreate_on_first_tx: @@ -667,7 +676,7 @@ def test_dynamic_create2_selfdestruct_collision_multi_tx( # as the result Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE()) + Op.CALL( - 100000, + subcall_gas, address_code, second_create2_value, 0, @@ -687,7 +696,7 @@ def test_dynamic_create2_selfdestruct_collision_multi_tx( # as the result Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE()) + Op.CALL( - 100000, + subcall_gas, address_code, second_create2_value, 0, @@ -703,7 +712,7 @@ def test_dynamic_create2_selfdestruct_collision_multi_tx( # Second tx code always calls the create2 contract at the end second_tx_code += Op.CALL( - 100000, create2_address, second_call_value, 0, 0, 0, 0 + subcall_gas, create2_address, second_call_value, 0, 0, 0, 0 ) first_tx_code += Op.SSTORE(part_1_worked, 1) diff --git a/tests/cancun/eip6780_selfdestruct/test_reentrancy_selfdestruct_revert.py b/tests/cancun/eip6780_selfdestruct/test_reentrancy_selfdestruct_revert.py index 6179045a240..bea4490a985 100644 --- a/tests/cancun/eip6780_selfdestruct/test_reentrancy_selfdestruct_revert.py +++ b/tests/cancun/eip6780_selfdestruct/test_reentrancy_selfdestruct_revert.py @@ -258,10 +258,13 @@ def test_reentrancy_selfdestruct_revert( ) expected_receipt = TransactionReceipt(logs=expected_logs) + gas_limit = 500_000 + if fork.is_eip_enabled(8037): + gas_limit = 5_000_000 tx = Transaction( sender=sender, to=executor_contract_address, - gas_limit=500_000, + gas_limit=gas_limit, value=0, expected_receipt=expected_receipt, ) diff --git a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py index 07a42652b2d..934c904aa1b 100644 --- a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py +++ b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py @@ -373,12 +373,15 @@ def test_create_selfdestruct_same_tx( # retain the stored values for verification. entry_code += Op.RETURN(max(len(selfdestruct_contract_initcode), 32), 1) + gas_limit = 500_000 + if fork.is_eip_enabled(8037): + gas_limit = 5_000_000 tx = Transaction( value=entry_code_balance, data=entry_code, sender=sender, to=None, - gas_limit=500_000, + gas_limit=gas_limit, ) assert tx.created_contract == entry_code_address @@ -519,12 +522,15 @@ def test_self_destructing_initcode( selfdestruct_contract_initial_balance, ) + gas_limit = 500_000 + if fork.is_eip_enabled(8037): + gas_limit = 5_000_000 tx = Transaction( value=entry_code_balance, data=entry_code, sender=sender, to=None, - gas_limit=500_000, + gas_limit=gas_limit, ) entry_code_address = tx.created_contract @@ -599,12 +605,15 @@ def test_self_destructing_initcode_create_tx( - Different initial balances for the self-destructing contract - Different transaction value amounts """ + gas_limit = 500_000 + if fork.is_eip_enabled(8037): + gas_limit = 5_000_000 tx = Transaction( sender=sender, value=tx_value, data=selfdestruct_code, to=None, - gas_limit=500_000, + gas_limit=gas_limit, ) selfdestruct_contract_address = tx.created_contract if selfdestruct_contract_initial_balance > 0: @@ -749,6 +758,9 @@ def test_recreate_self_destructed_contract_different_txs( if addr == SELF_ADDRESS: sendall_recipient_addresses[i] = selfdestruct_contract_address + gas_limit = 500_000 + if fork.is_eip_enabled(8037): + gas_limit = 5_000_000 txs: List[Transaction] = [] for i in range(recreate_times + 1): expected_receipt = None @@ -783,7 +795,7 @@ def test_recreate_self_destructed_contract_different_txs( data=Hash(i), sender=sender, to=entry_code_address, - gas_limit=500_000, + gas_limit=gas_limit, expected_receipt=expected_receipt, ) ) @@ -997,12 +1009,15 @@ def test_selfdestruct_pre_existing( # retain the stored values for verification. entry_code += Op.RETURN(32, 1) + gas_limit = 500_000 + if fork.is_eip_enabled(8037): + gas_limit = 5_000_000 tx = Transaction( value=entry_code_balance, data=entry_code, sender=sender, to=None, - gas_limit=500_000, + gas_limit=gas_limit, ) assert tx.created_contract == entry_code_address @@ -1166,13 +1181,16 @@ def test_selfdestruct_created_same_block_different_tx( running_balance = 0 tx2_receipt = TransactionReceipt(logs=tx2_logs) + gas_limit = 500_000 + if fork.is_eip_enabled(8037): + gas_limit = 5_000_000 txs = [ Transaction( value=selfdestruct_contract_initial_balance, data=selfdestruct_contract_initcode, sender=sender, to=None, - gas_limit=500_000, + gas_limit=gas_limit, expected_receipt=tx1_receipt, ), Transaction( @@ -1180,7 +1198,7 @@ def test_selfdestruct_created_same_block_different_tx( data=entry_code, sender=sender, to=None, - gas_limit=500_000, + gas_limit=gas_limit, expected_receipt=tx2_receipt, ), ] @@ -1323,12 +1341,15 @@ def test_calling_from_new_contract_to_pre_existing_contract( ), } + gas_limit = 500_000 + if fork.is_eip_enabled(8037): + gas_limit = 5_000_000 tx = Transaction( value=entry_code_balance, data=entry_code, sender=sender, to=None, - gas_limit=500_000, + gas_limit=gas_limit, ) if fork.is_eip_enabled(7708): @@ -1487,12 +1508,15 @@ def test_calling_from_pre_existing_contract_to_new_contract( # retain the stored values for verification. entry_code += Op.RETURN(max(len(selfdestruct_contract_initcode), 32), 1) + gas_limit = 500_000 + if fork.is_eip_enabled(8037): + gas_limit = 5_000_000 tx = Transaction( value=entry_code_balance, data=entry_code, sender=sender, to=None, - gas_limit=500_000, + gas_limit=gas_limit, ) entry_code_address = tx.created_contract @@ -1732,12 +1756,15 @@ def test_create_selfdestruct_same_tx_increased_nonce( # retain the stored values for verification. entry_code += Op.RETURN(max(len(selfdestruct_contract_initcode), 32), 1) + gas_limit = 1_000_000 + if fork.is_eip_enabled(8037): + gas_limit = 5_000_000 tx = Transaction( value=entry_code_balance, data=entry_code, sender=sender, to=None, - gas_limit=1_000_000, + gas_limit=gas_limit, ) assert tx.created_contract == entry_code_address @@ -1878,12 +1905,15 @@ def test_create_and_destroy_multiple_contracts_same_tx( entry_code += Op.RETURN(32, 1) + gas_limit = 1_000_000 + if fork.is_eip_enabled(8037): + gas_limit = 5_000_000 tx = Transaction( value=0, data=entry_code, sender=sender, to=None, - gas_limit=1_000_000, + gas_limit=gas_limit, ) post: Dict[Address, Account] = { @@ -2054,17 +2084,22 @@ def test_create_multiple_contracts_destroy_one_then_destroy_other_next_tx( ) tx2_receipt = TransactionReceipt(logs=tx2_logs) + # tx1 does 2 CREATE2 (NEW_ACCOUNT each) plus several first-time + # SSTOREs across entry/init code; tx2 does one SSTORE call. + # Bump scales with cpsb on Amsterdam. + new_account = fork.gas_costs().NEW_ACCOUNT + sstore_state = Op.SSTORE(new_value=1).gas_cost(fork) txs = [ Transaction( sender=sender, to=entry_code_address, - gas_limit=1_000_000, + gas_limit=1_000_000 + 2 * new_account + 6 * sstore_state, expected_receipt=tx1_receipt, ), Transaction( sender=sender, to=tx2_caller, - gas_limit=500_000, + gas_limit=500_000 + sstore_state, expected_receipt=tx2_receipt, ), ] @@ -2187,12 +2222,29 @@ def test_parent_creates_child_selfdestruct_one( entry_code += Op.RETURN(32, 1) + intrinsic_calc = fork.transaction_intrinsic_cost_calculator() + # Three frames execute under this tx: + # 1. entry_code (the contract-creation initcode of the tx) + # 2. parent_code (called by entry) + # 3. child_code (created by parent and, when !destroy_parent, called + # by parent) + # Each CREATE incurs NEW_ACCOUNT state once. SSTORE regular costs + # are picked up by each bytecode's `gas_cost(fork)`; the trailing + # SSTORE `gas_cost(fork)` adds headroom for the EIP-8037 state-gas + # charge on the 0->nonzero SSTORE the static calc cannot infer. tx = Transaction( value=0, data=entry_code, sender=sender, to=None, - gas_limit=1_000_000, + gas_limit=( + intrinsic_calc(calldata=entry_code, contract_creation=True) + + entry_code.gas_cost(fork) + + parent_code.gas_cost(fork) + + child_code.gas_cost(fork) + + 2 * fork.gas_costs().NEW_ACCOUNT + + Op.SSTORE(new_value=1).gas_cost(fork) + ), ) post: Dict[Address, Account] = { diff --git a/tests/cancun/eip6780_selfdestruct/test_selfdestruct_revert.py b/tests/cancun/eip6780_selfdestruct/test_selfdestruct_revert.py index 4892d1dfe11..42854604177 100644 --- a/tests/cancun/eip6780_selfdestruct/test_selfdestruct_revert.py +++ b/tests/cancun/eip6780_selfdestruct/test_selfdestruct_revert.py @@ -428,12 +428,15 @@ def test_selfdestruct_created_in_same_tx_with_revert( # noqa SC200 ) post[selfdestruct_recipient_address] = Account.NONEXISTENT # type: ignore + gas_limit = 500_000 + if fork.is_eip_enabled(8037): + gas_limit = 5_000_000 tx = Transaction( value=0, data=entry_code, sender=sender, to=None, - gas_limit=500_000, + gas_limit=gas_limit, ) expected_block_access_list = None @@ -529,6 +532,7 @@ def test_selfdestruct_created_in_same_tx_with_revert( # noqa SC200 @pytest.mark.valid_from("Cancun") def test_selfdestruct_not_created_in_same_tx_with_revert( state_test: StateTestFiller, + fork: Fork, sender: EOA, env: Environment, entry_code_address: Address, @@ -592,12 +596,15 @@ def test_selfdestruct_not_created_in_same_tx_with_revert( ) post[selfdestruct_recipient_address] = Account.NONEXISTENT # type: ignore + gas_limit = 500_000 + if fork.is_eip_enabled(8037): + gas_limit = 5_000_000 tx = Transaction( value=0, data=entry_code, sender=sender, to=None, - gas_limit=500_000, + gas_limit=gas_limit, ) state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/common/precompile_fixtures.py b/tests/common/precompile_fixtures.py index 6b134321f94..8831668ecba 100644 --- a/tests/common/precompile_fixtures.py +++ b/tests/common/precompile_fixtures.py @@ -183,7 +183,10 @@ def tx_gas_limit(fork: Fork, input_data: bytes, precompile_gas: int) -> int: fork.transaction_intrinsic_cost_calculator() ) memory_expansion_gas_calculator = fork.memory_expansion_gas_calculator() - extra_gas = 100_000 + # `call_contract_code` performs up to 3 SSTOREs per call + # (succeeds-flag, output-length, output-hash); under EIP-8037 + # each adds `sstore_state_gas()` of state work (0 otherwise). + extra_gas = 100_000 + 3 * Op.SSTORE(new_value=1).state_cost(fork) return ( extra_gas + intrinsic_gas_cost_calculator(calldata=input_data) diff --git a/tests/constantinople/eip1014_create2/test_create2_revert.py b/tests/constantinople/eip1014_create2/test_create2_revert.py index c611d06f4bd..b8a0fdbc654 100644 --- a/tests/constantinople/eip1014_create2/test_create2_revert.py +++ b/tests/constantinople/eip1014_create2/test_create2_revert.py @@ -7,6 +7,7 @@ Account, Alloc, Environment, + Fork, Initcode, Op, StateTestFiller, @@ -87,6 +88,7 @@ def test_create2_revert_preserves_balance( def test_create2_succeeds_after_reverted_create2( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """ Test that CREATE2 succeeds after a previous CREATE2 at the same address @@ -99,6 +101,9 @@ def test_create2_succeeds_after_reverted_create2( storage = Storage() salt = 1 + new_account = fork.gas_costs().NEW_ACCOUNT + sstore_state = Op.SSTORE(new_value=1).state_cost(fork) + runtime_code = Op.SSTORE(0, 1) + Op.STOP initcode = Initcode(deploy_code=runtime_code) @@ -129,7 +134,7 @@ def test_create2_succeeds_after_reverted_create2( Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) + Op.POP( Op.CALL( - gas=200_000, + gas=200_000 + new_account + sstore_state, address=creator, args_size=Op.CALLDATASIZE, ) @@ -144,7 +149,7 @@ def test_create2_succeeds_after_reverted_create2( + Op.SSTORE( storage.store_next(0, "reverter_call_result"), Op.CALL( - gas=300_000, + gas=300_000 + new_account + sstore_state, address=reverter, args_size=Op.CALLDATASIZE, ), @@ -153,7 +158,7 @@ def test_create2_succeeds_after_reverted_create2( + Op.SSTORE( storage.store_next(1, "creator_call_result"), Op.CALL( - gas=300_000, + gas=300_000 + new_account + sstore_state, address=creator, args_size=Op.CALLDATASIZE, ), @@ -177,7 +182,7 @@ def test_create2_succeeds_after_reverted_create2( tx=Transaction( sender=sender, to=outer, - gas_limit=2_000_000, + gas_limit=2_000_000 + 2 * (new_account + sstore_state), data=initcode, ), ) diff --git a/tests/constantinople/eip1014_create2/test_deterministic_deployment.py b/tests/constantinople/eip1014_create2/test_deterministic_deployment.py index 5e8ae9f19c2..f7c502fdec1 100644 --- a/tests/constantinople/eip1014_create2/test_deterministic_deployment.py +++ b/tests/constantinople/eip1014_create2/test_deterministic_deployment.py @@ -9,6 +9,7 @@ Alloc, Block, BlockchainTestFiller, + Fork, Hash, Op, Transaction, @@ -24,6 +25,7 @@ def test_deterministic_deployment( blockchain_test: BlockchainTestFiller, pre: Alloc, + fork: Fork, ) -> None: """ Test deterministic deployments for contracts using @@ -37,17 +39,27 @@ def test_deterministic_deployment( sender = pre.fund_eoa() + intrinsic_calc = fork.transaction_intrinsic_cost_calculator() + # Sized for the set-tx (Hash(1) calldata, with a nonzero byte) since + # its intrinsic is the larger of the two; `deploy_code.gas_cost(fork)` + # defaults SSTORE to cold zero->non-zero which slightly over-estimates + # the reset-tx (already-zero) — harmless. + tx_gas = ( + intrinsic_calc(calldata=Hash(1)) + + deploy_code.gas_cost(fork) + + Op.SSTORE(new_value=1).state_cost(fork) + ) reset_tx = Transaction( sender=sender, to=contract_address, data=Hash(0), - gas_limit=100_000, + gas_limit=tx_gas, ) set_tx = Transaction( sender=sender, to=contract_address, data=Hash(1), - gas_limit=100_000, + gas_limit=tx_gas, ) post = { diff --git a/tests/constantinople/eip1052_extcodehash/test_extcodehash.py b/tests/constantinople/eip1052_extcodehash/test_extcodehash.py index 72bdd7536e1..13373a5bafa 100644 --- a/tests/constantinople/eip1052_extcodehash/test_extcodehash.py +++ b/tests/constantinople/eip1052_extcodehash/test_extcodehash.py @@ -43,6 +43,7 @@ def test_extcodehash_self( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """ Test EXTCODEHASH/EXTCODESIZE of the currently executing account. @@ -60,10 +61,13 @@ def test_extcodehash_self( code_address = pre.deploy_contract(code, storage=storage.canary()) + gas_limit = 400_000 + if fork.is_eip_enabled(8037): + gas_limit = 1_000_000 tx = Transaction( sender=pre.fund_eoa(), to=code_address, - gas_limit=400_000, + gas_limit=gas_limit, ) state_test( @@ -84,6 +88,7 @@ def test_extcodehash_self( def test_extcodehash_of_empty( state_test: StateTestFiller, pre: Alloc, + fork: Fork, target_exists: bool, ) -> None: """ @@ -106,11 +111,14 @@ def test_extcodehash_of_empty( code_address = pre.deploy_contract(code, storage=storage.canary()) + gas_limit = 400_000 + if fork.is_eip_enabled(8037): + gas_limit = 1_000_000 tx = Transaction( sender=(pre.fund_eoa()), to=code_address, value=1, - gas_limit=400_000, + gas_limit=gas_limit, ) state_test( @@ -131,6 +139,7 @@ def test_extcodehash_of_empty( def test_extcodehash_empty_send_value( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """ Test EXTCODEHASH of non-existent account before and after sending value. @@ -160,10 +169,13 @@ def test_extcodehash_empty_send_value( code, balance=10**18, storage=storage.canary() ) + gas_limit = 400_000 + if fork.is_eip_enabled(8037): + gas_limit = 1_000_000 tx = Transaction( sender=pre.fund_eoa(), to=code_address, - gas_limit=400_000, + gas_limit=gas_limit, ) state_test( @@ -233,6 +245,7 @@ def test_extcodehash_empty_send_value( def test_extcodehash_empty_account_variants( state_test: StateTestFiller, pre: Alloc, + fork: Fork, account: Account, call_before: bool, expected_hash: bytes, @@ -272,11 +285,14 @@ def test_extcodehash_empty_account_variants( code, balance=10**18, storage=storage.canary() ) + gas_limit = 400_000 + if fork.is_eip_enabled(8037): + gas_limit = 1_000_000 tx = Transaction( sender=pre.fund_eoa(), to=code_address, value=1, - gas_limit=400_000, + gas_limit=gas_limit, ) state_test( @@ -298,6 +314,7 @@ def test_extcodehash_empty_account_variants( def test_extcodehash_empty_contract_creation( state_test: StateTestFiller, pre: Alloc, + fork: Fork, opcode: Op, ) -> None: """ @@ -347,10 +364,13 @@ def test_extcodehash_empty_contract_creation( ) storage[created_slot] = created_address + gas_limit = 400_000 + if fork.is_eip_enabled(8037): + gas_limit = 1_000_000 tx = Transaction( sender=pre.fund_eoa(), to=code_address, - gas_limit=400_000, + gas_limit=gas_limit, ) state_test( @@ -381,6 +401,7 @@ def test_extcodehash_empty_contract_creation( def test_extcodehash_codeless_with_storage( state_test: StateTestFiller, pre: Alloc, + fork: Fork, balance: int, nonce: int, ) -> None: @@ -405,10 +426,17 @@ def test_extcodehash_codeless_with_storage( code_address = pre.deploy_contract(code, storage=storage.canary()) + intrinsic_calc = fork.transaction_intrinsic_cost_calculator() tx = Transaction( sender=pre.fund_eoa(), to=code_address, - gas_limit=100_000, + # `code.gas_cost(fork)` covers both SSTOREs (regular + state under + # EIP-8037); EIP-1706 slack for the trailing SSTORE. + gas_limit=( + intrinsic_calc() + + code.gas_cost(fork) + + Op.SSTORE(new_value=1).state_cost(fork) + ), ) state_test( @@ -432,6 +460,7 @@ def test_extcodehash_dynamic_account_overwrite( state_test: StateTestFiller, pre: Alloc, target_exists: bool, + fork: Fork, ) -> None: """ Test EXTCODEHASH of non-existent/no-code account, @@ -536,11 +565,20 @@ def test_extcodehash_dynamic_account_overwrite( target_storage[target_storage_slot] = 1 sender = pre.fund_eoa() + # Test does ~10 first-time SSTOREs plus a CREATE2 (NEW_ACCOUNT) + # in the caller. Both terms are 0 pre-EIP-8037 and scale with cpsb + # on Amsterdam, keeping this CPSB-agnostic. + gas_limit = ( + 400_000 + + fork.gas_costs().NEW_ACCOUNT + + 10 * Op.SSTORE(new_value=1).state_cost(fork) + ) + tx = Transaction( sender=sender, to=caller_address, data=bytes(target_address).rjust(32, b"\0"), - gas_limit=400_000, + gas_limit=gas_limit, ) state_test( @@ -567,6 +605,7 @@ def test_extcodehash_dynamic_account_overwrite( def test_extcodehash_precompile( state_test: StateTestFiller, pre: Alloc, + fork: Fork, precompile: Address, ) -> None: """ @@ -586,10 +625,13 @@ def test_extcodehash_precompile( code_address = pre.deploy_contract(code, storage=storage.canary()) + gas_limit = 400_000 + if fork.is_eip_enabled(8037): + gas_limit = 1_000_000 tx = Transaction( sender=pre.fund_eoa(), to=code_address, - gas_limit=400_000, + gas_limit=gas_limit, ) state_test( @@ -617,6 +659,7 @@ def test_extcodehash_precompile( def test_extcodehash_new_account( state_test: StateTestFiller, pre: Alloc, + fork: Fork, deployed_code: bytes, opcode: Opcodes, ) -> None: @@ -657,10 +700,13 @@ def test_extcodehash_new_account( ) storage[created_slot] = created_address + gas_limit = 400_000 + if fork.is_eip_enabled(8037): + gas_limit = 1_000_000 tx = Transaction( sender=pre.fund_eoa(), to=code_address, - gas_limit=400_000, + gas_limit=gas_limit, ) state_test( @@ -689,6 +735,7 @@ def test_extcodehash_new_account( def test_extcodehash_via_call( state_test: StateTestFiller, pre: Alloc, + fork: Fork, opcode: Opcodes, ) -> None: """ @@ -724,10 +771,13 @@ def test_extcodehash_via_call( code_address = pre.deploy_contract(code, storage=storage.canary()) + gas_limit = 400_000 + if fork.is_eip_enabled(8037): + gas_limit = 1_000_000 tx = Transaction( sender=pre.fund_eoa(), to=code_address, - gas_limit=400_000, + gas_limit=gas_limit, ) state_test( @@ -829,10 +879,13 @@ def extcode_checks() -> Bytecode: ) storage[created_slot] = target_address + gas_limit = 400_000 + if fork.is_eip_enabled(8037): + gas_limit = 1_000_000 tx = Transaction( sender=pre.fund_eoa(), to=code_address, - gas_limit=400_000, + gas_limit=gas_limit, ) post: dict[Address, Account | None] = { @@ -856,6 +909,7 @@ def extcode_checks() -> Bytecode: def test_extcodehash_changed_account( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """ Test EXTCODEHASH/EXTCODESIZE before and after changing account state. @@ -896,10 +950,13 @@ def extcode_checks() -> Bytecode: code, balance=1, storage=storage.canary() ) + gas_limit = 400_000 + if fork.is_eip_enabled(8037): + gas_limit = 1_000_000 tx = Transaction( sender=pre.fund_eoa(), to=code_address, - gas_limit=400_000, + gas_limit=gas_limit, ) state_test( @@ -952,10 +1009,13 @@ def test_extcodehash_max_code_size( code_address = pre.deploy_contract(code, storage=storage.canary()) + gas_limit = 400_000 + if fork.is_eip_enabled(8037): + gas_limit = 1_000_000 tx = Transaction( sender=pre.fund_eoa(), to=code_address, - gas_limit=400_000, + gas_limit=gas_limit, ) state_test( @@ -977,6 +1037,7 @@ def test_extcodehash_max_code_size( def test_extcodehash_in_init_code( state_test: StateTestFiller, pre: Alloc, + fork: Fork, create_opcode: Opcodes | None, ) -> None: """ @@ -1004,6 +1065,10 @@ def test_extcodehash_in_init_code( ) initcode = checks + Op.RETURN(0, 0) + gas_limit = 400_000 + if fork.is_eip_enabled(8037): + gas_limit = 1_000_000 + if create_opcode is None: # Transaction-level creation: init code runs directly. sender = pre.fund_eoa() @@ -1011,7 +1076,7 @@ def test_extcodehash_in_init_code( sender=sender, to=None, data=initcode, - gas_limit=400_000, + gas_limit=gas_limit, ) created = compute_create_address( address=sender, @@ -1033,7 +1098,7 @@ def test_extcodehash_in_init_code( sender=pre.fund_eoa(), to=factory, data=initcode, - gas_limit=400_000, + gas_limit=gas_limit, ) created = compute_create_address( address=factory, @@ -1062,6 +1127,7 @@ def test_extcodehash_in_init_code( def test_extcodehash_self_in_init( state_test: StateTestFiller, pre: Alloc, + fork: Fork, create_opcode: Opcodes | None, ) -> None: """ @@ -1085,13 +1151,17 @@ def test_extcodehash_self_in_init( ) initcode = checks + Op.RETURN(0, 0) + gas_limit = 400_000 + if fork.is_eip_enabled(8037): + gas_limit = 1_000_000 + if create_opcode is None: sender = pre.fund_eoa() tx = Transaction( sender=sender, to=None, data=initcode, - gas_limit=400_000, + gas_limit=gas_limit, ) created = compute_create_address( address=sender, @@ -1112,7 +1182,7 @@ def test_extcodehash_self_in_init( sender=pre.fund_eoa(), to=factory, data=initcode, - gas_limit=400_000, + gas_limit=gas_limit, ) created = compute_create_address( address=factory, @@ -1148,6 +1218,7 @@ def test_extcodehash_self_in_init( def test_extcodehash_dynamic_argument( state_test: StateTestFiller, pre: Alloc, + fork: Fork, target_type: str, ) -> None: """ @@ -1193,11 +1264,14 @@ def test_extcodehash_dynamic_argument( code_address = pre.deploy_contract(code, storage=storage.canary()) + gas_limit = 400_000 + if fork.is_eip_enabled(8037): + gas_limit = 1_000_000 tx = Transaction( sender=pre.fund_eoa(), to=code_address, data=bytes(target_address).rjust(32, b"\0"), - gas_limit=400_000, + gas_limit=gas_limit, ) state_test( @@ -1217,6 +1291,7 @@ def test_extcodehash_dynamic_argument( def test_extcodehash_call_to_nonexistent( state_test: StateTestFiller, pre: Alloc, + fork: Fork, call_opcode: Opcodes, ) -> None: """ @@ -1238,10 +1313,13 @@ def test_extcodehash_call_to_nonexistent( code_address = pre.deploy_contract(code, storage=storage.canary()) + gas_limit = 400_000 + if fork.is_eip_enabled(8037): + gas_limit = 1_000_000 tx = Transaction( sender=pre.fund_eoa(), to=code_address, - gas_limit=400_000, + gas_limit=gas_limit, ) state_test( @@ -1281,9 +1359,14 @@ def test_extcodehash_call_to_selfdestruct( call_succeeds = call_opcode != Op.STATICCALL + # SELFDESTRUCT to a nonexistent beneficiary creates a new account + # whose state gas scales with cpsb on Amsterdam. Forward enough so + # the inner CALL still completes when NEW_ACCOUNT grows. + new_account = fork.gas_costs().NEW_ACCOUNT + sstore_state = Op.SSTORE(new_value=1).state_cost(fork) code = Op.SSTORE( storage.store_next(int(call_succeeds)), - call_opcode(address=target, gas=165_000), + call_opcode(address=target, gas=165_000 + new_account), ) + Op.SSTORE( storage.store_next(target_code.keccak256()), Op.EXTCODEHASH(target), @@ -1291,10 +1374,11 @@ def test_extcodehash_call_to_selfdestruct( code_address = pre.deploy_contract(code, storage=storage.canary()) + gas_limit = 400_000 + new_account + 2 * sstore_state tx = Transaction( sender=pre.fund_eoa(), to=code_address, - gas_limit=400_000, + gas_limit=gas_limit, ) # Pre-Cancun, CALLCODE/DELEGATECALL execute SELFDESTRUCT in the @@ -1331,6 +1415,7 @@ def test_extcodehash_call_to_selfdestruct( def test_extcodehash_created_and_deleted( state_test: StateTestFiller, pre: Alloc, + fork: Fork, trigger: Opcodes, ) -> None: """ @@ -1393,10 +1478,13 @@ def extcode_checks() -> Bytecode: ) storage[created_slot] = created + gas_limit = 400_000 + if fork.is_eip_enabled(8037): + gas_limit = 1_000_000 tx = Transaction( sender=pre.fund_eoa(), to=code_address, - gas_limit=400_000, + gas_limit=gas_limit, ) post: dict[Address, Account | None] = { @@ -1419,6 +1507,7 @@ def extcode_checks() -> Bytecode: def test_extcodehash_created_and_deleted_recheck_outer( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """ Test EXTCODEHASH of a created-and-selfdestructed account rechecked @@ -1499,10 +1588,17 @@ def inner_extcode_checks() -> Bytecode: ) outer = pre.deploy_contract(outer_code, storage=outer_storage.canary()) + # Test does ~10 first-time SSTOREs (across inner and outer) plus a + # CREATE2 (NEW_ACCOUNT). Both terms scale with cpsb on Amsterdam. + gas_limit = ( + 400_000 + + fork.gas_costs().NEW_ACCOUNT + + 10 * Op.SSTORE(new_value=1).state_cost(fork) + ) tx = Transaction( sender=pre.fund_eoa(), to=outer, - gas_limit=400_000, + gas_limit=gas_limit, ) post: dict[Address, Account | None] = { @@ -1557,9 +1653,14 @@ def test_extcodehash_subcall_selfdestruct( selfdestruct_code = Op.SELFDESTRUCT(beneficiary) target_c = pre.deploy_contract(selfdestruct_code) + # SELFDESTRUCT to a nonexistent beneficiary creates a new account + # whose state gas scales with cpsb on Amsterdam. + new_account = fork.gas_costs().NEW_ACCOUNT + sstore_state = Op.SSTORE(new_value=1).state_cost(fork) + # A: executes C's code in A's context via CALLCODE/DELEGATECALL a_code = call_opcode( - gas=350_000, + gas=350_000 + new_account, address=target_c, ret_size=32, ) @@ -1600,12 +1701,12 @@ def extcode_checks(target: Address | Bytecode) -> Bytecode: code += extcode_checks(a_target) code += Op.SSTORE( storage.store_next(1), - Op.CALL(gas=350_000, address=a_target), + Op.CALL(gas=350_000 + new_account, address=a_target), ) code += extcode_checks(a_target) code += Op.SSTORE( storage.store_next(1), - Op.CALL(gas=350_000, address=a_target), + Op.CALL(gas=350_000 + new_account, address=a_target), ) code_address = pre.deploy_contract(code, storage=storage.canary()) @@ -1614,10 +1715,12 @@ def extcode_checks(target: Address | Bytecode) -> Bytecode: a = compute_create_address(address=code_address, nonce=1) storage[created_slot] = a + # Test does up to ~7 first-time SSTOREs plus a CREATE for dynamic A. + gas_limit = 500_000 + new_account + 7 * sstore_state tx = Transaction( sender=pre.fund_eoa(), to=code_address, - gas_limit=500_000, + gas_limit=gas_limit, ) # Pre-Cancun, CALLCODE/DELEGATECALL executes SELFDESTRUCT in A's @@ -1654,6 +1757,7 @@ def extcode_checks(target: Address | Bytecode) -> Bytecode: def test_extcodehash_subcall_create2_oog( state_test: StateTestFiller, pre: Alloc, + fork: Fork, call_opcode: Opcodes, oog: bool, ) -> None: @@ -1671,6 +1775,12 @@ def test_extcodehash_subcall_create2_oog( deploy_code_bytes = bytes(deploy_code) initcode = Initcode(deploy_code=deploy_code) + # CREATE2 charges NEW_ACCOUNT state gas; the deploy_code's SSTORE + # also charges first-time SSTORE state gas. Both scale with cpsb + # on Amsterdam. + new_account = fork.gas_costs().NEW_ACCOUNT + sstore_state = Op.SSTORE(new_value=1).state_cost(fork) + # Factory: CREATE2, optionally consume all gas to trigger OOG. factory_code = Om.MSTORE(initcode, 0) + Op.MSTORE( 0, Op.CREATE2(value=0, offset=0, size=len(initcode), salt=0) @@ -1691,7 +1801,7 @@ def test_extcodehash_subcall_create2_oog( storage.store_next(int(not oog), "call_result"), call_opcode( address=factory, - gas=200_000, + gas=200_000 + new_account + sstore_state, ret_offset=0, ret_size=32, ), @@ -1727,10 +1837,12 @@ def test_extcodehash_subcall_create2_oog( else: post[created] = Account(nonce=1, code=deploy_code) + # Caller does ~5 first-time SSTOREs plus the inner CALL+CREATE2. + gas_limit = 500_000 + new_account + 5 * sstore_state tx = Transaction( sender=pre.fund_eoa(), to=code_address, - gas_limit=500_000, + gas_limit=gas_limit, data=created.rjust(32, b"\0"), ) @@ -1752,6 +1864,7 @@ def test_extcodehash_subcall_create2_oog( def test_extcodecopy_zero_code( state_test: StateTestFiller, pre: Alloc, + fork: Fork, target_type: str, ) -> None: """ @@ -1794,10 +1907,13 @@ def test_extcodecopy_zero_code( code_address = pre.deploy_contract(code, storage=storage.canary()) + gas_limit = 400_000 + if fork.is_eip_enabled(8037): + gas_limit = 1_000_000 tx = Transaction( sender=pre.fund_eoa(), to=code_address, - gas_limit=400_000, + gas_limit=gas_limit, ) state_test( diff --git a/tests/constantinople/eip145_bitwise_shift/test_shift_combinations.py b/tests/constantinople/eip145_bitwise_shift/test_shift_combinations.py index 8b88c2fd91b..46061f495b6 100644 --- a/tests/constantinople/eip145_bitwise_shift/test_shift_combinations.py +++ b/tests/constantinople/eip145_bitwise_shift/test_shift_combinations.py @@ -7,6 +7,7 @@ from execution_testing import ( Account, Alloc, + Fork, Op, StateTestFiller, Storage, @@ -61,7 +62,11 @@ ) @pytest.mark.eels_base_coverage def test_combinations( - state_test: StateTestFiller, pre: Alloc, opcode: Op, operation: Callable + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + opcode: Op, + operation: Callable, ) -> None: """Test bitwise shift combinations.""" result = Storage() @@ -80,10 +85,18 @@ def test_combinations( + Op.STOP, ) + # Osaka (EIP-7825) caps tx gas at 16,777,216; Amsterdam + # (EIP-8037) lifts that cap and lets state gas fund the test's + # ~400 SSTOREs from the reservoir. + # TODO: auto gas limit will remove this + gas_limit = 16_000_000 + if fork.is_eip_enabled(8037): + gas_limit = 25_000_000 + tx = Transaction( sender=pre.fund_eoa(), to=address_to, - gas_limit=5_000_000, + gas_limit=gas_limit, ) state_test(pre=pre, post={address_to: Account(storage=result)}, tx=tx) diff --git a/tests/frontier/create/test_create_one_byte.py b/tests/frontier/create/test_create_one_byte.py index ef14c91b87c..326bbccdc59 100644 --- a/tests/frontier/create/test_create_one_byte.py +++ b/tests/frontier/create/test_create_one_byte.py @@ -48,6 +48,12 @@ def test_create_one_byte( sender = pre.fund_eoa() expect_post = Storage() + new_account = fork.gas_costs().NEW_ACCOUNT + sstore_state = Op.SSTORE(new_value=1).state_cost(fork) + # Each call forwards gas to the create_contract that does CREATE; + # forward base + NEW_ACCOUNT (cpsb-agnostic). + call_gas = 50_000 + new_account + # make a subcontract that deploys code, because deploy 0xef eats ALL gas create_contract = pre.deploy_contract( code=Op.MSTORE(0, Op.CALLDATALOAD(0)) @@ -64,7 +70,7 @@ def test_create_one_byte( [ Op.MSTORE8(23, opcode) # correct the deploy byte + Op.CALL( - gas=50_000, + gas=call_gas, address=create_contract, args_size=32, ret_offset=32, @@ -95,8 +101,21 @@ def test_create_one_byte( expect_post[opcode] = created_accounts[opcode] expect_post[256] = 1 + # Osaka (EIP-7825) caps transaction gas at + # `fork.transaction_gas_limit_cap()`. Amsterdam (EIP-8037) adds + # state gas via the reservoir on top of the cap (256 CREATEs and + # 257 first-time SSTOREs in this test). Pre-Osaka there's no cap. + gas_cap = fork.transaction_gas_limit_cap() + if fork.is_eip_enabled(8037): + assert gas_cap is not None + gas_limit = gas_cap + 256 * new_account + 257 * sstore_state + elif gas_cap is not None: + gas_limit = gas_cap + else: + gas_limit = 50_000_000 + tx = Transaction( - gas_limit=14_000_000, + gas_limit=gas_limit, to=code, data=b"", nonce=0, diff --git a/tests/frontier/create/test_create_preimage_layout.py b/tests/frontier/create/test_create_preimage_layout.py index 287ab2c429d..d0f74b0342f 100644 --- a/tests/frontier/create/test_create_preimage_layout.py +++ b/tests/frontier/create/test_create_preimage_layout.py @@ -117,6 +117,7 @@ def test_create_preimage_layout_increment_nonce( def test_create_address_dynamic_nonce( pre: Alloc, state_test: StateTestFiller, + fork: Fork, ) -> None: """ Verify CreatePreimageLayout dynamic nonce encoding matches CREATE. @@ -162,9 +163,18 @@ def test_create_address_dynamic_nonce( contract = pre.deploy_contract(code=code) sender = pre.fund_eoa() + # Amsterdam EIP-8037 charges state gas per CREATE (new account). + # 260 CREATEs need ~34M state gas supplied via the reservoir. + gas_limit = 15_000_000 + if fork.create_state_gas(code_size=0) > 0: + gas_limit_cap = fork.transaction_gas_limit_cap() or gas_limit + gas_limit = gas_limit_cap + iterations * fork.create_state_gas( + code_size=0 + ) + tx = Transaction( to=contract, - gas_limit=15_000_000, + gas_limit=gas_limit, sender=sender, ) diff --git a/tests/frontier/identity_precompile/conftest.py b/tests/frontier/identity_precompile/conftest.py index fe649fa8369..7056066718d 100644 --- a/tests/frontier/identity_precompile/conftest.py +++ b/tests/frontier/identity_precompile/conftest.py @@ -1,9 +1,13 @@ """Pytest (plugin) definitions local to Identity precompile tests.""" import pytest +from execution_testing import Fork @pytest.fixture -def tx_gas_limit() -> int: +def tx_gas_limit(fork: Fork) -> int: """Return the gas limit for transactions.""" - return 365_224 + # The `nonzerovalue` variants transfer 1 wei to the identity + # precompile, creating its account and charging NEW_ACCOUNT + # state gas under EIP-8037 (0 otherwise). + return 365_224 + fork.gas_costs().NEW_ACCOUNT diff --git a/tests/frontier/identity_precompile/test_identity_returndatasize.py b/tests/frontier/identity_precompile/test_identity_returndatasize.py index e4799538b49..2a57182bae2 100644 --- a/tests/frontier/identity_precompile/test_identity_returndatasize.py +++ b/tests/frontier/identity_precompile/test_identity_returndatasize.py @@ -37,8 +37,8 @@ def test_identity_precompile_returndata( expected_returndatasize: int, ) -> None: """ - Test identity precompile RETURNDATA is sized correctly based on the input - size. + Test identity precompile RETURNDATASIZE matches the input size regardless + of the output buffer size. """ env = Environment() storage = Storage() diff --git a/tests/frontier/opcodes/test_all_opcodes.py b/tests/frontier/opcodes/test_all_opcodes.py index c13bb4f1563..959954046fb 100644 --- a/tests/frontier/opcodes/test_all_opcodes.py +++ b/tests/frontier/opcodes/test_all_opcodes.py @@ -122,9 +122,13 @@ def test_all_opcodes( ), } + # EIP-8037 needs gas_limit > TX_MAX_GAS_LIMIT + # (16,777,216) for a state_gas_reservoir for SSTORE/CREATE. + gas_limit = 50_000_000 if fork.is_eip_enabled(8037) else 9_000_000 + tx = Transaction( sender=pre.fund_eoa(), - gas_limit=9_000_000, + gas_limit=gas_limit, to=contract_address, protected=fork.supports_protected_txs(), ) diff --git a/tests/frontier/opcodes/test_blockhash.py b/tests/frontier/opcodes/test_blockhash.py index ca7c07459f6..44691f81213 100644 --- a/tests/frontier/opcodes/test_blockhash.py +++ b/tests/frontier/opcodes/test_blockhash.py @@ -52,6 +52,12 @@ def test_genesis_hash_available( contract = pre.deploy_contract(code=code) sender = pre.fund_eoa() + intrinsic = fork.transaction_intrinsic_cost_calculator() + tx_gas_limit = ( + intrinsic() + + code.gas_cost(fork) + + Op.SSTORE(new_value=1).state_cost(fork) + ) blocks = ( [ Block( @@ -59,7 +65,7 @@ def test_genesis_hash_available( Transaction( sender=sender, to=contract, - gas_limit=100_000, + gas_limit=tx_gas_limit, protected=fork.supports_protected_txs(), ) ] @@ -75,7 +81,7 @@ def test_genesis_hash_available( Transaction( sender=sender, to=contract, - gas_limit=100_000, + gas_limit=tx_gas_limit, protected=fork.supports_protected_txs(), ) ] diff --git a/tests/frontier/opcodes/test_call_and_callcode_gas_calculation.py b/tests/frontier/opcodes/test_call_and_callcode_gas_calculation.py index c44e26616d2..56e94cacfc0 100644 --- a/tests/frontier/opcodes/test_call_and_callcode_gas_calculation.py +++ b/tests/frontier/opcodes/test_call_and_callcode_gas_calculation.py @@ -200,10 +200,14 @@ def caller_address(pre: Alloc, caller_code: Bytecode) -> Address: @pytest.fixture def caller_tx(sender: EOA, caller_address: Address, fork: Fork) -> Transaction: """Transaction that performs the call to the caller contract.""" + gas_limit = 500_000 + if fork.is_eip_enabled(8037): + gas_limit = 1_000_000 + return Transaction( to=caller_address, value=1, - gas_limit=500_000, + gas_limit=gas_limit, sender=sender, protected=fork.supports_protected_txs(), ) @@ -326,8 +330,8 @@ def test_value_transfer_gas_calculation_byzantium( post: Dict[str, Account], ) -> None: """ - Tests the nested CALL/CALLCODE/DELEGATECALL/STATICCALL opcode gas - consumption with a positive value transfer. + Test nested CALL/CALLCODE/DELEGATECALL/STATICCALL gas consumption with + value transfer from Byzantium onward. """ state_test(env=Environment(), pre=pre, post=post, tx=caller_tx) diff --git a/tests/frontier/opcodes/test_calldatacopy.py b/tests/frontier/opcodes/test_calldatacopy.py index 336c464c9d2..3a54ac5cf41 100644 --- a/tests/frontier/opcodes/test_calldatacopy.py +++ b/tests/frontier/opcodes/test_calldatacopy.py @@ -189,9 +189,13 @@ def test_calldatacopy( ), ) + gas_limit = 100_000 + if fork.is_eip_enabled(8037): + gas_limit = 500_000 + tx = Transaction( data=tx_data, - gas_limit=100_000, + gas_limit=gas_limit, gas_price=0x0A, protected=fork.supports_protected_txs(), sender=pre.fund_eoa(), diff --git a/tests/frontier/opcodes/test_calldataload.py b/tests/frontier/opcodes/test_calldataload.py index d9ee82225ef..3d9c54ae14e 100644 --- a/tests/frontier/opcodes/test_calldataload.py +++ b/tests/frontier/opcodes/test_calldataload.py @@ -69,15 +69,24 @@ def test_calldataload( ae4791077e8fcf716136e70fe8392f1a1f1495fb/src/ GeneralStateTestsFiller/VMTests/vmTests/calldatacopyFiller.yml """ - contract_address = pre.deploy_contract( - Op.SSTORE(0, Op.CALLDATALOAD(offset=calldata_offset)) + Op.STOP, + contract_code = ( + Op.SSTORE(0, Op.CALLDATALOAD(offset=calldata_offset)) + Op.STOP ) + contract_address = pre.deploy_contract(contract_code) + intrinsic = fork.transaction_intrinsic_cost_calculator() + # EIP-1706 sentry: SSTORE fails if gas_left <= CALL_STIPEND (2300) + # before its base cost is deducted, so the inner frame needs that + # much headroom on top of the SSTORE cost. + sstore_sentry_slack = fork.gas_costs().CALL_STIPEND + 1 + # Outer's CALL reserves this many gas units (`Op.SUB(Op.GAS(), N)`) + # before forwarding the rest to the inner frame. + outer_call_reserve = 256 if calldata_source == "contract": - to = pre.deploy_contract( + outer_code = ( Om.MSTORE(calldata, 0x0) + Op.CALL( - gas=Op.SUB(Op.GAS(), 0x100), + gas=Op.SUB(Op.GAS(), outer_call_reserve), address=contract_address, value=0x0, args_offset=0x0, @@ -87,10 +96,18 @@ def test_calldataload( ) + Op.STOP ) + to = pre.deploy_contract(outer_code) tx = Transaction( data=calldata, - gas_limit=100_000, + gas_limit=( + intrinsic(calldata=calldata) + + outer_code.gas_cost(fork) + + outer_call_reserve + + contract_code.gas_cost(fork) + + sstore_sentry_slack + + Op.SSTORE(new_value=1).state_cost(fork) + ), protected=fork.supports_protected_txs(), sender=pre.fund_eoa(), to=to, @@ -99,7 +116,12 @@ def test_calldataload( else: tx = Transaction( data=calldata, - gas_limit=100_000, + gas_limit=( + intrinsic(calldata=calldata) + + contract_code.gas_cost(fork) + + sstore_sentry_slack + + Op.SSTORE(new_value=1).state_cost(fork) + ), protected=fork.supports_protected_txs(), sender=pre.fund_eoa(), to=contract_address, diff --git a/tests/frontier/opcodes/test_calldatasize.py b/tests/frontier/opcodes/test_calldatasize.py index 7b190f8b5c4..8c457314366 100644 --- a/tests/frontier/opcodes/test_calldatasize.py +++ b/tests/frontier/opcodes/test_calldatasize.py @@ -45,29 +45,39 @@ def test_calldatasize( 81862e4848585a438d64f911a19b3825f0f4cd95/src/ GeneralStateTestsFiller/VMTests/vmTests/calldatasizeFiller.yml """ - contract_address = pre.deploy_contract( - Op.SSTORE(key=0x0, value=Op.CALLDATASIZE) - ) + contract_code = Op.SSTORE(key=0x0, value=Op.CALLDATASIZE) + contract_address = pre.deploy_contract(contract_code) calldata = b"\x01" * args_size + intrinsic = fork.transaction_intrinsic_cost_calculator() + # EIP-1706 sentry: SSTORE fails if gas_left <= CALL_STIPEND (2300) + # before its base cost is deducted, so the inner frame needs that + # much headroom on top of the SSTORE cost. + sstore_sentry_slack = fork.gas_costs().CALL_STIPEND + 1 + # Outer's CALL reserves this many gas units (`Op.SUB(Op.GAS(), N)`) + # before forwarding the rest to the inner frame. + outer_call_reserve = 256 if calldata_source == "contract": - to = pre.deploy_contract( - code=( - Om.MSTORE(calldata, 0x0) - + Op.CALL( - gas=Op.SUB(Op.GAS(), 0x100), - address=contract_address, - value=0x0, - args_offset=0x0, - args_size=args_size, - ret_offset=0x0, - ret_size=0x0, - ) - ) + outer_code = Om.MSTORE(calldata, 0x0) + Op.CALL( + gas=Op.SUB(Op.GAS(), outer_call_reserve), + address=contract_address, + value=0x0, + args_offset=0x0, + args_size=args_size, + ret_offset=0x0, + ret_size=0x0, ) + to = pre.deploy_contract(code=outer_code) tx = Transaction( - gas_limit=100_000, + gas_limit=( + intrinsic() + + outer_code.gas_cost(fork) + + outer_call_reserve + + contract_code.gas_cost(fork) + + sstore_sentry_slack + + Op.SSTORE(new_value=1).state_cost(fork) + ), protected=fork.supports_protected_txs(), sender=pre.fund_eoa(), to=to, @@ -76,7 +86,12 @@ def test_calldatasize( else: tx = Transaction( data=calldata, - gas_limit=100_000, + gas_limit=( + intrinsic(calldata=calldata) + + contract_code.gas_cost(fork) + + sstore_sentry_slack + + Op.SSTORE(new_value=1).state_cost(fork) + ), protected=fork.supports_protected_txs(), sender=pre.fund_eoa(), to=contract_address, diff --git a/tests/frontier/opcodes/test_dup.py b/tests/frontier/opcodes/test_dup.py index c75a2953954..fd146d99c8b 100644 --- a/tests/frontier/opcodes/test_dup.py +++ b/tests/frontier/opcodes/test_dup.py @@ -66,10 +66,15 @@ def test_dup( account = pre.deploy_contract(account_code) + intrinsic = fork.transaction_intrinsic_cost_calculator() tx = Transaction( ty=0x0, to=account, - gas_limit=500000, + gas_limit=( + intrinsic() + + account_code.gas_cost(fork) + + Op.SSTORE(new_value=1).state_cost(fork) + ), gas_price=10, protected=fork.supports_protected_txs(), data="", diff --git a/tests/frontier/opcodes/test_swap.py b/tests/frontier/opcodes/test_swap.py index 03b1c6f3ca4..325a938ac93 100644 --- a/tests/frontier/opcodes/test_swap.py +++ b/tests/frontier/opcodes/test_swap.py @@ -70,11 +70,19 @@ def test_swap( # Deploy the contract with the generated bytecode. contract_address = pre.deploy_contract(contract_code) - # Create a transaction to execute the contract. + intrinsic_calc = fork.transaction_intrinsic_cost_calculator() + # `contract_code.gas_cost(fork)` covers all PUSHes, the SWAP, and the + # 16 SSTOREs (regular + state under EIP-8037). Some SSTOREs write zero, + # which the default cold zero->non-zero assumption over-estimates; + # harmless. EIP-1706 slack on the trailing SSTORE. tx = Transaction( sender=pre.fund_eoa(), to=contract_address, - gas_limit=500_000, + gas_limit=( + intrinsic_calc() + + contract_code.gas_cost(fork) + + Op.SSTORE(new_value=1).state_cost(fork) + ), protected=fork.supports_protected_txs(), ) @@ -141,11 +149,15 @@ def test_stack_underflow( # Deploy the contract with the generated bytecode. contract = pre.deploy_contract(contract_code) + gas_limit = 500_000 + if fork.is_eip_enabled(8037): + gas_limit = 1_000_000 + # Create a transaction to execute the contract. tx = Transaction( sender=pre.fund_eoa(), to=contract, - gas_limit=500_000, + gas_limit=gas_limit, protected=fork.supports_protected_txs(), ) diff --git a/tests/frontier/precompiles/test_precompile_absence.py b/tests/frontier/precompiles/test_precompile_absence.py index c0e28b79750..7dfe9087a74 100644 --- a/tests/frontier/precompiles/test_precompile_absence.py +++ b/tests/frontier/precompiles/test_precompile_absence.py @@ -60,9 +60,16 @@ def test_precompile_absence( call_code, storage=storage.canary() ) + # Osaka (EIP-7825) caps tx gas at 16,777,216. Amsterdam (EIP-8037) + # lifts the cap and increases SSTORE state gas; the 30M budget + # comfortably covers ~498 cold zero-to-nonzero SSTOREs. + gas_limit = 16_000_000 + if fork.is_eip_enabled(8037): + gas_limit = 30_000_000 + tx = Transaction( to=entry_point_address, - gas_limit=10_000_000, + gas_limit=gas_limit, sender=pre.fund_eoa(), protected=True, ) diff --git a/tests/frontier/scenarios/test_scenarios.py b/tests/frontier/scenarios/test_scenarios.py index f5cd856e97a..f046ef6ad0d 100644 --- a/tests/frontier/scenarios/test_scenarios.py +++ b/tests/frontier/scenarios/test_scenarios.py @@ -224,6 +224,11 @@ def test_scenarios( tx_max_gas = 1_000_000 if test_program.id == ProgramInvalidOpcode().id: tx_max_gas = 10_000_000 if fork.is_eip_enabled(8037) else 7_000_000 + if ( + test_program.id == ProgramAllFrontierOpcodes().id + and fork.is_eip_enabled(8037) + ): + tx_max_gas = 10_000_000 if scenario.category == "double_call_combinations": tx_max_gas *= 2 diff --git a/tests/homestead/identity_precompile/test_identity.py b/tests/homestead/identity_precompile/test_identity.py index 0d3bb2110ec..9593a9b1e79 100644 --- a/tests/homestead/identity_precompile/test_identity.py +++ b/tests/homestead/identity_precompile/test_identity.py @@ -5,6 +5,7 @@ Account, Alloc, Environment, + Fork, Op, StateTestFiller, Transaction, @@ -17,6 +18,7 @@ def test_identity_return_overwrite( state_test: StateTestFiller, pre: Alloc, + fork: Fork, call_opcode: Op, ) -> None: """ @@ -41,10 +43,15 @@ def test_identity_return_overwrite( contract_address = pre.deploy_contract( code=code, ) + intrinsic = fork.transaction_intrinsic_cost_calculator() tx = Transaction( sender=pre.fund_eoa(), to=contract_address, - gas_limit=100_000, + gas_limit=( + intrinsic() + + code.gas_cost(fork) + + Op.SSTORE(new_value=1).state_cost(fork) + ), ) post = { @@ -63,6 +70,7 @@ def test_identity_return_overwrite( def test_identity_return_buffer_modify( state_test: StateTestFiller, pre: Alloc, + fork: Fork, call_opcode: Op, ) -> None: """ @@ -89,10 +97,15 @@ def test_identity_return_buffer_modify( contract_address = pre.deploy_contract( code=code, ) + intrinsic = fork.transaction_intrinsic_cost_calculator() tx = Transaction( sender=pre.fund_eoa(), to=contract_address, - gas_limit=100_000, + gas_limit=( + intrinsic() + + code.gas_cost(fork) + + Op.SSTORE(new_value=1).state_cost(fork) + ), ) post = { diff --git a/tests/istanbul/eip1344_chainid/test_chainid.py b/tests/istanbul/eip1344_chainid/test_chainid.py index 963a7b3ba4e..d252a543da2 100644 --- a/tests/istanbul/eip1344_chainid/test_chainid.py +++ b/tests/istanbul/eip1344_chainid/test_chainid.py @@ -7,6 +7,7 @@ Account, Alloc, ChainConfig, + Fork, Op, StateTestFiller, Transaction, @@ -36,16 +37,33 @@ def test_chainid( state_test: StateTestFiller, pre: Alloc, + fork: Fork, chain_config: ChainConfig, typed_transaction: Transaction, ) -> None: """Test CHAINID opcode.""" chain_id = chain_config.chain_id - contract_address = pre.deploy_contract(Op.SSTORE(1, Op.CHAINID) + Op.STOP) + contract_code = Op.SSTORE(1, Op.CHAINID) + Op.STOP + contract_address = pre.deploy_contract(contract_code) + + intrinsic_calc = fork.transaction_intrinsic_cost_calculator() + # Tx-type-specific intrinsic args derived from the parametrized fixture. + intrinsic_kwargs: dict = {"calldata": typed_transaction.data} + if typed_transaction.access_list: + intrinsic_kwargs["access_list"] = typed_transaction.access_list + if typed_transaction.authorization_list: + intrinsic_kwargs["authorization_list_or_count"] = ( + typed_transaction.authorization_list + ) tx = typed_transaction.copy( chain_id=chain_id, to=contract_address, + gas_limit=( + intrinsic_calc(**intrinsic_kwargs) + + contract_code.gas_cost(fork) + + Op.SSTORE(new_value=1).state_cost(fork) + ), ) post = { diff --git a/tests/istanbul/eip152_blake2/common.py b/tests/istanbul/eip152_blake2/common.py index 9fce094f1dd..26e3b55e441 100644 --- a/tests/istanbul/eip152_blake2/common.py +++ b/tests/istanbul/eip152_blake2/common.py @@ -1,7 +1,5 @@ """Common classes used in the BLAKE2b precompile tests.""" -from dataclasses import dataclass - from execution_testing import Bytes, TestParameterGroup from .spec import Spec, SpecTestVectors @@ -63,7 +61,6 @@ def create_blake2b_tx_data(self) -> bytes: return _rounds + self.h + self.m + _t_0 + _t_1 + _f -@dataclass(kw_only=True, frozen=True, repr=False) class ExpectedOutput(TestParameterGroup): """ Expected test result. diff --git a/tests/istanbul/eip152_blake2/test_blake2.py b/tests/istanbul/eip152_blake2/test_blake2.py index 47b356f9598..dd06d741c6f 100644 --- a/tests/istanbul/eip152_blake2/test_blake2.py +++ b/tests/istanbul/eip152_blake2/test_blake2.py @@ -564,7 +564,16 @@ def max_tx_gas_limit(fork: Fork) -> int: def tx_gas_limits(fork: Fork) -> List[int]: """List of tx gas limits.""" - return [max_tx_gas_limit(fork), 90_000, 110_000, 200_000] + # Three coverage levels for BLAKE2 + SSTORE base costs. The + # contract writes two first-time SSTOREs (data_1, data_2), each + # adding `sstore_state_gas` under EIP-8037 (0 otherwise). + sstore_state = Op.SSTORE(new_value=1).state_cost(fork) + return [ + max_tx_gas_limit(fork), + 90_000 + 2 * sstore_state, + 110_000 + 2 * sstore_state, + 200_000 + 2 * sstore_state, + ] @pytest.mark.valid_from("Istanbul") diff --git a/tests/osaka/eip7825_transaction_gas_limit_cap/test_tx_gas_limit.py b/tests/osaka/eip7825_transaction_gas_limit_cap/test_tx_gas_limit.py index 62ec21497af..f4feb0d6da7 100644 --- a/tests/osaka/eip7825_transaction_gas_limit_cap/test_tx_gas_limit.py +++ b/tests/osaka/eip7825_transaction_gas_limit_cap/test_tx_gas_limit.py @@ -3,6 +3,10 @@ Tests for transaction gas limit cap in [EIP-7825: Transaction Gas Limit Cap](https://eips.ethereum.org/EIPS/eip-7825). + +Note: Most tests are limited to Osaka (valid_at/valid_until) because EIP-8037 +allows tx.gas_limit > TX_MAX_GAS_LIMIT with excess going to +state_gas_reservoir, changing the expected validation behavior. """ from typing import Callable, List @@ -86,6 +90,7 @@ def tx_gas_limit_cap_tests(fork: Fork) -> List[ParameterSet]: @pytest.mark.parametrize_by_fork("tx_gas_limit,error", tx_gas_limit_cap_tests) @pytest.mark.with_all_tx_types @pytest.mark.valid_from("Prague") +@pytest.mark.valid_before("EIP8037") def test_transaction_gas_limit_cap( state_test: StateTestFiller, pre: Alloc, @@ -94,9 +99,7 @@ def test_transaction_gas_limit_cap( error: TransactionException | None, tx_type: int, ) -> None: - """ - Test the transaction gas limit cap behavior for all transaction types. - """ + """Test the transaction gas limit cap for all transaction types.""" env = Environment() sender = pre.fund_eoa() @@ -342,6 +345,7 @@ def total_cost_floor_per_token(fork: Fork) -> int: ) @pytest.mark.parametrize("zero_byte", [True, False]) @pytest.mark.valid_from("Osaka") +@pytest.mark.valid_before("EIP8037") @pytest.mark.eels_base_coverage def test_tx_gas_limit_cap_full_calldata( state_test: StateTestFiller, @@ -476,6 +480,7 @@ def test_tx_gas_limit_cap_contract_creation( ], ) @pytest.mark.valid_from("Osaka") +@pytest.mark.valid_before("EIP8037") def test_tx_gas_limit_cap_access_list_with_diff_keys( state_test: StateTestFiller, exceed_tx_gas_limit: bool, @@ -562,6 +567,7 @@ def intrinsic_cost_for_num_storage_keys(storage_key_count: int) -> int: ], ) @pytest.mark.valid_from("Osaka") +@pytest.mark.valid_before("EIP8037") def test_tx_gas_limit_cap_access_list_with_diff_addr( state_test: StateTestFiller, pre: Alloc, @@ -665,14 +671,21 @@ def make_access_list(auth_count: int) -> List[AccessList]: for i in range(auth_count) ] - def intrinsic_cost_for_auth_list_length(auth_count: int) -> int: - return intrinsic_cost( + def capped_intrinsic_cost(auth_count: int) -> int: + """Return the intrinsic gas that counts toward the cap.""" + cost = intrinsic_cost( access_list=make_access_list(auth_count), authorization_list_or_count=auth_count, ) + if fork.is_eip_enabled(8037): + # EIP-8037 caps only the regular dimension, not state gas. + cost -= fork.transaction_intrinsic_state_gas( + authorization_count=auth_count + ) + return cost auth_list_length = max_count_with_intrinsic_cost_at_most( - intrinsic_cost_for_auth_list_length, tx_gas_limit_cap + capped_intrinsic_cost, tx_gas_limit_cap ) + int(exceed_tx_gas_limit) # EIP-7702 authorization transaction cost: @@ -702,13 +715,14 @@ def intrinsic_cost_for_auth_list_length(auth_count: int) -> int: correct_intrinsic_cost = intrinsic_cost( access_list=access_list, authorization_list_or_count=auth_list_length ) + correct_capped_cost = capped_intrinsic_cost(auth_list_length) if exceed_tx_gas_limit: - assert correct_intrinsic_cost > tx_gas_limit_cap, ( - "Correct intrinsic cost should exceed the tx gas limit cap" + assert correct_capped_cost > tx_gas_limit_cap, ( + "Correct capped intrinsic cost should exceed the tx gas limit cap" ) else: - assert correct_intrinsic_cost <= tx_gas_limit_cap, ( - "Correct intrinsic cost should be less than or " + assert correct_capped_cost <= tx_gas_limit_cap, ( + "Correct capped intrinsic cost should be less than or " "equal to the tx gas limit cap" ) @@ -724,7 +738,12 @@ def intrinsic_cost_for_auth_list_length(auth_count: int) -> int: sender=pre.fund_eoa(), access_list=access_list, authorization_list=auth_tuples, - error=TransactionException.GAS_LIMIT_EXCEEDS_MAXIMUM + # EIP-8037 reports a cap overflow as INTRINSIC_GAS_TOO_LOW. + error=( + TransactionException.INTRINSIC_GAS_TOO_LOW + if fork.is_eip_enabled(8037) + else TransactionException.GAS_LIMIT_EXCEEDS_MAXIMUM + ) if correct_intrinsic_cost_in_transaction_gas_limit and exceed_tx_gas_limit else TransactionException.INTRINSIC_GAS_TOO_LOW @@ -732,7 +751,13 @@ def intrinsic_cost_for_auth_list_length(auth_count: int) -> int: else None, ) + env = Environment() + if fork.is_eip_enabled(8037): + # Size the block so it fits the state reservoir. + env = Environment(gas_limit=correct_intrinsic_cost) + state_test( + env=env, pre=pre, post={}, tx=tx, diff --git a/tests/osaka/eip7883_modexp_gas_increase/conftest.py b/tests/osaka/eip7883_modexp_gas_increase/conftest.py index a94e3aa7252..1242efb8010 100644 --- a/tests/osaka/eip7883_modexp_gas_increase/conftest.py +++ b/tests/osaka/eip7883_modexp_gas_increase/conftest.py @@ -51,7 +51,6 @@ def call_contract_post_storage() -> Storage: @pytest.fixture def total_tx_gas_needed( fork: Fork, - modexp_expected: bytes, modexp_input: ModExpInput, precompile_gas: int, ) -> int: @@ -60,11 +59,11 @@ def total_tx_gas_needed( fork.transaction_intrinsic_cost_calculator() ) memory_expansion_gas_calculator = fork.memory_expansion_gas_calculator() - # `gas_measure_contract` does at most 4 SSTOREs to cold slots. sstore_gas = Op.SSTORE(key_warm=False).gas_cost(fork) * 4 - # Ensures that the precompile call is not starved by the 63/64 rule. precompile_gas_with_margin = precompile_gas * 64 // 63 extra_gas = 100_000 + if fork.is_eip_enabled(8037): + extra_gas = 500_000 return ( extra_gas @@ -77,9 +76,19 @@ def total_tx_gas_needed( @pytest.fixture def exceeds_tx_gas_cap( - total_tx_gas_needed: int, fork: Fork, env: Environment + total_tx_gas_needed: int, + fork: Fork, + env: Environment, + precompile_gas: int, ) -> bool: """Determine if total gas requirements exceed transaction gas cap.""" + if fork.is_eip_enabled(8037): + # EIP-8037: tx.gas can exceed TX_MAX_GAS_LIMIT; excess fills + # state_gas_reservoir. But regular gas is still capped at + # TX_MAX_GAS_LIMIT, so if the precompile alone needs more regular gas + # than the budget, the call will fail. + cap = fork.transaction_gas_limit_cap() + return cap is not None and precompile_gas > cap tx_gas_limit_cap = fork.transaction_gas_limit_cap() or env.gas_limit return total_tx_gas_needed > tx_gas_limit_cap @@ -155,18 +164,12 @@ def gas_measure_contract( 0, ) + gas_costs = fork.gas_costs() extra_gas = ( - call_opcode( - gas_used, - Spec.MODEXP_ADDRESS, - *value, - 0, - Op.CALLDATASIZE(), - 0, - 0, - address_warm=True, - ).gas_cost(fork) - + Op.GAS.gas_cost(fork) # second GAS in measurement + gas_costs.WARM_ACCESS + + (gas_costs.VERY_LOW * (len(call_opcode.kwargs) - 1)) + + gas_costs.BASE # CALLDATASIZE + + gas_costs.BASE # GAS ) # Build the gas measurement contract code @@ -228,11 +231,11 @@ def precompile_gas( Calculate gas cost for the ModExp precompile and verify it matches expected gas. """ - spec = Spec7883 if fork >= Osaka else Spec + spec = Spec if fork < Osaka else Spec7883 try: calculated_gas = spec.calculate_gas_cost(modexp_input) if gas_old is not None and gas_new is not None: - expected_gas = gas_new if fork >= Osaka else gas_old + expected_gas = gas_old if fork < Osaka else gas_new base_len = len(modexp_input.base) exp_len = len(modexp_input.exponent) mod_len = len(modexp_input.modulus) @@ -284,6 +287,9 @@ def tx_gas_limit( """ Transaction gas limit used for the test (Can be overridden in the test). """ + if fork.is_eip_enabled(8037): + # EIP-8037: tx gas limit can exceed TX_MAX_GAS_LIMIT. + return min(total_tx_gas_needed, env.gas_limit) tx_gas_limit_cap = fork.transaction_gas_limit_cap() or env.gas_limit return min(tx_gas_limit_cap, total_tx_gas_needed) diff --git a/tests/osaka/eip7883_modexp_gas_increase/test_modexp_thresholds.py b/tests/osaka/eip7883_modexp_gas_increase/test_modexp_thresholds.py index f439f9f983e..8f7e704891f 100644 --- a/tests/osaka/eip7883_modexp_gas_increase/test_modexp_thresholds.py +++ b/tests/osaka/eip7883_modexp_gas_increase/test_modexp_thresholds.py @@ -503,6 +503,7 @@ def test_contract_initcode( pre: Alloc, post: dict, tx: Transaction, + fork: Fork, modexp_input: bytes, modexp_expected: bytes, opcode: Op, @@ -559,7 +560,7 @@ def test_contract_initcode( tx = Transaction( sender=sender, - gas_limit=200_000, + gas_limit=(1_000_000 if fork.is_eip_enabled(8037) else 200_000), to=factory_contract_address, value=0, data=call_modexp_bytecode + bytes(modexp_input), diff --git a/tests/osaka/eip7918_blob_reserve_price/test_blob_base_fee.py b/tests/osaka/eip7918_blob_reserve_price/test_blob_base_fee.py index cd6b444f227..8a51a238574 100644 --- a/tests/osaka/eip7918_blob_reserve_price/test_blob_base_fee.py +++ b/tests/osaka/eip7918_blob_reserve_price/test_blob_base_fee.py @@ -14,6 +14,7 @@ Alloc, Block, BlockchainTestFiller, + Bytecode, Environment, Fork, Hash, @@ -38,16 +39,30 @@ def sender(pre: Alloc) -> Address: @pytest.fixture -def destination_account(pre: Alloc) -> Address: +def destination_code() -> Bytecode: + """Bytecode that stores the blob base fee at slot 0.""" + return Op.SSTORE(0, Op.BLOBBASEFEE) + + +@pytest.fixture +def destination_account(pre: Alloc, destination_code: Bytecode) -> Address: """Contract that stores the blob base fee for verification.""" - code = Op.SSTORE(0, Op.BLOBBASEFEE) - return pre.deploy_contract(code) + return pre.deploy_contract(destination_code) @pytest.fixture -def tx_gas() -> int: - """Gas limit for transactions sent during test.""" - return 100_000 +def tx_gas(fork: Fork, destination_code: Bytecode) -> int: + """ + Gas limit sized exactly for the destination's single SSTORE 0->non-zero + plus the EIP-1706 stipend slack and (under EIP-8037) one + `sstore_state_gas` of reservoir headroom. + """ + intrinsic = fork.transaction_intrinsic_cost_calculator() + return ( + intrinsic() + + destination_code.gas_cost(fork) + + Op.SSTORE(new_value=1).state_cost(fork) + ) @pytest.fixture @@ -94,6 +109,7 @@ def tx( def block( tx: Transaction, fork: Fork, + destination_code: Bytecode, parent_excess_blobs: int, parent_blobs: int, block_base_fee_per_gas: int, @@ -109,9 +125,14 @@ def block( parent_blob_count=parent_blobs, parent_base_fee_per_gas=block_base_fee_per_gas, ) + intrinsic = fork.transaction_intrinsic_cost_calculator() + code_state = destination_code.state_cost(fork) + code_regular = destination_code.gas_cost(fork) - code_state + expected_gas_used = max(intrinsic() + code_regular, code_state) return Block( txs=[tx], header_verify=Header( + gas_used=expected_gas_used, excess_blob_gas=expected_excess_blob_gas, blob_gas_used=blob_count * blob_gas_per_blob, ), @@ -149,8 +170,8 @@ def test_reserve_price_various_base_fee_scenarios( post: Dict[Address, Account], ) -> None: """ - Test reserve price mechanism across various block base fee and excess blob - gas scenarios. + Test reserve price enforcement across various base fee and excess blob gas + combinations within a single fork. """ blockchain_test( pre=pre, diff --git a/tests/osaka/eip7939_count_leading_zeros/test_count_leading_zeros.py b/tests/osaka/eip7939_count_leading_zeros/test_count_leading_zeros.py index 171b8255431..496667236e1 100644 --- a/tests/osaka/eip7939_count_leading_zeros/test_count_leading_zeros.py +++ b/tests/osaka/eip7939_count_leading_zeros/test_count_leading_zeros.py @@ -14,6 +14,7 @@ EIPChecklist, Environment, Fork, + Header, Op, StateTestFiller, Storage, @@ -233,24 +234,45 @@ def test_clz_stack_not_overflow( code += Op.PUSH0 * (max_stack_items - 2) for i in range(256): - code += Op.PUSH1(i) + Op.CLZ(1 << i) + Op.SWAP1 + Op.SSTORE + # `i=255` writes 0 to slot 255 (CLZ(1<<255) == 0); pin metadata so + # `gas_cost(fork)` picks the no-op SSTORE branch instead of the + # default cold zero->non-zero assumption. + sstore = Op.SSTORE.with_metadata( + key_warm=False, + original_value=0, + current_value=0, + new_value=255 - i, + ) + code += Op.PUSH1(i) + Op.CLZ(1 << i) + Op.SWAP1 + sstore code_address = pre.deploy_contract(code=code) post[code_address] = Account(storage={i: 255 - i for i in range(256)}) + intrinsic = fork.transaction_intrinsic_cost_calculator() + code_state = code.state_cost(fork) + code_regular = code.gas_cost(fork) - code_state + # Trailing SSTORE is a no-op (~2100); EIP-1706 requires gas_left >= + # CALL_STIPEND+1 at entry, so reserve that as slack on top of exact. + eip_1706_slack = fork.gas_costs().CALL_STIPEND + 1 tx = Transaction( to=code_address, sender=pre.fund_eoa(), - gas_limit=6_000_000, + gas_limit=(intrinsic() + code_regular + code_state + eip_1706_slack), ) - state_test(pre=pre, post=post, tx=tx) + expected_gas_used = max(intrinsic() + code_regular, code_state) + state_test( + pre=pre, + post=post, + tx=tx, + blockchain_test_header_verify=Header(gas_used=expected_gas_used), + ) @pytest.mark.valid_from("Osaka") def test_clz_push_operation_same_value( - state_test: StateTestFiller, pre: Alloc + state_test: StateTestFiller, pre: Alloc, fork: Fork ) -> None: """Test CLZ opcode returns the same value via different push operations.""" storage = {} @@ -267,10 +289,18 @@ def test_clz_push_operation_same_value( code_address = pre.deploy_contract(code=code) + intrinsic = fork.transaction_intrinsic_cost_calculator() + code_state = code.state_cost(fork) + code_regular = code.gas_cost(fork) - code_state tx = Transaction( to=code_address, sender=pre.fund_eoa(), - gas_limit=12_000_000, + gas_limit=( + intrinsic() + + code_regular + + code_state + + Op.SSTORE(new_value=1).state_cost(fork) + ), ) post = { @@ -279,7 +309,13 @@ def test_clz_push_operation_same_value( ) } - state_test(pre=pre, post=post, tx=tx) + expected_gas_used = max(intrinsic() + code_regular, code_state) + state_test( + pre=pre, + post=post, + tx=tx, + blockchain_test_header_verify=Header(gas_used=expected_gas_used), + ) @EIPChecklist.Opcode.Test.ForkTransition.Invalid() @@ -376,6 +412,7 @@ def test_clz_fork_transition( def test_clz_jump_operation( state_test: StateTestFiller, pre: Alloc, + fork: Fork, opcode: Op, valid_jump: bool, jumpi_condition: bool, @@ -392,19 +429,41 @@ def test_clz_jump_operation( if valid_jump: code += Op.JUMPDEST - code += Op.CLZ + Op.PUSH0 + Op.SSTORE + Op.RETURN(0, 0) + callee_code = code + Op.CLZ + Op.PUSH0 + Op.SSTORE + Op.RETURN(0, 0) - callee_address = pre.deploy_contract(code=code) + callee_address = pre.deploy_contract(code=callee_code) + caller_forwarded_gas = 0xFFFF + caller_code = Op.SSTORE( + 0, Op.CALL(gas=caller_forwarded_gas, address=callee_address) + ) caller_address = pre.deploy_contract( - code=Op.SSTORE(0, Op.CALL(gas=0xFFFF, address=callee_address)), + code=caller_code, storage={"0x00": "0xdeadbeef"}, ) + intrinsic = fork.transaction_intrinsic_cost_calculator() + # The inner CALL forwards a fixed 0xFFFF (65535) regular gas — too + # tight for callee's SSTORE state to spill into. Lift `gas_limit` past + # the EIP-7825 cap so the EIP-8037 reservoir holds the callee's state + # work and parent's SSTORE state, plus EIP-1706 slack. + gas_cap = fork.transaction_gas_limit_cap() + state_needed = caller_code.state_cost(fork) + callee_code.state_cost(fork) + if gas_cap is not None and state_needed > 0: + gas_limit = ( + gas_cap + state_needed + Op.SSTORE(new_value=1).state_cost(fork) + ) + else: + gas_limit = ( + intrinsic() + + caller_code.gas_cost(fork) + + caller_forwarded_gas + + Op.SSTORE(new_value=1).state_cost(fork) + ) tx = Transaction( to=caller_address, sender=pre.fund_eoa(), - gas_limit=200_000, + gas_limit=gas_limit, ) expected_clz = 255 - bits @@ -429,8 +488,9 @@ def test_clz_jump_operation( def test_clz_from_set_code( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: - """Test the address opcode in a set-code transaction.""" + """Test the CLZ opcode in a set-code transaction.""" storage = Storage() auth_signer = pre.fund_eoa(auth_account_start_balance) @@ -444,8 +504,10 @@ def test_clz_from_set_code( set_code_to_address = pre.deploy_contract(set_code) + # 4 first-time SSTOREs in the delegated code each add + # `sstore_state_gas` under EIP-8037 (0 otherwise). tx = Transaction( - gas_limit=200_000, + gas_limit=200_000 + 4 * Op.SSTORE(new_value=1).state_cost(fork), to=auth_signer, value=0, authorization_list=[ @@ -625,9 +687,9 @@ def test_clz_initcode_context(state_test: StateTestFiller, pre: Alloc) -> None: @pytest.mark.valid_from("Osaka") @pytest.mark.parametrize("opcode", [Op.CREATE, Op.CREATE2]) def test_clz_initcode_create( - state_test: StateTestFiller, pre: Alloc, opcode: Op + state_test: StateTestFiller, pre: Alloc, fork: Fork, opcode: Op ) -> None: - """Test CLZ opcode behavior when creating a contract.""" + """Test CLZ opcode behavior in initcode executed via CREATE/CREATE2.""" bits = [0, 1, 64, 128, 255] # expected values: [255, 254, 191, 127, 0] storage = Storage() @@ -653,9 +715,16 @@ def test_clz_initcode_create( opcode=opcode, ) + # CREATE charges NEW_ACCOUNT plus 5 first-time SSTOREs in the + # deployed contract; both terms add state gas under EIP-8037 + # (0 otherwise). tx = Transaction( to=factory_contract_address, - gas_limit=200_000, + gas_limit=( + 200_000 + + fork.gas_costs().NEW_ACCOUNT + + 5 * Op.SSTORE(new_value=1).state_cost(fork) + ), data=ext_code, sender=sender_address, ) @@ -700,6 +769,7 @@ class CallingContext: def test_clz_call_operation( state_test: StateTestFiller, pre: Alloc, + fork: Fork, opcode: Op, context: CallingContext, ) -> None: @@ -728,8 +798,13 @@ def test_clz_call_operation( callee_address = pre.deploy_contract(code=callee_code) + # 3 first-time SSTOREs in the callee (when context != no_context) + # and 3 more in the caller (when context == callee_context); each + # adds `sstore_state_gas` under EIP-8037 (0 otherwise). + sstore_state = Op.SSTORE(new_value=1).state_cost(fork) + subcall_gas = 0xFFFF + 3 * sstore_state caller_code = opcode( - gas=0xFFFF, + gas=subcall_gas, address=callee_address, ret_offset=0, ret_size=len(test_cases) * 0x20, @@ -745,7 +820,7 @@ def test_clz_call_operation( tx = Transaction( to=caller_address, sender=pre.fund_eoa(), - gas_limit=200_000, + gas_limit=200_000 + 6 * sstore_state, ) post = {} diff --git a/tests/osaka/eip7951_p256verify_precompiles/conftest.py b/tests/osaka/eip7951_p256verify_precompiles/conftest.py index af45811a5ba..3b5ff9c3d99 100644 --- a/tests/osaka/eip7951_p256verify_precompiles/conftest.py +++ b/tests/osaka/eip7951_p256verify_precompiles/conftest.py @@ -158,6 +158,8 @@ def tx_gas_limit(fork: Fork, input_data: bytes, precompile_gas: int) -> int: ) memory_expansion_gas_calculator = fork.memory_expansion_gas_calculator() extra_gas = 100_000 + if fork.is_eip_enabled(8037): + extra_gas = 500_000 return ( extra_gas + intrinsic_gas_cost_calculator(calldata=input_data) diff --git a/tests/osaka/eip7951_p256verify_precompiles/test_p256verify.py b/tests/osaka/eip7951_p256verify_precompiles/test_p256verify.py index 183ba71686c..77fc1abf0f3 100644 --- a/tests/osaka/eip7951_p256verify_precompiles/test_p256verify.py +++ b/tests/osaka/eip7951_p256verify_precompiles/test_p256verify.py @@ -1345,7 +1345,7 @@ def test_contract_initcode( tx = Transaction( sender=sender, - gas_limit=200_000, + gas_limit=(1_000_000 if fork.is_eip_enabled(8037) else 200_000), to=factory_contract_address, value=0, data=call_256verify_bytecode + input_data, diff --git a/tests/paris/eip7610_create_collision/test_collision_selfdestruct.py b/tests/paris/eip7610_create_collision/test_collision_selfdestruct.py index dea29c75234..2c6e3886d7e 100644 --- a/tests/paris/eip7610_create_collision/test_collision_selfdestruct.py +++ b/tests/paris/eip7610_create_collision/test_collision_selfdestruct.py @@ -10,6 +10,7 @@ Account, Alloc, Environment, + Fork, Initcode, Op, StateTestFiller, @@ -27,6 +28,7 @@ def test_selfdestruct_after_create2_collision( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """ Test that a failed CREATE2 collision does not count as creation. @@ -72,7 +74,14 @@ def test_selfdestruct_after_create2_collision( + Op.SSTORE( storage.store_next(1, "create2_call_success"), Op.CALL( - gas=500_000, + # Forwarded budget covers deployer's CREATE2 (charged + # then refunded on collision under EIP-8037) plus its + # SSTORE; both 0 pre-EIP-8037 and scale with cpsb. + gas=( + 500_000 + + fork.gas_costs().NEW_ACCOUNT + + Op.SSTORE(new_value=1).state_cost(fork) + ), address=deployer, args_size=Op.CALLDATASIZE, ), @@ -80,7 +89,7 @@ def test_selfdestruct_after_create2_collision( # Call target to trigger SELFDESTRUCT + Op.SSTORE( storage.store_next(1, "selfdestruct_call_success"), - Op.CALL(gas=100_000, address=target_address), + Op.CALL(gas=500_000, address=target_address), ) + Op.STOP ) @@ -106,10 +115,13 @@ def test_selfdestruct_after_create2_collision( env=env, pre=pre, post=post, + # 3 first-time SSTOREs (deployer's create2_result and + # controller's two outcome flags) each charge state gas under + # EIP-8037 (0 otherwise). tx=Transaction( sender=sender, to=controller, - gas_limit=2_000_000, + gas_limit=2_000_000 + 3 * Op.SSTORE(new_value=1).state_cost(fork), data=initcode, ), ) diff --git a/tests/paris/eip7610_create_collision/test_initcollision.py b/tests/paris/eip7610_create_collision/test_initcollision.py index e7f4fe032fd..04b0677e46c 100644 --- a/tests/paris/eip7610_create_collision/test_initcollision.py +++ b/tests/paris/eip7610_create_collision/test_initcollision.py @@ -76,12 +76,14 @@ def test_init_collision_create_tx( Test that a contract creation transaction exceptionally aborts when the target address has a non-empty storage, balance, nonce, or code. """ + # Contract-creation tx: intrinsic includes NEW_ACCOUNT state gas + # under EIP-8037 (0 otherwise). tx = Transaction( sender=pre.fund_eoa(), ty=tx_type, to=None, data=initcode, - gas_limit=200_000, + gas_limit=200_000 + fork.gas_costs().NEW_ACCOUNT, ) created_contract_address = tx.created_contract diff --git a/tests/paris/eip7610_create_collision/test_revert_in_create.py b/tests/paris/eip7610_create_collision/test_revert_in_create.py index 04a1f8f62f0..676ea852e14 100644 --- a/tests/paris/eip7610_create_collision/test_revert_in_create.py +++ b/tests/paris/eip7610_create_collision/test_revert_in_create.py @@ -7,6 +7,7 @@ Account, Alloc, Bytecode, + Fork, Initcode, Op, StateTestFiller, @@ -107,6 +108,7 @@ def test_create2_collision_storage( state_test: StateTestFiller, pre: Alloc, create2_initcode: Bytecode, + fork: Fork, ) -> None: """ Test that CREATE2 fails when targeting an address with pre-existing @@ -127,12 +129,16 @@ def test_create2_collision_storage( ) sender = pre.fund_eoa() + gas_limit = 400_000 + if fork.is_eip_enabled(8037): + gas_limit = 1_000_000 + tx = Transaction( sender=sender, to=None, data=deployer_code, value=1, - gas_limit=400_000, + gas_limit=gas_limit, ) deployer_address = tx.created_contract diff --git a/tests/paris/security/test_selfdestruct_balance_bug.py b/tests/paris/security/test_selfdestruct_balance_bug.py index 994003c2c5a..ec68a734a95 100644 --- a/tests/paris/security/test_selfdestruct_balance_bug.py +++ b/tests/paris/security/test_selfdestruct_balance_bug.py @@ -19,6 +19,7 @@ Block, BlockchainTestFiller, CalldataCase, + Fork, Initcode, Op, Switch, @@ -29,7 +30,7 @@ @pytest.mark.valid_from("Constantinople") def test_tx_selfdestruct_balance_bug( - blockchain_test: BlockchainTestFiller, pre: Alloc + blockchain_test: BlockchainTestFiller, pre: Alloc, fork: Fork ) -> None: """ Test that the vulnerability is not present by checking the balance of the @@ -95,35 +96,56 @@ def test_tx_selfdestruct_balance_bug( sender = pre.fund_eoa() + intrinsic_calc = fork.transaction_intrinsic_cost_calculator() + inner_call_gas = 100_000 # cc forwards this to each aa CALL + # Tx1 budget: cc bytecode + CREATE'd initcode execution + NEW_ACCOUNT + # state for the CREATE + the two forwarded inner CALL gas envelopes, + # plus EIP-1706 stipend slack for the trailing SSTORE. + cc_tx_gas = ( + intrinsic_calc(calldata=aa_code) + + cc_code.gas_cost(fork) + + aa_code.gas_cost(fork) + + fork.gas_costs().NEW_ACCOUNT + + 2 * inner_call_gas + + Op.SSTORE(new_value=1).state_cost(fork) + ) + # Balance-check tx: one zero->non-zero SSTORE. + balance_tx_gas = ( + intrinsic_calc() + + balance_code.gas_cost(fork) + + Op.SSTORE(new_value=1).state_cost(fork) + ) + # Plain value transfer to a (post-EIP-6780) non-existent account. + aa_value_tx_gas = intrinsic_calc() + blocks = [ Block( txs=[ - # Sender invokes caller, caller invokes 0xaa: - # calling with 1 wei call + # Sender invokes caller, caller invokes 0xaa. Transaction( sender=sender, to=cc_address, data=aa_code, - gas_limit=1000000, + gas_limit=cc_tx_gas, ), - # Dummy tx to store balance of 0xaa after first TX. + # Capture aa's balance after tx 1 (post selfdestruct). Transaction( sender=sender, to=balance_address_1, - gas_limit=100000, + gas_limit=balance_tx_gas, ), - # Sender calls 0xaa with 5 wei. + # Sender calls aa with 5 wei; aa no longer has code. Transaction( sender=sender, to=aa_location, - gas_limit=100000, + gas_limit=aa_value_tx_gas, value=5, ), - # Dummy tx to store balance of 0xaa after second TX. + # Capture aa's balance after tx 3. Transaction( sender=sender, to=balance_address_2, - gas_limit=100000, + gas_limit=balance_tx_gas, ), ], ), diff --git a/tests/ported_static/amsterdam_skip_list.txt b/tests/ported_static/amsterdam_skip_list.txt new file mode 100644 index 00000000000..0a07bbec654 --- /dev/null +++ b/tests/ported_static/amsterdam_skip_list.txt @@ -0,0 +1,569 @@ +# Amsterdam ported static skip list. +# +# Test cases in this list are temporarily skipped for the Amsterdam +# fork due to EIP-8037's two-dimensional gas model. Gas limits in the +# underlying ported static tests have not yet been updated to account +# for state gas. +# +# Entries are substring-matched against each pytest nodeid (after +# stripping the fixture-format suffix in conftest.py). +# +# Total entries: 480 + +# stAttackTest (1) +stAttackTest/test_crashing_transaction.py::test_crashing_transaction[fork_Amsterdam] + +# stBadOpcode (4) +stBadOpcode/test_measure_gas.py::test_measure_gas[fork_Amsterdam-CREATE2] +stBadOpcode/test_measure_gas.py::test_measure_gas[fork_Amsterdam-CREATE] +stBadOpcode/test_operation_diff_gas.py::test_operation_diff_gas[fork_Amsterdam-CREATE2] +stBadOpcode/test_operation_diff_gas.py::test_operation_diff_gas[fork_Amsterdam-CREATE] + +# stCallCodes (9) +stCallCodes/test_callcall_00_suicide_end.py::test_callcall_00_suicide_end[fork_Amsterdam] +stCallCodes/test_callcallcall_000_suicide_end.py::test_callcallcall_000_suicide_end[fork_Amsterdam] +stCallCodes/test_callcallcodecall_010_suicide_end.py::test_callcallcodecall_010_suicide_end[fork_Amsterdam] +stCallCodes/test_callcode_in_initcode_to_existing_contract.py::test_callcode_in_initcode_to_existing_contract[fork_Amsterdam-d0] +stCallCodes/test_callcode_in_initcode_to_existing_contract.py::test_callcode_in_initcode_to_existing_contract[fork_Amsterdam-d1] +stCallCodes/test_callcode_in_initcode_to_existing_contract_with_value_transfer.py::test_callcode_in_initcode_to_existing_contract_with_value_transfer[fork_Amsterdam] +stCallCodes/test_callcodecall_10_suicide_end.py::test_callcodecall_10_suicide_end[fork_Amsterdam] +stCallCodes/test_callcodecallcall_100_suicide_end.py::test_callcodecallcall_100_suicide_end[fork_Amsterdam] +stCallCodes/test_callcodecallcodecall_110_suicide_end.py::test_callcodecallcodecall_110_suicide_end[fork_Amsterdam] + +# stCallCreateCallCodeTest (12) +stCallCreateCallCodeTest/test_call1024_oog.py::test_call1024_oog[fork_Amsterdam--g0] +stCallCreateCallCodeTest/test_call1024_oog.py::test_call1024_oog[fork_Amsterdam--g1] +stCallCreateCallCodeTest/test_call1024_oog.py::test_call1024_oog[fork_Amsterdam--g2] +stCallCreateCallCodeTest/test_call1024_oog.py::test_call1024_oog[fork_Amsterdam--g3] +stCallCreateCallCodeTest/test_callcode1024_oog.py::test_callcode1024_oog[fork_Amsterdam--g0] +stCallCreateCallCodeTest/test_callcode1024_oog.py::test_callcode1024_oog[fork_Amsterdam--g1] +stCallCreateCallCodeTest/test_callcode_lose_gas_oog.py::test_callcode_lose_gas_oog[fork_Amsterdam--g2] +stCallCreateCallCodeTest/test_contract_creation_make_call_that_ask_more_gas_then_transaction_provided.py::test_contract_creation_make_call_that_ask_more_gas_then_transaction_provided[fork_Amsterdam--g0] +stCallCreateCallCodeTest/test_contract_creation_make_call_that_ask_more_gas_then_transaction_provided.py::test_contract_creation_make_call_that_ask_more_gas_then_transaction_provided[fork_Amsterdam--g1] +stCallCreateCallCodeTest/test_create_name_registrator_per_txs_not_enough_gas.py::test_create_name_registrator_per_txs_not_enough_gas[fork_Amsterdam--g0] +stCallCreateCallCodeTest/test_create_name_registrator_per_txs_not_enough_gas.py::test_create_name_registrator_per_txs_not_enough_gas[fork_Amsterdam--g1] +stCallCreateCallCodeTest/test_create_name_registrator_pre_store1_not_enough_gas.py::test_create_name_registrator_pre_store1_not_enough_gas[fork_Amsterdam] + +# stCallDelegateCodesCallCodeHomestead (10) +stCallDelegateCodesCallCodeHomestead/test_callcallcallcode_001_suicide_end.py::test_callcallcallcode_001_suicide_end[fork_Amsterdam] +stCallDelegateCodesCallCodeHomestead/test_callcallcode_01_suicide_end.py::test_callcallcode_01_suicide_end[fork_Amsterdam] +stCallDelegateCodesCallCodeHomestead/test_callcallcodecall_010_suicide_end.py::test_callcallcodecall_010_suicide_end[fork_Amsterdam] +stCallDelegateCodesCallCodeHomestead/test_callcallcodecallcode_011_oogm_before.py::test_callcallcodecallcode_011_oogm_before[fork_Amsterdam] +stCallDelegateCodesCallCodeHomestead/test_callcallcodecallcode_011_suicide_end.py::test_callcallcodecallcode_011_suicide_end[fork_Amsterdam] +stCallDelegateCodesCallCodeHomestead/test_callcodecall_10_suicide_end.py::test_callcodecall_10_suicide_end[fork_Amsterdam] +stCallDelegateCodesCallCodeHomestead/test_callcodecallcall_100_suicide_end.py::test_callcodecallcall_100_suicide_end[fork_Amsterdam] +stCallDelegateCodesCallCodeHomestead/test_callcodecallcallcode_101_suicide_end.py::test_callcodecallcallcode_101_suicide_end[fork_Amsterdam] +stCallDelegateCodesCallCodeHomestead/test_callcodecallcode_11_suicide_end.py::test_callcodecallcode_11_suicide_end[fork_Amsterdam] +stCallDelegateCodesCallCodeHomestead/test_callcodecallcodecall_110_suicide_end.py::test_callcodecallcodecall_110_suicide_end[fork_Amsterdam] + +# stCallDelegateCodesHomestead (10) +stCallDelegateCodesHomestead/test_callcallcallcode_001_suicide_end.py::test_callcallcallcode_001_suicide_end[fork_Amsterdam] +stCallDelegateCodesHomestead/test_callcallcode_01_suicide_end.py::test_callcallcode_01_suicide_end[fork_Amsterdam] +stCallDelegateCodesHomestead/test_callcallcodecall_010_suicide_end.py::test_callcallcodecall_010_suicide_end[fork_Amsterdam] +stCallDelegateCodesHomestead/test_callcallcodecallcode_011_suicide_end.py::test_callcallcodecallcode_011_suicide_end[fork_Amsterdam] +stCallDelegateCodesHomestead/test_callcodecall_10_suicide_end.py::test_callcodecall_10_suicide_end[fork_Amsterdam] +stCallDelegateCodesHomestead/test_callcodecallcall_100_suicide_end.py::test_callcodecallcall_100_suicide_end[fork_Amsterdam] +stCallDelegateCodesHomestead/test_callcodecallcallcode_101_suicide_end.py::test_callcodecallcallcode_101_suicide_end[fork_Amsterdam] +stCallDelegateCodesHomestead/test_callcodecallcode_11_suicide_end.py::test_callcodecallcode_11_suicide_end[fork_Amsterdam] +stCallDelegateCodesHomestead/test_callcodecallcodecall_110_suicide_end.py::test_callcodecallcodecall_110_suicide_end[fork_Amsterdam] +stCallDelegateCodesHomestead/test_callcodecallcodecallcode_111_suicide_end.py::test_callcodecallcodecallcode_111_suicide_end[fork_Amsterdam] + +# stCodeSizeLimit (2) +stCodeSizeLimit/test_create2_code_size_limit.py::test_create2_code_size_limit[fork_Amsterdam-valid] +stCodeSizeLimit/test_create_code_size_limit.py::test_create_code_size_limit[fork_Amsterdam-valid] + +# stCreate2 (38) +stCreate2/test_create2_oo_gafter_init_code_revert2.py::test_create2_oo_gafter_init_code_revert2[fork_Amsterdam] +stCreate2/test_create2_oog_from_call_refunds.py::test_create2_oog_from_call_refunds[fork_Amsterdam-SStore_CallCode_Refund_NoOoG] +stCreate2/test_create2_oog_from_call_refunds.py::test_create2_oog_from_call_refunds[fork_Amsterdam-SStore_Create2_Refund_NoOoG] +stCreate2/test_create2_oog_from_call_refunds.py::test_create2_oog_from_call_refunds[fork_Amsterdam-SStore_Create_Refund_NoOoG] +stCreate2/test_create2_oog_from_call_refunds.py::test_create2_oog_from_call_refunds[fork_Amsterdam-SStore_DelegateCall_Refund_NoOoG] +stCreate2/test_create2call_precompiles.py::test_create2call_precompiles[fork_Amsterdam-d7] +stCreate2/test_create2check_fields_in_initcode.py::test_create2check_fields_in_initcode[fork_Amsterdam-d0] +stCreate2/test_create2check_fields_in_initcode.py::test_create2check_fields_in_initcode[fork_Amsterdam-d1] +stCreate2/test_create2check_fields_in_initcode.py::test_create2check_fields_in_initcode[fork_Amsterdam-d2] +stCreate2/test_create2check_fields_in_initcode.py::test_create2check_fields_in_initcode[fork_Amsterdam-d4] +stCreate2/test_create2check_fields_in_initcode.py::test_create2check_fields_in_initcode[fork_Amsterdam-d5] +stCreate2/test_create2check_fields_in_initcode.py::test_create2check_fields_in_initcode[fork_Amsterdam-d6] +stCreate2/test_create2collision_selfdestructed_oog.py::test_create2collision_selfdestructed_oog[fork_Amsterdam-d0] +stCreate2/test_create2collision_selfdestructed_oog.py::test_create2collision_selfdestructed_oog[fork_Amsterdam-d1] +stCreate2/test_create2collision_selfdestructed_oog.py::test_create2collision_selfdestructed_oog[fork_Amsterdam-d2] +stCreate2/test_create2no_cash.py::test_create2no_cash[fork_Amsterdam-d1] +stCreate2/test_create_message_reverted_oog_in_init2.py::test_create_message_reverted_oog_in_init2[fork_Amsterdam--g0] +stCreate2/test_create_message_reverted_oog_in_init2.py::test_create_message_reverted_oog_in_init2[fork_Amsterdam--g1] +stCreate2/test_revert_depth_create2_oog.py::test_revert_depth_create2_oog[fork_Amsterdam-d0-g1-v0] +stCreate2/test_revert_depth_create2_oog.py::test_revert_depth_create2_oog[fork_Amsterdam-d0-g1-v1] +stCreate2/test_revert_depth_create2_oog.py::test_revert_depth_create2_oog[fork_Amsterdam-d1-g1-v0] +stCreate2/test_revert_depth_create2_oog.py::test_revert_depth_create2_oog[fork_Amsterdam-d1-g1-v1] +stCreate2/test_revert_depth_create2_oog_berlin.py::test_revert_depth_create2_oog_berlin[fork_Amsterdam-d0-g1-v0] +stCreate2/test_revert_depth_create2_oog_berlin.py::test_revert_depth_create2_oog_berlin[fork_Amsterdam-d0-g1-v1] +stCreate2/test_revert_depth_create2_oog_berlin.py::test_revert_depth_create2_oog_berlin[fork_Amsterdam-d1-g1-v0] +stCreate2/test_revert_depth_create2_oog_berlin.py::test_revert_depth_create2_oog_berlin[fork_Amsterdam-d1-g1-v1] +stCreate2/test_revert_depth_create_address_collision.py::test_revert_depth_create_address_collision[fork_Amsterdam-d0-g0-v0] +stCreate2/test_revert_depth_create_address_collision.py::test_revert_depth_create_address_collision[fork_Amsterdam-d0-g0-v1] +stCreate2/test_revert_depth_create_address_collision.py::test_revert_depth_create_address_collision[fork_Amsterdam-d1-g0-v0] +stCreate2/test_revert_depth_create_address_collision.py::test_revert_depth_create_address_collision[fork_Amsterdam-d1-g0-v1] +stCreate2/test_revert_depth_create_address_collision.py::test_revert_depth_create_address_collision[fork_Amsterdam-d1-g1-v0] +stCreate2/test_revert_depth_create_address_collision.py::test_revert_depth_create_address_collision[fork_Amsterdam-d1-g1-v1] +stCreate2/test_revert_depth_create_address_collision_berlin.py::test_revert_depth_create_address_collision_berlin[fork_Amsterdam-d0-g0-v0] +stCreate2/test_revert_depth_create_address_collision_berlin.py::test_revert_depth_create_address_collision_berlin[fork_Amsterdam-d0-g0-v1] +stCreate2/test_revert_depth_create_address_collision_berlin.py::test_revert_depth_create_address_collision_berlin[fork_Amsterdam-d1-g0-v0] +stCreate2/test_revert_depth_create_address_collision_berlin.py::test_revert_depth_create_address_collision_berlin[fork_Amsterdam-d1-g0-v1] +stCreate2/test_revert_depth_create_address_collision_berlin.py::test_revert_depth_create_address_collision_berlin[fork_Amsterdam-d1-g1-v0] +stCreate2/test_revert_depth_create_address_collision_berlin.py::test_revert_depth_create_address_collision_berlin[fork_Amsterdam-d1-g1-v1] + +# stCreateTest (52) +stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create-0xef-v1] +stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create-code-too-big-v1] +stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create-contructor-revert-v1] +stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create-high-nonce-v0] +stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create-high-nonce-v1] +stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create-invalid-opcode-v1] +stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create-ok-v1] +stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create-oog-constructor-v0] +stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create-oog-constructor-v1] +stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create-oog-post-constr-v0] +stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create-oog-post-constr-v1] +stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create2-0xef-v1] +stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create2-code-too-big-v1] +stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create2-contructor-revert-v1] +stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create2-invalid-opcode-v1] +stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create2-ok-v1] +stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create2-oog-constructor-v0] +stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create2-oog-constructor-v1] +stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create2-oog-post-constr-v0] +stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create2-oog-post-constr-v1] +stCreateTest/test_create_collision_results.py::test_create_collision_results[fork_Amsterdam-d0] +stCreateTest/test_create_collision_results.py::test_create_collision_results[fork_Amsterdam-d1] +stCreateTest/test_create_collision_to_empty2.py::test_create_collision_to_empty2[fork_Amsterdam-d0-g0-v0] +stCreateTest/test_create_collision_to_empty2.py::test_create_collision_to_empty2[fork_Amsterdam-d0-g0-v1] +stCreateTest/test_create_e_contract_create_ne_contract_in_init_oog_tr.py::test_create_e_contract_create_ne_contract_in_init_oog_tr[fork_Amsterdam--g0] +stCreateTest/test_create_e_contract_create_ne_contract_in_init_oog_tr.py::test_create_e_contract_create_ne_contract_in_init_oog_tr[fork_Amsterdam--g1] +stCreateTest/test_create_e_contract_then_call_to_non_existent_acc.py::test_create_e_contract_then_call_to_non_existent_acc[fork_Amsterdam] +stCreateTest/test_create_empty_contract.py::test_create_empty_contract[fork_Amsterdam] +stCreateTest/test_create_empty_contract_and_call_it_0wei.py::test_create_empty_contract_and_call_it_0wei[fork_Amsterdam] +stCreateTest/test_create_empty_contract_and_call_it_1wei.py::test_create_empty_contract_and_call_it_1wei[fork_Amsterdam] +stCreateTest/test_create_empty_contract_with_balance.py::test_create_empty_contract_with_balance[fork_Amsterdam] +stCreateTest/test_create_empty_contract_with_storage.py::test_create_empty_contract_with_storage[fork_Amsterdam] +stCreateTest/test_create_empty_contract_with_storage_and_call_it_0wei.py::test_create_empty_contract_with_storage_and_call_it_0wei[fork_Amsterdam] +stCreateTest/test_create_empty_contract_with_storage_and_call_it_1wei.py::test_create_empty_contract_with_storage_and_call_it_1wei[fork_Amsterdam] +stCreateTest/test_create_oo_gafter_init_code_returndata_size.py::test_create_oo_gafter_init_code_returndata_size[fork_Amsterdam] +stCreateTest/test_create_oo_gafter_init_code_revert2.py::test_create_oo_gafter_init_code_revert2[fork_Amsterdam-d0] +stCreateTest/test_create_oog_from_call_refunds.py::test_create_oog_from_call_refunds[fork_Amsterdam-SStore_Create2_Refund_NoOoG] +stCreateTest/test_create_oog_from_call_refunds.py::test_create_oog_from_call_refunds[fork_Amsterdam-SStore_Create_Refund_NoOoG] +stCreateTest/test_create_oog_from_call_refunds.py::test_create_oog_from_call_refunds[fork_Amsterdam-SStore_Refund_NoOoG2] +stCreateTest/test_create_oog_from_call_refunds.py::test_create_oog_from_call_refunds[fork_Amsterdam-SStore_Refund_NoOoG3] +stCreateTest/test_create_results.py::test_create_results[fork_Amsterdam-d0] +stCreateTest/test_create_results.py::test_create_results[fork_Amsterdam-d1] +stCreateTest/test_create_results.py::test_create_results[fork_Amsterdam-d2] +stCreateTest/test_create_results.py::test_create_results[fork_Amsterdam-d4] +stCreateTest/test_create_results.py::test_create_results[fork_Amsterdam-d5] +stCreateTest/test_create_results.py::test_create_results[fork_Amsterdam-d6] +stCreateTest/test_transaction_collision_to_empty2.py::test_transaction_collision_to_empty2[fork_Amsterdam--g1-v0] +stCreateTest/test_transaction_collision_to_empty2.py::test_transaction_collision_to_empty2[fork_Amsterdam--g1-v1] +stCreateTest/test_transaction_collision_to_empty_but_code.py::test_transaction_collision_to_empty_but_code[fork_Amsterdam--g1-v0] +stCreateTest/test_transaction_collision_to_empty_but_code.py::test_transaction_collision_to_empty_but_code[fork_Amsterdam--g1-v1] +stCreateTest/test_transaction_collision_to_empty_but_nonce.py::test_transaction_collision_to_empty_but_nonce[fork_Amsterdam--g1-v0] +stCreateTest/test_transaction_collision_to_empty_but_nonce.py::test_transaction_collision_to_empty_but_nonce[fork_Amsterdam--g1-v1] + +# stDelegatecallTestHomestead (7) +stDelegatecallTestHomestead/test_call1024_oog.py::test_call1024_oog[fork_Amsterdam--g0] +stDelegatecallTestHomestead/test_call1024_oog.py::test_call1024_oog[fork_Amsterdam--g1] +stDelegatecallTestHomestead/test_deleagate_call_after_value_transfer.py::test_deleagate_call_after_value_transfer[fork_Amsterdam] +stDelegatecallTestHomestead/test_delegatecall1024_oog.py::test_delegatecall1024_oog[fork_Amsterdam] +stDelegatecallTestHomestead/test_delegatecall_emptycontract.py::test_delegatecall_emptycontract[fork_Amsterdam] +stDelegatecallTestHomestead/test_delegatecall_in_initcode_to_existing_contract.py::test_delegatecall_in_initcode_to_existing_contract[fork_Amsterdam] +stDelegatecallTestHomestead/test_delegatecode_dynamic_code.py::test_delegatecode_dynamic_code[fork_Amsterdam] + +# stEIP150Specific (7) +stEIP150Specific/test_call_ask_more_gas_on_depth2_then_transaction_has.py::test_call_ask_more_gas_on_depth2_then_transaction_has[fork_Amsterdam] +stEIP150Specific/test_create_and_gas_inside_create.py::test_create_and_gas_inside_create[fork_Amsterdam] +stEIP150Specific/test_delegate_call_on_eip.py::test_delegate_call_on_eip[fork_Amsterdam] +stEIP150Specific/test_new_gas_price_for_codes.py::test_new_gas_price_for_codes[fork_Amsterdam] +stEIP150Specific/test_transaction64_rule_d64e0.py::test_transaction64_rule_d64e0[fork_Amsterdam] +stEIP150Specific/test_transaction64_rule_d64m1.py::test_transaction64_rule_d64m1[fork_Amsterdam] +stEIP150Specific/test_transaction64_rule_d64p1.py::test_transaction64_rule_d64p1[fork_Amsterdam] + +# stEIP150singleCodeGasPrices (28) +stEIP150singleCodeGasPrices/test_gas_cost.py::test_gas_cost[fork_Amsterdam-d40] +stEIP150singleCodeGasPrices/test_gas_cost_berlin.py::test_gas_cost_berlin[fork_Amsterdam-d40] +stEIP150singleCodeGasPrices/test_raw_call_code_gas.py::test_raw_call_code_gas[fork_Amsterdam] +stEIP150singleCodeGasPrices/test_raw_call_code_gas_ask.py::test_raw_call_code_gas_ask[fork_Amsterdam] +stEIP150singleCodeGasPrices/test_raw_call_code_gas_memory.py::test_raw_call_code_gas_memory[fork_Amsterdam] +stEIP150singleCodeGasPrices/test_raw_call_code_gas_memory_ask.py::test_raw_call_code_gas_memory_ask[fork_Amsterdam] +stEIP150singleCodeGasPrices/test_raw_call_code_gas_value_transfer.py::test_raw_call_code_gas_value_transfer[fork_Amsterdam] +stEIP150singleCodeGasPrices/test_raw_call_code_gas_value_transfer_ask.py::test_raw_call_code_gas_value_transfer_ask[fork_Amsterdam] +stEIP150singleCodeGasPrices/test_raw_call_code_gas_value_transfer_memory.py::test_raw_call_code_gas_value_transfer_memory[fork_Amsterdam] +stEIP150singleCodeGasPrices/test_raw_call_code_gas_value_transfer_memory_ask.py::test_raw_call_code_gas_value_transfer_memory_ask[fork_Amsterdam] +stEIP150singleCodeGasPrices/test_raw_call_gas.py::test_raw_call_gas[fork_Amsterdam] +stEIP150singleCodeGasPrices/test_raw_call_gas_ask.py::test_raw_call_gas_ask[fork_Amsterdam] +stEIP150singleCodeGasPrices/test_raw_call_gas_value_transfer.py::test_raw_call_gas_value_transfer[fork_Amsterdam] +stEIP150singleCodeGasPrices/test_raw_call_gas_value_transfer_ask.py::test_raw_call_gas_value_transfer_ask[fork_Amsterdam] +stEIP150singleCodeGasPrices/test_raw_call_gas_value_transfer_memory.py::test_raw_call_gas_value_transfer_memory[fork_Amsterdam] +stEIP150singleCodeGasPrices/test_raw_call_gas_value_transfer_memory_ask.py::test_raw_call_gas_value_transfer_memory_ask[fork_Amsterdam] +stEIP150singleCodeGasPrices/test_raw_call_memory_gas.py::test_raw_call_memory_gas[fork_Amsterdam] +stEIP150singleCodeGasPrices/test_raw_call_memory_gas_ask.py::test_raw_call_memory_gas_ask[fork_Amsterdam] +stEIP150singleCodeGasPrices/test_raw_create_fail_gas_value_transfer.py::test_raw_create_fail_gas_value_transfer[fork_Amsterdam] +stEIP150singleCodeGasPrices/test_raw_create_fail_gas_value_transfer2.py::test_raw_create_fail_gas_value_transfer2[fork_Amsterdam] +stEIP150singleCodeGasPrices/test_raw_create_gas.py::test_raw_create_gas[fork_Amsterdam] +stEIP150singleCodeGasPrices/test_raw_create_gas_memory.py::test_raw_create_gas_memory[fork_Amsterdam] +stEIP150singleCodeGasPrices/test_raw_create_gas_value_transfer.py::test_raw_create_gas_value_transfer[fork_Amsterdam] +stEIP150singleCodeGasPrices/test_raw_create_gas_value_transfer_memory.py::test_raw_create_gas_value_transfer_memory[fork_Amsterdam] +stEIP150singleCodeGasPrices/test_raw_delegate_call_gas.py::test_raw_delegate_call_gas[fork_Amsterdam] +stEIP150singleCodeGasPrices/test_raw_delegate_call_gas_ask.py::test_raw_delegate_call_gas_ask[fork_Amsterdam] +stEIP150singleCodeGasPrices/test_raw_delegate_call_gas_memory.py::test_raw_delegate_call_gas_memory[fork_Amsterdam] +stEIP150singleCodeGasPrices/test_raw_delegate_call_gas_memory_ask.py::test_raw_delegate_call_gas_memory_ask[fork_Amsterdam] + +# stEIP1559 (1) +stEIP1559/test_sender_balance.py::test_sender_balance[fork_Amsterdam] + +# stEIP158Specific (1) +stEIP158Specific/test_exp_empty.py::test_exp_empty[fork_Amsterdam] + +# stEIP3651_warmcoinbase (8) +stEIP3651_warmcoinbase/test_coinbase_warm_account_call_gas.py::test_coinbase_warm_account_call_gas[fork_Amsterdam-d0] +stEIP3651_warmcoinbase/test_coinbase_warm_account_call_gas.py::test_coinbase_warm_account_call_gas[fork_Amsterdam-d1] +stEIP3651_warmcoinbase/test_coinbase_warm_account_call_gas.py::test_coinbase_warm_account_call_gas[fork_Amsterdam-d2] +stEIP3651_warmcoinbase/test_coinbase_warm_account_call_gas.py::test_coinbase_warm_account_call_gas[fork_Amsterdam-d3] +stEIP3651_warmcoinbase/test_coinbase_warm_account_call_gas.py::test_coinbase_warm_account_call_gas[fork_Amsterdam-d4] +stEIP3651_warmcoinbase/test_coinbase_warm_account_call_gas.py::test_coinbase_warm_account_call_gas[fork_Amsterdam-d5] +stEIP3651_warmcoinbase/test_coinbase_warm_account_call_gas.py::test_coinbase_warm_account_call_gas[fork_Amsterdam-d6] +stEIP3651_warmcoinbase/test_coinbase_warm_account_call_gas.py::test_coinbase_warm_account_call_gas[fork_Amsterdam-d7] + +# stEIP3855_push0 (4) +stEIP3855_push0/test_push0.py::test_push0[fork_Amsterdam-1025_push0] +stEIP3855_push0/test_push0_gas.py::test_push0_gas[fork_Amsterdam] +stEIP3855_push0/test_push0_gas2.py::test_push0_gas2[fork_Amsterdam-use_push0] +stEIP3855_push0/test_push0_gas2.py::test_push0_gas2[fork_Amsterdam-use_push1_00] + +# stEIP3860_limitmeterinitcode (6) +stEIP3860_limitmeterinitcode/test_create2_init_code_size_limit.py::test_create2_init_code_size_limit[fork_Amsterdam-invalid] +stEIP3860_limitmeterinitcode/test_create2_init_code_size_limit.py::test_create2_init_code_size_limit[fork_Amsterdam-valid] +stEIP3860_limitmeterinitcode/test_create_init_code_size_limit.py::test_create_init_code_size_limit[fork_Amsterdam-invalid] +stEIP3860_limitmeterinitcode/test_create_init_code_size_limit.py::test_create_init_code_size_limit[fork_Amsterdam-valid] +stEIP3860_limitmeterinitcode/test_creation_tx_init_code_size_limit.py::test_creation_tx_init_code_size_limit[fork_Amsterdam-invalid] +stEIP3860_limitmeterinitcode/test_creation_tx_init_code_size_limit.py::test_creation_tx_init_code_size_limit[fork_Amsterdam-valid] + +# stEIP5656_MCOPY (55) +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src0_size0-g0] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src0_size0-g1] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src0_size1-g0] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src0_size1-g1] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src0_size31-g0] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src0_size31-g1] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src0_size32-g0] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src0_size32-g1] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src0_size33-g0] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src0_size33-g1] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src0_size44767-g0] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src0_size44767-g1] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src0_size44768-g0] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src0_size44768-g1] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src0_size44769-g0] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src0_size44769-g1] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src1_size0-g0] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src1_size0-g1] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src1_size1-g0] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src1_size1-g1] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src1_size31-g0] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src1_size31-g1] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src1_size32-g0] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src1_size32-g1] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src1_size33-g0] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src1_size33-g1] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src1_size44767-g0] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src1_size44768-g0] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src1_size44769-g0] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src31_size0-g0] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src31_size0-g1] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src31_size1-g0] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src31_size1-g1] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src31_size31-g0] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src31_size31-g1] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src31_size32-g0] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src31_size32-g1] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src31_size33-g0] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src31_size33-g1] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src31_size44767-g0] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src31_size44768-g0] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src31_size44769-g0] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src32_size0-g0] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src32_size0-g1] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src32_size1-g0] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src32_size1-g1] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src32_size31-g0] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src32_size31-g1] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src32_size32-g0] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src32_size32-g1] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src32_size33-g0] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src32_size33-g1] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src32_size44767-g0] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src32_size44768-g0] +stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src32_size44769-g0] + +# stHomesteadSpecific (1) +stHomesteadSpecific/test_contract_creation_oo_gdont_leave_empty_contract_via_transaction.py::test_contract_creation_oo_gdont_leave_empty_contract_via_transaction[fork_Amsterdam] + +# stInitCodeTest (7) +stInitCodeTest/test_out_of_gas_contract_creation.py::test_out_of_gas_contract_creation[fork_Amsterdam-d0-g0] +stInitCodeTest/test_out_of_gas_contract_creation.py::test_out_of_gas_contract_creation[fork_Amsterdam-d0-g1] +stInitCodeTest/test_out_of_gas_contract_creation.py::test_out_of_gas_contract_creation[fork_Amsterdam-d1-g0] +stInitCodeTest/test_out_of_gas_contract_creation.py::test_out_of_gas_contract_creation[fork_Amsterdam-d1-g1] +stInitCodeTest/test_out_of_gas_prefunded_contract_creation.py::test_out_of_gas_prefunded_contract_creation[fork_Amsterdam--g0] +stInitCodeTest/test_out_of_gas_prefunded_contract_creation.py::test_out_of_gas_prefunded_contract_creation[fork_Amsterdam--g1] +stInitCodeTest/test_out_of_gas_prefunded_contract_creation.py::test_out_of_gas_prefunded_contract_creation[fork_Amsterdam--g2] + +# stMemExpandingEIP150Calls (4) +stMemExpandingEIP150Calls/test_call_ask_more_gas_on_depth2_then_transaction_has_with_mem_expanding_calls.py::test_call_ask_more_gas_on_depth2_then_transaction_has_with_mem_expanding_calls[fork_Amsterdam] +stMemExpandingEIP150Calls/test_call_goes_oog_on_second_level_with_mem_expanding_calls.py::test_call_goes_oog_on_second_level_with_mem_expanding_calls[fork_Amsterdam] +stMemExpandingEIP150Calls/test_create_and_gas_inside_create_with_mem_expanding_calls.py::test_create_and_gas_inside_create_with_mem_expanding_calls[fork_Amsterdam] +stMemExpandingEIP150Calls/test_new_gas_price_for_codes_with_mem_expanding_calls.py::test_new_gas_price_for_codes_with_mem_expanding_calls[fork_Amsterdam] + +# stMemoryStressTest (1) +stMemoryStressTest/test_return_bounds.py::test_return_bounds[fork_Amsterdam--g1] + +# stMemoryTest (5) +stMemoryTest/test_call_data_copy_offset.py::test_call_data_copy_offset[fork_Amsterdam] +stMemoryTest/test_calldatacopy_dejavu2.py::test_calldatacopy_dejavu2[fork_Amsterdam] +stMemoryTest/test_code_copy_offset.py::test_code_copy_offset[fork_Amsterdam] +stMemoryTest/test_oog.py::test_oog[fork_Amsterdam-success14] +stMemoryTest/test_oog.py::test_oog[fork_Amsterdam-success15] + +# stNonZeroCallsTest (10) +stNonZeroCallsTest/test_non_zero_value_call.py::test_non_zero_value_call[fork_Amsterdam] +stNonZeroCallsTest/test_non_zero_value_call_to_empty_paris.py::test_non_zero_value_call_to_empty_paris[fork_Amsterdam] +stNonZeroCallsTest/test_non_zero_value_call_to_one_storage_key_paris.py::test_non_zero_value_call_to_one_storage_key_paris[fork_Amsterdam] +stNonZeroCallsTest/test_non_zero_value_callcode.py::test_non_zero_value_callcode[fork_Amsterdam] +stNonZeroCallsTest/test_non_zero_value_callcode_to_empty_paris.py::test_non_zero_value_callcode_to_empty_paris[fork_Amsterdam] +stNonZeroCallsTest/test_non_zero_value_callcode_to_one_storage_key_paris.py::test_non_zero_value_callcode_to_one_storage_key_paris[fork_Amsterdam] +stNonZeroCallsTest/test_non_zero_value_delegatecall.py::test_non_zero_value_delegatecall[fork_Amsterdam] +stNonZeroCallsTest/test_non_zero_value_delegatecall_to_empty_paris.py::test_non_zero_value_delegatecall_to_empty_paris[fork_Amsterdam] +stNonZeroCallsTest/test_non_zero_value_delegatecall_to_non_non_zero_balance.py::test_non_zero_value_delegatecall_to_non_non_zero_balance[fork_Amsterdam] +stNonZeroCallsTest/test_non_zero_value_delegatecall_to_one_storage_key_paris.py::test_non_zero_value_delegatecall_to_one_storage_key_paris[fork_Amsterdam] + +# stPreCompiledContracts2 (2) +stPreCompiledContracts2/test_call_sha256_1_nonzero_value.py::test_call_sha256_1_nonzero_value[fork_Amsterdam] +stPreCompiledContracts2/test_ecrecover_short_buff.py::test_ecrecover_short_buff[fork_Amsterdam] + +# stRecursiveCreate (1) +stRecursiveCreate/test_recursive_create.py::test_recursive_create[fork_Amsterdam] + +# stRefundTest (7) +stRefundTest/test_refund50_2.py::test_refund50_2[fork_Amsterdam] +stRefundTest/test_refund50percent_cap.py::test_refund50percent_cap[fork_Amsterdam] +stRefundTest/test_refund600.py::test_refund600[fork_Amsterdam] +stRefundTest/test_refund_call_a.py::test_refund_call_a[fork_Amsterdam] +stRefundTest/test_refund_suicide50procent_cap.py::test_refund_suicide50procent_cap[fork_Amsterdam-d0] +stRefundTest/test_refund_suicide50procent_cap.py::test_refund_suicide50procent_cap[fork_Amsterdam-d1] +stRefundTest/test_refund_tx_to_suicide.py::test_refund_tx_to_suicide[fork_Amsterdam] + +# stReturnDataTest (2) +stReturnDataTest/test_returndatasize_after_successful_callcode.py::test_returndatasize_after_successful_callcode[fork_Amsterdam] +stReturnDataTest/test_subcall_return_more_then_expected.py::test_subcall_return_more_then_expected[fork_Amsterdam] + +# stRevertTest (28) +stRevertTest/test_loop_calls_depth_then_revert.py::test_loop_calls_depth_then_revert[fork_Amsterdam] +stRevertTest/test_loop_calls_then_revert.py::test_loop_calls_then_revert[fork_Amsterdam] +stRevertTest/test_loop_delegate_calls_depth_then_revert.py::test_loop_delegate_calls_depth_then_revert[fork_Amsterdam] +stRevertTest/test_revert_depth2.py::test_revert_depth2[fork_Amsterdam--g1] +stRevertTest/test_revert_depth_create_address_collision.py::test_revert_depth_create_address_collision[fork_Amsterdam-d0-g1-v0] +stRevertTest/test_revert_depth_create_address_collision.py::test_revert_depth_create_address_collision[fork_Amsterdam-d0-g1-v1] +stRevertTest/test_revert_depth_create_address_collision.py::test_revert_depth_create_address_collision[fork_Amsterdam-d1-g1-v0] +stRevertTest/test_revert_depth_create_address_collision.py::test_revert_depth_create_address_collision[fork_Amsterdam-d1-g1-v1] +stRevertTest/test_revert_depth_create_oog.py::test_revert_depth_create_oog[fork_Amsterdam-d0-g1-v0] +stRevertTest/test_revert_depth_create_oog.py::test_revert_depth_create_oog[fork_Amsterdam-d0-g1-v1] +stRevertTest/test_revert_depth_create_oog.py::test_revert_depth_create_oog[fork_Amsterdam-d1-g1-v0] +stRevertTest/test_revert_depth_create_oog.py::test_revert_depth_create_oog[fork_Amsterdam-d1-g1-v1] +stRevertTest/test_revert_opcode_in_calls_on_non_empty_return_data.py::test_revert_opcode_in_calls_on_non_empty_return_data[fork_Amsterdam-d0-g0] +stRevertTest/test_revert_opcode_in_calls_on_non_empty_return_data.py::test_revert_opcode_in_calls_on_non_empty_return_data[fork_Amsterdam-d1-g0] +stRevertTest/test_revert_opcode_in_calls_on_non_empty_return_data.py::test_revert_opcode_in_calls_on_non_empty_return_data[fork_Amsterdam-d2-g0] +stRevertTest/test_revert_opcode_in_calls_on_non_empty_return_data.py::test_revert_opcode_in_calls_on_non_empty_return_data[fork_Amsterdam-d3-g0] +stRevertTest/test_revert_opcode_multiple_sub_calls.py::test_revert_opcode_multiple_sub_calls[fork_Amsterdam-d0-g0-v0] +stRevertTest/test_revert_opcode_multiple_sub_calls.py::test_revert_opcode_multiple_sub_calls[fork_Amsterdam-d0-g0-v1] +stRevertTest/test_revert_opcode_multiple_sub_calls.py::test_revert_opcode_multiple_sub_calls[fork_Amsterdam-d0-g2-v0] +stRevertTest/test_revert_opcode_multiple_sub_calls.py::test_revert_opcode_multiple_sub_calls[fork_Amsterdam-d0-g2-v1] +stRevertTest/test_revert_opcode_multiple_sub_calls.py::test_revert_opcode_multiple_sub_calls[fork_Amsterdam-d1-g0-v0] +stRevertTest/test_revert_opcode_multiple_sub_calls.py::test_revert_opcode_multiple_sub_calls[fork_Amsterdam-d1-g0-v1] +stRevertTest/test_revert_opcode_multiple_sub_calls.py::test_revert_opcode_multiple_sub_calls[fork_Amsterdam-d2-g0-v0] +stRevertTest/test_revert_opcode_multiple_sub_calls.py::test_revert_opcode_multiple_sub_calls[fork_Amsterdam-d2-g0-v1] +stRevertTest/test_revert_opcode_multiple_sub_calls.py::test_revert_opcode_multiple_sub_calls[fork_Amsterdam-d3-g0-v0] +stRevertTest/test_revert_opcode_multiple_sub_calls.py::test_revert_opcode_multiple_sub_calls[fork_Amsterdam-d3-g0-v1] +stRevertTest/test_revert_opcode_multiple_sub_calls.py::test_revert_opcode_multiple_sub_calls[fork_Amsterdam-d3-g2-v0] +stRevertTest/test_revert_opcode_multiple_sub_calls.py::test_revert_opcode_multiple_sub_calls[fork_Amsterdam-d3-g2-v1] + +# stSStoreTest (10) +stSStoreTest/test_sstore_change_from_external_call_in_init_code.py::test_sstore_change_from_external_call_in_init_code[fork_Amsterdam-d0] +stSStoreTest/test_sstore_change_from_external_call_in_init_code.py::test_sstore_change_from_external_call_in_init_code[fork_Amsterdam-d1] +stSStoreTest/test_sstore_change_from_external_call_in_init_code.py::test_sstore_change_from_external_call_in_init_code[fork_Amsterdam-d3] +stSStoreTest/test_sstore_change_from_external_call_in_init_code.py::test_sstore_change_from_external_call_in_init_code[fork_Amsterdam-d4] +stSStoreTest/test_sstore_change_from_external_call_in_init_code.py::test_sstore_change_from_external_call_in_init_code[fork_Amsterdam-d5] +stSStoreTest/test_sstore_change_from_external_call_in_init_code.py::test_sstore_change_from_external_call_in_init_code[fork_Amsterdam-d7] +stSStoreTest/test_sstore_gas.py::test_sstore_gas[fork_Amsterdam] +stSStoreTest/test_sstore_gas_left.py::test_sstore_gas_left[fork_Amsterdam-d2] +stSStoreTest/test_sstore_gas_left.py::test_sstore_gas_left[fork_Amsterdam-d5] +stSStoreTest/test_sstore_gas_left.py::test_sstore_gas_left[fork_Amsterdam-d8] + +# stSelfBalance (3) +stSelfBalance/test_self_balance.py::test_self_balance[fork_Amsterdam] +stSelfBalance/test_self_balance_equals_balance.py::test_self_balance_equals_balance[fork_Amsterdam] +stSelfBalance/test_self_balance_gas_cost.py::test_self_balance_gas_cost[fork_Amsterdam] + +# stSolidityTest (5) +stSolidityTest/test_recursive_create_contracts.py::test_recursive_create_contracts[fork_Amsterdam] +stSolidityTest/test_test_contract_interaction.py::test_test_contract_interaction[fork_Amsterdam] +stSolidityTest/test_test_contract_suicide.py::test_test_contract_suicide[fork_Amsterdam] +stSolidityTest/test_test_overflow.py::test_test_overflow[fork_Amsterdam] +stSolidityTest/test_test_structures_and_variabless.py::test_test_structures_and_variabless[fork_Amsterdam] + +# stSpecialTest (1) +stSpecialTest/test_make_money.py::test_make_money[fork_Amsterdam] + +# stStaticCall (81) +stStaticCall/test_static_call_ask_more_gas_on_depth2_then_transaction_has.py::test_static_call_ask_more_gas_on_depth2_then_transaction_has[fork_Amsterdam-d0] +stStaticCall/test_static_call_contract_to_create_contract_oog.py::test_static_call_contract_to_create_contract_oog[fork_Amsterdam--v1] +stStaticCall/test_static_call_recursive_bomb3.py::test_static_call_recursive_bomb3[fork_Amsterdam] +stStaticCall/test_static_call_sha256_1_nonzero_value.py::test_static_call_sha256_1_nonzero_value[fork_Amsterdam] +stStaticCall/test_static_call_value_inherit_from_call.py::test_static_call_value_inherit_from_call[fork_Amsterdam] +stStaticCall/test_static_callcall_00_ooge_1.py::test_static_callcall_00_ooge_1[fork_Amsterdam-d0] +stStaticCall/test_static_callcall_00_ooge_1.py::test_static_callcall_00_ooge_1[fork_Amsterdam-d1] +stStaticCall/test_static_callcallcode_01_ooge_2.py::test_static_callcallcode_01_ooge_2[fork_Amsterdam-d0] +stStaticCall/test_static_callcallcode_01_ooge_2.py::test_static_callcallcode_01_ooge_2[fork_Amsterdam-d1] +stStaticCall/test_static_callcallcodecallcode_011_ooge.py::test_static_callcallcodecallcode_011_ooge[fork_Amsterdam-d0] +stStaticCall/test_static_callcallcodecallcode_011_ooge.py::test_static_callcallcodecallcode_011_ooge[fork_Amsterdam-d1] +stStaticCall/test_static_callcallcodecallcode_011_ooge_2.py::test_static_callcallcodecallcode_011_ooge_2[fork_Amsterdam-d0] +stStaticCall/test_static_callcallcodecallcode_011_ooge_2.py::test_static_callcallcodecallcode_011_ooge_2[fork_Amsterdam-d1] +stStaticCall/test_static_callcallcodecallcode_011_oogm_after.py::test_static_callcallcodecallcode_011_oogm_after[fork_Amsterdam-d0] +stStaticCall/test_static_callcallcodecallcode_011_oogm_after.py::test_static_callcallcodecallcode_011_oogm_after[fork_Amsterdam-d1] +stStaticCall/test_static_callcallcodecallcode_011_oogm_after2.py::test_static_callcallcodecallcode_011_oogm_after2[fork_Amsterdam-d0] +stStaticCall/test_static_callcallcodecallcode_011_oogm_after2.py::test_static_callcallcodecallcode_011_oogm_after2[fork_Amsterdam-d1] +stStaticCall/test_static_callcallcodecallcode_011_oogm_after_1.py::test_static_callcallcodecallcode_011_oogm_after_1[fork_Amsterdam-d0] +stStaticCall/test_static_callcallcodecallcode_011_oogm_after_1.py::test_static_callcallcodecallcode_011_oogm_after_1[fork_Amsterdam-d1] +stStaticCall/test_static_callcallcodecallcode_011_oogm_after_2.py::test_static_callcallcodecallcode_011_oogm_after_2[fork_Amsterdam-d0] +stStaticCall/test_static_callcallcodecallcode_011_oogm_after_2.py::test_static_callcallcodecallcode_011_oogm_after_2[fork_Amsterdam-d1] +stStaticCall/test_static_callcallcodecallcode_011_oogm_before.py::test_static_callcallcodecallcode_011_oogm_before[fork_Amsterdam-d0] +stStaticCall/test_static_callcallcodecallcode_011_oogm_before.py::test_static_callcallcodecallcode_011_oogm_before[fork_Amsterdam-d1] +stStaticCall/test_static_callcallcodecallcode_011_oogm_before2.py::test_static_callcallcodecallcode_011_oogm_before2[fork_Amsterdam-d0] +stStaticCall/test_static_callcallcodecallcode_011_oogm_before2.py::test_static_callcallcodecallcode_011_oogm_before2[fork_Amsterdam-d1] +stStaticCall/test_static_callcallcodecallcode_011_oogm_before2.py::test_static_callcallcodecallcode_011_oogm_before2[fork_Amsterdam-d2] +stStaticCall/test_static_callcodecall_10_ooge.py::test_static_callcodecall_10_ooge[fork_Amsterdam-d0] +stStaticCall/test_static_callcodecall_10_ooge.py::test_static_callcodecall_10_ooge[fork_Amsterdam-d1] +stStaticCall/test_static_callcodecall_10_ooge_2.py::test_static_callcodecall_10_ooge_2[fork_Amsterdam-d0] +stStaticCall/test_static_callcodecall_10_ooge_2.py::test_static_callcodecall_10_ooge_2[fork_Amsterdam-d1] +stStaticCall/test_static_callcodecallcall_100_ooge.py::test_static_callcodecallcall_100_ooge[fork_Amsterdam-d0] +stStaticCall/test_static_callcodecallcall_100_ooge.py::test_static_callcodecallcall_100_ooge[fork_Amsterdam-d1] +stStaticCall/test_static_callcodecallcall_100_ooge2.py::test_static_callcodecallcall_100_ooge2[fork_Amsterdam-d0] +stStaticCall/test_static_callcodecallcall_100_ooge2.py::test_static_callcodecallcall_100_ooge2[fork_Amsterdam-d1] +stStaticCall/test_static_callcodecallcall_100_oogm_after_3.py::test_static_callcodecallcall_100_oogm_after_3[fork_Amsterdam--v0] +stStaticCall/test_static_callcodecallcall_100_oogm_after_3.py::test_static_callcodecallcall_100_oogm_after_3[fork_Amsterdam--v1] +stStaticCall/test_static_callcodecallcall_100_oogm_before.py::test_static_callcodecallcall_100_oogm_before[fork_Amsterdam-d0] +stStaticCall/test_static_callcodecallcall_100_oogm_before.py::test_static_callcodecallcall_100_oogm_before[fork_Amsterdam-d1] +stStaticCall/test_static_callcodecallcall_100_oogm_before2.py::test_static_callcodecallcall_100_oogm_before2[fork_Amsterdam-d0-v0] +stStaticCall/test_static_callcodecallcall_100_oogm_before2.py::test_static_callcodecallcall_100_oogm_before2[fork_Amsterdam-d0-v1] +stStaticCall/test_static_callcodecallcall_100_oogm_before2.py::test_static_callcodecallcall_100_oogm_before2[fork_Amsterdam-d1-v0] +stStaticCall/test_static_callcodecallcall_100_oogm_before2.py::test_static_callcodecallcall_100_oogm_before2[fork_Amsterdam-d1-v1] +stStaticCall/test_static_callcodecallcallcode_101_ooge_2.py::test_static_callcodecallcallcode_101_ooge_2[fork_Amsterdam] +stStaticCall/test_static_callcodecallcallcode_101_oogm_after.py::test_static_callcodecallcallcode_101_oogm_after[fork_Amsterdam] +stStaticCall/test_static_callcodecallcallcode_101_oogm_after2.py::test_static_callcodecallcallcode_101_oogm_after2[fork_Amsterdam--v0] +stStaticCall/test_static_callcodecallcallcode_101_oogm_after2.py::test_static_callcodecallcallcode_101_oogm_after2[fork_Amsterdam--v1] +stStaticCall/test_static_callcodecallcallcode_101_oogm_before.py::test_static_callcodecallcallcode_101_oogm_before[fork_Amsterdam] +stStaticCall/test_static_callcodecallcallcode_101_oogm_before2.py::test_static_callcodecallcallcode_101_oogm_before2[fork_Amsterdam--v0] +stStaticCall/test_static_callcodecallcallcode_101_oogm_before2.py::test_static_callcodecallcallcode_101_oogm_before2[fork_Amsterdam--v1] +stStaticCall/test_static_callcodecallcodecall_110_ooge.py::test_static_callcodecallcodecall_110_ooge[fork_Amsterdam] +stStaticCall/test_static_callcodecallcodecall_110_ooge2.py::test_static_callcodecallcodecall_110_ooge2[fork_Amsterdam--v0] +stStaticCall/test_static_callcodecallcodecall_110_ooge2.py::test_static_callcodecallcodecall_110_ooge2[fork_Amsterdam--v1] +stStaticCall/test_static_callcodecallcodecall_110_ooge2.py::test_static_callcodecallcodecall_110_ooge2[fork_Amsterdam--v2] +stStaticCall/test_static_callcodecallcodecall_110_oogm_after.py::test_static_callcodecallcodecall_110_oogm_after[fork_Amsterdam] +stStaticCall/test_static_callcodecallcodecall_110_oogm_after2.py::test_static_callcodecallcodecall_110_oogm_after2[fork_Amsterdam--v0] +stStaticCall/test_static_callcodecallcodecall_110_oogm_after2.py::test_static_callcodecallcodecall_110_oogm_after2[fork_Amsterdam--v1] +stStaticCall/test_static_callcodecallcodecall_110_oogm_after2.py::test_static_callcodecallcodecall_110_oogm_after2[fork_Amsterdam--v2] +stStaticCall/test_static_callcodecallcodecall_110_oogm_after_2.py::test_static_callcodecallcodecall_110_oogm_after_2[fork_Amsterdam] +stStaticCall/test_static_callcodecallcodecall_110_oogm_after_3.py::test_static_callcodecallcodecall_110_oogm_after_3[fork_Amsterdam] +stStaticCall/test_static_callcodecallcodecall_110_oogm_before.py::test_static_callcodecallcodecall_110_oogm_before[fork_Amsterdam] +stStaticCall/test_static_callcodecallcodecall_110_oogm_before2.py::test_static_callcodecallcodecall_110_oogm_before2[fork_Amsterdam--v0] +stStaticCall/test_static_callcodecallcodecall_110_oogm_before2.py::test_static_callcodecallcodecall_110_oogm_before2[fork_Amsterdam--v1] +stStaticCall/test_static_callcodecallcodecall_110_oogm_before2.py::test_static_callcodecallcodecall_110_oogm_before2[fork_Amsterdam--v2] +stStaticCall/test_static_calldelcode_01_ooge.py::test_static_calldelcode_01_ooge[fork_Amsterdam-d0] +stStaticCall/test_static_calldelcode_01_ooge.py::test_static_calldelcode_01_ooge[fork_Amsterdam-d1] +stStaticCall/test_static_check_opcodes4.py::test_static_check_opcodes4[fork_Amsterdam--g1-v0] +stStaticCall/test_static_check_opcodes4.py::test_static_check_opcodes4[fork_Amsterdam--g1-v1] +stStaticCall/test_static_check_opcodes5.py::test_static_check_opcodes5[fork_Amsterdam-d0-g1-v0] +stStaticCall/test_static_check_opcodes5.py::test_static_check_opcodes5[fork_Amsterdam-d0-g1-v1] +stStaticCall/test_static_check_opcodes5.py::test_static_check_opcodes5[fork_Amsterdam-d1-g1-v0] +stStaticCall/test_static_check_opcodes5.py::test_static_check_opcodes5[fork_Amsterdam-d1-g1-v1] +stStaticCall/test_static_check_opcodes5.py::test_static_check_opcodes5[fork_Amsterdam-d2-g1-v0] +stStaticCall/test_static_check_opcodes5.py::test_static_check_opcodes5[fork_Amsterdam-d2-g1-v1] +stStaticCall/test_static_check_opcodes5.py::test_static_check_opcodes5[fork_Amsterdam-d3-g1-v0] +stStaticCall/test_static_check_opcodes5.py::test_static_check_opcodes5[fork_Amsterdam-d3-g1-v1] +stStaticCall/test_static_check_opcodes5.py::test_static_check_opcodes5[fork_Amsterdam-d4-g1-v0] +stStaticCall/test_static_check_opcodes5.py::test_static_check_opcodes5[fork_Amsterdam-d4-g1-v1] +stStaticCall/test_static_create_empty_contract_and_call_it_0wei.py::test_static_create_empty_contract_and_call_it_0wei[fork_Amsterdam] +stStaticCall/test_static_create_empty_contract_with_storage_and_call_it_0wei.py::test_static_create_empty_contract_with_storage_and_call_it_0wei[fork_Amsterdam] +stStaticCall/test_static_execute_call_that_ask_fore_gas_then_trabsaction_has.py::test_static_execute_call_that_ask_fore_gas_then_trabsaction_has[fork_Amsterdam-d0] +stStaticCall/test_static_revert_opcode_calls.py::test_static_revert_opcode_calls[fork_Amsterdam--g1] + +# stStaticFlagEnabled (6) +stStaticFlagEnabled/test_callcode_to_precompile_from_called_contract.py::test_callcode_to_precompile_from_called_contract[fork_Amsterdam] +stStaticFlagEnabled/test_callcode_to_precompile_from_contract_initialization.py::test_callcode_to_precompile_from_contract_initialization[fork_Amsterdam] +stStaticFlagEnabled/test_callcode_to_precompile_from_transaction.py::test_callcode_to_precompile_from_transaction[fork_Amsterdam] +stStaticFlagEnabled/test_delegatecall_to_precompile_from_called_contract.py::test_delegatecall_to_precompile_from_called_contract[fork_Amsterdam] +stStaticFlagEnabled/test_delegatecall_to_precompile_from_contract_initialization.py::test_delegatecall_to_precompile_from_contract_initialization[fork_Amsterdam] +stStaticFlagEnabled/test_delegatecall_to_precompile_from_transaction.py::test_delegatecall_to_precompile_from_transaction[fork_Amsterdam] + +# stSystemOperationsTest (8) +stSystemOperationsTest/test_ab_acalls0.py::test_ab_acalls0[fork_Amsterdam] +stSystemOperationsTest/test_ab_acalls3.py::test_ab_acalls3[fork_Amsterdam] +stSystemOperationsTest/test_ab_acalls_suicide0.py::test_ab_acalls_suicide0[fork_Amsterdam] +stSystemOperationsTest/test_call10.py::test_call10[fork_Amsterdam] +stSystemOperationsTest/test_call_recursive_bomb3.py::test_call_recursive_bomb3[fork_Amsterdam] +stSystemOperationsTest/test_call_to_name_registrator_address_too_big_right.py::test_call_to_name_registrator_address_too_big_right[fork_Amsterdam] +stSystemOperationsTest/test_double_selfdestruct_touch_paris.py::test_double_selfdestruct_touch_paris[fork_Amsterdam--v1] +stSystemOperationsTest/test_double_selfdestruct_touch_paris.py::test_double_selfdestruct_touch_paris[fork_Amsterdam--v2] + +# stTransactionTest (21) +stTransactionTest/test_no_src_account_create.py::test_no_src_account_create[fork_Amsterdam-d0-g1-v0] +stTransactionTest/test_no_src_account_create.py::test_no_src_account_create[fork_Amsterdam-d0-g1-v1] +stTransactionTest/test_no_src_account_create.py::test_no_src_account_create[fork_Amsterdam-d1-g1-v0] +stTransactionTest/test_no_src_account_create.py::test_no_src_account_create[fork_Amsterdam-d1-g1-v1] +stTransactionTest/test_no_src_account_create.py::test_no_src_account_create[fork_Amsterdam-d2-g1-v0] +stTransactionTest/test_no_src_account_create.py::test_no_src_account_create[fork_Amsterdam-d2-g1-v1] +stTransactionTest/test_no_src_account_create.py::test_no_src_account_create[fork_Amsterdam-d3-g1-v0] +stTransactionTest/test_no_src_account_create.py::test_no_src_account_create[fork_Amsterdam-d3-g1-v1] +stTransactionTest/test_no_src_account_create.py::test_no_src_account_create[fork_Amsterdam-d4-g1-v0] +stTransactionTest/test_no_src_account_create.py::test_no_src_account_create[fork_Amsterdam-d4-g1-v1] +stTransactionTest/test_no_src_account_create1559.py::test_no_src_account_create1559[fork_Amsterdam-d0-g1-v0] +stTransactionTest/test_no_src_account_create1559.py::test_no_src_account_create1559[fork_Amsterdam-d0-g1-v1] +stTransactionTest/test_no_src_account_create1559.py::test_no_src_account_create1559[fork_Amsterdam-d1-g1-v0] +stTransactionTest/test_no_src_account_create1559.py::test_no_src_account_create1559[fork_Amsterdam-d1-g1-v1] +stTransactionTest/test_no_src_account_create1559.py::test_no_src_account_create1559[fork_Amsterdam-d2-g1-v0] +stTransactionTest/test_no_src_account_create1559.py::test_no_src_account_create1559[fork_Amsterdam-d2-g1-v1] +stTransactionTest/test_opcodes_transaction_init.py::test_opcodes_transaction_init[fork_Amsterdam-d120] +stTransactionTest/test_opcodes_transaction_init.py::test_opcodes_transaction_init[fork_Amsterdam-side_effects] +stTransactionTest/test_store_gas_on_create.py::test_store_gas_on_create[fork_Amsterdam] +stTransactionTest/test_suicides_and_internal_call_suicides_success.py::test_suicides_and_internal_call_suicides_success[fork_Amsterdam-d1] +vmArithmeticTest/test_exp_power256_of256.py::test_exp_power256_of256[fork_Amsterdam] + +# stWalletTest (2) +stWalletTest/test_day_limit_construction_partial.py::test_day_limit_construction_partial[fork_Amsterdam] +stWalletTest/test_wallet_construction_partial.py::test_wallet_construction_partial[fork_Amsterdam] + +# stZeroKnowledge (20) +stZeroKnowledge/test_point_mul_add.py::test_point_mul_add[fork_Amsterdam-d2-g3] +stZeroKnowledge/test_point_mul_add.py::test_point_mul_add[fork_Amsterdam-d7-g3] +stZeroKnowledge/test_point_mul_add.py::test_point_mul_add[fork_Amsterdam-d8-g3] +stZeroKnowledge/test_point_mul_add2.py::test_point_mul_add2[fork_Amsterdam-d0-g3] +stZeroKnowledge/test_point_mul_add2.py::test_point_mul_add2[fork_Amsterdam-d1-g3] +stZeroKnowledge/test_point_mul_add2.py::test_point_mul_add2[fork_Amsterdam-d12-g3] +stZeroKnowledge/test_point_mul_add2.py::test_point_mul_add2[fork_Amsterdam-d17-g3] +stZeroKnowledge/test_point_mul_add2.py::test_point_mul_add2[fork_Amsterdam-d2-g3] +stZeroKnowledge/test_point_mul_add2.py::test_point_mul_add2[fork_Amsterdam-d21-g3] +stZeroKnowledge/test_point_mul_add2.py::test_point_mul_add2[fork_Amsterdam-d26-g3] +stZeroKnowledge/test_point_mul_add2.py::test_point_mul_add2[fork_Amsterdam-d3-g3] +stZeroKnowledge/test_point_mul_add2.py::test_point_mul_add2[fork_Amsterdam-d30-g3] +stZeroKnowledge/test_point_mul_add2.py::test_point_mul_add2[fork_Amsterdam-d34-g3] +stZeroKnowledge/test_point_mul_add2.py::test_point_mul_add2[fork_Amsterdam-d4-g3] +stZeroKnowledge/test_point_mul_add2.py::test_point_mul_add2[fork_Amsterdam-d5-g3] +stZeroKnowledge/test_point_mul_add2.py::test_point_mul_add2[fork_Amsterdam-d6-g3] +stZeroKnowledge/test_point_mul_add2.py::test_point_mul_add2[fork_Amsterdam-d7-g3] +stZeroKnowledge/test_point_mul_add2.py::test_point_mul_add2[fork_Amsterdam-d8-g3] +stZeroKnowledge/test_point_mul_add2.py::test_point_mul_add2[fork_Amsterdam-d9-g3] +vmArithmeticTest/test_two_ops.py::test_two_ops[fork_Amsterdam] diff --git a/tests/ported_static/conftest.py b/tests/ported_static/conftest.py new file mode 100644 index 00000000000..b26f10ade4a --- /dev/null +++ b/tests/ported_static/conftest.py @@ -0,0 +1,58 @@ +""" +Conftest for ported static tests. + +Temporarily skip ported static tests that fail for Amsterdam due to EIP-8037's +two-dimensional gas model. The gas limits in these ported static test cases +have not yet been updated to account for state gas. + +TODO: Update gas limits in the 3452 failing ported static test cases and +remove this skip list. +""" + +from pathlib import Path + +import pytest + +_SKIP_LIST_PATH = Path(__file__).parent / "amsterdam_skip_list.txt" +_AMSTERDAM_SKIP_CASES: frozenset[str] = frozenset( + line.strip() + for line in _SKIP_LIST_PATH.read_text().splitlines() + if line.strip() and not line.lstrip().startswith("#") +) + +# Fixture format suffixes pytest appends inside the parametrize id. These +# must be stripped from the nodeid before substring-matching against the +# skip list, because the skip list predates these suffixes. +_FIXTURE_FORMAT_TOKENS: tuple[str, ...] = ( + "-blockchain_test_engine_from_state_test", + "-blockchain_test_from_state_test", + "-blockchain_test_engine", + "-blockchain_test", + "-state_test", +) + + +def _normalize_nodeid(nodeid: str) -> str: + """Strip pytest fixture-format suffixes to match the skip list format.""" + for token in _FIXTURE_FORMAT_TOKENS: + nodeid = nodeid.replace(token, "") + return nodeid + + +def pytest_collection_modifyitems( + config: pytest.Config, items: list[pytest.Item] +) -> None: + """Skip ported static test cases listed in amsterdam_skip_list.txt.""" + skip_marker = pytest.mark.skip( + reason="Ported static test gas limits not yet updated for EIP-8037" + ) + for item in items: + if "ported_static" not in item.nodeid: + continue + if "fork_Amsterdam" not in item.nodeid: + continue + normalized = _normalize_nodeid(item.nodeid) + for skip_case in _AMSTERDAM_SKIP_CASES: + if skip_case in normalized: + item.add_marker(skip_marker) + break diff --git a/tests/ported_static/stAttackTest/test_contract_creation_spam.py b/tests/ported_static/stAttackTest/test_contract_creation_spam.py index 1805f8cc254..49ffb2b4b42 100644 --- a/tests/ported_static/stAttackTest/test_contract_creation_spam.py +++ b/tests/ported_static/stAttackTest/test_contract_creation_spam.py @@ -14,8 +14,10 @@ Bytes, Environment, StateTestFiller, + Storage, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,6 +33,7 @@ def test_contract_creation_spam( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_contract_creation_spam.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) @@ -628,15 +631,22 @@ def test_contract_creation_spam( address=Address(0x6A0A0FC761C612C340A0E98D33B37A75E5268472), # noqa: E501 ) + gas_limit = 10000000 + if fork.is_eip_enabled(8037): + gas_limit += 100 * fork.gas_costs().NEW_ACCOUNT tx = Transaction( sender=sender, to=contract_0, data=Bytes(""), - gas_limit=10000000, + gas_limit=gas_limit, ) + contract_0_storage = Storage.model_validate({0: 0x10C20}) + if fork.is_eip_enabled(8037): + contract_0_storage = Storage.model_validate({}) + contract_0_storage.set_expect_any(0) post = { - contract_0: Account(storage={0: 0x10C20}, nonce=1), + contract_0: Account(storage=contract_0_storage, nonce=1), sender: Account(storage={}, nonce=1), Address( 0x0000000000000000000000000000000000000001 diff --git a/tests/ported_static/stCallCodes/test_callcall_00.py b/tests/ported_static/stCallCodes/test_callcall_00.py index b3738552685..668dd94809b 100644 --- a/tests/ported_static/stCallCodes/test_callcall_00.py +++ b/tests/ported_static/stCallCodes/test_callcall_00.py @@ -3,6 +3,16 @@ Ported from: state_tests/stCallCodes/callcall_00Filler.json + + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values from the original filler (250k / 300k / 350k) were tuned to +the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the +innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei +of regular gas), and the inner CALL OoGs before the test's SSTORE +markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL +chain has headroom on Amsterdam; older forks are unaffected because +only the requested gas changes, the actual consumption is identical. """ import pytest @@ -15,6 +25,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +40,18 @@ def test_callcall_00( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Call -> call -> code, params check.""" + # EIP-8037 inner-CALL gas bumps (original gas values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state-gas + # spill into regular gas on Amsterdam). + inner_call_gas = 0x3D090 + outer_call_gas = 0x55730 + if fork.is_eip_enabled(8037): + inner_call_gas = 0xF4240 + outer_call_gas = 0x155CC0 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -63,7 +84,7 @@ def test_callcall_00( code=Op.SSTORE( key=0x1, value=Op.CALL( - gas=0x3D090, + gas=inner_call_gas, address=addr_2, value=0x2, args_offset=0x0, @@ -82,7 +103,7 @@ def test_callcall_00( code=Op.SSTORE( key=0x0, value=Op.CALL( - gas=0x55730, + gas=outer_call_gas, address=addr, value=0x1, args_offset=0x0, diff --git a/tests/ported_static/stCallCodes/test_callcall_00_suicide_end.py b/tests/ported_static/stCallCodes/test_callcall_00_suicide_end.py index 86d19960801..ba8ab1441e5 100644 --- a/tests/ported_static/stCallCodes/test_callcall_00_suicide_end.py +++ b/tests/ported_static/stCallCodes/test_callcall_00_suicide_end.py @@ -3,6 +3,10 @@ Ported from: state_tests/stCallCodes/callcall_00_SuicideEndFiller.json + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. + """ import pytest @@ -15,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +34,18 @@ def test_callcall_00_suicide_end( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Call -> (call -> code) suicide .""" + # EIP-8037 inner-CALL gas bumps: original values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state- + # gas spill into regular gas on Amsterdam. + outer_call_gas = 150000 + inner_call_gas = 50000 + if fork.is_eip_enabled(8037): + outer_call_gas = 1000000 + inner_call_gas = 100000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -57,7 +72,7 @@ def test_callcall_00_suicide_end( code=Op.SSTORE( key=0x0, value=Op.CALL( - gas=0x249F0, + gas=outer_call_gas, address=0xF741CFEE7B7FB1025DCCEF3DB5A3CBC8FFB776F8, value=0x0, args_offset=0x0, @@ -77,7 +92,7 @@ def test_callcall_00_suicide_end( code=Op.SSTORE( key=0x1, value=Op.CALL( - gas=0xC350, + gas=inner_call_gas, address=0x703B936FD4D674F0FF5D6957F61097152F8781B8, value=0x0, args_offset=0x0, diff --git a/tests/ported_static/stCallCodes/test_callcallcall_000.py b/tests/ported_static/stCallCodes/test_callcallcall_000.py index e4289de543f..1026e276d3d 100644 --- a/tests/ported_static/stCallCodes/test_callcallcall_000.py +++ b/tests/ported_static/stCallCodes/test_callcallcall_000.py @@ -3,6 +3,16 @@ Ported from: state_tests/stCallCodes/callcallcall_000Filler.json + + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values from the original filler (250k / 300k / 350k) were tuned to +the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the +innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei +of regular gas), and the inner CALL OoGs before the test's SSTORE +markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL +chain has headroom on Amsterdam; older forks are unaffected because +only the requested gas changes, the actual consumption is identical. """ import pytest @@ -15,6 +25,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +40,20 @@ def test_callcallcall_000( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Call -> call -> call -> code, params check.""" + # EIP-8037 inner-CALL gas bumps (original gas values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state-gas + # spill into regular gas on Amsterdam). + inner_call_gas = 0x3D090 + middle_call_gas = 0x493E0 + outer_call_gas = 0x55730 + if fork.is_eip_enabled(8037): + inner_call_gas = 0xF4240 + middle_call_gas = 0x124F80 + outer_call_gas = 0x155CC0 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -63,7 +86,7 @@ def test_callcallcall_000( code=Op.SSTORE( key=0x2, value=Op.CALL( - gas=0x3D090, + gas=inner_call_gas, address=addr_3, value=0x3, args_offset=0x0, @@ -82,7 +105,7 @@ def test_callcallcall_000( code=Op.SSTORE( key=0x1, value=Op.CALL( - gas=0x493E0, + gas=middle_call_gas, address=addr_2, value=0x2, args_offset=0x0, @@ -101,7 +124,7 @@ def test_callcallcall_000( code=Op.SSTORE( key=0x0, value=Op.CALL( - gas=0x55730, + gas=outer_call_gas, address=addr, value=0x1, args_offset=0x0, diff --git a/tests/ported_static/stCallCodes/test_callcallcall_000_suicide_end.py b/tests/ported_static/stCallCodes/test_callcallcall_000_suicide_end.py index 1ddf3839b34..d33e9e42b6a 100644 --- a/tests/ported_static/stCallCodes/test_callcallcall_000_suicide_end.py +++ b/tests/ported_static/stCallCodes/test_callcallcall_000_suicide_end.py @@ -3,6 +3,10 @@ Ported from: state_tests/stCallCodes/callcallcall_000_SuicideEndFiller.json + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. + """ import pytest @@ -15,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +34,20 @@ def test_callcallcall_000_suicide_end( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Call -> call -> (call -> code) suicide.""" + # EIP-8037 inner-CALL gas bumps: original values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state- + # gas spill into regular gas on Amsterdam. + outer_call_gas = 150000 + middle_call_gas = 100000 + inner_call_gas = 50000 + if fork.is_eip_enabled(8037): + outer_call_gas = 1000000 + middle_call_gas = 800000 + inner_call_gas = 100000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -57,7 +74,7 @@ def test_callcallcall_000_suicide_end( code=Op.SSTORE( key=0x0, value=Op.CALL( - gas=0x249F0, + gas=outer_call_gas, address=0x77B749FFFF7EC61D31C79ED104F230A7959B2879, value=0x0, args_offset=0x0, @@ -77,7 +94,7 @@ def test_callcallcall_000_suicide_end( code=Op.SSTORE( key=0x1, value=Op.CALL( - gas=0x186A0, + gas=middle_call_gas, address=0xD957E143AD2C011BC6A2B142795F1A9BA70D0680, value=0x0, args_offset=0x0, @@ -97,7 +114,7 @@ def test_callcallcall_000_suicide_end( code=Op.SSTORE( key=0x2, value=Op.CALL( - gas=0xC350, + gas=inner_call_gas, address=0xCB6497F0337B6CD0F7239A8819295EC7D1DAFD34, value=0x0, args_offset=0x0, diff --git a/tests/ported_static/stCallCodes/test_callcallcall_abcb_recursive.py b/tests/ported_static/stCallCodes/test_callcallcall_abcb_recursive.py index 7a87fcd56c9..2e41a7d12b2 100644 --- a/tests/ported_static/stCallCodes/test_callcallcall_abcb_recursive.py +++ b/tests/ported_static/stCallCodes/test_callcallcall_abcb_recursive.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -28,6 +30,7 @@ @pytest.mark.pre_alloc_mutable def test_callcallcall_abcb_recursive( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Call -> call <-> call.""" @@ -40,7 +43,6 @@ def test_callcallcall_abcb_recursive( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=3000000000, ) # Source: lll @@ -108,7 +110,7 @@ def test_callcallcall_abcb_recursive( sender=sender, to=target, data=Bytes(""), - gas_limit=600000, + gas_limit=2600000 if fork >= Amsterdam else 600000, ) post = { diff --git a/tests/ported_static/stCallCodes/test_callcallcallcode_001.py b/tests/ported_static/stCallCodes/test_callcallcallcode_001.py index ca7c0a3198c..f1a4178583c 100644 --- a/tests/ported_static/stCallCodes/test_callcallcallcode_001.py +++ b/tests/ported_static/stCallCodes/test_callcallcallcode_001.py @@ -3,6 +3,16 @@ Ported from: state_tests/stCallCodes/callcallcallcode_001Filler.json + + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values from the original filler (250k / 300k / 350k) were tuned to +the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the +innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei +of regular gas), and the inner CALL OoGs before the test's SSTORE +markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL +chain has headroom on Amsterdam; older forks are unaffected because +only the requested gas changes, the actual consumption is identical. """ import pytest @@ -15,6 +25,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +40,20 @@ def test_callcallcallcode_001( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Call -> call -> callcode - > code, params check.""" + # EIP-8037 inner-CALL gas bumps (original gas values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state-gas + # spill into regular gas on Amsterdam). + inner_call_gas = 0x3D090 + middle_call_gas = 0x493E0 + outer_call_gas = 0x55730 + if fork.is_eip_enabled(8037): + inner_call_gas = 0xF4240 + middle_call_gas = 0x124F80 + outer_call_gas = 0x155CC0 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -63,7 +86,7 @@ def test_callcallcallcode_001( code=Op.SSTORE( key=0x2, value=Op.CALLCODE( - gas=0x3D090, + gas=inner_call_gas, address=addr_3, value=0x3, args_offset=0x0, @@ -82,7 +105,7 @@ def test_callcallcallcode_001( code=Op.SSTORE( key=0x1, value=Op.CALL( - gas=0x493E0, + gas=middle_call_gas, address=addr_2, value=0x2, args_offset=0x0, @@ -101,7 +124,7 @@ def test_callcallcallcode_001( code=Op.SSTORE( key=0x0, value=Op.CALL( - gas=0x55730, + gas=outer_call_gas, address=addr, value=0x1, args_offset=0x0, diff --git a/tests/ported_static/stCallCodes/test_callcallcallcode_001_suicide_end.py b/tests/ported_static/stCallCodes/test_callcallcallcode_001_suicide_end.py index 10bf588c464..1bc24e83ea6 100644 --- a/tests/ported_static/stCallCodes/test_callcallcallcode_001_suicide_end.py +++ b/tests/ported_static/stCallCodes/test_callcallcallcode_001_suicide_end.py @@ -3,6 +3,10 @@ Ported from: state_tests/stCallCodes/callcallcallcode_001_SuicideEndFiller.json + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. + """ import pytest @@ -15,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +34,20 @@ def test_callcallcallcode_001_suicide_end( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Call -> call -> ( callcode - > code ) suicide.""" + # EIP-8037 inner-CALL gas bumps: original values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state- + # gas spill into regular gas on Amsterdam. + outer_call_gas = 150000 + middle_call_gas = 100000 + inner_call_gas = 50000 + if fork.is_eip_enabled(8037): + outer_call_gas = 1000000 + middle_call_gas = 800000 + inner_call_gas = 100000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -57,7 +74,7 @@ def test_callcallcallcode_001_suicide_end( code=Op.SSTORE( key=0x0, value=Op.CALL( - gas=0x249F0, + gas=outer_call_gas, address=0x77B749FFFF7EC61D31C79ED104F230A7959B2879, value=0x0, args_offset=0x0, @@ -77,7 +94,7 @@ def test_callcallcallcode_001_suicide_end( code=Op.SSTORE( key=0x1, value=Op.CALL( - gas=0x186A0, + gas=middle_call_gas, address=0x94C8F980AEECBB6575B12AE614A249FC3E836F21, value=0x0, args_offset=0x0, @@ -97,7 +114,7 @@ def test_callcallcallcode_001_suicide_end( code=Op.SSTORE( key=0x2, value=Op.CALLCODE( - gas=0xC350, + gas=inner_call_gas, address=0x73B954EBC05BB0FF4A0F6A13A054D50AD1584099, value=0x0, args_offset=0x0, diff --git a/tests/ported_static/stCallCodes/test_callcallcallcode_abcb_recursive.py b/tests/ported_static/stCallCodes/test_callcallcallcode_abcb_recursive.py index 14f3e6c6cad..604e73c38cf 100644 --- a/tests/ported_static/stCallCodes/test_callcallcallcode_abcb_recursive.py +++ b/tests/ported_static/stCallCodes/test_callcallcallcode_abcb_recursive.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -28,6 +30,7 @@ @pytest.mark.pre_alloc_mutable def test_callcallcallcode_abcb_recursive( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Call -> call <-> callcode.""" @@ -40,7 +43,6 @@ def test_callcallcallcode_abcb_recursive( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=3000000000, ) # Source: lll @@ -108,13 +110,18 @@ def test_callcallcallcode_abcb_recursive( sender=sender, to=target, data=Bytes(""), - gas_limit=600000, + gas_limit=2600000 if fork >= Amsterdam else 600000, ) post = { target: Account(storage={0: 1, 1: 0}), addr: Account(storage={1: 1, 2: 0}), - addr_2: Account(storage={1: 0, 2: 0}), + addr_2: Account( + storage={ + 1: 0, + 2: 0, + } + ), } state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stCallCodes/test_callcallcode_01.py b/tests/ported_static/stCallCodes/test_callcallcode_01.py index f57fa53ef1d..08d46046488 100644 --- a/tests/ported_static/stCallCodes/test_callcallcode_01.py +++ b/tests/ported_static/stCallCodes/test_callcallcode_01.py @@ -3,6 +3,16 @@ Ported from: state_tests/stCallCodes/callcallcode_01Filler.json + + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values from the original filler (250k / 300k / 350k) were tuned to +the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the +innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei +of regular gas), and the inner CALL OoGs before the test's SSTORE +markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL +chain has headroom on Amsterdam; older forks are unaffected because +only the requested gas changes, the actual consumption is identical. """ import pytest @@ -15,6 +25,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +40,18 @@ def test_callcallcode_01( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Call -> callcode -> code, params check.""" + # EIP-8037 inner-CALL gas bumps (original gas values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state-gas + # spill into regular gas on Amsterdam). + inner_call_gas = 0x3D090 + outer_call_gas = 0x55730 + if fork.is_eip_enabled(8037): + inner_call_gas = 0xF4240 + outer_call_gas = 0x155CC0 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -63,7 +84,7 @@ def test_callcallcode_01( code=Op.SSTORE( key=0x1, value=Op.CALLCODE( - gas=0x3D090, + gas=inner_call_gas, address=addr_2, value=0x2, args_offset=0x0, @@ -82,7 +103,7 @@ def test_callcallcode_01( code=Op.SSTORE( key=0x0, value=Op.CALL( - gas=0x55730, + gas=outer_call_gas, address=addr, value=0x1, args_offset=0x0, diff --git a/tests/ported_static/stCallCodes/test_callcallcodecall_010.py b/tests/ported_static/stCallCodes/test_callcallcodecall_010.py index ba39016c1c6..51ab4615cc1 100644 --- a/tests/ported_static/stCallCodes/test_callcallcodecall_010.py +++ b/tests/ported_static/stCallCodes/test_callcallcodecall_010.py @@ -3,6 +3,16 @@ Ported from: state_tests/stCallCodes/callcallcodecall_010Filler.json + + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values from the original filler (250k / 300k / 350k) were tuned to +the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the +innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei +of regular gas), and the inner CALL OoGs before the test's SSTORE +markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL +chain has headroom on Amsterdam; older forks are unaffected because +only the requested gas changes, the actual consumption is identical. """ import pytest @@ -15,6 +25,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +40,20 @@ def test_callcallcodecall_010( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Call -> callcode -> call -> code, params check.""" + # EIP-8037 inner-CALL gas bumps (original gas values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state-gas + # spill into regular gas on Amsterdam). + inner_call_gas = 0x3D090 + middle_call_gas = 0x493E0 + outer_call_gas = 0x55730 + if fork.is_eip_enabled(8037): + inner_call_gas = 0xF4240 + middle_call_gas = 0x124F80 + outer_call_gas = 0x155CC0 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -63,7 +86,7 @@ def test_callcallcodecall_010( code=Op.SSTORE( key=0x2, value=Op.CALL( - gas=0x3D090, + gas=inner_call_gas, address=addr_3, value=0x3, args_offset=0x0, @@ -82,7 +105,7 @@ def test_callcallcodecall_010( code=Op.SSTORE( key=0x1, value=Op.CALLCODE( - gas=0x493E0, + gas=middle_call_gas, address=addr_2, value=0x2, args_offset=0x0, @@ -101,7 +124,7 @@ def test_callcallcodecall_010( code=Op.SSTORE( key=0x0, value=Op.CALL( - gas=0x55730, + gas=outer_call_gas, address=addr, value=0x1, args_offset=0x0, diff --git a/tests/ported_static/stCallCodes/test_callcallcodecall_010_suicide_end.py b/tests/ported_static/stCallCodes/test_callcallcodecall_010_suicide_end.py index f58f5b44ccc..f23b0ff9251 100644 --- a/tests/ported_static/stCallCodes/test_callcallcodecall_010_suicide_end.py +++ b/tests/ported_static/stCallCodes/test_callcallcodecall_010_suicide_end.py @@ -3,6 +3,10 @@ Ported from: state_tests/stCallCodes/callcallcodecall_010_SuicideEndFiller.json + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. + """ import pytest @@ -15,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +34,20 @@ def test_callcallcodecall_010_suicide_end( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Call -> callcode -> (call -> code) (suicide).""" + # EIP-8037 inner-CALL gas bumps: original values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state- + # gas spill into regular gas on Amsterdam. + outer_call_gas = 150000 + middle_call_gas = 100000 + inner_call_gas = 50000 + if fork.is_eip_enabled(8037): + outer_call_gas = 1000000 + middle_call_gas = 800000 + inner_call_gas = 100000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -57,7 +74,7 @@ def test_callcallcodecall_010_suicide_end( code=Op.SSTORE( key=0x0, value=Op.CALL( - gas=0x249F0, + gas=outer_call_gas, address=0xEAF8C2AE0D01A880CEA4E1AA88DEF5EDD153D57B, value=0x0, args_offset=0x0, @@ -77,7 +94,7 @@ def test_callcallcodecall_010_suicide_end( code=Op.SSTORE( key=0x1, value=Op.CALLCODE( - gas=0x186A0, + gas=middle_call_gas, address=0xD957E143AD2C011BC6A2B142795F1A9BA70D0680, value=0x0, args_offset=0x0, @@ -97,7 +114,7 @@ def test_callcallcodecall_010_suicide_end( code=Op.SSTORE( key=0x2, value=Op.CALL( - gas=0xC350, + gas=inner_call_gas, address=0x73B954EBC05BB0FF4A0F6A13A054D50AD1584099, value=0x0, args_offset=0x0, diff --git a/tests/ported_static/stCallCodes/test_callcallcodecall_abcb_recursive.py b/tests/ported_static/stCallCodes/test_callcallcodecall_abcb_recursive.py index 7df4c42b423..26b8a6e72da 100644 --- a/tests/ported_static/stCallCodes/test_callcallcodecall_abcb_recursive.py +++ b/tests/ported_static/stCallCodes/test_callcallcodecall_abcb_recursive.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -28,6 +30,7 @@ @pytest.mark.pre_alloc_mutable def test_callcallcodecall_abcb_recursive( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Call -> callcode <-> call.""" @@ -40,7 +43,6 @@ def test_callcallcodecall_abcb_recursive( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=3000000000, ) # Source: lll @@ -108,7 +110,7 @@ def test_callcallcodecall_abcb_recursive( sender=sender, to=target, data=Bytes(""), - gas_limit=600000, + gas_limit=2600000 if fork >= Amsterdam else 600000, ) post = { diff --git a/tests/ported_static/stCallCodes/test_callcallcodecallcode_011.py b/tests/ported_static/stCallCodes/test_callcallcodecallcode_011.py index f9b66fda98b..dd5241029ac 100644 --- a/tests/ported_static/stCallCodes/test_callcallcodecallcode_011.py +++ b/tests/ported_static/stCallCodes/test_callcallcodecallcode_011.py @@ -3,6 +3,16 @@ Ported from: state_tests/stCallCodes/callcallcodecallcode_011Filler.json + + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values from the original filler (250k / 300k / 350k) were tuned to +the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the +innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei +of regular gas), and the inner CALL OoGs before the test's SSTORE +markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL +chain has headroom on Amsterdam; older forks are unaffected because +only the requested gas changes, the actual consumption is identical. """ import pytest @@ -15,6 +25,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +40,20 @@ def test_callcallcodecallcode_011( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Call -> callcode -> callcode -> code, check params.""" + # EIP-8037 inner-CALL gas bumps (original gas values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state-gas + # spill into regular gas on Amsterdam). + inner_call_gas = 0x3D090 + middle_call_gas = 0x493E0 + outer_call_gas = 0x55730 + if fork.is_eip_enabled(8037): + inner_call_gas = 0xF4240 + middle_call_gas = 0x124F80 + outer_call_gas = 0x155CC0 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -63,7 +86,7 @@ def test_callcallcodecallcode_011( code=Op.SSTORE( key=0x2, value=Op.CALLCODE( - gas=0x3D090, + gas=inner_call_gas, address=addr_3, value=0x3, args_offset=0x0, @@ -82,7 +105,7 @@ def test_callcallcodecallcode_011( code=Op.SSTORE( key=0x1, value=Op.CALLCODE( - gas=0x493E0, + gas=middle_call_gas, address=addr_2, value=0x2, args_offset=0x0, @@ -101,7 +124,7 @@ def test_callcallcodecallcode_011( code=Op.SSTORE( key=0x0, value=Op.CALL( - gas=0x55730, + gas=outer_call_gas, address=addr, value=0x1, args_offset=0x0, diff --git a/tests/ported_static/stCallCodes/test_callcallcodecallcode_abcb_recursive.py b/tests/ported_static/stCallCodes/test_callcallcodecallcode_abcb_recursive.py index 8cbf5fa938c..f1c4f00d38f 100644 --- a/tests/ported_static/stCallCodes/test_callcallcodecallcode_abcb_recursive.py +++ b/tests/ported_static/stCallCodes/test_callcallcodecallcode_abcb_recursive.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -28,6 +30,7 @@ @pytest.mark.pre_alloc_mutable def test_callcallcodecallcode_abcb_recursive( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Call -> callcode <-> callcode.""" @@ -40,7 +43,6 @@ def test_callcallcodecallcode_abcb_recursive( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=3000000000, ) # Source: lll @@ -108,7 +110,7 @@ def test_callcallcodecallcode_abcb_recursive( sender=sender, to=target, data=Bytes(""), - gas_limit=600000, + gas_limit=2600000 if fork >= Amsterdam else 600000, ) post = { diff --git a/tests/ported_static/stCallCodes/test_callcode_check_pc.py b/tests/ported_static/stCallCodes/test_callcode_check_pc.py index 8f16fd6661e..acf70b1e3c0 100644 --- a/tests/ported_static/stCallCodes/test_callcode_check_pc.py +++ b/tests/ported_static/stCallCodes/test_callcode_check_pc.py @@ -40,7 +40,6 @@ def test_callcode_check_pc( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=3000000000, ) # Source: lll diff --git a/tests/ported_static/stCallCodes/test_callcode_dynamic_code.py b/tests/ported_static/stCallCodes/test_callcode_dynamic_code.py index b9d711740ab..516596f0896 100644 --- a/tests/ported_static/stCallCodes/test_callcode_dynamic_code.py +++ b/tests/ported_static/stCallCodes/test_callcode_dynamic_code.py @@ -3,6 +3,16 @@ Ported from: state_tests/stCallCodes/callcodeDynamicCodeFiller.json + + +@manually-enhanced: Do not overwrite. Hardcoded inner-CALL gas values +from the original filler (100k / 800k / 150k / 50k) were tuned to the +pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the inner +callee adds the EIP-8037 per-storage state-gas (37 568 wei of +regular gas), and the inner CALL OoGs before the test's SSTORE +markers fire. Bumped uniformly with extra headroom; older forks are +unaffected because only the requested gas changes, the actual +consumption is identical. """ import pytest @@ -70,6 +80,15 @@ def test_callcode_dynamic_code( v: int, ) -> None: """Callcode to a contract that is being created in the same transaction.""" + # EIP-8037 inner-CALL gas bumps (original gas values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state-gas + # spill into regular gas on Amsterdam). + inner_call_gas = 0x186A0 + outer_call_gas = 0xC3500 + if fork.is_eip_enabled(8037): + inner_call_gas = 0x2DC6C0 + outer_call_gas = 0x4C4B40 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) contract_0 = Address(0x1100000000000000000000000000000000000000) contract_1 = Address(0x1000000000000000000000000000000000000000) @@ -86,7 +105,7 @@ def test_callcode_dynamic_code( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=1000000, + gas_limit=10000000, ) pre[sender] = Account(balance=0x2386F26FC10000) @@ -94,7 +113,7 @@ def test_callcode_dynamic_code( # { (CALL 800000 (CALLDATALOAD 0) 0 0 0 0 0) } contract_0 = pre.deploy_contract( # noqa: F841 code=Op.CALL( - gas=0xC3500, + gas=outer_call_gas, address=Op.CALLDATALOAD(offset=0x0), value=0x0, args_offset=0x0, @@ -116,7 +135,7 @@ def test_callcode_dynamic_code( + Op.SSTORE( key=0xB, value=Op.CALLCODE( - gas=0x186A0, + gas=inner_call_gas, address=Op.SLOAD(key=0xA), value=0x0, args_offset=0x0, @@ -153,7 +172,7 @@ def test_callcode_dynamic_code( + Op.SSTORE( key=0xB, value=Op.CALLCODE( - gas=0x186A0, + gas=inner_call_gas, address=Op.SLOAD(key=0xA), value=0x0, args_offset=0x0, @@ -195,7 +214,7 @@ def test_callcode_dynamic_code( + Op.SSTORE( key=0xB, value=Op.CALLCODE( - gas=0x186A0, + gas=inner_call_gas, address=Op.SLOAD(key=0xA), value=0x0, args_offset=0x0, @@ -238,7 +257,7 @@ def test_callcode_dynamic_code( + Op.SSTORE( key=0xB, value=Op.CALLCODE( - gas=0x186A0, + gas=inner_call_gas, address=Op.SLOAD(key=0xA), value=0x0, args_offset=0x0, @@ -356,7 +375,13 @@ def test_callcode_dynamic_code( Hash(contract_3, left_padding=True), Hash(contract_4, left_padding=True), ] - tx_gas = [1000000] + # d2/d3 parametrizations do double-nested CREATE chains; EIP-8037 + # NEW_ACCOUNT state-gas spill on Amsterdam exceeds the original + # 1 000 000 budget. + outer_tx_gas = 1_000_000 + if fork.is_eip_enabled(8037): + outer_tx_gas = 6_000_000 + tx_gas = [outer_tx_gas] tx = Transaction( sender=sender, diff --git a/tests/ported_static/stCallCodes/test_callcode_dynamic_code2_self_call.py b/tests/ported_static/stCallCodes/test_callcode_dynamic_code2_self_call.py index 258217d6fae..0f5363e7c6f 100644 --- a/tests/ported_static/stCallCodes/test_callcode_dynamic_code2_self_call.py +++ b/tests/ported_static/stCallCodes/test_callcode_dynamic_code2_self_call.py @@ -3,6 +3,16 @@ Ported from: state_tests/stCallCodes/callcodeDynamicCode2SelfCallFiller.json + + +@manually-enhanced: Do not overwrite. Hardcoded inner-CALL gas values +from the original filler (100k / 800k / 150k / 50k) were tuned to the +pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the inner +callee adds the EIP-8037 per-storage state-gas (37 568 wei of +regular gas), and the inner CALL OoGs before the test's SSTORE +markers fire. Bumped uniformly with extra headroom; older forks are +unaffected because only the requested gas changes, the actual +consumption is identical. """ import pytest @@ -58,6 +68,15 @@ def test_callcode_dynamic_code2_self_call( v: int, ) -> None: """Callcode happen to a contract that is dynamically created from...""" + # EIP-8037 inner-CALL gas bumps (original gas values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state-gas + # spill into regular gas on Amsterdam). + inner_call_gas = 0x186A0 + outer_call_gas = 0xC3500 + if fork.is_eip_enabled(8037): + inner_call_gas = 0xF4240 + outer_call_gas = 0x1E8480 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) contract_0 = Address(0x1100000000000000000000000000000000000000) contract_1 = Address(0xA000000000000000000000000000000000000000) @@ -80,7 +99,7 @@ def test_callcode_dynamic_code2_self_call( # { (CALL 800000 (CALLDATALOAD 0) 0 0 0 0 0) } contract_0 = pre.deploy_contract( # noqa: F841 code=Op.CALL( - gas=0xC3500, + gas=outer_call_gas, address=Op.CALLDATALOAD(offset=0x0), value=0x0, args_offset=0x0, @@ -119,7 +138,7 @@ def test_callcode_dynamic_code2_self_call( + Op.SSTORE( key=0xB, value=Op.CALLCODE( - gas=0x186A0, + gas=inner_call_gas, address=Op.SLOAD(key=0xA), value=0x0, args_offset=0x0, @@ -133,7 +152,7 @@ def test_callcode_dynamic_code2_self_call( + Op.SSTORE( key=0x7A, value=Op.CALLCODE( - gas=0x186A0, + gas=inner_call_gas, address=0x13136008B64FF592819B2FA6D43F2835C452020E, value=0x0, args_offset=0x0, diff --git a/tests/ported_static/stCallCodes/test_callcode_in_initcode_to_empty_contract.py b/tests/ported_static/stCallCodes/test_callcode_in_initcode_to_empty_contract.py index edd2ad15114..045972d37f5 100644 --- a/tests/ported_static/stCallCodes/test_callcode_in_initcode_to_empty_contract.py +++ b/tests/ported_static/stCallCodes/test_callcode_in_initcode_to_empty_contract.py @@ -3,6 +3,10 @@ Ported from: state_tests/stCallCodes/callcodeInInitcodeToEmptyContractFiller.json +@manually-enhanced: Do not overwrite. Gas bumped fork-conditionally +to cover EIP-8037 state-gas spill into regular gas; pre-EIP-8037 +behavior unchanged. + """ import pytest @@ -58,6 +62,13 @@ def test_callcode_in_initcode_to_empty_contract( v: int, ) -> None: """Callcode inside create contract init to non-existent contract.""" + # EIP-8037 gas bumps: original values for pre-EIP-8037 forks. + outer_tx_gas = 1453081 + inner_call_gas = 300000 + if fork.is_eip_enabled(8037): + outer_tx_gas = 7265405 + inner_call_gas = 1500000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) contract_0 = Address(0x1100000000000000000000000000000000000000) contract_1 = Address(0x1000000000000000000000000000000000000000) @@ -80,7 +91,7 @@ def test_callcode_in_initcode_to_empty_contract( # { (CALL 300000 (CALLDATALOAD 0) 0 0 0 0 0) } contract_0 = pre.deploy_contract( # noqa: F841 code=Op.CALL( - gas=0x493E0, + gas=inner_call_gas, address=Op.CALLDATALOAD(offset=0x0), value=0x0, args_offset=0x0, @@ -175,7 +186,7 @@ def test_callcode_in_initcode_to_empty_contract( Hash(contract_1, left_padding=True), Hash(contract_2, left_padding=True), ] - tx_gas = [1453081] + tx_gas = [outer_tx_gas] tx = Transaction( sender=sender, diff --git a/tests/ported_static/stCallCodes/test_callcodecall_10.py b/tests/ported_static/stCallCodes/test_callcodecall_10.py index d35ec4f4c3d..2e2a3bc2ae9 100644 --- a/tests/ported_static/stCallCodes/test_callcodecall_10.py +++ b/tests/ported_static/stCallCodes/test_callcodecall_10.py @@ -3,6 +3,16 @@ Ported from: state_tests/stCallCodes/callcodecall_10Filler.json + + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values from the original filler (250k / 300k / 350k) were tuned to +the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the +innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei +of regular gas), and the inner CALL OoGs before the test's SSTORE +markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL +chain has headroom on Amsterdam; older forks are unaffected because +only the requested gas changes, the actual consumption is identical. """ import pytest @@ -15,6 +25,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +40,18 @@ def test_callcodecall_10( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Callcode -> call -> code, params check .""" + # EIP-8037 inner-CALL gas bumps (original gas values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state-gas + # spill into regular gas on Amsterdam). + inner_call_gas = 0x3D090 + outer_call_gas = 0x55730 + if fork.is_eip_enabled(8037): + inner_call_gas = 0xF4240 + outer_call_gas = 0x155CC0 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -63,7 +84,7 @@ def test_callcodecall_10( code=Op.SSTORE( key=0x1, value=Op.CALL( - gas=0x3D090, + gas=inner_call_gas, address=addr_2, value=0x2, args_offset=0x0, @@ -82,7 +103,7 @@ def test_callcodecall_10( code=Op.SSTORE( key=0x0, value=Op.CALLCODE( - gas=0x55730, + gas=outer_call_gas, address=addr, value=0x1, args_offset=0x0, diff --git a/tests/ported_static/stCallCodes/test_callcodecall_10_suicide_end.py b/tests/ported_static/stCallCodes/test_callcodecall_10_suicide_end.py index 9c9e867ae0b..d62da3d1016 100644 --- a/tests/ported_static/stCallCodes/test_callcodecall_10_suicide_end.py +++ b/tests/ported_static/stCallCodes/test_callcodecall_10_suicide_end.py @@ -3,6 +3,10 @@ Ported from: state_tests/stCallCodes/callcodecall_10_SuicideEndFiller.json + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. + """ import pytest @@ -15,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +34,18 @@ def test_callcodecall_10_suicide_end( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """CALLCODE -> (CALL -> code) (suicide).""" + # EIP-8037 inner-CALL gas bumps: original values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state- + # gas spill into regular gas on Amsterdam. + outer_call_gas = 150000 + inner_call_gas = 50000 + if fork.is_eip_enabled(8037): + outer_call_gas = 1000000 + inner_call_gas = 100000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -57,7 +72,7 @@ def test_callcodecall_10_suicide_end( code=Op.SSTORE( key=0x0, value=Op.CALLCODE( - gas=0x249F0, + gas=outer_call_gas, address=0xF741CFEE7B7FB1025DCCEF3DB5A3CBC8FFB776F8, value=0x0, args_offset=0x0, @@ -77,7 +92,7 @@ def test_callcodecall_10_suicide_end( code=Op.SSTORE( key=0x1, value=Op.CALL( - gas=0xC350, + gas=inner_call_gas, address=0x703B936FD4D674F0FF5D6957F61097152F8781B8, value=0x0, args_offset=0x0, diff --git a/tests/ported_static/stCallCodes/test_callcodecallcall_100.py b/tests/ported_static/stCallCodes/test_callcodecallcall_100.py index ae6e5485b21..7d15c2b1f8d 100644 --- a/tests/ported_static/stCallCodes/test_callcodecallcall_100.py +++ b/tests/ported_static/stCallCodes/test_callcodecallcall_100.py @@ -3,6 +3,16 @@ Ported from: state_tests/stCallCodes/callcodecallcall_100Filler.json + + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values from the original filler (250k / 300k / 350k) were tuned to +the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the +innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei +of regular gas), and the inner CALL OoGs before the test's SSTORE +markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL +chain has headroom on Amsterdam; older forks are unaffected because +only the requested gas changes, the actual consumption is identical. """ import pytest @@ -15,6 +25,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +40,20 @@ def test_callcodecallcall_100( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """CALLCODE -> CALL -> CALL-> code, params check.""" + # EIP-8037 inner-CALL gas bumps (original gas values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state-gas + # spill into regular gas on Amsterdam). + inner_call_gas = 0x3D090 + middle_call_gas = 0x493E0 + outer_call_gas = 0x55730 + if fork.is_eip_enabled(8037): + inner_call_gas = 0xF4240 + middle_call_gas = 0x124F80 + outer_call_gas = 0x155CC0 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -63,7 +86,7 @@ def test_callcodecallcall_100( code=Op.SSTORE( key=0x2, value=Op.CALL( - gas=0x3D090, + gas=inner_call_gas, address=addr_3, value=0x3, args_offset=0x0, @@ -82,7 +105,7 @@ def test_callcodecallcall_100( code=Op.SSTORE( key=0x1, value=Op.CALL( - gas=0x493E0, + gas=middle_call_gas, address=addr_2, value=0x2, args_offset=0x0, @@ -101,7 +124,7 @@ def test_callcodecallcall_100( code=Op.SSTORE( key=0x0, value=Op.CALLCODE( - gas=0x55730, + gas=outer_call_gas, address=addr, value=0x1, args_offset=0x0, diff --git a/tests/ported_static/stCallCodes/test_callcodecallcall_100_suicide_end.py b/tests/ported_static/stCallCodes/test_callcodecallcall_100_suicide_end.py index a3d8aa6d8fc..4a8b875f9c0 100644 --- a/tests/ported_static/stCallCodes/test_callcodecallcall_100_suicide_end.py +++ b/tests/ported_static/stCallCodes/test_callcodecallcall_100_suicide_end.py @@ -3,6 +3,10 @@ Ported from: state_tests/stCallCodes/callcodecallcall_100_SuicideEndFiller.json + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. + """ import pytest @@ -15,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +34,20 @@ def test_callcodecallcall_100_suicide_end( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """CALLCODE -> CALL -> (CALL-> code) (suicide).""" + # EIP-8037 inner-CALL gas bumps: original values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state- + # gas spill into regular gas on Amsterdam. + outer_call_gas = 150000 + middle_call_gas = 100000 + inner_call_gas = 50000 + if fork.is_eip_enabled(8037): + outer_call_gas = 1000000 + middle_call_gas = 800000 + inner_call_gas = 100000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -57,7 +74,7 @@ def test_callcodecallcall_100_suicide_end( code=Op.SSTORE( key=0x0, value=Op.CALLCODE( - gas=0x249F0, + gas=outer_call_gas, address=0x77B749FFFF7EC61D31C79ED104F230A7959B2879, value=0x0, args_offset=0x0, @@ -77,7 +94,7 @@ def test_callcodecallcall_100_suicide_end( code=Op.SSTORE( key=0x1, value=Op.CALL( - gas=0x186A0, + gas=middle_call_gas, address=0xD957E143AD2C011BC6A2B142795F1A9BA70D0680, value=0x0, args_offset=0x0, @@ -97,7 +114,7 @@ def test_callcodecallcall_100_suicide_end( code=Op.SSTORE( key=0x2, value=Op.CALL( - gas=0xC350, + gas=inner_call_gas, address=0x73B954EBC05BB0FF4A0F6A13A054D50AD1584099, value=0x0, args_offset=0x0, diff --git a/tests/ported_static/stCallCodes/test_callcodecallcall_abcb_recursive.py b/tests/ported_static/stCallCodes/test_callcodecallcall_abcb_recursive.py index 98afc48d9db..0f8f30f4269 100644 --- a/tests/ported_static/stCallCodes/test_callcodecallcall_abcb_recursive.py +++ b/tests/ported_static/stCallCodes/test_callcodecallcall_abcb_recursive.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -28,6 +30,7 @@ @pytest.mark.pre_alloc_mutable def test_callcodecallcall_abcb_recursive( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """CALLCODE -> CALL <-> CALL.""" @@ -40,7 +43,6 @@ def test_callcodecallcall_abcb_recursive( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=3000000000, ) # Source: lll @@ -108,7 +110,7 @@ def test_callcodecallcall_abcb_recursive( sender=sender, to=target, data=Bytes(""), - gas_limit=600000, + gas_limit=2600000 if fork >= Amsterdam else 600000, ) post = { diff --git a/tests/ported_static/stCallCodes/test_callcodecallcallcode_101.py b/tests/ported_static/stCallCodes/test_callcodecallcallcode_101.py index ed620198470..ef876653dac 100644 --- a/tests/ported_static/stCallCodes/test_callcodecallcallcode_101.py +++ b/tests/ported_static/stCallCodes/test_callcodecallcallcode_101.py @@ -3,6 +3,16 @@ Ported from: state_tests/stCallCodes/callcodecallcallcode_101Filler.json + + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values from the original filler (250k / 300k / 350k) were tuned to +the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the +innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei +of regular gas), and the inner CALL OoGs before the test's SSTORE +markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL +chain has headroom on Amsterdam; older forks are unaffected because +only the requested gas changes, the actual consumption is identical. """ import pytest @@ -15,6 +25,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +40,20 @@ def test_callcodecallcallcode_101( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """CALLCODE -> CALL -> CALLCODE -> code parameters check.""" + # EIP-8037 inner-CALL gas bumps (original gas values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state-gas + # spill into regular gas on Amsterdam). + inner_call_gas = 0x3D090 + middle_call_gas = 0x493E0 + outer_call_gas = 0x55730 + if fork.is_eip_enabled(8037): + inner_call_gas = 0xF4240 + middle_call_gas = 0x124F80 + outer_call_gas = 0x155CC0 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -63,7 +86,7 @@ def test_callcodecallcallcode_101( code=Op.SSTORE( key=0x2, value=Op.CALLCODE( - gas=0x3D090, + gas=inner_call_gas, address=addr_3, value=0x3, args_offset=0x0, @@ -82,7 +105,7 @@ def test_callcodecallcallcode_101( code=Op.SSTORE( key=0x1, value=Op.CALL( - gas=0x493E0, + gas=middle_call_gas, address=addr_2, value=0x2, args_offset=0x0, @@ -101,7 +124,7 @@ def test_callcodecallcallcode_101( code=Op.SSTORE( key=0x0, value=Op.CALLCODE( - gas=0x55730, + gas=outer_call_gas, address=addr, value=0x1, args_offset=0x0, diff --git a/tests/ported_static/stCallCodes/test_callcodecallcallcode_101_suicide_end.py b/tests/ported_static/stCallCodes/test_callcodecallcallcode_101_suicide_end.py index 7c20c53bf1a..3de342c0923 100644 --- a/tests/ported_static/stCallCodes/test_callcodecallcallcode_101_suicide_end.py +++ b/tests/ported_static/stCallCodes/test_callcodecallcallcode_101_suicide_end.py @@ -3,6 +3,10 @@ Ported from: state_tests/stCallCodes/callcodecallcallcode_101_SuicideEndFiller.json + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. + """ import pytest @@ -15,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +34,20 @@ def test_callcodecallcallcode_101_suicide_end( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """CALLCODE -> CALL -> (CALLCODE -> code) (suicide).""" + # EIP-8037 inner-CALL gas bumps: original values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state- + # gas spill into regular gas on Amsterdam. + outer_call_gas = 150000 + middle_call_gas = 100000 + inner_call_gas = 50000 + if fork.is_eip_enabled(8037): + outer_call_gas = 1000000 + middle_call_gas = 800000 + inner_call_gas = 100000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -57,7 +74,7 @@ def test_callcodecallcallcode_101_suicide_end( code=Op.SSTORE( key=0x0, value=Op.CALLCODE( - gas=0x249F0, + gas=outer_call_gas, address=0x77B749FFFF7EC61D31C79ED104F230A7959B2879, value=0x0, args_offset=0x0, @@ -77,7 +94,7 @@ def test_callcodecallcallcode_101_suicide_end( code=Op.SSTORE( key=0x1, value=Op.CALL( - gas=0x186A0, + gas=middle_call_gas, address=0x94C8F980AEECBB6575B12AE614A249FC3E836F21, value=0x0, args_offset=0x0, @@ -97,7 +114,7 @@ def test_callcodecallcallcode_101_suicide_end( code=Op.SSTORE( key=0x2, value=Op.CALLCODE( - gas=0xC350, + gas=inner_call_gas, address=0x73B954EBC05BB0FF4A0F6A13A054D50AD1584099, value=0x0, args_offset=0x0, diff --git a/tests/ported_static/stCallCodes/test_callcodecallcallcode_abcb_recursive.py b/tests/ported_static/stCallCodes/test_callcodecallcallcode_abcb_recursive.py index c4ad5a066d8..b6737a50208 100644 --- a/tests/ported_static/stCallCodes/test_callcodecallcallcode_abcb_recursive.py +++ b/tests/ported_static/stCallCodes/test_callcodecallcallcode_abcb_recursive.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -28,6 +30,7 @@ @pytest.mark.pre_alloc_mutable def test_callcodecallcallcode_abcb_recursive( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """CALLCODE -> CALL <-> CALLCODE.""" @@ -40,7 +43,6 @@ def test_callcodecallcallcode_abcb_recursive( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=3000000000, ) # Source: lll @@ -108,13 +110,18 @@ def test_callcodecallcallcode_abcb_recursive( sender=sender, to=target, data=Bytes(""), - gas_limit=600000, + gas_limit=2600000 if fork >= Amsterdam else 600000, ) post = { target: Account(storage={0: 1, 1: 1}), addr: Account(storage={1: 0, 2: 0}), - addr_2: Account(storage={1: 0, 2: 0}), + addr_2: Account( + storage={ + 1: 0, + 2: 0, + } + ), } state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stCallCodes/test_callcodecallcode_11.py b/tests/ported_static/stCallCodes/test_callcodecallcode_11.py index ae4f5601541..8383a42511c 100644 --- a/tests/ported_static/stCallCodes/test_callcodecallcode_11.py +++ b/tests/ported_static/stCallCodes/test_callcodecallcode_11.py @@ -3,6 +3,16 @@ Ported from: state_tests/stCallCodes/callcodecallcode_11Filler.json + + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values from the original filler (250k / 300k / 350k) were tuned to +the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the +innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei +of regular gas), and the inner CALL OoGs before the test's SSTORE +markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL +chain has headroom on Amsterdam; older forks are unaffected because +only the requested gas changes, the actual consumption is identical. """ import pytest @@ -15,6 +25,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +40,18 @@ def test_callcodecallcode_11( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """CALLCODE -> CALLCODE -> code, check parameters.""" + # EIP-8037 inner-CALL gas bumps (original gas values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state-gas + # spill into regular gas on Amsterdam). + inner_call_gas = 0x3D090 + outer_call_gas = 0x55730 + if fork.is_eip_enabled(8037): + inner_call_gas = 0xF4240 + outer_call_gas = 0x155CC0 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -63,7 +84,7 @@ def test_callcodecallcode_11( code=Op.SSTORE( key=0x1, value=Op.CALLCODE( - gas=0x3D090, + gas=inner_call_gas, address=addr_2, value=0x2, args_offset=0x0, @@ -82,7 +103,7 @@ def test_callcodecallcode_11( code=Op.SSTORE( key=0x0, value=Op.CALLCODE( - gas=0x55730, + gas=outer_call_gas, address=addr, value=0x1, args_offset=0x0, diff --git a/tests/ported_static/stCallCodes/test_callcodecallcodecall_110.py b/tests/ported_static/stCallCodes/test_callcodecallcodecall_110.py index 554208349a6..5c057849d04 100644 --- a/tests/ported_static/stCallCodes/test_callcodecallcodecall_110.py +++ b/tests/ported_static/stCallCodes/test_callcodecallcodecall_110.py @@ -3,6 +3,16 @@ Ported from: state_tests/stCallCodes/callcodecallcodecall_110Filler.json + + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values from the original filler (250k / 300k / 350k) were tuned to +the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the +innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei +of regular gas), and the inner CALL OoGs before the test's SSTORE +markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL +chain has headroom on Amsterdam; older forks are unaffected because +only the requested gas changes, the actual consumption is identical. """ import pytest @@ -15,6 +25,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +40,20 @@ def test_callcodecallcodecall_110( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """CALLCODE -> CALLCODE -> CALL -> code, check parameters.""" + # EIP-8037 inner-CALL gas bumps (original gas values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state-gas + # spill into regular gas on Amsterdam). + inner_call_gas = 0x3D090 + middle_call_gas = 0x493E0 + outer_call_gas = 0x55730 + if fork.is_eip_enabled(8037): + inner_call_gas = 0xF4240 + middle_call_gas = 0x124F80 + outer_call_gas = 0x155CC0 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -63,7 +86,7 @@ def test_callcodecallcodecall_110( code=Op.SSTORE( key=0x2, value=Op.CALL( - gas=0x3D090, + gas=inner_call_gas, address=addr_3, value=0x3, args_offset=0x0, @@ -82,7 +105,7 @@ def test_callcodecallcodecall_110( code=Op.SSTORE( key=0x1, value=Op.CALLCODE( - gas=0x493E0, + gas=middle_call_gas, address=addr_2, value=0x2, args_offset=0x0, @@ -101,7 +124,7 @@ def test_callcodecallcodecall_110( code=Op.SSTORE( key=0x0, value=Op.CALLCODE( - gas=0x55730, + gas=outer_call_gas, address=addr, value=0x1, args_offset=0x0, diff --git a/tests/ported_static/stCallCodes/test_callcodecallcodecall_110_suicide_end.py b/tests/ported_static/stCallCodes/test_callcodecallcodecall_110_suicide_end.py index b988b59cc26..fdeaf653e4d 100644 --- a/tests/ported_static/stCallCodes/test_callcodecallcodecall_110_suicide_end.py +++ b/tests/ported_static/stCallCodes/test_callcodecallcodecall_110_suicide_end.py @@ -3,6 +3,10 @@ Ported from: state_tests/stCallCodes/callcodecallcodecall_110_SuicideEndFiller.json + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. + """ import pytest @@ -15,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +34,20 @@ def test_callcodecallcodecall_110_suicide_end( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """CALLCODE -> CALLCODE -> (CALL -> code) (suicide) .""" + # EIP-8037 inner-CALL gas bumps: original values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state- + # gas spill into regular gas on Amsterdam. + outer_call_gas = 150000 + middle_call_gas = 100000 + inner_call_gas = 50000 + if fork.is_eip_enabled(8037): + outer_call_gas = 1000000 + middle_call_gas = 800000 + inner_call_gas = 100000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -57,7 +74,7 @@ def test_callcodecallcodecall_110_suicide_end( code=Op.SSTORE( key=0x0, value=Op.CALLCODE( - gas=0x249F0, + gas=outer_call_gas, address=0xEAF8C2AE0D01A880CEA4E1AA88DEF5EDD153D57B, value=0x0, args_offset=0x0, @@ -77,7 +94,7 @@ def test_callcodecallcodecall_110_suicide_end( code=Op.SSTORE( key=0x1, value=Op.CALLCODE( - gas=0x186A0, + gas=middle_call_gas, address=0xD957E143AD2C011BC6A2B142795F1A9BA70D0680, value=0x0, args_offset=0x0, @@ -97,7 +114,7 @@ def test_callcodecallcodecall_110_suicide_end( code=Op.SSTORE( key=0x2, value=Op.CALL( - gas=0xC350, + gas=inner_call_gas, address=0x73B954EBC05BB0FF4A0F6A13A054D50AD1584099, value=0x0, args_offset=0x0, diff --git a/tests/ported_static/stCallCodes/test_callcodecallcodecall_abcb_recursive.py b/tests/ported_static/stCallCodes/test_callcodecallcodecall_abcb_recursive.py index ce4795cb975..94b8ba2a1f7 100644 --- a/tests/ported_static/stCallCodes/test_callcodecallcodecall_abcb_recursive.py +++ b/tests/ported_static/stCallCodes/test_callcodecallcodecall_abcb_recursive.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -28,6 +30,7 @@ @pytest.mark.pre_alloc_mutable def test_callcodecallcodecall_abcb_recursive( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """CALLCODE -> CALLCODE <-> CALL .""" @@ -40,7 +43,6 @@ def test_callcodecallcodecall_abcb_recursive( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=3000000000, ) # Source: lll @@ -108,12 +110,17 @@ def test_callcodecallcodecall_abcb_recursive( sender=sender, to=target, data=Bytes(""), - gas_limit=600000, + gas_limit=2600000 if fork >= Amsterdam else 600000, ) post = { - target: Account(storage={0: 1, 1: 1}), - addr: Account(storage={1: 0, 2: 0}), + target: Account(storage={0: 1, 1: 1, 2: 0}), + addr: Account( + storage={ + 1: 0, + 2: 0, + } + ), addr_2: Account(storage={1: 0, 2: 0}), } diff --git a/tests/ported_static/stCallCodes/test_callcodecallcodecallcode_111.py b/tests/ported_static/stCallCodes/test_callcodecallcodecallcode_111.py index 3eecfb14554..397d7fff51a 100644 --- a/tests/ported_static/stCallCodes/test_callcodecallcodecallcode_111.py +++ b/tests/ported_static/stCallCodes/test_callcodecallcodecallcode_111.py @@ -3,6 +3,16 @@ Ported from: state_tests/stCallCodes/callcodecallcodecallcode_111Filler.json + + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values from the original filler (250k / 300k / 350k) were tuned to +the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the +innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei +of regular gas), and the inner CALL OoGs before the test's SSTORE +markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL +chain has headroom on Amsterdam; older forks are unaffected because +only the requested gas changes, the actual consumption is identical. """ import pytest @@ -15,6 +25,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +40,20 @@ def test_callcodecallcodecallcode_111( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """CALLCODE -> CALLCODE -> CALLCODE -> code check parameter opcodes.""" + # EIP-8037 inner-CALL gas bumps (original gas values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state-gas + # spill into regular gas on Amsterdam). + inner_call_gas = 0x3D090 + middle_call_gas = 0x493E0 + outer_call_gas = 0x55730 + if fork.is_eip_enabled(8037): + inner_call_gas = 0xF4240 + middle_call_gas = 0x124F80 + outer_call_gas = 0x155CC0 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -63,7 +86,7 @@ def test_callcodecallcodecallcode_111( code=Op.SSTORE( key=0x2, value=Op.CALLCODE( - gas=0x3D090, + gas=inner_call_gas, address=addr_3, value=0x3, args_offset=0x0, @@ -82,7 +105,7 @@ def test_callcodecallcodecallcode_111( code=Op.SSTORE( key=0x1, value=Op.CALLCODE( - gas=0x493E0, + gas=middle_call_gas, address=addr_2, value=0x2, args_offset=0x0, @@ -101,7 +124,7 @@ def test_callcodecallcodecallcode_111( code=Op.SSTORE( key=0x0, value=Op.CALLCODE( - gas=0x55730, + gas=outer_call_gas, address=addr, value=0x1, args_offset=0x0, diff --git a/tests/ported_static/stCallCodes/test_callcodecallcodecallcode_111_suicide_end.py b/tests/ported_static/stCallCodes/test_callcodecallcodecallcode_111_suicide_end.py index d3b29c3988d..b07feeb4693 100644 --- a/tests/ported_static/stCallCodes/test_callcodecallcodecallcode_111_suicide_end.py +++ b/tests/ported_static/stCallCodes/test_callcodecallcodecallcode_111_suicide_end.py @@ -3,6 +3,10 @@ Ported from: state_tests/stCallCodes/callcodecallcodecallcode_111_SuicideEndFiller.json + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. + """ import pytest @@ -15,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +36,20 @@ def test_callcodecallcodecallcode_111_suicide_end( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """CALLCODE -> CALLCODE -> (CALLCODE -> code) suicide.""" + # EIP-8037 inner-CALL gas bumps: original values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state- + # gas spill into regular gas on Amsterdam. + outer_call_gas = 150000 + middle_call_gas = 100000 + inner_call_gas = 50000 + if fork.is_eip_enabled(8037): + outer_call_gas = 1000000 + middle_call_gas = 800000 + inner_call_gas = 100000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -59,7 +76,7 @@ def test_callcodecallcodecallcode_111_suicide_end( code=Op.SSTORE( key=0x0, value=Op.CALLCODE( - gas=0x249F0, + gas=outer_call_gas, address=0xEAF8C2AE0D01A880CEA4E1AA88DEF5EDD153D57B, value=0x0, args_offset=0x0, @@ -79,7 +96,7 @@ def test_callcodecallcodecallcode_111_suicide_end( code=Op.SSTORE( key=0x1, value=Op.CALLCODE( - gas=0x186A0, + gas=middle_call_gas, address=0x94C8F980AEECBB6575B12AE614A249FC3E836F21, value=0x0, args_offset=0x0, @@ -99,7 +116,7 @@ def test_callcodecallcodecallcode_111_suicide_end( code=Op.SSTORE( key=0x2, value=Op.CALLCODE( - gas=0xC350, + gas=inner_call_gas, address=0x73B954EBC05BB0FF4A0F6A13A054D50AD1584099, value=0x0, args_offset=0x0, diff --git a/tests/ported_static/stCallCodes/test_callcodecallcodecallcode_abcb_recursive.py b/tests/ported_static/stCallCodes/test_callcodecallcodecallcode_abcb_recursive.py index 0c73ba78c27..b5f81bf93fc 100644 --- a/tests/ported_static/stCallCodes/test_callcodecallcodecallcode_abcb_recursive.py +++ b/tests/ported_static/stCallCodes/test_callcodecallcodecallcode_abcb_recursive.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,6 +32,7 @@ @pytest.mark.pre_alloc_mutable def test_callcodecallcodecallcode_abcb_recursive( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """CALLCODE -> CALLCODE2 -> CALLCODE3 -> CALLCODE2 -> .""" @@ -42,7 +45,6 @@ def test_callcodecallcodecallcode_abcb_recursive( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=3000000000, ) # Source: lll @@ -110,11 +112,11 @@ def test_callcodecallcodecallcode_abcb_recursive( sender=sender, to=target, data=Bytes(""), - gas_limit=600000, + gas_limit=2600000 if fork >= Amsterdam else 600000, ) post = { - target: Account(storage={0: 1, 1: 1}), + target: Account(storage={0: 1, 1: 1, 2: 0}), addr: Account(storage={1: 0, 2: 0}), addr_2: Account(storage={1: 0, 2: 0}), } diff --git a/tests/ported_static/stCallCreateCallCodeTest/test_call_lose_gas_oog.py b/tests/ported_static/stCallCreateCallCodeTest/test_call_lose_gas_oog.py index ccae45c2c76..55e2ad8f1d7 100644 --- a/tests/ported_static/stCallCreateCallCodeTest/test_call_lose_gas_oog.py +++ b/tests/ported_static/stCallCreateCallCodeTest/test_call_lose_gas_oog.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -28,6 +30,7 @@ @pytest.mark.pre_alloc_mutable def test_call_lose_gas_oog( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Recursive call.""" @@ -40,7 +43,6 @@ def test_call_lose_gas_oog( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) addr = pre.fund_eoa(amount=7000) # noqa: F841 @@ -73,7 +75,7 @@ def test_call_lose_gas_oog( sender=sender, to=target, data=Bytes(""), - gas_limit=200000, + gas_limit=2200000 if fork >= Amsterdam else 200000, value=10, ) diff --git a/tests/ported_static/stCallCreateCallCodeTest/test_call_with_high_value_oo_gin_call.py b/tests/ported_static/stCallCreateCallCodeTest/test_call_with_high_value_oo_gin_call.py index e460ca99647..7c34107e6b9 100644 --- a/tests/ported_static/stCallCreateCallCodeTest/test_call_with_high_value_oo_gin_call.py +++ b/tests/ported_static/stCallCreateCallCodeTest/test_call_with_high_value_oo_gin_call.py @@ -42,7 +42,6 @@ def test_call_with_high_value_oo_gin_call( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=30000000, ) # Source: raw diff --git a/tests/ported_static/stCallCreateCallCodeTest/test_create_fail_balance_too_low.py b/tests/ported_static/stCallCreateCallCodeTest/test_create_fail_balance_too_low.py index a9036d4c58c..89438cb2af2 100644 --- a/tests/ported_static/stCallCreateCallCodeTest/test_create_fail_balance_too_low.py +++ b/tests/ported_static/stCallCreateCallCodeTest/test_create_fail_balance_too_low.py @@ -3,6 +3,10 @@ Ported from: state_tests/stCallCreateCallCodeTest/createFailBalanceTooLowFiller.json +@manually-enhanced: Do not overwrite. Gas bumped fork-conditionally +to cover EIP-8037 state-gas spill into regular gas; pre-EIP-8037 +behavior unchanged. + """ import pytest @@ -60,6 +64,11 @@ def test_create_fail_balance_too_low( v: int, ) -> None: """Create fails because we try to send more wei to it that we have.""" + # EIP-8037 gas bumps: original values for pre-EIP-8037 forks. + outer_tx_gas = 253021 + if fork.is_eip_enabled(8037): + outer_tx_gas = 1265105 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) contract_0 = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87) sender = EOA( @@ -121,7 +130,7 @@ def test_create_fail_balance_too_low( tx_data = [ Bytes(""), ] - tx_gas = [253021] + tx_gas = [outer_tx_gas] tx_value = [23, 24] tx = Transaction( diff --git a/tests/ported_static/stCallCreateCallCodeTest/test_create_init_fail_undefined_instruction.py b/tests/ported_static/stCallCreateCallCodeTest/test_create_init_fail_undefined_instruction.py index bf25c3a23a2..b3d1812c13a 100644 --- a/tests/ported_static/stCallCreateCallCodeTest/test_create_init_fail_undefined_instruction.py +++ b/tests/ported_static/stCallCreateCallCodeTest/test_create_init_fail_undefined_instruction.py @@ -3,6 +3,10 @@ Ported from: state_tests/stCallCreateCallCodeTest/createInitFailUndefinedInstructionFiller.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 SSTORE-set state-gas spill (target performs 3 fresh +SSTOREs); pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +36,14 @@ def test_create_init_fail_undefined_instruction( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Create fails because init code has undefined opcode, trying to...""" + # EIP-8037 state-gas spill (3x fresh SSTORE-set) exceeds 900k tx_gas. + tx_gas_limit = 900000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 1_500_000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -42,7 +53,6 @@ def test_create_init_fail_undefined_instruction( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=1000000000, ) # Source: lll @@ -102,7 +112,7 @@ def test_create_init_fail_undefined_instruction( sender=sender, to=target, data=Bytes(""), - gas_limit=900000, + gas_limit=tx_gas_limit, value=0x186A0, ) diff --git a/tests/ported_static/stCallCreateCallCodeTest/test_create_js_no_collision.py b/tests/ported_static/stCallCreateCallCodeTest/test_create_js_no_collision.py index 98c0900c511..53488ba3ba8 100644 --- a/tests/ported_static/stCallCreateCallCodeTest/test_create_js_no_collision.py +++ b/tests/ported_static/stCallCreateCallCodeTest/test_create_js_no_collision.py @@ -12,10 +12,12 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, compute_create_address, ) +from execution_testing.forks import Amsterdam REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" @@ -27,6 +29,7 @@ @pytest.mark.valid_from("Cancun") def test_create_js_no_collision( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Deploy legacy contract normally.""" @@ -39,7 +42,7 @@ def test_create_js_no_collision( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=1000000, + gas_limit=3000000 if fork >= Amsterdam else 1000000, ) tx = Transaction( @@ -48,7 +51,7 @@ def test_create_js_no_collision( data=Bytes( "60406103ca600439600451602451336000819055506000600481905550816001819055508060028190555042600581905550336003819055505050610381806100496000396000f30060003560e060020a9004806343d726d61461004257806391b7f5ed14610050578063d686f9ee14610061578063f5bade661461006f578063fcfff16f1461008057005b61004a6101de565b60006000f35b61005b6004356100bf565b60006000f35b610069610304565b60006000f35b61007a60043561008e565b60006000f35b6100886100f0565b60006000f35b600054600160a060020a031633600160a060020a031614156100af576100b4565b6100bc565b806001819055505b50565b600054600160a060020a031633600160a060020a031614156100e0576100e5565b6100ed565b806002819055505b50565b600054600160a060020a031633600160a060020a031614806101255750600354600160a060020a031633600160a060020a0316145b61012e57610161565b60016004819055507f59ebeb90bc63057b6515673c3ecf9438e5058bca0f92585014eced636878c9a560006000a16101dc565b60045460011480610173575060015434105b6101b85760016004819055507f59ebeb90bc63057b6515673c3ecf9438e5058bca0f92585014eced636878c9a560006000a142600581905550336003819055506101db565b33600160a060020a03166000346000600060006000848787f16101d757005b5050505b5b565b60006004546000146101ef576101f4565b610301565b600054600160a060020a031633600160a060020a031614801561022c5750600054600160a060020a0316600354600160a060020a0316145b61023557610242565b6000600481905550610301565b600354600160a060020a031633600160a060020a03161461026257610300565b600554420360025402905060015481116102c757600354600160a060020a0316600082600154036000600060006000848787f161029b57005b505050600054600160a060020a03166000826000600060006000848787f16102bf57005b5050506102ee565b600054600160a060020a031660006001546000600060006000848787f16102ea57005b5050505b60006004819055506000546003819055505b5b50565b6000600054600160a060020a031633600160a060020a031614156103275761032c565b61037e565b600554420360025402905060015481116103455761037d565b600054600160a060020a031660006001546000600060006000848787f161036857005b50505060006004819055506000546003819055505b5b505600000000000000000000000000000000000000000000000000000000000000420000000000000000000000000000000000000000000000000000000000000023" # noqa: E501 ), - gas_limit=600000, + gas_limit=2600000 if fork >= Amsterdam else 600000, value=0x186A0, ) diff --git a/tests/ported_static/stCallCreateCallCodeTest/test_create_name_registrator_per_txs.py b/tests/ported_static/stCallCreateCallCodeTest/test_create_name_registrator_per_txs.py index 5a456ae36ae..8478c056bc7 100644 --- a/tests/ported_static/stCallCreateCallCodeTest/test_create_name_registrator_per_txs.py +++ b/tests/ported_static/stCallCreateCallCodeTest/test_create_name_registrator_per_txs.py @@ -41,7 +41,6 @@ def test_create_name_registrator_per_txs( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000000, ) tx = Transaction( diff --git a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcallcode_001.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcallcode_001.py index 34843e7be67..17ac54a7922 100644 --- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcallcode_001.py +++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcallcode_001.py @@ -3,6 +3,16 @@ Ported from: state_tests/stCallDelegateCodesCallCodeHomestead/callcallcallcode_001Filler.json + + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values from the original filler (250k / 300k / 350k) were tuned to +the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the +innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei +of regular gas), and the inner CALL OoGs before the test's SSTORE +markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL +chain has headroom on Amsterdam; older forks are unaffected because +only the requested gas changes, the actual consumption is identical. """ import pytest @@ -15,6 +25,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +42,20 @@ def test_callcallcallcode_001( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_callcallcallcode_001.""" + # EIP-8037 inner-CALL gas bumps (original gas values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state-gas + # spill into regular gas on Amsterdam). + inner_call_gas = 0x3D090 + middle_call_gas = 0x493E0 + outer_call_gas = 0x55730 + if fork.is_eip_enabled(8037): + inner_call_gas = 0xF4240 + middle_call_gas = 0x124F80 + outer_call_gas = 0x155CC0 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -65,7 +88,7 @@ def test_callcallcallcode_001( code=Op.SSTORE( key=0x2, value=Op.DELEGATECALL( - gas=0x3D090, + gas=inner_call_gas, address=addr_3, args_offset=0x0, args_size=0x40, @@ -83,7 +106,7 @@ def test_callcallcallcode_001( code=Op.SSTORE( key=0x1, value=Op.CALLCODE( - gas=0x493E0, + gas=middle_call_gas, address=addr_2, value=0x2, args_offset=0x0, @@ -102,7 +125,7 @@ def test_callcallcallcode_001( code=Op.SSTORE( key=0x0, value=Op.CALLCODE( - gas=0x55730, + gas=outer_call_gas, address=addr, value=0x1, args_offset=0x0, diff --git a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcallcode_001_suicide_end.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcallcode_001_suicide_end.py index 98081b8eff6..9d540998144 100644 --- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcallcode_001_suicide_end.py +++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcallcode_001_suicide_end.py @@ -3,6 +3,10 @@ Ported from: state_tests/stCallDelegateCodesCallCodeHomestead/callcallcallcode_001_SuicideEndFiller.json + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. + """ import pytest @@ -15,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +36,20 @@ def test_callcallcallcode_001_suicide_end( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_callcallcallcode_001_suicide_end.""" + # EIP-8037 inner-CALL gas bumps: original values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state- + # gas spill into regular gas on Amsterdam. + outer_call_gas = 150000 + middle_call_gas = 100000 + inner_call_gas = 50000 + if fork.is_eip_enabled(8037): + outer_call_gas = 1000000 + middle_call_gas = 800000 + inner_call_gas = 100000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -59,7 +76,7 @@ def test_callcallcallcode_001_suicide_end( code=Op.SSTORE( key=0x0, value=Op.CALLCODE( - gas=0x249F0, + gas=outer_call_gas, address=0xEAF8C2AE0D01A880CEA4E1AA88DEF5EDD153D57B, value=0x0, args_offset=0x0, @@ -79,7 +96,7 @@ def test_callcallcallcode_001_suicide_end( code=Op.SSTORE( key=0x1, value=Op.CALLCODE( - gas=0x186A0, + gas=middle_call_gas, address=0xAC521409E2FA9526BFE6B827805783D2E307C4CE, value=0x0, args_offset=0x0, @@ -99,7 +116,7 @@ def test_callcallcallcode_001_suicide_end( code=Op.SSTORE( key=0x2, value=Op.DELEGATECALL( - gas=0xC350, + gas=inner_call_gas, address=0x73B954EBC05BB0FF4A0F6A13A054D50AD1584099, args_offset=0x0, args_size=0x40, diff --git a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcallcode_abcb_recursive.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcallcode_abcb_recursive.py index df4566923a7..d65d916152d 100644 --- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcallcode_abcb_recursive.py +++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcallcode_abcb_recursive.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,6 +32,7 @@ @pytest.mark.pre_alloc_mutable def test_callcallcallcode_abcb_recursive( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """CALLCODE -> CALLCODE1 -> DELEGATECALL2 -> CALLCODE1 -> .""" @@ -42,7 +45,6 @@ def test_callcallcallcode_abcb_recursive( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=3000000000, ) # Source: lll @@ -109,11 +111,11 @@ def test_callcallcallcode_abcb_recursive( sender=sender, to=target, data=Bytes(""), - gas_limit=600000, + gas_limit=2600000 if fork >= Amsterdam else 600000, ) post = { - target: Account(storage={0: 1, 1: 1}), + target: Account(storage={0: 1, 1: 1, 2: 0}), addr: Account(storage={1: 0, 2: 0}), addr_2: Account(storage={1: 0, 2: 0}), } diff --git a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcode_01.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcode_01.py index 62281359646..4ec0e0470db 100644 --- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcode_01.py +++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcode_01.py @@ -3,6 +3,16 @@ Ported from: state_tests/stCallDelegateCodesCallCodeHomestead/callcallcode_01Filler.json + + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values from the original filler (250k / 300k / 350k) were tuned to +the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the +innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei +of regular gas), and the inner CALL OoGs before the test's SSTORE +markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL +chain has headroom on Amsterdam; older forks are unaffected because +only the requested gas changes, the actual consumption is identical. """ import pytest @@ -15,6 +25,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +42,18 @@ def test_callcallcode_01( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_callcallcode_01.""" + # EIP-8037 inner-CALL gas bumps (original gas values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state-gas + # spill into regular gas on Amsterdam). + inner_call_gas = 0x3D090 + outer_call_gas = 0x55730 + if fork.is_eip_enabled(8037): + inner_call_gas = 0xF4240 + outer_call_gas = 0x155CC0 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -65,7 +86,7 @@ def test_callcallcode_01( code=Op.SSTORE( key=0x1, value=Op.DELEGATECALL( - gas=0x3D090, + gas=inner_call_gas, address=addr_2, args_offset=0x0, args_size=0x40, @@ -83,7 +104,7 @@ def test_callcallcode_01( code=Op.SSTORE( key=0x0, value=Op.CALLCODE( - gas=0x55730, + gas=outer_call_gas, address=addr, value=0x1, args_offset=0x0, diff --git a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcode_01_suicide_end.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcode_01_suicide_end.py index 8e30857302f..fd2c72ca7c1 100644 --- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcode_01_suicide_end.py +++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcode_01_suicide_end.py @@ -3,6 +3,10 @@ Ported from: state_tests/stCallDelegateCodesCallCodeHomestead/callcallcode_01_SuicideEndFiller.json + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. + """ import pytest @@ -15,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +36,18 @@ def test_callcallcode_01_suicide_end( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_callcallcode_01_suicide_end.""" + # EIP-8037 inner-CALL gas bumps: original values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state- + # gas spill into regular gas on Amsterdam. + outer_call_gas = 150000 + inner_call_gas = 50000 + if fork.is_eip_enabled(8037): + outer_call_gas = 1000000 + inner_call_gas = 100000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -59,7 +74,7 @@ def test_callcallcode_01_suicide_end( code=Op.SSTORE( key=0x0, value=Op.CALLCODE( - gas=0x249F0, + gas=outer_call_gas, address=0x1CCA6E93108EC94304AE5EB121D323E6C317FE7A, value=0x0, args_offset=0x0, @@ -79,7 +94,7 @@ def test_callcallcode_01_suicide_end( code=Op.SSTORE( key=0x1, value=Op.DELEGATECALL( - gas=0xC350, + gas=inner_call_gas, address=0x703B936FD4D674F0FF5D6957F61097152F8781B8, args_offset=0x0, args_size=0x40, diff --git a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcodecall_010.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcodecall_010.py index 1b71c97c2cc..58e1eef4424 100644 --- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcodecall_010.py +++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcodecall_010.py @@ -3,6 +3,16 @@ Ported from: state_tests/stCallDelegateCodesCallCodeHomestead/callcallcodecall_010Filler.json + + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values from the original filler (250k / 300k / 350k) were tuned to +the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the +innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei +of regular gas), and the inner CALL OoGs before the test's SSTORE +markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL +chain has headroom on Amsterdam; older forks are unaffected because +only the requested gas changes, the actual consumption is identical. """ import pytest @@ -15,6 +25,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +42,20 @@ def test_callcallcodecall_010( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_callcallcodecall_010.""" + # EIP-8037 inner-CALL gas bumps (original gas values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state-gas + # spill into regular gas on Amsterdam). + inner_call_gas = 0x3D090 + middle_call_gas = 0x493E0 + outer_call_gas = 0x55730 + if fork.is_eip_enabled(8037): + inner_call_gas = 0xF4240 + middle_call_gas = 0x124F80 + outer_call_gas = 0x155CC0 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -65,7 +88,7 @@ def test_callcallcodecall_010( code=Op.SSTORE( key=0x2, value=Op.CALLCODE( - gas=0x3D090, + gas=inner_call_gas, address=addr_3, value=0x2, args_offset=0x0, @@ -85,7 +108,7 @@ def test_callcallcodecall_010( code=Op.SSTORE( key=0x1, value=Op.DELEGATECALL( - gas=0x493E0, + gas=middle_call_gas, address=addr_2, args_offset=0x0, args_size=0x40, @@ -103,7 +126,7 @@ def test_callcallcodecall_010( code=Op.SSTORE( key=0x0, value=Op.CALLCODE( - gas=0x55730, + gas=outer_call_gas, address=addr, value=0x1, args_offset=0x0, diff --git a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcodecall_010_suicide_end.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcodecall_010_suicide_end.py index 02e73911b14..5ba88271301 100644 --- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcodecall_010_suicide_end.py +++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcodecall_010_suicide_end.py @@ -3,6 +3,10 @@ Ported from: state_tests/stCallDelegateCodesCallCodeHomestead/callcallcodecall_010_SuicideEndFiller.json + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. + """ import pytest @@ -15,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +36,20 @@ def test_callcallcodecall_010_suicide_end( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_callcallcodecall_010_suicide_end.""" + # EIP-8037 inner-CALL gas bumps: original values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state- + # gas spill into regular gas on Amsterdam. + outer_call_gas = 150000 + middle_call_gas = 100000 + inner_call_gas = 50000 + if fork.is_eip_enabled(8037): + outer_call_gas = 1000000 + middle_call_gas = 800000 + inner_call_gas = 100000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -59,7 +76,7 @@ def test_callcallcodecall_010_suicide_end( code=Op.SSTORE( key=0x0, value=Op.CALLCODE( - gas=0x249F0, + gas=outer_call_gas, address=0x2CAC1D43F00E8B40B63426AB460C7E8717EE6455, value=0x0, args_offset=0x0, @@ -79,7 +96,7 @@ def test_callcallcodecall_010_suicide_end( code=Op.SSTORE( key=0x1, value=Op.DELEGATECALL( - gas=0x186A0, + gas=middle_call_gas, address=0x94C8F980AEECBB6575B12AE614A249FC3E836F21, args_offset=0x0, args_size=0x40, @@ -98,7 +115,7 @@ def test_callcallcodecall_010_suicide_end( code=Op.SSTORE( key=0x2, value=Op.CALLCODE( - gas=0xC350, + gas=inner_call_gas, address=0x73B954EBC05BB0FF4A0F6A13A054D50AD1584099, value=0x0, args_offset=0x0, diff --git a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcodecall_abcb_recursive.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcodecall_abcb_recursive.py index cb858cf97a2..a6471083fe9 100644 --- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcodecall_abcb_recursive.py +++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcodecall_abcb_recursive.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,6 +32,7 @@ @pytest.mark.pre_alloc_mutable def test_callcallcodecall_abcb_recursive( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """CALLCODE -> DELEGATECALL -> CALLCODE2 -> DELEGATECALL -> CALLCODE2...""" @@ -42,7 +45,6 @@ def test_callcallcodecall_abcb_recursive( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=3000000000, ) # Source: lll @@ -109,11 +111,11 @@ def test_callcallcodecall_abcb_recursive( sender=sender, to=target, data=Bytes(""), - gas_limit=600000, + gas_limit=2600000 if fork >= Amsterdam else 600000, ) post = { - target: Account(storage={0: 1, 1: 1}), + target: Account(storage={0: 1, 1: 1, 2: 0}), addr: Account(storage={1: 0, 2: 0}), addr_2: Account(storage={1: 0, 2: 0}), } diff --git a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcodecallcode_011.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcodecallcode_011.py index 48ad74e3fd5..fb05c8d0604 100644 --- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcodecallcode_011.py +++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcodecallcode_011.py @@ -3,6 +3,16 @@ Ported from: state_tests/stCallDelegateCodesCallCodeHomestead/callcallcodecallcode_011Filler.json + + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values from the original filler (250k / 300k / 350k) were tuned to +the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the +innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei +of regular gas), and the inner CALL OoGs before the test's SSTORE +markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL +chain has headroom on Amsterdam; older forks are unaffected because +only the requested gas changes, the actual consumption is identical. """ import pytest @@ -15,6 +25,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +42,20 @@ def test_callcallcodecallcode_011( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_callcallcodecallcode_011.""" + # EIP-8037 inner-CALL gas bumps (original gas values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state-gas + # spill into regular gas on Amsterdam). + inner_call_gas = 0x3D090 + middle_call_gas = 0x493E0 + outer_call_gas = 0x55730 + if fork.is_eip_enabled(8037): + inner_call_gas = 0xF4240 + middle_call_gas = 0x124F80 + outer_call_gas = 0x155CC0 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -65,7 +88,7 @@ def test_callcallcodecallcode_011( code=Op.SSTORE( key=0x2, value=Op.DELEGATECALL( - gas=0x30D40, + gas=inner_call_gas, address=addr_3, args_offset=0x0, args_size=0x40, @@ -82,7 +105,7 @@ def test_callcallcodecallcode_011( code=Op.SSTORE( key=0x1, value=Op.DELEGATECALL( - gas=0x493E0, + gas=middle_call_gas, address=addr_2, args_offset=0x0, args_size=0x40, @@ -99,7 +122,7 @@ def test_callcallcodecallcode_011( code=Op.SSTORE( key=0x0, value=Op.CALLCODE( - gas=0x55730, + gas=outer_call_gas, address=addr, value=0x1, args_offset=0x0, diff --git a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcodecallcode_011_suicide_end.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcodecallcode_011_suicide_end.py index 8ff0ce634da..842885bcc2a 100644 --- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcodecallcode_011_suicide_end.py +++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcodecallcode_011_suicide_end.py @@ -3,6 +3,10 @@ Ported from: state_tests/stCallDelegateCodesCallCodeHomestead/callcallcodecallcode_011_SuicideEndFiller.json + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. + """ import pytest @@ -15,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +36,20 @@ def test_callcallcodecallcode_011_suicide_end( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_callcallcodecallcode_011_suicide_end.""" + # EIP-8037 inner-CALL gas bumps: original values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state- + # gas spill into regular gas on Amsterdam. + outer_call_gas = 150000 + middle_call_gas = 100000 + inner_call_gas = 50000 + if fork.is_eip_enabled(8037): + outer_call_gas = 1000000 + middle_call_gas = 800000 + inner_call_gas = 100000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -59,7 +76,7 @@ def test_callcallcodecallcode_011_suicide_end( code=Op.SSTORE( key=0x0, value=Op.CALLCODE( - gas=0x249F0, + gas=outer_call_gas, address=0x2CAC1D43F00E8B40B63426AB460C7E8717EE6455, value=0x0, args_offset=0x0, @@ -79,7 +96,7 @@ def test_callcallcodecallcode_011_suicide_end( code=Op.SSTORE( key=0x1, value=Op.DELEGATECALL( - gas=0x186A0, + gas=middle_call_gas, address=0xAC521409E2FA9526BFE6B827805783D2E307C4CE, args_offset=0x0, args_size=0x40, @@ -98,7 +115,7 @@ def test_callcallcodecallcode_011_suicide_end( code=Op.SSTORE( key=0x2, value=Op.DELEGATECALL( - gas=0xC350, + gas=inner_call_gas, address=0x73B954EBC05BB0FF4A0F6A13A054D50AD1584099, args_offset=0x0, args_size=0x40, diff --git a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcodecallcode_abcb_recursive.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcodecallcode_abcb_recursive.py index c4c99123100..83904ae6c9c 100644 --- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcodecallcode_abcb_recursive.py +++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcodecallcode_abcb_recursive.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,6 +32,7 @@ @pytest.mark.pre_alloc_mutable def test_callcallcodecallcode_abcb_recursive( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """CALLCODE -> DELEGATECALL1 -> DELEGATECALL2 -> DELEGATECALL1 -> .""" @@ -42,7 +45,6 @@ def test_callcallcodecallcode_abcb_recursive( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=3000000000, ) # Source: lll @@ -108,11 +110,11 @@ def test_callcallcodecallcode_abcb_recursive( sender=sender, to=target, data=Bytes(""), - gas_limit=600000, + gas_limit=2600000 if fork >= Amsterdam else 600000, ) post = { - target: Account(storage={0: 1, 1: 1}), + target: Account(storage={0: 1, 1: 1, 2: 0}), addr: Account(storage={1: 0, 2: 0}), addr_2: Account(storage={1: 0, 2: 0}), } diff --git a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecall_10.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecall_10.py index 89451d38412..85607a610a4 100644 --- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecall_10.py +++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecall_10.py @@ -3,6 +3,16 @@ Ported from: state_tests/stCallDelegateCodesCallCodeHomestead/callcodecall_10Filler.json + + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values from the original filler (250k / 300k / 350k) were tuned to +the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the +innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei +of regular gas), and the inner CALL OoGs before the test's SSTORE +markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL +chain has headroom on Amsterdam; older forks are unaffected because +only the requested gas changes, the actual consumption is identical. """ import pytest @@ -15,6 +25,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +42,18 @@ def test_callcodecall_10( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_callcodecall_10.""" + # EIP-8037 inner-CALL gas bumps (original gas values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state-gas + # spill into regular gas on Amsterdam). + inner_call_gas = 0x3D090 + outer_call_gas = 0x55730 + if fork.is_eip_enabled(8037): + inner_call_gas = 0xF4240 + outer_call_gas = 0x155CC0 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -65,7 +86,7 @@ def test_callcodecall_10( code=Op.SSTORE( key=0x1, value=Op.CALLCODE( - gas=0x3D090, + gas=inner_call_gas, address=addr_2, value=0x2, args_offset=0x0, @@ -84,7 +105,7 @@ def test_callcodecall_10( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=0x55730, + gas=outer_call_gas, address=addr, args_offset=0x0, args_size=0x40, diff --git a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecall_10_suicide_end.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecall_10_suicide_end.py index ed560a050f0..66565fc3929 100644 --- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecall_10_suicide_end.py +++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecall_10_suicide_end.py @@ -3,6 +3,10 @@ Ported from: state_tests/stCallDelegateCodesCallCodeHomestead/callcodecall_10_SuicideEndFiller.json + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. + """ import pytest @@ -15,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +36,18 @@ def test_callcodecall_10_suicide_end( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_callcodecall_10_suicide_end.""" + # EIP-8037 inner-CALL gas bumps: original values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state- + # gas spill into regular gas on Amsterdam. + outer_call_gas = 150000 + inner_call_gas = 50000 + if fork.is_eip_enabled(8037): + outer_call_gas = 1000000 + inner_call_gas = 100000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -59,7 +74,7 @@ def test_callcodecall_10_suicide_end( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=0x249F0, + gas=outer_call_gas, address=0x799DA5A3C983A22F9C430DE1BF99134EE561E856, args_offset=0x0, args_size=0x40, @@ -78,7 +93,7 @@ def test_callcodecall_10_suicide_end( code=Op.SSTORE( key=0x1, value=Op.CALLCODE( - gas=0xC350, + gas=inner_call_gas, address=0x703B936FD4D674F0FF5D6957F61097152F8781B8, value=0x0, args_offset=0x0, diff --git a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcall_100.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcall_100.py index 571626d265a..b6072d270e1 100644 --- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcall_100.py +++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcall_100.py @@ -3,6 +3,16 @@ Ported from: state_tests/stCallDelegateCodesCallCodeHomestead/callcodecallcall_100Filler.json + + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values from the original filler (250k / 300k / 350k) were tuned to +the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the +innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei +of regular gas), and the inner CALL OoGs before the test's SSTORE +markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL +chain has headroom on Amsterdam; older forks are unaffected because +only the requested gas changes, the actual consumption is identical. """ import pytest @@ -15,6 +25,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +42,20 @@ def test_callcodecallcall_100( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_callcodecallcall_100.""" + # EIP-8037 inner-CALL gas bumps (original gas values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state-gas + # spill into regular gas on Amsterdam). + inner_call_gas = 0x3D090 + middle_call_gas = 0x493E0 + outer_call_gas = 0x55730 + if fork.is_eip_enabled(8037): + inner_call_gas = 0xF4240 + middle_call_gas = 0x124F80 + outer_call_gas = 0x155CC0 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -65,7 +88,7 @@ def test_callcodecallcall_100( code=Op.SSTORE( key=0x2, value=Op.CALLCODE( - gas=0x3D090, + gas=inner_call_gas, address=addr_3, value=0x2, args_offset=0x0, @@ -84,7 +107,7 @@ def test_callcodecallcall_100( code=Op.SSTORE( key=0x1, value=Op.CALLCODE( - gas=0x493E0, + gas=middle_call_gas, address=addr_2, value=0x1, args_offset=0x0, @@ -104,7 +127,7 @@ def test_callcodecallcall_100( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=0x55730, + gas=outer_call_gas, address=addr, args_offset=0x0, args_size=0x40, diff --git a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcall_100_suicide_end.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcall_100_suicide_end.py index 51991cb8e36..84802cd5c8c 100644 --- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcall_100_suicide_end.py +++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcall_100_suicide_end.py @@ -3,6 +3,10 @@ Ported from: state_tests/stCallDelegateCodesCallCodeHomestead/callcodecallcall_100_SuicideEndFiller.json + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. + """ import pytest @@ -15,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +36,20 @@ def test_callcodecallcall_100_suicide_end( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_callcodecallcall_100_suicide_end.""" + # EIP-8037 inner-CALL gas bumps: original values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state- + # gas spill into regular gas on Amsterdam. + outer_call_gas = 150000 + middle_call_gas = 100000 + inner_call_gas = 50000 + if fork.is_eip_enabled(8037): + outer_call_gas = 1000000 + middle_call_gas = 800000 + inner_call_gas = 100000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -59,7 +76,7 @@ def test_callcodecallcall_100_suicide_end( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=0x249F0, + gas=outer_call_gas, address=0xEAF8C2AE0D01A880CEA4E1AA88DEF5EDD153D57B, args_offset=0x0, args_size=0x40, @@ -78,7 +95,7 @@ def test_callcodecallcall_100_suicide_end( code=Op.SSTORE( key=0x1, value=Op.CALLCODE( - gas=0x186A0, + gas=middle_call_gas, address=0x94C8F980AEECBB6575B12AE614A249FC3E836F21, value=0x0, args_offset=0x0, @@ -98,7 +115,7 @@ def test_callcodecallcall_100_suicide_end( code=Op.SSTORE( key=0x2, value=Op.CALLCODE( - gas=0xC350, + gas=inner_call_gas, address=0x73B954EBC05BB0FF4A0F6A13A054D50AD1584099, value=0x0, args_offset=0x0, diff --git a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcall_abcb_recursive.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcall_abcb_recursive.py index 1265bc7fe0b..22a4e4a6f2c 100644 --- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcall_abcb_recursive.py +++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcall_abcb_recursive.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,6 +32,7 @@ @pytest.mark.pre_alloc_mutable def test_callcodecallcall_abcb_recursive( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """DELEGATE -> CALLCODE1 -> CALLCODE2 -> CALLCODE1 -> .""" @@ -42,7 +45,6 @@ def test_callcodecallcall_abcb_recursive( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=3000000000, ) # Source: lll @@ -109,7 +111,7 @@ def test_callcodecallcall_abcb_recursive( sender=sender, to=target, data=Bytes(""), - gas_limit=600000, + gas_limit=2600000 if fork >= Amsterdam else 600000, ) post = { diff --git a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcallcode_101.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcallcode_101.py index 9104aa608e2..f85f6c39c21 100644 --- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcallcode_101.py +++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcallcode_101.py @@ -3,6 +3,16 @@ Ported from: state_tests/stCallDelegateCodesCallCodeHomestead/callcodecallcallcode_101Filler.json + + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values from the original filler (250k / 300k / 350k) were tuned to +the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the +innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei +of regular gas), and the inner CALL OoGs before the test's SSTORE +markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL +chain has headroom on Amsterdam; older forks are unaffected because +only the requested gas changes, the actual consumption is identical. """ import pytest @@ -15,6 +25,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +42,20 @@ def test_callcodecallcallcode_101( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_callcodecallcallcode_101.""" + # EIP-8037 inner-CALL gas bumps (original gas values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state-gas + # spill into regular gas on Amsterdam). + inner_call_gas = 0x3D090 + middle_call_gas = 0x493E0 + outer_call_gas = 0x55730 + if fork.is_eip_enabled(8037): + inner_call_gas = 0xF4240 + middle_call_gas = 0x124F80 + outer_call_gas = 0x155CC0 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -65,7 +88,7 @@ def test_callcodecallcallcode_101( code=Op.SSTORE( key=0x2, value=Op.DELEGATECALL( - gas=0x3D090, + gas=inner_call_gas, address=addr_3, args_offset=0x0, args_size=0x40, @@ -84,7 +107,7 @@ def test_callcodecallcallcode_101( code=Op.SSTORE( key=0x1, value=Op.CALLCODE( - gas=0x493E0, + gas=middle_call_gas, address=addr_2, value=0x1, args_offset=0x0, @@ -104,7 +127,7 @@ def test_callcodecallcallcode_101( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=0x55730, + gas=outer_call_gas, address=addr, args_offset=0x0, args_size=0x40, diff --git a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcallcode_101_suicide_end.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcallcode_101_suicide_end.py index c83fec414f8..4a20cbfa33f 100644 --- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcallcode_101_suicide_end.py +++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcallcode_101_suicide_end.py @@ -3,6 +3,10 @@ Ported from: state_tests/stCallDelegateCodesCallCodeHomestead/callcodecallcallcode_101_SuicideEndFiller.json + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. + """ import pytest @@ -15,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +36,20 @@ def test_callcodecallcallcode_101_suicide_end( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_callcodecallcallcode_101_suicide_end.""" + # EIP-8037 inner-CALL gas bumps: original values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state- + # gas spill into regular gas on Amsterdam. + outer_call_gas = 150000 + middle_call_gas = 100000 + inner_call_gas = 50000 + if fork.is_eip_enabled(8037): + outer_call_gas = 1000000 + middle_call_gas = 800000 + inner_call_gas = 100000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -59,7 +76,7 @@ def test_callcodecallcallcode_101_suicide_end( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=0x249F0, + gas=outer_call_gas, address=0xEAF8C2AE0D01A880CEA4E1AA88DEF5EDD153D57B, args_offset=0x0, args_size=0x40, @@ -78,7 +95,7 @@ def test_callcodecallcallcode_101_suicide_end( code=Op.SSTORE( key=0x1, value=Op.CALLCODE( - gas=0x186A0, + gas=middle_call_gas, address=0xAC521409E2FA9526BFE6B827805783D2E307C4CE, value=0x0, args_offset=0x0, @@ -98,7 +115,7 @@ def test_callcodecallcallcode_101_suicide_end( code=Op.SSTORE( key=0x2, value=Op.DELEGATECALL( - gas=0xC350, + gas=inner_call_gas, address=0x73B954EBC05BB0FF4A0F6A13A054D50AD1584099, args_offset=0x0, args_size=0x40, diff --git a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcallcode_abcb_recursive.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcallcode_abcb_recursive.py index 277bfc1c57d..6cbdf4c9c89 100644 --- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcallcode_abcb_recursive.py +++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcallcode_abcb_recursive.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,6 +32,7 @@ @pytest.mark.pre_alloc_mutable def test_callcodecallcallcode_abcb_recursive( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """DELEGATECALL -> CALLCODE -> DELEGATECALL2 -> CALLCODE ->...""" @@ -42,7 +45,6 @@ def test_callcodecallcallcode_abcb_recursive( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=3000000000, ) # Source: lll @@ -108,11 +110,11 @@ def test_callcodecallcallcode_abcb_recursive( sender=sender, to=target, data=Bytes(""), - gas_limit=600000, + gas_limit=2600000 if fork >= Amsterdam else 600000, ) post = { - target: Account(storage={0: 1, 1: 1}), + target: Account(storage={0: 1, 1: 1, 2: 0}), addr: Account(storage={1: 0, 2: 0}), addr_2: Account(storage={1: 0, 2: 0}), sender: Account(storage={1: 0}), diff --git a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcode_11.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcode_11.py index b9187af14a7..9e2c24e09e6 100644 --- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcode_11.py +++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcode_11.py @@ -3,6 +3,16 @@ Ported from: state_tests/stCallDelegateCodesCallCodeHomestead/callcodecallcode_11Filler.json + + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values from the original filler (250k / 300k / 350k) were tuned to +the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the +innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei +of regular gas), and the inner CALL OoGs before the test's SSTORE +markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL +chain has headroom on Amsterdam; older forks are unaffected because +only the requested gas changes, the actual consumption is identical. """ import pytest @@ -15,6 +25,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +42,18 @@ def test_callcodecallcode_11( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_callcodecallcode_11.""" + # EIP-8037 inner-CALL gas bumps (original gas values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state-gas + # spill into regular gas on Amsterdam). + inner_call_gas = 0x3D090 + outer_call_gas = 0x55730 + if fork.is_eip_enabled(8037): + inner_call_gas = 0xF4240 + outer_call_gas = 0x155CC0 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -65,7 +86,7 @@ def test_callcodecallcode_11( code=Op.SSTORE( key=0x1, value=Op.DELEGATECALL( - gas=0x3D090, + gas=inner_call_gas, address=addr_2, args_offset=0x0, args_size=0x40, @@ -82,7 +103,7 @@ def test_callcodecallcode_11( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=0x55730, + gas=outer_call_gas, address=addr, args_offset=0x0, args_size=0x40, diff --git a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcode_11_suicide_end.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcode_11_suicide_end.py index 98290b68642..b1adb4e9e21 100644 --- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcode_11_suicide_end.py +++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcode_11_suicide_end.py @@ -3,6 +3,10 @@ Ported from: state_tests/stCallDelegateCodesCallCodeHomestead/callcodecallcode_11_SuicideEndFiller.json + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. + """ import pytest @@ -15,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +36,18 @@ def test_callcodecallcode_11_suicide_end( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_callcodecallcode_11_suicide_end.""" + # EIP-8037 inner-CALL gas bumps: original values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state- + # gas spill into regular gas on Amsterdam. + outer_call_gas = 150000 + inner_call_gas = 50000 + if fork.is_eip_enabled(8037): + outer_call_gas = 1000000 + inner_call_gas = 100000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -59,7 +74,7 @@ def test_callcodecallcode_11_suicide_end( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=0x249F0, + gas=outer_call_gas, address=0x1CCA6E93108EC94304AE5EB121D323E6C317FE7A, args_offset=0x0, args_size=0x40, @@ -78,7 +93,7 @@ def test_callcodecallcode_11_suicide_end( code=Op.SSTORE( key=0x1, value=Op.DELEGATECALL( - gas=0xC350, + gas=inner_call_gas, address=0x703B936FD4D674F0FF5D6957F61097152F8781B8, args_offset=0x0, args_size=0x40, diff --git a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcodecall_110.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcodecall_110.py index 7329364f7f2..32061749e58 100644 --- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcodecall_110.py +++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcodecall_110.py @@ -3,6 +3,16 @@ Ported from: state_tests/stCallDelegateCodesCallCodeHomestead/callcodecallcodecall_110Filler.json + + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values from the original filler (250k / 300k / 350k) were tuned to +the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the +innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei +of regular gas), and the inner CALL OoGs before the test's SSTORE +markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL +chain has headroom on Amsterdam; older forks are unaffected because +only the requested gas changes, the actual consumption is identical. """ import pytest @@ -15,6 +25,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +42,20 @@ def test_callcodecallcodecall_110( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_callcodecallcodecall_110.""" + # EIP-8037 inner-CALL gas bumps (original gas values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state-gas + # spill into regular gas on Amsterdam). + inner_call_gas = 0x3D090 + middle_call_gas = 0x493E0 + outer_call_gas = 0x55730 + if fork.is_eip_enabled(8037): + inner_call_gas = 0xF4240 + middle_call_gas = 0x124F80 + outer_call_gas = 0x155CC0 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -65,7 +88,7 @@ def test_callcodecallcodecall_110( code=Op.SSTORE( key=0x2, value=Op.CALLCODE( - gas=0x3D090, + gas=inner_call_gas, address=addr_3, value=0x1, args_offset=0x0, @@ -85,7 +108,7 @@ def test_callcodecallcodecall_110( code=Op.SSTORE( key=0x1, value=Op.DELEGATECALL( - gas=0x493E0, + gas=middle_call_gas, address=addr_2, args_offset=0x0, args_size=0x40, @@ -104,7 +127,7 @@ def test_callcodecallcodecall_110( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=0x55730, + gas=outer_call_gas, address=addr, args_offset=0x0, args_size=0x40, diff --git a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcodecall_110_suicide_end.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcodecall_110_suicide_end.py index 39e4bd7fee8..2323c57ec3a 100644 --- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcodecall_110_suicide_end.py +++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcodecall_110_suicide_end.py @@ -3,6 +3,10 @@ Ported from: state_tests/stCallDelegateCodesCallCodeHomestead/callcodecallcodecall_110_SuicideEndFiller.json + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. + """ import pytest @@ -15,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +36,20 @@ def test_callcodecallcodecall_110_suicide_end( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_callcodecallcodecall_110_suicide_end.""" + # EIP-8037 inner-CALL gas bumps: original values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state- + # gas spill into regular gas on Amsterdam. + outer_call_gas = 150000 + middle_call_gas = 100000 + inner_call_gas = 50000 + if fork.is_eip_enabled(8037): + outer_call_gas = 1000000 + middle_call_gas = 800000 + inner_call_gas = 100000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -59,7 +76,7 @@ def test_callcodecallcodecall_110_suicide_end( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=0x249F0, + gas=outer_call_gas, address=0x2CAC1D43F00E8B40B63426AB460C7E8717EE6455, args_offset=0x0, args_size=0x40, @@ -78,7 +95,7 @@ def test_callcodecallcodecall_110_suicide_end( code=Op.SSTORE( key=0x1, value=Op.DELEGATECALL( - gas=0x186A0, + gas=middle_call_gas, address=0x94C8F980AEECBB6575B12AE614A249FC3E836F21, args_offset=0x0, args_size=0x40, @@ -97,7 +114,7 @@ def test_callcodecallcodecall_110_suicide_end( code=Op.SSTORE( key=0x2, value=Op.CALLCODE( - gas=0xC350, + gas=inner_call_gas, address=0x73B954EBC05BB0FF4A0F6A13A054D50AD1584099, value=0x0, args_offset=0x0, diff --git a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcodecall_abcb_recursive.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcodecall_abcb_recursive.py index b671556b16a..5c95b370b78 100644 --- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcodecall_abcb_recursive.py +++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcodecall_abcb_recursive.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,6 +32,7 @@ @pytest.mark.pre_alloc_mutable def test_callcodecallcodecall_abcb_recursive( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """DELEGATECALL -> DELEGATECALL2 -> CALLCODE -> DELEGATECALL2 -> .""" @@ -42,7 +45,6 @@ def test_callcodecallcodecall_abcb_recursive( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=3000000000, ) # Source: lll @@ -108,11 +110,11 @@ def test_callcodecallcodecall_abcb_recursive( sender=sender, to=target, data=Bytes(""), - gas_limit=600000, + gas_limit=2600000 if fork >= Amsterdam else 600000, ) post = { - target: Account(storage={0: 1, 1: 1}), + target: Account(storage={0: 1, 1: 1, 2: 0}), addr: Account(storage={1: 0, 2: 0}), addr_2: Account(storage={1: 0, 2: 0}), sender: Account(storage={1: 0}), diff --git a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcodecallcode_111.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcodecallcode_111.py index 4c12782ba77..284d284adeb 100644 --- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcodecallcode_111.py +++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcodecallcode_111.py @@ -3,6 +3,16 @@ Ported from: state_tests/stCallDelegateCodesCallCodeHomestead/callcodecallcodecallcode_111Filler.json + + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values from the original filler (250k / 300k / 350k) were tuned to +the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the +innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei +of regular gas), and the inner CALL OoGs before the test's SSTORE +markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL +chain has headroom on Amsterdam; older forks are unaffected because +only the requested gas changes, the actual consumption is identical. """ import pytest @@ -15,6 +25,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +42,20 @@ def test_callcodecallcodecallcode_111( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_callcodecallcodecallcode_111.""" + # EIP-8037 inner-CALL gas bumps (original gas values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state-gas + # spill into regular gas on Amsterdam). + inner_call_gas = 0x3D090 + middle_call_gas = 0x493E0 + outer_call_gas = 0x55730 + if fork.is_eip_enabled(8037): + inner_call_gas = 0xF4240 + middle_call_gas = 0x124F80 + outer_call_gas = 0x155CC0 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -65,7 +88,7 @@ def test_callcodecallcodecallcode_111( code=Op.SSTORE( key=0x2, value=Op.DELEGATECALL( - gas=0x3D090, + gas=inner_call_gas, address=addr_3, args_offset=0x0, args_size=0x40, @@ -82,7 +105,7 @@ def test_callcodecallcodecallcode_111( code=Op.SSTORE( key=0x1, value=Op.DELEGATECALL( - gas=0x493E0, + gas=middle_call_gas, address=addr_2, args_offset=0x0, args_size=0x40, @@ -99,7 +122,7 @@ def test_callcodecallcodecallcode_111( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=0x55730, + gas=outer_call_gas, address=addr, args_offset=0x0, args_size=0x40, diff --git a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcodecallcode_111_suicide_end.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcodecallcode_111_suicide_end.py index de51b0fb329..d47d3c95ed5 100644 --- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcodecallcode_111_suicide_end.py +++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcodecallcode_111_suicide_end.py @@ -3,6 +3,16 @@ Ported from: state_tests/stCallDelegateCodesCallCodeHomestead/callcodecallcodecallcode_111_SuicideEndFiller.json + + +@manually-enhanced: Do not overwrite. Hardcoded inner-CALL gas values +from the original filler (100k / 800k / 150k / 50k) were tuned to the +pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the inner +callee adds the EIP-8037 per-storage state-gas (37 568 wei of +regular gas), and the inner CALL OoGs before the test's SSTORE +markers fire. Bumped uniformly with extra headroom; older forks are +unaffected because only the requested gas changes, the actual +consumption is identical. """ import pytest @@ -15,6 +25,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +42,20 @@ def test_callcodecallcodecallcode_111_suicide_end( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_callcodecallcodecallcode_111_suicide_end.""" + # EIP-8037 inner-CALL gas bumps (original gas values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state-gas + # spill into regular gas on Amsterdam). + inner_call_gas = 0x186A0 + middle_call_gas = 0x249F0 + inner_call_gas_b = 0xC350 + if fork.is_eip_enabled(8037): + inner_call_gas = 0x1E8480 + middle_call_gas = 0x1E8480 + inner_call_gas_b = 0x1E8480 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -59,7 +82,7 @@ def test_callcodecallcodecallcode_111_suicide_end( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=0x249F0, + gas=middle_call_gas, address=0x9CFF7A3C9C90A301C47982DC2C4399C93700F0FD, args_offset=0x0, args_size=0x40, @@ -78,7 +101,7 @@ def test_callcodecallcodecallcode_111_suicide_end( code=Op.SSTORE( key=0x1, value=Op.CALL( - gas=0x186A0, + gas=inner_call_gas, address=0xB207980945728D64A3C9F905932314C8F130EE38, value=0x1, args_offset=0x0, @@ -98,7 +121,7 @@ def test_callcodecallcodecallcode_111_suicide_end( code=Op.SSTORE( key=0x2, value=Op.CALLCODE( - gas=0xC350, + gas=inner_call_gas_b, address=0x73B954EBC05BB0FF4A0F6A13A054D50AD1584099, value=0x2, args_offset=0x0, diff --git a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcodecallcode_abcb_recursive.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcodecallcode_abcb_recursive.py index 57136f08e9f..ec60e53ca83 100644 --- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcodecallcode_abcb_recursive.py +++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcodecallcode_abcb_recursive.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,6 +32,7 @@ @pytest.mark.pre_alloc_mutable def test_callcodecallcodecallcode_abcb_recursive( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """DELEGATECALL -> DELEGATECALL1 -> DELEGATECALL2 -> DELEGATECAL1 -> .""" @@ -42,7 +45,6 @@ def test_callcodecallcodecallcode_abcb_recursive( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=3000000000, ) # Source: lll @@ -107,11 +109,11 @@ def test_callcodecallcodecallcode_abcb_recursive( sender=sender, to=target, data=Bytes(""), - gas_limit=600000, + gas_limit=2600000 if fork >= Amsterdam else 600000, ) post = { - target: Account(storage={0: 1, 1: 1}), + target: Account(storage={0: 1, 1: 1, 2: 0}), addr: Account(storage={1: 0, 2: 0}), addr_2: Account(storage={1: 0, 2: 0}), sender: Account(storage={1: 0}), diff --git a/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcallcode_001.py b/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcallcode_001.py index 521fce6999d..c8481280159 100644 --- a/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcallcode_001.py +++ b/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcallcode_001.py @@ -3,6 +3,16 @@ Ported from: state_tests/stCallDelegateCodesHomestead/callcallcallcode_001Filler.json + + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values from the original filler (250k / 300k / 350k) were tuned to +the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the +innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei +of regular gas), and the inner CALL OoGs before the test's SSTORE +markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL +chain has headroom on Amsterdam; older forks are unaffected because +only the requested gas changes, the actual consumption is identical. """ import pytest @@ -15,6 +25,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +42,20 @@ def test_callcallcallcode_001( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_callcallcallcode_001.""" + # EIP-8037 inner-CALL gas bumps (original gas values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state-gas + # spill into regular gas on Amsterdam). + inner_call_gas = 0x3D090 + middle_call_gas = 0x493E0 + outer_call_gas = 0x55730 + if fork.is_eip_enabled(8037): + inner_call_gas = 0xF4240 + middle_call_gas = 0x124F80 + outer_call_gas = 0x155CC0 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -65,7 +88,7 @@ def test_callcallcallcode_001( code=Op.SSTORE( key=0x2, value=Op.DELEGATECALL( - gas=0x3D090, + gas=inner_call_gas, address=addr_3, args_offset=0x0, args_size=0x40, @@ -83,7 +106,7 @@ def test_callcallcallcode_001( code=Op.SSTORE( key=0x1, value=Op.CALL( - gas=0x493E0, + gas=middle_call_gas, address=addr_2, value=0x2, args_offset=0x0, @@ -102,7 +125,7 @@ def test_callcallcallcode_001( code=Op.SSTORE( key=0x0, value=Op.CALL( - gas=0x55730, + gas=outer_call_gas, address=addr, value=0x1, args_offset=0x0, diff --git a/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcallcode_001_suicide_end.py b/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcallcode_001_suicide_end.py index 9d145663f00..bf50302cc14 100644 --- a/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcallcode_001_suicide_end.py +++ b/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcallcode_001_suicide_end.py @@ -3,6 +3,10 @@ Ported from: state_tests/stCallDelegateCodesHomestead/callcallcallcode_001_SuicideEndFiller.json + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. + """ import pytest @@ -15,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +36,20 @@ def test_callcallcallcode_001_suicide_end( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_callcallcallcode_001_suicide_end.""" + # EIP-8037 inner-CALL gas bumps: original values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state- + # gas spill into regular gas on Amsterdam. + outer_call_gas = 150000 + middle_call_gas = 100000 + inner_call_gas = 50000 + if fork.is_eip_enabled(8037): + outer_call_gas = 1000000 + middle_call_gas = 800000 + inner_call_gas = 100000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -59,7 +76,7 @@ def test_callcallcallcode_001_suicide_end( code=Op.SSTORE( key=0x0, value=Op.CALL( - gas=0x249F0, + gas=outer_call_gas, address=0x77B749FFFF7EC61D31C79ED104F230A7959B2879, value=0x0, args_offset=0x0, @@ -79,7 +96,7 @@ def test_callcallcallcode_001_suicide_end( code=Op.SSTORE( key=0x1, value=Op.CALL( - gas=0x186A0, + gas=middle_call_gas, address=0xAC521409E2FA9526BFE6B827805783D2E307C4CE, value=0x0, args_offset=0x0, @@ -99,7 +116,7 @@ def test_callcallcallcode_001_suicide_end( code=Op.SSTORE( key=0x2, value=Op.DELEGATECALL( - gas=0xC350, + gas=inner_call_gas, address=0x73B954EBC05BB0FF4A0F6A13A054D50AD1584099, args_offset=0x0, args_size=0x40, diff --git a/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcallcode_abcb_recursive.py b/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcallcode_abcb_recursive.py index 6800472a043..c634526f5a7 100644 --- a/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcallcode_abcb_recursive.py +++ b/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcallcode_abcb_recursive.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,6 +32,7 @@ @pytest.mark.pre_alloc_mutable def test_callcallcallcode_abcb_recursive( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """CALL -> CALL2 -> DELEGATECALL -> CALL2 -> .""" @@ -42,7 +45,6 @@ def test_callcallcallcode_abcb_recursive( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=3000000000, ) # Source: lll @@ -109,13 +111,18 @@ def test_callcallcallcode_abcb_recursive( sender=sender, to=target, data=Bytes(""), - gas_limit=600000, + gas_limit=2600000 if fork >= Amsterdam else 600000, ) post = { target: Account(storage={0: 1, 1: 0}), addr: Account(storage={1: 1, 2: 0}), - addr_2: Account(storage={1: 0, 2: 0}), + addr_2: Account( + storage={ + 1: 0, + 2: 0, + } + ), } state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcode_01.py b/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcode_01.py index b507415274d..539d0d35cdf 100644 --- a/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcode_01.py +++ b/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcode_01.py @@ -3,6 +3,16 @@ Ported from: state_tests/stCallDelegateCodesHomestead/callcallcode_01Filler.json + + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values from the original filler (250k / 300k / 350k) were tuned to +the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the +innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei +of regular gas), and the inner CALL OoGs before the test's SSTORE +markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL +chain has headroom on Amsterdam; older forks are unaffected because +only the requested gas changes, the actual consumption is identical. """ import pytest @@ -15,6 +25,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +40,18 @@ def test_callcallcode_01( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_callcallcode_01.""" + # EIP-8037 inner-CALL gas bumps (original gas values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state-gas + # spill into regular gas on Amsterdam). + inner_call_gas = 0x3D090 + outer_call_gas = 0x55730 + if fork.is_eip_enabled(8037): + inner_call_gas = 0xF4240 + outer_call_gas = 0x155CC0 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -63,7 +84,7 @@ def test_callcallcode_01( code=Op.SSTORE( key=0x1, value=Op.DELEGATECALL( - gas=0x3D090, + gas=inner_call_gas, address=addr_2, args_offset=0x0, args_size=0x40, @@ -80,7 +101,7 @@ def test_callcallcode_01( code=Op.SSTORE( key=0x0, value=Op.CALL( - gas=0x55730, + gas=outer_call_gas, address=addr, value=0x1, args_offset=0x0, diff --git a/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcode_01_suicide_end.py b/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcode_01_suicide_end.py index 9ce57b4ea26..4afdbbe5775 100644 --- a/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcode_01_suicide_end.py +++ b/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcode_01_suicide_end.py @@ -3,6 +3,10 @@ Ported from: state_tests/stCallDelegateCodesHomestead/callcallcode_01_SuicideEndFiller.json + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. + """ import pytest @@ -15,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +36,18 @@ def test_callcallcode_01_suicide_end( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_callcallcode_01_suicide_end.""" + # EIP-8037 inner-CALL gas bumps: original values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state- + # gas spill into regular gas on Amsterdam. + outer_call_gas = 150000 + inner_call_gas = 50000 + if fork.is_eip_enabled(8037): + outer_call_gas = 1000000 + inner_call_gas = 100000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -59,7 +74,7 @@ def test_callcallcode_01_suicide_end( code=Op.SSTORE( key=0x0, value=Op.CALL( - gas=0x249F0, + gas=outer_call_gas, address=0x1CCA6E93108EC94304AE5EB121D323E6C317FE7A, value=0x0, args_offset=0x0, @@ -79,7 +94,7 @@ def test_callcallcode_01_suicide_end( code=Op.SSTORE( key=0x1, value=Op.DELEGATECALL( - gas=0xC350, + gas=inner_call_gas, address=0x703B936FD4D674F0FF5D6957F61097152F8781B8, args_offset=0x0, args_size=0x40, diff --git a/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcodecall_010.py b/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcodecall_010.py index 2887e55659b..e62a62820a2 100644 --- a/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcodecall_010.py +++ b/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcodecall_010.py @@ -3,6 +3,16 @@ Ported from: state_tests/stCallDelegateCodesHomestead/callcallcodecall_010Filler.json + + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values from the original filler (250k / 300k / 350k) were tuned to +the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the +innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei +of regular gas), and the inner CALL OoGs before the test's SSTORE +markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL +chain has headroom on Amsterdam; older forks are unaffected because +only the requested gas changes, the actual consumption is identical. """ import pytest @@ -15,6 +25,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +42,20 @@ def test_callcallcodecall_010( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_callcallcodecall_010.""" + # EIP-8037 inner-CALL gas bumps (original gas values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state-gas + # spill into regular gas on Amsterdam). + inner_call_gas = 0x3D090 + middle_call_gas = 0x493E0 + outer_call_gas = 0x55730 + if fork.is_eip_enabled(8037): + inner_call_gas = 0xF4240 + middle_call_gas = 0x124F80 + outer_call_gas = 0x155CC0 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -65,7 +88,7 @@ def test_callcallcodecall_010( code=Op.SSTORE( key=0x2, value=Op.CALL( - gas=0x3D090, + gas=inner_call_gas, address=addr_3, value=0x2, args_offset=0x0, @@ -85,7 +108,7 @@ def test_callcallcodecall_010( code=Op.SSTORE( key=0x1, value=Op.DELEGATECALL( - gas=0x493E0, + gas=middle_call_gas, address=addr_2, args_offset=0x0, args_size=0x40, @@ -103,7 +126,7 @@ def test_callcallcodecall_010( code=Op.SSTORE( key=0x0, value=Op.CALL( - gas=0x55730, + gas=outer_call_gas, address=addr, value=0x1, args_offset=0x0, diff --git a/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcodecall_010_suicide_end.py b/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcodecall_010_suicide_end.py index 3e51d28ac20..32f1487a627 100644 --- a/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcodecall_010_suicide_end.py +++ b/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcodecall_010_suicide_end.py @@ -3,6 +3,10 @@ Ported from: state_tests/stCallDelegateCodesHomestead/callcallcodecall_010_SuicideEndFiller.json + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. + """ import pytest @@ -15,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +36,20 @@ def test_callcallcodecall_010_suicide_end( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_callcallcodecall_010_suicide_end.""" + # EIP-8037 inner-CALL gas bumps: original values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state- + # gas spill into regular gas on Amsterdam. + outer_call_gas = 150000 + middle_call_gas = 100000 + inner_call_gas = 50000 + if fork.is_eip_enabled(8037): + outer_call_gas = 1000000 + middle_call_gas = 800000 + inner_call_gas = 100000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -59,7 +76,7 @@ def test_callcallcodecall_010_suicide_end( code=Op.SSTORE( key=0x0, value=Op.CALL( - gas=0x249F0, + gas=outer_call_gas, address=0x2CAC1D43F00E8B40B63426AB460C7E8717EE6455, value=0x0, args_offset=0x0, @@ -79,7 +96,7 @@ def test_callcallcodecall_010_suicide_end( code=Op.SSTORE( key=0x1, value=Op.DELEGATECALL( - gas=0x186A0, + gas=middle_call_gas, address=0xD957E143AD2C011BC6A2B142795F1A9BA70D0680, args_offset=0x0, args_size=0x40, @@ -98,7 +115,7 @@ def test_callcallcodecall_010_suicide_end( code=Op.SSTORE( key=0x2, value=Op.CALL( - gas=0xC350, + gas=inner_call_gas, address=0x73B954EBC05BB0FF4A0F6A13A054D50AD1584099, value=0x0, args_offset=0x0, diff --git a/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcodecall_abcb_recursive.py b/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcodecall_abcb_recursive.py index bd68d956080..e545846e05c 100644 --- a/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcodecall_abcb_recursive.py +++ b/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcodecall_abcb_recursive.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,6 +32,7 @@ @pytest.mark.pre_alloc_mutable def test_callcallcodecall_abcb_recursive( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """CALL -> DELEGATECALL -> CALL2 -> DELEGATECALL -> .""" @@ -42,7 +45,6 @@ def test_callcallcodecall_abcb_recursive( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=3000000000, ) # Source: lll @@ -109,7 +111,7 @@ def test_callcallcodecall_abcb_recursive( sender=sender, to=target, data=Bytes(""), - gas_limit=600000, + gas_limit=2600000 if fork >= Amsterdam else 600000, ) post = { diff --git a/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcodecallcode_011.py b/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcodecallcode_011.py index 2230e5e1316..d41418e40d3 100644 --- a/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcodecallcode_011.py +++ b/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcodecallcode_011.py @@ -3,6 +3,16 @@ Ported from: state_tests/stCallDelegateCodesHomestead/callcallcodecallcode_011Filler.json + + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values from the original filler (250k / 300k / 350k) were tuned to +the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the +innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei +of regular gas), and the inner CALL OoGs before the test's SSTORE +markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL +chain has headroom on Amsterdam; older forks are unaffected because +only the requested gas changes, the actual consumption is identical. """ import pytest @@ -15,6 +25,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +42,20 @@ def test_callcallcodecallcode_011( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_callcallcodecallcode_011.""" + # EIP-8037 inner-CALL gas bumps (original gas values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state-gas + # spill into regular gas on Amsterdam). + inner_call_gas = 0x3D090 + middle_call_gas = 0x493E0 + outer_call_gas = 0x55730 + if fork.is_eip_enabled(8037): + inner_call_gas = 0xF4240 + middle_call_gas = 0x124F80 + outer_call_gas = 0x155CC0 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -65,7 +88,7 @@ def test_callcallcodecallcode_011( code=Op.SSTORE( key=0x2, value=Op.DELEGATECALL( - gas=0x3D090, + gas=inner_call_gas, address=addr_3, args_offset=0x0, args_size=0x40, @@ -83,7 +106,7 @@ def test_callcallcodecallcode_011( code=Op.SSTORE( key=0x1, value=Op.DELEGATECALL( - gas=0x493E0, + gas=middle_call_gas, address=addr_2, args_offset=0x0, args_size=0x40, @@ -101,7 +124,7 @@ def test_callcallcodecallcode_011( code=Op.SSTORE( key=0x0, value=Op.CALL( - gas=0x55730, + gas=outer_call_gas, address=addr, value=0x1, args_offset=0x0, diff --git a/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcodecallcode_011_suicide_end.py b/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcodecallcode_011_suicide_end.py index 2f8cd246a17..a0b5607e396 100644 --- a/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcodecallcode_011_suicide_end.py +++ b/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcodecallcode_011_suicide_end.py @@ -3,6 +3,10 @@ Ported from: state_tests/stCallDelegateCodesHomestead/callcallcodecallcode_011_SuicideEndFiller.json + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. + """ import pytest @@ -15,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +36,20 @@ def test_callcallcodecallcode_011_suicide_end( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_callcallcodecallcode_011_suicide_end.""" + # EIP-8037 inner-CALL gas bumps: original values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state- + # gas spill into regular gas on Amsterdam. + outer_call_gas = 150000 + middle_call_gas = 100000 + inner_call_gas = 50000 + if fork.is_eip_enabled(8037): + outer_call_gas = 1000000 + middle_call_gas = 800000 + inner_call_gas = 100000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -59,7 +76,7 @@ def test_callcallcodecallcode_011_suicide_end( code=Op.SSTORE( key=0x0, value=Op.CALL( - gas=0x249F0, + gas=outer_call_gas, address=0x2CAC1D43F00E8B40B63426AB460C7E8717EE6455, value=0x0, args_offset=0x0, @@ -79,7 +96,7 @@ def test_callcallcodecallcode_011_suicide_end( code=Op.SSTORE( key=0x1, value=Op.DELEGATECALL( - gas=0x186A0, + gas=middle_call_gas, address=0xAC521409E2FA9526BFE6B827805783D2E307C4CE, args_offset=0x0, args_size=0x40, @@ -98,7 +115,7 @@ def test_callcallcodecallcode_011_suicide_end( code=Op.SSTORE( key=0x2, value=Op.DELEGATECALL( - gas=0xC350, + gas=inner_call_gas, address=0x73B954EBC05BB0FF4A0F6A13A054D50AD1584099, args_offset=0x0, args_size=0x40, diff --git a/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcodecallcode_abcb_recursive.py b/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcodecallcode_abcb_recursive.py index f50349c2e61..35811014a4c 100644 --- a/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcodecallcode_abcb_recursive.py +++ b/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcodecallcode_abcb_recursive.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,6 +32,7 @@ @pytest.mark.pre_alloc_mutable def test_callcallcodecallcode_abcb_recursive( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_callcallcodecallcode_abcb_recursive.""" @@ -42,7 +45,6 @@ def test_callcallcodecallcode_abcb_recursive( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=3000000000, ) # Source: lll @@ -108,7 +110,7 @@ def test_callcallcodecallcode_abcb_recursive( sender=sender, to=target, data=Bytes(""), - gas_limit=600000, + gas_limit=2600000 if fork >= Amsterdam else 600000, ) post = { diff --git a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecall_10.py b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecall_10.py index b689ffd226d..944c9b6ca4d 100644 --- a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecall_10.py +++ b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecall_10.py @@ -3,6 +3,16 @@ Ported from: state_tests/stCallDelegateCodesHomestead/callcodecall_10Filler.json + + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values from the original filler (250k / 300k / 350k) were tuned to +the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the +innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei +of regular gas), and the inner CALL OoGs before the test's SSTORE +markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL +chain has headroom on Amsterdam; older forks are unaffected because +only the requested gas changes, the actual consumption is identical. """ import pytest @@ -15,6 +25,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +40,18 @@ def test_callcodecall_10( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_callcodecall_10.""" + # EIP-8037 inner-CALL gas bumps (original gas values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state-gas + # spill into regular gas on Amsterdam). + inner_call_gas = 0x3D090 + outer_call_gas = 0x55730 + if fork.is_eip_enabled(8037): + inner_call_gas = 0xF4240 + outer_call_gas = 0x155CC0 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -63,7 +84,7 @@ def test_callcodecall_10( code=Op.SSTORE( key=0x1, value=Op.CALL( - gas=0x3D090, + gas=inner_call_gas, address=addr_2, value=0x1, args_offset=0x0, @@ -82,7 +103,7 @@ def test_callcodecall_10( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=0x55730, + gas=outer_call_gas, address=addr, args_offset=0x0, args_size=0x40, diff --git a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecall_10_suicide_end.py b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecall_10_suicide_end.py index 10a12ffbe2a..d6f6e7d5a6f 100644 --- a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecall_10_suicide_end.py +++ b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecall_10_suicide_end.py @@ -3,6 +3,10 @@ Ported from: state_tests/stCallDelegateCodesHomestead/callcodecall_10_SuicideEndFiller.json + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. + """ import pytest @@ -15,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +36,18 @@ def test_callcodecall_10_suicide_end( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_callcodecall_10_suicide_end.""" + # EIP-8037 inner-CALL gas bumps: original values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state- + # gas spill into regular gas on Amsterdam. + outer_call_gas = 150000 + inner_call_gas = 50000 + if fork.is_eip_enabled(8037): + outer_call_gas = 1000000 + inner_call_gas = 100000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -59,7 +74,7 @@ def test_callcodecall_10_suicide_end( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=0x249F0, + gas=outer_call_gas, address=0xF741CFEE7B7FB1025DCCEF3DB5A3CBC8FFB776F8, args_offset=0x0, args_size=0x40, @@ -78,7 +93,7 @@ def test_callcodecall_10_suicide_end( code=Op.SSTORE( key=0x1, value=Op.CALL( - gas=0xC350, + gas=inner_call_gas, address=0x703B936FD4D674F0FF5D6957F61097152F8781B8, value=0x0, args_offset=0x0, diff --git a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcall_100.py b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcall_100.py index ce2dc3a924e..f5e7137363c 100644 --- a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcall_100.py +++ b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcall_100.py @@ -3,6 +3,16 @@ Ported from: state_tests/stCallDelegateCodesHomestead/callcodecallcall_100Filler.json + + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values from the original filler (250k / 300k / 350k) were tuned to +the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the +innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei +of regular gas), and the inner CALL OoGs before the test's SSTORE +markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL +chain has headroom on Amsterdam; older forks are unaffected because +only the requested gas changes, the actual consumption is identical. """ import pytest @@ -15,6 +25,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +42,20 @@ def test_callcodecallcall_100( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_callcodecallcall_100.""" + # EIP-8037 inner-CALL gas bumps (original gas values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state-gas + # spill into regular gas on Amsterdam). + inner_call_gas = 0x3D090 + middle_call_gas = 0x493E0 + outer_call_gas = 0x55730 + if fork.is_eip_enabled(8037): + inner_call_gas = 0xF4240 + middle_call_gas = 0x124F80 + outer_call_gas = 0x155CC0 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -65,7 +88,7 @@ def test_callcodecallcall_100( code=Op.SSTORE( key=0x2, value=Op.CALL( - gas=0x3D090, + gas=inner_call_gas, address=addr_3, value=0x2, args_offset=0x0, @@ -84,7 +107,7 @@ def test_callcodecallcall_100( code=Op.SSTORE( key=0x1, value=Op.CALL( - gas=0x493E0, + gas=middle_call_gas, address=addr_2, value=0x1, args_offset=0x0, @@ -104,7 +127,7 @@ def test_callcodecallcall_100( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=0x55730, + gas=outer_call_gas, address=addr, args_offset=0x0, args_size=0x40, diff --git a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcall_100_suicide_end.py b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcall_100_suicide_end.py index 37c468d6f04..49da4be2412 100644 --- a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcall_100_suicide_end.py +++ b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcall_100_suicide_end.py @@ -3,6 +3,10 @@ Ported from: state_tests/stCallDelegateCodesHomestead/callcodecallcall_100_SuicideEndFiller.json + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. + """ import pytest @@ -15,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +36,20 @@ def test_callcodecallcall_100_suicide_end( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_callcodecallcall_100_suicide_end.""" + # EIP-8037 inner-CALL gas bumps: original values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state- + # gas spill into regular gas on Amsterdam. + outer_call_gas = 150000 + middle_call_gas = 100000 + inner_call_gas = 50000 + if fork.is_eip_enabled(8037): + outer_call_gas = 1000000 + middle_call_gas = 800000 + inner_call_gas = 100000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -59,7 +76,7 @@ def test_callcodecallcall_100_suicide_end( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=0x249F0, + gas=outer_call_gas, address=0x77B749FFFF7EC61D31C79ED104F230A7959B2879, args_offset=0x0, args_size=0x40, @@ -78,7 +95,7 @@ def test_callcodecallcall_100_suicide_end( code=Op.SSTORE( key=0x1, value=Op.CALL( - gas=0x186A0, + gas=middle_call_gas, address=0xD957E143AD2C011BC6A2B142795F1A9BA70D0680, value=0x0, args_offset=0x0, @@ -98,7 +115,7 @@ def test_callcodecallcall_100_suicide_end( code=Op.SSTORE( key=0x2, value=Op.CALL( - gas=0xC350, + gas=inner_call_gas, address=0x73B954EBC05BB0FF4A0F6A13A054D50AD1584099, value=0x0, args_offset=0x0, diff --git a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcall_abcb_recursive.py b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcall_abcb_recursive.py index c9b8a4c7e21..5cc414f52a8 100644 --- a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcall_abcb_recursive.py +++ b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcall_abcb_recursive.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,6 +32,7 @@ @pytest.mark.pre_alloc_mutable def test_callcodecallcall_abcb_recursive( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """DELEGATECALL -> CALL1 -> CALL2 -> CALL1 -> .""" @@ -42,7 +45,6 @@ def test_callcodecallcall_abcb_recursive( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=3000000000, ) # Source: lll @@ -109,7 +111,7 @@ def test_callcodecallcall_abcb_recursive( sender=sender, to=target, data=Bytes(""), - gas_limit=600000, + gas_limit=2600000 if fork >= Amsterdam else 600000, ) post = { diff --git a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcallcode_101.py b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcallcode_101.py index 626025ac0f5..3bac074bbdc 100644 --- a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcallcode_101.py +++ b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcallcode_101.py @@ -3,6 +3,16 @@ Ported from: state_tests/stCallDelegateCodesHomestead/callcodecallcallcode_101Filler.json + + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values from the original filler (250k / 300k / 350k) were tuned to +the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the +innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei +of regular gas), and the inner CALL OoGs before the test's SSTORE +markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL +chain has headroom on Amsterdam; older forks are unaffected because +only the requested gas changes, the actual consumption is identical. """ import pytest @@ -15,6 +25,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +42,20 @@ def test_callcodecallcallcode_101( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_callcodecallcallcode_101.""" + # EIP-8037 inner-CALL gas bumps (original gas values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state-gas + # spill into regular gas on Amsterdam). + inner_call_gas = 0x3D090 + middle_call_gas = 0x493E0 + outer_call_gas = 0x55730 + if fork.is_eip_enabled(8037): + inner_call_gas = 0xF4240 + middle_call_gas = 0x124F80 + outer_call_gas = 0x155CC0 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -65,7 +88,7 @@ def test_callcodecallcallcode_101( code=Op.SSTORE( key=0x2, value=Op.DELEGATECALL( - gas=0x3D090, + gas=inner_call_gas, address=addr_3, args_offset=0x0, args_size=0x40, @@ -84,7 +107,7 @@ def test_callcodecallcallcode_101( code=Op.SSTORE( key=0x1, value=Op.CALL( - gas=0x493E0, + gas=middle_call_gas, address=addr_2, value=0x1, args_offset=0x0, @@ -104,7 +127,7 @@ def test_callcodecallcallcode_101( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=0x55730, + gas=outer_call_gas, address=addr, args_offset=0x0, args_size=0x40, diff --git a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcallcode_101_suicide_end.py b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcallcode_101_suicide_end.py index af4852ff563..96f3337c150 100644 --- a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcallcode_101_suicide_end.py +++ b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcallcode_101_suicide_end.py @@ -3,6 +3,10 @@ Ported from: state_tests/stCallDelegateCodesHomestead/callcodecallcallcode_101_SuicideEndFiller.json + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. + """ import pytest @@ -15,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +36,20 @@ def test_callcodecallcallcode_101_suicide_end( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_callcodecallcallcode_101_suicide_end.""" + # EIP-8037 inner-CALL gas bumps: original values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state- + # gas spill into regular gas on Amsterdam. + outer_call_gas = 150000 + middle_call_gas = 100000 + inner_call_gas = 50000 + if fork.is_eip_enabled(8037): + outer_call_gas = 1000000 + middle_call_gas = 800000 + inner_call_gas = 100000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -59,7 +76,7 @@ def test_callcodecallcallcode_101_suicide_end( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=0x249F0, + gas=outer_call_gas, address=0x77B749FFFF7EC61D31C79ED104F230A7959B2879, args_offset=0x0, args_size=0x40, @@ -78,7 +95,7 @@ def test_callcodecallcallcode_101_suicide_end( code=Op.SSTORE( key=0x1, value=Op.CALL( - gas=0x186A0, + gas=middle_call_gas, address=0xAC521409E2FA9526BFE6B827805783D2E307C4CE, value=0x0, args_offset=0x0, @@ -98,7 +115,7 @@ def test_callcodecallcallcode_101_suicide_end( code=Op.SSTORE( key=0x2, value=Op.DELEGATECALL( - gas=0xC350, + gas=inner_call_gas, address=0x73B954EBC05BB0FF4A0F6A13A054D50AD1584099, args_offset=0x0, args_size=0x40, diff --git a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcallcode_abcb_recursive.py b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcallcode_abcb_recursive.py index 152c6ca4597..549ca221d60 100644 --- a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcallcode_abcb_recursive.py +++ b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcallcode_abcb_recursive.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,6 +32,7 @@ @pytest.mark.pre_alloc_mutable def test_callcodecallcallcode_abcb_recursive( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """DELEGATECALL -> CALL -> DELEGATECALL2 -> CALL -> .""" @@ -42,7 +45,6 @@ def test_callcodecallcallcode_abcb_recursive( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=3000000000, ) # Source: lll @@ -108,13 +110,18 @@ def test_callcodecallcallcode_abcb_recursive( sender=sender, to=target, data=Bytes(""), - gas_limit=600000, + gas_limit=2600000 if fork >= Amsterdam else 600000, ) post = { target: Account(storage={0: 1, 1: 1}), addr: Account(storage={1: 0, 2: 0}), - addr_2: Account(storage={1: 0, 2: 0}), + addr_2: Account( + storage={ + 1: 0, + 2: 0, + } + ), sender: Account(storage={1: 0}), } diff --git a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcode_11.py b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcode_11.py index bc8e57b4105..055d2ccfe03 100644 --- a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcode_11.py +++ b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcode_11.py @@ -3,6 +3,16 @@ Ported from: state_tests/stCallDelegateCodesHomestead/callcodecallcode_11Filler.json + + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values from the original filler (250k / 300k / 350k) were tuned to +the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the +innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei +of regular gas), and the inner CALL OoGs before the test's SSTORE +markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL +chain has headroom on Amsterdam; older forks are unaffected because +only the requested gas changes, the actual consumption is identical. """ import pytest @@ -15,6 +25,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +42,18 @@ def test_callcodecallcode_11( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_callcodecallcode_11.""" + # EIP-8037 inner-CALL gas bumps (original gas values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state-gas + # spill into regular gas on Amsterdam). + inner_call_gas = 0x3D090 + outer_call_gas = 0x55730 + if fork.is_eip_enabled(8037): + inner_call_gas = 0xF4240 + outer_call_gas = 0x155CC0 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -65,7 +86,7 @@ def test_callcodecallcode_11( code=Op.SSTORE( key=0x1, value=Op.DELEGATECALL( - gas=0x3D090, + gas=inner_call_gas, address=addr_2, args_offset=0x0, args_size=0x40, @@ -82,7 +103,7 @@ def test_callcodecallcode_11( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=0x55730, + gas=outer_call_gas, address=addr, args_offset=0x0, args_size=0x40, diff --git a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcode_11_suicide_end.py b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcode_11_suicide_end.py index 4675d8f476b..5a96e25ac11 100644 --- a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcode_11_suicide_end.py +++ b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcode_11_suicide_end.py @@ -3,6 +3,10 @@ Ported from: state_tests/stCallDelegateCodesHomestead/callcodecallcode_11_SuicideEndFiller.json + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. + """ import pytest @@ -15,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +36,18 @@ def test_callcodecallcode_11_suicide_end( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_callcodecallcode_11_suicide_end.""" + # EIP-8037 inner-CALL gas bumps: original values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state- + # gas spill into regular gas on Amsterdam. + outer_call_gas = 150000 + inner_call_gas = 50000 + if fork.is_eip_enabled(8037): + outer_call_gas = 1000000 + inner_call_gas = 100000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -59,7 +74,7 @@ def test_callcodecallcode_11_suicide_end( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=0x249F0, + gas=outer_call_gas, address=0x1CCA6E93108EC94304AE5EB121D323E6C317FE7A, args_offset=0x0, args_size=0x40, @@ -78,7 +93,7 @@ def test_callcodecallcode_11_suicide_end( code=Op.SSTORE( key=0x1, value=Op.DELEGATECALL( - gas=0xC350, + gas=inner_call_gas, address=0x703B936FD4D674F0FF5D6957F61097152F8781B8, args_offset=0x0, args_size=0x40, diff --git a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcodecall_110.py b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcodecall_110.py index 6ff719a5e6c..0d66c6d33fe 100644 --- a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcodecall_110.py +++ b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcodecall_110.py @@ -3,6 +3,16 @@ Ported from: state_tests/stCallDelegateCodesHomestead/callcodecallcodecall_110Filler.json + + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values from the original filler (250k / 300k / 350k) were tuned to +the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the +innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei +of regular gas), and the inner CALL OoGs before the test's SSTORE +markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL +chain has headroom on Amsterdam; older forks are unaffected because +only the requested gas changes, the actual consumption is identical. """ import pytest @@ -15,6 +25,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +42,20 @@ def test_callcodecallcodecall_110( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_callcodecallcodecall_110.""" + # EIP-8037 inner-CALL gas bumps (original gas values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state-gas + # spill into regular gas on Amsterdam). + inner_call_gas = 0x3D090 + middle_call_gas = 0x493E0 + outer_call_gas = 0x55730 + if fork.is_eip_enabled(8037): + inner_call_gas = 0xF4240 + middle_call_gas = 0x124F80 + outer_call_gas = 0x155CC0 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -65,7 +88,7 @@ def test_callcodecallcodecall_110( code=Op.SSTORE( key=0x2, value=Op.CALL( - gas=0x3D090, + gas=inner_call_gas, address=addr_3, value=0x1, args_offset=0x0, @@ -85,7 +108,7 @@ def test_callcodecallcodecall_110( code=Op.SSTORE( key=0x1, value=Op.DELEGATECALL( - gas=0x493E0, + gas=middle_call_gas, address=addr_2, args_offset=0x0, args_size=0x40, @@ -104,7 +127,7 @@ def test_callcodecallcodecall_110( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=0x55730, + gas=outer_call_gas, address=addr, args_offset=0x0, args_size=0x40, diff --git a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcodecall_110_suicide_end.py b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcodecall_110_suicide_end.py index 1c41be7b462..c3fb455b3ef 100644 --- a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcodecall_110_suicide_end.py +++ b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcodecall_110_suicide_end.py @@ -3,6 +3,10 @@ Ported from: state_tests/stCallDelegateCodesHomestead/callcodecallcodecall_110_SuicideEndFiller.json + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. + """ import pytest @@ -15,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +36,20 @@ def test_callcodecallcodecall_110_suicide_end( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_callcodecallcodecall_110_suicide_end.""" + # EIP-8037 inner-CALL gas bumps: original values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state- + # gas spill into regular gas on Amsterdam. + outer_call_gas = 150000 + middle_call_gas = 100000 + inner_call_gas = 50000 + if fork.is_eip_enabled(8037): + outer_call_gas = 1000000 + middle_call_gas = 800000 + inner_call_gas = 100000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -59,7 +76,7 @@ def test_callcodecallcodecall_110_suicide_end( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=0x249F0, + gas=outer_call_gas, address=0x2CAC1D43F00E8B40B63426AB460C7E8717EE6455, args_offset=0x0, args_size=0x40, @@ -78,7 +95,7 @@ def test_callcodecallcodecall_110_suicide_end( code=Op.SSTORE( key=0x1, value=Op.DELEGATECALL( - gas=0x186A0, + gas=middle_call_gas, address=0xD957E143AD2C011BC6A2B142795F1A9BA70D0680, args_offset=0x0, args_size=0x40, @@ -97,7 +114,7 @@ def test_callcodecallcodecall_110_suicide_end( code=Op.SSTORE( key=0x2, value=Op.CALL( - gas=0xC350, + gas=inner_call_gas, address=0x73B954EBC05BB0FF4A0F6A13A054D50AD1584099, value=0x0, args_offset=0x0, diff --git a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcodecall_abcb_recursive.py b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcodecall_abcb_recursive.py index 61c2398da8c..2409e427525 100644 --- a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcodecall_abcb_recursive.py +++ b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcodecall_abcb_recursive.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,6 +32,7 @@ @pytest.mark.pre_alloc_mutable def test_callcodecallcodecall_abcb_recursive( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """DELEGATECALL -> DELEGATECALL2 -> CALL -> DELEGATECALL2 -> .""" @@ -42,7 +45,6 @@ def test_callcodecallcodecall_abcb_recursive( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=3000000000, ) # Source: lll @@ -108,12 +110,17 @@ def test_callcodecallcodecall_abcb_recursive( sender=sender, to=target, data=Bytes(""), - gas_limit=600000, + gas_limit=2600000 if fork >= Amsterdam else 600000, ) post = { - target: Account(storage={0: 1, 1: 1}), - addr: Account(storage={1: 0, 2: 0}), + target: Account(storage={0: 1, 1: 1, 2: 0}), + addr: Account( + storage={ + 1: 0, + 2: 0, + } + ), addr_2: Account(storage={1: 0, 2: 0}), sender: Account(storage={1: 0}), } diff --git a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcodecallcode_111.py b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcodecallcode_111.py index 79b74221b0c..f270f4c6af8 100644 --- a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcodecallcode_111.py +++ b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcodecallcode_111.py @@ -3,6 +3,16 @@ Ported from: state_tests/stCallDelegateCodesHomestead/callcodecallcodecallcode_111Filler.json + + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values from the original filler (250k / 300k / 350k) were tuned to +the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the +innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei +of regular gas), and the inner CALL OoGs before the test's SSTORE +markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL +chain has headroom on Amsterdam; older forks are unaffected because +only the requested gas changes, the actual consumption is identical. """ import pytest @@ -15,6 +25,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +42,20 @@ def test_callcodecallcodecallcode_111( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_callcodecallcodecallcode_111.""" + # EIP-8037 inner-CALL gas bumps (original gas values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state-gas + # spill into regular gas on Amsterdam). + inner_call_gas = 0x3D090 + middle_call_gas = 0x493E0 + outer_call_gas = 0x55730 + if fork.is_eip_enabled(8037): + inner_call_gas = 0xF4240 + middle_call_gas = 0x124F80 + outer_call_gas = 0x155CC0 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -65,7 +88,7 @@ def test_callcodecallcodecallcode_111( code=Op.SSTORE( key=0x2, value=Op.DELEGATECALL( - gas=0x3D090, + gas=inner_call_gas, address=addr_3, args_offset=0x0, args_size=0x40, @@ -83,7 +106,7 @@ def test_callcodecallcodecallcode_111( code=Op.SSTORE( key=0x1, value=Op.DELEGATECALL( - gas=0x493E0, + gas=middle_call_gas, address=addr_2, args_offset=0x0, args_size=0x40, @@ -101,7 +124,7 @@ def test_callcodecallcodecallcode_111( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=0x55730, + gas=outer_call_gas, address=addr, args_offset=0x0, args_size=0x40, diff --git a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcodecallcode_111_suicide_end.py b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcodecallcode_111_suicide_end.py index 881e54676f3..56fde94b9ab 100644 --- a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcodecallcode_111_suicide_end.py +++ b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcodecallcode_111_suicide_end.py @@ -3,6 +3,10 @@ Ported from: state_tests/stCallDelegateCodesHomestead/callcodecallcodecallcode_111_SuicideEndFiller.json + +@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas +values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. + """ import pytest @@ -15,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +36,20 @@ def test_callcodecallcodecallcode_111_suicide_end( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_callcodecallcodecallcode_111_suicide_end.""" + # EIP-8037 inner-CALL gas bumps: original values restored for + # pre-EIP-8037 forks; bumped values cover the per-storage state- + # gas spill into regular gas on Amsterdam. + outer_call_gas = 150000 + middle_call_gas = 100000 + inner_call_gas = 50000 + if fork.is_eip_enabled(8037): + outer_call_gas = 1000000 + middle_call_gas = 800000 + inner_call_gas = 100000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -59,7 +76,7 @@ def test_callcodecallcodecallcode_111_suicide_end( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=0x249F0, + gas=outer_call_gas, address=0x2CAC1D43F00E8B40B63426AB460C7E8717EE6455, args_offset=0x0, args_size=0x40, @@ -78,7 +95,7 @@ def test_callcodecallcodecallcode_111_suicide_end( code=Op.SSTORE( key=0x1, value=Op.DELEGATECALL( - gas=0x186A0, + gas=middle_call_gas, address=0xAC521409E2FA9526BFE6B827805783D2E307C4CE, args_offset=0x0, args_size=0x40, @@ -97,7 +114,7 @@ def test_callcodecallcodecallcode_111_suicide_end( code=Op.SSTORE( key=0x2, value=Op.DELEGATECALL( - gas=0xC350, + gas=inner_call_gas, address=0x73B954EBC05BB0FF4A0F6A13A054D50AD1584099, args_offset=0x0, args_size=0x40, diff --git a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcodecallcode_abcb_recursive.py b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcodecallcode_abcb_recursive.py index 4b7748b2981..b43b28f79f2 100644 --- a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcodecallcode_abcb_recursive.py +++ b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcodecallcode_abcb_recursive.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,6 +32,7 @@ @pytest.mark.pre_alloc_mutable def test_callcodecallcodecallcode_abcb_recursive( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """DELEGATECALL -> DELEGATECALL2 -> DELEGATECALl3 -> DELEGATECALL2 -> .""" @@ -42,7 +45,6 @@ def test_callcodecallcodecallcode_abcb_recursive( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=3000000000, ) # Source: lll @@ -107,11 +109,11 @@ def test_callcodecallcodecallcode_abcb_recursive( sender=sender, to=target, data=Bytes(""), - gas_limit=600000, + gas_limit=2600000 if fork >= Amsterdam else 600000, ) post = { - target: Account(storage={0: 1, 1: 1}), + target: Account(storage={0: 1, 1: 1, 2: 0}), addr: Account(storage={1: 0, 2: 0}), addr_2: Account(storage={1: 0, 2: 0}), sender: Account(storage={1: 0}), diff --git a/tests/ported_static/stCodeCopyTest/test_ext_code_copy_target_range_longer_than_code_tests.py b/tests/ported_static/stCodeCopyTest/test_ext_code_copy_target_range_longer_than_code_tests.py index 714f03d6c34..8bde75827ca 100644 --- a/tests/ported_static/stCodeCopyTest/test_ext_code_copy_target_range_longer_than_code_tests.py +++ b/tests/ported_static/stCodeCopyTest/test_ext_code_copy_target_range_longer_than_code_tests.py @@ -45,7 +45,6 @@ def test_ext_code_copy_target_range_longer_than_code_tests( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) diff --git a/tests/ported_static/stCodeCopyTest/test_ext_code_copy_tests_paris.py b/tests/ported_static/stCodeCopyTest/test_ext_code_copy_tests_paris.py index a3324ff89b2..fdc33843024 100644 --- a/tests/ported_static/stCodeCopyTest/test_ext_code_copy_tests_paris.py +++ b/tests/ported_static/stCodeCopyTest/test_ext_code_copy_tests_paris.py @@ -47,7 +47,6 @@ def test_ext_code_copy_tests_paris( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) diff --git a/tests/ported_static/stCodeSizeLimit/test_codesize_oog_invalid_size.py b/tests/ported_static/stCodeSizeLimit/test_codesize_oog_invalid_size.py index f7781ee3d97..4c284806244 100644 --- a/tests/ported_static/stCodeSizeLimit/test_codesize_oog_invalid_size.py +++ b/tests/ported_static/stCodeSizeLimit/test_codesize_oog_invalid_size.py @@ -44,6 +44,7 @@ ), ], ) +@pytest.mark.pre_alloc_mutable def test_codesize_oog_invalid_size( state_test: StateTestFiller, pre: Alloc, @@ -65,11 +66,18 @@ def test_codesize_oog_invalid_size( gas_limit=20000000, ) + # Return sizes are fork.max_code_size() + 13 and + 1 so CREATE + # always overflows the code-size limit. On pre-7954 forks this + # yields the original 0x600D / 0x6001 (max_code_size = 0x6000); + # on Amsterdam+ it scales with the raised limit. + max_code_size = fork.max_code_size() + size_d0 = max_code_size + 13 + size_d1 = max_code_size + 1 tx_data = [ - Op.CODECOPY(dest_offset=0x0, offset=0xD, size=0x600D) - + Op.RETURN(offset=0x0, size=0x600D), - Op.CODECOPY(dest_offset=0x0, offset=0xD, size=0x6001) - + Op.RETURN(offset=0x0, size=0x6001), + Op.CODECOPY(dest_offset=0x0, offset=0xD, size=size_d0) + + Op.RETURN(offset=0x0, size=size_d0), + Op.CODECOPY(dest_offset=0x0, offset=0xD, size=size_d1) + + Op.RETURN(offset=0x0, size=size_d1), ] tx_gas = [15000000] tx_value = [1] diff --git a/tests/ported_static/stCodeSizeLimit/test_codesize_valid.py b/tests/ported_static/stCodeSizeLimit/test_codesize_valid.py index b0a4a1fd5ff..6ac356a7b8c 100644 --- a/tests/ported_static/stCodeSizeLimit/test_codesize_valid.py +++ b/tests/ported_static/stCodeSizeLimit/test_codesize_valid.py @@ -3,6 +3,15 @@ Ported from: state_tests/stCodeSizeLimit/codesizeValidFiller.json + +@manually-enhanced: Do not overwrite. On Amsterdam (EIP-8037) the +contract-creation tx — which deploys ~24 KiB of code — needs extra +state-gas headroom on top of the 15 000 000 regular-gas budget that +suffices on earlier forks. Bump `tx.gas` to 30 000 000 fork- +conditionally; pre-Amsterdam keeps the original 15 000 000 (Osaka +caps `tx.gas` at `TX_MAX_GAS_LIMIT = 16 777 216`, so the bump must be +gated). `env.gas_limit` widened so the larger tx fits in the block. +Post-state expectations are unchanged on all forks. """ import pytest @@ -61,7 +70,7 @@ def test_codesize_valid( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=20000000, + gas_limit=45000000, ) tx_data = [ @@ -70,7 +79,7 @@ def test_codesize_valid( Op.CODECOPY(dest_offset=0x0, offset=0xD, size=0x6000) + Op.RETURN(offset=0x0, size=0x6000), ] - tx_gas = [15000000] + tx_gas = [40000000 if fork.is_eip_enabled(8037) else 15000000] tx_value = [1] tx = Transaction( diff --git a/tests/ported_static/stCodeSizeLimit/test_create2_code_size_limit.py b/tests/ported_static/stCodeSizeLimit/test_create2_code_size_limit.py index b3bf7840879..6c865aed2e5 100644 --- a/tests/ported_static/stCodeSizeLimit/test_create2_code_size_limit.py +++ b/tests/ported_static/stCodeSizeLimit/test_create2_code_size_limit.py @@ -15,6 +15,7 @@ Environment, StateTestFiller, Transaction, + compute_create_address, ) from execution_testing.forks import Fork from execution_testing.specs.static_state.expect_section import ( @@ -95,6 +96,22 @@ def test_create2_code_size_limit( address=Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B), # noqa: E501 ) + # Initcode: PUSH2 PUSH1 0 RETURN. Sizes scale with + # fork.max_code_size() so pre-7954 forks get the original 0x6000 + # / 0x6001 and Amsterdam+ gets 0x8000 / 0x8001. + max_code_size = fork.max_code_size() + tx_data = [ + Bytes(b"\x61" + max_code_size.to_bytes(2) + b"\x60\x00\xf3"), + Bytes(b"\x61" + (max_code_size + 1).to_bytes(2) + b"\x60\x00\xf3"), + ] + tx_gas = [15000000] + valid_create2_address = compute_create_address( + address=contract_0, + salt=0, + initcode=tx_data[0], + opcode=Op.CREATE2, + ) + expect_entries_: list[dict] = [ { "indexes": {"data": [0], "gas": -1, "value": -1}, @@ -103,13 +120,11 @@ def test_create2_code_size_limit( sender: Account(nonce=1), contract_0: Account( storage={ - 0: 0x81C305016AB9CA56033A07CC37E7A30FC3E079AC, + 0: valid_create2_address, 1: 1, }, ), - Address(0x81C305016AB9CA56033A07CC37E7A30FC3E079AC): Account( - storage={}, balance=0, nonce=1 - ), + valid_create2_address: Account(storage={}, balance=0, nonce=1), }, }, { @@ -118,21 +133,13 @@ def test_create2_code_size_limit( "result": { sender: Account(nonce=1), contract_0: Account(storage={0: 0, 1: 1}), - Address( - 0x81C305016AB9CA56033A07CC37E7A30FC3E079AC - ): Account.NONEXISTENT, + valid_create2_address: Account.NONEXISTENT, }, }, ] post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork) - tx_data = [ - Bytes("6160006000f3"), - Bytes("6160016000f3"), - ] - tx_gas = [15000000] - tx = Transaction( sender=sender, to=contract_0, diff --git a/tests/ported_static/stCodeSizeLimit/test_create_code_size_limit.py b/tests/ported_static/stCodeSizeLimit/test_create_code_size_limit.py index 84ab61dc09c..82d1b803ba4 100644 --- a/tests/ported_static/stCodeSizeLimit/test_create_code_size_limit.py +++ b/tests/ported_static/stCodeSizeLimit/test_create_code_size_limit.py @@ -121,9 +121,14 @@ def test_create_code_size_limit( post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork) + # Initcode: PUSH2 PUSH1 0 RETURN. Sizes scale with + # fork.max_code_size() so pre-7954 forks get 0x6000 / 0x6001 and + # Amsterdam+ gets 0x8000 / 0x8001. CREATE address is + # nonce-derived and unaffected by the initcode bytes. + max_code_size = fork.max_code_size() tx_data = [ - Bytes("6160006000f3"), - Bytes("6160016000f3"), + Bytes(b"\x61" + max_code_size.to_bytes(2) + b"\x60\x00\xf3"), + Bytes(b"\x61" + (max_code_size + 1).to_bytes(2) + b"\x60\x00\xf3"), ] tx_gas = [15000000] diff --git a/tests/ported_static/stCreate2/test_call_outsize_then_create2_successful_then_returndatasize.py b/tests/ported_static/stCreate2/test_call_outsize_then_create2_successful_then_returndatasize.py index 3c2976dd8f9..96080edc48d 100644 --- a/tests/ported_static/stCreate2/test_call_outsize_then_create2_successful_then_returndatasize.py +++ b/tests/ported_static/stCreate2/test_call_outsize_then_create2_successful_then_returndatasize.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,6 +32,7 @@ @pytest.mark.pre_alloc_mutable def test_call_outsize_then_create2_successful_then_returndatasize( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_call_outsize_then_create2_successful_then_returndatasize.""" @@ -44,7 +47,6 @@ def test_call_outsize_then_create2_successful_then_returndatasize( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=47244640256, ) # Source: lll @@ -91,7 +93,7 @@ def test_call_outsize_then_create2_successful_then_returndatasize( sender=sender, to=contract_1, data=Bytes(""), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, ) post = {contract_1: Account(storage={0: 0})} diff --git a/tests/ported_static/stCreate2/test_call_then_create2_successful_then_returndatasize.py b/tests/ported_static/stCreate2/test_call_then_create2_successful_then_returndatasize.py index ff1f8f14b55..07995e6fd03 100644 --- a/tests/ported_static/stCreate2/test_call_then_create2_successful_then_returndatasize.py +++ b/tests/ported_static/stCreate2/test_call_then_create2_successful_then_returndatasize.py @@ -13,9 +13,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,6 +33,7 @@ @pytest.mark.pre_alloc_mutable def test_call_then_create2_successful_then_returndatasize( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_call_then_create2_successful_then_returndatasize.""" @@ -47,7 +50,6 @@ def test_call_then_create2_successful_then_returndatasize( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=47244640256, ) pre[sender] = Account(balance=0x6400000000) @@ -97,7 +99,7 @@ def test_call_then_create2_successful_then_returndatasize( sender=sender, to=contract_1, data=Bytes(""), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, ) post = { diff --git a/tests/ported_static/stCreate2/test_create2_contract_suicide_during_init_then_store_then_return.py b/tests/ported_static/stCreate2/test_create2_contract_suicide_during_init_then_store_then_return.py index faa8f7c9a01..57e81b5a8c7 100644 --- a/tests/ported_static/stCreate2/test_create2_contract_suicide_during_init_then_store_then_return.py +++ b/tests/ported_static/stCreate2/test_create2_contract_suicide_during_init_then_store_then_return.py @@ -3,6 +3,12 @@ Ported from: state_tests/stCreate2/CREATE2_ContractSuicideDuringInit_ThenStoreThenReturnFiller.json + +@manually-enhanced: Do not overwrite. The inner CALL gas was raised +from 0x249F0 to 0x100000 and the tx gas_limit from 600 000 to +5 000 000 so the nested CREATE2 + init-code SELFDESTRUCT to address +0x01 can afford its EIP-8037 NEW_ACCOUNT state gas on Amsterdam +(post-state expectations are unchanged on all forks). """ import pytest @@ -16,6 +22,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -32,6 +39,7 @@ def test_create2_contract_suicide_during_init_then_store_then_return( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_create2_contract_suicide_during_init_then_store_then_return.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) @@ -51,6 +59,14 @@ def test_create2_contract_suicide_during_init_then_store_then_return( ) pre[sender] = Account(balance=0xE8D4A51000) + # EIP-8037 NEW_ACCOUNT state-gas on Amsterdam pushes both the inner + # CALL and the outer tx over the original budgets; pre-EIP-8037 + # forks keep the values the original filler was tuned for. + inner_call_gas = 0x249F0 + tx_gas_limit = 600_000 + if fork.is_eip_enabled(8037): + inner_call_gas = 0x100000 + tx_gas_limit = 5_000_000 # Source: lll # { (MSTORE 0 0x6d64600c6000556000526005601bf36000526001ff) (CREATE2 1 11 21 0) [[0]] 11 (RETURN 18 14) } # noqa: E501 contract_1 = pre.deploy_contract( # noqa: F841 @@ -70,7 +86,7 @@ def test_create2_contract_suicide_during_init_then_store_then_return( contract_0 = pre.deploy_contract( # noqa: F841 code=Op.POP( Op.CALL( - gas=0x249F0, + gas=inner_call_gas, address=contract_1, value=0x1, args_offset=0x0, @@ -90,7 +106,7 @@ def test_create2_contract_suicide_during_init_then_store_then_return( sender=sender, to=contract_0, data=Bytes(""), - gas_limit=600000, + gas_limit=tx_gas_limit, value=10, ) diff --git a/tests/ported_static/stCreate2/test_create2_first_byte_loop.py b/tests/ported_static/stCreate2/test_create2_first_byte_loop.py index 65d516db2aa..cd9a424e273 100644 --- a/tests/ported_static/stCreate2/test_create2_first_byte_loop.py +++ b/tests/ported_static/stCreate2/test_create2_first_byte_loop.py @@ -3,6 +3,10 @@ Ported from: state_tests/stCreate2/CREATE2_FirstByte_loopFiller.yml +@manually-enhanced: Do not overwrite. Gas bumped fork-conditionally +to cover EIP-8037 state-gas spill into regular gas; pre-EIP-8037 +behavior unchanged. + """ import pytest @@ -64,6 +68,11 @@ def test_create2_first_byte_loop( v: int, ) -> None: """Test_create2_first_byte_loop.""" + # EIP-8037 gas bumps: original values for pre-EIP-8037 forks. + outer_tx_gas = 16777216 + if fork.is_eip_enabled(8037): + outer_tx_gas = 83886080 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = EOA( key=0xF79127A3004ABDE26A4CBD80C428CB10F829FA11B54D36E7B326F4F4A5927ACF @@ -175,7 +184,7 @@ def test_create2_first_byte_loop( Bytes("1a8451e6") + Hash(0xEF) + Hash(0xF0), Bytes("1a8451e6") + Hash(0xF0) + Hash(0x100), ] - tx_gas = [16777216] + tx_gas = [outer_tx_gas] tx = Transaction( sender=sender, diff --git a/tests/ported_static/stCreate2/test_create2_oo_gafter_init_code.py b/tests/ported_static/stCreate2/test_create2_oo_gafter_init_code.py index ab52f770473..fd230fb046f 100644 --- a/tests/ported_static/stCreate2/test_create2_oo_gafter_init_code.py +++ b/tests/ported_static/stCreate2/test_create2_oo_gafter_init_code.py @@ -3,6 +3,9 @@ Ported from: state_tests/stCreate2/Create2OOGafterInitCodeFiller.json +@manually-enhanced: Do not overwrite. tx_gas[1] is tuned to barely +succeed CREATE2 on Cancun; on Amsterdam EIP-8037 the NEW_ACCOUNT +state-gas spills, so lift the budget by Fork.oog_budget_lift. """ import pytest @@ -69,7 +72,6 @@ def test_create2_oo_gafter_init_code( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) pre[sender] = Account(balance=0xE8D4A51000) @@ -111,7 +113,20 @@ def test_create2_oo_gafter_init_code( tx_data = [ Bytes(""), ] - tx_gas = [54000, 55000] + # Lift both entries on Amsterdam so the test still exercises its + # named scenario. With only tx_gas[1] lifted, g=0 OoG'd at CREATE2 + # dispatch (NEW_ACCOUNT state-gas spill) before init code ever ran — + # the assertion still passes (`NONEXISTENT` either way) but the + # failure mode is "dispatch-time OoG" instead of "OoG after init + # code". A simple `fork.oog_budget_lift(creates_before_oog=1)` (183600) + # is *too* generous and pushes g=0 past the deploy threshold; the + # Cancun 1000-gas gap between g=0 and g=1 collapses on Amsterdam + # because once dispatch is cleared, the 5-byte init code is cheap + # enough to always complete. The value below is the middle of the + # empirically-safe range (166499, 167000) where g=0 still OoGs at + # dispatch *and* g=1 just clears the deploy threshold (~221.5k). + _oog_lift = 166_750 if fork.is_eip_enabled(8037) else 0 + tx_gas = [54000 + _oog_lift, 55000 + _oog_lift] tx = Transaction( sender=sender, diff --git a/tests/ported_static/stCreate2/test_create2_oo_gafter_init_code_returndata2.py b/tests/ported_static/stCreate2/test_create2_oo_gafter_init_code_returndata2.py index f259f787b86..b85e078eaf9 100644 --- a/tests/ported_static/stCreate2/test_create2_oo_gafter_init_code_returndata2.py +++ b/tests/ported_static/stCreate2/test_create2_oo_gafter_init_code_returndata2.py @@ -3,6 +3,10 @@ Ported from: state_tests/stCreate2/Create2OOGafterInitCodeReturndata2Filler.json +@manually-enhanced: Do not overwrite. tx_gas[1] is tuned to barely +finish CREATE2 + two post-deploy SSTOREs on Cancun; on Amsterdam the +NEW_ACCOUNT and SSTORE-set state-gas spills, so lift the budget by +Fork.oog_budget_lift. """ import pytest @@ -70,7 +74,6 @@ def test_create2_oo_gafter_init_code_returndata2( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) pre[sender] = Account(balance=0xE8D4A51000) @@ -118,7 +121,11 @@ def test_create2_oo_gafter_init_code_returndata2( tx_data = [ Bytes(""), ] - tx_gas = [54000, 95000] + tx_gas = [ + 54000, + 95000 + + fork.oog_budget_lift(creates_before_oog=1, sstores_before_oog=2), + ] tx = Transaction( sender=sender, diff --git a/tests/ported_static/stCreate2/test_create2_oo_gafter_init_code_returndata_size.py b/tests/ported_static/stCreate2/test_create2_oo_gafter_init_code_returndata_size.py index 77799dc86dd..e95e39719a1 100644 --- a/tests/ported_static/stCreate2/test_create2_oo_gafter_init_code_returndata_size.py +++ b/tests/ported_static/stCreate2/test_create2_oo_gafter_init_code_returndata_size.py @@ -12,10 +12,12 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, compute_create_address, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +31,7 @@ @pytest.mark.pre_alloc_mutable def test_create2_oo_gafter_init_code_returndata_size( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Calls a contract that runs CREATE2 which deploy a code.""" @@ -61,7 +64,7 @@ def test_create2_oo_gafter_init_code_returndata_size( sender=sender, to=contract_0, data=Bytes(""), - gas_limit=55054, + gas_limit=2055054 if fork >= Amsterdam else 55054, value=1, ) diff --git a/tests/ported_static/stCreate2/test_create2_oo_gafter_init_code_revert.py b/tests/ported_static/stCreate2/test_create2_oo_gafter_init_code_revert.py index 7bc66a9747b..4784c88b0de 100644 --- a/tests/ported_static/stCreate2/test_create2_oo_gafter_init_code_revert.py +++ b/tests/ported_static/stCreate2/test_create2_oo_gafter_init_code_revert.py @@ -13,10 +13,12 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, compute_create_address, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,6 +32,7 @@ @pytest.mark.pre_alloc_mutable def test_create2_oo_gafter_init_code_revert( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Calls a contract that runs CREATE2 which deploy a code.""" @@ -85,7 +88,7 @@ def test_create2_oo_gafter_init_code_revert( sender=sender, to=contract_0, data=Bytes(""), - gas_limit=75000, + gas_limit=2075000 if fork >= Amsterdam else 75000, ) post = { diff --git a/tests/ported_static/stCreate2/test_create2_oog_from_call_refunds.py b/tests/ported_static/stCreate2/test_create2_oog_from_call_refunds.py index 41fa4bb6e0d..2c5c0b12a72 100644 --- a/tests/ported_static/stCreate2/test_create2_oog_from_call_refunds.py +++ b/tests/ported_static/stCreate2/test_create2_oog_from_call_refunds.py @@ -230,7 +230,6 @@ def test_create2_oog_from_call_refunds( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=4294967296, ) pre[sender] = Account(balance=0x3D0900, nonce=1) @@ -939,7 +938,37 @@ def test_create2_oog_from_call_refunds( address=Address(0x000000000000000000000000000000000000007A), # noqa: E501 ) - expect_entries_: list[dict] = [ + expect_entries_: list[dict] = [] + if fork.is_eip_enabled(8037): + expect_entries_.append( + { + "indexes": { + "data": [ + 1, + 2, + 4, + 5, + 7, + 8, + 10, + 11, + 13, + 14, + 16, + 17, + 19, + 20, + 22, + 23, + ], + "gas": -1, + "value": -1, + }, + "network": [">=Cancun"], + "result": {sender: Account(nonce=2)}, + } + ) + expect_entries_ += [ { "indexes": {"data": [0], "gas": -1, "value": -1}, "network": [">=Cancun"], diff --git a/tests/ported_static/stCreate2/test_create2_smart_init_code.py b/tests/ported_static/stCreate2/test_create2_smart_init_code.py index 0d5a967638e..09b73134629 100644 --- a/tests/ported_static/stCreate2/test_create2_smart_init_code.py +++ b/tests/ported_static/stCreate2/test_create2_smart_init_code.py @@ -3,6 +3,10 @@ Ported from: state_tests/stCreate2/create2SmartInitCodeFiller.json + +@manually-enhanced: Do not overwrite. tx_gas was raised from 400 000 to +1 000 000 so the CREATE2 path can afford its EIP-8037 NEW_ACCOUNT state +gas on Amsterdam (post-state expectations are unchanged on all forks). """ import pytest @@ -169,7 +173,12 @@ def test_create2_smart_init_code( Hash(contract_0, left_padding=True), Hash(contract_1, left_padding=True), ] - tx_gas = [400000] + # EIP-8037 NEW_ACCOUNT + per-byte state-gas spill into the regular + # budget on Amsterdam; pre-EIP-8037 forks keep the original 400 000. + outer_tx_gas = 400_000 + if fork.is_eip_enabled(8037): + outer_tx_gas = 1_000_000 + tx_gas = [outer_tx_gas] tx = Transaction( sender=sender, diff --git a/tests/ported_static/stCreate2/test_create2_suicide.py b/tests/ported_static/stCreate2/test_create2_suicide.py index 0fa854ae984..41d417f8456 100644 --- a/tests/ported_static/stCreate2/test_create2_suicide.py +++ b/tests/ported_static/stCreate2/test_create2_suicide.py @@ -3,6 +3,10 @@ Ported from: state_tests/stCreate2/CREATE2_SuicideFiller.json +@manually-enhanced: Do not overwrite. Gas bumped fork-conditionally +to cover EIP-8037 state-gas spill into regular gas; pre-EIP-8037 +behavior unchanged. + """ import pytest @@ -117,6 +121,13 @@ def test_create2_suicide( v: int, ) -> None: """CREATE2 suicide with/without value, CREATE2 suicide to itself + ...""" + # EIP-8037 gas bumps: original values for pre-EIP-8037 forks. + outer_tx_gas = 600000 + inner_call_gas = 150000 + if fork.is_eip_enabled(8037): + outer_tx_gas = 3000000 + inner_call_gas = 1000000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = EOA( key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8 @@ -223,7 +234,7 @@ def test_create2_suicide( Op.MSTORE(offset=0x0, value=0x626001FF6000526003601DF3) + Op.POP(Op.CREATE2(value=0x0, offset=0x14, size=0xC, salt=0x0)) + Op.CALL( - gas=0x249F0, + gas=inner_call_gas, address=0x5649527A8464A86CAE579719D347065F6EB27279, value=0x0, args_offset=0x0, @@ -238,7 +249,7 @@ def test_create2_suicide( Op.MSTORE(offset=0x0, value=0x626001FF6000526003601DF3) + Op.POP(Op.CREATE2(value=0x1, offset=0x14, size=0xC, salt=0x0)) + Op.CALL( - gas=0x249F0, + gas=inner_call_gas, address=0x5649527A8464A86CAE579719D347065F6EB27279, value=0x0, args_offset=0x0, @@ -253,7 +264,7 @@ def test_create2_suicide( Op.MSTORE(offset=0x0, value=0x6130FF6000526002601EF3) + Op.POP(Op.CREATE2(value=0x0, offset=0x15, size=0xB, salt=0x0)) + Op.CALL( - gas=0x249F0, + gas=inner_call_gas, address=0x6CD0E5133771823DA00D4CB545EC8CDAB0E38203, value=0x0, args_offset=0x0, @@ -268,7 +279,7 @@ def test_create2_suicide( Op.MSTORE(offset=0x0, value=0x6130FF6000526002601EF3) + Op.POP(Op.CREATE2(value=0x1, offset=0x15, size=0xB, salt=0x0)) + Op.CALL( - gas=0x249F0, + gas=inner_call_gas, address=0x6CD0E5133771823DA00D4CB545EC8CDAB0E38203, value=0x0, args_offset=0x0, @@ -280,7 +291,7 @@ def test_create2_suicide( Op.MSTORE(offset=0x0, value=0x626001FF6000526003601DF3) + Op.POP(Op.CREATE2(value=0x0, offset=0x14, size=0xC, salt=0x0)) + Op.STATICCALL( - gas=0x249F0, + gas=inner_call_gas, address=0x5649527A8464A86CAE579719D347065F6EB27279, args_offset=0x0, args_size=0x0, @@ -291,7 +302,7 @@ def test_create2_suicide( Op.MSTORE(offset=0x0, value=0x626001FF6000526003601DF3) + Op.POP(Op.CREATE2(value=0x1, offset=0x14, size=0xC, salt=0x0)) + Op.STATICCALL( - gas=0x249F0, + gas=inner_call_gas, address=0x5649527A8464A86CAE579719D347065F6EB27279, args_offset=0x0, args_size=0x0, @@ -302,7 +313,7 @@ def test_create2_suicide( Op.MSTORE(offset=0x0, value=0x6130FF6000526002601EF3) + Op.POP(Op.CREATE2(value=0x0, offset=0x15, size=0xB, salt=0x0)) + Op.STATICCALL( - gas=0x249F0, + gas=inner_call_gas, address=0x6CD0E5133771823DA00D4CB545EC8CDAB0E38203, args_offset=0x0, args_size=0x0, @@ -313,7 +324,7 @@ def test_create2_suicide( Op.MSTORE(offset=0x0, value=0x6130FF6000526002601EF3) + Op.POP(Op.CREATE2(value=0x1, offset=0x15, size=0xB, salt=0x0)) + Op.STATICCALL( - gas=0x249F0, + gas=inner_call_gas, address=0x6CD0E5133771823DA00D4CB545EC8CDAB0E38203, args_offset=0x0, args_size=0x0, @@ -322,7 +333,7 @@ def test_create2_suicide( ) + Op.STOP, ] - tx_gas = [600000] + tx_gas = [outer_tx_gas] tx_value = [10] tx = Transaction( diff --git a/tests/ported_static/stCreate2/test_create2call_precompiles.py b/tests/ported_static/stCreate2/test_create2call_precompiles.py index 969be62840e..4fe7a1ba9e5 100644 --- a/tests/ported_static/stCreate2/test_create2call_precompiles.py +++ b/tests/ported_static/stCreate2/test_create2call_precompiles.py @@ -105,7 +105,6 @@ def test_create2call_precompiles( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=1000000000000, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) diff --git a/tests/ported_static/stCreate2/test_create2collision_balance.py b/tests/ported_static/stCreate2/test_create2collision_balance.py index 463ebb5f599..b5b06b533e8 100644 --- a/tests/ported_static/stCreate2/test_create2collision_balance.py +++ b/tests/ported_static/stCreate2/test_create2collision_balance.py @@ -3,6 +3,12 @@ Ported from: state_tests/stCreate2/create2collisionBalanceFiller.json + +@manually-enhanced: Do not overwrite. `tx_gas` raised on Amsterdam to +cover EIP-8037 NEW_ACCOUNT state-gas spill into regular gas. Pre- +EIP-8037 keeps the original 400 000 budget; post-state expectations +unchanged on all forks. + """ import pytest @@ -178,7 +184,12 @@ def test_create2collision_balance( + Op.STOP, Op.CREATE2(value=0x1, offset=0x0, size=0x0, salt=0x0) + Op.STOP, ] - tx_gas = [400000] + # EIP-8037 NEW_ACCOUNT state-gas spill on Amsterdam exceeds + # the original 400 000 budget. Pre-EIP-8037 keeps the original. + outer_tx_gas = 400000 + if fork.is_eip_enabled(8037): + outer_tx_gas = 1_000_000 + tx_gas = [outer_tx_gas] tx_value = [1] tx = Transaction( diff --git a/tests/ported_static/stCreate2/test_create2collision_code.py b/tests/ported_static/stCreate2/test_create2collision_code.py index e234fd58d91..4c011c79963 100644 --- a/tests/ported_static/stCreate2/test_create2collision_code.py +++ b/tests/ported_static/stCreate2/test_create2collision_code.py @@ -3,6 +3,12 @@ Ported from: state_tests/stCreate2/create2collisionCodeFiller.json + +@manually-enhanced: Do not overwrite. `tx_gas` raised on Amsterdam to +cover EIP-8037 NEW_ACCOUNT state-gas spill into regular gas. Pre- +EIP-8037 keeps the original 400 000 budget; post-state expectations +unchanged on all forks. + """ import pytest @@ -109,7 +115,12 @@ def test_create2collision_code( + Op.CREATE2(value=0x0, offset=0x12, size=0xE, salt=0x0) + Op.STOP, ] - tx_gas = [400000] + # EIP-8037 NEW_ACCOUNT state-gas spill on Amsterdam exceeds + # the original 400 000 budget. Pre-EIP-8037 keeps the original. + outer_tx_gas = 400000 + if fork.is_eip_enabled(8037): + outer_tx_gas = 1_000_000 + tx_gas = [outer_tx_gas] tx_value = [1] tx = Transaction( diff --git a/tests/ported_static/stCreate2/test_create2collision_code2.py b/tests/ported_static/stCreate2/test_create2collision_code2.py index fce405e07c9..6d1bb84bfa3 100644 --- a/tests/ported_static/stCreate2/test_create2collision_code2.py +++ b/tests/ported_static/stCreate2/test_create2collision_code2.py @@ -3,6 +3,12 @@ Ported from: state_tests/stCreate2/create2collisionCode2Filler.json + +@manually-enhanced: Do not overwrite. `tx_gas` raised on Amsterdam to +cover EIP-8037 NEW_ACCOUNT state-gas spill into regular gas. Pre- +EIP-8037 keeps the original 400 000 budget; post-state expectations +unchanged on all forks. + """ import pytest @@ -120,7 +126,12 @@ def test_create2collision_code2( + Op.CREATE2(value=0x1, offset=0x14, size=0xC, salt=0x0) + Op.STOP, ] - tx_gas = [400000] + # EIP-8037 NEW_ACCOUNT state-gas spill on Amsterdam exceeds + # the original 400 000 budget. Pre-EIP-8037 keeps the original. + outer_tx_gas = 400000 + if fork.is_eip_enabled(8037): + outer_tx_gas = 1_000_000 + tx_gas = [outer_tx_gas] tx_value = [1] tx = Transaction( diff --git a/tests/ported_static/stCreate2/test_create2collision_nonce.py b/tests/ported_static/stCreate2/test_create2collision_nonce.py index 58135a80dd0..7a2f90f7644 100644 --- a/tests/ported_static/stCreate2/test_create2collision_nonce.py +++ b/tests/ported_static/stCreate2/test_create2collision_nonce.py @@ -3,6 +3,12 @@ Ported from: state_tests/stCreate2/create2collisionNonceFiller.json + +@manually-enhanced: Do not overwrite. `tx_gas` raised on Amsterdam to +cover EIP-8037 NEW_ACCOUNT state-gas spill into regular gas. Pre- +EIP-8037 keeps the original 400 000 budget; post-state expectations +unchanged on all forks. + """ import pytest @@ -109,7 +115,12 @@ def test_create2collision_nonce( + Op.CREATE2(value=0x0, offset=0x12, size=0xE, salt=0x0) + Op.STOP, ] - tx_gas = [400000] + # EIP-8037 NEW_ACCOUNT state-gas spill on Amsterdam exceeds + # the original 400 000 budget. Pre-EIP-8037 keeps the original. + outer_tx_gas = 400000 + if fork.is_eip_enabled(8037): + outer_tx_gas = 1_000_000 + tx_gas = [outer_tx_gas] tx_value = [1] tx = Transaction( diff --git a/tests/ported_static/stCreate2/test_create2collision_selfdestructed.py b/tests/ported_static/stCreate2/test_create2collision_selfdestructed.py index e3a2e1ae029..16263ba244b 100644 --- a/tests/ported_static/stCreate2/test_create2collision_selfdestructed.py +++ b/tests/ported_static/stCreate2/test_create2collision_selfdestructed.py @@ -3,6 +3,13 @@ Ported from: state_tests/stCreate2/create2collisionSelfdestructedFiller.json + +@manually-enhanced: Do not overwrite. The inner CALL's gas budget was +raised from 0xC350 to 0x40000 and the outer tx gas from 400 000 to +1 000 000 so the SELFDESTRUCT-to-empty path can afford its EIP-8037 +NEW_ACCOUNT state gas on Amsterdam (the test's intent — exercising +CREATE2 collision against a freshly self-destructed address — is +preserved on all forks). """ import pytest @@ -153,10 +160,19 @@ def test_create2collision_selfdestructed( post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork) + # EIP-8037 NEW_ACCOUNT state-gas pushes both the outer tx and the + # inner CALL over their original budgets on Amsterdam. Pre-EIP-8037 + # forks keep the original tuned values. + inner_call_gas = 0xC350 + outer_tx_gas = 400_000 + if fork.is_eip_enabled(8037): + inner_call_gas = 0x40000 + outer_tx_gas = 1_000_000 + tx_data = [ Op.POP( Op.CALL( - gas=0xC350, + gas=inner_call_gas, address=contract_0, value=0x0, args_offset=0x0, @@ -169,7 +185,7 @@ def test_create2collision_selfdestructed( + Op.STOP, Op.POP( Op.CALL( - gas=0xC350, + gas=inner_call_gas, address=contract_1, value=0x0, args_offset=0x0, @@ -183,7 +199,7 @@ def test_create2collision_selfdestructed( + Op.STOP, Op.POP( Op.CALL( - gas=0xC350, + gas=inner_call_gas, address=contract_2, value=0x0, args_offset=0x0, @@ -196,7 +212,7 @@ def test_create2collision_selfdestructed( + Op.CREATE2(value=0x0, offset=0x12, size=0xE, salt=0x0) + Op.STOP, ] - tx_gas = [400000] + tx_gas = [outer_tx_gas] tx_value = [1] tx = Transaction( diff --git a/tests/ported_static/stCreate2/test_create2collision_selfdestructed2.py b/tests/ported_static/stCreate2/test_create2collision_selfdestructed2.py index c55ec9f9d50..cc5a26cde9a 100644 --- a/tests/ported_static/stCreate2/test_create2collision_selfdestructed2.py +++ b/tests/ported_static/stCreate2/test_create2collision_selfdestructed2.py @@ -3,6 +3,13 @@ Ported from: state_tests/stCreate2/create2collisionSelfdestructed2Filler.json + +@manually-enhanced: Do not overwrite. The inner CALL's gas budget was +raised from 0xC350 to 0x40000 and the outer tx gas from 400 000 to +1 000 000 so the SELFDESTRUCT-to-empty path can afford its EIP-8037 +NEW_ACCOUNT state gas on Amsterdam (the test's intent — exercising +CREATE2 collision against a freshly self-destructed address — is +preserved on all forks). """ import pytest @@ -122,10 +129,19 @@ def test_create2collision_selfdestructed2( post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork) + # EIP-8037 NEW_ACCOUNT state-gas pushes both the outer tx and the + # inner CALL over their original budgets on Amsterdam. Pre-EIP-8037 + # forks keep the original tuned values. + inner_call_gas = 0xC350 + outer_tx_gas = 400_000 + if fork.is_eip_enabled(8037): + inner_call_gas = 0x40000 + outer_tx_gas = 1_000_000 + tx_data = [ Op.POP( Op.CALL( - gas=0xC350, + gas=inner_call_gas, address=contract_0, value=0x0, args_offset=0x0, @@ -139,7 +155,7 @@ def test_create2collision_selfdestructed2( + Op.STOP, Op.POP( Op.CALL( - gas=0xC350, + gas=inner_call_gas, address=contract_1, value=0x0, args_offset=0x0, @@ -152,7 +168,7 @@ def test_create2collision_selfdestructed2( + Op.CREATE2(value=0x0, offset=0x14, size=0xC, salt=0x0) + Op.STOP, ] - tx_gas = [400000] + tx_gas = [outer_tx_gas] tx = Transaction( sender=sender, diff --git a/tests/ported_static/stCreate2/test_create_message_reverted.py b/tests/ported_static/stCreate2/test_create_message_reverted.py index 0d0a5bff1f3..0927af9f824 100644 --- a/tests/ported_static/stCreate2/test_create_message_reverted.py +++ b/tests/ported_static/stCreate2/test_create_message_reverted.py @@ -3,6 +3,11 @@ Ported from: state_tests/stCreate2/CreateMessageRevertedFiller.json +@manually-enhanced: Do not overwrite. tx_gas[1] bumped on Amsterdam to +cover EIP-8037 state-gas spill (CREATE2 new account + 2 fresh +SSTOREs in init code); pre-EIP-8037 unchanged. g0 (OoG case) is +intentionally left alone. + """ import pytest @@ -72,7 +77,10 @@ def test_create_message_reverted( gas_limit=1000000000000, ) - pre[sender] = Account(balance=0x2DC6C0) + sender_balance = 3000000 + if fork.is_eip_enabled(8037): + sender_balance = 10000000 + pre[sender] = Account(balance=sender_balance) # Source: lll # {(MSTORE 0 0x600c600055600d600155) (CREATE2 0 22 10 0)} contract_0 = pre.deploy_contract( # noqa: F841 @@ -112,6 +120,8 @@ def test_create_message_reverted( Bytes(""), ] tx_gas = [80000, 150000] + if fork.is_eip_enabled(8037): + tx_gas = [80000, 500_000] tx_value = [100] tx = Transaction( diff --git a/tests/ported_static/stCreate2/test_returndatacopy_0_0_following_successful_create.py b/tests/ported_static/stCreate2/test_returndatacopy_0_0_following_successful_create.py index dcf711122d6..4efa8ef1339 100644 --- a/tests/ported_static/stCreate2/test_returndatacopy_0_0_following_successful_create.py +++ b/tests/ported_static/stCreate2/test_returndatacopy_0_0_following_successful_create.py @@ -13,9 +13,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,6 +33,7 @@ @pytest.mark.pre_alloc_mutable def test_returndatacopy_0_0_following_successful_create( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_returndatacopy_0_0_following_successful_create.""" @@ -46,7 +49,6 @@ def test_returndatacopy_0_0_following_successful_create( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=47244640256, ) pre[sender] = Account(balance=0x6400000000) @@ -73,7 +75,7 @@ def test_returndatacopy_0_0_following_successful_create( sender=sender, to=contract_0, data=Bytes(""), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, ) post = { diff --git a/tests/ported_static/stCreate2/test_returndatacopy_after_failing_create.py b/tests/ported_static/stCreate2/test_returndatacopy_after_failing_create.py index e08efa80d4d..4cddb876dea 100644 --- a/tests/ported_static/stCreate2/test_returndatacopy_after_failing_create.py +++ b/tests/ported_static/stCreate2/test_returndatacopy_after_failing_create.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -28,6 +30,7 @@ @pytest.mark.pre_alloc_mutable def test_returndatacopy_after_failing_create( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Returndatacopy after failing create case due to 0xfd code.""" @@ -41,7 +44,6 @@ def test_returndatacopy_after_failing_create( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=47244640256, ) # Source: lll @@ -61,7 +63,7 @@ def test_returndatacopy_after_failing_create( sender=sender, to=contract_0, data=Bytes(""), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, ) post = {contract_0: Account(storage={0: 32, 1: 2})} diff --git a/tests/ported_static/stCreate2/test_returndatacopy_following_revert_in_create.py b/tests/ported_static/stCreate2/test_returndatacopy_following_revert_in_create.py index f1a7794a0bc..d32c442a827 100644 --- a/tests/ported_static/stCreate2/test_returndatacopy_following_revert_in_create.py +++ b/tests/ported_static/stCreate2/test_returndatacopy_following_revert_in_create.py @@ -13,9 +13,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,6 +33,7 @@ @pytest.mark.pre_alloc_mutable def test_returndatacopy_following_revert_in_create( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Returndatacopy_following_revert_in_create for CREATE2.""" @@ -46,7 +49,6 @@ def test_returndatacopy_following_revert_in_create( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=47244640256, ) pre[sender] = Account(balance=0x6400000000) @@ -77,7 +79,7 @@ def test_returndatacopy_following_revert_in_create( sender=sender, to=contract_0, data=Bytes(""), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, ) post = { diff --git a/tests/ported_static/stCreate2/test_returndatasize_following_successful_create.py b/tests/ported_static/stCreate2/test_returndatasize_following_successful_create.py index 7d2fd9b8afc..47e68d62cae 100644 --- a/tests/ported_static/stCreate2/test_returndatasize_following_successful_create.py +++ b/tests/ported_static/stCreate2/test_returndatasize_following_successful_create.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,6 +32,7 @@ @pytest.mark.pre_alloc_mutable def test_returndatasize_following_successful_create( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Returndatasize_following_successful_create for create2.""" @@ -43,7 +46,6 @@ def test_returndatasize_following_successful_create( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=47244640256, ) # Source: lll @@ -68,7 +70,7 @@ def test_returndatasize_following_successful_create( sender=sender, to=contract_0, data=Bytes(""), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, ) post = {contract_0: Account(storage={0: 0})} diff --git a/tests/ported_static/stCreate2/test_revert_depth_create2_oog.py b/tests/ported_static/stCreate2/test_revert_depth_create2_oog.py index 293fe888c7a..860237360e1 100644 --- a/tests/ported_static/stCreate2/test_revert_depth_create2_oog.py +++ b/tests/ported_static/stCreate2/test_revert_depth_create2_oog.py @@ -106,7 +106,6 @@ def test_revert_depth_create2_oog( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) pre[sender] = Account(balance=0xE8D4A51000) diff --git a/tests/ported_static/stCreate2/test_revert_depth_create2_oog_berlin.py b/tests/ported_static/stCreate2/test_revert_depth_create2_oog_berlin.py index e55f6b2a6ef..d39042da203 100644 --- a/tests/ported_static/stCreate2/test_revert_depth_create2_oog_berlin.py +++ b/tests/ported_static/stCreate2/test_revert_depth_create2_oog_berlin.py @@ -106,7 +106,6 @@ def test_revert_depth_create2_oog_berlin( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) pre[sender] = Account(balance=0xE8D4A51000) diff --git a/tests/ported_static/stCreate2/test_revert_depth_create_address_collision.py b/tests/ported_static/stCreate2/test_revert_depth_create_address_collision.py index 3cd45030619..9ecb01ac432 100644 --- a/tests/ported_static/stCreate2/test_revert_depth_create_address_collision.py +++ b/tests/ported_static/stCreate2/test_revert_depth_create_address_collision.py @@ -3,6 +3,12 @@ Ported from: state_tests/stCreate2/RevertDepthCreateAddressCollisionFiller.json + +@manually-enhanced: Do not overwrite. `tx_gas` raised on Amsterdam to +cover EIP-8037 NEW_ACCOUNT state-gas spill on the CREATE2-via-revert +path. Pre-EIP-8037 keeps the original [110_000, 170_000] tuned budgets; +post-state expectations unchanged on all forks. + """ import pytest @@ -106,7 +112,6 @@ def test_revert_depth_create_address_collision( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) pre[sender] = Account(balance=0xE8D4A51000) @@ -197,7 +202,11 @@ def test_revert_depth_create_address_collision( Hash(0xEA60), Hash(0x1EA60), ] + # EIP-8037 NEW_ACCOUNT state-gas spill on Amsterdam exceeds the + # original tuned tx_gas budgets; pre-EIP-8037 keeps the originals. tx_gas = [110000, 170000] + if fork.is_eip_enabled(8037): + tx_gas = [500_000, 700_000] tx_value = [1, 0] tx = Transaction( diff --git a/tests/ported_static/stCreate2/test_revert_depth_create_address_collision_berlin.py b/tests/ported_static/stCreate2/test_revert_depth_create_address_collision_berlin.py index 72fca5e4127..7e8a5f6ff4b 100644 --- a/tests/ported_static/stCreate2/test_revert_depth_create_address_collision_berlin.py +++ b/tests/ported_static/stCreate2/test_revert_depth_create_address_collision_berlin.py @@ -3,6 +3,12 @@ Ported from: state_tests/stCreate2/RevertDepthCreateAddressCollisionBerlinFiller.json + +@manually-enhanced: Do not overwrite. `tx_gas` raised on Amsterdam to +cover EIP-8037 NEW_ACCOUNT state-gas spill on the CREATE2-via-revert +path. Pre-EIP-8037 keeps the original [110_000, 170_000] tuned budgets; +post-state expectations unchanged on all forks. + """ import pytest @@ -108,7 +114,6 @@ def test_revert_depth_create_address_collision_berlin( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) pre[sender] = Account(balance=0xE8D4A51000) @@ -199,7 +204,11 @@ def test_revert_depth_create_address_collision_berlin( Hash(0xEA60), Hash(0x1EA60), ] + # EIP-8037 NEW_ACCOUNT state-gas spill on Amsterdam exceeds the + # original tuned tx_gas budgets; pre-EIP-8037 keeps the originals. tx_gas = [110000, 170000] + if fork.is_eip_enabled(8037): + tx_gas = [500_000, 700_000] tx_value = [1, 0] tx = Transaction( diff --git a/tests/ported_static/stCreate2/test_revert_opcode_create.py b/tests/ported_static/stCreate2/test_revert_opcode_create.py index 73c21783789..75dd02849b3 100644 --- a/tests/ported_static/stCreate2/test_revert_opcode_create.py +++ b/tests/ported_static/stCreate2/test_revert_opcode_create.py @@ -66,7 +66,6 @@ def test_revert_opcode_create( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) # Source: lll diff --git a/tests/ported_static/stCreate2/test_revert_opcode_in_create_returns_create2.py b/tests/ported_static/stCreate2/test_revert_opcode_in_create_returns_create2.py index 6ead4a1821d..21426aaf465 100644 --- a/tests/ported_static/stCreate2/test_revert_opcode_in_create_returns_create2.py +++ b/tests/ported_static/stCreate2/test_revert_opcode_in_create_returns_create2.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -28,6 +30,7 @@ @pytest.mark.pre_alloc_mutable def test_revert_opcode_in_create_returns_create2( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """RevertOpcodeInCreateReturns for CREATE2.""" @@ -41,7 +44,6 @@ def test_revert_opcode_in_create_returns_create2( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=47244640256, ) # Source: lll @@ -66,7 +68,7 @@ def test_revert_opcode_in_create_returns_create2( sender=sender, to=contract_0, data=Bytes(""), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, ) post = {contract_0: Account(storage={0: 32})} diff --git a/tests/ported_static/stCreateTest/test_code_in_constructor.py b/tests/ported_static/stCreateTest/test_code_in_constructor.py index 1a33dbe1033..9227244b39b 100644 --- a/tests/ported_static/stCreateTest/test_code_in_constructor.py +++ b/tests/ported_static/stCreateTest/test_code_in_constructor.py @@ -72,7 +72,6 @@ def test_code_in_constructor( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=4294967296, ) pre[sender] = Account(balance=0xBA1A9CE0BA1A9CE) diff --git a/tests/ported_static/stCreateTest/test_create2_call_data.py b/tests/ported_static/stCreateTest/test_create2_call_data.py index 3bc1b02f2ad..109656c0692 100644 --- a/tests/ported_static/stCreateTest/test_create2_call_data.py +++ b/tests/ported_static/stCreateTest/test_create2_call_data.py @@ -14,9 +14,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,6 +32,7 @@ @pytest.mark.pre_alloc_mutable def test_create2_call_data( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test if calldata is empty in initcode context.""" @@ -44,7 +47,7 @@ def test_create2_call_data( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=1000000, + gas_limit=3000000 if fork >= Amsterdam else 1000000, ) pre[sender] = Account(balance=0x5AF3107A4000) @@ -87,7 +90,7 @@ def test_create2_call_data( sender=sender, to=contract_0, data=Bytes(""), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, ) post = { diff --git a/tests/ported_static/stCreateTest/test_create_address_warm_after_fail.py b/tests/ported_static/stCreateTest/test_create_address_warm_after_fail.py index fc6321be024..80c2ba100bc 100644 --- a/tests/ported_static/stCreateTest/test_create_address_warm_after_fail.py +++ b/tests/ported_static/stCreateTest/test_create_address_warm_after_fail.py @@ -862,7 +862,12 @@ def test_create_address_warm_after_fail( Bytes("52c3fd24") + Hash(0x7), Bytes("52c3fd24") + Hash(0x11), ] - tx_gas = [16777216] + # The dispatcher writes to ~14 fresh storage slots; under EIP-8037 + # each slot's 32-byte cost is settled at frame end out of the + # reservoir/`gas_left` (~37_500 gas/slot on Amsterdam). Add that + # headroom — `sstore_state_gas` is 0 pre-EIP-8037, so the budget + # is unchanged on older forks. + tx_gas = [16777216 + 14 * Op.SSTORE(new_value=1).state_cost(fork)] tx_value = [0, 1] tx = Transaction( diff --git a/tests/ported_static/stCreateTest/test_create_collision_results.py b/tests/ported_static/stCreateTest/test_create_collision_results.py index 19688d523ef..148c283bfb7 100644 --- a/tests/ported_static/stCreateTest/test_create_collision_results.py +++ b/tests/ported_static/stCreateTest/test_create_collision_results.py @@ -68,7 +68,6 @@ def test_create_collision_results( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=4294967296, ) pre[sender] = Account(balance=0xBA1A9CE0BA1A9CE) diff --git a/tests/ported_static/stCreateTest/test_create_collision_to_empty2.py b/tests/ported_static/stCreateTest/test_create_collision_to_empty2.py index a8cb5f0f42e..811c004c64b 100644 --- a/tests/ported_static/stCreateTest/test_create_collision_to_empty2.py +++ b/tests/ported_static/stCreateTest/test_create_collision_to_empty2.py @@ -135,7 +135,6 @@ def test_create_collision_to_empty2( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) pre[sender] = Account(balance=0xE8D4A51000) @@ -253,7 +252,13 @@ def test_create_collision_to_empty2( Hash(contract_2, left_padding=True), Hash(contract_3, left_padding=True), ] - tx_gas = [600000, 54000] + # The `g1` budget is the gas-cliff variant: it must leave the + # callee with too little gas to complete CREATE, so the inner + # frame OOGs and the d0 attempt rolls back. EIP-8037 cuts + # `OPCODE_CREATE_BASE` from 32_000 to 9_000, so reduce the + # original 54_000 budget by the same delta to track the cliff. + create_base_delta = 32000 - fork.gas_costs().OPCODE_CREATE_BASE + tx_gas = [600000, 54000 - create_base_delta] tx_value = [0, 1] tx = Transaction( diff --git a/tests/ported_static/stCreateTest/test_create_contract_sstore_during_init.py b/tests/ported_static/stCreateTest/test_create_contract_sstore_during_init.py index a84b5c722a0..0437c397bf3 100644 --- a/tests/ported_static/stCreateTest/test_create_contract_sstore_during_init.py +++ b/tests/ported_static/stCreateTest/test_create_contract_sstore_during_init.py @@ -11,10 +11,12 @@ Address, Alloc, Environment, + Fork, StateTestFiller, Transaction, compute_create_address, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -27,6 +29,7 @@ @pytest.mark.valid_from("Cancun") def test_create_contract_sstore_during_init( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_create_contract_sstore_during_init.""" @@ -46,7 +49,7 @@ def test_create_contract_sstore_during_init( sender=sender, to=None, data=Op.SSTORE(key=0x0, value=0xFF), - gas_limit=150000, + gas_limit=2150000 if fork >= Amsterdam else 150000, ) post = { diff --git a/tests/ported_static/stCreateTest/test_create_e_contract_create_e_contract_in_init_tr.py b/tests/ported_static/stCreateTest/test_create_e_contract_create_e_contract_in_init_tr.py index d3288602010..1941710660c 100644 --- a/tests/ported_static/stCreateTest/test_create_e_contract_create_e_contract_in_init_tr.py +++ b/tests/ported_static/stCreateTest/test_create_e_contract_create_e_contract_in_init_tr.py @@ -3,6 +3,10 @@ Ported from: state_tests/stCreateTest/CREATE_EContractCreateEContractInInit_TrFiller.json +@manually-enhanced: Do not overwrite. Inner-CALL gas and tx `gas_limit` +bumped on Amsterdam to cover EIP-8037 state-gas spill; pre-EIP-8037 +unchanged. + """ import pytest @@ -15,6 +19,7 @@ Transaction, compute_create_address, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +36,16 @@ def test_create_e_contract_create_e_contract_in_init_tr( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_create_e_contract_create_e_contract_in_init_tr.""" + # EIP-8037 state-gas spill OoGs the 60k inner CALL. + inner_call_gas = 60000 + tx_gas_limit = 600000 + if fork.is_eip_enabled(8037): + inner_call_gas = 200000 + tx_gas_limit = 1_000_000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) contract_0 = Address(0xC94F5374FCE5EDBC8E2A8697C15331677E6EBF0B) sender = pre.fund_eoa(amount=0xE8D4A51000) @@ -59,7 +72,7 @@ def test_create_e_contract_create_e_contract_in_init_tr( to=None, data=Op.POP( Op.CALL( - gas=0xEA60, + gas=inner_call_gas, address=contract_0, value=0x0, args_offset=0x0, @@ -69,7 +82,7 @@ def test_create_e_contract_create_e_contract_in_init_tr( ) ) + Op.CREATE(value=0x0, offset=0x0, size=0x20), - gas_limit=600000, + gas_limit=tx_gas_limit, ) post = { diff --git a/tests/ported_static/stCreateTest/test_create_e_contract_create_ne_contract_in_init_tr.py b/tests/ported_static/stCreateTest/test_create_e_contract_create_ne_contract_in_init_tr.py index 67efdc53a18..7f0e1a3c9d8 100644 --- a/tests/ported_static/stCreateTest/test_create_e_contract_create_ne_contract_in_init_tr.py +++ b/tests/ported_static/stCreateTest/test_create_e_contract_create_ne_contract_in_init_tr.py @@ -3,6 +3,10 @@ Ported from: state_tests/stCreateTest/CREATE_EContractCreateNEContractInInit_TrFiller.json +@manually-enhanced: Do not overwrite. Inner-CALL gas and tx `gas_limit` +bumped on Amsterdam to cover EIP-8037 state-gas spill; pre-EIP-8037 +unchanged. + """ import pytest @@ -15,6 +19,7 @@ Transaction, compute_create_address, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +36,16 @@ def test_create_e_contract_create_ne_contract_in_init_tr( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_create_e_contract_create_ne_contract_in_init_tr.""" + # EIP-8037 state-gas spill OoGs the 60k inner CALL. + inner_call_gas = 60000 + tx_gas_limit = 600000 + if fork.is_eip_enabled(8037): + inner_call_gas = 200000 + tx_gas_limit = 1_000_000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) contract_0 = Address(0xC94F5374FCE5EDBC8E2A8697C15331677E6EBF0B) sender = pre.fund_eoa(amount=0xE8D4A51000) @@ -59,7 +72,7 @@ def test_create_e_contract_create_ne_contract_in_init_tr( to=None, data=Op.POP( Op.CALL( - gas=0xEA60, + gas=inner_call_gas, address=contract_0, value=0x0, args_offset=0x0, @@ -70,7 +83,7 @@ def test_create_e_contract_create_ne_contract_in_init_tr( ) + Op.MSTORE(offset=0x0, value=0x64600C6000556000526005601BF3) + Op.CREATE(value=0x0, offset=0x12, size=0xE), - gas_limit=600000, + gas_limit=tx_gas_limit, ) post = { diff --git a/tests/ported_static/stCreateTest/test_create_empty000_createin_init_code_transaction.py b/tests/ported_static/stCreateTest/test_create_empty000_createin_init_code_transaction.py index 6161631fc90..583a541a861 100644 --- a/tests/ported_static/stCreateTest/test_create_empty000_createin_init_code_transaction.py +++ b/tests/ported_static/stCreateTest/test_create_empty000_createin_init_code_transaction.py @@ -3,6 +3,10 @@ Ported from: state_tests/stCreateTest/CREATE_empty000CreateinInitCode_TransactionFiller.json +@manually-enhanced: Do not overwrite. Inner-CALL gas and tx `gas_limit` +bumped on Amsterdam to cover EIP-8037 state-gas spill; pre-EIP-8037 +unchanged. + """ import pytest @@ -15,6 +19,7 @@ Transaction, compute_create_address, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +36,16 @@ def test_create_empty000_createin_init_code_transaction( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_create_empty000_createin_init_code_transaction.""" + # EIP-8037 state-gas spill OoGs the 60k inner CALL. + inner_call_gas = 60000 + tx_gas_limit = 600000 + if fork.is_eip_enabled(8037): + inner_call_gas = 200000 + tx_gas_limit = 1_000_000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) contract_0 = Address(0xC94F5374FCE5EDBC8E2A8697C15331677E6EBF0B) sender = pre.fund_eoa(amount=0xE8D4A51000) @@ -59,7 +72,7 @@ def test_create_empty000_createin_init_code_transaction( to=None, data=Op.POP( Op.CALL( - gas=0xEA60, + gas=inner_call_gas, address=contract_0, value=0x0, args_offset=0x0, @@ -69,7 +82,7 @@ def test_create_empty000_createin_init_code_transaction( ) ) + Op.CREATE(value=0x0, offset=0x0, size=0x0), - gas_limit=600000, + gas_limit=tx_gas_limit, ) post = { diff --git a/tests/ported_static/stCreateTest/test_create_oo_gafter_init_code.py b/tests/ported_static/stCreateTest/test_create_oo_gafter_init_code.py index 8a4e086e17c..76b1fd09653 100644 --- a/tests/ported_static/stCreateTest/test_create_oo_gafter_init_code.py +++ b/tests/ported_static/stCreateTest/test_create_oo_gafter_init_code.py @@ -3,6 +3,9 @@ Ported from: state_tests/stCreateTest/CreateOOGafterInitCodeFiller.json +@manually-enhanced: Do not overwrite. tx_gas[1] is tuned to barely +succeed CREATE on Cancun; on Amsterdam EIP-8037 the NEW_ACCOUNT +state-gas spills, so lift the budget by Fork.oog_budget_lift. """ import pytest @@ -67,7 +70,6 @@ def test_create_oo_gafter_init_code( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) # Source: lll @@ -107,7 +109,20 @@ def test_create_oo_gafter_init_code( tx_data = [ Bytes(""), ] - tx_gas = [54000, 55000] + # Lift both entries on Amsterdam so the test still exercises its + # named scenario. With only tx_gas[1] lifted, g=0 OoG'd at CREATE + # dispatch (NEW_ACCOUNT state-gas spill) before init code ever ran — + # the assertion still passes (`NONEXISTENT` either way) but the + # failure mode is "dispatch-time OoG" instead of "OoG after init + # code". A simple `fork.oog_budget_lift(creates_before_oog=1)` (183600) + # is *too* generous and pushes g=0 past the deploy threshold; the + # Cancun 1000-gas gap between g=0 and g=1 collapses on Amsterdam + # because once dispatch is cleared, the 5-byte init code is cheap + # enough to always complete. The value below is the middle of the + # empirically-safe range (166499, 167000) where g=0 still OoGs at + # dispatch *and* g=1 just clears the deploy threshold (~221.5k). + _oog_lift = 166_750 if fork.is_eip_enabled(8037) else 0 + tx_gas = [54000 + _oog_lift, 55000 + _oog_lift] tx = Transaction( sender=sender, diff --git a/tests/ported_static/stCreateTest/test_create_oo_gafter_init_code_returndata2.py b/tests/ported_static/stCreateTest/test_create_oo_gafter_init_code_returndata2.py index baf51e193f4..8b4ef73a768 100644 --- a/tests/ported_static/stCreateTest/test_create_oo_gafter_init_code_returndata2.py +++ b/tests/ported_static/stCreateTest/test_create_oo_gafter_init_code_returndata2.py @@ -3,6 +3,10 @@ Ported from: state_tests/stCreateTest/CreateOOGafterInitCodeReturndata2Filler.json +@manually-enhanced: Do not overwrite. tx_gas[1] is tuned to barely +finish CREATE + two post-deploy SSTOREs on Cancun; on Amsterdam the +NEW_ACCOUNT and SSTORE-set state-gas spills, so lift the budget by +Fork.oog_budget_lift. """ import pytest @@ -70,7 +74,6 @@ def test_create_oo_gafter_init_code_returndata2( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) pre[sender] = Account(balance=0xE8D4A51000) @@ -117,7 +120,11 @@ def test_create_oo_gafter_init_code_returndata2( tx_data = [ Bytes(""), ] - tx_gas = [54000, 95000] + tx_gas = [ + 54000, + 95000 + + fork.oog_budget_lift(creates_before_oog=1, sstores_before_oog=2), + ] tx = Transaction( sender=sender, diff --git a/tests/ported_static/stCreateTest/test_create_oo_gafter_init_code_revert2.py b/tests/ported_static/stCreateTest/test_create_oo_gafter_init_code_revert2.py index d7a8fbcadff..b6178a31982 100644 --- a/tests/ported_static/stCreateTest/test_create_oo_gafter_init_code_revert2.py +++ b/tests/ported_static/stCreateTest/test_create_oo_gafter_init_code_revert2.py @@ -73,21 +73,31 @@ def test_create_oo_gafter_init_code_revert2( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) pre[sender] = Account(balance=0xE8D4A51000) + # The two CALL budgets below straddle the callee's CREATE base + # charge: contract_1 sits ~1_000 gas above so its CREATE+REVERT + # completes; contract_2 sits ~1_000 gas below so it OOGs at + # CREATE and contract_2 reads zero from the un-written return + # buffer. Derived from `fork.gas_costs().OPCODE_CREATE_BASE` + # (32_000 pre-EIP-8037, 9_000 on Amsterdam+) so the cliff stays + # correct as the constant evolves. + create_base = fork.gas_costs().OPCODE_CREATE_BASE + contract_1_call_gas = create_base + 1000 + contract_2_call_gas = create_base - 1000 + # Source: lll # { (CALL (GAS) (CALLDATALOAD 0) 0 0 0 0 0) } contract_0 = pre.deploy_contract( # noqa: F841 code=Op.CALL( gas=Op.GAS, - address=Op.CALLDATALOAD(offset=0x0), - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, + address=Op.CALLDATALOAD(offset=0), + value=0, + args_offset=0, + args_size=0, + ret_offset=0, + ret_size=0, ) + Op.STOP, balance=0xE8D4A51000, @@ -97,9 +107,9 @@ def test_create_oo_gafter_init_code_revert2( # Source: lll # { (MSTORE 0 0x6460016001556000526005601bf3) (CREATE 0 18 14) (REVERT 0 32) } # noqa: E501 contract_3 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=0x6460016001556000526005601BF3) - + Op.POP(Op.CREATE(value=0x0, offset=0x12, size=0xE)) - + Op.REVERT(offset=0x0, size=0x20) + code=Op.MSTORE(offset=0, value=0x6460016001556000526005601BF3) + + Op.POP(Op.CREATE(value=0, offset=18, size=14)) + + Op.REVERT(offset=0, size=32) + Op.STOP, nonce=0, address=Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B), # noqa: E501 @@ -109,16 +119,16 @@ def test_create_oo_gafter_init_code_revert2( contract_1 = pre.deploy_contract( # noqa: F841 code=Op.POP( Op.CALL( - gas=0x80E8, + gas=contract_1_call_gas, address=0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B, - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x20, + value=0, + args_offset=0, + args_size=0, + ret_offset=0, + ret_size=32, ) ) - + Op.SSTORE(key=0x1, value=Op.MLOAD(offset=0x0)) + + Op.SSTORE(key=1, value=Op.MLOAD(offset=0)) + Op.STOP, storage={1: 255}, nonce=0, @@ -129,16 +139,16 @@ def test_create_oo_gafter_init_code_revert2( contract_2 = pre.deploy_contract( # noqa: F841 code=Op.POP( Op.CALL( - gas=0x59D8, + gas=contract_2_call_gas, address=0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B, - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x20, + value=0, + args_offset=0, + args_size=0, + ret_offset=0, + ret_size=32, ) ) - + Op.SSTORE(key=0x1, value=Op.MLOAD(offset=0x0)) + + Op.SSTORE(key=1, value=Op.MLOAD(offset=0)) + Op.STOP, storage={1: 255}, nonce=0, diff --git a/tests/ported_static/stCreateTest/test_create_oog_from_call_refunds.py b/tests/ported_static/stCreateTest/test_create_oog_from_call_refunds.py index f1ff087ac10..665dccfb452 100644 --- a/tests/ported_static/stCreateTest/test_create_oog_from_call_refunds.py +++ b/tests/ported_static/stCreateTest/test_create_oog_from_call_refunds.py @@ -231,7 +231,6 @@ def test_create_oog_from_call_refunds( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=4294967296, ) pre[sender] = Account(balance=0x3D0900, nonce=1) @@ -936,7 +935,37 @@ def test_create_oog_from_call_refunds( address=Address(0x000000000000000000000000000000000000007A), # noqa: E501 ) - expect_entries_: list[dict] = [ + expect_entries_: list[dict] = [] + if fork.is_eip_enabled(8037): + expect_entries_.append( + { + "indexes": { + "data": [ + 1, + 2, + 4, + 5, + 7, + 8, + 10, + 11, + 13, + 14, + 16, + 17, + 19, + 20, + 22, + 23, + ], + "gas": -1, + "value": -1, + }, + "network": [">=Cancun"], + "result": {sender: Account(nonce=2)}, + } + ) + expect_entries_ += [ { "indexes": {"data": [0, 9, 3, 6], "gas": -1, "value": -1}, "network": [">=Cancun"], diff --git a/tests/ported_static/stCreateTest/test_create_results.py b/tests/ported_static/stCreateTest/test_create_results.py index 67af6122a4d..3d9a852d13e 100644 --- a/tests/ported_static/stCreateTest/test_create_results.py +++ b/tests/ported_static/stCreateTest/test_create_results.py @@ -215,7 +215,6 @@ def test_create_results( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=4294967296, ) pre[sender] = Account(balance=0xBA1A9CE0BA1A9CE) diff --git a/tests/ported_static/stCreateTest/test_create_transaction_call_data.py b/tests/ported_static/stCreateTest/test_create_transaction_call_data.py index e30600d6af6..7c5810b6657 100644 --- a/tests/ported_static/stCreateTest/test_create_transaction_call_data.py +++ b/tests/ported_static/stCreateTest/test_create_transaction_call_data.py @@ -5,6 +5,10 @@ Ported from: state_tests/stCreateTest/CreateTransactionCallDataFiller.yml + +@manually-enhanced: Do not overwrite. tx_gas was raised from 100 000 to +500 000 so the CREATE path can afford its EIP-8037 NEW_ACCOUNT state +gas on Amsterdam (post-state expectations are unchanged on all forks). """ import pytest @@ -111,7 +115,12 @@ def test_create_transaction_call_data( Op.CODECOPY(dest_offset=Op.DUP1, offset=0x0, size=Op.CODESIZE) + Op.RETURN(offset=0x0, size=Op.CODESIZE), ] - tx_gas = [100000] + # EIP-8037 NEW_ACCOUNT + per-byte state-gas spill on Amsterdam; + # pre-EIP-8037 keeps the original 100 000 budget. + outer_tx_gas = 100_000 + if fork.is_eip_enabled(8037): + outer_tx_gas = 500_000 + tx_gas = [outer_tx_gas] tx = Transaction( sender=sender, diff --git a/tests/ported_static/stCreateTest/test_create_transaction_high_nonce.py b/tests/ported_static/stCreateTest/test_create_transaction_high_nonce.py index 6ba82dc452b..da3ad39d2b8 100644 --- a/tests/ported_static/stCreateTest/test_create_transaction_high_nonce.py +++ b/tests/ported_static/stCreateTest/test_create_transaction_high_nonce.py @@ -5,6 +5,12 @@ Ported from: state_tests/stCreateTest/CreateTransactionHighNonceFiller.yml + +@manually-enhanced: Do not overwrite. `tx_gas` was raised from 90 000 +to 500 000 so the transaction clears the EIP-8037 intrinsic-gas floor +on Amsterdam and the validator can actually reach the NONCE_IS_MAX +check the test asserts. Pre-Amsterdam the floor is lower, so the same +budget still triggers the same exception path. """ import pytest @@ -85,7 +91,15 @@ def test_create_transaction_high_nonce( tx_data = [ Op.RETURN(offset=0x0, size=0x1), ] - tx_gas = [90000] + # Original budget (90 000) is below the EIP-8037 intrinsic-gas + # floor for a create tx on Amsterdam, so the tx is rejected for + # `INTRINSIC_GAS_TOO_LOW` before the NONCE_IS_MAX check this test + # asserts ever runs. Bump on Amsterdam to clear the floor; pre- + # EIP-8037 forks keep the original. + nonce_check_tx_gas = 90000 + if fork.is_eip_enabled(8037): + nonce_check_tx_gas = 500000 + tx_gas = [nonce_check_tx_gas] tx_value = [0, 1] tx = Transaction( diff --git a/tests/ported_static/stCreateTest/test_create_transaction_refund_ef.py b/tests/ported_static/stCreateTest/test_create_transaction_refund_ef.py index a9d2329d3bb..30c434c8b68 100644 --- a/tests/ported_static/stCreateTest/test_create_transaction_refund_ef.py +++ b/tests/ported_static/stCreateTest/test_create_transaction_refund_ef.py @@ -13,10 +13,12 @@ Address, Alloc, Environment, + Fork, StateTestFiller, Transaction, compute_create_address, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,6 +32,7 @@ @pytest.mark.pre_alloc_mutable def test_create_transaction_refund_ef( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test combination of gas refund and EF-prefixed create transaction...""" @@ -44,7 +47,7 @@ def test_create_transaction_refund_ef( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=1000000, + gas_limit=3000000 if fork >= Amsterdam else 1000000, ) pre[sender] = Account(balance=0x5AF3107A4000) @@ -75,7 +78,7 @@ def test_create_transaction_refund_ef( ) + Op.MSTORE8(offset=0x0, value=0xEF) + Op.RETURN(offset=0x0, size=0x1), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, ) post = { diff --git a/tests/ported_static/stCreateTest/test_transaction_collision_to_empty_but_code.py b/tests/ported_static/stCreateTest/test_transaction_collision_to_empty_but_code.py index 36f9cdd064e..41fc7ade876 100644 --- a/tests/ported_static/stCreateTest/test_transaction_collision_to_empty_but_code.py +++ b/tests/ported_static/stCreateTest/test_transaction_collision_to_empty_but_code.py @@ -12,6 +12,7 @@ Address, Alloc, Environment, + Header, StateTestFiller, Transaction, ) @@ -136,4 +137,28 @@ def test_transaction_collision_to_empty_but_code( error=_exc, ) - state_test(env=env, pre=pre, post=post, tx=tx) + # On collision, all execution gas is reclassified to regular and the + # tx-time state reservoir is restored. Under EIP-8037 2D gas this + # gives header.gas_used = max(intrinsic_regular + execution_gas, + # intrinsic_state); pre-EIP-8037 the state component is zero, so the + # same expression collapses to tx.gas. + intrinsic_total = fork.transaction_intrinsic_cost_calculator()( + calldata=bytes(tx_data[d]), + contract_creation=True, + ) + intrinsic_state = fork.create_state_gas() + intrinsic_regular = intrinsic_total - intrinsic_state + execution_gas = tx_gas[g] - intrinsic_total + expected_header_gas_used = max( + intrinsic_regular + execution_gas, intrinsic_state + ) + + state_test( + env=env, + pre=pre, + post=post, + tx=tx, + blockchain_test_header_verify=Header( + gas_used=expected_header_gas_used, + ), + ) diff --git a/tests/ported_static/stCreateTest/test_transaction_collision_to_empty_but_nonce.py b/tests/ported_static/stCreateTest/test_transaction_collision_to_empty_but_nonce.py index 90368103d9b..997575c4479 100644 --- a/tests/ported_static/stCreateTest/test_transaction_collision_to_empty_but_nonce.py +++ b/tests/ported_static/stCreateTest/test_transaction_collision_to_empty_but_nonce.py @@ -12,6 +12,7 @@ Address, Alloc, Environment, + Header, StateTestFiller, Transaction, ) @@ -104,4 +105,28 @@ def test_transaction_collision_to_empty_but_nonce( contract_0: Account(storage={1: 0}, nonce=1), } - state_test(env=env, pre=pre, post=post, tx=tx) + # On collision, all execution gas is reclassified to regular and the + # tx-time state reservoir is restored. Under EIP-8037 2D gas this + # gives header.gas_used = max(intrinsic_regular + execution_gas, + # intrinsic_state); pre-EIP-8037 the state component is zero, so the + # same expression collapses to tx.gas. + intrinsic_total = fork.transaction_intrinsic_cost_calculator()( + calldata=bytes(tx_data[d]), + contract_creation=True, + ) + intrinsic_state = fork.create_state_gas() + intrinsic_regular = intrinsic_total - intrinsic_state + execution_gas = tx_gas[g] - intrinsic_total + expected_header_gas_used = max( + intrinsic_regular + execution_gas, intrinsic_state + ) + + state_test( + env=env, + pre=pre, + post=post, + tx=tx, + blockchain_test_header_verify=Header( + gas_used=expected_header_gas_used, + ), + ) diff --git a/tests/ported_static/stDelegatecallTestHomestead/test_call_lose_gas_oog.py b/tests/ported_static/stDelegatecallTestHomestead/test_call_lose_gas_oog.py index 6c3d024c7a4..b9fbc3e386c 100644 --- a/tests/ported_static/stDelegatecallTestHomestead/test_call_lose_gas_oog.py +++ b/tests/ported_static/stDelegatecallTestHomestead/test_call_lose_gas_oog.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -28,6 +30,7 @@ @pytest.mark.pre_alloc_mutable def test_call_lose_gas_oog( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_call_lose_gas_oog.""" @@ -40,7 +43,6 @@ def test_call_lose_gas_oog( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) addr = pre.fund_eoa(amount=7000) # noqa: F841 @@ -72,7 +74,7 @@ def test_call_lose_gas_oog( sender=sender, to=target, data=Bytes(""), - gas_limit=200000, + gas_limit=2200000 if fork >= Amsterdam else 200000, value=10, ) diff --git a/tests/ported_static/stDelegatecallTestHomestead/test_callcode_lose_gas_oog.py b/tests/ported_static/stDelegatecallTestHomestead/test_callcode_lose_gas_oog.py index 37d33839061..1327e8243c7 100644 --- a/tests/ported_static/stDelegatecallTestHomestead/test_callcode_lose_gas_oog.py +++ b/tests/ported_static/stDelegatecallTestHomestead/test_callcode_lose_gas_oog.py @@ -71,7 +71,6 @@ def test_callcode_lose_gas_oog( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) addr = pre.fund_eoa(amount=7000) # noqa: F841 diff --git a/tests/ported_static/stDelegatecallTestHomestead/test_delegatecall_in_initcode_to_existing_contract_oog.py b/tests/ported_static/stDelegatecallTestHomestead/test_delegatecall_in_initcode_to_existing_contract_oog.py index b9d882a9583..a2846ada095 100644 --- a/tests/ported_static/stDelegatecallTestHomestead/test_delegatecall_in_initcode_to_existing_contract_oog.py +++ b/tests/ported_static/stDelegatecallTestHomestead/test_delegatecall_in_initcode_to_existing_contract_oog.py @@ -13,10 +13,12 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, compute_create_address, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -32,6 +34,7 @@ @pytest.mark.pre_alloc_mutable def test_delegatecall_in_initcode_to_existing_contract_oog( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_delegatecall_in_initcode_to_existing_contract_oog.""" @@ -48,7 +51,7 @@ def test_delegatecall_in_initcode_to_existing_contract_oog( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=1000000, + gas_limit=3000000 if fork >= Amsterdam else 1000000, ) pre[sender] = Account(balance=0x2386F26FC10000) @@ -81,7 +84,7 @@ def test_delegatecall_in_initcode_to_existing_contract_oog( sender=sender, to=contract_0, data=Bytes(""), - gas_limit=153096, + gas_limit=2153096 if fork >= Amsterdam else 153096, ) post = { diff --git a/tests/ported_static/stDelegatecallTestHomestead/test_delegatecall_oo_gin_call.py b/tests/ported_static/stDelegatecallTestHomestead/test_delegatecall_oo_gin_call.py index f56089b6c0a..2a64be1ff60 100644 --- a/tests/ported_static/stDelegatecallTestHomestead/test_delegatecall_oo_gin_call.py +++ b/tests/ported_static/stDelegatecallTestHomestead/test_delegatecall_oo_gin_call.py @@ -42,7 +42,6 @@ def test_delegatecall_oo_gin_call( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=30000000, ) # Source: raw diff --git a/tests/ported_static/stEIP1153_transientStorage/test_10_revert_undoes_store_after_return.py b/tests/ported_static/stEIP1153_transientStorage/test_10_revert_undoes_store_after_return.py index 888bace084d..b172bab10cf 100644 --- a/tests/ported_static/stEIP1153_transientStorage/test_10_revert_undoes_store_after_return.py +++ b/tests/ported_static/stEIP1153_transientStorage/test_10_revert_undoes_store_after_return.py @@ -42,7 +42,6 @@ def test_10_revert_undoes_store_after_return( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=4503599627370496, ) # Source: yul diff --git a/tests/ported_static/stEIP1153_transientStorage/test_14_revert_after_nested_staticcall.py b/tests/ported_static/stEIP1153_transientStorage/test_14_revert_after_nested_staticcall.py index c68b03a7c0c..06ddaf015a0 100644 --- a/tests/ported_static/stEIP1153_transientStorage/test_14_revert_after_nested_staticcall.py +++ b/tests/ported_static/stEIP1153_transientStorage/test_14_revert_after_nested_staticcall.py @@ -42,7 +42,6 @@ def test_14_revert_after_nested_staticcall( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=4503599627370496, ) # Source: yul diff --git a/tests/ported_static/stEIP150Specific/test_execute_call_that_ask_fore_gas_then_trabsaction_has.py b/tests/ported_static/stEIP150Specific/test_execute_call_that_ask_fore_gas_then_trabsaction_has.py index 4e96ff618dd..f85f2c52531 100644 --- a/tests/ported_static/stEIP150Specific/test_execute_call_that_ask_fore_gas_then_trabsaction_has.py +++ b/tests/ported_static/stEIP150Specific/test_execute_call_that_ask_fore_gas_then_trabsaction_has.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,6 +32,7 @@ @pytest.mark.pre_alloc_mutable def test_execute_call_that_ask_fore_gas_then_trabsaction_has( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_execute_call_that_ask_fore_gas_then_trabsaction_has.""" @@ -75,7 +78,7 @@ def test_execute_call_that_ask_fore_gas_then_trabsaction_has( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, ) post = {addr: Account(storage={1: 12})} diff --git a/tests/ported_static/stEIP1559/test_base_fee_diff_places_osaka.py b/tests/ported_static/stEIP1559/test_base_fee_diff_places_osaka.py index 8eb133b8c6e..219a1153241 100644 --- a/tests/ported_static/stEIP1559/test_base_fee_diff_places_osaka.py +++ b/tests/ported_static/stEIP1559/test_base_fee_diff_places_osaka.py @@ -268,7 +268,6 @@ def test_base_fee_diff_places( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=4503599627370496, ) # Source: yul diff --git a/tests/ported_static/stEIP1559/test_gas_price_diff_places_osaka.py b/tests/ported_static/stEIP1559/test_gas_price_diff_places_osaka.py index 873dae98658..6ad703725f4 100644 --- a/tests/ported_static/stEIP1559/test_gas_price_diff_places_osaka.py +++ b/tests/ported_static/stEIP1559/test_gas_price_diff_places_osaka.py @@ -268,7 +268,6 @@ def test_gas_price_diff_places( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=4503599627370496, ) # Source: yul diff --git a/tests/ported_static/stEIP2930/test_address_opcodes.py b/tests/ported_static/stEIP2930/test_address_opcodes.py index 2461399d90c..2a55b6043b5 100644 --- a/tests/ported_static/stEIP2930/test_address_opcodes.py +++ b/tests/ported_static/stEIP2930/test_address_opcodes.py @@ -345,7 +345,6 @@ def test_address_opcodes( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=71794957647893862, ) # Source: lll diff --git a/tests/ported_static/stEIP2930/test_coinbase_t01.py b/tests/ported_static/stEIP2930/test_coinbase_t01.py index 62e00904240..b6c08afc361 100644 --- a/tests/ported_static/stEIP2930/test_coinbase_t01.py +++ b/tests/ported_static/stEIP2930/test_coinbase_t01.py @@ -73,7 +73,6 @@ def test_coinbase_t01( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=100, - gas_limit=71794957647893862, ) pre[coinbase] = Account(balance=0, nonce=1) diff --git a/tests/ported_static/stEIP2930/test_coinbase_t2.py b/tests/ported_static/stEIP2930/test_coinbase_t2.py index 22716cfa65b..ea96482ef8b 100644 --- a/tests/ported_static/stEIP2930/test_coinbase_t2.py +++ b/tests/ported_static/stEIP2930/test_coinbase_t2.py @@ -67,7 +67,6 @@ def test_coinbase_t2( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=100, - gas_limit=71794957647893862, ) pre[coinbase] = Account(balance=0, nonce=1) diff --git a/tests/ported_static/stEIP2930/test_manual_create.py b/tests/ported_static/stEIP2930/test_manual_create.py index 057b623c7e0..5ca118c9646 100644 --- a/tests/ported_static/stEIP2930/test_manual_create.py +++ b/tests/ported_static/stEIP2930/test_manual_create.py @@ -3,6 +3,15 @@ Ported from: state_tests/stEIP2930/manualCreateFiller.yml + +@manually-enhanced: Do not overwrite. The three parametrizations of +this test measure regular gas around a fresh SSTORE-set inside a +CREATE-deployed contract. EIP-8037 splits the Cancun-era SSTORE-set +base into a smaller regular portion plus 37 568 state-gas; with an +empty reservoir the full state-gas spills into regular gas and +`Op.GAS` reads +20 468 = 37 568 - 17 100 compared to Cancun. Bake +that delta into both `[">=Cancun"]` expect entries fork-conditionally +via `Op.SSTORE(new_value=1).state_cost(fork) - 17100`. """ import pytest @@ -81,13 +90,21 @@ def test_manual_create( pre[sender] = Account(balance=0x1000000000000000000, nonce=1) + # EIP-8037 SSTORE-set spillover: +20 468 regular gas per fresh set + # when the reservoir is empty. + sstore_set_delta = ( + (Op.SSTORE(new_value=1).state_cost(fork) - 17100) + if fork.is_eip_enabled(8037) + else 0 + ) + expect_entries_: list[dict] = [ { "indexes": {"data": [2], "gas": -1, "value": -1}, "network": [">=Cancun"], "result": { compute_create_address(address=sender, nonce=1): Account( - storage={0: 20008, 1: 106} + storage={0: 20008 + sstore_set_delta, 1: 106} ), }, }, @@ -96,7 +113,7 @@ def test_manual_create( "network": [">=Cancun"], "result": { compute_create_address(address=sender, nonce=1): Account( - storage={0: 22108, 1: 106} + storage={0: 22108 + sstore_set_delta, 1: 106} ), }, }, @@ -139,7 +156,13 @@ def test_manual_create( + Op.SSTORE(key=0x0, value=Op.SUB) + Op.STOP, ] - tx_gas = [400000] + # EIP-8037 NEW_ACCOUNT state-gas spill into regular gas on + # Amsterdam exceeds the original 400 000 budget. Pre-EIP-8037 + # keeps the original value. + outer_tx_gas = 400_000 + if fork.is_eip_enabled(8037): + outer_tx_gas = 1_000_000 + tx_gas = [outer_tx_gas] tx_access_lists: dict[int, list] = { 0: [ AccessList( diff --git a/tests/ported_static/stEIP2930/test_storage_costs.py b/tests/ported_static/stEIP2930/test_storage_costs.py index 2c5f0f91686..03bb03dd189 100644 --- a/tests/ported_static/stEIP2930/test_storage_costs.py +++ b/tests/ported_static/stEIP2930/test_storage_costs.py @@ -3,6 +3,20 @@ Ported from: state_tests/stEIP2930/storageCostsFiller.yml + +@manually-enhanced: Do not overwrite. The SSTORE gas measurements in +this test were authored against the Cancun-era SSTORE-set base cost +of 20 000 (per EIP-2200). EIP-8037 splits that cost into a smaller +regular portion (~2 900) plus a per-storage state-gas charge of +`STATE_BYTES_PER_STORAGE_SET (32) * COST_PER_STATE_BYTE (1174) = +37 568`. When the state-gas reservoir is empty — as it is here, since +the tests don't pre-allocate state-gas budget — the full state-gas +spills back into regular gas, so `Op.GAS` observes +`+37 568 - 17 100 = +20 468` regular gas per fresh SSTORE-set +compared to Cancun. Bake that fork-conditional delta into the +expected post-state values for the 10 parametrizations whose measured +SSTORE writes triggered the spill; the remaining entries (SLOAD-only, +no-op SSTOREs) are unaffected. """ import pytest @@ -282,7 +296,6 @@ def test_storage_costs( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=71794957647893862, ) # Source: lll @@ -648,16 +661,38 @@ def test_storage_costs( address=Address(0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC), # noqa: E501 ) + # EIP-8037 splits the SSTORE-set base cost (Cancun: 20 000 regular) + # into a smaller regular portion plus per-storage state-gas. When + # the state-gas reservoir is empty for these tests, the full state + # gas spills into regular gas, so Op.GAS sees +20 468 per fresh + # SSTORE-set compared to Cancun (=37 568 state-gas - 17 100 base + # regular drop). Apply that delta to the 10 measurements that + # trigger a fresh-set spill; the SLOAD-only and no-op SSTORE + # entries below are unchanged. + sstore_set_delta = ( + (Op.SSTORE(new_value=1).state_cost(fork) - 17100) + if fork.is_eip_enabled(8037) + else 0 + ) + expect_entries_: list[dict] = [ { "indexes": {"data": [0, 35], "gas": -1, "value": -1}, "network": [">=Cancun"], - "result": {contract_0: Account(storage={0: 2, 1: 20003})}, + "result": { + contract_0: Account( + storage={0: 2, 1: 20003 + sstore_set_delta} + ) + }, }, { "indexes": {"data": [6, 12, 18], "gas": -1, "value": -1}, "network": [">=Cancun"], - "result": {contract_0: Account(storage={0: 2, 1: 22103})}, + "result": { + contract_0: Account( + storage={0: 2, 1: 22103 + sstore_set_delta} + ) + }, }, { "indexes": {"data": [3], "gas": -1, "value": -1}, @@ -722,7 +757,11 @@ def test_storage_costs( { "indexes": {"data": [28, 29], "gas": -1, "value": -1}, "network": [">=Cancun"], - "result": {contract_8: Account(storage={0: 2, 1: 20000})}, + "result": { + contract_8: Account( + storage={0: 2, 1: 20000 + sstore_set_delta} + ) + }, }, { "indexes": {"data": [30, 31], "gas": -1, "value": -1}, @@ -734,7 +773,12 @@ def test_storage_costs( "network": [">=Cancun"], "result": { contract_10: Account( - storage={0: 2, 1: 100, 2: 20000, 24743: 57005} + storage={ + 0: 2, + 1: 100, + 2: 20000 + sstore_set_delta, + 24743: 57005, + } ) }, }, @@ -743,7 +787,12 @@ def test_storage_costs( "network": [">=Cancun"], "result": { contract_10: Account( - storage={0: 2, 1: 2100, 2: 22100, 24743: 57005} + storage={ + 0: 2, + 1: 2100, + 2: 22100 + sstore_set_delta, + 24743: 57005, + } ), }, }, @@ -789,7 +838,15 @@ def test_storage_costs( Bytes("693c6139") + Hash(0xFFF), Bytes("693c6139") + Hash(0x0), ] - tx_gas = [400000] + # The test's CALL chain does two SSTORE-sets in each measured + # contract; EIP-8037 spills both state-gas charges into regular gas + # when the reservoir is empty, pushing total consumption over the + # original 400 000 budget. Bump on EIP-8037; pre-EIP-8037 keeps the + # original value. + outer_tx_gas = 400_000 + if fork.is_eip_enabled(8037): + outer_tx_gas = 1_000_000 + tx_gas = [outer_tx_gas] tx_value = [100000] tx_access_lists: dict[int, list] = { 0: [ diff --git a/tests/ported_static/stEIP2930/test_varied_context.py b/tests/ported_static/stEIP2930/test_varied_context.py index 20f3bd03c20..3c78c50a4c9 100644 --- a/tests/ported_static/stEIP2930/test_varied_context.py +++ b/tests/ported_static/stEIP2930/test_varied_context.py @@ -3,6 +3,22 @@ Ported from: state_tests/stEIP2930/variedContextFiller.yml + +@manually-enhanced: Do not overwrite. 28 parametrizations of this +test measure gas consumption around SSTORE/CALL/SELFDESTRUCT in +various access-list contexts. EIP-8037 splits the Cancun-era base +costs (SSTORE-set 20 000, CALL-new-account 25 000, SELFDESTRUCT-new- +beneficiary 25 000) into smaller regular portions plus per-storage +or per-new-account state-gas charges. When the reservoir is empty — +the case here, since no state-gas budget is pre-allocated — the +full state-gas spills back into regular gas and Op.GAS reads three +distinct deltas: + +20 468 per fresh SSTORE-set + +106 488 per NEW_ACCOUNT (CALL with value or SELFDESTRUCT) + +126 956 = both, for SELFDESTRUCT-with-write paths +Each affected post-state literal is bumped by the appropriate +delta fork-conditionally; pre-EIP-8037 forks use the original +values. """ import pytest @@ -302,7 +318,6 @@ def test_varied_context( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=71794957647893862, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -1322,33 +1337,73 @@ def test_varied_context( address=Address(0x0000000000000000000000000000000000001016), # noqa: E501 ) + # EIP-8037 splits SSTORE-set, NEW_ACCOUNT call value transfer, and + # SELFDESTRUCT new-beneficiary base costs into state-gas portions. + # With an empty reservoir (the case here), the full state-gas + # spills into regular gas, which Op.GAS observes. + # sstore-set spill: +37 568 - 17 100 = +20 468 per fresh set + # new-account spill: +131 488 - 25 000 = +106 488 per CALL + # with value to a non-alive account, and + # per SELFDESTRUCT to non-alive beneficiary + # suicide-write spill: +126 956 = both deltas combined + sstore_set_delta = ( + (Op.SSTORE(new_value=1).state_cost(fork) - 17100) + if fork.is_eip_enabled(8037) + else 0 + ) + new_account_delta = ( + (fork.create_state_gas() - 25000) if fork.is_eip_enabled(8037) else 0 + ) + suicide_write_delta = sstore_set_delta + new_account_delta + expect_entries_: list[dict] = [ { "indexes": {"data": [0], "gas": -1, "value": -1}, "network": [">=Cancun"], - "result": {contract_0: Account(storage={0: 2, 1: 20003, 2: 107})}, + "result": { + contract_0: Account( + storage={0: 2, 1: (20003 + sstore_set_delta), 2: 107} + ) + }, }, { "indexes": {"data": [1], "gas": -1, "value": -1}, "network": [">=Cancun"], - "result": {contract_0: Account(storage={0: 2, 1: 22103, 2: 2107})}, + "result": { + contract_0: Account( + storage={0: 2, 1: (22103 + sstore_set_delta), 2: 2107} + ) + }, }, { "indexes": {"data": [2], "gas": -1, "value": -1}, "network": [">=Cancun"], - "result": {contract_2: Account(storage={0: 2, 1: 20003, 2: 107})}, + "result": { + contract_2: Account( + storage={0: 2, 1: (20003 + sstore_set_delta), 2: 107} + ) + }, }, { "indexes": {"data": [3], "gas": -1, "value": -1}, "network": [">=Cancun"], - "result": {contract_2: Account(storage={0: 2, 1: 22103, 2: 2107})}, + "result": { + contract_2: Account( + storage={0: 2, 1: (22103 + sstore_set_delta), 2: 2107} + ) + }, }, { "indexes": {"data": [4], "gas": -1, "value": -1}, "network": [">=Cancun"], "result": { contract_3: Account( - storage={0: 2, 1: 22103, 2: 2107, 24743: 57005} + storage={ + 0: 2, + 1: (22103 + sstore_set_delta), + 2: 2107, + 24743: 57005, + } ) }, }, @@ -1357,7 +1412,12 @@ def test_varied_context( "network": [">=Cancun"], "result": { contract_3: Account( - storage={0: 2, 1: 20003, 2: 107, 24743: 57005} + storage={ + 0: 2, + 1: (20003 + sstore_set_delta), + 2: 107, + 24743: 57005, + } ) }, }, @@ -1374,32 +1434,48 @@ def test_varied_context( { "indexes": {"data": [8], "gas": -1, "value": -1}, "network": [">=Cancun"], - "result": {contract_26: Account(storage={0: 20003, 1: 100})}, + "result": { + contract_26: Account( + storage={0: (20003 + sstore_set_delta), 1: 100} + ) + }, }, { "indexes": {"data": [9], "gas": -1, "value": -1}, "network": [">=Cancun"], - "result": {contract_26: Account(storage={0: 22103, 1: 2100})}, + "result": { + contract_26: Account( + storage={0: (22103 + sstore_set_delta), 1: 2100} + ) + }, }, { "indexes": {"data": [10], "gas": -1, "value": -1}, "network": [">=Cancun"], - "result": {contract_7: Account(storage={0: 20001})}, + "result": { + contract_7: Account(storage={0: (20001 + suicide_write_delta)}) + }, }, { "indexes": {"data": [11], "gas": -1, "value": -1}, "network": [">=Cancun"], - "result": {contract_7: Account(storage={0: 24601})}, + "result": { + contract_7: Account(storage={0: (24601 + suicide_write_delta)}) + }, }, { "indexes": {"data": [12], "gas": -1, "value": -1}, "network": [">=Cancun"], - "result": {contract_9: Account(storage={0: 100})}, + "result": { + contract_9: Account(storage={0: 100 + new_account_delta}) + }, }, { "indexes": {"data": [13], "gas": -1, "value": -1}, "network": [">=Cancun"], - "result": {contract_9: Account(storage={0: 4600})}, + "result": { + contract_9: Account(storage={0: 4600 + new_account_delta}) + }, }, { "indexes": {"data": [14, 15], "gas": -1, "value": -1}, @@ -1448,7 +1524,7 @@ def test_varied_context( 268: 103, 269: 103, 270: 103, - 271: 20003, + 271: (20003 + sstore_set_delta), 512: 100, 513: 100, 514: 100, @@ -1465,22 +1541,22 @@ def test_varied_context( 525: 100, 526: 100, 527: 100, - 768: 20003, - 769: 20003, - 770: 20003, - 771: 20003, - 772: 20003, - 773: 20003, - 774: 20003, - 775: 20003, - 776: 20003, - 777: 20003, - 778: 20003, - 779: 20003, - 780: 20003, - 781: 20003, - 782: 20003, - 783: 20003, + 768: (20003 + sstore_set_delta), + 769: (20003 + sstore_set_delta), + 770: (20003 + sstore_set_delta), + 771: (20003 + sstore_set_delta), + 772: (20003 + sstore_set_delta), + 773: (20003 + sstore_set_delta), + 774: (20003 + sstore_set_delta), + 775: (20003 + sstore_set_delta), + 776: (20003 + sstore_set_delta), + 777: (20003 + sstore_set_delta), + 778: (20003 + sstore_set_delta), + 779: (20003 + sstore_set_delta), + 780: (20003 + sstore_set_delta), + 781: (20003 + sstore_set_delta), + 782: (20003 + sstore_set_delta), + 783: (20003 + sstore_set_delta), 1024: 100, 1025: 100, 1026: 100, @@ -1541,7 +1617,7 @@ def test_varied_context( 268: 103, 269: 103, 270: 103, - 271: 22103, + 271: (22103 + sstore_set_delta), 512: 100, 513: 100, 514: 100, @@ -1558,22 +1634,22 @@ def test_varied_context( 525: 100, 526: 100, 527: 2100, - 768: 22103, - 769: 22103, - 770: 22103, - 771: 22103, - 772: 22103, - 773: 22103, - 774: 22103, - 775: 22103, - 776: 22103, - 777: 22103, - 778: 22103, - 779: 22103, - 780: 22103, - 781: 22103, - 782: 22103, - 783: 22103, + 768: (22103 + sstore_set_delta), + 769: (22103 + sstore_set_delta), + 770: (22103 + sstore_set_delta), + 771: (22103 + sstore_set_delta), + 772: (22103 + sstore_set_delta), + 773: (22103 + sstore_set_delta), + 774: (22103 + sstore_set_delta), + 775: (22103 + sstore_set_delta), + 776: (22103 + sstore_set_delta), + 777: (22103 + sstore_set_delta), + 778: (22103 + sstore_set_delta), + 779: (22103 + sstore_set_delta), + 780: (22103 + sstore_set_delta), + 781: (22103 + sstore_set_delta), + 782: (22103 + sstore_set_delta), + 783: (22103 + sstore_set_delta), 1024: 2100, 1025: 2100, 1026: 2100, @@ -1617,7 +1693,7 @@ def test_varied_context( "network": [">=Cancun"], "result": { compute_create_address(address=contract_18, nonce=0): Account( - storage={0: 65535, 1: 20017} + storage={0: 65535, 1: (20017 + sstore_set_delta)} ), }, }, @@ -1626,7 +1702,7 @@ def test_varied_context( "network": [">=Cancun"], "result": { compute_create_address(address=contract_18, nonce=0): Account( - storage={0: 65535, 1: 22117} + storage={0: 65535, 1: (22117 + sstore_set_delta)} ), }, }, @@ -1635,7 +1711,7 @@ def test_varied_context( "network": [">=Cancun"], "result": { Address(0xD82F21135ED7D7D833A9F2A0F1CF6C3DA214B8E3): Account( - storage={0: 65535, 1: 20017} + storage={0: 65535, 1: (20017 + sstore_set_delta)} ), }, }, @@ -1644,7 +1720,7 @@ def test_varied_context( "network": [">=Cancun"], "result": { Address(0xD82F21135ED7D7D833A9F2A0F1CF6C3DA214B8E3): Account( - storage={0: 65535, 1: 22117} + storage={0: 65535, 1: (22117 + sstore_set_delta)} ), }, }, @@ -1653,7 +1729,7 @@ def test_varied_context( "network": [">=Cancun"], "result": { compute_create_address(address=contract_20, nonce=0): Account( - storage={0: 65535, 1: 20017} + storage={0: 65535, 1: (20017 + sstore_set_delta)} ), }, }, @@ -1662,7 +1738,7 @@ def test_varied_context( "network": [">=Cancun"], "result": { compute_create_address(address=contract_20, nonce=0): Account( - storage={0: 65535, 1: 22117} + storage={0: 65535, 1: (22117 + sstore_set_delta)} ), }, }, @@ -1671,7 +1747,7 @@ def test_varied_context( "network": [">=Cancun"], "result": { Address(0x530508498D2AA75D8E591612809FEC3D37A45615): Account( - storage={0: 65535, 1: 20017} + storage={0: 65535, 1: (20017 + sstore_set_delta)} ), }, }, @@ -1680,7 +1756,7 @@ def test_varied_context( "network": [">=Cancun"], "result": { Address(0x530508498D2AA75D8E591612809FEC3D37A45615): Account( - storage={0: 65535, 1: 22117} + storage={0: 65535, 1: (22117 + sstore_set_delta)} ), }, }, @@ -1689,7 +1765,7 @@ def test_varied_context( "network": [">=Cancun"], "result": { compute_create_address(address=contract_22, nonce=0): Account( - storage={0: 65535, 1: 20017, 2: 117} + storage={0: 65535, 1: (20017 + sstore_set_delta), 2: 117} ), }, }, @@ -1698,7 +1774,7 @@ def test_varied_context( "network": [">=Cancun"], "result": { compute_create_address(address=contract_22, nonce=0): Account( - storage={0: 65535, 1: 22117, 2: 117} + storage={0: 65535, 1: (22117 + sstore_set_delta), 2: 117} ), }, }, @@ -1707,7 +1783,7 @@ def test_varied_context( "network": [">=Cancun"], "result": { Address(0x83FBDAE70258AC0FA837B701CC63CEDF48D4B6BF): Account( - storage={0: 65535, 1: 20017, 2: 117} + storage={0: 65535, 1: (20017 + sstore_set_delta), 2: 117} ), }, }, @@ -1716,7 +1792,7 @@ def test_varied_context( "network": [">=Cancun"], "result": { Address(0x83FBDAE70258AC0FA837B701CC63CEDF48D4B6BF): Account( - storage={0: 65535, 1: 22117, 2: 117} + storage={0: 65535, 1: (22117 + sstore_set_delta), 2: 117} ), }, }, @@ -1724,14 +1800,18 @@ def test_varied_context( "indexes": {"data": [34], "gas": -1, "value": -1}, "network": [">=Cancun"], "result": { - contract_25: Account(storage={0: 24743, 1: 20017, 2: 117}) + contract_25: Account( + storage={0: 24743, 1: (20017 + sstore_set_delta), 2: 117} + ) }, }, { "indexes": {"data": [35], "gas": -1, "value": -1}, "network": [">=Cancun"], "result": { - contract_25: Account(storage={0: 24743, 1: 22117, 2: 117}) + contract_25: Account( + storage={0: 24743, 1: (22117 + sstore_set_delta), 2: 117} + ) }, }, ] diff --git a/tests/ported_static/stEIP3607/test_init_colliding_with_non_empty_account.py b/tests/ported_static/stEIP3607/test_init_colliding_with_non_empty_account.py index 6e40d5dac89..7bb0f93c560 100644 --- a/tests/ported_static/stEIP3607/test_init_colliding_with_non_empty_account.py +++ b/tests/ported_static/stEIP3607/test_init_colliding_with_non_empty_account.py @@ -12,6 +12,7 @@ Address, Alloc, Environment, + Header, StateTestFiller, Transaction, compute_create_address, @@ -85,7 +86,6 @@ def test_init_colliding_with_non_empty_account( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=71794957647893862, ) pre[coinbase] = Account(balance=0, nonce=1) @@ -164,4 +164,28 @@ def test_init_colliding_with_non_empty_account( sender: Account(nonce=1), } - state_test(env=env, pre=pre, post=post, tx=tx) + # On collision, all execution gas is reclassified to regular and the + # tx-time state reservoir is restored. Under EIP-8037 2D gas this + # gives header.gas_used = max(intrinsic_regular + execution_gas, + # intrinsic_state); pre-EIP-8037 the state component is zero, so the + # same expression collapses to tx.gas. + intrinsic_total = fork.transaction_intrinsic_cost_calculator()( + calldata=bytes(tx_data[d]), + contract_creation=True, + ) + intrinsic_state = fork.create_state_gas() + intrinsic_regular = intrinsic_total - intrinsic_state + execution_gas = tx_gas[g] - intrinsic_total + expected_header_gas_used = max( + intrinsic_regular + execution_gas, intrinsic_state + ) + + state_test( + env=env, + pre=pre, + post=post, + tx=tx, + blockchain_test_header_verify=Header( + gas_used=expected_header_gas_used, + ), + ) diff --git a/tests/ported_static/stEIP3607/test_transaction_colliding_with_non_empty_account_init_paris.py b/tests/ported_static/stEIP3607/test_transaction_colliding_with_non_empty_account_init_paris.py index 2428bbb10d9..3682a79177a 100644 --- a/tests/ported_static/stEIP3607/test_transaction_colliding_with_non_empty_account_init_paris.py +++ b/tests/ported_static/stEIP3607/test_transaction_colliding_with_non_empty_account_init_paris.py @@ -87,7 +87,6 @@ def test_transaction_colliding_with_non_empty_account_init_paris( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=71794957647893862, ) pre[coinbase] = Account(balance=0, nonce=1) diff --git a/tests/ported_static/stEIP3651_warmcoinbase/test_coinbase_warm_account_call_gas_fail.py b/tests/ported_static/stEIP3651_warmcoinbase/test_coinbase_warm_account_call_gas_fail.py index cc58c53e55a..4487e382ead 100644 --- a/tests/ported_static/stEIP3651_warmcoinbase/test_coinbase_warm_account_call_gas_fail.py +++ b/tests/ported_static/stEIP3651_warmcoinbase/test_coinbase_warm_account_call_gas_fail.py @@ -3,6 +3,9 @@ Ported from: state_tests/Shanghai/stEIP3651_warmcoinbase/coinbaseWarmAccountCallGasFailFiller.yml +@manually-enhanced: Do not overwrite. `tx_gas` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -251,7 +254,11 @@ def test_coinbase_warm_account_call_gas_fail( Bytes("693c6139") + Hash(addr_3, left_padding=True), Bytes("693c6139") + Hash(addr_4, left_padding=True), ] - tx_gas = [80000] + # EIP-8037 state-gas spill on Amsterdam exceeds the original 80k. + outer_tx_gas = 80000 + if fork.is_eip_enabled(8037): + outer_tx_gas = 500_000 + tx_gas = [outer_tx_gas] tx = Transaction( sender=sender, diff --git a/tests/ported_static/stEIP3855_push0/test_push0.py b/tests/ported_static/stEIP3855_push0/test_push0.py index 82a91325613..45fdb4aa609 100644 --- a/tests/ported_static/stEIP3855_push0/test_push0.py +++ b/tests/ported_static/stEIP3855_push0/test_push0.py @@ -3,6 +3,9 @@ Ported from: state_tests/Shanghai/stEIP3855_push0/push0Filler.yml +@manually-enhanced: Do not overwrite. Inner-CALL gas bumped on +Amsterdam to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -80,6 +83,12 @@ def test_push0( v: int, ) -> None: """Test_push0.""" + # EIP-8037 inner-CALL gas: 100k OoGs the SSTORE-containing callees + # on Amsterdam (per-storage state-gas spill). Pre-EIP-8037 keeps + # the original 100k. + inner_call_gas = 100000 + if fork.is_eip_enabled(8037): + inner_call_gas = 1000000 coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) contract_0 = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B) contract_1 = Address(0x0000000000000000000000000000000000001000) @@ -113,7 +122,7 @@ def test_push0( code=Op.SSTORE( key=0x0, value=Op.CALL( - gas=0x186A0, + gas=inner_call_gas, address=Op.SHR(0x60, Op.CALLDATALOAD(offset=Op.DUP1)), value=Op.DUP1, args_offset=Op.DUP1, @@ -191,7 +200,7 @@ def test_push0( code=Op.SSTORE( key=0x0, value=Op.STATICCALL( - gas=0x186A0, + gas=inner_call_gas, address=0x600, args_offset=Op.DUP1, args_size=Op.DUP1, diff --git a/tests/ported_static/stEIP4844_blobtransactions/test_create_blobhash_tx.py b/tests/ported_static/stEIP4844_blobtransactions/test_create_blobhash_tx.py index d11cc789e44..245b567c32c 100644 --- a/tests/ported_static/stEIP4844_blobtransactions/test_create_blobhash_tx.py +++ b/tests/ported_static/stEIP4844_blobtransactions/test_create_blobhash_tx.py @@ -46,7 +46,6 @@ def test_create_blobhash_tx( prev_randao=0x20000, base_fee_per_gas=7, excess_blob_gas=0, - gas_limit=68719476736, ) # Source: lll diff --git a/tests/ported_static/stEIP5656_MCOPY/test_mcopy_copy_cost.py b/tests/ported_static/stEIP5656_MCOPY/test_mcopy_copy_cost.py index 92430e5fbce..8cd67806eee 100644 --- a/tests/ported_static/stEIP5656_MCOPY/test_mcopy_copy_cost.py +++ b/tests/ported_static/stEIP5656_MCOPY/test_mcopy_copy_cost.py @@ -436,7 +436,6 @@ def test_mcopy_copy_cost( timestamp=1687174231, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=1000000, ) # Source: yul diff --git a/tests/ported_static/stExample/test_add11.py b/tests/ported_static/stExample/test_add11.py index 5d6769f2d7a..4fd294a4c60 100644 --- a/tests/ported_static/stExample/test_add11.py +++ b/tests/ported_static/stExample/test_add11.py @@ -44,7 +44,6 @@ def test_add11( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=71794957647893862, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) diff --git a/tests/ported_static/stExample/test_add11_yml.py b/tests/ported_static/stExample/test_add11_yml.py index bf7cbfdba7d..621e26e88e3 100644 --- a/tests/ported_static/stExample/test_add11_yml.py +++ b/tests/ported_static/stExample/test_add11_yml.py @@ -44,7 +44,6 @@ def test_add11_yml( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=71794957647893862, ) pre[coinbase] = Account(balance=0, nonce=1) diff --git a/tests/ported_static/stExample/test_basefee_example.py b/tests/ported_static/stExample/test_basefee_example.py index 047f10ea622..14083b204db 100644 --- a/tests/ported_static/stExample/test_basefee_example.py +++ b/tests/ported_static/stExample/test_basefee_example.py @@ -42,7 +42,6 @@ def test_basefee_example( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=70000000, - gas_limit=68719476736, ) # Source: lll diff --git a/tests/ported_static/stExample/test_indexes_omit_example.py b/tests/ported_static/stExample/test_indexes_omit_example.py index 71c4aec9f8d..fc008d58b79 100644 --- a/tests/ported_static/stExample/test_indexes_omit_example.py +++ b/tests/ported_static/stExample/test_indexes_omit_example.py @@ -40,7 +40,6 @@ def test_indexes_omit_example( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=71794957647893862, ) pre[coinbase] = Account(balance=0, nonce=1) diff --git a/tests/ported_static/stExample/test_labels_example.py b/tests/ported_static/stExample/test_labels_example.py index 9a8208197ee..f78d807ae82 100644 --- a/tests/ported_static/stExample/test_labels_example.py +++ b/tests/ported_static/stExample/test_labels_example.py @@ -80,7 +80,6 @@ def test_labels_example( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=71794957647893862, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) diff --git a/tests/ported_static/stExample/test_ranges_example.py b/tests/ported_static/stExample/test_ranges_example.py index 2a1990456f4..25cf6aa2ded 100644 --- a/tests/ported_static/stExample/test_ranges_example.py +++ b/tests/ported_static/stExample/test_ranges_example.py @@ -200,7 +200,6 @@ def test_ranges_example( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=71794957647893862, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) diff --git a/tests/ported_static/stHomesteadSpecific/test_contract_creation_oo_gdont_leave_empty_contract.py b/tests/ported_static/stHomesteadSpecific/test_contract_creation_oo_gdont_leave_empty_contract.py index dce50fa3f12..a402592fcfa 100644 --- a/tests/ported_static/stHomesteadSpecific/test_contract_creation_oo_gdont_leave_empty_contract.py +++ b/tests/ported_static/stHomesteadSpecific/test_contract_creation_oo_gdont_leave_empty_contract.py @@ -44,7 +44,6 @@ def test_contract_creation_oo_gdont_leave_empty_contract( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=1000000, ) # Source: lll diff --git a/tests/ported_static/stHomesteadSpecific/test_create_contract_via_transaction_cost53000.py b/tests/ported_static/stHomesteadSpecific/test_create_contract_via_transaction_cost53000.py index aaa0ab8e85f..6468bb554b2 100644 --- a/tests/ported_static/stHomesteadSpecific/test_create_contract_via_transaction_cost53000.py +++ b/tests/ported_static/stHomesteadSpecific/test_create_contract_via_transaction_cost53000.py @@ -3,6 +3,12 @@ Ported from: state_tests/stHomesteadSpecific/createContractViaTransactionCost53000Filler.json + +@manually-enhanced: Do not overwrite. `tx.gas_limit` was raised from +100 000 to 500 000 (and sender funding bumped accordingly) so the +contract-creation tx clears the EIP-8037 intrinsic-gas floor on +Amsterdam. The test only asserts that the tx ran (sender.nonce == 1); +the higher gas budget doesn't change that post-state on any fork. """ import pytest @@ -15,6 +21,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" @@ -29,10 +36,19 @@ def test_create_contract_via_transaction_cost53000( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Trigger transaction creating gasPrice in the state.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xF4240) + # On EIP-8037 the contract-creation tx needs more gas to clear the + # intrinsic floor, and the sender therefore needs more balance to + # afford the upfront cost. Pre-EIP-8037 keeps the original values. + tx_gas_limit = 100000 + sender_amount = 0xF4240 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500000 + sender_amount = 0x4C4B40 + sender = pre.fund_eoa(amount=sender_amount) env = Environment( fee_recipient=coinbase, @@ -47,7 +63,7 @@ def test_create_contract_via_transaction_cost53000( sender=sender, to=None, data=Bytes(""), - gas_limit=100000, + gas_limit=tx_gas_limit, ) post = {sender: Account(nonce=1)} diff --git a/tests/ported_static/stInitCodeTest/test_call_contract_to_create_contract_and_call_it_oog.py b/tests/ported_static/stInitCodeTest/test_call_contract_to_create_contract_and_call_it_oog.py index e5dde4f4373..cdfb46d99bd 100644 --- a/tests/ported_static/stInitCodeTest/test_call_contract_to_create_contract_and_call_it_oog.py +++ b/tests/ported_static/stInitCodeTest/test_call_contract_to_create_contract_and_call_it_oog.py @@ -12,10 +12,12 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, compute_create_address, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,6 +33,7 @@ @pytest.mark.pre_alloc_mutable def test_call_contract_to_create_contract_and_call_it_oog( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_call_contract_to_create_contract_and_call_it_oog.""" @@ -73,7 +76,7 @@ def test_call_contract_to_create_contract_and_call_it_oog( sender=sender, to=contract_0, data=Bytes("00"), - gas_limit=203000, + gas_limit=2203000 if fork >= Amsterdam else 203000, ) post = { diff --git a/tests/ported_static/stInitCodeTest/test_call_contract_to_create_contract_oog_bonus_gas.py b/tests/ported_static/stInitCodeTest/test_call_contract_to_create_contract_oog_bonus_gas.py index 11a0f3780e7..c1b5cc67aae 100644 --- a/tests/ported_static/stInitCodeTest/test_call_contract_to_create_contract_oog_bonus_gas.py +++ b/tests/ported_static/stInitCodeTest/test_call_contract_to_create_contract_oog_bonus_gas.py @@ -12,10 +12,12 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, compute_create_address, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,6 +33,7 @@ @pytest.mark.pre_alloc_mutable def test_call_contract_to_create_contract_oog_bonus_gas( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_call_contract_to_create_contract_oog_bonus_gas.""" @@ -44,7 +47,6 @@ def test_call_contract_to_create_contract_oog_bonus_gas( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=1000000000, ) # Source: lll @@ -73,7 +75,7 @@ def test_call_contract_to_create_contract_oog_bonus_gas( sender=sender, to=contract_0, data=Bytes("00"), - gas_limit=200000, + gas_limit=2200000 if fork >= Amsterdam else 200000, ) post = { diff --git a/tests/ported_static/stInitCodeTest/test_call_contract_to_create_contract_which_would_create_contract_if_called.py b/tests/ported_static/stInitCodeTest/test_call_contract_to_create_contract_which_would_create_contract_if_called.py index 16fb7d6b4df..f45a3679df6 100644 --- a/tests/ported_static/stInitCodeTest/test_call_contract_to_create_contract_which_would_create_contract_if_called.py +++ b/tests/ported_static/stInitCodeTest/test_call_contract_to_create_contract_which_would_create_contract_if_called.py @@ -3,6 +3,10 @@ Ported from: state_tests/stInitCodeTest/CallContractToCreateContractWhichWouldCreateContractIfCalledFiller.json +@manually-enhanced: Do not overwrite. tx `gas_limit` and inner-CALL gas +bumped on Amsterdam to cover EIP-8037 state-gas spill; pre-EIP-8037 +unchanged. + """ import pytest @@ -16,6 +20,7 @@ Transaction, compute_create_address, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -32,8 +37,16 @@ def test_call_contract_to_create_contract_which_would_create_contract_if_called( # noqa: E501 state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_call_contract_to_create_contract_which_would_create_contract_i...""" # noqa: E501 + # EIP-8037 state-gas spill OoGs the inner CREATE/CALL chain. + inner_call_gas = 50000 + tx_gas_limit = 200000 + if fork.is_eip_enabled(8037): + inner_call_gas = 200000 + tx_gas_limit = 800_000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) contract_0 = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87) sender = pre.fund_eoa(amount=0x3B9ACA00) @@ -55,7 +68,7 @@ def test_call_contract_to_create_contract_which_would_create_contract_if_called( ) + Op.SSTORE(key=0x0, value=Op.CREATE(value=0x1, offset=0xB, size=0x15)) + Op.CALL( - gas=0xC350, + gas=inner_call_gas, address=Op.SLOAD(key=0x0), value=0x1, args_offset=0x0, @@ -73,7 +86,7 @@ def test_call_contract_to_create_contract_which_would_create_contract_if_called( sender=sender, to=contract_0, data=Bytes("00"), - gas_limit=200000, + gas_limit=tx_gas_limit, ) post = { diff --git a/tests/ported_static/stInitCodeTest/test_call_contract_to_create_contract_which_would_create_contract_in_init_code.py b/tests/ported_static/stInitCodeTest/test_call_contract_to_create_contract_which_would_create_contract_in_init_code.py index b1dabf644aa..565a96f8f2f 100644 --- a/tests/ported_static/stInitCodeTest/test_call_contract_to_create_contract_which_would_create_contract_in_init_code.py +++ b/tests/ported_static/stInitCodeTest/test_call_contract_to_create_contract_which_would_create_contract_in_init_code.py @@ -12,10 +12,12 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, compute_create_address, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,6 +33,7 @@ @pytest.mark.pre_alloc_mutable def test_call_contract_to_create_contract_which_would_create_contract_in_init_code( # noqa: E501 state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_call_contract_to_create_contract_which_would_create_contract_i...""" # noqa: E501 @@ -44,7 +47,6 @@ def test_call_contract_to_create_contract_which_would_create_contract_in_init_co timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=1000000000, ) # Source: lll @@ -61,7 +63,7 @@ def test_call_contract_to_create_contract_which_would_create_contract_in_init_co sender=sender, to=contract_0, data=Bytes("00"), - gas_limit=200000, + gas_limit=2200000 if fork >= Amsterdam else 200000, ) post = { diff --git a/tests/ported_static/stInitCodeTest/test_call_recursive_contract.py b/tests/ported_static/stInitCodeTest/test_call_recursive_contract.py index 971bc2d4760..3239ad51ffd 100644 --- a/tests/ported_static/stInitCodeTest/test_call_recursive_contract.py +++ b/tests/ported_static/stInitCodeTest/test_call_recursive_contract.py @@ -3,18 +3,22 @@ Ported from: state_tests/stInitCodeTest/CallRecursiveContractFiller.json + +@manually-enhanced: Do not overwrite. This test has been manually reviewed and +enhanced. """ +from typing import Generator + import pytest from execution_testing import ( - EOA, Account, Address, Alloc, - Bytes, - Environment, + Fork, StateTestFiller, Transaction, + compute_create_address, ) from execution_testing.vm import Op @@ -22,59 +26,80 @@ REFERENCE_SPEC_VERSION = "N/A" +def recursive_create_calculator( + contract: Address, depth: int +) -> Generator[Address, None, None]: + """ + Calculate the resulting address of a contract creating contracts + recursively. + """ + while depth > 0: + contract = compute_create_address(address=contract, nonce=1) + yield contract + depth -= 1 + + @pytest.mark.ported_from( ["state_tests/stInitCodeTest/CallRecursiveContractFiller.json"], ) @pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable def test_call_recursive_contract( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_call_recursive_contract.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87) - sender = EOA( - key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8 - ) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=100000000, - ) - - pre[sender] = Account(balance=0x989680) + sender = pre.fund_eoa() # Source: lll # {[[ 2 ]](ADDRESS)(CODECOPY 0 0 32)(CREATE 0 0 32)} - contract_0 = pre.deploy_contract( # noqa: F841 + entry_contract = pre.deploy_contract( code=Op.SSTORE(key=0x2, value=Op.ADDRESS) + Op.CODECOPY(dest_offset=0x0, offset=0x0, size=0x20) + Op.CREATE(value=0x0, offset=0x0, size=0x20) + Op.STOP, - nonce=40, - address=Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87), # noqa: E501 ) + gas_limit = 400_000 + pre_fund_deploy_addresses = False + if fork.is_eip_enabled(8037): + gas_limit = 2_000_000 + # In 8037, the cost of creating an account is beared by the parent + # creating it, so in order to not run out of gas when we return from + # contract creation we pre-fund the accounts. This way they are + # already in the trie and don't produce a cost. + pre_fund_deploy_addresses = True + tx = Transaction( sender=sender, - to=contract_0, - data=Bytes("00"), - gas_limit=400000, - value=1, + to=entry_contract, + gas_limit=gas_limit, ) + expected_depth = 5 + for i, contract in enumerate( + recursive_create_calculator(entry_contract, depth=expected_depth + 1) + ): + if pre_fund_deploy_addresses: + pre.fund_address(contract, 1) + if i == expected_depth - 1: + last_expected_contract = contract + elif i == expected_depth: + first_unexpected_contract = contract + + first_unexpected_contract_account = Account.NONEXISTENT + if pre_fund_deploy_addresses: + first_unexpected_contract_account = Account(balance=1, code=b"") + post = { - contract_0: Account(storage={2: contract_0}, balance=1, nonce=41), - Address( - 0x1A4C83E1A9834CDC7E4A905FF7F0CF44AED73180 - ): Account.NONEXISTENT, - Address( - 0x8E3411C91D5DD4081B4846FA2F93808F5AD19686 - ): Account.NONEXISTENT, + entry_contract: Account( + storage={2: entry_contract}, balance=0, nonce=2 + ), + last_expected_contract: Account( + storage={2: last_expected_contract}, + balance=1 if pre_fund_deploy_addresses else 0, + nonce=2, + ), + first_unexpected_contract: first_unexpected_contract_account, } - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stInitCodeTest/test_call_the_contract_to_create_empty_contract.py b/tests/ported_static/stInitCodeTest/test_call_the_contract_to_create_empty_contract.py index 08cb3cc54f5..45c06cfb5f7 100644 --- a/tests/ported_static/stInitCodeTest/test_call_the_contract_to_create_empty_contract.py +++ b/tests/ported_static/stInitCodeTest/test_call_the_contract_to_create_empty_contract.py @@ -3,6 +3,10 @@ Ported from: state_tests/stInitCodeTest/CallTheContractToCreateEmptyContractFiller.json + +@manually-enhanced: Do not overwrite. tx gas budget bumped +for EIP-8037 NEW_ACCOUNT state-gas headroom on Amsterdam (post-state +expectations are unchanged on all forks). """ import pytest @@ -16,6 +20,7 @@ Transaction, compute_create_address, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -32,6 +37,7 @@ def test_call_the_contract_to_create_empty_contract( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_call_the_contract_to_create_empty_contract.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) @@ -54,11 +60,16 @@ def test_call_the_contract_to_create_empty_contract( nonce=0, ) + # EIP-8037 NEW_ACCOUNT state-gas spill on Amsterdam; pre-EIP-8037 + # keeps the original 100 000 budget. + tx_gas_limit = 100_000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 tx = Transaction( sender=sender, to=contract_0, data=Bytes("00"), - gas_limit=100000, + gas_limit=tx_gas_limit, value=1, ) diff --git a/tests/ported_static/stInitCodeTest/test_return_test2.py b/tests/ported_static/stInitCodeTest/test_return_test2.py index 44212cd0b15..b214d9effe3 100644 --- a/tests/ported_static/stInitCodeTest/test_return_test2.py +++ b/tests/ported_static/stInitCodeTest/test_return_test2.py @@ -42,7 +42,6 @@ def test_return_test2( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=1000000000, ) # Source: lll diff --git a/tests/ported_static/stInitCodeTest/test_stack_under_flow_contract_creation.py b/tests/ported_static/stInitCodeTest/test_stack_under_flow_contract_creation.py index 6648c3ae81d..b0b6660592e 100644 --- a/tests/ported_static/stInitCodeTest/test_stack_under_flow_contract_creation.py +++ b/tests/ported_static/stInitCodeTest/test_stack_under_flow_contract_creation.py @@ -11,10 +11,12 @@ Address, Alloc, Environment, + Fork, StateTestFiller, Transaction, compute_create_address, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -28,6 +30,7 @@ @pytest.mark.pre_alloc_mutable def test_stack_under_flow_contract_creation( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_stack_under_flow_contract_creation.""" @@ -40,7 +43,6 @@ def test_stack_under_flow_contract_creation( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=1000000000000000, ) pre[coinbase] = Account(balance=0, nonce=1) @@ -49,7 +51,7 @@ def test_stack_under_flow_contract_creation( sender=sender, to=None, data=Op.PUSH1[0x0] + Op.CALL, - gas_limit=72000, + gas_limit=2072000 if fork >= Amsterdam else 72000, ) post = { diff --git a/tests/ported_static/stInitCodeTest/test_transaction_create_auto_suicide_contract.py b/tests/ported_static/stInitCodeTest/test_transaction_create_auto_suicide_contract.py index 271a5a75705..a92c63574f3 100644 --- a/tests/ported_static/stInitCodeTest/test_transaction_create_auto_suicide_contract.py +++ b/tests/ported_static/stInitCodeTest/test_transaction_create_auto_suicide_contract.py @@ -3,6 +3,10 @@ Ported from: state_tests/stInitCodeTest/TransactionCreateAutoSuicideContractFiller.json +@manually-enhanced: Do not overwrite. tx `gas_limit` and sender balance +bumped on Amsterdam to cover EIP-8037 TX_CREATE intrinsic (new-account +state-gas folded in); pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +36,16 @@ def test_transaction_create_auto_suicide_contract( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_transaction_create_auto_suicide_contract.""" + # EIP-8037 folds new-account state-gas into TX_CREATE intrinsic. + tx_gas_limit = 55000 + sender_balance = 1000000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 300_000 + sender_balance = 10000000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = EOA( key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8 @@ -47,7 +60,7 @@ def test_transaction_create_auto_suicide_contract( gas_limit=1000000, ) - pre[sender] = Account(balance=0xF4240) + pre[sender] = Account(balance=sender_balance) tx = Transaction( sender=sender, @@ -61,7 +74,7 @@ def test_transaction_create_auto_suicide_contract( + Op.PUSH1[0x0] + Op.BYTE(Op.DUP2, Op.CALLDATALOAD(offset=Op.DUP1)) + Op.DUP2, - gas_limit=55000, + gas_limit=tx_gas_limit, value=15, ) diff --git a/tests/ported_static/stInitCodeTest/test_transaction_create_random_init_code.py b/tests/ported_static/stInitCodeTest/test_transaction_create_random_init_code.py index 2924e4d01ee..e5c21a6d629 100644 --- a/tests/ported_static/stInitCodeTest/test_transaction_create_random_init_code.py +++ b/tests/ported_static/stInitCodeTest/test_transaction_create_random_init_code.py @@ -11,10 +11,12 @@ Address, Alloc, Environment, + Fork, StateTestFiller, Transaction, compute_create_address, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -28,6 +30,7 @@ @pytest.mark.pre_alloc_mutable def test_transaction_create_random_init_code( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Stack underflow in init code.""" @@ -40,7 +43,6 @@ def test_transaction_create_random_init_code( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000000, ) pre[coinbase] = Account(balance=0, nonce=1) @@ -58,7 +60,7 @@ def test_transaction_create_random_init_code( + Op.BYTE(Op.DUP2, Op.CALLDATALOAD(offset=Op.DUP1)) + Op.DUP2 + Op.STOP, - gas_limit=64599, + gas_limit=2064599 if fork >= Amsterdam else 64599, value=1, ) diff --git a/tests/ported_static/stInitCodeTest/test_transaction_create_stop_in_initcode.py b/tests/ported_static/stInitCodeTest/test_transaction_create_stop_in_initcode.py index 15156479273..e873a8e0211 100644 --- a/tests/ported_static/stInitCodeTest/test_transaction_create_stop_in_initcode.py +++ b/tests/ported_static/stInitCodeTest/test_transaction_create_stop_in_initcode.py @@ -3,6 +3,9 @@ Ported from: state_tests/stInitCodeTest/TransactionCreateStopInInitcodeFiller.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +above intrinsic+state-gas; pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +18,7 @@ Transaction, compute_create_address, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -28,10 +32,18 @@ def test_transaction_create_stop_in_initcode( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_transaction_create_stop_in_initcode.""" + # EIP-8037 folds new-account state-gas into TX_CREATE intrinsic. + tx_gas_limit = 55000 + sender_balance = 1000000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 300_000 + sender_balance = 10000000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xF4240) + sender = pre.fund_eoa(amount=sender_balance) env = Environment( fee_recipient=coinbase, @@ -55,7 +67,7 @@ def test_transaction_create_stop_in_initcode( + Op.PUSH1[0x0] + Op.BYTE(Op.DUP2, Op.CALLDATALOAD(offset=Op.DUP1)) + Op.DUP2, - gas_limit=55000, + gas_limit=tx_gas_limit, value=1, ) diff --git a/tests/ported_static/stInitCodeTest/test_transaction_create_suicide_in_initcode.py b/tests/ported_static/stInitCodeTest/test_transaction_create_suicide_in_initcode.py index 59214c2f5d8..a938f5a0fc8 100644 --- a/tests/ported_static/stInitCodeTest/test_transaction_create_suicide_in_initcode.py +++ b/tests/ported_static/stInitCodeTest/test_transaction_create_suicide_in_initcode.py @@ -11,10 +11,12 @@ Address, Alloc, Environment, + Fork, StateTestFiller, Transaction, compute_create_address, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,6 +32,7 @@ @pytest.mark.pre_alloc_mutable def test_transaction_create_suicide_in_initcode( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_transaction_create_suicide_in_initcode.""" @@ -51,7 +54,7 @@ def test_transaction_create_suicide_in_initcode( sender=sender, to=None, data=Op.SELFDESTRUCT(address=Op.ADDRESS) + Op.STOP, - gas_limit=155000, + gas_limit=2155000 if fork >= Amsterdam else 155000, value=1, ) diff --git a/tests/ported_static/stMemExpandingEIP150Calls/test_call_goes_oog_on_second_level_with_mem_expanding_calls.py b/tests/ported_static/stMemExpandingEIP150Calls/test_call_goes_oog_on_second_level_with_mem_expanding_calls.py index 3c9a3facb41..5f31ce42a8a 100644 --- a/tests/ported_static/stMemExpandingEIP150Calls/test_call_goes_oog_on_second_level_with_mem_expanding_calls.py +++ b/tests/ported_static/stMemExpandingEIP150Calls/test_call_goes_oog_on_second_level_with_mem_expanding_calls.py @@ -42,7 +42,6 @@ def test_call_goes_oog_on_second_level_with_mem_expanding_calls( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) # Source: hex diff --git a/tests/ported_static/stMemExpandingEIP150Calls/test_execute_call_that_ask_more_gas_then_transaction_has_with_mem_expanding_calls.py b/tests/ported_static/stMemExpandingEIP150Calls/test_execute_call_that_ask_more_gas_then_transaction_has_with_mem_expanding_calls.py index 0779b06c87b..db38f621ed0 100644 --- a/tests/ported_static/stMemExpandingEIP150Calls/test_execute_call_that_ask_more_gas_then_transaction_has_with_mem_expanding_calls.py +++ b/tests/ported_static/stMemExpandingEIP150Calls/test_execute_call_that_ask_more_gas_then_transaction_has_with_mem_expanding_calls.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,6 +32,7 @@ @pytest.mark.pre_alloc_mutable def test_execute_call_that_ask_more_gas_then_transaction_has_with_mem_expanding_calls( # noqa: E501 state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_execute_call_that_ask_more_gas_then_transaction_has_with_mem_e...""" # noqa: E501 @@ -74,7 +77,7 @@ def test_execute_call_that_ask_more_gas_then_transaction_has_with_mem_expanding_ sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, ) post = { diff --git a/tests/ported_static/stMemoryStressTest/test_return_bounds.py b/tests/ported_static/stMemoryStressTest/test_return_bounds.py index 8014fe56cb0..ae8816c1e31 100644 --- a/tests/ported_static/stMemoryStressTest/test_return_bounds.py +++ b/tests/ported_static/stMemoryStressTest/test_return_bounds.py @@ -73,7 +73,6 @@ def test_return_bounds( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: lll diff --git a/tests/ported_static/stMemoryStressTest/test_sstore_bounds.py b/tests/ported_static/stMemoryStressTest/test_sstore_bounds.py index 3aee2e858ab..702dd94aeb5 100644 --- a/tests/ported_static/stMemoryStressTest/test_sstore_bounds.py +++ b/tests/ported_static/stMemoryStressTest/test_sstore_bounds.py @@ -68,7 +68,6 @@ def test_sstore_bounds( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0x7FFFFFFFFFFFFFFFFFF) diff --git a/tests/ported_static/stMemoryTest/test_calldatacopy_dejavu2.py b/tests/ported_static/stMemoryTest/test_calldatacopy_dejavu2.py index f1b4ce171f7..3bddb40d23d 100644 --- a/tests/ported_static/stMemoryTest/test_calldatacopy_dejavu2.py +++ b/tests/ported_static/stMemoryTest/test_calldatacopy_dejavu2.py @@ -43,7 +43,6 @@ def test_calldatacopy_dejavu2( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=52949672960, ) pre[sender] = Account(balance=0x271000000000) diff --git a/tests/ported_static/stMemoryTest/test_mem0b_single_byte.py b/tests/ported_static/stMemoryTest/test_mem0b_single_byte.py index 843515cd61a..6499f41dd67 100644 --- a/tests/ported_static/stMemoryTest/test_mem0b_single_byte.py +++ b/tests/ported_static/stMemoryTest/test_mem0b_single_byte.py @@ -3,6 +3,9 @@ Ported from: state_tests/stMemoryTest/mem0b_singleByteFiller.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +18,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +33,14 @@ def test_mem0b_single_byte( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_mem0b_single_byte.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 200_000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0x6400000000) @@ -40,7 +50,6 @@ def test_mem0b_single_byte( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) # Source: lll @@ -57,7 +66,7 @@ def test_mem0b_single_byte( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=tx_gas_limit, value=10, ) diff --git a/tests/ported_static/stMemoryTest/test_mem31b_single_byte.py b/tests/ported_static/stMemoryTest/test_mem31b_single_byte.py index bfcb48d6313..ab2ee24082e 100644 --- a/tests/ported_static/stMemoryTest/test_mem31b_single_byte.py +++ b/tests/ported_static/stMemoryTest/test_mem31b_single_byte.py @@ -3,6 +3,9 @@ Ported from: state_tests/stMemoryTest/mem31b_singleByteFiller.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +18,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +33,14 @@ def test_mem31b_single_byte( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_mem31b_single_byte.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 200_000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0x6400000000) @@ -40,7 +50,6 @@ def test_mem31b_single_byte( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) # Source: lll @@ -57,7 +66,7 @@ def test_mem31b_single_byte( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=tx_gas_limit, value=10, ) diff --git a/tests/ported_static/stMemoryTest/test_mem32b_single_byte.py b/tests/ported_static/stMemoryTest/test_mem32b_single_byte.py index 10a1a44be41..b839594d38b 100644 --- a/tests/ported_static/stMemoryTest/test_mem32b_single_byte.py +++ b/tests/ported_static/stMemoryTest/test_mem32b_single_byte.py @@ -3,6 +3,9 @@ Ported from: state_tests/stMemoryTest/mem32b_singleByteFiller.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +18,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +33,14 @@ def test_mem32b_single_byte( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_mem32b_single_byte.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 200_000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0x6400000000) @@ -40,7 +50,6 @@ def test_mem32b_single_byte( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) # Source: lll @@ -57,7 +66,7 @@ def test_mem32b_single_byte( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=tx_gas_limit, value=10, ) diff --git a/tests/ported_static/stMemoryTest/test_mem32kb.py b/tests/ported_static/stMemoryTest/test_mem32kb.py index 6abb8684567..0bfc780fcb9 100644 --- a/tests/ported_static/stMemoryTest/test_mem32kb.py +++ b/tests/ported_static/stMemoryTest/test_mem32kb.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -28,6 +30,7 @@ @pytest.mark.pre_alloc_mutable def test_mem32kb( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_mem32kb.""" @@ -40,7 +43,6 @@ def test_mem32kb( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) # Source: lll @@ -58,7 +60,7 @@ def test_mem32kb( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=10, ) diff --git a/tests/ported_static/stMemoryTest/test_mem32kb_minus_1.py b/tests/ported_static/stMemoryTest/test_mem32kb_minus_1.py index d33ac793bf9..9a859a70ac9 100644 --- a/tests/ported_static/stMemoryTest/test_mem32kb_minus_1.py +++ b/tests/ported_static/stMemoryTest/test_mem32kb_minus_1.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -28,6 +30,7 @@ @pytest.mark.pre_alloc_mutable def test_mem32kb_minus_1( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_mem32kb_minus_1.""" @@ -40,7 +43,6 @@ def test_mem32kb_minus_1( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) # Source: lll @@ -58,7 +60,7 @@ def test_mem32kb_minus_1( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=10, ) diff --git a/tests/ported_static/stMemoryTest/test_mem32kb_minus_31.py b/tests/ported_static/stMemoryTest/test_mem32kb_minus_31.py index d02c97eac67..831da42c424 100644 --- a/tests/ported_static/stMemoryTest/test_mem32kb_minus_31.py +++ b/tests/ported_static/stMemoryTest/test_mem32kb_minus_31.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -28,6 +30,7 @@ @pytest.mark.pre_alloc_mutable def test_mem32kb_minus_31( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_mem32kb_minus_31.""" @@ -40,7 +43,6 @@ def test_mem32kb_minus_31( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) # Source: lll @@ -58,7 +60,7 @@ def test_mem32kb_minus_31( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=10, ) diff --git a/tests/ported_static/stMemoryTest/test_mem32kb_minus_32.py b/tests/ported_static/stMemoryTest/test_mem32kb_minus_32.py index 1093a4f54f6..a667c7b5bc2 100644 --- a/tests/ported_static/stMemoryTest/test_mem32kb_minus_32.py +++ b/tests/ported_static/stMemoryTest/test_mem32kb_minus_32.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -28,6 +30,7 @@ @pytest.mark.pre_alloc_mutable def test_mem32kb_minus_32( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_mem32kb_minus_32.""" @@ -40,7 +43,6 @@ def test_mem32kb_minus_32( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) # Source: lll @@ -58,7 +60,7 @@ def test_mem32kb_minus_32( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=10, ) diff --git a/tests/ported_static/stMemoryTest/test_mem32kb_minus_33.py b/tests/ported_static/stMemoryTest/test_mem32kb_minus_33.py index 64cbdd9afc7..da90cf1b862 100644 --- a/tests/ported_static/stMemoryTest/test_mem32kb_minus_33.py +++ b/tests/ported_static/stMemoryTest/test_mem32kb_minus_33.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -28,6 +30,7 @@ @pytest.mark.pre_alloc_mutable def test_mem32kb_minus_33( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_mem32kb_minus_33.""" @@ -40,7 +43,6 @@ def test_mem32kb_minus_33( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) # Source: lll @@ -58,7 +60,7 @@ def test_mem32kb_minus_33( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=10, ) diff --git a/tests/ported_static/stMemoryTest/test_mem32kb_plus_1.py b/tests/ported_static/stMemoryTest/test_mem32kb_plus_1.py index dafcda655e8..06588fb0a3e 100644 --- a/tests/ported_static/stMemoryTest/test_mem32kb_plus_1.py +++ b/tests/ported_static/stMemoryTest/test_mem32kb_plus_1.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -28,6 +30,7 @@ @pytest.mark.pre_alloc_mutable def test_mem32kb_plus_1( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_mem32kb_plus_1.""" @@ -40,7 +43,6 @@ def test_mem32kb_plus_1( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) # Source: lll @@ -58,7 +60,7 @@ def test_mem32kb_plus_1( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=10, ) diff --git a/tests/ported_static/stMemoryTest/test_mem32kb_plus_31.py b/tests/ported_static/stMemoryTest/test_mem32kb_plus_31.py index e7485db550c..6ff59923330 100644 --- a/tests/ported_static/stMemoryTest/test_mem32kb_plus_31.py +++ b/tests/ported_static/stMemoryTest/test_mem32kb_plus_31.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -28,6 +30,7 @@ @pytest.mark.pre_alloc_mutable def test_mem32kb_plus_31( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_mem32kb_plus_31.""" @@ -40,7 +43,6 @@ def test_mem32kb_plus_31( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) # Source: lll @@ -58,7 +60,7 @@ def test_mem32kb_plus_31( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=10, ) diff --git a/tests/ported_static/stMemoryTest/test_mem32kb_plus_32.py b/tests/ported_static/stMemoryTest/test_mem32kb_plus_32.py index 729337fa8ea..9722e13bb71 100644 --- a/tests/ported_static/stMemoryTest/test_mem32kb_plus_32.py +++ b/tests/ported_static/stMemoryTest/test_mem32kb_plus_32.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -28,6 +30,7 @@ @pytest.mark.pre_alloc_mutable def test_mem32kb_plus_32( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_mem32kb_plus_32.""" @@ -40,7 +43,6 @@ def test_mem32kb_plus_32( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) # Source: lll @@ -58,7 +60,7 @@ def test_mem32kb_plus_32( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=10, ) diff --git a/tests/ported_static/stMemoryTest/test_mem32kb_plus_33.py b/tests/ported_static/stMemoryTest/test_mem32kb_plus_33.py index f571510af1e..b3f85e1550d 100644 --- a/tests/ported_static/stMemoryTest/test_mem32kb_plus_33.py +++ b/tests/ported_static/stMemoryTest/test_mem32kb_plus_33.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -28,6 +30,7 @@ @pytest.mark.pre_alloc_mutable def test_mem32kb_plus_33( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_mem32kb_plus_33.""" @@ -40,7 +43,6 @@ def test_mem32kb_plus_33( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) # Source: lll @@ -58,7 +60,7 @@ def test_mem32kb_plus_33( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=10, ) diff --git a/tests/ported_static/stMemoryTest/test_mem32kb_single_byte.py b/tests/ported_static/stMemoryTest/test_mem32kb_single_byte.py index c44e45cc506..fa5d24dae28 100644 --- a/tests/ported_static/stMemoryTest/test_mem32kb_single_byte.py +++ b/tests/ported_static/stMemoryTest/test_mem32kb_single_byte.py @@ -3,6 +3,9 @@ Ported from: state_tests/stMemoryTest/mem32kb_singleByteFiller.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +18,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +33,14 @@ def test_mem32kb_single_byte( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_mem32kb_single_byte.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 300_000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0x6400000000) @@ -40,7 +50,6 @@ def test_mem32kb_single_byte( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) # Source: lll @@ -57,7 +66,7 @@ def test_mem32kb_single_byte( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=tx_gas_limit, value=10, ) diff --git a/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_minus_1.py b/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_minus_1.py index 27b8314fe8a..49986e0279f 100644 --- a/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_minus_1.py +++ b/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_minus_1.py @@ -3,6 +3,9 @@ Ported from: state_tests/stMemoryTest/mem32kb_singleByte-1Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +18,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +33,14 @@ def test_mem32kb_single_byte_minus_1( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_mem32kb_single_byte_minus_1.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 300_000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0x6400000000) @@ -40,7 +50,6 @@ def test_mem32kb_single_byte_minus_1( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) # Source: lll @@ -57,7 +66,7 @@ def test_mem32kb_single_byte_minus_1( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=tx_gas_limit, value=10, ) diff --git a/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_minus_31.py b/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_minus_31.py index f66669a5a5c..9931597621c 100644 --- a/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_minus_31.py +++ b/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_minus_31.py @@ -3,6 +3,9 @@ Ported from: state_tests/stMemoryTest/mem32kb_singleByte-31Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +18,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +33,14 @@ def test_mem32kb_single_byte_minus_31( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_mem32kb_single_byte_minus_31.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 300_000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0x6400000000) @@ -40,7 +50,6 @@ def test_mem32kb_single_byte_minus_31( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) # Source: lll @@ -57,7 +66,7 @@ def test_mem32kb_single_byte_minus_31( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=tx_gas_limit, value=10, ) diff --git a/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_minus_32.py b/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_minus_32.py index 8750a2af20d..b25a8187a58 100644 --- a/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_minus_32.py +++ b/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_minus_32.py @@ -3,6 +3,9 @@ Ported from: state_tests/stMemoryTest/mem32kb_singleByte-32Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +18,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +33,14 @@ def test_mem32kb_single_byte_minus_32( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_mem32kb_single_byte_minus_32.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 300_000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0x6400000000) @@ -40,7 +50,6 @@ def test_mem32kb_single_byte_minus_32( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) # Source: lll @@ -57,7 +66,7 @@ def test_mem32kb_single_byte_minus_32( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=tx_gas_limit, value=10, ) diff --git a/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_minus_33.py b/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_minus_33.py index da15d33663d..848a8a59319 100644 --- a/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_minus_33.py +++ b/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_minus_33.py @@ -3,6 +3,9 @@ Ported from: state_tests/stMemoryTest/mem32kb_singleByte-33Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +18,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +33,14 @@ def test_mem32kb_single_byte_minus_33( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_mem32kb_single_byte_minus_33.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 300_000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0x6400000000) @@ -40,7 +50,6 @@ def test_mem32kb_single_byte_minus_33( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) # Source: lll @@ -57,7 +66,7 @@ def test_mem32kb_single_byte_minus_33( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=tx_gas_limit, value=10, ) diff --git a/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_plus_1.py b/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_plus_1.py index 34345e79be7..2fb7c811573 100644 --- a/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_plus_1.py +++ b/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_plus_1.py @@ -3,6 +3,9 @@ Ported from: state_tests/stMemoryTest/mem32kb_singleByte+1Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +18,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +33,14 @@ def test_mem32kb_single_byte_plus_1( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_mem32kb_single_byte_plus_1.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 300_000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0x6400000000) @@ -40,7 +50,6 @@ def test_mem32kb_single_byte_plus_1( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) # Source: lll @@ -57,7 +66,7 @@ def test_mem32kb_single_byte_plus_1( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=tx_gas_limit, value=10, ) diff --git a/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_plus_31.py b/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_plus_31.py index 583fc5f103a..d03e663c1ba 100644 --- a/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_plus_31.py +++ b/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_plus_31.py @@ -3,6 +3,9 @@ Ported from: state_tests/stMemoryTest/mem32kb_singleByte+31Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +18,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +33,14 @@ def test_mem32kb_single_byte_plus_31( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_mem32kb_single_byte_plus_31.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 300_000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0x6400000000) @@ -40,7 +50,6 @@ def test_mem32kb_single_byte_plus_31( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) # Source: lll @@ -57,7 +66,7 @@ def test_mem32kb_single_byte_plus_31( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=tx_gas_limit, value=10, ) diff --git a/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_plus_32.py b/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_plus_32.py index a2bd740ed36..9cdbcc90c64 100644 --- a/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_plus_32.py +++ b/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_plus_32.py @@ -3,6 +3,9 @@ Ported from: state_tests/stMemoryTest/mem32kb_singleByte+32Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +18,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +33,14 @@ def test_mem32kb_single_byte_plus_32( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_mem32kb_single_byte_plus_32.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 300_000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0x6400000000) @@ -40,7 +50,6 @@ def test_mem32kb_single_byte_plus_32( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) # Source: lll @@ -57,7 +66,7 @@ def test_mem32kb_single_byte_plus_32( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=tx_gas_limit, value=10, ) diff --git a/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_plus_33.py b/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_plus_33.py index 556f398db04..8c976ddc056 100644 --- a/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_plus_33.py +++ b/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_plus_33.py @@ -3,6 +3,9 @@ Ported from: state_tests/stMemoryTest/mem32kb_singleByte+33Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +18,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +33,14 @@ def test_mem32kb_single_byte_plus_33( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_mem32kb_single_byte_plus_33.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 300_000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0x6400000000) @@ -40,7 +50,6 @@ def test_mem32kb_single_byte_plus_33( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) # Source: lll @@ -57,7 +66,7 @@ def test_mem32kb_single_byte_plus_33( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=tx_gas_limit, value=10, ) diff --git a/tests/ported_static/stMemoryTest/test_mem33b_single_byte.py b/tests/ported_static/stMemoryTest/test_mem33b_single_byte.py index 46c75434094..dd2e815624f 100644 --- a/tests/ported_static/stMemoryTest/test_mem33b_single_byte.py +++ b/tests/ported_static/stMemoryTest/test_mem33b_single_byte.py @@ -3,6 +3,9 @@ Ported from: state_tests/stMemoryTest/mem33b_singleByteFiller.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +18,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +33,14 @@ def test_mem33b_single_byte( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_mem33b_single_byte.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 200_000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0x6400000000) @@ -40,7 +50,6 @@ def test_mem33b_single_byte( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) # Source: lll @@ -57,7 +66,7 @@ def test_mem33b_single_byte( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=tx_gas_limit, value=10, ) diff --git a/tests/ported_static/stMemoryTest/test_mem64kb.py b/tests/ported_static/stMemoryTest/test_mem64kb.py index e21071e1249..b12cfaff0b0 100644 --- a/tests/ported_static/stMemoryTest/test_mem64kb.py +++ b/tests/ported_static/stMemoryTest/test_mem64kb.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -28,6 +30,7 @@ @pytest.mark.pre_alloc_mutable def test_mem64kb( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_mem64kb.""" @@ -40,7 +43,6 @@ def test_mem64kb( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) # Source: lll @@ -58,7 +60,7 @@ def test_mem64kb( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=10, ) diff --git a/tests/ported_static/stMemoryTest/test_mem64kb_minus_1.py b/tests/ported_static/stMemoryTest/test_mem64kb_minus_1.py index 22383d2a177..5b6f93c6673 100644 --- a/tests/ported_static/stMemoryTest/test_mem64kb_minus_1.py +++ b/tests/ported_static/stMemoryTest/test_mem64kb_minus_1.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -28,6 +30,7 @@ @pytest.mark.pre_alloc_mutable def test_mem64kb_minus_1( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_mem64kb_minus_1.""" @@ -40,7 +43,6 @@ def test_mem64kb_minus_1( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) # Source: lll @@ -58,7 +60,7 @@ def test_mem64kb_minus_1( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=10, ) diff --git a/tests/ported_static/stMemoryTest/test_mem64kb_minus_31.py b/tests/ported_static/stMemoryTest/test_mem64kb_minus_31.py index 98293337bae..988c1353ffb 100644 --- a/tests/ported_static/stMemoryTest/test_mem64kb_minus_31.py +++ b/tests/ported_static/stMemoryTest/test_mem64kb_minus_31.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -28,6 +30,7 @@ @pytest.mark.pre_alloc_mutable def test_mem64kb_minus_31( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_mem64kb_minus_31.""" @@ -40,7 +43,6 @@ def test_mem64kb_minus_31( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) # Source: lll @@ -58,7 +60,7 @@ def test_mem64kb_minus_31( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=10, ) diff --git a/tests/ported_static/stMemoryTest/test_mem64kb_minus_32.py b/tests/ported_static/stMemoryTest/test_mem64kb_minus_32.py index 2b119c33641..c9455d2f7b5 100644 --- a/tests/ported_static/stMemoryTest/test_mem64kb_minus_32.py +++ b/tests/ported_static/stMemoryTest/test_mem64kb_minus_32.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -28,6 +30,7 @@ @pytest.mark.pre_alloc_mutable def test_mem64kb_minus_32( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_mem64kb_minus_32.""" @@ -40,7 +43,6 @@ def test_mem64kb_minus_32( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) # Source: lll @@ -58,7 +60,7 @@ def test_mem64kb_minus_32( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=10, ) diff --git a/tests/ported_static/stMemoryTest/test_mem64kb_minus_33.py b/tests/ported_static/stMemoryTest/test_mem64kb_minus_33.py index 67241de1f79..4784be61c3b 100644 --- a/tests/ported_static/stMemoryTest/test_mem64kb_minus_33.py +++ b/tests/ported_static/stMemoryTest/test_mem64kb_minus_33.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -28,6 +30,7 @@ @pytest.mark.pre_alloc_mutable def test_mem64kb_minus_33( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_mem64kb_minus_33.""" @@ -40,7 +43,6 @@ def test_mem64kb_minus_33( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) # Source: lll @@ -58,7 +60,7 @@ def test_mem64kb_minus_33( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=10, ) diff --git a/tests/ported_static/stMemoryTest/test_mem64kb_plus_1.py b/tests/ported_static/stMemoryTest/test_mem64kb_plus_1.py index 5991bb0989d..b53bd6e43dd 100644 --- a/tests/ported_static/stMemoryTest/test_mem64kb_plus_1.py +++ b/tests/ported_static/stMemoryTest/test_mem64kb_plus_1.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -28,6 +30,7 @@ @pytest.mark.pre_alloc_mutable def test_mem64kb_plus_1( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_mem64kb_plus_1.""" @@ -40,7 +43,6 @@ def test_mem64kb_plus_1( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) # Source: lll @@ -58,7 +60,7 @@ def test_mem64kb_plus_1( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=10, ) diff --git a/tests/ported_static/stMemoryTest/test_mem64kb_plus_31.py b/tests/ported_static/stMemoryTest/test_mem64kb_plus_31.py index 8d556ff6c7f..93e826ed878 100644 --- a/tests/ported_static/stMemoryTest/test_mem64kb_plus_31.py +++ b/tests/ported_static/stMemoryTest/test_mem64kb_plus_31.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -28,6 +30,7 @@ @pytest.mark.pre_alloc_mutable def test_mem64kb_plus_31( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_mem64kb_plus_31.""" @@ -40,7 +43,6 @@ def test_mem64kb_plus_31( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) # Source: lll @@ -58,7 +60,7 @@ def test_mem64kb_plus_31( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=10, ) diff --git a/tests/ported_static/stMemoryTest/test_mem64kb_plus_32.py b/tests/ported_static/stMemoryTest/test_mem64kb_plus_32.py index 465babad461..29782b8f977 100644 --- a/tests/ported_static/stMemoryTest/test_mem64kb_plus_32.py +++ b/tests/ported_static/stMemoryTest/test_mem64kb_plus_32.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -28,6 +30,7 @@ @pytest.mark.pre_alloc_mutable def test_mem64kb_plus_32( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_mem64kb_plus_32.""" @@ -40,7 +43,6 @@ def test_mem64kb_plus_32( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) # Source: lll @@ -58,7 +60,7 @@ def test_mem64kb_plus_32( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=10, ) diff --git a/tests/ported_static/stMemoryTest/test_mem64kb_plus_33.py b/tests/ported_static/stMemoryTest/test_mem64kb_plus_33.py index 4c63e0333b9..abdeaaabfbc 100644 --- a/tests/ported_static/stMemoryTest/test_mem64kb_plus_33.py +++ b/tests/ported_static/stMemoryTest/test_mem64kb_plus_33.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -28,6 +30,7 @@ @pytest.mark.pre_alloc_mutable def test_mem64kb_plus_33( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_mem64kb_plus_33.""" @@ -40,7 +43,6 @@ def test_mem64kb_plus_33( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) # Source: lll @@ -58,7 +60,7 @@ def test_mem64kb_plus_33( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=10, ) diff --git a/tests/ported_static/stMemoryTest/test_mem64kb_single_byte.py b/tests/ported_static/stMemoryTest/test_mem64kb_single_byte.py index e095fa5ded9..68ac145e74f 100644 --- a/tests/ported_static/stMemoryTest/test_mem64kb_single_byte.py +++ b/tests/ported_static/stMemoryTest/test_mem64kb_single_byte.py @@ -3,6 +3,9 @@ Ported from: state_tests/stMemoryTest/mem64kb_singleByteFiller.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +18,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +33,14 @@ def test_mem64kb_single_byte( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_mem64kb_single_byte.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 1_000_000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0x6400000000) @@ -40,7 +50,6 @@ def test_mem64kb_single_byte( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) # Source: lll @@ -57,7 +66,7 @@ def test_mem64kb_single_byte( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=tx_gas_limit, value=10, ) diff --git a/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_minus_1.py b/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_minus_1.py index 6bf456d288f..d89f096cb45 100644 --- a/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_minus_1.py +++ b/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_minus_1.py @@ -3,6 +3,9 @@ Ported from: state_tests/stMemoryTest/mem64kb_singleByte-1Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +18,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +33,14 @@ def test_mem64kb_single_byte_minus_1( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_mem64kb_single_byte_minus_1.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 1_000_000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0x6400000000) @@ -40,7 +50,6 @@ def test_mem64kb_single_byte_minus_1( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) # Source: lll @@ -57,7 +66,7 @@ def test_mem64kb_single_byte_minus_1( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=tx_gas_limit, value=10, ) diff --git a/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_minus_31.py b/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_minus_31.py index b211c18b6f7..adbbfb14cd4 100644 --- a/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_minus_31.py +++ b/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_minus_31.py @@ -3,6 +3,9 @@ Ported from: state_tests/stMemoryTest/mem64kb_singleByte-31Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +18,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +33,14 @@ def test_mem64kb_single_byte_minus_31( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_mem64kb_single_byte_minus_31.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 1_000_000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0x6400000000) @@ -40,7 +50,6 @@ def test_mem64kb_single_byte_minus_31( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) # Source: lll @@ -57,7 +66,7 @@ def test_mem64kb_single_byte_minus_31( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=tx_gas_limit, value=10, ) diff --git a/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_minus_32.py b/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_minus_32.py index e79769c8d96..c03bd98cb1a 100644 --- a/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_minus_32.py +++ b/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_minus_32.py @@ -3,6 +3,9 @@ Ported from: state_tests/stMemoryTest/mem64kb_singleByte-32Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +18,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +33,14 @@ def test_mem64kb_single_byte_minus_32( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_mem64kb_single_byte_minus_32.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 1_000_000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0x6400000000) @@ -40,7 +50,6 @@ def test_mem64kb_single_byte_minus_32( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) # Source: lll @@ -57,7 +66,7 @@ def test_mem64kb_single_byte_minus_32( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=tx_gas_limit, value=10, ) diff --git a/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_minus_33.py b/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_minus_33.py index 00a4b9e6adf..e6697924f19 100644 --- a/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_minus_33.py +++ b/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_minus_33.py @@ -3,6 +3,9 @@ Ported from: state_tests/stMemoryTest/mem64kb_singleByte-33Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +18,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +33,14 @@ def test_mem64kb_single_byte_minus_33( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_mem64kb_single_byte_minus_33.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 1_000_000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0x6400000000) @@ -40,7 +50,6 @@ def test_mem64kb_single_byte_minus_33( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) # Source: lll @@ -57,7 +66,7 @@ def test_mem64kb_single_byte_minus_33( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=tx_gas_limit, value=10, ) diff --git a/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_plus_1.py b/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_plus_1.py index 9f92d0fea32..9717e71651b 100644 --- a/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_plus_1.py +++ b/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_plus_1.py @@ -3,6 +3,9 @@ Ported from: state_tests/stMemoryTest/mem64kb_singleByte+1Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +18,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +33,14 @@ def test_mem64kb_single_byte_plus_1( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_mem64kb_single_byte_plus_1.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 1_000_000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0x6400000000) @@ -40,7 +50,6 @@ def test_mem64kb_single_byte_plus_1( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) # Source: lll @@ -57,7 +66,7 @@ def test_mem64kb_single_byte_plus_1( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=tx_gas_limit, value=10, ) diff --git a/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_plus_31.py b/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_plus_31.py index 434a6dda64e..eff7dbdbbca 100644 --- a/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_plus_31.py +++ b/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_plus_31.py @@ -3,6 +3,9 @@ Ported from: state_tests/stMemoryTest/mem64kb_singleByte+31Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +18,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +33,14 @@ def test_mem64kb_single_byte_plus_31( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_mem64kb_single_byte_plus_31.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 1_000_000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0x6400000000) @@ -40,7 +50,6 @@ def test_mem64kb_single_byte_plus_31( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) # Source: lll @@ -57,7 +66,7 @@ def test_mem64kb_single_byte_plus_31( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=tx_gas_limit, value=10, ) diff --git a/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_plus_32.py b/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_plus_32.py index f8b36bbe824..b8882820448 100644 --- a/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_plus_32.py +++ b/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_plus_32.py @@ -3,6 +3,9 @@ Ported from: state_tests/stMemoryTest/mem64kb_singleByte+32Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +18,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +33,14 @@ def test_mem64kb_single_byte_plus_32( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_mem64kb_single_byte_plus_32.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 1_000_000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0x6400000000) @@ -40,7 +50,6 @@ def test_mem64kb_single_byte_plus_32( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) # Source: lll @@ -57,7 +66,7 @@ def test_mem64kb_single_byte_plus_32( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=tx_gas_limit, value=10, ) diff --git a/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_plus_33.py b/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_plus_33.py index 89d78adcb29..6e9b9c4d55b 100644 --- a/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_plus_33.py +++ b/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_plus_33.py @@ -3,6 +3,9 @@ Ported from: state_tests/stMemoryTest/mem64kb_singleByte+33Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +18,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +33,14 @@ def test_mem64kb_single_byte_plus_33( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_mem64kb_single_byte_plus_33.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 1_000_000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0x6400000000) @@ -40,7 +50,6 @@ def test_mem64kb_single_byte_plus_33( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) # Source: lll @@ -57,7 +66,7 @@ def test_mem64kb_single_byte_plus_33( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=tx_gas_limit, value=10, ) diff --git a/tests/ported_static/stPreCompiledContracts/test_precomps_eip2929_cancun.py b/tests/ported_static/stPreCompiledContracts/test_precomps_eip2929_cancun.py index 6bc03947432..27dbb54b3af 100644 --- a/tests/ported_static/stPreCompiledContracts/test_precomps_eip2929_cancun.py +++ b/tests/ported_static/stPreCompiledContracts/test_precomps_eip2929_cancun.py @@ -3,6 +3,17 @@ Ported from: state_tests/stPreCompiledContracts/precompsEIP2929CancunFiller.yml + +@manually-enhanced: Do not overwrite. 87 parametrizations of this +test measure the regular gas consumed by a CALL with value to an +inactive precompile address. EIP-8037 replaces the Cancun-era +CALL_NEW_ACCOUNT cost of 25 000 with a per-new-account state-gas +charge of `STATE_BYTES_PER_NEW_ACCOUNT (112) * COST_PER_STATE_BYTE +(1174) = 131 488`. With an empty reservoir (the case here), the +full state-gas spills back into regular gas, so `Op.GAS` reads ++106 488 (= 131 488 - 25 000) compared to Cancun. Bake that delta +into the two affected `[">=Cancun"]` expect-entries fork-condition- +ally; the third entry is gated to `["Cancun"]` only and unchanged. """ import pytest @@ -2825,7 +2836,6 @@ def test_precomps_eip2929_cancun( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=71794957647893862, ) # Source: yul @@ -3581,6 +3591,13 @@ def test_precomps_eip2929_cancun( nonce=1, ) + # EIP-8037 replaces the 25 000 CALL_NEW_ACCOUNT base cost with a + # 131 488 state-gas charge. With an empty reservoir the full + # state-gas spills into regular gas, so Op.GAS reads +106 488. + new_account_delta = ( + (fork.create_state_gas() - 25000) if fork.is_eip_enabled(8037) else 0 + ) + expect_entries_: list[dict] = [ { "indexes": { @@ -4219,7 +4236,9 @@ def test_precomps_eip2929_cancun( "value": -1, }, "network": [">=Cancun"], - "result": {target: Account(storage={0: 0, 1: 25000})}, + "result": { + target: Account(storage={0: 0, 1: 25000 + new_account_delta}) + }, }, { "indexes": { @@ -4228,7 +4247,9 @@ def test_precomps_eip2929_cancun( "value": -1, }, "network": [">=Cancun"], - "result": {target: Account(storage={0: 0, 1: 27500})}, + "result": { + target: Account(storage={0: 0, 1: 27500 + new_account_delta}) + }, }, { "indexes": { diff --git a/tests/ported_static/stPreCompiledContracts2/test_call_ecrecover_overflow.py b/tests/ported_static/stPreCompiledContracts2/test_call_ecrecover_overflow.py index a2eaac17506..f88c2d7b053 100644 --- a/tests/ported_static/stPreCompiledContracts2/test_call_ecrecover_overflow.py +++ b/tests/ported_static/stPreCompiledContracts2/test_call_ecrecover_overflow.py @@ -3,6 +3,13 @@ Ported from: state_tests/stPreCompiledContracts2/CallEcrecover_OverflowFiller.yml + +@manually-enhanced: Do not overwrite. `tx_gas` raised from 100 000 to +500 000 so the two outer SSTOREs that wrap the ecrecover precompile +CALL have headroom for EIP-8037 per-storage state-gas on Amsterdam. +The inner CALL still passes exactly 3 000 gas (the ecrecover precompile +cost — that's the test premise); only the outer tx budget grew. Post- +state expectations are unchanged on all forks. """ import pytest @@ -105,7 +112,6 @@ def test_call_ecrecover_overflow( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=71794957647893862, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -275,7 +281,15 @@ def test_call_ecrecover_overflow( 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD036413F ), ] - tx_gas = [100000] + # On Amsterdam the two outer SSTOREs that wrap the inner ecrecover + # CALL each accumulate EIP-8037 per-storage state-gas (37 568) that + # spills back into regular gas once the empty reservoir is drained, + # pushing the tx over the original 100 000 budget. Bump on EIP-8037 + # only; pre-EIP-8037 forks keep the original. + outer_tx_gas = 100000 + if fork.is_eip_enabled(8037): + outer_tx_gas = 500000 + tx_gas = [outer_tx_gas] tx_value = [100000] tx = Transaction( diff --git a/tests/ported_static/stPreCompiledContracts2/test_modexp_0_0_0_20500.py b/tests/ported_static/stPreCompiledContracts2/test_modexp_0_0_0_20500.py index 59e3af2c647..9d30c32379d 100644 --- a/tests/ported_static/stPreCompiledContracts2/test_modexp_0_0_0_20500.py +++ b/tests/ported_static/stPreCompiledContracts2/test_modexp_0_0_0_20500.py @@ -3,6 +3,9 @@ Ported from: state_tests/stPreCompiledContracts2/modexp_0_0_0_20500Filler.json +@manually-enhanced: Do not overwrite. tx_gas values bumped on +Amsterdam to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -326,6 +329,9 @@ def test_modexp_0_0_0_20500( + Hash(0x0), ] tx_gas = [42540, 90000, 110000, 200000] + if fork.is_eip_enabled(8037): + # EIP-8037 state-gas spill OoGs the SSTORE; bump to fit. + tx_gas = [42540, 200000, 200000, 200000] tx = Transaction( sender=sender, diff --git a/tests/ported_static/stPreCompiledContracts2/test_modexp_0_0_0_22000.py b/tests/ported_static/stPreCompiledContracts2/test_modexp_0_0_0_22000.py index 9390b7eb00c..19c7672ffd3 100644 --- a/tests/ported_static/stPreCompiledContracts2/test_modexp_0_0_0_22000.py +++ b/tests/ported_static/stPreCompiledContracts2/test_modexp_0_0_0_22000.py @@ -3,6 +3,9 @@ Ported from: state_tests/stPreCompiledContracts2/modexp_0_0_0_22000Filler.json +@manually-enhanced: Do not overwrite. tx_gas values bumped on +Amsterdam to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -268,6 +271,9 @@ def test_modexp_0_0_0_22000( + Hash(0x0), ] tx_gas = [48136, 90000, 110000, 200000] + if fork.is_eip_enabled(8037): + # EIP-8037 state-gas spill OoGs the SSTORE; bump to fit. + tx_gas = [200000, 200000, 200000, 200000] tx = Transaction( sender=sender, diff --git a/tests/ported_static/stPreCompiledContracts2/test_modexp_0_0_0_25000.py b/tests/ported_static/stPreCompiledContracts2/test_modexp_0_0_0_25000.py index 9ae5ef9d781..a9847bc8f27 100644 --- a/tests/ported_static/stPreCompiledContracts2/test_modexp_0_0_0_25000.py +++ b/tests/ported_static/stPreCompiledContracts2/test_modexp_0_0_0_25000.py @@ -3,6 +3,9 @@ Ported from: state_tests/stPreCompiledContracts2/modexp_0_0_0_25000Filler.json +@manually-enhanced: Do not overwrite. tx_gas values bumped on +Amsterdam to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -268,6 +271,9 @@ def test_modexp_0_0_0_25000( + Hash(0x0), ] tx_gas = [47040, 90000, 110000, 200000] + if fork.is_eip_enabled(8037): + # EIP-8037 state-gas spill OoGs the SSTORE; bump to fit. + tx_gas = [200000, 200000, 200000, 200000] tx = Transaction( sender=sender, diff --git a/tests/ported_static/stPreCompiledContracts2/test_modexp_0_0_0_35000.py b/tests/ported_static/stPreCompiledContracts2/test_modexp_0_0_0_35000.py index 89b4b07c0e8..0ddf844021c 100644 --- a/tests/ported_static/stPreCompiledContracts2/test_modexp_0_0_0_35000.py +++ b/tests/ported_static/stPreCompiledContracts2/test_modexp_0_0_0_35000.py @@ -3,6 +3,9 @@ Ported from: state_tests/stPreCompiledContracts2/modexp_0_0_0_35000Filler.json +@manually-enhanced: Do not overwrite. tx_gas values bumped on +Amsterdam to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -268,6 +271,9 @@ def test_modexp_0_0_0_35000( + Hash(0x0), ] tx_gas = [57040, 90000, 110000, 200000] + if fork.is_eip_enabled(8037): + # EIP-8037 state-gas spill OoGs the SSTORE; bump to fit. + tx_gas = [200000, 200000, 200000, 200000] tx = Transaction( sender=sender, diff --git a/tests/ported_static/stQuadraticComplexityTest/test_call20_kbytes_contract50_1.py b/tests/ported_static/stQuadraticComplexityTest/test_call20_kbytes_contract50_1.py index 8d7d15a89d2..4b1d03309c1 100644 --- a/tests/ported_static/stQuadraticComplexityTest/test_call20_kbytes_contract50_1.py +++ b/tests/ported_static/stQuadraticComplexityTest/test_call20_kbytes_contract50_1.py @@ -68,7 +68,6 @@ def test_call20_kbytes_contract50_1( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=882500000000, ) # Source: raw diff --git a/tests/ported_static/stQuadraticComplexityTest/test_return50000.py b/tests/ported_static/stQuadraticComplexityTest/test_return50000.py index db6a155085d..b1471276494 100644 --- a/tests/ported_static/stQuadraticComplexityTest/test_return50000.py +++ b/tests/ported_static/stQuadraticComplexityTest/test_return50000.py @@ -69,7 +69,6 @@ def test_return50000( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=8825000000, ) pre[sender] = Account(balance=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) diff --git a/tests/ported_static/stQuadraticComplexityTest/test_return50000_2.py b/tests/ported_static/stQuadraticComplexityTest/test_return50000_2.py index 2f1ea8906eb..d8f060e96a9 100644 --- a/tests/ported_static/stQuadraticComplexityTest/test_return50000_2.py +++ b/tests/ported_static/stQuadraticComplexityTest/test_return50000_2.py @@ -69,7 +69,6 @@ def test_return50000_2( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=8825000000, ) pre[sender] = Account(balance=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) diff --git a/tests/ported_static/stRandom/test_random_statetest100.py b/tests/ported_static/stRandom/test_random_statetest100.py index 4a17e601da9..516a6ea65a6 100644 --- a/tests/ported_static/stRandom/test_random_statetest100.py +++ b/tests/ported_static/stRandom/test_random_statetest100.py @@ -40,7 +40,6 @@ def test_random_statetest100( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw diff --git a/tests/ported_static/stRandom/test_random_statetest102.py b/tests/ported_static/stRandom/test_random_statetest102.py index 8301be48642..4c6423fd4fa 100644 --- a/tests/ported_static/stRandom/test_random_statetest102.py +++ b/tests/ported_static/stRandom/test_random_statetest102.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest102Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest102( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest102.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest102( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -87,7 +96,7 @@ def test_random_statetest102( data=Bytes( "457f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e7944447f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe6f157094ffff1a04893a9cf3858b8576" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x25D01AE2, ) diff --git a/tests/ported_static/stRandom/test_random_statetest104.py b/tests/ported_static/stRandom/test_random_statetest104.py index afe9dcba48b..114d7820a3a 100644 --- a/tests/ported_static/stRandom/test_random_statetest104.py +++ b/tests/ported_static/stRandom/test_random_statetest104.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest104Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest104( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest104.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest104( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -95,7 +104,7 @@ def test_random_statetest104( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe6f147d6b978c780a82619772417d5b6a" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x3A3FA7D5, ) diff --git a/tests/ported_static/stRandom/test_random_statetest105.py b/tests/ported_static/stRandom/test_random_statetest105.py index 90f64842584..84f03f71bd7 100644 --- a/tests/ported_static/stRandom/test_random_statetest105.py +++ b/tests/ported_static/stRandom/test_random_statetest105.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest105Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest105( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest105.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest105( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -89,7 +98,7 @@ def test_random_statetest105( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff437f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff6f9914639111156d1759ff65039a02926c" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x566920F7, ) diff --git a/tests/ported_static/stRandom/test_random_statetest106.py b/tests/ported_static/stRandom/test_random_statetest106.py index 5ad21bb902b..0b31c2aa12f 100644 --- a/tests/ported_static/stRandom/test_random_statetest106.py +++ b/tests/ported_static/stRandom/test_random_statetest106.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest106Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest106( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest106.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest106( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -92,7 +101,7 @@ def test_random_statetest106( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000100000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6f327043726481f25094828e21155779" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x41FF266C, ) diff --git a/tests/ported_static/stRandom/test_random_statetest107.py b/tests/ported_static/stRandom/test_random_statetest107.py index 2f1309ef117..3ed7e239af2 100644 --- a/tests/ported_static/stRandom/test_random_statetest107.py +++ b/tests/ported_static/stRandom/test_random_statetest107.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest107Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest107( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest107.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x945304EB96065B2A98B57A48A06AE28D285A71B5) contract_0 = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87) sender = EOA( @@ -44,7 +54,6 @@ def test_random_statetest107( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -93,7 +102,7 @@ def test_random_statetest107( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe457fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b509" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x4F9C450B, ) diff --git a/tests/ported_static/stRandom/test_random_statetest11.py b/tests/ported_static/stRandom/test_random_statetest11.py index 72d45df370e..0fa94e89cfc 100644 --- a/tests/ported_static/stRandom/test_random_statetest11.py +++ b/tests/ported_static/stRandom/test_random_statetest11.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest11Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest11( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest11.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest11( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -88,7 +97,7 @@ def test_random_statetest11( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000000000000000000000000000000000000000c3506fa093f3408a6e531735960a7617127a" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x6909D3EC, ) diff --git a/tests/ported_static/stRandom/test_random_statetest110.py b/tests/ported_static/stRandom/test_random_statetest110.py index d1979dc473f..92c3316ccd0 100644 --- a/tests/ported_static/stRandom/test_random_statetest110.py +++ b/tests/ported_static/stRandom/test_random_statetest110.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest110Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest110( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest110.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest110( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -87,7 +96,7 @@ def test_random_statetest110( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe417f00000000000000000000000000000000000000000000000000000000000000016f97543c343476cb7c8c84066217f102" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x2BEB343B, ) diff --git a/tests/ported_static/stRandom/test_random_statetest112.py b/tests/ported_static/stRandom/test_random_statetest112.py index a9b2faece89..f8e4ef355a9 100644 --- a/tests/ported_static/stRandom/test_random_statetest112.py +++ b/tests/ported_static/stRandom/test_random_statetest112.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest112Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest112( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest112.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest112( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -93,7 +102,7 @@ def test_random_statetest112( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff45447fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000006f549c5779398a848c35307514650541" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x779DB8A6, ) diff --git a/tests/ported_static/stRandom/test_random_statetest114.py b/tests/ported_static/stRandom/test_random_statetest114.py index f47ea665ee4..d6d636c9e8d 100644 --- a/tests/ported_static/stRandom/test_random_statetest114.py +++ b/tests/ported_static/stRandom/test_random_statetest114.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest114Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest114( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest114.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest114( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -94,7 +103,7 @@ def test_random_statetest114( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe417fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6f3584357ea388725483637d4471727f" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x3FA85EB3, ) diff --git a/tests/ported_static/stRandom/test_random_statetest115.py b/tests/ported_static/stRandom/test_random_statetest115.py index de2cae7b5b0..479bb130922 100644 --- a/tests/ported_static/stRandom/test_random_statetest115.py +++ b/tests/ported_static/stRandom/test_random_statetest115.py @@ -43,7 +43,6 @@ def test_random_statetest115( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) diff --git a/tests/ported_static/stRandom/test_random_statetest116.py b/tests/ported_static/stRandom/test_random_statetest116.py index 7a2db95bd91..388421585ea 100644 --- a/tests/ported_static/stRandom/test_random_statetest116.py +++ b/tests/ported_static/stRandom/test_random_statetest116.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest116Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest116( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest116.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest116( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -94,7 +103,7 @@ def test_random_statetest116( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe457fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e7907539337" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x162D4E87, ) diff --git a/tests/ported_static/stRandom/test_random_statetest117.py b/tests/ported_static/stRandom/test_random_statetest117.py index 4dd85d25fc1..5874efbb26b 100644 --- a/tests/ported_static/stRandom/test_random_statetest117.py +++ b/tests/ported_static/stRandom/test_random_statetest117.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest117Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest117( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest117.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest117( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -85,7 +94,7 @@ def test_random_statetest117( data=Bytes( "447f00000000000000000000000000000000000000000000000000000000000000017f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79427f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000006f8aa4a4980274f18c6158368d415714" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x691AC7A4, ) diff --git a/tests/ported_static/stRandom/test_random_statetest118.py b/tests/ported_static/stRandom/test_random_statetest118.py index 07766a98ece..e6cbb0601d6 100644 --- a/tests/ported_static/stRandom/test_random_statetest118.py +++ b/tests/ported_static/stRandom/test_random_statetest118.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest118Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest118( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest118.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest118( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -92,7 +101,7 @@ def test_random_statetest118( data=Bytes( "457ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000006f55817c037fa45bf3850320309a8f02" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x46F13668, ) diff --git a/tests/ported_static/stRandom/test_random_statetest119.py b/tests/ported_static/stRandom/test_random_statetest119.py index 1b61ded5c5c..724114ace31 100644 --- a/tests/ported_static/stRandom/test_random_statetest119.py +++ b/tests/ported_static/stRandom/test_random_statetest119.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest119Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest119( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest119.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest119( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -86,7 +95,7 @@ def test_random_statetest119( data=Bytes( "4559437f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000006f52503b127c115a9673a43137909566" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x189731CA, ) diff --git a/tests/ported_static/stRandom/test_random_statetest12.py b/tests/ported_static/stRandom/test_random_statetest12.py index 86f7a58f916..f1c61def0c7 100644 --- a/tests/ported_static/stRandom/test_random_statetest12.py +++ b/tests/ported_static/stRandom/test_random_statetest12.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest12Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest12( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest12.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest12( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -90,7 +99,7 @@ def test_random_statetest12( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff457ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79027f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff6f165490a41215369ef2760379411633" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x4576EB63, ) diff --git a/tests/ported_static/stRandom/test_random_statetest120.py b/tests/ported_static/stRandom/test_random_statetest120.py index af3ef063276..e37628a38ef 100644 --- a/tests/ported_static/stRandom/test_random_statetest120.py +++ b/tests/ported_static/stRandom/test_random_statetest120.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest120Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest120( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest120.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest120( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -92,7 +101,7 @@ def test_random_statetest120( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe8208" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x707BF5EA, ) diff --git a/tests/ported_static/stRandom/test_random_statetest121.py b/tests/ported_static/stRandom/test_random_statetest121.py index f8b577b6d51..3db32458324 100644 --- a/tests/ported_static/stRandom/test_random_statetest121.py +++ b/tests/ported_static/stRandom/test_random_statetest121.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest121Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest121( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest121.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest121( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -91,7 +100,7 @@ def test_random_statetest121( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000000000000000000000000000000000000000c350456f305842321509108c689f7ca3195a9d" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x492A84CF, ) diff --git a/tests/ported_static/stRandom/test_random_statetest122.py b/tests/ported_static/stRandom/test_random_statetest122.py index 6cc5f6b8caf..9eff336d8f6 100644 --- a/tests/ported_static/stRandom/test_random_statetest122.py +++ b/tests/ported_static/stRandom/test_random_statetest122.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest122Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest122( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest122.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest122( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -91,7 +100,7 @@ def test_random_statetest122( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6fa2825b6c338f8d717156560af045136b" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x201771A5, ) diff --git a/tests/ported_static/stRandom/test_random_statetest124.py b/tests/ported_static/stRandom/test_random_statetest124.py index 78e7b5a37e5..33c25c1e79d 100644 --- a/tests/ported_static/stRandom/test_random_statetest124.py +++ b/tests/ported_static/stRandom/test_random_statetest124.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest124Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest124( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest124.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest124( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -79,7 +88,7 @@ def test_random_statetest124( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08125580355b17457f7463587b9a7a43" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x6863F683, ) diff --git a/tests/ported_static/stRandom/test_random_statetest129.py b/tests/ported_static/stRandom/test_random_statetest129.py index 4d1bdf56d05..be9ff65c5d1 100644 --- a/tests/ported_static/stRandom/test_random_statetest129.py +++ b/tests/ported_static/stRandom/test_random_statetest129.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest129Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest129( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest129.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest129( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -89,7 +98,7 @@ def test_random_statetest129( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6f166e733343093a31a33b8e025a0270" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x422CD1CC, ) diff --git a/tests/ported_static/stRandom/test_random_statetest130.py b/tests/ported_static/stRandom/test_random_statetest130.py index b4d4e79efde..6895e8b5670 100644 --- a/tests/ported_static/stRandom/test_random_statetest130.py +++ b/tests/ported_static/stRandom/test_random_statetest130.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest130Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest130( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest130.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest130( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -90,7 +99,7 @@ def test_random_statetest130( data=Bytes( "417fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000016f368a668b76306d181a393611988317" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x720306C0, ) diff --git a/tests/ported_static/stRandom/test_random_statetest131.py b/tests/ported_static/stRandom/test_random_statetest131.py index 839150249a4..fee5ddcf1c4 100644 --- a/tests/ported_static/stRandom/test_random_statetest131.py +++ b/tests/ported_static/stRandom/test_random_statetest131.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest131Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest131( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest131.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest131( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -89,7 +98,7 @@ def test_random_statetest131( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000001000000000000000000000000000000000000000014416f36ff85758270710168547a9777886096" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x1C479F90, ) diff --git a/tests/ported_static/stRandom/test_random_statetest137.py b/tests/ported_static/stRandom/test_random_statetest137.py index fb64a290214..9d68283af2a 100644 --- a/tests/ported_static/stRandom/test_random_statetest137.py +++ b/tests/ported_static/stRandom/test_random_statetest137.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest137Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest137( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest137.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x945304EB96065B2A98B57A48A06AE28D285A71B5) contract_0 = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87) sender = EOA( @@ -44,7 +54,6 @@ def test_random_statetest137( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -88,7 +97,7 @@ def test_random_statetest137( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000000000000000000000000000000000000000000000087f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017e7f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b5a130e86ca17390989355f092a2" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x33AC85E7, ) diff --git a/tests/ported_static/stRandom/test_random_statetest138.py b/tests/ported_static/stRandom/test_random_statetest138.py index 6f2086dd8ed..9a1c9bb162c 100644 --- a/tests/ported_static/stRandom/test_random_statetest138.py +++ b/tests/ported_static/stRandom/test_random_statetest138.py @@ -13,9 +13,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +31,7 @@ @pytest.mark.pre_alloc_mutable def test_random_statetest138( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_random_statetest138.""" @@ -43,7 +46,6 @@ def test_random_statetest138( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -87,7 +89,7 @@ def test_random_statetest138( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000000000000000000000000000000000000000c350447f0000000000000000000000000000000000000000000000000000000000000001f15951" # noqa: E501 ), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=0x771A8DAA, ) diff --git a/tests/ported_static/stRandom/test_random_statetest139.py b/tests/ported_static/stRandom/test_random_statetest139.py index eeded5a3fb6..561422cc2e2 100644 --- a/tests/ported_static/stRandom/test_random_statetest139.py +++ b/tests/ported_static/stRandom/test_random_statetest139.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest139Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +18,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +33,14 @@ def test_random_statetest139( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest139.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -40,7 +50,6 @@ def test_random_statetest139( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw @@ -84,7 +93,7 @@ def test_random_statetest139( data=Bytes( "33447f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff43446133451545" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x409CEFF3, ) diff --git a/tests/ported_static/stRandom/test_random_statetest14.py b/tests/ported_static/stRandom/test_random_statetest14.py index 8c06ccf6ca2..5a0bb57933c 100644 --- a/tests/ported_static/stRandom/test_random_statetest14.py +++ b/tests/ported_static/stRandom/test_random_statetest14.py @@ -13,9 +13,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +31,7 @@ @pytest.mark.pre_alloc_mutable def test_random_statetest14( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_random_statetest14.""" @@ -43,7 +46,6 @@ def test_random_statetest14( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -79,7 +81,7 @@ def test_random_statetest14( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff20547f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeff61853634f06b907f899d74" # noqa: E501 ), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=0x12681417, ) diff --git a/tests/ported_static/stRandom/test_random_statetest142.py b/tests/ported_static/stRandom/test_random_statetest142.py index fa2332d513d..19cae9d2e03 100644 --- a/tests/ported_static/stRandom/test_random_statetest142.py +++ b/tests/ported_static/stRandom/test_random_statetest142.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest142Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest142( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest142.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest142( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -96,7 +105,7 @@ def test_random_statetest142( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff959137630364087e1a640431107c8801" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x6DD219A0, ) diff --git a/tests/ported_static/stRandom/test_random_statetest143.py b/tests/ported_static/stRandom/test_random_statetest143.py index b702e0e691e..6e603eebcc9 100644 --- a/tests/ported_static/stRandom/test_random_statetest143.py +++ b/tests/ported_static/stRandom/test_random_statetest143.py @@ -40,7 +40,6 @@ def test_random_statetest143( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw diff --git a/tests/ported_static/stRandom/test_random_statetest145.py b/tests/ported_static/stRandom/test_random_statetest145.py index 05baefb8eb7..ba633f6f5a4 100644 --- a/tests/ported_static/stRandom/test_random_statetest145.py +++ b/tests/ported_static/stRandom/test_random_statetest145.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest145Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +18,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +33,14 @@ def test_random_statetest145( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest145.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -40,7 +50,6 @@ def test_random_statetest145( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw @@ -85,7 +94,7 @@ def test_random_statetest145( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f0000000000000000000000000000000000000000000000000000000000000000427f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000100000000000000000000000000000000000000007f0000000000000000000000000000000000000000000000000000000000000001391333" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x7E1F26DA, ) diff --git a/tests/ported_static/stRandom/test_random_statetest147.py b/tests/ported_static/stRandom/test_random_statetest147.py index 476ca4fc8e8..9cfef9e813d 100644 --- a/tests/ported_static/stRandom/test_random_statetest147.py +++ b/tests/ported_static/stRandom/test_random_statetest147.py @@ -13,9 +13,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +31,7 @@ @pytest.mark.pre_alloc_mutable def test_random_statetest147( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_random_statetest147.""" @@ -43,7 +46,6 @@ def test_random_statetest147( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -79,7 +81,7 @@ def test_random_statetest147( data=Bytes( "657ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe43659a9360" # noqa: E501 ), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=0x40FD556A, ) diff --git a/tests/ported_static/stRandom/test_random_statetest148.py b/tests/ported_static/stRandom/test_random_statetest148.py index 87290b860ac..9909560b035 100644 --- a/tests/ported_static/stRandom/test_random_statetest148.py +++ b/tests/ported_static/stRandom/test_random_statetest148.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest148Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest148( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest148.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest148( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -83,7 +92,7 @@ def test_random_statetest148( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507f0000000000000000000000000000000000000000000000000000000000000001537f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e796f34847e390773919b16559077164472" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x30607FB3, ) diff --git a/tests/ported_static/stRandom/test_random_statetest15.py b/tests/ported_static/stRandom/test_random_statetest15.py index 7587f7cac55..50abb7919ce 100644 --- a/tests/ported_static/stRandom/test_random_statetest15.py +++ b/tests/ported_static/stRandom/test_random_statetest15.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest15Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest15( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest15.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest15( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -87,7 +96,7 @@ def test_random_statetest15( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000016f436af043189b6197733280a2f1f038" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x598426AC, ) diff --git a/tests/ported_static/stRandom/test_random_statetest153.py b/tests/ported_static/stRandom/test_random_statetest153.py index 242b2d246d1..63349cca035 100644 --- a/tests/ported_static/stRandom/test_random_statetest153.py +++ b/tests/ported_static/stRandom/test_random_statetest153.py @@ -40,7 +40,6 @@ def test_random_statetest153( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw diff --git a/tests/ported_static/stRandom/test_random_statetest155.py b/tests/ported_static/stRandom/test_random_statetest155.py index e0b64cbff1a..4a3ba8c177b 100644 --- a/tests/ported_static/stRandom/test_random_statetest155.py +++ b/tests/ported_static/stRandom/test_random_statetest155.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest155Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest155( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest155.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest155( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -90,7 +99,7 @@ def test_random_statetest155( data=Bytes( "457f00000000000000000000000000000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000006f3494f39b6ca29473a1995803089101" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x228B052C, ) diff --git a/tests/ported_static/stRandom/test_random_statetest156.py b/tests/ported_static/stRandom/test_random_statetest156.py index ac350f1bd3f..dd4b11a6002 100644 --- a/tests/ported_static/stRandom/test_random_statetest156.py +++ b/tests/ported_static/stRandom/test_random_statetest156.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest156Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest156( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest156.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest156( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -84,7 +93,7 @@ def test_random_statetest156( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3506f813982583141966b389c159aa48b3a88" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x7FFE6411, ) diff --git a/tests/ported_static/stRandom/test_random_statetest158.py b/tests/ported_static/stRandom/test_random_statetest158.py index a5ab51e59a3..39fa2b5981b 100644 --- a/tests/ported_static/stRandom/test_random_statetest158.py +++ b/tests/ported_static/stRandom/test_random_statetest158.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest158Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest158( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest158.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest158( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -90,7 +99,7 @@ def test_random_statetest158( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000100000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000100000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe4350" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x383BFC76, ) diff --git a/tests/ported_static/stRandom/test_random_statetest161.py b/tests/ported_static/stRandom/test_random_statetest161.py index 39c6b218f9f..7dc41e77504 100644 --- a/tests/ported_static/stRandom/test_random_statetest161.py +++ b/tests/ported_static/stRandom/test_random_statetest161.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest161Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest161( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest161.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest161( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -85,7 +94,7 @@ def test_random_statetest161( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000000000000000000000000000000000000000c350437f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000000000000000000000000000000000000000000001416f458a458076526052650a418c9b40863c" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x2D2470B1, ) diff --git a/tests/ported_static/stRandom/test_random_statetest162.py b/tests/ported_static/stRandom/test_random_statetest162.py index 963bdf0a924..ecc14ec7fc5 100644 --- a/tests/ported_static/stRandom/test_random_statetest162.py +++ b/tests/ported_static/stRandom/test_random_statetest162.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest162Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest162( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest162.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest162( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -89,7 +98,7 @@ def test_random_statetest162( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff6f355a7f614497339e3b63878b369804" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x2B36B8AD, ) diff --git a/tests/ported_static/stRandom/test_random_statetest164.py b/tests/ported_static/stRandom/test_random_statetest164.py index 2bd27133fea..9c2842bee5d 100644 --- a/tests/ported_static/stRandom/test_random_statetest164.py +++ b/tests/ported_static/stRandom/test_random_statetest164.py @@ -13,9 +13,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +31,7 @@ @pytest.mark.pre_alloc_mutable def test_random_statetest164( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_random_statetest164.""" @@ -43,7 +46,6 @@ def test_random_statetest164( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -96,7 +98,7 @@ def test_random_statetest164( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe417f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000001000000000000000000000000000000000000000083130539" # noqa: E501 ), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=0x6D7148EE, ) diff --git a/tests/ported_static/stRandom/test_random_statetest166.py b/tests/ported_static/stRandom/test_random_statetest166.py index 8dfb02fb2e1..b472e7ddf9e 100644 --- a/tests/ported_static/stRandom/test_random_statetest166.py +++ b/tests/ported_static/stRandom/test_random_statetest166.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest166Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest166( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest166.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest166( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -87,7 +96,7 @@ def test_random_statetest166( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff817f0000000000000000000000010000000000000000000000000000000000000000417f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000000000000000000000000000000000000000c350456f8eb7099d9f160532785143c5937e18" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x603D8563, ) diff --git a/tests/ported_static/stRandom/test_random_statetest167.py b/tests/ported_static/stRandom/test_random_statetest167.py index 75073e2980f..c1a689820af 100644 --- a/tests/ported_static/stRandom/test_random_statetest167.py +++ b/tests/ported_static/stRandom/test_random_statetest167.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest167Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest167( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest167.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest167( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -89,7 +98,7 @@ def test_random_statetest167( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000001437f0000000000000000000000000000000000000000000000000000000000000001027f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6fa00b875630178a439384941395369e" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x198F60AB, ) diff --git a/tests/ported_static/stRandom/test_random_statetest169.py b/tests/ported_static/stRandom/test_random_statetest169.py index 77f4c6a16c0..3d2d075902f 100644 --- a/tests/ported_static/stRandom/test_random_statetest169.py +++ b/tests/ported_static/stRandom/test_random_statetest169.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest169Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest169( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest169.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest169( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -91,7 +100,7 @@ def test_random_statetest169( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe447f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x33C6014B, ) diff --git a/tests/ported_static/stRandom/test_random_statetest17.py b/tests/ported_static/stRandom/test_random_statetest17.py index 34d7b4762fa..5b8bdc8f5cf 100644 --- a/tests/ported_static/stRandom/test_random_statetest17.py +++ b/tests/ported_static/stRandom/test_random_statetest17.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -28,6 +30,7 @@ @pytest.mark.pre_alloc_mutable def test_random_statetest17( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_random_statetest17.""" @@ -40,7 +43,6 @@ def test_random_statetest17( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw @@ -82,7 +84,7 @@ def test_random_statetest17( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507f0000000000000000000000000000000000000000000000000000000000000001427f000000000000000000000000000000000000000000000000000000000000c3507f0000000000000000000000000000000000000000000000000000000000000001430a7f000000000000000000000000000000000000000000000000000000000000000106813b37" # noqa: E501 ), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=0x61B5EC82, ) diff --git a/tests/ported_static/stRandom/test_random_statetest173.py b/tests/ported_static/stRandom/test_random_statetest173.py index bef3489d6d2..2b415f603a4 100644 --- a/tests/ported_static/stRandom/test_random_statetest173.py +++ b/tests/ported_static/stRandom/test_random_statetest173.py @@ -13,9 +13,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +31,7 @@ @pytest.mark.pre_alloc_mutable def test_random_statetest173( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_random_statetest173.""" @@ -44,7 +47,6 @@ def test_random_statetest173( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -80,7 +82,7 @@ def test_random_statetest173( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507f0000000000000000000000000000000000000000000000000000000000000001447fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b509ff979443703ca3" # noqa: E501 ), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=0x4C9CD459, ) diff --git a/tests/ported_static/stRandom/test_random_statetest174.py b/tests/ported_static/stRandom/test_random_statetest174.py index 331cdc37ee6..5d4acc69375 100644 --- a/tests/ported_static/stRandom/test_random_statetest174.py +++ b/tests/ported_static/stRandom/test_random_statetest174.py @@ -40,7 +40,6 @@ def test_random_statetest174( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw diff --git a/tests/ported_static/stRandom/test_random_statetest175.py b/tests/ported_static/stRandom/test_random_statetest175.py index 9b87f8d928d..63c7e71548a 100644 --- a/tests/ported_static/stRandom/test_random_statetest175.py +++ b/tests/ported_static/stRandom/test_random_statetest175.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest175Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest175( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest175.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest175( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -87,7 +96,7 @@ def test_random_statetest175( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3506f6985f2837e09689844171a0235833c" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x7F8A09B6, ) diff --git a/tests/ported_static/stRandom/test_random_statetest179.py b/tests/ported_static/stRandom/test_random_statetest179.py index 77fea7abbff..7fcc568ea78 100644 --- a/tests/ported_static/stRandom/test_random_statetest179.py +++ b/tests/ported_static/stRandom/test_random_statetest179.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest179Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest179( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest179.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest179( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -93,7 +102,7 @@ def test_random_statetest179( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3506f515480126a50a173506e0667621292" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x5E6CF4EC, ) diff --git a/tests/ported_static/stRandom/test_random_statetest180.py b/tests/ported_static/stRandom/test_random_statetest180.py index a52580d90f4..fb9e36a7218 100644 --- a/tests/ported_static/stRandom/test_random_statetest180.py +++ b/tests/ported_static/stRandom/test_random_statetest180.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest180Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest180( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest180.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest180( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -86,7 +95,7 @@ def test_random_statetest180( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007f0000000000000000000000000000000000000000000000000000000000000001447f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e796f11576b693c128a9e0820609c050a219d" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x21C3963D, ) diff --git a/tests/ported_static/stRandom/test_random_statetest183.py b/tests/ported_static/stRandom/test_random_statetest183.py index 945168e9838..045b6188782 100644 --- a/tests/ported_static/stRandom/test_random_statetest183.py +++ b/tests/ported_static/stRandom/test_random_statetest183.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest183Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest183( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest183.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest183( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -85,7 +94,7 @@ def test_random_statetest183( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000000000000000000000000000000000000000c3507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79436f4134547075687854849d7b64658630" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x451629C1, ) diff --git a/tests/ported_static/stRandom/test_random_statetest184.py b/tests/ported_static/stRandom/test_random_statetest184.py index 6abaa548ff1..33600950e0f 100644 --- a/tests/ported_static/stRandom/test_random_statetest184.py +++ b/tests/ported_static/stRandom/test_random_statetest184.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest184Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest184( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest184.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x6D6E40885310545835A5B582DBC23EF026404BDA) addr = Address(0xF377657E450772B703A269E12BB487FF421A5C6D) sender = EOA( @@ -44,7 +54,6 @@ def test_random_statetest184( timestamp=10000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=69449279085, ) pre[addr] = Account(balance=0x9740421FF0FF3AE3, nonce=29) @@ -76,7 +85,7 @@ def test_random_statetest184( sender=sender, to=target, data=Bytes("64dd3e4e84676723342c1dfaf9af4ef3"), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x6D1DD024, gas_price=28, ) diff --git a/tests/ported_static/stRandom/test_random_statetest187.py b/tests/ported_static/stRandom/test_random_statetest187.py index 47699fda71c..b1e665c5de0 100644 --- a/tests/ported_static/stRandom/test_random_statetest187.py +++ b/tests/ported_static/stRandom/test_random_statetest187.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest187Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest187( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest187.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest187( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -85,7 +94,7 @@ def test_random_statetest187( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff457f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000006f75988036a0562096036b04518877199d" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x372E4882, ) diff --git a/tests/ported_static/stRandom/test_random_statetest188.py b/tests/ported_static/stRandom/test_random_statetest188.py index b06c894e7e1..9eb3c7129d5 100644 --- a/tests/ported_static/stRandom/test_random_statetest188.py +++ b/tests/ported_static/stRandom/test_random_statetest188.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest188Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest188( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest188.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest188( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -90,7 +99,7 @@ def test_random_statetest188( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff817f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4286687859f38379718794" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x49195164, ) diff --git a/tests/ported_static/stRandom/test_random_statetest19.py b/tests/ported_static/stRandom/test_random_statetest19.py index dbac44602d3..afff390d5c9 100644 --- a/tests/ported_static/stRandom/test_random_statetest19.py +++ b/tests/ported_static/stRandom/test_random_statetest19.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest19Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest19( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest19.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest19( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -91,7 +100,7 @@ def test_random_statetest19( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe3a7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe417f0000000000000000000000000000000000000000000000000000000000000001587f000000000000000000000000000000000000000000000000000000000000c3506fff59876660063b7c8df1ff088a8414" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x1DCD74DE, ) diff --git a/tests/ported_static/stRandom/test_random_statetest191.py b/tests/ported_static/stRandom/test_random_statetest191.py index f1044347324..1b71f2b90ed 100644 --- a/tests/ported_static/stRandom/test_random_statetest191.py +++ b/tests/ported_static/stRandom/test_random_statetest191.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest191Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest191( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest191.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest191( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -93,7 +102,7 @@ def test_random_statetest191( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000010000000000000000000000000000000000000000447ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff6f678f0443457084700b645760018a10" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x17008747, ) diff --git a/tests/ported_static/stRandom/test_random_statetest192.py b/tests/ported_static/stRandom/test_random_statetest192.py index 04ed5961ba6..8f08b1019cc 100644 --- a/tests/ported_static/stRandom/test_random_statetest192.py +++ b/tests/ported_static/stRandom/test_random_statetest192.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest192Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest192( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest192.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest192( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -92,7 +101,7 @@ def test_random_statetest192( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff347f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe04" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x45A5235D, ) diff --git a/tests/ported_static/stRandom/test_random_statetest194.py b/tests/ported_static/stRandom/test_random_statetest194.py index bf41a6bf262..bfe9ecf28d6 100644 --- a/tests/ported_static/stRandom/test_random_statetest194.py +++ b/tests/ported_static/stRandom/test_random_statetest194.py @@ -3,6 +3,11 @@ Ported from: state_tests/stRandom/randomStatetest194Filler.json + +@manually-enhanced: Do not overwrite. `gas_limit` raised on Amsterdam +to cover EIP-8037 state-gas spill. Pre-EIP-8037 keeps the original +100 000. + """ import pytest @@ -15,6 +20,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +35,14 @@ def test_random_statetest194( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest194.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -40,7 +52,6 @@ def test_random_statetest194( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw @@ -84,7 +95,7 @@ def test_random_statetest194( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff097f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x31582CFB, ) diff --git a/tests/ported_static/stRandom/test_random_statetest195.py b/tests/ported_static/stRandom/test_random_statetest195.py index f2ac8f1690d..823838d4c9c 100644 --- a/tests/ported_static/stRandom/test_random_statetest195.py +++ b/tests/ported_static/stRandom/test_random_statetest195.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest195Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest195( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest195.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest195( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -86,7 +95,7 @@ def test_random_statetest195( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000000000000000000000000000000000000000c350417fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff097f0000000000000000000000010000000000000000000000000000000000000000" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x1252A41F, ) diff --git a/tests/ported_static/stRandom/test_random_statetest196.py b/tests/ported_static/stRandom/test_random_statetest196.py index a4d7bf36c81..9b8e822d671 100644 --- a/tests/ported_static/stRandom/test_random_statetest196.py +++ b/tests/ported_static/stRandom/test_random_statetest196.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest196Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +18,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +33,14 @@ def test_random_statetest196( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest196.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -40,7 +50,6 @@ def test_random_statetest196( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw @@ -88,7 +97,7 @@ def test_random_statetest196( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe447f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000003703659c5b3a6d7b9a935436" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x2819E4BE, ) diff --git a/tests/ported_static/stRandom/test_random_statetest198.py b/tests/ported_static/stRandom/test_random_statetest198.py index 04cad03d209..3c4f88484a0 100644 --- a/tests/ported_static/stRandom/test_random_statetest198.py +++ b/tests/ported_static/stRandom/test_random_statetest198.py @@ -13,9 +13,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +31,7 @@ @pytest.mark.pre_alloc_mutable def test_random_statetest198( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_random_statetest198.""" @@ -43,7 +46,6 @@ def test_random_statetest198( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -79,7 +81,7 @@ def test_random_statetest198( data=Bytes( "42417ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff09ff614044129a0169a2689415" # noqa: E501 ), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=0x39B58405, ) diff --git a/tests/ported_static/stRandom/test_random_statetest199.py b/tests/ported_static/stRandom/test_random_statetest199.py index ed83e6f0733..67fa1d53ca8 100644 --- a/tests/ported_static/stRandom/test_random_statetest199.py +++ b/tests/ported_static/stRandom/test_random_statetest199.py @@ -40,7 +40,6 @@ def test_random_statetest199( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw diff --git a/tests/ported_static/stRandom/test_random_statetest2.py b/tests/ported_static/stRandom/test_random_statetest2.py index 792dac6965e..8120e34f029 100644 --- a/tests/ported_static/stRandom/test_random_statetest2.py +++ b/tests/ported_static/stRandom/test_random_statetest2.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest2Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest2( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest2.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest2( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -87,7 +96,7 @@ def test_random_statetest2( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e7958437f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000016f3412a47c889e8da06a04049f049888" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x7C34BB45, ) diff --git a/tests/ported_static/stRandom/test_random_statetest200.py b/tests/ported_static/stRandom/test_random_statetest200.py index 2739268e301..ea5bfad15e3 100644 --- a/tests/ported_static/stRandom/test_random_statetest200.py +++ b/tests/ported_static/stRandom/test_random_statetest200.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest200Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest200( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest200.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest200( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -83,7 +92,7 @@ def test_random_statetest200( data=Bytes( "437f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79346f42051af2a24050039e9d3a678b028a0a80" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x3F51031D, ) diff --git a/tests/ported_static/stRandom/test_random_statetest201.py b/tests/ported_static/stRandom/test_random_statetest201.py index f38c3d0e026..3142158728b 100644 --- a/tests/ported_static/stRandom/test_random_statetest201.py +++ b/tests/ported_static/stRandom/test_random_statetest201.py @@ -13,9 +13,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +31,7 @@ @pytest.mark.pre_alloc_mutable def test_random_statetest201( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_random_statetest201.""" @@ -43,7 +46,6 @@ def test_random_statetest201( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -79,7 +81,7 @@ def test_random_statetest201( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff09ff7f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff8b8263974074da449e68610399" # noqa: E501 ), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=0x38D6AC56, ) diff --git a/tests/ported_static/stRandom/test_random_statetest202.py b/tests/ported_static/stRandom/test_random_statetest202.py index f3a36977176..e7dc23c0fd8 100644 --- a/tests/ported_static/stRandom/test_random_statetest202.py +++ b/tests/ported_static/stRandom/test_random_statetest202.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest202Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +18,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +33,14 @@ def test_random_statetest202( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest202.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -40,7 +50,6 @@ def test_random_statetest202( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw @@ -74,7 +83,7 @@ def test_random_statetest202( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507f0000000000000000000000000000000000000000000000000000000000000000557f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff6750a3190486f0" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x3158E7CD, ) diff --git a/tests/ported_static/stRandom/test_random_statetest204.py b/tests/ported_static/stRandom/test_random_statetest204.py index 56c6d9a85cd..80779e64297 100644 --- a/tests/ported_static/stRandom/test_random_statetest204.py +++ b/tests/ported_static/stRandom/test_random_statetest204.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest204Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest204( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest204.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest204( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -89,7 +98,7 @@ def test_random_statetest204( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000017f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff0982" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x763F0C95, ) diff --git a/tests/ported_static/stRandom/test_random_statetest206.py b/tests/ported_static/stRandom/test_random_statetest206.py index f5cd2080539..39a87632dd4 100644 --- a/tests/ported_static/stRandom/test_random_statetest206.py +++ b/tests/ported_static/stRandom/test_random_statetest206.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest206Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest206( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest206.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest206( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -89,7 +98,7 @@ def test_random_statetest206( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79427f0000000000000000000000000000000000000000000000000000000000000001427f000000000000000000000000000000000000000000000000000000000000c3507f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e796f45736d8e806138378d62087320313c" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x7CA24D6F, ) diff --git a/tests/ported_static/stRandom/test_random_statetest207.py b/tests/ported_static/stRandom/test_random_statetest207.py index 586808a044e..2bcdadfb6da 100644 --- a/tests/ported_static/stRandom/test_random_statetest207.py +++ b/tests/ported_static/stRandom/test_random_statetest207.py @@ -40,7 +40,6 @@ def test_random_statetest207( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw diff --git a/tests/ported_static/stRandom/test_random_statetest208.py b/tests/ported_static/stRandom/test_random_statetest208.py index 414e164a59f..2ebdf20f398 100644 --- a/tests/ported_static/stRandom/test_random_statetest208.py +++ b/tests/ported_static/stRandom/test_random_statetest208.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest208Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest208( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest208.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest208( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -91,7 +100,7 @@ def test_random_statetest208( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff09" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x7FD561B3, ) diff --git a/tests/ported_static/stRandom/test_random_statetest210.py b/tests/ported_static/stRandom/test_random_statetest210.py index aebd883f914..5ec0f49f96e 100644 --- a/tests/ported_static/stRandom/test_random_statetest210.py +++ b/tests/ported_static/stRandom/test_random_statetest210.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest210Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest210( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest210.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest210( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -89,7 +98,7 @@ def test_random_statetest210( data=Bytes( "457f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff427ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff09" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x6A1B0D6A, ) diff --git a/tests/ported_static/stRandom/test_random_statetest212.py b/tests/ported_static/stRandom/test_random_statetest212.py index 6b6ed928909..fee226a1a56 100644 --- a/tests/ported_static/stRandom/test_random_statetest212.py +++ b/tests/ported_static/stRandom/test_random_statetest212.py @@ -13,9 +13,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +31,7 @@ @pytest.mark.pre_alloc_mutable def test_random_statetest212( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_random_statetest212.""" @@ -43,7 +46,6 @@ def test_random_statetest212( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -101,7 +103,7 @@ def test_random_statetest212( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06180908ff3a68f28e61990a52" # noqa: E501 ), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=0x2F6DC2B, ) diff --git a/tests/ported_static/stRandom/test_random_statetest214.py b/tests/ported_static/stRandom/test_random_statetest214.py index bed7fed40ef..f9f806f4ee1 100644 --- a/tests/ported_static/stRandom/test_random_statetest214.py +++ b/tests/ported_static/stRandom/test_random_statetest214.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest214Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest214( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest214.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest214( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -88,7 +97,7 @@ def test_random_statetest214( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff150a6f7b056b335a15a48d7b8841163a503963" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x7D4430A5, ) diff --git a/tests/ported_static/stRandom/test_random_statetest215.py b/tests/ported_static/stRandom/test_random_statetest215.py index 8b60689e9e6..270221359f5 100644 --- a/tests/ported_static/stRandom/test_random_statetest215.py +++ b/tests/ported_static/stRandom/test_random_statetest215.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest215Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest215( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest215.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest215( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -84,7 +93,7 @@ def test_random_statetest215( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff446f728f4f1065583139780a981510173b9c" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x306B9921, ) diff --git a/tests/ported_static/stRandom/test_random_statetest216.py b/tests/ported_static/stRandom/test_random_statetest216.py index 46b7748dc64..ae66319835f 100644 --- a/tests/ported_static/stRandom/test_random_statetest216.py +++ b/tests/ported_static/stRandom/test_random_statetest216.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest216Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest216( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest216.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest216( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -88,7 +97,7 @@ def test_random_statetest216( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000000000000000000000000000000000000000c350447f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000000000000000000000000000000000000000c3507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000000000000000000000000000000000000000c3506d766d67fe078532089913064494" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x288181DD, ) diff --git a/tests/ported_static/stRandom/test_random_statetest217.py b/tests/ported_static/stRandom/test_random_statetest217.py index 6ac0a1b7147..e451e45b057 100644 --- a/tests/ported_static/stRandom/test_random_statetest217.py +++ b/tests/ported_static/stRandom/test_random_statetest217.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest217Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +18,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +33,14 @@ def test_random_statetest217( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest217.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -40,7 +50,6 @@ def test_random_statetest217( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw @@ -80,7 +89,7 @@ def test_random_statetest217( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000000000000000000000000000000000000000000001377f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c350" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x1C326D78, ) diff --git a/tests/ported_static/stRandom/test_random_statetest219.py b/tests/ported_static/stRandom/test_random_statetest219.py index 11619b3a35e..c1566abbec1 100644 --- a/tests/ported_static/stRandom/test_random_statetest219.py +++ b/tests/ported_static/stRandom/test_random_statetest219.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest219Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest219( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest219.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest219( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -86,7 +95,7 @@ def test_random_statetest219( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff437f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000000000000000000000000000000000000000c3506f6253443a4104027144577f33998320" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x46404EA, ) diff --git a/tests/ported_static/stRandom/test_random_statetest22.py b/tests/ported_static/stRandom/test_random_statetest22.py index 07d72306d51..c772e0f8fbf 100644 --- a/tests/ported_static/stRandom/test_random_statetest22.py +++ b/tests/ported_static/stRandom/test_random_statetest22.py @@ -13,9 +13,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +31,7 @@ @pytest.mark.pre_alloc_mutable def test_random_statetest22( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_random_statetest22.""" @@ -43,7 +46,6 @@ def test_random_statetest22( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -79,7 +81,7 @@ def test_random_statetest22( data=Bytes( "6d417fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7e969f926084143c79" # noqa: E501 ), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=0x6C243AE4, ) diff --git a/tests/ported_static/stRandom/test_random_statetest220.py b/tests/ported_static/stRandom/test_random_statetest220.py index 322eb005f67..c80332b5130 100644 --- a/tests/ported_static/stRandom/test_random_statetest220.py +++ b/tests/ported_static/stRandom/test_random_statetest220.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest220Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest220( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest220.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest220( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -85,7 +94,7 @@ def test_random_statetest220( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000016f420380a03c4282a3540a1a333a843a" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0xC5BFC9F, ) diff --git a/tests/ported_static/stRandom/test_random_statetest221.py b/tests/ported_static/stRandom/test_random_statetest221.py index 6008fe23e11..41805cb2db0 100644 --- a/tests/ported_static/stRandom/test_random_statetest221.py +++ b/tests/ported_static/stRandom/test_random_statetest221.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest221Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest221( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest221.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest221( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -87,7 +96,7 @@ def test_random_statetest221( data=Bytes( "457f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000100000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e796f977789947e197f828151867a73771a" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x6F0651EF, ) diff --git a/tests/ported_static/stRandom/test_random_statetest222.py b/tests/ported_static/stRandom/test_random_statetest222.py index afc7ae14c53..d967defdcf6 100644 --- a/tests/ported_static/stRandom/test_random_statetest222.py +++ b/tests/ported_static/stRandom/test_random_statetest222.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest222Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest222( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest222.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest222( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -86,7 +95,7 @@ def test_random_statetest222( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000001000000000000000000000000000000000000000043397f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c35081" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x66F96B9F, ) diff --git a/tests/ported_static/stRandom/test_random_statetest225.py b/tests/ported_static/stRandom/test_random_statetest225.py index edb243054c4..a29e2414eae 100644 --- a/tests/ported_static/stRandom/test_random_statetest225.py +++ b/tests/ported_static/stRandom/test_random_statetest225.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest225Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest225( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest225.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest225( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -95,7 +104,7 @@ def test_random_statetest225( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff506f69786c858e0703566f95f89931119019" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x9859445, ) diff --git a/tests/ported_static/stRandom/test_random_statetest227.py b/tests/ported_static/stRandom/test_random_statetest227.py index a7e4eb1926d..152f35cb0e6 100644 --- a/tests/ported_static/stRandom/test_random_statetest227.py +++ b/tests/ported_static/stRandom/test_random_statetest227.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest227Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest227( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest227.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest227( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -85,7 +94,7 @@ def test_random_statetest227( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e796f108fa27475689e44993a528752a1523359" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x462204C5, ) diff --git a/tests/ported_static/stRandom/test_random_statetest228.py b/tests/ported_static/stRandom/test_random_statetest228.py index 076b957a330..5c92b003c45 100644 --- a/tests/ported_static/stRandom/test_random_statetest228.py +++ b/tests/ported_static/stRandom/test_random_statetest228.py @@ -40,7 +40,6 @@ def test_random_statetest228( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw diff --git a/tests/ported_static/stRandom/test_random_statetest23.py b/tests/ported_static/stRandom/test_random_statetest23.py index 58b93c80c3f..dbeea4e8b60 100644 --- a/tests/ported_static/stRandom/test_random_statetest23.py +++ b/tests/ported_static/stRandom/test_random_statetest23.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest23Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest23( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest23.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest23( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -86,7 +95,7 @@ def test_random_statetest23( data=Bytes( "7f0000000000000000000000000000000000000000000000000000000000000001427f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6f89418c1076f1544315601489386c91" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x27CD2E4B, ) diff --git a/tests/ported_static/stRandom/test_random_statetest231.py b/tests/ported_static/stRandom/test_random_statetest231.py index c80184d684b..0888a359ceb 100644 --- a/tests/ported_static/stRandom/test_random_statetest231.py +++ b/tests/ported_static/stRandom/test_random_statetest231.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest231Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest231( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest231.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest231( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -87,7 +96,7 @@ def test_random_statetest231( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000000000000000000000000000000000000000c3507f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e796f7b98a491727a089df3365353329e80" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x4BCA4C7E, ) diff --git a/tests/ported_static/stRandom/test_random_statetest232.py b/tests/ported_static/stRandom/test_random_statetest232.py index 06d59d92bbc..5a961886404 100644 --- a/tests/ported_static/stRandom/test_random_statetest232.py +++ b/tests/ported_static/stRandom/test_random_statetest232.py @@ -13,9 +13,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +31,7 @@ @pytest.mark.pre_alloc_mutable def test_random_statetest232( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_random_statetest232.""" @@ -43,7 +46,6 @@ def test_random_statetest232( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -79,7 +81,7 @@ def test_random_statetest232( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0945415883ff9d77" # noqa: E501 ), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=0x7E7BBE03, ) diff --git a/tests/ported_static/stRandom/test_random_statetest236.py b/tests/ported_static/stRandom/test_random_statetest236.py index 6c905d7f42e..25d010b7edf 100644 --- a/tests/ported_static/stRandom/test_random_statetest236.py +++ b/tests/ported_static/stRandom/test_random_statetest236.py @@ -13,9 +13,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +31,7 @@ @pytest.mark.pre_alloc_mutable def test_random_statetest236( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_random_statetest236.""" @@ -43,7 +46,6 @@ def test_random_statetest236( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -92,7 +94,7 @@ def test_random_statetest236( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe417f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017f0000000000000000000000010000000000000000000000000000000000000000433918" # noqa: E501 ), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=0x5D5B2808, ) diff --git a/tests/ported_static/stRandom/test_random_statetest237.py b/tests/ported_static/stRandom/test_random_statetest237.py index 121d80b893e..f87d29de357 100644 --- a/tests/ported_static/stRandom/test_random_statetest237.py +++ b/tests/ported_static/stRandom/test_random_statetest237.py @@ -13,9 +13,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +31,7 @@ @pytest.mark.pre_alloc_mutable def test_random_statetest237( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_random_statetest237.""" @@ -43,7 +46,6 @@ def test_random_statetest237( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -91,7 +93,7 @@ def test_random_statetest237( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000000000000000000000000000000000000000000001537f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000003938" # noqa: E501 ), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=0x3A535DB4, ) diff --git a/tests/ported_static/stRandom/test_random_statetest238.py b/tests/ported_static/stRandom/test_random_statetest238.py index 0eefbdba085..9329c88c2ab 100644 --- a/tests/ported_static/stRandom/test_random_statetest238.py +++ b/tests/ported_static/stRandom/test_random_statetest238.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest238Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest238( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest238.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest238( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -89,7 +98,7 @@ def test_random_statetest238( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff307fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff6f7c748813587e990566719934f342316c" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0xFF8455C, ) diff --git a/tests/ported_static/stRandom/test_random_statetest242.py b/tests/ported_static/stRandom/test_random_statetest242.py index c4ef8e01baf..401917748c4 100644 --- a/tests/ported_static/stRandom/test_random_statetest242.py +++ b/tests/ported_static/stRandom/test_random_statetest242.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest242Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest242( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest242.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest242( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -90,7 +99,7 @@ def test_random_statetest242( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe427f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x7DCB2C64, ) diff --git a/tests/ported_static/stRandom/test_random_statetest243.py b/tests/ported_static/stRandom/test_random_statetest243.py index a7b3ff70602..468c36864ba 100644 --- a/tests/ported_static/stRandom/test_random_statetest243.py +++ b/tests/ported_static/stRandom/test_random_statetest243.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest243Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest243( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest243.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest243( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -81,7 +90,7 @@ def test_random_statetest243( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3506f424544664076406862554558668490" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x49903AC, ) diff --git a/tests/ported_static/stRandom/test_random_statetest244.py b/tests/ported_static/stRandom/test_random_statetest244.py index 528343eb2da..f284232b4df 100644 --- a/tests/ported_static/stRandom/test_random_statetest244.py +++ b/tests/ported_static/stRandom/test_random_statetest244.py @@ -40,7 +40,6 @@ def test_random_statetest244( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw diff --git a/tests/ported_static/stRandom/test_random_statetest245.py b/tests/ported_static/stRandom/test_random_statetest245.py index 36710ecc6df..19234d9038a 100644 --- a/tests/ported_static/stRandom/test_random_statetest245.py +++ b/tests/ported_static/stRandom/test_random_statetest245.py @@ -13,9 +13,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +31,7 @@ @pytest.mark.pre_alloc_mutable def test_random_statetest245( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_random_statetest245.""" @@ -43,7 +46,6 @@ def test_random_statetest245( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -94,7 +96,7 @@ def test_random_statetest245( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff157f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000000000000000000000000000000000000000c3507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0469877c3914165043458789" # noqa: E501 ), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=0x109B6E97, ) diff --git a/tests/ported_static/stRandom/test_random_statetest246.py b/tests/ported_static/stRandom/test_random_statetest246.py index 9faf3c0ea75..b7c8f8aba20 100644 --- a/tests/ported_static/stRandom/test_random_statetest246.py +++ b/tests/ported_static/stRandom/test_random_statetest246.py @@ -41,7 +41,6 @@ def test_random_statetest246( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw diff --git a/tests/ported_static/stRandom/test_random_statetest247.py b/tests/ported_static/stRandom/test_random_statetest247.py index c60df68ff8d..474148b54f8 100644 --- a/tests/ported_static/stRandom/test_random_statetest247.py +++ b/tests/ported_static/stRandom/test_random_statetest247.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest247Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest247( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest247.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest247( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -90,7 +99,7 @@ def test_random_statetest247( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe04" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x43B4ED79, ) diff --git a/tests/ported_static/stRandom/test_random_statetest248.py b/tests/ported_static/stRandom/test_random_statetest248.py index ab1cb29ee75..36e4656fdca 100644 --- a/tests/ported_static/stRandom/test_random_statetest248.py +++ b/tests/ported_static/stRandom/test_random_statetest248.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest248Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +18,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +33,14 @@ def test_random_statetest248( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest248.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -40,7 +50,6 @@ def test_random_statetest248( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw @@ -90,7 +99,7 @@ def test_random_statetest248( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000000000000000000000000000000000000000000001427f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8636f25990" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x20C4D1A6, ) diff --git a/tests/ported_static/stRandom/test_random_statetest249.py b/tests/ported_static/stRandom/test_random_statetest249.py index 7ab3fa90762..c4eb673ae28 100644 --- a/tests/ported_static/stRandom/test_random_statetest249.py +++ b/tests/ported_static/stRandom/test_random_statetest249.py @@ -3,6 +3,11 @@ Ported from: state_tests/stRandom/randomStatetest249Filler.json + +@manually-enhanced: Do not overwrite. `gas_limit` raised on Amsterdam +to cover EIP-8037 state-gas spill. Pre-EIP-8037 keeps the original +100 000. + """ import pytest @@ -15,6 +20,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +35,14 @@ def test_random_statetest249( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest249.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -40,7 +52,6 @@ def test_random_statetest249( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw @@ -84,7 +95,7 @@ def test_random_statetest249( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000012807f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000039" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x6F8F420B, ) diff --git a/tests/ported_static/stRandom/test_random_statetest254.py b/tests/ported_static/stRandom/test_random_statetest254.py index 0833e0aedfc..972ba4c86b7 100644 --- a/tests/ported_static/stRandom/test_random_statetest254.py +++ b/tests/ported_static/stRandom/test_random_statetest254.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest254Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest254( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest254.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest254( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -90,7 +99,7 @@ def test_random_statetest254( data=Bytes( "7f000000000000000000000001000000000000000000000000000000000000000041417f0000000000000000000000000000000000000000000000000000000000000001447fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000000000000000000000000000000000000000c3506f059b6b83f294740688598c52195a92" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x5C13C2FF, ) diff --git a/tests/ported_static/stRandom/test_random_statetest259.py b/tests/ported_static/stRandom/test_random_statetest259.py index 741bf622b88..b291a673bfb 100644 --- a/tests/ported_static/stRandom/test_random_statetest259.py +++ b/tests/ported_static/stRandom/test_random_statetest259.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest259Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest259( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest259.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest259( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -88,7 +97,7 @@ def test_random_statetest259( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000100000000000000000000000000000000000000007f0000000000000000000000000000000000000000000000000000000000000001587fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3506f04831adc0812f09544927407900709" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0xEF4B167, ) diff --git a/tests/ported_static/stRandom/test_random_statetest26.py b/tests/ported_static/stRandom/test_random_statetest26.py index aaba1ac0402..d2e900efdc9 100644 --- a/tests/ported_static/stRandom/test_random_statetest26.py +++ b/tests/ported_static/stRandom/test_random_statetest26.py @@ -40,7 +40,6 @@ def test_random_statetest26( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw diff --git a/tests/ported_static/stRandom/test_random_statetest264.py b/tests/ported_static/stRandom/test_random_statetest264.py index ff0a9191e40..1ed859df59d 100644 --- a/tests/ported_static/stRandom/test_random_statetest264.py +++ b/tests/ported_static/stRandom/test_random_statetest264.py @@ -3,6 +3,11 @@ Ported from: state_tests/stRandom/randomStatetest264Filler.json + +@manually-enhanced: Do not overwrite. `gas_limit` raised on Amsterdam +to cover EIP-8037 state-gas spill. Pre-EIP-8037 keeps the original +100 000. + """ import pytest @@ -15,6 +20,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +35,14 @@ def test_random_statetest264( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest264.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -40,7 +52,6 @@ def test_random_statetest264( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw @@ -85,7 +96,7 @@ def test_random_statetest264( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe427f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff09" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x457C78F7, ) diff --git a/tests/ported_static/stRandom/test_random_statetest267.py b/tests/ported_static/stRandom/test_random_statetest267.py index 8e738d330bb..0179d9e62c9 100644 --- a/tests/ported_static/stRandom/test_random_statetest267.py +++ b/tests/ported_static/stRandom/test_random_statetest267.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest267Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest267( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest267.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x945304EB96065B2A98B57A48A06AE28D285A71B5) contract_0 = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87) sender = EOA( @@ -44,7 +54,6 @@ def test_random_statetest267( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -89,7 +98,7 @@ def test_random_statetest267( data=Bytes( "447f000000000000000000000000000000000000000000000000000000000000c3507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b57f00000000000000000000000000000000000000000000000000000000000000007e7f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b5a132776d398e3b7c14686a07346f" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0xF5106AE, ) diff --git a/tests/ported_static/stRandom/test_random_statetest268.py b/tests/ported_static/stRandom/test_random_statetest268.py index f6c95ccca4c..eb8aa8ad175 100644 --- a/tests/ported_static/stRandom/test_random_statetest268.py +++ b/tests/ported_static/stRandom/test_random_statetest268.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest268Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest268( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest268.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest268( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -91,7 +100,7 @@ def test_random_statetest268( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000000000000000000000000000000000000000c3507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000016f7466f0a0733d863263934063409442" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x2360D94A, ) diff --git a/tests/ported_static/stRandom/test_random_statetest269.py b/tests/ported_static/stRandom/test_random_statetest269.py index 208ba09f6da..1bd356a4c89 100644 --- a/tests/ported_static/stRandom/test_random_statetest269.py +++ b/tests/ported_static/stRandom/test_random_statetest269.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest269Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest269( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest269.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest269( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -91,7 +100,7 @@ def test_random_statetest269( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe6676029968ffa27d04" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x49E002C2, ) diff --git a/tests/ported_static/stRandom/test_random_statetest27.py b/tests/ported_static/stRandom/test_random_statetest27.py index c45f829d231..e8a29a8e786 100644 --- a/tests/ported_static/stRandom/test_random_statetest27.py +++ b/tests/ported_static/stRandom/test_random_statetest27.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest27Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest27( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest27.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest27( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -89,7 +98,7 @@ def test_random_statetest27( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000001000000000000000000000000000000000000000009" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x204D3E8E, ) diff --git a/tests/ported_static/stRandom/test_random_statetest270.py b/tests/ported_static/stRandom/test_random_statetest270.py index 1331693dee1..73e605d03ce 100644 --- a/tests/ported_static/stRandom/test_random_statetest270.py +++ b/tests/ported_static/stRandom/test_random_statetest270.py @@ -13,9 +13,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +31,7 @@ @pytest.mark.pre_alloc_mutable def test_random_statetest270( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_random_statetest270.""" @@ -43,7 +46,6 @@ def test_random_statetest270( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -91,7 +93,7 @@ def test_random_statetest270( data=Bytes( "427f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000139" # noqa: E501 ), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=0x6157D615, ) diff --git a/tests/ported_static/stRandom/test_random_statetest273.py b/tests/ported_static/stRandom/test_random_statetest273.py index d7d77f8c1bc..280f4f6d21a 100644 --- a/tests/ported_static/stRandom/test_random_statetest273.py +++ b/tests/ported_static/stRandom/test_random_statetest273.py @@ -43,7 +43,6 @@ def test_random_statetest273( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) diff --git a/tests/ported_static/stRandom/test_random_statetest276.py b/tests/ported_static/stRandom/test_random_statetest276.py index 8815e7bf241..40c9a6e56c9 100644 --- a/tests/ported_static/stRandom/test_random_statetest276.py +++ b/tests/ported_static/stRandom/test_random_statetest276.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest276Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest276( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest276.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest276( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -90,7 +99,7 @@ def test_random_statetest276( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6f4382349f7b370589141a31f39741a4f2" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x3D3366FA, ) diff --git a/tests/ported_static/stRandom/test_random_statetest278.py b/tests/ported_static/stRandom/test_random_statetest278.py index b659b93237d..d66b5664484 100644 --- a/tests/ported_static/stRandom/test_random_statetest278.py +++ b/tests/ported_static/stRandom/test_random_statetest278.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest278Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest278( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest278.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest278( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -88,7 +97,7 @@ def test_random_statetest278( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000000000000000000000000000000000000000000001377f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x5E02EC7F, ) diff --git a/tests/ported_static/stRandom/test_random_statetest279.py b/tests/ported_static/stRandom/test_random_statetest279.py index 2420986fd76..9ebcff5c884 100644 --- a/tests/ported_static/stRandom/test_random_statetest279.py +++ b/tests/ported_static/stRandom/test_random_statetest279.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest279Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +18,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +33,14 @@ def test_random_statetest279( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest279.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -40,7 +50,6 @@ def test_random_statetest279( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw @@ -86,7 +95,7 @@ def test_random_statetest279( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000010000000000000000000000000000000000000000947f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x4E91B038, ) diff --git a/tests/ported_static/stRandom/test_random_statetest28.py b/tests/ported_static/stRandom/test_random_statetest28.py index fd926360efa..0454b3f339a 100644 --- a/tests/ported_static/stRandom/test_random_statetest28.py +++ b/tests/ported_static/stRandom/test_random_statetest28.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest28Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest28( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest28.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest28( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -87,7 +96,7 @@ def test_random_statetest28( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff417f00000000000000000000000100000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e796f38129d68939a19a2697172926f6a673630" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x51AF41E7, ) diff --git a/tests/ported_static/stRandom/test_random_statetest280.py b/tests/ported_static/stRandom/test_random_statetest280.py index 40474d5a42d..b0d276d3d1d 100644 --- a/tests/ported_static/stRandom/test_random_statetest280.py +++ b/tests/ported_static/stRandom/test_random_statetest280.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest280Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest280( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest280.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest280( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -84,7 +93,7 @@ def test_random_statetest280( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000000143507f000000000000000000000000000000000000000000000000000000000000c350417f00000000000000000000000000000000000000000000000000000000000000006f423b3c407e7c6f16718668738d193cf2" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x303CDC5A, ) diff --git a/tests/ported_static/stRandom/test_random_statetest281.py b/tests/ported_static/stRandom/test_random_statetest281.py index eb2fffccff2..acb3ffcc38f 100644 --- a/tests/ported_static/stRandom/test_random_statetest281.py +++ b/tests/ported_static/stRandom/test_random_statetest281.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest281Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest281( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest281.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest281( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -84,7 +93,7 @@ def test_random_statetest281( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79457f000000000000000000000000000000000000000000000000000000000000c350417f00000000000000000000000000000000000000000000000000000000000000016f649a7a3457645670a27fa170639718a2" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x662E647C, ) diff --git a/tests/ported_static/stRandom/test_random_statetest283.py b/tests/ported_static/stRandom/test_random_statetest283.py index b0f7f17ceff..aee36b2148e 100644 --- a/tests/ported_static/stRandom/test_random_statetest283.py +++ b/tests/ported_static/stRandom/test_random_statetest283.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest283Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest283( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest283.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest283( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -92,7 +101,7 @@ def test_random_statetest283( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff457fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000139" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x5F83295F, ) diff --git a/tests/ported_static/stRandom/test_random_statetest29.py b/tests/ported_static/stRandom/test_random_statetest29.py index 348e55ad7d8..cdc40338649 100644 --- a/tests/ported_static/stRandom/test_random_statetest29.py +++ b/tests/ported_static/stRandom/test_random_statetest29.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest29Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest29( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest29.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest29( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -89,7 +98,7 @@ def test_random_statetest29( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff087fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff09" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x214AB1F3, ) diff --git a/tests/ported_static/stRandom/test_random_statetest290.py b/tests/ported_static/stRandom/test_random_statetest290.py index 34a94a78a58..5c2372d1a16 100644 --- a/tests/ported_static/stRandom/test_random_statetest290.py +++ b/tests/ported_static/stRandom/test_random_statetest290.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest290Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest290( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest290.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest290( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -90,7 +99,7 @@ def test_random_statetest290( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe8309" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x6D5253D6, ) diff --git a/tests/ported_static/stRandom/test_random_statetest291.py b/tests/ported_static/stRandom/test_random_statetest291.py index 6fae04c441a..e38cb6a2ebe 100644 --- a/tests/ported_static/stRandom/test_random_statetest291.py +++ b/tests/ported_static/stRandom/test_random_statetest291.py @@ -13,9 +13,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +31,7 @@ @pytest.mark.pre_alloc_mutable def test_random_statetest291( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_random_statetest291.""" @@ -43,7 +46,6 @@ def test_random_statetest291( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -87,7 +89,7 @@ def test_random_statetest291( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f0000000000000000000000000000000000000000000000000000000000000001" # noqa: E501 ), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=0x3F7ADA4A, ) diff --git a/tests/ported_static/stRandom/test_random_statetest293.py b/tests/ported_static/stRandom/test_random_statetest293.py index 735739a25eb..c9c04f0e390 100644 --- a/tests/ported_static/stRandom/test_random_statetest293.py +++ b/tests/ported_static/stRandom/test_random_statetest293.py @@ -13,9 +13,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +31,7 @@ @pytest.mark.pre_alloc_mutable def test_random_statetest293( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_random_statetest293.""" @@ -43,7 +46,6 @@ def test_random_statetest293( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -88,7 +90,7 @@ def test_random_statetest293( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe417f00000000000000000000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe6f458962699489837460090897f305668284" # noqa: E501 ), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=0x3CF6D3A7, ) diff --git a/tests/ported_static/stRandom/test_random_statetest297.py b/tests/ported_static/stRandom/test_random_statetest297.py index 88f9ae81fcd..47b9b555a73 100644 --- a/tests/ported_static/stRandom/test_random_statetest297.py +++ b/tests/ported_static/stRandom/test_random_statetest297.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest297Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest297( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest297.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest297( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -90,7 +99,7 @@ def test_random_statetest297( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79437f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe426f91085661509214157d9c8a77758518" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x7C61878A, ) diff --git a/tests/ported_static/stRandom/test_random_statetest298.py b/tests/ported_static/stRandom/test_random_statetest298.py index 05af899ef1a..a6e343ab2f5 100644 --- a/tests/ported_static/stRandom/test_random_statetest298.py +++ b/tests/ported_static/stRandom/test_random_statetest298.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest298Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest298( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest298.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest298( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -85,7 +94,7 @@ def test_random_statetest298( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3500a6f7c542006528b69ff3a7a3a0401613c" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x7E0F660B, ) diff --git a/tests/ported_static/stRandom/test_random_statetest299.py b/tests/ported_static/stRandom/test_random_statetest299.py index bc153c40fd5..1583f1bf72c 100644 --- a/tests/ported_static/stRandom/test_random_statetest299.py +++ b/tests/ported_static/stRandom/test_random_statetest299.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest299Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest299( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest299.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest299( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -95,7 +104,7 @@ def test_random_statetest299( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe6f540813697adf70f20906389d128bf0" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x1A47B134, ) diff --git a/tests/ported_static/stRandom/test_random_statetest3.py b/tests/ported_static/stRandom/test_random_statetest3.py index c29a08bfa7f..53632e80c19 100644 --- a/tests/ported_static/stRandom/test_random_statetest3.py +++ b/tests/ported_static/stRandom/test_random_statetest3.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest3Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +18,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +33,14 @@ def test_random_statetest3( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest3.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -40,7 +50,6 @@ def test_random_statetest3( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw @@ -80,7 +89,7 @@ def test_random_statetest3( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe427f000000000000000000000000000000000000000000000000000000000000c35041" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x5EAA223F, ) diff --git a/tests/ported_static/stRandom/test_random_statetest30.py b/tests/ported_static/stRandom/test_random_statetest30.py index 261a71122b7..b7eff9c0205 100644 --- a/tests/ported_static/stRandom/test_random_statetest30.py +++ b/tests/ported_static/stRandom/test_random_statetest30.py @@ -40,7 +40,6 @@ def test_random_statetest30( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw diff --git a/tests/ported_static/stRandom/test_random_statetest301.py b/tests/ported_static/stRandom/test_random_statetest301.py index c7b65ce2bf7..179ae4337a2 100644 --- a/tests/ported_static/stRandom/test_random_statetest301.py +++ b/tests/ported_static/stRandom/test_random_statetest301.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest301Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest301( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest301.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest301( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -93,7 +102,7 @@ def test_random_statetest301( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000003784946a737aa092f1975664518a" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x1340F9CE, ) diff --git a/tests/ported_static/stRandom/test_random_statetest305.py b/tests/ported_static/stRandom/test_random_statetest305.py index d9093d48f46..cae2823329a 100644 --- a/tests/ported_static/stRandom/test_random_statetest305.py +++ b/tests/ported_static/stRandom/test_random_statetest305.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest305Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest305( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest305.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest305( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -89,7 +98,7 @@ def test_random_statetest305( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000100000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000006f606e048240069c409313318736200b" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0xD00D79E, ) diff --git a/tests/ported_static/stRandom/test_random_statetest31.py b/tests/ported_static/stRandom/test_random_statetest31.py index 0e99e6b5a26..74fd6e705e1 100644 --- a/tests/ported_static/stRandom/test_random_statetest31.py +++ b/tests/ported_static/stRandom/test_random_statetest31.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -28,6 +30,7 @@ @pytest.mark.pre_alloc_mutable def test_random_statetest31( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_random_statetest31.""" @@ -40,7 +43,6 @@ def test_random_statetest31( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw @@ -82,7 +84,7 @@ def test_random_statetest31( data=Bytes( "387f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000009037" # noqa: E501 ), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=0x50282FA0, ) diff --git a/tests/ported_static/stRandom/test_random_statetest310.py b/tests/ported_static/stRandom/test_random_statetest310.py index 86dfb9f61bc..f1bf7c47488 100644 --- a/tests/ported_static/stRandom/test_random_statetest310.py +++ b/tests/ported_static/stRandom/test_random_statetest310.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest310Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest310( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest310.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest310( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -93,7 +102,7 @@ def test_random_statetest310( data=Bytes( "44587fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff59907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1a37160b6a650645597c796e9c9795" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x1B76ED9D, ) diff --git a/tests/ported_static/stRandom/test_random_statetest311.py b/tests/ported_static/stRandom/test_random_statetest311.py index e4a48c3787b..4b8c5b450b3 100644 --- a/tests/ported_static/stRandom/test_random_statetest311.py +++ b/tests/ported_static/stRandom/test_random_statetest311.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest311Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest311( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest311.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest311( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -88,7 +97,7 @@ def test_random_statetest311( data=Bytes( "447f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3506f13971264a1197d72ff18971902387b" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x5CD0515, ) diff --git a/tests/ported_static/stRandom/test_random_statetest315.py b/tests/ported_static/stRandom/test_random_statetest315.py index 0c49be25102..aec15d52e45 100644 --- a/tests/ported_static/stRandom/test_random_statetest315.py +++ b/tests/ported_static/stRandom/test_random_statetest315.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest315Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest315( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest315.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest315( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -94,7 +103,7 @@ def test_random_statetest315( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff067f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe6f98516a388683755669892b8b371957" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x13D8A45E, ) diff --git a/tests/ported_static/stRandom/test_random_statetest316.py b/tests/ported_static/stRandom/test_random_statetest316.py index efa655732af..c2d8d69d8d7 100644 --- a/tests/ported_static/stRandom/test_random_statetest316.py +++ b/tests/ported_static/stRandom/test_random_statetest316.py @@ -3,6 +3,11 @@ Ported from: state_tests/stRandom/randomStatetest316Filler.json + +@manually-enhanced: Do not overwrite. `gas_limit` raised on Amsterdam +to cover EIP-8037 state-gas spill. Pre-EIP-8037 keeps the original +100 000. + """ import pytest @@ -15,6 +20,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +35,14 @@ def test_random_statetest316( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest316.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -40,7 +52,6 @@ def test_random_statetest316( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw @@ -87,7 +98,7 @@ def test_random_statetest316( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x3A0D0C77, ) diff --git a/tests/ported_static/stRandom/test_random_statetest318.py b/tests/ported_static/stRandom/test_random_statetest318.py index 4dbe1f506c2..6a493725f4d 100644 --- a/tests/ported_static/stRandom/test_random_statetest318.py +++ b/tests/ported_static/stRandom/test_random_statetest318.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest318Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest318( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest318.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest318( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -91,7 +100,7 @@ def test_random_statetest318( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c350457f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000000000000000000000000000000000000000c3506f8206a30a83887e5a3164667796308d" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x23F9C6F7, ) diff --git a/tests/ported_static/stRandom/test_random_statetest322.py b/tests/ported_static/stRandom/test_random_statetest322.py index 28424aecc18..f72ad9afd42 100644 --- a/tests/ported_static/stRandom/test_random_statetest322.py +++ b/tests/ported_static/stRandom/test_random_statetest322.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest322Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest322( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest322.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest322( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -88,7 +97,7 @@ def test_random_statetest322( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f0000000000000000000000000000000000000000000000000000000000000000427f000000000000000000000000000000000000000000000000000000000000c3506f1206060508840294304101a3128f34" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x312238E4, ) diff --git a/tests/ported_static/stRandom/test_random_statetest325.py b/tests/ported_static/stRandom/test_random_statetest325.py index b72f02566ec..862b46b526c 100644 --- a/tests/ported_static/stRandom/test_random_statetest325.py +++ b/tests/ported_static/stRandom/test_random_statetest325.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest325Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest325( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest325.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest325( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -94,7 +103,7 @@ def test_random_statetest325( data=Bytes( "437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff427f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff427fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe810903" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x1B449945, ) diff --git a/tests/ported_static/stRandom/test_random_statetest329.py b/tests/ported_static/stRandom/test_random_statetest329.py index a7c487bdc86..c2bbd0aa10a 100644 --- a/tests/ported_static/stRandom/test_random_statetest329.py +++ b/tests/ported_static/stRandom/test_random_statetest329.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest329Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest329( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest329.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest329( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -89,7 +98,7 @@ def test_random_statetest329( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff426fa48d775458574133769c8b750207ff" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x5B5A0B6C, ) diff --git a/tests/ported_static/stRandom/test_random_statetest332.py b/tests/ported_static/stRandom/test_random_statetest332.py index af3acdd68fc..93435882f34 100644 --- a/tests/ported_static/stRandom/test_random_statetest332.py +++ b/tests/ported_static/stRandom/test_random_statetest332.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest332Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest332( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest332.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest332( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -90,7 +99,7 @@ def test_random_statetest332( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3506f7c098e7d625a64319d9e514bf35075" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x53D5155E, ) diff --git a/tests/ported_static/stRandom/test_random_statetest333.py b/tests/ported_static/stRandom/test_random_statetest333.py index 07a193ba630..eba776ceb9e 100644 --- a/tests/ported_static/stRandom/test_random_statetest333.py +++ b/tests/ported_static/stRandom/test_random_statetest333.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest333Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest333( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest333.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest333( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -88,7 +97,7 @@ def test_random_statetest333( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79457fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6f410263f305963310856c15ff5037a0" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x3024B0A3, ) diff --git a/tests/ported_static/stRandom/test_random_statetest334.py b/tests/ported_static/stRandom/test_random_statetest334.py index 7d50a34d622..a92922d48b9 100644 --- a/tests/ported_static/stRandom/test_random_statetest334.py +++ b/tests/ported_static/stRandom/test_random_statetest334.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest334Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest334( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest334.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest334( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -87,7 +96,7 @@ def test_random_statetest334( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000013a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe6f424468208e181851308b7c7a776863a1" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x36993F17, ) diff --git a/tests/ported_static/stRandom/test_random_statetest337.py b/tests/ported_static/stRandom/test_random_statetest337.py index f8b1422cbe0..933efce1a3f 100644 --- a/tests/ported_static/stRandom/test_random_statetest337.py +++ b/tests/ported_static/stRandom/test_random_statetest337.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -28,6 +30,7 @@ @pytest.mark.pre_alloc_mutable def test_random_statetest337( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_random_statetest337.""" @@ -40,7 +43,6 @@ def test_random_statetest337( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw @@ -81,7 +83,7 @@ def test_random_statetest337( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000000000000000000000000000000000000000c350670b9af27e9a6468a1" # noqa: E501 ), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=0x13DA3C95, ) diff --git a/tests/ported_static/stRandom/test_random_statetest338.py b/tests/ported_static/stRandom/test_random_statetest338.py index d623ef7d74d..695c84e9178 100644 --- a/tests/ported_static/stRandom/test_random_statetest338.py +++ b/tests/ported_static/stRandom/test_random_statetest338.py @@ -13,9 +13,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +31,7 @@ @pytest.mark.pre_alloc_mutable def test_random_statetest338( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_random_statetest338.""" @@ -43,7 +46,6 @@ def test_random_statetest338( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -98,7 +100,7 @@ def test_random_statetest338( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff677a9df32e6851606c011906" # noqa: E501 ), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=0x69508BB7, ) diff --git a/tests/ported_static/stRandom/test_random_statetest339.py b/tests/ported_static/stRandom/test_random_statetest339.py index 6437037b241..f3d6c565d4a 100644 --- a/tests/ported_static/stRandom/test_random_statetest339.py +++ b/tests/ported_static/stRandom/test_random_statetest339.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest339Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest339( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest339.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest339( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -87,7 +96,7 @@ def test_random_statetest339( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3506f89029e850708a293905668f1a367a2" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x3F78C8AA, ) diff --git a/tests/ported_static/stRandom/test_random_statetest342.py b/tests/ported_static/stRandom/test_random_statetest342.py index 9bad4431ff6..da98d14951f 100644 --- a/tests/ported_static/stRandom/test_random_statetest342.py +++ b/tests/ported_static/stRandom/test_random_statetest342.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest342Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest342( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest342.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest342( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -89,7 +98,7 @@ def test_random_statetest342( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000000000000000000000000000000000000000000041147fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6f36314297399455797b42569e8f0556" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x6312A8C4, ) diff --git a/tests/ported_static/stRandom/test_random_statetest343.py b/tests/ported_static/stRandom/test_random_statetest343.py index 2127a424e0e..1d69954233f 100644 --- a/tests/ported_static/stRandom/test_random_statetest343.py +++ b/tests/ported_static/stRandom/test_random_statetest343.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -28,6 +30,7 @@ @pytest.mark.pre_alloc_mutable def test_random_statetest343( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_random_statetest343.""" @@ -40,7 +43,6 @@ def test_random_statetest343( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw @@ -91,7 +93,7 @@ def test_random_statetest343( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff111010374135" # noqa: E501 ), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=0x4D914770, ) diff --git a/tests/ported_static/stRandom/test_random_statetest348.py b/tests/ported_static/stRandom/test_random_statetest348.py index b4fff8ac8d8..bfe57ee9531 100644 --- a/tests/ported_static/stRandom/test_random_statetest348.py +++ b/tests/ported_static/stRandom/test_random_statetest348.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest348Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest348( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest348.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest348( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -93,7 +102,7 @@ def test_random_statetest348( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000142186f18208119191509036365739735608a" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x4F3B26DA, ) diff --git a/tests/ported_static/stRandom/test_random_statetest349.py b/tests/ported_static/stRandom/test_random_statetest349.py index 5f1aedfc476..4a43f0484bf 100644 --- a/tests/ported_static/stRandom/test_random_statetest349.py +++ b/tests/ported_static/stRandom/test_random_statetest349.py @@ -13,9 +13,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +31,7 @@ @pytest.mark.pre_alloc_mutable def test_random_statetest349( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_random_statetest349.""" @@ -43,7 +46,6 @@ def test_random_statetest349( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -90,7 +92,7 @@ def test_random_statetest349( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000000000000000000000000000000000000000c3507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0442" # noqa: E501 ), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=0xBFB8D02, ) diff --git a/tests/ported_static/stRandom/test_random_statetest351.py b/tests/ported_static/stRandom/test_random_statetest351.py index 63abf6c95d8..7880da18abb 100644 --- a/tests/ported_static/stRandom/test_random_statetest351.py +++ b/tests/ported_static/stRandom/test_random_statetest351.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest351Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest351( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest351.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest351( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -79,7 +88,7 @@ def test_random_statetest351( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0509355534707785320175fca414" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x486E44AE, ) diff --git a/tests/ported_static/stRandom/test_random_statetest354.py b/tests/ported_static/stRandom/test_random_statetest354.py index 53d7e262a47..15b13f2b299 100644 --- a/tests/ported_static/stRandom/test_random_statetest354.py +++ b/tests/ported_static/stRandom/test_random_statetest354.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest354Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest354( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest354.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest354( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -100,7 +109,7 @@ def test_random_statetest354( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000000000000000000000000000000000000000c350603b35641a8e739f86980a4337" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x24AAAAF6, ) diff --git a/tests/ported_static/stRandom/test_random_statetest356.py b/tests/ported_static/stRandom/test_random_statetest356.py index 9c8ed826c38..693aef48fd3 100644 --- a/tests/ported_static/stRandom/test_random_statetest356.py +++ b/tests/ported_static/stRandom/test_random_statetest356.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest356Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest356( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest356.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest356( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -91,7 +100,7 @@ def test_random_statetest356( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000100000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79827f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000000000000000000000000000000000000000c3507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe04" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x4F386503, ) diff --git a/tests/ported_static/stRandom/test_random_statetest358.py b/tests/ported_static/stRandom/test_random_statetest358.py index 32035f4e334..daf1478c698 100644 --- a/tests/ported_static/stRandom/test_random_statetest358.py +++ b/tests/ported_static/stRandom/test_random_statetest358.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest358Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest358( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest358.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest358( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -88,7 +97,7 @@ def test_random_statetest358( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000100000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79417fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff437f000000000000000000000000000000000000000000000000000000000000c3506f679b82a092078f136b5541888c057a" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x252F4B99, ) diff --git a/tests/ported_static/stRandom/test_random_statetest360.py b/tests/ported_static/stRandom/test_random_statetest360.py index 97a0128ab69..4b13e9e687a 100644 --- a/tests/ported_static/stRandom/test_random_statetest360.py +++ b/tests/ported_static/stRandom/test_random_statetest360.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest360Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest360( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest360.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest360( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -91,7 +100,7 @@ def test_random_statetest360( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000016f0441548af30803135562840563829c" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x3B167C0B, ) diff --git a/tests/ported_static/stRandom/test_random_statetest361.py b/tests/ported_static/stRandom/test_random_statetest361.py index ffefa4de78a..23c0c82de85 100644 --- a/tests/ported_static/stRandom/test_random_statetest361.py +++ b/tests/ported_static/stRandom/test_random_statetest361.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest361Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest361( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest361.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest361( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -91,7 +100,7 @@ def test_random_statetest361( data=Bytes( "41417ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000100000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff066f9e9092673a8f430b6ba11520901816" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x74BA18BD, ) diff --git a/tests/ported_static/stRandom/test_random_statetest362.py b/tests/ported_static/stRandom/test_random_statetest362.py index d0dc5cdacf9..07b7cb05555 100644 --- a/tests/ported_static/stRandom/test_random_statetest362.py +++ b/tests/ported_static/stRandom/test_random_statetest362.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest362Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest362( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest362.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x945304EB96065B2A98B57A48A06AE28D285A71B5) contract_0 = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87) sender = EOA( @@ -44,7 +54,6 @@ def test_random_statetest362( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -91,7 +100,7 @@ def test_random_statetest362( data=Bytes( "7f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b57f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b509" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x31025CBA, ) diff --git a/tests/ported_static/stRandom/test_random_statetest363.py b/tests/ported_static/stRandom/test_random_statetest363.py index e5ca12e8a91..64e32ba7846 100644 --- a/tests/ported_static/stRandom/test_random_statetest363.py +++ b/tests/ported_static/stRandom/test_random_statetest363.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest363Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest363( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest363.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest363( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -94,7 +103,7 @@ def test_random_statetest363( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c350117ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe6f7b20937d953695f369719f9a447905" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x4C18B65E, ) diff --git a/tests/ported_static/stRandom/test_random_statetest364.py b/tests/ported_static/stRandom/test_random_statetest364.py index 50baeaa3a88..5eb7a370885 100644 --- a/tests/ported_static/stRandom/test_random_statetest364.py +++ b/tests/ported_static/stRandom/test_random_statetest364.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest364Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest364( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest364.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest364( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -91,7 +100,7 @@ def test_random_statetest364( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000000000000000000000000000000000000000c350076f7332988d746694918859185920446d" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x25908FA1, ) diff --git a/tests/ported_static/stRandom/test_random_statetest365.py b/tests/ported_static/stRandom/test_random_statetest365.py index 2e722acb713..eaeb4040bcf 100644 --- a/tests/ported_static/stRandom/test_random_statetest365.py +++ b/tests/ported_static/stRandom/test_random_statetest365.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest365Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +18,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +33,14 @@ def test_random_statetest365( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest365.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -40,7 +50,6 @@ def test_random_statetest365( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw @@ -94,7 +103,7 @@ def test_random_statetest365( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff42417f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000000000000000000000000000000000000000000000143b42078537" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x48ACB162, ) diff --git a/tests/ported_static/stRandom/test_random_statetest366.py b/tests/ported_static/stRandom/test_random_statetest366.py index 2c03fd159a0..6846a0c5772 100644 --- a/tests/ported_static/stRandom/test_random_statetest366.py +++ b/tests/ported_static/stRandom/test_random_statetest366.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest366Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest366( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest366.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest366( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -93,7 +102,7 @@ def test_random_statetest366( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000100000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff446f516f0395f57433725580758f32f194" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x7D527F3C, ) diff --git a/tests/ported_static/stRandom/test_random_statetest367.py b/tests/ported_static/stRandom/test_random_statetest367.py index b0bf5dfc335..d5c56cd839f 100644 --- a/tests/ported_static/stRandom/test_random_statetest367.py +++ b/tests/ported_static/stRandom/test_random_statetest367.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest367Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest367( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest367.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x945304EB96065B2A98B57A48A06AE28D285A71B5) contract_0 = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87) sender = EOA( @@ -44,7 +54,6 @@ def test_random_statetest367( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -95,7 +104,7 @@ def test_random_statetest367( data=Bytes( "7f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b57f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000447f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b5447f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b51905810a6c7a5959339f3342838b" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x2BC3D730, ) diff --git a/tests/ported_static/stRandom/test_random_statetest368.py b/tests/ported_static/stRandom/test_random_statetest368.py index 931daeccdaf..3d47236fbe0 100644 --- a/tests/ported_static/stRandom/test_random_statetest368.py +++ b/tests/ported_static/stRandom/test_random_statetest368.py @@ -12,10 +12,12 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, compute_create_address, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +31,7 @@ @pytest.mark.pre_alloc_mutable def test_random_statetest368( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_random_statetest368.""" @@ -42,7 +45,6 @@ def test_random_statetest368( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw @@ -76,7 +78,7 @@ def test_random_statetest368( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe097f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b54206f06d8703393560579077" # noqa: E501 ), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=0x43AC3494, ) diff --git a/tests/ported_static/stRandom/test_random_statetest369.py b/tests/ported_static/stRandom/test_random_statetest369.py index dc7b6e31a3b..112d60404dc 100644 --- a/tests/ported_static/stRandom/test_random_statetest369.py +++ b/tests/ported_static/stRandom/test_random_statetest369.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest369Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest369( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest369.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest369( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -90,7 +99,7 @@ def test_random_statetest369( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff437f0000000000000000000000010000000000000000000000000000000000000000" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x1C20856A, ) diff --git a/tests/ported_static/stRandom/test_random_statetest37.py b/tests/ported_static/stRandom/test_random_statetest37.py index d12c00792ca..0762ac0083f 100644 --- a/tests/ported_static/stRandom/test_random_statetest37.py +++ b/tests/ported_static/stRandom/test_random_statetest37.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest37Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest37( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest37.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest37( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -91,7 +100,7 @@ def test_random_statetest37( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000016fa49835863514f0f29b930b97f11693" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x76C6A52D, ) diff --git a/tests/ported_static/stRandom/test_random_statetest371.py b/tests/ported_static/stRandom/test_random_statetest371.py index a2a302b67b4..29919aef795 100644 --- a/tests/ported_static/stRandom/test_random_statetest371.py +++ b/tests/ported_static/stRandom/test_random_statetest371.py @@ -13,9 +13,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +31,7 @@ @pytest.mark.pre_alloc_mutable def test_random_statetest371( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_random_statetest371.""" @@ -43,7 +46,6 @@ def test_random_statetest371( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -92,7 +94,7 @@ def test_random_statetest371( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507f0000000000000000000000010000000000000000000000000000000000000000435a1039" # noqa: E501 ), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=0x7E402352, ) diff --git a/tests/ported_static/stRandom/test_random_statetest372.py b/tests/ported_static/stRandom/test_random_statetest372.py index 8db67f5e1f8..6d5dda1765b 100644 --- a/tests/ported_static/stRandom/test_random_statetest372.py +++ b/tests/ported_static/stRandom/test_random_statetest372.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest372Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest372( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest372.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x945304EB96065B2A98B57A48A06AE28D285A71B5) contract_0 = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87) sender = EOA( @@ -44,7 +54,6 @@ def test_random_statetest372( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -93,7 +102,7 @@ def test_random_statetest372( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b57f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b57f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b57f00000000000000000000000000000000000000000000000000000000000000011808" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x6D4BEA09, ) diff --git a/tests/ported_static/stRandom/test_random_statetest376.py b/tests/ported_static/stRandom/test_random_statetest376.py index 9a25b9ccd87..bcd6dcb0e18 100644 --- a/tests/ported_static/stRandom/test_random_statetest376.py +++ b/tests/ported_static/stRandom/test_random_statetest376.py @@ -13,9 +13,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +31,7 @@ @pytest.mark.pre_alloc_mutable def test_random_statetest376( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_random_statetest376.""" @@ -43,7 +46,6 @@ def test_random_statetest376( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -79,7 +81,7 @@ def test_random_statetest376( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff427fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09ff8c3164" # noqa: E501 ), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=0x6F9D8CFB, ) diff --git a/tests/ported_static/stRandom/test_random_statetest379.py b/tests/ported_static/stRandom/test_random_statetest379.py index eb39731d420..a113aecc305 100644 --- a/tests/ported_static/stRandom/test_random_statetest379.py +++ b/tests/ported_static/stRandom/test_random_statetest379.py @@ -40,7 +40,6 @@ def test_random_statetest379( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw diff --git a/tests/ported_static/stRandom/test_random_statetest380.py b/tests/ported_static/stRandom/test_random_statetest380.py index 3f78aad3b7e..7d6630e53a3 100644 --- a/tests/ported_static/stRandom/test_random_statetest380.py +++ b/tests/ported_static/stRandom/test_random_statetest380.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest380Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest380( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest380.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest380( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -91,7 +100,7 @@ def test_random_statetest380( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe6f967737653485593c63408b39943975" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x25771D96, ) diff --git a/tests/ported_static/stRandom/test_random_statetest381.py b/tests/ported_static/stRandom/test_random_statetest381.py index ea94c2e026e..a2cfc8f33af 100644 --- a/tests/ported_static/stRandom/test_random_statetest381.py +++ b/tests/ported_static/stRandom/test_random_statetest381.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest381Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest381( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest381.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest381( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -88,7 +97,7 @@ def test_random_statetest381( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff417f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff6f098ba088881a64904570927a861835" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x2D1A0F83, ) diff --git a/tests/ported_static/stRandom/test_random_statetest382.py b/tests/ported_static/stRandom/test_random_statetest382.py index bf176290fca..2b564b3e7f7 100644 --- a/tests/ported_static/stRandom/test_random_statetest382.py +++ b/tests/ported_static/stRandom/test_random_statetest382.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest382Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest382( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest382.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest382( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -87,7 +96,7 @@ def test_random_statetest382( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x34AE0BF4, ) diff --git a/tests/ported_static/stRandom/test_random_statetest383.py b/tests/ported_static/stRandom/test_random_statetest383.py index 6ea6cfd8913..1b1dfe3d6a5 100644 --- a/tests/ported_static/stRandom/test_random_statetest383.py +++ b/tests/ported_static/stRandom/test_random_statetest383.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest383Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest383( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest383.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest383( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -79,7 +88,7 @@ def test_random_statetest383( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff09150255436c75107e" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x6379C077, ) diff --git a/tests/ported_static/stRandom/test_random_statetest39.py b/tests/ported_static/stRandom/test_random_statetest39.py index 9ad4e952cad..34760a84b3c 100644 --- a/tests/ported_static/stRandom/test_random_statetest39.py +++ b/tests/ported_static/stRandom/test_random_statetest39.py @@ -13,9 +13,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +31,7 @@ @pytest.mark.pre_alloc_mutable def test_random_statetest39( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_random_statetest39.""" @@ -43,7 +46,6 @@ def test_random_statetest39( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -94,7 +96,7 @@ def test_random_statetest39( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff427f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe9604638ea2179a5803" # noqa: E501 ), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=0x1040DC3B, ) diff --git a/tests/ported_static/stRandom/test_random_statetest41.py b/tests/ported_static/stRandom/test_random_statetest41.py index 6b633b051ff..7546db6b4f4 100644 --- a/tests/ported_static/stRandom/test_random_statetest41.py +++ b/tests/ported_static/stRandom/test_random_statetest41.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest41Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest41( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest41.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x945304EB96065B2A98B57A48A06AE28D285A71B5) contract_0 = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87) sender = EOA( @@ -44,7 +54,6 @@ def test_random_statetest41( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -89,7 +98,7 @@ def test_random_statetest41( data=Bytes( "7f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b57f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b57f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c350517f0000000000000000000000010000000000000000000000000000000000000000417f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b56a84a10719a1786a6510349b0282" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x4ADE804, ) diff --git a/tests/ported_static/stRandom/test_random_statetest43.py b/tests/ported_static/stRandom/test_random_statetest43.py index e0061e13bbe..af14f8e264a 100644 --- a/tests/ported_static/stRandom/test_random_statetest43.py +++ b/tests/ported_static/stRandom/test_random_statetest43.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -28,6 +30,7 @@ @pytest.mark.pre_alloc_mutable def test_random_statetest43( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_random_statetest43.""" @@ -40,7 +43,6 @@ def test_random_statetest43( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw @@ -90,7 +92,7 @@ def test_random_statetest43( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3b0a55096941861a3755a196f259a1" # noqa: E501 ), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=0x229C8BFA, ) diff --git a/tests/ported_static/stRandom/test_random_statetest47.py b/tests/ported_static/stRandom/test_random_statetest47.py index b3ad3b7862b..75436ccf17b 100644 --- a/tests/ported_static/stRandom/test_random_statetest47.py +++ b/tests/ported_static/stRandom/test_random_statetest47.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest47Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest47( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest47.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest47( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -90,7 +99,7 @@ def test_random_statetest47( data=Bytes( "437f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c350437f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6f1544898b167c6a6f6d5b953714457e" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x77A1A475, ) diff --git a/tests/ported_static/stRandom/test_random_statetest49.py b/tests/ported_static/stRandom/test_random_statetest49.py index 9544cdc81ab..80ee0fecba0 100644 --- a/tests/ported_static/stRandom/test_random_statetest49.py +++ b/tests/ported_static/stRandom/test_random_statetest49.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest49Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +18,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +33,14 @@ def test_random_statetest49( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest49.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -40,7 +50,6 @@ def test_random_statetest49( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw @@ -84,7 +93,7 @@ def test_random_statetest49( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000010000000000000000000000000000000000000000807f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000000000000000000000000000000000000000c3507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e7961859c" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x69D65F4B, ) diff --git a/tests/ported_static/stRandom/test_random_statetest52.py b/tests/ported_static/stRandom/test_random_statetest52.py index 3f22f5cc80d..92da6f37c3a 100644 --- a/tests/ported_static/stRandom/test_random_statetest52.py +++ b/tests/ported_static/stRandom/test_random_statetest52.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest52Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest52( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest52.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest52( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -88,7 +97,7 @@ def test_random_statetest52( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe410a81437f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000006f59a130a10a189fc653057a185b886c" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x27CBF98C, ) diff --git a/tests/ported_static/stRandom/test_random_statetest58.py b/tests/ported_static/stRandom/test_random_statetest58.py index 09e405888d9..7f6faa8aedf 100644 --- a/tests/ported_static/stRandom/test_random_statetest58.py +++ b/tests/ported_static/stRandom/test_random_statetest58.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest58Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest58( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest58.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest58( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -96,7 +105,7 @@ def test_random_statetest58( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe417fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c350367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe096902947d567838719e97f301" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x1F00EC9E, ) diff --git a/tests/ported_static/stRandom/test_random_statetest59.py b/tests/ported_static/stRandom/test_random_statetest59.py index ae2df70abd0..b5d3df091a1 100644 --- a/tests/ported_static/stRandom/test_random_statetest59.py +++ b/tests/ported_static/stRandom/test_random_statetest59.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest59Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest59( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest59.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest59( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -95,7 +104,7 @@ def test_random_statetest59( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff0208673a06756406548b99" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x2591EEF6, ) diff --git a/tests/ported_static/stRandom/test_random_statetest6.py b/tests/ported_static/stRandom/test_random_statetest6.py index 39c3c2d8d13..7a203fab94e 100644 --- a/tests/ported_static/stRandom/test_random_statetest6.py +++ b/tests/ported_static/stRandom/test_random_statetest6.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest6Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest6( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest6.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest6( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -91,7 +100,7 @@ def test_random_statetest6( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e794143416f1732797105f237768fe506871ac853" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x3227D64E, ) diff --git a/tests/ported_static/stRandom/test_random_statetest60.py b/tests/ported_static/stRandom/test_random_statetest60.py index fe8731cdbe4..55d243e4ef6 100644 --- a/tests/ported_static/stRandom/test_random_statetest60.py +++ b/tests/ported_static/stRandom/test_random_statetest60.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest60Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest60( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest60.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest60( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -88,7 +97,7 @@ def test_random_statetest60( data=Bytes( "427f0000000000000000000000000000000000000000000000000000000000000000427f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff437f000000000000000000000000000000000000000000000000000000000000c3507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe6f969001091aa15b8b9b75459d015a04" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x54DE1EAF, ) diff --git a/tests/ported_static/stRandom/test_random_statetest62.py b/tests/ported_static/stRandom/test_random_statetest62.py index cda4ae7bfe4..46dae90ae4a 100644 --- a/tests/ported_static/stRandom/test_random_statetest62.py +++ b/tests/ported_static/stRandom/test_random_statetest62.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest62Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest62( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest62.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest62( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -91,7 +100,7 @@ def test_random_statetest62( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff437f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000016f7268713013964a96ac575804332501" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x493FBF98, ) diff --git a/tests/ported_static/stRandom/test_random_statetest63.py b/tests/ported_static/stRandom/test_random_statetest63.py index b2ee59d3ab3..b8752371d61 100644 --- a/tests/ported_static/stRandom/test_random_statetest63.py +++ b/tests/ported_static/stRandom/test_random_statetest63.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest63Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest63( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest63.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest63( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -89,7 +98,7 @@ def test_random_statetest63( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000006f977f157e088003767a86928e825296" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x31B19D43, ) diff --git a/tests/ported_static/stRandom/test_random_statetest64.py b/tests/ported_static/stRandom/test_random_statetest64.py index 1a7f95a3d07..1082e072d49 100644 --- a/tests/ported_static/stRandom/test_random_statetest64.py +++ b/tests/ported_static/stRandom/test_random_statetest64.py @@ -13,9 +13,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +31,7 @@ @pytest.mark.pre_alloc_mutable def test_random_statetest64( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_random_statetest64.""" @@ -44,7 +47,6 @@ def test_random_statetest64( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -92,7 +94,7 @@ def test_random_statetest64( data=Bytes( "427f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe087f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b50a" # noqa: E501 ), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=0x34179199, ) diff --git a/tests/ported_static/stRandom/test_random_statetest66.py b/tests/ported_static/stRandom/test_random_statetest66.py index 7bdcae93402..b1ac9c6c177 100644 --- a/tests/ported_static/stRandom/test_random_statetest66.py +++ b/tests/ported_static/stRandom/test_random_statetest66.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest66Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest66( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest66.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x945304EB96065B2A98B57A48A06AE28D285A71B5) contract_0 = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87) sender = EOA( @@ -44,7 +54,6 @@ def test_random_statetest66( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -90,7 +99,7 @@ def test_random_statetest66( data=Bytes( "457fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff417fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b57ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe097f0000000000000000000000010000000000000000000000000000000000000000" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x2F5660CE, ) diff --git a/tests/ported_static/stRandom/test_random_statetest67.py b/tests/ported_static/stRandom/test_random_statetest67.py index fd4c0352f86..0ea82c5570b 100644 --- a/tests/ported_static/stRandom/test_random_statetest67.py +++ b/tests/ported_static/stRandom/test_random_statetest67.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest67Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest67( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest67.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest67( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -89,7 +98,7 @@ def test_random_statetest67( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000016f699776659a06a27607a2166d537331" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x7DF32855, ) diff --git a/tests/ported_static/stRandom/test_random_statetest69.py b/tests/ported_static/stRandom/test_random_statetest69.py index eaadb4756a1..58b0b1b4d2f 100644 --- a/tests/ported_static/stRandom/test_random_statetest69.py +++ b/tests/ported_static/stRandom/test_random_statetest69.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest69Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest69( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest69.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest69( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -90,7 +99,7 @@ def test_random_statetest69( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe43596f15a0770a7676611a6595057b768b64" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x2F6C315B, ) diff --git a/tests/ported_static/stRandom/test_random_statetest73.py b/tests/ported_static/stRandom/test_random_statetest73.py index 8f39446b096..cbe0f4f7fdd 100644 --- a/tests/ported_static/stRandom/test_random_statetest73.py +++ b/tests/ported_static/stRandom/test_random_statetest73.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest73Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest73( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest73.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x945304EB96065B2A98B57A48A06AE28D285A71B5) contract_0 = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87) sender = EOA( @@ -44,7 +54,6 @@ def test_random_statetest73( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -88,7 +97,7 @@ def test_random_statetest73( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b57e7f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b5b573198d729b711671056e0a0555346138" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x505D427, ) diff --git a/tests/ported_static/stRandom/test_random_statetest74.py b/tests/ported_static/stRandom/test_random_statetest74.py index ab71e17db0d..9444931dbdc 100644 --- a/tests/ported_static/stRandom/test_random_statetest74.py +++ b/tests/ported_static/stRandom/test_random_statetest74.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest74Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest74( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest74.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest74( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -88,7 +97,7 @@ def test_random_statetest74( data=Bytes( "427ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff3a7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000006f141097788a7b5a72139c07076f1842" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x48E72790, ) diff --git a/tests/ported_static/stRandom/test_random_statetest75.py b/tests/ported_static/stRandom/test_random_statetest75.py index c342198fc47..8001c8457a5 100644 --- a/tests/ported_static/stRandom/test_random_statetest75.py +++ b/tests/ported_static/stRandom/test_random_statetest75.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest75Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest75( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest75.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest75( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -93,7 +102,7 @@ def test_random_statetest75( data=Bytes( "457ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000100000000000000000000000000000000000000006f5893504553386c7d15400177928776" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0xABD0738, ) diff --git a/tests/ported_static/stRandom/test_random_statetest77.py b/tests/ported_static/stRandom/test_random_statetest77.py index 8dfd8020d48..0b8eb36b733 100644 --- a/tests/ported_static/stRandom/test_random_statetest77.py +++ b/tests/ported_static/stRandom/test_random_statetest77.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest77Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest77( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest77.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest77( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -86,7 +95,7 @@ def test_random_statetest77( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000141937f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000006f79a06df1a08d05373216d372190341" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x4A760CDB, ) diff --git a/tests/ported_static/stRandom/test_random_statetest80.py b/tests/ported_static/stRandom/test_random_statetest80.py index b294ed4588b..1a4602a5d18 100644 --- a/tests/ported_static/stRandom/test_random_statetest80.py +++ b/tests/ported_static/stRandom/test_random_statetest80.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest80Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest80( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest80.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x945304EB96065B2A98B57A48A06AE28D285A71B5) contract_0 = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87) sender = EOA( @@ -44,7 +54,6 @@ def test_random_statetest80( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -90,7 +99,7 @@ def test_random_statetest80( data=Bytes( "7f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b57f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b57f0000000000000000000000010000000000000000000000000000000000000000117fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7e7f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b5681069127b3b9c877d6f6169ff36" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x1ED9A5B6, ) diff --git a/tests/ported_static/stRandom/test_random_statetest81.py b/tests/ported_static/stRandom/test_random_statetest81.py index ae48614266f..7f18d30496c 100644 --- a/tests/ported_static/stRandom/test_random_statetest81.py +++ b/tests/ported_static/stRandom/test_random_statetest81.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest81Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest81( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest81.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest81( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -90,7 +99,7 @@ def test_random_statetest81( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000100000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe437f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff436f616c327e0435743c515b078453a03c" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x358AB2E1, ) diff --git a/tests/ported_static/stRandom/test_random_statetest83.py b/tests/ported_static/stRandom/test_random_statetest83.py index adb1b9f4b2a..045fc17a702 100644 --- a/tests/ported_static/stRandom/test_random_statetest83.py +++ b/tests/ported_static/stRandom/test_random_statetest83.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest83Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest83( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest83.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest83( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -89,7 +98,7 @@ def test_random_statetest83( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff427f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000307ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe6fa1109af20740728e72150a7a9c0959" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x3C81798C, ) diff --git a/tests/ported_static/stRandom/test_random_statetest85.py b/tests/ported_static/stRandom/test_random_statetest85.py index 85d9941e15e..d92db0d8f99 100644 --- a/tests/ported_static/stRandom/test_random_statetest85.py +++ b/tests/ported_static/stRandom/test_random_statetest85.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest85Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +18,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +33,14 @@ def test_random_statetest85( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest85.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -40,7 +50,6 @@ def test_random_statetest85( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw @@ -74,7 +83,7 @@ def test_random_statetest85( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000000000000000000000000000000000000000c350f25b557e348ff374819d123109539b" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x3B46EEB1, ) diff --git a/tests/ported_static/stRandom/test_random_statetest87.py b/tests/ported_static/stRandom/test_random_statetest87.py index 50a064810eb..18c8d7e6a0b 100644 --- a/tests/ported_static/stRandom/test_random_statetest87.py +++ b/tests/ported_static/stRandom/test_random_statetest87.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest87Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest87( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest87.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest87( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -88,7 +97,7 @@ def test_random_statetest87( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000005b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe6f446e638e7e16736c030393727d748174" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x7B1E5DC9, ) diff --git a/tests/ported_static/stRandom/test_random_statetest88.py b/tests/ported_static/stRandom/test_random_statetest88.py index c6b24419222..d9bb7b3c472 100644 --- a/tests/ported_static/stRandom/test_random_statetest88.py +++ b/tests/ported_static/stRandom/test_random_statetest88.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest88Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest88( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest88.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest88( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -87,7 +96,7 @@ def test_random_statetest88( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e794343537f000000000000000000000000000000000000000000000000000000000000c350117fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000016f34f06a7014541167033909103620f3" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x3E996CB5, ) diff --git a/tests/ported_static/stRandom/test_random_statetest89.py b/tests/ported_static/stRandom/test_random_statetest89.py index 37bb4e367c3..84c8b823240 100644 --- a/tests/ported_static/stRandom/test_random_statetest89.py +++ b/tests/ported_static/stRandom/test_random_statetest89.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest89Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest89( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest89.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest89( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -84,7 +93,7 @@ def test_random_statetest89( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000016f05648ce0ad106b7a6f3483379e62876b" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x41032F3B, ) diff --git a/tests/ported_static/stRandom/test_random_statetest9.py b/tests/ported_static/stRandom/test_random_statetest9.py index 9fd7f121cad..fc4d799e62e 100644 --- a/tests/ported_static/stRandom/test_random_statetest9.py +++ b/tests/ported_static/stRandom/test_random_statetest9.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest9Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest9( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest9.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest9( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -87,7 +96,7 @@ def test_random_statetest9( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000016f757fb845405bf1ff959ba03a9c336b" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0xCEFB419, ) diff --git a/tests/ported_static/stRandom/test_random_statetest90.py b/tests/ported_static/stRandom/test_random_statetest90.py index b6c9a96b8d3..d13636db170 100644 --- a/tests/ported_static/stRandom/test_random_statetest90.py +++ b/tests/ported_static/stRandom/test_random_statetest90.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest90Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest90( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest90.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest90( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -86,7 +95,7 @@ def test_random_statetest90( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff45157f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000016f116b4177f25178d7048212877e9568" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0xA10954E, ) diff --git a/tests/ported_static/stRandom/test_random_statetest92.py b/tests/ported_static/stRandom/test_random_statetest92.py index 15f258e054a..2307d7542f7 100644 --- a/tests/ported_static/stRandom/test_random_statetest92.py +++ b/tests/ported_static/stRandom/test_random_statetest92.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest92Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest92( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest92.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest92( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -90,7 +99,7 @@ def test_random_statetest92( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000006f59640c655956799087168f0658a11a" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x1C23D3BC, ) diff --git a/tests/ported_static/stRandom/test_random_statetest95.py b/tests/ported_static/stRandom/test_random_statetest95.py index 572f85c4f68..0ad602c99f9 100644 --- a/tests/ported_static/stRandom/test_random_statetest95.py +++ b/tests/ported_static/stRandom/test_random_statetest95.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest95Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest95( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest95.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest95( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -91,7 +100,7 @@ def test_random_statetest95( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff14447ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5b" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x7E83FA74, ) diff --git a/tests/ported_static/stRandom/test_random_statetest96.py b/tests/ported_static/stRandom/test_random_statetest96.py index f957c3f1d60..779a04aad03 100644 --- a/tests/ported_static/stRandom/test_random_statetest96.py +++ b/tests/ported_static/stRandom/test_random_statetest96.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom/randomStatetest96Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest96( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest96.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest96( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -89,7 +98,7 @@ def test_random_statetest96( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000006f183b68a09b08953085a854a39d9212" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x4A4D8FC4, ) diff --git a/tests/ported_static/stRandom/test_random_statetest98.py b/tests/ported_static/stRandom/test_random_statetest98.py index 9ab7e9bac2e..880eac68271 100644 --- a/tests/ported_static/stRandom/test_random_statetest98.py +++ b/tests/ported_static/stRandom/test_random_statetest98.py @@ -13,9 +13,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +31,7 @@ @pytest.mark.pre_alloc_mutable def test_random_statetest98( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_random_statetest98.""" @@ -43,7 +46,6 @@ def test_random_statetest98( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -92,7 +94,7 @@ def test_random_statetest98( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79417fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000000b08" # noqa: E501 ), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=0x231A7794, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest.py b/tests/ported_static/stRandom2/test_random_statetest.py index 9efe1661320..27df3124391 100644 --- a/tests/ported_static/stRandom2/test_random_statetest.py +++ b/tests/ported_static/stRandom2/test_random_statetest.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetestFiller.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -91,7 +100,7 @@ def test_random_statetest( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe6f29199c9aa4054170f1a15a55056f96" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0xF08F864, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest384.py b/tests/ported_static/stRandom2/test_random_statetest384.py index a6ffddfd636..b7ff2acd9a5 100644 --- a/tests/ported_static/stRandom2/test_random_statetest384.py +++ b/tests/ported_static/stRandom2/test_random_statetest384.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest384Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest384( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest384.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest384( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -89,7 +98,7 @@ def test_random_statetest384( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff6f16133502727c0a7f679b456df0935763" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x7B2BD74C, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest385.py b/tests/ported_static/stRandom2/test_random_statetest385.py index da8d8a10539..fbf8c5b914c 100644 --- a/tests/ported_static/stRandom2/test_random_statetest385.py +++ b/tests/ported_static/stRandom2/test_random_statetest385.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest385Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest385( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest385.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest385( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -85,7 +94,7 @@ def test_random_statetest385( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79547f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff6f785188182063156955631a7a85093a" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x2DCC90D2, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest386.py b/tests/ported_static/stRandom2/test_random_statetest386.py index a27676913c5..96cfc99e246 100644 --- a/tests/ported_static/stRandom2/test_random_statetest386.py +++ b/tests/ported_static/stRandom2/test_random_statetest386.py @@ -3,6 +3,11 @@ Ported from: state_tests/stRandom2/randomStatetest386Filler.json + +@manually-enhanced: Do not overwrite. `gas_limit` raised on Amsterdam +to cover EIP-8037 state-gas spill. Pre-EIP-8037 keeps the original +100 000. + """ import pytest @@ -15,6 +20,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +35,14 @@ def test_random_statetest386( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest386.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -40,7 +52,6 @@ def test_random_statetest386( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw @@ -92,7 +103,7 @@ def test_random_statetest386( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff047f000000000000000000000000000000000000000000000000000000000000000105133641010b8111" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x19D7AC44, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest388.py b/tests/ported_static/stRandom2/test_random_statetest388.py index 075141dd621..e799d490153 100644 --- a/tests/ported_static/stRandom2/test_random_statetest388.py +++ b/tests/ported_static/stRandom2/test_random_statetest388.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest388Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest388( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest388.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x945304EB96065B2A98B57A48A06AE28D285A71B5) contract_0 = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87) sender = EOA( @@ -44,7 +54,6 @@ def test_random_statetest388( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -89,7 +98,7 @@ def test_random_statetest388( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b57f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b57f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7e7f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b5765b8f743b9979a0905b6a189165" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x460B9F39, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest389.py b/tests/ported_static/stRandom2/test_random_statetest389.py index 43e4d648db8..8cfacc2758c 100644 --- a/tests/ported_static/stRandom2/test_random_statetest389.py +++ b/tests/ported_static/stRandom2/test_random_statetest389.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest389Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +18,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +33,14 @@ def test_random_statetest389( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest389.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -40,7 +50,6 @@ def test_random_statetest389( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw @@ -90,7 +99,7 @@ def test_random_statetest389( data=Bytes( "457ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000100000000000000000000000000000000000000007f0000000000000000000000000000000000000000000000000000000000000000427f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3503a863854581237" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x5BF15D9B, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest395.py b/tests/ported_static/stRandom2/test_random_statetest395.py index 7f637d20858..d3d5c53b250 100644 --- a/tests/ported_static/stRandom2/test_random_statetest395.py +++ b/tests/ported_static/stRandom2/test_random_statetest395.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest395Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest395( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest395.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest395( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -89,7 +98,7 @@ def test_random_statetest395( data=Bytes( "447f0000000000000000000000000000000000000000000000000000000000000001417f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6f823140710bf13990e4500136726d8b" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x5A9C61EF, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest398.py b/tests/ported_static/stRandom2/test_random_statetest398.py index 8d291f4499d..fbe759a2324 100644 --- a/tests/ported_static/stRandom2/test_random_statetest398.py +++ b/tests/ported_static/stRandom2/test_random_statetest398.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest398Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest398( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest398.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest398( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -86,7 +95,7 @@ def test_random_statetest398( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e796f3781413b695a69079d7f5105829207" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x69A26DE, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest399.py b/tests/ported_static/stRandom2/test_random_statetest399.py index 28398ba81ae..e8ba5a4ee29 100644 --- a/tests/ported_static/stRandom2/test_random_statetest399.py +++ b/tests/ported_static/stRandom2/test_random_statetest399.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest399Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest399( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest399.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest399( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -93,7 +102,7 @@ def test_random_statetest399( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe4544437f00000000000000000000000000000000000000000000000000000000000000017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6f98324016076d428a9898129b16849a" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x2099AF7A, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest402.py b/tests/ported_static/stRandom2/test_random_statetest402.py index 8f64d20bf31..528f4373389 100644 --- a/tests/ported_static/stRandom2/test_random_statetest402.py +++ b/tests/ported_static/stRandom2/test_random_statetest402.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest402Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest402( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest402.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest402( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -89,7 +98,7 @@ def test_random_statetest402( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff437f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000006f62138c87028162ea32a2db7e301004" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x37EBC742, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest405.py b/tests/ported_static/stRandom2/test_random_statetest405.py index 1ef48d84f27..f3592725b1a 100644 --- a/tests/ported_static/stRandom2/test_random_statetest405.py +++ b/tests/ported_static/stRandom2/test_random_statetest405.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest405Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest405( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest405.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest405( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -93,7 +102,7 @@ def test_random_statetest405( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff44457f0000000000000000000000010000000000000000000000000000000000000000037ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6f318d0707977199361171756f6d458e" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x10596FAF, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest406.py b/tests/ported_static/stRandom2/test_random_statetest406.py index 5dbbc156458..b3fa3581b2c 100644 --- a/tests/ported_static/stRandom2/test_random_statetest406.py +++ b/tests/ported_static/stRandom2/test_random_statetest406.py @@ -13,9 +13,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +31,7 @@ @pytest.mark.pre_alloc_mutable def test_random_statetest406( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_random_statetest406.""" @@ -44,7 +47,6 @@ def test_random_statetest406( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -92,7 +94,7 @@ def test_random_statetest406( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b57ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b57ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7e7f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b58b8e99f33c647165337e389f7b9c909cba" # noqa: E501 ), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=0x4527B6AD, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest407.py b/tests/ported_static/stRandom2/test_random_statetest407.py index 6590ba5af0e..b113779f4af 100644 --- a/tests/ported_static/stRandom2/test_random_statetest407.py +++ b/tests/ported_static/stRandom2/test_random_statetest407.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest407Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest407( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest407.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest407( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -89,7 +98,7 @@ def test_random_statetest407( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff437ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000000000000000000000000000000000000000c350437f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e796f6d71656f054471181163037902615b" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x313547F8, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest408.py b/tests/ported_static/stRandom2/test_random_statetest408.py index 72ea03a898e..7551ed6d5d4 100644 --- a/tests/ported_static/stRandom2/test_random_statetest408.py +++ b/tests/ported_static/stRandom2/test_random_statetest408.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest408Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest408( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest408.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest408( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -93,7 +102,7 @@ def test_random_statetest408( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe447f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000000000000000000000000000000000000000c3507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff6f80656e8e6478946a323482135a8bf7" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x63AD417F, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest409.py b/tests/ported_static/stRandom2/test_random_statetest409.py index 4852316345a..631e67dcf56 100644 --- a/tests/ported_static/stRandom2/test_random_statetest409.py +++ b/tests/ported_static/stRandom2/test_random_statetest409.py @@ -13,9 +13,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +31,7 @@ @pytest.mark.pre_alloc_mutable def test_random_statetest409( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_random_statetest409.""" @@ -43,7 +46,6 @@ def test_random_statetest409( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -103,7 +105,7 @@ def test_random_statetest409( data=Bytes( "5b7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000001000000000000000000000000000000000000000009ff511287868833063aa3579d8e58" # noqa: E501 ), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=0x41028C83, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest411.py b/tests/ported_static/stRandom2/test_random_statetest411.py index 8e07e657fa8..179bb190c71 100644 --- a/tests/ported_static/stRandom2/test_random_statetest411.py +++ b/tests/ported_static/stRandom2/test_random_statetest411.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest411Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest411( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest411.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest411( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -91,7 +100,7 @@ def test_random_statetest411( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000006f44a17892738b6895619d7a93507d649d" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x7E5B1276, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest412.py b/tests/ported_static/stRandom2/test_random_statetest412.py index 9d8f554e677..7460990cfe2 100644 --- a/tests/ported_static/stRandom2/test_random_statetest412.py +++ b/tests/ported_static/stRandom2/test_random_statetest412.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest412Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest412( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest412.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest412( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -87,7 +96,7 @@ def test_random_statetest412( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000000000000000000000000000000000000000c3507f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff6fa46ef06a5a858b9742198a37e1153c" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x75CF6AD, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest413.py b/tests/ported_static/stRandom2/test_random_statetest413.py index f919d7767e6..ddf970571ae 100644 --- a/tests/ported_static/stRandom2/test_random_statetest413.py +++ b/tests/ported_static/stRandom2/test_random_statetest413.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest413Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest413( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest413.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest413( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -89,7 +98,7 @@ def test_random_statetest413( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000000000000000000000000000000000000000c3507f0000000000000000000000010000000000000000000000000000000000000000817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe037f00000000000000000000000000000000000000000000000000000000000000016f086e2055149345ad1a018b06370814" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x47E29C11, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest416.py b/tests/ported_static/stRandom2/test_random_statetest416.py index 853a3e034d1..f9b9ba9332b 100644 --- a/tests/ported_static/stRandom2/test_random_statetest416.py +++ b/tests/ported_static/stRandom2/test_random_statetest416.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest416Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +18,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +33,14 @@ def test_random_statetest416( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest416.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -40,7 +50,6 @@ def test_random_statetest416( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw @@ -81,7 +90,7 @@ def test_random_statetest416( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff427f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e7943" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x4F622410, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest419.py b/tests/ported_static/stRandom2/test_random_statetest419.py index bc87d278efa..8af9b0c54fc 100644 --- a/tests/ported_static/stRandom2/test_random_statetest419.py +++ b/tests/ported_static/stRandom2/test_random_statetest419.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest419Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest419( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest419.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest419( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -95,7 +104,7 @@ def test_random_statetest419( data=Bytes( "437ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000000000000000000000000000000000000000c3507f0000000000000000000000000000000000000000000000000000000000000001417ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe6f73095b7ee211595a6b80a311900a78" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x6A4CEBB4, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest421.py b/tests/ported_static/stRandom2/test_random_statetest421.py index 0ae5687e6cc..f784c829ac2 100644 --- a/tests/ported_static/stRandom2/test_random_statetest421.py +++ b/tests/ported_static/stRandom2/test_random_statetest421.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest421Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest421( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest421.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest421( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -87,7 +96,7 @@ def test_random_statetest421( data=Bytes( "437f00000000000000000000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6f38454051968ff184a47d500912319717" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x52D1555F, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest424.py b/tests/ported_static/stRandom2/test_random_statetest424.py index 306991317fe..5a4b1706b07 100644 --- a/tests/ported_static/stRandom2/test_random_statetest424.py +++ b/tests/ported_static/stRandom2/test_random_statetest424.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest424Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest424( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest424.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest424( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -89,7 +98,7 @@ def test_random_statetest424( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79437f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000000000000000000000000000000000000000000000436f18116552626186825096665471140a" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x4BCD2F4F, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest425.py b/tests/ported_static/stRandom2/test_random_statetest425.py index 46142661c25..8f26fba2f5d 100644 --- a/tests/ported_static/stRandom2/test_random_statetest425.py +++ b/tests/ported_static/stRandom2/test_random_statetest425.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest425Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest425( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest425.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest425( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -84,7 +93,7 @@ def test_random_statetest425( data=Bytes( "7f0000000000000000000000010000000000000000000000000000000000000000417f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff6f885707818b889a89975552f0128442" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x22371A75, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest426.py b/tests/ported_static/stRandom2/test_random_statetest426.py index ed5c86a7885..7977467a6a5 100644 --- a/tests/ported_static/stRandom2/test_random_statetest426.py +++ b/tests/ported_static/stRandom2/test_random_statetest426.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest426Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest426( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest426.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest426( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -91,7 +100,7 @@ def test_random_statetest426( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79417ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000006f456d1687795a95938b0139976099f0" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x613B33CA, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest429.py b/tests/ported_static/stRandom2/test_random_statetest429.py index 72b994a345e..60a9e50233b 100644 --- a/tests/ported_static/stRandom2/test_random_statetest429.py +++ b/tests/ported_static/stRandom2/test_random_statetest429.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest429Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest429( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest429.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest429( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -91,7 +100,7 @@ def test_random_statetest429( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79417ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6f98121f388786729087773476331366" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x5430ADAF, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest430.py b/tests/ported_static/stRandom2/test_random_statetest430.py index 5eb40ab0a04..f99d9695f69 100644 --- a/tests/ported_static/stRandom2/test_random_statetest430.py +++ b/tests/ported_static/stRandom2/test_random_statetest430.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest430Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest430( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest430.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest430( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -91,7 +100,7 @@ def test_random_statetest430( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe427f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000006f7d41a29934035b748e96a3135b6964" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x6BF5E61F, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest435.py b/tests/ported_static/stRandom2/test_random_statetest435.py index 22fc8ca14e9..dd23b752174 100644 --- a/tests/ported_static/stRandom2/test_random_statetest435.py +++ b/tests/ported_static/stRandom2/test_random_statetest435.py @@ -13,9 +13,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +31,7 @@ @pytest.mark.pre_alloc_mutable def test_random_statetest435( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_random_statetest435.""" @@ -43,7 +46,6 @@ def test_random_statetest435( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -89,7 +91,7 @@ def test_random_statetest435( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017f0000000000000000000000000000000000000000000000000000000000000000447ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000042613488076233797f5539" # noqa: E501 ), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=0x4ADF9C16, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest436.py b/tests/ported_static/stRandom2/test_random_statetest436.py index 187a7c4ac79..586314ec15a 100644 --- a/tests/ported_static/stRandom2/test_random_statetest436.py +++ b/tests/ported_static/stRandom2/test_random_statetest436.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest436Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest436( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest436.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest436( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -90,7 +99,7 @@ def test_random_statetest436( data=Bytes( "367f00000000000000000000000000000000000000000000000000000000000000017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff417fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79456f8108067a345b7a76a20a835a0a0b6c10" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x57454F1E, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest437.py b/tests/ported_static/stRandom2/test_random_statetest437.py index 81f5042bee2..4578f601f4c 100644 --- a/tests/ported_static/stRandom2/test_random_statetest437.py +++ b/tests/ported_static/stRandom2/test_random_statetest437.py @@ -13,9 +13,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +31,7 @@ @pytest.mark.pre_alloc_mutable def test_random_statetest437( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_random_statetest437.""" @@ -44,7 +47,6 @@ def test_random_statetest437( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -93,7 +95,7 @@ def test_random_statetest437( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe437f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b57f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000013a133908" # noqa: E501 ), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=0x6873B903, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest438.py b/tests/ported_static/stRandom2/test_random_statetest438.py index c91b9a70a5c..0ce4a0ac366 100644 --- a/tests/ported_static/stRandom2/test_random_statetest438.py +++ b/tests/ported_static/stRandom2/test_random_statetest438.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest438Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +18,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +33,14 @@ def test_random_statetest438( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest438.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -40,7 +50,6 @@ def test_random_statetest438( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw @@ -86,7 +95,7 @@ def test_random_statetest438( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff097fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x3FDE3BBC, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest439.py b/tests/ported_static/stRandom2/test_random_statetest439.py index fc18736d3e1..69e9b00d114 100644 --- a/tests/ported_static/stRandom2/test_random_statetest439.py +++ b/tests/ported_static/stRandom2/test_random_statetest439.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest439Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest439( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest439.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest439( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -91,7 +100,7 @@ def test_random_statetest439( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6f5b1609653438813340097c53a49316" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x17BA0353, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest440.py b/tests/ported_static/stRandom2/test_random_statetest440.py index 873e5515793..ba3114d0f37 100644 --- a/tests/ported_static/stRandom2/test_random_statetest440.py +++ b/tests/ported_static/stRandom2/test_random_statetest440.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest440Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest440( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest440.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest440( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -85,7 +94,7 @@ def test_random_statetest440( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e7945457f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff416f01513a9b8216816f74f3676e9ea261" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x4A3FD736, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest442.py b/tests/ported_static/stRandom2/test_random_statetest442.py index 7959e6b1ef6..a3fc497061c 100644 --- a/tests/ported_static/stRandom2/test_random_statetest442.py +++ b/tests/ported_static/stRandom2/test_random_statetest442.py @@ -13,9 +13,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +31,7 @@ @pytest.mark.pre_alloc_mutable def test_random_statetest442( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_random_statetest442.""" @@ -43,7 +46,6 @@ def test_random_statetest442( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -90,7 +92,7 @@ def test_random_statetest442( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff917f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000000000000000000000000000000000000000c350183381" # noqa: E501 ), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=0x2F5A5AA2, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest446.py b/tests/ported_static/stRandom2/test_random_statetest446.py index 29fe4b0606d..fdcddb6bd89 100644 --- a/tests/ported_static/stRandom2/test_random_statetest446.py +++ b/tests/ported_static/stRandom2/test_random_statetest446.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest446Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest446( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest446.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest446( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -90,7 +99,7 @@ def test_random_statetest446( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff09" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x872ECB9, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest447.py b/tests/ported_static/stRandom2/test_random_statetest447.py index d1240b7a71a..5d05d1aeecb 100644 --- a/tests/ported_static/stRandom2/test_random_statetest447.py +++ b/tests/ported_static/stRandom2/test_random_statetest447.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest447Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest447( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest447.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest447( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -94,7 +103,7 @@ def test_random_statetest447( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000100000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe437f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff08" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x1569EBA8, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest450.py b/tests/ported_static/stRandom2/test_random_statetest450.py index f19bbc92f7c..50d97a3609d 100644 --- a/tests/ported_static/stRandom2/test_random_statetest450.py +++ b/tests/ported_static/stRandom2/test_random_statetest450.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest450Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +18,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +33,14 @@ def test_random_statetest450( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest450.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A764000000) @@ -40,7 +50,6 @@ def test_random_statetest450( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw @@ -88,7 +97,7 @@ def test_random_statetest450( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507f0000000000000000000000010000000000000000000000000000000000000000033a80" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x50F09196, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest451.py b/tests/ported_static/stRandom2/test_random_statetest451.py index f8c29c42d22..4b29183b8e3 100644 --- a/tests/ported_static/stRandom2/test_random_statetest451.py +++ b/tests/ported_static/stRandom2/test_random_statetest451.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest451Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest451( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest451.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest451( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -90,7 +99,7 @@ def test_random_statetest451( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000006fed05989a0659453076573a87041174" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x306CA21A, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest452.py b/tests/ported_static/stRandom2/test_random_statetest452.py index f293656b762..bab4ce182c0 100644 --- a/tests/ported_static/stRandom2/test_random_statetest452.py +++ b/tests/ported_static/stRandom2/test_random_statetest452.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest452Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest452( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest452.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest452( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -90,7 +99,7 @@ def test_random_statetest452( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e796f0a3289746806163630047dff983105" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x58F77982, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest455.py b/tests/ported_static/stRandom2/test_random_statetest455.py index 29f79d69583..e6cf5829f17 100644 --- a/tests/ported_static/stRandom2/test_random_statetest455.py +++ b/tests/ported_static/stRandom2/test_random_statetest455.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest455Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest455( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest455.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest455( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -89,7 +98,7 @@ def test_random_statetest455( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e796f858b1411f218693ca2245b918274f3" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x2BF8F04F, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest457.py b/tests/ported_static/stRandom2/test_random_statetest457.py index f8fca23bc05..ca20ff7a1c8 100644 --- a/tests/ported_static/stRandom2/test_random_statetest457.py +++ b/tests/ported_static/stRandom2/test_random_statetest457.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest457Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest457( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest457.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest457( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -87,7 +96,7 @@ def test_random_statetest457( data=Bytes( "44417f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe6f949fa28af308a37a136c626218927d" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x12DE4990, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest460.py b/tests/ported_static/stRandom2/test_random_statetest460.py index d710b0a6ea2..6c2c1f0b13a 100644 --- a/tests/ported_static/stRandom2/test_random_statetest460.py +++ b/tests/ported_static/stRandom2/test_random_statetest460.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest460Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest460( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest460.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest460( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -92,7 +101,7 @@ def test_random_statetest460( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000003a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c350046f16a23c6c90739ba201697b4315778a" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x5A8388BF, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest461.py b/tests/ported_static/stRandom2/test_random_statetest461.py index 2a9d8c2284d..08dce9529e4 100644 --- a/tests/ported_static/stRandom2/test_random_statetest461.py +++ b/tests/ported_static/stRandom2/test_random_statetest461.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest461Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +18,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +33,14 @@ def test_random_statetest461( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest461.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -40,7 +50,6 @@ def test_random_statetest461( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw @@ -83,7 +92,7 @@ def test_random_statetest461( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c350517f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff42515259" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x20B19906, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest462.py b/tests/ported_static/stRandom2/test_random_statetest462.py index 64e66e327df..7160533bddd 100644 --- a/tests/ported_static/stRandom2/test_random_statetest462.py +++ b/tests/ported_static/stRandom2/test_random_statetest462.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest462Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest462( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest462.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest462( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -84,7 +93,7 @@ def test_random_statetest462( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000100000000000000000000000000000000000000006f8e0186019d029d1354681482826f37" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x564E62DA, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest464.py b/tests/ported_static/stRandom2/test_random_statetest464.py index fd502ab1fac..5ef019e574a 100644 --- a/tests/ported_static/stRandom2/test_random_statetest464.py +++ b/tests/ported_static/stRandom2/test_random_statetest464.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest464Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest464( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest464.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest464( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -90,7 +99,7 @@ def test_random_statetest464( data=Bytes( "447f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8209" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x2491B9, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest465.py b/tests/ported_static/stRandom2/test_random_statetest465.py index d1ceeac89e2..616396458de 100644 --- a/tests/ported_static/stRandom2/test_random_statetest465.py +++ b/tests/ported_static/stRandom2/test_random_statetest465.py @@ -3,6 +3,11 @@ Ported from: state_tests/stRandom2/randomStatetest465Filler.json + +@manually-enhanced: Do not overwrite. `gas_limit` raised on Amsterdam +to cover EIP-8037 state-gas spill. Pre-EIP-8037 keeps the original +100 000. + """ import pytest @@ -15,6 +20,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +35,14 @@ def test_random_statetest465( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest465.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -40,7 +52,6 @@ def test_random_statetest465( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw @@ -83,7 +94,7 @@ def test_random_statetest465( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79437f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000001" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x5DE12C27, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest466.py b/tests/ported_static/stRandom2/test_random_statetest466.py index d538ce72f00..a92bfb9c2f9 100644 --- a/tests/ported_static/stRandom2/test_random_statetest466.py +++ b/tests/ported_static/stRandom2/test_random_statetest466.py @@ -40,7 +40,6 @@ def test_random_statetest466( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw diff --git a/tests/ported_static/stRandom2/test_random_statetest470.py b/tests/ported_static/stRandom2/test_random_statetest470.py index 61e748904b7..f0dfe17c0c7 100644 --- a/tests/ported_static/stRandom2/test_random_statetest470.py +++ b/tests/ported_static/stRandom2/test_random_statetest470.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest470Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest470( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest470.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest470( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -86,7 +95,7 @@ def test_random_statetest470( data=Bytes( "457f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000001357f00000000000000000000000000000000000000000000000000000000000000000b" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x67C37947, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest471.py b/tests/ported_static/stRandom2/test_random_statetest471.py index ee806426281..abd2d844583 100644 --- a/tests/ported_static/stRandom2/test_random_statetest471.py +++ b/tests/ported_static/stRandom2/test_random_statetest471.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest471Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest471( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest471.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest471( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -79,7 +88,7 @@ def test_random_statetest471( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000100000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09650618701355040655183a51377d82" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x63180FB7, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest473.py b/tests/ported_static/stRandom2/test_random_statetest473.py index 77696035ad1..a6192bdd920 100644 --- a/tests/ported_static/stRandom2/test_random_statetest473.py +++ b/tests/ported_static/stRandom2/test_random_statetest473.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest473Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +18,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +33,14 @@ def test_random_statetest473( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest473.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x945304EB96065B2A98B57A48A06AE28D285A71B5) contract_0 = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -41,7 +51,6 @@ def test_random_statetest473( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw @@ -91,7 +100,7 @@ def test_random_statetest473( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff317f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b5910209" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x4467CA41, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest474.py b/tests/ported_static/stRandom2/test_random_statetest474.py index 897f4e4170c..fbbeb0f9f06 100644 --- a/tests/ported_static/stRandom2/test_random_statetest474.py +++ b/tests/ported_static/stRandom2/test_random_statetest474.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest474Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest474( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest474.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest474( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -88,7 +97,7 @@ def test_random_statetest474( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe027f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6f7d6f6b1051778ea1670387810b5805" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x7B1ABEED, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest475.py b/tests/ported_static/stRandom2/test_random_statetest475.py index 2cdd0f7836b..65aa0f5f8d2 100644 --- a/tests/ported_static/stRandom2/test_random_statetest475.py +++ b/tests/ported_static/stRandom2/test_random_statetest475.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest475Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +18,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +33,14 @@ def test_random_statetest475( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest475.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -40,7 +50,6 @@ def test_random_statetest475( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw @@ -85,7 +94,7 @@ def test_random_statetest475( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff09" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x19883C24, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest477.py b/tests/ported_static/stRandom2/test_random_statetest477.py index 17c6da2359e..868fa22f1de 100644 --- a/tests/ported_static/stRandom2/test_random_statetest477.py +++ b/tests/ported_static/stRandom2/test_random_statetest477.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest477Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest477( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest477.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest477( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -90,7 +99,7 @@ def test_random_statetest477( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f0000000000000000000000000000000000000000000000000000000000000001417ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe6f9084a3758d3456763aa4f09c8b735b" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0xA9AAD5, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest480.py b/tests/ported_static/stRandom2/test_random_statetest480.py index c9504c8e817..63098954ae9 100644 --- a/tests/ported_static/stRandom2/test_random_statetest480.py +++ b/tests/ported_static/stRandom2/test_random_statetest480.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest480Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest480( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest480.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest480( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -91,7 +100,7 @@ def test_random_statetest480( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff037f0000000000000000000000000000000000000000000000000000000000000000427ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe6f5af3a474ff64f3a37d51f36a6a607f" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x2C6942FB, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest482.py b/tests/ported_static/stRandom2/test_random_statetest482.py index 7f0b462f558..2c6553c50be 100644 --- a/tests/ported_static/stRandom2/test_random_statetest482.py +++ b/tests/ported_static/stRandom2/test_random_statetest482.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest482Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest482( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest482.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest482( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -93,7 +102,7 @@ def test_random_statetest482( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff6f027c9d313d9b09376505927c8e7156" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x636F84BF, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest483.py b/tests/ported_static/stRandom2/test_random_statetest483.py index e337fc6a653..2a810280520 100644 --- a/tests/ported_static/stRandom2/test_random_statetest483.py +++ b/tests/ported_static/stRandom2/test_random_statetest483.py @@ -3,6 +3,11 @@ Ported from: state_tests/stRandom2/randomStatetest483Filler.json + +@manually-enhanced: Do not overwrite. `gas_limit` raised on Amsterdam +to cover EIP-8037 state-gas spill. Pre-EIP-8037 keeps the original +100 000. + """ import pytest @@ -15,6 +20,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +35,14 @@ def test_random_statetest483( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest483.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -40,7 +52,6 @@ def test_random_statetest483( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw @@ -82,7 +93,7 @@ def test_random_statetest483( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe8409" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x7EEDCE16, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest487.py b/tests/ported_static/stRandom2/test_random_statetest487.py index 5e3e4ff58ee..2a5df8ca96b 100644 --- a/tests/ported_static/stRandom2/test_random_statetest487.py +++ b/tests/ported_static/stRandom2/test_random_statetest487.py @@ -13,9 +13,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +31,7 @@ @pytest.mark.pre_alloc_mutable def test_random_statetest487( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_random_statetest487.""" @@ -43,7 +46,6 @@ def test_random_statetest487( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -79,7 +81,7 @@ def test_random_statetest487( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe337fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0308ff9f708d1710086a73a0766a6b" # noqa: E501 ), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=0x32216D83, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest488.py b/tests/ported_static/stRandom2/test_random_statetest488.py index e0a3e2346b5..4037e572732 100644 --- a/tests/ported_static/stRandom2/test_random_statetest488.py +++ b/tests/ported_static/stRandom2/test_random_statetest488.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest488Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest488( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest488.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest488( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -89,7 +98,7 @@ def test_random_statetest488( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79427f00000000000000000000000000000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe6f3250648093577f6364a218f0907e7d" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x53844097, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest489.py b/tests/ported_static/stRandom2/test_random_statetest489.py index 2371883ee4f..998c3c5301e 100644 --- a/tests/ported_static/stRandom2/test_random_statetest489.py +++ b/tests/ported_static/stRandom2/test_random_statetest489.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest489Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest489( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest489.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest489( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -86,7 +95,7 @@ def test_random_statetest489( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000000000000000000000000000000000000000000000456f2b8e846b91987417705a126e770764" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x6EA1DC52, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest491.py b/tests/ported_static/stRandom2/test_random_statetest491.py index 13bcc9e74bb..5bdc6ad3a55 100644 --- a/tests/ported_static/stRandom2/test_random_statetest491.py +++ b/tests/ported_static/stRandom2/test_random_statetest491.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest491Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest491( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest491.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest491( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -92,7 +101,7 @@ def test_random_statetest491( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000006fa0f670645a778c71127d3b5598308b17" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x6BA27C22, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest493.py b/tests/ported_static/stRandom2/test_random_statetest493.py index 84bd05560e1..fbc037008c4 100644 --- a/tests/ported_static/stRandom2/test_random_statetest493.py +++ b/tests/ported_static/stRandom2/test_random_statetest493.py @@ -13,9 +13,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +31,7 @@ @pytest.mark.pre_alloc_mutable def test_random_statetest493( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_random_statetest493.""" @@ -43,7 +46,6 @@ def test_random_statetest493( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -90,7 +92,7 @@ def test_random_statetest493( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79437f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff09" # noqa: E501 ), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=0x54D0F339, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest495.py b/tests/ported_static/stRandom2/test_random_statetest495.py index 9813c87ffcf..761559f2f5e 100644 --- a/tests/ported_static/stRandom2/test_random_statetest495.py +++ b/tests/ported_static/stRandom2/test_random_statetest495.py @@ -13,9 +13,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +31,7 @@ @pytest.mark.pre_alloc_mutable def test_random_statetest495( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_random_statetest495.""" @@ -43,7 +46,6 @@ def test_random_statetest495( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -79,7 +81,7 @@ def test_random_statetest495( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000100000000000000000000000000000000000000006f427ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7e6410f26f519c538ea2070a6c" # noqa: E501 ), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=0xCA044EE, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest497.py b/tests/ported_static/stRandom2/test_random_statetest497.py index 74327c70b2d..550bf2442fa 100644 --- a/tests/ported_static/stRandom2/test_random_statetest497.py +++ b/tests/ported_static/stRandom2/test_random_statetest497.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest497Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +18,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +33,14 @@ def test_random_statetest497( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest497.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -40,7 +50,6 @@ def test_random_statetest497( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw @@ -84,7 +93,7 @@ def test_random_statetest497( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0904" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x44240571, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest500.py b/tests/ported_static/stRandom2/test_random_statetest500.py index 6033b4afb37..f39cc52497e 100644 --- a/tests/ported_static/stRandom2/test_random_statetest500.py +++ b/tests/ported_static/stRandom2/test_random_statetest500.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest500Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest500( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest500.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest500( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -89,7 +98,7 @@ def test_random_statetest500( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6f87196584968a97046c679199311482" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x20D454F, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest501.py b/tests/ported_static/stRandom2/test_random_statetest501.py index a33fb39f1db..b194f239725 100644 --- a/tests/ported_static/stRandom2/test_random_statetest501.py +++ b/tests/ported_static/stRandom2/test_random_statetest501.py @@ -13,9 +13,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +31,7 @@ @pytest.mark.pre_alloc_mutable def test_random_statetest501( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_random_statetest501.""" @@ -43,7 +46,6 @@ def test_random_statetest501( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -96,7 +98,7 @@ def test_random_statetest501( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff0955" # noqa: E501 ), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=0x956B194, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest502.py b/tests/ported_static/stRandom2/test_random_statetest502.py index 9e1ab6746df..e22b5a3ee78 100644 --- a/tests/ported_static/stRandom2/test_random_statetest502.py +++ b/tests/ported_static/stRandom2/test_random_statetest502.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest502Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest502( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest502.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x945304EB96065B2A98B57A48A06AE28D285A71B5) contract_0 = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87) sender = EOA( @@ -44,7 +54,6 @@ def test_random_statetest502( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -89,7 +98,7 @@ def test_random_statetest502( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c350807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b57e7f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b59c66369a85a46da1821861586378" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x14960C58, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest503.py b/tests/ported_static/stRandom2/test_random_statetest503.py index 5a92ce9d5c8..3925ac7107b 100644 --- a/tests/ported_static/stRandom2/test_random_statetest503.py +++ b/tests/ported_static/stRandom2/test_random_statetest503.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest503Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest503( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest503.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest503( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -87,7 +96,7 @@ def test_random_statetest503( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000006f0886a83c66553c9889528d8f1294ff" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x7B7801AA, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest505.py b/tests/ported_static/stRandom2/test_random_statetest505.py index 7b8c09a831b..8d1dc12357e 100644 --- a/tests/ported_static/stRandom2/test_random_statetest505.py +++ b/tests/ported_static/stRandom2/test_random_statetest505.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest505Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest505( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest505.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest505( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -91,7 +100,7 @@ def test_random_statetest505( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe427f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe457f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000006f44a06f550371317376738c53998437" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x4013B563, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest506.py b/tests/ported_static/stRandom2/test_random_statetest506.py index f80b3b175e9..a574ead20f1 100644 --- a/tests/ported_static/stRandom2/test_random_statetest506.py +++ b/tests/ported_static/stRandom2/test_random_statetest506.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest506Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest506( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest506.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest506( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -90,7 +99,7 @@ def test_random_statetest506( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000000000000000000000000000000000000000000042377f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000006ba218f370862059149e3cff20" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x3879DAC6, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest511.py b/tests/ported_static/stRandom2/test_random_statetest511.py index 41a7c01c3d0..6bee58df4be 100644 --- a/tests/ported_static/stRandom2/test_random_statetest511.py +++ b/tests/ported_static/stRandom2/test_random_statetest511.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest511Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest511( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest511.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest511( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -88,7 +97,7 @@ def test_random_statetest511( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff416f6a52027f41f267453843630a66444145" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x1B89A723, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest512.py b/tests/ported_static/stRandom2/test_random_statetest512.py index f870798e489..f161e8e69c1 100644 --- a/tests/ported_static/stRandom2/test_random_statetest512.py +++ b/tests/ported_static/stRandom2/test_random_statetest512.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest512Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest512( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest512.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest512( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -91,7 +100,7 @@ def test_random_statetest512( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c350437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6f3b5bff405670977499515002634492" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x33F0AE08, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest514.py b/tests/ported_static/stRandom2/test_random_statetest514.py index dd6bb60d822..6caf5d66571 100644 --- a/tests/ported_static/stRandom2/test_random_statetest514.py +++ b/tests/ported_static/stRandom2/test_random_statetest514.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest514Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest514( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest514.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest514( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -90,7 +99,7 @@ def test_random_statetest514( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe44447f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3506c8ea356796d65546d3883768f" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x105D80AD, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest516.py b/tests/ported_static/stRandom2/test_random_statetest516.py index 12d4b1a5f6f..243141c25dd 100644 --- a/tests/ported_static/stRandom2/test_random_statetest516.py +++ b/tests/ported_static/stRandom2/test_random_statetest516.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest516Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest516( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest516.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest516( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -96,7 +105,7 @@ def test_random_statetest516( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff6a32787358019b391868619409" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x2C787EA, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest517.py b/tests/ported_static/stRandom2/test_random_statetest517.py index b0fb58a9a2e..99f3af5c020 100644 --- a/tests/ported_static/stRandom2/test_random_statetest517.py +++ b/tests/ported_static/stRandom2/test_random_statetest517.py @@ -13,9 +13,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +31,7 @@ @pytest.mark.pre_alloc_mutable def test_random_statetest517( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_random_statetest517.""" @@ -43,7 +46,6 @@ def test_random_statetest517( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -89,7 +91,7 @@ def test_random_statetest517( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff3a6f450831a46a867f32569596f0099f7b8c91" # noqa: E501 ), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=0x7291AA4F, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest518.py b/tests/ported_static/stRandom2/test_random_statetest518.py index 45a3b2ba671..0ff6bc1bcef 100644 --- a/tests/ported_static/stRandom2/test_random_statetest518.py +++ b/tests/ported_static/stRandom2/test_random_statetest518.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest518Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest518( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest518.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest518( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -89,7 +98,7 @@ def test_random_statetest518( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff6f3c589f416d947a5134f268515b6c92" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x415CB1C9, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest519.py b/tests/ported_static/stRandom2/test_random_statetest519.py index 6d76d819710..dd414e2d50d 100644 --- a/tests/ported_static/stRandom2/test_random_statetest519.py +++ b/tests/ported_static/stRandom2/test_random_statetest519.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest519Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest519( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest519.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest519( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -79,7 +88,7 @@ def test_random_statetest519( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000001000000000000000000000000000000000000000009457f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3501a02556b85a45311" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0xEA81BBF, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest520.py b/tests/ported_static/stRandom2/test_random_statetest520.py index c51999e59c8..ca90ac86420 100644 --- a/tests/ported_static/stRandom2/test_random_statetest520.py +++ b/tests/ported_static/stRandom2/test_random_statetest520.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest520Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest520( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest520.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest520( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -97,7 +106,7 @@ def test_random_statetest520( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff190308" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x13D9C7A3, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest521.py b/tests/ported_static/stRandom2/test_random_statetest521.py index c361b820dab..fcb311fa735 100644 --- a/tests/ported_static/stRandom2/test_random_statetest521.py +++ b/tests/ported_static/stRandom2/test_random_statetest521.py @@ -13,9 +13,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +31,7 @@ @pytest.mark.pre_alloc_mutable def test_random_statetest521( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_random_statetest521.""" @@ -43,7 +46,6 @@ def test_random_statetest521( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -92,7 +94,7 @@ def test_random_statetest521( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe6a73905597946a57769a6d920933" # noqa: E501 ), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=0x7CE8D3E3, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest526.py b/tests/ported_static/stRandom2/test_random_statetest526.py index 783e1d35401..33bf58469b1 100644 --- a/tests/ported_static/stRandom2/test_random_statetest526.py +++ b/tests/ported_static/stRandom2/test_random_statetest526.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest526Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest526( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest526.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x945304EB96065B2A98B57A48A06AE28D285A71B5) contract_0 = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87) sender = EOA( @@ -44,7 +54,6 @@ def test_random_statetest526( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -85,7 +94,7 @@ def test_random_statetest526( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b5417e7f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b5419e01950777810975058c746f" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x3AA8C462, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest532.py b/tests/ported_static/stRandom2/test_random_statetest532.py index a8750d93096..acfe4835fc5 100644 --- a/tests/ported_static/stRandom2/test_random_statetest532.py +++ b/tests/ported_static/stRandom2/test_random_statetest532.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest532Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest532( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest532.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest532( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -92,7 +101,7 @@ def test_random_statetest532( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe54447f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6f78297ba08ba478507f413b3597109c" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x43E5A248, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest533.py b/tests/ported_static/stRandom2/test_random_statetest533.py index 1661aa8af34..bd66b30f0b1 100644 --- a/tests/ported_static/stRandom2/test_random_statetest533.py +++ b/tests/ported_static/stRandom2/test_random_statetest533.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest533Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +18,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +33,14 @@ def test_random_statetest533( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest533.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -40,7 +50,6 @@ def test_random_statetest533( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw @@ -83,7 +92,7 @@ def test_random_statetest533( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000001847f00000000000000000000000100000000000000000000000000000000000000003a076152" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x70D690F4, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest534.py b/tests/ported_static/stRandom2/test_random_statetest534.py index e68f56aceb9..57e891e623b 100644 --- a/tests/ported_static/stRandom2/test_random_statetest534.py +++ b/tests/ported_static/stRandom2/test_random_statetest534.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest534Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest534( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest534.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest534( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -89,7 +98,7 @@ def test_random_statetest534( data=Bytes( "7f000000000000000000000001000000000000000000000000000000000000000045437f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff457f0000000000000000000000000000000000000000000000000000000000000000436ff3075243846d88747b6a9e7ff28c61" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x55DB76C1, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest535.py b/tests/ported_static/stRandom2/test_random_statetest535.py index 7c769fc3896..8f31eef37bf 100644 --- a/tests/ported_static/stRandom2/test_random_statetest535.py +++ b/tests/ported_static/stRandom2/test_random_statetest535.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest535Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest535( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest535.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest535( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -87,7 +96,7 @@ def test_random_statetest535( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000017f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000100000000000000000000000000000000000000007f0000000000000000000000000000000000000000000000000000000000000000" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x4CD4DC30, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest537.py b/tests/ported_static/stRandom2/test_random_statetest537.py index 416ba260478..5d544116d7f 100644 --- a/tests/ported_static/stRandom2/test_random_statetest537.py +++ b/tests/ported_static/stRandom2/test_random_statetest537.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest537Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest537( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest537.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x945304EB96065B2A98B57A48A06AE28D285A71B5) contract_0 = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87) sender = EOA( @@ -44,7 +54,6 @@ def test_random_statetest537( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -88,7 +97,7 @@ def test_random_statetest537( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7e7f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b5688068515a6a996a540a03686d6d" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x71E432D1, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest539.py b/tests/ported_static/stRandom2/test_random_statetest539.py index c6711af7f16..3dc5e4915a2 100644 --- a/tests/ported_static/stRandom2/test_random_statetest539.py +++ b/tests/ported_static/stRandom2/test_random_statetest539.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest539Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest539( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest539.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest539( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -89,7 +98,7 @@ def test_random_statetest539( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff457f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff096794200bf18b0b316e41" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x55285B09, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest541.py b/tests/ported_static/stRandom2/test_random_statetest541.py index 5e168521b8b..bc1abe1aaa3 100644 --- a/tests/ported_static/stRandom2/test_random_statetest541.py +++ b/tests/ported_static/stRandom2/test_random_statetest541.py @@ -3,6 +3,11 @@ Ported from: state_tests/stRandom2/randomStatetest541Filler.json + +@manually-enhanced: Do not overwrite. `gas_limit` raised on Amsterdam +to cover EIP-8037 state-gas spill. Pre-EIP-8037 keeps the original +100 000. + """ import pytest @@ -15,6 +20,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +35,14 @@ def test_random_statetest541( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest541.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -40,7 +52,6 @@ def test_random_statetest541( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw @@ -87,7 +98,7 @@ def test_random_statetest541( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff457f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000004335696e089257368d07897d57350b10" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x1F529315, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest542.py b/tests/ported_static/stRandom2/test_random_statetest542.py index 1d68c75d6ca..94b615e1547 100644 --- a/tests/ported_static/stRandom2/test_random_statetest542.py +++ b/tests/ported_static/stRandom2/test_random_statetest542.py @@ -13,9 +13,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +31,7 @@ @pytest.mark.pre_alloc_mutable def test_random_statetest542( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_random_statetest542.""" @@ -43,7 +46,6 @@ def test_random_statetest542( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -91,7 +93,7 @@ def test_random_statetest542( data=Bytes( "427f00000000000000000000000100000000000000000000000000000000000000007f0000000000000000000000000000000000000000000000000000000000000000397f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000100000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e7992" # noqa: E501 ), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=0x7DDACBDF, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest544.py b/tests/ported_static/stRandom2/test_random_statetest544.py index 028aaf69bea..77632478d50 100644 --- a/tests/ported_static/stRandom2/test_random_statetest544.py +++ b/tests/ported_static/stRandom2/test_random_statetest544.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest544Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest544( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest544.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest544( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -85,7 +94,7 @@ def test_random_statetest544( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3503b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff097f0000000000000000000000000000000000000000000000000000000000000000" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x505C017E, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest545.py b/tests/ported_static/stRandom2/test_random_statetest545.py index f54ba8d7035..7b95acefeb8 100644 --- a/tests/ported_static/stRandom2/test_random_statetest545.py +++ b/tests/ported_static/stRandom2/test_random_statetest545.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest545Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest545( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest545.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x945304EB96065B2A98B57A48A06AE28D285A71B5) contract_0 = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87) sender = EOA( @@ -44,7 +54,6 @@ def test_random_statetest545( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -91,7 +100,7 @@ def test_random_statetest545( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c350637c9c82133005" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x13226624, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest546.py b/tests/ported_static/stRandom2/test_random_statetest546.py index f4e83ed8904..8f975634bdf 100644 --- a/tests/ported_static/stRandom2/test_random_statetest546.py +++ b/tests/ported_static/stRandom2/test_random_statetest546.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest546Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest546( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest546.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest546( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -87,7 +96,7 @@ def test_random_statetest546( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff447f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f0000000000000000000000010000000000000000000000000000000000000000956f895258826c35576592208671731501" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x6B15392F, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest548.py b/tests/ported_static/stRandom2/test_random_statetest548.py index 3b095a329e6..8523f0f0278 100644 --- a/tests/ported_static/stRandom2/test_random_statetest548.py +++ b/tests/ported_static/stRandom2/test_random_statetest548.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest548Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest548( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest548.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest548( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -90,7 +99,7 @@ def test_random_statetest548( data=Bytes( "7f0000000000000000000000010000000000000000000000000000000000000000417fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000001000000000000000000000000000000000000000019417f00000000000000000000000000000000000000000000000000000000000000017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6f777a349a646633977da01a315a3c03" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x2AA46F82, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest550.py b/tests/ported_static/stRandom2/test_random_statetest550.py index f43654e5852..d4fd817ef9b 100644 --- a/tests/ported_static/stRandom2/test_random_statetest550.py +++ b/tests/ported_static/stRandom2/test_random_statetest550.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest550Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest550( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest550.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest550( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -92,7 +101,7 @@ def test_random_statetest550( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000006f4472a17829659c94a29041419564313a" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x52EBEDC8, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest552.py b/tests/ported_static/stRandom2/test_random_statetest552.py index 3ac771d3ecb..bcf8c8f4b85 100644 --- a/tests/ported_static/stRandom2/test_random_statetest552.py +++ b/tests/ported_static/stRandom2/test_random_statetest552.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest552Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest552( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest552.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest552( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -90,7 +99,7 @@ def test_random_statetest552( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff42147ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe547f00000000000000000000000000000000000000000000000000000000000000006f6a72a37b5219f089416d4336a08e82" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x6BD9B58C, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest553.py b/tests/ported_static/stRandom2/test_random_statetest553.py index 94406603337..b3942959256 100644 --- a/tests/ported_static/stRandom2/test_random_statetest553.py +++ b/tests/ported_static/stRandom2/test_random_statetest553.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest553Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest553( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest553.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest553( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -87,7 +96,7 @@ def test_random_statetest553( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000016f94819c780585376da073368c45828ca0" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x22FB6, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest555.py b/tests/ported_static/stRandom2/test_random_statetest555.py index 64260d4b894..e3a668857b2 100644 --- a/tests/ported_static/stRandom2/test_random_statetest555.py +++ b/tests/ported_static/stRandom2/test_random_statetest555.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest555Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest555( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest555.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest555( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -88,7 +97,7 @@ def test_random_statetest555( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000000000000000000000000000000000000000c3507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe437f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff437f000000000000000000000000000000000000000000000000000000000000c3506f3b8f936e6f3874603c59120707e3588c" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x719DE78, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest556.py b/tests/ported_static/stRandom2/test_random_statetest556.py index 52be5dae51c..a2778f086f9 100644 --- a/tests/ported_static/stRandom2/test_random_statetest556.py +++ b/tests/ported_static/stRandom2/test_random_statetest556.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest556Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest556( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest556.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest556( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -89,7 +98,7 @@ def test_random_statetest556( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000010000000000000000000000000000000000000000437f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000006f726e757692a2ad96526b9e8b77a33a" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x44F0B58C, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest559.py b/tests/ported_static/stRandom2/test_random_statetest559.py index 3b3ae3ead1f..43674d80109 100644 --- a/tests/ported_static/stRandom2/test_random_statetest559.py +++ b/tests/ported_static/stRandom2/test_random_statetest559.py @@ -13,9 +13,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +31,7 @@ @pytest.mark.pre_alloc_mutable def test_random_statetest559( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_random_statetest559.""" @@ -43,7 +46,6 @@ def test_random_statetest559( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -94,7 +96,7 @@ def test_random_statetest559( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000008509ff15" # noqa: E501 ), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=0x12A2A10C, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest564.py b/tests/ported_static/stRandom2/test_random_statetest564.py index 039a19f683f..22f3acce632 100644 --- a/tests/ported_static/stRandom2/test_random_statetest564.py +++ b/tests/ported_static/stRandom2/test_random_statetest564.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest564Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest564( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest564.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x945304EB96065B2A98B57A48A06AE28D285A71B5) contract_0 = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87) sender = EOA( @@ -44,7 +54,6 @@ def test_random_statetest564( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -90,7 +99,7 @@ def test_random_statetest564( data=Bytes( "5b7f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b57fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe45500816" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x11182998, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest565.py b/tests/ported_static/stRandom2/test_random_statetest565.py index 322caed6750..33a1df3daea 100644 --- a/tests/ported_static/stRandom2/test_random_statetest565.py +++ b/tests/ported_static/stRandom2/test_random_statetest565.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest565Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest565( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest565.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest565( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -88,7 +97,7 @@ def test_random_statetest565( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b57f000000000000000000000000000000000000000000000000000000000000c350137f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000009237" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x28CD0966, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest571.py b/tests/ported_static/stRandom2/test_random_statetest571.py index 92a3b7df336..38072ed934a 100644 --- a/tests/ported_static/stRandom2/test_random_statetest571.py +++ b/tests/ported_static/stRandom2/test_random_statetest571.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest571Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +18,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +33,14 @@ def test_random_statetest571( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest571.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -40,7 +50,6 @@ def test_random_statetest571( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw @@ -84,7 +93,7 @@ def test_random_statetest571( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000015b7f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e793c6508766c8b6b403a" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x53934784, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest574.py b/tests/ported_static/stRandom2/test_random_statetest574.py index 941ad48d67e..a8f165453a5 100644 --- a/tests/ported_static/stRandom2/test_random_statetest574.py +++ b/tests/ported_static/stRandom2/test_random_statetest574.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest574Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest574( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest574.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest574( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -89,7 +98,7 @@ def test_random_statetest574( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507f0000000000000000000000000000000000000000000000000000000000000001047f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff046d369354827d7433a335af" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x5F16646E, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest577.py b/tests/ported_static/stRandom2/test_random_statetest577.py index 6196d40cf1d..69813dc38e9 100644 --- a/tests/ported_static/stRandom2/test_random_statetest577.py +++ b/tests/ported_static/stRandom2/test_random_statetest577.py @@ -40,7 +40,6 @@ def test_random_statetest577( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw diff --git a/tests/ported_static/stRandom2/test_random_statetest578.py b/tests/ported_static/stRandom2/test_random_statetest578.py index 5cf34b9d666..01a23742ccc 100644 --- a/tests/ported_static/stRandom2/test_random_statetest578.py +++ b/tests/ported_static/stRandom2/test_random_statetest578.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest578Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest578( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest578.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest578( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -88,7 +97,7 @@ def test_random_statetest578( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c350457f00000000000000000000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff42" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x17C973D5, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest580.py b/tests/ported_static/stRandom2/test_random_statetest580.py index 48f1d7352d7..159a1cd03f1 100644 --- a/tests/ported_static/stRandom2/test_random_statetest580.py +++ b/tests/ported_static/stRandom2/test_random_statetest580.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest580Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest580( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest580.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest580( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -89,7 +98,7 @@ def test_random_statetest580( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000000000000000000000000000000000000000000000457f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000006f4640879d18777b953a209836379a30" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x2C360421, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest581.py b/tests/ported_static/stRandom2/test_random_statetest581.py index 62ed09442c4..bbbc262899d 100644 --- a/tests/ported_static/stRandom2/test_random_statetest581.py +++ b/tests/ported_static/stRandom2/test_random_statetest581.py @@ -13,9 +13,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +31,7 @@ @pytest.mark.pre_alloc_mutable def test_random_statetest581( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_random_statetest581.""" @@ -43,7 +46,6 @@ def test_random_statetest581( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -79,7 +81,7 @@ def test_random_statetest581( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017f0000000000000000000000000000000000000000000000000000000000000001037f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff44920907ff7e7d7012" # noqa: E501 ), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=0x5D315A13, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest584.py b/tests/ported_static/stRandom2/test_random_statetest584.py index 0d4ed6bebc1..8abd2379c91 100644 --- a/tests/ported_static/stRandom2/test_random_statetest584.py +++ b/tests/ported_static/stRandom2/test_random_statetest584.py @@ -13,9 +13,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +31,7 @@ @pytest.mark.pre_alloc_mutable def test_random_statetest584( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_random_statetest584.""" @@ -43,7 +46,6 @@ def test_random_statetest584( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -94,7 +96,7 @@ def test_random_statetest584( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000000000000000000000000000000000000000c3507f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe9692190933" # noqa: E501 ), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=0x15058F0D, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest585.py b/tests/ported_static/stRandom2/test_random_statetest585.py index e103f51782c..b6b61656f32 100644 --- a/tests/ported_static/stRandom2/test_random_statetest585.py +++ b/tests/ported_static/stRandom2/test_random_statetest585.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest585Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest585( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest585.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest585( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -90,7 +99,7 @@ def test_random_statetest585( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x16B2537A, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest586.py b/tests/ported_static/stRandom2/test_random_statetest586.py index 6dfec8e8411..c2c81827ba2 100644 --- a/tests/ported_static/stRandom2/test_random_statetest586.py +++ b/tests/ported_static/stRandom2/test_random_statetest586.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest586Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +18,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +33,14 @@ def test_random_statetest586( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest586.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -40,7 +50,6 @@ def test_random_statetest586( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw @@ -83,7 +92,7 @@ def test_random_statetest586( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000000000000000000000000000000000000000000137" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x65DC324C, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest587.py b/tests/ported_static/stRandom2/test_random_statetest587.py index 037dc6d5095..6f9aa5a84cd 100644 --- a/tests/ported_static/stRandom2/test_random_statetest587.py +++ b/tests/ported_static/stRandom2/test_random_statetest587.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest587Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest587( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest587.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest587( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -87,7 +96,7 @@ def test_random_statetest587( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c350117f0000000000000000000000000000000000000000000000000000000000000001457f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e796f8b7152a3958a923c1665b27557089a" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x11604410, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest588.py b/tests/ported_static/stRandom2/test_random_statetest588.py index d7d56fc223f..467e1489dfe 100644 --- a/tests/ported_static/stRandom2/test_random_statetest588.py +++ b/tests/ported_static/stRandom2/test_random_statetest588.py @@ -3,6 +3,11 @@ Ported from: state_tests/stRandom2/randomStatetest588Filler.json + +@manually-enhanced: Do not overwrite. `gas_limit` raised on Amsterdam +to cover EIP-8037 state-gas spill. Pre-EIP-8037 keeps the original +100 000. + """ import pytest @@ -15,6 +20,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +35,14 @@ def test_random_statetest588( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest588.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -40,7 +52,6 @@ def test_random_statetest588( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw @@ -87,7 +98,7 @@ def test_random_statetest588( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff41437f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff430637" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x66D6BC77, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest592.py b/tests/ported_static/stRandom2/test_random_statetest592.py index 8fcb0a5fbbf..ef4e670fcdd 100644 --- a/tests/ported_static/stRandom2/test_random_statetest592.py +++ b/tests/ported_static/stRandom2/test_random_statetest592.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest592Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest592( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest592.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest592( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -88,7 +97,7 @@ def test_random_statetest592( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79457fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff09" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x6339E0E5, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest596.py b/tests/ported_static/stRandom2/test_random_statetest596.py index 378a0feb172..9772ffda560 100644 --- a/tests/ported_static/stRandom2/test_random_statetest596.py +++ b/tests/ported_static/stRandom2/test_random_statetest596.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest596Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest596( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest596.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest596( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -85,7 +94,7 @@ def test_random_statetest596( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000001317f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000006f7066a3507f6e090653945638306520" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x2D99F481, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest599.py b/tests/ported_static/stRandom2/test_random_statetest599.py index 4c95373662d..9e8f8f0973c 100644 --- a/tests/ported_static/stRandom2/test_random_statetest599.py +++ b/tests/ported_static/stRandom2/test_random_statetest599.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest599Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest599( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest599.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest599( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -93,7 +102,7 @@ def test_random_statetest599( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6f8d6c60440a44449372068a976a8382" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x421144B6, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest600.py b/tests/ported_static/stRandom2/test_random_statetest600.py index a1aaa216c67..1386a39da07 100644 --- a/tests/ported_static/stRandom2/test_random_statetest600.py +++ b/tests/ported_static/stRandom2/test_random_statetest600.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest600Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest600( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest600.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest600( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -89,7 +98,7 @@ def test_random_statetest600( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000000000000000000000000000000000000000c35043457ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe6f0b6f37208e76a402927039198c969907" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0xAD3F19C, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest602.py b/tests/ported_static/stRandom2/test_random_statetest602.py index 79e37586ce8..9d569c1d26b 100644 --- a/tests/ported_static/stRandom2/test_random_statetest602.py +++ b/tests/ported_static/stRandom2/test_random_statetest602.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest602Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest602( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest602.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest602( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -86,7 +95,7 @@ def test_random_statetest602( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x25D01724, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest603.py b/tests/ported_static/stRandom2/test_random_statetest603.py index cac980e3f24..6f5517dc6a3 100644 --- a/tests/ported_static/stRandom2/test_random_statetest603.py +++ b/tests/ported_static/stRandom2/test_random_statetest603.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest603Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest603( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest603.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest603( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -88,7 +97,7 @@ def test_random_statetest603( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79427f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000100000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79086f655860560745326476a03cdc360634" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x23FCF7F2, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest605.py b/tests/ported_static/stRandom2/test_random_statetest605.py index 49d73144b9e..6dc04cf8552 100644 --- a/tests/ported_static/stRandom2/test_random_statetest605.py +++ b/tests/ported_static/stRandom2/test_random_statetest605.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest605Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest605( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest605.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest605( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -91,7 +100,7 @@ def test_random_statetest605( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000000000000000000000000000000000000000c350437f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9058038508" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x650044FA, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest607.py b/tests/ported_static/stRandom2/test_random_statetest607.py index 25d34d7fb5d..6f6e4f94516 100644 --- a/tests/ported_static/stRandom2/test_random_statetest607.py +++ b/tests/ported_static/stRandom2/test_random_statetest607.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest607Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest607( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest607.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest607( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -86,7 +95,7 @@ def test_random_statetest607( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff09" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x106DF7F8, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest608.py b/tests/ported_static/stRandom2/test_random_statetest608.py index c2e3f263a97..2a9daf5e2cb 100644 --- a/tests/ported_static/stRandom2/test_random_statetest608.py +++ b/tests/ported_static/stRandom2/test_random_statetest608.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest608Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest608( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest608.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest608( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -86,7 +95,7 @@ def test_random_statetest608( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000000000000000000000000000000000000000c350537fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x1EB2352A, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest610.py b/tests/ported_static/stRandom2/test_random_statetest610.py index cf244576c52..449efdeb9c9 100644 --- a/tests/ported_static/stRandom2/test_random_statetest610.py +++ b/tests/ported_static/stRandom2/test_random_statetest610.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest610Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest610( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest610.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest610( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -89,7 +98,7 @@ def test_random_statetest610( data=Bytes( "417f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000000000000000000000000000000000000000c3507f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff6f01f353a2437e4384726497587b8556" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x7FDD9C9C, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest612.py b/tests/ported_static/stRandom2/test_random_statetest612.py index 2bef95ebe43..b16abf8e224 100644 --- a/tests/ported_static/stRandom2/test_random_statetest612.py +++ b/tests/ported_static/stRandom2/test_random_statetest612.py @@ -13,9 +13,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +31,7 @@ @pytest.mark.pre_alloc_mutable def test_random_statetest612( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_random_statetest612.""" @@ -43,7 +46,6 @@ def test_random_statetest612( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -92,7 +94,7 @@ def test_random_statetest612( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff437f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000100000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff09" # noqa: E501 ), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=0x2AFE4542, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest615.py b/tests/ported_static/stRandom2/test_random_statetest615.py index db257da65f6..b3330682e09 100644 --- a/tests/ported_static/stRandom2/test_random_statetest615.py +++ b/tests/ported_static/stRandom2/test_random_statetest615.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest615Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest615( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest615.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest615( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -94,7 +103,7 @@ def test_random_statetest615( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe837f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000001000000000000000000000000000000000000000009556c6f390a3054d7368a9a" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x7BCC296A, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest616.py b/tests/ported_static/stRandom2/test_random_statetest616.py index 7e1333d043e..a5a984ee14d 100644 --- a/tests/ported_static/stRandom2/test_random_statetest616.py +++ b/tests/ported_static/stRandom2/test_random_statetest616.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest616Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest616( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest616.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest616( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -88,7 +97,7 @@ def test_random_statetest616( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000006f86a2409b991539f0423c0342363c3b" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x45949A6F, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest620.py b/tests/ported_static/stRandom2/test_random_statetest620.py index 1cee588f78d..d1120537994 100644 --- a/tests/ported_static/stRandom2/test_random_statetest620.py +++ b/tests/ported_static/stRandom2/test_random_statetest620.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest620Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest620( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest620.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest620( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -91,7 +100,7 @@ def test_random_statetest620( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff427f0000000000000000000000010000000000000000000000000000000000000000457ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6f6c54a420327d73727d9d1a667bf389" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x61F75E26, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest621.py b/tests/ported_static/stRandom2/test_random_statetest621.py index 5e4e914e873..53a4825c2f0 100644 --- a/tests/ported_static/stRandom2/test_random_statetest621.py +++ b/tests/ported_static/stRandom2/test_random_statetest621.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest621Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest621( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest621.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest621( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -83,7 +92,7 @@ def test_random_statetest621( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f0000000000000000000000000000000000000000000000000000000000000000441a7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff6f3ba187a19366899e595220741232905b" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x7FC94217, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest627.py b/tests/ported_static/stRandom2/test_random_statetest627.py index 8d8c8380c38..a614c461541 100644 --- a/tests/ported_static/stRandom2/test_random_statetest627.py +++ b/tests/ported_static/stRandom2/test_random_statetest627.py @@ -40,7 +40,6 @@ def test_random_statetest627( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw diff --git a/tests/ported_static/stRandom2/test_random_statetest628.py b/tests/ported_static/stRandom2/test_random_statetest628.py index c005e0c17c6..5acdfde9ae4 100644 --- a/tests/ported_static/stRandom2/test_random_statetest628.py +++ b/tests/ported_static/stRandom2/test_random_statetest628.py @@ -40,7 +40,6 @@ def test_random_statetest628( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw diff --git a/tests/ported_static/stRandom2/test_random_statetest629.py b/tests/ported_static/stRandom2/test_random_statetest629.py index 93bf1f10e4d..e5fb21ac908 100644 --- a/tests/ported_static/stRandom2/test_random_statetest629.py +++ b/tests/ported_static/stRandom2/test_random_statetest629.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest629Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest629( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest629.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest629( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -91,7 +100,7 @@ def test_random_statetest629( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79347f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79116f427277147c617f4354a35a1a47977a" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x3BCDBA80, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest630.py b/tests/ported_static/stRandom2/test_random_statetest630.py index 6718fbb3a99..232e5045340 100644 --- a/tests/ported_static/stRandom2/test_random_statetest630.py +++ b/tests/ported_static/stRandom2/test_random_statetest630.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest630Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest630( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest630.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest630( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -90,7 +99,7 @@ def test_random_statetest630( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000000000000000000000000000000000000000000001427f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6f9461a46e61507a1206917b17137e7e" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x188B5E42, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest633.py b/tests/ported_static/stRandom2/test_random_statetest633.py index 983500bac3d..200694e5db0 100644 --- a/tests/ported_static/stRandom2/test_random_statetest633.py +++ b/tests/ported_static/stRandom2/test_random_statetest633.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest633Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest633( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest633.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest633( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -93,7 +102,7 @@ def test_random_statetest633( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e796f82941340756317567250f1573a8976" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x50F61B39, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest635.py b/tests/ported_static/stRandom2/test_random_statetest635.py index a7e209f191f..71bfdb4cb03 100644 --- a/tests/ported_static/stRandom2/test_random_statetest635.py +++ b/tests/ported_static/stRandom2/test_random_statetest635.py @@ -13,9 +13,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +31,7 @@ @pytest.mark.pre_alloc_mutable def test_random_statetest635( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_random_statetest635.""" @@ -43,7 +46,6 @@ def test_random_statetest635( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -88,7 +90,7 @@ def test_random_statetest635( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff6a5b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe66f2707d83713b6b8f3208" # noqa: E501 ), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=0x6046B41D, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest637.py b/tests/ported_static/stRandom2/test_random_statetest637.py index 93704fddf5f..d896ccab731 100644 --- a/tests/ported_static/stRandom2/test_random_statetest637.py +++ b/tests/ported_static/stRandom2/test_random_statetest637.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest637Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest637( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest637.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest637( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -88,7 +97,7 @@ def test_random_statetest637( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6f44931064138e9df1768334028c201471" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x58337064, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest638.py b/tests/ported_static/stRandom2/test_random_statetest638.py index 1d1fee7b18a..35ce2c4613a 100644 --- a/tests/ported_static/stRandom2/test_random_statetest638.py +++ b/tests/ported_static/stRandom2/test_random_statetest638.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest638Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest638( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest638.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest638( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -90,7 +99,7 @@ def test_random_statetest638( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff09" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0x791E3396, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest641.py b/tests/ported_static/stRandom2/test_random_statetest641.py index 83cb0c21f18..f81dffeabc8 100644 --- a/tests/ported_static/stRandom2/test_random_statetest641.py +++ b/tests/ported_static/stRandom2/test_random_statetest641.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRandom2/randomStatetest641Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_random_statetest641( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_random_statetest641.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. + tx_gas_limit = 100000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 500_000 + coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -43,7 +53,6 @@ def test_random_statetest641( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -91,7 +100,7 @@ def test_random_statetest641( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe6f29199c9aa4054170f1a15a55056f96" # noqa: E501 ), - gas_limit=100000, + gas_limit=tx_gas_limit, value=0xF08F864, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest643.py b/tests/ported_static/stRandom2/test_random_statetest643.py index 8de4eb0b70c..1188da1382f 100644 --- a/tests/ported_static/stRandom2/test_random_statetest643.py +++ b/tests/ported_static/stRandom2/test_random_statetest643.py @@ -38,7 +38,6 @@ def test_random_statetest643( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=35761922600709271, ) # Source: raw diff --git a/tests/ported_static/stReturnDataTest/test_call_outsize_then_create_successful_then_returndatasize.py b/tests/ported_static/stReturnDataTest/test_call_outsize_then_create_successful_then_returndatasize.py index 51835c7831d..bc902ac9c02 100644 --- a/tests/ported_static/stReturnDataTest/test_call_outsize_then_create_successful_then_returndatasize.py +++ b/tests/ported_static/stReturnDataTest/test_call_outsize_then_create_successful_then_returndatasize.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,6 +32,7 @@ @pytest.mark.pre_alloc_mutable def test_call_outsize_then_create_successful_then_returndatasize( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_call_outsize_then_create_successful_then_returndatasize.""" @@ -42,7 +45,6 @@ def test_call_outsize_then_create_successful_then_returndatasize( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=111669149696, ) # Source: lll @@ -88,7 +90,7 @@ def test_call_outsize_then_create_successful_then_returndatasize( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, ) post = {target: Account(storage={0: 0})} diff --git a/tests/ported_static/stReturnDataTest/test_call_then_create_successful_then_returndatasize.py b/tests/ported_static/stReturnDataTest/test_call_then_create_successful_then_returndatasize.py index d4206eef60c..61fede45e7a 100644 --- a/tests/ported_static/stReturnDataTest/test_call_then_create_successful_then_returndatasize.py +++ b/tests/ported_static/stReturnDataTest/test_call_then_create_successful_then_returndatasize.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,6 +32,7 @@ @pytest.mark.pre_alloc_mutable def test_call_then_create_successful_then_returndatasize( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_call_then_create_successful_then_returndatasize.""" @@ -42,7 +45,6 @@ def test_call_then_create_successful_then_returndatasize( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=111669149696, ) # Source: lll @@ -88,7 +90,7 @@ def test_call_then_create_successful_then_returndatasize( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, ) post = {target: Account(storage={0: 0})} diff --git a/tests/ported_static/stReturnDataTest/test_create_callprecompile_returndatasize.py b/tests/ported_static/stReturnDataTest/test_create_callprecompile_returndatasize.py index b220fda28c6..bf02ecb4a37 100644 --- a/tests/ported_static/stReturnDataTest/test_create_callprecompile_returndatasize.py +++ b/tests/ported_static/stReturnDataTest/test_create_callprecompile_returndatasize.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,6 +32,7 @@ @pytest.mark.pre_alloc_mutable def test_create_callprecompile_returndatasize( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_create_callprecompile_returndatasize.""" @@ -42,7 +45,6 @@ def test_create_callprecompile_returndatasize( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=111669149696, ) # Source: lll @@ -89,7 +91,7 @@ def test_create_callprecompile_returndatasize( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, ) post = {target: Account(storage={0: 0})} diff --git a/tests/ported_static/stReturnDataTest/test_modexp_modsize0_returndatasize.py b/tests/ported_static/stReturnDataTest/test_modexp_modsize0_returndatasize.py index b5f159459d4..28bc4574fc5 100644 --- a/tests/ported_static/stReturnDataTest/test_modexp_modsize0_returndatasize.py +++ b/tests/ported_static/stReturnDataTest/test_modexp_modsize0_returndatasize.py @@ -84,7 +84,6 @@ def test_modexp_modsize0_returndatasize( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=100000000000, ) # Source: lll diff --git a/tests/ported_static/stReturnDataTest/test_returndatacopy_0_0_following_successful_create.py b/tests/ported_static/stReturnDataTest/test_returndatacopy_0_0_following_successful_create.py index c964d6505be..03f89dd4aa3 100644 --- a/tests/ported_static/stReturnDataTest/test_returndatacopy_0_0_following_successful_create.py +++ b/tests/ported_static/stReturnDataTest/test_returndatacopy_0_0_following_successful_create.py @@ -12,10 +12,12 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, compute_create_address, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,6 +33,7 @@ @pytest.mark.pre_alloc_mutable def test_returndatacopy_0_0_following_successful_create( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_returndatacopy_0_0_following_successful_create.""" @@ -44,7 +47,6 @@ def test_returndatacopy_0_0_following_successful_create( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=111669149696, ) # Source: lll @@ -68,7 +70,7 @@ def test_returndatacopy_0_0_following_successful_create( sender=sender, to=contract_0, data=Bytes(""), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, ) post = { diff --git a/tests/ported_static/stReturnDataTest/test_returndatacopy_after_failing_create.py b/tests/ported_static/stReturnDataTest/test_returndatacopy_after_failing_create.py index 027801052a2..d4593b87a95 100644 --- a/tests/ported_static/stReturnDataTest/test_returndatacopy_after_failing_create.py +++ b/tests/ported_static/stReturnDataTest/test_returndatacopy_after_failing_create.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,6 +32,7 @@ @pytest.mark.pre_alloc_mutable def test_returndatacopy_after_failing_create( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Returndatacopy after failing create case due to 0xfd code.""" @@ -42,7 +45,6 @@ def test_returndatacopy_after_failing_create( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=111669149696, ) # Source: lll @@ -62,7 +64,7 @@ def test_returndatacopy_after_failing_create( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, ) post = {target: Account(storage={0: 32, 1: 2})} diff --git a/tests/ported_static/stReturnDataTest/test_returndatacopy_following_revert_in_create.py b/tests/ported_static/stReturnDataTest/test_returndatacopy_following_revert_in_create.py index 868ae13c14e..edf961a5e24 100644 --- a/tests/ported_static/stReturnDataTest/test_returndatacopy_following_revert_in_create.py +++ b/tests/ported_static/stReturnDataTest/test_returndatacopy_following_revert_in_create.py @@ -13,9 +13,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,6 +33,7 @@ @pytest.mark.pre_alloc_mutable def test_returndatacopy_following_revert_in_create( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_returndatacopy_following_revert_in_create.""" @@ -45,7 +48,6 @@ def test_returndatacopy_following_revert_in_create( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=111669149696, ) pre[sender] = Account(balance=0x6400000000) @@ -75,7 +77,7 @@ def test_returndatacopy_following_revert_in_create( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, ) post = { diff --git a/tests/ported_static/stReturnDataTest/test_returndatasize_after_successful_callcode.py b/tests/ported_static/stReturnDataTest/test_returndatasize_after_successful_callcode.py index e862fa26864..3aed1ab7ffa 100644 --- a/tests/ported_static/stReturnDataTest/test_returndatasize_after_successful_callcode.py +++ b/tests/ported_static/stReturnDataTest/test_returndatasize_after_successful_callcode.py @@ -42,7 +42,6 @@ def test_returndatasize_after_successful_callcode( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=111669149696, ) # Source: lll diff --git a/tests/ported_static/stReturnDataTest/test_returndatasize_following_successful_create.py b/tests/ported_static/stReturnDataTest/test_returndatasize_following_successful_create.py index 0fe11d444fb..335fffd6010 100644 --- a/tests/ported_static/stReturnDataTest/test_returndatasize_following_successful_create.py +++ b/tests/ported_static/stReturnDataTest/test_returndatasize_following_successful_create.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,6 +32,7 @@ @pytest.mark.pre_alloc_mutable def test_returndatasize_following_successful_create( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_returndatasize_following_successful_create.""" @@ -42,7 +45,6 @@ def test_returndatasize_following_successful_create( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=111669149696, ) # Source: lll @@ -66,7 +68,7 @@ def test_returndatasize_following_successful_create( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, ) post = {target: Account(storage={0: 0})} diff --git a/tests/ported_static/stReturnDataTest/test_too_long_return_data_copy.py b/tests/ported_static/stReturnDataTest/test_too_long_return_data_copy.py index ee261f186bc..2d9ba21d3a9 100644 --- a/tests/ported_static/stReturnDataTest/test_too_long_return_data_copy.py +++ b/tests/ported_static/stReturnDataTest/test_too_long_return_data_copy.py @@ -198,7 +198,6 @@ def test_too_long_return_data_copy( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=4503599627370496, ) # Source: yul diff --git a/tests/ported_static/stRevertTest/test_revert_depth2.py b/tests/ported_static/stRevertTest/test_revert_depth2.py index 672da622c5c..467a8871f42 100644 --- a/tests/ported_static/stRevertTest/test_revert_depth2.py +++ b/tests/ported_static/stRevertTest/test_revert_depth2.py @@ -62,7 +62,6 @@ def test_revert_depth2( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) # Source: lll diff --git a/tests/ported_static/stRevertTest/test_revert_depth_create_address_collision.py b/tests/ported_static/stRevertTest/test_revert_depth_create_address_collision.py index 21dfadc052b..fe255c65d17 100644 --- a/tests/ported_static/stRevertTest/test_revert_depth_create_address_collision.py +++ b/tests/ported_static/stRevertTest/test_revert_depth_create_address_collision.py @@ -104,7 +104,6 @@ def test_revert_depth_create_address_collision( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) pre[sender] = Account(balance=0xE8D4A51000) diff --git a/tests/ported_static/stRevertTest/test_revert_depth_create_oog.py b/tests/ported_static/stRevertTest/test_revert_depth_create_oog.py index 12dadbe7ce4..8f3fa8594eb 100644 --- a/tests/ported_static/stRevertTest/test_revert_depth_create_oog.py +++ b/tests/ported_static/stRevertTest/test_revert_depth_create_oog.py @@ -104,7 +104,6 @@ def test_revert_depth_create_oog( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) # Source: lll diff --git a/tests/ported_static/stRevertTest/test_revert_in_call_code.py b/tests/ported_static/stRevertTest/test_revert_in_call_code.py index 77e8cdafda8..e2602b9d7e2 100644 --- a/tests/ported_static/stRevertTest/test_revert_in_call_code.py +++ b/tests/ported_static/stRevertTest/test_revert_in_call_code.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -28,6 +30,7 @@ @pytest.mark.pre_alloc_mutable def test_revert_in_call_code( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_revert_in_call_code.""" @@ -40,7 +43,7 @@ def test_revert_in_call_code( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=1000000, + gas_limit=3000000 if fork >= Amsterdam else 1000000, ) # Source: lll @@ -78,7 +81,7 @@ def test_revert_in_call_code( sender=sender, to=target, data=Bytes(""), - gas_limit=105044, + gas_limit=2105044 if fork >= Amsterdam else 105044, ) post = {target: Account(storage={1: 32, 2: 8754})} diff --git a/tests/ported_static/stRevertTest/test_revert_in_create_in_init_paris.py b/tests/ported_static/stRevertTest/test_revert_in_create_in_init_paris.py index e1f2206890f..89789d898b9 100644 --- a/tests/ported_static/stRevertTest/test_revert_in_create_in_init_paris.py +++ b/tests/ported_static/stRevertTest/test_revert_in_create_in_init_paris.py @@ -3,6 +3,10 @@ Ported from: state_tests/stRevertTest/RevertInCreateInInit_ParisFiller.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 NEW_ACCOUNT state-gas spill in nested CREATE; +pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +34,14 @@ def test_revert_in_create_in_init_paris( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_revert_in_create_in_init_paris.""" + # EIP-8037 NEW_ACCOUNT state-gas spill OoGs the nested CREATE. + tx_gas_limit = 200000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 1_000_000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) addr = Address(0x4757608F18B70777AE788DD4056EEED52F7AA68F) sender = EOA( @@ -43,7 +54,6 @@ def test_revert_in_create_in_init_paris( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) pre[addr] = Account(balance=10, storage={0: 1}) @@ -65,7 +75,7 @@ def test_revert_in_create_in_init_paris( + Op.MSTORE(offset=0x0, value=0x112233) + Op.REVERT(offset=0x0, size=0x20) + Op.STOP, - gas_limit=200000, + gas_limit=tx_gas_limit, ) post = {addr: Account(storage={0: 1}, balance=10)} diff --git a/tests/ported_static/stRevertTest/test_revert_in_delegate_call.py b/tests/ported_static/stRevertTest/test_revert_in_delegate_call.py index 85992148486..cea3462169a 100644 --- a/tests/ported_static/stRevertTest/test_revert_in_delegate_call.py +++ b/tests/ported_static/stRevertTest/test_revert_in_delegate_call.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -28,6 +30,7 @@ @pytest.mark.pre_alloc_mutable def test_revert_in_delegate_call( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_revert_in_delegate_call.""" @@ -40,7 +43,7 @@ def test_revert_in_delegate_call( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=1000000, + gas_limit=3000000 if fork >= Amsterdam else 1000000, ) # Source: lll @@ -77,7 +80,7 @@ def test_revert_in_delegate_call( sender=sender, to=target, data=Bytes(""), - gas_limit=105044, + gas_limit=2105044 if fork >= Amsterdam else 105044, ) post = {target: Account(storage={1: 32, 2: 10})} diff --git a/tests/ported_static/stRevertTest/test_revert_opcode_calls.py b/tests/ported_static/stRevertTest/test_revert_opcode_calls.py index 1ac94a6d4f7..1089825aeca 100644 --- a/tests/ported_static/stRevertTest/test_revert_opcode_calls.py +++ b/tests/ported_static/stRevertTest/test_revert_opcode_calls.py @@ -3,6 +3,10 @@ Ported from: state_tests/stRevertTest/RevertOpcodeCallsFiller.json +@manually-enhanced: Do not overwrite. Gas bumped fork-conditionally +to cover EIP-8037 state-gas spill into regular gas; pre-EIP-8037 +behavior unchanged. + """ import pytest @@ -92,6 +96,15 @@ def test_revert_opcode_calls( v: int, ) -> None: """Test_revert_opcode_calls.""" + # EIP-8037 gas bumps: original values for pre-EIP-8037 forks. + inner_call_gas = 50000 + inner_call_gas_2 = 100000 + inner_call_gas_3 = 260000 + if fork.is_eip_enabled(8037): + inner_call_gas = 1000000 + inner_call_gas_2 = 1000000 + inner_call_gas_3 = 1300000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xE8D4A51000) @@ -101,7 +114,6 @@ def test_revert_opcode_calls( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) # Source: lll @@ -110,7 +122,7 @@ def test_revert_opcode_calls( code=Op.SSTORE( key=0xA, value=Op.CALL( - gas=0x3F7A0, + gas=inner_call_gas_3, address=Op.CALLDATALOAD(offset=0x0), value=0x0, args_offset=0x0, @@ -141,7 +153,7 @@ def test_revert_opcode_calls( code=Op.SSTORE( key=0x4, value=Op.CALL( - gas=0xC350, + gas=inner_call_gas, address=0x93A599BDE9A3B6390AFDB06952AA5EC0B8C44F3B, value=0x0, args_offset=0x0, @@ -162,7 +174,7 @@ def test_revert_opcode_calls( code=Op.SSTORE( key=0x0, value=Op.CALL( - gas=0xC350, + gas=inner_call_gas, address=0x93A599BDE9A3B6390AFDB06952AA5EC0B8C44F3B, value=0x0, args_offset=0x0, @@ -183,7 +195,7 @@ def test_revert_opcode_calls( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=0xC350, + gas=inner_call_gas, address=0x93A599BDE9A3B6390AFDB06952AA5EC0B8C44F3B, args_offset=0x0, args_size=0x0, @@ -203,7 +215,7 @@ def test_revert_opcode_calls( code=Op.SSTORE( key=0x0, value=Op.CALLCODE( - gas=0xC350, + gas=inner_call_gas, address=0x93A599BDE9A3B6390AFDB06952AA5EC0B8C44F3B, value=0x0, args_offset=0x0, @@ -224,7 +236,7 @@ def test_revert_opcode_calls( code=Op.SSTORE( key=0x0, value=Op.CALL( - gas=0x186A0, + gas=inner_call_gas_2, address=0x652761B88018EA027F6F27E456FE55C2DC5D6A91, value=0x0, args_offset=0x0, diff --git a/tests/ported_static/stRevertTest/test_revert_opcode_create.py b/tests/ported_static/stRevertTest/test_revert_opcode_create.py index 2672aa489e8..3ac33f19c11 100644 --- a/tests/ported_static/stRevertTest/test_revert_opcode_create.py +++ b/tests/ported_static/stRevertTest/test_revert_opcode_create.py @@ -67,7 +67,6 @@ def test_revert_opcode_create( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) # Source: lll diff --git a/tests/ported_static/stRevertTest/test_revert_opcode_direct_call.py b/tests/ported_static/stRevertTest/test_revert_opcode_direct_call.py index 76b0eb62908..e653efdc770 100644 --- a/tests/ported_static/stRevertTest/test_revert_opcode_direct_call.py +++ b/tests/ported_static/stRevertTest/test_revert_opcode_direct_call.py @@ -65,7 +65,6 @@ def test_revert_opcode_direct_call( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) # Source: lll diff --git a/tests/ported_static/stRevertTest/test_revert_opcode_in_calls_on_non_empty_return_data.py b/tests/ported_static/stRevertTest/test_revert_opcode_in_calls_on_non_empty_return_data.py index 83e34b96b21..6079b5cf452 100644 --- a/tests/ported_static/stRevertTest/test_revert_opcode_in_calls_on_non_empty_return_data.py +++ b/tests/ported_static/stRevertTest/test_revert_opcode_in_calls_on_non_empty_return_data.py @@ -3,6 +3,10 @@ Ported from: state_tests/stRevertTest/RevertOpcodeInCallsOnNonEmptyReturnDataFiller.json +@manually-enhanced: Do not overwrite. Inner-CALL/DELEGATECALL gas +bumped on Amsterdam to cover EIP-8037 state-gas spill into regular gas; +pre-EIP-8037 unchanged. + """ import pytest @@ -110,6 +114,16 @@ def test_revert_opcode_in_calls_on_non_empty_return_data( ) pre[sender] = Account(balance=0xE8D4A51000) + # EIP-8037 inner-CALL/DELEGATECALL gas bumps: original values + # restored for pre-EIP-8037 forks; bumped for state-gas spill on + # Amsterdam. + inner_call_gas = 50000 + deeper_call_gas = 100000 + deepest_call_gas = 260000 + if fork.is_eip_enabled(8037): + inner_call_gas = 100000 + deeper_call_gas = 1000000 + deepest_call_gas = 1000000 # Source: lll # { [[1]] 12 (REVERT 0 1) [[3]] 13 } addr_6 = pre.deploy_contract( # noqa: F841 @@ -148,7 +162,7 @@ def test_revert_opcode_in_calls_on_non_empty_return_data( + Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=0xC350, + gas=inner_call_gas, address=0x93A599BDE9A3B6390AFDB06952AA5EC0B8C44F3B, args_offset=0x0, args_size=0x0, @@ -179,7 +193,7 @@ def test_revert_opcode_in_calls_on_non_empty_return_data( + Op.SSTORE( key=0x0, value=Op.CALLCODE( - gas=0xC350, + gas=inner_call_gas, address=0x93A599BDE9A3B6390AFDB06952AA5EC0B8C44F3B, value=0x0, args_offset=0x0, @@ -211,7 +225,7 @@ def test_revert_opcode_in_calls_on_non_empty_return_data( + Op.SSTORE( key=0x4, value=Op.CALL( - gas=0xC350, + gas=inner_call_gas, address=0x93A599BDE9A3B6390AFDB06952AA5EC0B8C44F3B, value=0x0, args_offset=0x0, @@ -243,7 +257,7 @@ def test_revert_opcode_in_calls_on_non_empty_return_data( + Op.SSTORE( key=0x0, value=Op.CALL( - gas=0xC350, + gas=inner_call_gas, address=0x93A599BDE9A3B6390AFDB06952AA5EC0B8C44F3B, value=0x0, args_offset=0x0, @@ -275,7 +289,7 @@ def test_revert_opcode_in_calls_on_non_empty_return_data( + Op.SSTORE( key=0xA, value=Op.CALL( - gas=0x3F7A0, + gas=deepest_call_gas, address=Op.CALLDATALOAD(offset=0x0), value=0x0, args_offset=0x0, @@ -307,7 +321,7 @@ def test_revert_opcode_in_calls_on_non_empty_return_data( + Op.SSTORE( key=0x0, value=Op.CALL( - gas=0x186A0, + gas=deeper_call_gas, address=0xEA519C47889074E6378B0D83747F2C3EA0B9CBC9, value=0x0, args_offset=0x0, diff --git a/tests/ported_static/stRevertTest/test_revert_opcode_in_create_returns.py b/tests/ported_static/stRevertTest/test_revert_opcode_in_create_returns.py index 8a64f8f6529..e96591bad6f 100644 --- a/tests/ported_static/stRevertTest/test_revert_opcode_in_create_returns.py +++ b/tests/ported_static/stRevertTest/test_revert_opcode_in_create_returns.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -28,6 +30,7 @@ @pytest.mark.pre_alloc_mutable def test_revert_opcode_in_create_returns( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_revert_opcode_in_create_returns.""" @@ -40,7 +43,6 @@ def test_revert_opcode_in_create_returns( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) # Source: lll @@ -64,7 +66,7 @@ def test_revert_opcode_in_create_returns( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, ) post = {target: Account(storage={0: 32})} diff --git a/tests/ported_static/stRevertTest/test_revert_opcode_in_init.py b/tests/ported_static/stRevertTest/test_revert_opcode_in_init.py index c5c7e8af07d..ffd62a548ee 100644 --- a/tests/ported_static/stRevertTest/test_revert_opcode_in_init.py +++ b/tests/ported_static/stRevertTest/test_revert_opcode_in_init.py @@ -3,6 +3,10 @@ Ported from: state_tests/stRevertTest/RevertOpcodeInInitFiller.json + +@manually-enhanced: Do not overwrite. tx gas budget bumped +for EIP-8037 NEW_ACCOUNT state-gas headroom on Amsterdam (post-state +expectations are unchanged on all forks). """ import pytest @@ -69,7 +73,12 @@ def test_revert_opcode_in_init( + Op.REVERT(offset=0x0, size=0x1) + Op.SSTORE(key=0x1, value=0x11), ] - tx_gas = [160000] + # EIP-8037 NEW_ACCOUNT + init-code state-gas spill on Amsterdam; + # pre-EIP-8037 keeps the original 160 000 budget. + outer_tx_gas = 160_000 + if fork.is_eip_enabled(8037): + outer_tx_gas = 800_000 + tx_gas = [outer_tx_gas] tx_value = [0, 10] tx = Transaction( diff --git a/tests/ported_static/stRevertTest/test_revert_opcode_multiple_sub_calls.py b/tests/ported_static/stRevertTest/test_revert_opcode_multiple_sub_calls.py index 4cb54d7251c..416bab3330a 100644 --- a/tests/ported_static/stRevertTest/test_revert_opcode_multiple_sub_calls.py +++ b/tests/ported_static/stRevertTest/test_revert_opcode_multiple_sub_calls.py @@ -248,7 +248,6 @@ def test_revert_opcode_multiple_sub_calls( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) pre[sender] = Account(balance=0xE8D4A51000) diff --git a/tests/ported_static/stRevertTest/test_revert_opcode_return.py b/tests/ported_static/stRevertTest/test_revert_opcode_return.py index 4cd809a728e..325f7568164 100644 --- a/tests/ported_static/stRevertTest/test_revert_opcode_return.py +++ b/tests/ported_static/stRevertTest/test_revert_opcode_return.py @@ -3,6 +3,10 @@ Ported from: state_tests/stRevertTest/RevertOpcodeReturnFiller.json +@manually-enhanced: Do not overwrite. tx_gas[1] bumped on Amsterdam to +cover EIP-8037 state-gas spill from target's two SSTORE-sets; +pre-EIP-8037 unchanged. + """ import pytest @@ -247,6 +251,8 @@ def test_revert_opcode_return( Hash(addr_6, left_padding=True), ] tx_gas = [800000, 80000] + if fork.is_eip_enabled(8037): + tx_gas = [800000, 250_000] tx = Transaction( sender=sender, diff --git a/tests/ported_static/stRevertTest/test_revert_precompiled_touch_exact_oog_paris.py b/tests/ported_static/stRevertTest/test_revert_precompiled_touch_exact_oog_paris.py index ef19a867f29..936a3505eb0 100644 --- a/tests/ported_static/stRevertTest/test_revert_precompiled_touch_exact_oog_paris.py +++ b/tests/ported_static/stRevertTest/test_revert_precompiled_touch_exact_oog_paris.py @@ -15,7 +15,7 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork +from execution_testing.forks import Fork, Prague from execution_testing.specs.static_state.expect_section import ( resolve_expect_post, ) @@ -925,7 +925,24 @@ def test_revert_precompiled_touch_exact_oog_paris( Hash(0x4000000000000000000000000000000000000000) + Hash(addr_12, left_padding=True), ] - tx_gas = [22500, 120000, 69000] + # The original ported test uses gas_limit tuned for an exact-OOG + # boundary on the CALLCODE-to-precompile path. EIP-7976 bumps the + # calldata floor cost per token from 10 to 16 (Amsterdam, with + # 8037), which would push the floor above the tightest budget. + # Shift gas_limit by the intrinsic delta so the same execution + # budget is preserved on every fork. + current_intrinsic = fork.transaction_intrinsic_cost_calculator()( + calldata=tx_data[d] + ) + baseline_intrinsic = Prague.transaction_intrinsic_cost_calculator()( + calldata=tx_data[d] + ) + intrinsic_delta = current_intrinsic - baseline_intrinsic + tx_gas = [ + 22500 + intrinsic_delta, + 120000 + intrinsic_delta, + 69000 + intrinsic_delta, + ] floor_cost = fork.transaction_data_floor_cost_calculator()(data=tx_data[d]) tx = Transaction( diff --git a/tests/ported_static/stRevertTest/test_revert_sub_call_storage_oog.py b/tests/ported_static/stRevertTest/test_revert_sub_call_storage_oog.py index db8c009d780..9ba23f0660e 100644 --- a/tests/ported_static/stRevertTest/test_revert_sub_call_storage_oog.py +++ b/tests/ported_static/stRevertTest/test_revert_sub_call_storage_oog.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRevertTest/RevertSubCallStorageOOGFiller.json +@manually-enhanced: Do not overwrite. tx_gas[1] is tuned to barely +fit 3 fresh SSTOREs on Cancun; on Amsterdam each fresh slot spills +state-gas, so lift the budget by Fork.oog_budget_lift. """ import pytest @@ -76,7 +79,6 @@ def test_revert_sub_call_storage_oog( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) # Source: raw @@ -116,7 +118,7 @@ def test_revert_sub_call_storage_oog( tx_data = [ Bytes("c0406226"), ] - tx_gas = [81000, 181000] + tx_gas = [81000, 181000 + fork.oog_budget_lift(sstores_before_oog=3)] tx_value = [0, 1] tx = Transaction( diff --git a/tests/ported_static/stRevertTest/test_revert_sub_call_storage_oog2.py b/tests/ported_static/stRevertTest/test_revert_sub_call_storage_oog2.py index 6b5bf707e63..dd3958fe322 100644 --- a/tests/ported_static/stRevertTest/test_revert_sub_call_storage_oog2.py +++ b/tests/ported_static/stRevertTest/test_revert_sub_call_storage_oog2.py @@ -3,6 +3,9 @@ Ported from: state_tests/stRevertTest/RevertSubCallStorageOOG2Filler.json +@manually-enhanced: Do not overwrite. tx_gas[1] is tuned to barely +fit 2 fresh SSTOREs on Cancun; on Amsterdam each fresh slot spills +state-gas, so lift the budget by Fork.oog_budget_lift. """ import pytest @@ -76,7 +79,6 @@ def test_revert_sub_call_storage_oog2( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) # Source: raw @@ -114,7 +116,7 @@ def test_revert_sub_call_storage_oog2( tx_data = [ Bytes("c0406226"), ] - tx_gas = [61500, 181000] + tx_gas = [61500, 181000 + fork.oog_budget_lift(sstores_before_oog=2)] tx_value = [0, 1] tx = Transaction( diff --git a/tests/ported_static/stSStoreTest/test_sstore_0to0.py b/tests/ported_static/stSStoreTest/test_sstore_0to0.py index 9b3b0321a91..a600afc7370 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_0to0.py +++ b/tests/ported_static/stSStoreTest/test_sstore_0to0.py @@ -179,7 +179,6 @@ def test_sstore_0to0( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) pre[sender] = Account(balance=0xE8D4A51000) diff --git a/tests/ported_static/stSStoreTest/test_sstore_0to0to0.py b/tests/ported_static/stSStoreTest/test_sstore_0to0to0.py index a0764f16d91..e7244014c13 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_0to0to0.py +++ b/tests/ported_static/stSStoreTest/test_sstore_0to0to0.py @@ -179,7 +179,6 @@ def test_sstore_0to0to0( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) pre[sender] = Account(balance=0xE8D4A51000) diff --git a/tests/ported_static/stSStoreTest/test_sstore_0to0to_x.py b/tests/ported_static/stSStoreTest/test_sstore_0to0to_x.py index 6fa2b1d32a3..9d6cf9ea9e6 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_0to0to_x.py +++ b/tests/ported_static/stSStoreTest/test_sstore_0to0to_x.py @@ -179,7 +179,6 @@ def test_sstore_0to0to_x( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) pre[sender] = Account(balance=0xE8D4A51000) diff --git a/tests/ported_static/stSStoreTest/test_sstore_0to_x.py b/tests/ported_static/stSStoreTest/test_sstore_0to_x.py index 616a7058319..797483aee1e 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_0to_x.py +++ b/tests/ported_static/stSStoreTest/test_sstore_0to_x.py @@ -179,7 +179,6 @@ def test_sstore_0to_x( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) pre[sender] = Account(balance=0xE8D4A51000) diff --git a/tests/ported_static/stSStoreTest/test_sstore_0to_xto0.py b/tests/ported_static/stSStoreTest/test_sstore_0to_xto0.py index 03b3c3bf134..aa8728ba931 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_0to_xto0.py +++ b/tests/ported_static/stSStoreTest/test_sstore_0to_xto0.py @@ -179,7 +179,6 @@ def test_sstore_0to_xto0( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) pre[sender] = Account(balance=0xE8D4A51000) diff --git a/tests/ported_static/stSStoreTest/test_sstore_0to_xto0to_x.py b/tests/ported_static/stSStoreTest/test_sstore_0to_xto0to_x.py index 14c8adefa63..ff9254c1ebf 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_0to_xto0to_x.py +++ b/tests/ported_static/stSStoreTest/test_sstore_0to_xto0to_x.py @@ -179,7 +179,6 @@ def test_sstore_0to_xto0to_x( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) pre[sender] = Account(balance=0xE8D4A51000) diff --git a/tests/ported_static/stSStoreTest/test_sstore_0to_xto_x.py b/tests/ported_static/stSStoreTest/test_sstore_0to_xto_x.py index 5ba077ae378..0b28bdfd8c8 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_0to_xto_x.py +++ b/tests/ported_static/stSStoreTest/test_sstore_0to_xto_x.py @@ -179,7 +179,6 @@ def test_sstore_0to_xto_x( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) pre[sender] = Account(balance=0xE8D4A51000) diff --git a/tests/ported_static/stSStoreTest/test_sstore_0to_xto_y.py b/tests/ported_static/stSStoreTest/test_sstore_0to_xto_y.py index e1930122ced..0c06be05a9e 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_0to_xto_y.py +++ b/tests/ported_static/stSStoreTest/test_sstore_0to_xto_y.py @@ -179,7 +179,6 @@ def test_sstore_0to_xto_y( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) pre[sender] = Account(balance=0xE8D4A51000) diff --git a/tests/ported_static/stSStoreTest/test_sstore_change_from_external_call_in_init_code.py b/tests/ported_static/stSStoreTest/test_sstore_change_from_external_call_in_init_code.py index f2cd4f56ff1..71d0e703e00 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_change_from_external_call_in_init_code.py +++ b/tests/ported_static/stSStoreTest/test_sstore_change_from_external_call_in_init_code.py @@ -3,6 +3,14 @@ Ported from: state_tests/stSStoreTest/sstore_changeFromExternalCallInInitCodeFiller.json + +@manually-enhanced: Do not overwrite. Gas budget refactored to be +fork-aware (`tx_gas = [intrinsic + tx_data[d].gas_cost(fork)]`), and +each `Op.CALL` annotated with `inner_call_cost=` metadata so +`Bytecode.gas_cost(fork)` covers the forwarded inner-frame gas. +Required for the test to fill correctly under EIP-8037's two- +dimensional gas model. Hex `gas=` literals also converted to +human-readable decimals. """ import pytest @@ -156,7 +164,6 @@ def test_sstore_change_from_external_call_in_init_code( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) pre[sender] = Account(balance=0xE8D4A51000) @@ -258,13 +265,14 @@ def test_sstore_change_from_external_call_in_init_code( tx_data = [ Op.CALL( - gas=0x186A0, + gas=100_000, address=contract_0, value=0x0, args_offset=0x0, args_size=0x0, ret_offset=0x0, ret_size=0x0, + inner_call_cost=100_000, ) + Op.STOP, Op.PUSH1[0x0] @@ -275,13 +283,14 @@ def test_sstore_change_from_external_call_in_init_code( + Op.STOP * 2 + Op.INVALID + Op.CALL( - gas=0x186A0, + gas=100_000, address=contract_0, value=0x0, args_offset=0x0, args_size=0x0, ret_offset=0x0, ret_size=0x0, + inner_call_cost=100_000, ) + Op.STOP, Op.PUSH1[0x0] @@ -293,13 +302,14 @@ def test_sstore_change_from_external_call_in_init_code( + Op.STOP * 2 + Op.INVALID + Op.CALL( - gas=0x186A0, + gas=100_000, address=contract_0, value=0x0, args_offset=0x0, args_size=0x0, ret_offset=0x0, ret_size=0x0, + inner_call_cost=100_000, ) + Op.STOP, Op.PUSH1[0x0] @@ -309,35 +319,38 @@ def test_sstore_change_from_external_call_in_init_code( + Op.POP(Op.CREATE2) + Op.POP( Op.CALL( - gas=0x30D40, + gas=200_000, address=contract_1, value=0x0, args_offset=0x0, args_size=0x0, ret_offset=0x0, ret_size=0x0, + inner_call_cost=200_000, ) ) + Op.STOP * 2 + Op.INVALID + Op.CALL( - gas=0x186A0, + gas=100_000, address=contract_0, value=0x0, args_offset=0x0, args_size=0x0, ret_offset=0x0, ret_size=0x0, + inner_call_cost=100_000, ) + Op.STOP, Op.CALLCODE( - gas=0x186A0, + gas=100_000, address=contract_0, value=0x0, args_offset=0x0, args_size=0x0, ret_offset=0x0, ret_size=0x0, + inner_call_cost=100_000, ) + Op.STOP, Op.PUSH1[0x0] @@ -348,13 +361,14 @@ def test_sstore_change_from_external_call_in_init_code( + Op.STOP * 2 + Op.INVALID + Op.CALLCODE( - gas=0x186A0, + gas=100_000, address=contract_0, value=0x0, args_offset=0x0, args_size=0x0, ret_offset=0x0, ret_size=0x0, + inner_call_cost=100_000, ) + Op.STOP, Op.PUSH1[0x0] @@ -366,13 +380,14 @@ def test_sstore_change_from_external_call_in_init_code( + Op.STOP * 2 + Op.INVALID + Op.CALLCODE( - gas=0x186A0, + gas=100_000, address=contract_0, value=0x0, args_offset=0x0, args_size=0x0, ret_offset=0x0, ret_size=0x0, + inner_call_cost=100_000, ) + Op.STOP, Op.PUSH1[0x0] @@ -382,29 +397,31 @@ def test_sstore_change_from_external_call_in_init_code( + Op.POP(Op.CREATE2) + Op.POP( Op.CALL( - gas=0x30D40, + gas=200_000, address=contract_1, value=0x0, args_offset=0x0, args_size=0x0, ret_offset=0x0, ret_size=0x0, + inner_call_cost=200_000, ) ) + Op.STOP * 2 + Op.INVALID + Op.CALLCODE( - gas=0x186A0, + gas=100_000, address=contract_0, value=0x0, args_offset=0x0, args_size=0x0, ret_offset=0x0, ret_size=0x0, + inner_call_cost=100_000, ) + Op.STOP, Op.DELEGATECALL( - gas=0x186A0, + gas=100_000, address=contract_0, args_offset=0x0, args_size=0x0, @@ -420,7 +437,7 @@ def test_sstore_change_from_external_call_in_init_code( + Op.STOP * 2 + Op.INVALID + Op.DELEGATECALL( - gas=0x186A0, + gas=100_000, address=contract_0, args_offset=0x0, args_size=0x0, @@ -437,7 +454,7 @@ def test_sstore_change_from_external_call_in_init_code( + Op.STOP * 2 + Op.INVALID + Op.DELEGATECALL( - gas=0x186A0, + gas=100_000, address=contract_0, args_offset=0x0, args_size=0x0, @@ -452,19 +469,20 @@ def test_sstore_change_from_external_call_in_init_code( + Op.POP(Op.CREATE2) + Op.POP( Op.CALL( - gas=0x30D40, + gas=200_000, address=contract_1, value=0x0, args_offset=0x0, args_size=0x0, ret_offset=0x0, ret_size=0x0, + inner_call_cost=200_000, ) ) + Op.STOP * 2 + Op.INVALID + Op.DELEGATECALL( - gas=0x186A0, + gas=100_000, address=contract_0, args_offset=0x0, args_size=0x0, @@ -473,7 +491,7 @@ def test_sstore_change_from_external_call_in_init_code( ) + Op.STOP, Op.STATICCALL( - gas=0x186A0, + gas=100_000, address=contract_0, args_offset=0x0, args_size=0x0, @@ -489,7 +507,7 @@ def test_sstore_change_from_external_call_in_init_code( + Op.STOP * 2 + Op.INVALID + Op.STATICCALL( - gas=0x186A0, + gas=100_000, address=contract_0, args_offset=0x0, args_size=0x0, @@ -506,7 +524,7 @@ def test_sstore_change_from_external_call_in_init_code( + Op.STOP * 2 + Op.INVALID + Op.STATICCALL( - gas=0x186A0, + gas=100_000, address=contract_0, args_offset=0x0, args_size=0x0, @@ -521,19 +539,20 @@ def test_sstore_change_from_external_call_in_init_code( + Op.POP(Op.CREATE2) + Op.POP( Op.CALL( - gas=0x30D40, + gas=200_000, address=contract_1, value=0x0, args_offset=0x0, args_size=0x0, ret_offset=0x0, ret_size=0x0, + inner_call_cost=200_000, ) ) + Op.STOP * 2 + Op.INVALID + Op.STATICCALL( - gas=0x186A0, + gas=100_000, address=contract_0, args_offset=0x0, args_size=0x0, @@ -542,7 +561,15 @@ def test_sstore_change_from_external_call_in_init_code( ) + Op.STOP, ] - tx_gas = [200000] + # Fork-aware gas budget: contract-creation intrinsic from the + # fork's calculator, plus the bytecode's own gas cost (which + # already includes the gas forwarded to inner CALLs via opcode + # metadata). + intrinsic = fork.transaction_intrinsic_cost_calculator()( + calldata=tx_data[d], + contract_creation=True, + ) + tx_gas = [intrinsic + tx_data[d].gas_cost(fork)] tx = Transaction( sender=sender, diff --git a/tests/ported_static/stSStoreTest/test_sstore_gas_left.py b/tests/ported_static/stSStoreTest/test_sstore_gas_left.py index caa81fb185e..4c4bf0c54c0 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_gas_left.py +++ b/tests/ported_static/stSStoreTest/test_sstore_gas_left.py @@ -3,6 +3,14 @@ Ported from: state_tests/stSStoreTest/sstore_gasLeftFiller.json + +@manually-enhanced: Do not overwrite. Gas budget refactored to be +fork-aware (`tx_gas = [intrinsic + tx_data[d].gas_cost(fork)]`), and +each `Op.CALL` annotated with `inner_call_cost=` metadata so +`Bytecode.gas_cost(fork)` covers the forwarded inner-frame gas. +Required for the test to fill correctly under EIP-8037's two- +dimensional gas model. Hex `gas=` literals also converted to +human-readable decimals. """ import pytest @@ -149,25 +157,27 @@ def test_sstore_gas_left( pc=0x4B, condition=Op.ISZERO( Op.CALL( - gas=0x901, + gas=2305, address=addr, value=0x0, args_offset=0x0, args_size=0x0, ret_offset=0x0, ret_size=0x0, + inner_call_cost=2305, ) ), ) + Op.POP( Op.CALL( - gas=0x7530, + gas=30_000, address=addr_2, value=0x0, args_offset=0x0, args_size=0x0, ret_offset=0x0, ret_size=0x0, + inner_call_cost=30_000, ) ) + Op.JUMPDEST @@ -176,25 +186,27 @@ def test_sstore_gas_left( pc=0x4B, condition=Op.ISZERO( Op.CALL( - gas=0x902, + gas=2306, address=addr, value=0x0, args_offset=0x0, args_size=0x0, ret_offset=0x0, ret_size=0x0, + inner_call_cost=2306, ) ), ) + Op.POP( Op.CALL( - gas=0x7530, + gas=30_000, address=addr_2, value=0x0, args_offset=0x0, args_size=0x0, ret_offset=0x0, ret_size=0x0, + inner_call_cost=30_000, ) ) + Op.JUMPDEST @@ -203,25 +215,27 @@ def test_sstore_gas_left( pc=0x4B, condition=Op.ISZERO( Op.CALL( - gas=0x903, + gas=2307, address=addr, value=0x0, args_offset=0x0, args_size=0x0, ret_offset=0x0, ret_size=0x0, + inner_call_cost=2307, ) ), ) + Op.POP( Op.CALL( - gas=0x7530, + gas=30_000, address=addr_2, value=0x0, args_offset=0x0, args_size=0x0, ret_offset=0x0, ret_size=0x0, + inner_call_cost=30_000, ) ) + Op.JUMPDEST @@ -231,25 +245,27 @@ def test_sstore_gas_left( pc=0x50, condition=Op.ISZERO( Op.CALLCODE( - gas=0x901, + gas=2305, address=addr, value=0x0, args_offset=0x0, args_size=0x0, ret_offset=0x0, ret_size=0x0, + inner_call_cost=2305, ) ), ) + Op.POP( Op.CALL( - gas=0x7530, + gas=30_000, address=addr_2, value=0x0, args_offset=0x0, args_size=0x0, ret_offset=0x0, ret_size=0x0, + inner_call_cost=30_000, ) ) + Op.JUMPDEST @@ -259,25 +275,27 @@ def test_sstore_gas_left( pc=0x50, condition=Op.ISZERO( Op.CALLCODE( - gas=0x902, + gas=2306, address=addr, value=0x0, args_offset=0x0, args_size=0x0, ret_offset=0x0, ret_size=0x0, + inner_call_cost=2306, ) ), ) + Op.POP( Op.CALL( - gas=0x7530, + gas=30_000, address=addr_2, value=0x0, args_offset=0x0, args_size=0x0, ret_offset=0x0, ret_size=0x0, + inner_call_cost=30_000, ) ) + Op.JUMPDEST @@ -287,25 +305,27 @@ def test_sstore_gas_left( pc=0x50, condition=Op.ISZERO( Op.CALLCODE( - gas=0x903, + gas=2307, address=addr, value=0x0, args_offset=0x0, args_size=0x0, ret_offset=0x0, ret_size=0x0, + inner_call_cost=2307, ) ), ) + Op.POP( Op.CALL( - gas=0x7530, + gas=30_000, address=addr_2, value=0x0, args_offset=0x0, args_size=0x0, ret_offset=0x0, ret_size=0x0, + inner_call_cost=30_000, ) ) + Op.JUMPDEST @@ -315,7 +335,7 @@ def test_sstore_gas_left( pc=0x4E, condition=Op.ISZERO( Op.DELEGATECALL( - gas=0x901, + gas=2305, address=addr, args_offset=0x0, args_size=0x0, @@ -326,13 +346,14 @@ def test_sstore_gas_left( ) + Op.POP( Op.CALL( - gas=0x7530, + gas=30_000, address=addr_2, value=0x0, args_offset=0x0, args_size=0x0, ret_offset=0x0, ret_size=0x0, + inner_call_cost=30_000, ) ) + Op.JUMPDEST @@ -342,7 +363,7 @@ def test_sstore_gas_left( pc=0x4E, condition=Op.ISZERO( Op.DELEGATECALL( - gas=0x902, + gas=2306, address=addr, args_offset=0x0, args_size=0x0, @@ -353,13 +374,14 @@ def test_sstore_gas_left( ) + Op.POP( Op.CALL( - gas=0x7530, + gas=30_000, address=addr_2, value=0x0, args_offset=0x0, args_size=0x0, ret_offset=0x0, ret_size=0x0, + inner_call_cost=30_000, ) ) + Op.JUMPDEST @@ -369,7 +391,7 @@ def test_sstore_gas_left( pc=0x4E, condition=Op.ISZERO( Op.DELEGATECALL( - gas=0x903, + gas=2307, address=addr, args_offset=0x0, args_size=0x0, @@ -380,19 +402,29 @@ def test_sstore_gas_left( ) + Op.POP( Op.CALL( - gas=0x7530, + gas=30_000, address=addr_2, value=0x0, args_offset=0x0, args_size=0x0, ret_offset=0x0, ret_size=0x0, + inner_call_cost=30_000, ) ) + Op.JUMPDEST + Op.STOP, ] - tx_gas = [200000] + # Fork-aware gas budget: contract-creation intrinsic from the + # fork's calculator, plus the bytecode's own gas cost (which + # already includes the gas forwarded to inner CALLs via opcode + # metadata). Any future fork-cost change is automatically + # respected. + intrinsic = fork.transaction_intrinsic_cost_calculator()( + calldata=tx_data[d], + contract_creation=True, + ) + tx_gas = [intrinsic + tx_data[d].gas_cost(fork)] tx_value = [1] tx = Transaction( diff --git a/tests/ported_static/stSStoreTest/test_sstore_xto0.py b/tests/ported_static/stSStoreTest/test_sstore_xto0.py index 5f6bcb9a9af..3c1e74378c0 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_xto0.py +++ b/tests/ported_static/stSStoreTest/test_sstore_xto0.py @@ -179,7 +179,6 @@ def test_sstore_xto0( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) pre[sender] = Account(balance=0xE8D4A51000) diff --git a/tests/ported_static/stSStoreTest/test_sstore_xto0to0.py b/tests/ported_static/stSStoreTest/test_sstore_xto0to0.py index e78926d2c21..b3857446a9a 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_xto0to0.py +++ b/tests/ported_static/stSStoreTest/test_sstore_xto0to0.py @@ -179,7 +179,6 @@ def test_sstore_xto0to0( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) pre[sender] = Account(balance=0xE8D4A51000) diff --git a/tests/ported_static/stSStoreTest/test_sstore_xto0to_x.py b/tests/ported_static/stSStoreTest/test_sstore_xto0to_x.py index 1f26059f935..6aafce6dbf1 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_xto0to_x.py +++ b/tests/ported_static/stSStoreTest/test_sstore_xto0to_x.py @@ -179,7 +179,6 @@ def test_sstore_xto0to_x( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) pre[sender] = Account(balance=0xE8D4A51000) diff --git a/tests/ported_static/stSStoreTest/test_sstore_xto0to_xto0.py b/tests/ported_static/stSStoreTest/test_sstore_xto0to_xto0.py index c65fa6c8539..f497142216d 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_xto0to_xto0.py +++ b/tests/ported_static/stSStoreTest/test_sstore_xto0to_xto0.py @@ -179,7 +179,6 @@ def test_sstore_xto0to_xto0( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) pre[sender] = Account(balance=0xE8D4A51000) diff --git a/tests/ported_static/stSStoreTest/test_sstore_xto0to_y.py b/tests/ported_static/stSStoreTest/test_sstore_xto0to_y.py index 0152a4437bd..2e64a0bc108 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_xto0to_y.py +++ b/tests/ported_static/stSStoreTest/test_sstore_xto0to_y.py @@ -179,7 +179,6 @@ def test_sstore_xto0to_y( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) pre[sender] = Account(balance=0xE8D4A51000) diff --git a/tests/ported_static/stSStoreTest/test_sstore_xto_x.py b/tests/ported_static/stSStoreTest/test_sstore_xto_x.py index 624f8477042..5f39f313acb 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_xto_x.py +++ b/tests/ported_static/stSStoreTest/test_sstore_xto_x.py @@ -179,7 +179,6 @@ def test_sstore_xto_x( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) pre[sender] = Account(balance=0xE8D4A51000) diff --git a/tests/ported_static/stSStoreTest/test_sstore_xto_xto0.py b/tests/ported_static/stSStoreTest/test_sstore_xto_xto0.py index f8cceff2e08..c7d245e5a27 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_xto_xto0.py +++ b/tests/ported_static/stSStoreTest/test_sstore_xto_xto0.py @@ -179,7 +179,6 @@ def test_sstore_xto_xto0( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) pre[sender] = Account(balance=0xE8D4A51000) diff --git a/tests/ported_static/stSStoreTest/test_sstore_xto_xto_x.py b/tests/ported_static/stSStoreTest/test_sstore_xto_xto_x.py index d35b0f6152b..9bc83904c33 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_xto_xto_x.py +++ b/tests/ported_static/stSStoreTest/test_sstore_xto_xto_x.py @@ -179,7 +179,6 @@ def test_sstore_xto_xto_x( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) pre[sender] = Account(balance=0xE8D4A51000) diff --git a/tests/ported_static/stSStoreTest/test_sstore_xto_xto_y.py b/tests/ported_static/stSStoreTest/test_sstore_xto_xto_y.py index dbddb17fed5..ea56e7f589e 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_xto_xto_y.py +++ b/tests/ported_static/stSStoreTest/test_sstore_xto_xto_y.py @@ -179,7 +179,6 @@ def test_sstore_xto_xto_y( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) pre[sender] = Account(balance=0xE8D4A51000) diff --git a/tests/ported_static/stSStoreTest/test_sstore_xto_y.py b/tests/ported_static/stSStoreTest/test_sstore_xto_y.py index 58220a6d3ea..d65925fd123 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_xto_y.py +++ b/tests/ported_static/stSStoreTest/test_sstore_xto_y.py @@ -179,7 +179,6 @@ def test_sstore_xto_y( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) pre[sender] = Account(balance=0xE8D4A51000) diff --git a/tests/ported_static/stSStoreTest/test_sstore_xto_yto0.py b/tests/ported_static/stSStoreTest/test_sstore_xto_yto0.py index 84a664f84cd..f4e5ace93de 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_xto_yto0.py +++ b/tests/ported_static/stSStoreTest/test_sstore_xto_yto0.py @@ -179,7 +179,6 @@ def test_sstore_xto_yto0( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) pre[sender] = Account(balance=0xE8D4A51000) diff --git a/tests/ported_static/stSStoreTest/test_sstore_xto_yto_x.py b/tests/ported_static/stSStoreTest/test_sstore_xto_yto_x.py index 2d393c8df0f..9b2c31951ed 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_xto_yto_x.py +++ b/tests/ported_static/stSStoreTest/test_sstore_xto_yto_x.py @@ -179,7 +179,6 @@ def test_sstore_xto_yto_x( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) pre[sender] = Account(balance=0xE8D4A51000) diff --git a/tests/ported_static/stSStoreTest/test_sstore_xto_yto_y.py b/tests/ported_static/stSStoreTest/test_sstore_xto_yto_y.py index f1a39c8dc91..b5254ed8235 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_xto_yto_y.py +++ b/tests/ported_static/stSStoreTest/test_sstore_xto_yto_y.py @@ -179,7 +179,6 @@ def test_sstore_xto_yto_y( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) pre[sender] = Account(balance=0xE8D4A51000) diff --git a/tests/ported_static/stSStoreTest/test_sstore_xto_yto_z.py b/tests/ported_static/stSStoreTest/test_sstore_xto_yto_z.py index 4e4d7d8f0dc..b6b7811d822 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_xto_yto_z.py +++ b/tests/ported_static/stSStoreTest/test_sstore_xto_yto_z.py @@ -179,7 +179,6 @@ def test_sstore_xto_yto_z( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) pre[sender] = Account(balance=0xE8D4A51000) diff --git a/tests/ported_static/stSelfBalance/test_self_balance.py b/tests/ported_static/stSelfBalance/test_self_balance.py index af929bf08c8..cbaba3c11c2 100644 --- a/tests/ported_static/stSelfBalance/test_self_balance.py +++ b/tests/ported_static/stSelfBalance/test_self_balance.py @@ -40,7 +40,6 @@ def test_self_balance( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000000, ) # Source: lll diff --git a/tests/ported_static/stSelfBalance/test_self_balance_call_types.py b/tests/ported_static/stSelfBalance/test_self_balance_call_types.py index fb8f5d29929..2d12d4e5179 100644 --- a/tests/ported_static/stSelfBalance/test_self_balance_call_types.py +++ b/tests/ported_static/stSelfBalance/test_self_balance_call_types.py @@ -71,7 +71,6 @@ def test_self_balance_call_types( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000000, ) # Source: lll diff --git a/tests/ported_static/stSelfBalance/test_self_balance_equals_balance.py b/tests/ported_static/stSelfBalance/test_self_balance_equals_balance.py index c4e11a686d2..0bd75d31ebc 100644 --- a/tests/ported_static/stSelfBalance/test_self_balance_equals_balance.py +++ b/tests/ported_static/stSelfBalance/test_self_balance_equals_balance.py @@ -40,7 +40,6 @@ def test_self_balance_equals_balance( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000000, ) # Source: lll diff --git a/tests/ported_static/stSelfBalance/test_self_balance_gas_cost.py b/tests/ported_static/stSelfBalance/test_self_balance_gas_cost.py index 7d45e07fe17..99909d00883 100644 --- a/tests/ported_static/stSelfBalance/test_self_balance_gas_cost.py +++ b/tests/ported_static/stSelfBalance/test_self_balance_gas_cost.py @@ -40,7 +40,6 @@ def test_self_balance_gas_cost( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000000, ) # Source: lll diff --git a/tests/ported_static/stSelfBalance/test_self_balance_update.py b/tests/ported_static/stSelfBalance/test_self_balance_update.py index 549903be9d1..274bf2965de 100644 --- a/tests/ported_static/stSelfBalance/test_self_balance_update.py +++ b/tests/ported_static/stSelfBalance/test_self_balance_update.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -28,6 +30,7 @@ @pytest.mark.pre_alloc_mutable def test_self_balance_update( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_self_balance_update.""" @@ -40,7 +43,6 @@ def test_self_balance_update( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000000, ) # Source: lll @@ -72,7 +74,7 @@ def test_self_balance_update( sender=sender, to=target, data=Bytes(""), - gas_limit=200000, + gas_limit=2200000 if fork >= Amsterdam else 200000, ) post = {target: Account(storage={1: 500, 2: 499, 3: 1})} diff --git a/tests/ported_static/stSolidityTest/test_call_low_level_creates_solidity.py b/tests/ported_static/stSolidityTest/test_call_low_level_creates_solidity.py index 506bac4b786..d9a3ee35cba 100644 --- a/tests/ported_static/stSolidityTest/test_call_low_level_creates_solidity.py +++ b/tests/ported_static/stSolidityTest/test_call_low_level_creates_solidity.py @@ -12,9 +12,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -28,6 +30,7 @@ @pytest.mark.pre_alloc_mutable def test_call_low_level_creates_solidity( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_call_low_level_creates_solidity.""" @@ -158,7 +161,7 @@ def test_call_low_level_creates_solidity( sender=sender, to=target, data=Bytes("c0406226"), - gas_limit=350000, + gas_limit=2350000 if fork >= Amsterdam else 350000, value=1, ) diff --git a/tests/ported_static/stSolidityTest/test_recursive_create_contracts_create4_contracts.py b/tests/ported_static/stSolidityTest/test_recursive_create_contracts_create4_contracts.py index 8c88bdf7583..435c6c14f97 100644 --- a/tests/ported_static/stSolidityTest/test_recursive_create_contracts_create4_contracts.py +++ b/tests/ported_static/stSolidityTest/test_recursive_create_contracts_create4_contracts.py @@ -12,11 +12,13 @@ Alloc, Bytes, Environment, + Fork, Hash, StateTestFiller, Transaction, compute_create_address, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -32,6 +34,7 @@ @pytest.mark.pre_alloc_mutable def test_recursive_create_contracts_create4_contracts( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_recursive_create_contracts_create4_contracts.""" @@ -252,7 +255,7 @@ def test_recursive_create_contracts_create4_contracts( sender=sender, to=contract_0, data=Bytes("a444f5e9") + Hash(0x4), - gas_limit=300000, + gas_limit=2300000 if fork >= Amsterdam else 300000, value=1, ) diff --git a/tests/ported_static/stSolidityTest/test_test_overflow.py b/tests/ported_static/stSolidityTest/test_test_overflow.py index dcfb038159d..2343732d411 100644 --- a/tests/ported_static/stSolidityTest/test_test_overflow.py +++ b/tests/ported_static/stSolidityTest/test_test_overflow.py @@ -40,7 +40,6 @@ def test_test_overflow( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: raw diff --git a/tests/ported_static/stSolidityTest/test_test_structures_and_variabless.py b/tests/ported_static/stSolidityTest/test_test_structures_and_variabless.py index d60edc74363..594bbf9fd21 100644 --- a/tests/ported_static/stSolidityTest/test_test_structures_and_variabless.py +++ b/tests/ported_static/stSolidityTest/test_test_structures_and_variabless.py @@ -43,7 +43,6 @@ def test_test_structures_and_variabless( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) pre[sender] = Account(balance=0x2540BE400) diff --git a/tests/ported_static/stSpecialTest/test_deployment_error.py b/tests/ported_static/stSpecialTest/test_deployment_error.py index 9cc8a073ac7..84f332a8146 100644 --- a/tests/ported_static/stSpecialTest/test_deployment_error.py +++ b/tests/ported_static/stSpecialTest/test_deployment_error.py @@ -12,10 +12,12 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, compute_create_address, ) +from execution_testing.forks import Amsterdam REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" @@ -27,6 +29,7 @@ @pytest.mark.valid_from("Cancun") def test_deployment_error( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_deployment_error.""" @@ -39,7 +42,6 @@ def test_deployment_error( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=314159200, ) tx = Transaction( @@ -48,7 +50,7 @@ def test_deployment_error( data=Bytes( "606060405260405160608061100383395060c06040525160805160a05160028054600160a060020a031916909317909255600355600455610fbf806100446000396000f3606060405236156100a35760e060020a60003504630a19b14a81146100ab578063278b8c0e146100e25780632e1a7d4d14610111578063338b5dea14610125578063577863941461015057806365e17c9d146101595780636c86888b1461016b57806393f0bb51146101da5780639e281a9814610207578063c281309e14610232578063d0e30db01461023b578063f7888aec14610287578063fb6e155f146102bb575b6103e6610002565b6103e660043560243560443560643560843560a43560c43560e4356101043561012435610144356000600034111561042b57610002565b6103e660043560243560443560643560843560a43560c43560e43561010435600060003411156108b457610002565b6103e66004356000341115610ab357610002565b6103e66004356024356000341180610146575081600160a060020a03166000145b15610b6157610002565b6103e860035481565b6103fa600254600160a060020a031681565b61041760043560243560443560643560843560a43560c43560e43561010435610124356101443561016435600160a060020a038c8116600090815260208181526040808320938516835292905290812054839010801590610c96575082610c938e8e8e8e8e8e8e8e8e8e6102df565b6103e660043560243560443560643560843560a43560c43560e435610104356000341115610ca457610002565b6103e66004356024356000341180610228575081600160a060020a03166000145b15610d3057610002565b6103e860045481565b6103e633600160a060020a03166000908152600080516020610f9f8339815191526020526040902054610ea390345b6000828201610f8f8482108015906102825750838210155b610660565b6103e8600435602435600160a060020a03828116600090815260208181526040808320938516835292905220545b92915050565b6103e860043560243560443560643560843560a43560c43560e43561010435610124355b600060006000600060028e8e8e8e8e8e6040518087600160a060020a0316606060020a02815260140186815260200185600160a060020a0316606060020a02815260140184815260200183815260200182815260200196505050505050506020604051808303816000866161da5a03f1156100025750506040805180516000828152602083810180865283905260ff8c1684860152606084018b9052608084018a90529351919650600160a060020a038c169360019360a0808201949293601f19840193928390039091019190866161da5a03f11561000257505060206040510351600160a060020a03161480156103d75750894311155b1515610f295760009350610f18565b005b60408051918252519081900360200190f35b60408051600160a060020a03929092168252519081900360200190f35b604080519115158252519081900360200190f35b60028c8c8c8c8c8c6040518087600160a060020a0316606060020a02815260140186815260200185600160a060020a0316606060020a02815260140184815260200183815260200182815260200196505050505050506020604051808303816000866161da5a03f1156100025750506040805180516000828152602083810180865283905260ff8a168486015260608401899052608084018890529351919450600160a060020a038a169360019360a0818101949293601f19840193928390039091019190866161da5a03f11561000257505060206040510351600160a060020a031614801561051b5750874311155b801561054057506000818152600160205260409020548b9061053d908461026a565b11155b80156105715750600160a060020a038c81166000908152602081815260408083203390941683529290522054829010155b80156105b457508a6105838a8461060a565b811561000257600160a060020a038c8116600090815260208181526040808320938c16835292905220549190049010155b151561062b57610002565b600160a060020a038d81166000908152602081815260408083203385168452909152808220939093559088168152205460035461066c9190670de0b6b3a7640000906106bc90869083035b6000828202610f8f8483148061028257508385838115610002570414610660565b600160a060020a038c811660009081526020818152604080832033909416835292905220546105bf90835b6000610f96838311155b801515610ab057610002565b600160a060020a038d81166000908152602081815260408083208b8516845290915280822093909355600254909116815220546003546106c89190670de0b6b3a7640000906106bc90869061060a565b8115610002570461026a565b600160a060020a038d8116600090815260208181526040808320600254851684528252808320949094558d83168252818152838220928a168252919091522054610717908c61076c8c8661060a565b600160a060020a038b81166000908152602081815260408083208b851684529091528082209390935533909116815220546004546107789190670de0b6b3a7640000908e906107cd906107e09084038f61060a565b81156100025704610656565b600160a060020a038b8116600090815260208181526040808320338516845290915280822093909355600254909116815220546004546107e69190670de0b6b3a7640000908e906107cd906107e0908f61060a565b811561000257048115610002570461026a565b8761060a565b600160a060020a038b81166000908152602081815260408083206002549094168352928152828220939093558381526001909252902054610827908361026a565b6000828152600160205260409020557f6effdda786735d5033bfad5f53e5131abcced9e52be6c507b62d639685fbed6d8c838c8e8d830281156100025760408051600160a060020a03968716815260208101959095529285168484015204606083015289831660808301523390921660a082015290519081900360c00190a1505050505050505050505050565b60028a8a8a8a8a8a6040518087600160a060020a0316606060020a02815260140186815260200185600160a060020a0316606060020a02815260140184815260200183815260200182815260200196505050505050506020604051808303816000866161da5a03f1156100025750506040805180516000828152602083810180865283905260ff8916848601526060840188905260808401879052935191945033600160a060020a03169360019360a0818101949293601f19840193928390039091019190866161da5a03f115610002575050604051601f190151600160a060020a0316146109a257610002565b6000818152600160209081526040918290208b90558151600160a060020a038d811682529181018c90528a821681840152606081018a90526080810189905260a081018890523390911660c082015260ff861660e08201526101008101859052610120810184905290517f1e0b760c386003e9cb9bcf4fcf3997886042859d9b6ed6320e804597fcdb28b0918190036101400190a150505050505050505050565b33600160a060020a03166000818152600080516020610f9f8339815191526020908152604080832054815193845291830193909352818301849052606082015290517ff341246adaac6f497bc2a656f546ab9e182111d630394f0c57c710a59a2cb5679181900360800190a15b50565b33600160a060020a03166000908152600080516020610f9f833981519152602052604090205481901015610ae657610002565b33600160a060020a03166000908152600080516020610f9f8339815191526020526040902054610b169082610656565b33600160a060020a03166000818152600080516020610f9f8339815191526020526040808220939093559151909183919081818185876185025a03f1925050501515610a4357610002565b81600160a060020a03166323b872dd3330846040518460e060020a0281526004018084600160a060020a0316815260200183600160a060020a031681526020018281526020019350505050602060405180830381600087803b15610002576161da5a03f1156100025750506040515115159050610bdd57610002565b600160a060020a038281166000908152602081815260408083203390941683529290522054610c0c908261026a565b600160a060020a03838116600081815260208181526040808320339095168084529482529182902085905581519283528201929092528082018490526060810192909252517fdcbc1c05240f31ff3ad067ef1ee35ce4997762752e3a095284754544f4c709d79181900360800190a15050565b5060015b9c9b505050505050505050505050565b10155b1515610c7f57506000610c83565b60408051600160a060020a038b81168252602082018b905289811682840152606082018990526080820188905260a08201879052331660c082015260ff851660e08201526101008101849052610120810183905290517f91daf02b6d1454acd74c097a67e389a9d9371da3ff51366947022dc36748ce4d918190036101400190a1505050505050505050565b600160a060020a03828116600090815260208181526040808320339094168352929052205481901015610d6257610002565b600160a060020a038281166000908152602081815260408083203390941683529290522054610d919082610656565b600160a060020a03838116600081815260208181526040808320339095168084529482528083209590955584517fa9059cbb0000000000000000000000000000000000000000000000000000000081526004810194909452602484018690529351919363a9059cbb936044818101949293918390030190829087803b15610002576161da5a03f1156100025750506040515115159050610e3057610002565b600160a060020a03828116600081815260208181526040808320339095168084529482529182902054825193845290830193909352818101849052606082019290925290517ff341246adaac6f497bc2a656f546ab9e182111d630394f0c57c710a59a2cb5679181900360800190a15050565b33600160a060020a03166000818152600080516020610f9f8339815191526020908152604080832085905580519283529082019290925234818301526060810192909252517fdcbc1c05240f31ff3ad067ef1ee35ce4997762752e3a095284754544f4c709d79181900360800190a1565b8093505b5050509a9950505050505050505050565b600083815260016020526040902054610f43908e90610656565b600160a060020a038d8116600090815260208181526040808320938d16835292905220549092508b90610f76908f61060a565b81156100025704905080821015610f1457819350610f18565b9392505050565b508082036102b556ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb50000000000000000000000001ed014aec47fae44c9e55bac7662c0b78ae617980000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000aa87bee538000" # noqa: E501 ), - gas_limit=5000000, + gas_limit=7000000 if fork >= Amsterdam else 5000000, ) post = { diff --git a/tests/ported_static/stSpecialTest/test_failed_create_reverts_deletion_paris.py b/tests/ported_static/stSpecialTest/test_failed_create_reverts_deletion_paris.py index cb4da7c841c..3daeed66e0a 100644 --- a/tests/ported_static/stSpecialTest/test_failed_create_reverts_deletion_paris.py +++ b/tests/ported_static/stSpecialTest/test_failed_create_reverts_deletion_paris.py @@ -12,9 +12,11 @@ Address, Alloc, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -28,6 +30,7 @@ @pytest.mark.pre_alloc_mutable def test_failed_create_reverts_deletion_paris( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """A modification of stRevertTests/RevertInCreateInInit.""" @@ -43,7 +46,6 @@ def test_failed_create_reverts_deletion_paris( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=43218108416, ) pre[addr] = Account(balance=10, storage={0: 1}) @@ -63,7 +65,7 @@ def test_failed_create_reverts_deletion_paris( + Op.MSTORE(offset=0x0, value=0x112233) + Op.REVERT(offset=0x0, size=0x20) + Op.STOP, - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, ) post = {addr: Account(storage={0: 1}, balance=10)} diff --git a/tests/ported_static/stSpecialTest/test_selfdestruct_eip2929.py b/tests/ported_static/stSpecialTest/test_selfdestruct_eip2929.py index a899276c3c7..2673f286c65 100644 --- a/tests/ported_static/stSpecialTest/test_selfdestruct_eip2929.py +++ b/tests/ported_static/stSpecialTest/test_selfdestruct_eip2929.py @@ -39,7 +39,6 @@ def test_selfdestruct_eip2929( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10944489199640098, ) addr = pre.fund_eoa(amount=0) # noqa: F841 diff --git a/tests/ported_static/stStackTests/test_shallow_stack.py b/tests/ported_static/stStackTests/test_shallow_stack.py index 0ba5126b719..fc01d658092 100644 --- a/tests/ported_static/stStackTests/test_shallow_stack.py +++ b/tests/ported_static/stStackTests/test_shallow_stack.py @@ -535,7 +535,6 @@ def test_shallow_stack( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) tx_data = [ diff --git a/tests/ported_static/stStackTests/test_stack_overflow.py b/tests/ported_static/stStackTests/test_stack_overflow.py index d8e7f4d4596..6aeb45b415d 100644 --- a/tests/ported_static/stStackTests/test_stack_overflow.py +++ b/tests/ported_static/stStackTests/test_stack_overflow.py @@ -145,7 +145,6 @@ def test_stack_overflow( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) contract_0 = pre.fund_eoa(amount=0xE8D4A5100000000000) # noqa: F841 diff --git a/tests/ported_static/stStackTests/test_stack_overflow_dup.py b/tests/ported_static/stStackTests/test_stack_overflow_dup.py index 2b935d280fa..6d6a7125803 100644 --- a/tests/ported_static/stStackTests/test_stack_overflow_dup.py +++ b/tests/ported_static/stStackTests/test_stack_overflow_dup.py @@ -145,7 +145,6 @@ def test_stack_overflow_dup( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) contract_0 = pre.fund_eoa(amount=0xE8D4A5100000000000) # noqa: F841 diff --git a/tests/ported_static/stStackTests/test_stack_overflow_m1.py b/tests/ported_static/stStackTests/test_stack_overflow_m1.py index c4d6fc9ee97..956cf7c8721 100644 --- a/tests/ported_static/stStackTests/test_stack_overflow_m1.py +++ b/tests/ported_static/stStackTests/test_stack_overflow_m1.py @@ -145,7 +145,6 @@ def test_stack_overflow_m1( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) contract_0 = pre.fund_eoa(amount=0xE8D4A5100000000000) # noqa: F841 diff --git a/tests/ported_static/stStackTests/test_stack_overflow_m1_dup.py b/tests/ported_static/stStackTests/test_stack_overflow_m1_dup.py index 53569c363c8..9b581251903 100644 --- a/tests/ported_static/stStackTests/test_stack_overflow_m1_dup.py +++ b/tests/ported_static/stStackTests/test_stack_overflow_m1_dup.py @@ -145,7 +145,6 @@ def test_stack_overflow_m1_dup( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) contract_0 = pre.fund_eoa(amount=0xE8D4A5100000000000) # noqa: F841 diff --git a/tests/ported_static/stStackTests/test_stack_overflow_swap.py b/tests/ported_static/stStackTests/test_stack_overflow_swap.py index 8cdda0a7279..8578d8b15a8 100644 --- a/tests/ported_static/stStackTests/test_stack_overflow_swap.py +++ b/tests/ported_static/stStackTests/test_stack_overflow_swap.py @@ -39,7 +39,6 @@ def test_stack_overflow_swap( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) contract_0 = pre.fund_eoa(amount=0xE8D4A5100000000000) # noqa: F841 diff --git a/tests/ported_static/stStackTests/test_stacksanity_swap.py b/tests/ported_static/stStackTests/test_stacksanity_swap.py index d37c14c48a5..05d12d07ca9 100644 --- a/tests/ported_static/stStackTests/test_stacksanity_swap.py +++ b/tests/ported_static/stStackTests/test_stacksanity_swap.py @@ -39,7 +39,6 @@ def test_stacksanity_swap( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=42949672960, ) contract_0 = pre.fund_eoa(amount=0xE8D4A5100000000000) # noqa: F841 diff --git a/tests/ported_static/stStaticCall/test_static_ab_acalls3.py b/tests/ported_static/stStaticCall/test_static_ab_acalls3.py index 7e32882c2b8..0a20f9a241f 100644 --- a/tests/ported_static/stStaticCall/test_static_ab_acalls3.py +++ b/tests/ported_static/stStaticCall/test_static_ab_acalls3.py @@ -66,7 +66,6 @@ def test_static_ab_acalls3( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=1000000000, ) # Source: lll diff --git a/tests/ported_static/stStaticCall/test_static_call10.py b/tests/ported_static/stStaticCall/test_static_call10.py index 36c2f6bf4e0..6329bb04db1 100644 --- a/tests/ported_static/stStaticCall/test_static_call10.py +++ b/tests/ported_static/stStaticCall/test_static_call10.py @@ -66,7 +66,6 @@ def test_static_call10( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) addr = pre.fund_eoa(amount=7000) # noqa: F841 @@ -171,6 +170,8 @@ def test_static_call10( Hash(addr_3, left_padding=True), ] tx_gas = [200000] + if fork.is_eip_enabled(8037): + tx_gas[0] += 4 * Op.SSTORE(new_value=1).state_cost(fork) tx_value = [10] tx = Transaction( diff --git a/tests/ported_static/stStaticCall/test_static_call1024_oog.py b/tests/ported_static/stStaticCall/test_static_call1024_oog.py index bdb6507da01..160aca4a1a3 100644 --- a/tests/ported_static/stStaticCall/test_static_call1024_oog.py +++ b/tests/ported_static/stStaticCall/test_static_call1024_oog.py @@ -66,7 +66,6 @@ def test_static_call1024_oog( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) addr = pre.fund_eoa(amount=7000) # noqa: F841 diff --git a/tests/ported_static/stStaticCall/test_static_call_contract_to_create_contract_oog.py b/tests/ported_static/stStaticCall/test_static_call_contract_to_create_contract_oog.py index cdf25c71569..0c32de25970 100644 --- a/tests/ported_static/stStaticCall/test_static_call_contract_to_create_contract_oog.py +++ b/tests/ported_static/stStaticCall/test_static_call_contract_to_create_contract_oog.py @@ -123,6 +123,10 @@ def test_static_call_contract_to_create_contract_oog( Bytes(""), ] tx_gas = [100000] + if fork.is_eip_enabled(8037): + tx_gas[0] += fork.gas_costs().NEW_ACCOUNT + Op.SSTORE( + new_value=1 + ).state_cost(fork) tx_value = [0, 1] tx = Transaction( diff --git a/tests/ported_static/stStaticCall/test_static_call_contract_to_create_contract_which_would_create_contract_if_called.py b/tests/ported_static/stStaticCall/test_static_call_contract_to_create_contract_which_would_create_contract_if_called.py index f36150e739a..c95058ae441 100644 --- a/tests/ported_static/stStaticCall/test_static_call_contract_to_create_contract_which_would_create_contract_if_called.py +++ b/tests/ported_static/stStaticCall/test_static_call_contract_to_create_contract_which_would_create_contract_if_called.py @@ -16,6 +16,7 @@ Transaction, compute_create_address, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -33,6 +34,7 @@ def test_static_call_contract_to_create_contract_which_would_create_contract_if_called( # noqa: E501 state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_static_call_contract_to_create_contract_which_would_create_con...""" # noqa: E501 coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) @@ -72,11 +74,16 @@ def test_static_call_contract_to_create_contract_which_would_create_contract_if_ address=Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87), # noqa: E501 ) + gas_limit = 300000 + if fork.is_eip_enabled(8037): + gas_limit += fork.gas_costs().NEW_ACCOUNT + 3 * Op.SSTORE( + new_value=1 + ).state_cost(fork) tx = Transaction( sender=sender, to=contract_0, data=Bytes(""), - gas_limit=300000, + gas_limit=gas_limit, ) post = { diff --git a/tests/ported_static/stStaticCall/test_static_call_lose_gas_oog.py b/tests/ported_static/stStaticCall/test_static_call_lose_gas_oog.py index da74a333e31..03175237c4a 100644 --- a/tests/ported_static/stStaticCall/test_static_call_lose_gas_oog.py +++ b/tests/ported_static/stStaticCall/test_static_call_lose_gas_oog.py @@ -15,6 +15,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,6 +31,7 @@ def test_static_call_lose_gas_oog( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_static_call_lose_gas_oog.""" coinbase = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B) @@ -69,11 +71,14 @@ def test_static_call_lose_gas_oog( address=Address(0x7F04B68576FC8573ABDC49251B804F6CB44617CE), # noqa: E501 ) + gas_limit = 200000 + if fork.is_eip_enabled(8037): + gas_limit += 2 * Op.SSTORE(new_value=1).state_cost(fork) tx = Transaction( sender=sender, to=target, data=Bytes(""), - gas_limit=200000, + gas_limit=gas_limit, value=10, ) diff --git a/tests/ported_static/stStaticCall/test_static_callcallcodecall_abcb_recursive.py b/tests/ported_static/stStaticCall/test_static_callcallcodecall_abcb_recursive.py index da1bee5f6fd..45155fabfbf 100644 --- a/tests/ported_static/stStaticCall/test_static_callcallcodecall_abcb_recursive.py +++ b/tests/ported_static/stStaticCall/test_static_callcallcodecall_abcb_recursive.py @@ -43,7 +43,6 @@ def test_static_callcallcodecall_abcb_recursive( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=3000000000, ) # Source: lll diff --git a/tests/ported_static/stStaticCall/test_static_callcallcodecall_abcb_recursive2.py b/tests/ported_static/stStaticCall/test_static_callcallcodecall_abcb_recursive2.py index c85fe757e18..6492fb2f64d 100644 --- a/tests/ported_static/stStaticCall/test_static_callcallcodecall_abcb_recursive2.py +++ b/tests/ported_static/stStaticCall/test_static_callcallcodecall_abcb_recursive2.py @@ -43,7 +43,6 @@ def test_static_callcallcodecall_abcb_recursive2( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=3000000000, ) # Source: lll diff --git a/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_abcb_recursive.py b/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_abcb_recursive.py index 2d76b12e3ba..8b738b3eae6 100644 --- a/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_abcb_recursive.py +++ b/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_abcb_recursive.py @@ -43,7 +43,6 @@ def test_static_callcallcodecallcode_abcb_recursive( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=3000000000, ) # Source: lll diff --git a/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_abcb_recursive2.py b/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_abcb_recursive2.py index 606cc541ecf..20838587d8e 100644 --- a/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_abcb_recursive2.py +++ b/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_abcb_recursive2.py @@ -43,7 +43,6 @@ def test_static_callcallcodecallcode_abcb_recursive2( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=3000000000, ) # Source: lll diff --git a/tests/ported_static/stStaticCall/test_static_callcode_check_pc.py b/tests/ported_static/stStaticCall/test_static_callcode_check_pc.py index c05cb8532bc..51384b88362 100644 --- a/tests/ported_static/stStaticCall/test_static_callcode_check_pc.py +++ b/tests/ported_static/stStaticCall/test_static_callcode_check_pc.py @@ -41,7 +41,6 @@ def test_static_callcode_check_pc( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=3000000000, ) # Source: lll diff --git a/tests/ported_static/stStaticCall/test_static_callcodecallcall_abcb_recursive.py b/tests/ported_static/stStaticCall/test_static_callcodecallcall_abcb_recursive.py index 81fd2f9a0c9..62754e5c500 100644 --- a/tests/ported_static/stStaticCall/test_static_callcodecallcall_abcb_recursive.py +++ b/tests/ported_static/stStaticCall/test_static_callcodecallcall_abcb_recursive.py @@ -43,7 +43,6 @@ def test_static_callcodecallcall_abcb_recursive( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=3000000000, ) # Source: lll diff --git a/tests/ported_static/stStaticCall/test_static_callcodecallcall_abcb_recursive2.py b/tests/ported_static/stStaticCall/test_static_callcodecallcall_abcb_recursive2.py index 4e7a099956b..e053ee992ac 100644 --- a/tests/ported_static/stStaticCall/test_static_callcodecallcall_abcb_recursive2.py +++ b/tests/ported_static/stStaticCall/test_static_callcodecallcall_abcb_recursive2.py @@ -65,7 +65,6 @@ def test_static_callcodecallcall_abcb_recursive2( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=3000000000, ) # Source: lll diff --git a/tests/ported_static/stStaticCall/test_static_callcodecallcallcode_101_oogm_after_3.py b/tests/ported_static/stStaticCall/test_static_callcodecallcallcode_101_oogm_after_3.py index 7b3110cfe17..d1005686e33 100644 --- a/tests/ported_static/stStaticCall/test_static_callcodecallcallcode_101_oogm_after_3.py +++ b/tests/ported_static/stStaticCall/test_static_callcodecallcallcode_101_oogm_after_3.py @@ -243,6 +243,8 @@ def test_static_callcodecallcallcode_101_oogm_after_3( Hash(addr_5, left_padding=True), ] tx_gas = [172000] + if fork.is_eip_enabled(8037): + tx_gas[0] += 7 * Op.SSTORE(new_value=1).state_cost(fork) tx = Transaction( sender=sender, diff --git a/tests/ported_static/stStaticCall/test_static_callcodecallcallcode_abcb_recursive.py b/tests/ported_static/stStaticCall/test_static_callcodecallcallcode_abcb_recursive.py index 4c89a5d22f3..07a2adedaaf 100644 --- a/tests/ported_static/stStaticCall/test_static_callcodecallcallcode_abcb_recursive.py +++ b/tests/ported_static/stStaticCall/test_static_callcodecallcallcode_abcb_recursive.py @@ -43,7 +43,6 @@ def test_static_callcodecallcallcode_abcb_recursive( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=3000000000, ) # Source: lll diff --git a/tests/ported_static/stStaticCall/test_static_callcodecallcallcode_abcb_recursive2.py b/tests/ported_static/stStaticCall/test_static_callcodecallcallcode_abcb_recursive2.py index 508e0dcf4ae..8990a609009 100644 --- a/tests/ported_static/stStaticCall/test_static_callcodecallcallcode_abcb_recursive2.py +++ b/tests/ported_static/stStaticCall/test_static_callcodecallcallcode_abcb_recursive2.py @@ -77,7 +77,6 @@ def test_static_callcodecallcallcode_abcb_recursive2( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=3000000000, ) # Source: lll diff --git a/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_110_suicide_end.py b/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_110_suicide_end.py index 773ac82c6cf..8cf7a6a5d41 100644 --- a/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_110_suicide_end.py +++ b/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_110_suicide_end.py @@ -13,6 +13,7 @@ Bytes, Environment, StateTestFiller, + Storage, Transaction, ) from execution_testing.forks import Fork @@ -146,6 +147,10 @@ def test_static_callcodecallcodecall_110_suicide_end( value=tx_value[v], ) - post = {target: Account(storage={0: 1, 1: 0x2CEC03}, balance=0, nonce=0)} + target_storage = Storage.model_validate({0: 1, 1: 0x2CEC03}) + if fork.is_eip_enabled(8037): + target_storage = Storage.model_validate({0: 1}) + target_storage.set_expect_any(1) + post = {target: Account(storage=target_storage, balance=0, nonce=0)} state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_110_suicide_end2.py b/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_110_suicide_end2.py index 6843361d732..a72cbc5d33e 100644 --- a/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_110_suicide_end2.py +++ b/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_110_suicide_end2.py @@ -13,6 +13,7 @@ Bytes, Environment, StateTestFiller, + Storage, Transaction, ) from execution_testing.forks import Fork @@ -137,26 +138,43 @@ def test_static_callcodecallcodecall_110_suicide_end2( address=Address(0xB7770360E0B87603E3D9C87C866451760C95ABCA), # noqa: E501 ) - expect_entries_: list[dict] = [ - { - "indexes": {"data": -1, "gas": -1, "value": 0}, - "network": [">=Cancun"], - "result": { - target: Account( - storage={0: 1, 1: 0x2CEBFF}, balance=0, nonce=0 - ) + if fork.is_eip_enabled(8037): + target_storage = Storage.model_validate({0: 1}) + target_storage.set_expect_any(1) + expect_entries_: list[dict] = [ + { + "indexes": {"data": -1, "gas": -1, "value": -1}, + "network": [">=Cancun"], + "result": { + target: Account(storage=target_storage, balance=0, nonce=0) + }, }, - }, - { - "indexes": {"data": -1, "gas": -1, "value": 1}, - "network": [">=Cancun"], - "result": { - target: Account( - storage={0: 1, 1: 0x2CB7A7}, balance=0, nonce=0 - ) + ] + else: + expect_entries_ = [ + { + "indexes": {"data": -1, "gas": -1, "value": 0}, + "network": [">=Cancun"], + "result": { + target: Account( + storage={0: 1, 1: 0x2CEBFF}, + balance=0, + nonce=0, + ) + }, }, - }, - ] + { + "indexes": {"data": -1, "gas": -1, "value": 1}, + "network": [">=Cancun"], + "result": { + target: Account( + storage={0: 1, 1: 0x2CB7A7}, + balance=0, + nonce=0, + ) + }, + }, + ] post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork) diff --git a/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_abcb_recursive.py b/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_abcb_recursive.py index 31a0c89588f..289cc93694c 100644 --- a/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_abcb_recursive.py +++ b/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_abcb_recursive.py @@ -43,7 +43,6 @@ def test_static_callcodecallcodecall_abcb_recursive( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=3000000000, ) # Source: lll diff --git a/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_abcb_recursive2.py b/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_abcb_recursive2.py index 481fa2e4dba..c4152198e6a 100644 --- a/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_abcb_recursive2.py +++ b/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_abcb_recursive2.py @@ -65,7 +65,6 @@ def test_static_callcodecallcodecall_abcb_recursive2( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=3000000000, ) # Source: lll diff --git a/tests/ported_static/stStaticCall/test_static_check_opcodes.py b/tests/ported_static/stStaticCall/test_static_check_opcodes.py index c909ed7c8c5..58ef0d469dc 100644 --- a/tests/ported_static/stStaticCall/test_static_check_opcodes.py +++ b/tests/ported_static/stStaticCall/test_static_check_opcodes.py @@ -269,6 +269,8 @@ def test_static_check_opcodes( Hash(addr_2, left_padding=True), ] tx_gas = [50000, 335000] + if fork.is_eip_enabled(8037): + tx_gas = [g + Op.SSTORE(new_value=1).state_cost(fork) for g in tx_gas] tx_value = [0, 100] tx = Transaction( diff --git a/tests/ported_static/stStaticCall/test_static_check_opcodes5.py b/tests/ported_static/stStaticCall/test_static_check_opcodes5.py index 253610f1dc7..74c6fd24a52 100644 --- a/tests/ported_static/stStaticCall/test_static_check_opcodes5.py +++ b/tests/ported_static/stStaticCall/test_static_check_opcodes5.py @@ -177,7 +177,6 @@ def test_static_check_opcodes5( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) pre[sender] = Account(balance=0xE8D4A51000) diff --git a/tests/ported_static/stStaticCall/test_static_contract_creation_make_call_that_ask_more_gas_then_transaction_provided.py b/tests/ported_static/stStaticCall/test_static_contract_creation_make_call_that_ask_more_gas_then_transaction_provided.py index 82b1fe9027c..2ba50e12f09 100644 --- a/tests/ported_static/stStaticCall/test_static_contract_creation_make_call_that_ask_more_gas_then_transaction_provided.py +++ b/tests/ported_static/stStaticCall/test_static_contract_creation_make_call_that_ask_more_gas_then_transaction_provided.py @@ -79,7 +79,13 @@ def test_static_contract_creation_make_call_that_ask_more_gas_then_transaction_p contract_4 = Address(0x4000000000000000000000000000000000000001) contract_5 = Address(0x5000000000000000000000000000000000000001) contract_6 = Address(0x4000000000000000000000000000000000000004) - sender = pre.fund_eoa(amount=0x10C8E0) + sender_amount = 0x10C8E0 + if fork.is_eip_enabled(8037): + sender_amount += ( + fork.gas_costs().NEW_ACCOUNT + + Op.SSTORE(new_value=1).state_cost(fork) + ) * 10 + sender = pre.fund_eoa(amount=sender_amount) env = Environment( fee_recipient=coinbase, @@ -250,6 +256,10 @@ def test_static_contract_creation_make_call_that_ask_more_gas_then_transaction_p ), ] tx_gas = [96000] + if fork.is_eip_enabled(8037): + tx_gas[0] += fork.gas_costs().NEW_ACCOUNT + Op.SSTORE( + new_value=1 + ).state_cost(fork) tx = Transaction( sender=sender, diff --git a/tests/ported_static/stStaticCall/test_static_contract_creation_oo_gdont_leave_empty_contract_via_transaction.py b/tests/ported_static/stStaticCall/test_static_contract_creation_oo_gdont_leave_empty_contract_via_transaction.py index eb6c5fd6f03..8b2e0e78cd5 100644 --- a/tests/ported_static/stStaticCall/test_static_contract_creation_oo_gdont_leave_empty_contract_via_transaction.py +++ b/tests/ported_static/stStaticCall/test_static_contract_creation_oo_gdont_leave_empty_contract_via_transaction.py @@ -15,6 +15,7 @@ Transaction, compute_create_address, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -32,13 +33,17 @@ def test_static_contract_creation_oo_gdont_leave_empty_contract_via_transaction( # noqa: E501 state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_static_contract_creation_oo_gdont_leave_empty_contract_via_tra...""" # noqa: E501 coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) contract_0 = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B) contract_1 = Address(0x1000000000000000000000000000000000000001) contract_2 = Address(0x2000000000000000000000000000000000000001) - sender = pre.fund_eoa(amount=0x10C8E0) + sender_amount = 0x10C8E0 + if fork.is_eip_enabled(8037): + sender_amount += fork.gas_costs().NEW_ACCOUNT * 10 + sender = pre.fund_eoa(amount=sender_amount) env = Environment( fee_recipient=coinbase, @@ -96,7 +101,11 @@ def test_static_contract_creation_oo_gdont_leave_empty_contract_via_transaction( ret_offset=0x0, ret_size=0x40, ), - gas_limit=96000, + gas_limit=( + 96000 + fork.gas_costs().NEW_ACCOUNT + if fork.is_eip_enabled(8037) + else 96000 + ), ) post = {compute_create_address(address=sender, nonce=0): Account(nonce=1)} diff --git a/tests/ported_static/stStaticCall/test_static_create_contract_suicide_during_init.py b/tests/ported_static/stStaticCall/test_static_create_contract_suicide_during_init.py index abdb26f1df3..da44ebdcca0 100644 --- a/tests/ported_static/stStaticCall/test_static_create_contract_suicide_during_init.py +++ b/tests/ported_static/stStaticCall/test_static_create_contract_suicide_during_init.py @@ -180,6 +180,10 @@ def test_static_create_contract_suicide_during_init( + Op.SELFDESTRUCT(address=contract_0), ] tx_gas = [150000] + if fork.is_eip_enabled(8037): + tx_gas[0] += fork.gas_costs().NEW_ACCOUNT + Op.SSTORE( + new_value=1 + ).state_cost(fork) tx = Transaction( sender=sender, diff --git a/tests/ported_static/stStaticCall/test_static_create_contract_suicide_during_init_with_value.py b/tests/ported_static/stStaticCall/test_static_create_contract_suicide_during_init_with_value.py index a094c7576c5..019ceb871e4 100644 --- a/tests/ported_static/stStaticCall/test_static_create_contract_suicide_during_init_with_value.py +++ b/tests/ported_static/stStaticCall/test_static_create_contract_suicide_during_init_with_value.py @@ -110,6 +110,10 @@ def test_static_create_contract_suicide_during_init_with_value( + Op.SELFDESTRUCT(address=contract_0), ] tx_gas = [150000] + if fork.is_eip_enabled(8037): + tx_gas[0] += fork.gas_costs().NEW_ACCOUNT + Op.SSTORE( + new_value=1 + ).state_cost(fork) tx_value = [10] tx = Transaction( diff --git a/tests/ported_static/stStaticCall/test_static_create_empty_contract_and_call_it_0wei.py b/tests/ported_static/stStaticCall/test_static_create_empty_contract_and_call_it_0wei.py index 06c2269aa0d..d426d271f6e 100644 --- a/tests/ported_static/stStaticCall/test_static_create_empty_contract_and_call_it_0wei.py +++ b/tests/ported_static/stStaticCall/test_static_create_empty_contract_and_call_it_0wei.py @@ -13,9 +13,11 @@ Bytes, Environment, StateTestFiller, + Storage, Transaction, compute_create_address, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -33,6 +35,7 @@ def test_static_create_empty_contract_and_call_it_0wei( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_static_create_empty_contract_and_call_it_0wei.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) @@ -78,16 +81,25 @@ def test_static_create_empty_contract_and_call_it_0wei( gas_limit=600000, ) - post = { - contract_0: Account( - storage={ + if fork.is_eip_enabled(8037): + contract_0_storage = Storage.model_validate( + {1: compute_create_address(address=contract_0, nonce=0), 3: 1} + ) + contract_0_storage.set_expect_any(0) + contract_0_storage.set_expect_any(2) + contract_0_storage.set_expect_any(100) + else: + contract_0_storage = Storage.model_validate( + { 0: 0x8D5B6, 1: compute_create_address(address=contract_0, nonce=0), 2: 0x7ABF8, 3: 1, 100: 0x6FE6E, - }, - ), + } + ) + post = { + contract_0: Account(storage=contract_0_storage), compute_create_address(address=contract_0, nonce=0): Account(nonce=1), } diff --git a/tests/ported_static/stStaticCall/test_static_create_empty_contract_with_storage_and_call_it_0wei.py b/tests/ported_static/stStaticCall/test_static_create_empty_contract_with_storage_and_call_it_0wei.py index 42399b955b1..91623e4efe9 100644 --- a/tests/ported_static/stStaticCall/test_static_create_empty_contract_with_storage_and_call_it_0wei.py +++ b/tests/ported_static/stStaticCall/test_static_create_empty_contract_with_storage_and_call_it_0wei.py @@ -13,9 +13,11 @@ Bytes, Environment, StateTestFiller, + Storage, Transaction, compute_create_address, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -33,6 +35,7 @@ def test_static_create_empty_contract_with_storage_and_call_it_0wei( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_static_create_empty_contract_with_storage_and_call_it_0wei.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) @@ -95,16 +98,25 @@ def test_static_create_empty_contract_with_storage_and_call_it_0wei( gas_limit=600000, ) - post = { - contract_0: Account( - storage={ + if fork.is_eip_enabled(8037): + contract_0_storage = Storage.model_validate( + {1: compute_create_address(address=contract_0, nonce=0), 3: 1} + ) + contract_0_storage.set_expect_any(0) + contract_0_storage.set_expect_any(2) + contract_0_storage.set_expect_any(100) + else: + contract_0_storage = Storage.model_validate( + { 0: 0x8D5B6, 1: compute_create_address(address=contract_0, nonce=0), 2: 0x6F4F0, 3: 1, 100: 0x64766, - }, - ), + } + ) + post = { + contract_0: Account(storage=contract_0_storage), compute_create_address(address=contract_0, nonce=0): Account(nonce=1), contract_1: Account(storage={1: 12}), } diff --git a/tests/ported_static/stStaticCall/test_static_return50000_2.py b/tests/ported_static/stStaticCall/test_static_return50000_2.py index fbcec0e7291..6817ccd604b 100644 --- a/tests/ported_static/stStaticCall/test_static_return50000_2.py +++ b/tests/ported_static/stStaticCall/test_static_return50000_2.py @@ -15,6 +15,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,6 +31,7 @@ def test_static_return50000_2( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_static_return50000_2.""" coinbase = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B) @@ -100,11 +102,14 @@ def test_static_return50000_2( nonce=0, ) + gas_limit = 15500000 + if fork.is_eip_enabled(8037): + gas_limit += 4 * Op.SSTORE(new_value=1).state_cost(fork) tx = Transaction( sender=sender, to=target, data=Bytes(""), - gas_limit=15500000, + gas_limit=gas_limit, value=10, ) diff --git a/tests/ported_static/stStaticCall/test_static_return_bounds.py b/tests/ported_static/stStaticCall/test_static_return_bounds.py index c11d3b559b2..b1711c7ba74 100644 --- a/tests/ported_static/stStaticCall/test_static_return_bounds.py +++ b/tests/ported_static/stStaticCall/test_static_return_bounds.py @@ -43,7 +43,6 @@ def test_static_return_bounds( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: lll diff --git a/tests/ported_static/stStaticCall/test_static_return_bounds_oog.py b/tests/ported_static/stStaticCall/test_static_return_bounds_oog.py index f6d2df13154..d3be7f1321f 100644 --- a/tests/ported_static/stStaticCall/test_static_return_bounds_oog.py +++ b/tests/ported_static/stStaticCall/test_static_return_bounds_oog.py @@ -68,7 +68,6 @@ def test_static_return_bounds_oog( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) # Source: lll diff --git a/tests/ported_static/stStaticCall/test_static_return_test2.py b/tests/ported_static/stStaticCall/test_static_return_test2.py index 6b38a20d22c..e1f5c591e19 100644 --- a/tests/ported_static/stStaticCall/test_static_return_test2.py +++ b/tests/ported_static/stStaticCall/test_static_return_test2.py @@ -43,7 +43,6 @@ def test_static_return_test2( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=1000000000, ) # Source: lll diff --git a/tests/ported_static/stStaticCall/test_staticcall_to_precompile_from_called_contract.py b/tests/ported_static/stStaticCall/test_staticcall_to_precompile_from_called_contract.py index 4331223b362..84ebae76f82 100644 --- a/tests/ported_static/stStaticCall/test_staticcall_to_precompile_from_called_contract.py +++ b/tests/ported_static/stStaticCall/test_staticcall_to_precompile_from_called_contract.py @@ -19,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -36,6 +37,7 @@ def test_staticcall_to_precompile_from_called_contract( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """STATICCALL to precompiled contracts from contract that called from...""" coinbase = Address(0xCAFE000000000000000000000000000000000001) @@ -359,11 +361,14 @@ def test_staticcall_to_precompile_from_called_contract( address=Address(0xB000000000000000000000000000000000000000), # noqa: E501 ) + gas_limit = 1000000 + if fork.is_eip_enabled(8037): + gas_limit += 22 * Op.SSTORE(new_value=1).state_cost(fork) tx = Transaction( sender=sender, to=contract_0, data=Bytes(""), - gas_limit=1000000, + gas_limit=gas_limit, value=100, ) diff --git a/tests/ported_static/stStaticCall/test_staticcall_to_precompile_from_contract_initialization.py b/tests/ported_static/stStaticCall/test_staticcall_to_precompile_from_contract_initialization.py index 1357cf578a1..0713b608520 100644 --- a/tests/ported_static/stStaticCall/test_staticcall_to_precompile_from_contract_initialization.py +++ b/tests/ported_static/stStaticCall/test_staticcall_to_precompile_from_contract_initialization.py @@ -19,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -36,6 +37,7 @@ def test_staticcall_to_precompile_from_contract_initialization( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """STATICCALL to precompiled contracts from contract initialization code.""" # noqa: E501 coinbase = Address(0xCAFE000000000000000000000000000000000001) @@ -81,7 +83,13 @@ def test_staticcall_to_precompile_from_contract_initialization( data=Bytes( "7f18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c600052601c6020527f73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75f6040527feeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c454960605260206103e860806000600162061a80fa60005560a060020a6103e851066001556001543214600255600060005260006020526000604052600060605260006103e8527c0ccccccccccccccccccccccccccccccccccccccccccccccccccc00000060005260206103e86020600060025afa6003556000516004556103e851600555600060005260006103e8527c0ccccccccccccccccccccccccccccccccccccccccccccccccccc00000060005260206103e86020600060035afa6006556000516007556103e851600855600060005260006103e8527c0ccccccccccccccccccccccccccccccccccccccccccccccccccc00000060005260206103e86020600060045afa6009556103e851601055600060005260006103e8526001600052602060205260206040527f03fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc6060527f2efffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc6080527f2f0000000000000000000000000000000000000000000000000000000000000060a05260206103e860a1600060055afa6011556103e85160125560006000526000602052600060405260006060526000608052600060a05260006103e8527f0f25929bcb43d5a57391564615c9e70a992b10eafa4db109709649cf48c50dd26000527f16da2f5cb6be7a0aa72c440c53c9bbdfec6c36c7d515536431b3a865468acbba6020527f1de49a4b0233273bba8146af82042d004f2085ec982397db0d97da17204cc2866040527f0217327ffc463919bef80cc166d09c6172639d8589799928761bcd9f22c903d460605260406103e86080600060065afa6013556103e85160145561040851601555600060005260006020526000604052600060605260006103e8526000610408527f0f25929bcb43d5a57391564615c9e70a992b10eafa4db109709649cf48c50dd26000527f16da2f5cb6be7a0aa72c440c53c9bbdfec6c36c7d515536431b3a865468acbba602052600360405260406103e86060600060075afa6016556103e8516017556104085160185560006000526000602052600060405260006103e8526000610408527f1c76476f4def4bb94541d57ebba1193381ffa7aa76ada664dd31c16024c43f596000527f3034dd2920f673e204fee2811c678745fc819b55d3e9d294e45c9b03a76aef416020527f209dd15ebff5d46c4bd888e51a93cf99a7329636c63514396b4a452003a35bf76040527f04bf11ca01483bfa8b34b43561848d28905960114c8ac04049af4b6315a416786060527f2bb8324af6cfc93537a2ad1a445cfd0ca2a71acd7ac41fadbf933c2a51be344d6080527f120a2a4cf30c1bf9845f20c6fe39e07ea2cce61f0c9bb048165fe5e4de87755060a0527f111e129f1cf1097710d41c4ac70fcdfa5ba2023c6ff1cbeac322de49d1b6df7c60c0527f2032c61a830e3c17286de9462bf242fca2883585b93870a73853face6a6bf41160e0527f198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2610100527f1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed610120527f090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b610140527f12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa6101605260206103e8610180600060085afa6019556103e85160205500" # noqa: E501 ), - gas_limit=1000000, + gas_limit=( + 1000000 + + fork.gas_costs().NEW_ACCOUNT + + 23 * Op.SSTORE(new_value=1).state_cost(fork) + if fork.is_eip_enabled(8037) + else 1000000 + ), value=100, ) diff --git a/tests/ported_static/stStaticCall/test_staticcall_to_precompile_from_transaction.py b/tests/ported_static/stStaticCall/test_staticcall_to_precompile_from_transaction.py index dd0bad3437f..22ff07d3369 100644 --- a/tests/ported_static/stStaticCall/test_staticcall_to_precompile_from_transaction.py +++ b/tests/ported_static/stStaticCall/test_staticcall_to_precompile_from_transaction.py @@ -19,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -36,6 +37,7 @@ def test_staticcall_to_precompile_from_transaction( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """STATICCALL to precompiled contracts from transaction code.""" coinbase = Address(0xCAFE000000000000000000000000000000000001) @@ -337,11 +339,14 @@ def test_staticcall_to_precompile_from_transaction( address=Address(0xA000000000000000000000000000000000000000), # noqa: E501 ) + gas_limit = 1000000 + if fork.is_eip_enabled(8037): + gas_limit += 21 * Op.SSTORE(new_value=1).state_cost(fork) tx = Transaction( sender=sender, to=contract_0, data=Bytes(""), - gas_limit=1000000, + gas_limit=gas_limit, value=100, ) diff --git a/tests/ported_static/stStaticFlagEnabled/test_callcode_to_precompile_from_called_contract.py b/tests/ported_static/stStaticFlagEnabled/test_callcode_to_precompile_from_called_contract.py index e9608c8f28e..75dcfd80640 100644 --- a/tests/ported_static/stStaticFlagEnabled/test_callcode_to_precompile_from_called_contract.py +++ b/tests/ported_static/stStaticFlagEnabled/test_callcode_to_precompile_from_called_contract.py @@ -18,9 +18,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -36,6 +38,7 @@ @pytest.mark.pre_alloc_mutable def test_callcode_to_precompile_from_called_contract( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Contract C calls contract B.""" @@ -615,7 +618,7 @@ def test_callcode_to_precompile_from_called_contract( sender=sender, to=contract_0, data=Bytes(""), - gas_limit=4000000, + gas_limit=6000000 if fork >= Amsterdam else 4000000, value=100, ) diff --git a/tests/ported_static/stStaticFlagEnabled/test_callcode_to_precompile_from_contract_initialization.py b/tests/ported_static/stStaticFlagEnabled/test_callcode_to_precompile_from_contract_initialization.py index ae2ff697bbd..8385f94ee28 100644 --- a/tests/ported_static/stStaticFlagEnabled/test_callcode_to_precompile_from_contract_initialization.py +++ b/tests/ported_static/stStaticFlagEnabled/test_callcode_to_precompile_from_contract_initialization.py @@ -18,9 +18,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -36,6 +38,7 @@ @pytest.mark.pre_alloc_mutable def test_callcode_to_precompile_from_contract_initialization( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Contract B creates new contract.""" @@ -515,7 +518,7 @@ def test_callcode_to_precompile_from_contract_initialization( data=Bytes( "7ffeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeed60005562012020620a00006000600073a0000000000000000000000000000000000000005afa507ffeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeed600155620a000051610a0055620b000051610b0055620a010051610a0155620b010051610b0155620a020051610a0255620b020051610b0255620a030051610a0355620b030051610b0355620a040051610a0455620b040051610b0455620a050051610a0555620b050051610b0555620a060051610a0655620b060051610b0655620a070051610a0755620b070051610b0755620a080051610a0855620b080051610b0855620a090051610a0955620b090051610b0955620a100051610a1055620b100051610b1055620a110051610a1155620b110051610b1155620a120051610a1255620b120051610b1255620a130051610a1355620b130051610b1355620a140051610a1455620b140051610b1455620a150051610a1555620b150051610b1555620a160051610a1655620b160051610b1655620a170051610a1755620b170051610b1755620a180051610a1855620b180051610b1855620a190051610a1955620b190051610b1955620a200051610a2055620b200051610b205500" # noqa: E501 ), - gas_limit=4000000, + gas_limit=6000000 if fork >= Amsterdam else 4000000, value=100, ) diff --git a/tests/ported_static/stStaticFlagEnabled/test_callcode_to_precompile_from_transaction.py b/tests/ported_static/stStaticFlagEnabled/test_callcode_to_precompile_from_transaction.py index 634fa6832d9..21124fa254f 100644 --- a/tests/ported_static/stStaticFlagEnabled/test_callcode_to_precompile_from_transaction.py +++ b/tests/ported_static/stStaticFlagEnabled/test_callcode_to_precompile_from_transaction.py @@ -17,9 +17,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -35,6 +37,7 @@ @pytest.mark.pre_alloc_mutable def test_callcode_to_precompile_from_transaction( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Contract B staticcalls contract A.""" @@ -578,7 +581,7 @@ def test_callcode_to_precompile_from_transaction( sender=sender, to=contract_0, data=Bytes(""), - gas_limit=4000000, + gas_limit=6000000 if fork >= Amsterdam else 4000000, value=100, ) diff --git a/tests/ported_static/stSystemOperationsTest/test_call10.py b/tests/ported_static/stSystemOperationsTest/test_call10.py index dd81ff55302..9ae1eb3184d 100644 --- a/tests/ported_static/stSystemOperationsTest/test_call10.py +++ b/tests/ported_static/stSystemOperationsTest/test_call10.py @@ -40,7 +40,6 @@ def test_call10( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=9223372036854775807, ) addr = pre.fund_eoa(amount=7000) # noqa: F841 diff --git a/tests/ported_static/stSystemOperationsTest/test_call_to_name_registrator0.py b/tests/ported_static/stSystemOperationsTest/test_call_to_name_registrator0.py index 891c3e8626a..0d739ebcddb 100644 --- a/tests/ported_static/stSystemOperationsTest/test_call_to_name_registrator0.py +++ b/tests/ported_static/stSystemOperationsTest/test_call_to_name_registrator0.py @@ -3,6 +3,10 @@ Ported from: state_tests/stSystemOperationsTest/CallToNameRegistrator0Filler.json +@manually-enhanced: Do not overwrite. Gas bumped fork-conditionally +to cover EIP-8037 state-gas spill into regular gas; pre-EIP-8037 +behavior unchanged. + """ import pytest @@ -15,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +34,14 @@ def test_call_to_name_registrator0( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_call_to_name_registrator0.""" + # EIP-8037 gas bumps: original values for pre-EIP-8037 forks. + inner_call_gas = 100000 + if fork.is_eip_enabled(8037): + inner_call_gas = 1000000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -72,7 +83,7 @@ def test_call_to_name_registrator0( + Op.SSTORE( key=0x0, value=Op.CALL( - gas=0x186A0, + gas=inner_call_gas, address=addr, value=0x17, args_offset=0x0, diff --git a/tests/ported_static/stSystemOperationsTest/test_call_to_name_registrator_zeor_size_mem_expansion.py b/tests/ported_static/stSystemOperationsTest/test_call_to_name_registrator_zeor_size_mem_expansion.py index 9fabe5dbf41..885556214a2 100644 --- a/tests/ported_static/stSystemOperationsTest/test_call_to_name_registrator_zeor_size_mem_expansion.py +++ b/tests/ported_static/stSystemOperationsTest/test_call_to_name_registrator_zeor_size_mem_expansion.py @@ -67,7 +67,6 @@ def test_call_to_name_registrator_zeor_size_mem_expansion( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) # Source: raw diff --git a/tests/ported_static/stSystemOperationsTest/test_callcode_to_name_registrator_zero_mem_expanion.py b/tests/ported_static/stSystemOperationsTest/test_callcode_to_name_registrator_zero_mem_expanion.py index e00c3e9847b..27617fd5e2e 100644 --- a/tests/ported_static/stSystemOperationsTest/test_callcode_to_name_registrator_zero_mem_expanion.py +++ b/tests/ported_static/stSystemOperationsTest/test_callcode_to_name_registrator_zero_mem_expanion.py @@ -67,7 +67,6 @@ def test_callcode_to_name_registrator_zero_mem_expanion( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) # Source: raw diff --git a/tests/ported_static/stSystemOperationsTest/test_callcode_to_return1.py b/tests/ported_static/stSystemOperationsTest/test_callcode_to_return1.py index 0b59d077378..6043500f367 100644 --- a/tests/ported_static/stSystemOperationsTest/test_callcode_to_return1.py +++ b/tests/ported_static/stSystemOperationsTest/test_callcode_to_return1.py @@ -3,6 +3,10 @@ Ported from: state_tests/stSystemOperationsTest/callcodeToReturn1Filler.json +@manually-enhanced: Do not overwrite. Gas bumped fork-conditionally +to cover EIP-8037 state-gas spill into regular gas; pre-EIP-8037 +behavior unchanged. + """ import pytest @@ -15,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +34,14 @@ def test_callcode_to_return1( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_callcode_to_return1.""" + # EIP-8037 gas bumps: original values for pre-EIP-8037 forks. + inner_call_gas = 50000 + if fork.is_eip_enabled(8037): + inner_call_gas = 1000000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -66,7 +77,7 @@ def test_callcode_to_return1( + Op.SSTORE( key=0x0, value=Op.CALLCODE( - gas=0xC350, + gas=inner_call_gas, address=addr, value=0x17, args_offset=0x0, diff --git a/tests/ported_static/stSystemOperationsTest/test_create_name_registrator.py b/tests/ported_static/stSystemOperationsTest/test_create_name_registrator.py index 8aceb541019..25f3523cd44 100644 --- a/tests/ported_static/stSystemOperationsTest/test_create_name_registrator.py +++ b/tests/ported_static/stSystemOperationsTest/test_create_name_registrator.py @@ -3,6 +3,9 @@ Ported from: state_tests/stSystemOperationsTest/createNameRegistratorFiller.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ Transaction, compute_create_address, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +34,14 @@ def test_create_name_registrator( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_create_name_registrator.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 300k tx_gas. + tx_gas_limit = 300000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 1_000_000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) contract_0 = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -64,7 +74,7 @@ def test_create_name_registrator( sender=sender, to=contract_0, data=Bytes(""), - gas_limit=300000, + gas_limit=tx_gas_limit, value=0x186A0, ) diff --git a/tests/ported_static/stSystemOperationsTest/test_create_name_registrator_zero_mem.py b/tests/ported_static/stSystemOperationsTest/test_create_name_registrator_zero_mem.py index fc00fef4328..5ac78d42aa9 100644 --- a/tests/ported_static/stSystemOperationsTest/test_create_name_registrator_zero_mem.py +++ b/tests/ported_static/stSystemOperationsTest/test_create_name_registrator_zero_mem.py @@ -3,6 +3,9 @@ Ported from: state_tests/stSystemOperationsTest/createNameRegistratorZeroMemFiller.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ Transaction, compute_create_address, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -32,8 +36,14 @@ def test_create_name_registrator_zero_mem( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_create_name_registrator_zero_mem.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 300k tx_gas. + tx_gas_limit = 300000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 1_000_000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) contract_0 = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -64,7 +74,7 @@ def test_create_name_registrator_zero_mem( sender=sender, to=contract_0, data=Bytes(""), - gas_limit=300000, + gas_limit=tx_gas_limit, value=0x186A0, ) diff --git a/tests/ported_static/stSystemOperationsTest/test_create_name_registrator_zero_mem2.py b/tests/ported_static/stSystemOperationsTest/test_create_name_registrator_zero_mem2.py index 874b1f517c4..2fada8818e4 100644 --- a/tests/ported_static/stSystemOperationsTest/test_create_name_registrator_zero_mem2.py +++ b/tests/ported_static/stSystemOperationsTest/test_create_name_registrator_zero_mem2.py @@ -3,6 +3,9 @@ Ported from: state_tests/stSystemOperationsTest/createNameRegistratorZeroMem2Filler.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ Transaction, compute_create_address, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -32,8 +36,14 @@ def test_create_name_registrator_zero_mem2( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_create_name_registrator_zero_mem2.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 300k tx_gas. + tx_gas_limit = 300000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 1_000_000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) contract_0 = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -71,7 +81,7 @@ def test_create_name_registrator_zero_mem2( sender=sender, to=contract_0, data=Bytes(""), - gas_limit=300000, + gas_limit=tx_gas_limit, value=0x186A0, ) diff --git a/tests/ported_static/stSystemOperationsTest/test_create_name_registrator_zero_mem_expansion.py b/tests/ported_static/stSystemOperationsTest/test_create_name_registrator_zero_mem_expansion.py index fa489f7aa9e..f7981ea400b 100644 --- a/tests/ported_static/stSystemOperationsTest/test_create_name_registrator_zero_mem_expansion.py +++ b/tests/ported_static/stSystemOperationsTest/test_create_name_registrator_zero_mem_expansion.py @@ -3,6 +3,9 @@ Ported from: state_tests/stSystemOperationsTest/createNameRegistratorZeroMemExpansionFiller.json +@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam +to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. + """ import pytest @@ -16,6 +19,7 @@ Transaction, compute_create_address, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -32,8 +36,14 @@ def test_create_name_registrator_zero_mem_expansion( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_create_name_registrator_zero_mem_expansion.""" + # EIP-8037 state-gas spill on Amsterdam exceeds 300k tx_gas. + tx_gas_limit = 300000 + if fork.is_eip_enabled(8037): + tx_gas_limit = 1_000_000 + coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) contract_0 = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -64,7 +74,7 @@ def test_create_name_registrator_zero_mem_expansion( sender=sender, to=contract_0, data=Bytes(""), - gas_limit=300000, + gas_limit=tx_gas_limit, value=0x186A0, ) diff --git a/tests/ported_static/stSystemOperationsTest/test_double_selfdestruct_test.py b/tests/ported_static/stSystemOperationsTest/test_double_selfdestruct_test.py index 49d06c563b2..39db66c2be4 100644 --- a/tests/ported_static/stSystemOperationsTest/test_double_selfdestruct_test.py +++ b/tests/ported_static/stSystemOperationsTest/test_double_selfdestruct_test.py @@ -113,7 +113,6 @@ def test_double_selfdestruct_test( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000000, ) pre[sender] = Account(balance=0xDE0B6B3A7640000, nonce=1) diff --git a/tests/ported_static/stSystemOperationsTest/test_extcodecopy.py b/tests/ported_static/stSystemOperationsTest/test_extcodecopy.py index 4e673f6ee76..1fffc7688a6 100644 --- a/tests/ported_static/stSystemOperationsTest/test_extcodecopy.py +++ b/tests/ported_static/stSystemOperationsTest/test_extcodecopy.py @@ -13,9 +13,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +31,7 @@ @pytest.mark.pre_alloc_mutable def test_extcodecopy( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """God knows what is happening in this test.""" @@ -146,7 +149,7 @@ def test_extcodecopy( data=Bytes( "6e27b0577f2549e5fa01e3db96e7b03a62e489115538620295677faf15040c1c1796bad130e2462a8b8d6bbe0fa35bf12087047ef4ff4e66df8772196b4401998ff7f4219c013a0d927b22d8d3fdf625809abb182507d180e687b666f4f1e4f3b8172e87760f436c701264b89739f3d7c50ec524f16b1a4f91397b760a5209b9b7710544694ecf2729643b3ca545c7" # noqa: E501 ), - gas_limit=100000, + gas_limit=2100000 if fork >= Amsterdam else 100000, value=0x24A39757, gas_price=483694712, ) diff --git a/tests/ported_static/stSystemOperationsTest/test_multi_selfdestruct.py b/tests/ported_static/stSystemOperationsTest/test_multi_selfdestruct.py index 3ce56c97495..004c4a8b668 100644 --- a/tests/ported_static/stSystemOperationsTest/test_multi_selfdestruct.py +++ b/tests/ported_static/stSystemOperationsTest/test_multi_selfdestruct.py @@ -88,7 +88,6 @@ def test_multi_selfdestruct( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=1000, - gas_limit=71794957647893862, ) # Source: yul diff --git a/tests/ported_static/stSystemOperationsTest/test_test_random_test.py b/tests/ported_static/stSystemOperationsTest/test_test_random_test.py index 4ce34dd1cf8..5952e5bc85b 100644 --- a/tests/ported_static/stSystemOperationsTest/test_test_random_test.py +++ b/tests/ported_static/stSystemOperationsTest/test_test_random_test.py @@ -13,9 +13,11 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +31,7 @@ @pytest.mark.pre_alloc_mutable def test_test_random_test( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_test_random_test.""" @@ -44,7 +47,7 @@ def test_test_random_test( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=1000000, + gas_limit=3000000 if fork >= Amsterdam else 1000000, ) pre[sender] = Account(balance=0xDE0B6B3A7640000) @@ -74,7 +77,7 @@ def test_test_random_test( sender=sender, to=contract_0, data=Bytes(""), - gas_limit=300000, + gas_limit=2300000 if fork >= Amsterdam else 300000, value=0x186A0, ) diff --git a/tests/ported_static/stTransactionTest/test_create_message_success.py b/tests/ported_static/stTransactionTest/test_create_message_success.py index 6f311c8c6e5..e73439924de 100644 --- a/tests/ported_static/stTransactionTest/test_create_message_success.py +++ b/tests/ported_static/stTransactionTest/test_create_message_success.py @@ -12,10 +12,12 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, compute_create_address, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +31,7 @@ @pytest.mark.pre_alloc_mutable def test_create_message_success( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_create_message_success.""" @@ -42,7 +45,6 @@ def test_create_message_success( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=1000000000000, ) # Source: lll @@ -58,7 +60,7 @@ def test_create_message_success( sender=sender, to=contract_0, data=Bytes(""), - gas_limit=131882, + gas_limit=2131882 if fork >= Amsterdam else 131882, value=100, ) diff --git a/tests/ported_static/stTransactionTest/test_create_transaction_success.py b/tests/ported_static/stTransactionTest/test_create_transaction_success.py index 522b2fdc141..9dcd98b12ff 100644 --- a/tests/ported_static/stTransactionTest/test_create_transaction_success.py +++ b/tests/ported_static/stTransactionTest/test_create_transaction_success.py @@ -11,10 +11,12 @@ Address, Alloc, Environment, + Fork, StateTestFiller, Transaction, compute_create_address, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -27,6 +29,7 @@ @pytest.mark.valid_from("Cancun") def test_create_transaction_success( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_create_transaction_success.""" @@ -39,7 +42,6 @@ def test_create_transaction_success( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=1000000000000, ) tx = Transaction( @@ -60,7 +62,7 @@ def test_create_transaction_success( + Op.RETURN(offset=0x0, size=0x0) + Op.JUMPDEST + Op.JUMP, - gas_limit=70000, + gas_limit=2070000 if fork >= Amsterdam else 70000, value=100, ) diff --git a/tests/ported_static/stTransactionTest/test_empty_transaction3.py b/tests/ported_static/stTransactionTest/test_empty_transaction3.py index 1fb6202c846..c72e88180f6 100644 --- a/tests/ported_static/stTransactionTest/test_empty_transaction3.py +++ b/tests/ported_static/stTransactionTest/test_empty_transaction3.py @@ -12,10 +12,12 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, compute_create_address, ) +from execution_testing.forks import Amsterdam REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" @@ -27,6 +29,7 @@ @pytest.mark.valid_from("Cancun") def test_empty_transaction3( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_empty_transaction3.""" @@ -39,14 +42,14 @@ def test_empty_transaction3( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=1000000, + gas_limit=3000000 if fork >= Amsterdam else 1000000, ) tx = Transaction( sender=sender, to=None, data=Bytes(""), - gas_limit=55000, + gas_limit=2055000 if fork >= Amsterdam else 55000, ) post = { diff --git a/tests/ported_static/stTransactionTest/test_internal_call_hitting_gas_limit2.py b/tests/ported_static/stTransactionTest/test_internal_call_hitting_gas_limit2.py index 78bcdfa205f..caa2d6817c9 100644 --- a/tests/ported_static/stTransactionTest/test_internal_call_hitting_gas_limit2.py +++ b/tests/ported_static/stTransactionTest/test_internal_call_hitting_gas_limit2.py @@ -40,7 +40,6 @@ def test_internal_call_hitting_gas_limit2( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=47766, ) # Source: lll diff --git a/tests/ported_static/stTransactionTest/test_internal_call_hitting_gas_limit_success.py b/tests/ported_static/stTransactionTest/test_internal_call_hitting_gas_limit_success.py index 89723d06162..7bdd73eb60c 100644 --- a/tests/ported_static/stTransactionTest/test_internal_call_hitting_gas_limit_success.py +++ b/tests/ported_static/stTransactionTest/test_internal_call_hitting_gas_limit_success.py @@ -3,6 +3,10 @@ Ported from: state_tests/stTransactionTest/InternalCallHittingGasLimitSuccessFiller.json +@manually-enhanced: Do not overwrite. Inner-CALL gas and outer tx gas +bumped on Amsterdam to cover EIP-8037 SSTORE-set state-gas spill; +pre-EIP-8037 unchanged. + """ import pytest @@ -15,6 +19,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +36,18 @@ def test_internal_call_hitting_gas_limit_success( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_internal_call_hitting_gas_limit_success.""" + # EIP-8037 SSTORE-set state-gas spill OoGs the 25k inner CALL. + inner_call_gas = 25000 + tx_gas_limit = 150000 + env_gas_limit = 220000 + if fork.is_eip_enabled(8037): + inner_call_gas = 200000 + tx_gas_limit = 500000 + env_gas_limit = 1_000_000 + coinbase = Address(0x2ADF5374FCE5EDBC8E2A8697C15331677E6EBF0B) sender = pre.fund_eoa(amount=0x3B9ACA00) @@ -42,7 +57,7 @@ def test_internal_call_hitting_gas_limit_success( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=220000, + gas_limit=env_gas_limit, ) # Source: lll @@ -55,7 +70,7 @@ def test_internal_call_hitting_gas_limit_success( # { (CALL 25000 1 0 0 0 0) } # noqa: E501 target = pre.deploy_contract( # noqa: F841 code=Op.CALL( - gas=0x61A8, + gas=inner_call_gas, address=addr, value=0x1, args_offset=0x0, @@ -71,7 +86,7 @@ def test_internal_call_hitting_gas_limit_success( sender=sender, to=target, data=Bytes(""), - gas_limit=150000, + gas_limit=tx_gas_limit, value=10, ) diff --git a/tests/ported_static/stTransactionTest/test_suicides_and_internal_call_suicides_oog.py b/tests/ported_static/stTransactionTest/test_suicides_and_internal_call_suicides_oog.py index 3e090d4ef5f..f04b8e5f655 100644 --- a/tests/ported_static/stTransactionTest/test_suicides_and_internal_call_suicides_oog.py +++ b/tests/ported_static/stTransactionTest/test_suicides_and_internal_call_suicides_oog.py @@ -42,7 +42,6 @@ def test_suicides_and_internal_call_suicides_oog( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=1000000, ) # Source: lll diff --git a/tests/ported_static/stTransactionTest/test_suicides_and_internal_call_suicides_success.py b/tests/ported_static/stTransactionTest/test_suicides_and_internal_call_suicides_success.py index d0240ef8265..661b972ec23 100644 --- a/tests/ported_static/stTransactionTest/test_suicides_and_internal_call_suicides_success.py +++ b/tests/ported_static/stTransactionTest/test_suicides_and_internal_call_suicides_success.py @@ -72,7 +72,6 @@ def test_suicides_and_internal_call_suicides_success( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) pre[sender] = Account(balance=0xABA9500) diff --git a/tests/ported_static/stTransactionTest/test_transaction_data_costs652.py b/tests/ported_static/stTransactionTest/test_transaction_data_costs652.py index 6e8dca69a78..b0f2bab81cd 100644 --- a/tests/ported_static/stTransactionTest/test_transaction_data_costs652.py +++ b/tests/ported_static/stTransactionTest/test_transaction_data_costs652.py @@ -15,7 +15,7 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork +from execution_testing.forks import Fork, Prague REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" @@ -66,7 +66,18 @@ def test_transaction_data_costs652( tx_data = [ Bytes("00000000000000000000112233445566778f32"), ] - tx_gas = [22000, 72000] + # EIP-7976 (enabled with EIP-8037 on Amsterdam) increases the + # calldata floor cost per byte, pushing the g0 budget below the + # new intrinsic. Shift gas_limits by the intrinsic delta versus + # the pre-7976 baseline so the tight / loose budgets still hold. + current_intrinsic = fork.transaction_intrinsic_cost_calculator()( + calldata=tx_data[d] + ) + baseline_intrinsic = Prague.transaction_intrinsic_cost_calculator()( + calldata=tx_data[d] + ) + intrinsic_delta = current_intrinsic - baseline_intrinsic + tx_gas = [22000 + intrinsic_delta, 72000 + intrinsic_delta] floor_cost = fork.transaction_data_floor_cost_calculator()(data=tx_data[d]) tx = Transaction( diff --git a/tests/ported_static/stTransactionTest/test_transaction_sending_to_empty.py b/tests/ported_static/stTransactionTest/test_transaction_sending_to_empty.py index b7ca2fb7c9e..b50c62cd0c4 100644 --- a/tests/ported_static/stTransactionTest/test_transaction_sending_to_empty.py +++ b/tests/ported_static/stTransactionTest/test_transaction_sending_to_empty.py @@ -12,10 +12,12 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, compute_create_address, ) +from execution_testing.forks import Amsterdam REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" @@ -27,6 +29,7 @@ @pytest.mark.valid_from("Cancun") def test_transaction_sending_to_empty( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_transaction_sending_to_empty.""" @@ -39,14 +42,14 @@ def test_transaction_sending_to_empty( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=1000000, + gas_limit=3000000 if fork >= Amsterdam else 1000000, ) tx = Transaction( sender=sender, to=None, data=Bytes(""), - gas_limit=53000, + gas_limit=2053000 if fork >= Amsterdam else 53000, ) post = { diff --git a/tests/ported_static/stTransitionTest/test_create_name_registrator_per_txs_after.py b/tests/ported_static/stTransitionTest/test_create_name_registrator_per_txs_after.py index 4819a4961d2..9dc24724099 100644 --- a/tests/ported_static/stTransitionTest/test_create_name_registrator_per_txs_after.py +++ b/tests/ported_static/stTransitionTest/test_create_name_registrator_per_txs_after.py @@ -11,10 +11,12 @@ Address, Alloc, Environment, + Fork, StateTestFiller, Transaction, compute_create_address, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +31,7 @@ @pytest.mark.valid_from("Cancun") def test_create_name_registrator_per_txs_after( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_create_name_registrator_per_txs_after.""" @@ -41,7 +44,6 @@ def test_create_name_registrator_per_txs_after( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000000, ) tx = Transaction( @@ -62,7 +64,7 @@ def test_create_name_registrator_per_txs_after( + Op.SSTORE( key=Op.CALLDATALOAD(offset=0x0), value=Op.CALLDATALOAD(offset=0x20) ), - gas_limit=200000, + gas_limit=2200000 if fork >= Amsterdam else 200000, value=0x186A0, ) diff --git a/tests/ported_static/stTransitionTest/test_create_name_registrator_per_txs_at.py b/tests/ported_static/stTransitionTest/test_create_name_registrator_per_txs_at.py index 3065cd7ade6..8af56d79f09 100644 --- a/tests/ported_static/stTransitionTest/test_create_name_registrator_per_txs_at.py +++ b/tests/ported_static/stTransitionTest/test_create_name_registrator_per_txs_at.py @@ -11,10 +11,12 @@ Address, Alloc, Environment, + Fork, StateTestFiller, Transaction, compute_create_address, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -27,6 +29,7 @@ @pytest.mark.valid_from("Cancun") def test_create_name_registrator_per_txs_at( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_create_name_registrator_per_txs_at.""" @@ -39,7 +42,6 @@ def test_create_name_registrator_per_txs_at( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000000, ) tx = Transaction( @@ -60,7 +62,7 @@ def test_create_name_registrator_per_txs_at( + Op.SSTORE( key=Op.CALLDATALOAD(offset=0x0), value=Op.CALLDATALOAD(offset=0x20) ), - gas_limit=200000, + gas_limit=2200000 if fork >= Amsterdam else 200000, value=0x186A0, ) diff --git a/tests/ported_static/stTransitionTest/test_create_name_registrator_per_txs_before.py b/tests/ported_static/stTransitionTest/test_create_name_registrator_per_txs_before.py index 6f4102cdf9c..19fd2679735 100644 --- a/tests/ported_static/stTransitionTest/test_create_name_registrator_per_txs_before.py +++ b/tests/ported_static/stTransitionTest/test_create_name_registrator_per_txs_before.py @@ -11,10 +11,12 @@ Address, Alloc, Environment, + Fork, StateTestFiller, Transaction, compute_create_address, ) +from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +31,7 @@ @pytest.mark.valid_from("Cancun") def test_create_name_registrator_per_txs_before( state_test: StateTestFiller, + fork: Fork, pre: Alloc, ) -> None: """Test_create_name_registrator_per_txs_before.""" @@ -41,7 +44,6 @@ def test_create_name_registrator_per_txs_before( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000000, ) tx = Transaction( @@ -62,7 +64,7 @@ def test_create_name_registrator_per_txs_before( + Op.SSTORE( key=Op.CALLDATALOAD(offset=0x0), value=Op.CALLDATALOAD(offset=0x20) ), - gas_limit=200000, + gas_limit=2200000 if fork >= Amsterdam else 200000, value=0x186A0, ) diff --git a/tests/ported_static/stWalletTest/test_day_limit_construction.py b/tests/ported_static/stWalletTest/test_day_limit_construction.py index 4206fa7b772..f2795bb1d53 100644 --- a/tests/ported_static/stWalletTest/test_day_limit_construction.py +++ b/tests/ported_static/stWalletTest/test_day_limit_construction.py @@ -3,6 +3,10 @@ Ported from: state_tests/stWalletTest/dayLimitConstructionFiller.json + +@manually-enhanced: Do not overwrite. Both `tx_gas` values bumped for +EIP-8037 NEW_ACCOUNT state-gas headroom on Amsterdam (the test has the +same post-state for both g indexes — both are 'should succeed' paths). """ import pytest @@ -75,7 +79,14 @@ def test_day_limit_construction( "606060409081526001600081815581805533600160a060020a0316600381905581526101026020529190912055620151804204610107556109b4806100456000396000f300606060405236156100985760e060020a6000350463173825d9811461009a5780632f54bf6e146100f65780634123cb6b1461011a5780635c52c2f5146101235780637065cb4814610154578063746c917114610188578063b20d30a914610191578063b75c7dc6146101c5578063ba51a6df146101f5578063c2cf732614610229578063f00d4b5d14610269578063f1736d86146102a2575b005b6100986004356000600036436040518084848082843750505090910190815260405190819003602001902090506105b9815b600160a060020a0333166000908152610102602052604081205481808083811415610719576108b0565b6102ac6004355b600160a060020a0316600090815261010260205260408120541190565b6102ac60015481565b6100986000364360405180848480828437505050909101908152604051908190036020019020905061070b816100cc565b61009860043560003643604051808484808284375050509091019081526040519081900360200190209050610531816100cc565b6102ac60005481565b610098600435600036436040518084848082843750505090910190815260405190819003602001902090506106ff816100cc565b610098600435600160a060020a03331660009081526101026020526040812054908080838114156102be57610340565b61009860043560003643604051808484808284375050509091019081526040519081900360200190209050610678816100cc565b6102ac600435602435600082815261010360209081526040808320600160a060020a0385168452610102909252822054829081818114156106d1576106f5565b6100986004356024356000600036436040518084848082843750505090910190815260405190819003602001902090506103ca816100cc565b6102ac6101055481565b60408051918252519081900360200190f35b5050506000828152610103602052604081206001810154600284900a92908316819011156103405781546001838101805492909101845590849003905560408051600160a060020a03331681526020810187905281517fc7fb647e59b18047309aa15aad418e5d7ca96d173ad704f1031a2c3d7591734b929181900390910190a15b5050505050565b600160a060020a038316600283610100811015610002570155600160a060020a0384811660008181526101026020908152604080832083905593871680835291849020869055835192835282015281517fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c929181900390910190a15b505b505050565b156103c3576103d8836100fd565b156103e357506103c5565b600160a060020a03841660009081526101026020526040812054925082141561040c57506103c5565b6103475b6101045460005b8181101561085f576101048054829081101561000257600091825260008051602061099483398151915201541461048a5761010480546101039160009184908110156100025760008051602061099483398151915201548252506020919091526040812081815560018101829055600201555b600101610417565b60018054810190819055600160a060020a038316906002906101008110156100025790900160005081905550600160005054610102600050600084600160a060020a03168152602001908152602001600020600050819055507f994a936646fe87ffe4f1e469d3d6aa417d6b855598397f323de5b449f765f0c3826040518082600160a060020a0316815260200191505060405180910390a15b505b50565b1561052c5761053f826100fd565b1561054a575061052e565b610552610410565b60015460fa90106105675761056561057c565b505b60015460fa9010610492575061052e565b6106365b600060015b600154811015610899575b600154811080156105ac5750600281610100811015610002570154600014155b156108b95760010161058c565b156103c557600160a060020a0383166000908152610102602052604081205492508214156105e7575061052c565b6001600160005054036000600050541115610602575061052c565b600060028361010081101561000257508301819055600160a060020a03841681526101026020526040812055610578610410565b5060408051600160a060020a038516815290517f58619076adf5bb0943d100ef88d52d7c3fd691b19d3a9071b555b651fbf418da9181900360200190a1505050565b1561052c5760015482111561068d575061052e565b600082905561069a610410565b6040805183815290517facbdb084c721332ac59f9b8e392196c9eb0e4932862da8eb9beaf0dad4f550da9181900360200190a15050565b506001830154600282900a908116600014156106f057600094506106f5565b600194505b5050505092915050565b1561052c575061010555565b1561052e5760006101065550565b60008681526101036020526040812080549094509092508214156107a2578154835560018381018390556101048054918201808255828015829011610771578183600052602060002091820191016107719190610885565b5050506002840181905561010480548892908110156100025760009190915260008051602061099483398151915201555b506001820154600284900a908116600014156108b05760408051600160a060020a03331681526020810188905281517fe1c52dc63b719ade82e8bea94cc41a0d5d28e4aaf536adb5e9cccc9ff8c1aeda929181900390910190a182546001901161089d57600086815261010360205260409020600201546101048054909190811015610002576040600090812060008051602061099483398151915292909201819055808255600180830182905560029092015595506108b09050565b61010480546000808355919091526103c590600080516020610994833981519152908101905b808211156108995760008155600101610885565b5090565b8254600019018355600183018054821790555b50505050919050565b5b600180541180156108dc57506001546002906101008110156100025701546000145b156108f057600180546000190190556108ba565b600154811080156109135750600154600290610100811015610002570154600014155b801561092d57506002816101008110156100025701546000145b1561098e57600154600290610100811015610002578101549082610100811015610002578101919091558190610102906000908361010081101561000257810154825260209290925260408120929092556001546101008110156100025701555b61058156004c0be60200faa20559308cb7b5a1bb3255c16cb1cab91f525b5ae7a03d02fabe" # noqa: E501 ), ] - tx_gas = [817083, 1217083] + # The deployed wallet contract does ~14 fresh SSTOREs during + # construction; EIP-8037 per-storage state-gas spills into regular + # gas on Amsterdam, exceeding the original 817 083 / 1 217 083 + # budgets. Pre-EIP-8037 keeps the original values. + construction_tx_gas = [817_083, 1_217_083] + if fork.is_eip_enabled(8037): + construction_tx_gas = [5_000_000, 7_000_000] + tx_gas = construction_tx_gas tx_value = [100] tx = Transaction( diff --git a/tests/ported_static/stWalletTest/test_multi_owned_construction_not_enough_gas_partial.py b/tests/ported_static/stWalletTest/test_multi_owned_construction_not_enough_gas_partial.py index 22e4a3b9a29..52ccc3ac3f3 100644 --- a/tests/ported_static/stWalletTest/test_multi_owned_construction_not_enough_gas_partial.py +++ b/tests/ported_static/stWalletTest/test_multi_owned_construction_not_enough_gas_partial.py @@ -3,6 +3,10 @@ Ported from: state_tests/stWalletTest/multiOwnedConstructionNotEnoughGasPartialFiller.json +@manually-enhanced: Do not overwrite. tx_gas[1] is tuned for the +multi-owned-wallet construction success path on Cancun; on Amsterdam +the NEW_ACCOUNT, 3 init-code SSTOREs, and 2314-byte code deposit +spill state-gas, so lift the budget by Fork.oog_budget_lift. """ import pytest @@ -66,7 +70,6 @@ def test_multi_owned_construction_not_enough_gas_partial( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) expect_entries_: list[dict] = [ @@ -99,7 +102,15 @@ def test_multi_owned_construction_not_enough_gas_partial( "606060409081526001600081815581805533600160a060020a0316600381905581526101026020529182205561090a90819061003b90396000f300606060405236156100775760e060020a6000350463173825d981146100795780632f54bf6e146100d55780634123cb6b146100f95780637065cb4814610102578063746c917114610136578063b75c7dc61461013f578063ba51a6df1461016f578063c2cf7326146101a3578063f00d4b5d146101e3575b005b610077600435600060003643604051808484808284375050509091019081526040519081900360200190209050610529815b600160a060020a033316600090815261010260205260408120548180808381141561066f57610806565b61021c6004355b600160a060020a0316600090815261010260205260408120541190565b61021c60015481565b610077600435600036436040518084848082843750505090910190815260405190819003602001902090506104a1816100ab565b61021c60005481565b610077600435600160a060020a033316600090815261010260205260408120549080808381141561022e576102b0565b610077600435600036436040518084848082843750505090910190815260405190819003602001902090506105e8816100ab565b61021c600435602435600082815261010360209081526040808320600160a060020a03851684526101029092528220548290818181141561064157610665565b61007760043560243560006000364360405180848480828437505050909101908152604051908190036020019020905061033a816100ab565b60408051918252519081900360200190f35b5050506000828152610103602052604081206001810154600284900a92908316819011156102b05781546001838101805492909101845590849003905560408051600160a060020a03331681526020810187905281517fc7fb647e59b18047309aa15aad418e5d7ca96d173ad704f1031a2c3d7591734b929181900390910190a15b5050505050565b600160a060020a038316600283610100811015610002570155600160a060020a0384811660008181526101026020908152604080832083905593871680835291849020869055835192835282015281517fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c929181900390910190a15b505b505050565b1561033357610348836100dc565b156103535750610335565b600160a060020a03841660009081526101026020526040812054925082141561037c5750610335565b6102b75b6101045460005b818110156107b557610104805482908110156100025760009182526000805160206108ea8339815191520154146103fa576101048054610103916000918490811015610002576000805160206108ea83398151915201548252506020919091526040812081815560018101829055600201555b600101610387565b60018054810190819055600160a060020a038316906002906101008110156100025790900160005081905550600160005054610102600050600084600160a060020a03168152602001908152602001600020600050819055507f994a936646fe87ffe4f1e469d3d6aa417d6b855598397f323de5b449f765f0c3826040518082600160a060020a0316815260200191505060405180910390a15b505b50565b1561049c576104af826100dc565b156104ba575061049e565b6104c2610380565b60015460fa90106104d7576104d56104ec565b505b60015460fa9010610402575061049e565b6105a65b600060015b6001548110156107ef575b6001548110801561051c5750600281610100811015610002570154600014155b1561080f576001016104fc565b1561033557600160a060020a038316600090815261010260205260408120549250821415610557575061049c565b6001600160005054036000600050541115610572575061049c565b600060028361010081101561000257508301819055600160a060020a038416815261010260205260408120556104e8610380565b5060408051600160a060020a038516815290517f58619076adf5bb0943d100ef88d52d7c3fd691b19d3a9071b555b651fbf418da9181900360200190a1505050565b1561049c576001548211156105fd575061049e565b600082905561060a610380565b6040805183815290517facbdb084c721332ac59f9b8e392196c9eb0e4932862da8eb9beaf0dad4f550da9181900360200190a15050565b506001830154600282900a908116600014156106605760009450610665565b600194505b5050505092915050565b60008681526101036020526040812080549094509092508214156106f85781548355600183810183905561010480549182018082558280158290116106c7578183600052602060002091820191016106c791906107db565b505050600284018190556101048054889290811015610002576000919091526000805160206108ea83398151915201555b506001820154600284900a908116600014156108065760408051600160a060020a03331681526020810188905281517fe1c52dc63b719ade82e8bea94cc41a0d5d28e4aaf536adb5e9cccc9ff8c1aeda929181900390910190a18254600190116107f35760008681526101036020526040902060020154610104805490919081101561000257604060009081206000805160206108ea83398151915292909201819055808255600180830182905560029092015595506108069050565b6101048054600080835591909152610335906000805160206108ea833981519152908101905b808211156107ef57600081556001016107db565b5090565b8254600019018355600183018054821790555b50505050919050565b5b6001805411801561083257506001546002906101008110156100025701546000145b156108465760018054600019019055610810565b600154811080156108695750600154600290610100811015610002570154600014155b801561088357506002816101008110156100025701546000145b156108e457600154600290610100811015610002578101549082610100811015610002578101919091558190610102906000908361010081101561000257810154825260209290925260408120929092556001546101008110156100025701555b6104f156004c0be60200faa20559308cb7b5a1bb3255c16cb1cab91f525b5ae7a03d02fabe" # noqa: E501 ), ] - tx_gas = [601249, 751249] + tx_gas = [ + 601249, + 751249 + + fork.oog_budget_lift( + creates_before_oog=1, + sstores_before_oog=3, + deploy_code_size=2314, + ), + ] tx_value = [100] tx = Transaction( diff --git a/tests/ported_static/stWalletTest/test_wallet_construction.py b/tests/ported_static/stWalletTest/test_wallet_construction.py index 3127da7cf6b..63ffd581365 100644 --- a/tests/ported_static/stWalletTest/test_wallet_construction.py +++ b/tests/ported_static/stWalletTest/test_wallet_construction.py @@ -3,6 +3,10 @@ Ported from: state_tests/stWalletTest/walletConstructionFiller.json + +@manually-enhanced: Do not overwrite. Both `tx_gas` values bumped for +EIP-8037 NEW_ACCOUNT state-gas headroom on Amsterdam (the test has the +same post-state for both g indexes — both are 'should succeed' paths). """ import pytest @@ -75,7 +79,14 @@ def test_wallet_construction( "6060604052604051602080611014833960806040818152925160016000818155818055600160a060020a03331660038190558152610102909452938320939093556201518042046101075582917f102d25c49d33fcdb8976a3f2744e0785c98d9e43b88364859e6aec4ae82eff5c91a250610f958061007f6000396000f300606060405236156100b95760e060020a6000350463173825d9811461010b5780632f54bf6e146101675780634123cb6b1461018f5780635c52c2f5146101985780637065cb48146101c9578063746c9171146101fd578063797af62714610206578063b20d30a914610219578063b61d27f61461024d578063b75c7dc61461026e578063ba51a6df1461029e578063c2cf7326146102d2578063cbf0b0c014610312578063f00d4b5d14610346578063f1736d861461037f575b61038960003411156101095760408051600160a060020a033316815234602082015281517fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c929181900390910190a15b565b610389600435600060003643604051808484808284375050509091019081526040519081900360200190209050610693815b600160a060020a0333166000908152610102602052604081205481808083811415610c1357610d6c565b61038b6004355b600160a060020a03811660009081526101026020526040812054115b919050565b61038b60015481565b610389600036436040518084848082843750505090910190815260405190819003602001902090506107e58161013d565b6103896004356000364360405180848480828437505050909101908152604051908190036020019020905061060b8161013d565b61038b60005481565b61038b6004355b600081610a4b8161013d565b610389600435600036436040518084848082843750505090910190815260405190819003602001902090506107d98161013d565b61038b6004803590602480359160443591820191013560006108043361016e565b610389600435600160a060020a033316600090815261010260205260408120549080808381141561039d5761041f565b610389600435600036436040518084848082843750505090910190815260405190819003602001902090506107528161013d565b61038b600435602435600082815261010360209081526040808320600160a060020a0385168452610102909252822054829081818114156107ab576107cf565b610389600435600036436040518084848082843750505090910190815260405190819003602001902090506107f38161013d565b6103896004356024356000600036436040518084848082843750505090910190815260405190819003602001902090506104ac8161013d565b61038b6101055481565b005b60408051918252519081900360200190f35b5050506000828152610103602052604081206001810154600284900a929083168190111561041f5781546001838101805492909101845590849003905560408051600160a060020a03331681526020810187905281517fc7fb647e59b18047309aa15aad418e5d7ca96d173ad704f1031a2c3d7591734b929181900390910190a15b5050505050565b600160a060020a03831660028361010081101561000257508301819055600160a060020a03851660008181526101026020908152604080832083905584835291829020869055815192835282019290925281517fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c929181900390910190a15b505b505050565b156104a5576104ba8361016e565b156104c557506104a7565b600160a060020a0384166000908152610102602052604081205492508214156104ee57506104a7565b6104265b6101045460005b81811015610eba57610104805461010891600091849081101561000257600080516020610f7583398151915201548252506020918252604081208054600160a060020a0319168155600181018290556002810180548382559083528383209193610f3f92601f9290920104810190610a33565b60018054810190819055600160a060020a038316906002906101008110156100025790900160005081905550600160005054610102600050600084600160a060020a03168152602001908152602001600020600050819055507f994a936646fe87ffe4f1e469d3d6aa417d6b855598397f323de5b449f765f0c3826040518082600160a060020a0316815260200191505060405180910390a15b505b50565b15610606576106198261016e565b156106245750610608565b61062c6104f2565b60015460fa90106106415761063f610656565b505b60015460fa901061056c5750610608565b6107105b600060015b600154811015610a47575b600154811080156106865750600281610100811015610002570154600014155b15610d7557600101610666565b156104a757600160a060020a0383166000908152610102602052604081205492508214156106c15750610606565b60016001600050540360006000505411156106dc5750610606565b600060028361010081101561000257508301819055600160a060020a038416815261010260205260408120556106526104f2565b5060408051600160a060020a038516815290517f58619076adf5bb0943d100ef88d52d7c3fd691b19d3a9071b555b651fbf418da9181900360200190a1505050565b15610606576001548211156107675750610608565b60008290556107746104f2565b6040805183815290517facbdb084c721332ac59f9b8e392196c9eb0e4932862da8eb9beaf0dad4f550da9181900360200190a15050565b506001830154600282900a908116600014156107ca57600094506107cf565b600194505b5050505092915050565b15610606575061010555565b156106085760006101065550565b156106065781600160a060020a0316ff5b15610a2357610818846000610e4f3361016e565b156108d4577f92ca3a80853e6663fa31fa10b99225f18d4902939b4c53a9caae9043f6efd00433858786866040518086600160a060020a0316815260200185815260200184600160a060020a031681526020018060200182810382528484828181526020019250808284378201915050965050505050505060405180910390a184600160a060020a03168484846040518083838082843750505090810191506000908083038185876185025a03f15060009350610a2392505050565b6000364360405180848480828437505050909101908152604051908190036020019020915061090490508161020d565b158015610927575060008181526101086020526040812054600160a060020a0316145b15610a235760008181526101086020908152604082208054600160a060020a03191688178155600181018790556002018054858255818452928290209092601f01919091048101908490868215610a2b579182015b82811115610a2b57823582600050559160200191906001019061097c565b50600050507f1733cbb53659d713b79580f79f3f9ff215f78a7c7aa45890f3b89fc5cddfbf328133868887876040518087815260200186600160a060020a0316815260200185815260200184600160a060020a03168152602001806020018281038252848482818152602001925080828437820191505097505050505050505060405180910390a15b949350505050565b5061099a9291505b80821115610a475760008155600101610a33565b5090565b15610c005760008381526101086020526040812054600160a060020a031614610c0057604080516000918220805460018201546002929092018054600160a060020a0392909216949293909291819084908015610acd57820191906000526020600020905b815481529060010190602001808311610ab057829003601f168201915b50509250505060006040518083038185876185025a03f1505050600084815261010860209081526040805181842080546001820154600160a060020a033381811686529685018c905294840181905293166060830181905260a06080840181815260029390930180549185018290527fe7c957c06e9a662c1a6c77366179f5b702b97651dc28eee7d5bf1dff6e40bb4a985095968b969294929390929160c083019085908015610ba257820191906000526020600020905b815481529060010190602001808311610b8557829003601f168201915b505097505050505050505060405180910390a160008381526101086020908152604082208054600160a060020a031916815560018101839055600281018054848255908452828420919392610c0692601f9290920104810190610a33565b50919050565b505050600191505061018a565b6000868152610103602052604081208054909450909250821415610c9c578154835560018381018390556101048054918201808255828015829011610c6b57818360005260206000209182019101610c6b9190610a33565b50505060028401819055610104805488929081101561000257600091909152600080516020610f7583398151915201555b506001820154600284900a90811660001415610d6c5760408051600160a060020a03331681526020810188905281517fe1c52dc63b719ade82e8bea94cc41a0d5d28e4aaf536adb5e9cccc9ff8c1aeda929181900390910190a1825460019011610d59576000868152610103602052604090206002015461010480549091908110156100025760406000908120600080516020610f758339815191529290920181905580825560018083018290556002909201559550610d6c9050565b8254600019018355600183018054821790555b50505050919050565b5b60018054118015610d9857506001546002906101008110156100025701546000145b15610dac5760018054600019019055610d76565b60015481108015610dcf5750600154600290610100811015610002570154600014155b8015610de957506002816101008110156100025701546000145b15610e4a57600154600290610100811015610002578101549082610100811015610002578101919091558190610102906000908361010081101561000257810154825260209290925260408120929092556001546101008110156100025701555b61065b565b1561018a5761010754610e655b62015180420490565b1115610e7e57600061010655610e79610e5c565b610107555b6101065480830110801590610e9c5750610106546101055490830111155b15610eb25750610106805482019055600161018a565b50600061018a565b6106066101045460005b81811015610f4a5761010480548290811015610002576000918252600080516020610f75833981519152015414610f3757610104805461010391600091849081101561000257600080516020610f7583398151915201548252506020919091526040812081815560018101829055600201555b600101610ec4565b5050506001016104f9565b61010480546000808355919091526104a790600080516020610f7583398151915290810190610a3356004c0be60200faa20559308cb7b5a1bb3255c16cb1cab91f525b5ae7a03d02fabe" # noqa: E501 ), ] - tx_gas = [1225023, 1825023] + # The deployed wallet contract does ~21 fresh SSTOREs during + # construction; EIP-8037 per-storage state-gas spills into regular + # gas on Amsterdam, exceeding the original 1 225 023 / 1 825 023 + # budgets. Pre-EIP-8037 keeps the original values. + construction_tx_gas = [1_225_023, 1_825_023] + if fork.is_eip_enabled(8037): + construction_tx_gas = [8_000_000, 10_000_000] + tx_gas = construction_tx_gas tx_value = [100] tx = Transaction( diff --git a/tests/ported_static/stWalletTest/test_wallet_construction_oog.py b/tests/ported_static/stWalletTest/test_wallet_construction_oog.py index d10fa9cb910..851f26cb823 100644 --- a/tests/ported_static/stWalletTest/test_wallet_construction_oog.py +++ b/tests/ported_static/stWalletTest/test_wallet_construction_oog.py @@ -3,6 +3,10 @@ Ported from: state_tests/stWalletTest/walletConstructionOOGFiller.json +@manually-enhanced: Do not overwrite. tx_gas[1] is tuned for the +contract-creation success path on Cancun; on Amsterdam the +NEW_ACCOUNT plus the 4 fresh storage slots in the deployed wallet +spill state-gas, so lift the budget by Fork.oog_budget_lift. """ import pytest @@ -68,7 +72,6 @@ def test_wallet_construction_oog( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) pre[sender] = Account(balance=0xDE0B6B3A75EF08F, nonce=1) @@ -114,7 +117,17 @@ def test_wallet_construction_oog( "6060604052604051602080611014833960806040818152925160016000818155818055600160a060020a03331660038190558152610102909452938320939093556201518042046101075582917f102d25c49d33fcdb8976a3f2744e0785c98d9e43b88364859e6aec4ae82eff5c91a250610f958061007f6000396000f300606060405236156100b95760e060020a6000350463173825d9811461010b5780632f54bf6e146101675780634123cb6b1461018f5780635c52c2f5146101985780637065cb48146101c9578063746c9171146101fd578063797af62714610206578063b20d30a914610219578063b61d27f61461024d578063b75c7dc61461026e578063ba51a6df1461029e578063c2cf7326146102d2578063cbf0b0c014610312578063f00d4b5d14610346578063f1736d861461037f575b61038960003411156101095760408051600160a060020a033316815234602082015281517fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c929181900390910190a15b565b610389600435600060003643604051808484808284375050509091019081526040519081900360200190209050610693815b600160a060020a0333166000908152610102602052604081205481808083811415610c1357610d6c565b61038b6004355b600160a060020a03811660009081526101026020526040812054115b919050565b61038b60015481565b610389600036436040518084848082843750505090910190815260405190819003602001902090506107e58161013d565b6103896004356000364360405180848480828437505050909101908152604051908190036020019020905061060b8161013d565b61038b60005481565b61038b6004355b600081610a4b8161013d565b610389600435600036436040518084848082843750505090910190815260405190819003602001902090506107d98161013d565b61038b6004803590602480359160443591820191013560006108043361016e565b610389600435600160a060020a033316600090815261010260205260408120549080808381141561039d5761041f565b610389600435600036436040518084848082843750505090910190815260405190819003602001902090506107528161013d565b61038b600435602435600082815261010360209081526040808320600160a060020a0385168452610102909252822054829081818114156107ab576107cf565b610389600435600036436040518084848082843750505090910190815260405190819003602001902090506107f38161013d565b6103896004356024356000600036436040518084848082843750505090910190815260405190819003602001902090506104ac8161013d565b61038b6101055481565b005b60408051918252519081900360200190f35b5050506000828152610103602052604081206001810154600284900a929083168190111561041f5781546001838101805492909101845590849003905560408051600160a060020a03331681526020810187905281517fc7fb647e59b18047309aa15aad418e5d7ca96d173ad704f1031a2c3d7591734b929181900390910190a15b5050505050565b600160a060020a03831660028361010081101561000257508301819055600160a060020a03851660008181526101026020908152604080832083905584835291829020869055815192835282019290925281517fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c929181900390910190a15b505b505050565b156104a5576104ba8361016e565b156104c557506104a7565b600160a060020a0384166000908152610102602052604081205492508214156104ee57506104a7565b6104265b6101045460005b81811015610eba57610104805461010891600091849081101561000257600080516020610f7583398151915201548252506020918252604081208054600160a060020a0319168155600181018290556002810180548382559083528383209193610f3f92601f9290920104810190610a33565b60018054810190819055600160a060020a038316906002906101008110156100025790900160005081905550600160005054610102600050600084600160a060020a03168152602001908152602001600020600050819055507f994a936646fe87ffe4f1e469d3d6aa417d6b855598397f323de5b449f765f0c3826040518082600160a060020a0316815260200191505060405180910390a15b505b50565b15610606576106198261016e565b156106245750610608565b61062c6104f2565b60015460fa90106106415761063f610656565b505b60015460fa901061056c5750610608565b6107105b600060015b600154811015610a47575b600154811080156106865750600281610100811015610002570154600014155b15610d7557600101610666565b156104a757600160a060020a0383166000908152610102602052604081205492508214156106c15750610606565b60016001600050540360006000505411156106dc5750610606565b600060028361010081101561000257508301819055600160a060020a038416815261010260205260408120556106526104f2565b5060408051600160a060020a038516815290517f58619076adf5bb0943d100ef88d52d7c3fd691b19d3a9071b555b651fbf418da9181900360200190a1505050565b15610606576001548211156107675750610608565b60008290556107746104f2565b6040805183815290517facbdb084c721332ac59f9b8e392196c9eb0e4932862da8eb9beaf0dad4f550da9181900360200190a15050565b506001830154600282900a908116600014156107ca57600094506107cf565b600194505b5050505092915050565b15610606575061010555565b156106085760006101065550565b156106065781600160a060020a0316ff5b15610a2357610818846000610e4f3361016e565b156108d4577f92ca3a80853e6663fa31fa10b99225f18d4902939b4c53a9caae9043f6efd00433858786866040518086600160a060020a0316815260200185815260200184600160a060020a031681526020018060200182810382528484828181526020019250808284378201915050965050505050505060405180910390a184600160a060020a03168484846040518083838082843750505090810191506000908083038185876185025a03f15060009350610a2392505050565b6000364360405180848480828437505050909101908152604051908190036020019020915061090490508161020d565b158015610927575060008181526101086020526040812054600160a060020a0316145b15610a235760008181526101086020908152604082208054600160a060020a03191688178155600181018790556002018054858255818452928290209092601f01919091048101908490868215610a2b579182015b82811115610a2b57823582600050559160200191906001019061097c565b50600050507f1733cbb53659d713b79580f79f3f9ff215f78a7c7aa45890f3b89fc5cddfbf328133868887876040518087815260200186600160a060020a0316815260200185815260200184600160a060020a03168152602001806020018281038252848482818152602001925080828437820191505097505050505050505060405180910390a15b949350505050565b5061099a9291505b80821115610a475760008155600101610a33565b5090565b15610c005760008381526101086020526040812054600160a060020a031614610c0057604080516000918220805460018201546002929092018054600160a060020a0392909216949293909291819084908015610acd57820191906000526020600020905b815481529060010190602001808311610ab057829003601f168201915b50509250505060006040518083038185876185025a03f1505050600084815261010860209081526040805181842080546001820154600160a060020a033381811686529685018c905294840181905293166060830181905260a06080840181815260029390930180549185018290527fe7c957c06e9a662c1a6c77366179f5b702b97651dc28eee7d5bf1dff6e40bb4a985095968b969294929390929160c083019085908015610ba257820191906000526020600020905b815481529060010190602001808311610b8557829003601f168201915b505097505050505050505060405180910390a160008381526101086020908152604082208054600160a060020a031916815560018101839055600281018054848255908452828420919392610c0692601f9290920104810190610a33565b50919050565b505050600191505061018a565b6000868152610103602052604081208054909450909250821415610c9c578154835560018381018390556101048054918201808255828015829011610c6b57818360005260206000209182019101610c6b9190610a33565b50505060028401819055610104805488929081101561000257600091909152600080516020610f7583398151915201555b506001820154600284900a90811660001415610d6c5760408051600160a060020a03331681526020810188905281517fe1c52dc63b719ade82e8bea94cc41a0d5d28e4aaf536adb5e9cccc9ff8c1aeda929181900390910190a1825460019011610d59576000868152610103602052604090206002015461010480549091908110156100025760406000908120600080516020610f758339815191529290920181905580825560018083018290556002909201559550610d6c9050565b8254600019018355600183018054821790555b50505050919050565b5b60018054118015610d9857506001546002906101008110156100025701546000145b15610dac5760018054600019019055610d76565b60015481108015610dcf5750600154600290610100811015610002570154600014155b8015610de957506002816101008110156100025701546000145b15610e4a57600154600290610100811015610002578101549082610100811015610002578101919091558190610102906000908361010081101561000257810154825260209290925260408120929092556001546101008110156100025701555b61065b565b1561018a5761010754610e655b62015180420490565b1115610e7e57600061010655610e79610e5c565b610107555b6101065480830110801590610e9c5750610106546101055490830111155b15610eb25750610106805482019055600161018a565b50600061018a565b6106066101045460005b81811015610f4a5761010480548290811015610002576000918252600080516020610f75833981519152015414610f3757610104805461010391600091849081101561000257600080516020610f7583398151915201548252506020919091526040812081815560018101829055600201555b600101610ec4565b5050506001016104f9565b61010480546000808355919091526104a790600080516020610f7583398151915290810190610a3356004c0be60200faa20559308cb7b5a1bb3255c16cb1cab91f525b5ae7a03d02fabe" # noqa: E501 ), ] - tx_gas = [427222, 1225022] + # Deployed wallet code length (matches the bytecode in expect_entries_). + _deployed_code_len = 3989 + tx_gas = [ + 427222, + 1225022 + + fork.oog_budget_lift( + creates_before_oog=1, + sstores_before_oog=4, + deploy_code_size=_deployed_code_len, + ), + ] tx_value = [100] tx = Transaction( diff --git a/tests/ported_static/stZeroCallsRevert/test_zero_value_call_oog_revert.py b/tests/ported_static/stZeroCallsRevert/test_zero_value_call_oog_revert.py index 4dfe73bfc18..bbc6f8ca537 100644 --- a/tests/ported_static/stZeroCallsRevert/test_zero_value_call_oog_revert.py +++ b/tests/ported_static/stZeroCallsRevert/test_zero_value_call_oog_revert.py @@ -44,7 +44,6 @@ def test_zero_value_call_oog_revert( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) pre[sender] = Account(balance=0xE8D4A51000) diff --git a/tests/ported_static/stZeroCallsRevert/test_zero_value_call_to_empty_oog_revert_paris.py b/tests/ported_static/stZeroCallsRevert/test_zero_value_call_to_empty_oog_revert_paris.py index 8d1c014999b..fae3e78ae43 100644 --- a/tests/ported_static/stZeroCallsRevert/test_zero_value_call_to_empty_oog_revert_paris.py +++ b/tests/ported_static/stZeroCallsRevert/test_zero_value_call_to_empty_oog_revert_paris.py @@ -42,7 +42,6 @@ def test_zero_value_call_to_empty_oog_revert_paris( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) addr = pre.fund_eoa(amount=10) # noqa: F841 diff --git a/tests/ported_static/stZeroCallsRevert/test_zero_value_call_to_non_zero_balance_oog_revert.py b/tests/ported_static/stZeroCallsRevert/test_zero_value_call_to_non_zero_balance_oog_revert.py index fafae7e0ec9..c39a1020369 100644 --- a/tests/ported_static/stZeroCallsRevert/test_zero_value_call_to_non_zero_balance_oog_revert.py +++ b/tests/ported_static/stZeroCallsRevert/test_zero_value_call_to_non_zero_balance_oog_revert.py @@ -42,7 +42,6 @@ def test_zero_value_call_to_non_zero_balance_oog_revert( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) addr = pre.fund_eoa(amount=100) # noqa: F841 diff --git a/tests/ported_static/stZeroCallsRevert/test_zero_value_call_to_one_storage_key_oog_revert_paris.py b/tests/ported_static/stZeroCallsRevert/test_zero_value_call_to_one_storage_key_oog_revert_paris.py index 341743b48e8..d861814cb30 100644 --- a/tests/ported_static/stZeroCallsRevert/test_zero_value_call_to_one_storage_key_oog_revert_paris.py +++ b/tests/ported_static/stZeroCallsRevert/test_zero_value_call_to_one_storage_key_oog_revert_paris.py @@ -46,7 +46,6 @@ def test_zero_value_call_to_one_storage_key_oog_revert_paris( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) pre[sender] = Account(balance=0xE8D4A51000) diff --git a/tests/ported_static/stZeroCallsRevert/test_zero_value_callcode_oog_revert.py b/tests/ported_static/stZeroCallsRevert/test_zero_value_callcode_oog_revert.py index c18cc81d254..25bf8c24eb3 100644 --- a/tests/ported_static/stZeroCallsRevert/test_zero_value_callcode_oog_revert.py +++ b/tests/ported_static/stZeroCallsRevert/test_zero_value_callcode_oog_revert.py @@ -44,7 +44,6 @@ def test_zero_value_callcode_oog_revert( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) pre[sender] = Account(balance=0xE8D4A51000) diff --git a/tests/ported_static/stZeroCallsRevert/test_zero_value_callcode_to_empty_oog_revert_paris.py b/tests/ported_static/stZeroCallsRevert/test_zero_value_callcode_to_empty_oog_revert_paris.py index b036c66aa4b..bbdf84ec5ce 100644 --- a/tests/ported_static/stZeroCallsRevert/test_zero_value_callcode_to_empty_oog_revert_paris.py +++ b/tests/ported_static/stZeroCallsRevert/test_zero_value_callcode_to_empty_oog_revert_paris.py @@ -42,7 +42,6 @@ def test_zero_value_callcode_to_empty_oog_revert_paris( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) addr = pre.fund_eoa(amount=10) # noqa: F841 diff --git a/tests/ported_static/stZeroCallsRevert/test_zero_value_callcode_to_non_zero_balance_oog_revert.py b/tests/ported_static/stZeroCallsRevert/test_zero_value_callcode_to_non_zero_balance_oog_revert.py index 4d2f36c5dfa..1b1feb76e3a 100644 --- a/tests/ported_static/stZeroCallsRevert/test_zero_value_callcode_to_non_zero_balance_oog_revert.py +++ b/tests/ported_static/stZeroCallsRevert/test_zero_value_callcode_to_non_zero_balance_oog_revert.py @@ -42,7 +42,6 @@ def test_zero_value_callcode_to_non_zero_balance_oog_revert( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) addr = pre.fund_eoa(amount=100) # noqa: F841 diff --git a/tests/ported_static/stZeroCallsRevert/test_zero_value_callcode_to_one_storage_key_oog_revert_paris.py b/tests/ported_static/stZeroCallsRevert/test_zero_value_callcode_to_one_storage_key_oog_revert_paris.py index b728eea6ee6..c891f297f3b 100644 --- a/tests/ported_static/stZeroCallsRevert/test_zero_value_callcode_to_one_storage_key_oog_revert_paris.py +++ b/tests/ported_static/stZeroCallsRevert/test_zero_value_callcode_to_one_storage_key_oog_revert_paris.py @@ -46,7 +46,6 @@ def test_zero_value_callcode_to_one_storage_key_oog_revert_paris( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) pre[sender] = Account(balance=0xE8D4A51000) diff --git a/tests/ported_static/stZeroCallsRevert/test_zero_value_delegatecall_oog_revert.py b/tests/ported_static/stZeroCallsRevert/test_zero_value_delegatecall_oog_revert.py index 2fb90912ad8..c92bbcadd39 100644 --- a/tests/ported_static/stZeroCallsRevert/test_zero_value_delegatecall_oog_revert.py +++ b/tests/ported_static/stZeroCallsRevert/test_zero_value_delegatecall_oog_revert.py @@ -46,7 +46,6 @@ def test_zero_value_delegatecall_oog_revert( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) pre[sender] = Account(balance=0xE8D4A51000) diff --git a/tests/ported_static/stZeroCallsRevert/test_zero_value_delegatecall_to_empty_oog_revert_paris.py b/tests/ported_static/stZeroCallsRevert/test_zero_value_delegatecall_to_empty_oog_revert_paris.py index 5be02211c8d..b469922b21a 100644 --- a/tests/ported_static/stZeroCallsRevert/test_zero_value_delegatecall_to_empty_oog_revert_paris.py +++ b/tests/ported_static/stZeroCallsRevert/test_zero_value_delegatecall_to_empty_oog_revert_paris.py @@ -42,7 +42,6 @@ def test_zero_value_delegatecall_to_empty_oog_revert_paris( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) addr = pre.fund_eoa(amount=10) # noqa: F841 diff --git a/tests/ported_static/stZeroCallsRevert/test_zero_value_delegatecall_to_non_zero_balance_oog_revert.py b/tests/ported_static/stZeroCallsRevert/test_zero_value_delegatecall_to_non_zero_balance_oog_revert.py index b917c075c0b..529dc3c2226 100644 --- a/tests/ported_static/stZeroCallsRevert/test_zero_value_delegatecall_to_non_zero_balance_oog_revert.py +++ b/tests/ported_static/stZeroCallsRevert/test_zero_value_delegatecall_to_non_zero_balance_oog_revert.py @@ -42,7 +42,6 @@ def test_zero_value_delegatecall_to_non_zero_balance_oog_revert( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) addr = pre.fund_eoa(amount=100) # noqa: F841 diff --git a/tests/ported_static/stZeroCallsRevert/test_zero_value_delegatecall_to_one_storage_key_oog_revert_paris.py b/tests/ported_static/stZeroCallsRevert/test_zero_value_delegatecall_to_one_storage_key_oog_revert_paris.py index 9259f02ee2b..d9863e2bcad 100644 --- a/tests/ported_static/stZeroCallsRevert/test_zero_value_delegatecall_to_one_storage_key_oog_revert_paris.py +++ b/tests/ported_static/stZeroCallsRevert/test_zero_value_delegatecall_to_one_storage_key_oog_revert_paris.py @@ -46,7 +46,6 @@ def test_zero_value_delegatecall_to_one_storage_key_oog_revert_paris( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) pre[sender] = Account(balance=0xE8D4A51000) diff --git a/tests/ported_static/stZeroCallsRevert/test_zero_value_suicide_oog_revert.py b/tests/ported_static/stZeroCallsRevert/test_zero_value_suicide_oog_revert.py index b950651b252..3aa2fc004b9 100644 --- a/tests/ported_static/stZeroCallsRevert/test_zero_value_suicide_oog_revert.py +++ b/tests/ported_static/stZeroCallsRevert/test_zero_value_suicide_oog_revert.py @@ -40,7 +40,6 @@ def test_zero_value_suicide_oog_revert( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) # Source: lll diff --git a/tests/ported_static/stZeroCallsRevert/test_zero_value_suicide_to_empty_oog_revert_paris.py b/tests/ported_static/stZeroCallsRevert/test_zero_value_suicide_to_empty_oog_revert_paris.py index 2151d4af342..75a68366c90 100644 --- a/tests/ported_static/stZeroCallsRevert/test_zero_value_suicide_to_empty_oog_revert_paris.py +++ b/tests/ported_static/stZeroCallsRevert/test_zero_value_suicide_to_empty_oog_revert_paris.py @@ -42,7 +42,6 @@ def test_zero_value_suicide_to_empty_oog_revert_paris( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) addr_2 = pre.fund_eoa(amount=10) # noqa: F841 diff --git a/tests/ported_static/stZeroCallsRevert/test_zero_value_suicide_to_non_zero_balance_oog_revert.py b/tests/ported_static/stZeroCallsRevert/test_zero_value_suicide_to_non_zero_balance_oog_revert.py index 8459e1da25d..620813a621d 100644 --- a/tests/ported_static/stZeroCallsRevert/test_zero_value_suicide_to_non_zero_balance_oog_revert.py +++ b/tests/ported_static/stZeroCallsRevert/test_zero_value_suicide_to_non_zero_balance_oog_revert.py @@ -42,7 +42,6 @@ def test_zero_value_suicide_to_non_zero_balance_oog_revert( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) addr_2 = pre.fund_eoa(amount=100) # noqa: F841 diff --git a/tests/ported_static/stZeroCallsRevert/test_zero_value_suicide_to_one_storage_key_oog_revert_paris.py b/tests/ported_static/stZeroCallsRevert/test_zero_value_suicide_to_one_storage_key_oog_revert_paris.py index 1abcd40243d..c81078e4f0f 100644 --- a/tests/ported_static/stZeroCallsRevert/test_zero_value_suicide_to_one_storage_key_oog_revert_paris.py +++ b/tests/ported_static/stZeroCallsRevert/test_zero_value_suicide_to_one_storage_key_oog_revert_paris.py @@ -46,7 +46,6 @@ def test_zero_value_suicide_to_one_storage_key_oog_revert_paris( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=10000000, ) pre[sender] = Account(balance=0xE8D4A51000) diff --git a/tests/ported_static/stZeroKnowledge/test_point_mul_add.py b/tests/ported_static/stZeroKnowledge/test_point_mul_add.py index 85872c32263..4a32c3ba08c 100644 --- a/tests/ported_static/stZeroKnowledge/test_point_mul_add.py +++ b/tests/ported_static/stZeroKnowledge/test_point_mul_add.py @@ -273,7 +273,6 @@ def test_point_mul_add( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=4012015, ) pre[sender] = Account(balance=0xDE0B6B3A7640000, nonce=1) diff --git a/tests/ported_static/stZeroKnowledge/test_point_mul_add2.py b/tests/ported_static/stZeroKnowledge/test_point_mul_add2.py index 7722b69ba5a..d97f92d87f5 100644 --- a/tests/ported_static/stZeroKnowledge/test_point_mul_add2.py +++ b/tests/ported_static/stZeroKnowledge/test_point_mul_add2.py @@ -969,7 +969,6 @@ def test_point_mul_add2( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=4012015, ) pre[sender] = Account(balance=0xDE0B6B3A7640000, nonce=1) diff --git a/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_variable_length_input_contracts.py b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_variable_length_input_contracts.py index bf26a08efdc..40769eaf3de 100644 --- a/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_variable_length_input_contracts.py +++ b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_variable_length_input_contracts.py @@ -173,6 +173,11 @@ def tx_gas_limit_calculator( ) memory_expansion_gas_calculator = fork.memory_expansion_gas_calculator() extra_gas = 22_500 * len(precompile_gas_list) + # Each SSTORE 0->non-zero contributes one state-set under EIP-8037 + # (returns 0 pre-fork). + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) * len( + precompile_gas_list + ) return ( extra_gas + intrinsic_gas_cost_calculator() @@ -180,6 +185,7 @@ def tx_gas_limit_calculator( new_bytes=max_precompile_input_length ) + sum(precompile_gas_list) + + sstore_state_gas ) @@ -253,9 +259,10 @@ def get_range_cost(min_index: int, max_index: int) -> int: new_range = (current_min, current_max) g1_msm_discount_table_ranges.append(new_range) current_min = current_max - elif current_max == discount_table_length: - new_range = (current_min, current_max + 1) - g1_msm_discount_table_ranges.append(new_range) + if current_min <= discount_table_length: + g1_msm_discount_table_ranges.append( + (current_min, discount_table_length + 1) + ) g1_msm_discount_table_splits = [ [ diff --git a/tests/prague/eip6110_deposits/conftest.py b/tests/prague/eip6110_deposits/conftest.py index 6e50c4f2e36..c9ec19524f8 100644 --- a/tests/prague/eip6110_deposits/conftest.py +++ b/tests/prague/eip6110_deposits/conftest.py @@ -30,13 +30,30 @@ def prepared_requests( @pytest.fixture def txs( + fork: Fork, prepared_requests: List[DepositInteractionBase], ) -> List[Transaction]: """List of transactions to include in the block.""" txs = [] for r in prepared_requests: txs += r.transactions() - return txs + # EIP-7976 (enabled with EIP-8037 on Amsterdam) raises calldata + # floor cost, pushing the intrinsic above the hardcoded + # tx_gas_limit of the large-calldata OOG fixtures. Lift each + # tx's gas_limit to the new intrinsic only when it falls below; + # the tx still OOGs on its first execution opcode, preserving + # the fixture's no-deposits-applied outcome. + if not (fork.is_eip_enabled(7976) and fork.is_eip_enabled(8037)): + return txs + current_calc = fork.transaction_intrinsic_cost_calculator() + bumped: List[Transaction] = [] + for tx in txs: + current_intrinsic = current_calc(calldata=tx.data) + if tx.gas_limit < current_intrinsic: + bumped.append(tx.copy(gas_limit=current_intrinsic)) + else: + bumped.append(tx) + return bumped @pytest.fixture diff --git a/tests/prague/eip6110_deposits/test_deposits.py b/tests/prague/eip6110_deposits/test_deposits.py index f0299c2a855..a27f3ab7288 100644 --- a/tests/prague/eip6110_deposits/test_deposits.py +++ b/tests/prague/eip6110_deposits/test_deposits.py @@ -698,6 +698,7 @@ ], id="single_deposit_from_contract_call_depth_3", ), + # TODO: Update tx_gas_limit for EIP-8037 state creation gas costs. pytest.param( [ DepositContract( @@ -715,6 +716,7 @@ ), ], id="single_deposit_from_contract_call_depth_high", + marks=pytest.mark.valid_before("EIP8037"), ), pytest.param( [ diff --git a/tests/prague/eip7002_el_triggerable_withdrawals/conftest.py b/tests/prague/eip7002_el_triggerable_withdrawals/conftest.py index de1eebb28f4..441f4117382 100644 --- a/tests/prague/eip7002_el_triggerable_withdrawals/conftest.py +++ b/tests/prague/eip7002_el_triggerable_withdrawals/conftest.py @@ -13,7 +13,10 @@ TransitionFork, ) -from .helpers import WithdrawalRequest, WithdrawalRequestInteractionBase +from .helpers import ( + WithdrawalRequest, + WithdrawalRequestInteractionBase, +) from .spec import Spec @@ -105,11 +108,12 @@ def blocks( included_requests, fillvalue=[], ): - header_verify: Header | None = None - if fork.fork_at( + block_fork = fork.fork_at( block_number=len(blocks) + 1, timestamp=timestamp, - ).header_requests_required(): + ) + header_verify: Header | None = None + if block_fork.header_requests_required(): header_verify = Header( requests_hash=Requests( *block_included_requests, @@ -119,7 +123,9 @@ def blocks( assert not block_included_requests blocks.append( Block( - txs=sum((r.transactions() for r in block_requests), []), + txs=sum( + (r.transactions(block_fork) for r in block_requests), [] + ), header_verify=header_verify, timestamp=timestamp, ) diff --git a/tests/prague/eip7002_el_triggerable_withdrawals/helpers.py b/tests/prague/eip7002_el_triggerable_withdrawals/helpers.py index 2c2e37b137e..41303a82805 100644 --- a/tests/prague/eip7002_el_triggerable_withdrawals/helpers.py +++ b/tests/prague/eip7002_el_triggerable_withdrawals/helpers.py @@ -10,6 +10,7 @@ Address, Alloc, Bytecode, + Fork, Op, Transaction, ) @@ -80,7 +81,7 @@ class WithdrawalRequestInteractionBase: requests: List[WithdrawalRequest] """Withdrawal request to be included in the block.""" - def transactions(self) -> List[Transaction]: + def transactions(self, fork: Fork | None = None) -> List[Transaction]: """Return a transaction for the withdrawal request.""" raise NotImplementedError @@ -109,8 +110,9 @@ class WithdrawalRequestTransaction(WithdrawalRequestInteractionBase): owned account. """ - def transactions(self) -> List[Transaction]: + def transactions(self, fork: Fork | None = None) -> List[Transaction]: """Return a transaction for the withdrawal request.""" + del fork assert self.sender_account is not None, ( "Sender account not initialized" ) @@ -148,8 +150,12 @@ def valid_requests( class WithdrawalRequestContract(WithdrawalRequestInteractionBase): """Class used to describe a withdrawal originated from a contract.""" - tx_gas_limit: int = 1_000_000 - """Gas limit for the transaction.""" + tx_gas_limit: int = 3_000_000 + """ + Gas limit for the transaction. Sized to comfortably cover + `MAX_WITHDRAWAL_REQUESTS_PER_BLOCK` zero-to-nonzero state-set + charges per tx under EIP-8037 plus regular dispatch overhead. + """ contract_balance: int = 1_000_000_000_000_000_000 """ @@ -168,6 +174,13 @@ class WithdrawalRequestContract(WithdrawalRequestInteractionBase): """Frame depth of the pre-deploy contract when it executes the call.""" extra_code: Bytecode = field(default_factory=Bytecode) """Extra code to be added to the contract code.""" + fund_state_reservoir: bool = False + """ + When True (and EIP-8037 is active), pad `tx_gas_limit` by exactly the + per-request state-set work so the excess funds the EIP-8037 reservoir. + Use only when `tx_gas_limit` is held at the cap (reservoir would + otherwise be empty) and state work must not drain the regular pool. + """ @property def contract_code(self) -> Bytecode: @@ -194,12 +207,24 @@ def contract_code(self) -> Bytecode: current_offset += len(r.calldata) return code + self.extra_code - def transactions(self) -> List[Transaction]: + def transactions(self, fork: Fork | None = None) -> List[Transaction]: """Return a transaction for the withdrawal request.""" assert self.entry_address is not None, "Entry address not initialized" + gas_limit = self.tx_gas_limit + if fork is not None and fork.is_eip_enabled(8037): + # Per request the system contract writes 3 entry slots + # (source, pubkey, amount); plus a queue-tail bump and + # one slot of headroom per tx. + sstores_per_request = 3 + queue_tail_and_slack_sstores = 2 + sstores = ( + len(self.requests) * sstores_per_request + + queue_tail_and_slack_sstores + ) + gas_limit += sstores * Op.SSTORE(new_value=1).state_cost(fork) return [ Transaction( - gas_limit=self.tx_gas_limit, + gas_limit=gas_limit, gas_price=1_000_000_000, to=self.entry_address, value=0, diff --git a/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests.py b/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests.py index 54417965417..15a071d2fe6 100644 --- a/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests.py +++ b/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests.py @@ -328,6 +328,12 @@ ], call_depth=264, tx_gas_limit=16_777_216, + # tx_gas_limit is held at the cap to test the + # 63/64 drain over the deep call chain. EIP-8037 + # state-set work is funded via the reservoir + # rather than the regular pool, which would + # corrupt the boundary the test pins. + fund_state_reservoir=True, ), ], ], diff --git a/tests/prague/eip7251_consolidations/conftest.py b/tests/prague/eip7251_consolidations/conftest.py index 083047e0377..0f79323993c 100644 --- a/tests/prague/eip7251_consolidations/conftest.py +++ b/tests/prague/eip7251_consolidations/conftest.py @@ -109,10 +109,11 @@ def blocks( included_requests, fillvalue=[], ): - header_verify: Header | None = None - if fork.fork_at( + active_fork = fork.fork_at( block_number=len(blocks) + 1, timestamp=timestamp - ).header_requests_required(): + ) + header_verify: Header | None = None + if active_fork.header_requests_required(): header_verify = Header( requests_hash=Requests(*block_included_requests) ) @@ -120,7 +121,10 @@ def blocks( assert not block_included_requests blocks.append( Block( - txs=sum((r.transactions() for r in block_requests), []), + txs=sum( + (r.transactions(active_fork) for r in block_requests), + [], + ), header_verify=header_verify, timestamp=timestamp, ) diff --git a/tests/prague/eip7251_consolidations/helpers.py b/tests/prague/eip7251_consolidations/helpers.py index f42d6934e42..625092fff93 100644 --- a/tests/prague/eip7251_consolidations/helpers.py +++ b/tests/prague/eip7251_consolidations/helpers.py @@ -10,6 +10,7 @@ Address, Alloc, Bytecode, + Fork, Op, Transaction, ) @@ -75,7 +76,7 @@ class ConsolidationRequestInteractionBase: requests: List[ConsolidationRequest] """Consolidation requests to be included in the block.""" - def transactions(self) -> List[Transaction]: + def transactions(self, fork: Fork | None = None) -> List[Transaction]: """Return a transaction for the consolidation request.""" raise NotImplementedError @@ -104,8 +105,9 @@ class ConsolidationRequestTransaction(ConsolidationRequestInteractionBase): owned account. """ - def transactions(self) -> List[Transaction]: + def transactions(self, fork: Fork | None = None) -> List[Transaction]: """Return a transaction for the consolidation request.""" + del fork assert self.sender_account is not None, ( "Sender account not initialized" ) @@ -163,6 +165,13 @@ class ConsolidationRequestContract(ConsolidationRequestInteractionBase): """Frame depth of the pre-deploy contract when it executes the call.""" extra_code: Bytecode = field(default_factory=Bytecode) """Extra code to be added to the contract code.""" + fund_state_reservoir: bool = False + """ + When True (and EIP-8037 is active), pad `tx_gas_limit` by exactly the + per-request state-set work so the excess funds the EIP-8037 reservoir. + Use only when `tx_gas_limit` is held at the cap (reservoir would + otherwise be empty) and state work must not drain the regular pool. + """ @property def contract_code(self) -> Bytecode: @@ -189,12 +198,29 @@ def contract_code(self) -> Bytecode: current_offset += len(r.calldata) return code + self.extra_code - def transactions(self) -> List[Transaction]: + def transactions(self, fork: Fork | None = None) -> List[Transaction]: """Return a transaction for the consolidation request.""" assert self.entry_address is not None, "Entry address not initialized" + gas_limit = self.tx_gas_limit + if ( + self.fund_state_reservoir + and fork is not None + and fork.is_eip_enabled(8037) + ): + # Per request the system contract writes 4 entry slots + # (source, src_pubkey, tgt_pubkey, fee); plus a queue-tail + # bump and one slot of headroom per tx. Fund the reservoir + # for the full state-set work so it stays off `gas_left`. + sstores_per_request = 4 + queue_tail_and_slack_sstores = 2 + sstores = ( + len(self.requests) * sstores_per_request + + queue_tail_and_slack_sstores + ) + gas_limit += sstores * Op.SSTORE(new_value=1).state_cost(fork) return [ Transaction( - gas_limit=self.tx_gas_limit, + gas_limit=gas_limit, gas_price=1_000_000_000, to=self.entry_address, value=0, diff --git a/tests/prague/eip7251_consolidations/test_consolidations.py b/tests/prague/eip7251_consolidations/test_consolidations.py index 4e276039e87..07d6d37e7fc 100644 --- a/tests/prague/eip7251_consolidations/test_consolidations.py +++ b/tests/prague/eip7251_consolidations/test_consolidations.py @@ -21,6 +21,9 @@ TestAddress2, ) +from ...amsterdam.eip8037_state_creation_gas_cost_increase.spec import ( + Spec as Spec8037, +) from .helpers import ( ConsolidationRequest, ConsolidationRequestContract, @@ -383,6 +386,13 @@ ) ], call_depth=100, + tx_gas_limit=Spec8037.TX_MAX_GAS_LIMIT, + # tx_gas_limit is held at the cap to test the + # 63/64 drain over the deep call chain. EIP-8037 + # state-set work is funded via the reservoir + # rather than the regular pool, which would + # corrupt the boundary the test pins. + fund_state_reservoir=True, ), ], ], diff --git a/tests/prague/eip7251_consolidations/test_modified_consolidation_contract.py b/tests/prague/eip7251_consolidations/test_modified_consolidation_contract.py index eb1fe9cfc28..82453a74d51 100644 --- a/tests/prague/eip7251_consolidations/test_modified_consolidation_contract.py +++ b/tests/prague/eip7251_consolidations/test_modified_consolidation_contract.py @@ -150,8 +150,8 @@ def test_extra_consolidations( ) def test_system_contract_errors() -> None: """ - Test system contract raising different errors when called by the system - account at the end of the block execution. + Test consolidation system contract raising different errors when called by + the system account at the end of the block execution. To see the list of generated tests, please refer to the `generate_system_contract_error_test` decorator definition. diff --git a/tests/prague/eip7623_increase_calldata_cost/conftest.py b/tests/prague/eip7623_increase_calldata_cost/conftest.py index 596a2db6721..512e45bbc6d 100644 --- a/tests/prague/eip7623_increase_calldata_cost/conftest.py +++ b/tests/prague/eip7623_increase_calldata_cost/conftest.py @@ -176,8 +176,13 @@ def tx_data( `FLOOR_GAS_COST_LESS_THAN_OR_EQUAL_TO_INTRINSIC_GAS` """ + # Encode `tokens` as `tokens` zero bytes: each zero byte is 1 + # EIP-7623 token, and byte count grows linearly with `tokens` so + # both EIP-7623 (per-token) and EIP-7976 (per-byte) floor costs + # are monotonic — required for the `find_floor_cost_threshold` + # binary search. def tokens_to_data(tokens: int) -> Bytes: - return Bytes(b"\x01" * (tokens // 4) + b"\x00" * (tokens % 4)) + return Bytes(b"\x00" * tokens) fork_intrinsic_cost_calculator = ( fork.transaction_intrinsic_cost_calculator() diff --git a/tests/prague/eip7623_increase_calldata_cost/test_execution_gas.py b/tests/prague/eip7623_increase_calldata_cost/test_execution_gas.py index 7e8bc2c80f6..5ada98c9c06 100644 --- a/tests/prague/eip7623_increase_calldata_cost/test_execution_gas.py +++ b/tests/prague/eip7623_increase_calldata_cost/test_execution_gas.py @@ -65,7 +65,12 @@ def to( pytest.param(1, True, None, id="type_1"), pytest.param(2, True, None, id="type_2"), pytest.param(3, True, None, id="type_3"), - pytest.param(4, True, [Address(1)], id="type_4"), + pytest.param( + 4, + True, + [Address(1)], + id="type_4", + ), ], indirect=["authorization_list"], ) diff --git a/tests/prague/eip7623_increase_calldata_cost/test_refunds.py b/tests/prague/eip7623_increase_calldata_cost/test_refunds.py index fdbe63e0c03..02cbbf12511 100644 --- a/tests/prague/eip7623_increase_calldata_cost/test_refunds.py +++ b/tests/prague/eip7623_increase_calldata_cost/test_refunds.py @@ -89,6 +89,16 @@ def ty(refund_type: RefundType) -> int: return 2 +@pytest.fixture +def state_gas_refund(fork: Fork, refund_type: RefundType) -> int: + """Return the state gas refund (direct return, not subject to 1/5 cap).""" + auth_existing = RefundType.AUTHORIZATION_EXISTING_AUTHORITY + if fork.is_eip_enabled(8037) and auth_existing in refund_type: + gas_costs = fork.gas_costs() + return gas_costs.REFUND_AUTH_PER_EXISTING_ACCOUNT + return 0 + + @pytest.fixture def max_refund(fork: Fork, refund_type: RefundType) -> int: """Return the max refund gas of the transaction.""" @@ -98,11 +108,9 @@ def max_refund(fork: Fork, refund_type: RefundType) -> int: if RefundType.STORAGE_CLEAR in refund_type else 0 ) - max_refund += ( - gas_costs.REFUND_AUTH_PER_EXISTING_ACCOUNT - if RefundType.AUTHORIZATION_EXISTING_AUTHORITY in refund_type - else 0 - ) + auth_existing = RefundType.AUTHORIZATION_EXISTING_AUTHORITY + if not fork.is_eip_enabled(8037) and auth_existing in refund_type: + max_refund += gas_costs.REFUND_AUTH_PER_EXISTING_ACCOUNT return max_refund @@ -172,6 +180,7 @@ def execution_gas_used( tx_intrinsic_gas_cost_before_execution: int, tx_floor_data_cost: int, max_refund: int, + state_gas_refund: int, prefix_code_gas: int, refund_test_type: RefundTestType, ) -> int: @@ -189,7 +198,9 @@ def execution_gas_used( def execution_gas_cost(execution_gas: int) -> int: total_gas_used = tx_intrinsic_gas_cost_before_execution + execution_gas - return total_gas_used - min(max_refund, total_gas_used // 5) + effective_gas = total_gas_used - state_gas_refund + capped_refund = min(max_refund, effective_gas // 5) + return effective_gas - capped_refund execution_gas = prefix_code_gas @@ -212,8 +223,6 @@ def execution_gas_cost(execution_gas: int) -> int: refund_test_type == RefundTestType.EXECUTION_GAS_MINUS_REFUND_GREATER_THAN_DATA_FLOOR ): - # Keep incrementing until we actually get gas_used > tx_floor_data_cost - # (adding just 1 may not be enough due to refund cap boundary effects) while execution_gas_cost(execution_gas) <= tx_floor_data_cost: execution_gas += 1 return execution_gas @@ -231,16 +240,19 @@ def refund( tx_intrinsic_gas_cost_before_execution: int, execution_gas_used: int, max_refund: int, + state_gas_refund: int, ) -> int: """Return the refund gas of the transaction.""" total_gas_used = ( tx_intrinsic_gas_cost_before_execution + execution_gas_used ) - return min(max_refund, total_gas_used // 5) + effective_gas = total_gas_used - state_gas_refund + return min(max_refund, effective_gas // 5) @pytest.fixture def to( + fork: Fork, pre: Alloc, execution_gas_used: int, prefix_code: Bytecode, @@ -250,16 +262,48 @@ def to( """ Return a contract that consumes the expected execution gas. - At the moment we naively use JUMPDEST to consume the gas, which can yield - very big contracts. - - Ideally, we can use memory expansion to consume gas. + Uses a counting loop when the naive JUMPDEST approach would exceed the max + contract code size. Loop gas costs are derived from the fork. """ extra_gas = execution_gas_used - prefix_code_gas - return pre.deploy_contract( - prefix_code + (Op.JUMPDEST * extra_gas) + Op.STOP, - storage=code_storage, + code = prefix_code + (Op.JUMPDEST * extra_gas) + Op.STOP + if len(code) <= fork.max_code_size(): + return pre.deploy_contract(code, storage=code_storage) + + loop_target = len(prefix_code) + len(Op.PUSH2(0)) + setup = Op.PUSH2(0) + loop_body = ( + Op.JUMPDEST + + Op.PUSH1(1) + + Op.SWAP1 + + Op.SUB + + Op.DUP1 + + Op.PUSH1(loop_target) + + Op.JUMPI + ) + teardown = Op.POP + overhead = setup.gas_cost(fork) + teardown.gas_cost(fork) + gas_per_iter = loop_body.gas_cost(fork) + + available = extra_gas - overhead + iterations = available // gas_per_iter + remaining = available % gas_per_iter + + code = ( + prefix_code + + Op.PUSH2(iterations) + + Op.JUMPDEST + + Op.PUSH1(1) + + Op.SWAP1 + + Op.SUB + + Op.DUP1 + + Op.PUSH1(loop_target) + + Op.JUMPI + + Op.POP + + (Op.JUMPDEST * remaining) + + Op.STOP ) + return pre.deploy_contract(code, storage=code_storage) @pytest.fixture @@ -295,6 +339,9 @@ def tx_gas_limit( RefundType.AUTHORIZATION_EXISTING_AUTHORITY, ], ) +# TODO[EIP-8037]: Authorization state gas split affects +# refund calculations for Amsterdam. +@pytest.mark.valid_before("EIP8037") def test_gas_refunds_from_data_floor( state_test: StateTestFiller, pre: Alloc, @@ -303,6 +350,7 @@ def test_gas_refunds_from_data_floor( tx_intrinsic_gas_cost_before_execution: int, execution_gas_used: int, refund: int, + state_gas_refund: int, refund_test_type: RefundTestType, ) -> None: """ @@ -310,7 +358,10 @@ def test_gas_refunds_from_data_floor( floor. """ gas_used = ( - tx_intrinsic_gas_cost_before_execution + execution_gas_used - refund + tx_intrinsic_gas_cost_before_execution + + execution_gas_used + - state_gas_refund + - refund ) if ( refund_test_type diff --git a/tests/prague/eip7623_increase_calldata_cost/test_transaction_validity.py b/tests/prague/eip7623_increase_calldata_cost/test_transaction_validity.py index 2750316ce79..d42d8c4f86e 100644 --- a/tests/prague/eip7623_increase_calldata_cost/test_transaction_validity.py +++ b/tests/prague/eip7623_increase_calldata_cost/test_transaction_validity.py @@ -156,6 +156,10 @@ def test_transaction_validity_type_0( "ty", [pytest.param(1, id="type_1"), pytest.param(2, id="type_2")], ) +# TODO[EIP-8037]: Contract creation state gas +# (G_TRANSACTION_CREATE) split affects intrinsic gas +# calculation for Amsterdam. +@pytest.mark.valid_before("EIP8037") def test_transaction_validity_type_1_type_2( state_test: StateTestFiller, pre: Alloc, diff --git a/tests/prague/eip7702_set_code_tx/test_calls.py b/tests/prague/eip7702_set_code_tx/test_calls.py index 3fc59d874dc..ec41ee684f2 100644 --- a/tests/prague/eip7702_set_code_tx/test_calls.py +++ b/tests/prague/eip7702_set_code_tx/test_calls.py @@ -9,6 +9,7 @@ Address, Alloc, Environment, + Fork, Op, StateTestFiller, Transaction, @@ -84,6 +85,7 @@ def target_address( def test_delegate_call_targets( state_test: StateTestFiller, pre: Alloc, + fork: Fork, target_account_type: TargetAccountType, target_address: Address, delegate: bool, @@ -109,6 +111,28 @@ def test_delegate_call_targets( slot_call_result, Op.DELEGATECALL(address=target_address) ) + Op.SSTORE(slot_code_worked, value_code_worked) + intrinsic = fork.transaction_intrinsic_cost_calculator() + # The DELEGATECALL forwards 63/64 of remaining gas; LEGACY_CONTRACT_INVALID + # consumes the lot, leaving only 1/64 to host the caller's two SSTORE state + # writes. Lift gas_limit past the EIP-7825 cap so the EIP-8037 reservoir + # holds the SSTORE state work and the inner-call burn doesn't drain it. + gas_cap = fork.transaction_gas_limit_cap() + state_needed = delegate_call_code.state_cost(fork) + 2 * Op.SSTORE( + new_value=1 + ).state_cost(fork) + base_gas = ( + intrinsic( + calldata=delegate_call_code, + contract_creation=call_from_initcode, + ) + + delegate_call_code.gas_cost(fork) + + 4_000_000 # forwarded inner-call envelope + ) + if gas_cap is not None and state_needed > 0: + gas_limit = gas_cap + state_needed + else: + gas_limit = base_gas + if call_from_initcode: # Call from initcode caller_contract = delegate_call_code + Op.RETURN(0, 0) @@ -116,7 +140,7 @@ def test_delegate_call_targets( sender=sender_address, to=None, data=caller_contract, - gas_limit=4_000_000, + gas_limit=gas_limit, ) calling_contract_address = tx.created_contract else: @@ -127,7 +151,7 @@ def test_delegate_call_targets( tx = Transaction( sender=sender_address, to=calling_contract_address, - gas_limit=4_000_000, + gas_limit=gas_limit, ) calling_storage = { diff --git a/tests/prague/eip7702_set_code_tx/test_gas.py b/tests/prague/eip7702_set_code_tx/test_gas.py index 0f594171faf..3ff7866ea6a 100644 --- a/tests/prague/eip7702_set_code_tx/test_gas.py +++ b/tests/prague/eip7702_set_code_tx/test_gas.py @@ -34,6 +34,9 @@ extend_with_defaults, ) +from ...amsterdam.eip8037_state_creation_gas_cost_increase.spec import ( + Spec as Spec8037, +) from .helpers import AddressType, ChainIDType from .spec import Spec, ref_spec_7702 @@ -794,13 +797,27 @@ def gas_test_parameter_args( ] if include_many: - # Fit as many authorizations as possible within the transaction gas - # limit. - max_gas = 16_777_216 - 21_000 + # Fit as many authorizations as possible within the + # transaction gas limit cap. Under EIP-8037 the per-auth + # intrinsic grows with cpsb; on older forks it is + # `Spec.AUTH_PER_EMPTY_ACCOUNT`. Divide by the larger so the + # count fits at any fork — older forks simply exercise fewer + # authorizations than the cap allows. + eip_8037_auth_cost = ( + Spec8037.PER_AUTH_BASE_COST + + ( + Spec8037.STATE_BYTES_PER_NEW_ACCOUNT + + Spec8037.STATE_BYTES_PER_AUTH_BASE + ) + * Spec8037.COST_PER_STATE_BYTE + ) + max_gas = Spec8037.TX_MAX_GAS_LIMIT - 21_000 # TX_BASE if execution_gas_allowance: # Leave some gas for the execution of the test code. max_gas -= 1_000_000 - many_authorizations_count = max_gas // Spec.AUTH_PER_EMPTY_ACCOUNT + many_authorizations_count = max_gas // max( + Spec.AUTH_PER_EMPTY_ACCOUNT, eip_8037_auth_cost + ) cases += [ pytest.param( { @@ -841,6 +858,11 @@ def gas_test_parameter_args( ) ) @pytest.mark.slow() +# TODO[EIP-8037]: discount accounting here uses Prague refund_counter +# mechanics (with the EIP-3529 1/5 cap). On Amsterdam the existing-authority +# refund flows through state_gas_reservoir / state_refund and is not capped +# the same way. Needs a fork-aware rewrite before this can run on Amsterdam. +@pytest.mark.valid_before("EIP8037") def test_gas_cost( state_test: StateTestFiller, pre: Alloc, diff --git a/tests/prague/eip7702_set_code_tx/test_invalid_tx.py b/tests/prague/eip7702_set_code_tx/test_invalid_tx.py index 89ba23aa985..e5100127416 100644 --- a/tests/prague/eip7702_set_code_tx/test_invalid_tx.py +++ b/tests/prague/eip7702_set_code_tx/test_invalid_tx.py @@ -324,8 +324,8 @@ def test_invalid_tx_invalid_nonce_as_list( delegate_address: Address, ) -> None: """ - Test sending a transaction where the nonce field of an authorization - overflows the maximum value. + Test sending a transaction where the nonce field of an authorization is + encoded as a list instead of a scalar. """ auth_signer = pre.fund_eoa() @@ -368,7 +368,7 @@ def test_invalid_tx_invalid_nonce_encoding( delegate_address: Address, ) -> None: """ - Test sending a transaction where the chain id field of an authorization has + Test sending a transaction where the nonce field of an authorization has an incorrect encoding. """ diff --git a/tests/prague/eip7702_set_code_tx/test_set_code_txs.py b/tests/prague/eip7702_set_code_tx/test_set_code_txs.py index 0a08b2cbfb9..06e86c41a85 100644 --- a/tests/prague/eip7702_set_code_tx/test_set_code_txs.py +++ b/tests/prague/eip7702_set_code_tx/test_set_code_txs.py @@ -52,7 +52,9 @@ from ...cancun.eip4844_blobs.spec import Spec as Spec4844 from ..eip6110_deposits.helpers import DepositRequest from ..eip7002_el_triggerable_withdrawals.helpers import WithdrawalRequest +from ..eip7002_el_triggerable_withdrawals.spec import Spec as Spec7002 from ..eip7251_consolidations.helpers import ConsolidationRequest +from ..eip7251_consolidations.spec import Spec as Spec7251 from .helpers import AddressType from .spec import Spec, ref_spec_7702 @@ -163,6 +165,7 @@ def test_self_sponsored_set_code( def test_set_code_to_sstore( state_test: StateTestFiller, pre: Alloc, + fork: Fork, suffix: Bytecode, succeeds: bool, tx_value: int, @@ -188,8 +191,15 @@ def test_set_code_to_sstore( set_code, ) + # 3 first-time SSTOREs plus auth+delegation; each SSTORE adds + # `sstore_state_gas` under EIP-8037, and an empty-account + # authority adds NEW_ACCOUNT (both 0 otherwise). tx = Transaction( - gas_limit=500_000, + gas_limit=( + 500_000 + + fork.gas_costs().NEW_ACCOUNT + + 3 * Op.SSTORE(new_value=1).state_cost(fork) + ), to=auth_signer, value=tx_value, authorization_list=[ @@ -275,6 +285,7 @@ def test_set_code_to_non_empty_storage_non_zero_nonce( def test_set_code_to_sstore_then_sload( blockchain_test: BlockchainTestFiller, pre: Alloc, + fork: Fork, access_list_in_tx: str | None, ) -> None: """ @@ -296,8 +307,11 @@ def test_set_code_to_sstore_then_sload( ) set_code_2_address = pre.deploy_contract(set_code_2) + gas_limit = 100_000 + if fork.is_eip_enabled(8037): + gas_limit = 500_000 # TODO: auto gas limit will remove this tx_1 = Transaction( - gas_limit=100_000, + gas_limit=gas_limit, to=auth_signer, value=0, authorization_list=[ @@ -323,7 +337,7 @@ def test_set_code_to_sstore_then_sload( else [] ) tx_2 = Transaction( - gas_limit=100_000, + gas_limit=gas_limit, to=auth_signer, value=0, authorization_list=[ @@ -368,6 +382,7 @@ def test_set_code_to_sstore_then_sload( def test_set_code_to_tstore_reentry( state_test: StateTestFiller, pre: Alloc, + fork: Fork, call_opcode: Op, return_opcode: Op, ) -> None: @@ -388,8 +403,11 @@ def test_set_code_to_tstore_reentry( ) set_code_to_address = pre.deploy_contract(set_code) + gas_limit = 100_000 + if fork.is_eip_enabled(8037): + gas_limit = 500_000 # TODO: auto gas limit will remove this tx = Transaction( - gas_limit=100_000, + gas_limit=gas_limit, to=auth_signer, value=0, authorization_list=[ @@ -430,6 +448,7 @@ def test_set_code_to_tstore_reentry( def test_set_code_to_tstore_available_at_correct_address( state_test: StateTestFiller, pre: Alloc, + fork: Fork, call_opcode: Op, call_eoa_first: bool, ) -> None: @@ -461,8 +480,11 @@ def make_call(call_type: Op, call_eoa: bool) -> Bytecode: target_call_chain_address = pre.deploy_contract(chain_code) + gas_limit = 100_000 + if fork.is_eip_enabled(8037): + gas_limit = 500_000 # TODO: auto gas limit will remove this tx = Transaction( - gas_limit=100_000, + gas_limit=gas_limit, to=target_call_chain_address, value=0, authorization_list=[ @@ -682,9 +704,12 @@ def test_delegated_eoa_can_send_creating_tx( ) assert initcode_len == len(initcode) + gas_limit = 200_000 + (Op.SSTORE(key_warm=False) * 7).gas_cost(fork) + if fork.is_eip_enabled(8037): + gas_limit = 10_000_000 tx = Transaction( ty=tx_type, - gas_limit=200_000 + (Op.SSTORE(key_warm=False) * 7).gas_cost(fork), + gas_limit=gas_limit, to=None, value=0, data=initcode, @@ -2340,6 +2365,7 @@ def test_set_code_all_invalid_authorization_tuples( def test_set_code_using_chain_specific_id( state_test: StateTestFiller, pre: Alloc, + fork: Fork, chain_config: ChainConfig, ) -> None: """ @@ -2353,8 +2379,11 @@ def test_set_code_using_chain_specific_id( set_code = Op.SSTORE(success_slot, 1) + Op.STOP set_code_to_address = pre.deploy_contract(set_code) + gas_limit = 100_000 + if fork.is_eip_enabled(8037): + gas_limit = 500_000 # TODO: auto gas limit will remove this tx = Transaction( - gas_limit=100_000, + gas_limit=gas_limit, to=auth_signer, value=0, authorization_list=[ @@ -2407,6 +2436,7 @@ def test_set_code_using_chain_specific_id( def test_set_code_using_valid_synthetic_signatures( state_test: StateTestFiller, pre: Alloc, + fork: Fork, chain_config: ChainConfig, v: int, r: int, @@ -2432,8 +2462,11 @@ def test_set_code_using_valid_synthetic_signatures( auth_signer = authorization_tuple.signer + gas_limit = 100_000 + if fork.is_eip_enabled(8037): + gas_limit = 500_000 # TODO: auto gas limit will remove this tx = Transaction( - gas_limit=100_000, + gas_limit=gas_limit, to=auth_signer, value=0, authorization_list=[authorization_tuple], @@ -2497,6 +2530,7 @@ def test_set_code_using_valid_synthetic_signatures( def test_valid_tx_invalid_auth_signature( state_test: StateTestFiller, pre: Alloc, + fork: Fork, chain_config: ChainConfig, v: int, r: int, @@ -2521,8 +2555,12 @@ def test_valid_tx_invalid_auth_signature( s=s, ) + gas_limit = 100_000 + if fork.is_eip_enabled(8037): + gas_limit = 500_000 # TODO: auto gas limit will remove this + tx = Transaction( - gas_limit=100_000, + gas_limit=gas_limit, to=callee_address, value=0, authorization_list=[authorization_tuple], @@ -2544,8 +2582,8 @@ def test_valid_tx_invalid_auth_signature( def test_signature_s_out_of_range( state_test: StateTestFiller, pre: Alloc, - chain_config: ChainConfig, fork: Fork, + chain_config: ChainConfig, ) -> None: """ Test sending a transaction with an authorization tuple where the signature @@ -2573,8 +2611,12 @@ def test_signature_s_out_of_range( entry_code = Op.SSTORE(success_slot, 1) + Op.STOP entry_address = pre.deploy_contract(entry_code) + gas_limit = 100_000 + if fork.is_eip_enabled(8037): + gas_limit = 500_000 # TODO: auto gas limit will remove this + tx = Transaction( - gas_limit=100_000, + gas_limit=gas_limit, to=entry_address, value=0, authorization_list=[authorization_tuple], @@ -2649,6 +2691,7 @@ class InvalidChainID(StrEnum): def test_valid_tx_invalid_chain_id( state_test: StateTestFiller, pre: Alloc, + fork: Fork, chain_config: ChainConfig, invalid_chain_id_case: InvalidChainID, ) -> None: @@ -2689,8 +2732,12 @@ def test_valid_tx_invalid_chain_id( ) entry_address = pre.deploy_contract(entry_code) + gas_limit = 100_000 + if fork.is_eip_enabled(8037): + gas_limit = 500_000 # TODO: auto gas limit will remove this + tx = Transaction( - gas_limit=100_000, + gas_limit=gas_limit, to=entry_address, value=0, authorization_list=[authorization], @@ -2743,9 +2790,9 @@ def test_valid_tx_invalid_chain_id( def test_nonce_validity( state_test: StateTestFiller, pre: Alloc, + fork: Fork, account_nonce: int, authorization_nonce: int, - fork: Fork, ) -> None: """ Test sending a transaction where the nonce field of an authorization almost @@ -2779,8 +2826,12 @@ def test_nonce_validity( ) entry_address = pre.deploy_contract(entry_code) + gas_limit = 100_000 + if fork.is_eip_enabled(8037): + gas_limit = 500_000 # TODO: auto gas limit will remove this + tx = Transaction( - gas_limit=100_000, + gas_limit=gas_limit, to=entry_address, value=0, authorization_list=[authorization], @@ -2895,6 +2946,7 @@ def test_nonce_validity( def test_nonce_overflow_after_first_authorization( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """ Test sending a transaction with two authorization where the first one bumps @@ -2931,8 +2983,12 @@ def test_nonce_overflow_after_first_authorization( ) entry_address = pre.deploy_contract(entry_code) + gas_limit = 200_000 + if fork.is_eip_enabled(8037): + gas_limit = 500_000 # TODO: auto gas limit will remove this + tx = Transaction( - gas_limit=200_000, + gas_limit=gas_limit, to=entry_address, value=0, authorization_list=authorization_list, @@ -3114,6 +3170,7 @@ def test_set_code_to_precompile( @pytest.mark.with_all_precompiles +@pytest.mark.valid_before("EIP8037") def test_set_code_to_precompile_not_enough_gas_for_precompile_execution( state_test: StateTestFiller, pre: Alloc, @@ -3123,6 +3180,18 @@ def test_set_code_to_precompile_not_enough_gas_for_precompile_execution( """ Test set code to precompile and making direct call in same transaction with intrinsic gas only, no extra gas for precompile execution. + + Redundant from EIP-8037: EIP-8037 replaces the one-dimensional + gas model this test verifies. Auth intrinsic cost becomes + (STATE_BYTES_PER_AUTH_BASE + STATE_BYTES_PER_NEW_ACCOUNT) * + cost_per_state_byte per auth (state gas), plus + PER_AUTH_BASE_COST (regular gas). Auth refund for existing + accounts goes to state_gas_reservoir instead of refund_counter, + making the discount calculation (PER_EMPTY_ACCOUNT_COST - + PER_AUTH_BASE_COST) and receipt gas expectation invalid. + + TODO: Add EIP-8037-specific variant in tests/amsterdam/ that + verifies receipt gas and auth refund under EIP-8037's 2D model. """ auth_signer = pre.fund_eoa(amount=1) auth = AuthorizationTuple( @@ -3132,8 +3201,13 @@ def test_set_code_to_precompile_not_enough_gas_for_precompile_execution( intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( authorization_list_or_count=[auth], ) + gas_costs = fork.gas_costs() + per_auth_discount = ( + gas_costs.AUTH_PER_EMPTY_ACCOUNT + - gas_costs.REFUND_AUTH_PER_EXISTING_ACCOUNT + ) discount = min( - Spec.AUTH_PER_EMPTY_ACCOUNT - Spec.REFUND_AUTH_PER_EXISTING_ACCOUNT, + per_auth_discount, intrinsic_gas // 5, # max discount EIP-3529 ) @@ -3242,7 +3316,7 @@ def test_set_code_to_system_contract( ) caller_payload = deposit_request.calldata call_value = deposit_request.value - case Address(0x00000961EF480EB55E80D19AD83579A64C007002): # EIP-7002 + case Address(Spec7002.WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS): # Fabricate a valid withdrawal request to the set-code account withdrawal_request = WithdrawalRequest( source_address=0x01, @@ -3252,7 +3326,7 @@ def test_set_code_to_system_contract( ) caller_payload = withdrawal_request.calldata call_value = withdrawal_request.value - case Address(0x0000BBDDC7CE488642FB579F8B00F3A590007251): # EIP-7251 + case Address(Spec7251.CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS): # Fabricate a valid consolidation request to the set-code account consolidation_request = ConsolidationRequest( source_address=0x01, @@ -3307,10 +3381,19 @@ def test_set_code_to_system_contract( caller_code_address = pre.deploy_contract(caller_code) sender = pre.fund_eoa() + # The 7002/7251 system contracts enqueue multiple state entries per + # request (4 and 5 slots respectively); pad gas_limit by that many + # SSTORE state-set worths so the EIP-8037 reservoir absorbs the work + # rather than draining the tx's regular pool through DELEGATECALL. + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + extra_state_slots = { + Address(Spec7002.WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS): 4, + Address(Spec7251.CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS): 5, + }.get(Address(system_contract), 0) txs = [ Transaction( sender=sender, - gas_limit=500_000, + gas_limit=500_000 + extra_state_slots * sstore_state_gas, to=caller_code_address, value=call_value, data=caller_payload, @@ -3575,6 +3658,7 @@ def test_reset_code( def test_contract_create( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test sending type-4 tx as a create transaction.""" authorization_tuple = AuthorizationTuple( @@ -3582,8 +3666,11 @@ def test_contract_create( nonce=0, signer=pre.fund_eoa(), ) + gas_limit = 100_000 + if fork.is_eip_enabled(8037): + gas_limit = 500_000 # TODO: auto gas limit will remove this tx = Transaction( - gas_limit=100_000, + gas_limit=gas_limit, to=None, value=0, authorization_list=[authorization_tuple], @@ -3640,6 +3727,7 @@ def test_empty_authorization_list( def test_delegation_clearing( state_test: StateTestFiller, pre: Alloc, + fork: Fork, pre_set_delegation_code: Bytecode | None, self_sponsored: bool, ) -> None: @@ -3687,8 +3775,12 @@ def test_delegation_clearing( signer=auth_signer, ) + gas_limit = 200_000 + if fork.is_eip_enabled(8037): + gas_limit = 500_000 # TODO: auto gas limit will remove this + tx = Transaction( - gas_limit=200_000, + gas_limit=gas_limit, to=entry_address, value=0, authorization_list=[authorization], @@ -3735,6 +3827,7 @@ def test_delegation_clearing( def test_delegation_clearing_tx_to( state_test: StateTestFiller, pre: Alloc, + fork: Fork, pre_set_delegation_code: Bytecode | None, self_sponsored: bool, ) -> None: @@ -3760,8 +3853,11 @@ def test_delegation_clearing_tx_to( sender = pre.fund_eoa() if not self_sponsored else auth_signer + # When `auth_signer` is an empty account (non-self-sponsored + # variant) the auth charges NEW_ACCOUNT state gas under EIP-8037 + # (0 otherwise). tx = Transaction( - gas_limit=200_000, + gas_limit=200_000 + fork.gas_costs().NEW_ACCOUNT, to=auth_signer, value=0, authorization_list=[ @@ -3798,6 +3894,7 @@ def test_delegation_clearing_tx_to( def test_delegation_clearing_and_set( state_test: StateTestFiller, pre: Alloc, + fork: Fork, pre_set_delegation_code: Bytecode | None, ) -> None: """ @@ -3823,8 +3920,12 @@ def test_delegation_clearing_and_set( sender = pre.fund_eoa() + gas_limit = 200_000 + if fork.is_eip_enabled(8037): + gas_limit = 500_000 # TODO: auto gas limit will remove this + tx = Transaction( - gas_limit=200_000, + gas_limit=gas_limit, to=auth_signer, value=0, authorization_list=[ @@ -3869,6 +3970,7 @@ def test_delegation_clearing_and_set( def test_delegation_clearing_failing_tx( state_test: StateTestFiller, pre: Alloc, + fork: Fork, entry_code: Bytecode, ) -> None: """ @@ -3888,8 +3990,12 @@ def test_delegation_clearing_failing_tx( signer=auth_signer, ) + gas_limit = 100_000 + if fork.is_eip_enabled(8037): + gas_limit = 500_000 # TODO: auto gas limit will remove this + tx = Transaction( - gas_limit=100_000, + gas_limit=gas_limit, to=entry_address, value=0, authorization_list=[authorization], @@ -3920,6 +4026,7 @@ def test_delegation_clearing_failing_tx( def test_deploying_delegation_designation_contract( state_test: StateTestFiller, pre: Alloc, + fork: Fork, initcode_is_delegation_designation: bool, ) -> None: """ @@ -3939,10 +4046,14 @@ def test_deploying_delegation_designation_contract( deploy_code=Spec.delegation_designation(set_to_address) ) + gas_limit = 100_000 + if fork.is_eip_enabled(8037): + gas_limit = 500_000 # TODO: auto gas limit will remove this + tx = Transaction( sender=sender, to=None, - gas_limit=100_000, + gas_limit=gas_limit, data=initcode, ) @@ -4061,7 +4172,8 @@ def test_many_delegations( max_gas = env.gas_limit gas_for_delegations = max_gas - 21_000 - 20_000 - (3 * 2) - delegation_count = gas_for_delegations // Spec.AUTH_PER_EMPTY_ACCOUNT + gas_costs = fork.gas_costs() + delegation_count = gas_for_delegations // gas_costs.AUTH_PER_EMPTY_ACCOUNT success_slot = 1 entry_code = Op.SSTORE(success_slot, 1) + Op.STOP @@ -4213,6 +4325,7 @@ def test_authorization_reusing_nonce( def test_set_code_from_account_with_non_delegating_code( state_test: StateTestFiller, pre: Alloc, + fork: Fork, set_code_type: AddressType, self_sponsored: bool, ) -> None: @@ -4244,8 +4357,12 @@ def test_set_code_from_account_with_non_delegating_code( raise ValueError(f"Unsupported set code type: {set_code_type}") callee_address = pre.deploy_contract(Op.SSTORE(0, 1) + Op.STOP) + gas_limit = 100_000 + if fork.is_eip_enabled(8037): + gas_limit = 500_000 # TODO: auto gas limit will remove this + tx = Transaction( - gas_limit=100_000, + gas_limit=gas_limit, to=callee_address, authorization_list=[ AuthorizationTuple( diff --git a/tests/prague/eip7702_set_code_tx/test_set_code_txs_2.py b/tests/prague/eip7702_set_code_tx/test_set_code_txs_2.py index c92a3c34ff6..bb54b9b4fd3 100644 --- a/tests/prague/eip7702_set_code_tx/test_set_code_txs_2.py +++ b/tests/prague/eip7702_set_code_tx/test_set_code_txs_2.py @@ -36,11 +36,21 @@ @pytest.mark.valid_from("Prague") +# TODO[EIP-8037]: Amsterdam expected_loop_count needs +# recalculating due to state gas. +@pytest.mark.valid_before("EIP8037") +# TODO[EIP-8037]: Fix Storage.KeyValueMismatchError for +# contract_loop expected values. +@pytest.mark.skip( + reason="EIP-8037: pointer loop storage values need " + "fixing for state gas model" +) @pytest.mark.parametrize("sender_delegated", [True, False]) @pytest.mark.parametrize("sender_is_auth_signer", [True, False]) def test_pointer_contract_pointer_loop( state_test: StateTestFiller, pre: Alloc, + fork: Fork, sender_delegated: bool, sender_is_auth_signer: bool, ) -> None: @@ -74,7 +84,10 @@ def test_pointer_contract_pointer_loop( ) storage_loop: Storage = Storage() - contract_worked = storage_loop.store_next(112, "contract_loop_worked") + expected_loop_count = 117 if fork.is_eip_enabled(8037) else 112 + contract_worked = storage_loop.store_next( + expected_loop_count, "contract_loop_worked" + ) contract_loop = pre.deploy_contract( code=Op.SSTORE(contract_worked, Op.ADD(1, Op.SLOAD(0))) + Op.CALL(gas=1_000_000, address=pointer_a) @@ -90,7 +103,7 @@ def test_pointer_contract_pointer_loop( tx = Transaction( to=pointer_a, - gas_limit=1_000_000, + gas_limit=(3_000_000 if fork.is_eip_enabled(8037) else 1_000_000), data=b"", value=0, sender=sender, @@ -271,7 +284,7 @@ def test_pointer_normal( @pytest.mark.valid_from("Prague") def test_pointer_measurements( - blockchain_test: BlockchainTestFiller, pre: Alloc + blockchain_test: BlockchainTestFiller, pre: Alloc, fork: Fork ) -> None: """ Check extcode* operations on pointer before and after pointer is set. @@ -383,9 +396,13 @@ def test_pointer_measurements( + Op.STOP, ) + # The pointer-code measurement contract performs ~10 first-time + # SSTOREs; each adds `sstore_state_gas` under EIP-8037 (0 + # otherwise). The non-pointer txs reuse the same headroom. + pointer_state = 10 * Op.SSTORE(new_value=1).state_cost(fork) tx = Transaction( to=contract_measurements, - gas_limit=1_000_000, + gas_limit=1_000_000 + pointer_state, data=b"", value=0, sender=sender, @@ -393,7 +410,7 @@ def test_pointer_measurements( tx_pointer = Transaction( to=contract_measurements_pointer, - gas_limit=1_000_000, + gas_limit=1_000_000 + pointer_state, data=b"", value=0, sender=sender, @@ -408,7 +425,7 @@ def test_pointer_measurements( tx_pointer_call = Transaction( to=pointer, - gas_limit=1_000_000, + gas_limit=1_000_000 + pointer_state, data=bytes.fromhex("11223344"), value=3, sender=sender, @@ -679,6 +696,7 @@ class AccessListTo(Enum): [AccessListTo.POINTER_ADDRESS, AccessListTo.CONTRACT_ADDRESS], ) @pytest.mark.valid_from("Prague") +@pytest.mark.valid_before("EIP8037") def test_gas_diff_pointer_vs_direct_call( blockchain_test: BlockchainTestFiller, pre: Alloc, @@ -688,8 +706,24 @@ def test_gas_diff_pointer_vs_direct_call( access_list_to: AccessListTo, ) -> None: """ - Check the gas difference when calling the contract directly vs as a pointer + Check the gas difference when calling the contract directly vs + as a pointer. + Combine with AccessList and AuthTuple gas reductions scenarios. + + Redundant from Amsterdam: EIP-8037 replaces the one-dimensional + SSTORE gas cost (G_STORAGE_SET) with a two-dimensional split: + regular gas (GAS_COLD_STORAGE_WRITE - GAS_COLD_SLOAD) and state gas + (STATE_BYTES_PER_STORAGE_SET * cost_per_state_byte). In sub-calls + state_gas_left=0, so state gas falls to gas_left -- changing what + the GAS opcode reports. Auth refund + (STATE_BYTES_PER_NEW_ACCOUNT * cost_per_state_byte) goes to + state_gas_reservoir, further altering gas visibility between + frames. + + TODO: Add Amsterdam-specific variant in tests/amsterdam/ that + verifies pointer vs direct call gas costs under EIP-8037's 2D + gas model with reservoir semantics. """ env = Environment() @@ -877,16 +911,27 @@ def test_gas_diff_pointer_vs_direct_call( @pytest.mark.valid_from("Prague") +@pytest.mark.valid_before("EIP8037") def test_pointer_call_followed_by_direct_call( state_test: StateTestFiller, pre: Alloc, fork: Fork, ) -> None: """ - If we first call by pointer then direct call, will the call/sload be hot - The direct call will warm because pointer access marks it warm But the - sload is still cold because storage marked hot from pointer's account in a - pointer call. + If we first call by pointer then direct call, will the + call/sload be hot. + + The direct call will warm because pointer access marks it warm. + But the sload is still cold because storage marked hot from + pointer's account in a pointer call. + + Redundant from Amsterdam: EIP-8037 replaces one-dimensional + SSTORE gas costs with a 2D split (regular + state gas), changing + what the GAS opcode reports. See + test_gas_diff_pointer_vs_direct_call for details. + + TODO: Add Amsterdam-specific variant in tests/amsterdam/ that + verifies pointer warming behavior with 2D gas cost measurements. """ env = Environment() @@ -1325,7 +1370,9 @@ class ReentryAction(IntEnum): @pytest.mark.valid_from("Prague") -def test_pointer_reentry(state_test: StateTestFiller, pre: Alloc) -> None: +def test_pointer_reentry( + state_test: StateTestFiller, pre: Alloc, fork: Fork +) -> None: """ Check operations when reenter the pointer again. @@ -1437,9 +1484,20 @@ def test_pointer_reentry(state_test: StateTestFiller, pre: Alloc) -> None: storage_b[slot_reentry_address] = contract_b + # Many nested CALLs and SSTOREs across pointer-via-proxy reentry. + # Lift above the EIP-7825 cap so the EIP-8037 reservoir holds the + # SSTORE state work, otherwise it spills into each frame's regular + # share and the deep call chain runs out. + gas_cap = fork.transaction_gas_limit_cap() + sstore_count = 10 # rough envelope across all frames + tx_gas_limit = ( + gas_cap + sstore_count * Op.SSTORE(new_value=1).state_cost(fork) + if gas_cap is not None and fork.is_eip_enabled(8037) + else 2_000_000 + ) tx = Transaction( to=pointer_b, - gas_limit=2_000_000, + gas_limit=tx_gas_limit, data=Hash(contract_b, left_padding=True) + Hash(ReentryAction.CALL_PROXY, left_padding=True), value=0, @@ -1775,6 +1833,7 @@ class DelegationTo(Enum): def test_double_auth( state_test: StateTestFiller, pre: Alloc, + fork: Fork, first_delegation: DelegationTo, second_delegation: DelegationTo, ) -> None: @@ -1808,7 +1867,7 @@ def test_double_auth( tx = Transaction( to=contract_main, - gas_limit=200_000, + gas_limit=(500_000 if fork.is_eip_enabled(8037) else 200_000), data=b"", value=0, sender=sender, @@ -1868,6 +1927,7 @@ def test_double_auth( def test_pointer_resets_an_empty_code_account_with_storage( blockchain_test: BlockchainTestFiller, pre: Alloc, + fork: Fork, ) -> None: """ So in Block1 we create a sender with empty code, but non empty storage @@ -1885,14 +1945,24 @@ def test_pointer_resets_an_empty_code_account_with_storage( sender_storage = Storage() sender_storage.store_next(1, "slot1") sender_storage.store_next(2, "slot2") - contract_1 = pre.deploy_contract( - code=Op.SSTORE(pointer_storage.store_next(1, "slot1"), 1) - + Op.SSTORE(pointer_storage.store_next(2, "slot2"), 2) + contract_1_code = Op.SSTORE( + pointer_storage.store_next(1, "slot1"), 1 + ) + Op.SSTORE(pointer_storage.store_next(2, "slot2"), 2) + contract_1 = pre.deploy_contract(code=contract_1_code) + + intrinsic_calc = fork.transaction_intrinsic_cost_calculator() + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + # The set-pointer-storage tx authorizes contract_1 then runs its two + # SSTOREs at the pointer; pad gas_limit with the auth + 2 SSTORE state + # work and EIP-1706 slack. + gas_limit = ( + intrinsic_calc(authorization_list_or_count=1) + + contract_1_code.gas_cost(fork) + + sstore_state_gas ) - tx_set_pointer_storage = Transaction( to=pointer, - gas_limit=200_000, + gas_limit=gas_limit, data=b"", value=0, sender=sender, @@ -1906,7 +1976,7 @@ def test_pointer_resets_an_empty_code_account_with_storage( ) tx_set_sender_storage = Transaction( to=sender, - gas_limit=200_000, + gas_limit=gas_limit, data=b"", value=0, sender=sender, @@ -1921,7 +1991,7 @@ def test_pointer_resets_an_empty_code_account_with_storage( tx_reset_code = Transaction( to=pointer, - gas_limit=200_000, + gas_limit=gas_limit, data=b"", value=0, nonce=3, @@ -1974,9 +2044,18 @@ def test_pointer_resets_an_empty_code_account_with_storage( address=contract_create, nonce=1 ) + # contract_create runs SSTORE(1, CREATE) then 3 CALLs into pointers + # whose deploy_code does an SSTORE + SELFDESTRUCT (1 NEW_ACCOUNT for + # CREATE, 1 SSTORE in contract_create, 3 SSTOREs across the pointer + # callees, plus 2 authorizations' state). + tx2_state = ( + fork.gas_costs().NEW_ACCOUNT + + 4 * sstore_state_gas + + fork.transaction_intrinsic_state_gas(authorization_count=2) + ) tx_create_suicide_from_pointer = Transaction( to=contract_create, - gas_limit=800_000, + gas_limit=800_000 + tx2_state + sstore_state_gas, data=Op.SSTORE(6, 6) + Op.MSTORE(0, deploy_code.hex()) + Op.RETURN(32 - len(deploy_code), len(deploy_code)), diff --git a/tests/shanghai/eip3651_warm_coinbase/test_warm_coinbase.py b/tests/shanghai/eip3651_warm_coinbase/test_warm_coinbase.py index 1a33c174470..561e7225fd6 100644 --- a/tests/shanghai/eip3651_warm_coinbase/test_warm_coinbase.py +++ b/tests/shanghai/eip3651_warm_coinbase/test_warm_coinbase.py @@ -91,9 +91,15 @@ def test_warm_coinbase_call_out_of_gas( ) caller_address = pre.deploy_contract(caller_code) + intrinsic_calc = fork.transaction_intrinsic_cost_calculator() tx = Transaction( to=caller_address, - gas_limit=100_000, + gas_limit=( + intrinsic_calc() + + caller_code.gas_cost(fork) + + call_gas_exact + + Op.SSTORE(new_value=1).state_cost(fork) + ), sender=sender, ) @@ -185,9 +191,14 @@ def test_warm_coinbase_gas_usage( # Coinbase is warm after EIP-3651 (Shanghai+), cold before expected_gas = Op.BALANCE(address_warm=(fork >= Shanghai)).gas_cost(fork) + intrinsic_calc = fork.transaction_intrinsic_cost_calculator() tx = Transaction( to=measure_address, - gas_limit=100_000, + gas_limit=( + intrinsic_calc() + + code_gas_measure.gas_cost(fork) + + Op.SSTORE(new_value=1).state_cost(fork) + ), sender=sender, ) @@ -199,9 +210,4 @@ def test_warm_coinbase_gas_usage( ) } - state_test( - env=env, - pre=pre, - post=post, - tx=tx, - ) + state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/shanghai/eip3855_push0/test_push0.py b/tests/shanghai/eip3855_push0/test_push0.py index ff15dc9c9f9..c08208e28aa 100644 --- a/tests/shanghai/eip3855_push0/test_push0.py +++ b/tests/shanghai/eip3855_push0/test_push0.py @@ -14,6 +14,7 @@ Bytecode, CodeGasMeasure, Environment, + Fork, Op, StateTestFiller, Transaction, @@ -83,12 +84,25 @@ def test_push0_contracts( pre: Alloc, post: Alloc, sender: EOA, + fork: Fork, contract_code: Bytecode, expected_storage: Account, ) -> None: """Tests PUSH0 within various deployed contracts.""" push0_contract = pre.deploy_contract(contract_code) - tx = Transaction(to=push0_contract, gas_limit=100_000, sender=sender) + intrinsic_calc = fork.transaction_intrinsic_cost_calculator() + tx = Transaction( + to=push0_contract, + # `contract_code.gas_cost(fork)` covers regular + (under EIP-8037) + # state work for the parametrized snippets; add EIP-1706 slack for + # the trailing SSTORE. + gas_limit=( + intrinsic_calc() + + contract_code.gas_cost(fork) + + Op.SSTORE(new_value=1).state_cost(fork) + ), + sender=sender, + ) post[push0_contract] = expected_storage state_test(env=env, pre=pre, post=post, tx=tx) @@ -115,24 +129,36 @@ def push0_contract_callee(self, pre: Alloc) -> Address: ) return push0_contract + PUSH0_CALL_FORWARDED_GAS = 100_000 + + @pytest.fixture + def push0_contract_caller_code( + self, call_opcode: Op, push0_contract_callee: Address + ) -> Bytecode: + """Bytecode for the caller contract.""" + return ( + Op.SSTORE( + 0, + call_opcode( + gas=self.PUSH0_CALL_FORWARDED_GAS, + address=push0_contract_callee, + ), + ) + + Op.SSTORE(0, 1) + + Op.RETURNDATACOPY(0x1F, 0, 1) + + Op.SSTORE(1, Op.MLOAD(0)) + ) + @pytest.fixture def push0_contract_caller( - self, pre: Alloc, call_opcode: Op, push0_contract_callee: Address + self, pre: Alloc, push0_contract_caller_code: Bytecode ) -> Address: """ Deploy the contract that calls the callee PUSH0 contract into `pre`. This fixture returns its address. """ - call_code = ( - Op.SSTORE( - 0, call_opcode(gas=100_000, address=push0_contract_callee) - ) - + Op.SSTORE(0, 1) - + Op.RETURNDATACOPY(0x1F, 0, 1) - + Op.SSTORE(1, Op.MLOAD(0)) - ) - return pre.deploy_contract(call_code) + return pre.deploy_contract(push0_contract_caller_code) @pytest.mark.xdist_group(name="bigmem") @pytest.mark.parametrize( @@ -153,10 +179,23 @@ def test_push0_contract_during_call_contexts( post: Alloc, sender: EOA, push0_contract_caller: Address, + push0_contract_caller_code: Bytecode, + fork: Fork, ) -> None: """Test PUSH0 during various call contexts.""" + intrinsic_calc = fork.transaction_intrinsic_cost_calculator() tx = Transaction( - to=push0_contract_caller, gas_limit=100_000, sender=sender + to=push0_contract_caller, + # Caller's static cost (3 SSTOREs + CALL static + RETURNDATACOPY + # + MLOAD) plus the forwarded inner-call gas, plus EIP-1706 + # stipend slack on the trailing SSTORE. + gas_limit=( + intrinsic_calc() + + push0_contract_caller_code.gas_cost(fork) + + self.PUSH0_CALL_FORWARDED_GAS + + Op.SSTORE(new_value=1).state_cost(fork) + ), + sender=sender, ) post[push0_contract_caller] = Account(storage={0x00: 0x01, 0x01: 0xFF}) state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/shanghai/eip3860_initcode/test_initcode.py b/tests/shanghai/eip3860_initcode/test_initcode.py index bc19611f6d6..ca478ddfe10 100644 --- a/tests/shanghai/eip3860_initcode/test_initcode.py +++ b/tests/shanghai/eip3860_initcode/test_initcode.py @@ -359,6 +359,13 @@ def post( ) return Alloc({create_contract_address: Account.NONEXISTENT}) + # Gated off under EIP-8037: state gas breaks the single-dimension + # intrinsic-gas equivalence asserted in `exact_intrinsic_gas`. The + # 2D-aware creation-gas metering is covered on Amsterdam by + # `test_create_tx_intrinsic_gas_boundary` and + # `test_max_initcode_size_gas_metering_via_create` in + # `eip8037_state_creation_gas_cost_increase/test_state_gas_create.py`. + @pytest.mark.valid_before("EIP8037") @pytest.mark.slow() def test_gas_usage( self, diff --git a/tests/shanghai/eip4895_withdrawals/test_withdrawals.py b/tests/shanghai/eip4895_withdrawals/test_withdrawals.py index f789f349919..207e864c663 100644 --- a/tests/shanghai/eip4895_withdrawals/test_withdrawals.py +++ b/tests/shanghai/eip4895_withdrawals/test_withdrawals.py @@ -142,22 +142,30 @@ def test_use_value_in_tx( def test_use_value_in_contract( blockchain_test: BlockchainTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test sending value from contract that has not received a withdrawal.""" sender = pre.fund_eoa() recipient = pre.fund_eoa(1) - contract_address = pre.deploy_contract( - Op.SSTORE( - Op.NUMBER, - Op.CALL(address=recipient, value=1000000000), - ) + contract_code = Op.SSTORE( + Op.NUMBER, + Op.CALL(address=recipient, value=1000000000), + ) + contract_address = pre.deploy_contract(contract_code) + + intrinsic_calc = fork.transaction_intrinsic_cost_calculator() + tx_gas = ( + intrinsic_calc() + + contract_code.gas_cost(fork) + + fork.gas_costs().CALL_VALUE + + Op.SSTORE(new_value=1).state_cost(fork) ) (tx_0, tx_1) = ( Transaction( sender=sender, value=0, - gas_limit=100_000, + gas_limit=tx_gas, to=contract_address, ) for _ in range(2) @@ -195,7 +203,7 @@ def test_use_value_in_contract( def test_balance_within_block( - blockchain_test: BlockchainTestFiller, pre: Alloc + blockchain_test: BlockchainTestFiller, pre: Alloc, fork: Fork ) -> None: """ Test withdrawal balance increase within the same block in a contract call. @@ -207,13 +215,19 @@ def test_balance_within_block( sender = pre.fund_eoa() recipient = pre.fund_eoa(ONE_GWEI) contract_address = pre.deploy_contract(save_balance_on_block_number) + intrinsic_calc = fork.transaction_intrinsic_cost_calculator() + tx_gas = ( + intrinsic_calc(calldata=Hash(recipient, left_padding=True)) + + save_balance_on_block_number.gas_cost(fork) + + Op.SSTORE(new_value=1).state_cost(fork) + ) blocks = [ Block( txs=[ Transaction( sender=sender, - gas_limit=100000, + gas_limit=tx_gas, to=contract_address, data=Hash(recipient, left_padding=True), ) @@ -231,7 +245,7 @@ def test_balance_within_block( txs=[ Transaction( sender=sender, - gas_limit=100000, + gas_limit=tx_gas, to=contract_address, data=Hash(recipient, left_padding=True), ) @@ -516,23 +530,29 @@ def test_newly_created_contract( def test_no_evm_execution( blockchain_test: BlockchainTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test withdrawals don't trigger EVM execution.""" sender = pre.fund_eoa() - contracts = [ - pre.deploy_contract(Op.SSTORE(Op.NUMBER, 1)) for _ in range(4) - ] + contract_code = Op.SSTORE(Op.NUMBER, 1) + contracts = [pre.deploy_contract(contract_code) for _ in range(4)] + intrinsic_calc = fork.transaction_intrinsic_cost_calculator() + tx_gas = ( + intrinsic_calc() + + contract_code.gas_cost(fork) + + Op.SSTORE(new_value=1).state_cost(fork) + ) blocks = [ Block( txs=[ Transaction( sender=sender, - gas_limit=100000, + gas_limit=tx_gas, to=contracts[2], ), Transaction( sender=sender, - gas_limit=100000, + gas_limit=tx_gas, to=contracts[3], ), ], @@ -555,12 +575,12 @@ def test_no_evm_execution( txs=[ Transaction( sender=sender, - gas_limit=100000, + gas_limit=tx_gas, to=contracts[0], ), Transaction( sender=sender, - gas_limit=100000, + gas_limit=tx_gas, to=contracts[1], ), ], diff --git a/vulture_whitelist.py b/vulture_whitelist.py index 1a663e745e5..ffd8e3992bd 100644 --- a/vulture_whitelist.py +++ b/vulture_whitelist.py @@ -128,6 +128,8 @@ Trace.returnData Trace.refund Trace.opName +Trace.stateGas +Trace.stateGasCost FinalTrace.gasUsed # src/ethereum_spec_tools/lint/lints/final_decorator.py diff --git a/whitelist.txt b/whitelist.txt index 958faf89571..47c7f750b07 100644 --- a/whitelist.txt +++ b/whitelist.txt @@ -268,6 +268,7 @@ CD cd CE ce +ceil32 CF cf CFI'd @@ -994,6 +995,7 @@ q1 qGpsxSA qs qube +quantized questionary quickstart qx From 21ecd8f029f736dac51d41caca3f4efe34b4b1a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=94=A1=E4=BD=B3=E8=AA=A0=20Louis=20Tsai?= <72684086+LouisTsai-Csie@users.noreply.github.com> Date: Wed, 10 Jun 2026 00:50:27 +0800 Subject: [PATCH 003/233] chore: remove duplicate tstore bench (#2966) --- tests/cancun/eip1153_tstore/test_tstorage.py | 66 -------------------- 1 file changed, 66 deletions(-) diff --git a/tests/cancun/eip1153_tstore/test_tstorage.py b/tests/cancun/eip1153_tstore/test_tstorage.py index a32bb0504df..14ca24b69bd 100644 --- a/tests/cancun/eip1153_tstore/test_tstorage.py +++ b/tests/cancun/eip1153_tstore/test_tstorage.py @@ -273,69 +273,3 @@ def test_gas_usage( ), } state_test(env=env, pre=pre, tx=tx, post=post) - - -@unique -class LoopRunUntilOutOfGasCases(PytestParameterEnum): - """Test cases to run until out of gas.""" - - TSTORE = { - "description": "Run tstore in loop until out of gas", - "repeat_bytecode": Op.TSTORE(Op.GAS, Op.GAS), - "bytecode_repeat_times": 1000, - } - TSTORE_WIDE_ADDRESS_SPACE = { - "description": "Run tstore in loop until out of gas, using a " - "wide address space", - "repeat_bytecode": Op.TSTORE(Op.ADD(Op.SHL(Op.PC, 1), Op.GAS), Op.GAS), - "bytecode_repeat_times": 32, - } - TSTORE_TLOAD = { - "description": "Run tstore and tload in loop until out of gas", - "repeat_bytecode": Op.GAS - + Op.DUP1 - + Op.DUP1 - + Op.TSTORE - + Op.TLOAD - + Op.POP, - "bytecode_repeat_times": 1000, - } - - -def max_tx_gas_limit(fork: Fork) -> list[int]: - """Return the maximum transaction gas limit for the given fork.""" - tx_limit = fork.transaction_gas_limit_cap() - return [tx_limit if tx_limit is not None else Environment().gas_limit] - - -@pytest.mark.ported_from( - [ - "https://github.com/ethereum/tests/blob/v13.3/src/GeneralStateTestsFiller/Cancun/stEIP1153-transientStorage/15_tstoreCannotBeDosdFiller.yml", # noqa: E501 - "https://github.com/ethereum/tests/blob/v13.3/src/GeneralStateTestsFiller/Cancun/stEIP1153-transientStorage/21_tstoreCannotBeDosdOOOFiller.yml", # noqa: E501 - ], - pr=["https://github.com/ethereum/execution-specs/pull/2385"], -) -@LoopRunUntilOutOfGasCases.parametrize() -@pytest.mark.slow() -@pytest.mark.parametrize_by_fork("tx_gas_limit", max_tx_gas_limit) -def test_run_until_out_of_gas( - state_test: StateTestFiller, - pre: Alloc, - tx_gas_limit: int, - repeat_bytecode: Bytecode, - bytecode_repeat_times: int, -) -> None: - """Use TSTORE over and over to different keys until we run out of gas.""" - bytecode = ( - Op.JUMPDEST - + repeat_bytecode * bytecode_repeat_times - + Op.JUMP(Op.PUSH0) - ) - code_address = pre.deploy_contract(code=bytecode) - tx = Transaction( - sender=pre.fund_eoa(), to=code_address, gas_limit=tx_gas_limit - ) - post = { - code_address: Account(code=bytecode, storage={}), - } - state_test(pre=pre, tx=tx, post=post) From 84e39e15bf383a278becc64c17f03927e38f9610 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Wed, 10 Jun 2026 04:16:10 +0800 Subject: [PATCH 004/233] fix(tests): encode z, y as big-endian in test_point_evaluation_precompile_gas (#2960) --- .../eip4844_blobs/test_point_evaluation_precompile_gas.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/cancun/eip4844_blobs/test_point_evaluation_precompile_gas.py b/tests/cancun/eip4844_blobs/test_point_evaluation_precompile_gas.py index 231f084a7f9..0efdab78b44 100644 --- a/tests/cancun/eip4844_blobs/test_point_evaluation_precompile_gas.py +++ b/tests/cancun/eip4844_blobs/test_point_evaluation_precompile_gas.py @@ -22,7 +22,7 @@ ceiling_division, ) -from .common import INF_POINT, Z +from .common import INF_POINT, Z_Y_VALID_ENDIANNESS, Z from .spec import Spec, ref_spec_4844 REFERENCE_SPEC_GIT_PATH = ref_spec_4844.git_path @@ -41,8 +41,8 @@ def precompile_input(proof: Literal["correct", "incorrect"]) -> bytes: versioned_hash = Spec.kzg_to_versioned_hash(kzg_commitment) return ( versioned_hash - + z.to_bytes(32, "little") - + y.to_bytes(32, "little") + + z.to_bytes(32, Z_Y_VALID_ENDIANNESS) + + y.to_bytes(32, Z_Y_VALID_ENDIANNESS) + kzg_commitment + kzg_proof ) From 505c4a4e4a859bb748dc2f93d177061e62fc8538 Mon Sep 17 00:00:00 2001 From: Guruprasad Kamath <48196632+gurukamath@users.noreply.github.com> Date: Wed, 10 Jun 2026 11:53:46 +0200 Subject: [PATCH 005/233] refactor(tests): sort refund types for deterministic fixture output (#2970) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `build_refund_tx` iterated `set(fork.refund_types())`, whose order depends on Python's per-process string-hash randomization (no `PYTHONHASHSEED` is set in this repo). Different `fill` invocations therefore appended the extra `PUSH0` from the `AUTHORIZATION_EXISTING_AUTHORITY` branch either before or after the `STORAGE_CLEAR` SSTOREs, producing two different bytecodes and — via `contract_address_from_hash` — two different deployment addresses for the same test, and hence two different pre-state allocations and state roots. Sort by enum-member name inside the iteration so the bytecode (and therefore the contract address) is deterministic across runs. --- .../test_gas_accounting.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/amsterdam/eip7778_block_gas_accounting_without_refunds/test_gas_accounting.py b/tests/amsterdam/eip7778_block_gas_accounting_without_refunds/test_gas_accounting.py index fff2265d819..001d273ea53 100644 --- a/tests/amsterdam/eip7778_block_gas_accounting_without_refunds/test_gas_accounting.py +++ b/tests/amsterdam/eip7778_block_gas_accounting_without_refunds/test_gas_accounting.py @@ -66,7 +66,10 @@ def build_refund_tx( auth_state_gas = 0 auth_state_refund = 0 - for refund_type in refund_types: + # Sort by name so iteration order is deterministic across Python + # invocations (set iteration over enum members depends on Python's + # per-process hash randomization). + for refund_type in sorted(refund_types, key=lambda r: r.name): match refund_type: case RefundTypes.STORAGE_CLEAR: for slot in storage_slots: From 5f132e7c73c01be23b83688ad6d4ba66646e1cd2 Mon Sep 17 00:00:00 2001 From: danceratopz Date: Wed, 10 Jun 2026 14:23:59 +0200 Subject: [PATCH 006/233] refactor(specs): remove unused regular gas constants in amsterdam (#2971) --- src/ethereum/forks/amsterdam/vm/gas.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/ethereum/forks/amsterdam/vm/gas.py b/src/ethereum/forks/amsterdam/vm/gas.py index 7ba692a9f51..c9eabffa4a9 100644 --- a/src/ethereum/forks/amsterdam/vm/gas.py +++ b/src/ethereum/forks/amsterdam/vm/gas.py @@ -68,13 +68,11 @@ class GasCosts: COLD_STORAGE_ACCESS: Final[Uint] = Uint(2100) # Storage - STORAGE_SET: Final[Uint] = Uint(20000) COLD_STORAGE_WRITE: Final[Uint] = Uint(5000) # Call CALL_VALUE: Final[Uint] = Uint(9000) CALL_STIPEND: Final[Uint] = Uint(2300) - NEW_ACCOUNT: Final[Uint] = Uint(25000) # Contract Creation CODE_DEPOSIT_PER_BYTE: Final[Uint] = Uint(200) @@ -82,7 +80,6 @@ class GasCosts: REGULAR_GAS_CREATE: Final[Uint] = Uint(9000) # Authorization - AUTH_PER_EMPTY_ACCOUNT: Final[int] = 25000 PER_AUTH_BASE_COST: Final[Uint] = Uint(7500) # Utility @@ -218,7 +215,6 @@ class GasCosts: OPCODE_LOG_DATA_PER_BYTE: Final[Uint] = Uint(8) OPCODE_LOG_TOPIC: Final[Uint] = Uint(375) OPCODE_SELFDESTRUCT_BASE: Final[Uint] = Uint(5000) - OPCODE_SELFDESTRUCT_NEW_ACCOUNT: Final[Uint] = Uint(25000) @final From 8ccd21f10f4fcdbee4eb6d68c818480ce7692c08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Wed, 10 Jun 2026 16:48:37 +0200 Subject: [PATCH 007/233] feat(tests): EIP-8037 CREATE-tx collision refunds state-gas reservoir (#2875) --- .../test_state_gas_create.py | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py index dcd4663b51c..89efe40eb4c 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py @@ -1933,6 +1933,66 @@ def test_create_tx_collision_refunds_intrinsic_new_account( ) +@pytest.mark.pre_alloc_mutable() +@pytest.mark.execute(pytest.mark.skip(reason="Requires specific gas price")) +@pytest.mark.valid_from("EIP8037") +def test_create_tx_collision_refunds_reservoir( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify the state-gas reservoir is refunded on a depth-0 CREATE-tx + address collision when `gas_limit > TX_MAX_GAS_LIMIT`. + + EIP-8037 splits `gas_limit` into the capped regular budget and a + state-gas reservoir. On collision the inner regular gas is burnt + and `intrinsic_state_gas` is refunded; the reservoir must also + be refunded to the sender. `header.gas_used` is fixed at the + regular cap regardless of reservoir handling, so the sender's + post-balance is the primary discriminating assertion. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + + init_code = Op.STOP + # +1 above intrinsic_state_gas (= create_state_gas(code_size=0) + # for empty-code CREATE-tx) makes message.state_gas_reservoir > 0. + reservoir = fork.create_state_gas(code_size=0) + 1 + gas_limit = gas_limit_cap + reservoir + initial_fund = 10**18 + + sender = pre.fund_eoa(initial_fund) + collision_target = compute_create_address(address=sender, nonce=0) + pre[collision_target] = Account(nonce=1) + + tx_gas_price = 7 + tx = Transaction( + to=None, + data=init_code, + gas_limit=gas_limit, + sender=sender, + gas_price=tx_gas_price, + ) + + blockchain_test( + pre=pre, + blocks=[ + Block( + txs=[tx], + header_verify=Header(gas_used=gas_limit_cap), + ), + ], + post={ + sender: Account( + balance=initial_fund - gas_limit_cap * tx_gas_price, + nonce=1, + ), + collision_target: Account(nonce=1, code=b"", storage={}), + }, + ) + + @pytest.mark.parametrize( "initcode_size_delta", [ From 4d47a44c5e171044395db459380f462cfe9ddb0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Wed, 10 Jun 2026 16:48:49 +0200 Subject: [PATCH 008/233] feat(tests): EIP-8037 reject tx when gas_limit covers regular but not state intrinsic (#2876) --- .../test_state_gas_create.py | 45 ++++++++++++ .../test_state_gas_set_code.py | 68 +++++++++++++++++++ 2 files changed, 113 insertions(+) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py index 89efe40eb4c..64a6dd644bb 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py @@ -520,6 +520,51 @@ def test_create_tx_intrinsic_gas_boundary( state_test(pre=pre, post={}, tx=tx) +@pytest.mark.exception_test +@pytest.mark.parametrize( + "extra_gas", + [ + pytest.param(0, id="at_regular_intrinsic"), + pytest.param(1, id="one_above_regular_intrinsic"), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_create_tx_below_total_intrinsic( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + extra_gas: int, +) -> None: + """ + Reject CREATE tx when gas_limit covers regular but not state intrinsic. + + EIP-8037 splits the CREATE intrinsic into regular and state + components (`STATE_BYTES_PER_NEW_ACCOUNT * COST_PER_STATE_BYTE`). + `test_create_tx_intrinsic_gas_boundary` pins the upper boundary + (`total - 1`); this pins the lower end — `intrinsic_regular` and + one gas above — to catch implementations that omit the state + component from the pre-validate check. + """ + total_intrinsic = fork.transaction_intrinsic_cost_calculator()( + contract_creation=True, + ) + intrinsic_state = fork.transaction_intrinsic_state_gas( + contract_creation=True, + ) + intrinsic_regular = total_intrinsic - intrinsic_state + gas_limit = intrinsic_regular + extra_gas + assert gas_limit < total_intrinsic + + tx = Transaction( + to=None, + gas_limit=gas_limit, + sender=pre.fund_eoa(), + error=TransactionException.INTRINSIC_GAS_TOO_LOW, + ) + + state_test(pre=pre, post={}, tx=tx) + + @pytest.mark.valid_from("EIP8037") def test_code_deposit_oog_preserves_parent_reservoir( state_test: StateTestFiller, diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py index 07a00196c24..1533cf5fa26 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py @@ -90,6 +90,74 @@ def test_authorization_state_gas_scaling( state_test(env=env, pre=pre, post={}, tx=tx) +@pytest.mark.exception_test +@pytest.mark.parametrize( + "num_auths", + [ + pytest.param(1, id="single_auth"), + pytest.param(2, id="two_auths"), + pytest.param(3, id="three_auths"), + ], +) +@pytest.mark.parametrize( + "extra_gas", + [ + pytest.param(0, id="at_regular_intrinsic"), + pytest.param(1, id="one_above_regular_intrinsic"), + pytest.param(-1, id="one_below_total_intrinsic"), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_set_code_tx_below_total_intrinsic( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + num_auths: int, + extra_gas: int, +) -> None: + """ + Reject set_code tx when gas_limit covers regular but not state intrinsic. + + EIP-8037 charges each authorization a state component + `(STATE_BYTES_PER_NEW_ACCOUNT + STATE_BYTES_PER_AUTH_BASE) * + COST_PER_STATE_BYTE`; total intrinsic = `regular + N * state` for + N authorizations. Sweep N = 1, 2, 3 and pin gas_limit at the + lower end of the rejected interval to catch implementations that + omit the state component from the pre-validate check. + """ + intrinsic_state = fork.transaction_intrinsic_state_gas( + authorization_count=num_auths, + ) + total_intrinsic = fork.transaction_intrinsic_cost_calculator()( + authorization_list_or_count=num_auths, + ) + intrinsic_regular = total_intrinsic - intrinsic_state + gas_limit = ( + intrinsic_regular if extra_gas >= 0 else total_intrinsic + ) + extra_gas + assert gas_limit < total_intrinsic + + contract = pre.deploy_contract(code=Op.STOP) + authorization_list = [ + AuthorizationTuple( + address=contract, + nonce=1, + signer=pre.fund_eoa(), + ) + for _ in range(num_auths) + ] + + tx = Transaction( + to=contract, + gas_limit=gas_limit, + authorization_list=authorization_list, + sender=pre.fund_eoa(), + error=TransactionException.INTRINSIC_GAS_TOO_LOW, + ) + + state_test(pre=pre, post={}, tx=tx) + + @pytest.mark.valid_from("EIP8037") def test_existing_account_refund( state_test: StateTestFiller, From 55ee6b16fafdcb50b41625255386d388ffd6af9e Mon Sep 17 00:00:00 2001 From: Bhargava Shastry Date: Wed, 10 Jun 2026 16:49:14 +0200 Subject: [PATCH 009/233] fix(tests): EIP-8037 unmask intrinsic-cap transaction-validity checks (#2956) --- .../test_state_gas_calldata_floor.py | 41 +++- .../test_state_gas_pricing.py | 191 +++++++++++++----- 2 files changed, 175 insertions(+), 57 deletions(-) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_calldata_floor.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_calldata_floor.py index 8dc98e0d063..d394a70c49f 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_calldata_floor.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_calldata_floor.py @@ -148,24 +148,30 @@ def test_calldata_floor_exceeding_tx_gas_limit_cap( exceeds_cap: bool, ) -> None: """ - Verify calldata floor > TX_MAX_GAS_LIMIT rejects the transaction. + Reject a transaction whose calldata floor exceeds the cap, isolating + the cap check from the sufficiency check. - When the EIP-7623 calldata floor cost exceeds the EIP-7825 transaction - gas limit cap, the transaction must be rejected at validation — - even though the regular intrinsic gas may be within the cap. + EIP-8037 caps ``max(intrinsic_regular, calldata_floor)`` at + ``TX_MAX_GAS_LIMIT``. When the EIP-7976 calldata floor crosses the cap + the transaction must be rejected even though the regular intrinsic gas + is within the cap. For the rejection case ``gas_limit`` is set above the + floor so the sufficiency check ``max(intrinsic_total, floor) <= tx.gas`` + passes and the cap is the only reason for rejection — the exact shape a + client with the sufficiency gate but no cap gate would wrongly execute. at_cap: tightest calldata floor that fits within the cap — transaction accepted. - exceeds_cap: one zero byte more tips the floor over the cap — + exceeds_cap: one byte more tips the floor over the cap — transaction rejected. """ gas_costs = fork.gas_costs() - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None + cap = fork.transaction_gas_limit_cap() + assert cap is not None + floor_cost = fork.transaction_data_floor_cost_calculator() floor_token = gas_costs.TX_DATA_TOKEN_FLOOR tx_base = gas_costs.TX_BASE - max_tokens = (gas_limit_cap - tx_base) // floor_token + max_tokens = (cap - tx_base) // floor_token if fork.is_eip_enabled(7976): # EIP-7976: all bytes contribute 4 floor tokens regardless of @@ -183,12 +189,29 @@ def test_calldata_floor_exceeding_tx_gas_limit_cap( if exceeds_cap: zero_bytes += 1 calldata = b"\x01" * nonzero_bytes + b"\x00" * zero_bytes + contract = pre.deploy_contract(Op.STOP) + floor = floor_cost(data=calldata) + + if exceeds_cap: + intrinsic = fork.transaction_intrinsic_cost_calculator() + regular = intrinsic( + calldata=calldata, + return_cost_deducted_prior_execution=True, + ) + assert floor > cap, "calldata floor must exceed the cap" + assert regular < cap, "regular intrinsic must stay below the cap" + # Fund the floor in full so the sufficiency check cannot reject the + # transaction first; only the cap check can. + gas_limit = floor + 1_000_000 + else: + assert floor <= cap + gas_limit = cap tx = Transaction( to=contract, data=calldata, - gas_limit=gas_limit_cap, + gas_limit=gas_limit, sender=pre.fund_eoa(), error=TransactionException.INTRINSIC_GAS_TOO_LOW if exceeds_cap diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_pricing.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_pricing.py index 64ec9c00946..e0b941a16c9 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_pricing.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_pricing.py @@ -16,7 +16,9 @@ import pytest from execution_testing import ( + AccessList, Account, + Address, Alloc, AuthorizationTuple, Environment, @@ -29,7 +31,7 @@ ) from execution_testing.checklists import EIPChecklist -from .spec import Spec, ref_spec_8037 +from .spec import ref_spec_8037 REFERENCE_SPEC_GIT_PATH = ref_spec_8037.git_path REFERENCE_SPEC_VERSION = ref_spec_8037.version @@ -266,6 +268,36 @@ def test_refund_with_reservoir_state_gas( state_test(env=env, pre=pre, post=post, tx=tx) +def _access_list_over_regular_cap( + fork: Fork, cap: int, *, margin_num: int = 1, margin_den: int = 1 +) -> list[AccessList]: + """ + Build an access list whose intrinsic *regular* gas exceeds ``cap`` by + roughly the factor ``margin_num / margin_den``. + + Each access-list address adds a fixed amount to the regular intrinsic + (the EIP-2930 address cost plus the EIP-7981 floor-token surcharge) and + a much smaller amount to the calldata floor, so the list raises the + regular operand of ``max(intrinsic_regular, calldata_floor)`` over the + cap while the floor stays below it. No state gas is incurred. + """ + intrinsic = fork.transaction_intrinsic_cost_calculator() + base_regular = intrinsic(return_cost_deducted_prior_execution=True) + per_address_regular = ( + intrinsic( + access_list=[AccessList(address=Address(0x100), storage_keys=[])], + return_cost_deducted_prior_execution=True, + ) + - base_regular + ) + assert per_address_regular > 0 + num_entries = (cap * margin_num) // (per_address_regular * margin_den) + 1 + return [ + AccessList(address=Address(0x10000 + i), storage_keys=[]) + for i in range(num_entries) + ] + + @pytest.mark.exception_test @pytest.mark.valid_from("EIP8037") def test_intrinsic_regular_gas_exceeds_cap( @@ -274,30 +306,43 @@ def test_intrinsic_regular_gas_exceeds_cap( fork: Fork, ) -> None: """ - Test that tx is rejected when intrinsic regular gas exceeds cap. - - validate_transaction checks that the intrinsic regular gas (or - calldata floor) does not exceed the transaction gas limit cap. - A transaction with enough calldata to push intrinsic cost above - the cap is invalid even with a high gas_limit. + Reject a transaction whose intrinsic *regular* gas exceeds the cap. + + EIP-8037 enforces ``max(intrinsic_regular, calldata_floor) <= + TX_MAX_GAS_LIMIT`` after the separate sufficiency check + ``max(intrinsic_total, calldata_floor) <= tx.gas``. A large access list + raises the regular intrinsic over the cap while adding no state gas and + keeping the calldata floor below the cap. ``gas_limit`` is set above the + total intrinsic so the sufficiency check passes and the cap is the only + reason the transaction is rejected; a client that compares the intrinsic + against ``tx.gas`` but never against the cap would wrongly accept it. """ - gas_costs = fork.gas_costs() - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - # One more non-zero byte than needed to exceed the cap - calldata_len = gas_limit_cap // gas_costs.TX_DATA_PER_NON_ZERO + 1 - calldata = b"\x01" * calldata_len + cap = fork.transaction_gas_limit_cap() + assert cap is not None + floor_cost = fork.transaction_data_floor_cost_calculator() + intrinsic = fork.transaction_intrinsic_cost_calculator() + + access_list = _access_list_over_regular_cap(fork, cap) + regular = intrinsic( + access_list=access_list, + return_cost_deducted_prior_execution=True, + ) + floor = floor_cost(data=b"", access_list=access_list) + state = fork.transaction_intrinsic_state_gas() + tx_gas = regular + state + 1_000_000 - contract = pre.deploy_contract(code=Op.STOP) + assert max(regular, floor) > cap, "cap check must fire" + assert regular + state <= tx_gas, "sufficiency check must not fire" + assert floor <= tx_gas tx = Transaction( - to=contract, - gas_limit=gas_limit_cap * 2, - data=calldata, + ty=1, + to=pre.deploy_contract(code=Op.STOP), + gas_limit=tx_gas, + access_list=access_list, sender=pre.fund_eoa(), error=TransactionException.INTRINSIC_GAS_TOO_LOW, ) - state_test(pre=pre, post={}, tx=tx) @@ -309,46 +354,96 @@ def test_intrinsic_regular_gas_exceeds_cap_with_floor_below_cap( fork: Fork, ) -> None: """ - Test rejection when intrinsic regular gas exceeds the per-tx gas - cap while the calldata floor stays below the cap. - - EIP-7825/8037 applies the cap to both intrinsic dimensions - independently. The companion `test_intrinsic_regular_gas_exceeds_cap` - pushes both dimensions above the cap with non-zero calldata, so an - implementation that only checks `max(regular, floor)` against the - cap would still pass. This test isolates the regular-only case via - a large EIP-7702 authorization list and minimal calldata. + Reject when intrinsic *regular* gas exceeds the cap while the calldata + floor stays below it, isolating the regular operand of + ``max(intrinsic_regular, calldata_floor)``. + + A large access list with no calldata pushes the regular intrinsic over + the cap while the floor stays well below it, and ``gas_limit`` covers + the total intrinsic so the sufficiency check passes. The explicit + ``floor < cap`` assertion guarantees the rejection comes from the + regular operand, so a client that compares only the calldata floor + against the cap would wrongly accept the transaction. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None + cap = fork.transaction_gas_limit_cap() + assert cap is not None + floor_cost = fork.transaction_data_floor_cost_calculator() + intrinsic = fork.transaction_intrinsic_cost_calculator() - # Authorizations contribute to regular intrinsic only (not floor). - # Pick enough to push regular > cap by a comfortable margin. - auth_count = (gas_limit_cap // Spec.PER_AUTH_BASE_COST) + 1 - calldata = b"\x01" * 4 # tiny: floor stays << cap. - - target = pre.deploy_contract(code=Op.STOP) - authorizations = [ - AuthorizationTuple( - address=target, - nonce=0, - signer=pre.fund_eoa(), - ) - for _ in range(auth_count) - ] + access_list = _access_list_over_regular_cap( + fork, cap, margin_num=5, margin_den=4 + ) + regular = intrinsic( + access_list=access_list, + return_cost_deducted_prior_execution=True, + ) + floor = floor_cost(data=b"", access_list=access_list) + state = fork.transaction_intrinsic_state_gas() + tx_gas = regular + state + 1_000_000 + + assert regular > cap, "regular operand must exceed the cap" + assert floor < cap, "calldata floor must stay below the cap" + assert regular + state <= tx_gas, "sufficiency check must not fire" tx = Transaction( - ty=4, - to=target, - gas_limit=gas_limit_cap * 2, - data=calldata, - authorization_list=authorizations, + ty=1, + to=pre.deploy_contract(code=Op.STOP), + gas_limit=tx_gas, + access_list=access_list, sender=pre.fund_eoa(), error=TransactionException.INTRINSIC_GAS_TOO_LOW, ) state_test(pre=pre, post={}, tx=tx) +@pytest.mark.valid_from("EIP8037") +def test_intrinsic_within_cap_gas_limit_above_cap( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Accept a transaction whose ``gas_limit`` exceeds the cap when both + intrinsic operands stay below it. + + EIP-8037 relaxes the EIP-7825 cap on ``tx.gas`` itself; only + ``max(intrinsic_regular, calldata_floor)`` is capped. This positive + control sets ``gas_limit`` above the cap with a small access list so + both operands are far below it, and the transaction must execute. It is + the accepting counterpart to the cap-rejection tests above. + """ + cap = fork.transaction_gas_limit_cap() + assert cap is not None + floor_cost = fork.transaction_data_floor_cost_calculator() + intrinsic = fork.transaction_intrinsic_cost_calculator() + + access_list = [ + AccessList(address=Address(0x10000 + i), storage_keys=[]) + for i in range(16) + ] + regular = intrinsic( + access_list=access_list, + return_cost_deducted_prior_execution=True, + ) + floor = floor_cost(data=b"", access_list=access_list) + assert regular <= cap + assert floor <= cap + + storage = Storage() + contract = pre.deploy_contract( + code=Op.SSTORE(storage.store_next(1), 1), + ) + + tx = Transaction( + ty=1, + to=contract, + gas_limit=cap + 3_000_000, + access_list=access_list, + sender=pre.fund_eoa(), + ) + state_test(pre=pre, post={contract: Account(storage=storage)}, tx=tx) + + @pytest.mark.parametrize( "above_floor", [ From 5becfa480e9a0d30b45281256c934dbd029326ac Mon Sep 17 00:00:00 2001 From: JackCC Date: Tue, 28 Apr 2026 11:48:22 +0800 Subject: [PATCH 010/233] refactor(forks): pass fork overrides to clone --- .../evm_tools/t8n/__init__.py | 109 +------------ src/ethereum_spec_tools/forks.py | 151 ++++++++++++++---- tests/evm_tools/test_fork_cache.py | 13 +- 3 files changed, 136 insertions(+), 137 deletions(-) diff --git a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py b/src/ethereum_spec_tools/evm_tools/t8n/__init__.py index c5fc7cde359..cd449ad9f03 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/__init__.py @@ -7,7 +7,6 @@ import json import os from contextlib import AbstractContextManager -from dataclasses import astuple, dataclass from typing import Any, Final, TextIO, Type, TypeVar from ethereum_rlp import rlp @@ -18,7 +17,11 @@ from ethereum.exceptions import EthereumException, InvalidBlock from ethereum.fork_criteria import ByBlockNumber, ByTimestamp, Unscheduled from ethereum.merkle_patricia_trie import copy_trie -from ethereum_spec_tools.forks import Hardfork, TemporaryHardfork +from ethereum_spec_tools.forks import ( + ForkOverrides, + Hardfork, + TemporaryHardfork, +) from ..loaders.fixture_loader import Load from ..utils import ( @@ -34,7 +37,6 @@ from .t8n_types import Alloc, Result, Txs T = TypeVar("T") -ForkCriteriaArgument = ByBlockNumber | ByTimestamp | Unscheduled | None def t8n_arguments(subparsers: argparse._SubParsersAction) -> None: @@ -92,95 +94,12 @@ def t8n_arguments(subparsers: argparse._SubParsersAction) -> None: t8n_parser.add_argument("--state-test", action="store_true") -@dataclass(frozen=True) -class _ForkOverrides: - """Store temporary hardfork override values.""" - - fork_criteria: ForkCriteriaArgument = None - blob_target_gas_per_block: U64 | None = None - gas_per_blob: U64 | None = None - blob_min_gasprice: Uint | None = None - blob_base_fee_update_fraction: Uint | None = None - max_blob_gas_per_block: U64 | None = None - blob_schedule_target: U64 | None = None - blob_schedule_max: U64 | None = None - - def is_empty(self) -> bool: - """Return true when all override values are unset.""" - return all(value is None for value in astuple(self)) - - @staticmethod - def _matches_field(override: object | None, on: object, name: str) -> bool: - if override is None: - return True - - try: - default = getattr(on, name) - except AttributeError: - return False - - return override == default - - def matches_template( - self, - template: Hardfork, - ) -> bool: - """Return true when the requested overrides match the template.""" - if self.is_empty(): - return True - - if ( - self.fork_criteria is not None - and self.fork_criteria != template.criteria - ): - return False - - fork_mod = template.module("fork") - gas_costs = template.module("vm.gas").GasCosts - - checks = ( - ( - self.max_blob_gas_per_block, - fork_mod, - "MAX_BLOB_GAS_PER_BLOCK", - ), - ( - self.blob_target_gas_per_block, - gas_costs, - "BLOB_TARGET_GAS_PER_BLOCK", - ), - (self.gas_per_blob, gas_costs, "PER_BLOB"), - ( - self.blob_min_gasprice, - gas_costs, - "BLOB_MIN_GASPRICE", - ), - ( - self.blob_base_fee_update_fraction, - gas_costs, - "BLOB_BASE_FEE_UPDATE_FRACTION", - ), - ( - self.blob_schedule_target, - gas_costs, - "BLOB_SCHEDULE_TARGET", - ), - ( - self.blob_schedule_max, - gas_costs, - "BLOB_SCHEDULE_MAX", - ), - ) - - return all(self._matches_field(*x) for x in checks) - - class ForkCache(AbstractContextManager): """ Stores references to temporary hardforks and cleans them up when exited. """ - _cache: Final[dict[tuple[str, _ForkOverrides], TemporaryHardfork]] + _cache: Final[dict[tuple[str, ForkOverrides], TemporaryHardfork]] def __init__(self) -> None: self._cache = {} @@ -207,7 +126,7 @@ def get( Search the cache for a matching hardfork, or create one if it doesn't exist. """ - overrides = _ForkOverrides( + overrides = ForkOverrides( fork_criteria=fork_criteria, blob_target_gas_per_block=blob_target_gas_per_block, gas_per_blob=gas_per_blob, @@ -226,19 +145,7 @@ def get( except KeyError: pass - clone = Hardfork.clone( - template=template, - fork_criteria=overrides.fork_criteria, - blob_target_gas_per_block=overrides.blob_target_gas_per_block, - gas_per_blob=overrides.gas_per_blob, - blob_min_gasprice=overrides.blob_min_gasprice, - blob_base_fee_update_fraction=( - overrides.blob_base_fee_update_fraction - ), - max_blob_gas_per_block=overrides.max_blob_gas_per_block, - blob_schedule_target=overrides.blob_schedule_target, - blob_schedule_max=overrides.blob_schedule_max, - ) + clone = Hardfork.clone(template=template, overrides=overrides) self._cache[cache_key] = clone return clone diff --git a/src/ethereum_spec_tools/forks.py b/src/ethereum_spec_tools/forks.py index cf29086b53b..e32433c115b 100644 --- a/src/ethereum_spec_tools/forks.py +++ b/src/ethereum_spec_tools/forks.py @@ -11,6 +11,7 @@ import random import sys from contextlib import AbstractContextManager +from dataclasses import astuple, dataclass from enum import Enum, auto from importlib.machinery import ModuleSpec, PathFinder from pathlib import Path @@ -26,20 +27,16 @@ Optional, Type, TypeVar, - Union, cast, ) from ethereum_types.numeric import U64, U256, Uint from typing_extensions import override +from ethereum.fork_criteria import ByBlockNumber, ByTimestamp, Unscheduled + if TYPE_CHECKING: - from ethereum.fork_criteria import ( - ByBlockNumber, - ByTimestamp, - ForkCriteria, - Unscheduled, - ) + from ethereum.fork_criteria import ForkCriteria class ConsensusType(Enum): @@ -64,6 +61,96 @@ def is_pos(self) -> bool: H = TypeVar("H", bound="Hardfork") +ForkCriteriaArgument = ByBlockNumber | ByTimestamp | Unscheduled | None + + +@dataclass(frozen=True) +class ForkOverrides: + """ + Temporary hardfork override values. + """ + + fork_criteria: ForkCriteriaArgument = None + blob_target_gas_per_block: U64 | None = None + gas_per_blob: U64 | None = None + blob_min_gasprice: Uint | None = None + blob_base_fee_update_fraction: Uint | None = None + max_blob_gas_per_block: U64 | None = None + blob_schedule_target: U64 | None = None + blob_schedule_max: U64 | None = None + + def is_empty(self) -> bool: + """ + Return true when all override values are unset. + """ + return all(value is None for value in astuple(self)) + + @staticmethod + def _matches_field(override: object | None, on: object, name: str) -> bool: + if override is None: + return True + + try: + default = getattr(on, name) + except AttributeError: + return False + + return override == default + + def matches_template( + self, + template: "Hardfork", + ) -> bool: + """ + Return true when the requested overrides match the template. + """ + if self.is_empty(): + return True + + if ( + self.fork_criteria is not None + and self.fork_criteria != template.criteria + ): + return False + + fork_mod = template.module("fork") + gas_costs = template.module("vm.gas").GasCosts + + checks = ( + ( + self.max_blob_gas_per_block, + fork_mod, + "MAX_BLOB_GAS_PER_BLOCK", + ), + ( + self.blob_target_gas_per_block, + gas_costs, + "BLOB_TARGET_GAS_PER_BLOCK", + ), + (self.gas_per_blob, gas_costs, "PER_BLOB"), + ( + self.blob_min_gasprice, + gas_costs, + "BLOB_MIN_GASPRICE", + ), + ( + self.blob_base_fee_update_fraction, + gas_costs, + "BLOB_BASE_FEE_UPDATE_FRACTION", + ), + ( + self.blob_schedule_target, + gas_costs, + "BLOB_SCHEDULE_TARGET", + ), + ( + self.blob_schedule_max, + gas_costs, + "BLOB_SCHEDULE_MAX", + ), + ) + + return all(self._matches_field(*x) for x in checks) class Hardfork: @@ -204,16 +291,7 @@ def load_from_json(cls: Type[H], json: Any) -> List[H]: @staticmethod def clone( template: H | str, - fork_criteria: Union[ - "ByBlockNumber", "ByTimestamp", "Unscheduled", None - ] = None, - blob_target_gas_per_block: U64 | None = None, - gas_per_blob: U64 | None = None, - blob_min_gasprice: Uint | None = None, - blob_base_fee_update_fraction: Uint | None = None, - max_blob_gas_per_block: U64 | None = None, - blob_schedule_target: U64 | None = None, - blob_schedule_max: U64 | None = None, + overrides: ForkOverrides | None = None, ) -> "TemporaryHardfork": """ Create a temporary clone of an existing fork, optionally tweaking its @@ -221,6 +299,9 @@ def clone( """ from .new_fork.builder import ForkBuilder + if overrides is None: + overrides = ForkOverrides() + maybe_directory: TemporaryDirectory | None = TemporaryDirectory() try: @@ -240,33 +321,37 @@ def clone( builder.output = Path(directory.name) - if fork_criteria is not None: - builder.fork_criteria = fork_criteria + if overrides.fork_criteria is not None: + builder.fork_criteria = overrides.fork_criteria - if blob_target_gas_per_block is not None: + if overrides.blob_target_gas_per_block is not None: builder.modify_target_blob_gas_per_block( - blob_target_gas_per_block + overrides.blob_target_gas_per_block ) - if gas_per_blob is not None: - builder.modify_gas_per_blob(gas_per_blob) + if overrides.gas_per_blob is not None: + builder.modify_gas_per_blob(overrides.gas_per_blob) - if blob_min_gasprice is not None: - builder.modify_min_blob_gasprice(blob_min_gasprice) + if overrides.blob_min_gasprice is not None: + builder.modify_min_blob_gasprice(overrides.blob_min_gasprice) - if blob_base_fee_update_fraction is not None: + if overrides.blob_base_fee_update_fraction is not None: builder.modify_blob_base_fee_update_fraction( - blob_base_fee_update_fraction + overrides.blob_base_fee_update_fraction ) - if max_blob_gas_per_block is not None: - builder.modify_max_blob_gas_per_block(max_blob_gas_per_block) + if overrides.max_blob_gas_per_block is not None: + builder.modify_max_blob_gas_per_block( + overrides.max_blob_gas_per_block + ) - if blob_schedule_target is not None: - builder.modify_blob_schedule_target(blob_schedule_target) + if overrides.blob_schedule_target is not None: + builder.modify_blob_schedule_target( + overrides.blob_schedule_target + ) - if blob_schedule_max is not None: - builder.modify_blob_schedule_max(blob_schedule_max) + if overrides.blob_schedule_max is not None: + builder.modify_blob_schedule_max(overrides.blob_schedule_max) builder.build() diff --git a/tests/evm_tools/test_fork_cache.py b/tests/evm_tools/test_fork_cache.py index b2b67f2bdf0..4d74f4647ab 100644 --- a/tests/evm_tools/test_fork_cache.py +++ b/tests/evm_tools/test_fork_cache.py @@ -13,7 +13,7 @@ Unscheduled, ) from ethereum_spec_tools.evm_tools.t8n import ForkCache -from ethereum_spec_tools.forks import Hardfork +from ethereum_spec_tools.forks import ForkOverrides, Hardfork pytestmark = pytest.mark.evm_tools @@ -40,6 +40,13 @@ def _template() -> Hardfork: return Hardfork(importlib.import_module("ethereum.forks.amsterdam")) +def _seen_overrides(seen: dict[str, Any]) -> ForkOverrides: + """Return the ForkOverrides passed to Hardfork.clone.""" + overrides = seen["overrides"] + assert isinstance(overrides, ForkOverrides) + return overrides + + def _different_fork_criteria( criteria: ByBlockNumber | ByTimestamp | Unscheduled, ) -> ByBlockNumber | ByTimestamp | Unscheduled: @@ -182,7 +189,7 @@ def clone(*args: Any, **kwargs: Any) -> DummyTemporaryFork: assert fork is cloned assert seen["template"] is template - assert seen["fork_criteria"] == changed_fork_criteria + assert _seen_overrides(seen).fork_criteria == changed_fork_criteria @pytest.mark.parametrize("field", OVERRIDE_FIELDS) @@ -228,7 +235,7 @@ def clone(*args: Any, **kwargs: Any) -> DummyTemporaryFork: assert fork is cloned assert seen["template"] is template - assert seen[field] == changed_value + assert getattr(_seen_overrides(seen), field) == changed_value def test_fork_cache_reuses_cached_clone_for_identical_changed_request( From bb030d0b831a0b94828e0ecae72f20242d036906 Mon Sep 17 00:00:00 2001 From: JackCC Date: Wed, 27 May 2026 10:19:57 +0800 Subject: [PATCH 011/233] style: fix ruff format and E501 line length violations --- tests/evm_tools/test_fork_cache.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/tests/evm_tools/test_fork_cache.py b/tests/evm_tools/test_fork_cache.py index 4d74f4647ab..516943aac5c 100644 --- a/tests/evm_tools/test_fork_cache.py +++ b/tests/evm_tools/test_fork_cache.py @@ -148,7 +148,9 @@ def test_fork_cache_returns_template_for_identical_overrides( ByBlockNumber | ByTimestamp | Unscheduled, ) - def clone(*args: Any, **kwargs: Any) -> DummyTemporaryFork: + def clone( + template: Hardfork, overrides: ForkOverrides + ) -> DummyTemporaryFork: pytest.fail("Hardfork.clone() should not run for identical overrides") monkeypatch.setattr(Hardfork, "clone", clone) @@ -171,8 +173,12 @@ def test_fork_cache_clones_when_fork_criteria_changes_template( cloned = DummyTemporaryFork() seen: dict[str, Any] = {} - def clone(*args: Any, **kwargs: Any) -> DummyTemporaryFork: - seen.update(kwargs) + def clone( + template: Hardfork, + overrides: ForkOverrides, + ) -> DummyTemporaryFork: + seen["template"] = template + seen["overrides"] = overrides return cloned monkeypatch.setattr(Hardfork, "clone", clone) @@ -201,7 +207,9 @@ def test_fork_cache_returns_template_for_each_identical_blob_override( template = _template() value = _override_defaults(template)[field] - def clone(*args: Any, **kwargs: Any) -> DummyTemporaryFork: + def clone( + template: Hardfork, overrides: ForkOverrides + ) -> DummyTemporaryFork: pytest.fail("Hardfork.clone() should not run for identical overrides") monkeypatch.setattr(Hardfork, "clone", clone) @@ -224,8 +232,12 @@ def test_fork_cache_clones_for_each_changed_blob_override( cloned = DummyTemporaryFork() seen: dict[str, Any] = {} - def clone(*args: Any, **kwargs: Any) -> DummyTemporaryFork: - seen.update(kwargs) + def clone( + template: Hardfork, + overrides: ForkOverrides, + ) -> DummyTemporaryFork: + seen["template"] = template + seen["overrides"] = overrides return cloned monkeypatch.setattr(Hardfork, "clone", clone) From 55f61ab651bef9a33b40283bbd0efd755800a34b Mon Sep 17 00:00:00 2001 From: raxhvl <10168946+raxhvl@users.noreply.github.com> Date: Wed, 10 Jun 2026 16:59:33 +0000 Subject: [PATCH 012/233] =?UTF-8?q?=E2=9C=A8=20feat(test):=20=20EIP-7928?= =?UTF-8?q?=20Selfdestruct=20a=20dirty=20account=20(#2967)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✨ feat(test): selfdestruct a drity account * nit Co-authored-by: Mario Vega * nit Co-authored-by: Mario Vega * nit Co-authored-by: Mario Vega * nit Co-authored-by: Mario Vega * nit Co-authored-by: Mario Vega * ✨ feat: Parameterise success / revert * 🧹 chore: lint * 🐞 fix: 8037 pricing change; forward all gas --------- Co-authored-by: raxhvl Co-authored-by: Mario Vega --- .../test_block_access_lists_opcodes.py | 206 ++++++++++++++++++ .../test_cases.md | 1 + 2 files changed, 207 insertions(+) diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py index a445801805d..5fbbf6e51e6 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py @@ -3744,3 +3744,209 @@ def test_bal_create2_selfdestruct_then_recreate_same_block( factory: Account(nonce=3, storage={0: target_a, 1: 1}), }, ) + + +@pytest.mark.parametrize( + "destruction_successful,oracle_suffix", + [ + pytest.param(True, Op.STOP, id="destruction_succeeds"), + pytest.param(False, Op.REVERT(0, 0), id="destruction_reverts"), + ], +) +@pytest.mark.with_all_create_opcodes +def test_bal_dirty_account_selfdestruct( + pre: Alloc, + blockchain_test: BlockchainTestFiller, + create_opcode: Op, + destruction_successful: bool, + oracle_suffix: Bytecode, +) -> None: + """ + BAL records dirty state changes on an ephemeral contract only when + its same-tx SELFDESTRUCT is rolled back by a reverting parent + frame. + + The factory deploys the ephemeral with non-zero endowment (balance + dirty), initcode SSTOREs and SLOADs own slots (storage dirty), + invokes an empty CREATE so the ephemeral's own nonce bumps 1→2 + (nonce dirty), and returns runtime (code dirty). The factory then + CALLs an oracle which CALLs the ephemeral's runtime + (SELFDESTRUCTs), and either STOPs or REVERTs. + + - destruction_succeeds: oracle STOPs; per EIP-6780 the same-tx + selfdestruct fully removes the ephemeral; per EIP-7928 its BAL + entry must contain no balance/nonce/code/storage changes — only + `storage_reads` for the demoted slots. + + - destruction_reverts: oracle REVERTs; the SELFDESTRUCT (and the + balance transfer to the beneficiary) are rolled back. The + ephemeral persists with all four dirtied fields, which BAL must + now record. + """ + alice = pre.fund_eoa() + beneficiary = pre.nonexistent_account() + factory_balance = 1000 + endowment = 100 + slot_write = 0x07 + slot_read = 0x09 + + init_code = Initcode( + deploy_code=Op.SELFDESTRUCT(beneficiary), + initcode_prefix=( + Op.SSTORE(slot_write, 0xCAFE) + + Op.POP(Op.SLOAD(slot_read)) + + Op.POP(create_opcode(value=0, offset=0, size=0)) + ), + ) + + # Oracle CALLs whatever address it receives as calldata, then + # either STOPs (destruction succeeds) or REVERTs (destruction + # rolled back). Pre-deployed so its own creation doesn't appear + # in the block's BAL. + oracle = pre.deploy_contract( + code=Op.POP(Op.CALL(Op.GAS, Op.CALLDATALOAD(0), 0, 0, 0, 0, 0)) + + oracle_suffix, + ) + + factory_code = ( + Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) + + Op.SSTORE( + 0, + create_opcode(value=endowment, offset=0, size=Op.CALLDATASIZE), + ) + + Op.MSTORE(0, Op.SLOAD(0)) + + Op.POP(Op.CALL(Op.GAS, oracle, 0, 0, 32, 0, 0)) + + Op.STOP + ) + factory = pre.deploy_contract(code=factory_code, balance=factory_balance) + + ephemeral = compute_create_address( + address=factory, + nonce=1, + initcode=init_code, + opcode=create_opcode, + ) + zombie = compute_create_address( + address=ephemeral, + nonce=1, + initcode=b"", + opcode=create_opcode, + ) + + expected_ephemeral_post: Account | None + expected_beneficiary_post: Account | None + if destruction_successful: + expected_ephemeral_bal = BalAccountExpectation( + balance_changes=[], + nonce_changes=[], + code_changes=[], + storage_changes=[], + storage_reads=[slot_write, slot_read], + ) + expected_beneficiary_bal = BalAccountExpectation( + balance_changes=[ + BalBalanceChange(block_access_index=1, post_balance=endowment) + ], + ) + expected_ephemeral_post = Account.NONEXISTENT + expected_beneficiary_post = Account(balance=endowment) + else: + expected_ephemeral_bal = BalAccountExpectation( + balance_changes=[ + BalBalanceChange(block_access_index=1, post_balance=endowment) + ], + nonce_changes=[BalNonceChange(block_access_index=1, post_nonce=2)], + code_changes=[ + BalCodeChange( + block_access_index=1, new_code=init_code.deploy_code + ) + ], + storage_changes=[ + BalStorageSlot( + slot=slot_write, + slot_changes=[ + BalStorageChange( + block_access_index=1, post_value=0xCAFE + ) + ], + ) + ], + storage_reads=[slot_read], + ) + expected_beneficiary_bal = BalAccountExpectation.empty() + expected_ephemeral_post = Account( + nonce=2, + balance=endowment, + code=init_code.deploy_code, + storage={slot_write: 0xCAFE}, + ) + expected_beneficiary_post = Account.NONEXISTENT + + tx = Transaction( + sender=alice, + to=factory, + data=init_code, + gas_limit=1_000_000, + ) + + block = Block( + txs=[tx], + expected_block_access_list=BlockAccessListExpectation( + account_expectations={ + alice: BalAccountExpectation( + nonce_changes=[ + BalNonceChange(block_access_index=1, post_nonce=1) + ], + ), + factory: BalAccountExpectation( + nonce_changes=[ + BalNonceChange(block_access_index=1, post_nonce=2) + ], + balance_changes=[ + BalBalanceChange( + block_access_index=1, + post_balance=factory_balance - endowment, + ) + ], + storage_changes=[ + BalStorageSlot( + slot=0, + slot_changes=[ + BalStorageChange( + block_access_index=1, + post_value=ephemeral, + ) + ], + ) + ], + ), + ephemeral: expected_ephemeral_bal, + # The zombie is ALWAYS crated + # since it was deployed inside the factory's frame, + # which never reverts. + zombie: BalAccountExpectation( + nonce_changes=[ + BalNonceChange(block_access_index=1, post_nonce=1) + ], + ), + oracle: BalAccountExpectation.empty(), + beneficiary: expected_beneficiary_bal, + } + ), + ) + + blockchain_test( + pre=pre, + blocks=[block], + post={ + alice: Account(nonce=1), + beneficiary: expected_beneficiary_post, + factory: Account( + nonce=2, + balance=factory_balance - endowment, + storage={0: ephemeral}, + ), + ephemeral: expected_ephemeral_post, + zombie: Account(nonce=1), + }, + ) diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_cases.md b/tests/amsterdam/eip7928_block_level_access_lists/test_cases.md index 0dd13217ee8..7304a4ce04e 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_cases.md +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_cases.md @@ -183,4 +183,5 @@ | `test_invalid_pre_fork_block_with_bal_hash_field` | Verify clients reject a pre-Amsterdam block whose header carries `block_access_list_hash`. File: `tests/amsterdam/eip7928_block_level_access_lists/test_fork_transition.py`. | Single block at `timestamp=14_999` with a regular transfer, mutated via `rlp_modifier=Header(block_access_list_hash=Hash(0))` to inject the field into the pre-fork header schema. | Block **MUST** be rejected with `BlockException.INVALID_BLOCK_HASH`: pre-fork clients compute the block hash without the injected field, mismatching the expected hash. | ✅ Completed | | `test_invalid_post_fork_block_without_bal_hash_field` | Verify clients reject an Amsterdam activation block whose header is missing `block_access_list_hash`. File: `tests/amsterdam/eip7928_block_level_access_lists/test_fork_transition.py`. | Single block at `timestamp=15_000` with a regular transfer, mutated via `rlp_modifier=Header(block_access_list_hash=Header.REMOVE_FIELD)` so the field is dropped from the header. | Block **MUST** be rejected with `BlockException.INVALID_BAL_HASH` or `BlockException.INVALID_BLOCK_HASH`: clients re-derive the BAL hash from execution and detect the mismatch either at the BAL hash check or the header hash check. | ✅ Completed | | `test_fork_transition_bal_size_constraint` | Verify the BAL size constraint (`bal_items <= gas_limit // BLOCK_ACCESS_LIST_ITEM`) applies only on/after Amsterdam. File: `tests/amsterdam/eip7928_block_level_access_lists/test_fork_transition.py`. Parametrized over `exceeds_limit_at_fork`: `at_fork_within_budget` (`gas_limit == empty_block_bal_item_count() * BLOCK_ACCESS_LIST_ITEM`) and `at_fork_over_budget` (`gas_limit` one wei below that). | Two empty blocks: pre-fork (`timestamp=14_999`) and activation block (`timestamp=15_000`). The same low `gas_limit` is used for both via `genesis_environment=Environment(gas_limit=...)`. | Pre-fork block **MUST** be accepted under both budgets (constraint not yet enforced). Activation block **MUST** be accepted at the exact budget and **MUST** be rejected with `BlockException.BLOCK_ACCESS_LIST_GAS_LIMIT_EXCEEDED` one item over the budget. | ✅ Completed | +| `test_bal_dirty_account_selfdestruct` | Ensure BAL does not record dirty state on a same-tx ephemeral contract whose `SELFDESTRUCT` takes effect. | A factory deploys an ephemeral whose initcode dirties balance, nonce, code, and storage; runtime `SELFDESTRUCT` routes through an intermediate oracle. | **success**: ephemeral's BAL entry contains only `storage_reads` for the demoted slots. **revert**: BAL records all four dirty fields (destruction rolled back). | ✅ Completed | From 40c85cb570381507c53c8dceb54cc6f618327b4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toni=20Wahrst=C3=A4tter?= <51536394+nerolation@users.noreply.github.com> Date: Wed, 10 Jun 2026 19:21:31 +0200 Subject: [PATCH 013/233] feat(execute): skip deterministic factory deploy when it can't be bootstrapped (#2944) * feat(execute): skip the deterministic factory deploy (and dependent tests) when it can't be bootstrapped The deterministic deployment proxy is bootstrapped in an autouse session fixture via a keyless transaction with a fixed gas limit. On chains where the contract-creation intrinsic gas exceeds that limit (so the keyless tx can never be mined), the deploy aborted the entire execute session, blocking even tests that never use the factory. - Pre-flight the deploy with `eth_estimateGas`: if the network requires more gas for the creation than the keyless tx's fixed gas limit, raise instead of attempting it (no funding tx, no doomed send, no inclusion wait). - Make the session fixture best-effort: warn instead of raising, so tests that don't need the factory still run. - Skip a test that requests a deterministic deployment when the factory is unavailable. - Add `EthRPC.estimate_gas` for the pre-flight. * chore: update comment --------- Co-authored-by: LouisTsai --- .../plugins/execute/contracts.py | 29 +++++++++++++++++++ .../plugins/execute/pre_alloc.py | 28 ++++++++++++------ .../testing/src/execution_testing/rpc/rpc.py | 16 ++++++++++ 3 files changed, 64 insertions(+), 9 deletions(-) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/contracts.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/contracts.py index 8a552f5d33c..8171bc87f48 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/contracts.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/contracts.py @@ -20,6 +20,13 @@ logger = get_logger(__name__) +class DeterministicFactoryNotDeployableError(Exception): + """ + Raised when the deterministic proxy cannot deploy. + Example: fixed gas limit insufficient for network creation cost. + """ + + def check_deterministic_factory_deployment( *, eth_rpc: EthRPC, @@ -96,6 +103,28 @@ def deploy_deterministic_factory_contract( ).with_signature_and_sender() deploy_tx_sender = deploy_tx.sender assert deploy_tx_sender is not None + + # Pre-flight: skip deploy if network gas > fixed limit. + # Gas limit is fixed as changing it alters sender/factory address. + # If network requires more gas, transaction can never be included. + try: + required_gas = eth_rpc.estimate_gas( + transaction={ + "from": f"{deploy_tx_sender}", + "input": f"{deploy_tx.data}", + } + ) + except Exception: + # If the estimate itself is unavailable, fall through and attempt the + # deploy as before (failures are still handled by the caller). + required_gas = None + if required_gas is not None and required_gas > deploy_tx_gas_limit: + raise DeterministicFactoryNotDeployableError( + f"network requires {required_gas} gas to create the deterministic " + f"deployment proxy, exceeding the keyless transaction's fixed gas " + f"limit of {deploy_tx_gas_limit}" + ) + required_deployer_balance = deploy_tx_gas_price * deploy_tx_gas_limit current_balance = eth_rpc.get_balance(deploy_tx_sender) if current_balance < required_deployer_balance: diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py index 27a887b43d3..daeb78b2035 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py @@ -152,6 +152,10 @@ def execute_required_contracts( Deploy required contracts for the execute command. - Deterministic deployment proxy + + Proxy deploy failure doesn't abort the session. + Tests skip deterministic deploys on use. + Details check `(see Alloc._resolve_deterministic_deploys)`. """ base_lock_file = session_temp_folder / "execute_required_contracts.lock" with FileLock(base_lock_file): @@ -171,12 +175,13 @@ def execute_required_contracts( gas_price=sender_funding_transactions_gas_price, ) except Exception as e: - raise RuntimeError( - f"Error deploying deterministic deployment contract:\n{e}" - "\nTry deploying the contract manually using a different " - "RPC endpoint with the following command:\n" - "uv run execute deploy-required-contracts" - ) from e + logger.warning( + "Could not deploy the deterministic deployment proxy; " + "tests that require it will be skipped. To deploy it " + "manually against a different RPC endpoint run " + "`uv run execute deploy-required-contracts`. " + f"Reason: {e}" + ) class PendingTransaction(Transaction): @@ -794,12 +799,17 @@ def _resolve_deterministic_deploys(self) -> None: ) else: if not factory_checked: - assert ( + if ( check_deterministic_factory_deployment( eth_rpc=self._eth_rpc, fork=fork ) - is not None - ), "Deployment contract code is not found" + is None + ): + pytest.skip( + "deterministic deployment proxy is not available " + "on this network; skipping test that requires a " + "deterministic contract deployment" + ) factory_checked = True logger.info( diff --git a/packages/testing/src/execution_testing/rpc/rpc.py b/packages/testing/src/execution_testing/rpc/rpc.py index 2c8cdcb50b5..3f9c64c332f 100644 --- a/packages/testing/src/execution_testing/rpc/rpc.py +++ b/packages/testing/src/execution_testing/rpc/rpc.py @@ -634,6 +634,22 @@ def get_balances( responses = self.post_batch_request(calls=calls) return [int(r.result_or_raise(), 16) for r in responses] + def estimate_gas( + self, + transaction: Dict[str, Any], + block_number: BlockNumberType = "latest", + ) -> int: + """`eth_estimateGas`: Return the gas required to execute a tx.""" + block = ( + hex(block_number) + if isinstance(block_number, int) + else block_number + ) + response = self.post_request( + request=RPCCall(method="estimateGas", params=[transaction, block]) + ).result_or_raise() + return int(response, 16) + def get_code( self, address: Address, block_number: BlockNumberType = "latest" ) -> Bytes: From 26ff8a649e048cb3d77c24c9646dde491aab3108 Mon Sep 17 00:00:00 2001 From: Edgar Date: Wed, 10 Jun 2026 19:29:08 +0200 Subject: [PATCH 014/233] feat(test): pass slot_number in build-block payload attributes (#2974) --- packages/testing/src/execution_testing/fixtures/blockchain.py | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/testing/src/execution_testing/fixtures/blockchain.py b/packages/testing/src/execution_testing/fixtures/blockchain.py index ff908059ba8..edd819ac30b 100644 --- a/packages/testing/src/execution_testing/fixtures/blockchain.py +++ b/packages/testing/src/execution_testing/fixtures/blockchain.py @@ -560,6 +560,7 @@ def get_payload_attributes(self) -> "PayloadAttributes": suggested_fee_recipient=execution_payload.fee_recipient, withdrawals=execution_payload.withdrawals, parent_beacon_block_root=parent_beacon_block_root, + slot_number=execution_payload.slot_number, ) @staticmethod From 252dc300cabcc0d52936a8a30e23f15129a56902 Mon Sep 17 00:00:00 2001 From: spencer Date: Thu, 11 Jun 2026 12:44:55 +0100 Subject: [PATCH 015/233] chore(test-forks): include sibling BPO forks in `--until` ranges (#2955) --- .../src/execution_testing/forks/helpers.py | 37 +++++++++++ .../forks/tests/test_forks.py | 62 +++++++++++++++++++ 2 files changed, 99 insertions(+) diff --git a/packages/testing/src/execution_testing/forks/helpers.py b/packages/testing/src/execution_testing/forks/helpers.py index 9a08b50ea52..ba643146bf4 100644 --- a/packages/testing/src/execution_testing/forks/helpers.py +++ b/packages/testing/src/execution_testing/forks/helpers.py @@ -195,12 +195,45 @@ def get_last_descendants( return resulting_forks +def get_bpo_sibling_forks( + forks: Set[Type[BaseFork]] | FrozenSet[Type[BaseFork]], + forks_from: Set[Type[BaseFork]], + forks_until: Set[Type[BaseFork]], +) -> Set[Type[BaseFork]]: + """ + Return BPO forks that branch off an ancestor of an `--until` fork. + + BPO (Blob Parameter Only) forks form a chain hanging off the fork + they extend (e.g. the `BPO3`/`BPO4`/`BPO5` chain branches off `BPO2`). + A later fork such as `Amsterdam` descends from that same `BPO2` on a + parallel branch, so an ancestry-based `--until=Amsterdam` range never + reaches the BPO chain. Return those siblings, bounded below by + `forks_from`, so filling until such a fork still exercises the + blob-parameter paths the BPO forks cover. + """ + siblings: Set[Type[BaseFork]] = set() + for fork_until in forks_until: + if issubclass(fork_until, TransitionBaseClass): + continue + for fork in forks: + if not fork.bpo_fork(): + continue + if fork <= fork_until or fork >= fork_until: + continue + if fork.non_bpo_ancestor() <= fork_until and any( + fork >= fork_from for fork_from in forks_from + ): + siblings.add(fork) + return siblings + + def get_selected_fork_set( *, single_fork: Set[Type[BaseFork]], forks_from: Set[Type[BaseFork]], forks_until: Set[Type[BaseFork]], transition_forks: bool = True, + bpo_siblings: bool = True, ) -> Set[Type[BaseFork | TransitionBaseClass]]: """ Process sets derived from `--fork`, `--until` and `--from` to return an @@ -225,6 +258,10 @@ def get_selected_fork_set( for fork_until in forks_until: if issubclass(fork_until, TransitionBaseClass): selected_fork_set.discard(fork_until.transitions_to()) + if bpo_siblings: + selected_fork_set |= get_bpo_sibling_forks( + ALL_FORKS, forks_from, forks_until + ) selected_fork_set_with_transitions: Set[ Type[BaseFork | TransitionBaseClass] ] = set() | selected_fork_set diff --git a/packages/testing/src/execution_testing/forks/tests/test_forks.py b/packages/testing/src/execution_testing/forks/tests/test_forks.py index 3251541e0dd..c9c06f0a7b4 100644 --- a/packages/testing/src/execution_testing/forks/tests/test_forks.py +++ b/packages/testing/src/execution_testing/forks/tests/test_forks.py @@ -14,6 +14,7 @@ BPO2, BPO3, BPO4, + BPO5, Amsterdam, Berlin, Cancun, @@ -683,6 +684,67 @@ def test_transition_from_normal_until(self) -> None: assert BPO1ToBPO2AtTime15k in result assert BPO2ToAmsterdamAtTime15k not in result + def test_until_amsterdam_includes_bpo_siblings(self) -> None: + """`--until=Amsterdam` pulls in the parallel BPO branch.""" + result = get_selected_fork_set( + single_fork=set(), + forks_from=set(), + forks_until={Amsterdam}, + ) + normal = self._normal_forks(result) + assert {BPO1, BPO2, BPO3, BPO4, BPO5, Amsterdam} <= normal + assert BPO2ToBPO3AtTime15k in result + assert BPO3ToBPO4AtTime15k in result + + def test_from_osaka_until_amsterdam_spans_bpo_branch(self) -> None: + """`--from=Osaka --until=Amsterdam` spans the full BPO branch.""" + result = get_selected_fork_set( + single_fork=set(), + forks_from={Osaka}, + forks_until={Amsterdam}, + ) + assert self._normal_forks(result) == { + Osaka, + BPO1, + BPO2, + BPO3, + BPO4, + BPO5, + Amsterdam, + } + + def test_until_amsterdam_bpo_siblings_disabled(self) -> None: + """`bpo_siblings=False` keeps the parallel BPO branch out.""" + result = get_selected_fork_set( + single_fork=set(), + forks_from=set(), + forks_until={Amsterdam}, + bpo_siblings=False, + ) + normal = self._normal_forks(result) + assert {BPO1, BPO2, Amsterdam} <= normal + assert not ({BPO3, BPO4, BPO5} & normal) + + def test_until_bpo2_excludes_later_bpo_siblings(self) -> None: + """`--until=BPO2` must not pull in the later BPO branch.""" + result = get_selected_fork_set( + single_fork=set(), + forks_from=set(), + forks_until={BPO2}, + ) + normal = self._normal_forks(result) + assert {BPO1, BPO2} <= normal + assert not ({BPO3, BPO4, BPO5} & normal) + + def test_from_amsterdam_until_amsterdam_excludes_bpos(self) -> None: + """`--from=Amsterdam --until=Amsterdam` stays Amsterdam-only.""" + result = get_selected_fork_set( + single_fork=set(), + forks_from={Amsterdam}, + forks_until={Amsterdam}, + ) + assert self._normal_forks(result) == {Amsterdam} + def test_blob_constants() -> None: # noqa: D103 assert Osaka.get_blob_constant("AMOUNT_CELL_PROOFS") == 128 From 49f46977e7f6117ad78380b2e6a43e80195d170d Mon Sep 17 00:00:00 2001 From: spencer-tb Date: Thu, 11 Jun 2026 11:39:20 +0100 Subject: [PATCH 016/233] feat(spec-specs): EIP-8037 - check static context upfront in CREATE opcodes --- src/ethereum/forks/amsterdam/vm/instructions/system.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/ethereum/forks/amsterdam/vm/instructions/system.py b/src/ethereum/forks/amsterdam/vm/instructions/system.py index a9b450e398d..c573ea743fb 100644 --- a/src/ethereum/forks/amsterdam/vm/instructions/system.py +++ b/src/ethereum/forks/amsterdam/vm/instructions/system.py @@ -99,9 +99,6 @@ def generic_create( create_message_gas = max_message_call_gas(Uint(evm.gas_left)) evm.gas_left -= create_message_gas - if evm.message.is_static: - raise WriteInStaticContext - # Move full reservoir to child (no 63/64 rule for state gas). Parent's # `state_gas_left` is zeroed and restored when the child returns. create_message_state_gas_reservoir = evm.state_gas_left @@ -182,6 +179,9 @@ def create(evm: Evm) -> None: The current EVM frame. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK endowment = pop(evm.stack) memory_start_position = pop(evm.stack) @@ -231,6 +231,9 @@ def create2(evm: Evm) -> None: The current EVM frame. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK endowment = pop(evm.stack) memory_start_position = pop(evm.stack) From fb4c370aaa1c1a6354e0731f96a4c6105a2a8948 Mon Sep 17 00:00:00 2001 From: spencer Date: Fri, 12 Jun 2026 13:09:00 +0100 Subject: [PATCH 017/233] chore(tests): EIP-7928 expect InvalidParams when newPayload misses BAL (#2980) --- .../eip7928_block_level_access_lists/test_cases.md | 2 +- .../test_fork_transition.py | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_cases.md b/tests/amsterdam/eip7928_block_level_access_lists/test_cases.md index 7304a4ce04e..10ce020ccaa 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_cases.md +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_cases.md @@ -181,7 +181,7 @@ | `test_bal_create_and_oog` | CREATE/CREATE2 OOG boundary test at three gas levels | Parametrized: `@pytest.mark.with_all_create_opcodes`, `OutOfGasBoundary` (OOG_BEFORE_TARGET_ACCESS, OOG_AFTER_TARGET_ACCESS, SUCCESS). BEFORE and AFTER differ by 1 gas, proving the static cost boundary. | OOG_BEFORE: created address **MUST NOT** appear in BAL. OOG_AFTER: created address IS in BAL as `empty()` (accessed, state reverted). SUCCESS: created address in BAL with `nonce_changes`/`code_changes`. | ✅ Completed | | `test_bal_fork_transition_happy_path` | Verify a BAL is produced at the Amsterdam activation block and absent before it. File: `tests/amsterdam/eip7928_block_level_access_lists/test_fork_transition.py`. Uses `@pytest.mark.valid_at_transition_to("Amsterdam")` to run across the `BPO2 -> Amsterdam` boundary. | Two blocks: pre-fork (`timestamp=14_999`) with a simple Alice→Bob transfer, then activation block (`timestamp=15_000`) with the same kind of transfer. | Pre-fork block header **MUST NOT** carry `block_access_list_hash`. Activation block **MUST** include `block_access_list_hash` correctly derived from the BAL body, and the BAL body **MUST** record Alice's `nonce_changes` and Bob's `balance_changes` at `block_access_index=1`. | ✅ Completed | | `test_invalid_pre_fork_block_with_bal_hash_field` | Verify clients reject a pre-Amsterdam block whose header carries `block_access_list_hash`. File: `tests/amsterdam/eip7928_block_level_access_lists/test_fork_transition.py`. | Single block at `timestamp=14_999` with a regular transfer, mutated via `rlp_modifier=Header(block_access_list_hash=Hash(0))` to inject the field into the pre-fork header schema. | Block **MUST** be rejected with `BlockException.INVALID_BLOCK_HASH`: pre-fork clients compute the block hash without the injected field, mismatching the expected hash. | ✅ Completed | -| `test_invalid_post_fork_block_without_bal_hash_field` | Verify clients reject an Amsterdam activation block whose header is missing `block_access_list_hash`. File: `tests/amsterdam/eip7928_block_level_access_lists/test_fork_transition.py`. | Single block at `timestamp=15_000` with a regular transfer, mutated via `rlp_modifier=Header(block_access_list_hash=Header.REMOVE_FIELD)` so the field is dropped from the header. | Block **MUST** be rejected with `BlockException.INVALID_BAL_HASH` or `BlockException.INVALID_BLOCK_HASH`: clients re-derive the BAL hash from execution and detect the mismatch either at the BAL hash check or the header hash check. | ✅ Completed | +| `test_invalid_post_fork_block_without_bal_hash_field` | Verify clients reject an Amsterdam activation block whose header is missing `block_access_list_hash`. File: `tests/amsterdam/eip7928_block_level_access_lists/test_fork_transition.py`. | Single block at `timestamp=15_000` with a regular transfer, mutated via `rlp_modifier=Header(block_access_list_hash=Header.REMOVE_FIELD)` so the field is dropped from the header. | Block **MUST** be rejected with `BlockException.INVALID_BAL_HASH`: clients re-derive the BAL hash from execution and find no header hash to match. The engine fixture omits the `blockAccessList` param from `newPayloadV5`, which **MUST** return `-32602: Invalid params`. | ✅ Completed | | `test_fork_transition_bal_size_constraint` | Verify the BAL size constraint (`bal_items <= gas_limit // BLOCK_ACCESS_LIST_ITEM`) applies only on/after Amsterdam. File: `tests/amsterdam/eip7928_block_level_access_lists/test_fork_transition.py`. Parametrized over `exceeds_limit_at_fork`: `at_fork_within_budget` (`gas_limit == empty_block_bal_item_count() * BLOCK_ACCESS_LIST_ITEM`) and `at_fork_over_budget` (`gas_limit` one wei below that). | Two empty blocks: pre-fork (`timestamp=14_999`) and activation block (`timestamp=15_000`). The same low `gas_limit` is used for both via `genesis_environment=Environment(gas_limit=...)`. | Pre-fork block **MUST** be accepted under both budgets (constraint not yet enforced). Activation block **MUST** be accepted at the exact budget and **MUST** be rejected with `BlockException.BLOCK_ACCESS_LIST_GAS_LIMIT_EXCEEDED` one item over the budget. | ✅ Completed | | `test_bal_dirty_account_selfdestruct` | Ensure BAL does not record dirty state on a same-tx ephemeral contract whose `SELFDESTRUCT` takes effect. | A factory deploys an ephemeral whose initcode dirties balance, nonce, code, and storage; runtime `SELFDESTRUCT` routes through an intermediate oracle. | **success**: ephemeral's BAL entry contains only `storage_reads` for the demoted slots. **revert**: BAL records all four dirty fields (destruction rolled back). | ✅ Completed | diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_fork_transition.py b/tests/amsterdam/eip7928_block_level_access_lists/test_fork_transition.py index 6be8458434c..28fea46d463 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_fork_transition.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_fork_transition.py @@ -12,6 +12,7 @@ BlockchainTestFiller, BlockException, EIPChecklist, + EngineAPIError, Environment, Hash, Header, @@ -93,6 +94,10 @@ def test_invalid_pre_fork_block_with_bal_hash_field( """ Reject a pre-Amsterdam block whose header carries `block_access_list_hash`. + + The engine fixture sends a pre-Amsterdam `newPayload` carrying an + empty `blockAccessList` param; the client's reconstructed header + omits the hash, so the block hash check fails. """ sender = pre.fund_eoa() receiver = pre.fund_eoa(amount=0) @@ -123,6 +128,9 @@ def test_invalid_post_fork_block_without_bal_hash_field( """ Reject an Amsterdam activation block whose header is missing `block_access_list_hash`. + + The engine fixture sends `newPayloadV5` with the `blockAccessList` + param omitted, which must return `-32602: Invalid params`. """ sender = pre.fund_eoa() receiver = pre.fund_eoa(amount=0) @@ -139,10 +147,8 @@ def test_invalid_post_fork_block_without_bal_hash_field( rlp_modifier=Header( block_access_list_hash=Header.REMOVE_FIELD, ), - exception=[ - BlockException.INVALID_BAL_HASH, - BlockException.INVALID_BLOCK_HASH, - ], + exception=BlockException.INVALID_BAL_HASH, + engine_api_error_code=EngineAPIError.InvalidParams, ), ], ) From 9d7738bdf9ba8caaf2bb20c0d21d5ebe2921fe2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=94=A1=E4=BD=B3=E8=AA=A0=20Louis=20Tsai?= <72684086+LouisTsai-Csie@users.noreply.github.com> Date: Fri, 12 Jun 2026 20:21:55 +0800 Subject: [PATCH 018/233] refactor(test-benchmark): port benchmark from bal-devnet-7 (#2977) Co-authored-by: CPerezz <37264926+CPerezz@users.noreply.github.com> Co-authored-by: danceratopz --- .../plugins/execute/pre_alloc.py | 37 +++- .../tools/tests/test_iterating_bytecode.py | 3 +- .../tools/tools_code/generators.py | 104 +++++---- .../stateful/bloatnet/test_single_opcode.py | 200 ++++++++++++------ .../bloatnet/test_transaction_types.py | 5 +- .../stateful/stubs/stubs_jochemnet.json | 6 + 6 files changed, 237 insertions(+), 118 deletions(-) create mode 100644 tests/benchmark/stateful/stubs/stubs_jochemnet.json diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py index daeb78b2035..cbb79588b5b 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py @@ -607,6 +607,11 @@ def _fund_eoa( # Send a transaction to fund the EOA fund_tx: PendingTransaction | None = None if delegation is not None or storage is not None: + fork = self._fork.fork_at( + block_number=self._block_number, timestamp=self._timestamp + ) + intrinsic_calc = fork.transaction_intrinsic_cost_calculator() + if storage is not None: if not isinstance(storage, Storage): storage = Storage.model_validate(storage) @@ -614,14 +619,24 @@ def _fund_eoa( f"Deploying storage contract for EOA {eoa} " f"with {len(storage)} storage slots" ) - sstore_address = self.deploy_contract( - code=( - sum( - Op.SSTORE(key, value) - for key, value in storage.items() + + storage_init_code = ( + sum( + Op.SSTORE( + key, + value, + # gas accounting + key_warm=False, + original_value=0, + current_value=0, + new_value=1, ) - + Op.STOP + for key, value in storage.items() ) + + Op.STOP + ) + sstore_address = self.deploy_contract( + code=storage_init_code, ) logger.debug( f"Storage contract deployed at {sstore_address} " @@ -641,7 +656,11 @@ def _fund_eoa( signer=eoa, ), ], - gas_limit=100_000, + gas_limit=( + intrinsic_calc(authorization_list_or_count=1) + + storage_init_code.gas_cost(fork) + + 500_000 + ), ) eoa.nonce = Number(eoa.nonce + 1) @@ -666,7 +685,7 @@ def _fund_eoa( signer=eoa, ), ], - gas_limit=100_000, + gas_limit=(intrinsic_calc(authorization_list_or_count=1)), ) eoa.nonce = Number(eoa.nonce + 1) else: @@ -684,7 +703,7 @@ def _fund_eoa( signer=eoa, ), ], - gas_limit=100_000, + gas_limit=intrinsic_calc(authorization_list_or_count=1), ) eoa.nonce = Number(eoa.nonce + 1) diff --git a/packages/testing/src/execution_testing/tools/tests/test_iterating_bytecode.py b/packages/testing/src/execution_testing/tools/tests/test_iterating_bytecode.py index 4c5309bfbbf..6e82db00bad 100644 --- a/packages/testing/src/execution_testing/tools/tests/test_iterating_bytecode.py +++ b/packages/testing/src/execution_testing/tools/tests/test_iterating_bytecode.py @@ -325,7 +325,8 @@ def test_tx_iterations_by_total_iteration_count_raises_on_impossible() -> None: with pytest.raises( ValueError, - match="Single iteration gas cost is greater than gas limit.", + match="Single iteration gas cost exceeds gas_limit " + "or compute_gas_limit.", ): list( bytecode.tx_iterations_by_total_iteration_count( diff --git a/packages/testing/src/execution_testing/tools/tools_code/generators.py b/packages/testing/src/execution_testing/tools/tools_code/generators.py index 3e82c18bd0f..9d15d425e59 100644 --- a/packages/testing/src/execution_testing/tools/tools_code/generators.py +++ b/packages/testing/src/execution_testing/tools/tools_code/generators.py @@ -806,6 +806,10 @@ class IteratingBytecode(Bytecode): """ cleanup: Bytecode """Bytecode executed once at the end after all iterations complete.""" + iterating_state_gas: int + """ + State-gas portion (EIP-8037) charged per loop iteration. + """ def __new__( cls, @@ -815,6 +819,7 @@ def __new__( cleanup: Bytecode | None = None, warm_iterating: Bytecode | None = None, iterating_subcall: Bytecode | int | None = None, + iterating_state_gas: int = 0, ) -> Self: """ Create a new iterating bytecode. @@ -833,6 +838,8 @@ def __new__( calculation. The value can also be an integer, in which case it represents the gas cost of the subcall (e.g. the subcall is a precompiled contract). + iterating_state_gas: EIP-8037 state-gas portion charged + per iteration, defaults to 0. Returns: A new IteratingBytecode instance. @@ -860,6 +867,7 @@ def __new__( if cleanup is None: cleanup = Bytecode() instance.cleanup = cleanup + instance.iterating_state_gas = iterating_state_gas return instance def iterating_subcall_gas_cost( @@ -985,60 +993,85 @@ def tx_gas_limit_by_iteration_count( **intrinsic_cost_kwargs, ) + self.iterating_subcall_reserve(fork=fork) + def _iterations_fit_within_gas_limits( + self, + *, + fork: Fork, + iteration_count: int, + start_iteration: int, + gas_limit: int, + compute_gas_limit: int | None = None, + **intrinsic_cost_kwargs: Any, + ) -> bool: + """ + Check whether iteration_count iterations fit within the gas limits. + + Returns True when both: + - The combined regular+state gas (i.e. tx.gas) is <= + gas_limit (block-budget constraint). + - The regular gas, computed as + combined - iteration_count * iterating_state_gas, + respects the compute_gas_limit. + """ + if iteration_count <= 0: + return True + combined = self.tx_gas_limit_by_iteration_count( + fork=fork, + iteration_count=iteration_count, + start_iteration=start_iteration, + **intrinsic_cost_kwargs, + ) + if combined > gas_limit: + return False + if compute_gas_limit is not None: + compute = combined - iteration_count * self.iterating_state_gas + if compute > compute_gas_limit: + return False + return True + def _binary_search_iterations( self, *, fork: Fork, gas_limit: int, start_iteration: int, + compute_gas_limit: int | None = None, **intrinsic_cost_kwargs: Any, ) -> Tuple[int, int]: """ Binary search for the maximum iterations that fit within a gas limit. """ - single_iteration_gas = self.tx_gas_limit_by_iteration_count( - fork=fork, - iteration_count=1, - start_iteration=start_iteration, + fits_kwargs: Dict[str, Any] = { + "fork": fork, + "start_iteration": start_iteration, + "gas_limit": gas_limit, + "compute_gas_limit": compute_gas_limit, **intrinsic_cost_kwargs, - ) - if single_iteration_gas > gas_limit: + } + + if not self._iterations_fit_within_gas_limits( + iteration_count=1, **fits_kwargs + ): raise ValueError( - "Single iteration gas cost is greater than gas limit." + "Single iteration gas cost exceeds gas_limit " + "or compute_gas_limit." ) + low = 1 high = 2 # Exponential search to find upper bound - high_gas_cost = self.tx_gas_limit_by_iteration_count( - fork=fork, - iteration_count=high, - start_iteration=start_iteration, - **intrinsic_cost_kwargs, - ) - while high_gas_cost < gas_limit: + while self._iterations_fit_within_gas_limits( + iteration_count=high, **fits_kwargs + ): low = high high *= 2 - high_gas_cost = self.tx_gas_limit_by_iteration_count( - fork=fork, - iteration_count=high, - start_iteration=start_iteration, - **intrinsic_cost_kwargs, - ) # Binary search for exact fit - best_iterations = 0 while low < high: mid = (low + high) // 2 - - if ( - self.tx_gas_limit_by_iteration_count( - fork=fork, - iteration_count=mid, - start_iteration=start_iteration, - **intrinsic_cost_kwargs, - ) - > gas_limit + if not self._iterations_fit_within_gas_limits( + iteration_count=mid, **fits_kwargs ): high = mid else: @@ -1082,17 +1115,11 @@ def tx_iterations_by_gas_limit( start_iteration=start_iteration, **intrinsic_cost_kwargs, ): - # Binary search for the maximum number of iterations that fits - # within remaining_gas - max_gas_limit = ( - min(remaining_gas, gas_limit_cap) - if gas_limit_cap is not None - else remaining_gas - ) best_iterations, best_iterations_gas = ( self._binary_search_iterations( fork=fork, - gas_limit=max_gas_limit, + gas_limit=remaining_gas, + compute_gas_limit=gas_limit_cap, start_iteration=start_iteration, **intrinsic_cost_kwargs, ) @@ -1142,6 +1169,7 @@ def tx_iterations_by_total_iteration_count( best_iterations, _ = self._binary_search_iterations( fork=fork, gas_limit=gas_limit_cap, + compute_gas_limit=gas_limit_cap, start_iteration=start_iteration, **intrinsic_cost_kwargs, ) diff --git a/tests/benchmark/stateful/bloatnet/test_single_opcode.py b/tests/benchmark/stateful/bloatnet/test_single_opcode.py index 6f97d3ff8ec..eb5cb4c25a3 100644 --- a/tests/benchmark/stateful/bloatnet/test_single_opcode.py +++ b/tests/benchmark/stateful/bloatnet/test_single_opcode.py @@ -9,7 +9,7 @@ from enum import Enum, auto from functools import partial -from typing import Generator, List +from typing import Any, Callable, Generator, List import pytest from execution_testing import ( @@ -93,6 +93,7 @@ def _sender_generator( def delegate_with_calldata( pre: Alloc, + fork: Fork, authority: EOA, address: Address, calldata: Hash, @@ -103,8 +104,13 @@ def delegate_with_calldata( The delegated code determines what happens with the calldata. The authority nonce is incremented in-place. """ + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( + calldata=bytes(calldata), + authorization_list_or_count=1, + ) + gas_limit = intrinsic_gas + 500_000 tx = Transaction( - gas_limit=100_000, + gas_limit=gas_limit, to=authority, value=0, data=calldata, @@ -133,12 +139,10 @@ def run_bloated_eoa_benchmark( existing_slots: bool, runtime_code: Bytecode, cache_strategy: CacheStrategy, + tx_generator: Callable[[EOA], list[Transaction]] | None = None, ) -> None: """ Run a bloated-EOA benchmark with the given runtime delegation code. - - Handles authority setup, slot 0 initialization, delegation to - runtime code, benchmark tx generation, and test invocation. """ slot_0_value = Hash(1) if existing_slots else Hash(START_SLOT) @@ -146,30 +150,41 @@ def run_bloated_eoa_benchmark( runtime_address = pre.deploy_contract(code=runtime_code) init_tx = delegate_with_calldata( - pre, authority, setter_address, slot_0_value + pre, + fork, + authority, + setter_address, + slot_0_value, ) runtime_tx = delegate_with_calldata( - pre, authority, runtime_address, Hash(0) + pre, + fork, + authority, + runtime_address, + Hash(0), ) blocks: list[Block] = [Block(txs=[init_tx, runtime_tx])] - gas_available = gas_benchmark_value - intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() sender = pre.fund_eoa() txs: list[Transaction] = [] with TestPhaseManager.execution(): - while gas_available >= intrinsic_gas: - tx_gas = min(gas_available, tx_gas_limit) - txs.append( - Transaction( - gas_limit=tx_gas, - to=authority, - sender=sender, + if tx_generator is not None: + txs = tx_generator(sender) + else: + gas_available = gas_benchmark_value + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() + while gas_available >= intrinsic_gas: + tx_gas = min(gas_available, tx_gas_limit) + txs.append( + Transaction( + gas_limit=tx_gas, + to=authority, + sender=sender, + ) ) - ) - gas_available -= tx_gas + gas_available -= tx_gas cache_txs: list[Transaction] = [] if cache_strategy == CacheStrategy.CACHE_PREVIOUS_BLOCK: @@ -179,6 +194,7 @@ def run_bloated_eoa_benchmark( cache_txs.append( Transaction( gas_limit=tx.gas_limit, + data=tx.data, to=authority, sender=cache_sender, ) @@ -197,7 +213,7 @@ def run_bloated_eoa_benchmark( @pytest.mark.repricing @pytest.mark.stub_parametrize("token_name", "bloated_eoa_") @pytest.mark.parametrize("existing_slots", [False, True]) -@pytest.mark.parametrize("cache_strategy", list(CacheStrategy)) +@pytest.mark.parametrize("cache_strategy", [CacheStrategy.NO_CACHE]) def test_sload_bloated( benchmark_test: BenchmarkTestFiller, pre: Alloc, @@ -309,7 +325,11 @@ def test_sload_bloated_prefetch_miss( # forcing the prefetcher's pre-block snapshot to disagree with # the actual slot 0 value seen by every max-gas tx that follows. delegation_tx = delegate_with_calldata( - pre, authority, runtime_address, Hash(0) + pre, + fork, + authority, + runtime_address, + Hash(0), ) blocks: list[Block] = [Block(txs=[delegation_tx])] @@ -578,7 +598,7 @@ def test_sload_bloated_multi_contract( @pytest.mark.stub_parametrize("token_name", "bloated_eoa_") @pytest.mark.parametrize("write_new_value", [False, True]) @pytest.mark.parametrize("existing_slots", [True, False]) -@pytest.mark.parametrize("cache_strategy", list(CacheStrategy)) +@pytest.mark.parametrize("cache_strategy", [CacheStrategy.NO_CACHE]) def test_sstore_bloated( benchmark_test: BenchmarkTestFiller, pre: Alloc, @@ -592,73 +612,117 @@ def test_sstore_bloated( ) -> None: """ Benchmark SSTORE opcodes targeting an EOA with storage bloated. - - The storage is assumed to be filled from 0-N linearly, where - each slot has the value of the key. Except slot 0, this is the - pointer to the next free (empty) storage slot. - - For this test to work correctly under all parameters then above - has to be true. If this is not the case then some tests will not - test what they claim to do. For instance, for `write_new_value` - set to False we need to know the current value of the slots. """ + sstore_metadata: dict[str, Any] = {} + # If CACHE_TX, there would be one cold SLOAD before SSTORE + sstore_metadata["key_warm"] = cache_strategy == CacheStrategy.CACHE_TX + + # SSTORE metadata matrix: + # + # existing_slots | write_new_value | original | current | new + # ---------------+-----------------+----------+---------+----- + # True | True | 1 | 1 | 2 + # True | False | 1 | 1 | 1 + # False | True | 0 | 0 | 1 + # False | False | 0 | 0 | 0 + + initial_value = int(existing_slots) + + # When existing_slots is False, the initial value is always 0 + # Otherwise, the initial value starts at 1 instead. + sstore_metadata["original_value"] = initial_value + sstore_metadata["current_value"] = initial_value + + # If not writing a new value, the new value is the same as the current one + # If writing a new value, the new value is current value + 1 + sstore_metadata["new_value"] = ( + initial_value if not write_new_value else initial_value + 1 + ) + setup = ( - Op.PUSH0 # [0] - + Op.SLOAD # [key], s[0] = key - + Op.DUP1 # [key, key] + Op.CALLDATALOAD(32) # [end_slot] + + Op.CALLDATALOAD(0) # [counter, end_slot] ) - if write_new_value: - setup += ( - Op.PUSH1(1) # [1, key, key] - + Op.ADD # [key+1, key] - + Op.SWAP1 # [key, key+1] - ) + # stack element: [counter, end_slot] - # After setup phase, the stack element represents - # [slot, value], slot to write and value to write + loop = Bytecode() + loop += Op.JUMPDEST # jump target - cache_op = Bytecode() + # If CACHE_TX, warm the slot with a cold SLOAD before the SSTORE loop if cache_strategy == CacheStrategy.CACHE_TX: - cache_op = ( - Op.DUP1 # [slot, slot, value] - + Op.SLOAD # [s[slot], slot, value] - + Op.POP # [slot, value] + loop += Op.POP(Op.SLOAD(Op.DUP1, key_warm=False)) + + sstore_op: Bytecode = Bytecode() + if write_new_value: + # s[counter] = counter + 1 + sstore_op = ( + Op.DUP1 # [counter, counter, end_slot] + + Op.DUP1 # [counter, counter, counter, end_slot] + + Op.PUSH1(1) # [1, counter, counter, counter, end_slot] + + Op.ADD # [counter+1, counter, counter, end_slot] + + Op.SWAP1 # [counter, counter+1, counter, end_slot] + + Op.SSTORE(**sstore_metadata) # [counter, end_slot] + ) + else: + # s[counter] = counter (existing slot) or 0 (non existing slot) + push_value = Op.DUP1 if existing_slots else Op.PUSH1(0) + sstore_op = ( + push_value # [value, counter, end_slot] + + Op.DUP2 # [counter, value, counter, end_slot] + + Op.SSTORE(**sstore_metadata) # [counter, end_slot] ) - # The cache mechanism touches the slot before SSTORE + loop += sstore_op - runtime_code = ( - setup - + While( - body=( - cache_op # [slot, value] - + Op.DUP2 # [value, slot, value] - + Op.DUP2 # [slot, value, slot, value] - + Op.SSTORE # [slot, value], s[slot] = value - + Op.PUSH1(1) # [1, slot, value] - + Op.ADD # [slot+1, value] - + Op.SWAP1 # [value, slot+1] - + Op.PUSH1(1) # [1, value, slot+1] - + Op.ADD # [value+1, slot+1] - + Op.SWAP1 # [slot+1, value+1] - ), - condition=Op.GT(Op.GAS, 0xFFFF), - ) - + Op.PUSH0 # [0, slot+1, value+1] - + Op.SSTORE # s[0] = slot+1 + # stack element: [counter, end_slot] + + loop += ( + Op.PUSH1(1) # [1, counter, end_slot] + + Op.ADD # [counter+1, end_slot] + + Op.DUP2 # [end_slot, counter+1, end_slot] + + Op.DUP2 # [counter+1, end_slot, counter+1, end_slot] + + Op.LT # [counter+1 bytes: + return Hash(start_iteration) + Hash(start_iteration + iteration_count) + + def tx_generator(sender: EOA) -> list[Transaction]: + return list( + runtime_code.transactions_by_gas_limit( + fork=fork, + gas_limit=gas_benchmark_value, + sender=sender, + to=authority, + start_iteration=start_slot, + calldata=calldata_gen, + ) + ) + run_bloated_eoa_benchmark( benchmark_test=benchmark_test, pre=pre, fork=fork, gas_benchmark_value=gas_benchmark_value, tx_gas_limit=tx_gas_limit, - authority=pre.stub_eoa(token_name), + authority=authority, existing_slots=existing_slots, runtime_code=runtime_code, cache_strategy=cache_strategy, + tx_generator=tx_generator, ) @@ -1510,7 +1574,7 @@ class AccountMode(Enum): @pytest.mark.repricing -@pytest.mark.parametrize("cache_strategy", list(CacheStrategy)) +@pytest.mark.parametrize("cache_strategy", [CacheStrategy.NO_CACHE]) @pytest.mark.parametrize( "opcode,value_sent,account_mode", account_access_params() ) diff --git a/tests/benchmark/stateful/bloatnet/test_transaction_types.py b/tests/benchmark/stateful/bloatnet/test_transaction_types.py index 875c69fecdf..171c12a9e08 100644 --- a/tests/benchmark/stateful/bloatnet/test_transaction_types.py +++ b/tests/benchmark/stateful/bloatnet/test_transaction_types.py @@ -16,13 +16,14 @@ Transaction, compute_create2_address, compute_create_address, + keccak256, ) # Deterministic sender pool of 15K accounts. # Funded via system contract withdrawals (funding.txt) in payload generation. # Placed outside pre-allocation to ensure accounts remain uncached. -SENDER_BASE_KEY = ( - 0x1111111111111111111111111111111111111111111111111111111111111111 +SENDER_BASE_KEY = int.from_bytes( + keccak256(b"gas-repricings-private-key"), "big" ) diff --git a/tests/benchmark/stateful/stubs/stubs_jochemnet.json b/tests/benchmark/stateful/stubs/stubs_jochemnet.json new file mode 100644 index 00000000000..a52d9d9b7de --- /dev/null +++ b/tests/benchmark/stateful/stubs/stubs_jochemnet.json @@ -0,0 +1,6 @@ +{ + "bloated_eoa_10GB": { + "addr": "0x87a6314da5ac8832f6e7a176c8fb133b19f5be04", + "pkey": "0x4da32d29f6dcffa26e09dc4e102033f2d105de1444fb893493ae703289275e0e" + } +} From ae18430131e62c22279100f1c64c1b854c6ec834 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Mon, 15 Jun 2026 05:37:02 -0600 Subject: [PATCH 019/233] feat(test-specs): Allow automatic transaction gas-limit (#2969) Co-authored-by: Leo Lara Co-authored-by: danceratopz Co-authored-by: spencer-tb --- .../plugins/execute/execute.py | 37 +- .../plugins/execute/pre_alloc.py | 1 + .../plugins/filler/pre_alloc.py | 2 +- .../plugins/filler/tests/test_filler.py | 10 +- .../filler/tests/test_verify_sync_marker.py | 6 +- .../plugins/shared/transaction_fixtures.py | 5 - .../src/execution_testing/execution/base.py | 21 +- .../execution/blob_transaction.py | 37 +- .../execution/transaction_post.py | 32 +- .../fixtures/tests/test_base.py | 2 + .../fixtures/tests/test_blockchain.py | 35 +- .../src/execution_testing/forks/base_fork.py | 8 + .../forks/forks/eips/amsterdam/eip_8037.py | 7 + .../forks/forks/eips/osaka/eip_7883.py | 15 + .../execution_testing/forks/forks/forks.py | 8 + .../src/execution_testing/specs/blockchain.py | 29 +- .../src/execution_testing/specs/state.py | 12 +- .../specs/tests/test_benchmark.py | 63 +-- .../specs/tests/test_transaction.py | 2 +- .../tests/test_implicit_gas_limit.py | 291 +++++++++++++ .../test_types/tests/test_transactions.py | 26 ++ .../test_types/tests/test_types.py | 12 +- .../test_types/transaction_types.py | 198 ++++++++- .../tools/utility/generators.py | 9 - .../test_burn_logs.py | 26 -- .../test_fork_transition.py | 25 +- .../test_transfer_logs.py | 94 +---- .../eip7843_slotnum/test_fork_transition.py | 6 +- .../amsterdam/eip7843_slotnum/test_slotnum.py | 66 +-- .../test_block_access_lists.py | 355 ++++------------ .../test_block_access_lists_cross_index.py | 68 +-- .../test_block_access_lists_eip2935.py | 34 +- .../test_block_access_lists_eip4788.py | 27 +- .../test_block_access_lists_eip4895.py | 61 +-- .../test_block_access_lists_eip7002.py | 22 - .../test_block_access_lists_eip7702.py | 26 -- .../test_block_access_lists_invalid.py | 33 +- .../test_block_access_lists_opcodes.py | 221 +++++----- .../test_fork_transition.py | 37 +- .../test_max_code_size.py | 15 +- .../eip8024_dupn_swapn_exchange/test_dupn.py | 18 +- .../test_eip_vectors.py | 38 +- .../test_endofcode_underflow.py | 2 +- .../test_exchange.py | 26 +- .../test_pc_advancement.py | 10 +- .../eip8024_dupn_swapn_exchange/test_swapn.py | 70 ++-- .../test_block_2d_gas_accounting.py | 20 +- .../test_eip_mainnet.py | 16 +- .../test_state_gas_call.py | 168 ++------ .../test_state_gas_calldata_floor.py | 18 +- .../test_state_gas_create.py | 107 ++--- .../test_state_gas_delegation_pointer.py | 22 +- .../test_state_gas_fork_transition.py | 12 +- .../test_state_gas_multi_block.py | 24 +- .../test_state_gas_ordering.py | 8 +- .../test_state_gas_pricing.py | 26 +- .../test_state_gas_reservoir.py | 61 +-- .../test_state_gas_selfdestruct.py | 56 +-- .../test_state_gas_set_code.py | 141 ++----- .../test_state_gas_sstore.py | 113 ++--- .../compute/precompile/test_blake2f.py | 16 +- tests/benchmark/stateful/helpers.py | 5 +- .../eip2929_gas_cost_increases/test_call.py | 41 +- tests/berlin/eip2930_access_list/test_acl.py | 17 - tests/byzantium/eip196_ec_add_mul/conftest.py | 1 - tests/byzantium/eip197_ec_pairing/conftest.py | 1 - .../eip214_staticcall/test_staticcall.py | 25 -- .../cancun/eip1153_tstore/test_basic_tload.py | 56 +-- .../cancun/eip1153_tstore/test_tload_calls.py | 12 +- .../eip1153_tstore/test_tload_reentrancy.py | 10 +- tests/cancun/eip1153_tstore/test_tstorage.py | 70 +--- .../test_tstorage_clear_after_tx.py | 42 +- .../test_tstorage_create_contexts.py | 12 - .../test_tstorage_execution_contexts.py | 9 +- .../test_tstorage_reentrancy_contexts.py | 6 +- .../test_tstorage_selfdestruct.py | 6 +- .../eip1153_tstore/test_tstore_reentrancy.py | 8 +- .../test_beacon_root_contract.py | 31 +- .../eip4844_blobs/test_blobhash_opcode.py | 17 - .../test_blobhash_opcode_contexts.py | 23 +- .../eip4844_blobs/test_excess_blob_gas.py | 35 +- .../test_point_evaluation_precompile.py | 10 - .../test_point_evaluation_precompile_gas.py | 11 +- tests/cancun/eip5656_mcopy/test_mcopy.py | 29 +- .../eip5656_mcopy/test_mcopy_contexts.py | 22 +- ..._dynamic_create2_selfdestruct_collision.py | 5 - .../test_journal_revert.py | 9 +- .../test_reentrancy_selfdestruct_revert.py | 9 +- .../eip6780_selfdestruct/test_selfdestruct.py | 70 ---- .../test_selfdestruct_revert.py | 10 - .../test_blobgasfee_opcode.py | 11 +- tests/common/precompile_fixtures.py | 24 -- .../eip1014_create2/test_create2_revert.py | 12 - .../eip1014_create2/test_create_returndata.py | 2 - .../test_deterministic_deployment.py | 14 - .../eip1014_create2/test_recreate.py | 7 - .../eip1052_extcodehash/test_extcodehash.py | 253 +---------- .../test_shift_combinations.py | 16 +- tests/frontier/create/test_create_one_byte.py | 17 - .../create/test_create_preimage_layout.py | 19 +- .../create/test_create_suicide_during_init.py | 1 - .../create/test_create_suicide_store.py | 1 - .../examples/test_block_intermediate_state.py | 12 +- .../frontier/identity_precompile/conftest.py | 13 - .../identity_precompile/test_identity.py | 22 +- .../test_identity_returndatasize.py | 5 +- tests/frontier/opcodes/test_all_opcodes.py | 11 - tests/frontier/opcodes/test_blockhash.py | 8 - .../test_blockhash_state_test_recency.py | 2 - tests/frontier/opcodes/test_call.py | 7 +- tests/frontier/opcodes/test_calldatacopy.py | 6 - tests/frontier/opcodes/test_calldataload.py | 21 - tests/frontier/opcodes/test_calldatasize.py | 21 - tests/frontier/opcodes/test_data_copy_oog.py | 17 +- tests/frontier/opcodes/test_dup.py | 13 +- tests/frontier/opcodes/test_extcodecopy.py | 1 - tests/frontier/opcodes/test_push.py | 2 - tests/frontier/opcodes/test_selfdestruct.py | 3 - tests/frontier/opcodes/test_swap.py | 16 +- tests/frontier/precompiles/test_ecrecover.py | 13 +- .../precompiles/test_precompile_absence.py | 8 - .../frontier/precompiles/test_precompiles.py | 8 +- tests/frontier/precompiles/test_ripemd.py | 6 +- tests/frontier/touch/test_touch.py | 1 - tests/frontier/validation/test_transaction.py | 1 - tests/homestead/coverage/test_coverage.py | 21 +- .../identity_precompile/test_identity.py | 15 - .../istanbul/eip1344_chainid/test_chainid.py | 20 +- tests/istanbul/eip152_blake2/common.py | 11 +- tests/istanbul/eip152_blake2/conftest.py | 34 +- tests/istanbul/eip152_blake2/test_blake2.py | 393 +++--------------- .../eip152_blake2/test_blake2_delegatecall.py | 12 +- .../eip7883_modexp_gas_increase/conftest.py | 27 +- .../test_modexp_thresholds.py | 6 - .../test_modexp_thresholds_transition.py | 11 +- .../test_blob_base_fee.py | 37 +- ...blob_reserve_price_with_bpo_transitions.py | 4 +- .../test_count_leading_zeros.py | 136 +----- .../conftest.py | 22 - .../test_p256verify.py | 4 - .../test_p256verify_before_fork.py | 22 - .../test_collision_selfdestruct.py | 4 - .../test_initcollision.py | 4 - .../test_revert_in_create.py | 8 - .../security/test_selfdestruct_balance_bug.py | 38 +- .../stCallCodes/test_callcall_00.py | 30 +- .../test_callcall_00_suicide_end.py | 24 +- .../stCallCodes/test_callcallcall_000.py | 33 +- .../test_callcallcall_000_suicide_end.py | 27 +- .../stCallCodes/test_callcallcallcode_001.py | 33 +- .../test_callcallcallcode_001_suicide_end.py | 27 +- .../stCallCodes/test_callcallcode_01.py | 30 +- .../stCallCodes/test_callcallcodecall_010.py | 33 +- .../test_callcallcodecall_010_suicide_end.py | 27 +- .../test_callcallcodecallcode_011.py | 33 +- .../stCallCodes/test_callcodecall_10.py | 30 +- .../test_callcodecall_10_suicide_end.py | 24 +- .../stCallCodes/test_callcodecallcall_100.py | 33 +- .../test_callcodecallcall_100_suicide_end.py | 27 +- .../test_callcodecallcallcode_101.py | 33 +- ...st_callcodecallcallcode_101_suicide_end.py | 27 +- .../stCallCodes/test_callcodecallcode_11.py | 30 +- .../test_callcodecallcodecall_110.py | 33 +- ...st_callcodecallcodecall_110_suicide_end.py | 27 +- .../test_callcodecallcodecallcode_111.py | 33 +- ...allcodecallcodecallcode_111_suicide_end.py | 27 +- ..._create_init_fail_undefined_instruction.py | 19 +- .../test_callcallcallcode_001.py | 33 +- .../test_callcallcode_01.py | 30 +- .../test_callcallcode_01_suicide_end.py | 24 +- .../test_callcallcodecall_010.py | 33 +- .../test_callcallcodecall_010_suicide_end.py | 27 +- .../test_callcallcodecallcode_011.py | 33 +- ...st_callcallcodecallcode_011_suicide_end.py | 27 +- .../test_callcodecall_10.py | 30 +- .../test_callcodecall_10_suicide_end.py | 24 +- .../test_callcodecallcall_100.py | 33 +- .../test_callcodecallcall_100_suicide_end.py | 27 +- .../test_callcodecallcallcode_101.py | 33 +- ...st_callcodecallcallcode_101_suicide_end.py | 27 +- .../test_callcodecallcode_11.py | 30 +- .../test_callcodecallcode_11_suicide_end.py | 24 +- .../test_callcodecallcodecall_110.py | 33 +- ...st_callcodecallcodecall_110_suicide_end.py | 27 +- .../test_callcodecallcodecallcode_111.py | 33 +- ...allcodecallcodecallcode_111_suicide_end.py | 33 +- .../test_callcallcallcode_001.py | 33 +- .../test_callcallcallcode_001_suicide_end.py | 27 +- .../test_callcallcode_01.py | 30 +- .../test_callcallcode_01_suicide_end.py | 24 +- .../test_callcallcodecall_010.py | 33 +- .../test_callcallcodecall_010_suicide_end.py | 27 +- .../test_callcallcodecallcode_011.py | 33 +- ...st_callcallcodecallcode_011_suicide_end.py | 27 +- .../test_callcodecall_10.py | 30 +- .../test_callcodecall_10_suicide_end.py | 24 +- .../test_callcodecallcall_100.py | 33 +- .../test_callcodecallcall_100_suicide_end.py | 27 +- .../test_callcodecallcallcode_101.py | 33 +- ...st_callcodecallcallcode_101_suicide_end.py | 27 +- .../test_callcodecallcode_11.py | 30 +- .../test_callcodecallcode_11_suicide_end.py | 24 +- .../test_callcodecallcodecall_110.py | 33 +- ...st_callcodecallcodecall_110_suicide_end.py | 27 +- .../test_callcodecallcodecallcode_111.py | 33 +- ...allcodecallcodecallcode_111_suicide_end.py | 27 +- .../stCodeSizeLimit/test_codesize_valid.py | 18 +- .../stMemoryTest/test_mem0b_single_byte.py | 18 +- .../stMemoryTest/test_mem31b_single_byte.py | 18 +- .../stMemoryTest/test_mem32b_single_byte.py | 18 +- .../stMemoryTest/test_mem32kb_single_byte.py | 18 +- .../test_mem32kb_single_byte_minus_1.py | 18 +- .../test_mem32kb_single_byte_minus_31.py | 18 +- .../test_mem32kb_single_byte_minus_32.py | 18 +- .../test_mem32kb_single_byte_minus_33.py | 18 +- .../test_mem32kb_single_byte_plus_1.py | 18 +- .../test_mem32kb_single_byte_plus_31.py | 18 +- .../test_mem32kb_single_byte_plus_32.py | 18 +- .../test_mem32kb_single_byte_plus_33.py | 18 +- .../stMemoryTest/test_mem33b_single_byte.py | 18 +- .../stMemoryTest/test_mem64kb_single_byte.py | 18 +- .../test_mem64kb_single_byte_minus_1.py | 18 +- .../test_mem64kb_single_byte_minus_31.py | 18 +- .../test_mem64kb_single_byte_minus_32.py | 18 +- .../test_mem64kb_single_byte_minus_33.py | 18 +- .../test_mem64kb_single_byte_plus_1.py | 18 +- .../test_mem64kb_single_byte_plus_31.py | 18 +- .../test_mem64kb_single_byte_plus_32.py | 18 +- .../test_mem64kb_single_byte_plus_33.py | 18 +- .../stRandom/test_random_statetest102.py | 11 +- .../stRandom/test_random_statetest104.py | 11 +- .../stRandom/test_random_statetest105.py | 11 +- .../stRandom/test_random_statetest106.py | 11 +- .../stRandom/test_random_statetest107.py | 11 +- .../stRandom/test_random_statetest11.py | 11 +- .../stRandom/test_random_statetest110.py | 11 +- .../stRandom/test_random_statetest112.py | 11 +- .../stRandom/test_random_statetest114.py | 11 +- .../stRandom/test_random_statetest116.py | 11 +- .../stRandom/test_random_statetest117.py | 11 +- .../stRandom/test_random_statetest118.py | 11 +- .../stRandom/test_random_statetest119.py | 11 +- .../stRandom/test_random_statetest12.py | 11 +- .../stRandom/test_random_statetest120.py | 11 +- .../stRandom/test_random_statetest121.py | 11 +- .../stRandom/test_random_statetest122.py | 11 +- .../stRandom/test_random_statetest124.py | 11 +- .../stRandom/test_random_statetest129.py | 11 +- .../stRandom/test_random_statetest130.py | 11 +- .../stRandom/test_random_statetest131.py | 11 +- .../stRandom/test_random_statetest137.py | 11 +- .../stRandom/test_random_statetest139.py | 11 +- .../stRandom/test_random_statetest142.py | 11 +- .../stRandom/test_random_statetest145.py | 11 +- .../stRandom/test_random_statetest148.py | 11 +- .../stRandom/test_random_statetest15.py | 11 +- .../stRandom/test_random_statetest155.py | 11 +- .../stRandom/test_random_statetest156.py | 11 +- .../stRandom/test_random_statetest158.py | 11 +- .../stRandom/test_random_statetest161.py | 11 +- .../stRandom/test_random_statetest162.py | 11 +- .../stRandom/test_random_statetest166.py | 11 +- .../stRandom/test_random_statetest167.py | 11 +- .../stRandom/test_random_statetest169.py | 11 +- .../stRandom/test_random_statetest175.py | 11 +- .../stRandom/test_random_statetest179.py | 11 +- .../stRandom/test_random_statetest180.py | 11 +- .../stRandom/test_random_statetest183.py | 11 +- .../stRandom/test_random_statetest184.py | 11 +- .../stRandom/test_random_statetest187.py | 11 +- .../stRandom/test_random_statetest188.py | 11 +- .../stRandom/test_random_statetest19.py | 11 +- .../stRandom/test_random_statetest191.py | 11 +- .../stRandom/test_random_statetest192.py | 11 +- .../stRandom/test_random_statetest194.py | 12 +- .../stRandom/test_random_statetest195.py | 11 +- .../stRandom/test_random_statetest196.py | 11 +- .../stRandom/test_random_statetest2.py | 11 +- .../stRandom/test_random_statetest200.py | 11 +- .../stRandom/test_random_statetest202.py | 11 +- .../stRandom/test_random_statetest204.py | 11 +- .../stRandom/test_random_statetest206.py | 11 +- .../stRandom/test_random_statetest208.py | 11 +- .../stRandom/test_random_statetest210.py | 11 +- .../stRandom/test_random_statetest214.py | 11 +- .../stRandom/test_random_statetest215.py | 11 +- .../stRandom/test_random_statetest216.py | 11 +- .../stRandom/test_random_statetest217.py | 11 +- .../stRandom/test_random_statetest219.py | 11 +- .../stRandom/test_random_statetest220.py | 11 +- .../stRandom/test_random_statetest221.py | 11 +- .../stRandom/test_random_statetest222.py | 11 +- .../stRandom/test_random_statetest225.py | 11 +- .../stRandom/test_random_statetest227.py | 11 +- .../stRandom/test_random_statetest23.py | 11 +- .../stRandom/test_random_statetest231.py | 11 +- .../stRandom/test_random_statetest238.py | 11 +- .../stRandom/test_random_statetest242.py | 11 +- .../stRandom/test_random_statetest243.py | 11 +- .../stRandom/test_random_statetest247.py | 11 +- .../stRandom/test_random_statetest248.py | 11 +- .../stRandom/test_random_statetest249.py | 12 +- .../stRandom/test_random_statetest254.py | 11 +- .../stRandom/test_random_statetest259.py | 11 +- .../stRandom/test_random_statetest264.py | 12 +- .../stRandom/test_random_statetest267.py | 11 +- .../stRandom/test_random_statetest268.py | 11 +- .../stRandom/test_random_statetest269.py | 11 +- .../stRandom/test_random_statetest27.py | 11 +- .../stRandom/test_random_statetest276.py | 11 +- .../stRandom/test_random_statetest278.py | 11 +- .../stRandom/test_random_statetest279.py | 11 +- .../stRandom/test_random_statetest28.py | 11 +- .../stRandom/test_random_statetest280.py | 11 +- .../stRandom/test_random_statetest281.py | 11 +- .../stRandom/test_random_statetest283.py | 11 +- .../stRandom/test_random_statetest29.py | 11 +- .../stRandom/test_random_statetest290.py | 11 +- .../stRandom/test_random_statetest297.py | 11 +- .../stRandom/test_random_statetest298.py | 11 +- .../stRandom/test_random_statetest299.py | 11 +- .../stRandom/test_random_statetest3.py | 11 +- .../stRandom/test_random_statetest301.py | 11 +- .../stRandom/test_random_statetest305.py | 11 +- .../stRandom/test_random_statetest310.py | 11 +- .../stRandom/test_random_statetest311.py | 11 +- .../stRandom/test_random_statetest315.py | 11 +- .../stRandom/test_random_statetest316.py | 12 +- .../stRandom/test_random_statetest318.py | 11 +- .../stRandom/test_random_statetest322.py | 11 +- .../stRandom/test_random_statetest325.py | 11 +- .../stRandom/test_random_statetest329.py | 11 +- .../stRandom/test_random_statetest332.py | 11 +- .../stRandom/test_random_statetest333.py | 11 +- .../stRandom/test_random_statetest334.py | 11 +- .../stRandom/test_random_statetest339.py | 11 +- .../stRandom/test_random_statetest342.py | 11 +- .../stRandom/test_random_statetest348.py | 11 +- .../stRandom/test_random_statetest351.py | 11 +- .../stRandom/test_random_statetest354.py | 11 +- .../stRandom/test_random_statetest356.py | 11 +- .../stRandom/test_random_statetest358.py | 11 +- .../stRandom/test_random_statetest360.py | 11 +- .../stRandom/test_random_statetest361.py | 11 +- .../stRandom/test_random_statetest362.py | 11 +- .../stRandom/test_random_statetest363.py | 11 +- .../stRandom/test_random_statetest364.py | 11 +- .../stRandom/test_random_statetest365.py | 11 +- .../stRandom/test_random_statetest366.py | 11 +- .../stRandom/test_random_statetest367.py | 11 +- .../stRandom/test_random_statetest369.py | 11 +- .../stRandom/test_random_statetest37.py | 11 +- .../stRandom/test_random_statetest372.py | 11 +- .../stRandom/test_random_statetest380.py | 11 +- .../stRandom/test_random_statetest381.py | 11 +- .../stRandom/test_random_statetest382.py | 11 +- .../stRandom/test_random_statetest383.py | 11 +- .../stRandom/test_random_statetest41.py | 11 +- .../stRandom/test_random_statetest47.py | 11 +- .../stRandom/test_random_statetest49.py | 11 +- .../stRandom/test_random_statetest52.py | 11 +- .../stRandom/test_random_statetest58.py | 11 +- .../stRandom/test_random_statetest59.py | 11 +- .../stRandom/test_random_statetest6.py | 11 +- .../stRandom/test_random_statetest60.py | 11 +- .../stRandom/test_random_statetest62.py | 11 +- .../stRandom/test_random_statetest63.py | 11 +- .../stRandom/test_random_statetest66.py | 11 +- .../stRandom/test_random_statetest67.py | 11 +- .../stRandom/test_random_statetest69.py | 11 +- .../stRandom/test_random_statetest73.py | 11 +- .../stRandom/test_random_statetest74.py | 11 +- .../stRandom/test_random_statetest75.py | 11 +- .../stRandom/test_random_statetest77.py | 11 +- .../stRandom/test_random_statetest80.py | 11 +- .../stRandom/test_random_statetest81.py | 11 +- .../stRandom/test_random_statetest83.py | 11 +- .../stRandom/test_random_statetest85.py | 11 +- .../stRandom/test_random_statetest87.py | 11 +- .../stRandom/test_random_statetest88.py | 11 +- .../stRandom/test_random_statetest89.py | 11 +- .../stRandom/test_random_statetest9.py | 11 +- .../stRandom/test_random_statetest90.py | 11 +- .../stRandom/test_random_statetest92.py | 11 +- .../stRandom/test_random_statetest95.py | 11 +- .../stRandom/test_random_statetest96.py | 11 +- .../stRandom2/test_random_statetest.py | 11 +- .../stRandom2/test_random_statetest384.py | 11 +- .../stRandom2/test_random_statetest385.py | 11 +- .../stRandom2/test_random_statetest386.py | 12 +- .../stRandom2/test_random_statetest388.py | 11 +- .../stRandom2/test_random_statetest389.py | 11 +- .../stRandom2/test_random_statetest395.py | 11 +- .../stRandom2/test_random_statetest398.py | 11 +- .../stRandom2/test_random_statetest399.py | 11 +- .../stRandom2/test_random_statetest402.py | 11 +- .../stRandom2/test_random_statetest405.py | 11 +- .../stRandom2/test_random_statetest407.py | 11 +- .../stRandom2/test_random_statetest408.py | 11 +- .../stRandom2/test_random_statetest411.py | 11 +- .../stRandom2/test_random_statetest412.py | 11 +- .../stRandom2/test_random_statetest413.py | 11 +- .../stRandom2/test_random_statetest416.py | 11 +- .../stRandom2/test_random_statetest419.py | 11 +- .../stRandom2/test_random_statetest421.py | 11 +- .../stRandom2/test_random_statetest424.py | 11 +- .../stRandom2/test_random_statetest425.py | 11 +- .../stRandom2/test_random_statetest426.py | 11 +- .../stRandom2/test_random_statetest429.py | 11 +- .../stRandom2/test_random_statetest430.py | 11 +- .../stRandom2/test_random_statetest436.py | 11 +- .../stRandom2/test_random_statetest438.py | 11 +- .../stRandom2/test_random_statetest439.py | 11 +- .../stRandom2/test_random_statetest440.py | 11 +- .../stRandom2/test_random_statetest446.py | 11 +- .../stRandom2/test_random_statetest447.py | 11 +- .../stRandom2/test_random_statetest450.py | 11 +- .../stRandom2/test_random_statetest451.py | 11 +- .../stRandom2/test_random_statetest452.py | 11 +- .../stRandom2/test_random_statetest455.py | 11 +- .../stRandom2/test_random_statetest457.py | 11 +- .../stRandom2/test_random_statetest460.py | 11 +- .../stRandom2/test_random_statetest461.py | 11 +- .../stRandom2/test_random_statetest462.py | 11 +- .../stRandom2/test_random_statetest464.py | 11 +- .../stRandom2/test_random_statetest465.py | 12 +- .../stRandom2/test_random_statetest470.py | 11 +- .../stRandom2/test_random_statetest471.py | 11 +- .../stRandom2/test_random_statetest473.py | 11 +- .../stRandom2/test_random_statetest474.py | 11 +- .../stRandom2/test_random_statetest475.py | 11 +- .../stRandom2/test_random_statetest477.py | 11 +- .../stRandom2/test_random_statetest480.py | 11 +- .../stRandom2/test_random_statetest482.py | 11 +- .../stRandom2/test_random_statetest483.py | 12 +- .../stRandom2/test_random_statetest488.py | 11 +- .../stRandom2/test_random_statetest489.py | 11 +- .../stRandom2/test_random_statetest491.py | 11 +- .../stRandom2/test_random_statetest497.py | 11 +- .../stRandom2/test_random_statetest500.py | 11 +- .../stRandom2/test_random_statetest502.py | 11 +- .../stRandom2/test_random_statetest503.py | 11 +- .../stRandom2/test_random_statetest505.py | 11 +- .../stRandom2/test_random_statetest506.py | 11 +- .../stRandom2/test_random_statetest511.py | 11 +- .../stRandom2/test_random_statetest512.py | 11 +- .../stRandom2/test_random_statetest514.py | 11 +- .../stRandom2/test_random_statetest516.py | 11 +- .../stRandom2/test_random_statetest518.py | 11 +- .../stRandom2/test_random_statetest519.py | 11 +- .../stRandom2/test_random_statetest520.py | 11 +- .../stRandom2/test_random_statetest526.py | 11 +- .../stRandom2/test_random_statetest532.py | 11 +- .../stRandom2/test_random_statetest533.py | 11 +- .../stRandom2/test_random_statetest534.py | 11 +- .../stRandom2/test_random_statetest535.py | 11 +- .../stRandom2/test_random_statetest537.py | 11 +- .../stRandom2/test_random_statetest539.py | 11 +- .../stRandom2/test_random_statetest541.py | 13 +- .../stRandom2/test_random_statetest544.py | 11 +- .../stRandom2/test_random_statetest545.py | 11 +- .../stRandom2/test_random_statetest546.py | 11 +- .../stRandom2/test_random_statetest548.py | 11 +- .../stRandom2/test_random_statetest550.py | 11 +- .../stRandom2/test_random_statetest552.py | 11 +- .../stRandom2/test_random_statetest553.py | 11 +- .../stRandom2/test_random_statetest555.py | 11 +- .../stRandom2/test_random_statetest556.py | 11 +- .../stRandom2/test_random_statetest564.py | 11 +- .../stRandom2/test_random_statetest565.py | 11 +- .../stRandom2/test_random_statetest571.py | 11 +- .../stRandom2/test_random_statetest574.py | 11 +- .../stRandom2/test_random_statetest578.py | 11 +- .../stRandom2/test_random_statetest580.py | 11 +- .../stRandom2/test_random_statetest585.py | 11 +- .../stRandom2/test_random_statetest586.py | 11 +- .../stRandom2/test_random_statetest587.py | 11 +- .../stRandom2/test_random_statetest588.py | 12 +- .../stRandom2/test_random_statetest592.py | 11 +- .../stRandom2/test_random_statetest596.py | 11 +- .../stRandom2/test_random_statetest599.py | 11 +- .../stRandom2/test_random_statetest600.py | 11 +- .../stRandom2/test_random_statetest602.py | 11 +- .../stRandom2/test_random_statetest603.py | 11 +- .../stRandom2/test_random_statetest605.py | 11 +- .../stRandom2/test_random_statetest607.py | 11 +- .../stRandom2/test_random_statetest608.py | 11 +- .../stRandom2/test_random_statetest610.py | 11 +- .../stRandom2/test_random_statetest615.py | 11 +- .../stRandom2/test_random_statetest616.py | 11 +- .../stRandom2/test_random_statetest620.py | 11 +- .../stRandom2/test_random_statetest621.py | 11 +- .../stRandom2/test_random_statetest629.py | 11 +- .../stRandom2/test_random_statetest630.py | 11 +- .../stRandom2/test_random_statetest633.py | 11 +- .../stRandom2/test_random_statetest637.py | 11 +- .../stRandom2/test_random_statetest638.py | 11 +- .../stRandom2/test_random_statetest641.py | 11 +- .../test_revert_in_create_in_init_paris.py | 12 +- .../test_callcode_to_return1.py | 20 +- .../test_create_name_registrator.py | 16 +- .../conftest.py | 1 - .../test_bls12_precompiles_before_fork.py | 22 - ...t_bls12_variable_length_input_contracts.py | 62 --- .../test_block_hashes.py | 10 +- .../test_contract_deployment.py | 6 +- tests/prague/eip6110_deposits/conftest.py | 25 +- tests/prague/eip6110_deposits/helpers.py | 51 +-- .../prague/eip6110_deposits/test_deposits.py | 11 - .../conftest.py | 9 +- .../helpers.py | 78 ++-- .../test_withdrawal_requests.py | 9 - .../prague/eip7251_consolidations/conftest.py | 5 +- .../prague/eip7251_consolidations/helpers.py | 78 ++-- .../test_consolidations.py | 13 - .../test_multi_type_requests.py | 1 - .../prague/eip7702_set_code_tx/test_calls.py | 26 -- tests/prague/eip7702_set_code_tx/test_gas.py | 2 - .../eip7702_set_code_tx/test_set_code_txs.py | 162 +------- .../test_set_code_txs_2.py | 113 +---- .../test_warm_coinbase.py | 20 +- tests/shanghai/eip3855_push0/test_push0.py | 65 +-- tests/shanghai/eip3860_initcode/conftest.py | 8 +- .../eip3860_initcode/test_initcode.py | 46 +- .../eip4895_withdrawals/test_withdrawals.py | 45 +- 525 files changed, 2165 insertions(+), 8898 deletions(-) create mode 100644 packages/testing/src/execution_testing/forks/forks/eips/osaka/eip_7883.py create mode 100644 packages/testing/src/execution_testing/test_types/tests/test_implicit_gas_limit.py delete mode 100644 tests/frontier/identity_precompile/conftest.py diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/execute.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/execute.py index be8743ac0d5..0a69a8c7799 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/execute.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/execute.py @@ -11,12 +11,14 @@ from execution_testing.base_types import Account from execution_testing.base_types import Alloc as BaseAlloc +from execution_testing.base_types.base_types import HexNumber from execution_testing.execution import BaseExecute from execution_testing.forks import Fork, TransitionFork from execution_testing.logging import get_logger from execution_testing.rpc import EngineRPC, EthRPC from execution_testing.specs import BaseTest from execution_testing.test_types import ( + Environment, EnvironmentDefaults, ) @@ -271,6 +273,17 @@ def gas_limit_accumulator() -> Generator[GasInfoAccumulator, None, None]: logger.info(f"Total minimum balance: {total_min_eth:.18f}") +@pytest.fixture(scope="session") +def env_gas_limit(eth_rpc: EthRPC) -> HexNumber: + """ + Return the environment gas limit derived from the head block before + tests start running. + """ + head_block = eth_rpc.get_block_by_number() + assert head_block is not None, "Unable to obtain head block from RPC" + return HexNumber(head_block["gasLimit"]) + + def base_test_parametrizer(cls: Type[BaseTest]) -> Any: """ Generate pytest.fixture for a given BaseTest subclass. @@ -288,7 +301,7 @@ def base_test_parametrizer(cls: Type[BaseTest]) -> Any: ) def base_test_parametrizer_func( request: Any, - fork: Fork | TransitionFork, + fork: Fork, pre: Alloc, eth_rpc: EthRPC, engine_rpc: EngineRPC | None, @@ -302,6 +315,7 @@ def base_test_parametrizer_func( max_fee_per_blob_gas: int, max_gas_limit_per_test: int | None, gas_limit_accumulator: GasInfoAccumulator, + env_gas_limit: HexNumber, is_tx_gas_heavy_test: bool, is_exception_test: bool, ) -> Type[BaseTest]: @@ -348,24 +362,23 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: if p not in kwargs } - # TODO: get values from network - timestamp = 0 - block_number = 0 - request.node.config.sender_address = str(pre._sender) super(BaseTestWrapper, self).__init__(*args, **kwargs) execute = self.execute(execute_format=execute_format) - # get balances of required sender accounts - required_balances = execute.get_required_sender_balances( + execute.prepare_transactions( + env=Environment(gas_limit=env_gas_limit), gas_price=gas_price, max_fee_per_gas=max_fee_per_gas, max_priority_fee_per_gas=max_priority_fee_per_gas, max_fee_per_blob_gas=max_fee_per_blob_gas, - fork=fork.fork_at( - block_number=block_number, timestamp=timestamp - ), + fork=fork, + ) + + # get balances of required sender accounts + required_balances = execute.get_required_sender_balances( + fork=fork, ) pre.resolve_deferred_checks() @@ -432,9 +445,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: ) execute_result = execute.execute( - fork=fork.fork_at( - block_number=block_number, timestamp=timestamp - ), + fork=fork, eth_rpc=eth_rpc, engine_rpc=engine_rpc, request=request, diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py index cbb79588b5b..f0d1f418351 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py @@ -1007,6 +1007,7 @@ def minimum_balance_for_pending_transactions( max_priority_fee_per_gas=max_priority_fee_per_gas, max_fee_per_blob_gas=max_fee_per_blob_gas, ) + assert "gas_limit" in tx.model_fields_set, "tx gas limit not set" gas_consumption += tx.gas_limit minimum_balance += tx.signer_minimum_balance(fork=fork) return minimum_balance + gas_consumption * gas_price, gas_consumption diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py index c3b76bf9d90..b7d96d3beea 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py @@ -66,7 +66,7 @@ def pytest_addoption(parser: pytest.Parser) -> None: class Alloc(SharedAlloc): """Allocation of accounts in the state, pre and post test execution.""" - _eoa_fund_amount_default: int = PrivateAttr(10**21) + _eoa_fund_amount_default: int = PrivateAttr(10**27) _account_salt: Dict[Hash, int] = PrivateAttr(default_factory=dict) _stub_accounts: Dict[str, Account] = PrivateAttr(default_factory=dict) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_filler.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_filler.py index 73e97d70f62..29f8d06f939 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_filler.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_filler.py @@ -58,13 +58,13 @@ def count_keys_in_fixture(file_path: Path) -> int: # noqa: D103 @pytest.mark.valid_until("Shanghai") def test_paris_one(state_test) -> None: state_test(env=Environment(), - pre={TestAddress: Account(balance=1_000_000)}, post={}, tx=Transaction()) + pre={TestAddress: Account(balance=1_000_000)}, post={}, tx=Transaction(gas_limit=0x5208)) @pytest.mark.valid_from("Paris") @pytest.mark.valid_until("Shanghai") def test_paris_two(state_test) -> None: state_test(env=Environment(), - pre={TestAddress: Account(balance=1_000_000)}, post={}, tx=Transaction()) + pre={TestAddress: Account(balance=1_000_000)}, post={}, tx=Transaction(gas_limit=0x5208)) """ ) test_count_paris = 4 @@ -79,14 +79,14 @@ def test_paris_two(state_test) -> None: @pytest.mark.valid_until("Shanghai") def test_shanghai_one(state_test) -> None: state_test(env=Environment(), - pre={TestAddress: Account(balance=1_000_000)}, post={}, tx=Transaction()) + pre={TestAddress: Account(balance=1_000_000)}, post={}, tx=Transaction(gas_limit=0x5208)) @pytest.mark.parametrize("x", [1, 2, 3]) @pytest.mark.valid_from("Paris") @pytest.mark.valid_until("Shanghai") def test_shanghai_two(state_test, x) -> None: state_test(env=Environment(), - pre={TestAddress: Account(balance=1_000_000)}, post={}, tx=Transaction()) + pre={TestAddress: Account(balance=1_000_000)}, post={}, tx=Transaction(gas_limit=0x5208)) """ ) @@ -988,7 +988,7 @@ def test_benchmark_one(state_test) -> None: env=Environment(), pre={TestAddress: Account(balance=1_000_000)}, post={}, - tx=Transaction(), + tx=Transaction(gas_limit=0x5208), ) """ ) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_verify_sync_marker.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_verify_sync_marker.py index 4e4fff33402..15b44ac53e5 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_verify_sync_marker.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_verify_sync_marker.py @@ -23,7 +23,7 @@ def test_verify_sync_default(blockchain_test) -> None: blockchain_test( pre={TestAddress: TEST_ADDRESS}, post={}, - blocks=[Block(txs=[Transaction()])] + blocks=[Block(txs=[Transaction(gas_limit=0x5208)])] ) @@ -33,7 +33,7 @@ def test_verify_sync_with_marker(blockchain_test) -> None: blockchain_test( pre={TestAddress: TEST_ADDRESS}, post={}, - blocks=[Block(txs=[Transaction()])] + blocks=[Block(txs=[Transaction(gas_limit=0x5208)])] ) @pytest.mark.valid_at("Cancun") @@ -52,7 +52,7 @@ def test_verify_sync_with_param_marks(blockchain_test, has_exception) -> None: post={}, blocks=[ Block( - txs=[Transaction()], + txs=[Transaction(gas_limit=0x5208)], rlp_modifier=Header(gas_limit=0) if has_exception else None, exception=BlockException.INCORRECT_BLOCK_FORMAT if has_exception else None, ) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/transaction_fixtures.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/transaction_fixtures.py index 33dd3693cf4..4143ea1fb2e 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/transaction_fixtures.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/transaction_fixtures.py @@ -25,7 +25,6 @@ def type_0_default_transaction(sender: EOA) -> Transaction: ty=0, sender=sender, gas_price=10**9, - gas_limit=100_000, data=b"\x00" * 100, protected=True, ) @@ -38,7 +37,6 @@ def type_1_default_transaction(sender: EOA) -> Transaction: ty=1, sender=sender, gas_price=10**9, - gas_limit=100_000, data=b"\x00" * 100, access_list=[ AccessList(address=0x1234, storage_keys=[0, 1, 2]), @@ -56,7 +54,6 @@ def type_2_default_transaction(sender: EOA) -> Transaction: sender=sender, max_fee_per_gas=10**10, max_priority_fee_per_gas=10**9, - gas_limit=100_000, data=b"\x00" * 200, access_list=[ AccessList(address=0x2468, storage_keys=[10, 20, 30]), @@ -74,7 +71,6 @@ def type_3_default_transaction(sender: EOA) -> Transaction: max_fee_per_gas=10**10, max_priority_fee_per_gas=10**9, max_fee_per_blob_gas=10**9, - gas_limit=100_000, data=b"\x00" * 150, access_list=[ AccessList(address=0x3690, storage_keys=[100, 200]), @@ -106,7 +102,6 @@ def type_4_default_transaction(sender: EOA, pre: Alloc) -> Transaction: sender=sender, max_fee_per_gas=10**10, max_priority_fee_per_gas=10**9, - gas_limit=500_000, data=b"\x00" * 200, access_list=[ AccessList(address=0x4567, storage_keys=[1000, 2000, 3000]), diff --git a/packages/testing/src/execution_testing/execution/base.py b/packages/testing/src/execution_testing/execution/base.py index 11cd9248cb2..159d66e9fe1 100644 --- a/packages/testing/src/execution_testing/execution/base.py +++ b/packages/testing/src/execution_testing/execution/base.py @@ -9,6 +9,7 @@ from execution_testing.base_types import Address, CamelModel from execution_testing.forks import Fork from execution_testing.rpc import EngineRPC, EthRPC +from execution_testing.test_types import Environment class ExecuteResult(CamelModel): @@ -42,18 +43,32 @@ def __pydantic_init_subclass__(cls, **kwargs: Any) -> None: # Register the new execute format BaseExecute.formats[cls.format_name] = cls - def get_required_sender_balances( + def prepare_transactions( self, *, + env: Environment, gas_price: int, max_fee_per_gas: int, max_priority_fee_per_gas: int, max_fee_per_blob_gas: int, fork: Fork, - ) -> Dict[Address, int]: - """Get the required sender balances.""" + ) -> None: + """Prepare transactions by setting their final gas properties.""" + del env del gas_price, max_fee_per_gas, max_priority_fee_per_gas del max_fee_per_blob_gas, fork + raise Exception( + "Method `prepare_transactions` not implemented for " + f"{self.format_name}" + ) + + def get_required_sender_balances( + self, + *, + fork: Fork, + ) -> Dict[Address, int]: + """Get the required sender balances.""" + del fork raise Exception( "Method `get_required_sender_balances` not implemented for " f"{self.format_name}" diff --git a/packages/testing/src/execution_testing/execution/blob_transaction.py b/packages/testing/src/execution_testing/execution/blob_transaction.py index fa3f22c0051..796f2c455ac 100644 --- a/packages/testing/src/execution_testing/execution/blob_transaction.py +++ b/packages/testing/src/execution_testing/execution/blob_transaction.py @@ -19,6 +19,7 @@ ) from execution_testing.rpc.rpc_types import GetBlobsResponse from execution_testing.test_types import ( + Environment, NetworkWrappedTransaction, Transaction, ) @@ -149,26 +150,50 @@ class BlobTransaction(BaseExecute): nonexisting_blob_hashes: List[Hash] | None = None get_blobs_version: int | None = None - def get_required_sender_balances( + def prepare_transactions( self, *, + env: Environment, gas_price: int, max_fee_per_gas: int, max_priority_fee_per_gas: int, max_fee_per_blob_gas: int, fork: Fork, - ) -> Dict[Address, int]: - """Get the required sender balances.""" - balances: Dict[Address, int] = {} + ) -> None: + """Prepare transactions by setting their final gas properties.""" + txs: List[Transaction] = [] for tx in self.txs: - sender = tx.sender - assert sender is not None, "Sender is None" + if isinstance(tx, NetworkWrappedTransaction): + txs.append(tx.tx) + else: + txs.append(tx) + max_tx_gas_limit = Transaction.calculate_max_gas_limit( + txs=txs, + env_gas_limit=int(env.gas_limit), + transaction_gas_limit_cap=fork.transaction_gas_limit_cap(), + state_gas_reservoir_enabled=fork.state_gas_reservoir_enabled(), + ) + for tx in txs: + tx.set_gas_limit( + max_gas_limit=max_tx_gas_limit, + transaction_gas_limit_cap=fork.transaction_gas_limit_cap(), + state_gas_reservoir_enabled=fork.state_gas_reservoir_enabled(), + ) tx.set_gas_price( gas_price=gas_price, max_fee_per_gas=max_fee_per_gas, max_priority_fee_per_gas=max_priority_fee_per_gas, max_fee_per_blob_gas=max_fee_per_blob_gas, ) + + def get_required_sender_balances( + self, *, fork: Fork + ) -> Dict[Address, int]: + """Get the required sender balances.""" + balances: Dict[Address, int] = {} + for tx in self.txs: + sender = tx.sender + assert sender is not None, "Sender is None" if sender not in balances: balances[sender] = 0 balances[sender] += tx.signer_minimum_balance(fork=fork) diff --git a/packages/testing/src/execution_testing/execution/transaction_post.py b/packages/testing/src/execution_testing/execution/transaction_post.py index d034e6e523a..fc5efce3c99 100644 --- a/packages/testing/src/execution_testing/execution/transaction_post.py +++ b/packages/testing/src/execution_testing/execution/transaction_post.py @@ -14,6 +14,7 @@ SendTransactionExceptionError, ) from execution_testing.test_types import ( + Environment, NetworkWrappedTransaction, TestPhase, Transaction, @@ -39,27 +40,46 @@ class TransactionPost(BaseExecute): "are included" ) - def get_required_sender_balances( + def prepare_transactions( self, *, + env: Environment, gas_price: int, max_fee_per_gas: int, max_priority_fee_per_gas: int, max_fee_per_blob_gas: int, fork: Fork, - ) -> Dict[Address, int]: - """Get the required sender balances.""" - balances: Dict[Address, int] = {} + ) -> None: + """Prepare transactions by setting their final gas properties.""" for block in self.blocks: + max_tx_gas_limit = Transaction.calculate_max_gas_limit( + txs=block, + env_gas_limit=int(env.gas_limit), + transaction_gas_limit_cap=fork.transaction_gas_limit_cap(), + state_gas_reservoir_enabled=fork.state_gas_reservoir_enabled(), + ) for tx in block: - sender = tx.sender - assert sender is not None, "Sender is None" + tx.set_gas_limit( + max_gas_limit=max_tx_gas_limit, + transaction_gas_limit_cap=fork.transaction_gas_limit_cap(), + state_gas_reservoir_enabled=fork.state_gas_reservoir_enabled(), + ) tx.set_gas_price( gas_price=gas_price, max_fee_per_gas=max_fee_per_gas, max_priority_fee_per_gas=max_priority_fee_per_gas, max_fee_per_blob_gas=max_fee_per_blob_gas, ) + + def get_required_sender_balances( + self, *, fork: Fork + ) -> Dict[Address, int]: + """Get the required sender balances.""" + balances: Dict[Address, int] = {} + for block in self.blocks: + for tx in block: + sender = tx.sender + assert sender is not None, "Sender is None" if sender not in balances: balances[sender] = 0 balances[sender] += tx.signer_minimum_balance(fork=fork) diff --git a/packages/testing/src/execution_testing/fixtures/tests/test_base.py b/packages/testing/src/execution_testing/fixtures/tests/test_base.py index b14ccc7d2a2..a05dd2210d2 100644 --- a/packages/testing/src/execution_testing/fixtures/tests/test_base.py +++ b/packages/testing/src/execution_testing/fixtures/tests/test_base.py @@ -98,6 +98,7 @@ def test_json_dict() -> None: ), transactions=[ Transaction( + gas_limit=0x5208, max_fee_per_gas=7, ).with_signature_and_sender(), ], @@ -133,6 +134,7 @@ def test_json_dict() -> None: ), transactions=[ Transaction( + gas_limit=0x5208, max_fee_per_gas=7, ).with_signature_and_sender(), ], diff --git a/packages/testing/src/execution_testing/fixtures/tests/test_blockchain.py b/packages/testing/src/execution_testing/fixtures/tests/test_blockchain.py index 445222f0369..827a430751a 100644 --- a/packages/testing/src/execution_testing/fixtures/tests/test_blockchain.py +++ b/packages/testing/src/execution_testing/fixtures/tests/test_blockchain.py @@ -78,7 +78,7 @@ pytest.param( True, FixtureTransaction.from_transaction( - Transaction().with_signature_and_sender() + Transaction(gas_limit=0x5208).with_signature_and_sender() ), { "type": "0x00", @@ -99,7 +99,10 @@ pytest.param( True, FixtureTransaction.from_transaction( - Transaction(to=None).with_signature_and_sender() + Transaction( + to=None, + gas_limit=0x5208, + ).with_signature_and_sender() ), { "type": "0x00", @@ -120,7 +123,7 @@ pytest.param( True, FixtureTransaction.from_transaction( - Transaction(ty=1).with_signature_and_sender() + Transaction(ty=1, gas_limit=0x5208).with_signature_and_sender() ), { "type": "0x01", @@ -143,7 +146,7 @@ True, FixtureTransaction.from_transaction( Transaction( - ty=2, max_fee_per_gas=7 + ty=2, max_fee_per_gas=7, gas_limit=0x5208 ).with_signature_and_sender() ), { @@ -172,6 +175,7 @@ max_fee_per_gas=7, max_fee_per_blob_gas=1, blob_versioned_hashes=[], + gas_limit=0x5208, ).with_signature_and_sender() ), { @@ -208,6 +212,7 @@ signer=EOA(key=TestPrivateKey), ) ], + gas_limit=0x5208, ).with_signature_and_sender() ), { @@ -256,6 +261,7 @@ max_fee_per_gas=20, max_fee_per_blob_gas=30, blob_versioned_hashes=[0, 1], + gas_limit=0x5208, ).with_signature_and_sender() ), { @@ -401,7 +407,9 @@ ), txs=[ FixtureTransaction.from_transaction( - Transaction().with_signature_and_sender() + Transaction( + gas_limit=0x5208 + ).with_signature_and_sender() ) ], ), @@ -475,7 +483,10 @@ ), txs=[ FixtureTransaction.from_transaction( - Transaction(to=None).with_signature_and_sender() + Transaction( + to=None, + gas_limit=0x5208, + ).with_signature_and_sender() ) ], ), @@ -615,6 +626,7 @@ max_fee_per_gas=20, max_fee_per_blob_gas=30, blob_versioned_hashes=[0, 1], + gas_limit=0x5208, ).with_signature_and_sender(), ], withdrawals=[ @@ -642,6 +654,7 @@ "transactions": [ Transaction( to=0x1234, + gas_limit=0x5208, data=b"\x01\x00", access_list=[ AccessList( @@ -701,6 +714,7 @@ transactions=[ Transaction( to=0x1234, + gas_limit=0x5208, data=b"\x01\x00", access_list=[ AccessList( @@ -767,6 +781,7 @@ "transactions": [ Transaction( to=0x1234, + gas_limit=0x5208, data=b"\x01\x00", access_list=[ AccessList( @@ -861,6 +876,7 @@ transactions=[ Transaction( to=0x1234, + gas_limit=0x5208, data=b"\x01\x00", access_list=[ AccessList( @@ -926,6 +942,7 @@ "transactions": [ Transaction( to=0x1234, + gas_limit=0x5208, data=b"\x01\x00", access_list=[ AccessList( @@ -1209,6 +1226,7 @@ def test_json_deserialization( transactions=[ Transaction( to=0x1234, + gas_limit=0x5208, data=b"\x01\x00", access_list=[ AccessList( @@ -1253,6 +1271,7 @@ def test_json_deserialization( "transactions": [ Transaction( to=0x1234, + gas_limit=0x5208, data=b"\x01\x00", access_list=[ AccessList( @@ -1312,6 +1331,7 @@ def test_json_deserialization( transactions=[ Transaction( to=0x1234, + gas_limit=0x5208, data=b"\x01\x00", access_list=[ AccessList( @@ -1358,6 +1378,7 @@ def test_json_deserialization( "transactions": [ Transaction( to=0x1234, + gas_limit=0x5208, data=b"\x01\x00", access_list=[ AccessList( @@ -1419,6 +1440,7 @@ def test_json_deserialization( transactions=[ Transaction( to=0x1234, + gas_limit=0x5208, data=b"\x01\x00", access_list=[ AccessList( @@ -1484,6 +1506,7 @@ def test_json_deserialization( "transactions": [ Transaction( to=0x1234, + gas_limit=0x5208, data=b"\x01\x00", access_list=[ AccessList( diff --git a/packages/testing/src/execution_testing/forks/base_fork.py b/packages/testing/src/execution_testing/forks/base_fork.py index 0182fbd6252..ff4cd70e7f5 100644 --- a/packages/testing/src/execution_testing/forks/base_fork.py +++ b/packages/testing/src/execution_testing/forks/base_fork.py @@ -842,6 +842,14 @@ def transaction_gas_limit_cap(cls) -> int | None: """ pass + @classmethod + @abstractmethod + def state_gas_reservoir_enabled(cls) -> bool: + """ + Return True if the fork enables a state gas reservoir. + """ + pass + @classmethod @abstractmethod def code_deposit_state_gas(cls, *, code_size: int) -> int: diff --git a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8037.py b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8037.py index bfc33e6c342..f1c2a4a4bda 100644 --- a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8037.py +++ b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8037.py @@ -39,6 +39,13 @@ def cost_per_state_byte(cls) -> int: """ return 1530 + @classmethod + def state_gas_reservoir_enabled(cls) -> bool: + """ + State gas reservoir becomes enabled. + """ + return True + @classmethod def system_call_gas_limit(cls) -> int: """ diff --git a/packages/testing/src/execution_testing/forks/forks/eips/osaka/eip_7883.py b/packages/testing/src/execution_testing/forks/forks/eips/osaka/eip_7883.py new file mode 100644 index 00000000000..da89b84e1e7 --- /dev/null +++ b/packages/testing/src/execution_testing/forks/forks/eips/osaka/eip_7883.py @@ -0,0 +1,15 @@ +""" +EIP-7883: ModExp Gas Cost Increase. + +Increases cost of ModExp precompile. + +https://eips.ethereum.org/EIPS/eip-7883 +""" + +from ....base_fork import BaseFork + + +class EIP7883(BaseFork): + """EIP-7883 class.""" + + pass diff --git a/packages/testing/src/execution_testing/forks/forks/forks.py b/packages/testing/src/execution_testing/forks/forks/forks.py index acaa4403176..cd2777c88a0 100644 --- a/packages/testing/src/execution_testing/forks/forks/forks.py +++ b/packages/testing/src/execution_testing/forks/forks/forks.py @@ -1025,6 +1025,13 @@ def transaction_gas_limit_cap(cls) -> int | None: """At Genesis, no transaction gas limit cap is imposed.""" return None + @classmethod + def state_gas_reservoir_enabled(cls) -> bool: + """ + At Genesis, state gas reservoir is not enabled. + """ + return False + @classmethod def code_deposit_state_gas(cls, *, code_size: int) -> int: """Return the state gas for code deposit of the given size.""" @@ -1524,6 +1531,7 @@ class Osaka( eips.EIP7918, eips.EIP7594, eips.EIP7951, + eips.EIP7883, Prague, solc_name="cancun", ): diff --git a/packages/testing/src/execution_testing/specs/blockchain.py b/packages/testing/src/execution_testing/specs/blockchain.py index 343c224dc1e..764217845bc 100644 --- a/packages/testing/src/execution_testing/specs/blockchain.py +++ b/packages/testing/src/execution_testing/specs/blockchain.py @@ -823,7 +823,28 @@ def generate_block_data( block_number=env.number, timestamp=env.timestamp ) env = env.set_fork_requirements(fork) - txs = [tx.with_signature_and_sender() for tx in block.txs] + txs = block.txs[:] + if any("gas_limit" not in tx.model_fields_set for tx in block.txs): + max_tx_gas_limit = Transaction.calculate_max_gas_limit( + txs=txs, + env_gas_limit=int(env.gas_limit), + transaction_gas_limit_cap=fork.transaction_gas_limit_cap(), + state_gas_reservoir_enabled=fork.state_gas_reservoir_enabled(), + ) + if max_tx_gas_limit == 0: + raise Exception( + "test correctness: unable to automatically calculate gas " + "limit for transactions (No remaining gas)." + ) + txs = [ + tx.with_gas_limit( + max_gas_limit=max_tx_gas_limit, + transaction_gas_limit_cap=fork.transaction_gas_limit_cap(), + state_gas_reservoir_enabled=fork.state_gas_reservoir_enabled(), + ) + for tx in txs + ] + txs = [tx.with_signature_and_sender() for tx in txs] if failing_tx_count := len([tx for tx in txs if tx.error]) > 0: if failing_tx_count > 1: @@ -1381,13 +1402,17 @@ def make_stateful_fixture( f"max_priority_fee_per_gas={max_priority_fee_per_gas}, " f"max_fee_per_blob_gas={max_fee_per_blob_gas}." ) - required_balances = execute_plan.get_required_sender_balances( + execute_plan.prepare_transactions( + env=Environment(gas_limit=HexNumber(start_block["gasLimit"])), gas_price=gas_price, max_fee_per_gas=max_fee_per_gas, max_priority_fee_per_gas=max_priority_fee_per_gas, max_fee_per_blob_gas=max_fee_per_blob_gas, fork=session_fork, ) + required_balances = execute_plan.get_required_sender_balances( + fork=session_fork, + ) resolve_deferred() min_balance( required_balances, diff --git a/packages/testing/src/execution_testing/specs/state.py b/packages/testing/src/execution_testing/specs/state.py index b0c456a3590..4fe88209fcf 100644 --- a/packages/testing/src/execution_testing/specs/state.py +++ b/packages/testing/src/execution_testing/specs/state.py @@ -356,7 +356,11 @@ def make_state_test_fixture( ) env = self.env.set_fork_requirements(fork) - tx = self.tx.with_signature_and_sender(keep_secret_key=True) + tx = self.tx.with_gas_limit( + max_gas_limit=env.gas_limit, + transaction_gas_limit_cap=fork.transaction_gas_limit_cap(), + state_gas_reservoir_enabled=fork.state_gas_reservoir_enabled(), + ).with_signature_and_sender(keep_secret_key=True) pre_alloc = Alloc.merge( Alloc.model_validate(fork.pre_allocation()), self.pre, @@ -415,18 +419,20 @@ def make_state_test_fixture( # First try reducing the gas limit only by one, if the validation # fails, it means that the traces change even with the slightest # modification to the gas. + tx_gas_limit = int(tx.gas_limit) + if self.verify_modified_gas_limit( t8n=t8n, base_tool_result=base_tool_result, base_tool_alloc=base_tool_alloc, fork=fork, - current_gas_limit=self.tx.gas_limit - 1, + current_gas_limit=tx_gas_limit - 1, pre_alloc=pre_alloc, env=env, ignore_gas_differences=ignore_gas_differences, ): minimum_gas_limit = 0 - maximum_gas_limit = int(self.tx.gas_limit) + maximum_gas_limit = tx_gas_limit while minimum_gas_limit < maximum_gas_limit: current_gas_limit = ( maximum_gas_limit + minimum_gas_limit diff --git a/packages/testing/src/execution_testing/specs/tests/test_benchmark.py b/packages/testing/src/execution_testing/specs/tests/test_benchmark.py index 3f859417d05..d2720a840b0 100644 --- a/packages/testing/src/execution_testing/specs/tests/test_benchmark.py +++ b/packages/testing/src/execution_testing/specs/tests/test_benchmark.py @@ -55,41 +55,40 @@ def test_split_transaction( f"{gas_benchmark_value_millions}M gas, got {len(split_txs)}" ) + total_gas = 0 + for i, tx in enumerate(split_txs): + tx_gas_limit = tx.gas_limit + assert tx_gas_limit is not None, f"Unexpected `None` gas_limit: {tx}" + total_gas += tx_gas_limit + # Verify no tx exceeds the cap + assert tx_gas_limit <= gas_limit_cap, ( + f"Transaction {i} gas limit {tx_gas_limit} " + f"exceeds cap {gas_limit_cap}" + ) + # Verify gas distribution + if i < len(split_txs) - 1: # All but last should be at cap + assert tx_gas_limit == gas_limit_cap, ( + f"Transaction {i} should have gas limit {gas_limit_cap}, " + f"got {tx_gas_limit}" + ) + else: + # Last transaction should have the remainder + if expected_splits > 1: + expected_last_gas = gas_benchmark_value - ( + gas_limit_cap * (expected_splits - 1) + ) + assert tx_gas_limit == expected_last_gas, ( + f"Last transaction should have {expected_last_gas} gas, " + f"got {tx_gas_limit}" + ) + # Verify nonces increment correctly + assert tx.nonce == i, f"Transaction {i} has incorrect nonce {tx.nonce}" # Verify total gas equals the benchmark value - total_gas = sum(tx.gas_limit for tx in split_txs) assert total_gas == gas_benchmark_value, ( f"Total gas {total_gas} doesn't match benchmark " f"value {gas_benchmark_value}" ) - # Verify no transaction exceeds the cap - for i, tx in enumerate(split_txs): - assert tx.gas_limit <= gas_limit_cap, ( - f"Transaction {i} gas limit {tx.gas_limit} " - f"exceeds cap {gas_limit_cap}" - ) - - # Verify nonces increment correctly - for i, tx in enumerate(split_txs): - assert tx.nonce == i, f"Transaction {i} has incorrect nonce {tx.nonce}" - - # Verify gas distribution - for i, tx in enumerate(split_txs[:-1]): # All but last should be at cap - assert tx.gas_limit == gas_limit_cap, ( - f"Transaction {i} should have gas limit {gas_limit_cap}, " - f"got {tx.gas_limit}" - ) - - # Last transaction should have the remainder - if expected_splits > 1: - expected_last_gas = gas_benchmark_value - ( - gas_limit_cap * (expected_splits - 1) - ) - assert split_txs[-1].gas_limit == expected_last_gas, ( - f"Last transaction should have {expected_last_gas} gas, " - f"got {split_txs[-1].gas_limit}" - ) - @pytest.mark.parametrize( "gas_benchmark_value,gas_limit_cap", @@ -132,6 +131,10 @@ def test_split_transaction_edge_cases( # When cap > benchmark, gas_limit should be # min of tx.gas_limit and benchmark assert benchmark_test.tx is not None, "Transaction should not be None" + benchmark_test_tx_gas_limit = benchmark_test.tx.gas_limit + assert benchmark_test_tx_gas_limit is not None, ( + "Transaction gas limit should not be None" + ) assert split_txs[0].gas_limit == min( - benchmark_test.tx.gas_limit, gas_benchmark_value + benchmark_test_tx_gas_limit, gas_benchmark_value ) diff --git a/packages/testing/src/execution_testing/specs/tests/test_transaction.py b/packages/testing/src/execution_testing/specs/tests/test_transaction.py index abc58a3421a..95df5252d04 100644 --- a/packages/testing/src/execution_testing/specs/tests/test_transaction.py +++ b/packages/testing/src/execution_testing/specs/tests/test_transaction.py @@ -20,7 +20,7 @@ @pytest.mark.parametrize( "name, tx, fork", [ - pytest.param("simple_type_0", Transaction(), Shanghai), + pytest.param("simple_type_0", Transaction(gas_limit=0x5208), Shanghai), ], ) def test_transaction_test_filling( diff --git a/packages/testing/src/execution_testing/test_types/tests/test_implicit_gas_limit.py b/packages/testing/src/execution_testing/test_types/tests/test_implicit_gas_limit.py new file mode 100644 index 00000000000..979212f70de --- /dev/null +++ b/packages/testing/src/execution_testing/test_types/tests/test_implicit_gas_limit.py @@ -0,0 +1,291 @@ +""" +Test suite for implicit transaction gas-limit resolution. + +Covers `Transaction.set_gas_limit` and +`Transaction.calculate_max_gas_limit`: the even split of remaining +environment gas, gas limit cap clamping, the state gas reservoir +(EIP-8037) semantics, and the test correctness errors raised on +contradictory test definitions. +""" + +from typing import List + +import pytest + +from execution_testing.forks import Amsterdam, Fork, Osaka, Prague + +from ..transaction_types import Transaction + +_osaka_cap = Osaka.transaction_gas_limit_cap() +assert _osaka_cap is not None +OSAKA_CAP: int = _osaka_cap +_amsterdam_cap = Amsterdam.transaction_gas_limit_cap() +assert _amsterdam_cap is not None +AMSTERDAM_CAP: int = _amsterdam_cap + +assert Prague.transaction_gas_limit_cap() is None +assert not Prague.state_gas_reservoir_enabled() +assert not Osaka.state_gas_reservoir_enabled() +assert Amsterdam.state_gas_reservoir_enabled() + + +def calculate_max_transaction_gas_limit( + txs: List[Transaction], + *, + env_gas_limit: int, + fork: Fork, +) -> int: + """Split the environment gas across `txs` for the given fork.""" + return Transaction.calculate_max_gas_limit( + txs=txs, + env_gas_limit=env_gas_limit, + transaction_gas_limit_cap=fork.transaction_gas_limit_cap(), + state_gas_reservoir_enabled=fork.state_gas_reservoir_enabled(), + ) + + +class TestSetGasLimit: + """Test `Transaction.set_gas_limit` resolution of unset limits.""" + + def test_unset_no_cap(self) -> None: + """An unset gas limit resolves to the maximum, uncapped.""" + tx = Transaction() + tx.set_gas_limit(max_gas_limit=100, transaction_gas_limit_cap=None) + assert tx.gas_limit == 100 + + def test_unset_clamped_to_cap(self) -> None: + """An unset gas limit is clamped to the gas limit cap.""" + tx = Transaction() + tx.set_gas_limit(max_gas_limit=100, transaction_gas_limit_cap=60) + assert tx.gas_limit == 60 + + def test_unset_cap_above_max(self) -> None: + """A cap above the maximum does not raise the gas limit.""" + tx = Transaction() + tx.set_gas_limit(max_gas_limit=100, transaction_gas_limit_cap=200) + assert tx.gas_limit == 100 + + def test_explicit_gas_limit_untouched(self) -> None: + """An explicit gas limit is never modified.""" + tx = Transaction(gas_limit=21_000) + tx.set_gas_limit(max_gas_limit=100, transaction_gas_limit_cap=60) + assert tx.gas_limit == 21_000 + + def test_explicit_none_treated_as_unset(self) -> None: + """An explicit `gas_limit=None` is treated as unset.""" + tx = Transaction(gas_limit=None) + tx.set_gas_limit(max_gas_limit=100, transaction_gas_limit_cap=None) + assert tx.gas_limit == 100 + + def test_resolution_is_sticky(self) -> None: + """A second call does not overwrite the resolved gas limit.""" + tx = Transaction() + tx.set_gas_limit(max_gas_limit=100, transaction_gas_limit_cap=None) + tx.set_gas_limit(max_gas_limit=50, transaction_gas_limit_cap=None) + assert tx.gas_limit == 100 + + def test_signing_requires_gas_limit(self) -> None: + """Signing a transaction with an unset gas limit raises.""" + with pytest.raises(ValueError, match="gas_limit must be set"): + Transaction().with_signature_and_sender() + + +class TestSetGasLimitStateGasReservoir: + """Test the state gas reservoir (EIP-8037) gas-limit semantics.""" + + def test_reservoir_unset_keeps_full_maximum(self) -> None: + """With the reservoir unset, the cap does not clamp the limit.""" + tx = Transaction() + tx.set_gas_limit( + max_gas_limit=100, + transaction_gas_limit_cap=60, + state_gas_reservoir_enabled=True, + ) + assert tx.gas_limit == 100 + + def test_reservoir_zero_pins_to_cap(self) -> None: + """An explicit zero reservoir pins the limit to exactly the cap.""" + tx = Transaction(state_gas_reservoir=0) + tx.set_gas_limit( + max_gas_limit=100, + transaction_gas_limit_cap=60, + state_gas_reservoir_enabled=True, + ) + assert tx.gas_limit == 60 + + def test_reservoir_pins_to_cap_plus_reservoir(self) -> None: + """A positive reservoir pins the limit to cap plus reservoir.""" + tx = Transaction(state_gas_reservoir=40) + tx.set_gas_limit( + max_gas_limit=200, + transaction_gas_limit_cap=60, + state_gas_reservoir_enabled=True, + ) + assert tx.gas_limit == 100 + + def test_reservoir_ignored_with_explicit_gas_limit(self) -> None: + """A reservoir is ignored when the gas limit is explicit.""" + tx = Transaction(gas_limit=21_000, state_gas_reservoir=40) + tx.set_gas_limit( + max_gas_limit=200, + transaction_gas_limit_cap=60, + state_gas_reservoir_enabled=True, + ) + assert tx.gas_limit == 21_000 + + def test_reservoir_exceeding_available_gas_raises(self) -> None: + """A reservoir that does not fit the available gas raises.""" + tx = Transaction(state_gas_reservoir=50) + with pytest.raises( + Exception, match="test correctness: the requested state" + ): + tx.set_gas_limit( + max_gas_limit=100, + transaction_gas_limit_cap=60, + state_gas_reservoir_enabled=True, + ) + + @pytest.mark.parametrize( + "gas_limit", + [ + pytest.param(None, id="implicit_gas_limit"), + pytest.param(21_000, id="explicit_gas_limit"), + ], + ) + def test_reservoir_on_unsupported_fork_raises( + self, gas_limit: int | None + ) -> None: + """A positive reservoir raises if the fork has no reservoir.""" + tx = Transaction(gas_limit=gas_limit, state_gas_reservoir=1) + with pytest.raises( + Exception, match="test correctness: transaction requests" + ): + tx.set_gas_limit( + max_gas_limit=100, + transaction_gas_limit_cap=60, + state_gas_reservoir_enabled=False, + ) + + def test_reservoir_zero_on_unsupported_fork_clamps_to_cap(self) -> None: + """An explicit zero reservoir is valid on forks without one.""" + tx = Transaction(state_gas_reservoir=0) + tx.set_gas_limit( + max_gas_limit=100, + transaction_gas_limit_cap=60, + state_gas_reservoir_enabled=False, + ) + assert tx.gas_limit == 60 + + def test_reservoir_without_cap_is_internal_invariant(self) -> None: + """A reservoir request without a cap violates an invariant.""" + tx = Transaction(state_gas_reservoir=1) + with pytest.raises(AssertionError, match="must also define a cap"): + tx.set_gas_limit( + max_gas_limit=100, + transaction_gas_limit_cap=None, + state_gas_reservoir_enabled=True, + ) + + +class TestCalculateMaxTransactionGasLimit: + """Test the even split of environment gas across transactions.""" + + def test_no_implicit_transactions(self) -> None: + """Return 0 when all transactions have explicit gas limits.""" + txs = [Transaction(gas_limit=200_000)] + assert ( + calculate_max_transaction_gas_limit( + txs, env_gas_limit=100_000, fork=Prague + ) + == 0 + ) + + def test_empty_transaction_list(self) -> None: + """Return 0 for an empty transaction list.""" + assert ( + calculate_max_transaction_gas_limit( + [], env_gas_limit=100_000, fork=Prague + ) + == 0 + ) + + def test_single_implicit_transaction(self) -> None: + """A single implicit transaction gets the full environment gas.""" + txs = [Transaction()] + assert ( + calculate_max_transaction_gas_limit( + txs, env_gas_limit=100_000, fork=Prague + ) + == 100_000 + ) + + def test_explicit_limits_reduce_available_gas(self) -> None: + """Explicit gas limits are deducted from the environment gas.""" + txs = [Transaction(gas_limit=40_000), Transaction()] + assert ( + calculate_max_transaction_gas_limit( + txs, env_gas_limit=100_000, fork=Prague + ) + == 60_000 + ) + + def test_even_split_across_implicit_transactions(self) -> None: + """Remaining gas is split evenly across implicit transactions.""" + txs = [Transaction(gas_limit=10_000), Transaction(), Transaction()] + assert ( + calculate_max_transaction_gas_limit( + txs, env_gas_limit=100_000, fork=Prague + ) + == 45_000 + ) + + def test_split_clamped_to_cap(self) -> None: + """The per-transaction share is clamped to the fork's cap.""" + env_gas_limit = 100_000_000 + assert env_gas_limit > OSAKA_CAP + txs = [Transaction()] + assert ( + calculate_max_transaction_gas_limit( + txs, env_gas_limit=env_gas_limit, fork=Osaka + ) + == OSAKA_CAP + ) + + def test_state_gas_reservoir_fork_removes_cap(self) -> None: + """A fork with the state gas reservoir does not clamp the share.""" + env_gas_limit = 100_000_000 + assert env_gas_limit > AMSTERDAM_CAP + txs = [Transaction()] + assert ( + calculate_max_transaction_gas_limit( + txs, env_gas_limit=env_gas_limit, fork=Amsterdam + ) + == env_gas_limit + ) + + @pytest.mark.parametrize( + "explicit_gas_limit", + [ + pytest.param(100_000, id="exactly_consumed"), + pytest.param(150_000, id="over_consumed"), + ], + ) + def test_no_remaining_gas_raises(self, explicit_gas_limit: int) -> None: + """Raise when explicit limits leave implicit transactions no gas.""" + txs = [Transaction(gas_limit=explicit_gas_limit), Transaction()] + with pytest.raises( + Exception, match="test correctness: unable to automatically" + ): + calculate_max_transaction_gas_limit( + txs, env_gas_limit=100_000, fork=Prague + ) + + def test_no_remaining_gas_all_explicit_does_not_raise(self) -> None: + """Over-consumption without implicit transactions returns 0.""" + txs = [Transaction(gas_limit=150_000)] + assert ( + calculate_max_transaction_gas_limit( + txs, env_gas_limit=100_000, fork=Prague + ) + == 0 + ) diff --git a/packages/testing/src/execution_testing/test_types/tests/test_transactions.py b/packages/testing/src/execution_testing/test_types/tests/test_transactions.py index 156b6cea65c..2d0a9912736 100644 --- a/packages/testing/src/execution_testing/test_types/tests/test_transactions.py +++ b/packages/testing/src/execution_testing/test_types/tests/test_transactions.py @@ -20,6 +20,7 @@ ( Transaction( ty=0, + gas_limit=21000, nonce=0, gas_price=1000000000, protected=False, @@ -37,6 +38,7 @@ ( Transaction( ty=0, + gas_limit=21000, nonce=0, gas_price=1000000000, protected=False, @@ -55,6 +57,7 @@ ( Transaction( ty=0, + gas_limit=21000, nonce=0, gas_price=1000000000, protected=True, @@ -72,6 +75,7 @@ ( Transaction( ty=1, + gas_limit=21000, nonce=0, gas_price=1000000000, ), @@ -88,6 +92,7 @@ ( Transaction( ty=1, + gas_limit=21000, nonce=0, gas_price=1000000000, access_list=[], @@ -105,6 +110,7 @@ ( Transaction( ty=1, + gas_limit=21000, nonce=0, gas_price=1000000000, access_list=[ @@ -129,6 +135,7 @@ ( Transaction( ty=1, + gas_limit=21000, nonce=0, gas_price=1000000000, to=None, @@ -151,6 +158,7 @@ ( Transaction( ty=2, + gas_limit=21000, nonce=0, access_list=[ AccessList(address=0x123, storage_keys=[0x456, 0x789]) @@ -173,6 +181,7 @@ ( Transaction( ty=2, + gas_limit=21000, nonce=0, to=None, access_list=[ @@ -196,6 +205,7 @@ ( Transaction( ty=3, + gas_limit=21000, nonce=0, access_list=[ AccessList(address=0x123, storage_keys=[0x456, 0x789]) @@ -220,6 +230,7 @@ ( Transaction( ty=3, + gas_limit=21000, nonce=0, access_list=[ AccessList(address=0x123, storage_keys=[0x456, 0x789]) @@ -273,3 +284,18 @@ def test_transaction_signing( assert tx.sender is not None assert tx.sender.hex() == expected_sender assert (tx.rlp().hex()) == expected_serialized + + +def test_gas_limit_none_is_unset() -> None: + """Test that `gas_limit=None` behaves exactly like omitting the field.""" + tx = Transaction(gas_limit=None) + assert "gas_limit" not in tx.model_fields_set + assert tx.gas_limit == 21_000 + + +@pytest.mark.parametrize("alias", ["gas_limit", "gasLimit", "gas"]) +def test_gas_limit_none_alias_is_unset(alias: str) -> None: + """Test that a `None` gas limit via any alias counts as unset.""" + tx = Transaction.model_validate({alias: None}) + assert "gas_limit" not in tx.model_fields_set + assert tx.gas_limit == 21_000 diff --git a/packages/testing/src/execution_testing/test_types/tests/test_types.py b/packages/testing/src/execution_testing/test_types/tests/test_types.py index 4770b941a61..3abe5dce247 100644 --- a/packages/testing/src/execution_testing/test_types/tests/test_types.py +++ b/packages/testing/src/execution_testing/test_types/tests/test_types.py @@ -573,7 +573,7 @@ def test_account_merge( ), pytest.param( True, - Transaction().with_signature_and_sender(), + Transaction(gas_limit=0x5208).with_signature_and_sender(), { "type": "0x0", "chainId": "0x1", @@ -594,6 +594,7 @@ def test_account_merge( True, Transaction( to=None, + gas_limit=0x5208, ).with_signature_and_sender(), { "type": "0x0", @@ -615,6 +616,7 @@ def test_account_merge( True, Transaction( to="", + gas_limit=0x5208, ).with_signature_and_sender(), { "type": "0x0", @@ -636,6 +638,7 @@ def test_account_merge( True, Transaction( to=0x1234, + gas_limit=0x5208, data=b"\x01\x00", access_list=[ AccessList( @@ -952,7 +955,7 @@ def test_model_copy(model: CopyValidateModel) -> None: "value, expected", [ pytest.param( - Transaction().with_signature_and_sender(), + Transaction(gas_limit=0x5208).with_signature_and_sender(), Bytes( "0xf85f800a8252089400000000000000000000000000000000000000aa808026a0cc61d852649c34" "cc0b71803115f38036ace257d2914f087bf885e6806a664fbda02020cb35f5d7731ab540d6261450" @@ -962,6 +965,7 @@ def test_model_copy(model: CopyValidateModel) -> None: ), pytest.param( Transaction( + gas_limit=0x5208, access_list=[AccessList(address=0, storage_keys=[0, 1])], ).with_signature_and_sender(), Bytes( @@ -976,6 +980,7 @@ def test_model_copy(model: CopyValidateModel) -> None: pytest.param( Transaction( access_list=[AccessList(address=0, storage_keys=[0, 1])], + gas_limit=0x5208, max_fee_per_gas=10, max_priority_fee_per_gas=5, ).with_signature_and_sender(), @@ -991,6 +996,7 @@ def test_model_copy(model: CopyValidateModel) -> None: pytest.param( Transaction( access_list=[AccessList(address=1, storage_keys=[2, 3])], + gas_limit=0x5208, max_fee_per_gas=10, max_priority_fee_per_gas=5, max_fee_per_blob_gas=20, @@ -1010,6 +1016,7 @@ def test_model_copy(model: CopyValidateModel) -> None: pytest.param( Transaction( access_list=[AccessList(address=0, storage_keys=[0, 1])], + gas_limit=0x5208, max_fee_per_gas=10, max_priority_fee_per_gas=5, authorization_list=[ @@ -1034,6 +1041,7 @@ def test_model_copy(model: CopyValidateModel) -> None: pytest.param( Transaction( access_list=[AccessList(address=0, storage_keys=[0, 1])], + gas_limit=0x5208, max_fee_per_gas=10, max_priority_fee_per_gas=5, authorization_list=[ diff --git a/packages/testing/src/execution_testing/test_types/transaction_types.py b/packages/testing/src/execution_testing/test_types/transaction_types.py index bc3d0794f3f..4b808e08b16 100644 --- a/packages/testing/src/execution_testing/test_types/transaction_types.py +++ b/packages/testing/src/execution_testing/test_types/transaction_types.py @@ -310,6 +310,16 @@ def strip_hash_from_t8n_output(cls, data: Any) -> Any: data.pop("hash", None) return data + @model_validator(mode="before") + @classmethod + def treat_none_gas_limit_as_unset(cls, data: Any) -> Any: + """Treat a `None` gas limit (any alias) as unset.""" + if isinstance(data, dict): + for alias in ("gas_limit", "gasLimit", "gas"): + if alias in data and data[alias] is None: + del data[alias] + return data + gas_limit: HexNumber = Field( HexNumber(21_000), serialization_alias="gas", @@ -331,6 +341,21 @@ def strip_hash_from_t8n_output(cls, data: Any) -> Any: expected_receipt: TransactionReceipt | None = Field(None, exclude=True) + state_gas_reservoir: int = Field( + 0, + exclude=True, + description=( + "Extra gas on top of the transaction gas limit cap, reserved " + "for state gas (EIP-8037). Only takes effect when `gas_limit` " + "is unset and the fork enables the state gas reservoir: " + "leaving it unset keeps the full implicit gas limit, an " + "explicit 0 pins the gas limit to exactly the cap (no " + "reservoir), and a positive value pins it to the cap plus the " + "requested reservoir. Requesting a positive reservoir on a " + "fork without the state gas reservoir raises an error." + ), + ) + zero: ClassVar[Literal[0]] = 0 metadata: TransactionTestMetadata | None = Field(None, exclude=True) @@ -571,6 +596,97 @@ def sign(self: "Transaction") -> None: # Signer remains `None` in this case pass + def _calculate_implicit_gas_limit( + self, + *, + max_gas_limit: int, + transaction_gas_limit_cap: int | None, + state_gas_reservoir_enabled: bool = False, + ) -> HexNumber: + """ + Calculate the gas limit given the current external factors. + + The implicit gas limit defaults to `max_gas_limit`, clamped to + the fork's transaction gas limit cap if there is one. On forks + with the state gas reservoir enabled (EIP-8037), + `state_gas_reservoir` refines this: unset keeps the full + `max_gas_limit` (any excess above the cap acts as an implicit + reservoir), an explicit 0 pins the gas limit to exactly the + cap, and a positive value pins it to the cap plus the requested + reservoir. + """ + tx_gas_limit = max_gas_limit + if state_gas_reservoir_enabled: + if "state_gas_reservoir" in self.model_fields_set: + assert transaction_gas_limit_cap is not None, ( + "state_gas_reservoir_enabled is True but " + "transaction_gas_limit_cap is None; the state " + "gas reservoir is defined as gas above the cap " + "(EIP-8037 builds on EIP-7825), so a fork that " + "enables it must also define a cap" + ) + if self.state_gas_reservoir > 0: + minimum_gas_with_reservoir = ( + transaction_gas_limit_cap + self.state_gas_reservoir + ) + if tx_gas_limit < minimum_gas_with_reservoir: + raise Exception( + "test correctness: the requested state " + "gas reservoir of " + f"{self.state_gas_reservoir} requires a " + f"gas limit of {minimum_gas_with_reservoir} " + "(transaction gas limit cap of " + f"{transaction_gas_limit_cap} plus " + "reservoir), but only " + f"{tx_gas_limit} gas is available for " + "this transaction." + ) + tx_gas_limit = minimum_gas_with_reservoir + else: + if tx_gas_limit > transaction_gas_limit_cap: + tx_gas_limit = transaction_gas_limit_cap + else: + if ( + transaction_gas_limit_cap is not None + and tx_gas_limit > transaction_gas_limit_cap + ): + tx_gas_limit = transaction_gas_limit_cap + return HexNumber(tx_gas_limit) + + def _check_state_gas_reservoir_supported( + self, *, state_gas_reservoir_enabled: bool + ) -> None: + """Raise if a positive reservoir is requested but unsupported.""" + if not state_gas_reservoir_enabled and self.state_gas_reservoir > 0: + raise Exception( + "test correctness: transaction requests a state gas " + f"reservoir of {self.state_gas_reservoir} but the fork " + "does not enable the state gas reservoir; the request " + "would be silently ignored." + ) + + def with_gas_limit( + self, + *, + max_gas_limit: int, + transaction_gas_limit_cap: int | None, + state_gas_reservoir_enabled: bool = False, + ) -> Self: + """Return copy of the transaction with the set gas limit.""" + updated_values: Dict[str, Any] = {} + + self._check_state_gas_reservoir_supported( + state_gas_reservoir_enabled=state_gas_reservoir_enabled + ) + if "gas_limit" not in self.model_fields_set: + updated_values["gas_limit"] = self._calculate_implicit_gas_limit( + max_gas_limit=max_gas_limit, + transaction_gas_limit_cap=transaction_gas_limit_cap, + state_gas_reservoir_enabled=state_gas_reservoir_enabled, + ) + + return self.model_copy(update=updated_values) + def with_signature_and_sender( self, *, keep_secret_key: bool = False ) -> Self: @@ -599,6 +715,9 @@ def with_signature_and_sender( if self.secret_key is None: raise ValueError("secret_key must be set to sign a transaction") + if "gas_limit" not in self.model_fields_set: + raise ValueError("gas_limit must be set to sign a transaction") + # Get the signing bytes signing_hash = self.rlp_signing_bytes().keccak256() @@ -796,6 +915,45 @@ def list_blob_versioned_hashes( for blob_versioned_hash in tx.blob_versioned_hashes ] + @staticmethod + def calculate_max_gas_limit( + *, + txs: List["Transaction"], + env_gas_limit: int, + transaction_gas_limit_cap: int | None, + state_gas_reservoir_enabled: bool, + ) -> int: + """ + Calculate the maximum gas limit that can be set in a transaction + given a list of transactions with and without gas-limits set + and a maximum available environment gas. + """ + available_gas = env_gas_limit + unset_gas_limit_tx_count = 0 + for tx in txs: + if "gas_limit" not in tx.model_fields_set: + unset_gas_limit_tx_count += 1 + else: + available_gas -= int(tx.gas_limit) + + if unset_gas_limit_tx_count == 0: + return 0 + + if available_gas <= 0: + raise Exception( + "test correctness: unable to automatically calculate gas " + "limit for transactions (no remaining gas: explicit " + "transaction gas limits already consume the full " + f"environment gas limit of {env_gas_limit})." + ) + + max_tx_gas_limit = available_gas // unset_gas_limit_tx_count + if state_gas_reservoir_enabled: + transaction_gas_limit_cap = None + if transaction_gas_limit_cap: + max_tx_gas_limit = min(max_tx_gas_limit, transaction_gas_limit_cap) + return max_tx_gas_limit + @cached_property def created_contract(self) -> Address: """Return address of the contract created by the transaction.""" @@ -837,13 +995,33 @@ def set_gas_price( if "max_fee_per_blob_gas" not in self.model_fields_set: self.max_fee_per_blob_gas = HexNumber(max_fee_per_blob_gas) + def set_gas_limit( + self, + *, + max_gas_limit: int, + transaction_gas_limit_cap: int | None, + state_gas_reservoir_enabled: bool = False, + ) -> None: + """Set the transaction gas limit if unset.""" + self._check_state_gas_reservoir_supported( + state_gas_reservoir_enabled=state_gas_reservoir_enabled + ) + if "gas_limit" not in self.model_fields_set: + self.gas_limit = self._calculate_implicit_gas_limit( + max_gas_limit=max_gas_limit, + transaction_gas_limit_cap=transaction_gas_limit_cap, + state_gas_reservoir_enabled=state_gas_reservoir_enabled, + ) + def signer_minimum_balance(self, *, fork: Fork) -> int: """Return minimum balance of the signer.""" gas_price = self.gas_price or self.max_fee_per_gas assert gas_price is not None, ( "Impossible to calculate minimum balance without gas price" ) - gas_limit = self.gas_limit + assert "gas_limit" in self.model_fields_set, ( + "Impossible to calculate minimum balance without a set gas limit" + ) if self.ty == 3 and self.blob_versioned_hashes is not None: max_fee_per_blob_gas = self.max_fee_per_blob_gas assert max_fee_per_blob_gas is not None, ( @@ -851,13 +1029,13 @@ def signer_minimum_balance(self, *, fork: Fork) -> int: "max_fee_per_blob_gas" ) return ( - gas_price * gas_limit + gas_price * self.gas_limit + self.value + max_fee_per_blob_gas * (fork.blob_gas_per_blob() * len(self.blob_versioned_hashes)) ) else: - return gas_price * gas_limit + self.value + return gas_price * self.gas_limit + self.value def _format_field_value(self, value: Any) -> str: """ @@ -1032,6 +1210,20 @@ def set_gas_price( max_fee_per_blob_gas=max_fee_per_blob_gas, ) + def set_gas_limit( + self, + *, + max_gas_limit: int, + transaction_gas_limit_cap: int | None, + state_gas_reservoir_enabled: bool = False, + ) -> None: + """Set the transaction gas limit if unset.""" + self.tx.set_gas_limit( + max_gas_limit=max_gas_limit, + transaction_gas_limit_cap=transaction_gas_limit_cap, + state_gas_reservoir_enabled=state_gas_reservoir_enabled, + ) + def signer_minimum_balance(self, *, fork: Fork) -> int: """Return minimum balance of the signer.""" return self.tx.signer_minimum_balance(fork=fork) diff --git a/packages/testing/src/execution_testing/tools/utility/generators.py b/packages/testing/src/execution_testing/tools/utility/generators.py index 233c84bb19a..028a114a6c4 100644 --- a/packages/testing/src/execution_testing/tools/utility/generators.py +++ b/packages/testing/src/execution_testing/tools/utility/generators.py @@ -621,15 +621,6 @@ def gas_test( LEGACY_CALL_SUCCESS ) - gas_sstore = Op.SSTORE(1, 1).gas_cost(fork=fork) - if tx_gas is None: - tx_gas = ( - 5 * gas_single_gas_run - + cold_gas - + 4 * warm_gas - + 5 * gas_sstore - + 500_000 - ) tx = Transaction( to=address_legacy_harness, gas_limit=tx_gas, sender=sender ) diff --git a/tests/amsterdam/eip7708_eth_transfer_logs/test_burn_logs.py b/tests/amsterdam/eip7708_eth_transfer_logs/test_burn_logs.py index 1776fde3276..4485939b8d7 100644 --- a/tests/amsterdam/eip7708_eth_transfer_logs/test_burn_logs.py +++ b/tests/amsterdam/eip7708_eth_transfer_logs/test_burn_logs.py @@ -59,7 +59,6 @@ def test_selfdestruct_to_self_pre_existing_no_log( sender=sender, to=contract, value=0, - gas_limit=100_000, expected_receipt=TransactionReceipt(logs=[]), ) @@ -84,7 +83,6 @@ def test_selfdestruct_to_self_same_tx( state_test: StateTestFiller, env: Environment, pre: Alloc, - fork: Fork, sender: EOA, contract_balance: int, create_opcode: Op, @@ -127,9 +125,6 @@ def test_selfdestruct_to_self_same_tx( sender=sender, to=factory, value=contract_balance, - # Same-tx CREATE+SELFDESTRUCT charges NEW_ACCOUNT state gas - # under EIP-8037 (0 otherwise). - gas_limit=200_000 + fork.gas_costs().NEW_ACCOUNT, expected_receipt=TransactionReceipt(logs=expected_logs), ) @@ -148,7 +143,6 @@ def test_selfdestruct_to_different_address_same_tx( state_test: StateTestFiller, env: Environment, pre: Alloc, - fork: Fork, sender: EOA, contract_balance: int, create_opcode: Op, @@ -194,9 +188,6 @@ def test_selfdestruct_to_different_address_same_tx( sender=sender, to=factory, value=contract_balance, - # Same-tx CREATE+SELFDESTRUCT charges NEW_ACCOUNT state gas - # under EIP-8037 (0 otherwise). - gas_limit=200_000 + fork.gas_costs().NEW_ACCOUNT, expected_receipt=TransactionReceipt(logs=expected_logs), ) @@ -229,7 +220,6 @@ def test_selfdestruct_same_tx_via_call( state_test: StateTestFiller, env: Environment, pre: Alloc, - fork: Fork, sender: EOA, to_self: bool, call_twice: bool, @@ -322,12 +312,6 @@ def test_selfdestruct_same_tx_via_call( tx = Transaction( sender=sender, to=factory, - value=0, - # Same-tx CREATE+CALL+SELFDESTRUCT with SSTOREs for verification. - # Under EIP-8037 the SSTORE state writes and the SELFDESTRUCT - # NEW_ACCOUNT charge are paid from the shared limit; bump to - # 1_000_000 plus NEW_ACCOUNT to cover both dimensions. - gas_limit=1_000_000 + fork.gas_costs().NEW_ACCOUNT, expected_receipt=TransactionReceipt(logs=expected_logs), ) @@ -523,9 +507,7 @@ def test_finalization_burn_logs( tx = Transaction( sender=sender, to=None, - value=0, data=factory_code, - gas_limit=2_000_000, expected_receipt=TransactionReceipt( logs=execution_logs + finalization_logs ), @@ -628,9 +610,7 @@ def test_finalization_burn_logs_multi_account_ordering( tx = Transaction( sender=sender, to=None, - value=0, data=factory_code, - gas_limit=fork.transaction_gas_limit_cap(), expected_receipt=TransactionReceipt( logs=execution_logs + finalization_logs ), @@ -733,9 +713,7 @@ def test_finalization_burn_log_single_account_multiple_transfers( tx = Transaction( sender=sender, to=None, - value=0, data=factory_code, - gas_limit=fork.transaction_gas_limit_cap(), expected_receipt=TransactionReceipt( logs=execution_logs + finalization_logs ), @@ -905,15 +883,11 @@ def test_selfdestruct_finalization_after_priority_fee( # TODO: Fix calculation of the exact expected gas usage finalization_balance = None expected_logs.append(burn_log(created_address, finalization_balance)) - gas_limit = 500_000 - if fork.is_eip_enabled(8037): - gas_limit = 2_000_000 tx = Transaction( sender=sender, to=None, value=0, data=factory_code, - gas_limit=gas_limit, gas_price=gas_price, expected_receipt=TransactionReceipt(logs=expected_logs), ) diff --git a/tests/amsterdam/eip7708_eth_transfer_logs/test_fork_transition.py b/tests/amsterdam/eip7708_eth_transfer_logs/test_fork_transition.py index 3c95d3b6628..cfba6e930b8 100644 --- a/tests/amsterdam/eip7708_eth_transfer_logs/test_fork_transition.py +++ b/tests/amsterdam/eip7708_eth_transfer_logs/test_fork_transition.py @@ -11,7 +11,6 @@ Alloc, Block, BlockchainTestFiller, - Fork, Op, Transaction, TransactionReceipt, @@ -36,7 +35,6 @@ def test_burn_log_at_fork_transition( blockchain_test: BlockchainTestFiller, pre: Alloc, - fork: Fork, same_tx: bool, to_self: bool, ) -> None: @@ -119,17 +117,6 @@ def test_burn_log_at_fork_transition( beneficiary: Account(balance=contract_balance * 3), } - # `fork` is a TransitionFork here; resolve to the post-transition - # fork (where the larger NEW_ACCOUNT applies) so the gas budget - # covers the same-tx CREATE+SELFDESTRUCT on the post-transition - # block. The pre-transition block has plenty of headroom. - pre_transition_timestamp = 14_999 - transition_timestamp = 15_000 - post_transition_timestamp = 15_001 - post_fork = fork.fork_at(timestamp=post_transition_timestamp) - gas_limit = 200_000 - if post_fork.is_eip_enabled(8037): - gas_limit += post_fork.gas_costs().NEW_ACCOUNT blocks = [ Block( timestamp=ts, @@ -137,18 +124,11 @@ def test_burn_log_at_fork_transition( Transaction( to=targets[i], sender=sender, - gas_limit=gas_limit, expected_receipt=TransactionReceipt(logs=expected_logs[i]), ) ], ) - for i, ts in enumerate( - [ - pre_transition_timestamp, - transition_timestamp, - post_transition_timestamp, - ] - ) + for i, ts in enumerate([14_999, 15_000, 15_001]) ] blockchain_test(pre=pre, blocks=blocks, post=post) @@ -175,7 +155,6 @@ def test_transfer_log_fork_transition( to=recipient, sender=sender, value=100, - gas_limit=21_000, expected_receipt=TransactionReceipt(logs=[]), ) ], @@ -187,7 +166,6 @@ def test_transfer_log_fork_transition( to=recipient, sender=sender, value=100, - gas_limit=21_000, expected_receipt=TransactionReceipt( logs=[transfer_log(sender, recipient, 100)] ), @@ -201,7 +179,6 @@ def test_transfer_log_fork_transition( to=recipient, sender=sender, value=100, - gas_limit=21_000, expected_receipt=TransactionReceipt( logs=[transfer_log(sender, recipient, 100)] ), diff --git a/tests/amsterdam/eip7708_eth_transfer_logs/test_transfer_logs.py b/tests/amsterdam/eip7708_eth_transfer_logs/test_transfer_logs.py index ddceefb56dc..70ebc714669 100644 --- a/tests/amsterdam/eip7708_eth_transfer_logs/test_transfer_logs.py +++ b/tests/amsterdam/eip7708_eth_transfer_logs/test_transfer_logs.py @@ -52,7 +52,6 @@ def test_simple_transfer_emits_log( sender=sender, to=recipient, value=1, - gas_limit=21_000, expected_receipt=TransactionReceipt( logs=[transfer_log(sender, recipient, 1)] ), @@ -81,7 +80,6 @@ def test_transfer_to_delegated_account_emits_log( sender=sender, to=recipient, value=1, - gas_limit=100_000, expected_receipt=TransactionReceipt( logs=[transfer_log(sender, recipient, 1)] ), @@ -102,7 +100,6 @@ def test_transfer_to_self_no_log( sender=sender, to=sender, value=1, - gas_limit=21_000, expected_receipt=TransactionReceipt(logs=[]), ) @@ -122,7 +119,6 @@ def test_zero_value_transfer_no_log( sender=sender, to=recipient, value=0, - gas_limit=21_000, expected_receipt=TransactionReceipt(logs=[]), ) @@ -152,14 +148,10 @@ def test_contract_creation_tx( expected_logs = ( [transfer_log(sender, created_address, tx_value)] if expect_log else [] ) - gas_limit = 100_000 - if fork.is_eip_enabled(8037): - gas_limit = 500_000 tx = Transaction( sender=sender, to=None, value=tx_value, - gas_limit=gas_limit, data=bytes(initcode), expected_receipt=TransactionReceipt(logs=expected_logs), ) @@ -180,7 +172,6 @@ def test_contract_creation_tx_collision( state_test: StateTestFiller, env: Environment, pre: Alloc, - fork: Fork, collision_nonce: int, collision_code: bytes, ) -> None: @@ -193,17 +184,10 @@ def test_contract_creation_tx_collision( value transfer, so EIP-7708 emits no Transfer log. """ sender = pre.fund_eoa() - # EIP-8037: a contract-creating tx charges intrinsic state gas for the - # new account, so the gas limit must cover it on top of the regular - # intrinsic cost. - gas_limit = 200_000 - if fork.is_eip_enabled(8037): - gas_limit += fork.create_state_gas() tx = Transaction( sender=sender, to=None, value=1000, - gas_limit=gas_limit, data=bytes(Op.RETURN(0, 0)), expected_receipt=TransactionReceipt(logs=[]), ) @@ -244,10 +228,10 @@ def test_call_opcodes_transfer_log_behavior( # Build the call based on opcode type if call_opcode in [Op.CALL, Op.CALLCODE]: # These opcodes have a value parameter - call_code = call_opcode(gas=100_000, address=callee, value=1) + call_code = call_opcode(address=callee, value=1) else: # DELEGATECALL and STATICCALL don't have value parameter - call_code = call_opcode(gas=100_000, address=callee) + call_code = call_opcode(address=callee) contract = pre.deploy_contract(call_code, balance=1) @@ -271,7 +255,6 @@ def test_call_opcodes_transfer_log_behavior( sender=sender, to=contract, value=1, - gas_limit=200_000, expected_receipt=TransactionReceipt(logs=expected_logs), ) @@ -303,7 +286,7 @@ def test_call_opcodes_insufficient_balance_no_log( callee = pre.deploy_contract(Op.STOP) contract_code = Op.SSTORE( - 0, call_opcode(gas=100_000, address=callee, value=attempted_value) + 0, call_opcode(address=callee, value=attempted_value) ) contract = pre.deploy_contract(contract_code, balance=caller_balance) @@ -311,7 +294,6 @@ def test_call_opcodes_insufficient_balance_no_log( sender=sender, to=contract, value=0, - gas_limit=200_000, expected_receipt=TransactionReceipt(logs=[]), ) @@ -337,18 +319,17 @@ def test_delegatecall_inner_call_with_value( recipient = pre.deploy_contract(Op.STOP) # B: code that CALLs recipient with value - code_b = Op.CALL(gas=50_000, address=recipient, value=1) + code_b = Op.CALL(address=recipient, value=1) contract_b = pre.deploy_contract(code_b) # A: DELEGATECALLs to B (executes B's code in A's context) - code_a = Op.DELEGATECALL(gas=100_000, address=contract_b) + code_a = Op.DELEGATECALL(address=contract_b) contract_a = pre.deploy_contract(code_a, balance=1) tx = Transaction( sender=sender, to=contract_a, value=0, - gas_limit=200_000, expected_receipt=TransactionReceipt( logs=[ # CALL from B executes in A's context, so A is the sender @@ -405,15 +386,10 @@ def test_create_opcode_emits_log( transfer_log(contract, created_address, create_value) ) - gas_limit = 200_000 - if fork.is_eip_enabled(8037): - gas_limit = 1_000_000 - tx = Transaction( sender=sender, to=contract, value=1, - gas_limit=gas_limit, expected_receipt=TransactionReceipt(logs=expected_logs), ) @@ -438,9 +414,7 @@ def test_initcode_calls_with_value( recipient = pre.deploy_contract(Op.STOP) # Initcode: CALL recipient with value, then RETURN empty code - initcode = Op.CALL(gas=50_000, address=recipient, value=1) + Op.RETURN( - 0, 0 - ) + initcode = Op.CALL(address=recipient, value=1) + Op.RETURN(0, 0) initcode_bytes = bytes(initcode) # Use Initcode helper or direct memory setup for longer initcode @@ -480,7 +454,6 @@ def test_initcode_calls_with_value( sender=sender, to=factory, value=0, - gas_limit=300_000, expected_receipt=TransactionReceipt( logs=[ # CREATE transfers value to new contract @@ -518,7 +491,6 @@ def test_create_initcode_stop_emits_log( sender=sender, to=contract, value=0, - gas_limit=500_000, expected_receipt=TransactionReceipt( logs=[transfer_log(contract, created_address, 1)] ), @@ -558,7 +530,6 @@ def test_failed_create_with_value_no_log( sender=sender, to=contract, value=1, - gas_limit=500_000, expected_receipt=TransactionReceipt( logs=[transfer_log(sender, contract, 1)] ), @@ -590,7 +561,6 @@ def test_create_insufficient_balance_no_log( sender=sender, to=contract, value=1, - gas_limit=500_000, expected_receipt=TransactionReceipt( logs=[transfer_log(sender, contract, 1)] ), @@ -689,7 +659,6 @@ def test_stack_underflow_no_log( sender=sender, to=contract, value=1000, - gas_limit=100_000, expected_receipt=TransactionReceipt(logs=[]), # TX fails, no logs ) @@ -744,7 +713,6 @@ def test_create_collision_no_log( sender=sender, to=factory, value=0, - gas_limit=200_000, expected_receipt=TransactionReceipt( logs=[] ), # No logs - CREATE failed @@ -767,15 +735,10 @@ def test_selfdestruct_with_value_emits_log( contract_code = Op.SELFDESTRUCT(beneficiary) contract = pre.deploy_contract(contract_code, balance=contract_balance) - gas_limit = 100_000 - if fork.is_eip_enabled(8037): - gas_limit = 500_000 - tx = Transaction( sender=sender, to=contract, value=0, - gas_limit=gas_limit, expected_receipt=TransactionReceipt( logs=[transfer_log(contract, beneficiary, contract_balance)] ), @@ -800,15 +763,10 @@ def test_selfdestruct_to_system_address( contract_code = Op.SELFDESTRUCT(Spec.SYSTEM_ADDRESS) contract = pre.deploy_contract(contract_code, balance=1) - gas_limit = 100_000 - if fork.is_eip_enabled(8037): - gas_limit = 500_000 - tx = Transaction( sender=sender, to=contract, value=0, - gas_limit=gas_limit, expected_receipt=TransactionReceipt( logs=[transfer_log(contract, Spec.SYSTEM_ADDRESS, 1)] ), @@ -836,7 +794,7 @@ def test_zero_value_operations_no_log( target = pre.nonexistent_account() if op_type == "call": - contract_code = Op.CALL(gas=100_000, address=target, value=0) + contract_code = Op.CALL(address=target, value=0) else: contract_code = Op.SELFDESTRUCT(target) @@ -846,7 +804,6 @@ def test_zero_value_operations_no_log( sender=sender, to=contract, value=0, - gas_limit=100_000, expected_receipt=TransactionReceipt(logs=[]), ) @@ -880,7 +837,7 @@ def test_call_to_self_no_log( Op.CALLDATASIZE + Op.PUSH1(20) + Op.JUMPI - + call_opcode(gas=100_000, address=Op.ADDRESS, value=1, args_size=1) + + call_opcode(address=Op.ADDRESS, value=1, args_size=1) + Op.JUMPDEST + Op.STOP ) @@ -890,7 +847,6 @@ def test_call_to_self_no_log( sender=sender, to=contract, value=0, - gas_limit=200_000, expected_receipt=TransactionReceipt(logs=[]), ) @@ -900,7 +856,7 @@ def test_call_to_self_no_log( @pytest.mark.parametrize( "recipient_code,call_gas,call_value,recipient_balance,contract_balance", [ - pytest.param(Op.REVERT(0, 0), 50_000, 500, 0, 500, id="call_reverted"), + pytest.param(Op.REVERT(0, 0), Op.GAS, 500, 0, 500, id="call_reverted"), pytest.param(Op.JUMP(0), 100, 500, 0, 500, id="call_out_of_gas"), pytest.param( # OOG with memory expansion - tries to access large memory offset @@ -921,7 +877,7 @@ def test_call_to_self_no_log( ), pytest.param( Op.STOP, - 50_000, + Op.GAS, 2000, 0, 0, @@ -935,7 +891,7 @@ def test_failed_inner_operation_no_log( pre: Alloc, sender: EOA, recipient_code: Bytecode, - call_gas: int, + call_gas: int | Op, call_value: int, recipient_balance: int, contract_balance: int, @@ -955,7 +911,6 @@ def test_failed_inner_operation_no_log( sender=sender, to=contract, value=tx_value, - gas_limit=100_000, expected_receipt=TransactionReceipt( logs=[transfer_log(sender, contract, tx_value)] ), @@ -998,7 +953,6 @@ def test_inner_call_succeeds_outer_reverts_no_log( sender=sender, to=contract, value=1, - gas_limit=500_000, expected_receipt=TransactionReceipt(logs=[]), ) @@ -1056,15 +1010,10 @@ def test_inner_create_succeeds_outer_reverts_no_log( ) entry = pre.deploy_contract(entry_code) - gas_limit = 200_000 - if fork.is_eip_enabled(8037): - gas_limit = 1_000_000 - tx = Transaction( sender=sender, to=entry, value=0, - gas_limit=gas_limit, expected_receipt=TransactionReceipt(logs=[]), ) @@ -1088,7 +1037,6 @@ def test_nested_calls_log_order( state_test: StateTestFiller, env: Environment, pre: Alloc, - fork: Fork, sender: EOA, call_depth: int, ) -> None: @@ -1127,7 +1075,6 @@ def test_nested_calls_log_order( sender=sender, to=entry_contract, value=tx_value, - gas_limit=fork.transaction_gas_limit_cap(), expected_receipt=TransactionReceipt(logs=expected_logs), ) @@ -1165,7 +1112,6 @@ def test_contract_log_and_transfer_ordering( sender=sender, to=contract, value=1, - gas_limit=200_000, expected_receipt=TransactionReceipt( logs=[ # 1. TX-level transfer @@ -1209,7 +1155,6 @@ def test_reverted_transaction_no_log( sender=sender, to=contract, value=1000, - gas_limit=100_000, expected_receipt=TransactionReceipt(logs=[]), ) @@ -1255,7 +1200,6 @@ def test_transfer_to_special_address( sender=sender, to=target, value=transfer_amount, - gas_limit=100_000, expected_receipt=TransactionReceipt( logs=[transfer_log(sender, target, transfer_amount)] ), @@ -1269,7 +1213,6 @@ def test_transfer_with_all_tx_types( state_test: StateTestFiller, env: Environment, pre: Alloc, - fork: Fork, sender: EOA, typed_transaction: Transaction, ) -> None: @@ -1277,12 +1220,9 @@ def test_transfer_with_all_tx_types( recipient = pre.nonexistent_account() transfer_amount = 1000 - # Sending value to a nonexistent recipient charges NEW_ACCOUNT - # state gas under EIP-8037 (0 otherwise). tx = typed_transaction.copy( to=recipient, value=transfer_amount, - gas_limit=typed_transaction.gas_limit + fork.gas_costs().NEW_ACCOUNT, expected_receipt=TransactionReceipt( logs=[transfer_log(sender, recipient, transfer_amount)] ), @@ -1313,7 +1253,6 @@ def test_multiple_transfers_same_block( sender=sender, nonce=0, value=100, - gas_limit=21_000, expected_receipt=TransactionReceipt( logs=[transfer_log(sender, recipient1, 100)] ), @@ -1323,7 +1262,6 @@ def test_multiple_transfers_same_block( sender=sender, nonce=1, value=200, - gas_limit=21_000, expected_receipt=TransactionReceipt( logs=[transfer_log(sender, recipient2, 200)] ), @@ -1362,10 +1300,6 @@ def test_selfdestruct_then_transfer_same_block( contract_code = Op.SELFDESTRUCT(beneficiary) contract = pre.deploy_contract(contract_code, balance=500) - gas_limit = 100_000 - if fork.is_eip_enabled(8037): - gas_limit = 500_000 - blocks = [ Block( txs=[ @@ -1374,7 +1308,6 @@ def test_selfdestruct_then_transfer_same_block( sender=sender, nonce=0, value=0, - gas_limit=gas_limit, expected_receipt=TransactionReceipt( logs=[transfer_log(contract, beneficiary, 500)] ), @@ -1384,7 +1317,6 @@ def test_selfdestruct_then_transfer_same_block( sender=sender, nonce=1, value=100, - gas_limit=gas_limit, expected_receipt=TransactionReceipt( logs=[ transfer_log(sender, contract, 100), @@ -1440,7 +1372,6 @@ def test_selfdestruct_to_self_cross_tx_no_log( nonce=0, value=contract_balance, data=bytes(initcode), - gas_limit=300_000, expected_receipt=TransactionReceipt( logs=[ transfer_log( @@ -1455,7 +1386,6 @@ def test_selfdestruct_to_self_cross_tx_no_log( sender=sender, nonce=1, value=0, - gas_limit=100_000, expected_receipt=TransactionReceipt(logs=[]), ), ], @@ -1495,7 +1425,6 @@ def test_call_to_delegated_account_with_value( sender=sender, to=caller, value=0, - gas_limit=200_000, expected_receipt=TransactionReceipt( logs=[transfer_log(caller, delegated_eoa, 100)] ), @@ -1537,7 +1466,6 @@ def test_call_with_value_to_coinbase_no_priority_fee_log( sender=sender, to=caller, value=0, - gas_limit=fork.transaction_gas_limit_cap(), max_fee_per_gas=max_fee_per_gas, max_priority_fee_per_gas=max_fee_per_gas, expected_receipt=TransactionReceipt( diff --git a/tests/amsterdam/eip7843_slotnum/test_fork_transition.py b/tests/amsterdam/eip7843_slotnum/test_fork_transition.py index 2660249284e..4c0bea784a0 100644 --- a/tests/amsterdam/eip7843_slotnum/test_fork_transition.py +++ b/tests/amsterdam/eip7843_slotnum/test_fork_transition.py @@ -6,7 +6,6 @@ Alloc, Block, BlockchainTestFiller, - Fork, Op, Transaction, ) @@ -21,7 +20,6 @@ def test_slotnum_at_fork_transition( blockchain_test: BlockchainTestFiller, pre: Alloc, - fork: Fork, ) -> None: """ Test SLOTNUM behavior across the EIP-7843 fork transition. @@ -46,13 +44,11 @@ def test_slotnum_at_fork_transition( at_fork_slot = 200 post_fork_slot = 201 - gas_limit = 100_000 + code.gas_cost(fork.transitions_to()) - blocks = [ Block( timestamp=ts, slot_number=slot, - txs=[Transaction(sender=sender, to=contract, gas_limit=gas_limit)], + txs=[Transaction(sender=sender, to=contract)], ) for ts, slot in [ (14_999, None), diff --git a/tests/amsterdam/eip7843_slotnum/test_slotnum.py b/tests/amsterdam/eip7843_slotnum/test_slotnum.py index 1fd9856ef41..c567da8b441 100644 --- a/tests/amsterdam/eip7843_slotnum/test_slotnum.py +++ b/tests/amsterdam/eip7843_slotnum/test_slotnum.py @@ -34,7 +34,6 @@ def test_slotnum_value( state_test: StateTestFiller, pre: Alloc, - fork: Fork, slot_number: int, ) -> None: """ @@ -43,32 +42,26 @@ def test_slotnum_value( The slot number is provided by the consensus layer and should be accessible via the SLOTNUM opcode (0x4B). """ - # Store SLOTNUM result at storage key 0. Metadata pins the - # storage transition (0->slot_number) so `code.gas_cost(fork)` - # picks the right SSTORE branch under EIP-8037's 2D gas model. - code = Op.SSTORE( - key=0, - value=Op.SLOTNUM, - key_warm=False, - original_value=0, - new_value=slot_number, - ) + # Store SLOTNUM result at storage key 0 + code = Op.SSTORE(0, Op.SLOTNUM) code_address = pre.deploy_contract(code) - intrinsic_cost = fork.transaction_intrinsic_cost_calculator()() - code_regular = code.gas_cost(fork) - tx = Transaction( sender=pre.fund_eoa(), - gas_limit=intrinsic_cost + code_regular, to=code_address, ) + post = { + code_address: Account( + storage={0: slot_number}, + ), + } + state_test( env=Environment(slot_number=slot_number), pre=pre, tx=tx, - post={code_address: Account(storage={0: slot_number})}, + post=post, ) @@ -96,47 +89,32 @@ def test_slotnum_gas_cost( callee_code = Op.SLOTNUM + Op.STOP callee_address = pre.deterministic_deploy_contract(deploy_code=callee_code) - # Caller calls the callee with `call_gas`; SSTOREs the call's - # success bit (1 if SLOTNUM had enough gas, 0 if it OOG'd). - sstore_value = 1 if call_succeeds else 0 - caller_code = Op.SSTORE( - key=0, - value=Op.CALL( - gas=call_gas, - address=callee_address, - address_warm=False, - ), - key_warm=False, - original_value=0, - new_value=sstore_value, - ) + # Caller calls the callee with limited gas and stores result + caller_code = Op.SSTORE(0, Op.CALL(gas=call_gas, address=callee_address)) caller_address = pre.deploy_contract(caller_code) - intrinsic_cost = fork.transaction_intrinsic_cost_calculator()() - # Static opcode-metadata calc misses the gas burned in the inner - # CALL frame; add it back. `call_gas` is the full forwarded amount - # — for `enough_gas` SLOTNUM consumes it all; for `out_of_gas` - # the OOG burns the entire forwarded budget. - code_regular = caller_code.gas_cost(fork) + call_gas - tx = Transaction( sender=pre.fund_eoa(), - gas_limit=intrinsic_cost + code_regular, to=caller_address, ) + post = { + caller_address: Account( + storage={0: 1 if call_succeeds else 0}, + ), + } + state_test( env=Environment(slot_number=12345), pre=pre, tx=tx, - post={caller_address: Account(storage={0: sstore_value})}, + post=post, ) def test_slotnum_distinct_per_block( blockchain_test: BlockchainTestFiller, pre: Alloc, - fork: Fork, ) -> None: """ Test that SLOTNUM returns each block's own slot number. @@ -148,19 +126,15 @@ def test_slotnum_distinct_per_block( in the final post-state. """ sender = pre.fund_eoa() - code = Op.SSTORE(Op.NUMBER, Op.SLOTNUM, new_value=1) + Op.STOP - contract = pre.deploy_contract(code) + contract = pre.deploy_contract(Op.SSTORE(Op.NUMBER, Op.SLOTNUM) + Op.STOP) # Non-monotonic on purpose: decrease, increase, jump to large value. slot_numbers = [100, 42, 7, 2**32] - intrinsic_cost = fork.transaction_intrinsic_cost_calculator()() - gas_limit = intrinsic_cost + code.gas_cost(fork) - blocks = [ Block( slot_number=slot, - txs=[Transaction(sender=sender, to=contract, gas_limit=gas_limit)], + txs=[Transaction(sender=sender, to=contract)], ) for slot in slot_numbers ] diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py index 5901e2903ba..72b94884cd0 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py @@ -98,14 +98,15 @@ def test_bal_balance_changes( contract_creation=False, access_list=[], ) - tx_gas_limit = intrinsic_gas_cost + 1000 # add a small buffer + # Hard-coded gas price allows to calculate the tx final price + gas_price = 1_000_000_000 + tx_value = 100 tx = Transaction( sender=alice, to=bob, - value=100, - gas_limit=tx_gas_limit, - gas_price=1_000_000_000, + value=tx_value, + gas_price=gas_price, ) alice_account = pre[alice] @@ -114,7 +115,7 @@ def test_bal_balance_changes( # Account for both the value sent and gas cost (gas_price * gas_used) alice_final_balance = ( - alice_initial_balance - 100 - (intrinsic_gas_cost * 1_000_000_000) + alice_initial_balance - tx_value - (intrinsic_gas_cost * gas_price) ) block = Block( @@ -195,7 +196,6 @@ def test_bal_code_changes( tx = Transaction( sender=alice, to=factory_contract, - gas_limit=500000, ) created_contract = compute_create_address( @@ -289,9 +289,7 @@ def test_bal_account_access_target( code=account_access_opcode(target_contract), ) - tx = Transaction( - sender=alice, to=oracle_contract, gas_limit=5_000_000, gas_price=0xA - ) + tx = Transaction(sender=alice, to=oracle_contract) block = Block( txs=[tx], @@ -314,7 +312,6 @@ def test_bal_account_access_target( def test_bal_callcode_nested_value_transfer( pre: Alloc, blockchain_test: BlockchainTestFiller, - fork: Fork, ) -> None: """ Ensure BAL captures balance changes from nested value transfers @@ -327,16 +324,11 @@ def test_bal_callcode_nested_value_transfer( target_code = Op.CALL(0, bob, 100, 0, 0, 0, 0) target_contract = pre.deploy_contract(code=target_code) - callcode_gas = 50_000 - if fork.is_eip_enabled(8037): - callcode_gas = 500_000 # Oracle contract that uses CALLCODE to execute TargetContract's code - oracle_code = Op.CALLCODE(callcode_gas, target_contract, 100, 0, 0, 0, 0) + oracle_code = Op.CALLCODE(address=target_contract, value=100) oracle_contract = pre.deploy_contract(code=oracle_code, balance=200) - tx = Transaction( - sender=alice, to=oracle_contract, gas_limit=1_000_000, gas_price=0xA - ) + tx = Transaction(sender=alice, to=oracle_contract) block = Block( txs=[tx], @@ -372,25 +364,14 @@ def test_bal_callcode_nested_value_transfer( @pytest.mark.parametrize( "delegated_opcode", [ - pytest.param( - lambda target_addr, inner_gas: Op.DELEGATECALL( - inner_gas, target_addr, 0, 0, 0, 0 - ), - id="delegatecall", - ), - pytest.param( - lambda target_addr, inner_gas: Op.CALLCODE( - inner_gas, target_addr, 0, 0, 0, 0, 0 - ), - id="callcode", - ), + pytest.param(Op.DELEGATECALL, id="delegatecall"), + pytest.param(Op.CALLCODE, id="callcode"), ], ) def test_bal_delegated_storage_writes( pre: Alloc, blockchain_test: BlockchainTestFiller, - delegated_opcode: Callable[[Address, int], Op], - fork: Fork, + delegated_opcode: Op, ) -> None: """ Ensure BAL captures delegated storage writes via @@ -398,33 +379,16 @@ def test_bal_delegated_storage_writes( """ alice = pre.fund_eoa() - # TargetContract that writes 0x42 to slot 0x01. - # Metadata pins the 0->0x42 transition so the gas calculator - # accounts for SSTORE state gas under EIP-8037. - target_code = Op.SSTORE.with_metadata( - key_warm=False, - original_value=0, - current_value=0, - new_value=0x42, - )(0x01, 0x42) + # TargetContract that writes 0x42 to slot 0x01 + target_code = Op.SSTORE(0x01, 0x42) target_contract = pre.deploy_contract(code=target_code) - # Forward enough inner gas to cover both the regular and (under - # EIP-8037) the spilled state-gas portion of the SSTORE — the - # oracle frame inherits `state_gas_reservoir=0` since the outer - # tx_gas stays below TX_MAX_GAS_LIMIT. - inner_gas = target_code.gas_cost(fork) + 100 # small buffer - # Oracle contract that uses delegated opcode to execute # TargetContract's code - oracle_code = delegated_opcode(target_contract, inner_gas) + oracle_code = delegated_opcode(address=target_contract) oracle_contract = pre.deploy_contract(code=oracle_code) - tx = Transaction( - sender=alice, - to=oracle_contract, - gas_limit=1_000_000, - ) + tx = Transaction(sender=alice, to=oracle_contract) block = Block( txs=[tx], @@ -495,7 +459,6 @@ def test_bal_delegated_storage_reads( tx = Transaction( sender=alice, to=oracle_contract, - gas_limit=1_000_000, ) block = Block( @@ -524,8 +487,6 @@ def test_bal_block_rewards( fork: Fork, ) -> None: """Ensure BAL captures fee recipient balance changes from block rewards.""" - alice_initial_balance = 1_000_000 - alice = pre.fund_eoa(amount=alice_initial_balance) bob = pre.fund_eoa(amount=0) charlie = pre.fund_eoa(amount=0) # fee recipient @@ -537,11 +498,18 @@ def test_bal_block_rewards( ) tx_gas_limit = intrinsic_gas + 1000 # add a small buffer gas_price = 0xA + tx_value = 100 + extra_balance = 1000 + + alice_initial_balance = ( + (tx_gas_limit * gas_price) + tx_value + extra_balance + ) + alice = pre.fund_eoa(amount=alice_initial_balance) tx = Transaction( sender=alice, to=bob, - value=100, + value=tx_value, gas_limit=tx_gas_limit, gas_price=gas_price, ) @@ -559,7 +527,7 @@ def test_bal_block_rewards( ) tip_to_charlie = (gas_price - base_fee_per_gas) * intrinsic_gas - alice_final_balance = alice_initial_balance - 100 - total_gas_cost + alice_final_balance = alice_initial_balance - tx_value - total_gas_cost block = Block( txs=[tx], @@ -610,7 +578,6 @@ def test_bal_block_rewards( def test_bal_selfdestruct_to_coinbase( pre: Alloc, state_test: StateTestFiller, - fork: Fork, same_tx: bool, ) -> None: """ @@ -632,7 +599,6 @@ def test_bal_selfdestruct_to_coinbase( base_fee_per_gas=base_fee_per_gas, fee_recipient=coinbase ) - tx_gas_limit = fork.transaction_gas_limit_cap() account_expectations: dict[Address, BalAccountExpectation] if same_tx: @@ -698,7 +664,6 @@ def test_bal_selfdestruct_to_coinbase( tx = Transaction( sender=alice, to=tx_target, - gas_limit=tx_gas_limit, gas_price=base_fee_per_gas, ) @@ -730,13 +695,10 @@ def test_bal_2930_account_listed_but_untouched( storage_keys=[Hash(0x1)], ) - gas_limit = 1_000_000 - tx = Transaction( ty=1, sender=alice, to=bob, - gas_limit=gas_limit, access_list=[access_list], ) @@ -767,7 +729,6 @@ def test_bal_2930_account_listed_but_untouched( def test_bal_2930_slot_listed_but_untouched( pre: Alloc, blockchain_test: BlockchainTestFiller, - fork: Fork, ) -> None: """Ensure BAL excludes untouched access list storage slots.""" alice = pre.fund_eoa() @@ -781,21 +742,10 @@ def test_bal_2930_slot_listed_but_untouched( storage_keys=[Hash(0x1)], ) - intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator() - gas_limit = ( - intrinsic_gas_calculator( - calldata=b"", - contract_creation=False, - access_list=[access_list], - ) - + 1000 - ) # intrinsic + buffer - tx = Transaction( ty=1, sender=alice, to=pure_calculator, - gas_limit=gas_limit, access_list=[access_list], ) @@ -826,7 +776,6 @@ def test_bal_2930_slot_listed_but_untouched( def test_bal_2930_slot_listed_and_unlisted_writes( pre: Alloc, blockchain_test: BlockchainTestFiller, - fork: Fork, ) -> None: """ Ensure BAL includes storage writes regardless of access list presence. @@ -843,24 +792,10 @@ def test_bal_2930_slot_listed_and_unlisted_writes( storage_keys=[Hash(0x01)], ) - intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator() - gas_buffer = 50_000 - if fork.is_eip_enabled(8037): - gas_buffer = 500_000 - gas_limit = ( - intrinsic_gas_calculator( - calldata=b"", - contract_creation=False, - access_list=[access_list], - ) - + gas_buffer - ) # intrinsic + buffer for storage writes - tx = Transaction( ty=1, sender=alice, to=storage_writer, - gas_limit=gas_limit, access_list=[access_list], ) @@ -910,7 +845,6 @@ def test_bal_2930_slot_listed_and_unlisted_writes( def test_bal_2930_slot_listed_and_unlisted_reads( pre: Alloc, blockchain_test: BlockchainTestFiller, - fork: Fork, ) -> None: """Ensure BAL includes storage reads regardless of access list presence.""" alice = pre.fund_eoa() @@ -926,21 +860,10 @@ def test_bal_2930_slot_listed_and_unlisted_reads( storage_keys=[Hash(0x01)], ) - intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator() - gas_limit = ( - intrinsic_gas_calculator( - calldata=b"", - contract_creation=False, - access_list=[access_list], - ) - + 50000 - ) # intrinsic + buffer for storage reads - tx = Transaction( ty=1, sender=alice, to=storage_reader, - gas_limit=gas_limit, access_list=[access_list], ) @@ -1107,11 +1030,7 @@ def test_bal_net_zero_balance_transfer( ) tx = Transaction( - sender=alice, - to=net_zero_bal_contract, - value=transfer_amount, - gas_limit=1_000_000, - gas_price=0xA, + sender=alice, to=net_zero_bal_contract, value=transfer_amount ) expected_balance_in_slot = initial_balance + transfer_amount @@ -1180,18 +1099,12 @@ def test_bal_net_zero_balance_transfer( def test_bal_pure_contract_call( pre: Alloc, blockchain_test: BlockchainTestFiller, - fork: Fork, ) -> None: """Test that BAL captures contract access for pure computation calls.""" alice = pre.fund_eoa() pure_contract = pre.deploy_contract(code=Op.ADD(0x3, 0x2)) - intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator() - gas_limit = intrinsic_gas_calculator() + 5_000 # Buffer - - tx = Transaction( - sender=alice, to=pure_contract, gas_limit=gas_limit, gas_price=0xA - ) + tx = Transaction(sender=alice, to=pure_contract) block = Block( txs=[tx], @@ -1214,7 +1127,6 @@ def test_bal_pure_contract_call( def test_bal_noop_storage_write( pre: Alloc, blockchain_test: BlockchainTestFiller, - fork: Fork, ) -> None: """Test that BAL correctly handles no-op storage write.""" alice = pre.fund_eoa() @@ -1223,12 +1135,7 @@ def test_bal_noop_storage_write( ) storage_contract = pre.deploy_contract(code=code, storage={0x01: 0x42}) - intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator() - gas_limit = intrinsic_gas_calculator() + code.gas_cost(fork) - - tx = Transaction( - sender=alice, to=storage_contract, gas_limit=gas_limit, gas_price=0xA - ) + tx = Transaction(sender=alice, to=storage_contract) block = Block( txs=[tx], @@ -1267,9 +1174,7 @@ def test_bal_aborted_storage_access( storage={0x01: 0x10}, # Pre-existing value in slot 0x01 ) - tx = Transaction( - sender=alice, to=storage_contract, gas_limit=5_000_000, gas_price=0xA - ) + tx = Transaction(sender=alice, to=storage_contract) block = Block( txs=[tx], @@ -1351,9 +1256,7 @@ def test_bal_aborted_account_access( code=account_access_opcode(target_contract) + abort_opcode, ) - tx = Transaction( - sender=alice, to=abort_contract, gas_limit=5_000_000, gas_price=0xA - ) + tx = Transaction(sender=alice, to=abort_contract) block = Block( txs=[tx], @@ -1391,7 +1294,6 @@ def test_bal_aborted_account_access( def test_bal_parent_revert_state_access( pre: Alloc, state_test: StateTestFiller, - fork: Fork, inner_action: str, outer_abort: Op, ) -> None: @@ -1425,9 +1327,7 @@ def test_bal_parent_revert_state_access( code=Op.CALL(gas=Op.GAS, address=inner) + outer_abort ) - tx = Transaction( - sender=alice, to=outer, gas_limit=fork.transaction_gas_limit_cap() - ) + tx = Transaction(sender=alice, to=outer) account_expectations: dict[Address, BalAccountExpectation] if inner_action in ("sstore", "sload"): @@ -1465,7 +1365,6 @@ def test_bal_parent_revert_state_access( def test_bal_outer_revert_with_inner_insufficient_funds( pre: Alloc, state_test: StateTestFiller, - fork: Fork, inner_op: str, ) -> None: """ @@ -1531,9 +1430,7 @@ def test_bal_outer_revert_with_inner_insufficient_funds( code=Op.CALL(gas=Op.GAS, address=inner) + Op.REVERT(0, 0) ) - tx = Transaction( - sender=alice, to=outer, gas_limit=fork.transaction_gas_limit_cap() - ) + tx = Transaction(sender=alice, to=outer) state_test( pre=pre, @@ -1577,9 +1474,7 @@ def test_bal_fully_unmutated_account( storage={0x01: 0x42}, # Pre-existing value ) - tx = Transaction( - sender=alice, to=oracle, gas_limit=1_000_000, value=0, gas_price=0xA - ) + tx = Transaction(sender=alice, to=oracle, value=0, gas_price=0xA) block = Block( txs=[tx], @@ -1635,8 +1530,6 @@ def test_bal_coinbase_zero_tip( fork: Fork, ) -> None: """Ensure BAL includes coinbase even when priority fee is zero.""" - alice_initial_balance = 1_000_000 - alice = pre.fund_eoa(amount=alice_initial_balance) bob = pre.fund_eoa(amount=0) coinbase = pre.fund_eoa(amount=0) # fee recipient @@ -1657,16 +1550,19 @@ def test_bal_coinbase_zero_tip( ) # Set gas_price equal to base_fee so tip = 0 + tx_value = 5 + alice_initial_balance = (tx_gas_limit * base_fee_per_gas) + tx_value + alice = pre.fund_eoa(amount=alice_initial_balance) tx = Transaction( sender=alice, to=bob, - value=5, + value=tx_value, gas_limit=tx_gas_limit, gas_price=base_fee_per_gas, ) alice_final_balance = ( - alice_initial_balance - 5 - (intrinsic_gas * base_fee_per_gas) + alice_initial_balance - tx_value - (intrinsic_gas * base_fee_per_gas) ) block = Block( @@ -1789,7 +1685,6 @@ def test_bal_precompile_funded( sender=alice, to=precompile, value=value, - gas_limit=5_000_000, data=tx_data, ) @@ -1846,15 +1741,10 @@ def test_bal_precompile_call_opcode( alice = pre.fund_eoa() oracle = pre.deploy_contract( - code=call_opcode(gas=100_000, address=precompile) + Op.STOP + code=call_opcode(address=precompile) + Op.STOP ) - tx = Transaction( - sender=alice, - to=oracle, - gas_limit=200_000, - gas_price=0xA, - ) + tx = Transaction(sender=alice, to=oracle) block = Block( txs=[tx], @@ -1884,7 +1774,7 @@ def test_bal_precompile_call_opcode( "value", [ pytest.param(0, id="zero_value"), - pytest.param(10**18, id="positive_value"), + pytest.param(1, id="positive_value"), ], ) def test_bal_nonexistent_value_transfer( @@ -1898,14 +1788,9 @@ def test_bal_nonexistent_value_transfer( Alice sends value directly to non-existent Bob. """ alice = pre.fund_eoa() - bob = Address(0xB0B) + bob = pre.nonexistent_account() - tx = Transaction( - sender=alice, - to=bob, - value=value, - gas_limit=100_000, - ) + tx = Transaction(sender=alice, to=bob, value=value) block = Block( txs=[tx], @@ -1981,17 +1866,13 @@ def test_bal_nonexistent_account_access_read_only( STATICCALL, DELEGATECALL). """ alice = pre.fund_eoa() - bob = Address(0xB0B) + bob = pre.nonexistent_account() oracle_balance = 2 * 10**18 oracle_code = account_access_opcode(bob) oracle = pre.deploy_contract(code=oracle_code, balance=oracle_balance) - tx = Transaction( - sender=alice, - to=oracle, - gas_limit=1_000_000, - ) + tx = Transaction(sender=alice, to=oracle) block = Block( txs=[tx], @@ -2020,18 +1901,23 @@ def test_bal_nonexistent_account_access_read_only( @pytest.mark.parametrize( - "opcode_type,value", + "opcode", + [ + pytest.param(Op.CALL), + pytest.param(Op.CALLCODE), + ], +) +@pytest.mark.parametrize( + "value", [ - pytest.param("call", 0, id="call_zero_value"), - pytest.param("call", 10**18, id="call_positive_value"), - pytest.param("callcode", 0, id="callcode_zero_value"), - pytest.param("callcode", 10**18, id="callcode_positive_value"), + pytest.param(0, id="zero_value"), + pytest.param(10**18, id="positive_value"), ], ) def test_bal_nonexistent_account_access_value_transfer( pre: Alloc, blockchain_test: BlockchainTestFiller, - opcode_type: str, + opcode: Op, value: int, ) -> None: """ @@ -2044,30 +1930,23 @@ def test_bal_nonexistent_account_access_value_transfer( - CALLCODE: Self-transfer (net zero), Bob accessed for code """ alice = pre.fund_eoa() - bob = Address(0xB0B) - oracle_balance = 2 * 10**18 + bob = pre.nonexistent_account() + oracle_balance = value + 10**18 - if opcode_type == "call": - oracle_code = Op.CALL(100_000, bob, value, 0, 0, 0, 0) - else: # callcode - oracle_code = Op.CALLCODE(100_000, bob, value, 0, 0, 0, 0) + oracle_code = opcode(gas=0, address=bob, value=value) oracle = pre.deploy_contract(code=oracle_code, balance=oracle_balance) - tx = Transaction( - sender=alice, - to=oracle, - gas_limit=1_000_000, - ) + tx = Transaction(sender=alice, to=oracle) # Calculate expected balances - if opcode_type == "call" and value > 0: + if opcode == Op.CALL and value > 0: # CALL: Oracle loses value, Bob gains value oracle_final_balance = oracle_balance - value bob_final_balance = value bob_has_balance_change = True oracle_has_balance_change = True - elif opcode_type == "callcode" and value > 0: + elif opcode == Op.CALLCODE and value > 0: # CALLCODE: Self-transfer (net zero), Bob just accessed for code oracle_final_balance = oracle_balance bob_final_balance = 0 @@ -2396,7 +2275,6 @@ def test_bal_nested_delegatecall_storage_writes_net_zero( def test_bal_create_transaction_empty_code( pre: Alloc, blockchain_test: BlockchainTestFiller, - fork: Fork, ) -> None: """ Ensure BAL does not record spurious code changes when a CREATE transaction @@ -2405,15 +2283,10 @@ def test_bal_create_transaction_empty_code( alice = pre.fund_eoa() contract_address = compute_create_address(address=alice, nonce=0) - gas_limit = 100_000 - if fork.is_eip_enabled(8037): - gas_limit = 500_000 - tx = Transaction( sender=alice, to=None, data=b"", - gas_limit=gas_limit, ) account_expectations = { @@ -2454,7 +2327,6 @@ def test_bal_cross_tx_storage_write( pre: Alloc, blockchain_test: BlockchainTestFiller, tx2_value: int, - fork: Fork, ) -> None: """ Tx1's storage_change must be preserved regardless of tx2's write. @@ -2469,40 +2341,16 @@ def test_bal_cross_tx_storage_write( contract = pre.deploy_contract(code=Op.SSTORE(0, Op.CALLDATALOAD(0))) - # Size each tx_gas_limit precisely against its SSTORE transition - # under EIP-8037's 2D gas model (regular + state). - intrinsic_calc = fork.transaction_intrinsic_cost_calculator() - tx1_data = Hash(tx1_value) - tx2_data = Hash(tx2_value) - tx1_code = Op.SSTORE.with_metadata( - key_warm=False, - original_value=0, - current_value=0, - new_value=tx1_value, - )(0, Op.CALLDATALOAD(0)) - tx2_code = Op.SSTORE.with_metadata( - key_warm=False, - original_value=tx1_value, - current_value=tx1_value, - new_value=tx2_value, - )(0, Op.CALLDATALOAD(0)) - tx1 = Transaction( sender=alice, to=contract, - data=tx1_data, - gas_limit=( - intrinsic_calc(calldata=tx1_data) + tx1_code.gas_cost(fork) - ), + data=Hash(tx1_value), ) tx2 = Transaction( sender=alice, to=contract, - data=tx2_data, - gas_limit=( - intrinsic_calc(calldata=tx2_data) + tx2_code.gas_cost(fork) - ), + data=Hash(tx2_value), ) slot_changes = [ @@ -2548,7 +2396,6 @@ def test_bal_cross_tx_storage_write( def test_bal_cross_tx_storage_chain( pre: Alloc, blockchain_test: BlockchainTestFiller, - fork: Fork, ) -> None: """ Verify clients apply BAL state changes from prior transactions before @@ -2590,7 +2437,6 @@ def test_bal_cross_tx_storage_chain( sender=sender, to=contract, data=Hash(i), - gas_limit=fork.transaction_gas_limit_cap(), ) ) @@ -2643,7 +2489,6 @@ def test_bal_cross_tx_deploy_then_call( pre: Alloc, blockchain_test: BlockchainTestFiller, create_opcode: Op, - fork: Fork, ) -> None: """ Verify clients apply Tx1's CREATE to their state view before @@ -2687,12 +2532,10 @@ def test_bal_cross_tx_deploy_then_call( sender=alice, to=factory, data=initcode_bytes, - gas_limit=fork.transaction_gas_limit_cap(), ) tx_call = Transaction( sender=bob, to=target, - gas_limit=fork.transaction_gas_limit_cap(), ) account_expectations = { @@ -2903,7 +2746,6 @@ def test_bal_cross_tx_balance_dependency( pre: Alloc, blockchain_test: BlockchainTestFiller, funding_method: str, - fork: Fork, ) -> None: """ Verify clients apply Tx1's balance change before executing Tx2 in @@ -2931,23 +2773,14 @@ def test_bal_cross_tx_balance_dependency( ) if funding_method == "direct_call": - tx_send = Transaction( - sender=alice, - to=contract, - value=transferred, - gas_limit=fork.transaction_gas_limit_cap(), - ) + tx_send = Transaction(sender=alice, to=contract, value=transferred) send_expectations: dict = {} elif funding_method == "selfdestruct": killer = pre.deploy_contract( code=Op.SELFDESTRUCT(contract), balance=transferred, ) - tx_send = Transaction( - sender=alice, - to=killer, - gas_limit=fork.transaction_gas_limit_cap(), - ) + tx_send = Transaction(sender=alice, to=killer) send_expectations = { killer: BalAccountExpectation( balance_changes=[ @@ -2958,12 +2791,7 @@ def test_bal_cross_tx_balance_dependency( else: raise ValueError(f"unknown funding_method: {funding_method}") - tx_read = Transaction( - sender=bob, - to=contract, - data=b"\x01", - gas_limit=fork.transaction_gas_limit_cap(), - ) + tx_read = Transaction(sender=bob, to=contract, data=b"\x01") account_expectations = { contract: BalAccountExpectation( @@ -3267,13 +3095,7 @@ def test_bal_cross_block_ripemd160_state_leak( # Block 1: Call RIPEMD-160 successfully block1 = Block( - txs=[ - Transaction( - sender=alice, - to=ripemd_caller, - gas_limit=100_000, - ) - ], + txs=[Transaction(sender=alice, to=ripemd_caller)], expected_block_access_list=BlockAccessListExpectation( account_expectations={ alice: BalAccountExpectation( @@ -3292,13 +3114,7 @@ def test_bal_cross_block_ripemd160_state_leak( # If internal state leaked from Block 1, RIPEMD-160 would incorrectly # appear in Block 2's BAL. block2 = Block( - txs=[ - Transaction( - sender=bob, - to=exception_contract, - gas_limit=100_000, - ) - ], + txs=[Transaction(sender=bob, to=exception_contract)], expected_block_access_list=BlockAccessListExpectation( account_expectations={ alice: None, @@ -3328,7 +3144,6 @@ def test_bal_cross_block_ripemd160_state_leak( def test_bal_all_transaction_types( pre: Alloc, blockchain_test: BlockchainTestFiller, - fork: Fork, ) -> None: """ Test BAL with all 5 tx types in single block. @@ -3345,10 +3160,6 @@ def test_bal_all_transaction_types( """ from tests.prague.eip7702_set_code_tx.spec import Spec as Spec7702 - gas_limit = 100_000 - if fork.is_eip_enabled(8037): - gas_limit = 500_000 - # Create senders for each transaction type sender_0 = pre.fund_eoa() # Type 0 - Legacy sender_1 = pre.fund_eoa() # Type 1 - Access List @@ -3375,7 +3186,6 @@ def test_bal_all_transaction_types( ty=0, sender=sender_0, to=contract_0, - gas_limit=gas_limit, gas_price=10, data=Hash(0x01), # Value to store ) @@ -3385,7 +3195,6 @@ def test_bal_all_transaction_types( ty=1, sender=sender_1, to=contract_1, - gas_limit=gas_limit, gas_price=10, data=Hash(0x02), access_list=[ @@ -3401,7 +3210,6 @@ def test_bal_all_transaction_types( ty=2, sender=sender_2, to=contract_2, - gas_limit=gas_limit, max_fee_per_gas=50, max_priority_fee_per_gas=5, data=Hash(0x03), @@ -3414,7 +3222,6 @@ def test_bal_all_transaction_types( ty=3, sender=sender_3, to=contract_3, - gas_limit=gas_limit, max_fee_per_gas=50, max_priority_fee_per_gas=5, max_fee_per_blob_gas=10, @@ -3427,7 +3234,6 @@ def test_bal_all_transaction_types( ty=4, sender=sender_4, to=alice, - gas_limit=gas_limit, max_fee_per_gas=50, max_priority_fee_per_gas=5, authorization_list=[ @@ -3642,11 +3448,7 @@ def test_bal_lexicographic_address_ordering( contract = pre.deploy_contract(code=contract_code) - tx = Transaction( - sender=alice, - to=contract, - gas_limit=1_000_000, - ) + tx = Transaction(sender=alice, to=contract) # BAL must be sorted lexicographically by address bytes # Order: low < mid < high < endian_low < endian_high @@ -3755,7 +3557,6 @@ def test_bal_gas_limit_boundary( sender=alice, to=bob, value=1, - gas_limit=21_000, gas_price=base_fee_per_gas, ) ) @@ -3837,18 +3638,14 @@ def test_bal_intra_tx_multiple_sstores_same_slot( single storage change with the final post-value; intermediate writes (0xAA, 0xBB) must not appear in the BAL. """ - alice = pre.fund_eoa(amount=10**18) + alice = pre.fund_eoa() code = ( Op.SSTORE(0x01, 0xAA) + Op.SSTORE(0x01, 0xBB) + Op.SSTORE(0x01, 0xCC) ) contract = pre.deploy_contract(code=code, storage={0x01: pre_value}) - tx = Transaction( - sender=alice, - to=contract, - gas_limit=200_000, - ) + tx = Transaction(sender=alice, to=contract) blockchain_test( pre=pre, @@ -3930,18 +3727,14 @@ def test_bal_intra_tx_sstores_same_slot_net_zero( net-zero result are filtered: the slot must appear in storage_reads (it was accessed) but must not appear in storage_changes. """ - alice = pre.fund_eoa(amount=10**18) + alice = pre.fund_eoa() code = Op.SSTORE(0x01, writes[0]) for v in writes[1:]: code += Op.SSTORE(0x01, v) contract = pre.deploy_contract(code=code, storage={0x01: pre_value}) - tx = Transaction( - sender=alice, - to=contract, - gas_limit=200_000, - ) + tx = Transaction(sender=alice, to=contract) blockchain_test( pre=pre, diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_cross_index.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_cross_index.py index 90715853114..d5b6f59bbd4 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_cross_index.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_cross_index.py @@ -24,8 +24,6 @@ BlockAccessListExpectation, BlockchainTestFiller, Bytecode, - Fork, - Header, Op, Transaction, ) @@ -71,7 +69,6 @@ def test_bal_withdrawal_contract_cross_index( to=WITHDRAWAL_REQUEST_ADDRESS, value=1, data=withdrawal_calldata, - gas_limit=1_000_000, ) blockchain_test( @@ -144,7 +141,6 @@ def test_bal_consolidation_contract_cross_index( to=CONSOLIDATION_REQUEST_ADDRESS, value=1, data=consolidation_calldata, - gas_limit=1_000_000, ) blockchain_test( @@ -199,7 +195,6 @@ def test_bal_consolidation_contract_cross_index( def test_bal_noop_write_filtering( pre: Alloc, blockchain_test: BlockchainTestFiller, - fork: Fork, ) -> None: """ Test that NOOP writes (writing same value or 0 to empty) are filtered. @@ -209,37 +204,15 @@ def test_bal_noop_write_filtering( 2. Writing the same value to a slot doesn't appear in BAL 3. Only actual changes are tracked """ - # Metadata pins each SSTORE's actual transition so the gas - # calculator picks the right branch under EIP-8037's 2D model. test_code = Bytecode( # Write 0 to uninitialized slot 1 (noop) - Op.SSTORE.with_metadata( - key_warm=False, - original_value=0, - current_value=0, - new_value=0, - )(1, 0) - # Write 42 to slot 2 (0->42, charges sstore_state_gas) - + Op.SSTORE.with_metadata( - key_warm=False, - original_value=0, - current_value=0, - new_value=42, - )(2, 42) - # Write 100 to slot 3 (same as pre-state, should be filtered) - + Op.SSTORE.with_metadata( - key_warm=False, - original_value=100, - current_value=100, - new_value=100, - )(3, 100) - # Write 200 to slot 4 (150->200, regular update) - + Op.SSTORE.with_metadata( - key_warm=False, - original_value=150, - current_value=150, - new_value=200, - )(4, 200) + Op.SSTORE(1, 0) + # Write 42 to slot 2 + + Op.SSTORE(2, 42) + # Write 100 to slot 3 (will be same as pre-state, should be filtered) + + Op.SSTORE(3, 100) + # Write 200 to slot 4 (different from pre-state 150, should appear) + + Op.SSTORE(4, 200) ) sender = pre.fund_eoa() @@ -248,12 +221,7 @@ def test_bal_noop_write_filtering( storage={3: 100, 4: 150}, ) - intrinsic_cost = fork.transaction_intrinsic_cost_calculator()() - tx = Transaction( - sender=sender, - to=test_address, - gas_limit=intrinsic_cost + test_code.gas_cost(fork), - ) + tx = Transaction(sender=sender, to=test_address) # Expected BAL should only show actual changes expected_block_access_list = BlockAccessListExpectation( @@ -281,17 +249,9 @@ def test_bal_noop_write_filtering( } ) - # Header `gas_used = max(regular, state)` for the single tx; the - # SSTORE metadata pins each transition so `regular_cost`/`state_cost` - # return the actual fork-priced amount. - expected_regular = intrinsic_cost + test_code.regular_cost(fork) - expected_state = test_code.state_cost(fork) block = Block( txs=[tx], expected_block_access_list=expected_block_access_list, - header_verify=Header( - gas_used=max(expected_regular, expected_state), - ), ) blockchain_test( @@ -334,8 +294,8 @@ def test_bal_intra_tx_round_trip_after_prior_tx_write( # Both txs go into the same block; tx 1 makes the real 0 -> 0x42 # change, tx 2 starts from 0x42 and ends at 0x42 (per-tx no-op). - tx_1 = Transaction(sender=sender_a, to=contract, gas_limit=200_000) - tx_2 = Transaction(sender=sender_b, to=contract, gas_limit=200_000) + tx_1 = Transaction(sender=sender_a, to=contract) + tx_2 = Transaction(sender=sender_b, to=contract) expected_block_access_list = BlockAccessListExpectation( account_expectations={ @@ -415,7 +375,6 @@ def test_bal_system_contract_noop_filtering( sender=sender, to=receiver, value=100, - gas_limit=21_000, ) # withdrawal and consolidation contracts should NOT have any storage @@ -495,14 +454,9 @@ def test_bal_withdrawal_predeploy_balance_observed_cross_tx( to=WITHDRAWAL_REQUEST_ADDRESS, value=fee, data=withdrawal_calldata, - gas_limit=1_000_000, ) - tx_read_balance = Transaction( - sender=sender_1, - to=reader, - gas_limit=200_000, - ) + tx_read_balance = Transaction(sender=sender_1, to=reader) expected_block_access_list = BlockAccessListExpectation( account_expectations={ diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip2935.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip2935.py index 72ca12b5de7..174fd5947e2 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip2935.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip2935.py @@ -12,7 +12,6 @@ Block, BlockAccessListExpectation, BlockchainTestFiller, - Fork, Hash, Op, Transaction, @@ -56,7 +55,6 @@ def block_hash_system_call_expectations(block_number: int) -> dict: def test_bal_2935_simple( pre: Alloc, blockchain_test: BlockchainTestFiller, - fork: Fork, ) -> None: """ Ensure BAL captures history storage writes during system call. @@ -71,19 +69,9 @@ def test_bal_2935_simple( transfer_value = 10 - tx1 = Transaction( - sender=alice, - to=charlie, - value=transfer_value, - gas_limit=fork.transaction_gas_limit_cap(), - ) + tx1 = Transaction(sender=alice, to=charlie, value=transfer_value) - tx2 = Transaction( - sender=bob, - to=charlie, - value=transfer_value, - gas_limit=fork.transaction_gas_limit_cap(), - ) + tx2 = Transaction(sender=bob, to=charlie, value=transfer_value) account_expectations = block_hash_system_call_expectations(0) @@ -162,7 +150,6 @@ def test_bal_2935_empty_block( def test_bal_2935_query( pre: Alloc, blockchain_test: BlockchainTestFiller, - fork: Fork, query_block_number: int, is_valid: bool, value: int, @@ -204,7 +191,6 @@ def test_bal_2935_query( to=oracle, data=Hash(query_block_number), value=value, - gas_limit=fork.transaction_gas_limit_cap(), ) # A setup up block that writes genesis block-hash @@ -291,7 +277,6 @@ def test_bal_2935_query( def test_bal_2935_selfdestruct_to_history_storage( pre: Alloc, blockchain_test: BlockchainTestFiller, - fork: Fork, ) -> None: """ Ensure BAL captures SELFDESTRUCT to history storage address alongside @@ -314,11 +299,7 @@ def test_bal_2935_selfdestruct_to_history_storage( balance=contract_balance, ) - tx = Transaction( - sender=alice, - to=selfdestruct_contract, - gas_limit=fork.transaction_gas_limit_cap(), - ) + tx = Transaction(sender=alice, to=selfdestruct_contract) account_expectations = block_hash_system_call_expectations(0) @@ -372,7 +353,6 @@ def test_bal_2935_selfdestruct_to_history_storage( def test_bal_2935_invalid_calldata_size( pre: Alloc, blockchain_test: BlockchainTestFiller, - fork: Fork, calldata_size: int, value: int, ) -> None: @@ -409,13 +389,7 @@ def test_bal_2935_invalid_calldata_size( # Pad calldata to requested size calldata = b"\x00" * calldata_size - tx = Transaction( - sender=alice, - to=oracle, - data=calldata, - value=value, - gas_limit=fork.transaction_gas_limit_cap(), - ) + tx = Transaction(sender=alice, to=oracle, data=calldata, value=value) # Block 1: Setup block that writes genesis block-hash via system call block_1 = Block( diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip4788.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip4788.py index 65e0b376dc7..13d08f06e6d 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip4788.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip4788.py @@ -13,7 +13,6 @@ Block, BlockAccessListExpectation, BlockchainTestFiller, - Fork, Hash, Op, Transaction, @@ -113,7 +112,6 @@ def build_beacon_root_setup_block( def test_bal_4788_simple( pre: Alloc, blockchain_test: BlockchainTestFiller, - fork: Fork, ) -> None: """ Ensure BAL captures beacon root storage writes during pre-execution @@ -132,19 +130,9 @@ def test_bal_4788_simple( transfer_value = 10 - tx1 = Transaction( - sender=alice, - to=charlie, - value=transfer_value, - gas_limit=fork.transaction_gas_limit_cap(), - ) + tx1 = Transaction(sender=alice, to=charlie, value=transfer_value) - tx2 = Transaction( - sender=bob, - to=charlie, - value=transfer_value, - gas_limit=fork.transaction_gas_limit_cap(), - ) + tx2 = Transaction(sender=bob, to=charlie, value=transfer_value) # Build BAL expectations starting with system call account_expectations = beacon_root_system_call_expectations( @@ -243,7 +231,6 @@ def test_bal_4788_empty_block( def test_bal_4788_query( pre: Alloc, blockchain_test: BlockchainTestFiller, - fork: Fork, timestamp: int, beacon_root: Hash, query_timestamp: int, @@ -295,7 +282,6 @@ def test_bal_4788_query( to=query_contract, data=Hash(query_timestamp), value=value, - gas_limit=fork.transaction_gas_limit_cap(), ) # Build BAL expectations for block 2 @@ -406,7 +392,6 @@ def test_bal_4788_query( def test_bal_4788_invalid_calldata_size( pre: Alloc, blockchain_test: BlockchainTestFiller, - fork: Fork, calldata_size: int, value: int, ) -> None: @@ -451,7 +436,6 @@ def test_bal_4788_invalid_calldata_size( to=query_contract, data=calldata, value=value, - gas_limit=fork.transaction_gas_limit_cap(), ) account_expectations = beacon_root_system_call_expectations( @@ -505,7 +489,6 @@ def test_bal_4788_invalid_calldata_size( def test_bal_4788_selfdestruct_to_beacon_root( pre: Alloc, blockchain_test: BlockchainTestFiller, - fork: Fork, ) -> None: """ Ensure BAL captures SELFDESTRUCT to beacon root address alongside @@ -530,11 +513,7 @@ def test_bal_4788_selfdestruct_to_beacon_root( balance=contract_balance, ) - tx = Transaction( - sender=alice, - to=selfdestruct_contract, - gas_limit=fork.transaction_gas_limit_cap(), - ) + tx = Transaction(sender=alice, to=selfdestruct_contract) # Build BAL expectations starting with system call account_expectations = beacon_root_system_call_expectations( diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip4895.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip4895.py index d72f262c240..a78d67763dc 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip4895.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip4895.py @@ -267,12 +267,7 @@ def test_bal_withdrawal_and_state_access_same_account( storage={0x01: 0x42}, ) - tx = Transaction( - sender=alice, - to=oracle, - gas_limit=1_000_000, - gas_price=0xA, - ) + tx = Transaction(sender=alice, to=oracle) block = Block( txs=[tx], @@ -449,12 +444,7 @@ def test_bal_withdrawal_and_selfdestruct( code=Op.SELFDESTRUCT(bob), ) - tx = Transaction( - sender=alice, - to=oracle, - gas_limit=1_000_000, - gas_price=0xA, - ) + tx = Transaction(sender=alice, to=oracle) block = Block( txs=[tx], @@ -525,8 +515,6 @@ def test_bal_withdrawal_and_new_contract( to=None, data=initcode, value=5 * GWEI, - gas_limit=1_000_000, - gas_price=0xA, ) block = Block( @@ -724,10 +712,12 @@ def test_bal_withdrawal_largest_amount( ) +@pytest.mark.parametrize("tx_type", [0, 2]) def test_bal_withdrawal_to_coinbase( pre: Alloc, blockchain_test: BlockchainTestFiller, fork: Fork, + tx_type: int, ) -> None: """ Ensure BAL captures withdrawal to coinbase address. @@ -741,16 +731,6 @@ def test_bal_withdrawal_to_coinbase( intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator() intrinsic_gas = intrinsic_gas_calculator() - tx_gas_limit = intrinsic_gas + 1000 - gas_price = 0xA - - tx = Transaction( - sender=alice, - to=bob, - value=5, - gas_limit=tx_gas_limit, - gas_price=gas_price, - ) # Calculate tip to coinbase genesis_env = Environment(base_fee_per_gas=0x7) @@ -759,8 +739,29 @@ def test_bal_withdrawal_to_coinbase( parent_gas_used=0, parent_gas_limit=genesis_env.gas_limit, ) - tip_to_coinbase = (gas_price - base_fee_per_gas) * intrinsic_gas - coinbase_final_balance = tip_to_coinbase + (10 * GWEI) + priority_fee = 1 + + tx_kwargs = {} + if tx_type == 2: + tx_kwargs["ty"] = 2 + tx_kwargs["max_fee_per_gas"] = base_fee_per_gas + priority_fee + tx_kwargs["max_priority_fee_per_gas"] = base_fee_per_gas + priority_fee + else: + tx_kwargs["ty"] = 0 + tx_kwargs["gas_price"] = base_fee_per_gas + priority_fee + + tx_value = 5 + tx = Transaction( + sender=alice, + to=bob, + value=tx_value, + gas_limit=intrinsic_gas, + **tx_kwargs, + ) + + tip_to_coinbase = priority_fee * intrinsic_gas + withdrawal_amount = 10 + coinbase_final_balance = tip_to_coinbase + (withdrawal_amount * GWEI) block = Block( txs=[tx], @@ -771,7 +772,7 @@ def test_bal_withdrawal_to_coinbase( index=0, validator_index=0, address=coinbase, - amount=10, + amount=withdrawal_amount, ) ], expected_block_access_list=BlockAccessListExpectation( @@ -783,7 +784,9 @@ def test_bal_withdrawal_to_coinbase( ), bob: BalAccountExpectation( balance_changes=[ - BalBalanceChange(block_access_index=1, post_balance=5) + BalBalanceChange( + block_access_index=1, post_balance=tx_value + ) ], ), coinbase: BalAccountExpectation( @@ -806,7 +809,7 @@ def test_bal_withdrawal_to_coinbase( blocks=[block], post={ alice: Account(nonce=1), - bob: Account(balance=5), + bob: Account(balance=tx_value), coinbase: Account(balance=coinbase_final_balance), }, genesis_environment=genesis_env, diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip7002.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip7002.py index 05f7c0574ae..bf9384224e6 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip7002.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip7002.py @@ -15,7 +15,6 @@ Block, BlockAccessListExpectation, BlockchainTestFiller, - Fork, Op, Transaction, ) @@ -177,7 +176,6 @@ def _build_incremental_changes( def test_bal_7002_clean_sweep( pre: Alloc, blockchain_test: BlockchainTestFiller, - fork: Fork, pubkey: bytes, amount: int, ) -> None: @@ -197,18 +195,12 @@ def test_bal_7002_clean_sweep( fee=Spec7002.get_fee(0), ) - # Predeploy sweep performs first-time SSTOREs for queue, count, and - # tail slots. `sstore_state_gas()` is 0 pre-EIP-8037 and scales with - # cpsb on Amsterdam, keeping this budget CPSB-agnostic. - gas_limit = 200_000 + 5 * Op.SSTORE(new_value=1).state_cost(fork) - # Transaction to system contract tx = Transaction( sender=alice, to=Address(Spec7002.WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS), value=withdrawal_request.fee, data=withdrawal_request.calldata, - gas_limit=gas_limit, ) # Build queue writes and reads based on pubkey @@ -290,7 +282,6 @@ def test_bal_7002_clean_sweep( def test_bal_7002_partial_sweep( pre: Alloc, blockchain_test: BlockchainTestFiller, - fork: Fork, ) -> None: """ Ensure BAL correctly tracks queue overflow when requests exceed MAX. @@ -301,11 +292,6 @@ def test_bal_7002_partial_sweep( fee = Spec7002.get_fee(0) senders = [pre.fund_eoa() for _ in range(num_requests)] - # Predeploy sweep performs first-time SSTOREs for queue, count, and - # tail slots. `sstore_state_gas()` is 0 pre-EIP-8037 and scales with - # cpsb on Amsterdam, keeping this budget CPSB-agnostic. - gas_limit = 200_000 + 5 * Op.SSTORE(new_value=1).state_cost(fork) - # Block 1: 20 withdrawal requests withdrawal_requests = [ WithdrawalRequest(validator_pubkey=i + 1, amount=0, fee=fee) @@ -320,7 +306,6 @@ def test_bal_7002_partial_sweep( to=eip7002_address, value=withdrawal_request.fee, data=withdrawal_request.calldata, - gas_limit=gas_limit, ) for sender, withdrawal_request in zip( senders, withdrawal_requests, strict=True @@ -468,7 +453,6 @@ def test_bal_7002_partial_sweep( def test_bal_7002_no_withdrawal_requests( pre: Alloc, blockchain_test: BlockchainTestFiller, - fork: Fork, ) -> None: """ Ensure BAL captures EIP-7002 system contract dequeue operation even @@ -483,16 +467,10 @@ def test_bal_7002_no_withdrawal_requests( value = 10 - # Predeploy sweep performs first-time SSTOREs for queue, count, and - # tail slots. `sstore_state_gas()` is 0 pre-EIP-8037 and scales with - # cpsb on Amsterdam, keeping this budget CPSB-agnostic. - gas_limit = 200_000 + 5 * Op.SSTORE(new_value=1).state_cost(fork) - tx = Transaction( sender=alice, to=bob, value=value, - gas_limit=gas_limit, ) block = Block( diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip7702.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip7702.py index 68fd84b1f97..dd0f036a714 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip7702.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip7702.py @@ -66,7 +66,6 @@ def test_bal_7702_delegation_create( sender=sender, to=bob, value=10, - gas_limit=1_000_000, authorization_list=[ AuthorizationTuple( address=oracle, @@ -191,7 +190,6 @@ def test_bal_7702_delegation_update( sender=sender_create, to=bob, value=10, - gas_limit=1_000_000, authorization_list=[ AuthorizationTuple( address=oracle1, @@ -206,7 +204,6 @@ def test_bal_7702_delegation_update( sender=sender_update, to=bob, value=10, - gas_limit=1_000_000, authorization_list=[ AuthorizationTuple( address=oracle2, @@ -330,7 +327,6 @@ def test_bal_7702_delegation_clear( sender=sender, to=bob, value=10, - gas_limit=1_000_000, authorization_list=[ AuthorizationTuple( address=oracle, @@ -345,7 +341,6 @@ def test_bal_7702_delegation_clear( sender=sender, to=bob, value=10, - gas_limit=1_000_000, authorization_list=[ AuthorizationTuple( address=abyss, @@ -441,8 +436,6 @@ def test_bal_7702_delegated_storage_access( sender=bob, to=alice, # Bob calls Alice (delegated account) value=10, - gas_limit=1_000_000, - gas_price=0xA, ) block = Block( @@ -506,7 +499,6 @@ def test_bal_7702_invalid_nonce_authorization( sender=relayer, # Sponsored transaction to=bob, value=10, - gas_limit=1_000_000, authorization_list=[ AuthorizationTuple( address=oracle, @@ -572,7 +564,6 @@ def test_bal_7702_invalid_authority_has_code_authorization( sender=relayer, # Sponsored transaction to=bob, value=10, - gas_limit=1_000_000, authorization_list=[ AuthorizationTuple( address=oracle, @@ -631,7 +622,6 @@ def test_bal_7702_invalid_chain_id_authorization( sender=relayer, # Sponsored transaction to=bob, value=10, - gas_limit=1_000_000, authorization_list=[ AuthorizationTuple( chain_id=999, # Wrong chain id @@ -710,8 +700,6 @@ def test_bal_7702_delegated_via_call_opcode( tx = Transaction( sender=bob, to=caller, # `bob` calls caller contract - gas_limit=10_000_000, - gas_price=0xA, ) block = Block( @@ -774,7 +762,6 @@ def test_bal_7702_multi_hop_delegation_chain( tx = Transaction( sender=alice, to=entry_address, - gas_limit=1_000_000, authorization_list=[ AuthorizationTuple( address=auth_b, @@ -884,7 +871,6 @@ def test_bal_7702_cross_tx_delegation_then_call( sender=relayer, to=bob, value=0, - gas_limit=1_000_000, authorization_list=[ AuthorizationTuple( address=counter, @@ -896,14 +882,10 @@ def test_bal_7702_cross_tx_delegation_then_call( tx_call_1 = Transaction( sender=bob, to=alice, - gas_limit=200_000, - gas_price=0xA, ) tx_call_2 = Transaction( sender=charlie, to=alice, - gas_limit=200_000, - gas_price=0xA, ) block = Block( @@ -970,7 +952,6 @@ def test_bal_7702_null_address_delegation_no_code_change( sender=alice, to=bob, value=10, - gas_limit=1_000_000, authorization_list=[ AuthorizationTuple( address=0, @@ -1050,7 +1031,6 @@ def test_bal_7702_double_auth_reset( sender=alice if self_funded else relayer, to=bob, value=10, - gas_limit=1_000_000, authorization_list=[ AuthorizationTuple( address=contract_a, @@ -1138,7 +1118,6 @@ def test_bal_7702_double_auth_swap( sender=relayer, to=bob, value=10, - gas_limit=1_000_000, authorization_list=[ AuthorizationTuple( address=contract_a, @@ -1239,7 +1218,6 @@ def test_bal_selfdestruct_to_7702_delegation( sender=relayer, to=bob, value=10, - gas_limit=1_000_000, authorization_list=[ AuthorizationTuple( address=oracle, @@ -1257,8 +1235,6 @@ def test_bal_selfdestruct_to_7702_delegation( nonce=1, sender=relayer, to=caller, - gas_limit=1_000_000, - gas_price=0xA, ) alice_final_balance = alice_initial_balance + victim_balance @@ -1371,7 +1347,6 @@ def test_bal_withdrawal_to_7702_delegation( sender=relayer, to=bob, value=10, - gas_limit=1_000_000, authorization_list=[ AuthorizationTuple( address=oracle, @@ -1502,7 +1477,6 @@ def test_bal_7702_delegated_create( tx = Transaction( sender=alice, to=deployer, - gas_limit=1_000_000, authorization_list=[ AuthorizationTuple( address=deployer, diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_invalid.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_invalid.py index 52d366019d8..9ae157823fc 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_invalid.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_invalid.py @@ -87,7 +87,6 @@ def test_bal_invalid_missing_nonce( sender=sender, to=receiver, value=10**15, - gas_limit=21_000, ) blockchain_test( @@ -132,7 +131,6 @@ def test_bal_invalid_nonce_value( sender=sender, to=receiver, value=10**15, - gas_limit=21_000, ) blockchain_test( @@ -180,11 +178,7 @@ def test_bal_invalid_storage_value( storage=storage.canary(), ) - tx = Transaction( - sender=sender, - to=contract, - gas_limit=100_000, - ) + tx = Transaction(sender=sender, to=contract) blockchain_test( pre=pre, @@ -259,14 +253,12 @@ def test_bal_invalid_tx_order( sender=sender1, to=receiver, value=10**15, - gas_limit=21_000, ) tx2 = Transaction( sender=sender2, to=receiver, value=2 * 10**15, - gas_limit=21_000, ) blockchain_test( @@ -332,7 +324,6 @@ def test_bal_invalid_account( sender=sender, to=receiver, value=10**15, - gas_limit=21_000, ) blockchain_test( @@ -390,7 +381,6 @@ def test_bal_invalid_duplicate_account( sender=sender, to=receiver, value=10**15, - gas_limit=21_000, ) blockchain_test( @@ -442,7 +432,6 @@ def test_bal_invalid_account_order( sender=sender, to=receiver, value=10**15, - gas_limit=21_000, ) blockchain_test( @@ -494,17 +483,12 @@ def test_bal_invalid_complex_corruption( storage=storage.canary(), ) - tx1 = Transaction( - sender=sender, - to=contract, - gas_limit=100_000, - ) + tx1 = Transaction(sender=sender, to=contract) tx2 = Transaction( sender=sender, to=receiver, value=10**15, - gas_limit=21_000, ) blockchain_test( @@ -603,7 +587,6 @@ def test_bal_invalid_missing_account( sender=sender, to=omitted, value=10**15, - gas_limit=21_000, ) post: dict = { sender: Account(balance=10**18, nonce=0), @@ -620,11 +603,7 @@ def test_bal_invalid_missing_account( elif scenario == "access_only": omitted = pre.fund_eoa(amount=1) checker = pre.deploy_contract(code=Op.BALANCE(omitted)) - tx = Transaction( - sender=sender, - to=checker, - gas_limit=100_000, - ) + tx = Transaction(sender=sender, to=checker) post = { sender: Account(balance=10**18, nonce=0), omitted: Account(balance=1), @@ -677,7 +656,6 @@ def test_bal_invalid_missing_withdrawal_account( sender=alice, to=bob, value=5, - gas_limit=21_000, ) blockchain_test( @@ -832,7 +810,6 @@ def test_bal_invalid_balance_value( sender=sender, to=receiver, value=10**15, - gas_limit=21_000, ) blockchain_test( @@ -1006,7 +983,6 @@ def test_bal_invalid_extraneous_entries( sender=alice, to=oracle, value=transfer_value, - gas_limit=1_000_000, ) blockchain_test( @@ -1121,7 +1097,6 @@ def test_bal_invalid_duplicate_entries( sender=alice, to=oracle, value=100, - gas_limit=2_000_000, ) blockchain_test( @@ -1205,7 +1180,6 @@ def test_bal_invalid_hash_mismatch( sender=sender, to=receiver, value=10**15, - gas_limit=21_000, ) blockchain_test( @@ -1289,7 +1263,6 @@ def test_bal_invalid_field_entries( sender=alice, to=oracle, value=100, - gas_limit=2_000_000, ) blockchain_test( diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py index 5fbbf6e51e6..658decefdde 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py @@ -359,11 +359,7 @@ def test_bal_account_touch_system_address( toucher = pre.deploy_contract(code=access_opcode(SYSTEM_ADDRESS) + Op.STOP) - tx = Transaction( - sender=alice, - to=toucher, - gas_limit=200_000, - ) + tx = Transaction(sender=alice, to=toucher) block = Block( txs=[tx], @@ -389,7 +385,6 @@ def test_bal_account_touch_system_address( def test_bal_selfdestruct_to_system_address_zero_balance( pre: Alloc, blockchain_test: BlockchainTestFiller, - fork: Fork, ) -> None: """ Ensure `SYSTEM_ADDRESS` is in BAL when accessed via `SELFDESTRUCT`, @@ -407,8 +402,6 @@ def test_bal_selfdestruct_to_system_address_zero_balance( to=None, # CREATE value=0, # zero contract balance at SELFDESTRUCT time data=init_code, - gas_limit=fork.transaction_gas_limit_cap(), - gas_price=10, ) block = Block( @@ -1882,11 +1875,7 @@ def test_bal_storage_write_read_same_frame( ) oracle = pre.deploy_contract(code=oracle_code, storage={0x01: 0x99}) - tx = Transaction( - sender=alice, - to=oracle, - gas_limit=1_000_000, - ) + tx = Transaction(sender=alice, to=oracle) block = Block( txs=[tx], @@ -1975,11 +1964,7 @@ def test_bal_storage_write_read_cross_frame( oracle = pre.deploy_contract(code=oracle_code, storage={0x01: 0x99}) - tx = Transaction( - sender=alice, - to=oracle, - gas_limit=1_000_000, - ) + tx = Transaction(sender=alice, to=oracle) block = Block( txs=[tx], @@ -2017,10 +2002,18 @@ def test_bal_storage_write_read_cross_frame( ) +@pytest.mark.parametrize( + "sufficient_gas", + [ + pytest.param(False, id="insufficient_gas"), + pytest.param(True, id="sufficient_gas"), + ], +) def test_bal_create_oog_code_deposit( pre: Alloc, blockchain_test: BlockchainTestFiller, fork: Fork, + sufficient_gas: bool, ) -> None: """ Ensure BAL correctly handles CREATE that runs out of gas during code @@ -2031,31 +2024,46 @@ def test_bal_create_oog_code_deposit( # create init code that returns a very large contract to force OOG deposited_len = 10_000 - initcode = Op.RETURN(0, deposited_len) + initcode = Op.RETURN( + 0, + deposited_len, + old_memory_size=0, + new_memory_size=deposited_len, + code_deposit_size=deposited_len, + ) + + create_code = Op.MSTORE( + 0, + Op.PUSH32(bytes(initcode)), + new_memory_size=len(initcode), + ) + Op.CREATE( + offset=32 - len(initcode), + size=len(initcode), + init_code_size=len(initcode), + ) + return_code = Op.PUSH1[0] + Op.MSTORE + Op.RETURN(0, 32) + factory_code = create_code + return_code - factory = pre.deploy_contract( - code=Op.MSTORE(0, Op.PUSH32(bytes(initcode))) - + Op.SSTORE( - 1, Op.CREATE(offset=32 - len(initcode), size=len(initcode)) - ) - + Op.STOP, - storage={1: 0xDEADBEEF}, - ) + factory = pre.deploy_contract(code=factory_code) contract_address = compute_create_address(address=factory, nonce=1) - intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator() - intrinsic_gas = intrinsic_gas_calculator( - calldata=b"", - contract_creation=False, - access_list=[], + initcode_cost = initcode.gas_cost(fork) + gas = (initcode_cost * 64 // 63) + create_code.gas_cost(fork) + if not sufficient_gas: + gas -= 1 + + entry_code = Op.SSTORE( + 0, Op.CALL(gas=gas, address=factory, ret_size=32) + ) + Op.SSTORE(1, Op.MLOAD(0)) + entry = pre.deploy_contract( + entry_code, storage={0: 0xDEADBEEF, 1: 0xDEADBEEF} ) - # NEW_ACCOUNT keeps the budget CPSB-agnostic but short of the deposit. tx = Transaction( sender=alice, - to=factory, - gas_limit=(intrinsic_gas + 500_000 + fork.gas_costs().NEW_ACCOUNT), + to=entry, + gas_limit=fork.transaction_gas_limit_cap(), # No state reservoir ) # BAL expectations: @@ -2063,23 +2071,56 @@ def test_bal_create_oog_code_deposit( # - Factory: nonce change (CREATE increments factory nonce) # - Contract address: empty changes (read during collision check, # nonce/code changes rolled back on OOG) + if sufficient_gas: + entry_storage_changes = [ + BalStorageSlot( + slot=0, + slot_changes=[ + BalStorageChange(block_access_index=1, post_value=1), + ], + ), + BalStorageSlot( + slot=1, + slot_changes=[ + # SSTORE saves address (CREATE succeeded) + BalStorageChange( + block_access_index=1, post_value=contract_address + ), + ], + ), + ] + else: + entry_storage_changes = [ + BalStorageSlot( + slot=0, + slot_changes=[ + BalStorageChange(block_access_index=1, post_value=1), + ], + ), + BalStorageSlot( + slot=1, + slot_changes=[ + # SSTORE saves 0 (CREATE failed) + BalStorageChange(block_access_index=1, post_value=0), + ], + ), + ] + account_expectations = { alice: BalAccountExpectation( nonce_changes=[BalNonceChange(block_access_index=1, post_nonce=1)], ), factory: BalAccountExpectation( nonce_changes=[BalNonceChange(block_access_index=1, post_nonce=2)], - storage_changes=[ - BalStorageSlot( - slot=1, - slot_changes=[ - # SSTORE saves 0 (CREATE failed) - BalStorageChange(block_access_index=1, post_value=0), - ], - ) - ], ), - contract_address: BalAccountExpectation.empty(), + entry: BalAccountExpectation( + storage_changes=entry_storage_changes, + ), + contract_address: BalAccountExpectation( + nonce_changes=[BalNonceChange(block_access_index=1, post_nonce=1)], + ) + if sufficient_gas + else BalAccountExpectation.empty(), } blockchain_test( @@ -2094,8 +2135,14 @@ def test_bal_create_oog_code_deposit( ], post={ alice: Account(nonce=1), - factory: Account(nonce=2, storage={1: 0}), - contract_address: Account.NONEXISTENT, + factory: Account(nonce=2), + entry: Account( + nonce=1, + storage={0: 1, 1: contract_address if sufficient_gas else 0}, + ), + contract_address: Account(nonce=1) + if sufficient_gas + else Account.NONEXISTENT, }, ) @@ -2128,11 +2175,7 @@ def test_bal_sstore_static_context( storage={0: 0xDEAD}, # non-zero so STATICCALL result (0) is detectable ) - tx = Transaction( - sender=alice, - to=contract_a, - gas_limit=2_000_000, - ) + tx = Transaction(sender=alice, to=contract_a) blockchain_test( pre=pre, @@ -2213,10 +2256,7 @@ def blockchain_test_under_static_call( ) tx = Transaction( - sender=alice, - to=static_caller, - gas_limit=2_000_000, - access_list=tx_access_list, + sender=alice, to=static_caller, access_list=tx_access_list ) # Inner call fails (returns 0) when forbidden opcodes are tested @@ -2496,11 +2536,7 @@ def test_bal_create_contract_init_revert( created_address = compute_create_address(address=factory, nonce=1) - tx = Transaction( - sender=alice, - to=caller, - gas_limit=500_000, - ) + tx = Transaction(sender=alice, to=caller) blockchain_test( pre=pre, @@ -2616,12 +2652,7 @@ def test_bal_call_revert_insufficient_funds( AccessList(address=delegation_target, storage_keys=[]) ) - tx = Transaction( - sender=alice, - to=caller, - gas_limit=1_000_000, - access_list=access_list, - ) + tx = Transaction(sender=alice, to=caller, access_list=access_list) account_expectations: Dict[Address, BalAccountExpectation | None] = { alice: BalAccountExpectation( @@ -2680,7 +2711,6 @@ def test_bal_call_revert_insufficient_funds( def test_bal_create_selfdestruct_to_self_with_call( pre: Alloc, blockchain_test: BlockchainTestFiller, - fork: Fork, ) -> None: """ Test BAL with init code that CALLs Oracle, writes storage, then @@ -2708,12 +2738,8 @@ def test_bal_create_selfdestruct_to_self_with_call( # 1. Calls Oracle (which writes to its slot 0x01) # 2. Writes 0x42 to own slot 0x01 # 3. Selfdestructs to self - # - # Forward enough gas for Oracle's first-time SSTORE - # (regular base + state gas, CPSB-agnostic). - oracle_call_gas = 100_000 + Op.SSTORE(new_value=1).state_cost(fork) initcode_runtime = ( - Op.CALL(oracle_call_gas, oracle, 0, 0, 0, 0, 0) + Op.CALL(address=oracle) + Op.POP # Write to own storage slot 0x01 + Op.SSTORE(0x01, 0x42) @@ -2774,18 +2800,7 @@ def test_bal_create_selfdestruct_to_self_with_call( opcode=Op.CREATE2, ) - # Budget for CREATE2 + 3 first-time SSTOREs, CPSB-agnostic via state gas. - gas_limit = ( - 1_000_000 - + fork.gas_costs().NEW_ACCOUNT - + 3 * Op.SSTORE(new_value=1).state_cost(fork) - ) - - tx = Transaction( - sender=alice, - to=factory, - gas_limit=gas_limit, - ) + tx = Transaction(sender=alice, to=factory) block = Block( txs=[tx], @@ -3063,11 +3078,7 @@ def test_bal_transient_storage_not_tracked( contract = pre.deploy_contract(code=contract_code) - tx = Transaction( - sender=alice, - to=contract, - gas_limit=1_000_000, - ) + tx = Transaction(sender=alice, to=contract) block = Block( txs=[tx], @@ -3464,11 +3475,7 @@ def test_bal_create_early_failure( opcode=create_opcode, ) - tx = Transaction( - sender=alice, - to=factory, - gas_limit=1_000_000, - ) + tx = Transaction(sender=alice, to=factory) block = Block( txs=[tx], @@ -3567,12 +3574,7 @@ def test_bal_create_storage_op_then_selfdestruct_same_tx( ) pre.fund_address(target_a, fund_amount) - tx = Transaction( - sender=alice, - to=factory, - data=initcode_bytes, - gas_limit=1_000_000, - ) + tx = Transaction(sender=alice, to=factory, data=initcode_bytes) block = Block( txs=[tx], @@ -3618,7 +3620,6 @@ def test_bal_create_storage_op_then_selfdestruct_same_tx( def test_bal_create2_selfdestruct_then_recreate_same_block( pre: Alloc, blockchain_test: BlockchainTestFiller, - fork: Fork, pre_balance: int, ) -> None: """ @@ -3673,20 +3674,8 @@ def test_bal_create2_selfdestruct_then_recreate_same_block( if pre_balance > 0: pre.fund_address(target_a, pre_balance) - # Headroom for the self-destruct to fund a fresh beneficiary. - gas_limit = (fork.transaction_gas_limit_cap() or 0) + 2_000_000 - tx1 = Transaction( - sender=alice, - to=factory, - data=initcode_bytes, - gas_limit=gas_limit, - ) - tx2 = Transaction( - sender=alice, - to=factory, - data=initcode_bytes, - gas_limit=gas_limit, - ) + tx1 = Transaction(sender=alice, to=factory, data=initcode_bytes) + tx2 = Transaction(sender=alice, to=factory, data=initcode_bytes) target_a_balance_changes = [] if pre_balance > 0: 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 ca41e6b9308..10931708eeb 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 @@ -38,8 +38,7 @@ def test_max_code_size_fork_transition( fork: TransitionFork, ) -> None: """Ensure the new max code size limit activates at the fork boundary.""" - post_fork = fork.transitions_to() - code_size = post_fork.max_code_size() + code_size = fork.transitions_to().max_code_size() deploy_code = Op.JUMPDEST * code_size initcode = Initcode(deploy_code=deploy_code) @@ -49,9 +48,6 @@ def test_max_code_size_fork_transition( create_address_pre = compute_create_address(address=alice, nonce=0) create_address_post = compute_create_address(address=bob, nonce=0) - post_fork_gas_limit = ( - post_fork.transaction_gas_limit_cap() or 0 - ) + post_fork.create_state_gas(code_size=code_size) blocks = [ Block( timestamp=14_999, @@ -60,7 +56,6 @@ def test_max_code_size_fork_transition( sender=alice, to=None, data=initcode, - gas_limit=fork.transitions_from().transaction_gas_limit_cap(), ) ], ), @@ -71,7 +66,6 @@ def test_max_code_size_fork_transition( sender=bob, to=None, data=initcode, - gas_limit=post_fork_gas_limit, ) ], ), @@ -93,8 +87,7 @@ def test_max_code_size_via_create_fork_transition( create_opcode: Op, ) -> None: """Ensure the new max code size limit activates at the fork via opcodes.""" - post_fork = fork.transitions_to() - code_size = post_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) @@ -142,7 +135,6 @@ def test_max_code_size_via_create_fork_transition( sender=alice, to=factory_pre, data=initcode_bytes, - gas_limit=fork.transitions_from().transaction_gas_limit_cap(), ) ], ), @@ -153,10 +145,6 @@ def test_max_code_size_via_create_fork_transition( sender=bob, to=factory_post, data=initcode_bytes, - gas_limit=( - (post_fork.transaction_gas_limit_cap() or 0) - + post_fork.create_state_gas(code_size=code_size) - ), ) ], ), @@ -199,7 +187,6 @@ def test_max_initcode_size_fork_transition( sender=alice, to=None, data=initcode, - gas_limit=fork.transitions_from().transaction_gas_limit_cap(), error=initcode_too_large, ) ], @@ -213,7 +200,6 @@ def test_max_initcode_size_fork_transition( sender=bob, to=None, data=initcode, - gas_limit=fork.transitions_to().transaction_gas_limit_cap(), ) ], ), @@ -283,7 +269,6 @@ def test_max_initcode_size_via_create_fork_transition( sender=alice, to=factory_pre, data=initcode_bytes, - gas_limit=fork.transitions_from().transaction_gas_limit_cap(), ) ], ), @@ -294,7 +279,6 @@ def test_max_initcode_size_via_create_fork_transition( sender=bob, to=factory_post, data=initcode_bytes, - gas_limit=fork.transitions_to().transaction_gas_limit_cap(), ) ], ), @@ -319,12 +303,10 @@ def test_max_code_size_with_max_initcode_fork_transition( fork: TransitionFork, ) -> None: """Ensure max code + max initcode activates at the fork boundary.""" - post_fork = fork.transitions_to() - code_size = post_fork.max_code_size() - deploy_code = Op.JUMPDEST * code_size + deploy_code = Op.JUMPDEST * fork.transitions_to().max_code_size() initcode = Initcode( deploy_code=deploy_code, - initcode_length=post_fork.max_initcode_size(), + initcode_length=fork.transitions_to().max_initcode_size(), ) alice = pre.fund_eoa() @@ -342,7 +324,6 @@ def test_max_code_size_with_max_initcode_fork_transition( sender=alice, to=None, data=initcode, - gas_limit=fork.transitions_from().transaction_gas_limit_cap(), error=initcode_too_large, ) ], @@ -355,10 +336,6 @@ def test_max_code_size_with_max_initcode_fork_transition( sender=bob, to=None, data=initcode, - gas_limit=( - (post_fork.transaction_gas_limit_cap() or 0) - + post_fork.create_state_gas(code_size=code_size) - ), ) ], ), @@ -380,7 +357,6 @@ def test_parent_max_code_size_across_fork( parent = fork.transitions_from() assert parent is not None, "Parent fork must be defined for this test" - post_fork = fork.transitions_to() code_size = parent.max_code_size() deploy_code = Op.JUMPDEST * code_size initcode = Initcode(deploy_code=deploy_code) @@ -399,7 +375,6 @@ def test_parent_max_code_size_across_fork( sender=alice, to=None, data=initcode, - gas_limit=fork.transitions_from().transaction_gas_limit_cap(), ) ], ), @@ -410,10 +385,6 @@ def test_parent_max_code_size_across_fork( sender=bob, to=None, data=initcode, - gas_limit=( - (post_fork.transaction_gas_limit_cap() or 0) - + post_fork.create_state_gas(code_size=code_size) - ), ) ], ), 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 d49848e9492..44bcc33e84b 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 @@ -52,10 +52,6 @@ def test_max_code_size( sender=alice, to=None, data=initcode, - gas_limit=( - (fork.transaction_gas_limit_cap() or 0) - + fork.create_state_gas(code_size=code_size) - ), ) post: dict[Any, Account | None] = {} @@ -112,10 +108,6 @@ def test_max_code_size_via_create( sender=alice, to=factory, data=initcode_bytes, - gas_limit=( - (fork.transaction_gas_limit_cap() or 0) - + fork.create_state_gas(code_size=code_size) - ), ) created = code_size <= fork.max_code_size() @@ -184,8 +176,7 @@ def test_max_code_size_with_max_initcode( fork: Fork, ) -> None: """Ensure max-size code deploys when initcode is also at max size.""" - code_size = fork.max_code_size() - deploy_code = Op.JUMPDEST * code_size + deploy_code = Op.JUMPDEST * fork.max_code_size() initcode = Initcode( deploy_code=deploy_code, initcode_length=fork.max_initcode_size(), @@ -198,10 +189,6 @@ def test_max_code_size_with_max_initcode( sender=alice, to=None, data=initcode, - gas_limit=( - (fork.transaction_gas_limit_cap() or 0) - + fork.create_state_gas(code_size=code_size) - ), ) post = {create_address: Account(code=deploy_code)} diff --git a/tests/amsterdam/eip8024_dupn_swapn_exchange/test_dupn.py b/tests/amsterdam/eip8024_dupn_swapn_exchange/test_dupn.py index d3fccb9ad1c..3339b343409 100644 --- a/tests/amsterdam/eip8024_dupn_swapn_exchange/test_dupn.py +++ b/tests/amsterdam/eip8024_dupn_swapn_exchange/test_dupn.py @@ -61,7 +61,7 @@ def test_dupn_basic( contract_address = pre.deploy_contract(code=code) - tx = Transaction(to=contract_address, sender=sender, gas_limit=1_000_000) + tx = Transaction(to=contract_address, sender=sender) post = {contract_address: Account(storage={0: expected_value})} @@ -101,7 +101,7 @@ def test_dupn_valid_immediates( contract_address = pre.deploy_contract(code=code) - tx = Transaction(to=contract_address, sender=sender, gas_limit=10_000_000) + tx = Transaction(to=contract_address, sender=sender) post = {contract_address: Account(storage={0: expected_value})} @@ -136,7 +136,7 @@ def test_dupn_stack_underflow( contract_address = pre.deploy_contract(code=code) - tx = Transaction(to=contract_address, sender=sender, gas_limit=1_000_000) + tx = Transaction(to=contract_address, sender=sender) # Transaction should fail, contract storage unchanged post = {contract_address: Account(storage={0: 0})} @@ -183,7 +183,7 @@ def test_dupn_gas_cost_boundary( storage={0: 0xDEADBEEF}, ) - tx = Transaction(to=call_address, sender=pre.fund_eoa(), gas_limit=200_000) + tx = Transaction(to=call_address, sender=pre.fund_eoa()) post = {call_address: Account(storage={0: 0 if gas_cost_delta < 0 else 1})} @@ -225,7 +225,7 @@ def test_endofcode_behavior( contract_address = pre.deploy_contract(code=code) - tx = Transaction(to=contract_address, sender=sender, gas_limit=1_000_000) + tx = Transaction(to=contract_address, sender=sender) # If tx succeeds, storage[0] = marker_value # Bad implementation would revert and have empty storage @@ -269,7 +269,7 @@ def test_dupn_invalid_immediate_aborts( contract_address = pre.deploy_contract(code=code) - tx = Transaction(to=contract_address, sender=sender, gas_limit=10_000_000) + tx = Transaction(to=contract_address, sender=sender) # Transaction should fail - invalid immediate causes abort post = {contract_address: Account(storage={})} @@ -305,7 +305,7 @@ def test_dupn_jump_to_immediate_byte_0x5b_succeeds( contract_address = pre.deploy_contract(code=code) - tx = Transaction(to=contract_address, sender=sender, gas_limit=1_000_000) + tx = Transaction(to=contract_address, sender=sender) # Transaction succeeds - 0x5b is preserved as valid JUMPDEST post = {contract_address: Account(storage={0: 0x42})} @@ -340,7 +340,7 @@ def test_dupn_jump_to_valid_immediate_fails( contract_address = pre.deploy_contract(code=code) - tx = Transaction(to=contract_address, sender=sender, gas_limit=1_000_000) + tx = Transaction(to=contract_address, sender=sender) # Transaction fails - position 4 is a valid immediate, not JUMPDEST post = {contract_address: Account(storage={})} @@ -383,7 +383,7 @@ def test_dupn_with_dup1_sequence( contract_address = pre.deploy_contract(code=code) - tx = Transaction(to=contract_address, sender=sender, gas_limit=1_000_000) + tx = Transaction(to=contract_address, sender=sender) # Expected: top (position 0) = 1, bottom (position 17) = 1, all others = 0 expected_storage = {} diff --git a/tests/amsterdam/eip8024_dupn_swapn_exchange/test_eip_vectors.py b/tests/amsterdam/eip8024_dupn_swapn_exchange/test_eip_vectors.py index 382d6d540d7..7ee9c1bf4d3 100644 --- a/tests/amsterdam/eip8024_dupn_swapn_exchange/test_eip_vectors.py +++ b/tests/amsterdam/eip8024_dupn_swapn_exchange/test_eip_vectors.py @@ -57,7 +57,7 @@ def test_eip_vector_dupn_duplicate_bottom( code += Op.STOP contract_address = pre.deploy_contract(code=code) - tx = Transaction(to=contract_address, sender=sender, gas_limit=1_000_000) + tx = Transaction(to=contract_address, sender=sender) post = { contract_address: Account( @@ -111,7 +111,7 @@ def test_eip_vector_swapn_swap_with_bottom( code += Op.STOP contract_address = pre.deploy_contract(code=code) - tx = Transaction(to=contract_address, sender=sender, gas_limit=1_000_000) + tx = Transaction(to=contract_address, sender=sender) post = { contract_address: Account( @@ -152,7 +152,7 @@ def test_eip_vector_exchange_swap_positions( code += Op.STOP contract_address = pre.deploy_contract(code=code) - tx = Transaction(to=contract_address, sender=sender, gas_limit=1_000_000) + tx = Transaction(to=contract_address, sender=sender) post = { contract_address: Account( @@ -190,7 +190,7 @@ def test_eip_vector_swapn_invalid_immediate_reverts( assert bytes(code) == bytes.fromhex("e75b") contract_address = pre.deploy_contract(code=code) - tx = Transaction(to=contract_address, sender=sender, gas_limit=1_000_000) + tx = Transaction(to=contract_address, sender=sender) # Transaction should fail, storage unchanged post = {contract_address: Account(storage={})} @@ -230,7 +230,7 @@ def test_eip_vector_jump_over_invalid_dupn( code += Op.STOP contract_address = pre.deploy_contract(code=code) - tx = Transaction(to=contract_address, sender=sender, gas_limit=1_000_000) + tx = Transaction(to=contract_address, sender=sender) # Transaction should succeed post = {contract_address: Account(storage={0: 1})} @@ -266,7 +266,7 @@ def test_eip_vector_exchange_with_iszero( code += Op.STOP contract_address = pre.deploy_contract(code=code) - tx = Transaction(to=contract_address, sender=sender, gas_limit=1_000_000) + tx = Transaction(to=contract_address, sender=sender) post = { contract_address: Account( @@ -308,7 +308,7 @@ def test_eip_vector_dupn_stack_underflow( code += Op.STOP contract_address = pre.deploy_contract(code=code) - tx = Transaction(to=contract_address, sender=sender, gas_limit=1_000_000) + tx = Transaction(to=contract_address, sender=sender) # Transaction should fail, storage unchanged post = {contract_address: Account(storage={})} @@ -346,7 +346,7 @@ def test_vector_dupn_followed_by_jumpdest( code += Op.STOP contract_address = pre.deploy_contract(code=code) - tx = Transaction(to=contract_address, sender=sender, gas_limit=1_000_000) + tx = Transaction(to=contract_address, sender=sender) # DUPN should duplicate position 17 (marker_value) post = {contract_address: Account(storage={0: marker_value})} @@ -381,7 +381,7 @@ def test_vector_dupn_invalid_0x60( code += Op.STOP contract_address = pre.deploy_contract(code=code) - tx = Transaction(to=contract_address, sender=sender, gas_limit=10_000_000) + tx = Transaction(to=contract_address, sender=sender) # Transaction should fail, storage unchanged post = {contract_address: Account(storage={})} @@ -416,7 +416,7 @@ def test_vector_swapn_invalid_0x61( code += Op.STOP contract_address = pre.deploy_contract(code=code) - tx = Transaction(to=contract_address, sender=sender, gas_limit=10_000_000) + tx = Transaction(to=contract_address, sender=sender) # Transaction should fail, storage unchanged post = {contract_address: Account(storage={})} @@ -450,7 +450,7 @@ def test_vector_dupn_invalid_0x5f( code += Op.STOP contract_address = pre.deploy_contract(code=code) - tx = Transaction(to=contract_address, sender=sender, gas_limit=10_000_000) + tx = Transaction(to=contract_address, sender=sender) # Transaction should fail, storage unchanged post = {contract_address: Account(storage={})} @@ -490,7 +490,7 @@ def test_vector_exchange_0x9d( code += Op.STOP contract_address = pre.deploy_contract(code=code) - tx = Transaction(to=contract_address, sender=sender, gas_limit=1_000_000) + tx = Transaction(to=contract_address, sender=sender) # After EXCHANGE[0x9d]: positions 3 and 4 are swapped post = { @@ -544,7 +544,7 @@ def test_vector_exchange_0x2f( code += Op.STOP contract_address = pre.deploy_contract(code=code) - tx = Transaction(to=contract_address, sender=sender, gas_limit=1_000_000) + tx = Transaction(to=contract_address, sender=sender) # After EXCHANGE[0x2f]: positions 2 and 20 are swapped post = { @@ -596,7 +596,7 @@ def test_vector_exchange_valid_0x50( code += Op.STOP contract_address = pre.deploy_contract(code=code) - tx = Transaction(to=contract_address, sender=sender, gas_limit=1_000_000) + tx = Transaction(to=contract_address, sender=sender) post = { contract_address: Account( @@ -644,7 +644,7 @@ def test_vector_exchange_valid_0x51( code += Op.STOP contract_address = pre.deploy_contract(code=code) - tx = Transaction(to=contract_address, sender=sender, gas_limit=1_000_000) + tx = Transaction(to=contract_address, sender=sender) post = { contract_address: Account( @@ -694,7 +694,7 @@ def test_eip_vector_exchange_end_of_code( contract_address = pre.deploy_contract( code=Op.SSTORE(0, 0x42) + code + Op.STOP ) - tx = Transaction(to=contract_address, sender=sender, gas_limit=1_000_000) + tx = Transaction(to=contract_address, sender=sender) # Verify marker was stored (tx succeeded) post = {contract_address: Account(storage={0: 0x42})} @@ -737,7 +737,7 @@ def test_eip_vector_exchange_30_items( code += Op.STOP contract_address = pre.deploy_contract(code=code) - tx = Transaction(to=contract_address, sender=sender, gas_limit=1_000_000) + tx = Transaction(to=contract_address, sender=sender) post = { contract_address: Account( @@ -778,7 +778,7 @@ def test_vector_exchange_invalid_0x52( code += Op.STOP contract_address = pre.deploy_contract(code=code) - tx = Transaction(to=contract_address, sender=sender, gas_limit=1_000_000) + tx = Transaction(to=contract_address, sender=sender) # Transaction should fail, storage unchanged post = {contract_address: Account(storage={})} @@ -824,7 +824,7 @@ def test_eip_vector_end_of_code( ) contract_address = pre.deploy_contract(code=code) - tx = Transaction(to=contract_address, sender=sender, gas_limit=1_000_000) + tx = Transaction(to=contract_address, sender=sender) # verify marker was stored (tx succeeded) post = {contract_address: Account(storage={0: marker_value})} diff --git a/tests/amsterdam/eip8024_dupn_swapn_exchange/test_endofcode_underflow.py b/tests/amsterdam/eip8024_dupn_swapn_exchange/test_endofcode_underflow.py index b07d8622bbc..cfb88b2054b 100644 --- a/tests/amsterdam/eip8024_dupn_swapn_exchange/test_endofcode_underflow.py +++ b/tests/amsterdam/eip8024_dupn_swapn_exchange/test_endofcode_underflow.py @@ -72,7 +72,7 @@ def test_end_of_code_stack_underflow( ) contract_address = pre.deploy_contract(code=code) - tx = Transaction(to=contract_address, sender=sender, gas_limit=1_000_000) + tx = Transaction(to=contract_address, sender=sender) # Transaction must fail (stack underflow), leaving storage untouched. post = {contract_address: Account(storage={})} diff --git a/tests/amsterdam/eip8024_dupn_swapn_exchange/test_exchange.py b/tests/amsterdam/eip8024_dupn_swapn_exchange/test_exchange.py index 3ab77031139..ae63cbfb4f9 100644 --- a/tests/amsterdam/eip8024_dupn_swapn_exchange/test_exchange.py +++ b/tests/amsterdam/eip8024_dupn_swapn_exchange/test_exchange.py @@ -75,11 +75,7 @@ def test_exchange_basic( contract_address = pre.deploy_contract(code=code) - gas_limit = 1_000_000 - if fork.is_eip_enabled(8037): - gas_limit = 5_000_000 - - tx = Transaction(to=contract_address, sender=sender, gas_limit=gas_limit) + tx = Transaction(to=contract_address, sender=sender) # Build expected storage expected_storage = {} @@ -142,11 +138,7 @@ def test_exchange_valid_immediates( contract_address = pre.deploy_contract(code=code) - gas_limit = 1_000_000 - if fork.is_eip_enabled(8037): - gas_limit = 5_000_000 - - tx = Transaction(to=contract_address, sender=sender, gas_limit=gas_limit) + tx = Transaction(to=contract_address, sender=sender) # Build expected storage expected_storage = {} @@ -199,7 +191,7 @@ def test_exchange_preserves_other_items( contract_address = pre.deploy_contract(code=code) - tx = Transaction(to=contract_address, sender=sender, gas_limit=1_000_000) + tx = Transaction(to=contract_address, sender=sender) post = { contract_address: Account( @@ -246,7 +238,7 @@ def test_exchange_stack_underflow( code += Op.STOP contract_address = pre.deploy_contract(code=code) - tx = Transaction(to=contract_address, sender=sender, gas_limit=1_000_000) + tx = Transaction(to=contract_address, sender=sender) # Transaction should fail, contract storage unchanged post = {contract_address: Account(storage={})} @@ -298,7 +290,7 @@ def test_exchange_gas_cost_boundary( storage={0: 0xDEADBEEF}, ) - tx = Transaction(to=call_address, sender=pre.fund_eoa(), gas_limit=200_000) + tx = Transaction(to=call_address, sender=pre.fund_eoa()) post = {call_address: Account(storage={0: 0 if gas_cost_delta < 0 else 1})} @@ -340,7 +332,7 @@ def test_endofcode_behavior( contract_address = pre.deploy_contract(code=code) - tx = Transaction(to=contract_address, sender=sender, gas_limit=1_000_000) + tx = Transaction(to=contract_address, sender=sender) # If tx succeeds, storage[0] = marker_value # Bad implementation would revert and have empty storage @@ -395,7 +387,7 @@ def test_exchange_jump_to_immediate_byte( code += Op.STOP contract_address = pre.deploy_contract(code=code) - tx = Transaction(to=contract_address, sender=sender, gas_limit=1_000_000) + tx = Transaction(to=contract_address, sender=sender) if immediate == 0x5B: # JUMPDEST - only case where jump succeeds post = {contract_address: Account(storage={0: 0x42})} @@ -440,7 +432,7 @@ def test_exchange_with_push_sequence( contract_address = pre.deploy_contract(code=code) - tx = Transaction(to=contract_address, sender=sender, gas_limit=1_000_000) + tx = Transaction(to=contract_address, sender=sender) # Expected: position 9 has 0xBBBB (from pos 17), position 16 has # 0xAAAA (from pos 10), rest = 0 @@ -488,7 +480,7 @@ def test_exchange_invalid_immediate_aborts( code += Op.STOP contract_address = pre.deploy_contract(code=code) - tx = Transaction(to=contract_address, sender=sender, gas_limit=1_000_000) + tx = Transaction(to=contract_address, sender=sender) # Execution aborted, transaction reverts post = {contract_address: Account(storage={})} diff --git a/tests/amsterdam/eip8024_dupn_swapn_exchange/test_pc_advancement.py b/tests/amsterdam/eip8024_dupn_swapn_exchange/test_pc_advancement.py index b98cf813e44..78b41f9792f 100644 --- a/tests/amsterdam/eip8024_dupn_swapn_exchange/test_pc_advancement.py +++ b/tests/amsterdam/eip8024_dupn_swapn_exchange/test_pc_advancement.py @@ -67,7 +67,7 @@ def test_dupn_pc_advances_by_2( code += Op.STOP contract_address = pre.deploy_contract(code=code) - tx = Transaction(to=contract_address, sender=sender, gas_limit=1_000_000) + tx = Transaction(to=contract_address, sender=sender) # The difference should be: # PUSH1(2) + SSTORE(1) + DUPN(2) + PC(1) = 6 @@ -127,7 +127,7 @@ def test_swapn_pc_advances_by_2( code += Op.STOP contract_address = pre.deploy_contract(code=code) - tx = Transaction(to=contract_address, sender=sender, gas_limit=1_000_000) + tx = Transaction(to=contract_address, sender=sender) post = { contract_address: Account( @@ -183,7 +183,7 @@ def test_exchange_pc_advances_by_2( code += Op.STOP contract_address = pre.deploy_contract(code=code) - tx = Transaction(to=contract_address, sender=sender, gas_limit=1_000_000) + tx = Transaction(to=contract_address, sender=sender) post = { contract_address: Account( @@ -241,7 +241,7 @@ def test_dupn_multiple_consecutive_pc_advancement( code += Op.STOP contract_address = pre.deploy_contract(code=code) - tx = Transaction(to=contract_address, sender=sender, gas_limit=1_000_000) + tx = Transaction(to=contract_address, sender=sender) post = { contract_address: Account( @@ -299,7 +299,7 @@ def test_mixed_opcodes_pc_advancement( code += Op.STOP contract_address = pre.deploy_contract(code=code) - tx = Transaction(to=contract_address, sender=sender, gas_limit=1_000_000) + tx = Transaction(to=contract_address, sender=sender) post = { contract_address: Account( diff --git a/tests/amsterdam/eip8024_dupn_swapn_exchange/test_swapn.py b/tests/amsterdam/eip8024_dupn_swapn_exchange/test_swapn.py index 7103c75f6dc..adef348ccf7 100644 --- a/tests/amsterdam/eip8024_dupn_swapn_exchange/test_swapn.py +++ b/tests/amsterdam/eip8024_dupn_swapn_exchange/test_swapn.py @@ -12,7 +12,6 @@ Bytecode, EIPChecklist, Fork, - Header, Op, StateTestFiller, Transaction, @@ -73,7 +72,7 @@ def test_swapn_basic( contract_address = pre.deploy_contract(code=code) - tx = Transaction(to=contract_address, sender=sender, gas_limit=1_000_000) + tx = Transaction(to=contract_address, sender=sender) post = { contract_address: Account( @@ -129,7 +128,7 @@ def test_swapn_valid_immediates( contract_address = pre.deploy_contract(code=code) - tx = Transaction(to=contract_address, sender=sender, gas_limit=10_000_000) + tx = Transaction(to=contract_address, sender=sender) post = { contract_address: Account( @@ -145,7 +144,6 @@ def test_swapn_valid_immediates( def test_swapn_preserves_other_stack_items( pre: Alloc, state_test: StateTestFiller, - fork: Fork, ) -> None: """Test SWAPN only swaps the specified items, leaving others unchanged.""" sender = pre.fund_eoa() @@ -155,16 +153,6 @@ def test_swapn_preserves_other_stack_items( stack_index = 17 stack_height = stack_index + 1 # Need 18 items - # Compute expected storage values (post-swap stack reads). - expected_storage: dict = {} - for i in range(stack_height): - if i == 0: - expected_storage[i] = 0x1000 # Was at bottom, now at top - elif i == stack_height - 1: - expected_storage[i] = 0x1011 # Was at top, now at bottom - else: - expected_storage[i] = 0x1000 + (stack_height - 1 - i) - # Create a stack with 18 distinct values code = Bytecode() for i in range(stack_height): @@ -174,39 +162,31 @@ def test_swapn_preserves_other_stack_items( # Pass stack index directly - encoder will handle encoding code += Op.SWAPN[stack_index] - # Store all values; metadata pins each slot's 0->non-zero - # transition so `code.gas_cost(fork)` accounts for SSTORE state - # gas under EIP-8037. + # Store all values to verify only the swapped ones changed for i in range(stack_height): - code += Op.PUSH1(i) + Op.SSTORE.with_metadata( - key_warm=False, - original_value=0, - current_value=0, - new_value=expected_storage[i], - ) + code += Op.PUSH1(i) + Op.SSTORE code += Op.STOP contract_address = pre.deploy_contract(code=code) - intrinsic_cost = fork.transaction_intrinsic_cost_calculator()() - code_state = code.state_cost(fork) - code_regular = code.gas_cost(fork) - code_state + tx = Transaction(to=contract_address, sender=sender) - tx = Transaction( - to=contract_address, - sender=sender, - gas_limit=intrinsic_cost + code_regular + code_state, - ) + # After swap: position 1 and position 18 are swapped + # Original stack (top to bottom): 0x1011, 0x1010, ..., 0x1001, 0x1000 + # After SWAPN[0]: 0x1000, 0x1010, ..., 0x1001, 0x1011 + expected_storage = {} + for i in range(stack_height): + if i == 0: + expected_storage[i] = 0x1000 # Was at bottom, now at top + elif i == stack_height - 1: + expected_storage[i] = 0x1011 # Was at top, now at bottom + else: + expected_storage[i] = 0x1000 + (stack_height - 1 - i) - expected_gas_used = max(intrinsic_cost + code_regular, code_state) + post = {contract_address: Account(storage=expected_storage)} - state_test( - pre=pre, - post={contract_address: Account(storage=expected_storage)}, - tx=tx, - blockchain_test_header_verify=Header(gas_used=expected_gas_used), - ) + state_test(pre=pre, post=post, tx=tx) def test_swapn_stack_underflow( @@ -228,7 +208,7 @@ def test_swapn_stack_underflow( contract_address = pre.deploy_contract(code=code) - tx = Transaction(to=contract_address, sender=sender, gas_limit=1_000_000) + tx = Transaction(to=contract_address, sender=sender) # Transaction should fail, contract storage unchanged post = {contract_address: Account(storage={})} @@ -277,7 +257,7 @@ def test_swapn_gas_cost_boundary( storage={0: 0xDEADBEEF}, ) - tx = Transaction(to=call_address, sender=pre.fund_eoa(), gas_limit=200_000) + tx = Transaction(to=call_address, sender=pre.fund_eoa()) post = {call_address: Account(storage={0: 0 if gas_cost_delta < 0 else 1})} @@ -321,7 +301,7 @@ def test_swapn_invalid_immediate_aborts( contract_address = pre.deploy_contract(code=code) - tx = Transaction(to=contract_address, sender=sender, gas_limit=10_000_000) + tx = Transaction(to=contract_address, sender=sender) # Transaction should fail - invalid immediate causes abort. post = {contract_address: Account(storage={})} @@ -364,7 +344,7 @@ def test_endofcode_behavior( contract_address = pre.deploy_contract(code=code) - tx = Transaction(to=contract_address, sender=sender, gas_limit=1_000_000) + tx = Transaction(to=contract_address, sender=sender) # If tx succeeds, storage[0] = marker_value # Bad implementation would revert and have empty storage @@ -401,7 +381,7 @@ def test_swapn_jump_to_immediate_byte_0x5b_succeeds( contract_address = pre.deploy_contract(code=code) - tx = Transaction(to=contract_address, sender=sender, gas_limit=1_000_000) + tx = Transaction(to=contract_address, sender=sender) # Transaction succeeds - 0x5b is preserved as valid JUMPDEST post = {contract_address: Account(storage={0: 0x42})} @@ -436,7 +416,7 @@ def test_swapn_jump_to_valid_immediate_fails( contract_address = pre.deploy_contract(code=code) - tx = Transaction(to=contract_address, sender=sender, gas_limit=1_000_000) + tx = Transaction(to=contract_address, sender=sender) # Transaction fails - position 4 is a valid immediate, not JUMPDEST post = {contract_address: Account(storage={})} @@ -480,7 +460,7 @@ def test_swapn_with_dup1_and_push( contract_address = pre.deploy_contract(code=code) - tx = Transaction(to=contract_address, sender=sender, gas_limit=1_000_000) + tx = Transaction(to=contract_address, sender=sender) # Expected: top (position 0) = 1, bottom (position 17) = 2, rest = 0 expected_storage = {} diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py index 0299ce309ba..a657275e98d 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py @@ -237,8 +237,6 @@ def test_block_gas_refund_eip7778_no_block_reduction( (EIP-7778). State gas refund goes to the reservoir and DOES reduce `block_state_gas_used` (net zero state growth). """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() @@ -258,7 +256,7 @@ def test_block_gas_refund_eip7778_no_block_reduction( txs.append( Transaction( to=contract, - gas_limit=gas_limit_cap + sstore_state_gas, + state_gas_reservoir=sstore_state_gas, sender=pre.fund_eoa(), ) ) @@ -364,8 +362,6 @@ def test_block_gas_used_call_new_account( GAS_NEW_ACCOUNT state gas) then SSTORE. Combined with a STOP tx, the 2D max must reflect state gas from account creation. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None new_account_state_gas = fork.gas_costs().NEW_ACCOUNT sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) @@ -383,9 +379,7 @@ def test_block_gas_used_call_new_account( txs = [ Transaction( to=parent, - gas_limit=( - gas_limit_cap + new_account_state_gas + sstore_state_gas - ), + state_gas_reservoir=new_account_state_gas + sstore_state_gas, sender=pre.fund_eoa(), ), ] + stop_txs(pre, fork, 1) @@ -409,8 +403,6 @@ def test_block_gas_used_create_tx( Contract creation charges GAS_NEW_ACCOUNT as intrinsic state gas. Combined with a STOP tx, verify the 2D max is correct. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None intrinsic_calc = fork.transaction_intrinsic_cost_calculator() create_state_gas = fork.create_state_gas(code_size=0) @@ -430,7 +422,7 @@ def test_block_gas_used_create_tx( Transaction( to=None, data=init_code, - gas_limit=gas_limit_cap + create_state_gas, + state_gas_reservoir=create_state_gas, sender=pre.fund_eoa(), ), ] + stop_txs(pre, fork, 1) @@ -624,9 +616,7 @@ def test_receipt_cumulative_differs_from_header_gas_used( tx_regular, tx_state = sstore_tx_gas(fork) num_txs = 3 - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - tx_gas_limit = gas_limit_cap + Op.SSTORE(new_value=1).state_cost(fork) + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) per_tx_gas_used = tx_regular + tx_state txs: list[Transaction] = [] @@ -639,7 +629,7 @@ def test_receipt_cumulative_differs_from_header_gas_used( txs.append( Transaction( to=contract, - gas_limit=tx_gas_limit, + state_gas_reservoir=sstore_state_gas, sender=pre.fund_eoa(), expected_receipt=TransactionReceipt( cumulative_gas_used=(i + 1) * per_tx_gas_used, diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_eip_mainnet.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_eip_mainnet.py index bee5ed11565..f01b048ff3a 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_eip_mainnet.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_eip_mainnet.py @@ -7,7 +7,6 @@ from execution_testing import ( Account, Alloc, - Fork, Op, StateTestFiller, Storage, @@ -25,11 +24,8 @@ def test_sstore_zero_to_nonzero( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test SSTORE zero-to-nonzero charges state gas and succeeds.""" - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None storage = Storage() contract = pre.deploy_contract( code=Op.SSTORE(storage.store_next(1), 1), @@ -37,7 +33,7 @@ def test_sstore_zero_to_nonzero( tx = Transaction( to=contract, - gas_limit=gas_limit_cap, + state_gas_reservoir=0, sender=pre.fund_eoa(), ) @@ -48,11 +44,8 @@ def test_sstore_zero_to_nonzero( def test_create_charges_state_gas( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test CREATE charges state gas for new account creation.""" - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None init_code = Op.STOP storage = Storage() @@ -72,7 +65,7 @@ def test_create_charges_state_gas( tx = Transaction( to=contract, - gas_limit=gas_limit_cap, + state_gas_reservoir=0, sender=pre.fund_eoa(), ) @@ -83,15 +76,12 @@ def test_create_charges_state_gas( def test_create_tx_deploys_contract( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test contract creation transaction succeeds with state gas.""" - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None tx = Transaction( to=None, data=Op.STOP, - gas_limit=gas_limit_cap, + state_gas_reservoir=0, sender=pre.fund_eoa(), ) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py index a31c1cdfd54..d90bfbcb63b 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py @@ -22,7 +22,6 @@ Block, BlockchainTestFiller, Bytecode, - Environment, Fork, Header, Op, @@ -53,9 +52,6 @@ def test_child_call_uses_reservoir( (zero-to-nonzero). The state gas for the SSTORE is drawn from the reservoir passed from the parent. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - env = Environment() sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) child_storage = Storage() @@ -75,7 +71,7 @@ def test_child_call_uses_reservoir( tx = Transaction( to=parent, - gas_limit=gas_limit_cap + sstore_state_gas, + state_gas_reservoir=sstore_state_gas, sender=pre.fund_eoa(), ) @@ -83,14 +79,13 @@ def test_child_call_uses_reservoir( parent: Account(storage=parent_storage), child: Account(storage=child_storage), } - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) @pytest.mark.valid_from("EIP8037") def test_delegatecall_child_spill_not_double_charged( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """ Test DELEGATECALL child state gas paid from `gas_left` is not recharged. @@ -100,8 +95,6 @@ def test_delegatecall_child_spill_not_double_charged( `gas_left`. The parent frame must not charge the same state growth again at frame end. """ - env = Environment() - child_code = sum(Op.SSTORE(i, i + 1) for i in range(6)) + Op.STOP child = pre.deploy_contract(code=child_code) @@ -127,7 +120,7 @@ def test_delegatecall_child_spill_not_double_charged( post = { caller: Account(storage={i: i + 1 for i in range(6)}), } - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) @pytest.mark.valid_from("EIP8037") @@ -142,9 +135,6 @@ def test_reservoir_returned_on_revert( The child contract reverts. The parent should recover the reservoir and be able to use it for its own SSTORE. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - env = Environment() sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) child = pre.deploy_contract(code=Op.REVERT(0, 0)) @@ -161,12 +151,12 @@ def test_reservoir_returned_on_revert( tx = Transaction( to=parent, - gas_limit=gas_limit_cap + sstore_state_gas, + state_gas_reservoir=sstore_state_gas, sender=pre.fund_eoa(), ) post = {parent: Account(storage=parent_storage)} - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) @pytest.mark.valid_from("EIP8037") @@ -181,9 +171,6 @@ def test_reservoir_returned_on_oog( The child runs out of regular gas. The parent recovers the reservoir and can use it for its own state operations. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - env = Environment() sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) # Child that consumes all gas @@ -201,12 +188,12 @@ def test_reservoir_returned_on_oog( tx = Transaction( to=parent, - gas_limit=gas_limit_cap + sstore_state_gas, + state_gas_reservoir=sstore_state_gas, sender=pre.fund_eoa(), ) post = {parent: Account(storage=parent_storage)} - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) @pytest.mark.valid_from("EIP8037") @@ -225,9 +212,6 @@ def test_reservoir_restored_after_child_spill_and_revert( restored to the parent's reservoir. The parent can then perform two SSTOREs using only the recovered reservoir. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - env = Environment() sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) # Child does two SSTOREs then reverts — the second SSTORE's @@ -250,12 +234,12 @@ def test_reservoir_restored_after_child_spill_and_revert( # Reservoir = 1 SSTORE's worth of state gas — child will spill tx = Transaction( to=parent, - gas_limit=gas_limit_cap + sstore_state_gas, + state_gas_reservoir=sstore_state_gas, sender=pre.fund_eoa(), ) post = {parent: Account(storage=parent_storage)} - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) @pytest.mark.valid_from("EIP8037") @@ -275,9 +259,6 @@ def test_reservoir_restored_after_child_spill_and_halt( The parent does two SSTOREs: the first drains the recovered reservoir, the second spills from the parent's own `gas_left`. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - env = Environment() sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) # Child does two SSTOREs then halts @@ -300,12 +281,12 @@ def test_reservoir_restored_after_child_spill_and_halt( # Reservoir = 1 SSTORE's worth of state gas — child will spill tx = Transaction( to=parent, - gas_limit=gas_limit_cap + sstore_state_gas, + state_gas_reservoir=sstore_state_gas, sender=pre.fund_eoa(), ) post = {parent: Account(storage=parent_storage)} - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) @pytest.mark.valid_from("EIP8037") @@ -321,9 +302,6 @@ def test_reservoir_restored_after_child_full_drain_and_revert( (no spill into gas_left), then REVERTs. The full reservoir is returned to the parent. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - env = Environment() sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) child = pre.deploy_contract( @@ -340,12 +318,12 @@ def test_reservoir_restored_after_child_full_drain_and_revert( tx = Transaction( to=parent, - gas_limit=gas_limit_cap + sstore_state_gas, + state_gas_reservoir=sstore_state_gas, sender=pre.fund_eoa(), ) post = {parent: Account(storage=parent_storage)} - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) @pytest.mark.valid_from("EIP8037") @@ -362,9 +340,6 @@ def test_sequential_calls_reservoir_restored_between_reverts( child failures restore the reservoir, so the parent can use it for its own SSTORE at the end. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - env = Environment() sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) child = pre.deploy_contract( @@ -385,12 +360,12 @@ def test_sequential_calls_reservoir_restored_between_reverts( tx = Transaction( to=parent, - gas_limit=gas_limit_cap + sstore_state_gas, + state_gas_reservoir=sstore_state_gas, sender=pre.fund_eoa(), ) post = {parent: Account(storage=parent_storage)} - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) @pytest.mark.valid_from("EIP8037") @@ -406,9 +381,6 @@ def test_nested_calls_reservoir_passing( using the reservoir gas. After all calls return, A verifies success. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - env = Environment() sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) c_storage = Storage() @@ -432,7 +404,7 @@ def test_nested_calls_reservoir_passing( tx = Transaction( to=a, - gas_limit=gas_limit_cap + sstore_state_gas, + state_gas_reservoir=sstore_state_gas, sender=pre.fund_eoa(), ) @@ -440,7 +412,7 @@ def test_nested_calls_reservoir_passing( a: Account(storage=a_storage), c: Account(storage=c_storage), } - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) @pytest.mark.valid_from("EIP8037") @@ -456,9 +428,6 @@ def test_call_value_transfer_new_account( new account, charging new-account state gas of state gas. """ gas_costs = fork.gas_costs() - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - env = Environment() new_account_state_gas = gas_costs.NEW_ACCOUNT # Target address that doesn't exist in pre-state @@ -477,19 +446,18 @@ def test_call_value_transfer_new_account( tx = Transaction( to=parent, - gas_limit=gas_limit_cap + new_account_state_gas, + state_gas_reservoir=new_account_state_gas, sender=pre.fund_eoa(), ) post = {parent: Account(storage=parent_storage)} - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) @pytest.mark.valid_from("EIP8037") def test_call_value_transfer_existing_account_no_state_gas( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """ Test CALL with value to existing account charges no state gas. @@ -497,8 +465,6 @@ def test_call_value_transfer_existing_account_no_state_gas( A CALL that transfers value to an already-alive account does not create new state, so no state gas is charged. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None # Existing target account target = pre.fund_eoa(amount=0) @@ -515,7 +481,7 @@ def test_call_value_transfer_existing_account_no_state_gas( tx = Transaction( to=parent, - gas_limit=gas_limit_cap, + state_gas_reservoir=0, sender=pre.fund_eoa(), ) @@ -537,9 +503,6 @@ def test_child_state_gas_tracked_in_parent( succeeding with enough total gas but would OOG if state gas wasn't tracked across frames. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - env = Environment() sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) child_storage = Storage() @@ -563,7 +526,7 @@ def test_child_state_gas_tracked_in_parent( # Provide enough reservoir for both SSTOREs tx = Transaction( to=parent, - gas_limit=gas_limit_cap + sstore_state_gas * 2, + state_gas_reservoir=sstore_state_gas * 2, sender=pre.fund_eoa(), ) @@ -571,7 +534,7 @@ def test_child_state_gas_tracked_in_parent( parent: Account(storage=parent_storage), child: Account(storage=child_storage), } - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) @pytest.mark.valid_from("EIP8037") @@ -587,9 +550,6 @@ def test_delegatecall_reservoir_passing( The child's SSTORE writes to the parent's storage using state gas from the reservoir. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - env = Environment() sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) # Library code that writes to slot 0 — runs in parent's context @@ -605,12 +565,12 @@ def test_delegatecall_reservoir_passing( tx = Transaction( to=parent, - gas_limit=gas_limit_cap + sstore_state_gas, + state_gas_reservoir=sstore_state_gas, sender=pre.fund_eoa(), ) post = {parent: Account(storage=parent_storage)} - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) @pytest.mark.valid_from("EIP8037") @@ -626,9 +586,6 @@ def test_staticcall_passes_reservoir( passed to the child but cannot be consumed. After the STATICCALL returns, the parent can still use the reservoir for its own SSTORE. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - env = Environment() sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) # Child does a read-only operation @@ -647,12 +604,12 @@ def test_staticcall_passes_reservoir( tx = Transaction( to=parent, - gas_limit=gas_limit_cap + sstore_state_gas, + state_gas_reservoir=sstore_state_gas, sender=pre.fund_eoa(), ) post = {parent: Account(storage=parent_storage)} - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) @pytest.mark.valid_from("EIP8037") @@ -668,9 +625,6 @@ def test_gas_opcode_excludes_reservoir( reservoir is non-empty, the GAS return value should be less than the total remaining gas (gas_left + reservoir). """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - env = Environment() sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) storage = Storage() @@ -687,7 +641,7 @@ def test_gas_opcode_excludes_reservoir( reservoir_gas = sstore_state_gas * 100 tx = Transaction( to=contract, - gas_limit=gas_limit_cap + reservoir_gas, + state_gas_reservoir=reservoir_gas, sender=pre.fund_eoa(), ) @@ -696,7 +650,7 @@ def test_gas_opcode_excludes_reservoir( # We can't check the exact value, but we verify the SSTORE # succeeded and the contract executed correctly post = {contract: Account(storage=storage)} - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) @pytest.mark.parametrize( @@ -723,9 +677,6 @@ def test_call_insufficient_balance_returns_reservoir( subsequent SSTORE. """ gas_costs = fork.gas_costs() - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - env = Environment() sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) target: int | Address @@ -752,12 +703,12 @@ def test_call_insufficient_balance_returns_reservoir( tx = Transaction( to=contract, - gas_limit=gas_limit_cap + reservoir, + state_gas_reservoir=reservoir, sender=pre.fund_eoa(), ) post = {contract: Account(storage=storage)} - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) @pytest.mark.valid_from("EIP8037") @@ -773,9 +724,6 @@ def test_create_insufficient_balance_returns_reservoir( for the endowment, the operation fails and both gas and state gas reservoir are returned to the parent frame. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - env = Environment() sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) storage = Storage() @@ -794,12 +742,12 @@ def test_create_insufficient_balance_returns_reservoir( tx = Transaction( to=contract, - gas_limit=gas_limit_cap + sstore_state_gas, + state_gas_reservoir=sstore_state_gas, sender=pre.fund_eoa(), ) post = {contract: Account(storage=storage)} - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) @pytest.mark.valid_from("EIP8037") @@ -815,9 +763,6 @@ def test_call_stack_depth_returns_reservoir( and gas and state gas reservoir are returned. The parent can still use the reservoir for state operations. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - env = Environment() sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) # Contract that recursively calls itself until depth exhausted, @@ -835,12 +780,12 @@ def test_call_stack_depth_returns_reservoir( tx = Transaction( to=recursive, - gas_limit=gas_limit_cap + sstore_state_gas, + state_gas_reservoir=sstore_state_gas, sender=pre.fund_eoa(), ) post = {recursive: Account(storage=storage)} - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) @pytest.mark.valid_from("EIP8037") @@ -864,8 +809,6 @@ def test_call_pre_charged_costs_excluded_from_forwarding( the child to OOG and the SSTORE to revert. """ gas_costs = fork.gas_costs() - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) # Child: SSTORE(0, 1) as proof of execution @@ -910,7 +853,7 @@ def test_call_pre_charged_costs_excluded_from_forwarding( tx = Transaction( sender=sender, to=caller, - gas_limit=gas_limit_cap + sstore_state_gas, + state_gas_reservoir=sstore_state_gas, ) post = { @@ -934,8 +877,6 @@ def test_call_new_account_header_gas_used( correct 2D max(regular, state) accounting in the header. """ gas_costs = fork.gas_costs() - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None new_account_state_gas = gas_costs.NEW_ACCOUNT target = pre.fund_eoa(amount=0) @@ -953,7 +894,7 @@ def test_call_new_account_header_gas_used( tx = Transaction( to=contract, - gas_limit=gas_limit_cap + new_account_state_gas, + state_gas_reservoir=new_account_state_gas, sender=pre.fund_eoa(), ) @@ -993,9 +934,6 @@ def test_call_value_to_self_destructed_same_tx_account( the no charge behavior lives in `test_call_value_to_self_destructed_header_gas_used`. """ - env = Environment() - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None new_account_state_gas = fork.gas_costs().NEW_ACCOUNT sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) @@ -1023,12 +961,12 @@ def test_call_value_to_self_destructed_same_tx_account( tx = Transaction( to=orchestrator, - gas_limit=gas_limit_cap + new_account_state_gas + sstore_state_gas, + state_gas_reservoir=new_account_state_gas + sstore_state_gas, sender=pre.fund_eoa(), ) post = {orchestrator: Account(storage=storage)} - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) @pytest.mark.parametrize( @@ -1065,8 +1003,6 @@ def test_call_value_to_self_destructed_header_gas_used( targeted itself or an external beneficiary, so the no charge behavior holds across both cases. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None new_account_state_gas = fork.gas_costs().NEW_ACCOUNT if selfdestruct_beneficiary == "self": @@ -1095,7 +1031,7 @@ def test_call_value_to_self_destructed_header_gas_used( tx = Transaction( to=orchestrator, - gas_limit=gas_limit_cap + new_account_state_gas, + state_gas_reservoir=new_account_state_gas, sender=pre.fund_eoa(), ) @@ -1138,8 +1074,6 @@ def test_call_value_to_self_destructed_burns_value( address. At the end of the transaction the account is removed and the accumulated balance is lost. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None new_account_state_gas = fork.gas_costs().NEW_ACCOUNT inner_code = Op.SELFDESTRUCT(Op.ADDRESS) @@ -1182,7 +1116,7 @@ def test_call_value_to_self_destructed_burns_value( tx = Transaction( to=orchestrator, - gas_limit=gas_limit_cap + new_account_state_gas, + state_gas_reservoir=new_account_state_gas, sender=pre.fund_eoa(), ) @@ -1220,8 +1154,6 @@ def test_call_zero_value_to_self_destructed_same_tx_account( value CALL (value gate broken) would double the state gas component. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None new_account_state_gas = fork.gas_costs().NEW_ACCOUNT inner_code = Op.SELFDESTRUCT(Op.ADDRESS) @@ -1244,7 +1176,7 @@ def test_call_zero_value_to_self_destructed_same_tx_account( tx = Transaction( to=orchestrator, - gas_limit=gas_limit_cap + new_account_state_gas, + state_gas_reservoir=new_account_state_gas, sender=pre.fund_eoa(), ) @@ -1284,8 +1216,6 @@ def test_call_value_to_pre_existing_selfdestructed_account( new account charge on the value bearing CALL would push the header up by that charge, breaking the assertion. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) # Enough probes that the combined probe state gas dominates the @@ -1320,7 +1250,7 @@ def test_call_value_to_pre_existing_selfdestructed_account( tx = Transaction( to=orchestrator, - gas_limit=gas_limit_cap + probe_state_gas, + state_gas_reservoir=probe_state_gas, sender=pre.fund_eoa(), ) @@ -1397,7 +1327,7 @@ def test_top_level_halt_refunds_total_state_gas( tx = Transaction( to=parent, - gas_limit=tx_gas, + state_gas_reservoir=reservoir, sender=pre.fund_eoa(), ) @@ -1429,8 +1359,6 @@ def test_callcode_value_no_new_account_state_gas( Verify CALLCODE with value does not charge new-account state gas, since the value stays with the caller. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) target = pre.fund_eoa(amount=0) @@ -1452,7 +1380,7 @@ def test_callcode_value_no_new_account_state_gas( tx = Transaction( to=contract, - gas_limit=gas_limit_cap + sstore_state_gas, + state_gas_reservoir=sstore_state_gas, sender=pre.fund_eoa(), ) @@ -1477,8 +1405,6 @@ def test_create_oog_during_state_gas_charge( SSTORE is forwarded only its regular stipend, so it succeeds only if the refund landed in the reservoir (not in `gas_left`). """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None gas_costs = fork.gas_costs() sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) @@ -1514,7 +1440,7 @@ def test_create_oog_during_state_gas_charge( tx = Transaction( to=parent, - gas_limit=gas_limit_cap + sstore_state_gas, + state_gas_reservoir=sstore_state_gas, sender=pre.fund_eoa(), ) @@ -1589,8 +1515,6 @@ def test_child_failure_refunds_state_gas_to_reservoir_not_gas_left( tight regular stipend. Covers SSTORE and CALL-value (new account) state-gas charge paths. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None gas_costs = fork.gas_costs() sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) @@ -1630,7 +1554,7 @@ def test_child_failure_refunds_state_gas_to_reservoir_not_gas_left( tx = Transaction( to=parent, - gas_limit=gas_limit_cap + reservoir, + state_gas_reservoir=reservoir, sender=pre.fund_eoa(), ) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_calldata_floor.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_calldata_floor.py index d394a70c49f..52a5b034cac 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_calldata_floor.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_calldata_floor.py @@ -16,7 +16,6 @@ Alloc, Block, BlockchainTestFiller, - Environment, Fork, Op, StateTestFiller, @@ -37,7 +36,6 @@ def test_calldata_floor_with_sstore( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """ Test calldata floor does not affect state gas charging. @@ -45,8 +43,6 @@ def test_calldata_floor_with_sstore( A transaction with large calldata triggers the calldata floor for regular gas, but state gas for SSTORE is charged independently. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None storage = Storage() contract = pre.deploy_contract( code=Op.SSTORE(storage.store_next(1), 1), @@ -58,7 +54,7 @@ def test_calldata_floor_with_sstore( tx = Transaction( to=contract, data=calldata, - gas_limit=gas_limit_cap, + state_gas_reservoir=0, sender=pre.fund_eoa(), ) @@ -70,7 +66,6 @@ def test_calldata_floor_with_sstore( def test_calldata_floor_independent_of_state_gas( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """ Test calldata floor applies only to regular gas dimension. @@ -80,8 +75,6 @@ def test_calldata_floor_independent_of_state_gas( high calldata and no state operations should succeed even when the floor exceeds actual execution gas. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None contract = pre.deploy_contract(code=Op.STOP) # Large calldata so the floor exceeds actual execution gas @@ -90,7 +83,7 @@ def test_calldata_floor_independent_of_state_gas( tx = Transaction( to=contract, data=calldata, - gas_limit=gas_limit_cap, + state_gas_reservoir=0, sender=pre.fund_eoa(), ) @@ -109,9 +102,6 @@ def test_calldata_floor_higher_than_execution_with_state_ops( Even when calldata floor > actual regular gas used, state gas for SSTORE is charged normally from the reservoir or gas_left. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - env = Environment() sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) storage = Storage() @@ -125,12 +115,12 @@ def test_calldata_floor_higher_than_execution_with_state_ops( tx = Transaction( to=contract, data=calldata, - gas_limit=gas_limit_cap + sstore_state_gas, + state_gas_reservoir=sstore_state_gas, sender=pre.fund_eoa(), ) post = {contract: Account(storage=storage)} - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) @pytest.mark.parametrize( diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py index 64a6dd644bb..54d0cc76bfe 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py @@ -17,7 +17,6 @@ Block, BlockchainTestFiller, Bytecode, - Environment, Fork, Header, Initcode, @@ -43,7 +42,6 @@ def test_create_charges_state_gas( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """ Test CREATE charges state gas for new account and code deposit. @@ -51,8 +49,6 @@ def test_create_charges_state_gas( A successful CREATE charges new-account state gas plus code deposit state gas proportional to the deployed code size. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None init_code = Op.STOP storage = Storage() @@ -72,7 +68,7 @@ def test_create_charges_state_gas( tx = Transaction( to=contract, - gas_limit=gas_limit_cap, + state_gas_reservoir=0, sender=pre.fund_eoa(), ) @@ -101,9 +97,6 @@ def test_create_with_reservoir( is drawn from the reservoir rather than gas_left. """ gas_costs = fork.gas_costs() - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - env = Environment() create_state_gas = gas_costs.NEW_ACCOUNT storage = Storage() @@ -130,12 +123,12 @@ def test_create_with_reservoir( tx = Transaction( to=contract, - gas_limit=gas_limit_cap + create_state_gas, + state_gas_reservoir=create_state_gas, sender=pre.fund_eoa(), ) post = {contract: Account(storage=storage)} - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) @pytest.mark.valid_from("EIP8037") @@ -150,8 +143,6 @@ def test_create2_child_spill_not_double_charged( pays new-account and storage state gas by spilling from `gas_left`. The factory must not charge the same state growth again at frame end. """ - env = Environment() - init_code = sum(Op.SSTORE(i, i + 1) for i in range(6)) + Op.STOP mstore_value, initcode_size = init_code_at_high_bytes(init_code) @@ -183,7 +174,7 @@ def test_create2_child_spill_not_double_charged( post = { created: Account(nonce=1, storage={i: i + 1 for i in range(6)}), } - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) @pytest.mark.parametrize( @@ -218,9 +209,6 @@ def test_code_deposit_state_gas_scales_with_size( code_size = fork.max_code_size() + 1 assert isinstance(code_size, int) - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - env = Environment() # State gas: new account + code deposit total_state_gas = fork.create_state_gas(code_size=code_size) @@ -232,7 +220,7 @@ def test_code_deposit_state_gas_scales_with_size( tx = Transaction( to=None, data=init_code, - gas_limit=gas_limit_cap + total_state_gas, + state_gas_reservoir=total_state_gas, sender=sender, ) @@ -242,7 +230,7 @@ def test_code_deposit_state_gas_scales_with_size( else: post = {} - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) @pytest.mark.valid_from("EIP8037") @@ -316,7 +304,6 @@ def test_repeated_create_same_code_charges_each_account( def test_create_tx_state_gas( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """ Test contract creation transaction charges intrinsic state gas. @@ -325,12 +312,10 @@ def test_create_tx_state_gas( as intrinsic state gas for the new account, plus code deposit state gas for the deployed bytecode. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None tx = Transaction( to=None, data=Op.STOP, - gas_limit=gas_limit_cap, + state_gas_reservoir=0, sender=pre.fund_eoa(), ) @@ -341,7 +326,6 @@ def test_create_tx_state_gas( def test_create_revert_no_code_deposit_state_gas( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """ Test reverted CREATE does not charge code deposit state gas. @@ -350,8 +334,6 @@ def test_create_revert_no_code_deposit_state_gas( account state gas is consumed but no code deposit state gas is charged because no code was deployed. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None init_code = Op.REVERT(0, 0) storage = Storage() @@ -371,7 +353,7 @@ def test_create_revert_no_code_deposit_state_gas( tx = Transaction( to=contract, - gas_limit=gas_limit_cap, + state_gas_reservoir=0, sender=pre.fund_eoa(), ) @@ -581,8 +563,6 @@ def test_code_deposit_oog_preserves_parent_reservoir( CREATE proves the reservoir was not inflated by a spill-then-halt refund. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None gas_costs = fork.gas_costs() new_account_state_gas = gas_costs.NEW_ACCOUNT sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) @@ -627,7 +607,7 @@ def test_code_deposit_oog_preserves_parent_reservoir( # gas_left, which the limited CALL gas cannot cover. tx = Transaction( to=caller, - gas_limit=(gas_limit_cap + new_account_state_gas + sstore_state_gas), + state_gas_reservoir=new_account_state_gas + sstore_state_gas, sender=pre.fund_eoa(), ) @@ -961,12 +941,11 @@ def test_sstore_oog_no_reservoir_inflation( ) sender = pre.fund_eoa() - # gas_limit = cap, reservoir = 0 tx = Transaction( sender=sender, to=caller, data=bytes(initcode), - gas_limit=fork.transaction_gas_limit_cap(), + state_gas_reservoir=0, ) created = not gas_shortfall @@ -1075,13 +1054,11 @@ def test_max_initcode_size_gas_metering_via_create( + Op.STOP ) - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None tx = Transaction( sender=alice, to=caller, data=bytes(initcode), - gas_limit=gas_limit_cap + factory_state_gas, + state_gas_reservoir=factory_state_gas, ) created = not gas_shortfall @@ -1134,12 +1111,10 @@ def test_create_no_double_charge_new_account( ) ) - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None tx = Transaction( sender=pre.fund_eoa(), to=caller, - gas_limit=gas_limit_cap + create_state_gas, + state_gas_reservoir=create_state_gas, ) post = { @@ -1186,9 +1161,6 @@ def test_code_deposit_halt_discards_initcode_state_gas( in block_state_gas_used, which determines the block header gas_used via max(block_regular_gas, block_state_gas). """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - subcall_forwarded_value = 1 entry_account_value = 1 if state_opcode == Op.CALL: @@ -1220,7 +1192,7 @@ def test_code_deposit_halt_discards_initcode_state_gas( to=None, data=initcode, value=entry_account_value + subcall_forwarded_value, - gas_limit=gas_limit_cap, + state_gas_reservoir=0, sender=pre.fund_eoa(), ), ], @@ -1244,9 +1216,6 @@ def test_create_tx_header_gas_used( header. Catches bugs where clients report gas_limit instead of actual consumed gas. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - gas_costs = fork.gas_costs() initcode = Op.STOP create_state_gas = fork.create_state_gas(code_size=1) @@ -1254,7 +1223,7 @@ def test_create_tx_header_gas_used( tx = Transaction( to=None, data=initcode, - gas_limit=gas_limit_cap + create_state_gas, + state_gas_reservoir=create_state_gas, sender=pre.fund_eoa(), ) @@ -1306,7 +1275,7 @@ def test_create_initcode_halt_no_code_deposit_state_gas( tx = Transaction( to=None, data=initcode, - gas_limit=gas_limit, + state_gas_reservoir=intrinsic_state_gas, sender=pre.fund_eoa(), ) @@ -1343,9 +1312,6 @@ def test_state_gas_spill_header_gas_used( the reservoir and partially spilling into gas_left. Verify the block header gas_used reflects the correct 2D max accounting. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - # SSTORE zero-to-nonzero with small reservoir sstore_code = Op.SSTORE(0, 1) + Op.STOP contract = pre.deploy_contract(code=sstore_code) @@ -1358,11 +1324,10 @@ def test_state_gas_spill_header_gas_used( # Reservoir = half the SSTORE state gas, rest spills to gas_left reservoir = sstore_state_gas // 2 - gas_limit = gas_limit_cap + reservoir tx = Transaction( to=contract, - gas_limit=gas_limit, + state_gas_reservoir=reservoir, sender=pre.fund_eoa(), ) @@ -1405,8 +1370,6 @@ def test_failed_create_header_gas_used( halt). Verify the block is accepted with correct gas accounting. Parametrized across failure modes and create opcodes. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None create_state_gas = fork.create_state_gas(code_size=0) if failure_mode == "revert": @@ -1433,7 +1396,7 @@ def test_failed_create_header_gas_used( tx = Transaction( to=factory, - gas_limit=gas_limit_cap + create_state_gas, + state_gas_reservoir=create_state_gas, sender=pre.fund_eoa(), ) @@ -1467,8 +1430,6 @@ def test_create_silent_failure_refunds_state_gas( balance) refund `GAS_NEW_ACCOUNT` to the reservoir. Block state gas reflects only the probe SSTORE, not the refunded CREATE. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None gas_costs = fork.gas_costs() sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) intrinsic_cost = fork.transaction_intrinsic_cost_calculator()() @@ -1489,7 +1450,7 @@ def test_create_silent_failure_refunds_state_gas( tx = Transaction( to=factory, - gas_limit=gas_limit_cap + sstore_state_gas, + state_gas_reservoir=sstore_state_gas, sender=pre.fund_eoa(), ) @@ -1618,8 +1579,6 @@ def test_create_child_halt_refunds_state_gas( but not enough to spill the state portion, so the probe SSTORE can only succeed via the refunded reservoir. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None gas_costs = fork.gas_costs() sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) new_account_state_gas = gas_costs.NEW_ACCOUNT @@ -1668,7 +1627,7 @@ def test_create_child_halt_refunds_state_gas( ) tx = Transaction( to=caller, - gas_limit=gas_limit_cap + new_account_state_gas, + state_gas_reservoir=new_account_state_gas, sender=pre.fund_eoa(), ) @@ -1689,8 +1648,6 @@ def test_create_mixed_success_and_failure_block_accounting( One successful CREATE plus one failed CREATE (REVERT): block state gas reflects only the successful charges. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() create_account_state_gas = fork.create_state_gas(code_size=0) @@ -1722,7 +1679,7 @@ def call(size: int, salt: int) -> Bytecode: tx = Transaction( to=factory, - gas_limit=gas_limit_cap + 2 * create_account_state_gas, + state_gas_reservoir=2 * create_account_state_gas, sender=pre.fund_eoa(), ) @@ -1752,8 +1709,6 @@ def test_create_collision_refunds_state_gas( probe SSTORE can only succeed via the refunded reservoir, not by spilling state gas from `gas_left`. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None gas_costs = fork.gas_costs() sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) new_account_state_gas = gas_costs.NEW_ACCOUNT @@ -1803,7 +1758,7 @@ def test_create_collision_refunds_state_gas( ) tx = Transaction( to=caller, - gas_limit=gas_limit_cap + new_account_state_gas, + state_gas_reservoir=new_account_state_gas, sender=pre.fund_eoa(), ) @@ -1827,8 +1782,6 @@ def test_create_code_deposit_oog_refunds_state_gas( `gas_left` so the probe SSTORE can only succeed via the refunded reservoir, not by spilling state gas from `gas_left`. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None gas_costs = fork.gas_costs() sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) new_account_state_gas = gas_costs.NEW_ACCOUNT @@ -1869,7 +1822,7 @@ def test_create_code_deposit_oog_refunds_state_gas( ) tx = Transaction( to=caller, - gas_limit=gas_limit_cap + new_account_state_gas, + state_gas_reservoir=new_account_state_gas, sender=pre.fund_eoa(), ) @@ -2063,15 +2016,13 @@ def test_oversized_initcode_tx_no_state_gas( sender = pre.fund_eoa() create_address = compute_create_address(address=sender, nonce=0) - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None create_state_gas = fork.create_state_gas(code_size=len(Op.STOP)) tx = Transaction( sender=sender, to=None, data=initcode, - gas_limit=gas_limit_cap + create_state_gas, + state_gas_reservoir=create_state_gas, ) if initcode_size_delta > 0: @@ -2121,8 +2072,6 @@ def test_oversized_initcode_opcode_no_state_gas( initcode = Initcode(deploy_code=Op.STOP, initcode_length=size) initcode_bytes = bytes(initcode) - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None gas_costs = fork.gas_costs() create_state_gas = gas_costs.NEW_ACCOUNT @@ -2157,7 +2106,7 @@ def test_oversized_initcode_opcode_no_state_gas( sender=pre.fund_eoa(), to=factory, data=initcode_bytes, - gas_limit=gas_limit_cap + create_state_gas, + state_gas_reservoir=create_state_gas, ) post: dict = {factory: Account(storage=storage)} @@ -2333,8 +2282,6 @@ def test_nested_create_fail_parent_revert_state_gas( Verify factory nonce is rolled back when the factory reverts after a failed inner CREATE, and preserved when the factory returns. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None gas_costs = fork.gas_costs() create_state_gas = gas_costs.NEW_ACCOUNT @@ -2369,7 +2316,7 @@ def test_nested_create_fail_parent_revert_state_gas( tx = Transaction( to=caller, - gas_limit=gas_limit_cap + create_state_gas, + state_gas_reservoir=create_state_gas, sender=pre.fund_eoa(), ) @@ -2409,8 +2356,6 @@ def test_create_stack_depth_state_gas_consumed( Verify the state gas reservoir survives a deep recursion of nested CALLs that silently fail on gas or depth exhaustion. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) storage = Storage() @@ -2423,7 +2368,7 @@ def test_create_stack_depth_state_gas_consumed( tx = Transaction( to=recursive, - gas_limit=gas_limit_cap + sstore_state_gas, + state_gas_reservoir=sstore_state_gas, sender=pre.fund_eoa(), ) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_delegation_pointer.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_delegation_pointer.py index b6c79007c1d..4907e573878 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_delegation_pointer.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_delegation_pointer.py @@ -14,7 +14,6 @@ Account, Alloc, AuthorizationTuple, - Environment, Fork, Op, StateTestFiller, @@ -42,9 +41,6 @@ def test_sstore_via_delegation_pointer( contract code in the EOA's context. The SSTORE state gas should be charged from the reservoir just as it would for a direct call. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - env = Environment() auth_state_gas = fork.transaction_intrinsic_state_gas( authorization_count=1, ) @@ -61,7 +57,7 @@ def test_sstore_via_delegation_pointer( sender = pre.fund_eoa() tx = Transaction( to=delegator, - gas_limit=(gas_limit_cap + auth_state_gas + sstore_state_gas), + state_gas_reservoir=auth_state_gas + sstore_state_gas, authorization_list=[ AuthorizationTuple( address=contract, @@ -74,7 +70,7 @@ def test_sstore_via_delegation_pointer( # SSTORE writes to the delegator's storage context post = {delegator: Account(storage=storage)} - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) @pytest.mark.valid_from("EIP8037") @@ -89,9 +85,6 @@ def test_sstore_direct_call_same_contract( Baseline comparison: calling the contract directly (not via a delegation pointer) charges SSTORE state gas identically. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - env = Environment() sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) storage = Storage() @@ -102,12 +95,12 @@ def test_sstore_direct_call_same_contract( sender = pre.fund_eoa() tx = Transaction( to=contract, - gas_limit=gas_limit_cap + sstore_state_gas, + state_gas_reservoir=sstore_state_gas, sender=sender, ) post = {contract: Account(storage=storage)} - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) @pytest.mark.valid_from("EIP8037") @@ -124,9 +117,6 @@ def test_delegation_pointer_new_account_state_gas( is charged identically to a direct call. """ gas_costs = fork.gas_costs() - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - env = Environment() auth_state_gas = fork.transaction_intrinsic_state_gas( authorization_count=1, ) @@ -151,7 +141,7 @@ def test_delegation_pointer_new_account_state_gas( sender = pre.fund_eoa() tx = Transaction( to=delegator, - gas_limit=(gas_limit_cap + auth_state_gas + new_account_state_gas), + state_gas_reservoir=auth_state_gas + new_account_state_gas, authorization_list=[ AuthorizationTuple( address=contract, @@ -164,4 +154,4 @@ def test_delegation_pointer_new_account_state_gas( # CALL success stored in delegator's storage context post = {delegator: Account(storage=parent_storage)} - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_fork_transition.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_fork_transition.py index 5438eaba8ce..2b0c379d36f 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_fork_transition.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_fork_transition.py @@ -42,7 +42,6 @@ def test_sstore_state_gas_at_transition( blockchain_test: BlockchainTestFiller, pre: Alloc, - fork: Fork, ) -> None: """ Test SSTORE state gas activates at the EIP-8037 fork boundary. @@ -52,9 +51,6 @@ def test_sstore_state_gas_at_transition( operation requires state gas. Both blocks use TX_MAX_GAS_LIMIT which provides enough gas in either regime. """ - after_fork = fork.fork_at(timestamp=15_000) - gas_limit_cap = after_fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None contract_before = pre.deploy_contract( code=Op.SSTORE(0, 1), ) @@ -69,7 +65,7 @@ def test_sstore_state_gas_at_transition( txs=[ Transaction( to=contract_before, - gas_limit=gas_limit_cap, + state_gas_reservoir=0, sender=pre.fund_eoa(), ), ], @@ -80,7 +76,7 @@ def test_sstore_state_gas_at_transition( txs=[ Transaction( to=contract_after, - gas_limit=gas_limit_cap, + state_gas_reservoir=0, sender=pre.fund_eoa(), ), ], @@ -196,8 +192,6 @@ def test_reservoir_available_after_transition( which child calls can draw from for state operations. """ after_fork = fork.fork_at(timestamp=15_000) - gas_limit_cap = after_fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None sstore_state_gas = Op.SSTORE(new_value=1).state_cost(after_fork) child_storage = Storage() @@ -221,7 +215,7 @@ def test_reservoir_available_after_transition( txs=[ Transaction( to=parent, - gas_limit=gas_limit_cap + sstore_state_gas, + state_gas_reservoir=sstore_state_gas, sender=pre.fund_eoa(), ), ], diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_multi_block.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_multi_block.py index 2398717d9c9..37ad180f529 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_multi_block.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_multi_block.py @@ -53,8 +53,6 @@ def test_exact_coinbase_fee_simple_sstore( where clients diverged on cumulative `receipt_gas_used`. """ gas_costs = fork.gas_costs() - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) # Gas breakdown for tx 1 (SSTORE zero-to-nonzero, no calldata): @@ -83,14 +81,14 @@ def test_exact_coinbase_fee_simple_sstore( txs=[ Transaction( to=sstore_contract, - gas_limit=(gas_limit_cap + sstore_state_gas), + state_gas_reservoir=sstore_state_gas, max_priority_fee_per_gas=1, max_fee_per_gas=8, sender=pre.fund_eoa(), ), Transaction( to=reporter, - gas_limit=gas_limit_cap, + state_gas_reservoir=0, max_priority_fee_per_gas=1, max_fee_per_gas=8, sender=pre.fund_eoa(), @@ -122,8 +120,6 @@ def test_multi_block_mixed_state_operations( This mixed scenario tests that `receipt_gas_used` is consistent across different state gas paths within a multi-block chain. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) reverting_child = pre.deploy_contract( @@ -148,7 +144,7 @@ def test_multi_block_mixed_state_operations( block1_txs.append( Transaction( to=contract, - gas_limit=(gas_limit_cap + sstore_state_gas), + state_gas_reservoir=sstore_state_gas, max_priority_fee_per_gas=1, max_fee_per_gas=8, sender=pre.fund_eoa(), @@ -175,7 +171,7 @@ def test_multi_block_mixed_state_operations( block2_txs.append( Transaction( to=parent, - gas_limit=(gas_limit_cap + sstore_state_gas), + state_gas_reservoir=sstore_state_gas, max_priority_fee_per_gas=1, max_fee_per_gas=8, sender=pre.fund_eoa(), @@ -202,7 +198,7 @@ def test_multi_block_mixed_state_operations( block3_txs.append( Transaction( to=parent, - gas_limit=(gas_limit_cap + sstore_state_gas), + state_gas_reservoir=sstore_state_gas, max_priority_fee_per_gas=1, max_fee_per_gas=8, sender=pre.fund_eoa(), @@ -244,8 +240,6 @@ def test_multi_block_observed_coinbase_balance( (coinbase earns fee through different code path). Tx 4: Store `BALANCE(COINBASE)` in slot 0. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) reporter1 = pre.deploy_contract( @@ -278,14 +272,14 @@ def test_multi_block_observed_coinbase_balance( txs=[ Transaction( to=sstore_contract, - gas_limit=(gas_limit_cap + sstore_state_gas), + state_gas_reservoir=sstore_state_gas, max_priority_fee_per_gas=1, max_fee_per_gas=8, sender=pre.fund_eoa(), ), Transaction( to=reporter1, - gas_limit=gas_limit_cap, + state_gas_reservoir=0, max_priority_fee_per_gas=1, max_fee_per_gas=8, sender=pre.fund_eoa(), @@ -296,14 +290,14 @@ def test_multi_block_observed_coinbase_balance( txs=[ Transaction( to=spill_parent, - gas_limit=(gas_limit_cap + sstore_state_gas), + state_gas_reservoir=sstore_state_gas, max_priority_fee_per_gas=1, max_fee_per_gas=8, sender=pre.fund_eoa(), ), Transaction( to=reporter2, - gas_limit=gas_limit_cap, + state_gas_reservoir=0, max_priority_fee_per_gas=1, max_fee_per_gas=8, sender=pre.fund_eoa(), diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_ordering.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_ordering.py index 70661c498ec..d834129c84a 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_ordering.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_ordering.py @@ -139,7 +139,7 @@ def test_sstore_oog_reservoir_inflation_detection( sender=sender, to=caller, data=bytes(initcode), - gas_limit=fork.transaction_gas_limit_cap(), + state_gas_reservoir=0, ) post = { @@ -199,7 +199,7 @@ def test_call_oog_reservoir_inflation_detection( tx = Transaction( sender=sender, to=caller, - gas_limit=fork.transaction_gas_limit_cap(), + state_gas_reservoir=0, ) post = {caller: Account(storage=caller_storage)} @@ -251,7 +251,7 @@ def test_selfdestruct_oog_reservoir_inflation_detection( tx = Transaction( sender=sender, to=caller, - gas_limit=fork.transaction_gas_limit_cap(), + state_gas_reservoir=0, ) post = {caller: Account(storage=caller_storage)} @@ -331,7 +331,7 @@ def test_create_oog_reservoir_inflation_detection( tx = Transaction( sender=sender, to=caller, - gas_limit=fork.transaction_gas_limit_cap(), + state_gas_reservoir=0, ) post = {caller: Account(storage=caller_storage)} diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_pricing.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_pricing.py index e0b941a16c9..8522effa94b 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_pricing.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_pricing.py @@ -100,9 +100,6 @@ def test_charge_draws_entirely_from_reservoir( gas_left should not be reduced by the state charge. Verify by performing a regular-gas-heavy computation after the SSTORE. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - env = Environment() sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) storage = Storage() @@ -121,12 +118,12 @@ def test_charge_draws_entirely_from_reservoir( # Provide exact state gas in the reservoir tx = Transaction( to=contract, - gas_limit=gas_limit_cap + sstore_state_gas * 2, + state_gas_reservoir=sstore_state_gas * 2, sender=pre.fund_eoa(), ) post = {contract: Account(storage=storage)} - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) @pytest.mark.valid_from("EIP8037") @@ -142,9 +139,6 @@ def test_charge_spills_to_gas_left( state charge, the remainder is taken from gas_left. The SSTORE should still succeed. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - env = Environment() sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) storage = Storage() @@ -156,12 +150,12 @@ def test_charge_spills_to_gas_left( half_state_gas = sstore_state_gas // 2 tx = Transaction( to=contract, - gas_limit=gas_limit_cap + half_state_gas, + state_gas_reservoir=half_state_gas, sender=pre.fund_eoa(), ) post = {contract: Account(storage=storage)} - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) @EIPChecklist.GasCostChanges.Test.OutOfGas() @@ -203,7 +197,6 @@ def test_charge_oog_both_pools_insufficient( def test_refund_cap_includes_state_gas( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """ Test the 1/5 refund cap includes state gas used from gas_left. @@ -214,8 +207,6 @@ def test_refund_cap_includes_state_gas( performs an SSTORE zero-to-nonzero-to-zero sequence to generate a refund and verifies the transaction succeeds. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None contract = pre.deploy_contract( code=(Op.SSTORE(0, 1) + Op.SSTORE(0, 0)), ) @@ -223,7 +214,7 @@ def test_refund_cap_includes_state_gas( # No reservoir — all gas from gas_left, refund cap applies tx = Transaction( to=contract, - gas_limit=gas_limit_cap, + state_gas_reservoir=0, sender=pre.fund_eoa(), ) @@ -248,9 +239,6 @@ def test_refund_with_reservoir_state_gas( both dimensions. An SSTORE zero-to-nonzero-to-zero sequence should refund correctly. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - env = Environment() sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) contract = pre.deploy_contract( @@ -259,13 +247,13 @@ def test_refund_with_reservoir_state_gas( tx = Transaction( to=contract, - gas_limit=gas_limit_cap + sstore_state_gas, + state_gas_reservoir=sstore_state_gas, sender=pre.fund_eoa(), ) # Slot 0 restored to zero post = {contract: Account(storage={0: 0})} - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) def _access_list_over_regular_cap( diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_reservoir.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_reservoir.py index e50d38e73ea..11d0de0ac94 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_reservoir.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_reservoir.py @@ -113,10 +113,6 @@ def test_sstore_state_gas_source( When False, the reservoir is minimal (1 gas unit) and state gas must spill into gas_left. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - env = Environment() - storage = Storage() code = Bytecode() for _ in range(num_sstores): @@ -130,19 +126,18 @@ def test_sstore_state_gas_source( tx = Transaction( to=contract, - gas_limit=gas_limit_cap + extra_gas, + state_gas_reservoir=extra_gas, sender=pre.fund_eoa(), ) post = {contract: Account(storage=storage)} - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) @pytest.mark.valid_from("EIP8037") def test_sstore_state_gas_entirely_from_gas_left( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """ Test SSTORE state gas charged entirely from gas_left (no reservoir). @@ -150,8 +145,6 @@ def test_sstore_state_gas_entirely_from_gas_left( When tx.gas <= TX_MAX_GAS_LIMIT, the reservoir is zero. All state gas must come from gas_left. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None storage = Storage() contract = pre.deploy_contract( code=Op.SSTORE(storage.store_next(1), 1), @@ -159,7 +152,7 @@ def test_sstore_state_gas_entirely_from_gas_left( tx = Transaction( to=contract, - gas_limit=gas_limit_cap, + state_gas_reservoir=0, sender=pre.fund_eoa(), ) @@ -584,15 +577,13 @@ def test_block_gas_used_with_state_ops( block_gas_used and block_state_gas_used. The block header gas_used is max(block_gas_used, block_state_gas_used). """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None storage = Storage() code = Op.SSTORE(storage.store_next(1), 1) contract = pre.deploy_contract(code=code) tx = Transaction( to=contract, - gas_limit=gas_limit_cap, + state_gas_reservoir=0, sender=pre.fund_eoa(), ) @@ -714,7 +705,6 @@ def test_create_tx_reservoir( assert gas_limit_cap is not None init_code = Op.STOP - env = Environment() create_state_gas = gas_costs.NEW_ACCOUNT if gas_above_cap: @@ -729,7 +719,7 @@ def test_create_tx_reservoir( sender=pre.fund_eoa(), ) - state_test(env=env, pre=pre, post={}, tx=tx) + state_test(pre=pre, post={}, tx=tx) @pytest.mark.parametrize( @@ -785,7 +775,7 @@ def test_top_level_failure_refunds_execution_state_gas( tx = Transaction( to=contract, - gas_limit=tx_gas, + state_gas_reservoir=sstore_state_gas, sender=pre.fund_eoa(), expected_receipt=TransactionReceipt( cumulative_gas_used=expected_cumulative, @@ -835,7 +825,7 @@ def test_top_level_failure_zeros_block_state_gas( tx_gas = gas_limit_cap + sstore_state_gas tx = Transaction( to=contract, - gas_limit=tx_gas, + state_gas_reservoir=sstore_state_gas, sender=pre.fund_eoa(), ) @@ -887,7 +877,7 @@ def test_creation_tx_failure_preserves_intrinsic_state_gas( tx = Transaction( to=None, data=Op.SSTORE(0, 1) + Op.INVALID, - gas_limit=tx_gas, + state_gas_reservoir=create_intrinsic_state + sstore_state_gas, sender=pre.fund_eoa(), ) @@ -921,8 +911,6 @@ def test_subcall_failure_does_not_zero_top_level_state_gas( parent's own SSTORE contributes state gas that appears in `block_state_gas_used`. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) child = pre.deploy_contract(code=Op.REVERT(0, 0)) @@ -936,7 +924,7 @@ def test_subcall_failure_does_not_zero_top_level_state_gas( tx = Transaction( to=parent, - gas_limit=gas_limit_cap + sstore_state_gas, + state_gas_reservoir=sstore_state_gas, sender=pre.fund_eoa(), ) @@ -1013,7 +1001,7 @@ def test_top_level_failure_spilled_state_gas( tx = Transaction( to=contract, - gas_limit=tx_gas, + state_gas_reservoir=sstore_state_gas // 2, sender=pre.fund_eoa(), expected_receipt=TransactionReceipt( cumulative_gas_used=expected_cumulative, @@ -1093,7 +1081,7 @@ def test_top_level_failure_propagated_state_gas( tx = Transaction( to=parent, - gas_limit=tx_gas, + state_gas_reservoir=sstore_state_gas // 2, sender=pre.fund_eoa(), expected_receipt=TransactionReceipt( cumulative_gas_used=expected_cumulative, @@ -1369,7 +1357,7 @@ def test_nested_failure_resets_to_tx_reservoir( tx = Transaction( to=top, - gas_limit=tx_gas, + state_gas_reservoir=reservoir, sender=pre.fund_eoa(), expected_receipt=TransactionReceipt( cumulative_gas_used=expected_cumulative, @@ -1432,9 +1420,6 @@ def test_nested_state_gas_refund_consumed_at_depth( the chain returns; it succeeds only when its frame holds enough reservoir, so a missing or mis-propagated credit OOGs it. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - is_auth_scenario = refund_scenario == "auth_existing_leaf" probe_address = pre.deploy_contract(code=Op.SSTORE(0, 1)) @@ -1509,7 +1494,7 @@ def test_nested_state_gas_refund_consumed_at_depth( tx = Transaction( to=top, - gas_limit=gas_limit_cap, + state_gas_reservoir=0, authorization_list=authorization_list, sender=pre.fund_eoa(), ) @@ -1625,8 +1610,6 @@ def test_access_list_warm_savings_stay_regular( fork: Fork, ) -> None: """Verify access-list warm savings stay in regular gas.""" - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) contract = pre.deploy_contract( @@ -1648,11 +1631,10 @@ def test_access_list_warm_savings_stay_regular( evm_gas = contract_code.gas_cost(fork) expected_gas_used = intrinsic_gas + evm_gas - gas_limit = gas_limit_cap + sstore_state_gas tx = Transaction( to=contract, - gas_limit=gas_limit, + state_gas_reservoir=sstore_state_gas, sender=pre.fund_eoa(), access_list=access_list, ) @@ -1705,8 +1687,6 @@ def test_subcall_revert_does_not_leak_grandchild_storage_clear_credit( state_gas_reservoir` would charge the sender 5 * sstore_state_gas less. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) intrinsic_cost = fork.transaction_intrinsic_cost_calculator()() @@ -1745,7 +1725,6 @@ def test_subcall_revert_does_not_leak_grandchild_storage_clear_credit( # Reservoir sized to the legitimate state cost only; any # phantom credit surfaces as residual reservoir at tx end. legit_state_cost = 2 * num_slots * sstore_state_gas - tx_gas = gas_limit_cap + legit_state_cost # `bytecode.gas_cost(fork)` sums each opcode's regular and state # contributions. Setup/phantom SSTOREs predict +sstore_state_gas @@ -1763,7 +1742,7 @@ def test_subcall_revert_does_not_leak_grandchild_storage_clear_credit( tx = Transaction( to=top, - gas_limit=tx_gas, + state_gas_reservoir=legit_state_cost, sender=pre.fund_eoa(), expected_receipt=TransactionReceipt( cumulative_gas_used=expected_cumulative, @@ -1810,8 +1789,6 @@ def test_revert_discards_descendant_storage_clear_credit_through_depth( `incorporate_child_on_error`. The receipt invariant holds for every `k`. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) intrinsic_cost = fork.transaction_intrinsic_cost_calculator()() @@ -1859,7 +1836,6 @@ def test_revert_discards_descendant_storage_clear_credit_through_depth( top = pre.deploy_contract(code=top_code) legit_state_cost = 2 * num_slots * sstore_state_gas - tx_gas = gas_limit_cap + legit_state_cost expected_cumulative = ( intrinsic_cost @@ -1871,7 +1847,7 @@ def test_revert_discards_descendant_storage_clear_credit_through_depth( tx = Transaction( to=top, - gas_limit=tx_gas, + state_gas_reservoir=legit_state_cost, sender=pre.fund_eoa(), expected_receipt=TransactionReceipt( cumulative_gas_used=expected_cumulative, @@ -1918,8 +1894,6 @@ def test_subcall_set_clear_revert_pays_no_state_gas( """ intrinsic_cost = fork.transaction_intrinsic_cost_calculator()() sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None set_op = Op.SSTORE.with_metadata( key_warm=False, @@ -1940,7 +1914,6 @@ def test_subcall_set_clear_revert_pays_no_state_gas( top = pre.deploy_contract(code=top_code) reservoir = 0 if spill_mode == "spill" else sstore_state_gas - tx_gas = gas_limit_cap + reservoir expected_cumulative = ( intrinsic_cost @@ -1950,7 +1923,7 @@ def test_subcall_set_clear_revert_pays_no_state_gas( tx = Transaction( to=top, - gas_limit=tx_gas, + state_gas_reservoir=reservoir, sender=pre.fund_eoa(), expected_receipt=TransactionReceipt( cumulative_gas_used=expected_cumulative, diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_selfdestruct.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_selfdestruct.py index 88df5dbefaa..5e5ee91ea08 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_selfdestruct.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_selfdestruct.py @@ -17,7 +17,6 @@ Block, BlockchainTestFiller, Bytecode, - Environment, Fork, Header, Initcode, @@ -48,9 +47,6 @@ def test_selfdestruct_new_beneficiary_charges_state_gas( creating the new beneficiary account. """ gas_costs = fork.gas_costs() - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - env = Environment() new_account_state_gas = gas_costs.NEW_ACCOUNT # Non-existent beneficiary @@ -63,18 +59,17 @@ def test_selfdestruct_new_beneficiary_charges_state_gas( tx = Transaction( to=contract, - gas_limit=gas_limit_cap + new_account_state_gas, + state_gas_reservoir=new_account_state_gas, sender=pre.fund_eoa(), ) - state_test(env=env, pre=pre, post={}, tx=tx) + state_test(pre=pre, post={}, tx=tx) @pytest.mark.valid_from("EIP8037") def test_selfdestruct_existing_beneficiary_no_state_gas( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """ Test SELFDESTRUCT to existing beneficiary charges no state gas. @@ -82,8 +77,6 @@ def test_selfdestruct_existing_beneficiary_no_state_gas( When the beneficiary already exists, no new account is created and no state gas is charged. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None beneficiary = pre.fund_eoa(amount=0) contract = pre.deploy_contract( @@ -93,7 +86,7 @@ def test_selfdestruct_existing_beneficiary_no_state_gas( tx = Transaction( to=contract, - gas_limit=gas_limit_cap, + state_gas_reservoir=0, sender=pre.fund_eoa(), ) @@ -104,7 +97,6 @@ def test_selfdestruct_existing_beneficiary_no_state_gas( def test_selfdestruct_zero_balance_no_state_gas( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """ Test SELFDESTRUCT with zero balance charges no state gas. @@ -113,8 +105,6 @@ def test_selfdestruct_zero_balance_no_state_gas( transferred, so no new account is created even if the beneficiary does not exist. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None # Non-existent beneficiary but contract has zero balance beneficiary = 0xDEAD @@ -125,7 +115,7 @@ def test_selfdestruct_zero_balance_no_state_gas( tx = Transaction( to=contract, - gas_limit=gas_limit_cap, + state_gas_reservoir=0, sender=pre.fund_eoa(), ) @@ -145,9 +135,6 @@ def test_selfdestruct_state_gas_from_reservoir( for the non-existent beneficiary is drawn from the reservoir. """ gas_costs = fork.gas_costs() - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - env = Environment() new_account_state_gas = gas_costs.NEW_ACCOUNT beneficiary = 0xDEAD @@ -159,11 +146,11 @@ def test_selfdestruct_state_gas_from_reservoir( tx = Transaction( to=contract, - gas_limit=gas_limit_cap + new_account_state_gas, + state_gas_reservoir=new_account_state_gas, sender=pre.fund_eoa(), ) - state_test(env=env, pre=pre, post={}, tx=tx) + state_test(pre=pre, post={}, tx=tx) @pytest.mark.valid_from("EIP8037") @@ -182,7 +169,6 @@ def test_selfdestruct_to_self_in_create_tx( """ gas_limit_cap = fork.transaction_gas_limit_cap() assert gas_limit_cap is not None - env = Environment() inner_code = Op.SELFDESTRUCT(Op.ADDRESS) @@ -204,7 +190,7 @@ def test_selfdestruct_to_self_in_create_tx( sender=pre.fund_eoa(), ) - state_test(env=env, pre=pre, post={}, tx=tx) + state_test(pre=pre, post={}, tx=tx) @pytest.mark.valid_from("EIP8037") @@ -221,8 +207,6 @@ def test_selfdestruct_new_beneficiary_header_gas_used( be accepted with correct 2D gas accounting in the header. """ gas_costs = fork.gas_costs() - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None new_account_state_gas = gas_costs.NEW_ACCOUNT beneficiary = pre.fund_eoa(amount=0) @@ -241,7 +225,7 @@ def test_selfdestruct_new_beneficiary_header_gas_used( tx = Transaction( to=caller, - gas_limit=gas_limit_cap + new_account_state_gas, + state_gas_reservoir=new_account_state_gas, sender=pre.fund_eoa(), ) @@ -272,8 +256,6 @@ def test_create_selfdestruct_no_refund_account_and_storage( num_slots: int, ) -> None: """Verify same tx CREATE+SELFDESTRUCT does not refund state gas.""" - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None new_account_state_gas = fork.gas_costs().NEW_ACCOUNT sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() @@ -313,7 +295,7 @@ def test_create_selfdestruct_no_refund_account_and_storage( tx = Transaction( to=factory, - gas_limit=gas_limit_cap + total_state_gas, + state_gas_reservoir=total_state_gas, sender=pre.fund_eoa(), ) @@ -347,8 +329,6 @@ def test_create_selfdestruct_no_refund_code_deposit_state_gas( state gas. """ assert code_size >= 2 - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None new_account_state_gas = fork.gas_costs().NEW_ACCOUNT code_deposit_state_gas = fork.code_deposit_state_gas(code_size=code_size) @@ -391,7 +371,7 @@ def test_create_selfdestruct_no_refund_code_deposit_state_gas( tx = Transaction( to=factory, data=bytes(initcode), - gas_limit=gas_limit_cap + total_state_gas, + state_gas_reservoir=total_state_gas, sender=pre.fund_eoa(), ) @@ -412,8 +392,6 @@ def test_create_selfdestruct_code_deposit_no_refund_header_check( Verify block header gas reflects the full account plus code-deposit state-gas charge on a same-tx CREATE+SELFDESTRUCT. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None gas_costs = fork.gas_costs() new_account_state_gas = gas_costs.NEW_ACCOUNT @@ -450,7 +428,7 @@ def test_create_selfdestruct_code_deposit_no_refund_header_check( tx = Transaction( to=factory, data=bytes(initcode), - gas_limit=gas_limit_cap + total_state_gas, + state_gas_reservoir=total_state_gas, sender=pre.fund_eoa(), ) @@ -479,8 +457,6 @@ def test_create_selfdestruct_sstore_restoration_refund( Verify SSTORE restoration still refunds its slot state gas when the surrounding contract SELFDESTRUCTs. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None new_account_state_gas = fork.gas_costs().NEW_ACCOUNT sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() @@ -521,7 +497,7 @@ def test_create_selfdestruct_sstore_restoration_refund( tx = Transaction( to=factory, - gas_limit=gas_limit_cap + new_account_state_gas + sstore_state_gas, + state_gas_reservoir=new_account_state_gas + sstore_state_gas, sender=pre.fund_eoa(), ) @@ -551,8 +527,6 @@ def test_selfdestruct_pre_existing_account_no_refund( header `gas_used` reflects the full regular-gas tx cost (no state-gas refund offset). """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() # Victim deployed in `pre` (NOT same-tx-created). SELFDESTRUCTs @@ -571,7 +545,7 @@ def test_selfdestruct_pre_existing_account_no_refund( tx = Transaction( to=caller, - gas_limit=gas_limit_cap, + state_gas_reservoir=0, sender=pre.fund_eoa(), ) @@ -606,8 +580,6 @@ def test_selfdestruct_via_delegatecall_chain_no_refund( Verify SELFDESTRUCT in a nested DELEGATECALL/CALLCODE frame below a same-tx-created contract does not refund state gas. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None new_account_state_gas = fork.gas_costs().NEW_ACCOUNT sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() @@ -709,7 +681,7 @@ def test_selfdestruct_via_delegatecall_chain_no_refund( tx = Transaction( to=factory, data=bytes(initcode), - gas_limit=gas_limit_cap + total_state_gas, + state_gas_reservoir=total_state_gas, sender=pre.fund_eoa(), ) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py index 1533cf5fa26..4b4252fb03c 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py @@ -18,7 +18,6 @@ Block, BlockchainTestFiller, Bytecode, - Environment, Fork, Header, Op, @@ -59,9 +58,6 @@ def test_authorization_state_gas_scaling( cost_per_state_byte of intrinsic state gas. The transaction should succeed with enough total gas. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - env = Environment() auth_state_gas = fork.transaction_intrinsic_state_gas( authorization_count=1, ) @@ -82,12 +78,12 @@ def test_authorization_state_gas_scaling( sender = pre.fund_eoa() tx = Transaction( to=contract, - gas_limit=gas_limit_cap + auth_state_gas * num_auths, + state_gas_reservoir=auth_state_gas * num_auths, authorization_list=authorization_list, sender=sender, ) - state_test(env=env, pre=pre, post={}, tx=tx) + state_test(pre=pre, post={}, tx=tx) @pytest.mark.exception_test @@ -162,7 +158,6 @@ def test_set_code_tx_below_total_intrinsic( def test_existing_account_refund( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """ Test authorization targeting existing account refunds state gas. @@ -172,10 +167,6 @@ def test_existing_account_refund( intrinsic_state_gas. Only 23 * cost_per_state_byte is effectively charged. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - env = Environment() - contract = pre.deploy_contract(code=Op.STOP) # Signer is an existing funded EOA (account_exists = True) @@ -195,12 +186,12 @@ def test_existing_account_refund( sender = pre.fund_eoa() tx = Transaction( to=contract, - gas_limit=gas_limit_cap, + state_gas_reservoir=0, authorization_list=authorization_list, sender=sender, ) - state_test(env=env, pre=pre, post={}, tx=tx) + state_test(pre=pre, post={}, tx=tx) @pytest.mark.valid_from("EIP8037") @@ -216,9 +207,6 @@ def test_mixed_new_and_existing_auths( another targets a new account (no refund). The total state gas should reflect the mixed charges. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - env = Environment() full_auth_state_gas = fork.transaction_intrinsic_state_gas( authorization_count=1, ) @@ -256,12 +244,12 @@ def test_mixed_new_and_existing_auths( sender = pre.fund_eoa() tx = Transaction( to=contract, - gas_limit=gas_limit_cap + full_auth_state_gas * 2, + state_gas_reservoir=full_auth_state_gas * 2, authorization_list=authorization_list, sender=sender, ) - state_test(env=env, pre=pre, post={}, tx=tx) + state_test(pre=pre, post={}, tx=tx) @pytest.mark.valid_from("EIP8037") @@ -277,9 +265,6 @@ def test_authorization_with_sstore( contract performs an SSTORE. Both the authorization state gas and the SSTORE state gas are charged. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - env = Environment() auth_state_gas = fork.transaction_intrinsic_state_gas( authorization_count=1, ) @@ -302,13 +287,13 @@ def test_authorization_with_sstore( sender = pre.fund_eoa() tx = Transaction( to=contract, - gas_limit=(gas_limit_cap + auth_state_gas + sstore_state_gas), + state_gas_reservoir=auth_state_gas + sstore_state_gas, authorization_list=authorization_list, sender=sender, ) post = {contract: Account(storage=storage)} - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) @pytest.mark.valid_from("EIP8037") @@ -325,9 +310,6 @@ def test_existing_account_refund_enables_sstore( This refunded gas should then be available for SSTORE state gas in the execution phase. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - env = Environment() auth_state_gas = fork.transaction_intrinsic_state_gas( authorization_count=1, ) @@ -353,13 +335,13 @@ def test_existing_account_refund_enables_sstore( sender = pre.fund_eoa() tx = Transaction( to=contract, - gas_limit=(gas_limit_cap + auth_state_gas + sstore_state_gas), + state_gas_reservoir=auth_state_gas + sstore_state_gas, authorization_list=authorization_list, sender=sender, ) post = {contract: Account(storage=storage)} - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) @pytest.mark.parametrize( @@ -407,8 +389,6 @@ def test_auth_refund_block_gas_accounting( Verified via header `gas_used`, receipt `cumulative_gas_used`, and the authority post-state (catches a silently-skipped auth). """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None intrinsic_state_gas = fork.transaction_intrinsic_state_gas( authorization_count=1, ) @@ -474,7 +454,7 @@ def test_auth_refund_block_gas_accounting( tx = Transaction( to=contract_new, - gas_limit=gas_limit_cap + intrinsic_state_gas, + state_gas_reservoir=intrinsic_state_gas, authorization_list=authorization_list, sender=pre.fund_eoa(), expected_receipt=TransactionReceipt( @@ -503,9 +483,6 @@ def test_invalid_nonce_auth_still_charges_intrinsic_state_gas( but its intrinsic state gas (135 * cpsb) is still charged upfront as part of the transaction's intrinsic gas. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - env = Environment() auth_state_gas = fork.transaction_intrinsic_state_gas( authorization_count=1, ) @@ -524,12 +501,12 @@ def test_invalid_nonce_auth_still_charges_intrinsic_state_gas( sender = pre.fund_eoa() tx = Transaction( to=contract, - gas_limit=gas_limit_cap + auth_state_gas, + state_gas_reservoir=auth_state_gas, authorization_list=authorization_list, sender=sender, ) - state_test(env=env, pre=pre, post={}, tx=tx) + state_test(pre=pre, post={}, tx=tx) @pytest.mark.valid_from("EIP8037") @@ -544,9 +521,6 @@ def test_invalid_chain_id_auth_still_charges_intrinsic_state_gas( An authorization with a mismatched chain ID is skipped during processing, but intrinsic state gas is still charged upfront. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - env = Environment() auth_state_gas = fork.transaction_intrinsic_state_gas( authorization_count=1, ) @@ -566,12 +540,12 @@ def test_invalid_chain_id_auth_still_charges_intrinsic_state_gas( sender = pre.fund_eoa() tx = Transaction( to=contract, - gas_limit=gas_limit_cap + auth_state_gas, + state_gas_reservoir=auth_state_gas, authorization_list=authorization_list, sender=sender, ) - state_test(env=env, pre=pre, post={}, tx=tx) + state_test(pre=pre, post={}, tx=tx) @pytest.mark.valid_from("EIP8037") @@ -588,9 +562,6 @@ def test_self_sponsored_authorization( charged. Since the sender account already exists, the new-account state gas refund applies. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - env = Environment() auth_state_gas = fork.transaction_intrinsic_state_gas( authorization_count=1, ) @@ -612,13 +583,13 @@ def test_self_sponsored_authorization( tx = Transaction( to=contract, - gas_limit=gas_limit_cap + auth_state_gas, + state_gas_reservoir=auth_state_gas, authorization_list=authorization_list, sender=sender, ) post = {contract: Account(storage=storage)} - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) @pytest.mark.valid_from("EIP8037") @@ -635,9 +606,6 @@ def test_duplicate_signer_authorizations( Only the last valid authorization takes effect, but all contribute to intrinsic state gas. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - env = Environment() auth_state_gas = fork.transaction_intrinsic_state_gas( authorization_count=1, ) @@ -664,12 +632,12 @@ def test_duplicate_signer_authorizations( sender = pre.fund_eoa() tx = Transaction( to=contract_a, - gas_limit=gas_limit_cap + auth_state_gas * 2, + state_gas_reservoir=auth_state_gas * 2, authorization_list=authorization_list, sender=sender, ) - state_test(env=env, pre=pre, post={}, tx=tx) + state_test(pre=pre, post={}, tx=tx) @pytest.mark.valid_from("EIP8037") @@ -685,9 +653,6 @@ def test_auth_with_calldata_and_access_list( authorization state gas. All components contribute to the total intrinsic gas requirement. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - env = Environment() auth_state_gas = fork.transaction_intrinsic_state_gas( authorization_count=1, ) @@ -711,14 +676,14 @@ def test_auth_with_calldata_and_access_list( sender = pre.fund_eoa() tx = Transaction( to=contract, - gas_limit=(gas_limit_cap + auth_state_gas + sstore_state_gas), + state_gas_reservoir=auth_state_gas + sstore_state_gas, data=b"\x00" * 31 + b"\x42", # Calldata adds to intrinsic gas authorization_list=authorization_list, sender=sender, ) post = {contract: Account(storage=storage)} - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) @pytest.mark.parametrize( @@ -745,9 +710,6 @@ def test_mixed_valid_and_invalid_auths( state gas is still consumed. The total intrinsic state gas equals (num_valid + num_invalid) * 135 * cpsb. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - env = Environment() auth_state_gas = fork.transaction_intrinsic_state_gas( authorization_count=1, ) @@ -782,12 +744,12 @@ def test_mixed_valid_and_invalid_auths( sender = pre.fund_eoa() tx = Transaction( to=contract, - gas_limit=gas_limit_cap + auth_state_gas * total_auths, + state_gas_reservoir=auth_state_gas * total_auths, authorization_list=authorization_list, sender=sender, ) - state_test(env=env, pre=pre, post={}, tx=tx) + state_test(pre=pre, post={}, tx=tx) @pytest.mark.valid_from("EIP8037") @@ -803,9 +765,6 @@ def test_many_authorizations_state_gas( The total state gas is drawn from the reservoir. Verifies that large authorization lists scale correctly. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - env = Environment() auth_state_gas = fork.transaction_intrinsic_state_gas( authorization_count=1, ) @@ -827,12 +786,12 @@ def test_many_authorizations_state_gas( sender = pre.fund_eoa() tx = Transaction( to=contract, - gas_limit=gas_limit_cap + auth_state_gas * num_auths, + state_gas_reservoir=auth_state_gas * num_auths, authorization_list=authorization_list, sender=sender, ) - state_test(env=env, pre=pre, post={}, tx=tx) + state_test(pre=pre, post={}, tx=tx) @pytest.mark.valid_from("EIP8037") @@ -848,9 +807,6 @@ def test_auth_with_multiple_sstores( charges all draw from the same reservoir. Verifies combined state gas accounting across intrinsic and execution phases. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - env = Environment() auth_state_gas = fork.transaction_intrinsic_state_gas( authorization_count=1, ) @@ -877,13 +833,13 @@ def test_auth_with_multiple_sstores( sender = pre.fund_eoa() tx = Transaction( to=contract, - gas_limit=gas_limit_cap + total_state_gas, + state_gas_reservoir=total_state_gas, authorization_list=authorization_list, sender=sender, ) post = {contract: Account(storage=storage)} - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) @pytest.mark.parametrize( @@ -970,9 +926,6 @@ def test_authorization_to_precompile_address( The authorization is processed and the signer's code is set to the precompile address delegation designator. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - env = Environment() auth_state_gas = fork.transaction_intrinsic_state_gas( authorization_count=1, ) @@ -992,12 +945,12 @@ def test_authorization_to_precompile_address( sender = pre.fund_eoa() tx = Transaction( to=signer, - gas_limit=gas_limit_cap + auth_state_gas, + state_gas_reservoir=auth_state_gas, authorization_list=authorization_list, sender=sender, ) - state_test(env=env, pre=pre, post={}, tx=tx) + state_test(pre=pre, post={}, tx=tx) @pytest.mark.valid_from("EIP8037") @@ -1018,8 +971,6 @@ def test_multi_tx_block_auth_refund_and_sstore( Verifies block-level state gas accounting correctly handles both the auth refund from tx1 and the SSTORE charge from tx2. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None auth_state_gas = fork.transaction_intrinsic_state_gas( authorization_count=1, ) @@ -1039,7 +990,7 @@ def test_multi_tx_block_auth_refund_and_sstore( sender_1 = pre.fund_eoa() tx_1 = Transaction( to=contract, - gas_limit=gas_limit_cap + auth_state_gas, + state_gas_reservoir=auth_state_gas, authorization_list=authorization_list, sender=sender_1, ) @@ -1052,7 +1003,7 @@ def test_multi_tx_block_auth_refund_and_sstore( sender_2 = pre.fund_eoa() tx_2 = Transaction( to=sstore_contract, - gas_limit=gas_limit_cap + sstore_state_gas, + state_gas_reservoir=sstore_state_gas, sender=sender_2, ) @@ -1083,9 +1034,6 @@ def test_auth_refund_bypasses_one_fifth_cap( the SSTOREs would OOG. By succeeding, this test proves the refund bypasses the cap. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - env = Environment() auth_state_gas = fork.transaction_intrinsic_state_gas( authorization_count=1, ) @@ -1122,15 +1070,13 @@ def test_auth_refund_bypasses_one_fifth_cap( sender = pre.fund_eoa() tx = Transaction( to=contract, - gas_limit=( - gas_limit_cap + auth_state_gas + sstore_state_gas * num_sstores - ), + state_gas_reservoir=auth_state_gas + sstore_state_gas * num_sstores, authorization_list=authorization_list, sender=sender, ) post = {contract: Account(storage=storage)} - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) @pytest.mark.parametrize( @@ -1159,8 +1105,6 @@ def test_existing_account_auth_header_gas_used_reflects_refund( header gas_used equals `max(intrinsic_regular, intrinsic_state - N * auth_refund)`. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None intrinsic_state_gas = fork.transaction_intrinsic_state_gas( authorization_count=num_auths, ) @@ -1179,7 +1123,7 @@ def test_existing_account_auth_header_gas_used_reflects_refund( tx = Transaction( to=contract, - gas_limit=gas_limit_cap + intrinsic_state_gas, + state_gas_reservoir=intrinsic_state_gas, authorization_list=authorization_list, sender=pre.fund_eoa(), ) @@ -1222,8 +1166,6 @@ def test_mixed_auths_header_gas_used_reflects_existing_refunds( authorities contribute none. Header gas_used is `max(intrinsic_regular, intrinsic_state - num_existing * refund)`. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None num_auths = num_existing + num_new intrinsic_state_gas = fork.transaction_intrinsic_state_gas( authorization_count=num_auths, @@ -1258,7 +1200,7 @@ def test_mixed_auths_header_gas_used_reflects_existing_refunds( tx = Transaction( to=contract, - gas_limit=gas_limit_cap + intrinsic_state_gas, + state_gas_reservoir=intrinsic_state_gas, authorization_list=authorization_list, sender=pre.fund_eoa(), ) @@ -1298,8 +1240,6 @@ def test_existing_auth_refund_survives_top_level_revert( with `execution_state` netting to 0 because of the revert. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None intrinsic_state_gas = fork.transaction_intrinsic_state_gas( authorization_count=1, ) @@ -1333,7 +1273,7 @@ def test_existing_auth_refund_survives_top_level_revert( tx = Transaction( to=contract, - gas_limit=gas_limit_cap + intrinsic_state_gas, + state_gas_reservoir=intrinsic_state_gas, authorization_list=authorization_list, sender=pre.fund_eoa(), ) @@ -1420,7 +1360,7 @@ def test_auth_state_gas_in_header_after_failure( tx = Transaction( ty=4, to=target, - gas_limit=tx_gas, + state_gas_reservoir=auth_intrinsic_state, sender=pre.fund_eoa(), authorization_list=[ AuthorizationTuple( @@ -1473,9 +1413,6 @@ def test_auth_sender_billing_after_failure( the sender's bill via the billing formula. The sender pays less than in the new-account case by exactly the refund amount. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - auth_intrinsic_state = fork.transaction_intrinsic_state_gas( authorization_count=1, ) @@ -1492,8 +1429,6 @@ def test_auth_sender_billing_after_failure( else: signer = pre.fund_eoa(0) - tx_gas = gas_limit_cap + auth_intrinsic_state - revert_gas = (Op.REVERT(0, 0)).gas_cost(fork) auth_refund = new_account_refund if authority_exists else 0 expected_cumulative = intrinsic_total + revert_gas - auth_refund @@ -1505,7 +1440,7 @@ def test_auth_sender_billing_after_failure( tx = Transaction( ty=4, to=target, - gas_limit=tx_gas, + state_gas_reservoir=auth_intrinsic_state, sender=pre.fund_eoa(), authorization_list=[ AuthorizationTuple( diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_sstore.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_sstore.py index 9ae878adea6..d8b25968d91 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_sstore.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_sstore.py @@ -20,7 +20,6 @@ Block, BlockchainTestFiller, Bytecode, - Environment, Fork, Header, Op, @@ -41,7 +40,6 @@ def test_sstore_zero_to_nonzero( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """ Test SSTORE zero-to-nonzero charges state gas. @@ -50,8 +48,6 @@ def test_sstore_zero_to_nonzero( STATE_BYTES_PER_STORAGE_SET * cost_per_state_byte of state gas in addition to regular gas. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None storage = Storage() contract = pre.deploy_contract( code=Op.SSTORE(storage.store_next(1), 1), @@ -59,7 +55,7 @@ def test_sstore_zero_to_nonzero( tx = Transaction( to=contract, - gas_limit=gas_limit_cap, + state_gas_reservoir=0, sender=pre.fund_eoa(), ) @@ -71,7 +67,6 @@ def test_sstore_zero_to_nonzero( def test_sstore_nonzero_to_nonzero( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """ Test SSTORE nonzero-to-nonzero charges no state gas. @@ -79,8 +74,6 @@ def test_sstore_nonzero_to_nonzero( Updating a slot that already holds a nonzero value to a different nonzero value does not create new state, so no state gas is charged. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None storage = Storage() contract = pre.deploy_contract( code=Op.SSTORE(storage.store_next(2), 2), @@ -89,7 +82,7 @@ def test_sstore_nonzero_to_nonzero( tx = Transaction( to=contract, - gas_limit=gas_limit_cap, + state_gas_reservoir=0, sender=pre.fund_eoa(), ) @@ -101,7 +94,6 @@ def test_sstore_nonzero_to_nonzero( def test_sstore_nonzero_to_zero( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """ Test SSTORE nonzero-to-zero charges no state gas. @@ -109,8 +101,6 @@ def test_sstore_nonzero_to_zero( Clearing a storage slot (setting to zero) does not grow state and earns a regular gas refund (GAS_STORAGE_CLEAR_REFUND). """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None storage = Storage() contract = pre.deploy_contract( code=Op.SSTORE(storage.store_next(0), 0), @@ -119,7 +109,7 @@ def test_sstore_nonzero_to_zero( tx = Transaction( to=contract, - gas_limit=gas_limit_cap, + state_gas_reservoir=0, sender=pre.fund_eoa(), ) @@ -131,7 +121,6 @@ def test_sstore_nonzero_to_zero( def test_sstore_zero_to_zero( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """ Test SSTORE zero-to-zero charges no state gas. @@ -139,8 +128,6 @@ def test_sstore_zero_to_zero( Writing zero to an already-zero slot creates no new state. Only the warm access regular gas cost is charged. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None storage = Storage() contract = pre.deploy_contract( code=Op.SSTORE(storage.store_next(0), 0), @@ -148,7 +135,7 @@ def test_sstore_zero_to_zero( tx = Transaction( to=contract, - gas_limit=gas_limit_cap, + state_gas_reservoir=0, sender=pre.fund_eoa(), ) @@ -250,7 +237,6 @@ def test_sstore_restoration_refund_credits_local_reservoir( def test_sstore_restoration_refund( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """ Test SSTORE zero-to-nonzero-to-zero restoration refunds state gas. @@ -260,15 +246,13 @@ def test_sstore_restoration_refund( (STATE_BYTES_PER_STORAGE_SET * cost_per_state_byte) is refunded via refund_counter along with the regular gas write cost. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None contract = pre.deploy_contract( code=(Op.SSTORE(0, 1) + Op.SSTORE(0, 0)), ) tx = Transaction( to=contract, - gas_limit=gas_limit_cap, + state_gas_reservoir=0, sender=pre.fund_eoa(), ) @@ -281,7 +265,6 @@ def test_sstore_restoration_refund( def test_sstore_restoration_nonzero_no_state_refund( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """ Test nonzero-to-nonzero-to-original restoration has no state gas refund. @@ -290,8 +273,6 @@ def test_sstore_restoration_nonzero_no_state_refund( restoring it never involves state gas (no state growth occurred), so only regular gas refunds apply. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None contract = pre.deploy_contract( code=(Op.SSTORE(0, 2) + Op.SSTORE(0, 1)), storage={0: 1}, @@ -299,7 +280,7 @@ def test_sstore_restoration_nonzero_no_state_refund( tx = Transaction( to=contract, - gas_limit=gas_limit_cap, + state_gas_reservoir=0, sender=pre.fund_eoa(), ) @@ -311,7 +292,6 @@ def test_sstore_restoration_nonzero_no_state_refund( def test_sstore_clear_refund_reversal( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """ Test clearing a nonzero slot then un-clearing reverses the refund. @@ -320,8 +300,6 @@ def test_sstore_clear_refund_reversal( the clear refund is granted. If the slot is then set back to a nonzero value, the clear refund is reversed via refund_counter. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None contract = pre.deploy_contract( code=(Op.SSTORE(0, 0) + Op.SSTORE(0, 2)), storage={0: 1}, @@ -329,7 +307,7 @@ def test_sstore_clear_refund_reversal( tx = Transaction( to=contract, - gas_limit=gas_limit_cap, + state_gas_reservoir=0, sender=pre.fund_eoa(), ) @@ -350,7 +328,6 @@ def test_sstore_multiple_slots( state_test: StateTestFiller, pre: Alloc, num_slots: int, - fork: Fork, ) -> None: """ Test multiple zero-to-nonzero SSTOREs each charge state gas. @@ -358,8 +335,6 @@ def test_sstore_multiple_slots( Each slot written from zero to nonzero independently charges STATE_BYTES_PER_STORAGE_SET * cost_per_state_byte of state gas. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None storage = Storage() code = Bytecode() for _ in range(num_slots): @@ -368,7 +343,7 @@ def test_sstore_multiple_slots( tx = Transaction( to=contract, - gas_limit=gas_limit_cap, + state_gas_reservoir=0, sender=pre.fund_eoa(), ) @@ -389,9 +364,6 @@ def test_sstore_state_gas_drawn_from_reservoir( SSTORE state gas from the reservoir, leaving gas_left untouched by the state gas charge. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - env = Environment() sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) storage = Storage() @@ -401,12 +373,12 @@ def test_sstore_state_gas_drawn_from_reservoir( tx = Transaction( to=contract, - gas_limit=gas_limit_cap + sstore_state_gas, + state_gas_reservoir=sstore_state_gas, sender=pre.fund_eoa(), ) post = {contract: Account(storage=storage)} - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) @pytest.mark.with_all_typed_transactions @@ -415,27 +387,20 @@ def test_sstore_state_gas_all_tx_types( state_test: StateTestFiller, pre: Alloc, typed_transaction: Transaction, - fork: Fork, ) -> None: """ Test SSTORE state gas works across all transaction types. - Different tx types (legacy, access list, EIP-1559, blob, SetCode) - have different intrinsic costs, which affects the gas split between - gas_left and state_gas_reservoir. Verify SSTORE succeeds with - each type. + With the gas limit pinned to the cap (zero reservoir), each tx + type's SSTORE state gas spills into gas_left despite differing + intrinsic costs. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None storage = Storage() contract = pre.deploy_contract( code=Op.SSTORE(storage.store_next(1), 1), ) - tx = typed_transaction.copy( - to=contract, - gas_limit=gas_limit_cap, - ) + tx = typed_transaction.copy(to=contract, state_gas_reservoir=0) post = {contract: Account(storage=storage)} state_test(pre=pre, post=post, tx=tx) @@ -502,12 +467,10 @@ def test_sstore_stipend_check_excludes_reservoir( ) ) - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None tx = Transaction( sender=pre.fund_eoa(), to=caller, - gas_limit=gas_limit_cap + sstore_state_gas, + state_gas_reservoir=sstore_state_gas, ) post = {caller: Account(storage=caller_storage)} @@ -535,8 +498,6 @@ def test_sstore_restoration_block_state_gas_zero( `state_gas_reservoir` rather than `refund_counter`, so block state gas is not inflated by the charges. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() @@ -555,7 +516,7 @@ def test_sstore_restoration_block_state_gas_zero( contract = pre.deploy_contract(code=code) tx = Transaction( to=contract, - gas_limit=gas_limit_cap + num_cycles * sstore_state_gas, + state_gas_reservoir=num_cycles * sstore_state_gas, sender=pre.fund_eoa(), ) @@ -587,8 +548,6 @@ def test_sstore_restoration_mixed_with_genuine_sstore( persists, contributing exactly one `sstore_state_gas` to block state gas. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() @@ -611,7 +570,7 @@ def test_sstore_restoration_mixed_with_genuine_sstore( contract = pre.deploy_contract(code=code) tx = Transaction( to=contract, - gas_limit=gas_limit_cap + num_0_to_1 * sstore_state_gas, + state_gas_reservoir=num_0_to_1 * sstore_state_gas, sender=pre.fund_eoa(), ) @@ -638,8 +597,6 @@ def test_sstore_restoration_intermediate_values( first 0 to x; no charge for nonzero-to-nonzero; refund to reservoir at y to 0. Net block state gas is zero. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() @@ -663,7 +620,7 @@ def test_sstore_restoration_intermediate_values( contract = pre.deploy_contract(code=code) tx = Transaction( to=contract, - gas_limit=gas_limit_cap + sstore_state_gas, + state_gas_reservoir=sstore_state_gas, sender=pre.fund_eoa(), ) @@ -687,8 +644,6 @@ def test_sstore_restoration_then_reset( the subsequent 0 to 1 re-charges state gas. Net: one charge remains, one state gas worth counted in block state gas. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() @@ -713,7 +668,7 @@ def test_sstore_restoration_then_reset( contract = pre.deploy_contract(code=code) tx = Transaction( to=contract, - gas_limit=gas_limit_cap + sstore_state_gas, + state_gas_reservoir=sstore_state_gas, sender=pre.fund_eoa(), ) @@ -737,8 +692,6 @@ def test_sstore_restoration_reservoir_replenished_inline( on slot 0, the reservoir refill allows a second 0 to 1 on slot 1 to draw from it. Block state gas reflects only slot 1. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() @@ -758,7 +711,7 @@ def test_sstore_restoration_reservoir_replenished_inline( contract = pre.deploy_contract(code=code) tx = Transaction( to=contract, - gas_limit=gas_limit_cap + sstore_state_gas, + state_gas_reservoir=sstore_state_gas, sender=pre.fund_eoa(), ) @@ -788,8 +741,6 @@ def test_sstore_restoration_cross_frame( applies regardless of storage ownership. Net block state gas is zero. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() @@ -814,7 +765,7 @@ def test_sstore_restoration_cross_frame( tx = Transaction( to=parent, - gas_limit=gas_limit_cap + sstore_state_gas, + state_gas_reservoir=sstore_state_gas, sender=pre.fund_eoa(), ) @@ -856,8 +807,6 @@ def test_sstore_restoration_charge_in_ancestor( refund must propagate up the chain to the ancestor that charged the 0 to x. A probe SSTORE sized to OOG by 1 detects any loss. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None gas_costs = fork.gas_costs() sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) probe_gas = ( @@ -903,7 +852,7 @@ def test_sstore_restoration_charge_in_ancestor( tx = Transaction( sender=pre.fund_eoa(), to=parent, - gas_limit=gas_limit_cap + sstore_state_gas, + state_gas_reservoir=sstore_state_gas, ) post = {parent: Account(storage=parent_storage)} @@ -958,7 +907,7 @@ def test_sstore_restoration_sub_frame_revert( tx = Transaction( sender=pre.fund_eoa(), to=caller, - gas_limit=fork.transaction_gas_limit_cap(), + state_gas_reservoir=0, ) post = {caller: Account(storage=caller_storage)} @@ -1042,7 +991,7 @@ def test_sstore_restoration_ancestor_revert( tx = Transaction( sender=pre.fund_eoa(), to=caller, - gas_limit=fork.transaction_gas_limit_cap(), + state_gas_reservoir=0, ) state_test( @@ -1075,8 +1024,6 @@ def test_sstore_restoration_charge_in_ancestor_intermediate_revert( """ gas_costs = fork.gas_costs() sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None intrinsic_cost = fork.transaction_intrinsic_cost_calculator()() # Probe SSTORE(0, 1): 2 pushes + cold storage write + state gas - 1, # so it OOGs by 1 when the reservoir is 0 and succeeds otherwise. @@ -1142,7 +1089,7 @@ def test_sstore_restoration_charge_in_ancestor_intermediate_revert( tx = Transaction( sender=pre.fund_eoa(), to=caller, - gas_limit=gas_limit_cap + 2 * sstore_state_gas, + state_gas_reservoir=2 * sstore_state_gas, ) state_test( @@ -1219,7 +1166,7 @@ def test_sstore_restoration_create_init_revert( # gas_limit at the cap means the caller's reservoir starts at 0. tx = Transaction( to=caller, - gas_limit=fork.transaction_gas_limit_cap(), + state_gas_reservoir=0, sender=pre.fund_eoa(), ) @@ -1243,8 +1190,6 @@ def test_sstore_restoration_create_init_success( restoration path works inside init and the refund doesn't disturb deployment. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) create_state_gas = fork.create_state_gas(code_size=0) @@ -1281,7 +1226,7 @@ def test_sstore_restoration_create_init_success( tx = Transaction( to=caller, - gas_limit=gas_limit_cap + create_state_gas + sstore_state_gas, + state_gas_reservoir=create_state_gas + sstore_state_gas, sender=pre.fund_eoa(), ) @@ -1303,8 +1248,6 @@ def test_sstore_restoration_reservoir_spillover( `state_gas_reservoir` (not back to gas_left), moving gas between buckets. Block state gas is zero. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() @@ -1319,7 +1262,7 @@ def test_sstore_restoration_reservoir_spillover( contract = pre.deploy_contract(code=code) tx = Transaction( to=contract, - gas_limit=gas_limit_cap, + state_gas_reservoir=0, sender=pre.fund_eoa(), ) diff --git a/tests/benchmark/compute/precompile/test_blake2f.py b/tests/benchmark/compute/precompile/test_blake2f.py index 7083d108e56..0fad03827b0 100644 --- a/tests/benchmark/compute/precompile/test_blake2f.py +++ b/tests/benchmark/compute/precompile/test_blake2f.py @@ -19,7 +19,7 @@ from tests.istanbul.eip152_blake2.common import Blake2bInput from tests.istanbul.eip152_blake2.spec import Spec as Blake2bSpec -from ..helpers import Precompile, concatenate_parameters +from ..helpers import Precompile @pytest.mark.parametrize( @@ -27,13 +27,7 @@ [ pytest.param( Blake2bSpec.BLAKE2_PRECOMPILE_ADDRESS, - concatenate_parameters( - [ - Blake2bInput( - rounds=0xFFFF, f=True - ).create_blake2b_tx_data(), - ] - ), + Blake2bInput(rounds=0xFFFF, f=True), id="blake2f", ), ], @@ -76,7 +70,7 @@ def test_blake2f_benchmark( if precompile_address not in fork.precompiles(): pytest.skip("Precompile not enabled") - calldata = Blake2bInput(rounds=num_rounds, f=True).create_blake2b_tx_data() + calldata = Blake2bInput(rounds=num_rounds, f=True) attack_block = Op.POP( Op.STATICCALL( @@ -163,9 +157,7 @@ def test_blake2f_uncachable( iteration_cost = loop.gas_cost(fork) - base_calldata = Blake2bInput( - rounds=num_rounds, f=True - ).create_blake2b_tx_data() + base_calldata = bytes(Blake2bInput(rounds=num_rounds, f=True)) txs: list[Transaction] = [] remaining_gas = gas_benchmark_value diff --git a/tests/benchmark/stateful/helpers.py b/tests/benchmark/stateful/helpers.py index c746038a7d2..033d5320499 100644 --- a/tests/benchmark/stateful/helpers.py +++ b/tests/benchmark/stateful/helpers.py @@ -158,13 +158,14 @@ def pack_transactions_into_blocks( current_gas = 0 for tx in transactions: - if current_gas + tx.gas_limit > gas_limit and current_txs: + tx_gas_limit = tx.gas_limit + if current_gas + tx_gas_limit > gas_limit and current_txs: blocks.append(Block(txs=current_txs)) current_txs = [] current_gas = 0 current_txs.append(tx) - current_gas += tx.gas_limit + current_gas += tx_gas_limit if current_txs: blocks.append(Block(txs=current_txs)) diff --git a/tests/berlin/eip2929_gas_cost_increases/test_call.py b/tests/berlin/eip2929_gas_cost_increases/test_call.py index ab3e21d1d03..d9e1c562bf7 100644 --- a/tests/berlin/eip2929_gas_cost_increases/test_call.py +++ b/tests/berlin/eip2929_gas_cost_increases/test_call.py @@ -27,32 +27,31 @@ def test_call_insufficient_balance( """ destination = pre.fund_eoa(1) warm_code = Op.BALANCE(destination, address_warm=True) - contract_code = Op.SSTORE( - 0, - Op.CALL( - gas=Op.GAS, - address=destination, - value=1, - args_offset=0, - args_size=0, - ret_offset=0, - ret_size=0, + contract_address = pre.deploy_contract( + # Perform the aborted external calls + Op.SSTORE( + 0, + Op.CALL( + gas=Op.GAS, + address=destination, + value=1, + args_offset=0, + args_size=0, + ret_offset=0, + ret_size=0, + ), + ) + # Measure the gas cost for BALANCE operation + + CodeGasMeasure( + code=warm_code, + extra_stack_items=1, # BALANCE puts balance on stack + sstore_key=1, ), - ) + CodeGasMeasure( - code=warm_code, - extra_stack_items=1, # BALANCE puts balance on stack - sstore_key=1, + balance=0, ) - contract_address = pre.deploy_contract(contract_code, balance=0) - intrinsic_calc = fork.transaction_intrinsic_cost_calculator() tx = Transaction( to=contract_address, - gas_limit=( - intrinsic_calc() - + contract_code.gas_cost(fork) - + Op.SSTORE(new_value=1).state_cost(fork) - ), sender=pre.fund_eoa(), ) diff --git a/tests/berlin/eip2930_access_list/test_acl.py b/tests/berlin/eip2930_access_list/test_acl.py index 0564b93ad17..f8cec0ed3f5 100644 --- a/tests/berlin/eip2930_access_list/test_acl.py +++ b/tests/berlin/eip2930_access_list/test_acl.py @@ -85,28 +85,12 @@ def test_account_storage_warm_cold_state( sender = pre.fund_eoa() - contract_creation = False tx_data = b"" - intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator() - - # CodeGasMeasure SSTOREs the measured cost; budget for one - # first-time SSTORE whose state gas scales with cpsb on Amsterdam. - tx_gas_limit = ( - intrinsic_gas_calculator( - calldata=tx_data, - contract_creation=contract_creation, - access_list=access_lists, - ) - + 100_000 - + Op.SSTORE(new_value=1).state_cost(fork) - ) - tx = Transaction( ty=1, data=tx_data, to=contract_address, - gas_limit=tx_gas_limit, access_list=access_lists, sender=sender, ) @@ -317,7 +301,6 @@ def test_repeated_address_acl( contract = pre.deploy_contract(sload0_measure + sload1_measure) tx = Transaction( - gas_limit=500_000, to=contract, value=0, sender=sender, diff --git a/tests/byzantium/eip196_ec_add_mul/conftest.py b/tests/byzantium/eip196_ec_add_mul/conftest.py index 42e0d29776f..bb0f331c3d4 100644 --- a/tests/byzantium/eip196_ec_add_mul/conftest.py +++ b/tests/byzantium/eip196_ec_add_mul/conftest.py @@ -16,7 +16,6 @@ precompile_gas_modifier, # noqa: F401 sender, # noqa: F401 tx, # noqa: F401 - tx_gas_limit, # noqa: F401 ) from .spec import Spec diff --git a/tests/byzantium/eip197_ec_pairing/conftest.py b/tests/byzantium/eip197_ec_pairing/conftest.py index 8f3164caadf..adb21442569 100644 --- a/tests/byzantium/eip197_ec_pairing/conftest.py +++ b/tests/byzantium/eip197_ec_pairing/conftest.py @@ -13,7 +13,6 @@ precompile_gas_modifier, # noqa: F401 sender, # noqa: F401 tx, # noqa: F401 - tx_gas_limit, # noqa: F401 ) diff --git a/tests/byzantium/eip214_staticcall/test_staticcall.py b/tests/byzantium/eip214_staticcall/test_staticcall.py index caf1f15c5f1..0e1430a66ab 100644 --- a/tests/byzantium/eip214_staticcall/test_staticcall.py +++ b/tests/byzantium/eip214_staticcall/test_staticcall.py @@ -143,21 +143,9 @@ def test_staticcall_reentrant_call_to_precompile( target = pre.deploy_contract(code=target_code, balance=target_balance) tx_value = 100 - # The outer SSTORE (slot 0 = STATICCALL result) needs state work even - # though STATICCALL forwards 63/64 of remaining gas to the reentrant - # frame. Lift past the EIP-7825 cap so the EIP-8037 reservoir hosts - # the SSTORE state. - gas_cap = fork.transaction_gas_limit_cap() - sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) - if gas_cap is not None and sstore_state_gas > 0: - gas_limit = gas_cap + sstore_state_gas - else: - gas_limit = 1_000_000 - tx = Transaction( sender=alice, to=target, - gas_limit=gas_limit, value=tx_value, protected=True, ) @@ -324,7 +312,6 @@ def test_staticcall_call_to_precompile( tx=Transaction( sender=alice, to=contract_a, - gas_limit=500_000, value=tx_value, protected=True, ), @@ -453,22 +440,11 @@ def test_staticcall_nested_call_to_precompile( account_expectations=account_expectations ) - # Six SSTOREs across A and B, plus CALL/STATICCALL forwarding 63/64 - # at each frame. Lift past the EIP-7825 cap so the EIP-8037 reservoir - # holds the SSTORE state work for both contracts. - gas_cap = fork.transaction_gas_limit_cap() - sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) - if gas_cap is not None and sstore_state_gas > 0: - gas_limit = gas_cap + 6 * sstore_state_gas - else: - gas_limit = 500_000 - state_test( pre=pre, tx=Transaction( sender=alice, to=contract_b, - gas_limit=gas_limit, value=tx_value, protected=True, ), @@ -669,7 +645,6 @@ def test_staticcall_call_to_precompile_from_contract_init( tx=Transaction( sender=alice, to=contract_a, - gas_limit=4_000_000, value=tx_value, data=bytes(initcode), protected=True, diff --git a/tests/cancun/eip1153_tstore/test_basic_tload.py b/tests/cancun/eip1153_tstore/test_basic_tload.py index 6cd9eddd647..acbb4c5cd24 100644 --- a/tests/cancun/eip1153_tstore/test_basic_tload.py +++ b/tests/cancun/eip1153_tstore/test_basic_tload.py @@ -10,7 +10,6 @@ Account, Address, Alloc, - Environment, Fork, Op, StateTestFiller, @@ -62,16 +61,9 @@ def test_basic_tload_transaction_begin( ) } - tx = Transaction( - sender=pre.fund_eoa(7_000_000_000_000_000_000), - to=address_to, - gas_price=10, - data=b"", - gas_limit=5000000, - value=0, - ) + tx = Transaction(sender=pre.fund_eoa(), to=address_to) - state_test(env=Environment(), pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) @pytest.mark.ported_from( @@ -120,16 +112,9 @@ def test_basic_tload_works( ) } - tx = Transaction( - sender=pre.fund_eoa(7_000_000_000_000_000_000), - to=address_to, - gas_price=10, - data=b"", - gas_limit=5000000, - value=0, - ) + tx = Transaction(sender=pre.fund_eoa(), to=address_to) - state_test(env=Environment(), pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) @pytest.mark.ported_from( @@ -174,16 +159,9 @@ def test_basic_tload_other_after_tstore( ) } - tx = Transaction( - sender=pre.fund_eoa(7_000_000_000_000_000_000), - to=address_to, - gas_price=10, - data=b"", - gas_limit=5000000, - value=0, - ) + tx = Transaction(sender=pre.fund_eoa(), to=address_to) - state_test(env=Environment(), pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) @pytest.mark.ported_from( @@ -270,16 +248,9 @@ def test_basic_tload_gasprice( ) } - tx = Transaction( - sender=pre.fund_eoa(7_000_000_000_000_000_000), - to=address_to, - gas_price=10, - data=b"", - gas_limit=5000000, - value=0, - ) + tx = Transaction(sender=pre.fund_eoa(), to=address_to) - state_test(env=Environment(), pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) @pytest.mark.ported_from( @@ -324,13 +295,6 @@ def test_basic_tload_after_store( } ) - tx = Transaction( - sender=pre.fund_eoa(7_000_000_000_000_000_000), - to=address_to, - gas_price=10, - data=b"", - gas_limit=5000000, - value=0, - ) + tx = Transaction(sender=pre.fund_eoa(), to=address_to) - state_test(env=Environment(), pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/cancun/eip1153_tstore/test_tload_calls.py b/tests/cancun/eip1153_tstore/test_tload_calls.py index fdfeb521937..3e153e7330b 100644 --- a/tests/cancun/eip1153_tstore/test_tload_calls.py +++ b/tests/cancun/eip1153_tstore/test_tload_calls.py @@ -8,7 +8,6 @@ Address, Alloc, Bytecode, - Environment, Op, StateTestFiller, Transaction, @@ -104,13 +103,6 @@ def make_call(call_type: Op, address: Address) -> Bytecode: ), } - tx = Transaction( - sender=pre.fund_eoa(7_000_000_000_000_000_000), - to=address_to, - gas_price=10, - data=b"", - gas_limit=5000000, - value=0, - ) + tx = Transaction(sender=pre.fund_eoa(), to=address_to) - state_test(env=Environment(), pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/cancun/eip1153_tstore/test_tload_reentrancy.py b/tests/cancun/eip1153_tstore/test_tload_reentrancy.py index 57e3ae2b10b..d659622090a 100644 --- a/tests/cancun/eip1153_tstore/test_tload_reentrancy.py +++ b/tests/cancun/eip1153_tstore/test_tload_reentrancy.py @@ -11,7 +11,6 @@ Alloc, Bytecode, Case, - Environment, Hash, Op, StateTestFiller, @@ -165,12 +164,7 @@ def make_call(call_type: Op) -> Bytecode: } tx = Transaction( - sender=pre.fund_eoa(7_000_000_000_000_000_000), - to=address_to, - gas_price=10, - data=Hash(do_reenter), - gas_limit=5000000, - value=0, + sender=pre.fund_eoa(), to=address_to, data=Hash(do_reenter) ) - state_test(env=Environment(), pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/cancun/eip1153_tstore/test_tstorage.py b/tests/cancun/eip1153_tstore/test_tstorage.py index 14ca24b69bd..6544c5a4931 100644 --- a/tests/cancun/eip1153_tstore/test_tstorage.py +++ b/tests/cancun/eip1153_tstore/test_tstorage.py @@ -13,7 +13,6 @@ Alloc, Bytecode, CodeGasMeasure, - Environment, Fork, Op, StateTestFiller, @@ -44,8 +43,6 @@ def test_transient_storage_unset_values( 9b00b68593f5869eb51a6659e1cc983e875e616b/src/EIPTestsFiller/StateTests/ stEIP1153-transientStorage/01_tloadBeginningTxnFiller.yml)", """ - env = Environment() - slots_under_test = [0, 1, 2, 2**128, 2**256 - 1] code = sum(Op.SSTORE(slot, Op.TLOAD(slot)) for slot in slots_under_test) @@ -54,20 +51,11 @@ def test_transient_storage_unset_values( storage=dict.fromkeys(slots_under_test, 1), ) - tx = Transaction( - sender=pre.fund_eoa(), - to=code_address, - gas_limit=1_000_000, - ) + tx = Transaction(sender=pre.fund_eoa(), to=code_address) post = {code_address: Account(storage=dict.fromkeys(slots_under_test, 0))} - state_test( - env=env, - pre=pre, - post=post, - tx=tx, - ) + state_test(pre=pre, post=post, tx=tx) def test_tload_after_tstore(state_test: StateTestFiller, pre: Alloc) -> None: @@ -81,8 +69,6 @@ def test_tload_after_tstore(state_test: StateTestFiller, pre: Alloc) -> None: 9b00b68593f5869eb51a6659e1cc983e875e616b/src/EIPTestsFiller/StateTests/ stEIP1153-transientStorage/02_tloadAfterTstoreFiller.yml)", """ - env = Environment() - slots_under_test = [0, 1, 2, 2**128, 2**256 - 1] code = sum( Op.TSTORE(slot, slot) + Op.SSTORE(slot, Op.TLOAD(slot)) @@ -93,11 +79,7 @@ def test_tload_after_tstore(state_test: StateTestFiller, pre: Alloc) -> None: storage=dict.fromkeys(slots_under_test, 0xFF), ) - tx = Transaction( - sender=pre.fund_eoa(), - to=code_address, - gas_limit=1_000_000, - ) + tx = Transaction(sender=pre.fund_eoa(), to=code_address) post = { code_address: Account( @@ -105,12 +87,7 @@ def test_tload_after_tstore(state_test: StateTestFiller, pre: Alloc) -> None: ) } - state_test( - env=env, - pre=pre, - post=post, - tx=tx, - ) + state_test(pre=pre, post=post, tx=tx) def test_tload_after_sstore(state_test: StateTestFiller, pre: Alloc) -> None: @@ -125,8 +102,6 @@ def test_tload_after_sstore(state_test: StateTestFiller, pre: Alloc) -> None: EIPTestsFiller/StateTests/stEIP1153-transientStorage/ 18_tloadAfterStoreFiller.yml)", """ - env = Environment() - slots_under_test = [1, 3, 2**128, 2**256 - 1] code = sum( Op.SSTORE(slot - 1, 0xFF) + Op.SSTORE(slot, Op.TLOAD(slot - 1)) @@ -137,11 +112,7 @@ def test_tload_after_sstore(state_test: StateTestFiller, pre: Alloc) -> None: storage=dict.fromkeys(slots_under_test, 1), ) - tx = Transaction( - sender=pre.fund_eoa(), - to=code_address, - gas_limit=1_000_000, - ) + tx = Transaction(sender=pre.fund_eoa(), to=code_address) post = { code_address: Account( @@ -151,12 +122,7 @@ def test_tload_after_sstore(state_test: StateTestFiller, pre: Alloc) -> None: ) } - state_test( - env=env, - pre=pre, - post=post, - tx=tx, - ) + state_test(pre=pre, post=post, tx=tx) def test_tload_after_tstore_is_zero( @@ -171,8 +137,6 @@ def test_tload_after_tstore_is_zero( EIPTestsFiller/StateTests/ stEIP1153-transientStorage/03_tloadAfterStoreIs0Filler.yml)", """ - env = Environment() - slots_to_write = [1, 4, 2**128, 2**256 - 2] slots_to_read = [slot - 1 for slot in slots_to_write] + [ slot + 1 for slot in slots_to_write @@ -188,11 +152,7 @@ def test_tload_after_tstore_is_zero( storage=dict.fromkeys(slots_to_write + slots_to_read, 0xFFFF), ) - tx = Transaction( - sender=pre.fund_eoa(), - to=code_address, - gas_limit=1_000_000, - ) + tx = Transaction(sender=pre.fund_eoa(), to=code_address) post = { code_address: Account( @@ -201,12 +161,7 @@ def test_tload_after_tstore_is_zero( ) } - state_test( - env=env, - pre=pre, - post=post, - tx=tx, - ) + state_test(pre=pre, post=post, tx=tx) @unique @@ -260,16 +215,11 @@ def test_gas_usage( extra_stack_items=extra_stack_items, ) - env = Environment() code_address = pre.deploy_contract(code=gas_measure_bytecode) - tx = Transaction( - sender=pre.fund_eoa(), - to=code_address, - gas_limit=1_000_000, - ) + tx = Transaction(sender=pre.fund_eoa(), to=code_address) post = { code_address: Account( code=gas_measure_bytecode, storage={0: expected_gas} ), } - state_test(env=env, pre=pre, tx=tx, post=post) + state_test(pre=pre, tx=tx, post=post) diff --git a/tests/cancun/eip1153_tstore/test_tstorage_clear_after_tx.py b/tests/cancun/eip1153_tstore/test_tstorage_clear_after_tx.py index 77b6bc20d00..22049cfbe7c 100644 --- a/tests/cancun/eip1153_tstore/test_tstorage_clear_after_tx.py +++ b/tests/cancun/eip1153_tstore/test_tstorage_clear_after_tx.py @@ -6,8 +6,6 @@ Alloc, Block, BlockchainTestFiller, - Environment, - Fork, Initcode, Op, Transaction, @@ -22,7 +20,6 @@ @pytest.mark.valid_from("Cancun") def test_tstore_clear_after_deployment_tx( blockchain_test: BlockchainTestFiller, - fork: Fork, pre: Alloc, ) -> None: """ @@ -31,8 +28,6 @@ def test_tstore_clear_after_deployment_tx( 1. The transient storage should be cleared after creating the contract (at tx-level), so the storage should stay empty. """ - env = Environment() - init_code = Op.TSTORE(1, 1) deploy_code = Op.SSTORE(1, Op.TLOAD(1)) @@ -40,24 +35,11 @@ def test_tstore_clear_after_deployment_tx( sender = pre.fund_eoa() - gas_limit = 100_000 - if fork.is_eip_enabled(8037): - gas_limit = 500_000 - - deployment_tx = Transaction( - gas_limit=gas_limit, - data=code, - to=None, - sender=sender, - ) + deployment_tx = Transaction(data=code, to=None, sender=sender) address = deployment_tx.created_contract - invoke_contract_tx = Transaction( - gas_limit=gas_limit, - to=address, - sender=sender, - ) + invoke_contract_tx = Transaction(to=address, sender=sender) txs = [deployment_tx, invoke_contract_tx] @@ -65,9 +47,7 @@ def test_tstore_clear_after_deployment_tx( address: Account(storage={0x01: 0x00}), } - blockchain_test( - genesis_environment=env, pre=pre, post=post, blocks=[Block(txs=txs)] - ) + blockchain_test(pre=pre, post=post, blocks=[Block(txs=txs)]) @pytest.mark.valid_from("Cancun") @@ -80,22 +60,14 @@ def test_tstore_clear_after_tx( slot 1. The second tx will re-call the contract. The storage should stay empty, because the transient storage is cleared after the transaction. """ - env = Environment() - code = Op.SSTORE(1, Op.TLOAD(1)) + Op.TSTORE(1, 1) account = pre.deploy_contract(code) sender = pre.fund_eoa() - poke_tstore_tx = Transaction( - gas_limit=100000, - to=account, - sender=sender, - ) + poke_tstore_tx = Transaction(to=account, sender=sender) - re_poke_tstore_tx = Transaction( - gas_limit=100000, to=account, sender=sender - ) + re_poke_tstore_tx = Transaction(to=account, sender=sender) txs = [poke_tstore_tx, re_poke_tstore_tx] @@ -103,6 +75,4 @@ def test_tstore_clear_after_tx( account: Account(storage={0x01: 0x00}), } - blockchain_test( - genesis_environment=env, pre=pre, post=post, blocks=[Block(txs=txs)] - ) + blockchain_test(pre=pre, post=post, blocks=[Block(txs=txs)]) diff --git a/tests/cancun/eip1153_tstore/test_tstorage_create_contexts.py b/tests/cancun/eip1153_tstore/test_tstorage_create_contexts.py index f382fcaba45..bd69ce23086 100644 --- a/tests/cancun/eip1153_tstore/test_tstorage_create_contexts.py +++ b/tests/cancun/eip1153_tstore/test_tstorage_create_contexts.py @@ -244,7 +244,6 @@ def test_contract_creation( sender=sender, to=creator_address, data=initcode, - gas_limit=1_000_000, ) post = { @@ -328,21 +327,10 @@ def test_tstore_rollback_on_failed_create( ) caller_address = pre.deploy_contract(caller_code, storage={0: 1, 1: 1}) - gas_limit = 16_000_000 - if fork.is_eip_enabled(8037): - gas_limit_cap = fork.transaction_gas_limit_cap() or gas_limit - code_deposit_state = fork.code_deposit_state_gas( - code_size=max_code_size + 0x0A - ) - new_account_state = fork.gas_costs().NEW_ACCOUNT - state_gas = 2 * (code_deposit_state + new_account_state) - gas_limit = gas_limit_cap + state_gas - sender = pre.fund_eoa() tx = Transaction( sender=sender, to=caller_address, - gas_limit=gas_limit, access_list=[ AccessList(address=caller_address, storage_keys=[0, 1]), ], diff --git a/tests/cancun/eip1153_tstore/test_tstorage_execution_contexts.py b/tests/cancun/eip1153_tstore/test_tstorage_execution_contexts.py index 25708e81a15..9f9048dab30 100644 --- a/tests/cancun/eip1153_tstore/test_tstorage_execution_contexts.py +++ b/tests/cancun/eip1153_tstore/test_tstorage_execution_contexts.py @@ -350,7 +350,6 @@ def tx( # noqa: D103 sender=pre.fund_eoa(), to=caller_address, data=Hash(callee_address, left_padding=True), - gas_limit=1_000_000, ) @@ -384,7 +383,7 @@ def test_subcall( - `DELEGATECALL` - `STATICCALL` """ - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) @pytest.mark.ported_from( @@ -421,11 +420,7 @@ def test_tstore_rollback_on_callcode_revert( caller_address = pre.deploy_contract(caller_code) sender = pre.fund_eoa() - tx = Transaction( - sender=sender, - to=caller_address, - gas_limit=1_000_000, - ) + tx = Transaction(sender=sender, to=caller_address) post = { # CALLCODE returns 0 (reverted), TLOAD(4) = 0 (rolled back) diff --git a/tests/cancun/eip1153_tstore/test_tstorage_reentrancy_contexts.py b/tests/cancun/eip1153_tstore/test_tstorage_reentrancy_contexts.py index ad52b674dac..0a121c508d3 100644 --- a/tests/cancun/eip1153_tstore/test_tstorage_reentrancy_contexts.py +++ b/tests/cancun/eip1153_tstore/test_tstorage_reentrancy_contexts.py @@ -12,7 +12,6 @@ Bytecode, CalldataCase, Conditional, - Environment, Hash, Op, StateTestFiller, @@ -342,17 +341,14 @@ def test_reentrant_call( expected_storage: Dict, ) -> None: """Test transient storage in different reentrancy contexts.""" - env = Environment() - callee_address = pre.deploy_contract(bytecode) tx = Transaction( sender=pre.fund_eoa(), to=callee_address, data=Hash(1), - gas_limit=1_000_000, ) post = {callee_address: Account(code=bytecode, storage=expected_storage)} - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/cancun/eip1153_tstore/test_tstorage_selfdestruct.py b/tests/cancun/eip1153_tstore/test_tstorage_selfdestruct.py index b1d9cd3d1cf..7557602b971 100644 --- a/tests/cancun/eip1153_tstore/test_tstorage_selfdestruct.py +++ b/tests/cancun/eip1153_tstore/test_tstorage_selfdestruct.py @@ -14,7 +14,6 @@ Alloc, Bytecode, CalldataCase, - Environment, Hash, Initcode, Op, @@ -255,8 +254,6 @@ def test_reentrant_selfdestructing_call( Test transient storage in different reentrancy contexts after selfdestructing. """ - env = Environment() - caller_address = pre.deploy_contract(code=caller_bytecode) data: bytes | Bytecode @@ -272,7 +269,6 @@ def test_reentrant_selfdestructing_call( tx = Transaction( sender=pre.fund_eoa(), to=caller_address, - gas_limit=1_000_000, data=data, ) @@ -285,4 +281,4 @@ def test_reentrant_selfdestructing_call( else: post[callee_address] = Account.NONEXISTENT - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/cancun/eip1153_tstore/test_tstore_reentrancy.py b/tests/cancun/eip1153_tstore/test_tstore_reentrancy.py index 9fb864cbcfb..ce4146fd56e 100644 --- a/tests/cancun/eip1153_tstore/test_tstore_reentrancy.py +++ b/tests/cancun/eip1153_tstore/test_tstore_reentrancy.py @@ -9,7 +9,6 @@ Alloc, Bytecode, Case, - Environment, Hash, Op, StateTestFiller, @@ -228,12 +227,9 @@ def make_call(call_type: Op) -> Bytecode: } tx = Transaction( - sender=pre.fund_eoa(7_000_000_000_000_000_000), + sender=pre.fund_eoa(), to=address_to, - gas_price=10, data=Hash(do_reenter), - gas_limit=5000000, - value=0, ) - state_test(env=Environment(), pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/cancun/eip4788_beacon_root/test_beacon_root_contract.py b/tests/cancun/eip4788_beacon_root/test_beacon_root_contract.py index dc4888003f8..3150ca50ee7 100644 --- a/tests/cancun/eip4788_beacon_root/test_beacon_root_contract.py +++ b/tests/cancun/eip4788_beacon_root/test_beacon_root_contract.py @@ -27,7 +27,6 @@ Block, BlockchainTestFiller, Bytecode, - Fork, Hash, Op, Storage, @@ -319,7 +318,6 @@ def test_beacon_root_selfdestruct( beacon_root: bytes, timestamp: int, pre: Alloc, - fork: Fork, tx: Transaction, post: Dict, ) -> None: @@ -333,20 +331,15 @@ def test_beacon_root_selfdestruct( balance=0xBA1, ) # self destruct caller - selfdestruct_call_forwarded_gas = 100_000 - self_destruct_caller_code = Op.CALL( - gas=selfdestruct_call_forwarded_gas, - address=self_destruct_actor_address, - ) + Op.SSTORE(0, Op.BALANCE(Spec.BEACON_ROOTS_ADDRESS)) self_destruct_caller_address = pre.deploy_contract( - self_destruct_caller_code + Op.CALL(gas=100_000, address=self_destruct_actor_address) + + Op.SSTORE(0, Op.BALANCE(Spec.BEACON_ROOTS_ADDRESS)) ) post = { self_destruct_caller_address: Account( storage=Storage({0: 0xBA1}), # type: ignore ) } - intrinsic_calc = fork.transaction_intrinsic_cost_calculator() blockchain_test( pre=pre, blocks=[ @@ -355,14 +348,6 @@ def test_beacon_root_selfdestruct( Transaction( sender=pre.fund_eoa(), to=self_destruct_caller_address, - # Caller's static cost + forwarded inner gas + EIP-1706 - # stipend slack on the trailing SSTORE. - gas_limit=( - intrinsic_calc() - + self_destruct_caller_code.gas_cost(fork) - + selfdestruct_call_forwarded_gas - + Op.SSTORE(new_value=1).state_cost(fork) - ), ) ] ) @@ -416,7 +401,6 @@ def test_beacon_root_selfdestruct( def test_multi_block_beacon_root_timestamp_calls( blockchain_test: BlockchainTestFiller, pre: Alloc, - fork: Fork, timestamps_factory: Callable[[], Iterator[int]], beacon_roots: Iterator[bytes], block_count: int, @@ -451,7 +435,6 @@ def test_multi_block_beacon_root_timestamp_calls( all_timestamps: List[int] = [] sender = pre.fund_eoa() - intrinsic_calc = fork.transaction_intrinsic_cost_calculator() for timestamp, beacon_root, _i in zip( timestamps, @@ -510,14 +493,6 @@ def test_multi_block_beacon_root_timestamp_calls( post[current_call_account_address] = Account( storage=current_call_account_expected_storage, ) - # Bytecode's regular+state cost + N forwarded call_gas envelopes - # (one per `t` in all_timestamps) + EIP-1706 stipend slack. - block_gas_limit = ( - intrinsic_calc(calldata=Hash(timestamp)) - + current_call_account_code.gas_cost(fork) - + len(all_timestamps) * call_gas - + Op.SSTORE(new_value=1).state_cost(fork) - ) blocks.append( Block( txs=[ @@ -525,7 +500,6 @@ def test_multi_block_beacon_root_timestamp_calls( sender=sender, to=current_call_account_address, data=Hash(timestamp), - gas_limit=block_gas_limit, ) ], parent_beacon_block_root=beacon_root, @@ -661,7 +635,6 @@ def test_beacon_root_transition( sender=sender, to=current_call_account_address, data=Hash(timestamp), - gas_limit=1_000_000, ) ], parent_beacon_block_root=beacon_root if transitioned else None, diff --git a/tests/cancun/eip4844_blobs/test_blobhash_opcode.py b/tests/cancun/eip4844_blobs/test_blobhash_opcode.py index b8b5733a4c6..b5acc5d5d51 100644 --- a/tests/cancun/eip4844_blobs/test_blobhash_opcode.py +++ b/tests/cancun/eip4844_blobs/test_blobhash_opcode.py @@ -202,7 +202,6 @@ def test_blobhash_gas_cost( "sender": sender, "to": address, "data": Hash(0), - "gas_limit": 500_000, "max_fee_per_blob_gas": (fork.min_base_fee_per_blob_gas() * 10) if tx_type == 3 else None, @@ -265,10 +264,6 @@ def test_blobhash_scenarios( ) sender = pre.fund_eoa() - gas_limit = 500_000 - if fork.is_eip_enabled(8037): - gas_limit = 5_000_000 - blocks: List[Block] = [] post = {} for i in range(total_blocks): @@ -281,7 +276,6 @@ def test_blobhash_scenarios( sender=sender, to=address, data=Hash(0), - gas_limit=gas_limit, access_list=[], max_fee_per_blob_gas=( fork.min_base_fee_per_blob_gas() * 10 @@ -333,11 +327,6 @@ def test_blobhash_invalid_blob_index( scenario_name=scenario, max_blobs_per_tx=max_blobs_per_tx ) sender = pre.fund_eoa() - - gas_limit = 500_000 - if fork.is_eip_enabled(8037): - gas_limit = 5_000_000 - blocks: List[Block] = [] post = {} for i in range(total_blocks): @@ -351,7 +340,6 @@ def test_blobhash_invalid_blob_index( ty=Spec.BLOB_TX_TYPE, sender=sender, to=address, - gas_limit=gas_limit, data=Hash(0), access_list=[], max_fee_per_blob_gas=( @@ -399,17 +387,12 @@ def test_blobhash_multiple_txs_in_block( addresses = [pre.deploy_contract(blobhash_bytecode) for _ in range(4)] sender = pre.fund_eoa() - gas_limit = 500_000 - if fork.is_eip_enabled(8037): - gas_limit = 5_000_000 - def blob_tx(address: Address, tx_type: int) -> Transaction: return Transaction( ty=tx_type, sender=sender, to=address, data=Hash(0), - gas_limit=gas_limit, access_list=[] if tx_type >= 1 else None, max_fee_per_blob_gas=(fork.min_base_fee_per_blob_gas() * 10) if tx_type >= 3 diff --git a/tests/cancun/eip4844_blobs/test_blobhash_opcode_contexts.py b/tests/cancun/eip4844_blobs/test_blobhash_opcode_contexts.py index 8c9ab56b99f..6a2c7c3498b 100644 --- a/tests/cancun/eip4844_blobs/test_blobhash_opcode_contexts.py +++ b/tests/cancun/eip4844_blobs/test_blobhash_opcode_contexts.py @@ -90,7 +90,6 @@ def deploy_contract( indexes: The indexes to request using the BLOBHASH opcode """ - indexes = list(indexes) match self: case ( BlobhashContext.BLOBHASH_SSTORE @@ -313,19 +312,11 @@ def test_blobhash_opcode_contexts( case _: raise Exception(f"Unknown test case {test_case}") - # Budget covers all branches (simple SSTOREs, CREATE / CREATE2 - # initcode + deploy) plus per-blob SSTOREs whose state cost - # scales with cpsb under EIP-8037 (`sstore_state_gas()` is 0 - # otherwise). - gas_limit = 500_000 + max_blobs_per_tx * Op.SSTORE(new_value=1).state_cost( - fork - ) state_test( pre=pre, tx=Transaction( ty=Spec.BLOB_TX_TYPE, to=tx_to, - gas_limit=gas_limit, max_fee_per_blob_gas=fork.min_base_fee_per_blob_gas() * 10, blob_versioned_hashes=simple_blob_hashes, sender=pre.fund_eoa(), @@ -341,12 +332,16 @@ def test_blobhash_opcode_contexts_tx_types( state_test: StateTestFiller, ) -> None: """ - Test that the `BLOBHASH` opcode returns zero in non-blob transaction - types. + Tests that the `BLOBHASH` opcode functions correctly when called in + different contexts. - Verify BLOBHASH behavior across transaction types 0, 1, and 2 in - various calling contexts including top-level, CALL, DELEGATECALL, - STATICCALL, CALLCODE, initcode, CREATE, and CREATE2. + - `BLOBHASH` opcode on the top level of the call stack. + - `BLOBHASH` opcode on the max value. + - `BLOBHASH` opcode on `CALL`, `DELEGATECALL`, `STATICCALL`, and + `CALLCODE`. + - `BLOBHASH` opcode on Initcode. + - `BLOBHASH` opcode on `CREATE` and `CREATE2`. + - `BLOBHASH` opcode on transaction types 0, 1 and 2. """ blobhash_sstore_address = BlobhashContext.BLOBHASH_SSTORE.deploy_contract( pre=pre, indexes=[0] diff --git a/tests/cancun/eip4844_blobs/test_excess_blob_gas.py b/tests/cancun/eip4844_blobs/test_excess_blob_gas.py index f34cf973971..c9e3be46953 100644 --- a/tests/cancun/eip4844_blobs/test_excess_blob_gas.py +++ b/tests/cancun/eip4844_blobs/test_excess_blob_gas.py @@ -105,34 +105,6 @@ def tx_blob_data_cost( return tx_max_fee_per_blob_gas * blob_gas_per_blob * new_blobs -@pytest.fixture -def tx_gas_limit(fork: Fork) -> int: # noqa: D103 - if fork.is_eip_enabled(8037): - return 500_000 - return 45000 - - -@pytest.fixture -def tx_exact_cost( - tx_value: int, - tx_max_fee_per_gas: int, - tx_blob_data_cost: int, - tx_gas_limit: int, - new_blobs: int, - fork: Fork, -) -> int: - """Calculate exact cost for all transactions.""" - if new_blobs == 0: - num_transactions = 1 - else: - num_transactions = ( - new_blobs + fork.max_blobs_per_tx() - 1 - ) // fork.max_blobs_per_tx() - base_cost_per_tx = (tx_gas_limit * tx_max_fee_per_gas) + tx_value - total_base_cost = base_cost_per_tx * num_transactions - return total_base_cost + tx_blob_data_cost - - @pytest.fixture def destination_account_bytecode() -> Bytecode: # noqa: D103 # Verify that the BLOBBASEFEE opcode reflects the current blob gas cost @@ -148,8 +120,8 @@ def destination_account( # noqa: D103 @pytest.fixture -def sender(pre: Alloc, tx_exact_cost: int) -> Address: # noqa: D103 - return pre.fund_eoa(tx_exact_cost) +def sender(pre: Alloc) -> Address: # noqa: D103 + return pre.fund_eoa() @pytest.fixture @@ -158,7 +130,6 @@ def txs( # noqa: D103 new_blobs: int, tx_max_fee_per_gas: int, tx_max_fee_per_blob_gas: int, - tx_gas_limit: int, destination_account: Address, fork: Fork, ) -> List[Transaction]: @@ -170,7 +141,6 @@ def txs( # noqa: D103 sender=sender, to=destination_account, value=1, - gas_limit=tx_gas_limit, max_fee_per_gas=tx_max_fee_per_gas, max_priority_fee_per_gas=0, access_list=[], @@ -189,7 +159,6 @@ def txs( # noqa: D103 sender=sender, to=destination_account, value=1, - gas_limit=tx_gas_limit, max_fee_per_gas=tx_max_fee_per_gas, max_priority_fee_per_gas=0, max_fee_per_blob_gas=tx_max_fee_per_blob_gas, diff --git a/tests/cancun/eip4844_blobs/test_point_evaluation_precompile.py b/tests/cancun/eip4844_blobs/test_point_evaluation_precompile.py index d73ae12916c..ba706445ea4 100644 --- a/tests/cancun/eip4844_blobs/test_point_evaluation_precompile.py +++ b/tests/cancun/eip4844_blobs/test_point_evaluation_precompile.py @@ -51,7 +51,6 @@ Storage, Transaction, TransactionReceipt, - TransitionFork, call_return_code, ) @@ -201,15 +200,12 @@ def tx( precompile_caller_address: Address, precompile_input: bytes, sender: EOA, - fork: Fork | TransitionFork, ) -> Transaction: """Prepare transaction used to call the precompile caller account.""" return Transaction( sender=sender, data=precompile_input, to=precompile_caller_address, - gas_limit=fork.transitions_to().gas_costs().PRECOMPILE_POINT_EVALUATION - * 100, ) @@ -777,14 +773,10 @@ def test_precompile_during_fork( precompile_caller_address: Address, precompile_input: bytes, sender: EOA, - fork: TransitionFork, ) -> None: """ Test calling the Point Evaluation Precompile during the appropriate fork. """ - precompile_gas = ( - fork.transitions_to().gas_costs().PRECOMPILE_POINT_EVALUATION - ) # Blocks before fork blocks = [ Block( @@ -794,7 +786,6 @@ def test_precompile_during_fork( sender=sender, data=precompile_input, to=precompile_caller_address, - gas_limit=precompile_gas * 100, ) ], ) @@ -809,7 +800,6 @@ def test_precompile_during_fork( sender=sender, data=precompile_input, to=precompile_caller_address, - gas_limit=precompile_gas * 100, ) ], ) diff --git a/tests/cancun/eip4844_blobs/test_point_evaluation_precompile_gas.py b/tests/cancun/eip4844_blobs/test_point_evaluation_precompile_gas.py index 0efdab78b44..c69d750f67c 100644 --- a/tests/cancun/eip4844_blobs/test_point_evaluation_precompile_gas.py +++ b/tests/cancun/eip4844_blobs/test_point_evaluation_precompile_gas.py @@ -14,7 +14,6 @@ Alloc, Bytecode, CodeGasMeasure, - Environment, Fork, Op, StateTestFiller, @@ -149,15 +148,12 @@ def tx( pre: Alloc, precompile_caller_address: Address, precompile_input: bytes, - fork: Fork, ) -> Transaction: """Prepare transaction used to call the precompile caller account.""" return Transaction( sender=pre.fund_eoa(), data=precompile_input, to=precompile_caller_address, - value=0, - gas_limit=fork.gas_costs().PRECOMPILE_POINT_EVALUATION * 20, ) @@ -218,9 +214,4 @@ def test_point_evaluation_precompile_gas_usage( Test using different gas limits (exact gas, insufficient gas, extra gas) - Test using correct and incorrect proofs """ - state_test( - env=Environment(), - pre=pre, - post=post, - tx=tx, - ) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/cancun/eip5656_mcopy/test_mcopy.py b/tests/cancun/eip5656_mcopy/test_mcopy.py index 93cd8b4d34f..e14cc607cc7 100644 --- a/tests/cancun/eip5656_mcopy/test_mcopy.py +++ b/tests/cancun/eip5656_mcopy/test_mcopy.py @@ -10,8 +10,6 @@ Address, Alloc, Bytecode, - Environment, - Fork, Hash, Op, StateTestFiller, @@ -116,20 +114,12 @@ def code_address(pre: Alloc, code_bytecode: Bytecode) -> Address: @pytest.fixture def tx( # noqa: D103 - pre: Alloc, - fork: Fork, - code_address: Address, - dest: int, - src: int, - length: int, + pre: Alloc, code_address: Address, dest: int, src: int, length: int ) -> Transaction: - # The test SSTOREs each memory word it reads, so budget for ~10 - # first-time SSTOREs whose state gas scales with cpsb on Amsterdam. return Transaction( sender=pre.fund_eoa(), to=code_address, data=Hash(dest) + Hash(src) + Hash(length), - gas_limit=1_000_000 + 10 * Op.SSTORE(new_value=1).state_cost(fork), ) @@ -205,12 +195,7 @@ def test_valid_mcopy_operations( - Memory extensions (copy to a location that is out of bounds) - Memory clear (copy from a location that is out of bounds). """ - state_test( - env=Environment(), - pre=pre, - post=post, - tx=tx, - ) + state_test(pre=pre, post=post, tx=tx) PATTERN = bytes.fromhex( @@ -239,7 +224,6 @@ def test_valid_mcopy_operations( def test_mcopy_repeated( state_test: StateTestFiller, pre: Alloc, - fork: Fork, dest: int, src: int, length: int, @@ -296,14 +280,12 @@ def test_mcopy_repeated( post = {contract: Account(storage=storage)} state_test( - env=Environment(), pre=pre, post=post, tx=Transaction( sender=pre.fund_eoa(), to=contract, data=Hash(dest) + Hash(src) + Hash(length), - gas_limit=1_000_000 + 2 * Op.SSTORE(new_value=1).state_cost(fork), ), ) @@ -323,9 +305,4 @@ def test_mcopy_on_empty_memory( Perform MCOPY operations on an empty memory, using different offsets and lengths. """ - state_test( - env=Environment(), - pre=pre, - post=post, - tx=tx, - ) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/cancun/eip5656_mcopy/test_mcopy_contexts.py b/tests/cancun/eip5656_mcopy/test_mcopy_contexts.py index ff7d369e96c..0fdb84441fa 100644 --- a/tests/cancun/eip5656_mcopy/test_mcopy_contexts.py +++ b/tests/cancun/eip5656_mcopy/test_mcopy_contexts.py @@ -13,8 +13,6 @@ Address, Alloc, Bytecode, - Environment, - Fork, Op, StateTestFiller, Storage, @@ -139,14 +137,10 @@ def callee_address(pre: Alloc, callee_bytecode: Bytecode) -> Address: # noqa: D @pytest.fixture -def tx(pre: Alloc, fork: Fork, caller_address: Address) -> Transaction: # noqa: D103 - gas_limit = 1_000_000 - if fork.is_eip_enabled(8037): - gas_limit = 5_000_000 +def tx(pre: Alloc, caller_address: Address) -> Transaction: # noqa: D103 return Transaction( sender=pre.fund_eoa(), to=caller_address, - gas_limit=gas_limit, ) @@ -180,12 +174,7 @@ def test_no_memory_corruption_on_upper_call_stack_levels( Perform a subcall with any of the following opcodes, which uses MCOPY during its execution, and verify that the caller's memory is unaffected. """ - state_test( - env=Environment(), - pre=pre, - post=post, - tx=tx, - ) + state_test(pre=pre, post=post, tx=tx) @pytest.mark.parametrize( @@ -208,9 +197,4 @@ def test_no_memory_corruption_on_upper_create_stack_levels( - `CREATE` - `CREATE2`. """ - state_test( - env=Environment(), - pre=pre, - post=post, - tx=tx, - ) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/cancun/eip6780_selfdestruct/test_dynamic_create2_selfdestruct_collision.py b/tests/cancun/eip6780_selfdestruct/test_dynamic_create2_selfdestruct_collision.py index 4fc6232dc95..8ee8e65c663 100644 --- a/tests/cancun/eip6780_selfdestruct/test_dynamic_create2_selfdestruct_collision.py +++ b/tests/cancun/eip6780_selfdestruct/test_dynamic_create2_selfdestruct_collision.py @@ -253,7 +253,6 @@ def test_dynamic_create2_selfdestruct_collision( tx = Transaction( to=address_to, data=initcode, - gas_limit=5_000_000, sender=sender, ) @@ -526,13 +525,11 @@ def test_dynamic_create2_selfdestruct_collision_two_different_transactions( Transaction( to=address_to, data=initcode, - gas_limit=5_000_000, sender=sender, ), Transaction( to=address_to_second, data=initcode, - gas_limit=5_000_000, sender=sender, ), ] @@ -805,13 +802,11 @@ def test_dynamic_create2_selfdestruct_collision_multi_tx( Transaction( to=address_to, data=initcode, - gas_limit=5_000_000, sender=sender, ), Transaction( to=address_to, data=initcode, - gas_limit=5_000_000, sender=sender, ), ] diff --git a/tests/cancun/eip6780_selfdestruct/test_journal_revert.py b/tests/cancun/eip6780_selfdestruct/test_journal_revert.py index 6dfe7fa5d57..0c35da6058f 100644 --- a/tests/cancun/eip6780_selfdestruct/test_journal_revert.py +++ b/tests/cancun/eip6780_selfdestruct/test_journal_revert.py @@ -8,7 +8,6 @@ Alloc, BalAccountExpectation, BlockAccessListExpectation, - Environment, Fork, Op, StateTestFiller, @@ -24,7 +23,6 @@ @pytest.mark.valid_from("Cancun") def test_selfdestruct_balance_transfer_reverted( state_test: StateTestFiller, - env: Environment, pre: Alloc, fork: Fork, ) -> None: @@ -49,13 +47,12 @@ def test_selfdestruct_balance_transfer_reverted( # Controller calls victim (triggers SELFDESTRUCT) then reverts. controller = pre.deploy_contract( - Op.POP(Op.CALL(gas=100_000, address=victim)) - + Op.REVERT(offset=0, size=0) + Op.POP(Op.CALL(address=victim)) + Op.REVERT(offset=0, size=0) ) # Outer calls controller, then checks beneficiary balance. outer = pre.deploy_contract( - Op.POP(Op.CALL(gas=200_000, address=controller)) + Op.POP(Op.CALL(address=controller)) + Op.SSTORE( storage.store_next(beneficiary_balance, "beneficiary_balance"), Op.BALANCE(beneficiary), @@ -92,7 +89,6 @@ def test_selfdestruct_balance_transfer_reverted( ) state_test( - env=env, pre=pre, post={ outer: Account(storage=storage), @@ -104,7 +100,6 @@ def test_selfdestruct_balance_transfer_reverted( tx=Transaction( sender=sender, to=outer, - gas_limit=1_000_000, expected_receipt=expected_receipt, ), expected_block_access_list=expected_bal, diff --git a/tests/cancun/eip6780_selfdestruct/test_reentrancy_selfdestruct_revert.py b/tests/cancun/eip6780_selfdestruct/test_reentrancy_selfdestruct_revert.py index bea4490a985..c5bb7c78851 100644 --- a/tests/cancun/eip6780_selfdestruct/test_reentrancy_selfdestruct_revert.py +++ b/tests/cancun/eip6780_selfdestruct/test_reentrancy_selfdestruct_revert.py @@ -12,7 +12,6 @@ Address, Alloc, Bytecode, - Environment, Fork, Op, StateTestFiller, @@ -146,7 +145,6 @@ def revert_contract_address( ) def test_reentrancy_selfdestruct_revert( pre: Alloc, - env: Environment, sender: EOA, fork: Fork, first_selfdestruct: Op, @@ -258,15 +256,10 @@ def test_reentrancy_selfdestruct_revert( ) expected_receipt = TransactionReceipt(logs=expected_logs) - gas_limit = 500_000 - if fork.is_eip_enabled(8037): - gas_limit = 5_000_000 tx = Transaction( sender=sender, to=executor_contract_address, - gas_limit=gas_limit, - value=0, expected_receipt=expected_receipt, ) - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py index 934c904aa1b..d37cfbb4490 100644 --- a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py +++ b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py @@ -373,15 +373,11 @@ def test_create_selfdestruct_same_tx( # retain the stored values for verification. entry_code += Op.RETURN(max(len(selfdestruct_contract_initcode), 32), 1) - gas_limit = 500_000 - if fork.is_eip_enabled(8037): - gas_limit = 5_000_000 tx = Transaction( value=entry_code_balance, data=entry_code, sender=sender, to=None, - gas_limit=gas_limit, ) assert tx.created_contract == entry_code_address @@ -522,15 +518,11 @@ def test_self_destructing_initcode( selfdestruct_contract_initial_balance, ) - gas_limit = 500_000 - if fork.is_eip_enabled(8037): - gas_limit = 5_000_000 tx = Transaction( value=entry_code_balance, data=entry_code, sender=sender, to=None, - gas_limit=gas_limit, ) entry_code_address = tx.created_contract @@ -605,15 +597,11 @@ def test_self_destructing_initcode_create_tx( - Different initial balances for the self-destructing contract - Different transaction value amounts """ - gas_limit = 500_000 - if fork.is_eip_enabled(8037): - gas_limit = 5_000_000 tx = Transaction( sender=sender, value=tx_value, data=selfdestruct_code, to=None, - gas_limit=gas_limit, ) selfdestruct_contract_address = tx.created_contract if selfdestruct_contract_initial_balance > 0: @@ -758,9 +746,6 @@ def test_recreate_self_destructed_contract_different_txs( if addr == SELF_ADDRESS: sendall_recipient_addresses[i] = selfdestruct_contract_address - gas_limit = 500_000 - if fork.is_eip_enabled(8037): - gas_limit = 5_000_000 txs: List[Transaction] = [] for i in range(recreate_times + 1): expected_receipt = None @@ -795,7 +780,6 @@ def test_recreate_self_destructed_contract_different_txs( data=Hash(i), sender=sender, to=entry_code_address, - gas_limit=gas_limit, expected_receipt=expected_receipt, ) ) @@ -1009,15 +993,11 @@ def test_selfdestruct_pre_existing( # retain the stored values for verification. entry_code += Op.RETURN(32, 1) - gas_limit = 500_000 - if fork.is_eip_enabled(8037): - gas_limit = 5_000_000 tx = Transaction( value=entry_code_balance, data=entry_code, sender=sender, to=None, - gas_limit=gas_limit, ) assert tx.created_contract == entry_code_address @@ -1181,16 +1161,12 @@ def test_selfdestruct_created_same_block_different_tx( running_balance = 0 tx2_receipt = TransactionReceipt(logs=tx2_logs) - gas_limit = 500_000 - if fork.is_eip_enabled(8037): - gas_limit = 5_000_000 txs = [ Transaction( value=selfdestruct_contract_initial_balance, data=selfdestruct_contract_initcode, sender=sender, to=None, - gas_limit=gas_limit, expected_receipt=tx1_receipt, ), Transaction( @@ -1198,7 +1174,6 @@ def test_selfdestruct_created_same_block_different_tx( data=entry_code, sender=sender, to=None, - gas_limit=gas_limit, expected_receipt=tx2_receipt, ), ] @@ -1341,15 +1316,11 @@ def test_calling_from_new_contract_to_pre_existing_contract( ), } - gas_limit = 500_000 - if fork.is_eip_enabled(8037): - gas_limit = 5_000_000 tx = Transaction( value=entry_code_balance, data=entry_code, sender=sender, to=None, - gas_limit=gas_limit, ) if fork.is_eip_enabled(7708): @@ -1508,15 +1479,11 @@ def test_calling_from_pre_existing_contract_to_new_contract( # retain the stored values for verification. entry_code += Op.RETURN(max(len(selfdestruct_contract_initcode), 32), 1) - gas_limit = 500_000 - if fork.is_eip_enabled(8037): - gas_limit = 5_000_000 tx = Transaction( value=entry_code_balance, data=entry_code, sender=sender, to=None, - gas_limit=gas_limit, ) entry_code_address = tx.created_contract @@ -1756,15 +1723,11 @@ def test_create_selfdestruct_same_tx_increased_nonce( # retain the stored values for verification. entry_code += Op.RETURN(max(len(selfdestruct_contract_initcode), 32), 1) - gas_limit = 1_000_000 - if fork.is_eip_enabled(8037): - gas_limit = 5_000_000 tx = Transaction( value=entry_code_balance, data=entry_code, sender=sender, to=None, - gas_limit=gas_limit, ) assert tx.created_contract == entry_code_address @@ -1905,15 +1868,10 @@ def test_create_and_destroy_multiple_contracts_same_tx( entry_code += Op.RETURN(32, 1) - gas_limit = 1_000_000 - if fork.is_eip_enabled(8037): - gas_limit = 5_000_000 tx = Transaction( - value=0, data=entry_code, sender=sender, to=None, - gas_limit=gas_limit, ) post: Dict[Address, Account] = { @@ -2084,22 +2042,15 @@ def test_create_multiple_contracts_destroy_one_then_destroy_other_next_tx( ) tx2_receipt = TransactionReceipt(logs=tx2_logs) - # tx1 does 2 CREATE2 (NEW_ACCOUNT each) plus several first-time - # SSTOREs across entry/init code; tx2 does one SSTORE call. - # Bump scales with cpsb on Amsterdam. - new_account = fork.gas_costs().NEW_ACCOUNT - sstore_state = Op.SSTORE(new_value=1).gas_cost(fork) txs = [ Transaction( sender=sender, to=entry_code_address, - gas_limit=1_000_000 + 2 * new_account + 6 * sstore_state, expected_receipt=tx1_receipt, ), Transaction( sender=sender, to=tx2_caller, - gas_limit=500_000 + sstore_state, expected_receipt=tx2_receipt, ), ] @@ -2222,29 +2173,10 @@ def test_parent_creates_child_selfdestruct_one( entry_code += Op.RETURN(32, 1) - intrinsic_calc = fork.transaction_intrinsic_cost_calculator() - # Three frames execute under this tx: - # 1. entry_code (the contract-creation initcode of the tx) - # 2. parent_code (called by entry) - # 3. child_code (created by parent and, when !destroy_parent, called - # by parent) - # Each CREATE incurs NEW_ACCOUNT state once. SSTORE regular costs - # are picked up by each bytecode's `gas_cost(fork)`; the trailing - # SSTORE `gas_cost(fork)` adds headroom for the EIP-8037 state-gas - # charge on the 0->nonzero SSTORE the static calc cannot infer. tx = Transaction( - value=0, data=entry_code, sender=sender, to=None, - gas_limit=( - intrinsic_calc(calldata=entry_code, contract_creation=True) - + entry_code.gas_cost(fork) - + parent_code.gas_cost(fork) - + child_code.gas_cost(fork) - + 2 * fork.gas_costs().NEW_ACCOUNT - + Op.SSTORE(new_value=1).gas_cost(fork) - ), ) post: Dict[Address, Account] = { @@ -2417,11 +2349,9 @@ def test_recursive_contract_creation_and_selfdestruct( entry_code += Op.RETURN(32, 1) tx = Transaction( - value=0, data=entry_code, sender=sender, to=None, - gas_limit=2_000_000, ) post: Dict[Address, Account] = { diff --git a/tests/cancun/eip6780_selfdestruct/test_selfdestruct_revert.py b/tests/cancun/eip6780_selfdestruct/test_selfdestruct_revert.py index 42854604177..f9f27484a9e 100644 --- a/tests/cancun/eip6780_selfdestruct/test_selfdestruct_revert.py +++ b/tests/cancun/eip6780_selfdestruct/test_selfdestruct_revert.py @@ -428,15 +428,10 @@ def test_selfdestruct_created_in_same_tx_with_revert( # noqa SC200 ) post[selfdestruct_recipient_address] = Account.NONEXISTENT # type: ignore - gas_limit = 500_000 - if fork.is_eip_enabled(8037): - gas_limit = 5_000_000 tx = Transaction( - value=0, data=entry_code, sender=sender, to=None, - gas_limit=gas_limit, ) expected_block_access_list = None @@ -532,7 +527,6 @@ def test_selfdestruct_created_in_same_tx_with_revert( # noqa SC200 @pytest.mark.valid_from("Cancun") def test_selfdestruct_not_created_in_same_tx_with_revert( state_test: StateTestFiller, - fork: Fork, sender: EOA, env: Environment, entry_code_address: Address, @@ -596,15 +590,11 @@ def test_selfdestruct_not_created_in_same_tx_with_revert( ) post[selfdestruct_recipient_address] = Account.NONEXISTENT # type: ignore - gas_limit = 500_000 - if fork.is_eip_enabled(8037): - gas_limit = 5_000_000 tx = Transaction( value=0, data=entry_code, sender=sender, to=None, - gas_limit=gas_limit, ) state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/cancun/eip7516_blobgasfee/test_blobgasfee_opcode.py b/tests/cancun/eip7516_blobgasfee/test_blobgasfee_opcode.py index ca483e637da..c28a18f991b 100644 --- a/tests/cancun/eip7516_blobgasfee/test_blobgasfee_opcode.py +++ b/tests/cancun/eip7516_blobgasfee/test_blobgasfee_opcode.py @@ -83,7 +83,6 @@ def tx(pre: Alloc, caller_address: Address) -> Transaction: """ return Transaction( sender=pre.fund_eoa(), - gas_limit=1_000_000, to=caller_address, value=1, ) @@ -118,7 +117,6 @@ def test_blobbasefee_stack_overflow( ), } state_test( - env=Environment(), pre=pre, tx=tx, post=post, @@ -154,7 +152,6 @@ def test_blobbasefee_out_of_gas( tx = Transaction( sender=pre.fund_eoa(), - gas_limit=1_000_000, to=caller_address, value=1, ) @@ -167,12 +164,7 @@ def test_blobbasefee_out_of_gas( balance=0, ), } - state_test( - env=Environment(), - pre=pre, - tx=tx, - post=post, - ) + state_test(pre=pre, tx=tx, post=post) @pytest.mark.parametrize("caller_pre_storage", [{1: 1}], ids=[""]) @@ -258,7 +250,6 @@ def test_blobbasefee_during_fork( ), } blockchain_test( - genesis_environment=Environment(), pre=pre, blocks=blocks, post=post, diff --git a/tests/common/precompile_fixtures.py b/tests/common/precompile_fixtures.py index 8831668ecba..15594333eca 100644 --- a/tests/common/precompile_fixtures.py +++ b/tests/common/precompile_fixtures.py @@ -14,7 +14,6 @@ Address, Alloc, Bytecode, - Fork, Op, Storage, Transaction, @@ -174,37 +173,14 @@ def post( } -@pytest.fixture -def tx_gas_limit(fork: Fork, input_data: bytes, precompile_gas: int) -> int: - """ - Transaction gas limit used for the test (Can be overridden in the test). - """ - intrinsic_gas_cost_calculator = ( - fork.transaction_intrinsic_cost_calculator() - ) - memory_expansion_gas_calculator = fork.memory_expansion_gas_calculator() - # `call_contract_code` performs up to 3 SSTOREs per call - # (succeeds-flag, output-length, output-hash); under EIP-8037 - # each adds `sstore_state_gas()` of state work (0 otherwise). - extra_gas = 100_000 + 3 * Op.SSTORE(new_value=1).state_cost(fork) - return ( - extra_gas - + intrinsic_gas_cost_calculator(calldata=input_data) - + memory_expansion_gas_calculator(new_bytes=len(input_data)) - + precompile_gas - ) - - @pytest.fixture def tx( input_data: bytes, - tx_gas_limit: int, call_contract_address: Address, sender: EOA, ) -> Transaction: """Transaction for the test.""" return Transaction( - gas_limit=tx_gas_limit, data=input_data, to=call_contract_address, sender=sender, diff --git a/tests/constantinople/eip1014_create2/test_create2_revert.py b/tests/constantinople/eip1014_create2/test_create2_revert.py index b8a0fdbc654..23624550d55 100644 --- a/tests/constantinople/eip1014_create2/test_create2_revert.py +++ b/tests/constantinople/eip1014_create2/test_create2_revert.py @@ -7,7 +7,6 @@ Account, Alloc, Environment, - Fork, Initcode, Op, StateTestFiller, @@ -78,7 +77,6 @@ def test_create2_revert_preserves_balance( tx=Transaction( sender=sender, to=factory, - gas_limit=1_000_000, data=initcode, ), ) @@ -88,7 +86,6 @@ def test_create2_revert_preserves_balance( def test_create2_succeeds_after_reverted_create2( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """ Test that CREATE2 succeeds after a previous CREATE2 at the same address @@ -97,13 +94,9 @@ def test_create2_succeeds_after_reverted_create2( Inner call does CREATE2 then REVERTs. Outer call then does the same CREATE2 which should succeed since the first was rolled back. """ - env = Environment() storage = Storage() salt = 1 - new_account = fork.gas_costs().NEW_ACCOUNT - sstore_state = Op.SSTORE(new_value=1).state_cost(fork) - runtime_code = Op.SSTORE(0, 1) + Op.STOP initcode = Initcode(deploy_code=runtime_code) @@ -134,7 +127,6 @@ def test_create2_succeeds_after_reverted_create2( Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) + Op.POP( Op.CALL( - gas=200_000 + new_account + sstore_state, address=creator, args_size=Op.CALLDATASIZE, ) @@ -149,7 +141,6 @@ def test_create2_succeeds_after_reverted_create2( + Op.SSTORE( storage.store_next(0, "reverter_call_result"), Op.CALL( - gas=300_000 + new_account + sstore_state, address=reverter, args_size=Op.CALLDATASIZE, ), @@ -158,7 +149,6 @@ def test_create2_succeeds_after_reverted_create2( + Op.SSTORE( storage.store_next(1, "creator_call_result"), Op.CALL( - gas=300_000 + new_account + sstore_state, address=creator, args_size=Op.CALLDATASIZE, ), @@ -170,7 +160,6 @@ def test_create2_succeeds_after_reverted_create2( sender = pre.fund_eoa() state_test( - env=env, pre=pre, post={ outer: Account(storage=storage), @@ -182,7 +171,6 @@ def test_create2_succeeds_after_reverted_create2( tx=Transaction( sender=sender, to=outer, - gas_limit=2_000_000 + 2 * (new_account + sstore_state), data=initcode, ), ) diff --git a/tests/constantinople/eip1014_create2/test_create_returndata.py b/tests/constantinople/eip1014_create2/test_create_returndata.py index 918af9cfc74..d54899f0e70 100644 --- a/tests/constantinople/eip1014_create2/test_create_returndata.py +++ b/tests/constantinople/eip1014_create2/test_create_returndata.py @@ -165,8 +165,6 @@ def test_create2_return_data( to=address_to, protected=False, data=initcode, - gas_limit=500_000, - value=0, ) state_test(pre=pre, post=post, tx=tx) diff --git a/tests/constantinople/eip1014_create2/test_deterministic_deployment.py b/tests/constantinople/eip1014_create2/test_deterministic_deployment.py index f7c502fdec1..2e3663cccb3 100644 --- a/tests/constantinople/eip1014_create2/test_deterministic_deployment.py +++ b/tests/constantinople/eip1014_create2/test_deterministic_deployment.py @@ -9,7 +9,6 @@ Alloc, Block, BlockchainTestFiller, - Fork, Hash, Op, Transaction, @@ -25,7 +24,6 @@ def test_deterministic_deployment( blockchain_test: BlockchainTestFiller, pre: Alloc, - fork: Fork, ) -> None: """ Test deterministic deployments for contracts using @@ -39,27 +37,15 @@ def test_deterministic_deployment( sender = pre.fund_eoa() - intrinsic_calc = fork.transaction_intrinsic_cost_calculator() - # Sized for the set-tx (Hash(1) calldata, with a nonzero byte) since - # its intrinsic is the larger of the two; `deploy_code.gas_cost(fork)` - # defaults SSTORE to cold zero->non-zero which slightly over-estimates - # the reset-tx (already-zero) — harmless. - tx_gas = ( - intrinsic_calc(calldata=Hash(1)) - + deploy_code.gas_cost(fork) - + Op.SSTORE(new_value=1).state_cost(fork) - ) reset_tx = Transaction( sender=sender, to=contract_address, data=Hash(0), - gas_limit=tx_gas, ) set_tx = Transaction( sender=sender, to=contract_address, data=Hash(1), - gas_limit=tx_gas, ) post = { diff --git a/tests/constantinople/eip1014_create2/test_recreate.py b/tests/constantinople/eip1014_create2/test_recreate.py index f89849779b9..67281bec9d8 100644 --- a/tests/constantinople/eip1014_create2/test_recreate.py +++ b/tests/constantinople/eip1014_create2/test_recreate.py @@ -6,7 +6,6 @@ Alloc, Block, BlockchainTestFiller, - Fork, Initcode, Op, Transaction, @@ -25,7 +24,6 @@ def test_recreate( blockchain_test: BlockchainTestFiller, pre: Alloc, - fork: Fork, recreate_on_separate_block: bool, ) -> None: """ @@ -52,7 +50,6 @@ def test_recreate( initcode = Initcode(deploy_code=deploy_code) create_tx = Transaction( - gas_limit=100_000, to=creator_address, data=initcode, sender=sender, @@ -63,7 +60,6 @@ def test_recreate( ) set_storage_tx = Transaction( - gas_limit=100_000, to=created_contract_address, value=1, sender=sender, @@ -72,7 +68,6 @@ def test_recreate( blocks = [Block(txs=[create_tx, set_storage_tx])] destruct_tx = Transaction( - gas_limit=100_000, to=created_contract_address, value=0, sender=sender, @@ -80,14 +75,12 @@ def test_recreate( balance = 1 send_funds_tx = Transaction( - gas_limit=100_000, to=created_contract_address, value=balance, sender=sender, ) re_create_tx = Transaction( - gas_limit=100_000, to=creator_address, data=initcode, sender=sender, diff --git a/tests/constantinople/eip1052_extcodehash/test_extcodehash.py b/tests/constantinople/eip1052_extcodehash/test_extcodehash.py index 13373a5bafa..b8c56e839be 100644 --- a/tests/constantinople/eip1052_extcodehash/test_extcodehash.py +++ b/tests/constantinople/eip1052_extcodehash/test_extcodehash.py @@ -43,7 +43,6 @@ def test_extcodehash_self( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """ Test EXTCODEHASH/EXTCODESIZE of the currently executing account. @@ -61,20 +60,9 @@ def test_extcodehash_self( code_address = pre.deploy_contract(code, storage=storage.canary()) - gas_limit = 400_000 - if fork.is_eip_enabled(8037): - gas_limit = 1_000_000 - tx = Transaction( - sender=pre.fund_eoa(), - to=code_address, - gas_limit=gas_limit, - ) + tx = Transaction(sender=pre.fund_eoa(), to=code_address) - state_test( - pre=pre, - post={code_address: Account(storage=storage)}, - tx=tx, - ) + state_test(pre=pre, post={code_address: Account(storage=storage)}, tx=tx) @pytest.mark.ported_from( @@ -88,7 +76,6 @@ def test_extcodehash_self( def test_extcodehash_of_empty( state_test: StateTestFiller, pre: Alloc, - fork: Fork, target_exists: bool, ) -> None: """ @@ -111,15 +98,7 @@ def test_extcodehash_of_empty( code_address = pre.deploy_contract(code, storage=storage.canary()) - gas_limit = 400_000 - if fork.is_eip_enabled(8037): - gas_limit = 1_000_000 - tx = Transaction( - sender=(pre.fund_eoa()), - to=code_address, - value=1, - gas_limit=gas_limit, - ) + tx = Transaction(sender=pre.fund_eoa(), to=code_address, value=1) state_test( pre=pre, @@ -139,7 +118,6 @@ def test_extcodehash_of_empty( def test_extcodehash_empty_send_value( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """ Test EXTCODEHASH of non-existent account before and after sending value. @@ -169,14 +147,7 @@ def test_extcodehash_empty_send_value( code, balance=10**18, storage=storage.canary() ) - gas_limit = 400_000 - if fork.is_eip_enabled(8037): - gas_limit = 1_000_000 - tx = Transaction( - sender=pre.fund_eoa(), - to=code_address, - gas_limit=gas_limit, - ) + tx = Transaction(sender=pre.fund_eoa(), to=code_address) state_test( pre=pre, @@ -245,7 +216,6 @@ def test_extcodehash_empty_send_value( def test_extcodehash_empty_account_variants( state_test: StateTestFiller, pre: Alloc, - fork: Fork, account: Account, call_before: bool, expected_hash: bytes, @@ -285,15 +255,7 @@ def test_extcodehash_empty_account_variants( code, balance=10**18, storage=storage.canary() ) - gas_limit = 400_000 - if fork.is_eip_enabled(8037): - gas_limit = 1_000_000 - tx = Transaction( - sender=pre.fund_eoa(), - to=code_address, - value=1, - gas_limit=gas_limit, - ) + tx = Transaction(sender=pre.fund_eoa(), to=code_address, value=1) state_test( pre=pre, @@ -314,7 +276,6 @@ def test_extcodehash_empty_account_variants( def test_extcodehash_empty_contract_creation( state_test: StateTestFiller, pre: Alloc, - fork: Fork, opcode: Op, ) -> None: """ @@ -364,14 +325,7 @@ def test_extcodehash_empty_contract_creation( ) storage[created_slot] = created_address - gas_limit = 400_000 - if fork.is_eip_enabled(8037): - gas_limit = 1_000_000 - tx = Transaction( - sender=pre.fund_eoa(), - to=code_address, - gas_limit=gas_limit, - ) + tx = Transaction(sender=pre.fund_eoa(), to=code_address) state_test( pre=pre, @@ -401,7 +355,6 @@ def test_extcodehash_empty_contract_creation( def test_extcodehash_codeless_with_storage( state_test: StateTestFiller, pre: Alloc, - fork: Fork, balance: int, nonce: int, ) -> None: @@ -426,18 +379,7 @@ def test_extcodehash_codeless_with_storage( code_address = pre.deploy_contract(code, storage=storage.canary()) - intrinsic_calc = fork.transaction_intrinsic_cost_calculator() - tx = Transaction( - sender=pre.fund_eoa(), - to=code_address, - # `code.gas_cost(fork)` covers both SSTOREs (regular + state under - # EIP-8037); EIP-1706 slack for the trailing SSTORE. - gas_limit=( - intrinsic_calc() - + code.gas_cost(fork) - + Op.SSTORE(new_value=1).state_cost(fork) - ), - ) + tx = Transaction(sender=pre.fund_eoa(), to=code_address) state_test( pre=pre, @@ -460,7 +402,6 @@ def test_extcodehash_dynamic_account_overwrite( state_test: StateTestFiller, pre: Alloc, target_exists: bool, - fork: Fork, ) -> None: """ Test EXTCODEHASH of non-existent/no-code account, @@ -565,20 +506,10 @@ def test_extcodehash_dynamic_account_overwrite( target_storage[target_storage_slot] = 1 sender = pre.fund_eoa() - # Test does ~10 first-time SSTOREs plus a CREATE2 (NEW_ACCOUNT) - # in the caller. Both terms are 0 pre-EIP-8037 and scale with cpsb - # on Amsterdam, keeping this CPSB-agnostic. - gas_limit = ( - 400_000 - + fork.gas_costs().NEW_ACCOUNT - + 10 * Op.SSTORE(new_value=1).state_cost(fork) - ) - tx = Transaction( sender=sender, to=caller_address, data=bytes(target_address).rjust(32, b"\0"), - gas_limit=gas_limit, ) state_test( @@ -605,7 +536,6 @@ def test_extcodehash_dynamic_account_overwrite( def test_extcodehash_precompile( state_test: StateTestFiller, pre: Alloc, - fork: Fork, precompile: Address, ) -> None: """ @@ -625,14 +555,7 @@ def test_extcodehash_precompile( code_address = pre.deploy_contract(code, storage=storage.canary()) - gas_limit = 400_000 - if fork.is_eip_enabled(8037): - gas_limit = 1_000_000 - tx = Transaction( - sender=pre.fund_eoa(), - to=code_address, - gas_limit=gas_limit, - ) + tx = Transaction(sender=pre.fund_eoa(), to=code_address) state_test( pre=pre, @@ -659,7 +582,6 @@ def test_extcodehash_precompile( def test_extcodehash_new_account( state_test: StateTestFiller, pre: Alloc, - fork: Fork, deployed_code: bytes, opcode: Opcodes, ) -> None: @@ -700,14 +622,7 @@ def test_extcodehash_new_account( ) storage[created_slot] = created_address - gas_limit = 400_000 - if fork.is_eip_enabled(8037): - gas_limit = 1_000_000 - tx = Transaction( - sender=pre.fund_eoa(), - to=code_address, - gas_limit=gas_limit, - ) + tx = Transaction(sender=pre.fund_eoa(), to=code_address) state_test( pre=pre, @@ -735,7 +650,6 @@ def test_extcodehash_new_account( def test_extcodehash_via_call( state_test: StateTestFiller, pre: Alloc, - fork: Fork, opcode: Opcodes, ) -> None: """ @@ -771,14 +685,7 @@ def test_extcodehash_via_call( code_address = pre.deploy_contract(code, storage=storage.canary()) - gas_limit = 400_000 - if fork.is_eip_enabled(8037): - gas_limit = 1_000_000 - tx = Transaction( - sender=pre.fund_eoa(), - to=code_address, - gas_limit=gas_limit, - ) + tx = Transaction(sender=pre.fund_eoa(), to=code_address) state_test( pre=pre, @@ -879,14 +786,7 @@ def extcode_checks() -> Bytecode: ) storage[created_slot] = target_address - gas_limit = 400_000 - if fork.is_eip_enabled(8037): - gas_limit = 1_000_000 - tx = Transaction( - sender=pre.fund_eoa(), - to=code_address, - gas_limit=gas_limit, - ) + tx = Transaction(sender=pre.fund_eoa(), to=code_address) post: dict[Address, Account | None] = { code_address: Account(storage=storage), @@ -909,7 +809,6 @@ def extcode_checks() -> Bytecode: def test_extcodehash_changed_account( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """ Test EXTCODEHASH/EXTCODESIZE before and after changing account state. @@ -950,14 +849,7 @@ def extcode_checks() -> Bytecode: code, balance=1, storage=storage.canary() ) - gas_limit = 400_000 - if fork.is_eip_enabled(8037): - gas_limit = 1_000_000 - tx = Transaction( - sender=pre.fund_eoa(), - to=code_address, - gas_limit=gas_limit, - ) + tx = Transaction(sender=pre.fund_eoa(), to=code_address) state_test( pre=pre, @@ -1009,14 +901,7 @@ def test_extcodehash_max_code_size( code_address = pre.deploy_contract(code, storage=storage.canary()) - gas_limit = 400_000 - if fork.is_eip_enabled(8037): - gas_limit = 1_000_000 - tx = Transaction( - sender=pre.fund_eoa(), - to=code_address, - gas_limit=gas_limit, - ) + tx = Transaction(sender=pre.fund_eoa(), to=code_address) state_test( pre=pre, @@ -1037,7 +922,6 @@ def test_extcodehash_max_code_size( def test_extcodehash_in_init_code( state_test: StateTestFiller, pre: Alloc, - fork: Fork, create_opcode: Opcodes | None, ) -> None: """ @@ -1065,10 +949,6 @@ def test_extcodehash_in_init_code( ) initcode = checks + Op.RETURN(0, 0) - gas_limit = 400_000 - if fork.is_eip_enabled(8037): - gas_limit = 1_000_000 - if create_opcode is None: # Transaction-level creation: init code runs directly. sender = pre.fund_eoa() @@ -1076,7 +956,6 @@ def test_extcodehash_in_init_code( sender=sender, to=None, data=initcode, - gas_limit=gas_limit, ) created = compute_create_address( address=sender, @@ -1098,7 +977,6 @@ def test_extcodehash_in_init_code( sender=pre.fund_eoa(), to=factory, data=initcode, - gas_limit=gas_limit, ) created = compute_create_address( address=factory, @@ -1127,7 +1005,6 @@ def test_extcodehash_in_init_code( def test_extcodehash_self_in_init( state_test: StateTestFiller, pre: Alloc, - fork: Fork, create_opcode: Opcodes | None, ) -> None: """ @@ -1151,17 +1028,12 @@ def test_extcodehash_self_in_init( ) initcode = checks + Op.RETURN(0, 0) - gas_limit = 400_000 - if fork.is_eip_enabled(8037): - gas_limit = 1_000_000 - if create_opcode is None: sender = pre.fund_eoa() tx = Transaction( sender=sender, to=None, data=initcode, - gas_limit=gas_limit, ) created = compute_create_address( address=sender, @@ -1182,7 +1054,6 @@ def test_extcodehash_self_in_init( sender=pre.fund_eoa(), to=factory, data=initcode, - gas_limit=gas_limit, ) created = compute_create_address( address=factory, @@ -1218,7 +1089,6 @@ def test_extcodehash_self_in_init( def test_extcodehash_dynamic_argument( state_test: StateTestFiller, pre: Alloc, - fork: Fork, target_type: str, ) -> None: """ @@ -1264,14 +1134,10 @@ def test_extcodehash_dynamic_argument( code_address = pre.deploy_contract(code, storage=storage.canary()) - gas_limit = 400_000 - if fork.is_eip_enabled(8037): - gas_limit = 1_000_000 tx = Transaction( sender=pre.fund_eoa(), to=code_address, data=bytes(target_address).rjust(32, b"\0"), - gas_limit=gas_limit, ) state_test( @@ -1291,7 +1157,6 @@ def test_extcodehash_dynamic_argument( def test_extcodehash_call_to_nonexistent( state_test: StateTestFiller, pre: Alloc, - fork: Fork, call_opcode: Opcodes, ) -> None: """ @@ -1313,14 +1178,7 @@ def test_extcodehash_call_to_nonexistent( code_address = pre.deploy_contract(code, storage=storage.canary()) - gas_limit = 400_000 - if fork.is_eip_enabled(8037): - gas_limit = 1_000_000 - tx = Transaction( - sender=pre.fund_eoa(), - to=code_address, - gas_limit=gas_limit, - ) + tx = Transaction(sender=pre.fund_eoa(), to=code_address) state_test( pre=pre, @@ -1359,14 +1217,9 @@ def test_extcodehash_call_to_selfdestruct( call_succeeds = call_opcode != Op.STATICCALL - # SELFDESTRUCT to a nonexistent beneficiary creates a new account - # whose state gas scales with cpsb on Amsterdam. Forward enough so - # the inner CALL still completes when NEW_ACCOUNT grows. - new_account = fork.gas_costs().NEW_ACCOUNT - sstore_state = Op.SSTORE(new_value=1).state_cost(fork) code = Op.SSTORE( storage.store_next(int(call_succeeds)), - call_opcode(address=target, gas=165_000 + new_account), + call_opcode(address=target), ) + Op.SSTORE( storage.store_next(target_code.keccak256()), Op.EXTCODEHASH(target), @@ -1374,12 +1227,7 @@ def test_extcodehash_call_to_selfdestruct( code_address = pre.deploy_contract(code, storage=storage.canary()) - gas_limit = 400_000 + new_account + 2 * sstore_state - tx = Transaction( - sender=pre.fund_eoa(), - to=code_address, - gas_limit=gas_limit, - ) + tx = Transaction(sender=pre.fund_eoa(), to=code_address) # Pre-Cancun, CALLCODE/DELEGATECALL execute SELFDESTRUCT in the # caller's context, destroying the test contract at end of tx. @@ -1415,7 +1263,6 @@ def test_extcodehash_call_to_selfdestruct( def test_extcodehash_created_and_deleted( state_test: StateTestFiller, pre: Alloc, - fork: Fork, trigger: Opcodes, ) -> None: """ @@ -1478,14 +1325,7 @@ def extcode_checks() -> Bytecode: ) storage[created_slot] = created - gas_limit = 400_000 - if fork.is_eip_enabled(8037): - gas_limit = 1_000_000 - tx = Transaction( - sender=pre.fund_eoa(), - to=code_address, - gas_limit=gas_limit, - ) + tx = Transaction(sender=pre.fund_eoa(), to=code_address) post: dict[Address, Account | None] = { code_address: Account(storage=storage), @@ -1507,7 +1347,6 @@ def extcode_checks() -> Bytecode: def test_extcodehash_created_and_deleted_recheck_outer( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """ Test EXTCODEHASH of a created-and-selfdestructed account rechecked @@ -1588,18 +1427,7 @@ def inner_extcode_checks() -> Bytecode: ) outer = pre.deploy_contract(outer_code, storage=outer_storage.canary()) - # Test does ~10 first-time SSTOREs (across inner and outer) plus a - # CREATE2 (NEW_ACCOUNT). Both terms scale with cpsb on Amsterdam. - gas_limit = ( - 400_000 - + fork.gas_costs().NEW_ACCOUNT - + 10 * Op.SSTORE(new_value=1).state_cost(fork) - ) - tx = Transaction( - sender=pre.fund_eoa(), - to=outer, - gas_limit=gas_limit, - ) + tx = Transaction(sender=pre.fund_eoa(), to=outer) post: dict[Address, Account | None] = { inner: Account(storage=inner_storage), @@ -1653,14 +1481,8 @@ def test_extcodehash_subcall_selfdestruct( selfdestruct_code = Op.SELFDESTRUCT(beneficiary) target_c = pre.deploy_contract(selfdestruct_code) - # SELFDESTRUCT to a nonexistent beneficiary creates a new account - # whose state gas scales with cpsb on Amsterdam. - new_account = fork.gas_costs().NEW_ACCOUNT - sstore_state = Op.SSTORE(new_value=1).state_cost(fork) - # A: executes C's code in A's context via CALLCODE/DELEGATECALL a_code = call_opcode( - gas=350_000 + new_account, address=target_c, ret_size=32, ) @@ -1701,12 +1523,12 @@ def extcode_checks(target: Address | Bytecode) -> Bytecode: code += extcode_checks(a_target) code += Op.SSTORE( storage.store_next(1), - Op.CALL(gas=350_000 + new_account, address=a_target), + Op.CALL(address=a_target), ) code += extcode_checks(a_target) code += Op.SSTORE( storage.store_next(1), - Op.CALL(gas=350_000 + new_account, address=a_target), + Op.CALL(address=a_target), ) code_address = pre.deploy_contract(code, storage=storage.canary()) @@ -1715,13 +1537,7 @@ def extcode_checks(target: Address | Bytecode) -> Bytecode: a = compute_create_address(address=code_address, nonce=1) storage[created_slot] = a - # Test does up to ~7 first-time SSTOREs plus a CREATE for dynamic A. - gas_limit = 500_000 + new_account + 7 * sstore_state - tx = Transaction( - sender=pre.fund_eoa(), - to=code_address, - gas_limit=gas_limit, - ) + tx = Transaction(sender=pre.fund_eoa(), to=code_address) # Pre-Cancun, CALLCODE/DELEGATECALL executes SELFDESTRUCT in A's # context, deleting A at end of transaction. @@ -1757,7 +1573,6 @@ def extcode_checks(target: Address | Bytecode) -> Bytecode: def test_extcodehash_subcall_create2_oog( state_test: StateTestFiller, pre: Alloc, - fork: Fork, call_opcode: Opcodes, oog: bool, ) -> None: @@ -1775,12 +1590,6 @@ def test_extcodehash_subcall_create2_oog( deploy_code_bytes = bytes(deploy_code) initcode = Initcode(deploy_code=deploy_code) - # CREATE2 charges NEW_ACCOUNT state gas; the deploy_code's SSTORE - # also charges first-time SSTORE state gas. Both scale with cpsb - # on Amsterdam. - new_account = fork.gas_costs().NEW_ACCOUNT - sstore_state = Op.SSTORE(new_value=1).state_cost(fork) - # Factory: CREATE2, optionally consume all gas to trigger OOG. factory_code = Om.MSTORE(initcode, 0) + Op.MSTORE( 0, Op.CREATE2(value=0, offset=0, size=len(initcode), salt=0) @@ -1801,7 +1610,6 @@ def test_extcodehash_subcall_create2_oog( storage.store_next(int(not oog), "call_result"), call_opcode( address=factory, - gas=200_000 + new_account + sstore_state, ret_offset=0, ret_size=32, ), @@ -1837,12 +1645,9 @@ def test_extcodehash_subcall_create2_oog( else: post[created] = Account(nonce=1, code=deploy_code) - # Caller does ~5 first-time SSTOREs plus the inner CALL+CREATE2. - gas_limit = 500_000 + new_account + 5 * sstore_state tx = Transaction( sender=pre.fund_eoa(), to=code_address, - gas_limit=gas_limit, data=created.rjust(32, b"\0"), ) @@ -1864,7 +1669,6 @@ def test_extcodehash_subcall_create2_oog( def test_extcodecopy_zero_code( state_test: StateTestFiller, pre: Alloc, - fork: Fork, target_type: str, ) -> None: """ @@ -1907,14 +1711,7 @@ def test_extcodecopy_zero_code( code_address = pre.deploy_contract(code, storage=storage.canary()) - gas_limit = 400_000 - if fork.is_eip_enabled(8037): - gas_limit = 1_000_000 - tx = Transaction( - sender=pre.fund_eoa(), - to=code_address, - gas_limit=gas_limit, - ) + tx = Transaction(sender=pre.fund_eoa(), to=code_address) state_test( pre=pre, @@ -1988,11 +1785,7 @@ def test_codecopy_zero_in_create2( # First 32 bytes of initcode — what CODECOPY(0,0,32) returns. initcode_word0 = bytes(initcode)[:32] - tx = Transaction( - sender=pre.fund_eoa(), - to=caller, - gas_limit=1_400_000, - ) + tx = Transaction(sender=pre.fund_eoa(), to=caller) state_test( pre=pre, diff --git a/tests/constantinople/eip145_bitwise_shift/test_shift_combinations.py b/tests/constantinople/eip145_bitwise_shift/test_shift_combinations.py index 46061f495b6..f2a375020c5 100644 --- a/tests/constantinople/eip145_bitwise_shift/test_shift_combinations.py +++ b/tests/constantinople/eip145_bitwise_shift/test_shift_combinations.py @@ -7,7 +7,6 @@ from execution_testing import ( Account, Alloc, - Fork, Op, StateTestFiller, Storage, @@ -62,11 +61,7 @@ ) @pytest.mark.eels_base_coverage def test_combinations( - state_test: StateTestFiller, - pre: Alloc, - fork: Fork, - opcode: Op, - operation: Callable, + state_test: StateTestFiller, pre: Alloc, opcode: Op, operation: Callable ) -> None: """Test bitwise shift combinations.""" result = Storage() @@ -85,18 +80,9 @@ def test_combinations( + Op.STOP, ) - # Osaka (EIP-7825) caps tx gas at 16,777,216; Amsterdam - # (EIP-8037) lifts that cap and lets state gas fund the test's - # ~400 SSTOREs from the reservoir. - # TODO: auto gas limit will remove this - gas_limit = 16_000_000 - if fork.is_eip_enabled(8037): - gas_limit = 25_000_000 - tx = Transaction( sender=pre.fund_eoa(), to=address_to, - gas_limit=gas_limit, ) state_test(pre=pre, post={address_to: Account(storage=result)}, tx=tx) diff --git a/tests/frontier/create/test_create_one_byte.py b/tests/frontier/create/test_create_one_byte.py index 326bbccdc59..066d9ac440c 100644 --- a/tests/frontier/create/test_create_one_byte.py +++ b/tests/frontier/create/test_create_one_byte.py @@ -49,7 +49,6 @@ def test_create_one_byte( expect_post = Storage() new_account = fork.gas_costs().NEW_ACCOUNT - sstore_state = Op.SSTORE(new_value=1).state_cost(fork) # Each call forwards gas to the create_contract that does CREATE; # forward base + NEW_ACCOUNT (cpsb-agnostic). call_gas = 50_000 + new_account @@ -101,24 +100,8 @@ def test_create_one_byte( expect_post[opcode] = created_accounts[opcode] expect_post[256] = 1 - # Osaka (EIP-7825) caps transaction gas at - # `fork.transaction_gas_limit_cap()`. Amsterdam (EIP-8037) adds - # state gas via the reservoir on top of the cap (256 CREATEs and - # 257 first-time SSTOREs in this test). Pre-Osaka there's no cap. - gas_cap = fork.transaction_gas_limit_cap() - if fork.is_eip_enabled(8037): - assert gas_cap is not None - gas_limit = gas_cap + 256 * new_account + 257 * sstore_state - elif gas_cap is not None: - gas_limit = gas_cap - else: - gas_limit = 50_000_000 - tx = Transaction( - gas_limit=gas_limit, to=code, - data=b"", - nonce=0, sender=sender, protected=fork.supports_protected_txs(), ) diff --git a/tests/frontier/create/test_create_preimage_layout.py b/tests/frontier/create/test_create_preimage_layout.py index d0f74b0342f..899f57c022c 100644 --- a/tests/frontier/create/test_create_preimage_layout.py +++ b/tests/frontier/create/test_create_preimage_layout.py @@ -51,7 +51,6 @@ def test_create_preimage_layout_address( sender=sender, to=contract, data=nonce.to_bytes(32, "big"), - gas_limit=1_000_000, protected=fork.supports_protected_txs(), ) @@ -97,7 +96,6 @@ def test_create_preimage_layout_increment_nonce( sender=sender, to=contract, data=(1).to_bytes(32, "big"), - gas_limit=5_000_000, protected=fork.supports_protected_txs(), ) @@ -117,7 +115,6 @@ def test_create_preimage_layout_increment_nonce( def test_create_address_dynamic_nonce( pre: Alloc, state_test: StateTestFiller, - fork: Fork, ) -> None: """ Verify CreatePreimageLayout dynamic nonce encoding matches CREATE. @@ -163,18 +160,8 @@ def test_create_address_dynamic_nonce( contract = pre.deploy_contract(code=code) sender = pre.fund_eoa() - # Amsterdam EIP-8037 charges state gas per CREATE (new account). - # 260 CREATEs need ~34M state gas supplied via the reservoir. - gas_limit = 15_000_000 - if fork.create_state_gas(code_size=0) > 0: - gas_limit_cap = fork.transaction_gas_limit_cap() or gas_limit - gas_limit = gas_limit_cap + iterations * fork.create_state_gas( - code_size=0 - ) - tx = Transaction( to=contract, - gas_limit=gas_limit, sender=sender, ) @@ -259,11 +246,7 @@ def test_create_address_nonce_boundary( ) sender = pre.fund_eoa() - tx = Transaction( - to=DEPLOYER_ADDRESS, - gas_limit=15_000_000, - sender=sender, - ) + tx = Transaction(to=DEPLOYER_ADDRESS, sender=sender) post = {DEPLOYER_ADDRESS: Account(storage={0: 1})} for nonce in range(starting_nonce, starting_nonce + BOUNDARY_ITERATIONS): diff --git a/tests/frontier/create/test_create_suicide_during_init.py b/tests/frontier/create/test_create_suicide_during_init.py index ec9bc4bd64c..88ce37f0b31 100644 --- a/tests/frontier/create/test_create_suicide_during_init.py +++ b/tests/frontier/create/test_create_suicide_during_init.py @@ -91,7 +91,6 @@ def test_create_suicide_during_transaction_create( tx_value = 100 tx = Transaction( - gas_limit=1_000_000, to=None if transaction_create else contract_deploy, data=contract_initcode, value=tx_value, diff --git a/tests/frontier/create/test_create_suicide_store.py b/tests/frontier/create/test_create_suicide_store.py index 56340ef03c4..7183f38223a 100644 --- a/tests/frontier/create/test_create_suicide_store.py +++ b/tests/frontier/create/test_create_suicide_store.py @@ -143,7 +143,6 @@ def test_create_suicide_store( expect_post[slot_program_success] = 1 tx = Transaction( - gas_limit=1_000_000, to=create_contract, data=suicide_initcode, sender=sender, diff --git a/tests/frontier/examples/test_block_intermediate_state.py b/tests/frontier/examples/test_block_intermediate_state.py index 522b1b444e5..598f884219a 100644 --- a/tests/frontier/examples/test_block_intermediate_state.py +++ b/tests/frontier/examples/test_block_intermediate_state.py @@ -6,26 +6,19 @@ Alloc, Block, BlockchainTestFiller, - Environment, Transaction, ) @pytest.mark.valid_from("Frontier") -@pytest.mark.valid_before("SpuriousDragon") def test_block_intermediate_state( blockchain_test: BlockchainTestFiller, pre: Alloc ) -> None: """Verify intermediate block states.""" - env = Environment() sender = pre.fund_eoa() - tx = Transaction( - gas_limit=100_000, to=None, data=b"", sender=sender, protected=False - ) - tx_2 = Transaction( - gas_limit=100_000, to=None, data=b"", sender=sender, protected=False - ) + tx = Transaction(to=None, data=b"", sender=sender, protected=False) + tx_2 = Transaction(to=None, data=b"", sender=sender, protected=False) block_1 = Block( txs=[tx], @@ -48,7 +41,6 @@ def test_block_intermediate_state( ) blockchain_test( - genesis_environment=env, pre=pre, post=block_3.expected_post_state, blocks=[block_1, block_2, block_3], diff --git a/tests/frontier/identity_precompile/conftest.py b/tests/frontier/identity_precompile/conftest.py deleted file mode 100644 index 7056066718d..00000000000 --- a/tests/frontier/identity_precompile/conftest.py +++ /dev/null @@ -1,13 +0,0 @@ -"""Pytest (plugin) definitions local to Identity precompile tests.""" - -import pytest -from execution_testing import Fork - - -@pytest.fixture -def tx_gas_limit(fork: Fork) -> int: - """Return the gas limit for transactions.""" - # The `nonzerovalue` variants transfer 1 wei to the identity - # precompile, creating its account and charging NEW_ACCOUNT - # state gas under EIP-8037 (0 otherwise). - return 365_224 + fork.gas_costs().NEW_ACCOUNT diff --git a/tests/frontier/identity_precompile/test_identity.py b/tests/frontier/identity_precompile/test_identity.py index c4418e09937..f38b2c3409d 100644 --- a/tests/frontier/identity_precompile/test_identity.py +++ b/tests/frontier/identity_precompile/test_identity.py @@ -6,7 +6,6 @@ from execution_testing import ( Account, Alloc, - Environment, Op, StateTestFiller, Storage, @@ -121,14 +120,12 @@ def test_call_identity_precompile( call_args: CallArgs, memory_values: Tuple[int, ...], call_succeeds: bool, - tx_gas_limit: int, contract_balance: int, ) -> None: """ Test identity precompile RETURNDATA is sized correctly based on the input size. """ - env = Environment() storage = Storage() contract_bytecode = generate_identity_call_bytecode( @@ -145,15 +142,11 @@ def test_call_identity_precompile( balance=contract_balance, ) - tx = Transaction( - to=account, - sender=pre.fund_eoa(), - gas_limit=tx_gas_limit, - ) + tx = Transaction(to=account, sender=pre.fund_eoa()) post = {account: Account(storage=storage)} - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) @pytest.mark.ported_from( @@ -192,7 +185,6 @@ def test_call_identity_precompile( ), ], ) -@pytest.mark.parametrize("tx_gas_limit", [10_000_000]) def test_call_identity_precompile_large_params( state_test: StateTestFiller, pre: Alloc, @@ -200,10 +192,8 @@ def test_call_identity_precompile_large_params( call_args: CallArgs, memory_values: Tuple[int, ...], call_succeeds: bool, - tx_gas_limit: int, ) -> None: """Test identity precompile when out of gas occurs.""" - env = Environment() storage = Storage() contract_bytecode = generate_identity_call_bytecode( @@ -219,12 +209,8 @@ def test_call_identity_precompile_large_params( storage=storage.canary(), ) - tx = Transaction( - to=account, - sender=pre.fund_eoa(), - gas_limit=tx_gas_limit, - ) + tx = Transaction(to=account, sender=pre.fund_eoa()) post = {account: Account(storage=storage)} - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/frontier/identity_precompile/test_identity_returndatasize.py b/tests/frontier/identity_precompile/test_identity_returndatasize.py index 2a57182bae2..f9480c1818c 100644 --- a/tests/frontier/identity_precompile/test_identity_returndatasize.py +++ b/tests/frontier/identity_precompile/test_identity_returndatasize.py @@ -4,7 +4,6 @@ from execution_testing import ( Account, Alloc, - Environment, Op, StateTestFiller, Storage, @@ -40,7 +39,6 @@ def test_identity_precompile_returndata( Test identity precompile RETURNDATASIZE matches the input size regardless of the output buffer size. """ - env = Environment() storage = Storage() account = pre.deploy_contract( @@ -69,10 +67,9 @@ def test_identity_precompile_returndata( tx = Transaction( to=account, sender=pre.fund_eoa(), - gas_limit=200_000, protected=True, ) post = {account: Account(storage=storage)} - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/frontier/opcodes/test_all_opcodes.py b/tests/frontier/opcodes/test_all_opcodes.py index 959954046fb..8429e9661d2 100644 --- a/tests/frontier/opcodes/test_all_opcodes.py +++ b/tests/frontier/opcodes/test_all_opcodes.py @@ -122,13 +122,8 @@ def test_all_opcodes( ), } - # EIP-8037 needs gas_limit > TX_MAX_GAS_LIMIT - # (16,777,216) for a state_gas_reservoir for SSTORE/CREATE. - gas_limit = 50_000_000 if fork.is_eip_enabled(8037) else 9_000_000 - tx = Transaction( sender=pre.fund_eoa(), - gas_limit=gas_limit, to=contract_address, protected=fork.supports_protected_txs(), ) @@ -141,7 +136,6 @@ def test_cover_revert(state_test: StateTestFiller, pre: Alloc) -> None: """Cover state revert from original tests for the coverage script.""" tx = Transaction( sender=pre.fund_eoa(), - gas_limit=1_000_000, data=Op.SSTORE(1, 1) + Op.REVERT(0, 0), to=None, value=0, @@ -194,7 +188,6 @@ def test_stack_overflow( ) tx = Transaction( - gas_limit=100_000, to=contract, sender=pre.fund_eoa(), protected=fork.supports_protected_txs(), @@ -258,11 +251,7 @@ def test_max_stack( + Op.STOP, storage={slot_code_worked: value_code_failed}, ) - gas_limit = 100_000 - if fork.is_eip_enabled(8037): - gas_limit = 500_000 tx = Transaction( - gas_limit=gas_limit, to=contract, sender=pre.fund_eoa(), protected=fork.supports_protected_txs(), diff --git a/tests/frontier/opcodes/test_blockhash.py b/tests/frontier/opcodes/test_blockhash.py index 44691f81213..0c730f8edc8 100644 --- a/tests/frontier/opcodes/test_blockhash.py +++ b/tests/frontier/opcodes/test_blockhash.py @@ -52,12 +52,6 @@ def test_genesis_hash_available( contract = pre.deploy_contract(code=code) sender = pre.fund_eoa() - intrinsic = fork.transaction_intrinsic_cost_calculator() - tx_gas_limit = ( - intrinsic() - + code.gas_cost(fork) - + Op.SSTORE(new_value=1).state_cost(fork) - ) blocks = ( [ Block( @@ -65,7 +59,6 @@ def test_genesis_hash_available( Transaction( sender=sender, to=contract, - gas_limit=tx_gas_limit, protected=fork.supports_protected_txs(), ) ] @@ -81,7 +74,6 @@ def test_genesis_hash_available( Transaction( sender=sender, to=contract, - gas_limit=tx_gas_limit, protected=fork.supports_protected_txs(), ) ] diff --git a/tests/frontier/opcodes/test_blockhash_state_test_recency.py b/tests/frontier/opcodes/test_blockhash_state_test_recency.py index 401017f9bee..b88df1de786 100644 --- a/tests/frontier/opcodes/test_blockhash_state_test_recency.py +++ b/tests/frontier/opcodes/test_blockhash_state_test_recency.py @@ -91,7 +91,6 @@ def test_blockhash_zero_out_of_window( tx = Transaction( sender=sender, to=contract, - gas_limit=200_000, protected=False, # legacy tx so it fills on pre-EIP-155 forks too ) @@ -136,7 +135,6 @@ def test_blockhash_zero_in_window_control( tx = Transaction( sender=sender, to=contract, - gas_limit=200_000, protected=False, # legacy tx so it fills on pre-EIP-155 forks too ) diff --git a/tests/frontier/opcodes/test_call.py b/tests/frontier/opcodes/test_call.py index 95ad749a868..53233ee9188 100644 --- a/tests/frontier/opcodes/test_call.py +++ b/tests/frontier/opcodes/test_call.py @@ -54,7 +54,6 @@ def test_call_large_offset_mstore( contract = pre.deploy_contract(call_measure + mstore_measure) tx = Transaction( - gas_limit=500_000, to=contract, value=0, sender=sender, @@ -66,7 +65,6 @@ def test_call_large_offset_mstore( # mstore cost: base cost + expansion cost mstore_cost = Op.MSTORE(new_memory_size=mem_offset + 32).gas_cost(fork) state_test( - env=Environment(), pre=pre, tx=tx, post={ @@ -129,7 +127,6 @@ def test_call_memory_expands_on_early_revert( ) tx = Transaction( - gas_limit=500_000, to=contract, value=0, sender=sender, @@ -143,7 +140,7 @@ def test_call_memory_expands_on_early_revert( Op.CALL( address_warm=False, value_transfer=True, - account_new=True, + account_new=not fork.is_eip_enabled(8037), # TODO: Gas calc check new_memory_size=ret_size, ).gas_cost(fork) - gsc.CALL_STIPEND @@ -153,7 +150,6 @@ def test_call_memory_expands_on_early_revert( # on CALL. mstore_cost = Op.MSTORE(new_memory_size=0).gas_cost(fork) state_test( - env=Environment(), pre=pre, tx=tx, post={ @@ -199,7 +195,6 @@ def test_call_large_args_offset_size_zero( contract = pre.deploy_contract(call_measure) tx = Transaction( - gas_limit=500_000, to=contract, value=0, sender=sender, diff --git a/tests/frontier/opcodes/test_calldatacopy.py b/tests/frontier/opcodes/test_calldatacopy.py index 3a54ac5cf41..2bb83462624 100644 --- a/tests/frontier/opcodes/test_calldatacopy.py +++ b/tests/frontier/opcodes/test_calldatacopy.py @@ -189,14 +189,8 @@ def test_calldatacopy( ), ) - gas_limit = 100_000 - if fork.is_eip_enabled(8037): - gas_limit = 500_000 - tx = Transaction( data=tx_data, - gas_limit=gas_limit, - gas_price=0x0A, protected=fork.supports_protected_txs(), sender=pre.fund_eoa(), to=to, diff --git a/tests/frontier/opcodes/test_calldataload.py b/tests/frontier/opcodes/test_calldataload.py index 3d9c54ae14e..834c87d816e 100644 --- a/tests/frontier/opcodes/test_calldataload.py +++ b/tests/frontier/opcodes/test_calldataload.py @@ -74,13 +74,6 @@ def test_calldataload( ) contract_address = pre.deploy_contract(contract_code) - intrinsic = fork.transaction_intrinsic_cost_calculator() - # EIP-1706 sentry: SSTORE fails if gas_left <= CALL_STIPEND (2300) - # before its base cost is deducted, so the inner frame needs that - # much headroom on top of the SSTORE cost. - sstore_sentry_slack = fork.gas_costs().CALL_STIPEND + 1 - # Outer's CALL reserves this many gas units (`Op.SUB(Op.GAS(), N)`) - # before forwarding the rest to the inner frame. outer_call_reserve = 256 if calldata_source == "contract": outer_code = ( @@ -100,14 +93,6 @@ def test_calldataload( tx = Transaction( data=calldata, - gas_limit=( - intrinsic(calldata=calldata) - + outer_code.gas_cost(fork) - + outer_call_reserve - + contract_code.gas_cost(fork) - + sstore_sentry_slack - + Op.SSTORE(new_value=1).state_cost(fork) - ), protected=fork.supports_protected_txs(), sender=pre.fund_eoa(), to=to, @@ -116,12 +101,6 @@ def test_calldataload( else: tx = Transaction( data=calldata, - gas_limit=( - intrinsic(calldata=calldata) - + contract_code.gas_cost(fork) - + sstore_sentry_slack - + Op.SSTORE(new_value=1).state_cost(fork) - ), protected=fork.supports_protected_txs(), sender=pre.fund_eoa(), to=contract_address, diff --git a/tests/frontier/opcodes/test_calldatasize.py b/tests/frontier/opcodes/test_calldatasize.py index 8c457314366..50c8e1dbaed 100644 --- a/tests/frontier/opcodes/test_calldatasize.py +++ b/tests/frontier/opcodes/test_calldatasize.py @@ -49,13 +49,6 @@ def test_calldatasize( contract_address = pre.deploy_contract(contract_code) calldata = b"\x01" * args_size - intrinsic = fork.transaction_intrinsic_cost_calculator() - # EIP-1706 sentry: SSTORE fails if gas_left <= CALL_STIPEND (2300) - # before its base cost is deducted, so the inner frame needs that - # much headroom on top of the SSTORE cost. - sstore_sentry_slack = fork.gas_costs().CALL_STIPEND + 1 - # Outer's CALL reserves this many gas units (`Op.SUB(Op.GAS(), N)`) - # before forwarding the rest to the inner frame. outer_call_reserve = 256 if calldata_source == "contract": outer_code = Om.MSTORE(calldata, 0x0) + Op.CALL( @@ -70,14 +63,6 @@ def test_calldatasize( to = pre.deploy_contract(code=outer_code) tx = Transaction( - gas_limit=( - intrinsic() - + outer_code.gas_cost(fork) - + outer_call_reserve - + contract_code.gas_cost(fork) - + sstore_sentry_slack - + Op.SSTORE(new_value=1).state_cost(fork) - ), protected=fork.supports_protected_txs(), sender=pre.fund_eoa(), to=to, @@ -86,12 +71,6 @@ def test_calldatasize( else: tx = Transaction( data=calldata, - gas_limit=( - intrinsic(calldata=calldata) - + contract_code.gas_cost(fork) - + sstore_sentry_slack - + Op.SSTORE(new_value=1).state_cost(fork) - ), protected=fork.supports_protected_txs(), sender=pre.fund_eoa(), to=contract_address, diff --git a/tests/frontier/opcodes/test_data_copy_oog.py b/tests/frontier/opcodes/test_data_copy_oog.py index 36ab996d1dc..167b5b7d395 100644 --- a/tests/frontier/opcodes/test_data_copy_oog.py +++ b/tests/frontier/opcodes/test_data_copy_oog.py @@ -6,7 +6,6 @@ from execution_testing import ( Account, Alloc, - Environment, Op, StateTestFiller, Storage, @@ -107,17 +106,11 @@ def test_calldatacopy_word_copy_oog( tx = Transaction( to=outer_address, sender=sender, - gas_limit=500_000, # Plenty of gas for outer call ) post = {outer_address: Account(storage=storage)} - state_test( - env=Environment(), - pre=pre, - post=post, - tx=tx, - ) + state_test(pre=pre, post=post, tx=tx) @pytest.mark.parametrize( @@ -176,14 +169,8 @@ def test_codecopy_word_copy_oog( tx = Transaction( to=outer_address, sender=sender, - gas_limit=500_000, ) post = {outer_address: Account(storage=storage)} - state_test( - env=Environment(), - pre=pre, - post=post, - tx=tx, - ) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/frontier/opcodes/test_dup.py b/tests/frontier/opcodes/test_dup.py index fd146d99c8b..577ee399b4e 100644 --- a/tests/frontier/opcodes/test_dup.py +++ b/tests/frontier/opcodes/test_dup.py @@ -4,7 +4,6 @@ from execution_testing import ( Account, Alloc, - Environment, Fork, Op, StateTestFiller, @@ -51,7 +50,6 @@ def test_dup( vmTests/dup.json](https://github.com/ethereum/tests/blob/ v14.0/GeneralStateTests/VMTests/vmTests/dup.json) by Ori Pomerantz. """ - env = Environment() sender = pre.fund_eoa() post = {} @@ -66,18 +64,9 @@ def test_dup( account = pre.deploy_contract(account_code) - intrinsic = fork.transaction_intrinsic_cost_calculator() tx = Transaction( - ty=0x0, to=account, - gas_limit=( - intrinsic() - + account_code.gas_cost(fork) - + Op.SSTORE(new_value=1).state_cost(fork) - ), - gas_price=10, protected=fork.supports_protected_txs(), - data="", sender=sender, ) @@ -112,4 +101,4 @@ def test_dup( post[account] = Account(storage=s) - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/frontier/opcodes/test_extcodecopy.py b/tests/frontier/opcodes/test_extcodecopy.py index 993996e504c..51889e4200b 100644 --- a/tests/frontier/opcodes/test_extcodecopy.py +++ b/tests/frontier/opcodes/test_extcodecopy.py @@ -73,7 +73,6 @@ def test_extcodecopy_bounds( tx = Transaction( sender=pre.fund_eoa(), to=code_address, - gas_limit=400_000, protected=fork.supports_protected_txs(), ) diff --git a/tests/frontier/opcodes/test_push.py b/tests/frontier/opcodes/test_push.py index c1e59bf9f06..ffc7f1e3447 100644 --- a/tests/frontier/opcodes/test_push.py +++ b/tests/frontier/opcodes/test_push.py @@ -75,7 +75,6 @@ def test_push( tx = Transaction( sender=pre.fund_eoa(), to=contract, - gas_limit=500_000, protected=fork.supports_protected_txs(), ) @@ -147,7 +146,6 @@ def test_stack_overflow( tx = Transaction( sender=pre.fund_eoa(), to=contract, - gas_limit=500_000, protected=fork.supports_protected_txs(), ) diff --git a/tests/frontier/opcodes/test_selfdestruct.py b/tests/frontier/opcodes/test_selfdestruct.py index 6ab8ff78e05..23dc1420640 100644 --- a/tests/frontier/opcodes/test_selfdestruct.py +++ b/tests/frontier/opcodes/test_selfdestruct.py @@ -29,7 +29,6 @@ def test_double_kill( initcode = Initcode(deploy_code=deploy_code) create_tx = Transaction( - gas_limit=100_000, protected=False, to=None, data=initcode, @@ -39,14 +38,12 @@ def test_double_kill( block_1 = Block(txs=[create_tx]) first_kill = Transaction( - gas_limit=100_000, protected=False, to=create_tx.created_contract, sender=sender, ) second_kill = Transaction( - gas_limit=100_000, protected=False, to=create_tx.created_contract, sender=sender, diff --git a/tests/frontier/opcodes/test_swap.py b/tests/frontier/opcodes/test_swap.py index 325a938ac93..0a445a7914f 100644 --- a/tests/frontier/opcodes/test_swap.py +++ b/tests/frontier/opcodes/test_swap.py @@ -70,19 +70,10 @@ def test_swap( # Deploy the contract with the generated bytecode. contract_address = pre.deploy_contract(contract_code) - intrinsic_calc = fork.transaction_intrinsic_cost_calculator() - # `contract_code.gas_cost(fork)` covers all PUSHes, the SWAP, and the - # 16 SSTOREs (regular + state under EIP-8037). Some SSTOREs write zero, - # which the default cold zero->non-zero assumption over-estimates; - # harmless. EIP-1706 slack on the trailing SSTORE. + # Create a transaction to execute the contract. tx = Transaction( sender=pre.fund_eoa(), to=contract_address, - gas_limit=( - intrinsic_calc() - + contract_code.gas_cost(fork) - + Op.SSTORE(new_value=1).state_cost(fork) - ), protected=fork.supports_protected_txs(), ) @@ -149,15 +140,10 @@ def test_stack_underflow( # Deploy the contract with the generated bytecode. contract = pre.deploy_contract(contract_code) - gas_limit = 500_000 - if fork.is_eip_enabled(8037): - gas_limit = 1_000_000 - # Create a transaction to execute the contract. tx = Transaction( sender=pre.fund_eoa(), to=contract, - gas_limit=gas_limit, protected=fork.supports_protected_txs(), ) diff --git a/tests/frontier/precompiles/test_ecrecover.py b/tests/frontier/precompiles/test_ecrecover.py index 32da8ed80ee..b98b69f42f6 100644 --- a/tests/frontier/precompiles/test_ecrecover.py +++ b/tests/frontier/precompiles/test_ecrecover.py @@ -1,13 +1,7 @@ """Tests ecrecover precompiled contract.""" import pytest -from execution_testing import ( - Account, - Alloc, - Environment, - StateTestFiller, - Transaction, -) +from execution_testing import Account, Alloc, StateTestFiller, Transaction from execution_testing.forks.helpers import Fork from execution_testing.vm import Opcodes as Op @@ -408,8 +402,6 @@ def test_precompiles( """ Tests the behavior of `ecrecover` precompiled contract. """ - env = Environment() - # Memory hash_offset = 0 v_offset = 32 @@ -438,10 +430,9 @@ def test_precompiles( tx = Transaction( to=account, sender=pre.fund_eoa(), - gas_limit=1_000_000, protected=fork.supports_protected_txs(), ) post = {account: Account(storage={0: output})} - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/frontier/precompiles/test_precompile_absence.py b/tests/frontier/precompiles/test_precompile_absence.py index 7dfe9087a74..3c0c288f40d 100644 --- a/tests/frontier/precompiles/test_precompile_absence.py +++ b/tests/frontier/precompiles/test_precompile_absence.py @@ -60,16 +60,8 @@ def test_precompile_absence( call_code, storage=storage.canary() ) - # Osaka (EIP-7825) caps tx gas at 16,777,216. Amsterdam (EIP-8037) - # lifts the cap and increases SSTORE state gas; the 30M budget - # comfortably covers ~498 cold zero-to-nonzero SSTOREs. - gas_limit = 16_000_000 - if fork.is_eip_enabled(8037): - gas_limit = 30_000_000 - tx = Transaction( to=entry_point_address, - gas_limit=gas_limit, sender=pre.fund_eoa(), protected=True, ) diff --git a/tests/frontier/precompiles/test_precompiles.py b/tests/frontier/precompiles/test_precompiles.py index dce0628b465..acb06089fff 100644 --- a/tests/frontier/precompiles/test_precompiles.py +++ b/tests/frontier/precompiles/test_precompiles.py @@ -7,7 +7,6 @@ Account, Address, Alloc, - Environment, Fork, Op, StateTestFiller, @@ -83,10 +82,8 @@ def test_precompiles( precompiled contract exists at the given address. """ - env = Environment() - # Empty account to serve as reference - empty_account = pre.fund_eoa(amount=0) + empty_account = pre.nonexistent_account() # Memory args_offset = 0 @@ -134,7 +131,6 @@ def test_precompiles( tx = Transaction( to=account, sender=pre.fund_eoa(), - gas_limit=1_000_000, protected=True, ) @@ -142,4 +138,4 @@ def test_precompiles( # Expect 0x00 when a precompile exists at the address, 0x01 otherwise post = {account: Account(storage={0: 0 if precompile_exists else 1})} - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/frontier/precompiles/test_ripemd.py b/tests/frontier/precompiles/test_ripemd.py index 3b788e20b2b..346e23aefd9 100644 --- a/tests/frontier/precompiles/test_ripemd.py +++ b/tests/frontier/precompiles/test_ripemd.py @@ -4,7 +4,6 @@ from execution_testing import ( Account, Alloc, - Environment, StateTestFiller, Transaction, ) @@ -153,8 +152,6 @@ def test_precompiles( """ Tests the behavior of `RIPEMD-160` precompiled contract. """ - env = Environment() - account = pre.deploy_contract( code=Op.CALLDATACOPY(0, 0, len(msg)) + Op.MLOAD(0) @@ -174,11 +171,10 @@ def test_precompiles( tx = Transaction( to=account, sender=pre.fund_eoa(), - gas_limit=1_000_0000, data=msg, protected=fork.supports_protected_txs(), ) post = {account: Account(storage={0: output if not oog else 0})} - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/frontier/touch/test_touch.py b/tests/frontier/touch/test_touch.py index 6a70146af72..901d6b5d7a0 100644 --- a/tests/frontier/touch/test_touch.py +++ b/tests/frontier/touch/test_touch.py @@ -30,7 +30,6 @@ def test_zero_gas_price_and_touching( ) tx = Transaction( - gas_limit=500_000, to=contract, gas_price=0, # Part of the test, do not change. sender=sender, diff --git a/tests/frontier/validation/test_transaction.py b/tests/frontier/validation/test_transaction.py index 1b665a4e073..3e3f487317f 100644 --- a/tests/frontier/validation/test_transaction.py +++ b/tests/frontier/validation/test_transaction.py @@ -127,7 +127,6 @@ def test_sender_balance( """ Tests that the sender has sufficient balance. """ - sender = pre.fund_eoa() to = pre.fund_eoa() intrinsic_cost = fork.transaction_intrinsic_cost_calculator() diff --git a/tests/homestead/coverage/test_coverage.py b/tests/homestead/coverage/test_coverage.py index 423e16b0045..aa8e50be6e4 100644 --- a/tests/homestead/coverage/test_coverage.py +++ b/tests/homestead/coverage/test_coverage.py @@ -4,14 +4,7 @@ """ import pytest -from execution_testing import ( - Alloc, - Environment, - Fork, - Op, - StateTestFiller, - Transaction, -) +from execution_testing import Alloc, Fork, Op, StateTestFiller, Transaction from execution_testing.forks import Cancun REFERENCE_SPEC_GIT_PATH = "N/A" @@ -73,11 +66,8 @@ def test_coverage( if fork >= Cancun: tx = Transaction( - sender=pre.fund_eoa(7_000_000_000_000_000_000), - gas_limit=100000, + sender=pre.fund_eoa(), to=address_to, - data=b"", - value=0, protected=False, access_list=[], max_fee_per_gas=10, @@ -85,12 +75,9 @@ def test_coverage( ) else: tx = Transaction( - sender=pre.fund_eoa(7_000_000_000_000_000_000), - gas_limit=100000, + sender=pre.fund_eoa(), to=address_to, - data=b"", - value=0, protected=False, ) - state_test(env=Environment(), pre=pre, post={}, tx=tx) + state_test(pre=pre, post={}, tx=tx) diff --git a/tests/homestead/identity_precompile/test_identity.py b/tests/homestead/identity_precompile/test_identity.py index 9593a9b1e79..ad2062900e6 100644 --- a/tests/homestead/identity_precompile/test_identity.py +++ b/tests/homestead/identity_precompile/test_identity.py @@ -5,7 +5,6 @@ Account, Alloc, Environment, - Fork, Op, StateTestFiller, Transaction, @@ -18,7 +17,6 @@ def test_identity_return_overwrite( state_test: StateTestFiller, pre: Alloc, - fork: Fork, call_opcode: Op, ) -> None: """ @@ -43,15 +41,9 @@ def test_identity_return_overwrite( contract_address = pre.deploy_contract( code=code, ) - intrinsic = fork.transaction_intrinsic_cost_calculator() tx = Transaction( sender=pre.fund_eoa(), to=contract_address, - gas_limit=( - intrinsic() - + code.gas_cost(fork) - + Op.SSTORE(new_value=1).state_cost(fork) - ), ) post = { @@ -70,7 +62,6 @@ def test_identity_return_overwrite( def test_identity_return_buffer_modify( state_test: StateTestFiller, pre: Alloc, - fork: Fork, call_opcode: Op, ) -> None: """ @@ -97,15 +88,9 @@ def test_identity_return_buffer_modify( contract_address = pre.deploy_contract( code=code, ) - intrinsic = fork.transaction_intrinsic_cost_calculator() tx = Transaction( sender=pre.fund_eoa(), to=contract_address, - gas_limit=( - intrinsic() - + code.gas_cost(fork) - + Op.SSTORE(new_value=1).state_cost(fork) - ), ) post = { diff --git a/tests/istanbul/eip1344_chainid/test_chainid.py b/tests/istanbul/eip1344_chainid/test_chainid.py index d252a543da2..963a7b3ba4e 100644 --- a/tests/istanbul/eip1344_chainid/test_chainid.py +++ b/tests/istanbul/eip1344_chainid/test_chainid.py @@ -7,7 +7,6 @@ Account, Alloc, ChainConfig, - Fork, Op, StateTestFiller, Transaction, @@ -37,33 +36,16 @@ def test_chainid( state_test: StateTestFiller, pre: Alloc, - fork: Fork, chain_config: ChainConfig, typed_transaction: Transaction, ) -> None: """Test CHAINID opcode.""" chain_id = chain_config.chain_id - contract_code = Op.SSTORE(1, Op.CHAINID) + Op.STOP - contract_address = pre.deploy_contract(contract_code) - - intrinsic_calc = fork.transaction_intrinsic_cost_calculator() - # Tx-type-specific intrinsic args derived from the parametrized fixture. - intrinsic_kwargs: dict = {"calldata": typed_transaction.data} - if typed_transaction.access_list: - intrinsic_kwargs["access_list"] = typed_transaction.access_list - if typed_transaction.authorization_list: - intrinsic_kwargs["authorization_list_or_count"] = ( - typed_transaction.authorization_list - ) + contract_address = pre.deploy_contract(Op.SSTORE(1, Op.CHAINID) + Op.STOP) tx = typed_transaction.copy( chain_id=chain_id, to=contract_address, - gas_limit=( - intrinsic_calc(**intrinsic_kwargs) - + contract_code.gas_cost(fork) - + Op.SSTORE(new_value=1).state_cost(fork) - ), ) post = { diff --git a/tests/istanbul/eip152_blake2/common.py b/tests/istanbul/eip152_blake2/common.py index 26e3b55e441..d748f197fd6 100644 --- a/tests/istanbul/eip152_blake2/common.py +++ b/tests/istanbul/eip152_blake2/common.py @@ -1,6 +1,6 @@ """Common classes used in the BLAKE2b precompile tests.""" -from execution_testing import Bytes, TestParameterGroup +from execution_testing import Bytes, Fork, TestParameterGroup from .spec import Spec, SpecTestVectors @@ -35,7 +35,7 @@ class Blake2bInput(TestParameterGroup): t_1: int | Bytes = SpecTestVectors.BLAKE2_OFFSET_COUNTER_1 f: bool | int = True - def create_blake2b_tx_data(self) -> bytes: + def __bytes__(self) -> bytes: """Generate input for the BLAKE2b precompile.""" _rounds = self.rounds.to_bytes( length=self.rounds_length, byteorder="big" @@ -60,19 +60,20 @@ def create_blake2b_tx_data(self) -> bytes: return _rounds + self.h + self.m + _t_0 + _t_1 + _f + def estimate_gas(self, fork: Fork) -> int: + """Estimate the gas used by the precompile call.""" + return self.rounds * fork.gas_costs().PRECOMPILE_BLAKE2F_PER_ROUND + class ExpectedOutput(TestParameterGroup): """ Expected test result. Attributes: - call_succeeds (str | bool): A hex string or boolean to indicate - whether the call was successful or not. data_1 (str): String value of the first updated state vector. data_2 (str): String value of the second updated state vector. """ - call_succeeds: str | bool data_1: str data_2: str diff --git a/tests/istanbul/eip152_blake2/conftest.py b/tests/istanbul/eip152_blake2/conftest.py index 1e793c12538..95ab61aa201 100644 --- a/tests/istanbul/eip152_blake2/conftest.py +++ b/tests/istanbul/eip152_blake2/conftest.py @@ -1,22 +1,52 @@ """pytest fixtures for testing the BLAKE2b precompile.""" import pytest -from execution_testing import Bytecode, Op +from execution_testing import Bytecode, Fork, Op +from .common import Blake2bInput from .spec import Spec @pytest.fixture -def blake2b_contract_bytecode(call_opcode: Op) -> Bytecode: +def precompile_gas(fork: Fork, data: Blake2bInput | bytes) -> int | None: + """ + Amount of gas to redirect to the precompile address. + + `None` means redirect all gas. + """ + assert isinstance(data, Blake2bInput), ( + "Tests that don't use `Blake2bInput` as input must specify " + "`precompile_gas`" + ) + return data.estimate_gas(fork) + + +@pytest.fixture +def precompile_gas_modifier() -> int: + """ + Amount of gas to redirect add or subtract from the call forwarded gas. + """ + return 0 + + +@pytest.fixture +def blake2b_contract_bytecode( + call_opcode: Op, + precompile_gas: int, + precompile_gas_modifier: int, +) -> Bytecode: """ Contract code that performs the provided opcode (CALL or CALLCODE) to the BLAKE2b precompile and stores the result. """ + if precompile_gas_modifier: + precompile_gas += precompile_gas_modifier return ( Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE()) + Op.SSTORE( 0, call_opcode( + gas=precompile_gas, address=Spec.BLAKE2_PRECOMPILE_ADDRESS, args_offset=0, args_size=Op.CALLDATASIZE(), diff --git a/tests/istanbul/eip152_blake2/test_blake2.py b/tests/istanbul/eip152_blake2/test_blake2.py index dd06d741c6f..04e2422748e 100644 --- a/tests/istanbul/eip152_blake2/test_blake2.py +++ b/tests/istanbul/eip152_blake2/test_blake2.py @@ -2,15 +2,12 @@ Tests [EIP-152: BLAKE2b compression precompile](https://eips.ethereum.org/EIPS/eip-152). """ -from typing import List - import pytest from execution_testing import ( Account, Alloc, Bytecode, - Environment, - Fork, + Bytes, Op, StateTestFiller, Transaction, @@ -47,61 +44,11 @@ @pytest.mark.parametrize( ["data", "output"], [ - pytest.param( - Blake2bInput( - rounds=0, - rounds_length=0, - h="", - m="", - t_0="", - t_1="", - ), - ExpectedOutput( - call_succeeds=False, - data_1="0x00", - data_2="0x00", - ), - id="empty-input", - ), - pytest.param( - Blake2bInput( - rounds_length=3, - ), - ExpectedOutput( - call_succeeds=False, - data_1="0x00", - data_2="0x00", - ), - id="invalid-rounds-length-short", - ), - pytest.param( - Blake2bInput( - rounds_length=5, - ), - ExpectedOutput( - call_succeeds=False, - data_1="0x00", - data_2="0x00", - ), - id="invalid-rounds-length-long", - ), - pytest.param( - Blake2bInput( - f=2, - ), - ExpectedOutput( - call_succeeds=False, - data_1="0x00", - data_2="0x00", - ), - id="invalid-final-block-flag-value-0x02", - ), pytest.param( Blake2bInput( rounds=0, ), ExpectedOutput( - call_succeeds=True, data_1="0x08c9bcf367e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5", data_2="0xd282e6ad7f520e511f6c3e2b8c68059b9442be0454267ce079217e1319cde05b", ), @@ -110,7 +57,6 @@ pytest.param( Blake2bInput(), ExpectedOutput( - call_succeeds=True, data_1="0xba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d1", data_2="0x7d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923", ), @@ -121,7 +67,6 @@ f=False, ), ExpectedOutput( - call_succeeds=True, data_1="0x75ab69d3190a562c51aef8d88f1c2775876944407270c42c9844252c26d28752", data_2="0x98743e7f6d5ea2f2d3e8d226039cd31b4e426ac4f2d3d666a610c2116fde4735", ), @@ -132,24 +77,11 @@ rounds=1, ), ExpectedOutput( - call_succeeds=True, data_1="0xb63a380cb2897d521994a85234ee2c181b5f844d2c624c002677e9703449d2fb", data_2="0xa551b3a8333bcdf5f2f7e08993d53923de3d64fcc68c034e717b9293fed7a421", ), id="valid-rounds-1", ), - # Excessive number of rounds expects to run out of gas - pytest.param( - Blake2bInput( - rounds=4294967295, - ), - ExpectedOutput( - call_succeeds=False, - data_1="0x0", - data_2="0x0", - ), - id="oog-rounds-4294967295", - ), # Case from https://github.com/ethereum/tests/pull/948#issuecomment-925964632 pytest.param( Blake2bInput( @@ -157,7 +89,6 @@ t_0=5, ), ExpectedOutput( - call_succeeds=True, data_1="0xf3e89a60ec4b0b1854744984e421d22b82f181bd4601fb9b1726b2662da61c29", data_2="0xdff09e75814acb2639fd79e56616e55fc135f8476f0302b3dc8d44e082eb83a8", ), @@ -168,7 +99,6 @@ rounds=16, ), ExpectedOutput( - call_succeeds=True, data_1="0xa8ef8236e5f48a74af375df15681d128457891c1cc4706f30747b2d40300b2f4", data_2="0x9d19f80fbd0945fd87736e1fc1ff10a80fd85a7aa5125154f3aaa3789ddff673", ), @@ -179,7 +109,6 @@ rounds=32, ), ExpectedOutput( - call_succeeds=True, data_1="0xbc5e888ed71b546da7b1506179bdd6c184a6410c40de33f9c330207417797889", data_2="0x5dbe74144468aefe5c2afce693c62dbca99e5e076dd467fe90a41278b16d691e", ), @@ -190,7 +119,6 @@ rounds=64, ), ExpectedOutput( - call_succeeds=True, data_1="0x74097ae7b16ffd18c742aee5c55dc89d54b6f1a8a19e6139ccfb38afba56b6b0", data_2="0x2cc35c441c19c21194fefb6841e72202f7c9d05eb9c3cfd8f94c67aa77d473c1", ), @@ -201,7 +129,6 @@ rounds=128, ), ExpectedOutput( - call_succeeds=True, data_1="0xd82c6a670dc90af9d7f77644eacbeddfed91b760c65c927871784abceaab3f81", data_2="0x3759733a1736254fb1cfc515dbfee467930955af56e27ee435f836fc3e65969f", ), @@ -212,7 +139,6 @@ rounds=256, ), ExpectedOutput( - call_succeeds=True, data_1="0x5d6ff04d5ebaee5687d634613ab21e9a7d36f782033c74f91d562669aaf9d592", data_2="0xc86346cb2df390243a952834306b389e656876a67934e2c023bce4918a016d4e", ), @@ -223,7 +149,6 @@ rounds=512, ), ExpectedOutput( - call_succeeds=True, data_1="0xa2c1eb780a6e1249156fe0751e5d4687ea9357b0651c78df660ab004cb477363", data_2="0x6298bbbc683e4a0261574b6d857a6a99e06b2eea50b16f86343d2625ff222b98", ), @@ -234,7 +159,6 @@ rounds=1024, ), ExpectedOutput( - call_succeeds=True, data_1="0x689419d2bf32b5a9901a2c733b9946727026a60d8773117eabb35f04a52cdcf1", data_2="0xb8fb4473454cf03d46c36a10b3f784aae4dc80a24424960e66a8ad5a8c2bfb30", ), @@ -247,7 +171,6 @@ t_0=16, ), ExpectedOutput( - call_succeeds=True, data_1="0x4ab6df9d1f57140bbd27b5e164f42102d9e2b0bf4d53da501273f81a37e505c7", data_2="0xf6e136f9ca4b693aa6e990b04c6412296dc09540c23c395f183011a0c5d7392e", ), @@ -260,7 +183,6 @@ t_0=16, ), ExpectedOutput( - call_succeeds=True, data_1="0x7af9b4f9c25ba3e3fd4fcb957e703b7b2e648990fe8e24c6ca2a2dfac4ce76e6", data_2="0x18acffc26913d6759843362adeb4c95299777baaa977b5d94dd219d1777e4cb", ), @@ -273,7 +195,6 @@ t_0=16, ), ExpectedOutput( - call_succeeds=True, data_1="0x97eb79f7abc085a3da64d6e8643d196cbf522a51985ba2cc6a7ca14289b59df0", data_2="0x73366eb68e41966eb8b33ab5bd6078d0de2fa4edc986b1d2afc4c92f2fc30cda", ), @@ -286,7 +207,6 @@ t_0=16, ), ExpectedOutput( - call_succeeds=True, data_1="0x5ef3d6ee148936390a9053e91ab5a92f4de4dfc62ebb95d71485be26d9b78c8d", data_2="0x8989dfe319f2fb5f11784174db63a7bcfc50de04e13fad57bea159e46e8811df", ), @@ -299,7 +219,6 @@ t_0=16, ), ExpectedOutput( - call_succeeds=True, data_1="0xa36be13275fec9a91779f0c9b06b1b40d8c8a13ab0786d0764c2eb708cc8eb81", data_2="0xf1acb2a3c7abd2ff5a9fdfe88b81f6f56288dc5260a9c810f023ae83b9b64a1a", ), @@ -312,7 +231,6 @@ t_0=16, ), ExpectedOutput( - call_succeeds=True, data_1="0xc987e560e3f90833c0d10ae1282bd9d35a7ba06d8abaa13a994d0962ed2bbaa9", data_2="0xf69c1e1e7c9aedb75e72d1b46e9f1b2ad8f8c2f7f858a04ed8aec16f964a96da", ), @@ -325,7 +243,6 @@ t_0=16, ), ExpectedOutput( - call_succeeds=True, data_1="0x224138a6afa847230ff09c23e2ca66522e22d26884b09d7740e2dd127cb61057", data_2="0x90cecbd4de6a52a733ca4a59583c064ad6ec7653d5d457b681de332f16f3d45", ), @@ -338,7 +255,6 @@ t_0=120, ), ExpectedOutput( - call_succeeds=True, data_1="0xabcd200f2962ede252fc455ea70d12b236ad2f4046b91e17558a7741d9da39a2", data_2="0x548083b610bb8591ca50418eabd15b6489a936b178a435b4c182ffa475eba4d8", ), @@ -351,7 +267,6 @@ t_0=120, ), ExpectedOutput( - call_succeeds=True, data_1="0x39fc2077154fba422b3d628d10908c596beebea8dfd90f14566aec4f60bdb2bc", data_2="0xa75d73ab2b224d58c3568cbc7fc8905cc849f10745f00addef02384032d53729", ), @@ -364,29 +279,11 @@ t_0=120, ), ExpectedOutput( - call_succeeds=True, data_1="0x5bb981381beb687d5fdbe5e7c096fbd1ce193b780948c1d74ebbb7c58db364c7", data_2="0xb7695d32f918444dbdcbdcff476fc70a926e228c4cbb7d05473711d3b56e5b33", ), id="valid-rounds-64-offset-0x78", ), - pytest.param( - Blake2bInput( - rounds=0, - rounds_length=0, - h="00", - m="00", - t_0=0, - t_1=SpecTestVectors.BLAKE2_OFFSET_COUNTER_1, - f=0, - ), - ExpectedOutput( - call_succeeds=False, - data_1="0x00", - data_2="0x00", - ), - id="EIP-152-RFC-7693-zero-input", - ), ], ) @pytest.mark.slow() @@ -395,190 +292,112 @@ def test_blake2b( pre: Alloc, call_opcode: Op, blake2b_contract_bytecode: Bytecode, - data: Blake2bInput | str | bytes, + data: Blake2bInput | bytes, output: ExpectedOutput, ) -> None: """Test BLAKE2b precompile.""" - env = Environment() - account = pre.deploy_contract( blake2b_contract_bytecode, storage={0: 0xDEADBEEF} ) sender = pre.fund_eoa() - if isinstance(data, Blake2bInput): - data = data.create_blake2b_tx_data() - elif isinstance(data, str): - data = bytes.fromhex(data) - - if isinstance(data, Blake2bInput): - data = data.create_blake2b_tx_data() - elif isinstance(data, str): - data = bytes.fromhex(data) - - tx = Transaction( - ty=0x0, - to=account, - data=data, - gas_limit=1_000_000, - protected=True, - sender=sender, - value=100000, - ) + tx = Transaction(to=account, data=data, sender=sender) post = { account: Account( storage={ - 0: 0x1 if output.call_succeeds else 0x0, + 0: 0x1, 1: output.data_1, 2: output.data_2, } ) } - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) @pytest.mark.valid_from("Istanbul") @pytest.mark.parametrize("call_opcode", [Op.CALL, Op.CALLCODE]) -@pytest.mark.parametrize("gas_limit", [90_000, 110_000, 200_000]) @pytest.mark.parametrize( - ["data", "output"], + ["data"], [ pytest.param( - b"", - ExpectedOutput( - call_succeeds=False, - data_1="0x00", - data_2="0x00", - ), - id="EIP-152-case1-data0-invalid-low-gas", + Bytes(), + id="empty-input", + ), + pytest.param( + Bytes(b"\0" * 212), + id="one_too_short", + ), + pytest.param( + Bytes(b"\0" * 214), + id="one_too_long", ), pytest.param( Blake2bInput( rounds_length=3, ), - ExpectedOutput( - call_succeeds=False, - data_1="0x00", - data_2="0x00", - ), - id="EIP-152-case1-data1-invalid-low-gas", + id="invalid_rounds_length_short", ), pytest.param( Blake2bInput( rounds_length=5, ), - ExpectedOutput( - call_succeeds=False, - data_1="0x00", - data_2="0x00", - ), - id="EIP-152-case1-data2-invalid-low-gas", + id="invalid_rounds_length_long", ), pytest.param( Blake2bInput( f=2, ), - ExpectedOutput( - call_succeeds=False, - data_1="0x00", - data_2="0x00", - ), - id="EIP-152-case1-data3-invalid-low-gas", + id="invalid_final_block_flag_value_0x02", ), pytest.param( Blake2bInput( - rounds=8000000, - ), - ExpectedOutput( - call_succeeds=False, - data_1="0x00", - data_2="0x00", + rounds=0, + rounds_length=0, + h="00", + m="00", + t_0=0, + t_1=SpecTestVectors.BLAKE2_OFFSET_COUNTER_1, + f=0, ), - id="EIP-152-case1-data9-invalid-low-gas", + id="RFC_7693_zero_input", ), + # Excessive number of rounds expects to run out of gas, valid otherwise pytest.param( - "000c", - ExpectedOutput( - call_succeeds=False, - data_1="0x00", - data_2="0x00", + Blake2bInput( + rounds=4294967295, ), - id="EIP-152-case1-data10-invalid-low-gas", + id="oog-rounds-4294967295", ), ], ) -@pytest.mark.eels_base_coverage -def test_blake2b_invalid_gas( +@pytest.mark.parametrize("precompile_gas", [0, 200_000]) +def test_blake2b_invalid_input( state_test: StateTestFiller, pre: Alloc, call_opcode: Op, blake2b_contract_bytecode: Bytecode, - gas_limit: int, - data: Blake2bInput | str | bytes, - output: ExpectedOutput, + data: Blake2bInput | bytes, ) -> None: """Test BLAKE2b precompile invalid calls using different gas limits.""" - env = Environment() - account = pre.deploy_contract( - blake2b_contract_bytecode, storage={0: 0xDEADBEEF} + blake2b_contract_bytecode, + storage={0: 0xDEADBEEF, 1: 0xDEADBEEF, 2: 0xDEADBEEF}, ) sender = pre.fund_eoa() - - if isinstance(data, Blake2bInput): - data = data.create_blake2b_tx_data() - elif isinstance(data, str): - data = bytes.fromhex(data) - - tx = Transaction( - ty=0x0, - to=account, - data=data, - gas_limit=gas_limit, - protected=True, - sender=sender, - value=0, - ) + tx = Transaction(to=account, data=data, sender=sender) post = { account: Account( - storage={ - 0: 0xDEADBEEF, - 1: output.data_1, - 2: output.data_2, - }, + storage={0: 0x0, 1: 0x0, 2: 0x0}, nonce=0x1, ) } - state_test(env=env, pre=pre, post=post, tx=tx) - - -def max_tx_gas_limit(fork: Fork) -> int: - """Maximum gas limit for a transaction (fork agnostic).""" - tx_limit = fork.transaction_gas_limit_cap() - if tx_limit is not None: - return tx_limit - return Environment().gas_limit - - -def tx_gas_limits(fork: Fork) -> List[int]: - """List of tx gas limits.""" - # Three coverage levels for BLAKE2 + SSTORE base costs. The - # contract writes two first-time SSTOREs (data_1, data_2), each - # adding `sstore_state_gas` under EIP-8037 (0 otherwise). - sstore_state = Op.SSTORE(new_value=1).state_cost(fork) - return [ - max_tx_gas_limit(fork), - 90_000 + 2 * sstore_state, - 110_000 + 2 * sstore_state, - 200_000 + 2 * sstore_state, - ] + state_test(pre=pre, post=post, tx=tx) @pytest.mark.valid_from("Istanbul") @pytest.mark.parametrize("call_opcode", [Op.CALL, Op.CALLCODE]) -@pytest.mark.parametrize_by_fork("gas_limit", tx_gas_limits) @pytest.mark.parametrize( ["data", "output"], [ @@ -587,7 +406,6 @@ def tx_gas_limits(fork: Fork) -> List[int]: rounds=0, ), ExpectedOutput( - call_succeeds=True, data_1="0x08c9bcf367e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5", data_2="0xd282e6ad7f520e511f6c3e2b8c68059b9442be0454267ce079217e1319cde05b", ), @@ -596,7 +414,6 @@ def tx_gas_limits(fork: Fork) -> List[int]: pytest.param( Blake2bInput(), ExpectedOutput( - call_succeeds=True, data_1="0xba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d1", data_2="0x7d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923", ), @@ -607,7 +424,6 @@ def tx_gas_limits(fork: Fork) -> List[int]: f=False, ), ExpectedOutput( - call_succeeds=True, data_1="0x75ab69d3190a562c51aef8d88f1c2775876944407270c42c9844252c26d28752", data_2="0x98743e7f6d5ea2f2d3e8d226039cd31b4e426ac4f2d3d666a610c2116fde4735", ), @@ -618,7 +434,6 @@ def tx_gas_limits(fork: Fork) -> List[int]: rounds=1, ), ExpectedOutput( - call_succeeds=True, data_1="0xb63a380cb2897d521994a85234ee2c181b5f844d2c624c002677e9703449d2fb", data_2="0xa551b3a8333bcdf5f2f7e08993d53923de3d64fcc68c034e717b9293fed7a421", ), @@ -634,7 +449,6 @@ def tx_gas_limits(fork: Fork) -> List[int]: f=0, ), ExpectedOutput( - call_succeeds=True, data_1="0x08c9bcf367e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5", data_2="0xd182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b", ), @@ -642,109 +456,56 @@ def tx_gas_limits(fork: Fork) -> List[int]: ), ], ) +@pytest.mark.parametrize( + "precompile_gas_modifier", + [ + pytest.param(0, id="sufficient_gas"), + pytest.param(-1, id="insufficient_gas"), + ], +) @pytest.mark.eels_base_coverage -def test_blake2b_gas_limit( +def test_blake2b_gas( state_test: StateTestFiller, pre: Alloc, call_opcode: Op, blake2b_contract_bytecode: Bytecode, - gas_limit: int, - data: Blake2bInput | str | bytes, + data: Blake2bInput | bytes, + precompile_gas: int, output: ExpectedOutput, + precompile_gas_modifier: int, ) -> None: """Test BLAKE2b precompile with different gas limits.""" + sufficient_gas = precompile_gas_modifier == 0 + if not sufficient_gas and precompile_gas == 0: + pytest.skip("Precompile cost is zero, cannot run oog") + account = pre.deploy_contract( blake2b_contract_bytecode, storage={0: 0xDEADBEEF} ) sender = pre.fund_eoa() - if isinstance(data, Blake2bInput): - data = data.create_blake2b_tx_data() - elif isinstance(data, str): - data = bytes.fromhex(data) - - tx = Transaction( - ty=0x0, - to=account, - data=data, - gas_limit=gas_limit, - protected=True, - sender=sender, - value=0, - ) + tx = Transaction(to=account, data=data, sender=sender) post = { account: Account( - storage={ - 0: 0x1 if output.call_succeeds else 0x0, - 1: output.data_1, - 2: output.data_2, - } + storage={0: 0x1, 1: output.data_1, 2: output.data_2} + if sufficient_gas + else {0: 0x0, 1: 0x0, 2: 0x0} ) } - state_test( - pre=pre, - post=post, - tx=tx, - ) + state_test(pre=pre, post=post, tx=tx) @pytest.mark.valid_from("Istanbul") @pytest.mark.parametrize("call_opcode", [Op.CALL, Op.CALLCODE]) -@pytest.mark.parametrize_by_fork( - "gas_limit", lambda fork: [max_tx_gas_limit(fork)] -) @pytest.mark.parametrize( ["data", "output"], [ - pytest.param( - b"", - ExpectedOutput( - call_succeeds=False, - data_1="0x00", - data_2="0x00", - ), - id="EIP-152-case0-data0-large-gas-limit", - ), - pytest.param( - Blake2bInput( - rounds_length=3, - ), - ExpectedOutput( - call_succeeds=False, - data_1="0x00", - data_2="0x00", - ), - id="EIP-152-case2-data1-large-gas-limit", - ), - pytest.param( - Blake2bInput( - rounds_length=5, - ), - ExpectedOutput( - call_succeeds=False, - data_1="0x00", - data_2="0x00", - ), - id="EIP-152-case2-data2-large-gas-limit", - ), - pytest.param( - Blake2bInput( - f=2, - ), - ExpectedOutput( - call_succeeds=False, - data_1="0x00", - data_2="0x00", - ), - id="EIP-152-case2-data3-large-gas-limit", - ), pytest.param( Blake2bInput( rounds=100_000, ), ExpectedOutput( - call_succeeds=True, data_1="0x165da71a32e91bca2623bfaeab079f7e6edfba2259028cc854ec497f9fb0fe75", data_2="0xd37f63034b83f4a0a07cd238483874862921ef0c40630826a76e41bf3b02ffe3", ), @@ -755,22 +516,12 @@ def test_blake2b_gas_limit( rounds=8000000, ), ExpectedOutput( - call_succeeds=True, data_1="0x6d2ce9e534d50e18ff866ae92d70cceba79bbcd14c63819fe48752c8aca87a4b", data_2="0xb7dcc230d22a4047f0486cfcfb50a17b24b2899eb8fca370f22240adb5170189", ), id="EIP-152-case8-data9-large-gas-limit", marks=pytest.mark.skip("Times-out during fill"), ), - pytest.param( - "000c", - ExpectedOutput( - call_succeeds=False, - data_1="0x00", - data_2="0x00", - ), - id="EIP-152-case9-data10-large-gas-limit", - ), ], ) @pytest.mark.slow() @@ -778,9 +529,8 @@ def test_blake2b_large_gas_limit( state_test: StateTestFiller, pre: Alloc, call_opcode: Op, - gas_limit: int, blake2b_contract_bytecode: Bytecode, - data: Blake2bInput | str | bytes, + data: Blake2bInput | bytes, output: ExpectedOutput, ) -> None: """Test BLAKE2b precompile with large gas limit.""" @@ -789,28 +539,9 @@ def test_blake2b_large_gas_limit( ) sender = pre.fund_eoa() - if isinstance(data, Blake2bInput): - data = data.create_blake2b_tx_data() - elif isinstance(data, str): - data = bytes.fromhex(data) - - tx = Transaction( - ty=0x0, - to=account, - data=data, - gas_limit=gas_limit, - protected=True, - sender=sender, - value=0, - ) + tx = Transaction(to=account, data=data, sender=sender) post = { - account: Account( - storage={ - 0: 0x1 if output.call_succeeds else 0x0, - 1: output.data_1, - 2: output.data_2, - } - ) + account: Account(storage={0: 0x1, 1: output.data_1, 2: output.data_2}) } state_test(pre=pre, post=post, tx=tx) diff --git a/tests/istanbul/eip152_blake2/test_blake2_delegatecall.py b/tests/istanbul/eip152_blake2/test_blake2_delegatecall.py index 34dd125aa4f..53cb74fa3a8 100644 --- a/tests/istanbul/eip152_blake2/test_blake2_delegatecall.py +++ b/tests/istanbul/eip152_blake2/test_blake2_delegatecall.py @@ -6,7 +6,6 @@ from execution_testing import ( Account, Alloc, - Environment, Fork, Op, StateTestFiller, @@ -28,8 +27,6 @@ def test_blake2_precompile_delegatecall( Test delegatecall consumes specified gas for the Blake2B precompile when it exists. """ - env = Environment() - account = pre.deploy_contract( Op.SSTORE( 0, @@ -42,12 +39,7 @@ def test_blake2_precompile_delegatecall( storage={0: 0xDEADBEEF}, ) - tx = Transaction( - to=account, - sender=pre.fund_eoa(), - gas_limit=90_000, - protected=True, - ) + tx = Transaction(to=account, sender=pre.fund_eoa()) # If precompile exists, DELEGATECALL will fail, otherwise DELEGATECALL will # succeed @@ -59,4 +51,4 @@ def test_blake2_precompile_delegatecall( ) } - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/osaka/eip7883_modexp_gas_increase/conftest.py b/tests/osaka/eip7883_modexp_gas_increase/conftest.py index 1242efb8010..4f824901b99 100644 --- a/tests/osaka/eip7883_modexp_gas_increase/conftest.py +++ b/tests/osaka/eip7883_modexp_gas_increase/conftest.py @@ -15,7 +15,6 @@ Transaction, keccak256, ) -from execution_testing.forks import London, Osaka from ...byzantium.eip198_modexp_precompile.helpers import ModExpInput from .spec import Spec, Spec7883 @@ -231,11 +230,13 @@ def precompile_gas( Calculate gas cost for the ModExp precompile and verify it matches expected gas. """ - spec = Spec if fork < Osaka else Spec7883 + spec = Spec if not fork.is_eip_enabled(7883) else Spec7883 try: calculated_gas = spec.calculate_gas_cost(modexp_input) if gas_old is not None and gas_new is not None: - expected_gas = gas_old if fork < Osaka else gas_new + expected_gas = ( + gas_old if not fork.is_eip_enabled(7883) else gas_new + ) base_len = len(modexp_input.base) exp_len = len(modexp_input.exponent) mod_len = len(modexp_input.modulus) @@ -253,7 +254,7 @@ def precompile_gas( except Exception: # Used for `test_modexp_invalid_inputs` we expect the call to not # succeed. Return is for completeness. - return 500 if fork >= Osaka else 200 + return 500 if fork.is_eip_enabled(7883) else 200 @pytest.fixture @@ -264,36 +265,18 @@ def precompile_gas_modifier() -> int: @pytest.fixture def tx( - fork: Fork, pre: Alloc, gas_measure_contract: Address, modexp_input: ModExpInput, - tx_gas_limit: int, ) -> Transaction: """Transaction to measure gas consumption of the ModExp precompile.""" return Transaction( - ty=0x02 if fork >= London else 0x00, sender=pre.fund_eoa(), to=gas_measure_contract, data=bytes(modexp_input), - gas_limit=tx_gas_limit, ) -@pytest.fixture -def tx_gas_limit( - total_tx_gas_needed: int, fork: Fork, env: Environment -) -> int: - """ - Transaction gas limit used for the test (Can be overridden in the test). - """ - if fork.is_eip_enabled(8037): - # EIP-8037: tx gas limit can exceed TX_MAX_GAS_LIMIT. - return min(total_tx_gas_needed, env.gas_limit) - tx_gas_limit_cap = fork.transaction_gas_limit_cap() or env.gas_limit - return min(tx_gas_limit_cap, total_tx_gas_needed) - - @pytest.fixture def post( gas_measure_contract: Address, diff --git a/tests/osaka/eip7883_modexp_gas_increase/test_modexp_thresholds.py b/tests/osaka/eip7883_modexp_gas_increase/test_modexp_thresholds.py index 8f7e704891f..dbc0dfaf14c 100644 --- a/tests/osaka/eip7883_modexp_gas_increase/test_modexp_thresholds.py +++ b/tests/osaka/eip7883_modexp_gas_increase/test_modexp_thresholds.py @@ -398,7 +398,6 @@ def test_modexp_used_in_transaction_entry_points( pre: Alloc, tx: Transaction, modexp_input: bytes, - tx_gas_limit: int, call_values: int, ) -> None: """ @@ -409,7 +408,6 @@ def test_modexp_used_in_transaction_entry_points( to=Spec.MODEXP_ADDRESS, sender=pre.fund_eoa(), data=bytes(modexp_input), - gas_limit=tx_gas_limit, value=call_values, ) state_test(pre=pre, tx=tx, post={}) @@ -470,9 +468,7 @@ def test_contract_creation_transaction( tx = Transaction( sender=sender, - gas_limit=1_000_000, to=None, - value=0, data=contract_bytecode + bytes(modexp_input), ) @@ -560,9 +556,7 @@ def test_contract_initcode( tx = Transaction( sender=sender, - gas_limit=(1_000_000 if fork.is_eip_enabled(8037) else 200_000), to=factory_contract_address, - value=0, data=call_modexp_bytecode + bytes(modexp_input), ) diff --git a/tests/osaka/eip7883_modexp_gas_increase/test_modexp_thresholds_transition.py b/tests/osaka/eip7883_modexp_gas_increase/test_modexp_thresholds_transition.py index 2ef88752bc8..4fa9374838f 100644 --- a/tests/osaka/eip7883_modexp_gas_increase/test_modexp_thresholds_transition.py +++ b/tests/osaka/eip7883_modexp_gas_increase/test_modexp_thresholds_transition.py @@ -10,7 +10,6 @@ BlockchainTestFiller, Bytecode, EIPChecklist, - Environment, Fork, Op, Transaction, @@ -42,7 +41,6 @@ def test_modexp_fork_transition( blockchain_test: BlockchainTestFiller, pre: Alloc, - env: Environment, fork: TransitionFork, gas_old: int, gas_new: int, @@ -94,10 +92,6 @@ def generate_code(fork: Fork) -> Bytecode: ) return code - def calc_tx_gas_limit(fork: Fork) -> int: - tx_gas_limit_cap = fork.transaction_gas_limit_cap() or env.gas_limit - return tx_gas_limit_cap - timestamps = [14_999, 15_000, 15_001] contracts = [ pre.deploy_contract(generate_code(fork.fork_at(timestamp=t))) @@ -110,10 +104,7 @@ def calc_tx_gas_limit(fork: Fork) -> int: timestamp=ts, txs=[ Transaction( - to=contract, - data=modexp_input, - sender=pre.fund_eoa(), - gas_limit=calc_tx_gas_limit(fork.fork_at(timestamp=ts)), + to=contract, data=modexp_input, sender=pre.fund_eoa() ) ], ) diff --git a/tests/osaka/eip7918_blob_reserve_price/test_blob_base_fee.py b/tests/osaka/eip7918_blob_reserve_price/test_blob_base_fee.py index 8a51a238574..9a8b60f1837 100644 --- a/tests/osaka/eip7918_blob_reserve_price/test_blob_base_fee.py +++ b/tests/osaka/eip7918_blob_reserve_price/test_blob_base_fee.py @@ -14,7 +14,6 @@ Alloc, Block, BlockchainTestFiller, - Bytecode, Environment, Fork, Hash, @@ -35,34 +34,14 @@ @pytest.fixture def sender(pre: Alloc) -> Address: """Sender account with enough balance for tests.""" - return pre.fund_eoa(10**18) + return pre.fund_eoa() @pytest.fixture -def destination_code() -> Bytecode: - """Bytecode that stores the blob base fee at slot 0.""" - return Op.SSTORE(0, Op.BLOBBASEFEE) - - -@pytest.fixture -def destination_account(pre: Alloc, destination_code: Bytecode) -> Address: +def destination_account(pre: Alloc) -> Address: """Contract that stores the blob base fee for verification.""" - return pre.deploy_contract(destination_code) - - -@pytest.fixture -def tx_gas(fork: Fork, destination_code: Bytecode) -> int: - """ - Gas limit sized exactly for the destination's single SSTORE 0->non-zero - plus the EIP-1706 stipend slack and (under EIP-8037) one - `sstore_state_gas` of reservoir headroom. - """ - intrinsic = fork.transaction_intrinsic_cost_calculator() - return ( - intrinsic() - + destination_code.gas_cost(fork) - + Op.SSTORE(new_value=1).state_cost(fork) - ) + code = Op.SSTORE(0, Op.BLOBBASEFEE) + return pre.deploy_contract(code) @pytest.fixture @@ -84,7 +63,6 @@ def blob_hashes_per_tx(blobs_per_tx: int) -> List[Hash]: def tx( sender: Address, destination_account: Address, - tx_gas: int, tx_value: int, blob_hashes_per_tx: List[Hash], block_base_fee_per_gas: int, @@ -96,7 +74,6 @@ def tx( sender=sender, to=destination_account, value=tx_value, - gas_limit=tx_gas, max_fee_per_gas=block_base_fee_per_gas, max_priority_fee_per_gas=0, max_fee_per_blob_gas=tx_max_fee_per_blob_gas, @@ -109,7 +86,6 @@ def tx( def block( tx: Transaction, fork: Fork, - destination_code: Bytecode, parent_excess_blobs: int, parent_blobs: int, block_base_fee_per_gas: int, @@ -125,14 +101,9 @@ def block( parent_blob_count=parent_blobs, parent_base_fee_per_gas=block_base_fee_per_gas, ) - intrinsic = fork.transaction_intrinsic_cost_calculator() - code_state = destination_code.state_cost(fork) - code_regular = destination_code.gas_cost(fork) - code_state - expected_gas_used = max(intrinsic() + code_regular, code_state) return Block( txs=[tx], header_verify=Header( - gas_used=expected_gas_used, excess_blob_gas=expected_excess_blob_gas, blob_gas_used=blob_count * blob_gas_per_blob, ), diff --git a/tests/osaka/eip7918_blob_reserve_price/test_blob_reserve_price_with_bpo_transitions.py b/tests/osaka/eip7918_blob_reserve_price/test_blob_reserve_price_with_bpo_transitions.py index 810bc410635..c34890a07aa 100644 --- a/tests/osaka/eip7918_blob_reserve_price/test_blob_reserve_price_with_bpo_transitions.py +++ b/tests/osaka/eip7918_blob_reserve_price/test_blob_reserve_price_with_bpo_transitions.py @@ -262,7 +262,9 @@ def parent_block_txs( parent_base_fee_per_gas=parent_base_fee_per_gas, required_base_fee_per_gas=transition_block_base_fee_per_gas, ) - blob_txs_execution_gas = sum(tx.gas_limit for tx in parent_block_blob_txs) + blob_txs_execution_gas = 0 + for tx in parent_block_blob_txs: + blob_txs_execution_gas += tx.gas_limit assert blob_txs_execution_gas <= required_gas_used extra_tx_gas_limit = required_gas_used - blob_txs_execution_gas assert extra_tx_gas_limit >= 21_000 diff --git a/tests/osaka/eip7939_count_leading_zeros/test_count_leading_zeros.py b/tests/osaka/eip7939_count_leading_zeros/test_count_leading_zeros.py index 496667236e1..7b294c31b1b 100644 --- a/tests/osaka/eip7939_count_leading_zeros/test_count_leading_zeros.py +++ b/tests/osaka/eip7939_count_leading_zeros/test_count_leading_zeros.py @@ -14,7 +14,6 @@ EIPChecklist, Environment, Fork, - Header, Op, StateTestFiller, Storage, @@ -118,7 +117,6 @@ def test_clz_opcode_scenarios( tx = Transaction( to=contract_address, sender=sender, - gas_limit=200_000, ) post = { contract_address: Account(storage={"0x00": expected_clz}), @@ -143,7 +141,7 @@ def test_clz_gas_cost( storage={"0x00": "0xdeadbeef"}, ) sender = pre.fund_eoa() - tx = Transaction(to=contract_address, sender=sender, gas_limit=200_000) + tx = Transaction(to=contract_address, sender=sender) post = { contract_address: Account( # Cost measured is CLZ + PUSH1 storage={"0x00": Op.CLZ.gas_cost(fork)} @@ -182,7 +180,7 @@ def test_clz_gas_cost_boundary( storage={"0x00": "0xdeadbeef"}, ) - tx = Transaction(to=call_address, sender=pre.fund_eoa(), gas_limit=200_000) + tx = Transaction(to=call_address, sender=pre.fund_eoa()) post = { call_address: Account(storage={"0x00": 0 if gas_cost_delta < 0 else 1}) @@ -206,11 +204,7 @@ def test_clz_stack_underflow(state_test: StateTestFiller, pre: Alloc) -> None: code=Op.SSTORE(0, Op.CALL(gas=0xFFFF, address=callee_address)), storage={"0x00": "0xdeadbeef"}, ) - tx = Transaction( - to=caller_address, - sender=sender, - gas_limit=200_000, - ) + tx = Transaction(to=caller_address, sender=sender) post = { caller_address: Account( storage={"0x00": 0} # Call failed due to stack underflow @@ -234,45 +228,23 @@ def test_clz_stack_not_overflow( code += Op.PUSH0 * (max_stack_items - 2) for i in range(256): - # `i=255` writes 0 to slot 255 (CLZ(1<<255) == 0); pin metadata so - # `gas_cost(fork)` picks the no-op SSTORE branch instead of the - # default cold zero->non-zero assumption. - sstore = Op.SSTORE.with_metadata( - key_warm=False, - original_value=0, - current_value=0, - new_value=255 - i, - ) - code += Op.PUSH1(i) + Op.CLZ(1 << i) + Op.SWAP1 + sstore + code += Op.PUSH1(i) + Op.CLZ(1 << i) + Op.SWAP1 + Op.SSTORE code_address = pre.deploy_contract(code=code) post[code_address] = Account(storage={i: 255 - i for i in range(256)}) - intrinsic = fork.transaction_intrinsic_cost_calculator() - code_state = code.state_cost(fork) - code_regular = code.gas_cost(fork) - code_state - # Trailing SSTORE is a no-op (~2100); EIP-1706 requires gas_left >= - # CALL_STIPEND+1 at entry, so reserve that as slack on top of exact. - eip_1706_slack = fork.gas_costs().CALL_STIPEND + 1 tx = Transaction( to=code_address, sender=pre.fund_eoa(), - gas_limit=(intrinsic() + code_regular + code_state + eip_1706_slack), ) - expected_gas_used = max(intrinsic() + code_regular, code_state) - state_test( - pre=pre, - post=post, - tx=tx, - blockchain_test_header_verify=Header(gas_used=expected_gas_used), - ) + state_test(pre=pre, post=post, tx=tx) @pytest.mark.valid_from("Osaka") def test_clz_push_operation_same_value( - state_test: StateTestFiller, pre: Alloc, fork: Fork + state_test: StateTestFiller, pre: Alloc ) -> None: """Test CLZ opcode returns the same value via different push operations.""" storage = {} @@ -289,19 +261,7 @@ def test_clz_push_operation_same_value( code_address = pre.deploy_contract(code=code) - intrinsic = fork.transaction_intrinsic_cost_calculator() - code_state = code.state_cost(fork) - code_regular = code.gas_cost(fork) - code_state - tx = Transaction( - to=code_address, - sender=pre.fund_eoa(), - gas_limit=( - intrinsic() - + code_regular - + code_state - + Op.SSTORE(new_value=1).state_cost(fork) - ), - ) + tx = Transaction(to=code_address, sender=pre.fund_eoa()) post = { code_address: Account( @@ -309,13 +269,7 @@ def test_clz_push_operation_same_value( ) } - expected_gas_used = max(intrinsic() + code_regular, code_state) - state_test( - pre=pre, - post=post, - tx=tx, - blockchain_test_header_verify=Header(gas_used=expected_gas_used), - ) + state_test(pre=pre, post=post, tx=tx) @EIPChecklist.Opcode.Test.ForkTransition.Invalid() @@ -344,7 +298,6 @@ def test_clz_fork_transition( to=caller_address, sender=sender, nonce=0, - gas_limit=200_000, ) ], ), @@ -355,7 +308,6 @@ def test_clz_fork_transition( to=caller_address, sender=sender, nonce=1, - gas_limit=200_000, ) ], ), @@ -366,7 +318,6 @@ def test_clz_fork_transition( to=caller_address, sender=sender, nonce=2, - gas_limit=200_000, ) ], ), @@ -412,7 +363,6 @@ def test_clz_fork_transition( def test_clz_jump_operation( state_test: StateTestFiller, pre: Alloc, - fork: Fork, opcode: Op, valid_jump: bool, jumpi_condition: bool, @@ -442,29 +392,7 @@ def test_clz_jump_operation( storage={"0x00": "0xdeadbeef"}, ) - intrinsic = fork.transaction_intrinsic_cost_calculator() - # The inner CALL forwards a fixed 0xFFFF (65535) regular gas — too - # tight for callee's SSTORE state to spill into. Lift `gas_limit` past - # the EIP-7825 cap so the EIP-8037 reservoir holds the callee's state - # work and parent's SSTORE state, plus EIP-1706 slack. - gas_cap = fork.transaction_gas_limit_cap() - state_needed = caller_code.state_cost(fork) + callee_code.state_cost(fork) - if gas_cap is not None and state_needed > 0: - gas_limit = ( - gas_cap + state_needed + Op.SSTORE(new_value=1).state_cost(fork) - ) - else: - gas_limit = ( - intrinsic() - + caller_code.gas_cost(fork) - + caller_forwarded_gas - + Op.SSTORE(new_value=1).state_cost(fork) - ) - tx = Transaction( - to=caller_address, - sender=pre.fund_eoa(), - gas_limit=gas_limit, - ) + tx = Transaction(to=caller_address, sender=pre.fund_eoa()) expected_clz = 255 - bits @@ -488,7 +416,6 @@ def test_clz_jump_operation( def test_clz_from_set_code( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test the CLZ opcode in a set-code transaction.""" storage = Storage() @@ -504,10 +431,7 @@ def test_clz_from_set_code( set_code_to_address = pre.deploy_contract(set_code) - # 4 first-time SSTOREs in the delegated code each add - # `sstore_state_gas` under EIP-8037 (0 otherwise). tx = Transaction( - gas_limit=200_000 + 4 * Op.SSTORE(new_value=1).state_cost(fork), to=auth_signer, value=0, authorization_list=[ @@ -581,11 +505,7 @@ def test_clz_code_copy_operation( } ) } - tx = Transaction( - to=clz_contract_address, - sender=pre.fund_eoa(), - gas_limit=200_000, - ) + tx = Transaction(to=clz_contract_address, sender=pre.fund_eoa()) state_test(pre=pre, post=post, tx=tx) @@ -644,11 +564,7 @@ def test_clz_with_memory_operation( ), } - tx = Transaction( - to=clz_contract_address, - sender=pre.fund_eoa(), - gas_limit=200_000, - ) + tx = Transaction(to=clz_contract_address, sender=pre.fund_eoa()) state_test(pre=pre, post=post, tx=tx) @@ -669,12 +585,7 @@ def test_clz_initcode_context(state_test: StateTestFiller, pre: Alloc) -> None: contract_address = compute_create_address(address=sender_address, nonce=0) - tx = Transaction( - to=None, - gas_limit=6_000_000, - data=init_code, - sender=sender_address, - ) + tx = Transaction(to=None, data=init_code, sender=sender_address) post = { contract_address: Account(storage=storage), @@ -687,7 +598,7 @@ def test_clz_initcode_context(state_test: StateTestFiller, pre: Alloc) -> None: @pytest.mark.valid_from("Osaka") @pytest.mark.parametrize("opcode", [Op.CREATE, Op.CREATE2]) def test_clz_initcode_create( - state_test: StateTestFiller, pre: Alloc, fork: Fork, opcode: Op + state_test: StateTestFiller, pre: Alloc, opcode: Op ) -> None: """Test CLZ opcode behavior in initcode executed via CREATE/CREATE2.""" bits = [0, 1, 64, 128, 255] # expected values: [255, 254, 191, 127, 0] @@ -715,16 +626,8 @@ def test_clz_initcode_create( opcode=opcode, ) - # CREATE charges NEW_ACCOUNT plus 5 first-time SSTOREs in the - # deployed contract; both terms add state gas under EIP-8037 - # (0 otherwise). tx = Transaction( to=factory_contract_address, - gas_limit=( - 200_000 - + fork.gas_costs().NEW_ACCOUNT - + 5 * Op.SSTORE(new_value=1).state_cost(fork) - ), data=ext_code, sender=sender_address, ) @@ -769,7 +672,6 @@ class CallingContext: def test_clz_call_operation( state_test: StateTestFiller, pre: Alloc, - fork: Fork, opcode: Op, context: CallingContext, ) -> None: @@ -798,13 +700,7 @@ def test_clz_call_operation( callee_address = pre.deploy_contract(code=callee_code) - # 3 first-time SSTOREs in the callee (when context != no_context) - # and 3 more in the caller (when context == callee_context); each - # adds `sstore_state_gas` under EIP-8037 (0 otherwise). - sstore_state = Op.SSTORE(new_value=1).state_cost(fork) - subcall_gas = 0xFFFF + 3 * sstore_state caller_code = opcode( - gas=subcall_gas, address=callee_address, ret_offset=0, ret_size=len(test_cases) * 0x20, @@ -817,11 +713,7 @@ def test_clz_call_operation( caller_address = pre.deploy_contract(code=caller_code) - tx = Transaction( - to=caller_address, - sender=pre.fund_eoa(), - gas_limit=200_000 + 6 * sstore_state, - ) + tx = Transaction(to=caller_address, sender=pre.fund_eoa()) post = {} diff --git a/tests/osaka/eip7951_p256verify_precompiles/conftest.py b/tests/osaka/eip7951_p256verify_precompiles/conftest.py index 3b5ff9c3d99..f79cc77a4d1 100644 --- a/tests/osaka/eip7951_p256verify_precompiles/conftest.py +++ b/tests/osaka/eip7951_p256verify_precompiles/conftest.py @@ -148,37 +148,15 @@ def post( } -@pytest.fixture -def tx_gas_limit(fork: Fork, input_data: bytes, precompile_gas: int) -> int: - """ - Transaction gas limit used for the test (Can be overridden in the test). - """ - intrinsic_gas_cost_calculator = ( - fork.transaction_intrinsic_cost_calculator() - ) - memory_expansion_gas_calculator = fork.memory_expansion_gas_calculator() - extra_gas = 100_000 - if fork.is_eip_enabled(8037): - extra_gas = 500_000 - return ( - extra_gas - + intrinsic_gas_cost_calculator(calldata=input_data) - + memory_expansion_gas_calculator(new_bytes=len(input_data)) - + precompile_gas - ) - - @pytest.fixture def tx( input_data: bytes, - tx_gas_limit: int, call_contract_address: Address, sender: EOA, ) -> Transaction: """Transaction for the test.""" return Transaction( ty=0x02, - gas_limit=tx_gas_limit, data=input_data, to=call_contract_address, sender=sender, diff --git a/tests/osaka/eip7951_p256verify_precompiles/test_p256verify.py b/tests/osaka/eip7951_p256verify_precompiles/test_p256verify.py index 77fc1abf0f3..8667f465f6b 100644 --- a/tests/osaka/eip7951_p256verify_precompiles/test_p256verify.py +++ b/tests/osaka/eip7951_p256verify_precompiles/test_p256verify.py @@ -1120,7 +1120,6 @@ def test_precompile_will_return_success_with_tx_value( contract_address = pre.deploy_contract(call_256verify_bytecode) tx = Transaction( sender=sender, - gas_limit=1000000, to=contract_address, value=1000, data=input_data, @@ -1262,9 +1261,7 @@ def test_contract_creation_transaction( tx = Transaction( sender=sender, - gas_limit=1000000, to=None, - value=0, data=contract_bytecode + input_data, ) @@ -1345,7 +1342,6 @@ def test_contract_initcode( tx = Transaction( sender=sender, - gas_limit=(1_000_000 if fork.is_eip_enabled(8037) else 200_000), to=factory_contract_address, value=0, data=call_256verify_bytecode + input_data, diff --git a/tests/osaka/eip7951_p256verify_precompiles/test_p256verify_before_fork.py b/tests/osaka/eip7951_p256verify_precompiles/test_p256verify_before_fork.py index 1c8b5f141b8..da8efdfd589 100644 --- a/tests/osaka/eip7951_p256verify_precompiles/test_p256verify_before_fork.py +++ b/tests/osaka/eip7951_p256verify_precompiles/test_p256verify_before_fork.py @@ -37,28 +37,6 @@ def precompile_gas(vector_gas_value: int | None, fork: TransitionFork) -> int: return gas -@pytest.fixture -def tx_gas_limit( - fork: TransitionFork, input_data: bytes, precompile_gas: int -) -> int: - """ - Transaction gas limit used for the test (Can be overridden in the test). - """ - intrinsic_gas_cost_calculator = ( - fork.transitions_from().transaction_intrinsic_cost_calculator() - ) - memory_expansion_gas_calculator = ( - fork.transitions_from().memory_expansion_gas_calculator() - ) - extra_gas = 100_000 - return ( - extra_gas - + intrinsic_gas_cost_calculator(calldata=input_data) - + memory_expansion_gas_calculator(new_bytes=len(input_data)) - + precompile_gas - ) - - @pytest.mark.parametrize( "precompile_address,input_data,precompile_gas_modifier", [ diff --git a/tests/paris/eip7610_create_collision/test_collision_selfdestruct.py b/tests/paris/eip7610_create_collision/test_collision_selfdestruct.py index 2c6e3886d7e..a0f58bbbf18 100644 --- a/tests/paris/eip7610_create_collision/test_collision_selfdestruct.py +++ b/tests/paris/eip7610_create_collision/test_collision_selfdestruct.py @@ -115,13 +115,9 @@ def test_selfdestruct_after_create2_collision( env=env, pre=pre, post=post, - # 3 first-time SSTOREs (deployer's create2_result and - # controller's two outcome flags) each charge state gas under - # EIP-8037 (0 otherwise). tx=Transaction( sender=sender, to=controller, - gas_limit=2_000_000 + 3 * Op.SSTORE(new_value=1).state_cost(fork), data=initcode, ), ) diff --git a/tests/paris/eip7610_create_collision/test_initcollision.py b/tests/paris/eip7610_create_collision/test_initcollision.py index 04b0677e46c..544a7442d44 100644 --- a/tests/paris/eip7610_create_collision/test_initcollision.py +++ b/tests/paris/eip7610_create_collision/test_initcollision.py @@ -76,14 +76,11 @@ def test_init_collision_create_tx( Test that a contract creation transaction exceptionally aborts when the target address has a non-empty storage, balance, nonce, or code. """ - # Contract-creation tx: intrinsic includes NEW_ACCOUNT state gas - # under EIP-8037 (0 otherwise). tx = Transaction( sender=pre.fund_eoa(), ty=tx_type, to=None, data=initcode, - gas_limit=200_000 + fork.gas_costs().NEW_ACCOUNT, ) created_contract_address = tx.created_contract @@ -152,7 +149,6 @@ def test_init_collision_create_opcode( sender=pre.fund_eoa(), to=contract_creator_address, data=initcode, - gas_limit=2_000_000, ) pre[created_contract_address] = Account( diff --git a/tests/paris/eip7610_create_collision/test_revert_in_create.py b/tests/paris/eip7610_create_collision/test_revert_in_create.py index 676ea852e14..d733a1bddc9 100644 --- a/tests/paris/eip7610_create_collision/test_revert_in_create.py +++ b/tests/paris/eip7610_create_collision/test_revert_in_create.py @@ -7,7 +7,6 @@ Account, Alloc, Bytecode, - Fork, Initcode, Op, StateTestFiller, @@ -64,7 +63,6 @@ def test_collision_with_create2_revert_in_initcode( sender=sender, to=None, data=initcode, - gas_limit=10_000_000, ) # Pre-existing account with storage - this causes collision per EIP-7610. @@ -108,7 +106,6 @@ def test_create2_collision_storage( state_test: StateTestFiller, pre: Alloc, create2_initcode: Bytecode, - fork: Fork, ) -> None: """ Test that CREATE2 fails when targeting an address with pre-existing @@ -129,16 +126,11 @@ def test_create2_collision_storage( ) sender = pre.fund_eoa() - gas_limit = 400_000 - if fork.is_eip_enabled(8037): - gas_limit = 1_000_000 - tx = Transaction( sender=sender, to=None, data=deployer_code, value=1, - gas_limit=gas_limit, ) deployer_address = tx.created_contract diff --git a/tests/paris/security/test_selfdestruct_balance_bug.py b/tests/paris/security/test_selfdestruct_balance_bug.py index ec68a734a95..719f78d3c93 100644 --- a/tests/paris/security/test_selfdestruct_balance_bug.py +++ b/tests/paris/security/test_selfdestruct_balance_bug.py @@ -19,7 +19,6 @@ Block, BlockchainTestFiller, CalldataCase, - Fork, Initcode, Op, Switch, @@ -30,7 +29,7 @@ @pytest.mark.valid_from("Constantinople") def test_tx_selfdestruct_balance_bug( - blockchain_test: BlockchainTestFiller, pre: Alloc, fork: Fork + blockchain_test: BlockchainTestFiller, pre: Alloc ) -> None: """ Test that the vulnerability is not present by checking the balance of the @@ -96,56 +95,31 @@ def test_tx_selfdestruct_balance_bug( sender = pre.fund_eoa() - intrinsic_calc = fork.transaction_intrinsic_cost_calculator() - inner_call_gas = 100_000 # cc forwards this to each aa CALL - # Tx1 budget: cc bytecode + CREATE'd initcode execution + NEW_ACCOUNT - # state for the CREATE + the two forwarded inner CALL gas envelopes, - # plus EIP-1706 stipend slack for the trailing SSTORE. - cc_tx_gas = ( - intrinsic_calc(calldata=aa_code) - + cc_code.gas_cost(fork) - + aa_code.gas_cost(fork) - + fork.gas_costs().NEW_ACCOUNT - + 2 * inner_call_gas - + Op.SSTORE(new_value=1).state_cost(fork) - ) - # Balance-check tx: one zero->non-zero SSTORE. - balance_tx_gas = ( - intrinsic_calc() - + balance_code.gas_cost(fork) - + Op.SSTORE(new_value=1).state_cost(fork) - ) - # Plain value transfer to a (post-EIP-6780) non-existent account. - aa_value_tx_gas = intrinsic_calc() - blocks = [ Block( txs=[ - # Sender invokes caller, caller invokes 0xaa. + # Sender invokes caller, caller invokes 0xaa: + # calling with 1 wei call Transaction( sender=sender, to=cc_address, data=aa_code, - gas_limit=cc_tx_gas, ), - # Capture aa's balance after tx 1 (post selfdestruct). + # Dummy tx to store balance of 0xaa after first TX. Transaction( sender=sender, to=balance_address_1, - gas_limit=balance_tx_gas, ), - # Sender calls aa with 5 wei; aa no longer has code. + # Sender calls 0xaa with 5 wei. Transaction( sender=sender, to=aa_location, - gas_limit=aa_value_tx_gas, value=5, ), - # Capture aa's balance after tx 3. + # Dummy tx to store balance of 0xaa after second TX. Transaction( sender=sender, to=balance_address_2, - gas_limit=balance_tx_gas, ), ], ), diff --git a/tests/ported_static/stCallCodes/test_callcall_00.py b/tests/ported_static/stCallCodes/test_callcall_00.py index 668dd94809b..174b23b7844 100644 --- a/tests/ported_static/stCallCodes/test_callcall_00.py +++ b/tests/ported_static/stCallCodes/test_callcall_00.py @@ -4,15 +4,7 @@ Ported from: state_tests/stCallCodes/callcall_00Filler.json - -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values from the original filler (250k / 300k / 350k) were tuned to -the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the -innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei -of regular gas), and the inner CALL OoGs before the test's SSTORE -markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL -chain has headroom on Amsterdam; older forks are unaffected because -only the requested gas changes, the actual consumption is identical. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -25,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -40,18 +31,8 @@ def test_callcall_00( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Call -> call -> code, params check.""" - # EIP-8037 inner-CALL gas bumps (original gas values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state-gas - # spill into regular gas on Amsterdam). - inner_call_gas = 0x3D090 - outer_call_gas = 0x55730 - if fork.is_eip_enabled(8037): - inner_call_gas = 0xF4240 - outer_call_gas = 0x155CC0 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -84,7 +65,6 @@ def test_callcall_00( code=Op.SSTORE( key=0x1, value=Op.CALL( - gas=inner_call_gas, address=addr_2, value=0x2, args_offset=0x0, @@ -103,7 +83,6 @@ def test_callcall_00( code=Op.SSTORE( key=0x0, value=Op.CALL( - gas=outer_call_gas, address=addr, value=0x1, args_offset=0x0, @@ -117,12 +96,7 @@ def test_callcall_00( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { addr_2: Account( diff --git a/tests/ported_static/stCallCodes/test_callcall_00_suicide_end.py b/tests/ported_static/stCallCodes/test_callcall_00_suicide_end.py index ba8ab1441e5..bbf2605c2e5 100644 --- a/tests/ported_static/stCallCodes/test_callcall_00_suicide_end.py +++ b/tests/ported_static/stCallCodes/test_callcall_00_suicide_end.py @@ -4,9 +4,7 @@ Ported from: state_tests/stCallCodes/callcall_00_SuicideEndFiller.json -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. - +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,18 +31,8 @@ def test_callcall_00_suicide_end( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Call -> (call -> code) suicide .""" - # EIP-8037 inner-CALL gas bumps: original values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state- - # gas spill into regular gas on Amsterdam. - outer_call_gas = 150000 - inner_call_gas = 50000 - if fork.is_eip_enabled(8037): - outer_call_gas = 1000000 - inner_call_gas = 100000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -72,7 +59,6 @@ def test_callcall_00_suicide_end( code=Op.SSTORE( key=0x0, value=Op.CALL( - gas=outer_call_gas, address=0xF741CFEE7B7FB1025DCCEF3DB5A3CBC8FFB776F8, value=0x0, args_offset=0x0, @@ -92,7 +78,6 @@ def test_callcall_00_suicide_end( code=Op.SSTORE( key=0x1, value=Op.CALL( - gas=inner_call_gas, address=0x703B936FD4D674F0FF5D6957F61097152F8781B8, value=0x0, args_offset=0x0, @@ -108,12 +93,7 @@ def test_callcall_00_suicide_end( address=Address(0xF741CFEE7B7FB1025DCCEF3DB5A3CBC8FFB776F8), # noqa: E501 ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { target: Account(balance=0xDE0B6B5FB6FE400), diff --git a/tests/ported_static/stCallCodes/test_callcallcall_000.py b/tests/ported_static/stCallCodes/test_callcallcall_000.py index 1026e276d3d..0242f70f02b 100644 --- a/tests/ported_static/stCallCodes/test_callcallcall_000.py +++ b/tests/ported_static/stCallCodes/test_callcallcall_000.py @@ -4,15 +4,7 @@ Ported from: state_tests/stCallCodes/callcallcall_000Filler.json - -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values from the original filler (250k / 300k / 350k) were tuned to -the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the -innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei -of regular gas), and the inner CALL OoGs before the test's SSTORE -markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL -chain has headroom on Amsterdam; older forks are unaffected because -only the requested gas changes, the actual consumption is identical. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -25,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -40,20 +31,8 @@ def test_callcallcall_000( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Call -> call -> call -> code, params check.""" - # EIP-8037 inner-CALL gas bumps (original gas values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state-gas - # spill into regular gas on Amsterdam). - inner_call_gas = 0x3D090 - middle_call_gas = 0x493E0 - outer_call_gas = 0x55730 - if fork.is_eip_enabled(8037): - inner_call_gas = 0xF4240 - middle_call_gas = 0x124F80 - outer_call_gas = 0x155CC0 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -86,7 +65,6 @@ def test_callcallcall_000( code=Op.SSTORE( key=0x2, value=Op.CALL( - gas=inner_call_gas, address=addr_3, value=0x3, args_offset=0x0, @@ -105,7 +83,6 @@ def test_callcallcall_000( code=Op.SSTORE( key=0x1, value=Op.CALL( - gas=middle_call_gas, address=addr_2, value=0x2, args_offset=0x0, @@ -124,7 +101,6 @@ def test_callcallcall_000( code=Op.SSTORE( key=0x0, value=Op.CALL( - gas=outer_call_gas, address=addr, value=0x1, args_offset=0x0, @@ -138,12 +114,7 @@ def test_callcallcall_000( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { addr_3: Account( diff --git a/tests/ported_static/stCallCodes/test_callcallcall_000_suicide_end.py b/tests/ported_static/stCallCodes/test_callcallcall_000_suicide_end.py index d33e9e42b6a..752156fee71 100644 --- a/tests/ported_static/stCallCodes/test_callcallcall_000_suicide_end.py +++ b/tests/ported_static/stCallCodes/test_callcallcall_000_suicide_end.py @@ -4,9 +4,7 @@ Ported from: state_tests/stCallCodes/callcallcall_000_SuicideEndFiller.json -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. - +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,20 +31,8 @@ def test_callcallcall_000_suicide_end( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Call -> call -> (call -> code) suicide.""" - # EIP-8037 inner-CALL gas bumps: original values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state- - # gas spill into regular gas on Amsterdam. - outer_call_gas = 150000 - middle_call_gas = 100000 - inner_call_gas = 50000 - if fork.is_eip_enabled(8037): - outer_call_gas = 1000000 - middle_call_gas = 800000 - inner_call_gas = 100000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -74,7 +59,6 @@ def test_callcallcall_000_suicide_end( code=Op.SSTORE( key=0x0, value=Op.CALL( - gas=outer_call_gas, address=0x77B749FFFF7EC61D31C79ED104F230A7959B2879, value=0x0, args_offset=0x0, @@ -94,7 +78,6 @@ def test_callcallcall_000_suicide_end( code=Op.SSTORE( key=0x1, value=Op.CALL( - gas=middle_call_gas, address=0xD957E143AD2C011BC6A2B142795F1A9BA70D0680, value=0x0, args_offset=0x0, @@ -114,7 +97,6 @@ def test_callcallcall_000_suicide_end( code=Op.SSTORE( key=0x2, value=Op.CALL( - gas=inner_call_gas, address=0xCB6497F0337B6CD0F7239A8819295EC7D1DAFD34, value=0x0, args_offset=0x0, @@ -130,12 +112,7 @@ def test_callcallcall_000_suicide_end( address=Address(0xD957E143AD2C011BC6A2B142795F1A9BA70D0680), # noqa: E501 ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { addr: Account(balance=0x4A817C800), diff --git a/tests/ported_static/stCallCodes/test_callcallcallcode_001.py b/tests/ported_static/stCallCodes/test_callcallcallcode_001.py index f1a4178583c..d7d4ec9d015 100644 --- a/tests/ported_static/stCallCodes/test_callcallcallcode_001.py +++ b/tests/ported_static/stCallCodes/test_callcallcallcode_001.py @@ -4,15 +4,7 @@ Ported from: state_tests/stCallCodes/callcallcallcode_001Filler.json - -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values from the original filler (250k / 300k / 350k) were tuned to -the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the -innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei -of regular gas), and the inner CALL OoGs before the test's SSTORE -markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL -chain has headroom on Amsterdam; older forks are unaffected because -only the requested gas changes, the actual consumption is identical. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -25,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -40,20 +31,8 @@ def test_callcallcallcode_001( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Call -> call -> callcode - > code, params check.""" - # EIP-8037 inner-CALL gas bumps (original gas values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state-gas - # spill into regular gas on Amsterdam). - inner_call_gas = 0x3D090 - middle_call_gas = 0x493E0 - outer_call_gas = 0x55730 - if fork.is_eip_enabled(8037): - inner_call_gas = 0xF4240 - middle_call_gas = 0x124F80 - outer_call_gas = 0x155CC0 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -86,7 +65,6 @@ def test_callcallcallcode_001( code=Op.SSTORE( key=0x2, value=Op.CALLCODE( - gas=inner_call_gas, address=addr_3, value=0x3, args_offset=0x0, @@ -105,7 +83,6 @@ def test_callcallcallcode_001( code=Op.SSTORE( key=0x1, value=Op.CALL( - gas=middle_call_gas, address=addr_2, value=0x2, args_offset=0x0, @@ -124,7 +101,6 @@ def test_callcallcallcode_001( code=Op.SSTORE( key=0x0, value=Op.CALL( - gas=outer_call_gas, address=addr, value=0x1, args_offset=0x0, @@ -138,12 +114,7 @@ def test_callcallcallcode_001( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { addr_2: Account( diff --git a/tests/ported_static/stCallCodes/test_callcallcallcode_001_suicide_end.py b/tests/ported_static/stCallCodes/test_callcallcallcode_001_suicide_end.py index 1bc24e83ea6..facef0e01ca 100644 --- a/tests/ported_static/stCallCodes/test_callcallcallcode_001_suicide_end.py +++ b/tests/ported_static/stCallCodes/test_callcallcallcode_001_suicide_end.py @@ -4,9 +4,7 @@ Ported from: state_tests/stCallCodes/callcallcallcode_001_SuicideEndFiller.json -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. - +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,20 +31,8 @@ def test_callcallcallcode_001_suicide_end( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Call -> call -> ( callcode - > code ) suicide.""" - # EIP-8037 inner-CALL gas bumps: original values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state- - # gas spill into regular gas on Amsterdam. - outer_call_gas = 150000 - middle_call_gas = 100000 - inner_call_gas = 50000 - if fork.is_eip_enabled(8037): - outer_call_gas = 1000000 - middle_call_gas = 800000 - inner_call_gas = 100000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -74,7 +59,6 @@ def test_callcallcallcode_001_suicide_end( code=Op.SSTORE( key=0x0, value=Op.CALL( - gas=outer_call_gas, address=0x77B749FFFF7EC61D31C79ED104F230A7959B2879, value=0x0, args_offset=0x0, @@ -94,7 +78,6 @@ def test_callcallcallcode_001_suicide_end( code=Op.SSTORE( key=0x1, value=Op.CALL( - gas=middle_call_gas, address=0x94C8F980AEECBB6575B12AE614A249FC3E836F21, value=0x0, args_offset=0x0, @@ -114,7 +97,6 @@ def test_callcallcallcode_001_suicide_end( code=Op.SSTORE( key=0x2, value=Op.CALLCODE( - gas=inner_call_gas, address=0x73B954EBC05BB0FF4A0F6A13A054D50AD1584099, value=0x0, args_offset=0x0, @@ -130,12 +112,7 @@ def test_callcallcallcode_001_suicide_end( address=Address(0x94C8F980AEECBB6575B12AE614A249FC3E836F21), # noqa: E501 ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { addr: Account(balance=0x4A817C800), diff --git a/tests/ported_static/stCallCodes/test_callcallcode_01.py b/tests/ported_static/stCallCodes/test_callcallcode_01.py index 08d46046488..e8f2b2285ec 100644 --- a/tests/ported_static/stCallCodes/test_callcallcode_01.py +++ b/tests/ported_static/stCallCodes/test_callcallcode_01.py @@ -4,15 +4,7 @@ Ported from: state_tests/stCallCodes/callcallcode_01Filler.json - -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values from the original filler (250k / 300k / 350k) were tuned to -the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the -innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei -of regular gas), and the inner CALL OoGs before the test's SSTORE -markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL -chain has headroom on Amsterdam; older forks are unaffected because -only the requested gas changes, the actual consumption is identical. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -25,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -40,18 +31,8 @@ def test_callcallcode_01( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Call -> callcode -> code, params check.""" - # EIP-8037 inner-CALL gas bumps (original gas values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state-gas - # spill into regular gas on Amsterdam). - inner_call_gas = 0x3D090 - outer_call_gas = 0x55730 - if fork.is_eip_enabled(8037): - inner_call_gas = 0xF4240 - outer_call_gas = 0x155CC0 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -84,7 +65,6 @@ def test_callcallcode_01( code=Op.SSTORE( key=0x1, value=Op.CALLCODE( - gas=inner_call_gas, address=addr_2, value=0x2, args_offset=0x0, @@ -103,7 +83,6 @@ def test_callcallcode_01( code=Op.SSTORE( key=0x0, value=Op.CALL( - gas=outer_call_gas, address=addr, value=0x1, args_offset=0x0, @@ -117,12 +96,7 @@ def test_callcallcode_01( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { addr: Account( diff --git a/tests/ported_static/stCallCodes/test_callcallcodecall_010.py b/tests/ported_static/stCallCodes/test_callcallcodecall_010.py index 51ab4615cc1..0f9c9278148 100644 --- a/tests/ported_static/stCallCodes/test_callcallcodecall_010.py +++ b/tests/ported_static/stCallCodes/test_callcallcodecall_010.py @@ -4,15 +4,7 @@ Ported from: state_tests/stCallCodes/callcallcodecall_010Filler.json - -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values from the original filler (250k / 300k / 350k) were tuned to -the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the -innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei -of regular gas), and the inner CALL OoGs before the test's SSTORE -markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL -chain has headroom on Amsterdam; older forks are unaffected because -only the requested gas changes, the actual consumption is identical. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -25,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -40,20 +31,8 @@ def test_callcallcodecall_010( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Call -> callcode -> call -> code, params check.""" - # EIP-8037 inner-CALL gas bumps (original gas values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state-gas - # spill into regular gas on Amsterdam). - inner_call_gas = 0x3D090 - middle_call_gas = 0x493E0 - outer_call_gas = 0x55730 - if fork.is_eip_enabled(8037): - inner_call_gas = 0xF4240 - middle_call_gas = 0x124F80 - outer_call_gas = 0x155CC0 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -86,7 +65,6 @@ def test_callcallcodecall_010( code=Op.SSTORE( key=0x2, value=Op.CALL( - gas=inner_call_gas, address=addr_3, value=0x3, args_offset=0x0, @@ -105,7 +83,6 @@ def test_callcallcodecall_010( code=Op.SSTORE( key=0x1, value=Op.CALLCODE( - gas=middle_call_gas, address=addr_2, value=0x2, args_offset=0x0, @@ -124,7 +101,6 @@ def test_callcallcodecall_010( code=Op.SSTORE( key=0x0, value=Op.CALL( - gas=outer_call_gas, address=addr, value=0x1, args_offset=0x0, @@ -138,12 +114,7 @@ def test_callcallcodecall_010( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { addr: Account(storage={1: 1, 2: 1}), diff --git a/tests/ported_static/stCallCodes/test_callcallcodecall_010_suicide_end.py b/tests/ported_static/stCallCodes/test_callcallcodecall_010_suicide_end.py index f23b0ff9251..7fc69c98b23 100644 --- a/tests/ported_static/stCallCodes/test_callcallcodecall_010_suicide_end.py +++ b/tests/ported_static/stCallCodes/test_callcallcodecall_010_suicide_end.py @@ -4,9 +4,7 @@ Ported from: state_tests/stCallCodes/callcallcodecall_010_SuicideEndFiller.json -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. - +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,20 +31,8 @@ def test_callcallcodecall_010_suicide_end( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Call -> callcode -> (call -> code) (suicide).""" - # EIP-8037 inner-CALL gas bumps: original values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state- - # gas spill into regular gas on Amsterdam. - outer_call_gas = 150000 - middle_call_gas = 100000 - inner_call_gas = 50000 - if fork.is_eip_enabled(8037): - outer_call_gas = 1000000 - middle_call_gas = 800000 - inner_call_gas = 100000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -74,7 +59,6 @@ def test_callcallcodecall_010_suicide_end( code=Op.SSTORE( key=0x0, value=Op.CALL( - gas=outer_call_gas, address=0xEAF8C2AE0D01A880CEA4E1AA88DEF5EDD153D57B, value=0x0, args_offset=0x0, @@ -94,7 +78,6 @@ def test_callcallcodecall_010_suicide_end( code=Op.SSTORE( key=0x1, value=Op.CALLCODE( - gas=middle_call_gas, address=0xD957E143AD2C011BC6A2B142795F1A9BA70D0680, value=0x0, args_offset=0x0, @@ -114,7 +97,6 @@ def test_callcallcodecall_010_suicide_end( code=Op.SSTORE( key=0x2, value=Op.CALL( - gas=inner_call_gas, address=0x73B954EBC05BB0FF4A0F6A13A054D50AD1584099, value=0x0, args_offset=0x0, @@ -130,12 +112,7 @@ def test_callcallcodecall_010_suicide_end( address=Address(0xD957E143AD2C011BC6A2B142795F1A9BA70D0680), # noqa: E501 ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { addr_2: Account(storage={1: 0, 2: 0}, balance=0x2540BE400), diff --git a/tests/ported_static/stCallCodes/test_callcallcodecallcode_011.py b/tests/ported_static/stCallCodes/test_callcallcodecallcode_011.py index dd5241029ac..758d721cfa1 100644 --- a/tests/ported_static/stCallCodes/test_callcallcodecallcode_011.py +++ b/tests/ported_static/stCallCodes/test_callcallcodecallcode_011.py @@ -4,15 +4,7 @@ Ported from: state_tests/stCallCodes/callcallcodecallcode_011Filler.json - -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values from the original filler (250k / 300k / 350k) were tuned to -the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the -innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei -of regular gas), and the inner CALL OoGs before the test's SSTORE -markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL -chain has headroom on Amsterdam; older forks are unaffected because -only the requested gas changes, the actual consumption is identical. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -25,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -40,20 +31,8 @@ def test_callcallcodecallcode_011( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Call -> callcode -> callcode -> code, check params.""" - # EIP-8037 inner-CALL gas bumps (original gas values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state-gas - # spill into regular gas on Amsterdam). - inner_call_gas = 0x3D090 - middle_call_gas = 0x493E0 - outer_call_gas = 0x55730 - if fork.is_eip_enabled(8037): - inner_call_gas = 0xF4240 - middle_call_gas = 0x124F80 - outer_call_gas = 0x155CC0 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -86,7 +65,6 @@ def test_callcallcodecallcode_011( code=Op.SSTORE( key=0x2, value=Op.CALLCODE( - gas=inner_call_gas, address=addr_3, value=0x3, args_offset=0x0, @@ -105,7 +83,6 @@ def test_callcallcodecallcode_011( code=Op.SSTORE( key=0x1, value=Op.CALLCODE( - gas=middle_call_gas, address=addr_2, value=0x2, args_offset=0x0, @@ -124,7 +101,6 @@ def test_callcallcodecallcode_011( code=Op.SSTORE( key=0x0, value=Op.CALL( - gas=outer_call_gas, address=addr, value=0x1, args_offset=0x0, @@ -138,12 +114,7 @@ def test_callcallcodecallcode_011( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { addr: Account( diff --git a/tests/ported_static/stCallCodes/test_callcodecall_10.py b/tests/ported_static/stCallCodes/test_callcodecall_10.py index 2e2a3bc2ae9..bf8db0c79f3 100644 --- a/tests/ported_static/stCallCodes/test_callcodecall_10.py +++ b/tests/ported_static/stCallCodes/test_callcodecall_10.py @@ -4,15 +4,7 @@ Ported from: state_tests/stCallCodes/callcodecall_10Filler.json - -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values from the original filler (250k / 300k / 350k) were tuned to -the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the -innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei -of regular gas), and the inner CALL OoGs before the test's SSTORE -markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL -chain has headroom on Amsterdam; older forks are unaffected because -only the requested gas changes, the actual consumption is identical. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -25,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -40,18 +31,8 @@ def test_callcodecall_10( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Callcode -> call -> code, params check .""" - # EIP-8037 inner-CALL gas bumps (original gas values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state-gas - # spill into regular gas on Amsterdam). - inner_call_gas = 0x3D090 - outer_call_gas = 0x55730 - if fork.is_eip_enabled(8037): - inner_call_gas = 0xF4240 - outer_call_gas = 0x155CC0 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -84,7 +65,6 @@ def test_callcodecall_10( code=Op.SSTORE( key=0x1, value=Op.CALL( - gas=inner_call_gas, address=addr_2, value=0x2, args_offset=0x0, @@ -103,7 +83,6 @@ def test_callcodecall_10( code=Op.SSTORE( key=0x0, value=Op.CALLCODE( - gas=outer_call_gas, address=addr, value=0x1, args_offset=0x0, @@ -117,12 +96,7 @@ def test_callcodecall_10( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { target: Account(storage={0: 1, 1: 1}), diff --git a/tests/ported_static/stCallCodes/test_callcodecall_10_suicide_end.py b/tests/ported_static/stCallCodes/test_callcodecall_10_suicide_end.py index d62da3d1016..3c8df70d9b5 100644 --- a/tests/ported_static/stCallCodes/test_callcodecall_10_suicide_end.py +++ b/tests/ported_static/stCallCodes/test_callcodecall_10_suicide_end.py @@ -4,9 +4,7 @@ Ported from: state_tests/stCallCodes/callcodecall_10_SuicideEndFiller.json -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. - +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,18 +31,8 @@ def test_callcodecall_10_suicide_end( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """CALLCODE -> (CALL -> code) (suicide).""" - # EIP-8037 inner-CALL gas bumps: original values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state- - # gas spill into regular gas on Amsterdam. - outer_call_gas = 150000 - inner_call_gas = 50000 - if fork.is_eip_enabled(8037): - outer_call_gas = 1000000 - inner_call_gas = 100000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -72,7 +59,6 @@ def test_callcodecall_10_suicide_end( code=Op.SSTORE( key=0x0, value=Op.CALLCODE( - gas=outer_call_gas, address=0xF741CFEE7B7FB1025DCCEF3DB5A3CBC8FFB776F8, value=0x0, args_offset=0x0, @@ -92,7 +78,6 @@ def test_callcodecall_10_suicide_end( code=Op.SSTORE( key=0x1, value=Op.CALL( - gas=inner_call_gas, address=0x703B936FD4D674F0FF5D6957F61097152F8781B8, value=0x0, args_offset=0x0, @@ -108,12 +93,7 @@ def test_callcodecall_10_suicide_end( address=Address(0xF741CFEE7B7FB1025DCCEF3DB5A3CBC8FFB776F8), # noqa: E501 ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { addr: Account(storage={0: 0, 1: 0}, balance=0x2540BE400), diff --git a/tests/ported_static/stCallCodes/test_callcodecallcall_100.py b/tests/ported_static/stCallCodes/test_callcodecallcall_100.py index 7d15c2b1f8d..3d48274bf52 100644 --- a/tests/ported_static/stCallCodes/test_callcodecallcall_100.py +++ b/tests/ported_static/stCallCodes/test_callcodecallcall_100.py @@ -4,15 +4,7 @@ Ported from: state_tests/stCallCodes/callcodecallcall_100Filler.json - -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values from the original filler (250k / 300k / 350k) were tuned to -the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the -innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei -of regular gas), and the inner CALL OoGs before the test's SSTORE -markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL -chain has headroom on Amsterdam; older forks are unaffected because -only the requested gas changes, the actual consumption is identical. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -25,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -40,20 +31,8 @@ def test_callcodecallcall_100( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """CALLCODE -> CALL -> CALL-> code, params check.""" - # EIP-8037 inner-CALL gas bumps (original gas values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state-gas - # spill into regular gas on Amsterdam). - inner_call_gas = 0x3D090 - middle_call_gas = 0x493E0 - outer_call_gas = 0x55730 - if fork.is_eip_enabled(8037): - inner_call_gas = 0xF4240 - middle_call_gas = 0x124F80 - outer_call_gas = 0x155CC0 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -86,7 +65,6 @@ def test_callcodecallcall_100( code=Op.SSTORE( key=0x2, value=Op.CALL( - gas=inner_call_gas, address=addr_3, value=0x3, args_offset=0x0, @@ -105,7 +83,6 @@ def test_callcodecallcall_100( code=Op.SSTORE( key=0x1, value=Op.CALL( - gas=middle_call_gas, address=addr_2, value=0x2, args_offset=0x0, @@ -124,7 +101,6 @@ def test_callcodecallcall_100( code=Op.SSTORE( key=0x0, value=Op.CALLCODE( - gas=outer_call_gas, address=addr, value=0x1, args_offset=0x0, @@ -138,12 +114,7 @@ def test_callcodecallcall_100( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { target: Account(storage={0: 1, 1: 1}), diff --git a/tests/ported_static/stCallCodes/test_callcodecallcall_100_suicide_end.py b/tests/ported_static/stCallCodes/test_callcodecallcall_100_suicide_end.py index 4a8b875f9c0..846202968df 100644 --- a/tests/ported_static/stCallCodes/test_callcodecallcall_100_suicide_end.py +++ b/tests/ported_static/stCallCodes/test_callcodecallcall_100_suicide_end.py @@ -4,9 +4,7 @@ Ported from: state_tests/stCallCodes/callcodecallcall_100_SuicideEndFiller.json -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. - +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,20 +31,8 @@ def test_callcodecallcall_100_suicide_end( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """CALLCODE -> CALL -> (CALL-> code) (suicide).""" - # EIP-8037 inner-CALL gas bumps: original values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state- - # gas spill into regular gas on Amsterdam. - outer_call_gas = 150000 - middle_call_gas = 100000 - inner_call_gas = 50000 - if fork.is_eip_enabled(8037): - outer_call_gas = 1000000 - middle_call_gas = 800000 - inner_call_gas = 100000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -74,7 +59,6 @@ def test_callcodecallcall_100_suicide_end( code=Op.SSTORE( key=0x0, value=Op.CALLCODE( - gas=outer_call_gas, address=0x77B749FFFF7EC61D31C79ED104F230A7959B2879, value=0x0, args_offset=0x0, @@ -94,7 +78,6 @@ def test_callcodecallcall_100_suicide_end( code=Op.SSTORE( key=0x1, value=Op.CALL( - gas=middle_call_gas, address=0xD957E143AD2C011BC6A2B142795F1A9BA70D0680, value=0x0, args_offset=0x0, @@ -114,7 +97,6 @@ def test_callcodecallcall_100_suicide_end( code=Op.SSTORE( key=0x2, value=Op.CALL( - gas=inner_call_gas, address=0x73B954EBC05BB0FF4A0F6A13A054D50AD1584099, value=0x0, args_offset=0x0, @@ -130,12 +112,7 @@ def test_callcodecallcall_100_suicide_end( address=Address(0xD957E143AD2C011BC6A2B142795F1A9BA70D0680), # noqa: E501 ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { target: Account(storage={0: 1, 1: 1}, balance=0xDE0B6B3A7640000), diff --git a/tests/ported_static/stCallCodes/test_callcodecallcallcode_101.py b/tests/ported_static/stCallCodes/test_callcodecallcallcode_101.py index ef876653dac..8c8218c8c55 100644 --- a/tests/ported_static/stCallCodes/test_callcodecallcallcode_101.py +++ b/tests/ported_static/stCallCodes/test_callcodecallcallcode_101.py @@ -4,15 +4,7 @@ Ported from: state_tests/stCallCodes/callcodecallcallcode_101Filler.json - -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values from the original filler (250k / 300k / 350k) were tuned to -the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the -innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei -of regular gas), and the inner CALL OoGs before the test's SSTORE -markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL -chain has headroom on Amsterdam; older forks are unaffected because -only the requested gas changes, the actual consumption is identical. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -25,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -40,20 +31,8 @@ def test_callcodecallcallcode_101( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """CALLCODE -> CALL -> CALLCODE -> code parameters check.""" - # EIP-8037 inner-CALL gas bumps (original gas values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state-gas - # spill into regular gas on Amsterdam). - inner_call_gas = 0x3D090 - middle_call_gas = 0x493E0 - outer_call_gas = 0x55730 - if fork.is_eip_enabled(8037): - inner_call_gas = 0xF4240 - middle_call_gas = 0x124F80 - outer_call_gas = 0x155CC0 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -86,7 +65,6 @@ def test_callcodecallcallcode_101( code=Op.SSTORE( key=0x2, value=Op.CALLCODE( - gas=inner_call_gas, address=addr_3, value=0x3, args_offset=0x0, @@ -105,7 +83,6 @@ def test_callcodecallcallcode_101( code=Op.SSTORE( key=0x1, value=Op.CALL( - gas=middle_call_gas, address=addr_2, value=0x2, args_offset=0x0, @@ -124,7 +101,6 @@ def test_callcodecallcallcode_101( code=Op.SSTORE( key=0x0, value=Op.CALLCODE( - gas=outer_call_gas, address=addr, value=0x1, args_offset=0x0, @@ -138,12 +114,7 @@ def test_callcodecallcallcode_101( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { target: Account(storage={0: 1, 1: 1}), diff --git a/tests/ported_static/stCallCodes/test_callcodecallcallcode_101_suicide_end.py b/tests/ported_static/stCallCodes/test_callcodecallcallcode_101_suicide_end.py index 3de342c0923..824042c2695 100644 --- a/tests/ported_static/stCallCodes/test_callcodecallcallcode_101_suicide_end.py +++ b/tests/ported_static/stCallCodes/test_callcodecallcallcode_101_suicide_end.py @@ -4,9 +4,7 @@ Ported from: state_tests/stCallCodes/callcodecallcallcode_101_SuicideEndFiller.json -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. - +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,20 +31,8 @@ def test_callcodecallcallcode_101_suicide_end( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """CALLCODE -> CALL -> (CALLCODE -> code) (suicide).""" - # EIP-8037 inner-CALL gas bumps: original values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state- - # gas spill into regular gas on Amsterdam. - outer_call_gas = 150000 - middle_call_gas = 100000 - inner_call_gas = 50000 - if fork.is_eip_enabled(8037): - outer_call_gas = 1000000 - middle_call_gas = 800000 - inner_call_gas = 100000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -74,7 +59,6 @@ def test_callcodecallcallcode_101_suicide_end( code=Op.SSTORE( key=0x0, value=Op.CALLCODE( - gas=outer_call_gas, address=0x77B749FFFF7EC61D31C79ED104F230A7959B2879, value=0x0, args_offset=0x0, @@ -94,7 +78,6 @@ def test_callcodecallcallcode_101_suicide_end( code=Op.SSTORE( key=0x1, value=Op.CALL( - gas=middle_call_gas, address=0x94C8F980AEECBB6575B12AE614A249FC3E836F21, value=0x0, args_offset=0x0, @@ -114,7 +97,6 @@ def test_callcodecallcallcode_101_suicide_end( code=Op.SSTORE( key=0x2, value=Op.CALLCODE( - gas=inner_call_gas, address=0x73B954EBC05BB0FF4A0F6A13A054D50AD1584099, value=0x0, args_offset=0x0, @@ -130,12 +112,7 @@ def test_callcodecallcallcode_101_suicide_end( address=Address(0x94C8F980AEECBB6575B12AE614A249FC3E836F21), # noqa: E501 ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { target: Account(storage={0: 1, 1: 1}), diff --git a/tests/ported_static/stCallCodes/test_callcodecallcode_11.py b/tests/ported_static/stCallCodes/test_callcodecallcode_11.py index 8383a42511c..ff3da1cd960 100644 --- a/tests/ported_static/stCallCodes/test_callcodecallcode_11.py +++ b/tests/ported_static/stCallCodes/test_callcodecallcode_11.py @@ -4,15 +4,7 @@ Ported from: state_tests/stCallCodes/callcodecallcode_11Filler.json - -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values from the original filler (250k / 300k / 350k) were tuned to -the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the -innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei -of regular gas), and the inner CALL OoGs before the test's SSTORE -markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL -chain has headroom on Amsterdam; older forks are unaffected because -only the requested gas changes, the actual consumption is identical. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -25,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -40,18 +31,8 @@ def test_callcodecallcode_11( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """CALLCODE -> CALLCODE -> code, check parameters.""" - # EIP-8037 inner-CALL gas bumps (original gas values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state-gas - # spill into regular gas on Amsterdam). - inner_call_gas = 0x3D090 - outer_call_gas = 0x55730 - if fork.is_eip_enabled(8037): - inner_call_gas = 0xF4240 - outer_call_gas = 0x155CC0 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -84,7 +65,6 @@ def test_callcodecallcode_11( code=Op.SSTORE( key=0x1, value=Op.CALLCODE( - gas=inner_call_gas, address=addr_2, value=0x2, args_offset=0x0, @@ -103,7 +83,6 @@ def test_callcodecallcode_11( code=Op.SSTORE( key=0x0, value=Op.CALLCODE( - gas=outer_call_gas, address=addr, value=0x1, args_offset=0x0, @@ -117,12 +96,7 @@ def test_callcodecallcode_11( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { target: Account( diff --git a/tests/ported_static/stCallCodes/test_callcodecallcodecall_110.py b/tests/ported_static/stCallCodes/test_callcodecallcodecall_110.py index 5c057849d04..090803a99fd 100644 --- a/tests/ported_static/stCallCodes/test_callcodecallcodecall_110.py +++ b/tests/ported_static/stCallCodes/test_callcodecallcodecall_110.py @@ -4,15 +4,7 @@ Ported from: state_tests/stCallCodes/callcodecallcodecall_110Filler.json - -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values from the original filler (250k / 300k / 350k) were tuned to -the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the -innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei -of regular gas), and the inner CALL OoGs before the test's SSTORE -markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL -chain has headroom on Amsterdam; older forks are unaffected because -only the requested gas changes, the actual consumption is identical. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -25,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -40,20 +31,8 @@ def test_callcodecallcodecall_110( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """CALLCODE -> CALLCODE -> CALL -> code, check parameters.""" - # EIP-8037 inner-CALL gas bumps (original gas values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state-gas - # spill into regular gas on Amsterdam). - inner_call_gas = 0x3D090 - middle_call_gas = 0x493E0 - outer_call_gas = 0x55730 - if fork.is_eip_enabled(8037): - inner_call_gas = 0xF4240 - middle_call_gas = 0x124F80 - outer_call_gas = 0x155CC0 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -86,7 +65,6 @@ def test_callcodecallcodecall_110( code=Op.SSTORE( key=0x2, value=Op.CALL( - gas=inner_call_gas, address=addr_3, value=0x3, args_offset=0x0, @@ -105,7 +83,6 @@ def test_callcodecallcodecall_110( code=Op.SSTORE( key=0x1, value=Op.CALLCODE( - gas=middle_call_gas, address=addr_2, value=0x2, args_offset=0x0, @@ -124,7 +101,6 @@ def test_callcodecallcodecall_110( code=Op.SSTORE( key=0x0, value=Op.CALLCODE( - gas=outer_call_gas, address=addr, value=0x1, args_offset=0x0, @@ -138,12 +114,7 @@ def test_callcodecallcodecall_110( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { target: Account(storage={0: 1, 1: 1, 2: 1}), diff --git a/tests/ported_static/stCallCodes/test_callcodecallcodecall_110_suicide_end.py b/tests/ported_static/stCallCodes/test_callcodecallcodecall_110_suicide_end.py index fdeaf653e4d..a57ed1f17bc 100644 --- a/tests/ported_static/stCallCodes/test_callcodecallcodecall_110_suicide_end.py +++ b/tests/ported_static/stCallCodes/test_callcodecallcodecall_110_suicide_end.py @@ -4,9 +4,7 @@ Ported from: state_tests/stCallCodes/callcodecallcodecall_110_SuicideEndFiller.json -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. - +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,20 +31,8 @@ def test_callcodecallcodecall_110_suicide_end( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """CALLCODE -> CALLCODE -> (CALL -> code) (suicide) .""" - # EIP-8037 inner-CALL gas bumps: original values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state- - # gas spill into regular gas on Amsterdam. - outer_call_gas = 150000 - middle_call_gas = 100000 - inner_call_gas = 50000 - if fork.is_eip_enabled(8037): - outer_call_gas = 1000000 - middle_call_gas = 800000 - inner_call_gas = 100000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -74,7 +59,6 @@ def test_callcodecallcodecall_110_suicide_end( code=Op.SSTORE( key=0x0, value=Op.CALLCODE( - gas=outer_call_gas, address=0xEAF8C2AE0D01A880CEA4E1AA88DEF5EDD153D57B, value=0x0, args_offset=0x0, @@ -94,7 +78,6 @@ def test_callcodecallcodecall_110_suicide_end( code=Op.SSTORE( key=0x1, value=Op.CALLCODE( - gas=middle_call_gas, address=0xD957E143AD2C011BC6A2B142795F1A9BA70D0680, value=0x0, args_offset=0x0, @@ -114,7 +97,6 @@ def test_callcodecallcodecall_110_suicide_end( code=Op.SSTORE( key=0x2, value=Op.CALL( - gas=inner_call_gas, address=0x73B954EBC05BB0FF4A0F6A13A054D50AD1584099, value=0x0, args_offset=0x0, @@ -130,12 +112,7 @@ def test_callcodecallcodecall_110_suicide_end( address=Address(0xD957E143AD2C011BC6A2B142795F1A9BA70D0680), # noqa: E501 ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { addr: Account(balance=0xDE0B6B5FB6FE400), diff --git a/tests/ported_static/stCallCodes/test_callcodecallcodecallcode_111.py b/tests/ported_static/stCallCodes/test_callcodecallcodecallcode_111.py index 397d7fff51a..d2c205fdf9c 100644 --- a/tests/ported_static/stCallCodes/test_callcodecallcodecallcode_111.py +++ b/tests/ported_static/stCallCodes/test_callcodecallcodecallcode_111.py @@ -4,15 +4,7 @@ Ported from: state_tests/stCallCodes/callcodecallcodecallcode_111Filler.json - -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values from the original filler (250k / 300k / 350k) were tuned to -the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the -innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei -of regular gas), and the inner CALL OoGs before the test's SSTORE -markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL -chain has headroom on Amsterdam; older forks are unaffected because -only the requested gas changes, the actual consumption is identical. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -25,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -40,20 +31,8 @@ def test_callcodecallcodecallcode_111( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """CALLCODE -> CALLCODE -> CALLCODE -> code check parameter opcodes.""" - # EIP-8037 inner-CALL gas bumps (original gas values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state-gas - # spill into regular gas on Amsterdam). - inner_call_gas = 0x3D090 - middle_call_gas = 0x493E0 - outer_call_gas = 0x55730 - if fork.is_eip_enabled(8037): - inner_call_gas = 0xF4240 - middle_call_gas = 0x124F80 - outer_call_gas = 0x155CC0 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -86,7 +65,6 @@ def test_callcodecallcodecallcode_111( code=Op.SSTORE( key=0x2, value=Op.CALLCODE( - gas=inner_call_gas, address=addr_3, value=0x3, args_offset=0x0, @@ -105,7 +83,6 @@ def test_callcodecallcodecallcode_111( code=Op.SSTORE( key=0x1, value=Op.CALLCODE( - gas=middle_call_gas, address=addr_2, value=0x2, args_offset=0x0, @@ -124,7 +101,6 @@ def test_callcodecallcodecallcode_111( code=Op.SSTORE( key=0x0, value=Op.CALLCODE( - gas=outer_call_gas, address=addr, value=0x1, args_offset=0x0, @@ -138,12 +114,7 @@ def test_callcodecallcodecallcode_111( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { target: Account( diff --git a/tests/ported_static/stCallCodes/test_callcodecallcodecallcode_111_suicide_end.py b/tests/ported_static/stCallCodes/test_callcodecallcodecallcode_111_suicide_end.py index b07feeb4693..301771e779c 100644 --- a/tests/ported_static/stCallCodes/test_callcodecallcodecallcode_111_suicide_end.py +++ b/tests/ported_static/stCallCodes/test_callcodecallcodecallcode_111_suicide_end.py @@ -4,9 +4,7 @@ Ported from: state_tests/stCallCodes/callcodecallcodecallcode_111_SuicideEndFiller.json -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. - +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -36,20 +33,8 @@ def test_callcodecallcodecallcode_111_suicide_end( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """CALLCODE -> CALLCODE -> (CALLCODE -> code) suicide.""" - # EIP-8037 inner-CALL gas bumps: original values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state- - # gas spill into regular gas on Amsterdam. - outer_call_gas = 150000 - middle_call_gas = 100000 - inner_call_gas = 50000 - if fork.is_eip_enabled(8037): - outer_call_gas = 1000000 - middle_call_gas = 800000 - inner_call_gas = 100000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -76,7 +61,6 @@ def test_callcodecallcodecallcode_111_suicide_end( code=Op.SSTORE( key=0x0, value=Op.CALLCODE( - gas=outer_call_gas, address=0xEAF8C2AE0D01A880CEA4E1AA88DEF5EDD153D57B, value=0x0, args_offset=0x0, @@ -96,7 +80,6 @@ def test_callcodecallcodecallcode_111_suicide_end( code=Op.SSTORE( key=0x1, value=Op.CALLCODE( - gas=middle_call_gas, address=0x94C8F980AEECBB6575B12AE614A249FC3E836F21, value=0x0, args_offset=0x0, @@ -116,7 +99,6 @@ def test_callcodecallcodecallcode_111_suicide_end( code=Op.SSTORE( key=0x2, value=Op.CALLCODE( - gas=inner_call_gas, address=0x73B954EBC05BB0FF4A0F6A13A054D50AD1584099, value=0x0, args_offset=0x0, @@ -132,12 +114,7 @@ def test_callcodecallcodecallcode_111_suicide_end( address=Address(0x94C8F980AEECBB6575B12AE614A249FC3E836F21), # noqa: E501 ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { addr: Account(balance=0xDE0B6B5FB6FE400), diff --git a/tests/ported_static/stCallCreateCallCodeTest/test_create_init_fail_undefined_instruction.py b/tests/ported_static/stCallCreateCallCodeTest/test_create_init_fail_undefined_instruction.py index b3d1812c13a..1c0b0de14b6 100644 --- a/tests/ported_static/stCallCreateCallCodeTest/test_create_init_fail_undefined_instruction.py +++ b/tests/ported_static/stCallCreateCallCodeTest/test_create_init_fail_undefined_instruction.py @@ -3,10 +3,8 @@ Ported from: state_tests/stCallCreateCallCodeTest/createInitFailUndefinedInstructionFiller.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 SSTORE-set state-gas spill (target performs 3 fresh -SSTOREs); pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -36,14 +33,8 @@ def test_create_init_fail_undefined_instruction( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Create fails because init code has undefined opcode, trying to...""" - # EIP-8037 state-gas spill (3x fresh SSTORE-set) exceeds 900k tx_gas. - tx_gas_limit = 900000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 1_500_000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -108,13 +99,7 @@ def test_create_init_fail_undefined_instruction( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=tx_gas_limit, - value=0x186A0, - ) + tx = Transaction(sender=sender, to=target, data=Bytes(""), value=0x186A0) post = {target: Account(storage={2: 1})} diff --git a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcallcode_001.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcallcode_001.py index 17ac54a7922..9901d0615a1 100644 --- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcallcode_001.py +++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcallcode_001.py @@ -4,15 +4,7 @@ Ported from: state_tests/stCallDelegateCodesCallCodeHomestead/callcallcallcode_001Filler.json - -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values from the original filler (250k / 300k / 350k) were tuned to -the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the -innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei -of regular gas), and the inner CALL OoGs before the test's SSTORE -markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL -chain has headroom on Amsterdam; older forks are unaffected because -only the requested gas changes, the actual consumption is identical. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -25,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -42,20 +33,8 @@ def test_callcallcallcode_001( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_callcallcallcode_001.""" - # EIP-8037 inner-CALL gas bumps (original gas values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state-gas - # spill into regular gas on Amsterdam). - inner_call_gas = 0x3D090 - middle_call_gas = 0x493E0 - outer_call_gas = 0x55730 - if fork.is_eip_enabled(8037): - inner_call_gas = 0xF4240 - middle_call_gas = 0x124F80 - outer_call_gas = 0x155CC0 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -88,7 +67,6 @@ def test_callcallcallcode_001( code=Op.SSTORE( key=0x2, value=Op.DELEGATECALL( - gas=inner_call_gas, address=addr_3, args_offset=0x0, args_size=0x40, @@ -106,7 +84,6 @@ def test_callcallcallcode_001( code=Op.SSTORE( key=0x1, value=Op.CALLCODE( - gas=middle_call_gas, address=addr_2, value=0x2, args_offset=0x0, @@ -125,7 +102,6 @@ def test_callcallcallcode_001( code=Op.SSTORE( key=0x0, value=Op.CALLCODE( - gas=outer_call_gas, address=addr, value=0x1, args_offset=0x0, @@ -139,12 +115,7 @@ def test_callcallcallcode_001( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { target: Account( diff --git a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcode_01.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcode_01.py index 4ec0e0470db..93bd4443f4b 100644 --- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcode_01.py +++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcode_01.py @@ -4,15 +4,7 @@ Ported from: state_tests/stCallDelegateCodesCallCodeHomestead/callcallcode_01Filler.json - -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values from the original filler (250k / 300k / 350k) were tuned to -the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the -innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei -of regular gas), and the inner CALL OoGs before the test's SSTORE -markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL -chain has headroom on Amsterdam; older forks are unaffected because -only the requested gas changes, the actual consumption is identical. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -25,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -42,18 +33,8 @@ def test_callcallcode_01( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_callcallcode_01.""" - # EIP-8037 inner-CALL gas bumps (original gas values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state-gas - # spill into regular gas on Amsterdam). - inner_call_gas = 0x3D090 - outer_call_gas = 0x55730 - if fork.is_eip_enabled(8037): - inner_call_gas = 0xF4240 - outer_call_gas = 0x155CC0 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -86,7 +67,6 @@ def test_callcallcode_01( code=Op.SSTORE( key=0x1, value=Op.DELEGATECALL( - gas=inner_call_gas, address=addr_2, args_offset=0x0, args_size=0x40, @@ -104,7 +84,6 @@ def test_callcallcode_01( code=Op.SSTORE( key=0x0, value=Op.CALLCODE( - gas=outer_call_gas, address=addr, value=0x1, args_offset=0x0, @@ -118,12 +97,7 @@ def test_callcallcode_01( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { target: Account( diff --git a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcode_01_suicide_end.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcode_01_suicide_end.py index fd2c72ca7c1..5df67aa7d7a 100644 --- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcode_01_suicide_end.py +++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcode_01_suicide_end.py @@ -4,9 +4,7 @@ Ported from: state_tests/stCallDelegateCodesCallCodeHomestead/callcallcode_01_SuicideEndFiller.json -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. - +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -36,18 +33,8 @@ def test_callcallcode_01_suicide_end( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_callcallcode_01_suicide_end.""" - # EIP-8037 inner-CALL gas bumps: original values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state- - # gas spill into regular gas on Amsterdam. - outer_call_gas = 150000 - inner_call_gas = 50000 - if fork.is_eip_enabled(8037): - outer_call_gas = 1000000 - inner_call_gas = 100000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -74,7 +61,6 @@ def test_callcallcode_01_suicide_end( code=Op.SSTORE( key=0x0, value=Op.CALLCODE( - gas=outer_call_gas, address=0x1CCA6E93108EC94304AE5EB121D323E6C317FE7A, value=0x0, args_offset=0x0, @@ -94,7 +80,6 @@ def test_callcallcode_01_suicide_end( code=Op.SSTORE( key=0x1, value=Op.DELEGATECALL( - gas=inner_call_gas, address=0x703B936FD4D674F0FF5D6957F61097152F8781B8, args_offset=0x0, args_size=0x40, @@ -109,12 +94,7 @@ def test_callcallcode_01_suicide_end( address=Address(0x1CCA6E93108EC94304AE5EB121D323E6C317FE7A), # noqa: E501 ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { target: Account( diff --git a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcodecall_010.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcodecall_010.py index 58e1eef4424..602c913b926 100644 --- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcodecall_010.py +++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcodecall_010.py @@ -4,15 +4,7 @@ Ported from: state_tests/stCallDelegateCodesCallCodeHomestead/callcallcodecall_010Filler.json - -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values from the original filler (250k / 300k / 350k) were tuned to -the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the -innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei -of regular gas), and the inner CALL OoGs before the test's SSTORE -markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL -chain has headroom on Amsterdam; older forks are unaffected because -only the requested gas changes, the actual consumption is identical. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -25,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -42,20 +33,8 @@ def test_callcallcodecall_010( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_callcallcodecall_010.""" - # EIP-8037 inner-CALL gas bumps (original gas values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state-gas - # spill into regular gas on Amsterdam). - inner_call_gas = 0x3D090 - middle_call_gas = 0x493E0 - outer_call_gas = 0x55730 - if fork.is_eip_enabled(8037): - inner_call_gas = 0xF4240 - middle_call_gas = 0x124F80 - outer_call_gas = 0x155CC0 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -88,7 +67,6 @@ def test_callcallcodecall_010( code=Op.SSTORE( key=0x2, value=Op.CALLCODE( - gas=inner_call_gas, address=addr_3, value=0x2, args_offset=0x0, @@ -108,7 +86,6 @@ def test_callcallcodecall_010( code=Op.SSTORE( key=0x1, value=Op.DELEGATECALL( - gas=middle_call_gas, address=addr_2, args_offset=0x0, args_size=0x40, @@ -126,7 +103,6 @@ def test_callcallcodecall_010( code=Op.SSTORE( key=0x0, value=Op.CALLCODE( - gas=outer_call_gas, address=addr, value=0x1, args_offset=0x0, @@ -140,12 +116,7 @@ def test_callcallcodecall_010( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { target: Account( diff --git a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcodecall_010_suicide_end.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcodecall_010_suicide_end.py index 5ba88271301..4406ecf6aaf 100644 --- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcodecall_010_suicide_end.py +++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcodecall_010_suicide_end.py @@ -4,9 +4,7 @@ Ported from: state_tests/stCallDelegateCodesCallCodeHomestead/callcallcodecall_010_SuicideEndFiller.json -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. - +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -36,20 +33,8 @@ def test_callcallcodecall_010_suicide_end( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_callcallcodecall_010_suicide_end.""" - # EIP-8037 inner-CALL gas bumps: original values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state- - # gas spill into regular gas on Amsterdam. - outer_call_gas = 150000 - middle_call_gas = 100000 - inner_call_gas = 50000 - if fork.is_eip_enabled(8037): - outer_call_gas = 1000000 - middle_call_gas = 800000 - inner_call_gas = 100000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -76,7 +61,6 @@ def test_callcallcodecall_010_suicide_end( code=Op.SSTORE( key=0x0, value=Op.CALLCODE( - gas=outer_call_gas, address=0x2CAC1D43F00E8B40B63426AB460C7E8717EE6455, value=0x0, args_offset=0x0, @@ -96,7 +80,6 @@ def test_callcallcodecall_010_suicide_end( code=Op.SSTORE( key=0x1, value=Op.DELEGATECALL( - gas=middle_call_gas, address=0x94C8F980AEECBB6575B12AE614A249FC3E836F21, args_offset=0x0, args_size=0x40, @@ -115,7 +98,6 @@ def test_callcallcodecall_010_suicide_end( code=Op.SSTORE( key=0x2, value=Op.CALLCODE( - gas=inner_call_gas, address=0x73B954EBC05BB0FF4A0F6A13A054D50AD1584099, value=0x0, args_offset=0x0, @@ -131,12 +113,7 @@ def test_callcallcodecall_010_suicide_end( address=Address(0x94C8F980AEECBB6575B12AE614A249FC3E836F21), # noqa: E501 ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { target: Account(storage={0: 1, 1: 1, 2: 1, 3: 1}), diff --git a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcodecallcode_011.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcodecallcode_011.py index fb05c8d0604..6c913955bef 100644 --- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcodecallcode_011.py +++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcodecallcode_011.py @@ -4,15 +4,7 @@ Ported from: state_tests/stCallDelegateCodesCallCodeHomestead/callcallcodecallcode_011Filler.json - -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values from the original filler (250k / 300k / 350k) were tuned to -the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the -innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei -of regular gas), and the inner CALL OoGs before the test's SSTORE -markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL -chain has headroom on Amsterdam; older forks are unaffected because -only the requested gas changes, the actual consumption is identical. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -25,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -42,20 +33,8 @@ def test_callcallcodecallcode_011( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_callcallcodecallcode_011.""" - # EIP-8037 inner-CALL gas bumps (original gas values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state-gas - # spill into regular gas on Amsterdam). - inner_call_gas = 0x3D090 - middle_call_gas = 0x493E0 - outer_call_gas = 0x55730 - if fork.is_eip_enabled(8037): - inner_call_gas = 0xF4240 - middle_call_gas = 0x124F80 - outer_call_gas = 0x155CC0 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -88,7 +67,6 @@ def test_callcallcodecallcode_011( code=Op.SSTORE( key=0x2, value=Op.DELEGATECALL( - gas=inner_call_gas, address=addr_3, args_offset=0x0, args_size=0x40, @@ -105,7 +83,6 @@ def test_callcallcodecallcode_011( code=Op.SSTORE( key=0x1, value=Op.DELEGATECALL( - gas=middle_call_gas, address=addr_2, args_offset=0x0, args_size=0x40, @@ -122,7 +99,6 @@ def test_callcallcodecallcode_011( code=Op.SSTORE( key=0x0, value=Op.CALLCODE( - gas=outer_call_gas, address=addr, value=0x1, args_offset=0x0, @@ -136,12 +112,7 @@ def test_callcallcodecallcode_011( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { target: Account( diff --git a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcodecallcode_011_suicide_end.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcodecallcode_011_suicide_end.py index 842885bcc2a..bb07f396807 100644 --- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcodecallcode_011_suicide_end.py +++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcodecallcode_011_suicide_end.py @@ -4,9 +4,7 @@ Ported from: state_tests/stCallDelegateCodesCallCodeHomestead/callcallcodecallcode_011_SuicideEndFiller.json -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. - +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -36,20 +33,8 @@ def test_callcallcodecallcode_011_suicide_end( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_callcallcodecallcode_011_suicide_end.""" - # EIP-8037 inner-CALL gas bumps: original values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state- - # gas spill into regular gas on Amsterdam. - outer_call_gas = 150000 - middle_call_gas = 100000 - inner_call_gas = 50000 - if fork.is_eip_enabled(8037): - outer_call_gas = 1000000 - middle_call_gas = 800000 - inner_call_gas = 100000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -76,7 +61,6 @@ def test_callcallcodecallcode_011_suicide_end( code=Op.SSTORE( key=0x0, value=Op.CALLCODE( - gas=outer_call_gas, address=0x2CAC1D43F00E8B40B63426AB460C7E8717EE6455, value=0x0, args_offset=0x0, @@ -96,7 +80,6 @@ def test_callcallcodecallcode_011_suicide_end( code=Op.SSTORE( key=0x1, value=Op.DELEGATECALL( - gas=middle_call_gas, address=0xAC521409E2FA9526BFE6B827805783D2E307C4CE, args_offset=0x0, args_size=0x40, @@ -115,7 +98,6 @@ def test_callcallcodecallcode_011_suicide_end( code=Op.SSTORE( key=0x2, value=Op.DELEGATECALL( - gas=inner_call_gas, address=0x73B954EBC05BB0FF4A0F6A13A054D50AD1584099, args_offset=0x0, args_size=0x40, @@ -130,12 +112,7 @@ def test_callcallcodecallcode_011_suicide_end( address=Address(0xAC521409E2FA9526BFE6B827805783D2E307C4CE), # noqa: E501 ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { target: Account(storage={0: 1, 1: 1, 2: 1, 3: 1}), diff --git a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecall_10.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecall_10.py index 85607a610a4..1ff010fb617 100644 --- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecall_10.py +++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecall_10.py @@ -4,15 +4,7 @@ Ported from: state_tests/stCallDelegateCodesCallCodeHomestead/callcodecall_10Filler.json - -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values from the original filler (250k / 300k / 350k) were tuned to -the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the -innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei -of regular gas), and the inner CALL OoGs before the test's SSTORE -markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL -chain has headroom on Amsterdam; older forks are unaffected because -only the requested gas changes, the actual consumption is identical. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -25,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -42,18 +33,8 @@ def test_callcodecall_10( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_callcodecall_10.""" - # EIP-8037 inner-CALL gas bumps (original gas values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state-gas - # spill into regular gas on Amsterdam). - inner_call_gas = 0x3D090 - outer_call_gas = 0x55730 - if fork.is_eip_enabled(8037): - inner_call_gas = 0xF4240 - outer_call_gas = 0x155CC0 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -86,7 +67,6 @@ def test_callcodecall_10( code=Op.SSTORE( key=0x1, value=Op.CALLCODE( - gas=inner_call_gas, address=addr_2, value=0x2, args_offset=0x0, @@ -105,7 +85,6 @@ def test_callcodecall_10( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=outer_call_gas, address=addr, args_offset=0x0, args_size=0x40, @@ -118,12 +97,7 @@ def test_callcodecall_10( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { target: Account( diff --git a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecall_10_suicide_end.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecall_10_suicide_end.py index 66565fc3929..f3664b9da7e 100644 --- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecall_10_suicide_end.py +++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecall_10_suicide_end.py @@ -4,9 +4,7 @@ Ported from: state_tests/stCallDelegateCodesCallCodeHomestead/callcodecall_10_SuicideEndFiller.json -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. - +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -36,18 +33,8 @@ def test_callcodecall_10_suicide_end( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_callcodecall_10_suicide_end.""" - # EIP-8037 inner-CALL gas bumps: original values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state- - # gas spill into regular gas on Amsterdam. - outer_call_gas = 150000 - inner_call_gas = 50000 - if fork.is_eip_enabled(8037): - outer_call_gas = 1000000 - inner_call_gas = 100000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -74,7 +61,6 @@ def test_callcodecall_10_suicide_end( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=outer_call_gas, address=0x799DA5A3C983A22F9C430DE1BF99134EE561E856, args_offset=0x0, args_size=0x40, @@ -93,7 +79,6 @@ def test_callcodecall_10_suicide_end( code=Op.SSTORE( key=0x1, value=Op.CALLCODE( - gas=inner_call_gas, address=0x703B936FD4D674F0FF5D6957F61097152F8781B8, value=0x0, args_offset=0x0, @@ -109,12 +94,7 @@ def test_callcodecall_10_suicide_end( address=Address(0x799DA5A3C983A22F9C430DE1BF99134EE561E856), # noqa: E501 ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { target: Account(storage={0: 1, 1: 1, 2: 1}), diff --git a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcall_100.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcall_100.py index b6072d270e1..a3a73dc9710 100644 --- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcall_100.py +++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcall_100.py @@ -4,15 +4,7 @@ Ported from: state_tests/stCallDelegateCodesCallCodeHomestead/callcodecallcall_100Filler.json - -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values from the original filler (250k / 300k / 350k) were tuned to -the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the -innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei -of regular gas), and the inner CALL OoGs before the test's SSTORE -markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL -chain has headroom on Amsterdam; older forks are unaffected because -only the requested gas changes, the actual consumption is identical. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -25,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -42,20 +33,8 @@ def test_callcodecallcall_100( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_callcodecallcall_100.""" - # EIP-8037 inner-CALL gas bumps (original gas values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state-gas - # spill into regular gas on Amsterdam). - inner_call_gas = 0x3D090 - middle_call_gas = 0x493E0 - outer_call_gas = 0x55730 - if fork.is_eip_enabled(8037): - inner_call_gas = 0xF4240 - middle_call_gas = 0x124F80 - outer_call_gas = 0x155CC0 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -88,7 +67,6 @@ def test_callcodecallcall_100( code=Op.SSTORE( key=0x2, value=Op.CALLCODE( - gas=inner_call_gas, address=addr_3, value=0x2, args_offset=0x0, @@ -107,7 +85,6 @@ def test_callcodecallcall_100( code=Op.SSTORE( key=0x1, value=Op.CALLCODE( - gas=middle_call_gas, address=addr_2, value=0x1, args_offset=0x0, @@ -127,7 +104,6 @@ def test_callcodecallcall_100( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=outer_call_gas, address=addr, args_offset=0x0, args_size=0x40, @@ -140,12 +116,7 @@ def test_callcodecallcall_100( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { target: Account( diff --git a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcall_100_suicide_end.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcall_100_suicide_end.py index 84802cd5c8c..e68f4bb1f1d 100644 --- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcall_100_suicide_end.py +++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcall_100_suicide_end.py @@ -4,9 +4,7 @@ Ported from: state_tests/stCallDelegateCodesCallCodeHomestead/callcodecallcall_100_SuicideEndFiller.json -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. - +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -36,20 +33,8 @@ def test_callcodecallcall_100_suicide_end( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_callcodecallcall_100_suicide_end.""" - # EIP-8037 inner-CALL gas bumps: original values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state- - # gas spill into regular gas on Amsterdam. - outer_call_gas = 150000 - middle_call_gas = 100000 - inner_call_gas = 50000 - if fork.is_eip_enabled(8037): - outer_call_gas = 1000000 - middle_call_gas = 800000 - inner_call_gas = 100000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -76,7 +61,6 @@ def test_callcodecallcall_100_suicide_end( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=outer_call_gas, address=0xEAF8C2AE0D01A880CEA4E1AA88DEF5EDD153D57B, args_offset=0x0, args_size=0x40, @@ -95,7 +79,6 @@ def test_callcodecallcall_100_suicide_end( code=Op.SSTORE( key=0x1, value=Op.CALLCODE( - gas=middle_call_gas, address=0x94C8F980AEECBB6575B12AE614A249FC3E836F21, value=0x0, args_offset=0x0, @@ -115,7 +98,6 @@ def test_callcodecallcall_100_suicide_end( code=Op.SSTORE( key=0x2, value=Op.CALLCODE( - gas=inner_call_gas, address=0x73B954EBC05BB0FF4A0F6A13A054D50AD1584099, value=0x0, args_offset=0x0, @@ -131,12 +113,7 @@ def test_callcodecallcall_100_suicide_end( address=Address(0x94C8F980AEECBB6575B12AE614A249FC3E836F21), # noqa: E501 ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { target: Account(storage={0: 1, 1: 1, 2: 1, 3: 1}), diff --git a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcallcode_101.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcallcode_101.py index f85f6c39c21..de65e8b34ca 100644 --- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcallcode_101.py +++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcallcode_101.py @@ -4,15 +4,7 @@ Ported from: state_tests/stCallDelegateCodesCallCodeHomestead/callcodecallcallcode_101Filler.json - -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values from the original filler (250k / 300k / 350k) were tuned to -the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the -innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei -of regular gas), and the inner CALL OoGs before the test's SSTORE -markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL -chain has headroom on Amsterdam; older forks are unaffected because -only the requested gas changes, the actual consumption is identical. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -25,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -42,20 +33,8 @@ def test_callcodecallcallcode_101( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_callcodecallcallcode_101.""" - # EIP-8037 inner-CALL gas bumps (original gas values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state-gas - # spill into regular gas on Amsterdam). - inner_call_gas = 0x3D090 - middle_call_gas = 0x493E0 - outer_call_gas = 0x55730 - if fork.is_eip_enabled(8037): - inner_call_gas = 0xF4240 - middle_call_gas = 0x124F80 - outer_call_gas = 0x155CC0 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -88,7 +67,6 @@ def test_callcodecallcallcode_101( code=Op.SSTORE( key=0x2, value=Op.DELEGATECALL( - gas=inner_call_gas, address=addr_3, args_offset=0x0, args_size=0x40, @@ -107,7 +85,6 @@ def test_callcodecallcallcode_101( code=Op.SSTORE( key=0x1, value=Op.CALLCODE( - gas=middle_call_gas, address=addr_2, value=0x1, args_offset=0x0, @@ -127,7 +104,6 @@ def test_callcodecallcallcode_101( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=outer_call_gas, address=addr, args_offset=0x0, args_size=0x40, @@ -140,12 +116,7 @@ def test_callcodecallcallcode_101( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { target: Account( diff --git a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcallcode_101_suicide_end.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcallcode_101_suicide_end.py index 4a20cbfa33f..f5f4d5d957b 100644 --- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcallcode_101_suicide_end.py +++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcallcode_101_suicide_end.py @@ -4,9 +4,7 @@ Ported from: state_tests/stCallDelegateCodesCallCodeHomestead/callcodecallcallcode_101_SuicideEndFiller.json -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. - +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -36,20 +33,8 @@ def test_callcodecallcallcode_101_suicide_end( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_callcodecallcallcode_101_suicide_end.""" - # EIP-8037 inner-CALL gas bumps: original values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state- - # gas spill into regular gas on Amsterdam. - outer_call_gas = 150000 - middle_call_gas = 100000 - inner_call_gas = 50000 - if fork.is_eip_enabled(8037): - outer_call_gas = 1000000 - middle_call_gas = 800000 - inner_call_gas = 100000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -76,7 +61,6 @@ def test_callcodecallcallcode_101_suicide_end( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=outer_call_gas, address=0xEAF8C2AE0D01A880CEA4E1AA88DEF5EDD153D57B, args_offset=0x0, args_size=0x40, @@ -95,7 +79,6 @@ def test_callcodecallcallcode_101_suicide_end( code=Op.SSTORE( key=0x1, value=Op.CALLCODE( - gas=middle_call_gas, address=0xAC521409E2FA9526BFE6B827805783D2E307C4CE, value=0x0, args_offset=0x0, @@ -115,7 +98,6 @@ def test_callcodecallcallcode_101_suicide_end( code=Op.SSTORE( key=0x2, value=Op.DELEGATECALL( - gas=inner_call_gas, address=0x73B954EBC05BB0FF4A0F6A13A054D50AD1584099, args_offset=0x0, args_size=0x40, @@ -130,12 +112,7 @@ def test_callcodecallcallcode_101_suicide_end( address=Address(0xAC521409E2FA9526BFE6B827805783D2E307C4CE), # noqa: E501 ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { target: Account(storage={0: 1, 1: 1, 2: 1, 3: 1}), diff --git a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcode_11.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcode_11.py index 9e2c24e09e6..685d387ac85 100644 --- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcode_11.py +++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcode_11.py @@ -4,15 +4,7 @@ Ported from: state_tests/stCallDelegateCodesCallCodeHomestead/callcodecallcode_11Filler.json - -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values from the original filler (250k / 300k / 350k) were tuned to -the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the -innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei -of regular gas), and the inner CALL OoGs before the test's SSTORE -markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL -chain has headroom on Amsterdam; older forks are unaffected because -only the requested gas changes, the actual consumption is identical. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -25,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -42,18 +33,8 @@ def test_callcodecallcode_11( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_callcodecallcode_11.""" - # EIP-8037 inner-CALL gas bumps (original gas values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state-gas - # spill into regular gas on Amsterdam). - inner_call_gas = 0x3D090 - outer_call_gas = 0x55730 - if fork.is_eip_enabled(8037): - inner_call_gas = 0xF4240 - outer_call_gas = 0x155CC0 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -86,7 +67,6 @@ def test_callcodecallcode_11( code=Op.SSTORE( key=0x1, value=Op.DELEGATECALL( - gas=inner_call_gas, address=addr_2, args_offset=0x0, args_size=0x40, @@ -103,7 +83,6 @@ def test_callcodecallcode_11( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=outer_call_gas, address=addr, args_offset=0x0, args_size=0x40, @@ -116,12 +95,7 @@ def test_callcodecallcode_11( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { target: Account( diff --git a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcode_11_suicide_end.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcode_11_suicide_end.py index b1adb4e9e21..5b18d769850 100644 --- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcode_11_suicide_end.py +++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcode_11_suicide_end.py @@ -4,9 +4,7 @@ Ported from: state_tests/stCallDelegateCodesCallCodeHomestead/callcodecallcode_11_SuicideEndFiller.json -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. - +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -36,18 +33,8 @@ def test_callcodecallcode_11_suicide_end( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_callcodecallcode_11_suicide_end.""" - # EIP-8037 inner-CALL gas bumps: original values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state- - # gas spill into regular gas on Amsterdam. - outer_call_gas = 150000 - inner_call_gas = 50000 - if fork.is_eip_enabled(8037): - outer_call_gas = 1000000 - inner_call_gas = 100000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -74,7 +61,6 @@ def test_callcodecallcode_11_suicide_end( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=outer_call_gas, address=0x1CCA6E93108EC94304AE5EB121D323E6C317FE7A, args_offset=0x0, args_size=0x40, @@ -93,7 +79,6 @@ def test_callcodecallcode_11_suicide_end( code=Op.SSTORE( key=0x1, value=Op.DELEGATECALL( - gas=inner_call_gas, address=0x703B936FD4D674F0FF5D6957F61097152F8781B8, args_offset=0x0, args_size=0x40, @@ -108,12 +93,7 @@ def test_callcodecallcode_11_suicide_end( address=Address(0x1CCA6E93108EC94304AE5EB121D323E6C317FE7A), # noqa: E501 ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { target: Account(storage={0: 1, 1: 1, 2: 1}), diff --git a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcodecall_110.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcodecall_110.py index 32061749e58..cbdda4dcb84 100644 --- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcodecall_110.py +++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcodecall_110.py @@ -4,15 +4,7 @@ Ported from: state_tests/stCallDelegateCodesCallCodeHomestead/callcodecallcodecall_110Filler.json - -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values from the original filler (250k / 300k / 350k) were tuned to -the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the -innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei -of regular gas), and the inner CALL OoGs before the test's SSTORE -markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL -chain has headroom on Amsterdam; older forks are unaffected because -only the requested gas changes, the actual consumption is identical. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -25,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -42,20 +33,8 @@ def test_callcodecallcodecall_110( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_callcodecallcodecall_110.""" - # EIP-8037 inner-CALL gas bumps (original gas values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state-gas - # spill into regular gas on Amsterdam). - inner_call_gas = 0x3D090 - middle_call_gas = 0x493E0 - outer_call_gas = 0x55730 - if fork.is_eip_enabled(8037): - inner_call_gas = 0xF4240 - middle_call_gas = 0x124F80 - outer_call_gas = 0x155CC0 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -88,7 +67,6 @@ def test_callcodecallcodecall_110( code=Op.SSTORE( key=0x2, value=Op.CALLCODE( - gas=inner_call_gas, address=addr_3, value=0x1, args_offset=0x0, @@ -108,7 +86,6 @@ def test_callcodecallcodecall_110( code=Op.SSTORE( key=0x1, value=Op.DELEGATECALL( - gas=middle_call_gas, address=addr_2, args_offset=0x0, args_size=0x40, @@ -127,7 +104,6 @@ def test_callcodecallcodecall_110( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=outer_call_gas, address=addr, args_offset=0x0, args_size=0x40, @@ -140,12 +116,7 @@ def test_callcodecallcodecall_110( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { target: Account( diff --git a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcodecall_110_suicide_end.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcodecall_110_suicide_end.py index 2323c57ec3a..9a97900067d 100644 --- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcodecall_110_suicide_end.py +++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcodecall_110_suicide_end.py @@ -4,9 +4,7 @@ Ported from: state_tests/stCallDelegateCodesCallCodeHomestead/callcodecallcodecall_110_SuicideEndFiller.json -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. - +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -36,20 +33,8 @@ def test_callcodecallcodecall_110_suicide_end( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_callcodecallcodecall_110_suicide_end.""" - # EIP-8037 inner-CALL gas bumps: original values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state- - # gas spill into regular gas on Amsterdam. - outer_call_gas = 150000 - middle_call_gas = 100000 - inner_call_gas = 50000 - if fork.is_eip_enabled(8037): - outer_call_gas = 1000000 - middle_call_gas = 800000 - inner_call_gas = 100000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -76,7 +61,6 @@ def test_callcodecallcodecall_110_suicide_end( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=outer_call_gas, address=0x2CAC1D43F00E8B40B63426AB460C7E8717EE6455, args_offset=0x0, args_size=0x40, @@ -95,7 +79,6 @@ def test_callcodecallcodecall_110_suicide_end( code=Op.SSTORE( key=0x1, value=Op.DELEGATECALL( - gas=middle_call_gas, address=0x94C8F980AEECBB6575B12AE614A249FC3E836F21, args_offset=0x0, args_size=0x40, @@ -114,7 +97,6 @@ def test_callcodecallcodecall_110_suicide_end( code=Op.SSTORE( key=0x2, value=Op.CALLCODE( - gas=inner_call_gas, address=0x73B954EBC05BB0FF4A0F6A13A054D50AD1584099, value=0x0, args_offset=0x0, @@ -130,12 +112,7 @@ def test_callcodecallcodecall_110_suicide_end( address=Address(0x94C8F980AEECBB6575B12AE614A249FC3E836F21), # noqa: E501 ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { target: Account(storage={0: 1, 1: 1, 2: 1, 3: 1}), diff --git a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcodecallcode_111.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcodecallcode_111.py index 284d284adeb..7954b4dc0cc 100644 --- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcodecallcode_111.py +++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcodecallcode_111.py @@ -4,15 +4,7 @@ Ported from: state_tests/stCallDelegateCodesCallCodeHomestead/callcodecallcodecallcode_111Filler.json - -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values from the original filler (250k / 300k / 350k) were tuned to -the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the -innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei -of regular gas), and the inner CALL OoGs before the test's SSTORE -markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL -chain has headroom on Amsterdam; older forks are unaffected because -only the requested gas changes, the actual consumption is identical. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -25,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -42,20 +33,8 @@ def test_callcodecallcodecallcode_111( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_callcodecallcodecallcode_111.""" - # EIP-8037 inner-CALL gas bumps (original gas values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state-gas - # spill into regular gas on Amsterdam). - inner_call_gas = 0x3D090 - middle_call_gas = 0x493E0 - outer_call_gas = 0x55730 - if fork.is_eip_enabled(8037): - inner_call_gas = 0xF4240 - middle_call_gas = 0x124F80 - outer_call_gas = 0x155CC0 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -88,7 +67,6 @@ def test_callcodecallcodecallcode_111( code=Op.SSTORE( key=0x2, value=Op.DELEGATECALL( - gas=inner_call_gas, address=addr_3, args_offset=0x0, args_size=0x40, @@ -105,7 +83,6 @@ def test_callcodecallcodecallcode_111( code=Op.SSTORE( key=0x1, value=Op.DELEGATECALL( - gas=middle_call_gas, address=addr_2, args_offset=0x0, args_size=0x40, @@ -122,7 +99,6 @@ def test_callcodecallcodecallcode_111( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=outer_call_gas, address=addr, args_offset=0x0, args_size=0x40, @@ -135,12 +111,7 @@ def test_callcodecallcodecallcode_111( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { target: Account( diff --git a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcodecallcode_111_suicide_end.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcodecallcode_111_suicide_end.py index d47d3c95ed5..c9afc938d33 100644 --- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcodecallcode_111_suicide_end.py +++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcodecallcodecallcode_111_suicide_end.py @@ -4,15 +4,7 @@ Ported from: state_tests/stCallDelegateCodesCallCodeHomestead/callcodecallcodecallcode_111_SuicideEndFiller.json - -@manually-enhanced: Do not overwrite. Hardcoded inner-CALL gas values -from the original filler (100k / 800k / 150k / 50k) were tuned to the -pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the inner -callee adds the EIP-8037 per-storage state-gas (37 568 wei of -regular gas), and the inner CALL OoGs before the test's SSTORE -markers fire. Bumped uniformly with extra headroom; older forks are -unaffected because only the requested gas changes, the actual -consumption is identical. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -25,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -42,20 +33,8 @@ def test_callcodecallcodecallcode_111_suicide_end( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_callcodecallcodecallcode_111_suicide_end.""" - # EIP-8037 inner-CALL gas bumps (original gas values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state-gas - # spill into regular gas on Amsterdam). - inner_call_gas = 0x186A0 - middle_call_gas = 0x249F0 - inner_call_gas_b = 0xC350 - if fork.is_eip_enabled(8037): - inner_call_gas = 0x1E8480 - middle_call_gas = 0x1E8480 - inner_call_gas_b = 0x1E8480 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -82,7 +61,6 @@ def test_callcodecallcodecallcode_111_suicide_end( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=middle_call_gas, address=0x9CFF7A3C9C90A301C47982DC2C4399C93700F0FD, args_offset=0x0, args_size=0x40, @@ -101,7 +79,6 @@ def test_callcodecallcodecallcode_111_suicide_end( code=Op.SSTORE( key=0x1, value=Op.CALL( - gas=inner_call_gas, address=0xB207980945728D64A3C9F905932314C8F130EE38, value=0x1, args_offset=0x0, @@ -121,7 +98,6 @@ def test_callcodecallcodecallcode_111_suicide_end( code=Op.SSTORE( key=0x2, value=Op.CALLCODE( - gas=inner_call_gas_b, address=0x73B954EBC05BB0FF4A0F6A13A054D50AD1584099, value=0x2, args_offset=0x0, @@ -137,12 +113,7 @@ def test_callcodecallcodecallcode_111_suicide_end( address=Address(0xB207980945728D64A3C9F905932314C8F130EE38), # noqa: E501 ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { target: Account(storage={0: 1, 1: 1, 2: 0, 3: 0}), diff --git a/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcallcode_001.py b/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcallcode_001.py index c8481280159..a2e112bfa53 100644 --- a/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcallcode_001.py +++ b/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcallcode_001.py @@ -4,15 +4,7 @@ Ported from: state_tests/stCallDelegateCodesHomestead/callcallcallcode_001Filler.json - -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values from the original filler (250k / 300k / 350k) were tuned to -the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the -innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei -of regular gas), and the inner CALL OoGs before the test's SSTORE -markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL -chain has headroom on Amsterdam; older forks are unaffected because -only the requested gas changes, the actual consumption is identical. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -25,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -42,20 +33,8 @@ def test_callcallcallcode_001( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_callcallcallcode_001.""" - # EIP-8037 inner-CALL gas bumps (original gas values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state-gas - # spill into regular gas on Amsterdam). - inner_call_gas = 0x3D090 - middle_call_gas = 0x493E0 - outer_call_gas = 0x55730 - if fork.is_eip_enabled(8037): - inner_call_gas = 0xF4240 - middle_call_gas = 0x124F80 - outer_call_gas = 0x155CC0 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -88,7 +67,6 @@ def test_callcallcallcode_001( code=Op.SSTORE( key=0x2, value=Op.DELEGATECALL( - gas=inner_call_gas, address=addr_3, args_offset=0x0, args_size=0x40, @@ -106,7 +84,6 @@ def test_callcallcallcode_001( code=Op.SSTORE( key=0x1, value=Op.CALL( - gas=middle_call_gas, address=addr_2, value=0x2, args_offset=0x0, @@ -125,7 +102,6 @@ def test_callcallcallcode_001( code=Op.SSTORE( key=0x0, value=Op.CALL( - gas=outer_call_gas, address=addr, value=0x1, args_offset=0x0, @@ -139,12 +115,7 @@ def test_callcallcallcode_001( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { target: Account(storage={0: 1, 1: 0, 3: 0, 4: 0}), diff --git a/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcallcode_001_suicide_end.py b/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcallcode_001_suicide_end.py index bf50302cc14..9f4aa38224e 100644 --- a/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcallcode_001_suicide_end.py +++ b/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcallcode_001_suicide_end.py @@ -4,9 +4,7 @@ Ported from: state_tests/stCallDelegateCodesHomestead/callcallcallcode_001_SuicideEndFiller.json -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. - +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -36,20 +33,8 @@ def test_callcallcallcode_001_suicide_end( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_callcallcallcode_001_suicide_end.""" - # EIP-8037 inner-CALL gas bumps: original values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state- - # gas spill into regular gas on Amsterdam. - outer_call_gas = 150000 - middle_call_gas = 100000 - inner_call_gas = 50000 - if fork.is_eip_enabled(8037): - outer_call_gas = 1000000 - middle_call_gas = 800000 - inner_call_gas = 100000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -76,7 +61,6 @@ def test_callcallcallcode_001_suicide_end( code=Op.SSTORE( key=0x0, value=Op.CALL( - gas=outer_call_gas, address=0x77B749FFFF7EC61D31C79ED104F230A7959B2879, value=0x0, args_offset=0x0, @@ -96,7 +80,6 @@ def test_callcallcallcode_001_suicide_end( code=Op.SSTORE( key=0x1, value=Op.CALL( - gas=middle_call_gas, address=0xAC521409E2FA9526BFE6B827805783D2E307C4CE, value=0x0, args_offset=0x0, @@ -116,7 +99,6 @@ def test_callcallcallcode_001_suicide_end( code=Op.SSTORE( key=0x2, value=Op.DELEGATECALL( - gas=inner_call_gas, address=0x73B954EBC05BB0FF4A0F6A13A054D50AD1584099, args_offset=0x0, args_size=0x40, @@ -131,12 +113,7 @@ def test_callcallcallcode_001_suicide_end( address=Address(0xAC521409E2FA9526BFE6B827805783D2E307C4CE), # noqa: E501 ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { target: Account(storage={0: 1, 2: 0}), diff --git a/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcode_01.py b/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcode_01.py index 539d0d35cdf..f6f9404e6c4 100644 --- a/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcode_01.py +++ b/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcode_01.py @@ -4,15 +4,7 @@ Ported from: state_tests/stCallDelegateCodesHomestead/callcallcode_01Filler.json - -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values from the original filler (250k / 300k / 350k) were tuned to -the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the -innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei -of regular gas), and the inner CALL OoGs before the test's SSTORE -markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL -chain has headroom on Amsterdam; older forks are unaffected because -only the requested gas changes, the actual consumption is identical. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -25,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -40,18 +31,8 @@ def test_callcallcode_01( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_callcallcode_01.""" - # EIP-8037 inner-CALL gas bumps (original gas values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state-gas - # spill into regular gas on Amsterdam). - inner_call_gas = 0x3D090 - outer_call_gas = 0x55730 - if fork.is_eip_enabled(8037): - inner_call_gas = 0xF4240 - outer_call_gas = 0x155CC0 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -84,7 +65,6 @@ def test_callcallcode_01( code=Op.SSTORE( key=0x1, value=Op.DELEGATECALL( - gas=inner_call_gas, address=addr_2, args_offset=0x0, args_size=0x40, @@ -101,7 +81,6 @@ def test_callcallcode_01( code=Op.SSTORE( key=0x0, value=Op.CALL( - gas=outer_call_gas, address=addr, value=0x1, args_offset=0x0, @@ -115,12 +94,7 @@ def test_callcallcode_01( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { target: Account(storage={0: 1}), diff --git a/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcode_01_suicide_end.py b/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcode_01_suicide_end.py index 4afdbbe5775..3090bd24b68 100644 --- a/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcode_01_suicide_end.py +++ b/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcode_01_suicide_end.py @@ -4,9 +4,7 @@ Ported from: state_tests/stCallDelegateCodesHomestead/callcallcode_01_SuicideEndFiller.json -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. - +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -36,18 +33,8 @@ def test_callcallcode_01_suicide_end( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_callcallcode_01_suicide_end.""" - # EIP-8037 inner-CALL gas bumps: original values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state- - # gas spill into regular gas on Amsterdam. - outer_call_gas = 150000 - inner_call_gas = 50000 - if fork.is_eip_enabled(8037): - outer_call_gas = 1000000 - inner_call_gas = 100000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -74,7 +61,6 @@ def test_callcallcode_01_suicide_end( code=Op.SSTORE( key=0x0, value=Op.CALL( - gas=outer_call_gas, address=0x1CCA6E93108EC94304AE5EB121D323E6C317FE7A, value=0x0, args_offset=0x0, @@ -94,7 +80,6 @@ def test_callcallcode_01_suicide_end( code=Op.SSTORE( key=0x1, value=Op.DELEGATECALL( - gas=inner_call_gas, address=0x703B936FD4D674F0FF5D6957F61097152F8781B8, args_offset=0x0, args_size=0x40, @@ -109,12 +94,7 @@ def test_callcallcode_01_suicide_end( address=Address(0x1CCA6E93108EC94304AE5EB121D323E6C317FE7A), # noqa: E501 ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { target: Account(storage={0: 1, 2: 0}), diff --git a/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcodecall_010.py b/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcodecall_010.py index e62a62820a2..b56b71e4de5 100644 --- a/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcodecall_010.py +++ b/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcodecall_010.py @@ -4,15 +4,7 @@ Ported from: state_tests/stCallDelegateCodesHomestead/callcallcodecall_010Filler.json - -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values from the original filler (250k / 300k / 350k) were tuned to -the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the -innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei -of regular gas), and the inner CALL OoGs before the test's SSTORE -markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL -chain has headroom on Amsterdam; older forks are unaffected because -only the requested gas changes, the actual consumption is identical. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -25,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -42,20 +33,8 @@ def test_callcallcodecall_010( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_callcallcodecall_010.""" - # EIP-8037 inner-CALL gas bumps (original gas values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state-gas - # spill into regular gas on Amsterdam). - inner_call_gas = 0x3D090 - middle_call_gas = 0x493E0 - outer_call_gas = 0x55730 - if fork.is_eip_enabled(8037): - inner_call_gas = 0xF4240 - middle_call_gas = 0x124F80 - outer_call_gas = 0x155CC0 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -88,7 +67,6 @@ def test_callcallcodecall_010( code=Op.SSTORE( key=0x2, value=Op.CALL( - gas=inner_call_gas, address=addr_3, value=0x2, args_offset=0x0, @@ -108,7 +86,6 @@ def test_callcallcodecall_010( code=Op.SSTORE( key=0x1, value=Op.DELEGATECALL( - gas=middle_call_gas, address=addr_2, args_offset=0x0, args_size=0x40, @@ -126,7 +103,6 @@ def test_callcallcodecall_010( code=Op.SSTORE( key=0x0, value=Op.CALL( - gas=outer_call_gas, address=addr, value=0x1, args_offset=0x0, @@ -140,12 +116,7 @@ def test_callcallcodecall_010( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { target: Account(storage={0: 1, 2: 0}), diff --git a/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcodecall_010_suicide_end.py b/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcodecall_010_suicide_end.py index 32f1487a627..5c4b1b42e0d 100644 --- a/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcodecall_010_suicide_end.py +++ b/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcodecall_010_suicide_end.py @@ -4,9 +4,7 @@ Ported from: state_tests/stCallDelegateCodesHomestead/callcallcodecall_010_SuicideEndFiller.json -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. - +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -36,20 +33,8 @@ def test_callcallcodecall_010_suicide_end( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_callcallcodecall_010_suicide_end.""" - # EIP-8037 inner-CALL gas bumps: original values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state- - # gas spill into regular gas on Amsterdam. - outer_call_gas = 150000 - middle_call_gas = 100000 - inner_call_gas = 50000 - if fork.is_eip_enabled(8037): - outer_call_gas = 1000000 - middle_call_gas = 800000 - inner_call_gas = 100000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -76,7 +61,6 @@ def test_callcallcodecall_010_suicide_end( code=Op.SSTORE( key=0x0, value=Op.CALL( - gas=outer_call_gas, address=0x2CAC1D43F00E8B40B63426AB460C7E8717EE6455, value=0x0, args_offset=0x0, @@ -96,7 +80,6 @@ def test_callcallcodecall_010_suicide_end( code=Op.SSTORE( key=0x1, value=Op.DELEGATECALL( - gas=middle_call_gas, address=0xD957E143AD2C011BC6A2B142795F1A9BA70D0680, args_offset=0x0, args_size=0x40, @@ -115,7 +98,6 @@ def test_callcallcodecall_010_suicide_end( code=Op.SSTORE( key=0x2, value=Op.CALL( - gas=inner_call_gas, address=0x73B954EBC05BB0FF4A0F6A13A054D50AD1584099, value=0x0, args_offset=0x0, @@ -131,12 +113,7 @@ def test_callcallcodecall_010_suicide_end( address=Address(0xD957E143AD2C011BC6A2B142795F1A9BA70D0680), # noqa: E501 ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { target: Account(storage={0: 1, 1: 0, 3: 0}), diff --git a/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcodecallcode_011.py b/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcodecallcode_011.py index d41418e40d3..7e85a2280f5 100644 --- a/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcodecallcode_011.py +++ b/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcodecallcode_011.py @@ -4,15 +4,7 @@ Ported from: state_tests/stCallDelegateCodesHomestead/callcallcodecallcode_011Filler.json - -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values from the original filler (250k / 300k / 350k) were tuned to -the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the -innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei -of regular gas), and the inner CALL OoGs before the test's SSTORE -markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL -chain has headroom on Amsterdam; older forks are unaffected because -only the requested gas changes, the actual consumption is identical. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -25,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -42,20 +33,8 @@ def test_callcallcodecallcode_011( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_callcallcodecallcode_011.""" - # EIP-8037 inner-CALL gas bumps (original gas values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state-gas - # spill into regular gas on Amsterdam). - inner_call_gas = 0x3D090 - middle_call_gas = 0x493E0 - outer_call_gas = 0x55730 - if fork.is_eip_enabled(8037): - inner_call_gas = 0xF4240 - middle_call_gas = 0x124F80 - outer_call_gas = 0x155CC0 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -88,7 +67,6 @@ def test_callcallcodecallcode_011( code=Op.SSTORE( key=0x2, value=Op.DELEGATECALL( - gas=inner_call_gas, address=addr_3, args_offset=0x0, args_size=0x40, @@ -106,7 +84,6 @@ def test_callcallcodecallcode_011( code=Op.SSTORE( key=0x1, value=Op.DELEGATECALL( - gas=middle_call_gas, address=addr_2, args_offset=0x0, args_size=0x40, @@ -124,7 +101,6 @@ def test_callcallcodecallcode_011( code=Op.SSTORE( key=0x0, value=Op.CALL( - gas=outer_call_gas, address=addr, value=0x1, args_offset=0x0, @@ -138,12 +114,7 @@ def test_callcallcodecallcode_011( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { target: Account(storage={0: 1, 2: 0, 3: 0, 4: 0}), diff --git a/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcodecallcode_011_suicide_end.py b/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcodecallcode_011_suicide_end.py index a0b5607e396..67dfb5017ee 100644 --- a/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcodecallcode_011_suicide_end.py +++ b/tests/ported_static/stCallDelegateCodesHomestead/test_callcallcodecallcode_011_suicide_end.py @@ -4,9 +4,7 @@ Ported from: state_tests/stCallDelegateCodesHomestead/callcallcodecallcode_011_SuicideEndFiller.json -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. - +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -36,20 +33,8 @@ def test_callcallcodecallcode_011_suicide_end( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_callcallcodecallcode_011_suicide_end.""" - # EIP-8037 inner-CALL gas bumps: original values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state- - # gas spill into regular gas on Amsterdam. - outer_call_gas = 150000 - middle_call_gas = 100000 - inner_call_gas = 50000 - if fork.is_eip_enabled(8037): - outer_call_gas = 1000000 - middle_call_gas = 800000 - inner_call_gas = 100000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -76,7 +61,6 @@ def test_callcallcodecallcode_011_suicide_end( code=Op.SSTORE( key=0x0, value=Op.CALL( - gas=outer_call_gas, address=0x2CAC1D43F00E8B40B63426AB460C7E8717EE6455, value=0x0, args_offset=0x0, @@ -96,7 +80,6 @@ def test_callcallcodecallcode_011_suicide_end( code=Op.SSTORE( key=0x1, value=Op.DELEGATECALL( - gas=middle_call_gas, address=0xAC521409E2FA9526BFE6B827805783D2E307C4CE, args_offset=0x0, args_size=0x40, @@ -115,7 +98,6 @@ def test_callcallcodecallcode_011_suicide_end( code=Op.SSTORE( key=0x2, value=Op.DELEGATECALL( - gas=inner_call_gas, address=0x73B954EBC05BB0FF4A0F6A13A054D50AD1584099, args_offset=0x0, args_size=0x40, @@ -130,12 +112,7 @@ def test_callcallcodecallcode_011_suicide_end( address=Address(0xAC521409E2FA9526BFE6B827805783D2E307C4CE), # noqa: E501 ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { target: Account(storage={0: 1}), diff --git a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecall_10.py b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecall_10.py index 944c9b6ca4d..b2589e6b2dc 100644 --- a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecall_10.py +++ b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecall_10.py @@ -4,15 +4,7 @@ Ported from: state_tests/stCallDelegateCodesHomestead/callcodecall_10Filler.json - -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values from the original filler (250k / 300k / 350k) were tuned to -the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the -innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei -of regular gas), and the inner CALL OoGs before the test's SSTORE -markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL -chain has headroom on Amsterdam; older forks are unaffected because -only the requested gas changes, the actual consumption is identical. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -25,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -40,18 +31,8 @@ def test_callcodecall_10( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_callcodecall_10.""" - # EIP-8037 inner-CALL gas bumps (original gas values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state-gas - # spill into regular gas on Amsterdam). - inner_call_gas = 0x3D090 - outer_call_gas = 0x55730 - if fork.is_eip_enabled(8037): - inner_call_gas = 0xF4240 - outer_call_gas = 0x155CC0 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -84,7 +65,6 @@ def test_callcodecall_10( code=Op.SSTORE( key=0x1, value=Op.CALL( - gas=inner_call_gas, address=addr_2, value=0x1, args_offset=0x0, @@ -103,7 +83,6 @@ def test_callcodecall_10( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=outer_call_gas, address=addr, args_offset=0x0, args_size=0x40, @@ -116,12 +95,7 @@ def test_callcodecall_10( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { target: Account(storage={0: 1, 1: 1}), diff --git a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecall_10_suicide_end.py b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecall_10_suicide_end.py index d6f6e7d5a6f..8e3c9005976 100644 --- a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecall_10_suicide_end.py +++ b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecall_10_suicide_end.py @@ -4,9 +4,7 @@ Ported from: state_tests/stCallDelegateCodesHomestead/callcodecall_10_SuicideEndFiller.json -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. - +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -36,18 +33,8 @@ def test_callcodecall_10_suicide_end( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_callcodecall_10_suicide_end.""" - # EIP-8037 inner-CALL gas bumps: original values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state- - # gas spill into regular gas on Amsterdam. - outer_call_gas = 150000 - inner_call_gas = 50000 - if fork.is_eip_enabled(8037): - outer_call_gas = 1000000 - inner_call_gas = 100000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -74,7 +61,6 @@ def test_callcodecall_10_suicide_end( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=outer_call_gas, address=0xF741CFEE7B7FB1025DCCEF3DB5A3CBC8FFB776F8, args_offset=0x0, args_size=0x40, @@ -93,7 +79,6 @@ def test_callcodecall_10_suicide_end( code=Op.SSTORE( key=0x1, value=Op.CALL( - gas=inner_call_gas, address=0x703B936FD4D674F0FF5D6957F61097152F8781B8, value=0x0, args_offset=0x0, @@ -109,12 +94,7 @@ def test_callcodecall_10_suicide_end( address=Address(0xF741CFEE7B7FB1025DCCEF3DB5A3CBC8FFB776F8), # noqa: E501 ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { target: Account(storage={0: 1, 1: 1}, balance=0xDE0B6B3A7640000), diff --git a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcall_100.py b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcall_100.py index f5e7137363c..e7bb5131167 100644 --- a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcall_100.py +++ b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcall_100.py @@ -4,15 +4,7 @@ Ported from: state_tests/stCallDelegateCodesHomestead/callcodecallcall_100Filler.json - -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values from the original filler (250k / 300k / 350k) were tuned to -the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the -innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei -of regular gas), and the inner CALL OoGs before the test's SSTORE -markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL -chain has headroom on Amsterdam; older forks are unaffected because -only the requested gas changes, the actual consumption is identical. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -25,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -42,20 +33,8 @@ def test_callcodecallcall_100( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_callcodecallcall_100.""" - # EIP-8037 inner-CALL gas bumps (original gas values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state-gas - # spill into regular gas on Amsterdam). - inner_call_gas = 0x3D090 - middle_call_gas = 0x493E0 - outer_call_gas = 0x55730 - if fork.is_eip_enabled(8037): - inner_call_gas = 0xF4240 - middle_call_gas = 0x124F80 - outer_call_gas = 0x155CC0 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -88,7 +67,6 @@ def test_callcodecallcall_100( code=Op.SSTORE( key=0x2, value=Op.CALL( - gas=inner_call_gas, address=addr_3, value=0x2, args_offset=0x0, @@ -107,7 +85,6 @@ def test_callcodecallcall_100( code=Op.SSTORE( key=0x1, value=Op.CALL( - gas=middle_call_gas, address=addr_2, value=0x1, args_offset=0x0, @@ -127,7 +104,6 @@ def test_callcodecallcall_100( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=outer_call_gas, address=addr, args_offset=0x0, args_size=0x40, @@ -140,12 +116,7 @@ def test_callcodecallcall_100( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { target: Account(storage={0: 1, 1: 1, 5: sender}), diff --git a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcall_100_suicide_end.py b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcall_100_suicide_end.py index 49da4be2412..36c3ed77b48 100644 --- a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcall_100_suicide_end.py +++ b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcall_100_suicide_end.py @@ -4,9 +4,7 @@ Ported from: state_tests/stCallDelegateCodesHomestead/callcodecallcall_100_SuicideEndFiller.json -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. - +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -36,20 +33,8 @@ def test_callcodecallcall_100_suicide_end( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_callcodecallcall_100_suicide_end.""" - # EIP-8037 inner-CALL gas bumps: original values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state- - # gas spill into regular gas on Amsterdam. - outer_call_gas = 150000 - middle_call_gas = 100000 - inner_call_gas = 50000 - if fork.is_eip_enabled(8037): - outer_call_gas = 1000000 - middle_call_gas = 800000 - inner_call_gas = 100000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -76,7 +61,6 @@ def test_callcodecallcall_100_suicide_end( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=outer_call_gas, address=0x77B749FFFF7EC61D31C79ED104F230A7959B2879, args_offset=0x0, args_size=0x40, @@ -95,7 +79,6 @@ def test_callcodecallcall_100_suicide_end( code=Op.SSTORE( key=0x1, value=Op.CALL( - gas=middle_call_gas, address=0xD957E143AD2C011BC6A2B142795F1A9BA70D0680, value=0x0, args_offset=0x0, @@ -115,7 +98,6 @@ def test_callcodecallcall_100_suicide_end( code=Op.SSTORE( key=0x2, value=Op.CALL( - gas=inner_call_gas, address=0x73B954EBC05BB0FF4A0F6A13A054D50AD1584099, value=0x0, args_offset=0x0, @@ -131,12 +113,7 @@ def test_callcodecallcall_100_suicide_end( address=Address(0xD957E143AD2C011BC6A2B142795F1A9BA70D0680), # noqa: E501 ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { target: Account(storage={0: 1, 1: 1, 2: 0}), diff --git a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcallcode_101.py b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcallcode_101.py index 3bac074bbdc..0a4d8e91d71 100644 --- a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcallcode_101.py +++ b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcallcode_101.py @@ -4,15 +4,7 @@ Ported from: state_tests/stCallDelegateCodesHomestead/callcodecallcallcode_101Filler.json - -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values from the original filler (250k / 300k / 350k) were tuned to -the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the -innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei -of regular gas), and the inner CALL OoGs before the test's SSTORE -markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL -chain has headroom on Amsterdam; older forks are unaffected because -only the requested gas changes, the actual consumption is identical. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -25,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -42,20 +33,8 @@ def test_callcodecallcallcode_101( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_callcodecallcallcode_101.""" - # EIP-8037 inner-CALL gas bumps (original gas values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state-gas - # spill into regular gas on Amsterdam). - inner_call_gas = 0x3D090 - middle_call_gas = 0x493E0 - outer_call_gas = 0x55730 - if fork.is_eip_enabled(8037): - inner_call_gas = 0xF4240 - middle_call_gas = 0x124F80 - outer_call_gas = 0x155CC0 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -88,7 +67,6 @@ def test_callcodecallcallcode_101( code=Op.SSTORE( key=0x2, value=Op.DELEGATECALL( - gas=inner_call_gas, address=addr_3, args_offset=0x0, args_size=0x40, @@ -107,7 +85,6 @@ def test_callcodecallcallcode_101( code=Op.SSTORE( key=0x1, value=Op.CALL( - gas=middle_call_gas, address=addr_2, value=0x1, args_offset=0x0, @@ -127,7 +104,6 @@ def test_callcodecallcallcode_101( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=outer_call_gas, address=addr, args_offset=0x0, args_size=0x40, @@ -140,12 +116,7 @@ def test_callcodecallcallcode_101( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { target: Account(storage={0: 1, 1: 1, 5: sender}), diff --git a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcallcode_101_suicide_end.py b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcallcode_101_suicide_end.py index 96f3337c150..4496997a3b9 100644 --- a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcallcode_101_suicide_end.py +++ b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcallcode_101_suicide_end.py @@ -4,9 +4,7 @@ Ported from: state_tests/stCallDelegateCodesHomestead/callcodecallcallcode_101_SuicideEndFiller.json -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. - +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -36,20 +33,8 @@ def test_callcodecallcallcode_101_suicide_end( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_callcodecallcallcode_101_suicide_end.""" - # EIP-8037 inner-CALL gas bumps: original values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state- - # gas spill into regular gas on Amsterdam. - outer_call_gas = 150000 - middle_call_gas = 100000 - inner_call_gas = 50000 - if fork.is_eip_enabled(8037): - outer_call_gas = 1000000 - middle_call_gas = 800000 - inner_call_gas = 100000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -76,7 +61,6 @@ def test_callcodecallcallcode_101_suicide_end( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=outer_call_gas, address=0x77B749FFFF7EC61D31C79ED104F230A7959B2879, args_offset=0x0, args_size=0x40, @@ -95,7 +79,6 @@ def test_callcodecallcallcode_101_suicide_end( code=Op.SSTORE( key=0x1, value=Op.CALL( - gas=middle_call_gas, address=0xAC521409E2FA9526BFE6B827805783D2E307C4CE, value=0x0, args_offset=0x0, @@ -115,7 +98,6 @@ def test_callcodecallcallcode_101_suicide_end( code=Op.SSTORE( key=0x2, value=Op.DELEGATECALL( - gas=inner_call_gas, address=0x73B954EBC05BB0FF4A0F6A13A054D50AD1584099, args_offset=0x0, args_size=0x40, @@ -130,12 +112,7 @@ def test_callcodecallcallcode_101_suicide_end( address=Address(0xAC521409E2FA9526BFE6B827805783D2E307C4CE), # noqa: E501 ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { target: Account(storage={0: 1, 1: 1, 2: 0, 3: 0}), diff --git a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcode_11.py b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcode_11.py index 055d2ccfe03..89178b5d7e0 100644 --- a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcode_11.py +++ b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcode_11.py @@ -4,15 +4,7 @@ Ported from: state_tests/stCallDelegateCodesHomestead/callcodecallcode_11Filler.json - -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values from the original filler (250k / 300k / 350k) were tuned to -the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the -innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei -of regular gas), and the inner CALL OoGs before the test's SSTORE -markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL -chain has headroom on Amsterdam; older forks are unaffected because -only the requested gas changes, the actual consumption is identical. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -25,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -42,18 +33,8 @@ def test_callcodecallcode_11( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_callcodecallcode_11.""" - # EIP-8037 inner-CALL gas bumps (original gas values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state-gas - # spill into regular gas on Amsterdam). - inner_call_gas = 0x3D090 - outer_call_gas = 0x55730 - if fork.is_eip_enabled(8037): - inner_call_gas = 0xF4240 - outer_call_gas = 0x155CC0 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -86,7 +67,6 @@ def test_callcodecallcode_11( code=Op.SSTORE( key=0x1, value=Op.DELEGATECALL( - gas=inner_call_gas, address=addr_2, args_offset=0x0, args_size=0x40, @@ -103,7 +83,6 @@ def test_callcodecallcode_11( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=outer_call_gas, address=addr, args_offset=0x0, args_size=0x40, @@ -116,12 +95,7 @@ def test_callcodecallcode_11( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { target: Account( diff --git a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcode_11_suicide_end.py b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcode_11_suicide_end.py index 5a96e25ac11..3435376fe0e 100644 --- a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcode_11_suicide_end.py +++ b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcode_11_suicide_end.py @@ -4,9 +4,7 @@ Ported from: state_tests/stCallDelegateCodesHomestead/callcodecallcode_11_SuicideEndFiller.json -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. - +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -36,18 +33,8 @@ def test_callcodecallcode_11_suicide_end( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_callcodecallcode_11_suicide_end.""" - # EIP-8037 inner-CALL gas bumps: original values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state- - # gas spill into regular gas on Amsterdam. - outer_call_gas = 150000 - inner_call_gas = 50000 - if fork.is_eip_enabled(8037): - outer_call_gas = 1000000 - inner_call_gas = 100000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -74,7 +61,6 @@ def test_callcodecallcode_11_suicide_end( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=outer_call_gas, address=0x1CCA6E93108EC94304AE5EB121D323E6C317FE7A, args_offset=0x0, args_size=0x40, @@ -93,7 +79,6 @@ def test_callcodecallcode_11_suicide_end( code=Op.SSTORE( key=0x1, value=Op.DELEGATECALL( - gas=inner_call_gas, address=0x703B936FD4D674F0FF5D6957F61097152F8781B8, args_offset=0x0, args_size=0x40, @@ -108,12 +93,7 @@ def test_callcodecallcode_11_suicide_end( address=Address(0x1CCA6E93108EC94304AE5EB121D323E6C317FE7A), # noqa: E501 ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { target: Account( diff --git a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcodecall_110.py b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcodecall_110.py index 0d66c6d33fe..d87caf4c654 100644 --- a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcodecall_110.py +++ b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcodecall_110.py @@ -4,15 +4,7 @@ Ported from: state_tests/stCallDelegateCodesHomestead/callcodecallcodecall_110Filler.json - -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values from the original filler (250k / 300k / 350k) were tuned to -the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the -innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei -of regular gas), and the inner CALL OoGs before the test's SSTORE -markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL -chain has headroom on Amsterdam; older forks are unaffected because -only the requested gas changes, the actual consumption is identical. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -25,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -42,20 +33,8 @@ def test_callcodecallcodecall_110( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_callcodecallcodecall_110.""" - # EIP-8037 inner-CALL gas bumps (original gas values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state-gas - # spill into regular gas on Amsterdam). - inner_call_gas = 0x3D090 - middle_call_gas = 0x493E0 - outer_call_gas = 0x55730 - if fork.is_eip_enabled(8037): - inner_call_gas = 0xF4240 - middle_call_gas = 0x124F80 - outer_call_gas = 0x155CC0 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -88,7 +67,6 @@ def test_callcodecallcodecall_110( code=Op.SSTORE( key=0x2, value=Op.CALL( - gas=inner_call_gas, address=addr_3, value=0x1, args_offset=0x0, @@ -108,7 +86,6 @@ def test_callcodecallcodecall_110( code=Op.SSTORE( key=0x1, value=Op.DELEGATECALL( - gas=middle_call_gas, address=addr_2, args_offset=0x0, args_size=0x40, @@ -127,7 +104,6 @@ def test_callcodecallcodecall_110( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=outer_call_gas, address=addr, args_offset=0x0, args_size=0x40, @@ -140,12 +116,7 @@ def test_callcodecallcodecall_110( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { target: Account(storage={0: 1, 1: 1, 2: 1, 5: sender, 6: sender}), diff --git a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcodecall_110_suicide_end.py b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcodecall_110_suicide_end.py index c3fb455b3ef..cf1efe6aacf 100644 --- a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcodecall_110_suicide_end.py +++ b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcodecall_110_suicide_end.py @@ -4,9 +4,7 @@ Ported from: state_tests/stCallDelegateCodesHomestead/callcodecallcodecall_110_SuicideEndFiller.json -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. - +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -36,20 +33,8 @@ def test_callcodecallcodecall_110_suicide_end( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_callcodecallcodecall_110_suicide_end.""" - # EIP-8037 inner-CALL gas bumps: original values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state- - # gas spill into regular gas on Amsterdam. - outer_call_gas = 150000 - middle_call_gas = 100000 - inner_call_gas = 50000 - if fork.is_eip_enabled(8037): - outer_call_gas = 1000000 - middle_call_gas = 800000 - inner_call_gas = 100000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -76,7 +61,6 @@ def test_callcodecallcodecall_110_suicide_end( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=outer_call_gas, address=0x2CAC1D43F00E8B40B63426AB460C7E8717EE6455, args_offset=0x0, args_size=0x40, @@ -95,7 +79,6 @@ def test_callcodecallcodecall_110_suicide_end( code=Op.SSTORE( key=0x1, value=Op.DELEGATECALL( - gas=middle_call_gas, address=0xD957E143AD2C011BC6A2B142795F1A9BA70D0680, args_offset=0x0, args_size=0x40, @@ -114,7 +97,6 @@ def test_callcodecallcodecall_110_suicide_end( code=Op.SSTORE( key=0x2, value=Op.CALL( - gas=inner_call_gas, address=0x73B954EBC05BB0FF4A0F6A13A054D50AD1584099, value=0x0, args_offset=0x0, @@ -130,12 +112,7 @@ def test_callcodecallcodecall_110_suicide_end( address=Address(0xD957E143AD2C011BC6A2B142795F1A9BA70D0680), # noqa: E501 ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { target: Account(storage={0: 1, 1: 1, 2: 1}, balance=0), diff --git a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcodecallcode_111.py b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcodecallcode_111.py index f270f4c6af8..543e76e4d49 100644 --- a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcodecallcode_111.py +++ b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcodecallcode_111.py @@ -4,15 +4,7 @@ Ported from: state_tests/stCallDelegateCodesHomestead/callcodecallcodecallcode_111Filler.json - -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values from the original filler (250k / 300k / 350k) were tuned to -the pre-EIP-8037 gas budget. On Amsterdam each SSTORE in the -innermost callee adds the EIP-8037 per-storage state-gas (37 568 wei -of regular gas), and the inner CALL OoGs before the test's SSTORE -markers fire. Bumped uniformly to 1M / 1.2M / 1.4M so the inner CALL -chain has headroom on Amsterdam; older forks are unaffected because -only the requested gas changes, the actual consumption is identical. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -25,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -42,20 +33,8 @@ def test_callcodecallcodecallcode_111( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_callcodecallcodecallcode_111.""" - # EIP-8037 inner-CALL gas bumps (original gas values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state-gas - # spill into regular gas on Amsterdam). - inner_call_gas = 0x3D090 - middle_call_gas = 0x493E0 - outer_call_gas = 0x55730 - if fork.is_eip_enabled(8037): - inner_call_gas = 0xF4240 - middle_call_gas = 0x124F80 - outer_call_gas = 0x155CC0 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -88,7 +67,6 @@ def test_callcodecallcodecallcode_111( code=Op.SSTORE( key=0x2, value=Op.DELEGATECALL( - gas=inner_call_gas, address=addr_3, args_offset=0x0, args_size=0x40, @@ -106,7 +84,6 @@ def test_callcodecallcodecallcode_111( code=Op.SSTORE( key=0x1, value=Op.DELEGATECALL( - gas=middle_call_gas, address=addr_2, args_offset=0x0, args_size=0x40, @@ -124,7 +101,6 @@ def test_callcodecallcodecallcode_111( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=outer_call_gas, address=addr, args_offset=0x0, args_size=0x40, @@ -137,12 +113,7 @@ def test_callcodecallcodecallcode_111( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { target: Account( diff --git a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcodecallcode_111_suicide_end.py b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcodecallcode_111_suicide_end.py index 56fde94b9ab..8077f1f29d9 100644 --- a/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcodecallcode_111_suicide_end.py +++ b/tests/ported_static/stCallDelegateCodesHomestead/test_callcodecallcodecallcode_111_suicide_end.py @@ -4,9 +4,7 @@ Ported from: state_tests/stCallDelegateCodesHomestead/callcodecallcodecallcode_111_SuicideEndFiller.json -@manually-enhanced: Do not overwrite. The hardcoded inner-CALL gas -values (50k / 100k / 150k) were tuned to the pre-EIP-8037 gas budget. - +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -36,20 +33,8 @@ def test_callcodecallcodecallcode_111_suicide_end( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_callcodecallcodecallcode_111_suicide_end.""" - # EIP-8037 inner-CALL gas bumps: original values restored for - # pre-EIP-8037 forks; bumped values cover the per-storage state- - # gas spill into regular gas on Amsterdam. - outer_call_gas = 150000 - middle_call_gas = 100000 - inner_call_gas = 50000 - if fork.is_eip_enabled(8037): - outer_call_gas = 1000000 - middle_call_gas = 800000 - inner_call_gas = 100000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -76,7 +61,6 @@ def test_callcodecallcodecallcode_111_suicide_end( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=outer_call_gas, address=0x2CAC1D43F00E8B40B63426AB460C7E8717EE6455, args_offset=0x0, args_size=0x40, @@ -95,7 +79,6 @@ def test_callcodecallcodecallcode_111_suicide_end( code=Op.SSTORE( key=0x1, value=Op.DELEGATECALL( - gas=middle_call_gas, address=0xAC521409E2FA9526BFE6B827805783D2E307C4CE, args_offset=0x0, args_size=0x40, @@ -114,7 +97,6 @@ def test_callcodecallcodecallcode_111_suicide_end( code=Op.SSTORE( key=0x2, value=Op.DELEGATECALL( - gas=inner_call_gas, address=0x73B954EBC05BB0FF4A0F6A13A054D50AD1584099, args_offset=0x0, args_size=0x40, @@ -129,12 +111,7 @@ def test_callcodecallcodecallcode_111_suicide_end( address=Address(0xAC521409E2FA9526BFE6B827805783D2E307C4CE), # noqa: E501 ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - ) + tx = Transaction(sender=sender, to=target, data=Bytes("")) post = { target: Account(storage={0: 1, 1: 1, 2: 1, 3: 1}, balance=0), diff --git a/tests/ported_static/stCodeSizeLimit/test_codesize_valid.py b/tests/ported_static/stCodeSizeLimit/test_codesize_valid.py index 6ac356a7b8c..8f9c8f5b8df 100644 --- a/tests/ported_static/stCodeSizeLimit/test_codesize_valid.py +++ b/tests/ported_static/stCodeSizeLimit/test_codesize_valid.py @@ -4,14 +4,7 @@ Ported from: state_tests/stCodeSizeLimit/codesizeValidFiller.json -@manually-enhanced: Do not overwrite. On Amsterdam (EIP-8037) the -contract-creation tx — which deploys ~24 KiB of code — needs extra -state-gas headroom on top of the 15 000 000 regular-gas budget that -suffices on earlier forks. Bump `tx.gas` to 30 000 000 fork- -conditionally; pre-Amsterdam keeps the original 15 000 000 (Osaka -caps `tx.gas` at `TX_MAX_GAS_LIMIT = 16 777 216`, so the bump must be -gated). `env.gas_limit` widened so the larger tx fits in the block. -Post-state expectations are unchanged on all forks. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -24,7 +17,6 @@ Transaction, compute_create_address, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -55,7 +47,6 @@ def test_codesize_valid( state_test: StateTestFiller, pre: Alloc, - fork: Fork, d: int, g: int, v: int, @@ -79,15 +70,10 @@ def test_codesize_valid( Op.CODECOPY(dest_offset=0x0, offset=0xD, size=0x6000) + Op.RETURN(offset=0x0, size=0x6000), ] - tx_gas = [40000000 if fork.is_eip_enabled(8037) else 15000000] tx_value = [1] tx = Transaction( - sender=sender, - to=None, - data=tx_data[d], - gas_limit=tx_gas[g], - value=tx_value[v], + sender=sender, to=None, data=tx_data[d], value=tx_value[v] ) post = { diff --git a/tests/ported_static/stMemoryTest/test_mem0b_single_byte.py b/tests/ported_static/stMemoryTest/test_mem0b_single_byte.py index 6499f41dd67..780127bb03b 100644 --- a/tests/ported_static/stMemoryTest/test_mem0b_single_byte.py +++ b/tests/ported_static/stMemoryTest/test_mem0b_single_byte.py @@ -3,9 +3,8 @@ Ported from: state_tests/stMemoryTest/mem0b_singleByteFiller.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -18,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -33,14 +31,8 @@ def test_mem0b_single_byte( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_mem0b_single_byte.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 200_000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0x6400000000) @@ -62,13 +54,7 @@ def test_mem0b_single_byte( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=tx_gas_limit, - value=10, - ) + tx = Transaction(sender=sender, to=target, data=Bytes(""), value=10) post = { target: Account(storage={0: 32}, nonce=0), diff --git a/tests/ported_static/stMemoryTest/test_mem31b_single_byte.py b/tests/ported_static/stMemoryTest/test_mem31b_single_byte.py index ab2ee24082e..cd346824db6 100644 --- a/tests/ported_static/stMemoryTest/test_mem31b_single_byte.py +++ b/tests/ported_static/stMemoryTest/test_mem31b_single_byte.py @@ -3,9 +3,8 @@ Ported from: state_tests/stMemoryTest/mem31b_singleByteFiller.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -18,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -33,14 +31,8 @@ def test_mem31b_single_byte( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_mem31b_single_byte.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 200_000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0x6400000000) @@ -62,13 +54,7 @@ def test_mem31b_single_byte( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=tx_gas_limit, - value=10, - ) + tx = Transaction(sender=sender, to=target, data=Bytes(""), value=10) post = { target: Account(storage={0: 32}, nonce=0), diff --git a/tests/ported_static/stMemoryTest/test_mem32b_single_byte.py b/tests/ported_static/stMemoryTest/test_mem32b_single_byte.py index b839594d38b..4f5526737dd 100644 --- a/tests/ported_static/stMemoryTest/test_mem32b_single_byte.py +++ b/tests/ported_static/stMemoryTest/test_mem32b_single_byte.py @@ -3,9 +3,8 @@ Ported from: state_tests/stMemoryTest/mem32b_singleByteFiller.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -18,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -33,14 +31,8 @@ def test_mem32b_single_byte( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_mem32b_single_byte.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 200_000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0x6400000000) @@ -62,13 +54,7 @@ def test_mem32b_single_byte( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=tx_gas_limit, - value=10, - ) + tx = Transaction(sender=sender, to=target, data=Bytes(""), value=10) post = { target: Account(storage={0: 32}, nonce=0), diff --git a/tests/ported_static/stMemoryTest/test_mem32kb_single_byte.py b/tests/ported_static/stMemoryTest/test_mem32kb_single_byte.py index fa5d24dae28..ac6ee951b02 100644 --- a/tests/ported_static/stMemoryTest/test_mem32kb_single_byte.py +++ b/tests/ported_static/stMemoryTest/test_mem32kb_single_byte.py @@ -3,9 +3,8 @@ Ported from: state_tests/stMemoryTest/mem32kb_singleByteFiller.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -18,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -33,14 +31,8 @@ def test_mem32kb_single_byte( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_mem32kb_single_byte.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 300_000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0x6400000000) @@ -62,13 +54,7 @@ def test_mem32kb_single_byte( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=tx_gas_limit, - value=10, - ) + tx = Transaction(sender=sender, to=target, data=Bytes(""), value=10) post = { target: Account(storage={0: 32000}, nonce=0), diff --git a/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_minus_1.py b/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_minus_1.py index 49986e0279f..05ab6ff8a2f 100644 --- a/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_minus_1.py +++ b/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_minus_1.py @@ -3,9 +3,8 @@ Ported from: state_tests/stMemoryTest/mem32kb_singleByte-1Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -18,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -33,14 +31,8 @@ def test_mem32kb_single_byte_minus_1( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_mem32kb_single_byte_minus_1.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 300_000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0x6400000000) @@ -62,13 +54,7 @@ def test_mem32kb_single_byte_minus_1( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=tx_gas_limit, - value=10, - ) + tx = Transaction(sender=sender, to=target, data=Bytes(""), value=10) post = { target: Account(storage={0: 32000}, nonce=0), diff --git a/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_minus_31.py b/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_minus_31.py index 9931597621c..afbeeec0d17 100644 --- a/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_minus_31.py +++ b/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_minus_31.py @@ -3,9 +3,8 @@ Ported from: state_tests/stMemoryTest/mem32kb_singleByte-31Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -18,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -33,14 +31,8 @@ def test_mem32kb_single_byte_minus_31( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_mem32kb_single_byte_minus_31.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 300_000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0x6400000000) @@ -62,13 +54,7 @@ def test_mem32kb_single_byte_minus_31( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=tx_gas_limit, - value=10, - ) + tx = Transaction(sender=sender, to=target, data=Bytes(""), value=10) post = { target: Account(storage={0: 32000}, nonce=0), diff --git a/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_minus_32.py b/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_minus_32.py index b25a8187a58..e6219711ca2 100644 --- a/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_minus_32.py +++ b/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_minus_32.py @@ -3,9 +3,8 @@ Ported from: state_tests/stMemoryTest/mem32kb_singleByte-32Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -18,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -33,14 +31,8 @@ def test_mem32kb_single_byte_minus_32( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_mem32kb_single_byte_minus_32.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 300_000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0x6400000000) @@ -62,13 +54,7 @@ def test_mem32kb_single_byte_minus_32( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=tx_gas_limit, - value=10, - ) + tx = Transaction(sender=sender, to=target, data=Bytes(""), value=10) post = { target: Account(storage={0: 31968}, nonce=0), diff --git a/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_minus_33.py b/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_minus_33.py index 848a8a59319..d8bdc089124 100644 --- a/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_minus_33.py +++ b/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_minus_33.py @@ -3,9 +3,8 @@ Ported from: state_tests/stMemoryTest/mem32kb_singleByte-33Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -18,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -33,14 +31,8 @@ def test_mem32kb_single_byte_minus_33( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_mem32kb_single_byte_minus_33.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 300_000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0x6400000000) @@ -62,13 +54,7 @@ def test_mem32kb_single_byte_minus_33( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=tx_gas_limit, - value=10, - ) + tx = Transaction(sender=sender, to=target, data=Bytes(""), value=10) post = { target: Account(storage={0: 31968}, nonce=0), diff --git a/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_plus_1.py b/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_plus_1.py index 2fb7c811573..ec9bcfbeb59 100644 --- a/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_plus_1.py +++ b/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_plus_1.py @@ -3,9 +3,8 @@ Ported from: state_tests/stMemoryTest/mem32kb_singleByte+1Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -18,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -33,14 +31,8 @@ def test_mem32kb_single_byte_plus_1( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_mem32kb_single_byte_plus_1.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 300_000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0x6400000000) @@ -62,13 +54,7 @@ def test_mem32kb_single_byte_plus_1( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=tx_gas_limit, - value=10, - ) + tx = Transaction(sender=sender, to=target, data=Bytes(""), value=10) post = { target: Account(storage={0: 32032}, nonce=0), diff --git a/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_plus_31.py b/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_plus_31.py index d03e663c1ba..e62542e9628 100644 --- a/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_plus_31.py +++ b/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_plus_31.py @@ -3,9 +3,8 @@ Ported from: state_tests/stMemoryTest/mem32kb_singleByte+31Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -18,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -33,14 +31,8 @@ def test_mem32kb_single_byte_plus_31( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_mem32kb_single_byte_plus_31.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 300_000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0x6400000000) @@ -62,13 +54,7 @@ def test_mem32kb_single_byte_plus_31( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=tx_gas_limit, - value=10, - ) + tx = Transaction(sender=sender, to=target, data=Bytes(""), value=10) post = { target: Account(storage={0: 32032}, nonce=0), diff --git a/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_plus_32.py b/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_plus_32.py index 9cdbcc90c64..911620e273e 100644 --- a/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_plus_32.py +++ b/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_plus_32.py @@ -3,9 +3,8 @@ Ported from: state_tests/stMemoryTest/mem32kb_singleByte+32Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -18,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -33,14 +31,8 @@ def test_mem32kb_single_byte_plus_32( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_mem32kb_single_byte_plus_32.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 300_000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0x6400000000) @@ -62,13 +54,7 @@ def test_mem32kb_single_byte_plus_32( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=tx_gas_limit, - value=10, - ) + tx = Transaction(sender=sender, to=target, data=Bytes(""), value=10) post = { target: Account(storage={0: 32032}, nonce=0), diff --git a/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_plus_33.py b/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_plus_33.py index 8c976ddc056..7f116b090f3 100644 --- a/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_plus_33.py +++ b/tests/ported_static/stMemoryTest/test_mem32kb_single_byte_plus_33.py @@ -3,9 +3,8 @@ Ported from: state_tests/stMemoryTest/mem32kb_singleByte+33Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -18,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -33,14 +31,8 @@ def test_mem32kb_single_byte_plus_33( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_mem32kb_single_byte_plus_33.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 300_000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0x6400000000) @@ -62,13 +54,7 @@ def test_mem32kb_single_byte_plus_33( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=tx_gas_limit, - value=10, - ) + tx = Transaction(sender=sender, to=target, data=Bytes(""), value=10) post = { target: Account(storage={0: 32064}, nonce=0), diff --git a/tests/ported_static/stMemoryTest/test_mem33b_single_byte.py b/tests/ported_static/stMemoryTest/test_mem33b_single_byte.py index dd2e815624f..6fc11fc4287 100644 --- a/tests/ported_static/stMemoryTest/test_mem33b_single_byte.py +++ b/tests/ported_static/stMemoryTest/test_mem33b_single_byte.py @@ -3,9 +3,8 @@ Ported from: state_tests/stMemoryTest/mem33b_singleByteFiller.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -18,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -33,14 +31,8 @@ def test_mem33b_single_byte( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_mem33b_single_byte.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 200_000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0x6400000000) @@ -62,13 +54,7 @@ def test_mem33b_single_byte( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=tx_gas_limit, - value=10, - ) + tx = Transaction(sender=sender, to=target, data=Bytes(""), value=10) post = { target: Account(storage={0: 64}, nonce=0), diff --git a/tests/ported_static/stMemoryTest/test_mem64kb_single_byte.py b/tests/ported_static/stMemoryTest/test_mem64kb_single_byte.py index 68ac145e74f..aee1042aa83 100644 --- a/tests/ported_static/stMemoryTest/test_mem64kb_single_byte.py +++ b/tests/ported_static/stMemoryTest/test_mem64kb_single_byte.py @@ -3,9 +3,8 @@ Ported from: state_tests/stMemoryTest/mem64kb_singleByteFiller.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -18,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -33,14 +31,8 @@ def test_mem64kb_single_byte( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_mem64kb_single_byte.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 1_000_000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0x6400000000) @@ -62,13 +54,7 @@ def test_mem64kb_single_byte( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=tx_gas_limit, - value=10, - ) + tx = Transaction(sender=sender, to=target, data=Bytes(""), value=10) post = { target: Account(storage={0: 64000}, nonce=0), diff --git a/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_minus_1.py b/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_minus_1.py index d89f096cb45..6c69e209c31 100644 --- a/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_minus_1.py +++ b/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_minus_1.py @@ -3,9 +3,8 @@ Ported from: state_tests/stMemoryTest/mem64kb_singleByte-1Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -18,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -33,14 +31,8 @@ def test_mem64kb_single_byte_minus_1( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_mem64kb_single_byte_minus_1.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 1_000_000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0x6400000000) @@ -62,13 +54,7 @@ def test_mem64kb_single_byte_minus_1( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=tx_gas_limit, - value=10, - ) + tx = Transaction(sender=sender, to=target, data=Bytes(""), value=10) post = { target: Account(storage={0: 64000}, nonce=0), diff --git a/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_minus_31.py b/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_minus_31.py index adbbfb14cd4..63d2d7ef9d5 100644 --- a/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_minus_31.py +++ b/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_minus_31.py @@ -3,9 +3,8 @@ Ported from: state_tests/stMemoryTest/mem64kb_singleByte-31Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -18,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -33,14 +31,8 @@ def test_mem64kb_single_byte_minus_31( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_mem64kb_single_byte_minus_31.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 1_000_000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0x6400000000) @@ -62,13 +54,7 @@ def test_mem64kb_single_byte_minus_31( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=tx_gas_limit, - value=10, - ) + tx = Transaction(sender=sender, to=target, data=Bytes(""), value=10) post = { target: Account(storage={0: 64000}, nonce=0), diff --git a/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_minus_32.py b/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_minus_32.py index c03bd98cb1a..93281a401fb 100644 --- a/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_minus_32.py +++ b/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_minus_32.py @@ -3,9 +3,8 @@ Ported from: state_tests/stMemoryTest/mem64kb_singleByte-32Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -18,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -33,14 +31,8 @@ def test_mem64kb_single_byte_minus_32( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_mem64kb_single_byte_minus_32.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 1_000_000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0x6400000000) @@ -62,13 +54,7 @@ def test_mem64kb_single_byte_minus_32( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=tx_gas_limit, - value=10, - ) + tx = Transaction(sender=sender, to=target, data=Bytes(""), value=10) post = { target: Account(storage={0: 63968}, nonce=0), diff --git a/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_minus_33.py b/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_minus_33.py index e6697924f19..6d2886fac7e 100644 --- a/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_minus_33.py +++ b/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_minus_33.py @@ -3,9 +3,8 @@ Ported from: state_tests/stMemoryTest/mem64kb_singleByte-33Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -18,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -33,14 +31,8 @@ def test_mem64kb_single_byte_minus_33( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_mem64kb_single_byte_minus_33.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 1_000_000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0x6400000000) @@ -62,13 +54,7 @@ def test_mem64kb_single_byte_minus_33( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=tx_gas_limit, - value=10, - ) + tx = Transaction(sender=sender, to=target, data=Bytes(""), value=10) post = { target: Account(storage={0: 63968}, nonce=0), diff --git a/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_plus_1.py b/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_plus_1.py index 9717e71651b..9f4df80383d 100644 --- a/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_plus_1.py +++ b/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_plus_1.py @@ -3,9 +3,8 @@ Ported from: state_tests/stMemoryTest/mem64kb_singleByte+1Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -18,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -33,14 +31,8 @@ def test_mem64kb_single_byte_plus_1( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_mem64kb_single_byte_plus_1.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 1_000_000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0x6400000000) @@ -62,13 +54,7 @@ def test_mem64kb_single_byte_plus_1( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=tx_gas_limit, - value=10, - ) + tx = Transaction(sender=sender, to=target, data=Bytes(""), value=10) post = { target: Account(storage={0: 64032}, nonce=0), diff --git a/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_plus_31.py b/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_plus_31.py index eff7dbdbbca..328fd9653d6 100644 --- a/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_plus_31.py +++ b/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_plus_31.py @@ -3,9 +3,8 @@ Ported from: state_tests/stMemoryTest/mem64kb_singleByte+31Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -18,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -33,14 +31,8 @@ def test_mem64kb_single_byte_plus_31( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_mem64kb_single_byte_plus_31.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 1_000_000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0x6400000000) @@ -62,13 +54,7 @@ def test_mem64kb_single_byte_plus_31( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=tx_gas_limit, - value=10, - ) + tx = Transaction(sender=sender, to=target, data=Bytes(""), value=10) post = { target: Account(storage={0: 64032}, nonce=0), diff --git a/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_plus_32.py b/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_plus_32.py index b8882820448..05c6e938bd3 100644 --- a/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_plus_32.py +++ b/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_plus_32.py @@ -3,9 +3,8 @@ Ported from: state_tests/stMemoryTest/mem64kb_singleByte+32Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -18,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -33,14 +31,8 @@ def test_mem64kb_single_byte_plus_32( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_mem64kb_single_byte_plus_32.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 1_000_000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0x6400000000) @@ -62,13 +54,7 @@ def test_mem64kb_single_byte_plus_32( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=tx_gas_limit, - value=10, - ) + tx = Transaction(sender=sender, to=target, data=Bytes(""), value=10) post = { target: Account(storage={0: 64032}, nonce=0), diff --git a/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_plus_33.py b/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_plus_33.py index 6e9b9c4d55b..20c643d51db 100644 --- a/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_plus_33.py +++ b/tests/ported_static/stMemoryTest/test_mem64kb_single_byte_plus_33.py @@ -3,9 +3,8 @@ Ported from: state_tests/stMemoryTest/mem64kb_singleByte+33Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -18,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -33,14 +31,8 @@ def test_mem64kb_single_byte_plus_33( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_mem64kb_single_byte_plus_33.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 1_000_000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0x6400000000) @@ -62,13 +54,7 @@ def test_mem64kb_single_byte_plus_33( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=tx_gas_limit, - value=10, - ) + tx = Transaction(sender=sender, to=target, data=Bytes(""), value=10) post = { target: Account(storage={0: 64064}, nonce=0), diff --git a/tests/ported_static/stRandom/test_random_statetest102.py b/tests/ported_static/stRandom/test_random_statetest102.py index 4c6423fd4fa..0d576dc8482 100644 --- a/tests/ported_static/stRandom/test_random_statetest102.py +++ b/tests/ported_static/stRandom/test_random_statetest102.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest102Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest102( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest102.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -96,7 +88,6 @@ def test_random_statetest102( data=Bytes( "457f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e7944447f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe6f157094ffff1a04893a9cf3858b8576" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x25D01AE2, ) diff --git a/tests/ported_static/stRandom/test_random_statetest104.py b/tests/ported_static/stRandom/test_random_statetest104.py index 114d7820a3a..c98bab49092 100644 --- a/tests/ported_static/stRandom/test_random_statetest104.py +++ b/tests/ported_static/stRandom/test_random_statetest104.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest104Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest104( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest104.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -104,7 +96,6 @@ def test_random_statetest104( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe6f147d6b978c780a82619772417d5b6a" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x3A3FA7D5, ) diff --git a/tests/ported_static/stRandom/test_random_statetest105.py b/tests/ported_static/stRandom/test_random_statetest105.py index 84f03f71bd7..c1dd2310265 100644 --- a/tests/ported_static/stRandom/test_random_statetest105.py +++ b/tests/ported_static/stRandom/test_random_statetest105.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest105Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest105( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest105.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -98,7 +90,6 @@ def test_random_statetest105( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff437f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff6f9914639111156d1759ff65039a02926c" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x566920F7, ) diff --git a/tests/ported_static/stRandom/test_random_statetest106.py b/tests/ported_static/stRandom/test_random_statetest106.py index 0b31c2aa12f..61ef6d931c9 100644 --- a/tests/ported_static/stRandom/test_random_statetest106.py +++ b/tests/ported_static/stRandom/test_random_statetest106.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest106Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest106( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest106.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -101,7 +93,6 @@ def test_random_statetest106( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000100000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6f327043726481f25094828e21155779" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x41FF266C, ) diff --git a/tests/ported_static/stRandom/test_random_statetest107.py b/tests/ported_static/stRandom/test_random_statetest107.py index 3ed7e239af2..2c7d646c47f 100644 --- a/tests/ported_static/stRandom/test_random_statetest107.py +++ b/tests/ported_static/stRandom/test_random_statetest107.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest107Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest107( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest107.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x945304EB96065B2A98B57A48A06AE28D285A71B5) contract_0 = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87) sender = EOA( @@ -102,7 +94,6 @@ def test_random_statetest107( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe457fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b509" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x4F9C450B, ) diff --git a/tests/ported_static/stRandom/test_random_statetest11.py b/tests/ported_static/stRandom/test_random_statetest11.py index 0fa94e89cfc..70cce2648dc 100644 --- a/tests/ported_static/stRandom/test_random_statetest11.py +++ b/tests/ported_static/stRandom/test_random_statetest11.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest11Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest11( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest11.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -97,7 +89,6 @@ def test_random_statetest11( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000000000000000000000000000000000000000c3506fa093f3408a6e531735960a7617127a" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x6909D3EC, ) diff --git a/tests/ported_static/stRandom/test_random_statetest110.py b/tests/ported_static/stRandom/test_random_statetest110.py index 92c3316ccd0..a292f781261 100644 --- a/tests/ported_static/stRandom/test_random_statetest110.py +++ b/tests/ported_static/stRandom/test_random_statetest110.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest110Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest110( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest110.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -96,7 +88,6 @@ def test_random_statetest110( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe417f00000000000000000000000000000000000000000000000000000000000000016f97543c343476cb7c8c84066217f102" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x2BEB343B, ) diff --git a/tests/ported_static/stRandom/test_random_statetest112.py b/tests/ported_static/stRandom/test_random_statetest112.py index f8e4ef355a9..3a1ff38db27 100644 --- a/tests/ported_static/stRandom/test_random_statetest112.py +++ b/tests/ported_static/stRandom/test_random_statetest112.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest112Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest112( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest112.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -102,7 +94,6 @@ def test_random_statetest112( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff45447fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000006f549c5779398a848c35307514650541" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x779DB8A6, ) diff --git a/tests/ported_static/stRandom/test_random_statetest114.py b/tests/ported_static/stRandom/test_random_statetest114.py index d6d636c9e8d..75f2fea4a0c 100644 --- a/tests/ported_static/stRandom/test_random_statetest114.py +++ b/tests/ported_static/stRandom/test_random_statetest114.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest114Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest114( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest114.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -103,7 +95,6 @@ def test_random_statetest114( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe417fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6f3584357ea388725483637d4471727f" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x3FA85EB3, ) diff --git a/tests/ported_static/stRandom/test_random_statetest116.py b/tests/ported_static/stRandom/test_random_statetest116.py index 388421585ea..7609913a4ad 100644 --- a/tests/ported_static/stRandom/test_random_statetest116.py +++ b/tests/ported_static/stRandom/test_random_statetest116.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest116Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest116( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest116.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -103,7 +95,6 @@ def test_random_statetest116( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe457fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e7907539337" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x162D4E87, ) diff --git a/tests/ported_static/stRandom/test_random_statetest117.py b/tests/ported_static/stRandom/test_random_statetest117.py index 5874efbb26b..39aa6237255 100644 --- a/tests/ported_static/stRandom/test_random_statetest117.py +++ b/tests/ported_static/stRandom/test_random_statetest117.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest117Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest117( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest117.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -94,7 +86,6 @@ def test_random_statetest117( data=Bytes( "447f00000000000000000000000000000000000000000000000000000000000000017f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79427f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000006f8aa4a4980274f18c6158368d415714" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x691AC7A4, ) diff --git a/tests/ported_static/stRandom/test_random_statetest118.py b/tests/ported_static/stRandom/test_random_statetest118.py index e6cbb0601d6..6af025604c1 100644 --- a/tests/ported_static/stRandom/test_random_statetest118.py +++ b/tests/ported_static/stRandom/test_random_statetest118.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest118Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest118( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest118.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -101,7 +93,6 @@ def test_random_statetest118( data=Bytes( "457ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000006f55817c037fa45bf3850320309a8f02" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x46F13668, ) diff --git a/tests/ported_static/stRandom/test_random_statetest119.py b/tests/ported_static/stRandom/test_random_statetest119.py index 724114ace31..0d793d553ea 100644 --- a/tests/ported_static/stRandom/test_random_statetest119.py +++ b/tests/ported_static/stRandom/test_random_statetest119.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest119Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest119( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest119.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -95,7 +87,6 @@ def test_random_statetest119( data=Bytes( "4559437f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000006f52503b127c115a9673a43137909566" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x189731CA, ) diff --git a/tests/ported_static/stRandom/test_random_statetest12.py b/tests/ported_static/stRandom/test_random_statetest12.py index f1c61def0c7..ae09d4f9f6a 100644 --- a/tests/ported_static/stRandom/test_random_statetest12.py +++ b/tests/ported_static/stRandom/test_random_statetest12.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest12Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest12( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest12.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -99,7 +91,6 @@ def test_random_statetest12( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff457ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79027f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff6f165490a41215369ef2760379411633" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x4576EB63, ) diff --git a/tests/ported_static/stRandom/test_random_statetest120.py b/tests/ported_static/stRandom/test_random_statetest120.py index e37628a38ef..5864dbbe3a7 100644 --- a/tests/ported_static/stRandom/test_random_statetest120.py +++ b/tests/ported_static/stRandom/test_random_statetest120.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest120Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest120( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest120.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -101,7 +93,6 @@ def test_random_statetest120( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe8208" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x707BF5EA, ) diff --git a/tests/ported_static/stRandom/test_random_statetest121.py b/tests/ported_static/stRandom/test_random_statetest121.py index 3db32458324..82a93ce4d74 100644 --- a/tests/ported_static/stRandom/test_random_statetest121.py +++ b/tests/ported_static/stRandom/test_random_statetest121.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest121Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest121( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest121.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -100,7 +92,6 @@ def test_random_statetest121( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000000000000000000000000000000000000000c350456f305842321509108c689f7ca3195a9d" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x492A84CF, ) diff --git a/tests/ported_static/stRandom/test_random_statetest122.py b/tests/ported_static/stRandom/test_random_statetest122.py index 9eff336d8f6..30e1ad00e0f 100644 --- a/tests/ported_static/stRandom/test_random_statetest122.py +++ b/tests/ported_static/stRandom/test_random_statetest122.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest122Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest122( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest122.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -100,7 +92,6 @@ def test_random_statetest122( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6fa2825b6c338f8d717156560af045136b" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x201771A5, ) diff --git a/tests/ported_static/stRandom/test_random_statetest124.py b/tests/ported_static/stRandom/test_random_statetest124.py index 33c25c1e79d..545cd436ab9 100644 --- a/tests/ported_static/stRandom/test_random_statetest124.py +++ b/tests/ported_static/stRandom/test_random_statetest124.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest124Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest124( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest124.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -88,7 +80,6 @@ def test_random_statetest124( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08125580355b17457f7463587b9a7a43" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x6863F683, ) diff --git a/tests/ported_static/stRandom/test_random_statetest129.py b/tests/ported_static/stRandom/test_random_statetest129.py index be9ff65c5d1..df9d0173a7d 100644 --- a/tests/ported_static/stRandom/test_random_statetest129.py +++ b/tests/ported_static/stRandom/test_random_statetest129.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest129Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest129( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest129.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -98,7 +90,6 @@ def test_random_statetest129( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6f166e733343093a31a33b8e025a0270" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x422CD1CC, ) diff --git a/tests/ported_static/stRandom/test_random_statetest130.py b/tests/ported_static/stRandom/test_random_statetest130.py index 6895e8b5670..221d0c100f7 100644 --- a/tests/ported_static/stRandom/test_random_statetest130.py +++ b/tests/ported_static/stRandom/test_random_statetest130.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest130Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest130( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest130.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -99,7 +91,6 @@ def test_random_statetest130( data=Bytes( "417fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000016f368a668b76306d181a393611988317" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x720306C0, ) diff --git a/tests/ported_static/stRandom/test_random_statetest131.py b/tests/ported_static/stRandom/test_random_statetest131.py index fee5ddcf1c4..d12c3833a45 100644 --- a/tests/ported_static/stRandom/test_random_statetest131.py +++ b/tests/ported_static/stRandom/test_random_statetest131.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest131Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest131( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest131.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -98,7 +90,6 @@ def test_random_statetest131( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000001000000000000000000000000000000000000000014416f36ff85758270710168547a9777886096" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x1C479F90, ) diff --git a/tests/ported_static/stRandom/test_random_statetest137.py b/tests/ported_static/stRandom/test_random_statetest137.py index 9d68283af2a..7a0fe4797d6 100644 --- a/tests/ported_static/stRandom/test_random_statetest137.py +++ b/tests/ported_static/stRandom/test_random_statetest137.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest137Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest137( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest137.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x945304EB96065B2A98B57A48A06AE28D285A71B5) contract_0 = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87) sender = EOA( @@ -97,7 +89,6 @@ def test_random_statetest137( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000000000000000000000000000000000000000000000087f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017e7f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b5a130e86ca17390989355f092a2" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x33AC85E7, ) diff --git a/tests/ported_static/stRandom/test_random_statetest139.py b/tests/ported_static/stRandom/test_random_statetest139.py index 561422cc2e2..ea8c45d37d9 100644 --- a/tests/ported_static/stRandom/test_random_statetest139.py +++ b/tests/ported_static/stRandom/test_random_statetest139.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest139Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -18,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -33,14 +31,8 @@ def test_random_statetest139( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest139.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -93,7 +85,6 @@ def test_random_statetest139( data=Bytes( "33447f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff43446133451545" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x409CEFF3, ) diff --git a/tests/ported_static/stRandom/test_random_statetest142.py b/tests/ported_static/stRandom/test_random_statetest142.py index 19cae9d2e03..442720c001e 100644 --- a/tests/ported_static/stRandom/test_random_statetest142.py +++ b/tests/ported_static/stRandom/test_random_statetest142.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest142Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest142( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest142.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -105,7 +97,6 @@ def test_random_statetest142( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff959137630364087e1a640431107c8801" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x6DD219A0, ) diff --git a/tests/ported_static/stRandom/test_random_statetest145.py b/tests/ported_static/stRandom/test_random_statetest145.py index ba633f6f5a4..f2def3075dc 100644 --- a/tests/ported_static/stRandom/test_random_statetest145.py +++ b/tests/ported_static/stRandom/test_random_statetest145.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest145Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -18,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -33,14 +31,8 @@ def test_random_statetest145( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest145.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -94,7 +86,6 @@ def test_random_statetest145( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f0000000000000000000000000000000000000000000000000000000000000000427f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000100000000000000000000000000000000000000007f0000000000000000000000000000000000000000000000000000000000000001391333" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x7E1F26DA, ) diff --git a/tests/ported_static/stRandom/test_random_statetest148.py b/tests/ported_static/stRandom/test_random_statetest148.py index 9909560b035..71d00424946 100644 --- a/tests/ported_static/stRandom/test_random_statetest148.py +++ b/tests/ported_static/stRandom/test_random_statetest148.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest148Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest148( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest148.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -92,7 +84,6 @@ def test_random_statetest148( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507f0000000000000000000000000000000000000000000000000000000000000001537f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e796f34847e390773919b16559077164472" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x30607FB3, ) diff --git a/tests/ported_static/stRandom/test_random_statetest15.py b/tests/ported_static/stRandom/test_random_statetest15.py index 50abb7919ce..1af383a925f 100644 --- a/tests/ported_static/stRandom/test_random_statetest15.py +++ b/tests/ported_static/stRandom/test_random_statetest15.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest15Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest15( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest15.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -96,7 +88,6 @@ def test_random_statetest15( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000016f436af043189b6197733280a2f1f038" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x598426AC, ) diff --git a/tests/ported_static/stRandom/test_random_statetest155.py b/tests/ported_static/stRandom/test_random_statetest155.py index 4a3ba8c177b..39f2cc60107 100644 --- a/tests/ported_static/stRandom/test_random_statetest155.py +++ b/tests/ported_static/stRandom/test_random_statetest155.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest155Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest155( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest155.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -99,7 +91,6 @@ def test_random_statetest155( data=Bytes( "457f00000000000000000000000000000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000006f3494f39b6ca29473a1995803089101" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x228B052C, ) diff --git a/tests/ported_static/stRandom/test_random_statetest156.py b/tests/ported_static/stRandom/test_random_statetest156.py index dd4b11a6002..ff34c557bf2 100644 --- a/tests/ported_static/stRandom/test_random_statetest156.py +++ b/tests/ported_static/stRandom/test_random_statetest156.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest156Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest156( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest156.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -93,7 +85,6 @@ def test_random_statetest156( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3506f813982583141966b389c159aa48b3a88" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x7FFE6411, ) diff --git a/tests/ported_static/stRandom/test_random_statetest158.py b/tests/ported_static/stRandom/test_random_statetest158.py index 39fa2b5981b..472af6f1e99 100644 --- a/tests/ported_static/stRandom/test_random_statetest158.py +++ b/tests/ported_static/stRandom/test_random_statetest158.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest158Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest158( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest158.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -99,7 +91,6 @@ def test_random_statetest158( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000100000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000100000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe4350" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x383BFC76, ) diff --git a/tests/ported_static/stRandom/test_random_statetest161.py b/tests/ported_static/stRandom/test_random_statetest161.py index 7dc41e77504..0c8527b4b51 100644 --- a/tests/ported_static/stRandom/test_random_statetest161.py +++ b/tests/ported_static/stRandom/test_random_statetest161.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest161Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest161( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest161.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -94,7 +86,6 @@ def test_random_statetest161( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000000000000000000000000000000000000000c350437f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000000000000000000000000000000000000000000001416f458a458076526052650a418c9b40863c" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x2D2470B1, ) diff --git a/tests/ported_static/stRandom/test_random_statetest162.py b/tests/ported_static/stRandom/test_random_statetest162.py index ecc14ec7fc5..17aadbad253 100644 --- a/tests/ported_static/stRandom/test_random_statetest162.py +++ b/tests/ported_static/stRandom/test_random_statetest162.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest162Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest162( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest162.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -98,7 +90,6 @@ def test_random_statetest162( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff6f355a7f614497339e3b63878b369804" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x2B36B8AD, ) diff --git a/tests/ported_static/stRandom/test_random_statetest166.py b/tests/ported_static/stRandom/test_random_statetest166.py index b472e7ddf9e..4f4887e202d 100644 --- a/tests/ported_static/stRandom/test_random_statetest166.py +++ b/tests/ported_static/stRandom/test_random_statetest166.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest166Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest166( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest166.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -96,7 +88,6 @@ def test_random_statetest166( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff817f0000000000000000000000010000000000000000000000000000000000000000417f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000000000000000000000000000000000000000c350456f8eb7099d9f160532785143c5937e18" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x603D8563, ) diff --git a/tests/ported_static/stRandom/test_random_statetest167.py b/tests/ported_static/stRandom/test_random_statetest167.py index c1a689820af..42e16523102 100644 --- a/tests/ported_static/stRandom/test_random_statetest167.py +++ b/tests/ported_static/stRandom/test_random_statetest167.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest167Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest167( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest167.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -98,7 +90,6 @@ def test_random_statetest167( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000001437f0000000000000000000000000000000000000000000000000000000000000001027f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6fa00b875630178a439384941395369e" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x198F60AB, ) diff --git a/tests/ported_static/stRandom/test_random_statetest169.py b/tests/ported_static/stRandom/test_random_statetest169.py index 3d2d075902f..7d29bdc517e 100644 --- a/tests/ported_static/stRandom/test_random_statetest169.py +++ b/tests/ported_static/stRandom/test_random_statetest169.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest169Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest169( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest169.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -100,7 +92,6 @@ def test_random_statetest169( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe447f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x33C6014B, ) diff --git a/tests/ported_static/stRandom/test_random_statetest175.py b/tests/ported_static/stRandom/test_random_statetest175.py index 63c7e71548a..7bf972d1825 100644 --- a/tests/ported_static/stRandom/test_random_statetest175.py +++ b/tests/ported_static/stRandom/test_random_statetest175.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest175Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest175( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest175.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -96,7 +88,6 @@ def test_random_statetest175( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3506f6985f2837e09689844171a0235833c" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x7F8A09B6, ) diff --git a/tests/ported_static/stRandom/test_random_statetest179.py b/tests/ported_static/stRandom/test_random_statetest179.py index 7fcc568ea78..442a9081ff6 100644 --- a/tests/ported_static/stRandom/test_random_statetest179.py +++ b/tests/ported_static/stRandom/test_random_statetest179.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest179Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest179( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest179.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -102,7 +94,6 @@ def test_random_statetest179( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3506f515480126a50a173506e0667621292" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x5E6CF4EC, ) diff --git a/tests/ported_static/stRandom/test_random_statetest180.py b/tests/ported_static/stRandom/test_random_statetest180.py index fb9e36a7218..6dbdb743b71 100644 --- a/tests/ported_static/stRandom/test_random_statetest180.py +++ b/tests/ported_static/stRandom/test_random_statetest180.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest180Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest180( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest180.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -95,7 +87,6 @@ def test_random_statetest180( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007f0000000000000000000000000000000000000000000000000000000000000001447f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e796f11576b693c128a9e0820609c050a219d" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x21C3963D, ) diff --git a/tests/ported_static/stRandom/test_random_statetest183.py b/tests/ported_static/stRandom/test_random_statetest183.py index 045b6188782..9338f4e8d1e 100644 --- a/tests/ported_static/stRandom/test_random_statetest183.py +++ b/tests/ported_static/stRandom/test_random_statetest183.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest183Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest183( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest183.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -94,7 +86,6 @@ def test_random_statetest183( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000000000000000000000000000000000000000c3507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79436f4134547075687854849d7b64658630" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x451629C1, ) diff --git a/tests/ported_static/stRandom/test_random_statetest184.py b/tests/ported_static/stRandom/test_random_statetest184.py index 33600950e0f..d6ceed7597b 100644 --- a/tests/ported_static/stRandom/test_random_statetest184.py +++ b/tests/ported_static/stRandom/test_random_statetest184.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest184Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest184( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest184.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x6D6E40885310545835A5B582DBC23EF026404BDA) addr = Address(0xF377657E450772B703A269E12BB487FF421A5C6D) sender = EOA( @@ -85,7 +77,6 @@ def test_random_statetest184( sender=sender, to=target, data=Bytes("64dd3e4e84676723342c1dfaf9af4ef3"), - gas_limit=tx_gas_limit, value=0x6D1DD024, gas_price=28, ) diff --git a/tests/ported_static/stRandom/test_random_statetest187.py b/tests/ported_static/stRandom/test_random_statetest187.py index b1e665c5de0..419f21abb3d 100644 --- a/tests/ported_static/stRandom/test_random_statetest187.py +++ b/tests/ported_static/stRandom/test_random_statetest187.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest187Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest187( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest187.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -94,7 +86,6 @@ def test_random_statetest187( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff457f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000006f75988036a0562096036b04518877199d" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x372E4882, ) diff --git a/tests/ported_static/stRandom/test_random_statetest188.py b/tests/ported_static/stRandom/test_random_statetest188.py index 9eb3c7129d5..49fc6ab0543 100644 --- a/tests/ported_static/stRandom/test_random_statetest188.py +++ b/tests/ported_static/stRandom/test_random_statetest188.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest188Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest188( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest188.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -99,7 +91,6 @@ def test_random_statetest188( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff817f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4286687859f38379718794" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x49195164, ) diff --git a/tests/ported_static/stRandom/test_random_statetest19.py b/tests/ported_static/stRandom/test_random_statetest19.py index afff390d5c9..51195e1cf08 100644 --- a/tests/ported_static/stRandom/test_random_statetest19.py +++ b/tests/ported_static/stRandom/test_random_statetest19.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest19Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest19( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest19.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -100,7 +92,6 @@ def test_random_statetest19( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe3a7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe417f0000000000000000000000000000000000000000000000000000000000000001587f000000000000000000000000000000000000000000000000000000000000c3506fff59876660063b7c8df1ff088a8414" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x1DCD74DE, ) diff --git a/tests/ported_static/stRandom/test_random_statetest191.py b/tests/ported_static/stRandom/test_random_statetest191.py index 1b71f2b90ed..9bf77cde723 100644 --- a/tests/ported_static/stRandom/test_random_statetest191.py +++ b/tests/ported_static/stRandom/test_random_statetest191.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest191Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest191( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest191.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -102,7 +94,6 @@ def test_random_statetest191( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000010000000000000000000000000000000000000000447ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff6f678f0443457084700b645760018a10" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x17008747, ) diff --git a/tests/ported_static/stRandom/test_random_statetest192.py b/tests/ported_static/stRandom/test_random_statetest192.py index 8f08b1019cc..93ccf539e03 100644 --- a/tests/ported_static/stRandom/test_random_statetest192.py +++ b/tests/ported_static/stRandom/test_random_statetest192.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest192Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest192( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest192.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -101,7 +93,6 @@ def test_random_statetest192( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff347f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe04" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x45A5235D, ) diff --git a/tests/ported_static/stRandom/test_random_statetest194.py b/tests/ported_static/stRandom/test_random_statetest194.py index bfe9ecf28d6..3d3b1205b0e 100644 --- a/tests/ported_static/stRandom/test_random_statetest194.py +++ b/tests/ported_static/stRandom/test_random_statetest194.py @@ -4,10 +4,8 @@ Ported from: state_tests/stRandom/randomStatetest194Filler.json -@manually-enhanced: Do not overwrite. `gas_limit` raised on Amsterdam -to cover EIP-8037 state-gas spill. Pre-EIP-8037 keeps the original -100 000. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -20,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -35,14 +32,8 @@ def test_random_statetest194( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest194.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -95,7 +86,6 @@ def test_random_statetest194( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff097f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x31582CFB, ) diff --git a/tests/ported_static/stRandom/test_random_statetest195.py b/tests/ported_static/stRandom/test_random_statetest195.py index 823838d4c9c..6f31ee1c784 100644 --- a/tests/ported_static/stRandom/test_random_statetest195.py +++ b/tests/ported_static/stRandom/test_random_statetest195.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest195Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest195( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest195.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -95,7 +87,6 @@ def test_random_statetest195( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000000000000000000000000000000000000000c350417fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff097f0000000000000000000000010000000000000000000000000000000000000000" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x1252A41F, ) diff --git a/tests/ported_static/stRandom/test_random_statetest196.py b/tests/ported_static/stRandom/test_random_statetest196.py index 9b8e822d671..a737f5564fa 100644 --- a/tests/ported_static/stRandom/test_random_statetest196.py +++ b/tests/ported_static/stRandom/test_random_statetest196.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest196Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -18,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -33,14 +31,8 @@ def test_random_statetest196( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest196.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -97,7 +89,6 @@ def test_random_statetest196( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe447f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000003703659c5b3a6d7b9a935436" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x2819E4BE, ) diff --git a/tests/ported_static/stRandom/test_random_statetest2.py b/tests/ported_static/stRandom/test_random_statetest2.py index 8120e34f029..a93ef6e6551 100644 --- a/tests/ported_static/stRandom/test_random_statetest2.py +++ b/tests/ported_static/stRandom/test_random_statetest2.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest2Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest2( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest2.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -96,7 +88,6 @@ def test_random_statetest2( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e7958437f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000016f3412a47c889e8da06a04049f049888" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x7C34BB45, ) diff --git a/tests/ported_static/stRandom/test_random_statetest200.py b/tests/ported_static/stRandom/test_random_statetest200.py index ea5bfad15e3..03c3334cfed 100644 --- a/tests/ported_static/stRandom/test_random_statetest200.py +++ b/tests/ported_static/stRandom/test_random_statetest200.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest200Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest200( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest200.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -92,7 +84,6 @@ def test_random_statetest200( data=Bytes( "437f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79346f42051af2a24050039e9d3a678b028a0a80" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x3F51031D, ) diff --git a/tests/ported_static/stRandom/test_random_statetest202.py b/tests/ported_static/stRandom/test_random_statetest202.py index e7dc23c0fd8..0dd454a967d 100644 --- a/tests/ported_static/stRandom/test_random_statetest202.py +++ b/tests/ported_static/stRandom/test_random_statetest202.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest202Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -18,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -33,14 +31,8 @@ def test_random_statetest202( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest202.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -83,7 +75,6 @@ def test_random_statetest202( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507f0000000000000000000000000000000000000000000000000000000000000000557f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff6750a3190486f0" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x3158E7CD, ) diff --git a/tests/ported_static/stRandom/test_random_statetest204.py b/tests/ported_static/stRandom/test_random_statetest204.py index 80779e64297..de30d9e8c14 100644 --- a/tests/ported_static/stRandom/test_random_statetest204.py +++ b/tests/ported_static/stRandom/test_random_statetest204.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest204Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest204( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest204.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -98,7 +90,6 @@ def test_random_statetest204( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000017f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff0982" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x763F0C95, ) diff --git a/tests/ported_static/stRandom/test_random_statetest206.py b/tests/ported_static/stRandom/test_random_statetest206.py index 39a87632dd4..4ef692abb3e 100644 --- a/tests/ported_static/stRandom/test_random_statetest206.py +++ b/tests/ported_static/stRandom/test_random_statetest206.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest206Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest206( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest206.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -98,7 +90,6 @@ def test_random_statetest206( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79427f0000000000000000000000000000000000000000000000000000000000000001427f000000000000000000000000000000000000000000000000000000000000c3507f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e796f45736d8e806138378d62087320313c" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x7CA24D6F, ) diff --git a/tests/ported_static/stRandom/test_random_statetest208.py b/tests/ported_static/stRandom/test_random_statetest208.py index 2ebdf20f398..ff24015db4a 100644 --- a/tests/ported_static/stRandom/test_random_statetest208.py +++ b/tests/ported_static/stRandom/test_random_statetest208.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest208Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest208( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest208.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -100,7 +92,6 @@ def test_random_statetest208( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff09" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x7FD561B3, ) diff --git a/tests/ported_static/stRandom/test_random_statetest210.py b/tests/ported_static/stRandom/test_random_statetest210.py index 5ec0f49f96e..4ed821eb58d 100644 --- a/tests/ported_static/stRandom/test_random_statetest210.py +++ b/tests/ported_static/stRandom/test_random_statetest210.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest210Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest210( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest210.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -98,7 +90,6 @@ def test_random_statetest210( data=Bytes( "457f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff427ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff09" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x6A1B0D6A, ) diff --git a/tests/ported_static/stRandom/test_random_statetest214.py b/tests/ported_static/stRandom/test_random_statetest214.py index f9f806f4ee1..9bcf17ad20e 100644 --- a/tests/ported_static/stRandom/test_random_statetest214.py +++ b/tests/ported_static/stRandom/test_random_statetest214.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest214Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest214( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest214.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -97,7 +89,6 @@ def test_random_statetest214( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff150a6f7b056b335a15a48d7b8841163a503963" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x7D4430A5, ) diff --git a/tests/ported_static/stRandom/test_random_statetest215.py b/tests/ported_static/stRandom/test_random_statetest215.py index 270221359f5..224dfa986cd 100644 --- a/tests/ported_static/stRandom/test_random_statetest215.py +++ b/tests/ported_static/stRandom/test_random_statetest215.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest215Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest215( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest215.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -93,7 +85,6 @@ def test_random_statetest215( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff446f728f4f1065583139780a981510173b9c" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x306B9921, ) diff --git a/tests/ported_static/stRandom/test_random_statetest216.py b/tests/ported_static/stRandom/test_random_statetest216.py index ae66319835f..17993db1301 100644 --- a/tests/ported_static/stRandom/test_random_statetest216.py +++ b/tests/ported_static/stRandom/test_random_statetest216.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest216Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest216( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest216.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -97,7 +89,6 @@ def test_random_statetest216( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000000000000000000000000000000000000000c350447f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000000000000000000000000000000000000000c3507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000000000000000000000000000000000000000c3506d766d67fe078532089913064494" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x288181DD, ) diff --git a/tests/ported_static/stRandom/test_random_statetest217.py b/tests/ported_static/stRandom/test_random_statetest217.py index e451e45b057..84e77da47dc 100644 --- a/tests/ported_static/stRandom/test_random_statetest217.py +++ b/tests/ported_static/stRandom/test_random_statetest217.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest217Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -18,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -33,14 +31,8 @@ def test_random_statetest217( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest217.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -89,7 +81,6 @@ def test_random_statetest217( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000000000000000000000000000000000000000000001377f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c350" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x1C326D78, ) diff --git a/tests/ported_static/stRandom/test_random_statetest219.py b/tests/ported_static/stRandom/test_random_statetest219.py index c1566abbec1..d9c3212ba3f 100644 --- a/tests/ported_static/stRandom/test_random_statetest219.py +++ b/tests/ported_static/stRandom/test_random_statetest219.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest219Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest219( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest219.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -95,7 +87,6 @@ def test_random_statetest219( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff437f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000000000000000000000000000000000000000c3506f6253443a4104027144577f33998320" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x46404EA, ) diff --git a/tests/ported_static/stRandom/test_random_statetest220.py b/tests/ported_static/stRandom/test_random_statetest220.py index c80332b5130..e4d3fc20fc9 100644 --- a/tests/ported_static/stRandom/test_random_statetest220.py +++ b/tests/ported_static/stRandom/test_random_statetest220.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest220Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest220( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest220.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -94,7 +86,6 @@ def test_random_statetest220( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000016f420380a03c4282a3540a1a333a843a" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0xC5BFC9F, ) diff --git a/tests/ported_static/stRandom/test_random_statetest221.py b/tests/ported_static/stRandom/test_random_statetest221.py index 41805cb2db0..c0e3ad64a19 100644 --- a/tests/ported_static/stRandom/test_random_statetest221.py +++ b/tests/ported_static/stRandom/test_random_statetest221.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest221Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest221( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest221.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -96,7 +88,6 @@ def test_random_statetest221( data=Bytes( "457f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000100000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e796f977789947e197f828151867a73771a" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x6F0651EF, ) diff --git a/tests/ported_static/stRandom/test_random_statetest222.py b/tests/ported_static/stRandom/test_random_statetest222.py index d967defdcf6..0ffd77403cd 100644 --- a/tests/ported_static/stRandom/test_random_statetest222.py +++ b/tests/ported_static/stRandom/test_random_statetest222.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest222Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest222( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest222.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -95,7 +87,6 @@ def test_random_statetest222( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000001000000000000000000000000000000000000000043397f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c35081" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x66F96B9F, ) diff --git a/tests/ported_static/stRandom/test_random_statetest225.py b/tests/ported_static/stRandom/test_random_statetest225.py index a29e2414eae..9c3fe80da55 100644 --- a/tests/ported_static/stRandom/test_random_statetest225.py +++ b/tests/ported_static/stRandom/test_random_statetest225.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest225Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest225( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest225.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -104,7 +96,6 @@ def test_random_statetest225( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff506f69786c858e0703566f95f89931119019" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x9859445, ) diff --git a/tests/ported_static/stRandom/test_random_statetest227.py b/tests/ported_static/stRandom/test_random_statetest227.py index 152f35cb0e6..7600ea491f4 100644 --- a/tests/ported_static/stRandom/test_random_statetest227.py +++ b/tests/ported_static/stRandom/test_random_statetest227.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest227Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest227( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest227.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -94,7 +86,6 @@ def test_random_statetest227( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e796f108fa27475689e44993a528752a1523359" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x462204C5, ) diff --git a/tests/ported_static/stRandom/test_random_statetest23.py b/tests/ported_static/stRandom/test_random_statetest23.py index dbeea4e8b60..32dbd8624d8 100644 --- a/tests/ported_static/stRandom/test_random_statetest23.py +++ b/tests/ported_static/stRandom/test_random_statetest23.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest23Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest23( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest23.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -95,7 +87,6 @@ def test_random_statetest23( data=Bytes( "7f0000000000000000000000000000000000000000000000000000000000000001427f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6f89418c1076f1544315601489386c91" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x27CD2E4B, ) diff --git a/tests/ported_static/stRandom/test_random_statetest231.py b/tests/ported_static/stRandom/test_random_statetest231.py index 0888a359ceb..cbe2df6a177 100644 --- a/tests/ported_static/stRandom/test_random_statetest231.py +++ b/tests/ported_static/stRandom/test_random_statetest231.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest231Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest231( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest231.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -96,7 +88,6 @@ def test_random_statetest231( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000000000000000000000000000000000000000c3507f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e796f7b98a491727a089df3365353329e80" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x4BCA4C7E, ) diff --git a/tests/ported_static/stRandom/test_random_statetest238.py b/tests/ported_static/stRandom/test_random_statetest238.py index 9329c88c2ab..e3af7722da9 100644 --- a/tests/ported_static/stRandom/test_random_statetest238.py +++ b/tests/ported_static/stRandom/test_random_statetest238.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest238Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest238( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest238.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -98,7 +90,6 @@ def test_random_statetest238( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff307fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff6f7c748813587e990566719934f342316c" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0xFF8455C, ) diff --git a/tests/ported_static/stRandom/test_random_statetest242.py b/tests/ported_static/stRandom/test_random_statetest242.py index 401917748c4..c308099cb1e 100644 --- a/tests/ported_static/stRandom/test_random_statetest242.py +++ b/tests/ported_static/stRandom/test_random_statetest242.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest242Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest242( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest242.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -99,7 +91,6 @@ def test_random_statetest242( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe427f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x7DCB2C64, ) diff --git a/tests/ported_static/stRandom/test_random_statetest243.py b/tests/ported_static/stRandom/test_random_statetest243.py index 468c36864ba..f02622d3348 100644 --- a/tests/ported_static/stRandom/test_random_statetest243.py +++ b/tests/ported_static/stRandom/test_random_statetest243.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest243Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest243( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest243.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -90,7 +82,6 @@ def test_random_statetest243( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3506f424544664076406862554558668490" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x49903AC, ) diff --git a/tests/ported_static/stRandom/test_random_statetest247.py b/tests/ported_static/stRandom/test_random_statetest247.py index 474148b54f8..8d6b7448437 100644 --- a/tests/ported_static/stRandom/test_random_statetest247.py +++ b/tests/ported_static/stRandom/test_random_statetest247.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest247Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest247( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest247.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -99,7 +91,6 @@ def test_random_statetest247( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe04" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x43B4ED79, ) diff --git a/tests/ported_static/stRandom/test_random_statetest248.py b/tests/ported_static/stRandom/test_random_statetest248.py index 36e4656fdca..609a26c4eef 100644 --- a/tests/ported_static/stRandom/test_random_statetest248.py +++ b/tests/ported_static/stRandom/test_random_statetest248.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest248Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -18,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -33,14 +31,8 @@ def test_random_statetest248( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest248.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -99,7 +91,6 @@ def test_random_statetest248( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000000000000000000000000000000000000000000001427f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8636f25990" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x20C4D1A6, ) diff --git a/tests/ported_static/stRandom/test_random_statetest249.py b/tests/ported_static/stRandom/test_random_statetest249.py index c4eb673ae28..15938c8d5f1 100644 --- a/tests/ported_static/stRandom/test_random_statetest249.py +++ b/tests/ported_static/stRandom/test_random_statetest249.py @@ -4,10 +4,8 @@ Ported from: state_tests/stRandom/randomStatetest249Filler.json -@manually-enhanced: Do not overwrite. `gas_limit` raised on Amsterdam -to cover EIP-8037 state-gas spill. Pre-EIP-8037 keeps the original -100 000. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -20,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -35,14 +32,8 @@ def test_random_statetest249( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest249.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -95,7 +86,6 @@ def test_random_statetest249( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000012807f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000039" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x6F8F420B, ) diff --git a/tests/ported_static/stRandom/test_random_statetest254.py b/tests/ported_static/stRandom/test_random_statetest254.py index 972ba4c86b7..32ea8ec1c88 100644 --- a/tests/ported_static/stRandom/test_random_statetest254.py +++ b/tests/ported_static/stRandom/test_random_statetest254.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest254Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest254( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest254.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -99,7 +91,6 @@ def test_random_statetest254( data=Bytes( "7f000000000000000000000001000000000000000000000000000000000000000041417f0000000000000000000000000000000000000000000000000000000000000001447fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000000000000000000000000000000000000000c3506f059b6b83f294740688598c52195a92" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x5C13C2FF, ) diff --git a/tests/ported_static/stRandom/test_random_statetest259.py b/tests/ported_static/stRandom/test_random_statetest259.py index b291a673bfb..ca33ee8be7c 100644 --- a/tests/ported_static/stRandom/test_random_statetest259.py +++ b/tests/ported_static/stRandom/test_random_statetest259.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest259Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest259( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest259.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -97,7 +89,6 @@ def test_random_statetest259( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000100000000000000000000000000000000000000007f0000000000000000000000000000000000000000000000000000000000000001587fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3506f04831adc0812f09544927407900709" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0xEF4B167, ) diff --git a/tests/ported_static/stRandom/test_random_statetest264.py b/tests/ported_static/stRandom/test_random_statetest264.py index 1ed859df59d..09af5cff8f7 100644 --- a/tests/ported_static/stRandom/test_random_statetest264.py +++ b/tests/ported_static/stRandom/test_random_statetest264.py @@ -4,10 +4,8 @@ Ported from: state_tests/stRandom/randomStatetest264Filler.json -@manually-enhanced: Do not overwrite. `gas_limit` raised on Amsterdam -to cover EIP-8037 state-gas spill. Pre-EIP-8037 keeps the original -100 000. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -20,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -35,14 +32,8 @@ def test_random_statetest264( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest264.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -96,7 +87,6 @@ def test_random_statetest264( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe427f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff09" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x457C78F7, ) diff --git a/tests/ported_static/stRandom/test_random_statetest267.py b/tests/ported_static/stRandom/test_random_statetest267.py index 0179d9e62c9..db3e8860e21 100644 --- a/tests/ported_static/stRandom/test_random_statetest267.py +++ b/tests/ported_static/stRandom/test_random_statetest267.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest267Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest267( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest267.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x945304EB96065B2A98B57A48A06AE28D285A71B5) contract_0 = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87) sender = EOA( @@ -98,7 +90,6 @@ def test_random_statetest267( data=Bytes( "447f000000000000000000000000000000000000000000000000000000000000c3507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b57f00000000000000000000000000000000000000000000000000000000000000007e7f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b5a132776d398e3b7c14686a07346f" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0xF5106AE, ) diff --git a/tests/ported_static/stRandom/test_random_statetest268.py b/tests/ported_static/stRandom/test_random_statetest268.py index eb8aa8ad175..713eda7f360 100644 --- a/tests/ported_static/stRandom/test_random_statetest268.py +++ b/tests/ported_static/stRandom/test_random_statetest268.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest268Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest268( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest268.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -100,7 +92,6 @@ def test_random_statetest268( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000000000000000000000000000000000000000c3507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000016f7466f0a0733d863263934063409442" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x2360D94A, ) diff --git a/tests/ported_static/stRandom/test_random_statetest269.py b/tests/ported_static/stRandom/test_random_statetest269.py index 1bd356a4c89..54bbf23eb99 100644 --- a/tests/ported_static/stRandom/test_random_statetest269.py +++ b/tests/ported_static/stRandom/test_random_statetest269.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest269Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest269( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest269.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -100,7 +92,6 @@ def test_random_statetest269( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe6676029968ffa27d04" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x49E002C2, ) diff --git a/tests/ported_static/stRandom/test_random_statetest27.py b/tests/ported_static/stRandom/test_random_statetest27.py index e8a29a8e786..b9d6f411280 100644 --- a/tests/ported_static/stRandom/test_random_statetest27.py +++ b/tests/ported_static/stRandom/test_random_statetest27.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest27Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest27( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest27.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -98,7 +90,6 @@ def test_random_statetest27( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000001000000000000000000000000000000000000000009" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x204D3E8E, ) diff --git a/tests/ported_static/stRandom/test_random_statetest276.py b/tests/ported_static/stRandom/test_random_statetest276.py index 40c9a6e56c9..6daf9f9e162 100644 --- a/tests/ported_static/stRandom/test_random_statetest276.py +++ b/tests/ported_static/stRandom/test_random_statetest276.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest276Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest276( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest276.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -99,7 +91,6 @@ def test_random_statetest276( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6f4382349f7b370589141a31f39741a4f2" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x3D3366FA, ) diff --git a/tests/ported_static/stRandom/test_random_statetest278.py b/tests/ported_static/stRandom/test_random_statetest278.py index d66b5664484..e0d9cbaea9c 100644 --- a/tests/ported_static/stRandom/test_random_statetest278.py +++ b/tests/ported_static/stRandom/test_random_statetest278.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest278Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest278( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest278.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -97,7 +89,6 @@ def test_random_statetest278( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000000000000000000000000000000000000000000001377f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x5E02EC7F, ) diff --git a/tests/ported_static/stRandom/test_random_statetest279.py b/tests/ported_static/stRandom/test_random_statetest279.py index 9ebcff5c884..38c7ad3949a 100644 --- a/tests/ported_static/stRandom/test_random_statetest279.py +++ b/tests/ported_static/stRandom/test_random_statetest279.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest279Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -18,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -33,14 +31,8 @@ def test_random_statetest279( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest279.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -95,7 +87,6 @@ def test_random_statetest279( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000010000000000000000000000000000000000000000947f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x4E91B038, ) diff --git a/tests/ported_static/stRandom/test_random_statetest28.py b/tests/ported_static/stRandom/test_random_statetest28.py index 0454b3f339a..65770e55b95 100644 --- a/tests/ported_static/stRandom/test_random_statetest28.py +++ b/tests/ported_static/stRandom/test_random_statetest28.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest28Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest28( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest28.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -96,7 +88,6 @@ def test_random_statetest28( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff417f00000000000000000000000100000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e796f38129d68939a19a2697172926f6a673630" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x51AF41E7, ) diff --git a/tests/ported_static/stRandom/test_random_statetest280.py b/tests/ported_static/stRandom/test_random_statetest280.py index b0d276d3d1d..b1ba224f02c 100644 --- a/tests/ported_static/stRandom/test_random_statetest280.py +++ b/tests/ported_static/stRandom/test_random_statetest280.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest280Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest280( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest280.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -93,7 +85,6 @@ def test_random_statetest280( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000000143507f000000000000000000000000000000000000000000000000000000000000c350417f00000000000000000000000000000000000000000000000000000000000000006f423b3c407e7c6f16718668738d193cf2" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x303CDC5A, ) diff --git a/tests/ported_static/stRandom/test_random_statetest281.py b/tests/ported_static/stRandom/test_random_statetest281.py index acb3ffcc38f..a1b50ea385d 100644 --- a/tests/ported_static/stRandom/test_random_statetest281.py +++ b/tests/ported_static/stRandom/test_random_statetest281.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest281Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest281( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest281.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -93,7 +85,6 @@ def test_random_statetest281( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79457f000000000000000000000000000000000000000000000000000000000000c350417f00000000000000000000000000000000000000000000000000000000000000016f649a7a3457645670a27fa170639718a2" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x662E647C, ) diff --git a/tests/ported_static/stRandom/test_random_statetest283.py b/tests/ported_static/stRandom/test_random_statetest283.py index aee36b2148e..d68775daaad 100644 --- a/tests/ported_static/stRandom/test_random_statetest283.py +++ b/tests/ported_static/stRandom/test_random_statetest283.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest283Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest283( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest283.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -101,7 +93,6 @@ def test_random_statetest283( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff457fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000139" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x5F83295F, ) diff --git a/tests/ported_static/stRandom/test_random_statetest29.py b/tests/ported_static/stRandom/test_random_statetest29.py index cdc40338649..82136472ece 100644 --- a/tests/ported_static/stRandom/test_random_statetest29.py +++ b/tests/ported_static/stRandom/test_random_statetest29.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest29Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest29( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest29.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -98,7 +90,6 @@ def test_random_statetest29( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff087fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff09" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x214AB1F3, ) diff --git a/tests/ported_static/stRandom/test_random_statetest290.py b/tests/ported_static/stRandom/test_random_statetest290.py index 5c2372d1a16..3a308777139 100644 --- a/tests/ported_static/stRandom/test_random_statetest290.py +++ b/tests/ported_static/stRandom/test_random_statetest290.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest290Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest290( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest290.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -99,7 +91,6 @@ def test_random_statetest290( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe8309" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x6D5253D6, ) diff --git a/tests/ported_static/stRandom/test_random_statetest297.py b/tests/ported_static/stRandom/test_random_statetest297.py index 47b9b555a73..13ce84df817 100644 --- a/tests/ported_static/stRandom/test_random_statetest297.py +++ b/tests/ported_static/stRandom/test_random_statetest297.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest297Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest297( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest297.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -99,7 +91,6 @@ def test_random_statetest297( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79437f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe426f91085661509214157d9c8a77758518" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x7C61878A, ) diff --git a/tests/ported_static/stRandom/test_random_statetest298.py b/tests/ported_static/stRandom/test_random_statetest298.py index a6e343ab2f5..d2feafe31db 100644 --- a/tests/ported_static/stRandom/test_random_statetest298.py +++ b/tests/ported_static/stRandom/test_random_statetest298.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest298Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest298( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest298.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -94,7 +86,6 @@ def test_random_statetest298( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3500a6f7c542006528b69ff3a7a3a0401613c" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x7E0F660B, ) diff --git a/tests/ported_static/stRandom/test_random_statetest299.py b/tests/ported_static/stRandom/test_random_statetest299.py index 1583f1bf72c..d897e342d9d 100644 --- a/tests/ported_static/stRandom/test_random_statetest299.py +++ b/tests/ported_static/stRandom/test_random_statetest299.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest299Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest299( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest299.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -104,7 +96,6 @@ def test_random_statetest299( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe6f540813697adf70f20906389d128bf0" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x1A47B134, ) diff --git a/tests/ported_static/stRandom/test_random_statetest3.py b/tests/ported_static/stRandom/test_random_statetest3.py index 53632e80c19..7f548876e1d 100644 --- a/tests/ported_static/stRandom/test_random_statetest3.py +++ b/tests/ported_static/stRandom/test_random_statetest3.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest3Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -18,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -33,14 +31,8 @@ def test_random_statetest3( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest3.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -89,7 +81,6 @@ def test_random_statetest3( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe427f000000000000000000000000000000000000000000000000000000000000c35041" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x5EAA223F, ) diff --git a/tests/ported_static/stRandom/test_random_statetest301.py b/tests/ported_static/stRandom/test_random_statetest301.py index 179ae4337a2..3de87c51f0e 100644 --- a/tests/ported_static/stRandom/test_random_statetest301.py +++ b/tests/ported_static/stRandom/test_random_statetest301.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest301Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest301( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest301.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -102,7 +94,6 @@ def test_random_statetest301( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000003784946a737aa092f1975664518a" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x1340F9CE, ) diff --git a/tests/ported_static/stRandom/test_random_statetest305.py b/tests/ported_static/stRandom/test_random_statetest305.py index cae2823329a..0502b11cc8c 100644 --- a/tests/ported_static/stRandom/test_random_statetest305.py +++ b/tests/ported_static/stRandom/test_random_statetest305.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest305Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest305( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest305.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -98,7 +90,6 @@ def test_random_statetest305( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000100000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000006f606e048240069c409313318736200b" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0xD00D79E, ) diff --git a/tests/ported_static/stRandom/test_random_statetest310.py b/tests/ported_static/stRandom/test_random_statetest310.py index f1bf7c47488..29af9f3952f 100644 --- a/tests/ported_static/stRandom/test_random_statetest310.py +++ b/tests/ported_static/stRandom/test_random_statetest310.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest310Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest310( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest310.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -102,7 +94,6 @@ def test_random_statetest310( data=Bytes( "44587fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff59907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1a37160b6a650645597c796e9c9795" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x1B76ED9D, ) diff --git a/tests/ported_static/stRandom/test_random_statetest311.py b/tests/ported_static/stRandom/test_random_statetest311.py index 4b8c5b450b3..261c98d4d66 100644 --- a/tests/ported_static/stRandom/test_random_statetest311.py +++ b/tests/ported_static/stRandom/test_random_statetest311.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest311Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest311( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest311.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -97,7 +89,6 @@ def test_random_statetest311( data=Bytes( "447f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3506f13971264a1197d72ff18971902387b" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x5CD0515, ) diff --git a/tests/ported_static/stRandom/test_random_statetest315.py b/tests/ported_static/stRandom/test_random_statetest315.py index aec15d52e45..ec9cf4899b1 100644 --- a/tests/ported_static/stRandom/test_random_statetest315.py +++ b/tests/ported_static/stRandom/test_random_statetest315.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest315Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest315( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest315.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -103,7 +95,6 @@ def test_random_statetest315( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff067f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe6f98516a388683755669892b8b371957" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x13D8A45E, ) diff --git a/tests/ported_static/stRandom/test_random_statetest316.py b/tests/ported_static/stRandom/test_random_statetest316.py index c2d8d69d8d7..43f5ba20cf8 100644 --- a/tests/ported_static/stRandom/test_random_statetest316.py +++ b/tests/ported_static/stRandom/test_random_statetest316.py @@ -4,10 +4,8 @@ Ported from: state_tests/stRandom/randomStatetest316Filler.json -@manually-enhanced: Do not overwrite. `gas_limit` raised on Amsterdam -to cover EIP-8037 state-gas spill. Pre-EIP-8037 keeps the original -100 000. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -20,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -35,14 +32,8 @@ def test_random_statetest316( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest316.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -98,7 +89,6 @@ def test_random_statetest316( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x3A0D0C77, ) diff --git a/tests/ported_static/stRandom/test_random_statetest318.py b/tests/ported_static/stRandom/test_random_statetest318.py index 6a493725f4d..86f6d74130e 100644 --- a/tests/ported_static/stRandom/test_random_statetest318.py +++ b/tests/ported_static/stRandom/test_random_statetest318.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest318Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest318( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest318.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -100,7 +92,6 @@ def test_random_statetest318( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c350457f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000000000000000000000000000000000000000c3506f8206a30a83887e5a3164667796308d" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x23F9C6F7, ) diff --git a/tests/ported_static/stRandom/test_random_statetest322.py b/tests/ported_static/stRandom/test_random_statetest322.py index f72ad9afd42..1d6052f0ddf 100644 --- a/tests/ported_static/stRandom/test_random_statetest322.py +++ b/tests/ported_static/stRandom/test_random_statetest322.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest322Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest322( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest322.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -97,7 +89,6 @@ def test_random_statetest322( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f0000000000000000000000000000000000000000000000000000000000000000427f000000000000000000000000000000000000000000000000000000000000c3506f1206060508840294304101a3128f34" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x312238E4, ) diff --git a/tests/ported_static/stRandom/test_random_statetest325.py b/tests/ported_static/stRandom/test_random_statetest325.py index 862b46b526c..0f3b869e9f5 100644 --- a/tests/ported_static/stRandom/test_random_statetest325.py +++ b/tests/ported_static/stRandom/test_random_statetest325.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest325Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest325( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest325.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -103,7 +95,6 @@ def test_random_statetest325( data=Bytes( "437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff427f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff427fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe810903" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x1B449945, ) diff --git a/tests/ported_static/stRandom/test_random_statetest329.py b/tests/ported_static/stRandom/test_random_statetest329.py index c2bbd0aa10a..2f2a996b850 100644 --- a/tests/ported_static/stRandom/test_random_statetest329.py +++ b/tests/ported_static/stRandom/test_random_statetest329.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest329Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest329( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest329.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -98,7 +90,6 @@ def test_random_statetest329( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff426fa48d775458574133769c8b750207ff" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x5B5A0B6C, ) diff --git a/tests/ported_static/stRandom/test_random_statetest332.py b/tests/ported_static/stRandom/test_random_statetest332.py index 93435882f34..359f5ec94fe 100644 --- a/tests/ported_static/stRandom/test_random_statetest332.py +++ b/tests/ported_static/stRandom/test_random_statetest332.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest332Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest332( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest332.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -99,7 +91,6 @@ def test_random_statetest332( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3506f7c098e7d625a64319d9e514bf35075" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x53D5155E, ) diff --git a/tests/ported_static/stRandom/test_random_statetest333.py b/tests/ported_static/stRandom/test_random_statetest333.py index eba776ceb9e..c1cdec7c3e3 100644 --- a/tests/ported_static/stRandom/test_random_statetest333.py +++ b/tests/ported_static/stRandom/test_random_statetest333.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest333Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest333( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest333.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -97,7 +89,6 @@ def test_random_statetest333( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79457fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6f410263f305963310856c15ff5037a0" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x3024B0A3, ) diff --git a/tests/ported_static/stRandom/test_random_statetest334.py b/tests/ported_static/stRandom/test_random_statetest334.py index a92922d48b9..eff987d6901 100644 --- a/tests/ported_static/stRandom/test_random_statetest334.py +++ b/tests/ported_static/stRandom/test_random_statetest334.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest334Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest334( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest334.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -96,7 +88,6 @@ def test_random_statetest334( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000013a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe6f424468208e181851308b7c7a776863a1" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x36993F17, ) diff --git a/tests/ported_static/stRandom/test_random_statetest339.py b/tests/ported_static/stRandom/test_random_statetest339.py index f3d6c565d4a..03298cb0b02 100644 --- a/tests/ported_static/stRandom/test_random_statetest339.py +++ b/tests/ported_static/stRandom/test_random_statetest339.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest339Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest339( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest339.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -96,7 +88,6 @@ def test_random_statetest339( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3506f89029e850708a293905668f1a367a2" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x3F78C8AA, ) diff --git a/tests/ported_static/stRandom/test_random_statetest342.py b/tests/ported_static/stRandom/test_random_statetest342.py index da98d14951f..64c069f6492 100644 --- a/tests/ported_static/stRandom/test_random_statetest342.py +++ b/tests/ported_static/stRandom/test_random_statetest342.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest342Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest342( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest342.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -98,7 +90,6 @@ def test_random_statetest342( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000000000000000000000000000000000000000000041147fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6f36314297399455797b42569e8f0556" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x6312A8C4, ) diff --git a/tests/ported_static/stRandom/test_random_statetest348.py b/tests/ported_static/stRandom/test_random_statetest348.py index bfe57ee9531..b85dff30f6e 100644 --- a/tests/ported_static/stRandom/test_random_statetest348.py +++ b/tests/ported_static/stRandom/test_random_statetest348.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest348Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest348( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest348.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -102,7 +94,6 @@ def test_random_statetest348( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000142186f18208119191509036365739735608a" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x4F3B26DA, ) diff --git a/tests/ported_static/stRandom/test_random_statetest351.py b/tests/ported_static/stRandom/test_random_statetest351.py index 7880da18abb..908ad3b152b 100644 --- a/tests/ported_static/stRandom/test_random_statetest351.py +++ b/tests/ported_static/stRandom/test_random_statetest351.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest351Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest351( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest351.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -88,7 +80,6 @@ def test_random_statetest351( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0509355534707785320175fca414" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x486E44AE, ) diff --git a/tests/ported_static/stRandom/test_random_statetest354.py b/tests/ported_static/stRandom/test_random_statetest354.py index 15b13f2b299..395c910e028 100644 --- a/tests/ported_static/stRandom/test_random_statetest354.py +++ b/tests/ported_static/stRandom/test_random_statetest354.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest354Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest354( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest354.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -109,7 +101,6 @@ def test_random_statetest354( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000000000000000000000000000000000000000c350603b35641a8e739f86980a4337" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x24AAAAF6, ) diff --git a/tests/ported_static/stRandom/test_random_statetest356.py b/tests/ported_static/stRandom/test_random_statetest356.py index 693aef48fd3..0a3f66001d0 100644 --- a/tests/ported_static/stRandom/test_random_statetest356.py +++ b/tests/ported_static/stRandom/test_random_statetest356.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest356Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest356( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest356.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -100,7 +92,6 @@ def test_random_statetest356( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000100000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79827f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000000000000000000000000000000000000000c3507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe04" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x4F386503, ) diff --git a/tests/ported_static/stRandom/test_random_statetest358.py b/tests/ported_static/stRandom/test_random_statetest358.py index daf1478c698..da3ed15c7ef 100644 --- a/tests/ported_static/stRandom/test_random_statetest358.py +++ b/tests/ported_static/stRandom/test_random_statetest358.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest358Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest358( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest358.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -97,7 +89,6 @@ def test_random_statetest358( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000100000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79417fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff437f000000000000000000000000000000000000000000000000000000000000c3506f679b82a092078f136b5541888c057a" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x252F4B99, ) diff --git a/tests/ported_static/stRandom/test_random_statetest360.py b/tests/ported_static/stRandom/test_random_statetest360.py index 4b13e9e687a..af779dae142 100644 --- a/tests/ported_static/stRandom/test_random_statetest360.py +++ b/tests/ported_static/stRandom/test_random_statetest360.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest360Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest360( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest360.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -100,7 +92,6 @@ def test_random_statetest360( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000016f0441548af30803135562840563829c" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x3B167C0B, ) diff --git a/tests/ported_static/stRandom/test_random_statetest361.py b/tests/ported_static/stRandom/test_random_statetest361.py index 23c0c82de85..475da58cf1f 100644 --- a/tests/ported_static/stRandom/test_random_statetest361.py +++ b/tests/ported_static/stRandom/test_random_statetest361.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest361Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest361( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest361.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -100,7 +92,6 @@ def test_random_statetest361( data=Bytes( "41417ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000100000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff066f9e9092673a8f430b6ba11520901816" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x74BA18BD, ) diff --git a/tests/ported_static/stRandom/test_random_statetest362.py b/tests/ported_static/stRandom/test_random_statetest362.py index 07b7cb05555..88694b0a270 100644 --- a/tests/ported_static/stRandom/test_random_statetest362.py +++ b/tests/ported_static/stRandom/test_random_statetest362.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest362Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest362( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest362.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x945304EB96065B2A98B57A48A06AE28D285A71B5) contract_0 = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87) sender = EOA( @@ -100,7 +92,6 @@ def test_random_statetest362( data=Bytes( "7f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b57f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b509" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x31025CBA, ) diff --git a/tests/ported_static/stRandom/test_random_statetest363.py b/tests/ported_static/stRandom/test_random_statetest363.py index 64e32ba7846..a34ee24f613 100644 --- a/tests/ported_static/stRandom/test_random_statetest363.py +++ b/tests/ported_static/stRandom/test_random_statetest363.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest363Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest363( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest363.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -103,7 +95,6 @@ def test_random_statetest363( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c350117ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe6f7b20937d953695f369719f9a447905" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x4C18B65E, ) diff --git a/tests/ported_static/stRandom/test_random_statetest364.py b/tests/ported_static/stRandom/test_random_statetest364.py index 5eb7a370885..86a27a55c40 100644 --- a/tests/ported_static/stRandom/test_random_statetest364.py +++ b/tests/ported_static/stRandom/test_random_statetest364.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest364Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest364( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest364.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -100,7 +92,6 @@ def test_random_statetest364( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000000000000000000000000000000000000000c350076f7332988d746694918859185920446d" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x25908FA1, ) diff --git a/tests/ported_static/stRandom/test_random_statetest365.py b/tests/ported_static/stRandom/test_random_statetest365.py index eaeb4040bcf..b9d4d6f4a05 100644 --- a/tests/ported_static/stRandom/test_random_statetest365.py +++ b/tests/ported_static/stRandom/test_random_statetest365.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest365Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -18,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -33,14 +31,8 @@ def test_random_statetest365( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest365.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -103,7 +95,6 @@ def test_random_statetest365( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff42417f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000000000000000000000000000000000000000000000143b42078537" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x48ACB162, ) diff --git a/tests/ported_static/stRandom/test_random_statetest366.py b/tests/ported_static/stRandom/test_random_statetest366.py index 6846a0c5772..22ad109b53b 100644 --- a/tests/ported_static/stRandom/test_random_statetest366.py +++ b/tests/ported_static/stRandom/test_random_statetest366.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest366Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest366( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest366.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -102,7 +94,6 @@ def test_random_statetest366( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000100000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff446f516f0395f57433725580758f32f194" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x7D527F3C, ) diff --git a/tests/ported_static/stRandom/test_random_statetest367.py b/tests/ported_static/stRandom/test_random_statetest367.py index d5c56cd839f..bafc8331c25 100644 --- a/tests/ported_static/stRandom/test_random_statetest367.py +++ b/tests/ported_static/stRandom/test_random_statetest367.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest367Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest367( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest367.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x945304EB96065B2A98B57A48A06AE28D285A71B5) contract_0 = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87) sender = EOA( @@ -104,7 +96,6 @@ def test_random_statetest367( data=Bytes( "7f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b57f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000447f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b5447f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b51905810a6c7a5959339f3342838b" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x2BC3D730, ) diff --git a/tests/ported_static/stRandom/test_random_statetest369.py b/tests/ported_static/stRandom/test_random_statetest369.py index 112d60404dc..23c318bec82 100644 --- a/tests/ported_static/stRandom/test_random_statetest369.py +++ b/tests/ported_static/stRandom/test_random_statetest369.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest369Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest369( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest369.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -99,7 +91,6 @@ def test_random_statetest369( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff437f0000000000000000000000010000000000000000000000000000000000000000" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x1C20856A, ) diff --git a/tests/ported_static/stRandom/test_random_statetest37.py b/tests/ported_static/stRandom/test_random_statetest37.py index 0762ac0083f..99afe8c0be8 100644 --- a/tests/ported_static/stRandom/test_random_statetest37.py +++ b/tests/ported_static/stRandom/test_random_statetest37.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest37Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest37( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest37.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -100,7 +92,6 @@ def test_random_statetest37( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000016fa49835863514f0f29b930b97f11693" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x76C6A52D, ) diff --git a/tests/ported_static/stRandom/test_random_statetest372.py b/tests/ported_static/stRandom/test_random_statetest372.py index 6d5dda1765b..d564e6f2501 100644 --- a/tests/ported_static/stRandom/test_random_statetest372.py +++ b/tests/ported_static/stRandom/test_random_statetest372.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest372Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest372( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest372.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x945304EB96065B2A98B57A48A06AE28D285A71B5) contract_0 = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87) sender = EOA( @@ -102,7 +94,6 @@ def test_random_statetest372( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b57f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b57f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b57f00000000000000000000000000000000000000000000000000000000000000011808" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x6D4BEA09, ) diff --git a/tests/ported_static/stRandom/test_random_statetest380.py b/tests/ported_static/stRandom/test_random_statetest380.py index 7d6630e53a3..8dfb4fdd8cd 100644 --- a/tests/ported_static/stRandom/test_random_statetest380.py +++ b/tests/ported_static/stRandom/test_random_statetest380.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest380Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest380( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest380.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -100,7 +92,6 @@ def test_random_statetest380( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe6f967737653485593c63408b39943975" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x25771D96, ) diff --git a/tests/ported_static/stRandom/test_random_statetest381.py b/tests/ported_static/stRandom/test_random_statetest381.py index a2cfc8f33af..7e080c082a1 100644 --- a/tests/ported_static/stRandom/test_random_statetest381.py +++ b/tests/ported_static/stRandom/test_random_statetest381.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest381Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest381( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest381.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -97,7 +89,6 @@ def test_random_statetest381( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff417f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff6f098ba088881a64904570927a861835" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x2D1A0F83, ) diff --git a/tests/ported_static/stRandom/test_random_statetest382.py b/tests/ported_static/stRandom/test_random_statetest382.py index 2b564b3e7f7..a78f6d6b980 100644 --- a/tests/ported_static/stRandom/test_random_statetest382.py +++ b/tests/ported_static/stRandom/test_random_statetest382.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest382Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest382( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest382.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -96,7 +88,6 @@ def test_random_statetest382( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x34AE0BF4, ) diff --git a/tests/ported_static/stRandom/test_random_statetest383.py b/tests/ported_static/stRandom/test_random_statetest383.py index 1b1dfe3d6a5..38877cf35a0 100644 --- a/tests/ported_static/stRandom/test_random_statetest383.py +++ b/tests/ported_static/stRandom/test_random_statetest383.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest383Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest383( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest383.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -88,7 +80,6 @@ def test_random_statetest383( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff09150255436c75107e" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x6379C077, ) diff --git a/tests/ported_static/stRandom/test_random_statetest41.py b/tests/ported_static/stRandom/test_random_statetest41.py index 7546db6b4f4..9a8a7b7ff78 100644 --- a/tests/ported_static/stRandom/test_random_statetest41.py +++ b/tests/ported_static/stRandom/test_random_statetest41.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest41Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest41( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest41.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x945304EB96065B2A98B57A48A06AE28D285A71B5) contract_0 = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87) sender = EOA( @@ -98,7 +90,6 @@ def test_random_statetest41( data=Bytes( "7f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b57f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b57f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c350517f0000000000000000000000010000000000000000000000000000000000000000417f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b56a84a10719a1786a6510349b0282" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x4ADE804, ) diff --git a/tests/ported_static/stRandom/test_random_statetest47.py b/tests/ported_static/stRandom/test_random_statetest47.py index 75436ccf17b..4814873fe6f 100644 --- a/tests/ported_static/stRandom/test_random_statetest47.py +++ b/tests/ported_static/stRandom/test_random_statetest47.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest47Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest47( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest47.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -99,7 +91,6 @@ def test_random_statetest47( data=Bytes( "437f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c350437f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6f1544898b167c6a6f6d5b953714457e" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x77A1A475, ) diff --git a/tests/ported_static/stRandom/test_random_statetest49.py b/tests/ported_static/stRandom/test_random_statetest49.py index 80ee0fecba0..e84eb9a653c 100644 --- a/tests/ported_static/stRandom/test_random_statetest49.py +++ b/tests/ported_static/stRandom/test_random_statetest49.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest49Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -18,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -33,14 +31,8 @@ def test_random_statetest49( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest49.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -93,7 +85,6 @@ def test_random_statetest49( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000010000000000000000000000000000000000000000807f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000000000000000000000000000000000000000c3507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e7961859c" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x69D65F4B, ) diff --git a/tests/ported_static/stRandom/test_random_statetest52.py b/tests/ported_static/stRandom/test_random_statetest52.py index 92da6f37c3a..2771aa5b195 100644 --- a/tests/ported_static/stRandom/test_random_statetest52.py +++ b/tests/ported_static/stRandom/test_random_statetest52.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest52Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest52( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest52.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -97,7 +89,6 @@ def test_random_statetest52( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe410a81437f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000006f59a130a10a189fc653057a185b886c" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x27CBF98C, ) diff --git a/tests/ported_static/stRandom/test_random_statetest58.py b/tests/ported_static/stRandom/test_random_statetest58.py index 7f6faa8aedf..e8121e89950 100644 --- a/tests/ported_static/stRandom/test_random_statetest58.py +++ b/tests/ported_static/stRandom/test_random_statetest58.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest58Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest58( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest58.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -105,7 +97,6 @@ def test_random_statetest58( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe417fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c350367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe096902947d567838719e97f301" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x1F00EC9E, ) diff --git a/tests/ported_static/stRandom/test_random_statetest59.py b/tests/ported_static/stRandom/test_random_statetest59.py index b5d3df091a1..d78ba76facb 100644 --- a/tests/ported_static/stRandom/test_random_statetest59.py +++ b/tests/ported_static/stRandom/test_random_statetest59.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest59Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest59( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest59.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -104,7 +96,6 @@ def test_random_statetest59( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff0208673a06756406548b99" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x2591EEF6, ) diff --git a/tests/ported_static/stRandom/test_random_statetest6.py b/tests/ported_static/stRandom/test_random_statetest6.py index 7a203fab94e..96251ae835d 100644 --- a/tests/ported_static/stRandom/test_random_statetest6.py +++ b/tests/ported_static/stRandom/test_random_statetest6.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest6Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest6( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest6.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -100,7 +92,6 @@ def test_random_statetest6( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e794143416f1732797105f237768fe506871ac853" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x3227D64E, ) diff --git a/tests/ported_static/stRandom/test_random_statetest60.py b/tests/ported_static/stRandom/test_random_statetest60.py index 55d243e4ef6..a044d0fc654 100644 --- a/tests/ported_static/stRandom/test_random_statetest60.py +++ b/tests/ported_static/stRandom/test_random_statetest60.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest60Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest60( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest60.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -97,7 +89,6 @@ def test_random_statetest60( data=Bytes( "427f0000000000000000000000000000000000000000000000000000000000000000427f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff437f000000000000000000000000000000000000000000000000000000000000c3507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe6f969001091aa15b8b9b75459d015a04" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x54DE1EAF, ) diff --git a/tests/ported_static/stRandom/test_random_statetest62.py b/tests/ported_static/stRandom/test_random_statetest62.py index 46dae90ae4a..7118251bf4c 100644 --- a/tests/ported_static/stRandom/test_random_statetest62.py +++ b/tests/ported_static/stRandom/test_random_statetest62.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest62Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest62( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest62.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -100,7 +92,6 @@ def test_random_statetest62( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff437f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000016f7268713013964a96ac575804332501" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x493FBF98, ) diff --git a/tests/ported_static/stRandom/test_random_statetest63.py b/tests/ported_static/stRandom/test_random_statetest63.py index b8752371d61..909326d4fd3 100644 --- a/tests/ported_static/stRandom/test_random_statetest63.py +++ b/tests/ported_static/stRandom/test_random_statetest63.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest63Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest63( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest63.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -98,7 +90,6 @@ def test_random_statetest63( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000006f977f157e088003767a86928e825296" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x31B19D43, ) diff --git a/tests/ported_static/stRandom/test_random_statetest66.py b/tests/ported_static/stRandom/test_random_statetest66.py index b1ac9c6c177..c91cee9a922 100644 --- a/tests/ported_static/stRandom/test_random_statetest66.py +++ b/tests/ported_static/stRandom/test_random_statetest66.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest66Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest66( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest66.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x945304EB96065B2A98B57A48A06AE28D285A71B5) contract_0 = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87) sender = EOA( @@ -99,7 +91,6 @@ def test_random_statetest66( data=Bytes( "457fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff417fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b57ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe097f0000000000000000000000010000000000000000000000000000000000000000" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x2F5660CE, ) diff --git a/tests/ported_static/stRandom/test_random_statetest67.py b/tests/ported_static/stRandom/test_random_statetest67.py index 0ea82c5570b..a9f19ef5f70 100644 --- a/tests/ported_static/stRandom/test_random_statetest67.py +++ b/tests/ported_static/stRandom/test_random_statetest67.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest67Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest67( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest67.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -98,7 +90,6 @@ def test_random_statetest67( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000016f699776659a06a27607a2166d537331" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x7DF32855, ) diff --git a/tests/ported_static/stRandom/test_random_statetest69.py b/tests/ported_static/stRandom/test_random_statetest69.py index 58b0b1b4d2f..ba16b5b1d72 100644 --- a/tests/ported_static/stRandom/test_random_statetest69.py +++ b/tests/ported_static/stRandom/test_random_statetest69.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest69Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest69( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest69.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -99,7 +91,6 @@ def test_random_statetest69( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe43596f15a0770a7676611a6595057b768b64" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x2F6C315B, ) diff --git a/tests/ported_static/stRandom/test_random_statetest73.py b/tests/ported_static/stRandom/test_random_statetest73.py index cbe0f4f7fdd..a95db38a899 100644 --- a/tests/ported_static/stRandom/test_random_statetest73.py +++ b/tests/ported_static/stRandom/test_random_statetest73.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest73Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest73( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest73.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x945304EB96065B2A98B57A48A06AE28D285A71B5) contract_0 = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87) sender = EOA( @@ -97,7 +89,6 @@ def test_random_statetest73( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b57e7f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b5b573198d729b711671056e0a0555346138" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x505D427, ) diff --git a/tests/ported_static/stRandom/test_random_statetest74.py b/tests/ported_static/stRandom/test_random_statetest74.py index 9444931dbdc..a0a31459b1e 100644 --- a/tests/ported_static/stRandom/test_random_statetest74.py +++ b/tests/ported_static/stRandom/test_random_statetest74.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest74Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest74( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest74.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -97,7 +89,6 @@ def test_random_statetest74( data=Bytes( "427ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff3a7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000006f141097788a7b5a72139c07076f1842" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x48E72790, ) diff --git a/tests/ported_static/stRandom/test_random_statetest75.py b/tests/ported_static/stRandom/test_random_statetest75.py index 8001c8457a5..85f2352d2ab 100644 --- a/tests/ported_static/stRandom/test_random_statetest75.py +++ b/tests/ported_static/stRandom/test_random_statetest75.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest75Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest75( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest75.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -102,7 +94,6 @@ def test_random_statetest75( data=Bytes( "457ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000100000000000000000000000000000000000000006f5893504553386c7d15400177928776" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0xABD0738, ) diff --git a/tests/ported_static/stRandom/test_random_statetest77.py b/tests/ported_static/stRandom/test_random_statetest77.py index 0b8eb36b733..9be12420794 100644 --- a/tests/ported_static/stRandom/test_random_statetest77.py +++ b/tests/ported_static/stRandom/test_random_statetest77.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest77Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest77( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest77.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -95,7 +87,6 @@ def test_random_statetest77( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000141937f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000006f79a06df1a08d05373216d372190341" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x4A760CDB, ) diff --git a/tests/ported_static/stRandom/test_random_statetest80.py b/tests/ported_static/stRandom/test_random_statetest80.py index 1a4602a5d18..e0543031205 100644 --- a/tests/ported_static/stRandom/test_random_statetest80.py +++ b/tests/ported_static/stRandom/test_random_statetest80.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest80Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest80( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest80.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x945304EB96065B2A98B57A48A06AE28D285A71B5) contract_0 = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87) sender = EOA( @@ -99,7 +91,6 @@ def test_random_statetest80( data=Bytes( "7f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b57f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b57f0000000000000000000000010000000000000000000000000000000000000000117fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7e7f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b5681069127b3b9c877d6f6169ff36" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x1ED9A5B6, ) diff --git a/tests/ported_static/stRandom/test_random_statetest81.py b/tests/ported_static/stRandom/test_random_statetest81.py index 7f18d30496c..f4ef9c0a9c5 100644 --- a/tests/ported_static/stRandom/test_random_statetest81.py +++ b/tests/ported_static/stRandom/test_random_statetest81.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest81Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest81( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest81.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -99,7 +91,6 @@ def test_random_statetest81( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000100000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe437f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff436f616c327e0435743c515b078453a03c" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x358AB2E1, ) diff --git a/tests/ported_static/stRandom/test_random_statetest83.py b/tests/ported_static/stRandom/test_random_statetest83.py index 045fc17a702..21b6e751ecb 100644 --- a/tests/ported_static/stRandom/test_random_statetest83.py +++ b/tests/ported_static/stRandom/test_random_statetest83.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest83Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest83( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest83.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -98,7 +90,6 @@ def test_random_statetest83( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff427f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000307ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe6fa1109af20740728e72150a7a9c0959" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x3C81798C, ) diff --git a/tests/ported_static/stRandom/test_random_statetest85.py b/tests/ported_static/stRandom/test_random_statetest85.py index d92db0d8f99..e62f281618d 100644 --- a/tests/ported_static/stRandom/test_random_statetest85.py +++ b/tests/ported_static/stRandom/test_random_statetest85.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest85Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -18,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -33,14 +31,8 @@ def test_random_statetest85( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest85.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -83,7 +75,6 @@ def test_random_statetest85( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000000000000000000000000000000000000000c350f25b557e348ff374819d123109539b" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x3B46EEB1, ) diff --git a/tests/ported_static/stRandom/test_random_statetest87.py b/tests/ported_static/stRandom/test_random_statetest87.py index 18c8d7e6a0b..e76829d6ccc 100644 --- a/tests/ported_static/stRandom/test_random_statetest87.py +++ b/tests/ported_static/stRandom/test_random_statetest87.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest87Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest87( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest87.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -97,7 +89,6 @@ def test_random_statetest87( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000005b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe6f446e638e7e16736c030393727d748174" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x7B1E5DC9, ) diff --git a/tests/ported_static/stRandom/test_random_statetest88.py b/tests/ported_static/stRandom/test_random_statetest88.py index d9bb7b3c472..049041d3053 100644 --- a/tests/ported_static/stRandom/test_random_statetest88.py +++ b/tests/ported_static/stRandom/test_random_statetest88.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest88Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest88( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest88.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -96,7 +88,6 @@ def test_random_statetest88( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e794343537f000000000000000000000000000000000000000000000000000000000000c350117fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000016f34f06a7014541167033909103620f3" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x3E996CB5, ) diff --git a/tests/ported_static/stRandom/test_random_statetest89.py b/tests/ported_static/stRandom/test_random_statetest89.py index 84c8b823240..2d8d728dd32 100644 --- a/tests/ported_static/stRandom/test_random_statetest89.py +++ b/tests/ported_static/stRandom/test_random_statetest89.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest89Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest89( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest89.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -93,7 +85,6 @@ def test_random_statetest89( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000016f05648ce0ad106b7a6f3483379e62876b" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x41032F3B, ) diff --git a/tests/ported_static/stRandom/test_random_statetest9.py b/tests/ported_static/stRandom/test_random_statetest9.py index fc4d799e62e..27f02d61230 100644 --- a/tests/ported_static/stRandom/test_random_statetest9.py +++ b/tests/ported_static/stRandom/test_random_statetest9.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest9Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest9( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest9.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -96,7 +88,6 @@ def test_random_statetest9( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000016f757fb845405bf1ff959ba03a9c336b" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0xCEFB419, ) diff --git a/tests/ported_static/stRandom/test_random_statetest90.py b/tests/ported_static/stRandom/test_random_statetest90.py index d13636db170..0d18aa16693 100644 --- a/tests/ported_static/stRandom/test_random_statetest90.py +++ b/tests/ported_static/stRandom/test_random_statetest90.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest90Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest90( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest90.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -95,7 +87,6 @@ def test_random_statetest90( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff45157f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000016f116b4177f25178d7048212877e9568" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0xA10954E, ) diff --git a/tests/ported_static/stRandom/test_random_statetest92.py b/tests/ported_static/stRandom/test_random_statetest92.py index 2307d7542f7..725dc8078d8 100644 --- a/tests/ported_static/stRandom/test_random_statetest92.py +++ b/tests/ported_static/stRandom/test_random_statetest92.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest92Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest92( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest92.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -99,7 +91,6 @@ def test_random_statetest92( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000006f59640c655956799087168f0658a11a" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x1C23D3BC, ) diff --git a/tests/ported_static/stRandom/test_random_statetest95.py b/tests/ported_static/stRandom/test_random_statetest95.py index 0ad602c99f9..466b79bee04 100644 --- a/tests/ported_static/stRandom/test_random_statetest95.py +++ b/tests/ported_static/stRandom/test_random_statetest95.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest95Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest95( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest95.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -100,7 +92,6 @@ def test_random_statetest95( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff14447ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5b" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x7E83FA74, ) diff --git a/tests/ported_static/stRandom/test_random_statetest96.py b/tests/ported_static/stRandom/test_random_statetest96.py index 779a04aad03..728a5d88843 100644 --- a/tests/ported_static/stRandom/test_random_statetest96.py +++ b/tests/ported_static/stRandom/test_random_statetest96.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom/randomStatetest96Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest96( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest96.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -98,7 +90,6 @@ def test_random_statetest96( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000006f183b68a09b08953085a854a39d9212" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x4A4D8FC4, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest.py b/tests/ported_static/stRandom2/test_random_statetest.py index 27df3124391..381d9b5ad72 100644 --- a/tests/ported_static/stRandom2/test_random_statetest.py +++ b/tests/ported_static/stRandom2/test_random_statetest.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetestFiller.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -100,7 +92,6 @@ def test_random_statetest( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe6f29199c9aa4054170f1a15a55056f96" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0xF08F864, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest384.py b/tests/ported_static/stRandom2/test_random_statetest384.py index b7ff2acd9a5..a15bee09275 100644 --- a/tests/ported_static/stRandom2/test_random_statetest384.py +++ b/tests/ported_static/stRandom2/test_random_statetest384.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest384Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest384( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest384.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -98,7 +90,6 @@ def test_random_statetest384( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff6f16133502727c0a7f679b456df0935763" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x7B2BD74C, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest385.py b/tests/ported_static/stRandom2/test_random_statetest385.py index fbf8c5b914c..ff18d79504f 100644 --- a/tests/ported_static/stRandom2/test_random_statetest385.py +++ b/tests/ported_static/stRandom2/test_random_statetest385.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest385Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest385( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest385.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -94,7 +86,6 @@ def test_random_statetest385( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79547f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff6f785188182063156955631a7a85093a" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x2DCC90D2, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest386.py b/tests/ported_static/stRandom2/test_random_statetest386.py index 96cfc99e246..89ee427dc65 100644 --- a/tests/ported_static/stRandom2/test_random_statetest386.py +++ b/tests/ported_static/stRandom2/test_random_statetest386.py @@ -4,10 +4,8 @@ Ported from: state_tests/stRandom2/randomStatetest386Filler.json -@manually-enhanced: Do not overwrite. `gas_limit` raised on Amsterdam -to cover EIP-8037 state-gas spill. Pre-EIP-8037 keeps the original -100 000. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -20,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -35,14 +32,8 @@ def test_random_statetest386( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest386.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -103,7 +94,6 @@ def test_random_statetest386( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff047f000000000000000000000000000000000000000000000000000000000000000105133641010b8111" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x19D7AC44, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest388.py b/tests/ported_static/stRandom2/test_random_statetest388.py index e799d490153..b32661ac43a 100644 --- a/tests/ported_static/stRandom2/test_random_statetest388.py +++ b/tests/ported_static/stRandom2/test_random_statetest388.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest388Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest388( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest388.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x945304EB96065B2A98B57A48A06AE28D285A71B5) contract_0 = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87) sender = EOA( @@ -98,7 +90,6 @@ def test_random_statetest388( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b57f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b57f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7e7f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b5765b8f743b9979a0905b6a189165" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x460B9F39, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest389.py b/tests/ported_static/stRandom2/test_random_statetest389.py index 8cfacc2758c..07dd743fdb5 100644 --- a/tests/ported_static/stRandom2/test_random_statetest389.py +++ b/tests/ported_static/stRandom2/test_random_statetest389.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest389Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -18,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -33,14 +31,8 @@ def test_random_statetest389( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest389.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -99,7 +91,6 @@ def test_random_statetest389( data=Bytes( "457ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000100000000000000000000000000000000000000007f0000000000000000000000000000000000000000000000000000000000000000427f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3503a863854581237" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x5BF15D9B, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest395.py b/tests/ported_static/stRandom2/test_random_statetest395.py index d3d5c53b250..6de0e56a9e4 100644 --- a/tests/ported_static/stRandom2/test_random_statetest395.py +++ b/tests/ported_static/stRandom2/test_random_statetest395.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest395Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest395( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest395.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -98,7 +90,6 @@ def test_random_statetest395( data=Bytes( "447f0000000000000000000000000000000000000000000000000000000000000001417f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6f823140710bf13990e4500136726d8b" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x5A9C61EF, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest398.py b/tests/ported_static/stRandom2/test_random_statetest398.py index fbe759a2324..94fcf445373 100644 --- a/tests/ported_static/stRandom2/test_random_statetest398.py +++ b/tests/ported_static/stRandom2/test_random_statetest398.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest398Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest398( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest398.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -95,7 +87,6 @@ def test_random_statetest398( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e796f3781413b695a69079d7f5105829207" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x69A26DE, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest399.py b/tests/ported_static/stRandom2/test_random_statetest399.py index e8ba5a4ee29..918db55c7b8 100644 --- a/tests/ported_static/stRandom2/test_random_statetest399.py +++ b/tests/ported_static/stRandom2/test_random_statetest399.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest399Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest399( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest399.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -102,7 +94,6 @@ def test_random_statetest399( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe4544437f00000000000000000000000000000000000000000000000000000000000000017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6f98324016076d428a9898129b16849a" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x2099AF7A, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest402.py b/tests/ported_static/stRandom2/test_random_statetest402.py index 528f4373389..7fd4f296c4b 100644 --- a/tests/ported_static/stRandom2/test_random_statetest402.py +++ b/tests/ported_static/stRandom2/test_random_statetest402.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest402Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest402( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest402.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -98,7 +90,6 @@ def test_random_statetest402( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff437f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000006f62138c87028162ea32a2db7e301004" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x37EBC742, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest405.py b/tests/ported_static/stRandom2/test_random_statetest405.py index f3592725b1a..596add3df18 100644 --- a/tests/ported_static/stRandom2/test_random_statetest405.py +++ b/tests/ported_static/stRandom2/test_random_statetest405.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest405Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest405( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest405.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -102,7 +94,6 @@ def test_random_statetest405( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff44457f0000000000000000000000010000000000000000000000000000000000000000037ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6f318d0707977199361171756f6d458e" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x10596FAF, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest407.py b/tests/ported_static/stRandom2/test_random_statetest407.py index b113779f4af..627a88bc89a 100644 --- a/tests/ported_static/stRandom2/test_random_statetest407.py +++ b/tests/ported_static/stRandom2/test_random_statetest407.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest407Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest407( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest407.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -98,7 +90,6 @@ def test_random_statetest407( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff437ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000000000000000000000000000000000000000c350437f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e796f6d71656f054471181163037902615b" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x313547F8, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest408.py b/tests/ported_static/stRandom2/test_random_statetest408.py index 7551ed6d5d4..e8bbf2f0955 100644 --- a/tests/ported_static/stRandom2/test_random_statetest408.py +++ b/tests/ported_static/stRandom2/test_random_statetest408.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest408Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest408( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest408.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -102,7 +94,6 @@ def test_random_statetest408( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe447f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000000000000000000000000000000000000000c3507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff6f80656e8e6478946a323482135a8bf7" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x63AD417F, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest411.py b/tests/ported_static/stRandom2/test_random_statetest411.py index 179bb190c71..b902a71e74f 100644 --- a/tests/ported_static/stRandom2/test_random_statetest411.py +++ b/tests/ported_static/stRandom2/test_random_statetest411.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest411Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest411( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest411.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -100,7 +92,6 @@ def test_random_statetest411( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000006f44a17892738b6895619d7a93507d649d" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x7E5B1276, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest412.py b/tests/ported_static/stRandom2/test_random_statetest412.py index 7460990cfe2..9fa348b42f2 100644 --- a/tests/ported_static/stRandom2/test_random_statetest412.py +++ b/tests/ported_static/stRandom2/test_random_statetest412.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest412Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest412( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest412.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -96,7 +88,6 @@ def test_random_statetest412( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000000000000000000000000000000000000000c3507f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff6fa46ef06a5a858b9742198a37e1153c" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x75CF6AD, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest413.py b/tests/ported_static/stRandom2/test_random_statetest413.py index ddf970571ae..6fb74ab95d7 100644 --- a/tests/ported_static/stRandom2/test_random_statetest413.py +++ b/tests/ported_static/stRandom2/test_random_statetest413.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest413Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest413( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest413.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -98,7 +90,6 @@ def test_random_statetest413( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000000000000000000000000000000000000000c3507f0000000000000000000000010000000000000000000000000000000000000000817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe037f00000000000000000000000000000000000000000000000000000000000000016f086e2055149345ad1a018b06370814" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x47E29C11, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest416.py b/tests/ported_static/stRandom2/test_random_statetest416.py index f9b9ba9332b..b588430660e 100644 --- a/tests/ported_static/stRandom2/test_random_statetest416.py +++ b/tests/ported_static/stRandom2/test_random_statetest416.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest416Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -18,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -33,14 +31,8 @@ def test_random_statetest416( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest416.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -90,7 +82,6 @@ def test_random_statetest416( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff427f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e7943" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x4F622410, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest419.py b/tests/ported_static/stRandom2/test_random_statetest419.py index 8af9b0c54fc..58c21dcc75b 100644 --- a/tests/ported_static/stRandom2/test_random_statetest419.py +++ b/tests/ported_static/stRandom2/test_random_statetest419.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest419Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest419( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest419.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -104,7 +96,6 @@ def test_random_statetest419( data=Bytes( "437ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000000000000000000000000000000000000000c3507f0000000000000000000000000000000000000000000000000000000000000001417ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe6f73095b7ee211595a6b80a311900a78" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x6A4CEBB4, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest421.py b/tests/ported_static/stRandom2/test_random_statetest421.py index f784c829ac2..790b2f8d4b0 100644 --- a/tests/ported_static/stRandom2/test_random_statetest421.py +++ b/tests/ported_static/stRandom2/test_random_statetest421.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest421Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest421( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest421.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -96,7 +88,6 @@ def test_random_statetest421( data=Bytes( "437f00000000000000000000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6f38454051968ff184a47d500912319717" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x52D1555F, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest424.py b/tests/ported_static/stRandom2/test_random_statetest424.py index 5a4b1706b07..05bc62eef22 100644 --- a/tests/ported_static/stRandom2/test_random_statetest424.py +++ b/tests/ported_static/stRandom2/test_random_statetest424.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest424Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest424( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest424.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -98,7 +90,6 @@ def test_random_statetest424( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79437f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000000000000000000000000000000000000000000000436f18116552626186825096665471140a" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x4BCD2F4F, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest425.py b/tests/ported_static/stRandom2/test_random_statetest425.py index 8f26fba2f5d..c3a2dcececb 100644 --- a/tests/ported_static/stRandom2/test_random_statetest425.py +++ b/tests/ported_static/stRandom2/test_random_statetest425.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest425Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest425( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest425.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -93,7 +85,6 @@ def test_random_statetest425( data=Bytes( "7f0000000000000000000000010000000000000000000000000000000000000000417f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff6f885707818b889a89975552f0128442" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x22371A75, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest426.py b/tests/ported_static/stRandom2/test_random_statetest426.py index 7977467a6a5..8d4b20e457c 100644 --- a/tests/ported_static/stRandom2/test_random_statetest426.py +++ b/tests/ported_static/stRandom2/test_random_statetest426.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest426Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest426( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest426.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -100,7 +92,6 @@ def test_random_statetest426( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79417ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000006f456d1687795a95938b0139976099f0" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x613B33CA, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest429.py b/tests/ported_static/stRandom2/test_random_statetest429.py index 60a9e50233b..42b725735e5 100644 --- a/tests/ported_static/stRandom2/test_random_statetest429.py +++ b/tests/ported_static/stRandom2/test_random_statetest429.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest429Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest429( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest429.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -100,7 +92,6 @@ def test_random_statetest429( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79417ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6f98121f388786729087773476331366" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x5430ADAF, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest430.py b/tests/ported_static/stRandom2/test_random_statetest430.py index f99d9695f69..297234d6cd3 100644 --- a/tests/ported_static/stRandom2/test_random_statetest430.py +++ b/tests/ported_static/stRandom2/test_random_statetest430.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest430Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest430( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest430.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -100,7 +92,6 @@ def test_random_statetest430( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe427f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000006f7d41a29934035b748e96a3135b6964" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x6BF5E61F, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest436.py b/tests/ported_static/stRandom2/test_random_statetest436.py index 586314ec15a..7cd00de6e08 100644 --- a/tests/ported_static/stRandom2/test_random_statetest436.py +++ b/tests/ported_static/stRandom2/test_random_statetest436.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest436Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest436( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest436.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -99,7 +91,6 @@ def test_random_statetest436( data=Bytes( "367f00000000000000000000000000000000000000000000000000000000000000017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff417fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79456f8108067a345b7a76a20a835a0a0b6c10" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x57454F1E, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest438.py b/tests/ported_static/stRandom2/test_random_statetest438.py index 0ce4a0ac366..88693e737f9 100644 --- a/tests/ported_static/stRandom2/test_random_statetest438.py +++ b/tests/ported_static/stRandom2/test_random_statetest438.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest438Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -18,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -33,14 +31,8 @@ def test_random_statetest438( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest438.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -95,7 +87,6 @@ def test_random_statetest438( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff097fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x3FDE3BBC, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest439.py b/tests/ported_static/stRandom2/test_random_statetest439.py index 69e9b00d114..bee9883581b 100644 --- a/tests/ported_static/stRandom2/test_random_statetest439.py +++ b/tests/ported_static/stRandom2/test_random_statetest439.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest439Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest439( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest439.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -100,7 +92,6 @@ def test_random_statetest439( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6f5b1609653438813340097c53a49316" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x17BA0353, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest440.py b/tests/ported_static/stRandom2/test_random_statetest440.py index ba3114d0f37..5e88688f97a 100644 --- a/tests/ported_static/stRandom2/test_random_statetest440.py +++ b/tests/ported_static/stRandom2/test_random_statetest440.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest440Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest440( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest440.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -94,7 +86,6 @@ def test_random_statetest440( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e7945457f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff416f01513a9b8216816f74f3676e9ea261" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x4A3FD736, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest446.py b/tests/ported_static/stRandom2/test_random_statetest446.py index fdcddb6bd89..47dd0efba21 100644 --- a/tests/ported_static/stRandom2/test_random_statetest446.py +++ b/tests/ported_static/stRandom2/test_random_statetest446.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest446Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest446( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest446.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -99,7 +91,6 @@ def test_random_statetest446( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff09" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x872ECB9, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest447.py b/tests/ported_static/stRandom2/test_random_statetest447.py index 5d05d1aeecb..07bb8f1538a 100644 --- a/tests/ported_static/stRandom2/test_random_statetest447.py +++ b/tests/ported_static/stRandom2/test_random_statetest447.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest447Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest447( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest447.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -103,7 +95,6 @@ def test_random_statetest447( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000100000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe437f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff08" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x1569EBA8, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest450.py b/tests/ported_static/stRandom2/test_random_statetest450.py index 50d97a3609d..8df96136097 100644 --- a/tests/ported_static/stRandom2/test_random_statetest450.py +++ b/tests/ported_static/stRandom2/test_random_statetest450.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest450Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -18,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -33,14 +31,8 @@ def test_random_statetest450( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest450.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A764000000) @@ -97,7 +89,6 @@ def test_random_statetest450( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507f0000000000000000000000010000000000000000000000000000000000000000033a80" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x50F09196, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest451.py b/tests/ported_static/stRandom2/test_random_statetest451.py index 4b29183b8e3..5798c77f990 100644 --- a/tests/ported_static/stRandom2/test_random_statetest451.py +++ b/tests/ported_static/stRandom2/test_random_statetest451.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest451Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest451( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest451.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -99,7 +91,6 @@ def test_random_statetest451( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000006fed05989a0659453076573a87041174" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x306CA21A, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest452.py b/tests/ported_static/stRandom2/test_random_statetest452.py index bab4ce182c0..6525cf7d1f4 100644 --- a/tests/ported_static/stRandom2/test_random_statetest452.py +++ b/tests/ported_static/stRandom2/test_random_statetest452.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest452Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest452( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest452.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -99,7 +91,6 @@ def test_random_statetest452( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e796f0a3289746806163630047dff983105" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x58F77982, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest455.py b/tests/ported_static/stRandom2/test_random_statetest455.py index e6cf5829f17..8dda27e9af2 100644 --- a/tests/ported_static/stRandom2/test_random_statetest455.py +++ b/tests/ported_static/stRandom2/test_random_statetest455.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest455Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest455( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest455.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -98,7 +90,6 @@ def test_random_statetest455( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e796f858b1411f218693ca2245b918274f3" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x2BF8F04F, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest457.py b/tests/ported_static/stRandom2/test_random_statetest457.py index ca20ff7a1c8..a52bc3385b2 100644 --- a/tests/ported_static/stRandom2/test_random_statetest457.py +++ b/tests/ported_static/stRandom2/test_random_statetest457.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest457Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest457( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest457.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -96,7 +88,6 @@ def test_random_statetest457( data=Bytes( "44417f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe6f949fa28af308a37a136c626218927d" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x12DE4990, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest460.py b/tests/ported_static/stRandom2/test_random_statetest460.py index 6c2c1f0b13a..92dca4015e8 100644 --- a/tests/ported_static/stRandom2/test_random_statetest460.py +++ b/tests/ported_static/stRandom2/test_random_statetest460.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest460Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest460( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest460.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -101,7 +93,6 @@ def test_random_statetest460( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000003a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c350046f16a23c6c90739ba201697b4315778a" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x5A8388BF, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest461.py b/tests/ported_static/stRandom2/test_random_statetest461.py index 08dce9529e4..32a4dfa24ff 100644 --- a/tests/ported_static/stRandom2/test_random_statetest461.py +++ b/tests/ported_static/stRandom2/test_random_statetest461.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest461Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -18,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -33,14 +31,8 @@ def test_random_statetest461( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest461.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -92,7 +84,6 @@ def test_random_statetest461( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c350517f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff42515259" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x20B19906, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest462.py b/tests/ported_static/stRandom2/test_random_statetest462.py index 7160533bddd..28019cf7c9e 100644 --- a/tests/ported_static/stRandom2/test_random_statetest462.py +++ b/tests/ported_static/stRandom2/test_random_statetest462.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest462Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest462( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest462.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -93,7 +85,6 @@ def test_random_statetest462( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000100000000000000000000000000000000000000006f8e0186019d029d1354681482826f37" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x564E62DA, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest464.py b/tests/ported_static/stRandom2/test_random_statetest464.py index 5ef019e574a..e39de2c83d9 100644 --- a/tests/ported_static/stRandom2/test_random_statetest464.py +++ b/tests/ported_static/stRandom2/test_random_statetest464.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest464Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest464( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest464.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -99,7 +91,6 @@ def test_random_statetest464( data=Bytes( "447f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8209" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x2491B9, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest465.py b/tests/ported_static/stRandom2/test_random_statetest465.py index 616396458de..45e66598f58 100644 --- a/tests/ported_static/stRandom2/test_random_statetest465.py +++ b/tests/ported_static/stRandom2/test_random_statetest465.py @@ -4,10 +4,8 @@ Ported from: state_tests/stRandom2/randomStatetest465Filler.json -@manually-enhanced: Do not overwrite. `gas_limit` raised on Amsterdam -to cover EIP-8037 state-gas spill. Pre-EIP-8037 keeps the original -100 000. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -20,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -35,14 +32,8 @@ def test_random_statetest465( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest465.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -94,7 +85,6 @@ def test_random_statetest465( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79437f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000001" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x5DE12C27, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest470.py b/tests/ported_static/stRandom2/test_random_statetest470.py index f0dfe17c0c7..e84b0c32381 100644 --- a/tests/ported_static/stRandom2/test_random_statetest470.py +++ b/tests/ported_static/stRandom2/test_random_statetest470.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest470Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest470( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest470.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -95,7 +87,6 @@ def test_random_statetest470( data=Bytes( "457f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000001357f00000000000000000000000000000000000000000000000000000000000000000b" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x67C37947, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest471.py b/tests/ported_static/stRandom2/test_random_statetest471.py index abd2d844583..2cfba80dca9 100644 --- a/tests/ported_static/stRandom2/test_random_statetest471.py +++ b/tests/ported_static/stRandom2/test_random_statetest471.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest471Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest471( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest471.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -88,7 +80,6 @@ def test_random_statetest471( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000100000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09650618701355040655183a51377d82" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x63180FB7, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest473.py b/tests/ported_static/stRandom2/test_random_statetest473.py index a6192bdd920..993951d548f 100644 --- a/tests/ported_static/stRandom2/test_random_statetest473.py +++ b/tests/ported_static/stRandom2/test_random_statetest473.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest473Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -18,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -33,14 +31,8 @@ def test_random_statetest473( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest473.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x945304EB96065B2A98B57A48A06AE28D285A71B5) contract_0 = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -100,7 +92,6 @@ def test_random_statetest473( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff317f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b5910209" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x4467CA41, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest474.py b/tests/ported_static/stRandom2/test_random_statetest474.py index fbbeb0f9f06..41fca3563bc 100644 --- a/tests/ported_static/stRandom2/test_random_statetest474.py +++ b/tests/ported_static/stRandom2/test_random_statetest474.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest474Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest474( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest474.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -97,7 +89,6 @@ def test_random_statetest474( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe027f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6f7d6f6b1051778ea1670387810b5805" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x7B1ABEED, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest475.py b/tests/ported_static/stRandom2/test_random_statetest475.py index 65aa0f5f8d2..8c9aae84e71 100644 --- a/tests/ported_static/stRandom2/test_random_statetest475.py +++ b/tests/ported_static/stRandom2/test_random_statetest475.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest475Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -18,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -33,14 +31,8 @@ def test_random_statetest475( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest475.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -94,7 +86,6 @@ def test_random_statetest475( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff09" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x19883C24, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest477.py b/tests/ported_static/stRandom2/test_random_statetest477.py index 868fa22f1de..71e2ca0631a 100644 --- a/tests/ported_static/stRandom2/test_random_statetest477.py +++ b/tests/ported_static/stRandom2/test_random_statetest477.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest477Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest477( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest477.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -99,7 +91,6 @@ def test_random_statetest477( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f0000000000000000000000000000000000000000000000000000000000000001417ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe6f9084a3758d3456763aa4f09c8b735b" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0xA9AAD5, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest480.py b/tests/ported_static/stRandom2/test_random_statetest480.py index 63098954ae9..f21058d0b1e 100644 --- a/tests/ported_static/stRandom2/test_random_statetest480.py +++ b/tests/ported_static/stRandom2/test_random_statetest480.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest480Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest480( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest480.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -100,7 +92,6 @@ def test_random_statetest480( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff037f0000000000000000000000000000000000000000000000000000000000000000427ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe6f5af3a474ff64f3a37d51f36a6a607f" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x2C6942FB, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest482.py b/tests/ported_static/stRandom2/test_random_statetest482.py index 2c6553c50be..d5f4b4e9396 100644 --- a/tests/ported_static/stRandom2/test_random_statetest482.py +++ b/tests/ported_static/stRandom2/test_random_statetest482.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest482Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest482( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest482.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -102,7 +94,6 @@ def test_random_statetest482( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff6f027c9d313d9b09376505927c8e7156" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x636F84BF, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest483.py b/tests/ported_static/stRandom2/test_random_statetest483.py index 2a810280520..54ef625e4b9 100644 --- a/tests/ported_static/stRandom2/test_random_statetest483.py +++ b/tests/ported_static/stRandom2/test_random_statetest483.py @@ -4,10 +4,8 @@ Ported from: state_tests/stRandom2/randomStatetest483Filler.json -@manually-enhanced: Do not overwrite. `gas_limit` raised on Amsterdam -to cover EIP-8037 state-gas spill. Pre-EIP-8037 keeps the original -100 000. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -20,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -35,14 +32,8 @@ def test_random_statetest483( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest483.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -93,7 +84,6 @@ def test_random_statetest483( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe8409" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x7EEDCE16, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest488.py b/tests/ported_static/stRandom2/test_random_statetest488.py index 4037e572732..118af78d93a 100644 --- a/tests/ported_static/stRandom2/test_random_statetest488.py +++ b/tests/ported_static/stRandom2/test_random_statetest488.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest488Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest488( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest488.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -98,7 +90,6 @@ def test_random_statetest488( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79427f00000000000000000000000000000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe6f3250648093577f6364a218f0907e7d" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x53844097, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest489.py b/tests/ported_static/stRandom2/test_random_statetest489.py index 998c3c5301e..e0c3d43b186 100644 --- a/tests/ported_static/stRandom2/test_random_statetest489.py +++ b/tests/ported_static/stRandom2/test_random_statetest489.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest489Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest489( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest489.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -95,7 +87,6 @@ def test_random_statetest489( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000000000000000000000000000000000000000000000456f2b8e846b91987417705a126e770764" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x6EA1DC52, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest491.py b/tests/ported_static/stRandom2/test_random_statetest491.py index 5bdc6ad3a55..7e9b4090c3e 100644 --- a/tests/ported_static/stRandom2/test_random_statetest491.py +++ b/tests/ported_static/stRandom2/test_random_statetest491.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest491Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest491( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest491.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -101,7 +93,6 @@ def test_random_statetest491( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000006fa0f670645a778c71127d3b5598308b17" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x6BA27C22, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest497.py b/tests/ported_static/stRandom2/test_random_statetest497.py index 550bf2442fa..700ed36357f 100644 --- a/tests/ported_static/stRandom2/test_random_statetest497.py +++ b/tests/ported_static/stRandom2/test_random_statetest497.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest497Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -18,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -33,14 +31,8 @@ def test_random_statetest497( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest497.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -93,7 +85,6 @@ def test_random_statetest497( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0904" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x44240571, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest500.py b/tests/ported_static/stRandom2/test_random_statetest500.py index f39cc52497e..868c0b501fe 100644 --- a/tests/ported_static/stRandom2/test_random_statetest500.py +++ b/tests/ported_static/stRandom2/test_random_statetest500.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest500Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest500( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest500.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -98,7 +90,6 @@ def test_random_statetest500( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6f87196584968a97046c679199311482" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x20D454F, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest502.py b/tests/ported_static/stRandom2/test_random_statetest502.py index e22b5a3ee78..7ae7d39a68d 100644 --- a/tests/ported_static/stRandom2/test_random_statetest502.py +++ b/tests/ported_static/stRandom2/test_random_statetest502.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest502Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest502( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest502.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x945304EB96065B2A98B57A48A06AE28D285A71B5) contract_0 = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87) sender = EOA( @@ -98,7 +90,6 @@ def test_random_statetest502( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c350807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b57e7f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b59c66369a85a46da1821861586378" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x14960C58, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest503.py b/tests/ported_static/stRandom2/test_random_statetest503.py index 3925ac7107b..ab822db30df 100644 --- a/tests/ported_static/stRandom2/test_random_statetest503.py +++ b/tests/ported_static/stRandom2/test_random_statetest503.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest503Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest503( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest503.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -96,7 +88,6 @@ def test_random_statetest503( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000006f0886a83c66553c9889528d8f1294ff" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x7B7801AA, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest505.py b/tests/ported_static/stRandom2/test_random_statetest505.py index 8d1dc12357e..cba7314aafa 100644 --- a/tests/ported_static/stRandom2/test_random_statetest505.py +++ b/tests/ported_static/stRandom2/test_random_statetest505.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest505Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest505( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest505.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -100,7 +92,6 @@ def test_random_statetest505( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe427f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe457f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000006f44a06f550371317376738c53998437" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x4013B563, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest506.py b/tests/ported_static/stRandom2/test_random_statetest506.py index a574ead20f1..fc0561322f9 100644 --- a/tests/ported_static/stRandom2/test_random_statetest506.py +++ b/tests/ported_static/stRandom2/test_random_statetest506.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest506Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest506( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest506.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -99,7 +91,6 @@ def test_random_statetest506( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000000000000000000000000000000000000000000042377f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000006ba218f370862059149e3cff20" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x3879DAC6, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest511.py b/tests/ported_static/stRandom2/test_random_statetest511.py index 6bee58df4be..4ca2b67bd72 100644 --- a/tests/ported_static/stRandom2/test_random_statetest511.py +++ b/tests/ported_static/stRandom2/test_random_statetest511.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest511Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest511( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest511.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -97,7 +89,6 @@ def test_random_statetest511( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3507f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff416f6a52027f41f267453843630a66444145" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x1B89A723, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest512.py b/tests/ported_static/stRandom2/test_random_statetest512.py index f161e8e69c1..ab69357d013 100644 --- a/tests/ported_static/stRandom2/test_random_statetest512.py +++ b/tests/ported_static/stRandom2/test_random_statetest512.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest512Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest512( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest512.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -100,7 +92,6 @@ def test_random_statetest512( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c350437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6f3b5bff405670977499515002634492" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x33F0AE08, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest514.py b/tests/ported_static/stRandom2/test_random_statetest514.py index 6caf5d66571..9726d55bc4b 100644 --- a/tests/ported_static/stRandom2/test_random_statetest514.py +++ b/tests/ported_static/stRandom2/test_random_statetest514.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest514Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest514( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest514.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -99,7 +91,6 @@ def test_random_statetest514( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe44447f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3506c8ea356796d65546d3883768f" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x105D80AD, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest516.py b/tests/ported_static/stRandom2/test_random_statetest516.py index 243141c25dd..5396b19ae2a 100644 --- a/tests/ported_static/stRandom2/test_random_statetest516.py +++ b/tests/ported_static/stRandom2/test_random_statetest516.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest516Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest516( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest516.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -105,7 +97,6 @@ def test_random_statetest516( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff6a32787358019b391868619409" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x2C787EA, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest518.py b/tests/ported_static/stRandom2/test_random_statetest518.py index 0ff6bc1bcef..1a50ca640a2 100644 --- a/tests/ported_static/stRandom2/test_random_statetest518.py +++ b/tests/ported_static/stRandom2/test_random_statetest518.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest518Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest518( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest518.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -98,7 +90,6 @@ def test_random_statetest518( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff6f3c589f416d947a5134f268515b6c92" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x415CB1C9, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest519.py b/tests/ported_static/stRandom2/test_random_statetest519.py index dd414e2d50d..e7ec4d68eb5 100644 --- a/tests/ported_static/stRandom2/test_random_statetest519.py +++ b/tests/ported_static/stRandom2/test_random_statetest519.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest519Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest519( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest519.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -88,7 +80,6 @@ def test_random_statetest519( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000001000000000000000000000000000000000000000009457f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3501a02556b85a45311" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0xEA81BBF, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest520.py b/tests/ported_static/stRandom2/test_random_statetest520.py index ca90ac86420..8fb45f3f078 100644 --- a/tests/ported_static/stRandom2/test_random_statetest520.py +++ b/tests/ported_static/stRandom2/test_random_statetest520.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest520Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest520( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest520.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -106,7 +98,6 @@ def test_random_statetest520( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff190308" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x13D9C7A3, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest526.py b/tests/ported_static/stRandom2/test_random_statetest526.py index 33bf58469b1..f8461e8265b 100644 --- a/tests/ported_static/stRandom2/test_random_statetest526.py +++ b/tests/ported_static/stRandom2/test_random_statetest526.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest526Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest526( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest526.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x945304EB96065B2A98B57A48A06AE28D285A71B5) contract_0 = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87) sender = EOA( @@ -94,7 +86,6 @@ def test_random_statetest526( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b5417e7f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b5419e01950777810975058c746f" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x3AA8C462, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest532.py b/tests/ported_static/stRandom2/test_random_statetest532.py index acfe4835fc5..ebc928ac2a4 100644 --- a/tests/ported_static/stRandom2/test_random_statetest532.py +++ b/tests/ported_static/stRandom2/test_random_statetest532.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest532Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest532( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest532.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -101,7 +93,6 @@ def test_random_statetest532( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe54447f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6f78297ba08ba478507f413b3597109c" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x43E5A248, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest533.py b/tests/ported_static/stRandom2/test_random_statetest533.py index bd66b30f0b1..f482c7c05a3 100644 --- a/tests/ported_static/stRandom2/test_random_statetest533.py +++ b/tests/ported_static/stRandom2/test_random_statetest533.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest533Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -18,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -33,14 +31,8 @@ def test_random_statetest533( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest533.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -92,7 +84,6 @@ def test_random_statetest533( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000001847f00000000000000000000000100000000000000000000000000000000000000003a076152" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x70D690F4, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest534.py b/tests/ported_static/stRandom2/test_random_statetest534.py index 57e891e623b..47ec5ed56fc 100644 --- a/tests/ported_static/stRandom2/test_random_statetest534.py +++ b/tests/ported_static/stRandom2/test_random_statetest534.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest534Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest534( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest534.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -98,7 +90,6 @@ def test_random_statetest534( data=Bytes( "7f000000000000000000000001000000000000000000000000000000000000000045437f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff457f0000000000000000000000000000000000000000000000000000000000000000436ff3075243846d88747b6a9e7ff28c61" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x55DB76C1, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest535.py b/tests/ported_static/stRandom2/test_random_statetest535.py index 8f31eef37bf..37b56c5f44b 100644 --- a/tests/ported_static/stRandom2/test_random_statetest535.py +++ b/tests/ported_static/stRandom2/test_random_statetest535.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest535Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest535( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest535.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -96,7 +88,6 @@ def test_random_statetest535( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000017f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000100000000000000000000000000000000000000007f0000000000000000000000000000000000000000000000000000000000000000" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x4CD4DC30, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest537.py b/tests/ported_static/stRandom2/test_random_statetest537.py index 5d544116d7f..a6a874b13e6 100644 --- a/tests/ported_static/stRandom2/test_random_statetest537.py +++ b/tests/ported_static/stRandom2/test_random_statetest537.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest537Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest537( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest537.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x945304EB96065B2A98B57A48A06AE28D285A71B5) contract_0 = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87) sender = EOA( @@ -97,7 +89,6 @@ def test_random_statetest537( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7e7f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b5688068515a6a996a540a03686d6d" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x71E432D1, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest539.py b/tests/ported_static/stRandom2/test_random_statetest539.py index 3dc5e4915a2..a96c6badafc 100644 --- a/tests/ported_static/stRandom2/test_random_statetest539.py +++ b/tests/ported_static/stRandom2/test_random_statetest539.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest539Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest539( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest539.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -98,7 +90,6 @@ def test_random_statetest539( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff457f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff096794200bf18b0b316e41" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x55285B09, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest541.py b/tests/ported_static/stRandom2/test_random_statetest541.py index bc1abe1aaa3..e2bf1dbd0b8 100644 --- a/tests/ported_static/stRandom2/test_random_statetest541.py +++ b/tests/ported_static/stRandom2/test_random_statetest541.py @@ -4,10 +4,7 @@ Ported from: state_tests/stRandom2/randomStatetest541Filler.json -@manually-enhanced: Do not overwrite. `gas_limit` raised on Amsterdam -to cover EIP-8037 state-gas spill. Pre-EIP-8037 keeps the original -100 000. - +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -20,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -35,14 +31,8 @@ def test_random_statetest541( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest541.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -98,7 +88,6 @@ def test_random_statetest541( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff457f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000004335696e089257368d07897d57350b10" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x1F529315, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest544.py b/tests/ported_static/stRandom2/test_random_statetest544.py index 77632478d50..c9c27fdc8a3 100644 --- a/tests/ported_static/stRandom2/test_random_statetest544.py +++ b/tests/ported_static/stRandom2/test_random_statetest544.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest544Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest544( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest544.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -94,7 +86,6 @@ def test_random_statetest544( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c3503b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff097f0000000000000000000000000000000000000000000000000000000000000000" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x505C017E, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest545.py b/tests/ported_static/stRandom2/test_random_statetest545.py index 7b95acefeb8..7f30c4566d0 100644 --- a/tests/ported_static/stRandom2/test_random_statetest545.py +++ b/tests/ported_static/stRandom2/test_random_statetest545.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest545Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest545( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest545.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x945304EB96065B2A98B57A48A06AE28D285A71B5) contract_0 = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87) sender = EOA( @@ -100,7 +92,6 @@ def test_random_statetest545( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c350637c9c82133005" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x13226624, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest546.py b/tests/ported_static/stRandom2/test_random_statetest546.py index 8f975634bdf..38aefee3f33 100644 --- a/tests/ported_static/stRandom2/test_random_statetest546.py +++ b/tests/ported_static/stRandom2/test_random_statetest546.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest546Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest546( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest546.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -96,7 +88,6 @@ def test_random_statetest546( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff447f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f0000000000000000000000010000000000000000000000000000000000000000956f895258826c35576592208671731501" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x6B15392F, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest548.py b/tests/ported_static/stRandom2/test_random_statetest548.py index 8523f0f0278..5fa4f888890 100644 --- a/tests/ported_static/stRandom2/test_random_statetest548.py +++ b/tests/ported_static/stRandom2/test_random_statetest548.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest548Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest548( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest548.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -99,7 +91,6 @@ def test_random_statetest548( data=Bytes( "7f0000000000000000000000010000000000000000000000000000000000000000417fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000001000000000000000000000000000000000000000019417f00000000000000000000000000000000000000000000000000000000000000017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6f777a349a646633977da01a315a3c03" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x2AA46F82, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest550.py b/tests/ported_static/stRandom2/test_random_statetest550.py index d4fd817ef9b..1c559d499e3 100644 --- a/tests/ported_static/stRandom2/test_random_statetest550.py +++ b/tests/ported_static/stRandom2/test_random_statetest550.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest550Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest550( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest550.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -101,7 +93,6 @@ def test_random_statetest550( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000006f4472a17829659c94a29041419564313a" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x52EBEDC8, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest552.py b/tests/ported_static/stRandom2/test_random_statetest552.py index bcf8c8f4b85..7c4cc681e0f 100644 --- a/tests/ported_static/stRandom2/test_random_statetest552.py +++ b/tests/ported_static/stRandom2/test_random_statetest552.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest552Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest552( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest552.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -99,7 +91,6 @@ def test_random_statetest552( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff42147ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe547f00000000000000000000000000000000000000000000000000000000000000006f6a72a37b5219f089416d4336a08e82" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x6BD9B58C, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest553.py b/tests/ported_static/stRandom2/test_random_statetest553.py index b3942959256..ea6b94637ba 100644 --- a/tests/ported_static/stRandom2/test_random_statetest553.py +++ b/tests/ported_static/stRandom2/test_random_statetest553.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest553Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest553( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest553.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -96,7 +88,6 @@ def test_random_statetest553( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000016f94819c780585376da073368c45828ca0" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x22FB6, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest555.py b/tests/ported_static/stRandom2/test_random_statetest555.py index e3a668857b2..a69ad529ef3 100644 --- a/tests/ported_static/stRandom2/test_random_statetest555.py +++ b/tests/ported_static/stRandom2/test_random_statetest555.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest555Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest555( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest555.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -97,7 +89,6 @@ def test_random_statetest555( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000000000000000000000000000000000000000c3507ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe437f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff437f000000000000000000000000000000000000000000000000000000000000c3506f3b8f936e6f3874603c59120707e3588c" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x719DE78, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest556.py b/tests/ported_static/stRandom2/test_random_statetest556.py index a2778f086f9..f4619849408 100644 --- a/tests/ported_static/stRandom2/test_random_statetest556.py +++ b/tests/ported_static/stRandom2/test_random_statetest556.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest556Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest556( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest556.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -98,7 +90,6 @@ def test_random_statetest556( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000010000000000000000000000000000000000000000437f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000006f726e757692a2ad96526b9e8b77a33a" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x44F0B58C, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest564.py b/tests/ported_static/stRandom2/test_random_statetest564.py index 22f3acce632..ea8b16d9a10 100644 --- a/tests/ported_static/stRandom2/test_random_statetest564.py +++ b/tests/ported_static/stRandom2/test_random_statetest564.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest564Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest564( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest564.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x945304EB96065B2A98B57A48A06AE28D285A71B5) contract_0 = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87) sender = EOA( @@ -99,7 +91,6 @@ def test_random_statetest564( data=Bytes( "5b7f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b57fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe45500816" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x11182998, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest565.py b/tests/ported_static/stRandom2/test_random_statetest565.py index 33a1df3daea..2a47a89f34b 100644 --- a/tests/ported_static/stRandom2/test_random_statetest565.py +++ b/tests/ported_static/stRandom2/test_random_statetest565.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest565Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest565( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest565.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -97,7 +89,6 @@ def test_random_statetest565( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b57f000000000000000000000000000000000000000000000000000000000000c350137f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000009237" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x28CD0966, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest571.py b/tests/ported_static/stRandom2/test_random_statetest571.py index 38072ed934a..be395a11ad9 100644 --- a/tests/ported_static/stRandom2/test_random_statetest571.py +++ b/tests/ported_static/stRandom2/test_random_statetest571.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest571Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -18,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -33,14 +31,8 @@ def test_random_statetest571( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest571.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -93,7 +85,6 @@ def test_random_statetest571( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000015b7f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e793c6508766c8b6b403a" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x53934784, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest574.py b/tests/ported_static/stRandom2/test_random_statetest574.py index a8f165453a5..b6d30e95fec 100644 --- a/tests/ported_static/stRandom2/test_random_statetest574.py +++ b/tests/ported_static/stRandom2/test_random_statetest574.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest574Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest574( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest574.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -98,7 +90,6 @@ def test_random_statetest574( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000c3507f0000000000000000000000000000000000000000000000000000000000000001047f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff046d369354827d7433a335af" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x5F16646E, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest578.py b/tests/ported_static/stRandom2/test_random_statetest578.py index 01a23742ccc..de6856875cb 100644 --- a/tests/ported_static/stRandom2/test_random_statetest578.py +++ b/tests/ported_static/stRandom2/test_random_statetest578.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest578Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest578( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest578.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -97,7 +89,6 @@ def test_random_statetest578( data=Bytes( "7f000000000000000000000000000000000000000000000000000000000000c350457f00000000000000000000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff42" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x17C973D5, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest580.py b/tests/ported_static/stRandom2/test_random_statetest580.py index 159a1cd03f1..92ed30c5c22 100644 --- a/tests/ported_static/stRandom2/test_random_statetest580.py +++ b/tests/ported_static/stRandom2/test_random_statetest580.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest580Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest580( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest580.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -98,7 +90,6 @@ def test_random_statetest580( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000000000000000000000000000000000000000000000457f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000006f4640879d18777b953a209836379a30" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x2C360421, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest585.py b/tests/ported_static/stRandom2/test_random_statetest585.py index b6b61656f32..d58bca571c4 100644 --- a/tests/ported_static/stRandom2/test_random_statetest585.py +++ b/tests/ported_static/stRandom2/test_random_statetest585.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest585Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest585( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest585.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -99,7 +91,6 @@ def test_random_statetest585( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x16B2537A, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest586.py b/tests/ported_static/stRandom2/test_random_statetest586.py index c2c81827ba2..f4c991735fe 100644 --- a/tests/ported_static/stRandom2/test_random_statetest586.py +++ b/tests/ported_static/stRandom2/test_random_statetest586.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest586Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -18,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -33,14 +31,8 @@ def test_random_statetest586( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest586.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -92,7 +84,6 @@ def test_random_statetest586( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000000000000000000000000000000000000000000137" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x65DC324C, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest587.py b/tests/ported_static/stRandom2/test_random_statetest587.py index 6f9aa5a84cd..cda51656336 100644 --- a/tests/ported_static/stRandom2/test_random_statetest587.py +++ b/tests/ported_static/stRandom2/test_random_statetest587.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest587Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest587( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest587.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -96,7 +88,6 @@ def test_random_statetest587( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c350117f0000000000000000000000000000000000000000000000000000000000000001457f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e796f8b7152a3958a923c1665b27557089a" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x11604410, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest588.py b/tests/ported_static/stRandom2/test_random_statetest588.py index 467e1489dfe..d5e3d32ffb2 100644 --- a/tests/ported_static/stRandom2/test_random_statetest588.py +++ b/tests/ported_static/stRandom2/test_random_statetest588.py @@ -4,10 +4,8 @@ Ported from: state_tests/stRandom2/randomStatetest588Filler.json -@manually-enhanced: Do not overwrite. `gas_limit` raised on Amsterdam -to cover EIP-8037 state-gas spill. Pre-EIP-8037 keeps the original -100 000. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -20,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -35,14 +32,8 @@ def test_random_statetest588( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest588.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -98,7 +89,6 @@ def test_random_statetest588( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff41437f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff430637" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x66D6BC77, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest592.py b/tests/ported_static/stRandom2/test_random_statetest592.py index ef4e670fcdd..ab07f946f34 100644 --- a/tests/ported_static/stRandom2/test_random_statetest592.py +++ b/tests/ported_static/stRandom2/test_random_statetest592.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest592Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest592( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest592.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -97,7 +89,6 @@ def test_random_statetest592( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79457fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff09" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x6339E0E5, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest596.py b/tests/ported_static/stRandom2/test_random_statetest596.py index 9772ffda560..2f438f47db7 100644 --- a/tests/ported_static/stRandom2/test_random_statetest596.py +++ b/tests/ported_static/stRandom2/test_random_statetest596.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest596Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest596( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest596.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -94,7 +86,6 @@ def test_random_statetest596( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000001317f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000006f7066a3507f6e090653945638306520" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x2D99F481, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest599.py b/tests/ported_static/stRandom2/test_random_statetest599.py index 9e8f8f0973c..f7f52ec9962 100644 --- a/tests/ported_static/stRandom2/test_random_statetest599.py +++ b/tests/ported_static/stRandom2/test_random_statetest599.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest599Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest599( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest599.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -102,7 +94,6 @@ def test_random_statetest599( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6f8d6c60440a44449372068a976a8382" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x421144B6, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest600.py b/tests/ported_static/stRandom2/test_random_statetest600.py index 1386a39da07..ae373101aa7 100644 --- a/tests/ported_static/stRandom2/test_random_statetest600.py +++ b/tests/ported_static/stRandom2/test_random_statetest600.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest600Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest600( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest600.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -98,7 +90,6 @@ def test_random_statetest600( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000000000000000000000000000000000000000c35043457ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe6f0b6f37208e76a402927039198c969907" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0xAD3F19C, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest602.py b/tests/ported_static/stRandom2/test_random_statetest602.py index 9d569c1d26b..05cf83d935e 100644 --- a/tests/ported_static/stRandom2/test_random_statetest602.py +++ b/tests/ported_static/stRandom2/test_random_statetest602.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest602Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest602( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest602.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -95,7 +87,6 @@ def test_random_statetest602( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x25D01724, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest603.py b/tests/ported_static/stRandom2/test_random_statetest603.py index 6f5517dc6a3..0e6dde9eff3 100644 --- a/tests/ported_static/stRandom2/test_random_statetest603.py +++ b/tests/ported_static/stRandom2/test_random_statetest603.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest603Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest603( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest603.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -97,7 +89,6 @@ def test_random_statetest603( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79427f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f00000000000000000000000100000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79086f655860560745326476a03cdc360634" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x23FCF7F2, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest605.py b/tests/ported_static/stRandom2/test_random_statetest605.py index 6dc04cf8552..91275188c47 100644 --- a/tests/ported_static/stRandom2/test_random_statetest605.py +++ b/tests/ported_static/stRandom2/test_random_statetest605.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest605Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest605( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest605.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -100,7 +92,6 @@ def test_random_statetest605( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000000000000000000000000000000000000000c350437f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9058038508" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x650044FA, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest607.py b/tests/ported_static/stRandom2/test_random_statetest607.py index 6f6e4f94516..4b9d2f729e2 100644 --- a/tests/ported_static/stRandom2/test_random_statetest607.py +++ b/tests/ported_static/stRandom2/test_random_statetest607.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest607Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest607( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest607.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -95,7 +87,6 @@ def test_random_statetest607( data=Bytes( "7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff09" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x106DF7F8, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest608.py b/tests/ported_static/stRandom2/test_random_statetest608.py index 2a9daf5e2cb..84918022a5b 100644 --- a/tests/ported_static/stRandom2/test_random_statetest608.py +++ b/tests/ported_static/stRandom2/test_random_statetest608.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest608Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest608( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest608.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -95,7 +87,6 @@ def test_random_statetest608( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000000000000000000000000000000000000000c350537fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x1EB2352A, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest610.py b/tests/ported_static/stRandom2/test_random_statetest610.py index 449efdeb9c9..2db82865c51 100644 --- a/tests/ported_static/stRandom2/test_random_statetest610.py +++ b/tests/ported_static/stRandom2/test_random_statetest610.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest610Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest610( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest610.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -98,7 +90,6 @@ def test_random_statetest610( data=Bytes( "417f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000000000000000000000000000000000000000c3507f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff6f01f353a2437e4384726497587b8556" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x7FDD9C9C, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest615.py b/tests/ported_static/stRandom2/test_random_statetest615.py index b3330682e09..9bd0741df64 100644 --- a/tests/ported_static/stRandom2/test_random_statetest615.py +++ b/tests/ported_static/stRandom2/test_random_statetest615.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest615Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest615( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest615.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -103,7 +95,6 @@ def test_random_statetest615( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe837f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000001000000000000000000000000000000000000000009556c6f390a3054d7368a9a" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x7BCC296A, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest616.py b/tests/ported_static/stRandom2/test_random_statetest616.py index a5a984ee14d..e104befe476 100644 --- a/tests/ported_static/stRandom2/test_random_statetest616.py +++ b/tests/ported_static/stRandom2/test_random_statetest616.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest616Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest616( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest616.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -97,7 +89,6 @@ def test_random_statetest616( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000006f86a2409b991539f0423c0342363c3b" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x45949A6F, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest620.py b/tests/ported_static/stRandom2/test_random_statetest620.py index d1120537994..a35f659fe76 100644 --- a/tests/ported_static/stRandom2/test_random_statetest620.py +++ b/tests/ported_static/stRandom2/test_random_statetest620.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest620Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest620( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest620.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -100,7 +92,6 @@ def test_random_statetest620( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff427f0000000000000000000000010000000000000000000000000000000000000000457ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6f6c54a420327d73727d9d1a667bf389" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x61F75E26, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest621.py b/tests/ported_static/stRandom2/test_random_statetest621.py index 53a4825c2f0..a36b82e0fe4 100644 --- a/tests/ported_static/stRandom2/test_random_statetest621.py +++ b/tests/ported_static/stRandom2/test_random_statetest621.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest621Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest621( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest621.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -92,7 +84,6 @@ def test_random_statetest621( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f0000000000000000000000000000000000000000000000000000000000000000441a7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff6f3ba187a19366899e595220741232905b" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x7FC94217, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest629.py b/tests/ported_static/stRandom2/test_random_statetest629.py index e5fb21ac908..1ae7ffdd02e 100644 --- a/tests/ported_static/stRandom2/test_random_statetest629.py +++ b/tests/ported_static/stRandom2/test_random_statetest629.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest629Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest629( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest629.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -100,7 +92,6 @@ def test_random_statetest629( data=Bytes( "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79347f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e79116f427277147c617f4354a35a1a47977a" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x3BCDBA80, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest630.py b/tests/ported_static/stRandom2/test_random_statetest630.py index 232e5045340..13619602736 100644 --- a/tests/ported_static/stRandom2/test_random_statetest630.py +++ b/tests/ported_static/stRandom2/test_random_statetest630.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest630Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest630( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest630.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -99,7 +91,6 @@ def test_random_statetest630( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000017ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f0000000000000000000000000000000000000000000000000000000000000001427f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000100000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6f9461a46e61507a1206917b17137e7e" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x188B5E42, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest633.py b/tests/ported_static/stRandom2/test_random_statetest633.py index 200694e5db0..262759dd674 100644 --- a/tests/ported_static/stRandom2/test_random_statetest633.py +++ b/tests/ported_static/stRandom2/test_random_statetest633.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest633Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest633( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest633.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -102,7 +94,6 @@ def test_random_statetest633( data=Bytes( "7f00000000000000000000000100000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000017f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e796f82941340756317567250f1573a8976" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x50F61B39, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest637.py b/tests/ported_static/stRandom2/test_random_statetest637.py index d896ccab731..5f0cc74718f 100644 --- a/tests/ported_static/stRandom2/test_random_statetest637.py +++ b/tests/ported_static/stRandom2/test_random_statetest637.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest637Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest637( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest637.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -97,7 +89,6 @@ def test_random_statetest637( data=Bytes( "7f00000000000000000000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000000000000000000000000000000000000000c3507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6f44931064138e9df1768334028c201471" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x58337064, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest638.py b/tests/ported_static/stRandom2/test_random_statetest638.py index 35ce2c4613a..0a732a2f891 100644 --- a/tests/ported_static/stRandom2/test_random_statetest638.py +++ b/tests/ported_static/stRandom2/test_random_statetest638.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest638Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest638( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest638.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -99,7 +91,6 @@ def test_random_statetest638( data=Bytes( "7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000100000000000000000000000000000000000000007f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff09" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0x791E3396, ) diff --git a/tests/ported_static/stRandom2/test_random_statetest641.py b/tests/ported_static/stRandom2/test_random_statetest641.py index f81dffeabc8..a1e2749decb 100644 --- a/tests/ported_static/stRandom2/test_random_statetest641.py +++ b/tests/ported_static/stRandom2/test_random_statetest641.py @@ -3,9 +3,8 @@ Ported from: state_tests/stRandom2/randomStatetest641Filler.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_random_statetest641( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_random_statetest641.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 100k tx_gas. - tx_gas_limit = 100000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 500_000 - coinbase = Address(0x4F3F701464972E74606D6EA82D4D3080599A0E79) sender = EOA( key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 @@ -100,7 +92,6 @@ def test_random_statetest641( data=Bytes( "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000017f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000004f3f701464972e74606d6ea82d4d3080599a0e797f000000000000000000000000ffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe6f29199c9aa4054170f1a15a55056f96" # noqa: E501 ), - gas_limit=tx_gas_limit, value=0xF08F864, ) diff --git a/tests/ported_static/stRevertTest/test_revert_in_create_in_init_paris.py b/tests/ported_static/stRevertTest/test_revert_in_create_in_init_paris.py index 89789d898b9..87e09b013fb 100644 --- a/tests/ported_static/stRevertTest/test_revert_in_create_in_init_paris.py +++ b/tests/ported_static/stRevertTest/test_revert_in_create_in_init_paris.py @@ -3,10 +3,8 @@ Ported from: state_tests/stRevertTest/RevertInCreateInInit_ParisFiller.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 NEW_ACCOUNT state-gas spill in nested CREATE; -pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +31,8 @@ def test_revert_in_create_in_init_paris( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_revert_in_create_in_init_paris.""" - # EIP-8037 NEW_ACCOUNT state-gas spill OoGs the nested CREATE. - tx_gas_limit = 200000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 1_000_000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) addr = Address(0x4757608F18B70777AE788DD4056EEED52F7AA68F) sender = EOA( @@ -75,7 +66,6 @@ def test_revert_in_create_in_init_paris( + Op.MSTORE(offset=0x0, value=0x112233) + Op.REVERT(offset=0x0, size=0x20) + Op.STOP, - gas_limit=tx_gas_limit, ) post = {addr: Account(storage={0: 1}, balance=10)} diff --git a/tests/ported_static/stSystemOperationsTest/test_callcode_to_return1.py b/tests/ported_static/stSystemOperationsTest/test_callcode_to_return1.py index 6043500f367..4b13b9bf3aa 100644 --- a/tests/ported_static/stSystemOperationsTest/test_callcode_to_return1.py +++ b/tests/ported_static/stSystemOperationsTest/test_callcode_to_return1.py @@ -3,10 +3,8 @@ Ported from: state_tests/stSystemOperationsTest/callcodeToReturn1Filler.json -@manually-enhanced: Do not overwrite. Gas bumped fork-conditionally -to cover EIP-8037 state-gas spill into regular gas; pre-EIP-8037 -behavior unchanged. +@manually-enhanced: Do not overwrite. Explicit gas values removed. """ import pytest @@ -19,7 +17,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +31,8 @@ def test_callcode_to_return1( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_callcode_to_return1.""" - # EIP-8037 gas bumps: original values for pre-EIP-8037 forks. - inner_call_gas = 50000 - if fork.is_eip_enabled(8037): - inner_call_gas = 1000000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -77,7 +68,6 @@ def test_callcode_to_return1( + Op.SSTORE( key=0x0, value=Op.CALLCODE( - gas=inner_call_gas, address=addr, value=0x17, args_offset=0x0, @@ -91,13 +81,7 @@ def test_callcode_to_return1( nonce=0, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=3000000, - value=0x186A0, - ) + tx = Transaction(sender=sender, to=target, data=Bytes(""), value=0x186A0) post = {target: Account(storage={0: 1, 1: 1}, nonce=0)} diff --git a/tests/ported_static/stSystemOperationsTest/test_create_name_registrator.py b/tests/ported_static/stSystemOperationsTest/test_create_name_registrator.py index 25f3523cd44..5b1437d14d8 100644 --- a/tests/ported_static/stSystemOperationsTest/test_create_name_registrator.py +++ b/tests/ported_static/stSystemOperationsTest/test_create_name_registrator.py @@ -3,9 +3,8 @@ Ported from: state_tests/stSystemOperationsTest/createNameRegistratorFiller.json -@manually-enhanced: Do not overwrite. tx `gas_limit` bumped on Amsterdam -to cover EIP-8037 state-gas spill; pre-EIP-8037 unchanged. +@manually-enhanced: Do not overwrite. tx `gas_limit` has been removed. """ import pytest @@ -19,7 +18,6 @@ Transaction, compute_create_address, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -34,14 +32,8 @@ def test_create_name_registrator( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test_create_name_registrator.""" - # EIP-8037 state-gas spill on Amsterdam exceeds 300k tx_gas. - tx_gas_limit = 300000 - if fork.is_eip_enabled(8037): - tx_gas_limit = 1_000_000 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) contract_0 = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87) sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) @@ -71,11 +63,7 @@ def test_create_name_registrator( ) tx = Transaction( - sender=sender, - to=contract_0, - data=Bytes(""), - gas_limit=tx_gas_limit, - value=0x186A0, + sender=sender, to=contract_0, data=Bytes(""), value=0x186A0 ) post = { diff --git a/tests/prague/eip2537_bls_12_381_precompiles/conftest.py b/tests/prague/eip2537_bls_12_381_precompiles/conftest.py index 3c2777df3c7..eb0c3ad4cb7 100644 --- a/tests/prague/eip2537_bls_12_381_precompiles/conftest.py +++ b/tests/prague/eip2537_bls_12_381_precompiles/conftest.py @@ -13,7 +13,6 @@ precompile_gas_modifier, # noqa: F401 sender, # noqa: F401 tx, # noqa: F401 - tx_gas_limit, # noqa: F401 ) from .helpers import BLSPointGenerator from .spec import build_gas_calculation_function_map diff --git a/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_precompiles_before_fork.py b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_precompiles_before_fork.py index ac69e5a7299..f0808bd3273 100644 --- a/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_precompiles_before_fork.py +++ b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_precompiles_before_fork.py @@ -49,28 +49,6 @@ def precompile_gas( return calculated_gas -@pytest.fixture -def tx_gas_limit( - fork: TransitionFork, input_data: bytes, precompile_gas: int -) -> int: - """ - Transaction gas limit used for the test (Can be overridden in the test). - """ - intrinsic_gas_cost_calculator = ( - fork.transitions_from().transaction_intrinsic_cost_calculator() - ) - memory_expansion_gas_calculator = ( - fork.transitions_from().memory_expansion_gas_calculator() - ) - extra_gas = 100_000 - return ( - extra_gas - + intrinsic_gas_cost_calculator(calldata=input_data) - + memory_expansion_gas_calculator(new_bytes=len(input_data)) - + precompile_gas - ) - - @pytest.mark.parametrize( "precompile_address,input_data", [ diff --git a/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_variable_length_input_contracts.py b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_variable_length_input_contracts.py index 40769eaf3de..8d4a4002710 100644 --- a/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_variable_length_input_contracts.py +++ b/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_variable_length_input_contracts.py @@ -13,7 +13,6 @@ from execution_testing import ( Alloc, Bytecode, - Environment, Fork, Op, ParameterSet, @@ -67,21 +66,6 @@ def input_length_modifier() -> int: return 0 -@pytest.fixture -def env(fork: Fork, tx: Transaction) -> Environment: - """Environment used for all tests.""" - env = Environment() - tx_gas_limit_cap = fork.transaction_gas_limit_cap() - if tx_gas_limit_cap is not None: - assert tx.gas_limit <= tx_gas_limit_cap, ( - "tx exceeds gas limit cap: " - f"{int(tx.gas_limit)} > {tx_gas_limit_cap}" - ) - if tx.gas_limit > env.gas_limit: - env = Environment(gas_limit=tx.gas_limit) - return env - - @pytest.fixture def call_contract_code( precompile_address: int, @@ -189,22 +173,6 @@ def tx_gas_limit_calculator( ) -@pytest.fixture -def tx_gas_limit( - fork: Fork, - input_data: bytes, - precompile_gas_list: List[int], - precompile_data_length_list: List[int], -) -> int: - """ - Transaction gas limit used for the test (Can be overridden in the test). - """ - assert len(input_data) == 0, "Expected empty data in the transaction." - return tx_gas_limit_calculator( - fork, precompile_gas_list, max(precompile_data_length_list) - ) - - def get_split_discount_table_by_fork( gas_fn: Callable, discount_table_length: int, element_length: int ) -> Callable[[Fork], List[ParameterSet]]: @@ -303,7 +271,6 @@ def get_range_cost(min_index: int, max_index: int) -> int: @pytest.mark.slow() def test_valid_gas_g1msm( state_test: StateTestFiller, - env: Environment, pre: Alloc, post: dict, tx: Transaction, @@ -316,7 +283,6 @@ def test_valid_gas_g1msm( If any of the calls fail, the test will fail. """ state_test( - env=env, pre=pre, tx=tx, post=post, @@ -337,14 +303,12 @@ def test_valid_gas_g1msm( @pytest.mark.parametrize("precompile_address", [Spec.G1MSM]) def test_invalid_zero_gas_g1msm( state_test: StateTestFiller, - env: Environment, pre: Alloc, post: dict, tx: Transaction, ) -> None: """Test the BLS12_G1MSM precompile calling it with zero gas.""" state_test( - env=env, pre=pre, tx=tx, post=post, @@ -365,7 +329,6 @@ def test_invalid_zero_gas_g1msm( @pytest.mark.eels_base_coverage def test_invalid_gas_g1msm( state_test: StateTestFiller, - env: Environment, pre: Alloc, post: dict, tx: Transaction, @@ -378,7 +341,6 @@ def test_invalid_gas_g1msm( If any of the calls succeeds, the test will fail. """ state_test( - env=env, pre=pre, tx=tx, post=post, @@ -399,14 +361,12 @@ def test_invalid_gas_g1msm( @pytest.mark.parametrize("precompile_address", [Spec.G1MSM]) def test_invalid_zero_length_g1msm( state_test: StateTestFiller, - env: Environment, pre: Alloc, post: dict, tx: Transaction, ) -> None: """Test the BLS12_G1MSM precompile by passing an input with zero length.""" state_test( - env=env, pre=pre, tx=tx, post=post, @@ -430,7 +390,6 @@ def test_invalid_zero_length_g1msm( @pytest.mark.parametrize("precompile_address", [Spec.G1MSM]) def test_invalid_length_g1msm( state_test: StateTestFiller, - env: Environment, pre: Alloc, post: dict, tx: Transaction, @@ -443,7 +402,6 @@ def test_invalid_length_g1msm( If any of the calls succeeds, the test will fail. """ state_test( - env=env, pre=pre, tx=tx, post=post, @@ -462,7 +420,6 @@ def test_invalid_length_g1msm( @pytest.mark.slow() def test_valid_gas_g2msm( state_test: StateTestFiller, - env: Environment, pre: Alloc, post: dict, tx: Transaction, @@ -475,7 +432,6 @@ def test_valid_gas_g2msm( If any of the calls fail, the test will fail. """ state_test( - env=env, pre=pre, tx=tx, post=post, @@ -496,14 +452,12 @@ def test_valid_gas_g2msm( @pytest.mark.parametrize("precompile_address", [Spec.G2MSM]) def test_invalid_zero_gas_g2msm( state_test: StateTestFiller, - env: Environment, pre: Alloc, post: dict, tx: Transaction, ) -> None: """Test the BLS12_G2MSM precompile calling it with zero gas.""" state_test( - env=env, pre=pre, tx=tx, post=post, @@ -524,7 +478,6 @@ def test_invalid_zero_gas_g2msm( @pytest.mark.eels_base_coverage def test_invalid_gas_g2msm( state_test: StateTestFiller, - env: Environment, pre: Alloc, post: dict, tx: Transaction, @@ -537,7 +490,6 @@ def test_invalid_gas_g2msm( If any of the calls succeeds, the test will fail. """ state_test( - env=env, pre=pre, tx=tx, post=post, @@ -558,14 +510,12 @@ def test_invalid_gas_g2msm( @pytest.mark.parametrize("precompile_address", [Spec.G2MSM]) def test_invalid_zero_length_g2msm( state_test: StateTestFiller, - env: Environment, pre: Alloc, post: dict, tx: Transaction, ) -> None: """Test the BLS12_G2MSM precompile by passing an input with zero length.""" state_test( - env=env, pre=pre, tx=tx, post=post, @@ -589,7 +539,6 @@ def test_invalid_zero_length_g2msm( @pytest.mark.parametrize("precompile_address", [Spec.G2MSM]) def test_invalid_length_g2msm( state_test: StateTestFiller, - env: Environment, pre: Alloc, post: dict, tx: Transaction, @@ -602,7 +551,6 @@ def test_invalid_length_g2msm( If any of the calls succeeds, the test will fail. """ state_test( - env=env, pre=pre, tx=tx, post=post, @@ -621,7 +569,6 @@ def test_invalid_length_g2msm( @pytest.mark.slow() def test_valid_gas_pairing( state_test: StateTestFiller, - env: Environment, pre: Alloc, post: dict, tx: Transaction, @@ -633,7 +580,6 @@ def test_valid_gas_pairing( If any of the calls fails, the test will fail. """ state_test( - env=env, pre=pre, tx=tx, post=post, @@ -654,14 +600,12 @@ def test_valid_gas_pairing( @pytest.mark.parametrize("precompile_address", [Spec.PAIRING]) def test_invalid_zero_gas_pairing( state_test: StateTestFiller, - env: Environment, pre: Alloc, post: dict, tx: Transaction, ) -> None: """Test the BLS12_PAIRING precompile calling it with zero gas.""" state_test( - env=env, pre=pre, tx=tx, post=post, @@ -681,7 +625,6 @@ def test_invalid_zero_gas_pairing( @pytest.mark.parametrize("precompile_address", [Spec.PAIRING]) def test_invalid_gas_pairing( state_test: StateTestFiller, - env: Environment, pre: Alloc, post: dict, tx: Transaction, @@ -694,7 +637,6 @@ def test_invalid_gas_pairing( If any of the calls succeeds, the test will fail. """ state_test( - env=env, pre=pre, tx=tx, post=post, @@ -715,7 +657,6 @@ def test_invalid_gas_pairing( @pytest.mark.parametrize("precompile_address", [Spec.PAIRING]) def test_invalid_zero_length_pairing( state_test: StateTestFiller, - env: Environment, pre: Alloc, post: dict, tx: Transaction, @@ -724,7 +665,6 @@ def test_invalid_zero_length_pairing( Test the BLS12_PAIRING precompile by passing an input with zero length. """ state_test( - env=env, pre=pre, tx=tx, post=post, @@ -748,7 +688,6 @@ def test_invalid_zero_length_pairing( @pytest.mark.parametrize("precompile_address", [Spec.PAIRING]) def test_invalid_length_pairing( state_test: StateTestFiller, - env: Environment, pre: Alloc, post: dict, tx: Transaction, @@ -761,7 +700,6 @@ def test_invalid_length_pairing( If any of the calls succeeds, the test will fail. """ state_test( - env=env, pre=pre, tx=tx, post=post, diff --git a/tests/prague/eip2935_historical_block_hashes_from_state/test_block_hashes.py b/tests/prague/eip2935_historical_block_hashes_from_state/test_block_hashes.py index e82689e0b46..9f6f9bb710d 100644 --- a/tests/prague/eip2935_historical_block_hashes_from_state/test_block_hashes.py +++ b/tests/prague/eip2935_historical_block_hashes_from_state/test_block_hashes.py @@ -136,7 +136,7 @@ def test_block_hashes_history_at_transition( blocks: List[Block] = [] assert blocks_before_fork >= 1 and blocks_before_fork < Spec.FORK_TIMESTAMP - sender = pre.fund_eoa(10_000_000_000) + sender = pre.fund_eoa() post: Dict[Address, Account] = {} current_block_number = 1 fork_block_number = current_block_number + blocks_before_fork @@ -176,7 +176,6 @@ def test_block_hashes_history_at_transition( txs.append( Transaction( to=check_blocks_before_fork_address, - gas_limit=10_000_000, sender=sender, ) ) @@ -208,7 +207,6 @@ def test_block_hashes_history_at_transition( txs.append( Transaction( to=check_blocks_after_fork_address, - gas_limit=10_000_000, sender=sender, ) ) @@ -258,7 +256,7 @@ def test_block_hashes_history( """ blocks: List[Block] = [] - sender = pre.fund_eoa(10_000_000_000) + sender = pre.fund_eoa() post: Dict[Address, Account] = {} current_block_number = 1 fork_block_number = 0 # We fork at genesis @@ -328,7 +326,6 @@ def test_block_hashes_history( txs.append( Transaction( to=check_blocks_after_fork_address, - gas_limit=10_000_000, sender=sender, ) ) @@ -383,7 +380,6 @@ def test_block_hashes_call_opcodes( txs=[ Transaction( to=contract_address, - gas_limit=10_000_000, sender=pre.fund_eoa(), ) ] @@ -452,7 +448,6 @@ def test_invalid_history_contract_calls( txs = [ Transaction( to=check_contract_address, - gas_limit=10_000_000, sender=pre.fund_eoa(), ) ] @@ -515,7 +510,6 @@ def test_invalid_history_contract_calls_input_size( txs = [ Transaction( to=check_contract_address, - gas_limit=10_000_000, sender=pre.fund_eoa(), ) ] diff --git a/tests/prague/eip2935_historical_block_hashes_from_state/test_contract_deployment.py b/tests/prague/eip2935_historical_block_hashes_from_state/test_contract_deployment.py index e09070116d1..f22eef36184 100644 --- a/tests/prague/eip2935_historical_block_hashes_from_state/test_contract_deployment.py +++ b/tests/prague/eip2935_historical_block_hashes_from_state/test_contract_deployment.py @@ -62,11 +62,7 @@ def test_system_contract_deployment( ) deployed_contract = pre.deploy_contract(code) - tx = Transaction( - to=deployed_contract, - gas_limit=10_000_000, - sender=pre.fund_eoa(), - ) + tx = Transaction(to=deployed_contract, sender=pre.fund_eoa()) yield Block(txs=[tx]) diff --git a/tests/prague/eip6110_deposits/conftest.py b/tests/prague/eip6110_deposits/conftest.py index c9ec19524f8..2f26cde8ddb 100644 --- a/tests/prague/eip6110_deposits/conftest.py +++ b/tests/prague/eip6110_deposits/conftest.py @@ -12,6 +12,7 @@ Requests, Transaction, ) +from execution_testing.base_types import HexNumber from .helpers import DepositInteractionBase, DepositRequest @@ -34,26 +35,18 @@ def txs( prepared_requests: List[DepositInteractionBase], ) -> List[Transaction]: """List of transactions to include in the block.""" + floor_cost = fork.transaction_data_floor_cost_calculator() txs = [] for r in prepared_requests: txs += r.transactions() - # EIP-7976 (enabled with EIP-8037 on Amsterdam) raises calldata - # floor cost, pushing the intrinsic above the hardcoded - # tx_gas_limit of the large-calldata OOG fixtures. Lift each - # tx's gas_limit to the new intrinsic only when it falls below; - # the tx still OOGs on its first execution opcode, preserving - # the fixture's no-deposits-applied outcome. - if not (fork.is_eip_enabled(7976) and fork.is_eip_enabled(8037)): - return txs - current_calc = fork.transaction_intrinsic_cost_calculator() - bumped: List[Transaction] = [] for tx in txs: - current_intrinsic = current_calc(calldata=tx.data) - if tx.gas_limit < current_intrinsic: - bumped.append(tx.copy(gas_limit=current_intrinsic)) - else: - bumped.append(tx) - return bumped + if "gas_limit" in tx.model_fields_set and tx.error is None: + # Keep explicit limits above the fork's calldata floor + # (EIP-8037 repricing). Error tests keep their exact limit. + tx.gas_limit = HexNumber( + max(int(tx.gas_limit), floor_cost(data=tx.data) + 1) + ) + return txs @pytest.fixture diff --git a/tests/prague/eip6110_deposits/helpers.py b/tests/prague/eip6110_deposits/helpers.py index e20b7b64966..dcccf62cba2 100644 --- a/tests/prague/eip6110_deposits/helpers.py +++ b/tests/prague/eip6110_deposits/helpers.py @@ -85,7 +85,7 @@ class DepositRequest(DepositRequestBase): valid: bool = True """Whether the deposit request is valid or not.""" - gas_limit: int = 1_000_000 + gas_limit: int | None = None """Gas limit for the call.""" calldata_modifier: Callable[[bytes], bytes] = lambda x: x """Calldata modifier function.""" @@ -218,8 +218,6 @@ def with_source_address(self, source_address: Address) -> "DepositRequest": class DepositInteractionBase: """Base class for all types of deposit transactions we want to test.""" - sender_balance: int = 32_000_000_000_000_000_000 * 100 - """Balance of the account that sends the transaction.""" sender_account: EOA | None = None """Account that sends the transaction.""" requests: List[DepositRequest] @@ -257,21 +255,30 @@ def transactions(self) -> List[Transaction]: assert self.sender_account is not None, ( "Sender account not initialized" ) - return [ - Transaction( - gas_limit=request.gas_limit, - gas_price=0x07, - to=request.interaction_contract_address, - value=request.value, - data=request.calldata, - sender=self.sender_account, - ) - for request in self.requests - ] + txs: List[Transaction] = [] + for request in self.requests: + gas_limit = request.gas_limit + if gas_limit is not None: + tx = Transaction( + gas_limit=request.gas_limit, + to=request.interaction_contract_address, + value=request.value, + data=request.calldata, + sender=self.sender_account, + ) + else: + tx = Transaction( + to=request.interaction_contract_address, + value=request.value, + data=request.calldata, + sender=self.sender_account, + ) + txs.append(tx) + return txs def update_pre(self, pre: Alloc) -> Self: """Return a copy of self with `sender_account` populated.""" - return replace(self, sender_account=pre.fund_eoa(self.sender_balance)) + return replace(self, sender_account=pre.fund_eoa()) def valid_requests(self, current_minimum_fee: int) -> List[DepositRequest]: """ @@ -289,8 +296,8 @@ def valid_requests(self, current_minimum_fee: int) -> List[DepositRequest]: class DepositContract(DepositInteractionBase): """Class used to describe a deposit originated from a contract.""" - tx_gas_limit: int = 1_000_000 - """Gas limit for the transaction.""" + tx_gas_limit: int | None = None + """Gas limit for the transaction. `None` uses the implicit gas limit.""" tx_value: int = 0 """Value to send with the transaction.""" @@ -326,7 +333,7 @@ def contract_code(self) -> Bytecode: 0, current_offset, len(r.calldata) ) + Op.POP( self.call_type( - Op.GAS if r.gas_limit == -1 else r.gas_limit, + Op.GAS if r.gas_limit is None else r.gas_limit, r.interaction_contract_address, *value_arg, 0, @@ -343,7 +350,6 @@ def transactions(self) -> List[Transaction]: return [ Transaction( gas_limit=self.tx_gas_limit, - gas_price=0x07, to=self.entry_address, value=self.tx_value, data=b"".join(r.calldata for r in self.requests), @@ -356,12 +362,7 @@ def update_pre(self, pre: Alloc) -> Self: Return a copy of self with the allocated sender/contract/entry addresses populated. """ - required_balance = self.sender_balance - if self.tx_value > 0: - required_balance = max( - required_balance, self.tx_value + self.tx_gas_limit * 7 - ) - sender_account = pre.fund_eoa(required_balance) + sender_account = pre.fund_eoa() contract_address = pre.deploy_contract( code=self.contract_code, balance=self.contract_balance ) diff --git a/tests/prague/eip6110_deposits/test_deposits.py b/tests/prague/eip6110_deposits/test_deposits.py index a27f3ab7288..82f42cd9fdf 100644 --- a/tests/prague/eip6110_deposits/test_deposits.py +++ b/tests/prague/eip6110_deposits/test_deposits.py @@ -13,7 +13,6 @@ Block, BlockchainTestFiller, BlockException, - Environment, Macros, Op, ) @@ -58,7 +57,6 @@ index=0x0, ) ], - sender_balance=120_000_001_000_000_000 * 10**9, ), ], id="single_deposit_from_eoa_huge_amount", @@ -319,7 +317,6 @@ ) for i in range(450) ], - tx_gas_limit=16_777_216, ), ], id="many_deposits_from_contract", @@ -391,7 +388,6 @@ withdrawal_credentials=0x02, amount=1_000_000_000, signature=0x03, - gas_limit=1_000_000, index=0x0, ), ], @@ -409,7 +405,6 @@ amount=1_000_000_000, signature=0x03, index=0x0, - gas_limit=1_000_000, ), DepositRequest( pubkey=0x01, @@ -712,7 +707,6 @@ ) ], call_depth=271, - tx_gas_limit=16_777_216, ), ], id="single_deposit_from_contract_call_depth_high", @@ -923,12 +917,7 @@ def test_deposit( blocks: List[Block], ) -> None: """Test making a deposit to the beacon chain deposit contract.""" - total_gas_limit = sum(tx.gas_limit for tx in blocks[0].txs) - env = Environment() - if total_gas_limit > env.gas_limit: - env = Environment(gas_limit=total_gas_limit) blockchain_test( - genesis_environment=env, pre=pre, post={}, blocks=blocks, diff --git a/tests/prague/eip7002_el_triggerable_withdrawals/conftest.py b/tests/prague/eip7002_el_triggerable_withdrawals/conftest.py index 441f4117382..ffcfd918794 100644 --- a/tests/prague/eip7002_el_triggerable_withdrawals/conftest.py +++ b/tests/prague/eip7002_el_triggerable_withdrawals/conftest.py @@ -13,10 +13,7 @@ TransitionFork, ) -from .helpers import ( - WithdrawalRequest, - WithdrawalRequestInteractionBase, -) +from .helpers import WithdrawalRequest, WithdrawalRequestInteractionBase from .spec import Spec @@ -123,9 +120,7 @@ def blocks( assert not block_included_requests blocks.append( Block( - txs=sum( - (r.transactions(block_fork) for r in block_requests), [] - ), + txs=sum((r.transactions() for r in block_requests), []), header_verify=header_verify, timestamp=timestamp, ) diff --git a/tests/prague/eip7002_el_triggerable_withdrawals/helpers.py b/tests/prague/eip7002_el_triggerable_withdrawals/helpers.py index 41303a82805..5eb36d1de2b 100644 --- a/tests/prague/eip7002_el_triggerable_withdrawals/helpers.py +++ b/tests/prague/eip7002_el_triggerable_withdrawals/helpers.py @@ -10,7 +10,6 @@ Address, Alloc, Bytecode, - Fork, Op, Transaction, ) @@ -33,7 +32,7 @@ class WithdrawalRequest(WithdrawalRequestBase): """ valid: bool = True """Whether the withdrawal request is valid or not.""" - gas_limit: int = 1_000_000 + gas_limit: int | None = None """Gas limit for the call.""" calldata_modifier: Callable[[bytes], bytes] = lambda x: x """Calldata modifier function.""" @@ -74,14 +73,12 @@ def with_source_address( class WithdrawalRequestInteractionBase: """Base class for all types of withdrawal transactions we want to test.""" - sender_balance: int = 1_000_000_000_000_000_000 - """Balance of the account that sends the transaction.""" sender_account: EOA | None = None """Account that will send the transaction.""" requests: List[WithdrawalRequest] """Withdrawal request to be included in the block.""" - def transactions(self, fork: Fork | None = None) -> List[Transaction]: + def transactions(self) -> List[Transaction]: """Return a transaction for the withdrawal request.""" raise NotImplementedError @@ -110,27 +107,35 @@ class WithdrawalRequestTransaction(WithdrawalRequestInteractionBase): owned account. """ - def transactions(self, fork: Fork | None = None) -> List[Transaction]: + def transactions(self) -> List[Transaction]: """Return a transaction for the withdrawal request.""" - del fork assert self.sender_account is not None, ( "Sender account not initialized" ) - return [ - Transaction( - gas_limit=request.gas_limit, - gas_price=1_000_000_000, - to=request.interaction_contract_address, - value=request.value, - data=request.calldata, - sender=self.sender_account, - ) - for request in self.requests - ] + txs: List[Transaction] = [] + for request in self.requests: + gas_limit = request.gas_limit + if gas_limit is not None: + tx = Transaction( + gas_limit=request.gas_limit, + to=request.interaction_contract_address, + value=request.value, + data=request.calldata, + sender=self.sender_account, + ) + else: + tx = Transaction( + to=request.interaction_contract_address, + value=request.value, + data=request.calldata, + sender=self.sender_account, + ) + txs.append(tx) + return txs def update_pre(self, pre: Alloc) -> Self: """Return a copy of self with `sender_account` populated.""" - return replace(self, sender_account=pre.fund_eoa(self.sender_balance)) + return replace(self, sender_account=pre.fund_eoa()) def valid_requests( self, current_minimum_fee: int @@ -150,13 +155,6 @@ def valid_requests( class WithdrawalRequestContract(WithdrawalRequestInteractionBase): """Class used to describe a withdrawal originated from a contract.""" - tx_gas_limit: int = 3_000_000 - """ - Gas limit for the transaction. Sized to comfortably cover - `MAX_WITHDRAWAL_REQUESTS_PER_BLOCK` zero-to-nonzero state-set - charges per tx under EIP-8037 plus regular dispatch overhead. - """ - contract_balance: int = 1_000_000_000_000_000_000 """ Balance of the contract that will make the call to the pre-deploy contract. @@ -174,13 +172,6 @@ class WithdrawalRequestContract(WithdrawalRequestInteractionBase): """Frame depth of the pre-deploy contract when it executes the call.""" extra_code: Bytecode = field(default_factory=Bytecode) """Extra code to be added to the contract code.""" - fund_state_reservoir: bool = False - """ - When True (and EIP-8037 is active), pad `tx_gas_limit` by exactly the - per-request state-set work so the excess funds the EIP-8037 reservoir. - Use only when `tx_gas_limit` is held at the cap (reservoir would - otherwise be empty) and state work must not drain the regular pool. - """ @property def contract_code(self) -> Bytecode: @@ -195,7 +186,7 @@ def contract_code(self) -> Bytecode: 0, current_offset, len(r.calldata) ) + Op.POP( self.call_type( - Op.GAS if r.gas_limit == -1 else r.gas_limit, + Op.GAS if r.gas_limit is None else r.gas_limit, r.interaction_contract_address, *value_arg, 0, @@ -207,27 +198,12 @@ def contract_code(self) -> Bytecode: current_offset += len(r.calldata) return code + self.extra_code - def transactions(self, fork: Fork | None = None) -> List[Transaction]: + def transactions(self) -> List[Transaction]: """Return a transaction for the withdrawal request.""" assert self.entry_address is not None, "Entry address not initialized" - gas_limit = self.tx_gas_limit - if fork is not None and fork.is_eip_enabled(8037): - # Per request the system contract writes 3 entry slots - # (source, pubkey, amount); plus a queue-tail bump and - # one slot of headroom per tx. - sstores_per_request = 3 - queue_tail_and_slack_sstores = 2 - sstores = ( - len(self.requests) * sstores_per_request - + queue_tail_and_slack_sstores - ) - gas_limit += sstores * Op.SSTORE(new_value=1).state_cost(fork) return [ Transaction( - gas_limit=gas_limit, - gas_price=1_000_000_000, to=self.entry_address, - value=0, data=b"".join(r.calldata for r in self.requests), sender=self.sender_account, ) @@ -238,7 +214,7 @@ def update_pre(self, pre: Alloc) -> Self: Return a copy of self with the allocated sender/contract/entry addresses populated. """ - sender_account = pre.fund_eoa(self.sender_balance) + sender_account = pre.fund_eoa() contract_address = pre.deploy_contract( code=self.contract_code, balance=self.contract_balance ) diff --git a/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests.py b/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests.py index 15a071d2fe6..2b13ec75d54 100644 --- a/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests.py +++ b/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests.py @@ -327,13 +327,6 @@ ), ], call_depth=264, - tx_gas_limit=16_777_216, - # tx_gas_limit is held at the cap to test the - # 63/64 drain over the deep call chain. EIP-8037 - # state-set work is funded via the reservoir - # rather than the regular pool, which would - # corrupt the boundary the test pins. - fund_state_reservoir=True, ), ], ], @@ -444,7 +437,6 @@ amount=Spec.MAX_AMOUNT - 1 if i % 2 == 0 else 0, - gas_limit=1_000_000, fee=Spec.get_fee(0), valid=True, ) @@ -468,7 +460,6 @@ if i % 2 == 0 else 0, fee=Spec.get_fee(0), - gas_limit=1_000_000, valid=True, ) for i in range( diff --git a/tests/prague/eip7251_consolidations/conftest.py b/tests/prague/eip7251_consolidations/conftest.py index 0f79323993c..27604e746a2 100644 --- a/tests/prague/eip7251_consolidations/conftest.py +++ b/tests/prague/eip7251_consolidations/conftest.py @@ -121,10 +121,7 @@ def blocks( assert not block_included_requests blocks.append( Block( - txs=sum( - (r.transactions(active_fork) for r in block_requests), - [], - ), + txs=sum((r.transactions() for r in block_requests), []), header_verify=header_verify, timestamp=timestamp, ) diff --git a/tests/prague/eip7251_consolidations/helpers.py b/tests/prague/eip7251_consolidations/helpers.py index 625092fff93..6de1b365abb 100644 --- a/tests/prague/eip7251_consolidations/helpers.py +++ b/tests/prague/eip7251_consolidations/helpers.py @@ -10,7 +10,6 @@ Address, Alloc, Bytecode, - Fork, Op, Transaction, ) @@ -28,7 +27,7 @@ class ConsolidationRequest(ConsolidationRequestBase): """Fee to be paid to the system contract for the consolidation request.""" valid: bool = True """Whether the consolidation request is valid or not.""" - gas_limit: int = 1_000_000 + gas_limit: int | None = None """Gas limit for the call.""" calldata_modifier: Callable[[bytes], bytes] = lambda x: x """Calldata modifier function.""" @@ -69,14 +68,12 @@ class ConsolidationRequestInteractionBase: Base class for all types of consolidation transactions we want to test. """ - sender_balance: int = 1_000_000_000_000_000_000 - """Balance of the account that sends the transaction.""" sender_account: EOA | None = None """Account that will send the transaction.""" requests: List[ConsolidationRequest] """Consolidation requests to be included in the block.""" - def transactions(self, fork: Fork | None = None) -> List[Transaction]: + def transactions(self) -> List[Transaction]: """Return a transaction for the consolidation request.""" raise NotImplementedError @@ -105,27 +102,35 @@ class ConsolidationRequestTransaction(ConsolidationRequestInteractionBase): owned account. """ - def transactions(self, fork: Fork | None = None) -> List[Transaction]: + def transactions(self) -> List[Transaction]: """Return a transaction for the consolidation request.""" - del fork assert self.sender_account is not None, ( "Sender account not initialized" ) - return [ - Transaction( - gas_limit=request.gas_limit, - gas_price=1_000_000_000, - to=request.interaction_contract_address, - value=request.value, - data=request.calldata, - sender=self.sender_account, - ) - for request in self.requests - ] + txs: List[Transaction] = [] + for request in self.requests: + gas_limit = request.gas_limit + if gas_limit is not None: + tx = Transaction( + gas_limit=gas_limit, + to=request.interaction_contract_address, + value=request.value, + data=request.calldata, + sender=self.sender_account, + ) + else: + tx = Transaction( + to=request.interaction_contract_address, + value=request.value, + data=request.calldata, + sender=self.sender_account, + ) + txs.append(tx) + return txs def update_pre(self, pre: Alloc) -> Self: """Return a copy of self with `sender_account` populated.""" - return replace(self, sender_account=pre.fund_eoa(self.sender_balance)) + return replace(self, sender_account=pre.fund_eoa()) def valid_requests( self, current_minimum_fee: int @@ -145,9 +150,6 @@ def valid_requests( class ConsolidationRequestContract(ConsolidationRequestInteractionBase): """Class used to describe a consolidation originated from a contract.""" - tx_gas_limit: int = 10_000_000 - """Gas limit for the transaction.""" - contract_balance: int = 1_000_000_000_000_000_000 """ Balance of the contract that will make the call to the pre-deploy contract. @@ -165,13 +167,6 @@ class ConsolidationRequestContract(ConsolidationRequestInteractionBase): """Frame depth of the pre-deploy contract when it executes the call.""" extra_code: Bytecode = field(default_factory=Bytecode) """Extra code to be added to the contract code.""" - fund_state_reservoir: bool = False - """ - When True (and EIP-8037 is active), pad `tx_gas_limit` by exactly the - per-request state-set work so the excess funds the EIP-8037 reservoir. - Use only when `tx_gas_limit` is held at the cap (reservoir would - otherwise be empty) and state work must not drain the regular pool. - """ @property def contract_code(self) -> Bytecode: @@ -186,7 +181,7 @@ def contract_code(self) -> Bytecode: 0, current_offset, len(r.calldata) ) + Op.POP( self.call_type( - Op.GAS if r.gas_limit == -1 else r.gas_limit, + Op.GAS if r.gas_limit is None else r.gas_limit, r.interaction_contract_address, *value_arg, 0, @@ -198,30 +193,11 @@ def contract_code(self) -> Bytecode: current_offset += len(r.calldata) return code + self.extra_code - def transactions(self, fork: Fork | None = None) -> List[Transaction]: + def transactions(self) -> List[Transaction]: """Return a transaction for the consolidation request.""" assert self.entry_address is not None, "Entry address not initialized" - gas_limit = self.tx_gas_limit - if ( - self.fund_state_reservoir - and fork is not None - and fork.is_eip_enabled(8037) - ): - # Per request the system contract writes 4 entry slots - # (source, src_pubkey, tgt_pubkey, fee); plus a queue-tail - # bump and one slot of headroom per tx. Fund the reservoir - # for the full state-set work so it stays off `gas_left`. - sstores_per_request = 4 - queue_tail_and_slack_sstores = 2 - sstores = ( - len(self.requests) * sstores_per_request - + queue_tail_and_slack_sstores - ) - gas_limit += sstores * Op.SSTORE(new_value=1).state_cost(fork) return [ Transaction( - gas_limit=gas_limit, - gas_price=1_000_000_000, to=self.entry_address, value=0, data=b"".join(r.calldata for r in self.requests), @@ -234,7 +210,7 @@ def update_pre(self, pre: Alloc) -> Self: Return a copy of self with the allocated sender/contract/entry addresses populated. """ - sender_account = pre.fund_eoa(self.sender_balance) + sender_account = pre.fund_eoa() contract_address = pre.deploy_contract( code=self.contract_code, balance=self.contract_balance ) diff --git a/tests/prague/eip7251_consolidations/test_consolidations.py b/tests/prague/eip7251_consolidations/test_consolidations.py index 07d6d37e7fc..a9d7af6b380 100644 --- a/tests/prague/eip7251_consolidations/test_consolidations.py +++ b/tests/prague/eip7251_consolidations/test_consolidations.py @@ -21,9 +21,6 @@ TestAddress2, ) -from ...amsterdam.eip8037_state_creation_gas_cost_increase.spec import ( - Spec as Spec8037, -) from .helpers import ( ConsolidationRequest, ConsolidationRequestContract, @@ -379,20 +376,12 @@ source_pubkey=i * 2, target_pubkey=i * 2 + 1, fee=Spec.get_fee(0), - gas_limit=6_000_000, ) for i in range( Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK * 5 ) ], call_depth=100, - tx_gas_limit=Spec8037.TX_MAX_GAS_LIMIT, - # tx_gas_limit is held at the cap to test the - # 63/64 drain over the deep call chain. EIP-8037 - # state-set work is funded via the reservoir - # rather than the regular pool, which would - # corrupt the boundary the test pins. - fund_state_reservoir=True, ), ], ], @@ -468,7 +457,6 @@ ConsolidationRequest( source_pubkey=i * 2, target_pubkey=i * 2 + 1, - gas_limit=1_000_000, fee=Spec.get_fee(0), valid=True, ) @@ -491,7 +479,6 @@ source_pubkey=i * 2, target_pubkey=i * 2 + 1, fee=Spec.get_fee(0), - gas_limit=1_000_000, valid=True, ) for i in range( diff --git a/tests/prague/eip7685_general_purpose_el_requests/test_multi_type_requests.py b/tests/prague/eip7685_general_purpose_el_requests/test_multi_type_requests.py index cd3fb4b2d7c..b9d3bcf61e1 100644 --- a/tests/prague/eip7685_general_purpose_el_requests/test_multi_type_requests.py +++ b/tests/prague/eip7685_general_purpose_el_requests/test_multi_type_requests.py @@ -416,7 +416,6 @@ def test_valid_multi_type_request_from_same_tx( ) tx: Transaction = Transaction( - gas_limit=10_000_000, to=contract_address, value=total_value, data=calldata, diff --git a/tests/prague/eip7702_set_code_tx/test_calls.py b/tests/prague/eip7702_set_code_tx/test_calls.py index ec41ee684f2..ad2befc5857 100644 --- a/tests/prague/eip7702_set_code_tx/test_calls.py +++ b/tests/prague/eip7702_set_code_tx/test_calls.py @@ -9,7 +9,6 @@ Address, Alloc, Environment, - Fork, Op, StateTestFiller, Transaction, @@ -85,7 +84,6 @@ def target_address( def test_delegate_call_targets( state_test: StateTestFiller, pre: Alloc, - fork: Fork, target_account_type: TargetAccountType, target_address: Address, delegate: bool, @@ -111,28 +109,6 @@ def test_delegate_call_targets( slot_call_result, Op.DELEGATECALL(address=target_address) ) + Op.SSTORE(slot_code_worked, value_code_worked) - intrinsic = fork.transaction_intrinsic_cost_calculator() - # The DELEGATECALL forwards 63/64 of remaining gas; LEGACY_CONTRACT_INVALID - # consumes the lot, leaving only 1/64 to host the caller's two SSTORE state - # writes. Lift gas_limit past the EIP-7825 cap so the EIP-8037 reservoir - # holds the SSTORE state work and the inner-call burn doesn't drain it. - gas_cap = fork.transaction_gas_limit_cap() - state_needed = delegate_call_code.state_cost(fork) + 2 * Op.SSTORE( - new_value=1 - ).state_cost(fork) - base_gas = ( - intrinsic( - calldata=delegate_call_code, - contract_creation=call_from_initcode, - ) - + delegate_call_code.gas_cost(fork) - + 4_000_000 # forwarded inner-call envelope - ) - if gas_cap is not None and state_needed > 0: - gas_limit = gas_cap + state_needed - else: - gas_limit = base_gas - if call_from_initcode: # Call from initcode caller_contract = delegate_call_code + Op.RETURN(0, 0) @@ -140,7 +116,6 @@ def test_delegate_call_targets( sender=sender_address, to=None, data=caller_contract, - gas_limit=gas_limit, ) calling_contract_address = tx.created_contract else: @@ -151,7 +126,6 @@ def test_delegate_call_targets( tx = Transaction( sender=sender_address, to=calling_contract_address, - gas_limit=gas_limit, ) calling_storage = { diff --git a/tests/prague/eip7702_set_code_tx/test_gas.py b/tests/prague/eip7702_set_code_tx/test_gas.py index 3ff7866ea6a..1c0e111ecf8 100644 --- a/tests/prague/eip7702_set_code_tx/test_gas.py +++ b/tests/prague/eip7702_set_code_tx/test_gas.py @@ -1111,7 +1111,6 @@ def test_account_warming( ) tx = Transaction( - gas_limit=1_000_000, to=callee_address, authorization_list=authorization_list if authorization_list else None, access_list=access_list, @@ -1215,7 +1214,6 @@ def test_self_set_code_cost( callee_storage[slot_call_cost] = 200 if not pre_authorized else 2700 tx = Transaction( - gas_limit=1_000_000, to=callee_address, authorization_list=[ AuthorizationTuple( diff --git a/tests/prague/eip7702_set_code_tx/test_set_code_txs.py b/tests/prague/eip7702_set_code_tx/test_set_code_txs.py index 06e86c41a85..293cbe16b49 100644 --- a/tests/prague/eip7702_set_code_tx/test_set_code_txs.py +++ b/tests/prague/eip7702_set_code_tx/test_set_code_txs.py @@ -112,7 +112,6 @@ def test_self_sponsored_set_code( ) tx = Transaction( - gas_limit=10_000_000, to=sender, value=tx_value, authorization_list=[ @@ -165,7 +164,6 @@ def test_self_sponsored_set_code( def test_set_code_to_sstore( state_test: StateTestFiller, pre: Alloc, - fork: Fork, suffix: Bytecode, succeeds: bool, tx_value: int, @@ -191,15 +189,7 @@ def test_set_code_to_sstore( set_code, ) - # 3 first-time SSTOREs plus auth+delegation; each SSTORE adds - # `sstore_state_gas` under EIP-8037, and an empty-account - # authority adds NEW_ACCOUNT (both 0 otherwise). tx = Transaction( - gas_limit=( - 500_000 - + fork.gas_costs().NEW_ACCOUNT - + 3 * Op.SSTORE(new_value=1).state_cost(fork) - ), to=auth_signer, value=tx_value, authorization_list=[ @@ -246,7 +236,6 @@ def test_set_code_to_non_empty_storage_non_zero_nonce( ) tx = Transaction( - gas_limit=500_000, to=auth_signer, value=0, authorization_list=[ @@ -285,7 +274,6 @@ def test_set_code_to_non_empty_storage_non_zero_nonce( def test_set_code_to_sstore_then_sload( blockchain_test: BlockchainTestFiller, pre: Alloc, - fork: Fork, access_list_in_tx: str | None, ) -> None: """ @@ -307,11 +295,7 @@ def test_set_code_to_sstore_then_sload( ) set_code_2_address = pre.deploy_contract(set_code_2) - gas_limit = 100_000 - if fork.is_eip_enabled(8037): - gas_limit = 500_000 # TODO: auto gas limit will remove this tx_1 = Transaction( - gas_limit=gas_limit, to=auth_signer, value=0, authorization_list=[ @@ -337,7 +321,6 @@ def test_set_code_to_sstore_then_sload( else [] ) tx_2 = Transaction( - gas_limit=gas_limit, to=auth_signer, value=0, authorization_list=[ @@ -382,7 +365,6 @@ def test_set_code_to_sstore_then_sload( def test_set_code_to_tstore_reentry( state_test: StateTestFiller, pre: Alloc, - fork: Fork, call_opcode: Op, return_opcode: Op, ) -> None: @@ -403,11 +385,7 @@ def test_set_code_to_tstore_reentry( ) set_code_to_address = pre.deploy_contract(set_code) - gas_limit = 100_000 - if fork.is_eip_enabled(8037): - gas_limit = 500_000 # TODO: auto gas limit will remove this tx = Transaction( - gas_limit=gas_limit, to=auth_signer, value=0, authorization_list=[ @@ -448,7 +426,6 @@ def test_set_code_to_tstore_reentry( def test_set_code_to_tstore_available_at_correct_address( state_test: StateTestFiller, pre: Alloc, - fork: Fork, call_opcode: Op, call_eoa_first: bool, ) -> None: @@ -480,11 +457,7 @@ def make_call(call_type: Op, call_eoa: bool) -> Bytecode: target_call_chain_address = pre.deploy_contract(chain_code) - gas_limit = 100_000 - if fork.is_eip_enabled(8037): - gas_limit = 500_000 # TODO: auto gas limit will remove this tx = Transaction( - gas_limit=gas_limit, to=target_call_chain_address, value=0, authorization_list=[ @@ -538,7 +511,6 @@ def test_set_code_to_self_destruct( ) tx = Transaction( - gas_limit=10_000_000, to=auth_signer, value=0, authorization_list=[ @@ -615,7 +587,6 @@ def test_creating_tx_to_contract_creator( ) tx = Transaction( - gas_limit=10_000_000, to=None, value=0, data=initcode, @@ -704,12 +675,8 @@ def test_delegated_eoa_can_send_creating_tx( ) assert initcode_len == len(initcode) - gas_limit = 200_000 + (Op.SSTORE(key_warm=False) * 7).gas_cost(fork) - if fork.is_eip_enabled(8037): - gas_limit = 10_000_000 tx = Transaction( ty=tx_type, - gas_limit=gas_limit, to=None, value=0, data=initcode, @@ -765,7 +732,6 @@ def test_set_code_to_contract_creator( creator_code_address = pre.deploy_contract(creator_code) tx = Transaction( - gas_limit=10_000_000, to=auth_signer, value=0, data=initcode, @@ -840,7 +806,6 @@ def test_set_code_to_self_caller( set_code_to_address = pre.deploy_contract(set_code) tx = Transaction( - gas_limit=10_000_000, to=auth_signer, value=value, authorization_list=[ @@ -983,7 +948,6 @@ def test_set_code_call_set_code( set_code_to_address_2 = pre.deploy_contract(set_code_2) tx = Transaction( - gas_limit=10_000_000, to=auth_signer_1, value=value, authorization_list=[ @@ -1046,7 +1010,6 @@ def test_address_from_set_code( set_code_to_address = pre.deploy_contract(set_code) tx = Transaction( - gas_limit=10_000_000, to=auth_signer, value=0, authorization_list=[ @@ -1085,7 +1048,6 @@ def test_tx_into_self_delegating_set_code( auth_signer = pre.fund_eoa(auth_account_start_balance) tx = Transaction( - gas_limit=10_000_000, to=auth_signer, value=0, authorization_list=[ @@ -1123,7 +1085,6 @@ def test_tx_into_chain_delegating_set_code( auth_signer_2 = pre.fund_eoa(auth_account_start_balance) tx = Transaction( - gas_limit=10_000_000, to=auth_signer_1, value=0, authorization_list=[ @@ -1176,7 +1137,6 @@ def test_call_into_self_delegating_set_code( entry_address = pre.deploy_contract(entry_code) tx = Transaction( - gas_limit=10_000_000, to=entry_address, value=0, authorization_list=[ @@ -1264,7 +1224,6 @@ def test_call_into_chain_delegating_set_code( entry_address = pre.deploy_contract(entry_code) tx = Transaction( - gas_limit=10_000_000, to=entry_address, value=0, authorization_list=[ @@ -1410,7 +1369,6 @@ def test_ext_code_on_set_code( callee_storage[slot_ext_balance_result] = balance tx = Transaction( - gas_limit=10_000_000, to=callee_address, authorization_list=[ AuthorizationTuple( @@ -1483,7 +1441,6 @@ def test_ext_code_on_self_set_code( set_code_storage[slot_ext_balance_result] = balance tx = Transaction( - gas_limit=10_000_000, to=auth_signer, authorization_list=[ AuthorizationTuple( @@ -1569,7 +1526,6 @@ def test_set_code_address_and_authority_warm_state( ) tx = Transaction( - gas_limit=1_000_000, to=callee_address, authorization_list=[ AuthorizationTuple( @@ -1646,7 +1602,6 @@ def test_set_code_address_and_authority_warm_state_call_types( callee_storage[slot_call_success] = 1 tx = Transaction( - gas_limit=1_000_000, to=callee_address, authorization_list=[ AuthorizationTuple( @@ -1717,7 +1672,6 @@ def test_ext_code_on_self_delegating_set_code( callee_storage[slot_ext_balance_result] = balance tx = Transaction( - gas_limit=10_000_000, to=callee_address, authorization_list=[ AuthorizationTuple( @@ -1811,7 +1765,6 @@ def test_ext_code_on_chain_delegating_set_code( callee_storage[slot_ext_balance_result_2] = auth_signer_2_balance tx = Transaction( - gas_limit=10_000_000, to=callee_address, authorization_list=[ AuthorizationTuple( @@ -1880,7 +1833,6 @@ def test_self_code_on_set_code( storage[slot_self_balance_result] = balance tx = Transaction( - gas_limit=10_000_000, to=auth_signer, authorization_list=[ AuthorizationTuple( @@ -1959,7 +1911,6 @@ def test_set_code_to_account_deployed_in_same_tx( ) tx = Transaction( - gas_limit=10_000_000, to=contract_creator_address, value=0, data=initcode, @@ -2070,7 +2021,6 @@ def test_set_code_to_self_destructing_account_deployed_in_same_tx( ) tx = Transaction( - gas_limit=10_000_000, to=contract_creator_address, value=0, data=initcode, @@ -2133,7 +2083,6 @@ def test_set_code_multiple_first_valid_authorization_tuples_same_signer( ] tx = Transaction( - gas_limit=10_000_000, to=auth_signer, value=0, authorization_list=[ @@ -2185,7 +2134,6 @@ def test_set_code_multiple_valid_authorization_tuples_same_signer_increasing_non ] tx = Transaction( - gas_limit=10_000_000, # TODO: Reduce gas limit of all tests to=auth_signer, value=0, authorization_list=[ @@ -2238,7 +2186,6 @@ def test_set_code_multiple_valid_authorization_tuples_same_signer_increasing_non ] tx = Transaction( - gas_limit=10_000_000, # TODO: Reduce gas limit of all tests to=auth_signer, value=0, authorization_list=[ @@ -2288,7 +2235,6 @@ def test_set_code_multiple_valid_authorization_tuples_first_invalid_same_signer( ] tx = Transaction( - gas_limit=10_000_000, to=auth_signer, value=0, authorization_list=[ @@ -2337,7 +2283,6 @@ def test_set_code_all_invalid_authorization_tuples( ] tx = Transaction( - gas_limit=10_000_000, to=auth_signer, value=0, authorization_list=[ @@ -2365,7 +2310,6 @@ def test_set_code_all_invalid_authorization_tuples( def test_set_code_using_chain_specific_id( state_test: StateTestFiller, pre: Alloc, - fork: Fork, chain_config: ChainConfig, ) -> None: """ @@ -2379,11 +2323,7 @@ def test_set_code_using_chain_specific_id( set_code = Op.SSTORE(success_slot, 1) + Op.STOP set_code_to_address = pre.deploy_contract(set_code) - gas_limit = 100_000 - if fork.is_eip_enabled(8037): - gas_limit = 500_000 # TODO: auto gas limit will remove this tx = Transaction( - gas_limit=gas_limit, to=auth_signer, value=0, authorization_list=[ @@ -2436,7 +2376,6 @@ def test_set_code_using_chain_specific_id( def test_set_code_using_valid_synthetic_signatures( state_test: StateTestFiller, pre: Alloc, - fork: Fork, chain_config: ChainConfig, v: int, r: int, @@ -2462,11 +2401,7 @@ def test_set_code_using_valid_synthetic_signatures( auth_signer = authorization_tuple.signer - gas_limit = 100_000 - if fork.is_eip_enabled(8037): - gas_limit = 500_000 # TODO: auto gas limit will remove this tx = Transaction( - gas_limit=gas_limit, to=auth_signer, value=0, authorization_list=[authorization_tuple], @@ -2530,7 +2465,6 @@ def test_set_code_using_valid_synthetic_signatures( def test_valid_tx_invalid_auth_signature( state_test: StateTestFiller, pre: Alloc, - fork: Fork, chain_config: ChainConfig, v: int, r: int, @@ -2555,12 +2489,7 @@ def test_valid_tx_invalid_auth_signature( s=s, ) - gas_limit = 100_000 - if fork.is_eip_enabled(8037): - gas_limit = 500_000 # TODO: auto gas limit will remove this - tx = Transaction( - gas_limit=gas_limit, to=callee_address, value=0, authorization_list=[authorization_tuple], @@ -2582,8 +2511,8 @@ def test_valid_tx_invalid_auth_signature( def test_signature_s_out_of_range( state_test: StateTestFiller, pre: Alloc, - fork: Fork, chain_config: ChainConfig, + fork: Fork, ) -> None: """ Test sending a transaction with an authorization tuple where the signature @@ -2611,12 +2540,7 @@ def test_signature_s_out_of_range( entry_code = Op.SSTORE(success_slot, 1) + Op.STOP entry_address = pre.deploy_contract(entry_code) - gas_limit = 100_000 - if fork.is_eip_enabled(8037): - gas_limit = 500_000 # TODO: auto gas limit will remove this - tx = Transaction( - gas_limit=gas_limit, to=entry_address, value=0, authorization_list=[authorization_tuple], @@ -2691,7 +2615,6 @@ class InvalidChainID(StrEnum): def test_valid_tx_invalid_chain_id( state_test: StateTestFiller, pre: Alloc, - fork: Fork, chain_config: ChainConfig, invalid_chain_id_case: InvalidChainID, ) -> None: @@ -2732,12 +2655,7 @@ def test_valid_tx_invalid_chain_id( ) entry_address = pre.deploy_contract(entry_code) - gas_limit = 100_000 - if fork.is_eip_enabled(8037): - gas_limit = 500_000 # TODO: auto gas limit will remove this - tx = Transaction( - gas_limit=gas_limit, to=entry_address, value=0, authorization_list=[authorization], @@ -2790,9 +2708,9 @@ def test_valid_tx_invalid_chain_id( def test_nonce_validity( state_test: StateTestFiller, pre: Alloc, - fork: Fork, account_nonce: int, authorization_nonce: int, + fork: Fork, ) -> None: """ Test sending a transaction where the nonce field of an authorization almost @@ -2826,12 +2744,7 @@ def test_nonce_validity( ) entry_address = pre.deploy_contract(entry_code) - gas_limit = 100_000 - if fork.is_eip_enabled(8037): - gas_limit = 500_000 # TODO: auto gas limit will remove this - tx = Transaction( - gas_limit=gas_limit, to=entry_address, value=0, authorization_list=[authorization], @@ -2946,7 +2859,6 @@ def test_nonce_validity( def test_nonce_overflow_after_first_authorization( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """ Test sending a transaction with two authorization where the first one bumps @@ -2983,12 +2895,7 @@ def test_nonce_overflow_after_first_authorization( ) entry_address = pre.deploy_contract(entry_code) - gas_limit = 200_000 - if fork.is_eip_enabled(8037): - gas_limit = 500_000 # TODO: auto gas limit will remove this - tx = Transaction( - gas_limit=gas_limit, to=entry_address, value=0, authorization_list=authorization_list, @@ -3053,7 +2960,6 @@ def test_set_code_to_log( set_to_address = pre.deploy_contract(set_to_code) tx = Transaction( - gas_limit=10_000_000, to=sender, value=0, authorization_list=[ @@ -3116,7 +3022,6 @@ def test_set_code_to_precompile( tx = Transaction( sender=pre.fund_eoa(), - gas_limit=500_000, to=caller_code_address, authorization_list=[ AuthorizationTuple( @@ -3381,19 +3286,9 @@ def test_set_code_to_system_contract( caller_code_address = pre.deploy_contract(caller_code) sender = pre.fund_eoa() - # The 7002/7251 system contracts enqueue multiple state entries per - # request (4 and 5 slots respectively); pad gas_limit by that many - # SSTORE state-set worths so the EIP-8037 reservoir absorbs the work - # rather than draining the tx's regular pool through DELEGATECALL. - sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) - extra_state_slots = { - Address(Spec7002.WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS): 4, - Address(Spec7251.CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS): 5, - }.get(Address(system_contract), 0) txs = [ Transaction( sender=sender, - gas_limit=500_000 + extra_state_slots * sstore_state_gas, to=caller_code_address, value=call_value, data=caller_payload, @@ -3469,7 +3364,6 @@ def test_eoa_tx_after_set_code( first_eoa_tx = Transaction( sender=pre.fund_eoa(), - gas_limit=500_000, to=auth_signer, value=0, authorization_list=[ @@ -3490,7 +3384,6 @@ def test_eoa_tx_after_set_code( Transaction( ty=tx_type, sender=auth_signer, - gas_limit=500_000, to=auth_signer, value=0, protected=True, @@ -3498,7 +3391,6 @@ def test_eoa_tx_after_set_code( Transaction( ty=tx_type, sender=auth_signer, - gas_limit=500_000, to=auth_signer, value=0, protected=False, @@ -3510,7 +3402,6 @@ def test_eoa_tx_after_set_code( Transaction( ty=tx_type, sender=auth_signer, - gas_limit=500_000, to=auth_signer, value=0, access_list=[ @@ -3526,7 +3417,6 @@ def test_eoa_tx_after_set_code( Transaction( ty=tx_type, sender=auth_signer, - gas_limit=500_000, to=auth_signer, value=0, max_fee_per_gas=1_000, @@ -3538,7 +3428,6 @@ def test_eoa_tx_after_set_code( Transaction( ty=tx_type, sender=auth_signer, - gas_limit=500_000, to=auth_signer, value=0, max_fee_per_gas=1_000, @@ -3604,7 +3493,6 @@ def test_reset_code( txs = [ Transaction( sender=sender, - gas_limit=500_000, to=auth_signer, value=0, authorization_list=[ @@ -3625,7 +3513,6 @@ def test_reset_code( txs.append( Transaction( sender=sender, - gas_limit=500_000, to=auth_signer, value=0, authorization_list=[ @@ -3658,7 +3545,6 @@ def test_reset_code( def test_contract_create( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test sending type-4 tx as a create transaction.""" authorization_tuple = AuthorizationTuple( @@ -3666,11 +3552,7 @@ def test_contract_create( nonce=0, signer=pre.fund_eoa(), ) - gas_limit = 100_000 - if fork.is_eip_enabled(8037): - gas_limit = 500_000 # TODO: auto gas limit will remove this tx = Transaction( - gas_limit=gas_limit, to=None, value=0, authorization_list=[authorization_tuple], @@ -3694,7 +3576,6 @@ def test_empty_authorization_list( ) -> None: """Test sending an invalid transaction with empty authorization list.""" tx = Transaction( - gas_limit=100_000, to=pre.deploy_contract(code=b""), value=0, authorization_list=[], @@ -3727,7 +3608,6 @@ def test_empty_authorization_list( def test_delegation_clearing( state_test: StateTestFiller, pre: Alloc, - fork: Fork, pre_set_delegation_code: Bytecode | None, self_sponsored: bool, ) -> None: @@ -3775,12 +3655,7 @@ def test_delegation_clearing( signer=auth_signer, ) - gas_limit = 200_000 - if fork.is_eip_enabled(8037): - gas_limit = 500_000 # TODO: auto gas limit will remove this - tx = Transaction( - gas_limit=gas_limit, to=entry_address, value=0, authorization_list=[authorization], @@ -3827,7 +3702,6 @@ def test_delegation_clearing( def test_delegation_clearing_tx_to( state_test: StateTestFiller, pre: Alloc, - fork: Fork, pre_set_delegation_code: Bytecode | None, self_sponsored: bool, ) -> None: @@ -3853,11 +3727,7 @@ def test_delegation_clearing_tx_to( sender = pre.fund_eoa() if not self_sponsored else auth_signer - # When `auth_signer` is an empty account (non-self-sponsored - # variant) the auth charges NEW_ACCOUNT state gas under EIP-8037 - # (0 otherwise). tx = Transaction( - gas_limit=200_000 + fork.gas_costs().NEW_ACCOUNT, to=auth_signer, value=0, authorization_list=[ @@ -3894,7 +3764,6 @@ def test_delegation_clearing_tx_to( def test_delegation_clearing_and_set( state_test: StateTestFiller, pre: Alloc, - fork: Fork, pre_set_delegation_code: Bytecode | None, ) -> None: """ @@ -3920,12 +3789,7 @@ def test_delegation_clearing_and_set( sender = pre.fund_eoa() - gas_limit = 200_000 - if fork.is_eip_enabled(8037): - gas_limit = 500_000 # TODO: auto gas limit will remove this - tx = Transaction( - gas_limit=gas_limit, to=auth_signer, value=0, authorization_list=[ @@ -3970,7 +3834,6 @@ def test_delegation_clearing_and_set( def test_delegation_clearing_failing_tx( state_test: StateTestFiller, pre: Alloc, - fork: Fork, entry_code: Bytecode, ) -> None: """ @@ -3990,12 +3853,7 @@ def test_delegation_clearing_failing_tx( signer=auth_signer, ) - gas_limit = 100_000 - if fork.is_eip_enabled(8037): - gas_limit = 500_000 # TODO: auto gas limit will remove this - tx = Transaction( - gas_limit=gas_limit, to=entry_address, value=0, authorization_list=[authorization], @@ -4026,7 +3884,6 @@ def test_delegation_clearing_failing_tx( def test_deploying_delegation_designation_contract( state_test: StateTestFiller, pre: Alloc, - fork: Fork, initcode_is_delegation_designation: bool, ) -> None: """ @@ -4046,14 +3903,9 @@ def test_deploying_delegation_designation_contract( deploy_code=Spec.delegation_designation(set_to_address) ) - gas_limit = 100_000 - if fork.is_eip_enabled(8037): - gas_limit = 500_000 # TODO: auto gas limit will remove this - tx = Transaction( sender=sender, to=None, - gas_limit=gas_limit, data=initcode, ) @@ -4116,7 +3968,6 @@ def test_creating_delegation_designation_contract( tx = Transaction( to=contract_a, - gas_limit=1_000_000, data=create_init, value=0, sender=sender, @@ -4230,7 +4081,6 @@ def test_invalid_transaction_after_authorization( txs = [ Transaction( sender=pre.fund_eoa(), - gas_limit=500_000, to=recipient, value=0, authorization_list=[ @@ -4286,7 +4136,6 @@ def test_authorization_reusing_nonce( ), Transaction( sender=sender, - gas_limit=500_000, to=recipient, value=0, authorization_list=[ @@ -4325,7 +4174,6 @@ def test_authorization_reusing_nonce( def test_set_code_from_account_with_non_delegating_code( state_test: StateTestFiller, pre: Alloc, - fork: Fork, set_code_type: AddressType, self_sponsored: bool, ) -> None: @@ -4357,12 +4205,7 @@ def test_set_code_from_account_with_non_delegating_code( raise ValueError(f"Unsupported set code type: {set_code_type}") callee_address = pre.deploy_contract(Op.SSTORE(0, 1) + Op.STOP) - gas_limit = 100_000 - if fork.is_eip_enabled(8037): - gas_limit = 500_000 # TODO: auto gas limit will remove this - tx = Transaction( - gas_limit=gas_limit, to=callee_address, authorization_list=[ AuthorizationTuple( @@ -4428,7 +4271,6 @@ def test_set_code_transaction_fee_validations( auth_signer = pre.fund_eoa(amount=0) tx = Transaction( sender=pre.fund_eoa(), - gas_limit=500_000, to=auth_signer, value=0, max_fee_per_gas=max_fee_per_gas, diff --git a/tests/prague/eip7702_set_code_tx/test_set_code_txs_2.py b/tests/prague/eip7702_set_code_tx/test_set_code_txs_2.py index bb54b9b4fd3..995a6b9dadc 100644 --- a/tests/prague/eip7702_set_code_tx/test_set_code_txs_2.py +++ b/tests/prague/eip7702_set_code_tx/test_set_code_txs_2.py @@ -174,9 +174,6 @@ def test_pointer_to_pointer( tx = Transaction( to=pointer_a, - gas_limit=1_000_000, - data=b"", - value=0, sender=sender, authorization_list=[ AuthorizationTuple( @@ -240,9 +237,6 @@ def test_pointer_normal( tx = Transaction( to=pointer_a, - gas_limit=1_000_000, - data=b"", - value=0, sender=sender, authorization_list=[ AuthorizationTuple( @@ -256,9 +250,6 @@ def test_pointer_normal( # Other normal tx can interact with previously assigned pointers tx_2 = Transaction( to=pointer_a, - gas_limit=1_000_000, - data=b"", - value=0, sender=sender, nonce=(nonce := nonce + 1), ) @@ -266,9 +257,6 @@ def test_pointer_normal( # Event from another block tx_3 = Transaction( to=pointer_a, - gas_limit=1_000_000, - data=b"", - value=0, sender=sender, nonce=(nonce := nonce + 1), ) @@ -284,7 +272,7 @@ def test_pointer_normal( @pytest.mark.valid_from("Prague") def test_pointer_measurements( - blockchain_test: BlockchainTestFiller, pre: Alloc, fork: Fork + blockchain_test: BlockchainTestFiller, pre: Alloc ) -> None: """ Check extcode* operations on pointer before and after pointer is set. @@ -396,13 +384,8 @@ def test_pointer_measurements( + Op.STOP, ) - # The pointer-code measurement contract performs ~10 first-time - # SSTOREs; each adds `sstore_state_gas` under EIP-8037 (0 - # otherwise). The non-pointer txs reuse the same headroom. - pointer_state = 10 * Op.SSTORE(new_value=1).state_cost(fork) tx = Transaction( to=contract_measurements, - gas_limit=1_000_000 + pointer_state, data=b"", value=0, sender=sender, @@ -410,7 +393,6 @@ def test_pointer_measurements( tx_pointer = Transaction( to=contract_measurements_pointer, - gas_limit=1_000_000 + pointer_state, data=b"", value=0, sender=sender, @@ -425,7 +407,6 @@ def test_pointer_measurements( tx_pointer_call = Transaction( to=pointer, - gas_limit=1_000_000 + pointer_state, data=bytes.fromhex("11223344"), value=3, sender=sender, @@ -518,9 +499,7 @@ def test_call_to_precompile_in_pointer_context( tx = Transaction( to=contract_a, - gas_limit=3_000_000, data=[0x11] * 256, - value=0, sender=sender, authorization_list=[ AuthorizationTuple( @@ -626,9 +605,7 @@ def test_pointer_to_precompile( tx = Transaction( to=contract_a, - gas_limit=3_000_000, data=[0x11] * 256, - value=0, sender=sender, authorization_list=[ AuthorizationTuple( @@ -814,9 +791,6 @@ def test_gas_diff_pointer_vs_direct_call( tx_0 = Transaction( to=1, - gas_limit=3_000_000, - data=b"", - value=0, sender=sender, authorization_list=( [ @@ -833,9 +807,6 @@ def test_gas_diff_pointer_vs_direct_call( tx = Transaction( to=contract_test_normal, - gas_limit=3_000_000, - data=b"", - value=0, sender=sender, authorization_list=( [ @@ -863,9 +834,6 @@ def test_gas_diff_pointer_vs_direct_call( ) tx2 = Transaction( to=contract_test_pointer, - gas_limit=3_000_000, - data=b"", - value=0, sender=sender, authorization_list=( [ @@ -977,9 +945,6 @@ def test_pointer_call_followed_by_direct_call( tx = Transaction( to=contract_test_gas, - gas_limit=3_000_000, - data=b"", - value=0, sender=sender, authorization_list=( [ @@ -1057,9 +1022,6 @@ def test_pointer_to_static( tx = Transaction( to=pointer_a, - gas_limit=3_000_000, - data=b"", - value=0, sender=sender, authorization_list=[ AuthorizationTuple( @@ -1134,9 +1096,6 @@ def test_static_to_pointer( tx = Transaction( to=contract_a, - gas_limit=3_000_000, - data=b"", - value=0, sender=sender, authorization_list=[ AuthorizationTuple( @@ -1234,9 +1193,7 @@ def test_pointer_to_static_reentry( tx = Transaction( to=pointer_a, - gas_limit=3_000_000, data=[0x00] * 32, - value=0, sender=sender, authorization_list=[ AuthorizationTuple( @@ -1335,9 +1292,6 @@ def test_contract_storage_to_pointer_with_storage( tx = Transaction( to=contract_a, - gas_limit=3_000_000, - data=b"", - value=0, sender=sender, authorization_list=[ AuthorizationTuple( @@ -1370,9 +1324,7 @@ class ReentryAction(IntEnum): @pytest.mark.valid_from("Prague") -def test_pointer_reentry( - state_test: StateTestFiller, pre: Alloc, fork: Fork -) -> None: +def test_pointer_reentry(state_test: StateTestFiller, pre: Alloc) -> None: """ Check operations when reenter the pointer again. @@ -1484,23 +1436,10 @@ def test_pointer_reentry( storage_b[slot_reentry_address] = contract_b - # Many nested CALLs and SSTOREs across pointer-via-proxy reentry. - # Lift above the EIP-7825 cap so the EIP-8037 reservoir holds the - # SSTORE state work, otherwise it spills into each frame's regular - # share and the deep call chain runs out. - gas_cap = fork.transaction_gas_limit_cap() - sstore_count = 10 # rough envelope across all frames - tx_gas_limit = ( - gas_cap + sstore_count * Op.SSTORE(new_value=1).state_cost(fork) - if gas_cap is not None and fork.is_eip_enabled(8037) - else 2_000_000 - ) tx = Transaction( to=pointer_b, - gas_limit=tx_gas_limit, data=Hash(contract_b, left_padding=True) + Hash(ReentryAction.CALL_PROXY, left_padding=True), - value=0, sender=sender, authorization_list=[ AuthorizationTuple( @@ -1540,9 +1479,6 @@ def test_eoa_init_as_pointer(state_test: StateTestFiller, pre: Alloc) -> None: tx = Transaction( to=sender, - gas_limit=200_000, - data=b"", - value=0, sender=sender, ) post = {sender: Account(storage=storage)} @@ -1634,7 +1570,6 @@ def test_call_pointer_to_created_from_create_after_oog_call_again( tx = Transaction( to=contract_main, - gas_limit=800_000, data=Op.SSTORE(storage_create.store_next(1, "create_init_code"), 1) + Op.SSTORE( storage_create.store_next(1, "call_pointer_from_init"), @@ -1642,7 +1577,6 @@ def test_call_pointer_to_created_from_create_after_oog_call_again( ) + Op.MSTORE(0, deploy_code.hex()) + Op.RETURN(32 - len(deploy_code), len(deploy_code)), - value=0, sender=sender, authorization_list=[ AuthorizationTuple( @@ -1788,9 +1722,6 @@ def test_pointer_reverts( ) tx = Transaction( to=contract_main, - gas_limit=800_000, - data=b"", - value=0, sender=sender, authorization_list=[ AuthorizationTuple( @@ -1833,7 +1764,6 @@ class DelegationTo(Enum): def test_double_auth( state_test: StateTestFiller, pre: Alloc, - fork: Fork, first_delegation: DelegationTo, second_delegation: DelegationTo, ) -> None: @@ -1867,9 +1797,6 @@ def test_double_auth( tx = Transaction( to=contract_main, - gas_limit=(500_000 if fork.is_eip_enabled(8037) else 200_000), - data=b"", - value=0, sender=sender, authorization_list=[ AuthorizationTuple( @@ -1927,7 +1854,6 @@ def test_double_auth( def test_pointer_resets_an_empty_code_account_with_storage( blockchain_test: BlockchainTestFiller, pre: Alloc, - fork: Fork, ) -> None: """ So in Block1 we create a sender with empty code, but non empty storage @@ -1950,21 +1876,8 @@ def test_pointer_resets_an_empty_code_account_with_storage( ) + Op.SSTORE(pointer_storage.store_next(2, "slot2"), 2) contract_1 = pre.deploy_contract(code=contract_1_code) - intrinsic_calc = fork.transaction_intrinsic_cost_calculator() - sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) - # The set-pointer-storage tx authorizes contract_1 then runs its two - # SSTOREs at the pointer; pad gas_limit with the auth + 2 SSTORE state - # work and EIP-1706 slack. - gas_limit = ( - intrinsic_calc(authorization_list_or_count=1) - + contract_1_code.gas_cost(fork) - + sstore_state_gas - ) tx_set_pointer_storage = Transaction( to=pointer, - gas_limit=gas_limit, - data=b"", - value=0, sender=sender, authorization_list=[ AuthorizationTuple( @@ -1976,9 +1889,6 @@ def test_pointer_resets_an_empty_code_account_with_storage( ) tx_set_sender_storage = Transaction( to=sender, - gas_limit=gas_limit, - data=b"", - value=0, sender=sender, authorization_list=[ AuthorizationTuple( @@ -1991,9 +1901,6 @@ def test_pointer_resets_an_empty_code_account_with_storage( tx_reset_code = Transaction( to=pointer, - gas_limit=gas_limit, - data=b"", - value=0, nonce=3, sender=sender, authorization_list=[ @@ -2013,9 +1920,6 @@ def test_pointer_resets_an_empty_code_account_with_storage( contract_2 = pre.deploy_contract(code=Op.SSTORE(1, 1)) tx_send_from_empty_code_with_storage = Transaction( to=contract_2, - gas_limit=200_000, - data=b"", - value=0, nonce=5, sender=sender, ) @@ -2044,18 +1948,8 @@ def test_pointer_resets_an_empty_code_account_with_storage( address=contract_create, nonce=1 ) - # contract_create runs SSTORE(1, CREATE) then 3 CALLs into pointers - # whose deploy_code does an SSTORE + SELFDESTRUCT (1 NEW_ACCOUNT for - # CREATE, 1 SSTORE in contract_create, 3 SSTOREs across the pointer - # callees, plus 2 authorizations' state). - tx2_state = ( - fork.gas_costs().NEW_ACCOUNT - + 4 * sstore_state_gas - + fork.transaction_intrinsic_state_gas(authorization_count=2) - ) tx_create_suicide_from_pointer = Transaction( to=contract_create, - gas_limit=800_000 + tx2_state + sstore_state_gas, data=Op.SSTORE(6, 6) + Op.MSTORE(0, deploy_code.hex()) + Op.RETURN(32 - len(deploy_code), len(deploy_code)), @@ -2146,7 +2040,6 @@ def test_set_code_type_tx_pre_fork( ) tx = Transaction( - gas_limit=10_000_000, to=sender, value=tx_value, authorization_list=[ @@ -2204,9 +2097,7 @@ def test_delegation_replacement_call_previous_contract( ) tx = Transaction( - gas_limit=500_000, to=auth_signer, - value=0, authorization_list=[ AuthorizationTuple( address=set_code_to_address, diff --git a/tests/shanghai/eip3651_warm_coinbase/test_warm_coinbase.py b/tests/shanghai/eip3651_warm_coinbase/test_warm_coinbase.py index 561e7225fd6..9d36f59096e 100644 --- a/tests/shanghai/eip3651_warm_coinbase/test_warm_coinbase.py +++ b/tests/shanghai/eip3651_warm_coinbase/test_warm_coinbase.py @@ -91,15 +91,8 @@ def test_warm_coinbase_call_out_of_gas( ) caller_address = pre.deploy_contract(caller_code) - intrinsic_calc = fork.transaction_intrinsic_cost_calculator() tx = Transaction( to=caller_address, - gas_limit=( - intrinsic_calc() - + caller_code.gas_cost(fork) - + call_gas_exact - + Op.SSTORE(new_value=1).state_cost(fork) - ), sender=sender, ) @@ -191,14 +184,8 @@ def test_warm_coinbase_gas_usage( # Coinbase is warm after EIP-3651 (Shanghai+), cold before expected_gas = Op.BALANCE(address_warm=(fork >= Shanghai)).gas_cost(fork) - intrinsic_calc = fork.transaction_intrinsic_cost_calculator() tx = Transaction( to=measure_address, - gas_limit=( - intrinsic_calc() - + code_gas_measure.gas_cost(fork) - + Op.SSTORE(new_value=1).state_cost(fork) - ), sender=sender, ) @@ -210,4 +197,9 @@ def test_warm_coinbase_gas_usage( ) } - state_test(env=env, pre=pre, post=post, tx=tx) + state_test( + env=env, + pre=pre, + post=post, + tx=tx, + ) diff --git a/tests/shanghai/eip3855_push0/test_push0.py b/tests/shanghai/eip3855_push0/test_push0.py index c08208e28aa..919952673f4 100644 --- a/tests/shanghai/eip3855_push0/test_push0.py +++ b/tests/shanghai/eip3855_push0/test_push0.py @@ -14,7 +14,6 @@ Bytecode, CodeGasMeasure, Environment, - Fork, Op, StateTestFiller, Transaction, @@ -84,25 +83,12 @@ def test_push0_contracts( pre: Alloc, post: Alloc, sender: EOA, - fork: Fork, contract_code: Bytecode, expected_storage: Account, ) -> None: """Tests PUSH0 within various deployed contracts.""" push0_contract = pre.deploy_contract(contract_code) - intrinsic_calc = fork.transaction_intrinsic_cost_calculator() - tx = Transaction( - to=push0_contract, - # `contract_code.gas_cost(fork)` covers regular + (under EIP-8037) - # state work for the parametrized snippets; add EIP-1706 slack for - # the trailing SSTORE. - gas_limit=( - intrinsic_calc() - + contract_code.gas_cost(fork) - + Op.SSTORE(new_value=1).state_cost(fork) - ), - sender=sender, - ) + tx = Transaction(to=push0_contract, sender=sender) post[push0_contract] = expected_storage state_test(env=env, pre=pre, post=post, tx=tx) @@ -129,36 +115,24 @@ def push0_contract_callee(self, pre: Alloc) -> Address: ) return push0_contract - PUSH0_CALL_FORWARDED_GAS = 100_000 - - @pytest.fixture - def push0_contract_caller_code( - self, call_opcode: Op, push0_contract_callee: Address - ) -> Bytecode: - """Bytecode for the caller contract.""" - return ( - Op.SSTORE( - 0, - call_opcode( - gas=self.PUSH0_CALL_FORWARDED_GAS, - address=push0_contract_callee, - ), - ) - + Op.SSTORE(0, 1) - + Op.RETURNDATACOPY(0x1F, 0, 1) - + Op.SSTORE(1, Op.MLOAD(0)) - ) - @pytest.fixture def push0_contract_caller( - self, pre: Alloc, push0_contract_caller_code: Bytecode + self, pre: Alloc, call_opcode: Op, push0_contract_callee: Address ) -> Address: """ Deploy the contract that calls the callee PUSH0 contract into `pre`. This fixture returns its address. """ - return pre.deploy_contract(push0_contract_caller_code) + call_code = ( + Op.SSTORE( + 0, call_opcode(gas=100_000, address=push0_contract_callee) + ) + + Op.SSTORE(0, 1) + + Op.RETURNDATACOPY(0x1F, 0, 1) + + Op.SSTORE(1, Op.MLOAD(0)) + ) + return pre.deploy_contract(call_code) @pytest.mark.xdist_group(name="bigmem") @pytest.mark.parametrize( @@ -179,23 +153,8 @@ def test_push0_contract_during_call_contexts( post: Alloc, sender: EOA, push0_contract_caller: Address, - push0_contract_caller_code: Bytecode, - fork: Fork, ) -> None: """Test PUSH0 during various call contexts.""" - intrinsic_calc = fork.transaction_intrinsic_cost_calculator() - tx = Transaction( - to=push0_contract_caller, - # Caller's static cost (3 SSTOREs + CALL static + RETURNDATACOPY - # + MLOAD) plus the forwarded inner-call gas, plus EIP-1706 - # stipend slack on the trailing SSTORE. - gas_limit=( - intrinsic_calc() - + push0_contract_caller_code.gas_cost(fork) - + self.PUSH0_CALL_FORWARDED_GAS - + Op.SSTORE(new_value=1).state_cost(fork) - ), - sender=sender, - ) + tx = Transaction(to=push0_contract_caller, sender=sender) post[push0_contract_caller] = Account(storage={0x00: 0x01, 0x01: 0xFF}) state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/shanghai/eip3860_initcode/conftest.py b/tests/shanghai/eip3860_initcode/conftest.py index 1a5f7534a74..009e25b27b1 100644 --- a/tests/shanghai/eip3860_initcode/conftest.py +++ b/tests/shanghai/eip3860_initcode/conftest.py @@ -1,13 +1,7 @@ """Fixtures for the EIP-3860 initcode tests.""" import pytest -from execution_testing import Alloc, Environment - - -@pytest.fixture -def env() -> Environment: - """Environment fixture.""" - return Environment() +from execution_testing import Alloc @pytest.fixture diff --git a/tests/shanghai/eip3860_initcode/test_initcode.py b/tests/shanghai/eip3860_initcode/test_initcode.py index ca478ddfe10..9037d46c7ca 100644 --- a/tests/shanghai/eip3860_initcode/test_initcode.py +++ b/tests/shanghai/eip3860_initcode/test_initcode.py @@ -16,7 +16,6 @@ Address, Alloc, Bytecode, - Environment, Fork, Initcode, Op, @@ -125,7 +124,6 @@ def initcode(fork: Fork, initcode_name: str) -> Initcode: @pytest.mark.eels_base_coverage def test_contract_creating_tx( state_test: StateTestFiller, - env: Environment, pre: Alloc, post: Alloc, sender: EOA, @@ -140,12 +138,7 @@ def test_contract_creating_tx( nonce=0, ) - tx = Transaction( - to=None, - data=initcode, - gas_limit=10_000_000, - sender=sender, - ) + tx = Transaction(to=None, data=initcode, sender=sender) if len(initcode) > fork.max_initcode_size(): # Initcode is above the max size, tx inclusion in the block makes @@ -157,12 +150,7 @@ def test_contract_creating_tx( # is ok and the contract is successfully created. post[create_contract_address] = Account(code=Op.STOP) - state_test( - env=env, - pre=pre, - post=post, - tx=tx, - ) + state_test(pre=pre, post=post, tx=tx) ZERO_GAS_SPECS = {"empty", "single_byte"} @@ -370,7 +358,6 @@ def post( def test_gas_usage( self, state_test: StateTestFiller, - env: Environment, pre: Alloc, post: Alloc, tx: Transaction, @@ -378,12 +365,7 @@ def test_gas_usage( """ Test transaction and contract creation using different gas limits. """ - state_test( - env=env, - pre=pre, - post=post, - tx=tx, - ) + state_test(pre=pre, post=post, tx=tx) @pytest.mark.parametrize( @@ -471,7 +453,7 @@ def creator_contract_address( self, pre: Alloc, creator_code: Bytecode ) -> Address: """Return address of creator contract.""" - return pre.deploy_contract(creator_code) + return pre.deploy_contract(creator_code, label="creator_contract") @pytest.fixture def created_contract_address( # noqa: D103 @@ -499,7 +481,7 @@ def caller_code(self, creator_contract_address: Address) -> Bytecode: """ return Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) + Op.SSTORE( Op.CALL( - 5000000, creator_contract_address, 0, 0, Op.CALLDATASIZE, 0, 0 + address=creator_contract_address, args_size=Op.CALLDATASIZE ), 1, ) @@ -509,7 +491,7 @@ def caller_contract_address( self, pre: Alloc, caller_code: Bytecode ) -> Address: """Return address of the caller contract.""" - return pre.deploy_contract(caller_code) + return pre.deploy_contract(caller_code, label="caller_contract") @pytest.fixture def tx( @@ -519,7 +501,7 @@ def tx( return Transaction( to=caller_contract_address, data=initcode, - gas_limit=10_000_000, + state_gas_reservoir=0, # Don't hide state gas from Op.GAS sender=sender, ) @@ -528,7 +510,6 @@ def tx( def test_create_opcode_initcode( self, state_test: StateTestFiller, - env: Environment, pre: Alloc, post: Alloc, tx: Transaction, @@ -587,12 +568,7 @@ def test_create_opcode_initcode( }, ) - state_test( - env=env, - pre=pre, - post=post, - tx=tx, - ) + state_test(pre=pre, post=post, tx=tx) @pytest.mark.ported_from( @@ -638,11 +614,7 @@ def test_create2_oversized_initcode_with_insufficient_balance( caller_address = pre.deploy_contract(caller_code) sender = pre.fund_eoa() - tx = Transaction( - sender=sender, - to=caller_address, - gas_limit=10_000_000, - ) + tx = Transaction(sender=sender, to=caller_address) post = { caller_address: Account(storage={1: expected_storage_1}), diff --git a/tests/shanghai/eip4895_withdrawals/test_withdrawals.py b/tests/shanghai/eip4895_withdrawals/test_withdrawals.py index 207e864c663..8c4c62adf60 100644 --- a/tests/shanghai/eip4895_withdrawals/test_withdrawals.py +++ b/tests/shanghai/eip4895_withdrawals/test_withdrawals.py @@ -142,30 +142,21 @@ def test_use_value_in_tx( def test_use_value_in_contract( blockchain_test: BlockchainTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test sending value from contract that has not received a withdrawal.""" sender = pre.fund_eoa() recipient = pre.fund_eoa(1) - contract_code = Op.SSTORE( - Op.NUMBER, - Op.CALL(address=recipient, value=1000000000), - ) - contract_address = pre.deploy_contract(contract_code) - - intrinsic_calc = fork.transaction_intrinsic_cost_calculator() - tx_gas = ( - intrinsic_calc() - + contract_code.gas_cost(fork) - + fork.gas_costs().CALL_VALUE - + Op.SSTORE(new_value=1).state_cost(fork) + contract_address = pre.deploy_contract( + Op.SSTORE( + Op.NUMBER, + Op.CALL(address=recipient, value=1000000000), + ) ) (tx_0, tx_1) = ( Transaction( sender=sender, value=0, - gas_limit=tx_gas, to=contract_address, ) for _ in range(2) @@ -203,7 +194,7 @@ def test_use_value_in_contract( def test_balance_within_block( - blockchain_test: BlockchainTestFiller, pre: Alloc, fork: Fork + blockchain_test: BlockchainTestFiller, pre: Alloc ) -> None: """ Test withdrawal balance increase within the same block in a contract call. @@ -215,19 +206,12 @@ def test_balance_within_block( sender = pre.fund_eoa() recipient = pre.fund_eoa(ONE_GWEI) contract_address = pre.deploy_contract(save_balance_on_block_number) - intrinsic_calc = fork.transaction_intrinsic_cost_calculator() - tx_gas = ( - intrinsic_calc(calldata=Hash(recipient, left_padding=True)) - + save_balance_on_block_number.gas_cost(fork) - + Op.SSTORE(new_value=1).state_cost(fork) - ) blocks = [ Block( txs=[ Transaction( sender=sender, - gas_limit=tx_gas, to=contract_address, data=Hash(recipient, left_padding=True), ) @@ -245,7 +229,6 @@ def test_balance_within_block( txs=[ Transaction( sender=sender, - gas_limit=tx_gas, to=contract_address, data=Hash(recipient, left_padding=True), ) @@ -530,29 +513,21 @@ def test_newly_created_contract( def test_no_evm_execution( blockchain_test: BlockchainTestFiller, pre: Alloc, - fork: Fork, ) -> None: """Test withdrawals don't trigger EVM execution.""" sender = pre.fund_eoa() - contract_code = Op.SSTORE(Op.NUMBER, 1) - contracts = [pre.deploy_contract(contract_code) for _ in range(4)] - intrinsic_calc = fork.transaction_intrinsic_cost_calculator() - tx_gas = ( - intrinsic_calc() - + contract_code.gas_cost(fork) - + Op.SSTORE(new_value=1).state_cost(fork) - ) + contracts = [ + pre.deploy_contract(Op.SSTORE(Op.NUMBER, 1)) for _ in range(4) + ] blocks = [ Block( txs=[ Transaction( sender=sender, - gas_limit=tx_gas, to=contracts[2], ), Transaction( sender=sender, - gas_limit=tx_gas, to=contracts[3], ), ], @@ -575,12 +550,10 @@ def test_no_evm_execution( txs=[ Transaction( sender=sender, - gas_limit=tx_gas, to=contracts[0], ), Transaction( sender=sender, - gas_limit=tx_gas, to=contracts[1], ), ], From b1a898ffb5d758a3132c87784eedf0be4dad4077 Mon Sep 17 00:00:00 2001 From: felix Date: Mon, 15 Jun 2026 14:05:30 +0200 Subject: [PATCH 020/233] feat(tests): sign state test transactions with their secret key to prevent bogus sender recovery (#2983) --- tests/frontier/touch/test_touch.py | 42 ++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/tests/frontier/touch/test_touch.py b/tests/frontier/touch/test_touch.py index 901d6b5d7a0..79c30a6b782 100644 --- a/tests/frontier/touch/test_touch.py +++ b/tests/frontier/touch/test_touch.py @@ -42,3 +42,45 @@ def test_zero_gas_price_and_touching( tx=tx, post={contract: Account(storage={0: value})}, ) + + +@pytest.mark.valid_from("Frontier") +@pytest.mark.valid_before("EIP1559") +def test_zero_gas_price_nonexistent_sender( + state_test: StateTestFiller, + pre: Alloc, +) -> None: + """ + Test a zero gasprice, zero value transaction from a sender that does not + exist in the pre-state. + + Because the transaction is free (gas_price=0) and transfers no value, no + balance is ever deducted from the sender, so the sender account is only + materialized when its nonce is incremented. Clients must create the sender + account in this case rather than failing on a missing account. + + """ + # amount=0 means the sender is NOT added to the pre-alloc. + sender = pre.fund_eoa(amount=0) + + contract = pre.deploy_contract( + code=(Op.SSTORE(0, 0x01) + Op.STOP), + ) + + tx = Transaction( + to=contract, + gas_price=0, # Part of the test, do not change. + value=0, # Part of the test, do not change. + sender=sender, + protected=False, + ) + + state_test( + env=Environment(), + pre=pre, + tx=tx, + post={ + contract: Account(storage={0: 0x01}), + sender: Account(nonce=1, balance=0), + }, + ) From 5c7c53d86caacc44ad147a23afc721defb4ead12 Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Mon, 15 Jun 2026 13:41:23 +0100 Subject: [PATCH 021/233] feat(tests): EIP-7928 cover many storage changes for one account (#2985) --- docs/writing_tests/post_mortems.md | 28 +++++++ .../test_block_access_lists.py | 79 +++++++++++++++++++ 2 files changed, 107 insertions(+) diff --git a/docs/writing_tests/post_mortems.md b/docs/writing_tests/post_mortems.md index 5c18ef41fd4..6e51f3aa7b3 100644 --- a/docs/writing_tests/post_mortems.md +++ b/docs/writing_tests/post_mortems.md @@ -44,6 +44,34 @@ None required - the existing framework supported writing these tests. --- +## 2026-06 - Block Access List Storage Change Cardinality - Amsterdam + +### Description + +A stateless zkEVM client implementation was found to mishandle a single account that accumulates a large number of distinct storage changes in the block access list (EIP-7928). When preloading the transaction recipient's BAL storage keys, the client copied them into a fixed-size buffer sized for 16 slots with no bounds check. A transaction that wrote more than 16 distinct storage slots to one contract overflowed the buffer into adjacent state, corrupting the transaction's computed gas usage and therefore the block validity verdict. + +The bug was latent against the existing test suite: no fixture exercised more than eight distinct storage changes for a single account, and those eight were spread one-per-transaction (`test_bal_cross_tx_storage_chain`), so the per-account, per-transaction storage-change cardinality never approached the buffer boundary. + +### Root Cause Analysis + +- Existing BAL storage tests focused on the correctness of recording, ordering, and the uniqueness rules for small numbers of slots; the high-cardinality case (many distinct slots for one account in one transaction) was implicitly assumed covered or low-risk. +- No fixture pushed a single account past a handful of storage changes, so fixed-size per-account buffers in client implementations were never stressed. +- The block access list is a new structure in EIP-7928, so client-side handling of large per-account storage-change lists had little prior fuzzing or property-based coverage. + +### Steps Taken To Avoid Recurrence + +- Added a parametrized regression test that writes many distinct, previously-zero storage slots (17, 32, and 128) to one contract in a single transaction and asserts the BAL records every slot, in ascending order, at a single block access index. + +### Implemented Test Case + +- `tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py::test_bal_many_storage_writes_single_account` + +### Framework/Documentation Changes + +None required - the existing framework supported writing these tests. + +--- + ## TEMPLATE ## Date - Title - Fork diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py index 72b94884cd0..bbf3877e673 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py @@ -2484,6 +2484,85 @@ def test_bal_cross_tx_storage_chain( ) +@pytest.mark.parametrize( + "num_slots", + [ + pytest.param(17, id="17_slots"), + pytest.param(32, id="32_slots"), + pytest.param(128, id="128_slots"), + ], +) +def test_bal_many_storage_writes_single_account( + pre: Alloc, + blockchain_test: BlockchainTestFiller, + fork: Fork, + num_slots: int, +) -> None: + """ + Verify the BAL records many distinct storage changes for a single + account written by a single transaction. + + One transaction calls a contract that writes `num_slots` distinct, + previously-zero slots (`slot[i] = i + 1` for `i` in `0..num_slots`). + The account's `storage_changes` in the BAL must list every slot, in + ascending slot order, each at `block_access_index=1`. + + Existing BAL storage tests touch at most a handful of slots per + account (e.g. `test_bal_cross_tx_storage_chain` writes 8 slots, one + per transaction). This exercises a much higher per-account, + per-transaction storage-change cardinality, which stresses any client + that records or preloads an account's BAL storage keys into a + fixed-size buffer. + """ + contract_code = Op.SSTORE(0, 1) + for i in range(1, num_slots): + contract_code += Op.SSTORE(i, i + 1) + contract_code += Op.STOP + contract = pre.deploy_contract(code=contract_code) + + alice = pre.fund_eoa() + tx = Transaction( + sender=alice, + to=contract, + gas_limit=fork.transaction_gas_limit_cap(), + ) + + account_expectations = { + alice: BalAccountExpectation( + nonce_changes=[BalNonceChange(block_access_index=1, post_nonce=1)], + ), + contract: BalAccountExpectation( + storage_changes=[ + BalStorageSlot( + slot=i, + slot_changes=[ + BalStorageChange( + block_access_index=1, post_value=i + 1 + ) + ], + ) + for i in range(num_slots) + ], + storage_reads=[], + ), + } + + blockchain_test( + pre=pre, + blocks=[ + Block( + txs=[tx], + expected_block_access_list=BlockAccessListExpectation( + account_expectations=account_expectations + ), + ) + ], + post={ + contract: Account(storage={i: i + 1 for i in range(num_slots)}), + }, + ) + + @pytest.mark.with_all_create_opcodes def test_bal_cross_tx_deploy_then_call( pre: Alloc, From 54246cd940960f1366471e408f08310cf4b200bc Mon Sep 17 00:00:00 2001 From: Sam Wilson Date: Tue, 9 Jun 2026 11:27:32 -0400 Subject: [PATCH 022/233] chore(specs): add type alias for fee market txs --- src/ethereum/forks/amsterdam/fork.py | 6 ++---- src/ethereum/forks/amsterdam/transactions.py | 13 +++++++++++++ src/ethereum/forks/bpo1/fork.py | 6 ++---- src/ethereum/forks/bpo1/transactions.py | 13 +++++++++++++ src/ethereum/forks/bpo2/fork.py | 6 ++---- src/ethereum/forks/bpo2/transactions.py | 13 +++++++++++++ src/ethereum/forks/bpo3/fork.py | 6 ++---- src/ethereum/forks/bpo3/transactions.py | 13 +++++++++++++ src/ethereum/forks/bpo4/fork.py | 6 ++---- src/ethereum/forks/bpo4/transactions.py | 13 +++++++++++++ src/ethereum/forks/bpo5/fork.py | 6 ++---- src/ethereum/forks/bpo5/transactions.py | 13 +++++++++++++ src/ethereum/forks/cancun/fork.py | 3 ++- src/ethereum/forks/cancun/transactions.py | 11 +++++++++++ src/ethereum/forks/osaka/fork.py | 6 ++---- src/ethereum/forks/osaka/transactions.py | 13 +++++++++++++ src/ethereum/forks/prague/fork.py | 6 ++---- src/ethereum/forks/prague/transactions.py | 13 +++++++++++++ 18 files changed, 133 insertions(+), 33 deletions(-) diff --git a/src/ethereum/forks/amsterdam/fork.py b/src/ethereum/forks/amsterdam/fork.py index 50761a89047..ec654585291 100644 --- a/src/ethereum/forks/amsterdam/fork.py +++ b/src/ethereum/forks/amsterdam/fork.py @@ -82,7 +82,7 @@ from .transactions import ( TX_MAX_GAS_LIMIT, BlobTransaction, - FeeMarketTransaction, + FeeMarketCapableTransaction, IntrinsicGasCost, LegacyTransaction, SetCodeTransaction, @@ -588,9 +588,7 @@ def check_transaction( sender_address = recover_sender(block_env.chain_id, tx) sender_account = get_account(tx_state, sender_address) - if isinstance( - tx, (FeeMarketTransaction, BlobTransaction, SetCodeTransaction) - ): + if isinstance(tx, FeeMarketCapableTransaction): if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: raise PriorityFeeGreaterThanMaxFeeError( "priority fee greater than max fee" diff --git a/src/ethereum/forks/amsterdam/transactions.py b/src/ethereum/forks/amsterdam/transactions.py index 320922cce46..598f3552710 100644 --- a/src/ethereum/forks/amsterdam/transactions.py +++ b/src/ethereum/forks/amsterdam/transactions.py @@ -507,6 +507,19 @@ class SetCodeTransaction: """ +FeeMarketCapableTransaction = ( + FeeMarketTransaction | BlobTransaction | SetCodeTransaction +) +""" +Transaction types that include the [EIP-1559]-style fee structure. + +See [`FeeMarketTransaction`][fmt] for more details. + +[EIP-1559]: https://eips.ethereum.org/EIPS/eip-1559 +[fmt]: ref:ethereum.forks.amsterdam.transactions.FeeMarketTransaction +""" + + def encode_transaction(tx: Transaction) -> LegacyTransaction | Bytes: """ Encode a transaction into its RLP or typed transaction format. diff --git a/src/ethereum/forks/bpo1/fork.py b/src/ethereum/forks/bpo1/fork.py index 69b586fde1d..a256625deb7 100644 --- a/src/ethereum/forks/bpo1/fork.py +++ b/src/ethereum/forks/bpo1/fork.py @@ -71,7 +71,7 @@ ) from .transactions import ( BlobTransaction, - FeeMarketTransaction, + FeeMarketCapableTransaction, LegacyTransaction, SetCodeTransaction, Transaction, @@ -486,9 +486,7 @@ def check_transaction( sender_address = recover_sender(block_env.chain_id, tx) sender_account = get_account(tx_state, sender_address) - if isinstance( - tx, (FeeMarketTransaction, BlobTransaction, SetCodeTransaction) - ): + if isinstance(tx, FeeMarketCapableTransaction): if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: raise PriorityFeeGreaterThanMaxFeeError( "priority fee greater than max fee" diff --git a/src/ethereum/forks/bpo1/transactions.py b/src/ethereum/forks/bpo1/transactions.py index 3d8cdf3754d..acd3e8b07c3 100644 --- a/src/ethereum/forks/bpo1/transactions.py +++ b/src/ethereum/forks/bpo1/transactions.py @@ -480,6 +480,19 @@ class SetCodeTransaction: """ +FeeMarketCapableTransaction = ( + FeeMarketTransaction | BlobTransaction | SetCodeTransaction +) +""" +Transaction types that include the [EIP-1559]-style fee structure. + +See [`FeeMarketTransaction`][fmt] for more details. + +[EIP-1559]: https://eips.ethereum.org/EIPS/eip-1559 +[fmt]: ref:ethereum.forks.bpo1.transactions.FeeMarketTransaction +""" + + def encode_transaction(tx: Transaction) -> LegacyTransaction | Bytes: """ Encode a transaction into its RLP or typed transaction format. diff --git a/src/ethereum/forks/bpo2/fork.py b/src/ethereum/forks/bpo2/fork.py index 69b586fde1d..a256625deb7 100644 --- a/src/ethereum/forks/bpo2/fork.py +++ b/src/ethereum/forks/bpo2/fork.py @@ -71,7 +71,7 @@ ) from .transactions import ( BlobTransaction, - FeeMarketTransaction, + FeeMarketCapableTransaction, LegacyTransaction, SetCodeTransaction, Transaction, @@ -486,9 +486,7 @@ def check_transaction( sender_address = recover_sender(block_env.chain_id, tx) sender_account = get_account(tx_state, sender_address) - if isinstance( - tx, (FeeMarketTransaction, BlobTransaction, SetCodeTransaction) - ): + if isinstance(tx, FeeMarketCapableTransaction): if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: raise PriorityFeeGreaterThanMaxFeeError( "priority fee greater than max fee" diff --git a/src/ethereum/forks/bpo2/transactions.py b/src/ethereum/forks/bpo2/transactions.py index 3d8cdf3754d..983dcefa419 100644 --- a/src/ethereum/forks/bpo2/transactions.py +++ b/src/ethereum/forks/bpo2/transactions.py @@ -480,6 +480,19 @@ class SetCodeTransaction: """ +FeeMarketCapableTransaction = ( + FeeMarketTransaction | BlobTransaction | SetCodeTransaction +) +""" +Transaction types that include the [EIP-1559]-style fee structure. + +See [`FeeMarketTransaction`][fmt] for more details. + +[EIP-1559]: https://eips.ethereum.org/EIPS/eip-1559 +[fmt]: ref:ethereum.forks.bpo2.transactions.FeeMarketTransaction +""" + + def encode_transaction(tx: Transaction) -> LegacyTransaction | Bytes: """ Encode a transaction into its RLP or typed transaction format. diff --git a/src/ethereum/forks/bpo3/fork.py b/src/ethereum/forks/bpo3/fork.py index 69b586fde1d..a256625deb7 100644 --- a/src/ethereum/forks/bpo3/fork.py +++ b/src/ethereum/forks/bpo3/fork.py @@ -71,7 +71,7 @@ ) from .transactions import ( BlobTransaction, - FeeMarketTransaction, + FeeMarketCapableTransaction, LegacyTransaction, SetCodeTransaction, Transaction, @@ -486,9 +486,7 @@ def check_transaction( sender_address = recover_sender(block_env.chain_id, tx) sender_account = get_account(tx_state, sender_address) - if isinstance( - tx, (FeeMarketTransaction, BlobTransaction, SetCodeTransaction) - ): + if isinstance(tx, FeeMarketCapableTransaction): if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: raise PriorityFeeGreaterThanMaxFeeError( "priority fee greater than max fee" diff --git a/src/ethereum/forks/bpo3/transactions.py b/src/ethereum/forks/bpo3/transactions.py index 3d8cdf3754d..6592faf2ea9 100644 --- a/src/ethereum/forks/bpo3/transactions.py +++ b/src/ethereum/forks/bpo3/transactions.py @@ -480,6 +480,19 @@ class SetCodeTransaction: """ +FeeMarketCapableTransaction = ( + FeeMarketTransaction | BlobTransaction | SetCodeTransaction +) +""" +Transaction types that include the [EIP-1559]-style fee structure. + +See [`FeeMarketTransaction`][fmt] for more details. + +[EIP-1559]: https://eips.ethereum.org/EIPS/eip-1559 +[fmt]: ref:ethereum.forks.bpo3.transactions.FeeMarketTransaction +""" + + def encode_transaction(tx: Transaction) -> LegacyTransaction | Bytes: """ Encode a transaction into its RLP or typed transaction format. diff --git a/src/ethereum/forks/bpo4/fork.py b/src/ethereum/forks/bpo4/fork.py index 69b586fde1d..a256625deb7 100644 --- a/src/ethereum/forks/bpo4/fork.py +++ b/src/ethereum/forks/bpo4/fork.py @@ -71,7 +71,7 @@ ) from .transactions import ( BlobTransaction, - FeeMarketTransaction, + FeeMarketCapableTransaction, LegacyTransaction, SetCodeTransaction, Transaction, @@ -486,9 +486,7 @@ def check_transaction( sender_address = recover_sender(block_env.chain_id, tx) sender_account = get_account(tx_state, sender_address) - if isinstance( - tx, (FeeMarketTransaction, BlobTransaction, SetCodeTransaction) - ): + if isinstance(tx, FeeMarketCapableTransaction): if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: raise PriorityFeeGreaterThanMaxFeeError( "priority fee greater than max fee" diff --git a/src/ethereum/forks/bpo4/transactions.py b/src/ethereum/forks/bpo4/transactions.py index 3d8cdf3754d..1a916ab853c 100644 --- a/src/ethereum/forks/bpo4/transactions.py +++ b/src/ethereum/forks/bpo4/transactions.py @@ -480,6 +480,19 @@ class SetCodeTransaction: """ +FeeMarketCapableTransaction = ( + FeeMarketTransaction | BlobTransaction | SetCodeTransaction +) +""" +Transaction types that include the [EIP-1559]-style fee structure. + +See [`FeeMarketTransaction`][fmt] for more details. + +[EIP-1559]: https://eips.ethereum.org/EIPS/eip-1559 +[fmt]: ref:ethereum.forks.bpo4.transactions.FeeMarketTransaction +""" + + def encode_transaction(tx: Transaction) -> LegacyTransaction | Bytes: """ Encode a transaction into its RLP or typed transaction format. diff --git a/src/ethereum/forks/bpo5/fork.py b/src/ethereum/forks/bpo5/fork.py index 69b586fde1d..a256625deb7 100644 --- a/src/ethereum/forks/bpo5/fork.py +++ b/src/ethereum/forks/bpo5/fork.py @@ -71,7 +71,7 @@ ) from .transactions import ( BlobTransaction, - FeeMarketTransaction, + FeeMarketCapableTransaction, LegacyTransaction, SetCodeTransaction, Transaction, @@ -486,9 +486,7 @@ def check_transaction( sender_address = recover_sender(block_env.chain_id, tx) sender_account = get_account(tx_state, sender_address) - if isinstance( - tx, (FeeMarketTransaction, BlobTransaction, SetCodeTransaction) - ): + if isinstance(tx, FeeMarketCapableTransaction): if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: raise PriorityFeeGreaterThanMaxFeeError( "priority fee greater than max fee" diff --git a/src/ethereum/forks/bpo5/transactions.py b/src/ethereum/forks/bpo5/transactions.py index 3d8cdf3754d..8c765b0fd5d 100644 --- a/src/ethereum/forks/bpo5/transactions.py +++ b/src/ethereum/forks/bpo5/transactions.py @@ -480,6 +480,19 @@ class SetCodeTransaction: """ +FeeMarketCapableTransaction = ( + FeeMarketTransaction | BlobTransaction | SetCodeTransaction +) +""" +Transaction types that include the [EIP-1559]-style fee structure. + +See [`FeeMarketTransaction`][fmt] for more details. + +[EIP-1559]: https://eips.ethereum.org/EIPS/eip-1559 +[fmt]: ref:ethereum.forks.bpo5.transactions.FeeMarketTransaction +""" + + def encode_transaction(tx: Transaction) -> LegacyTransaction | Bytes: """ Encode a transaction into its RLP or typed transaction format. diff --git a/src/ethereum/forks/cancun/fork.py b/src/ethereum/forks/cancun/fork.py index 9387ba1fcc3..753912fd9d5 100644 --- a/src/ethereum/forks/cancun/fork.py +++ b/src/ethereum/forks/cancun/fork.py @@ -63,6 +63,7 @@ from .transactions import ( AccessListTransaction, BlobTransaction, + FeeMarketCapableTransaction, FeeMarketTransaction, LegacyTransaction, Transaction, @@ -448,7 +449,7 @@ def check_transaction( sender_address = recover_sender(block_env.chain_id, tx) sender_account = get_account(tx_state, sender_address) - if isinstance(tx, (FeeMarketTransaction, BlobTransaction)): + if isinstance(tx, FeeMarketCapableTransaction): if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: raise PriorityFeeGreaterThanMaxFeeError( "priority fee greater than max fee" diff --git a/src/ethereum/forks/cancun/transactions.py b/src/ethereum/forks/cancun/transactions.py index e8b3efbfddb..a0d80d8c67a 100644 --- a/src/ethereum/forks/cancun/transactions.py +++ b/src/ethereum/forks/cancun/transactions.py @@ -358,6 +358,17 @@ class BlobTransaction: """ +FeeMarketCapableTransaction = FeeMarketTransaction | BlobTransaction +""" +Transaction types that include the [EIP-1559]-style fee structure. + +See [`FeeMarketTransaction`][fmt] for more details. + +[EIP-1559]: https://eips.ethereum.org/EIPS/eip-1559 +[fmt]: ref:ethereum.forks.cancun.transactions.FeeMarketTransaction +""" + + def encode_transaction(tx: Transaction) -> LegacyTransaction | Bytes: """ Encode a transaction into its RLP or typed transaction format. diff --git a/src/ethereum/forks/osaka/fork.py b/src/ethereum/forks/osaka/fork.py index 69b586fde1d..a256625deb7 100644 --- a/src/ethereum/forks/osaka/fork.py +++ b/src/ethereum/forks/osaka/fork.py @@ -71,7 +71,7 @@ ) from .transactions import ( BlobTransaction, - FeeMarketTransaction, + FeeMarketCapableTransaction, LegacyTransaction, SetCodeTransaction, Transaction, @@ -486,9 +486,7 @@ def check_transaction( sender_address = recover_sender(block_env.chain_id, tx) sender_account = get_account(tx_state, sender_address) - if isinstance( - tx, (FeeMarketTransaction, BlobTransaction, SetCodeTransaction) - ): + if isinstance(tx, FeeMarketCapableTransaction): if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: raise PriorityFeeGreaterThanMaxFeeError( "priority fee greater than max fee" diff --git a/src/ethereum/forks/osaka/transactions.py b/src/ethereum/forks/osaka/transactions.py index 3e24a84f079..b0a90152275 100644 --- a/src/ethereum/forks/osaka/transactions.py +++ b/src/ethereum/forks/osaka/transactions.py @@ -484,6 +484,19 @@ class SetCodeTransaction: """ +FeeMarketCapableTransaction = ( + FeeMarketTransaction | BlobTransaction | SetCodeTransaction +) +""" +Transaction types that include the [EIP-1559]-style fee structure. + +See [`FeeMarketTransaction`][fmt] for more details. + +[EIP-1559]: https://eips.ethereum.org/EIPS/eip-1559 +[fmt]: ref:ethereum.forks.osaka.transactions.FeeMarketTransaction +""" + + def encode_transaction(tx: Transaction) -> LegacyTransaction | Bytes: """ Encode a transaction into its RLP or typed transaction format. diff --git a/src/ethereum/forks/prague/fork.py b/src/ethereum/forks/prague/fork.py index 34f67ce6177..f48695cf5c6 100644 --- a/src/ethereum/forks/prague/fork.py +++ b/src/ethereum/forks/prague/fork.py @@ -70,7 +70,7 @@ ) from .transactions import ( BlobTransaction, - FeeMarketTransaction, + FeeMarketCapableTransaction, LegacyTransaction, SetCodeTransaction, Transaction, @@ -474,9 +474,7 @@ def check_transaction( sender_address = recover_sender(block_env.chain_id, tx) sender_account = get_account(tx_state, sender_address) - if isinstance( - tx, (FeeMarketTransaction, BlobTransaction, SetCodeTransaction) - ): + if isinstance(tx, FeeMarketCapableTransaction): if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: raise PriorityFeeGreaterThanMaxFeeError( "priority fee greater than max fee" diff --git a/src/ethereum/forks/prague/transactions.py b/src/ethereum/forks/prague/transactions.py index 30af712a4fd..ed549ec1d39 100644 --- a/src/ethereum/forks/prague/transactions.py +++ b/src/ethereum/forks/prague/transactions.py @@ -477,6 +477,19 @@ class SetCodeTransaction: """ +FeeMarketCapableTransaction = ( + FeeMarketTransaction | BlobTransaction | SetCodeTransaction +) +""" +Transaction types that include the [EIP-1559]-style fee structure. + +See [`FeeMarketTransaction`][fmt] for more details. + +[EIP-1559]: https://eips.ethereum.org/EIPS/eip-1559 +[fmt]: ref:ethereum.forks.prague.transactions.FeeMarketTransaction +""" + + def encode_transaction(tx: Transaction) -> LegacyTransaction | Bytes: """ Encode a transaction into its RLP or typed transaction format. From 94f7d12c6039b98465eb2de36d10ca2783742c53 Mon Sep 17 00:00:00 2001 From: danceratopz Date: Mon, 15 Jun 2026 21:48:49 +0200 Subject: [PATCH 023/233] fix(tooling): include namespace packages in coverage reports (#2982) * fix(tooling): include namespace packages in coverage reports * fix(ci): lower json-loader minimum coverage --------- Co-authored-by: Sam Wilson --- Justfile | 2 +- pyproject.toml | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Justfile b/Justfile index 350ba08ab61..477268369b2 100644 --- a/Justfile +++ b/Justfile @@ -163,7 +163,7 @@ json-loader *args: --output="tests/json_loader/fixtures" \ --cov-config=pyproject.toml \ --cov=ethereum \ - --cov-fail-under=85 + --cov-fail-under=80 uv run pytest \ -m "not slow" \ -n auto --maxprocesses 6 --dist=loadfile \ diff --git a/pyproject.toml b/pyproject.toml index ed8de388b72..c436bfe5ff8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -303,6 +303,12 @@ omit = [ "*/ethereum/forks/bpo*/*", ] +[tool.coverage.report] +# `ethereum.forks` is a namespace package (no `__init__.py`); without this +# option, coverage skips it when reporting never-imported files at 0%, so +# forks absent from a run silently drop out of the coverage denominator. +include_namespace_packages = true + [tool.docc] context = [ "docc.listing.context", From 0a4030b0892466fb4f1611cc97f2e58e01c29e67 Mon Sep 17 00:00:00 2001 From: danceratopz Date: Mon, 15 Jun 2026 21:58:34 +0200 Subject: [PATCH 024/233] feat(ci,docs): deploy default branch docs at /docs/execution-specs/ (#2973) --- .github/configs/docs-branches.yaml | 9 +++-- .github/workflows/docs-build.yaml | 54 +++++++++++++++++++++++------- 2 files changed, 48 insertions(+), 15 deletions(-) diff --git a/.github/configs/docs-branches.yaml b/.github/configs/docs-branches.yaml index bcc44b6b1dd..a5befe7df18 100644 --- a/.github/configs/docs-branches.yaml +++ b/.github/configs/docs-branches.yaml @@ -5,14 +5,19 @@ # `docs-config.yml`; a branch that publishes here but isn't listed there # is dispatched but dropped by the aggregator. # +# `default_branch` names the branch whose docs are deployed at the root +# (steel.ethereum.foundation/docs/execution-specs/) instead of a +# branch-nested path. It must also appear in `branches[]` below and must +# match steel-website's `default_branch` setting in `docs-config.yml`. +# # Each entry defines: # - path: The branch name (must match the Git branch exactly) # - label: Human-readable label for the version switcher UI +default_branch: forks/amsterdam + branches: - path: "mainnet" label: "Mainnet (BPO2)" - path: "forks/amsterdam" label: "Amsterdam" - - path: "devnets/bal/4" - label: "bal-devnet-4" diff --git a/.github/workflows/docs-build.yaml b/.github/workflows/docs-build.yaml index 477d159119b..5b7c60a7fed 100644 --- a/.github/workflows/docs-build.yaml +++ b/.github/workflows/docs-build.yaml @@ -6,10 +6,14 @@ # the aggregator workflow in steel-website for deployment # # Site structure at steel.ethereum.foundation/docs/execution-specs/: -# /docs/execution-specs/ - Default branch docs (mirrored) -# /docs/execution-specs/specs/reference/ - Default branch spec reference (mirrored) -# /docs/execution-specs// - Branch-specific docs -# /docs/execution-specs//specs/reference - Branch-specific spec reference (docc output) +# /docs/execution-specs/ - Default branch docs (deployed at root) +# /docs/execution-specs/specs/reference/ - Default branch spec reference +# /docs/execution-specs// - Non-default branch docs +# /docs/execution-specs//specs/reference - Non-default branch spec reference (docc output) +# +# The "default branch" is named in .github/configs/docs-branches.yaml. Its +# SITE_URL is built without a `/` segment so canonical/OG/sitemap +# URLs match where steel-website actually publishes the artifact. name: Build Docs @@ -80,6 +84,8 @@ jobs: branch: ${{ steps.check.outputs.branch }} branch_artifact_name: ${{ steps.check.outputs.branch_artifact_name }} commit_sha: ${{ steps.check.outputs.commit_sha }} + default_branch: ${{ steps.check.outputs.default_branch }} + site_url: ${{ steps.check.outputs.site_url }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: @@ -108,6 +114,19 @@ jobs: exit 1 fi + # The default branch is deployed at /docs/execution-specs/ (no branch + # segment) so its SITE_URL and the publish summary differ. + DEFAULT_BRANCH=$(yq '.default_branch' .github/configs/docs-branches.yaml 2>/dev/null || echo "") + if [ -z "$DEFAULT_BRANCH" ] || [ "$DEFAULT_BRANCH" = "null" ]; then + echo "ERROR: default_branch is missing from .github/configs/docs-branches.yaml" + exit 1 + fi + if ! echo "$ALLOWLISTED_BRANCHES" | grep -qxF "$DEFAULT_BRANCH"; then + echo "ERROR: default_branch '$DEFAULT_BRANCH' is not present in branches[]" + exit 1 + fi + echo "default_branch=$DEFAULT_BRANCH" >> "$GITHUB_OUTPUT" + # Check if branch is in allowlist if echo "$ALLOWLISTED_BRANCHES" | grep -qxF "$BRANCH_INPUT"; then SHOULD_PUBLISH="true" @@ -116,6 +135,20 @@ jobs: fi echo "should_publish=$SHOULD_PUBLISH" >> "$GITHUB_OUTPUT" + # Resolve SITE_URL once so downstream consumers don't re-derive it. + # Default branch publishes at the root of /docs/execution-specs/ so + # canonical/OG/sitemap URLs omit the / segment. + if [ "$SHOULD_PUBLISH" = "true" ]; then + if [ "$BRANCH_INPUT" = "$DEFAULT_BRANCH" ]; then + SITE_URL="https://steel.ethereum.foundation/docs/execution-specs/" + else + SITE_URL="https://steel.ethereum.foundation/docs/execution-specs/${BRANCH_INPUT}/" + fi + else + SITE_URL="https://example.com/docs/${BRANCH_INPUT}/" + fi + echo "site_url=$SITE_URL" >> "$GITHUB_OUTPUT" + # Resolve the concrete SHA we will build, so metadata.json and the # aggregator dispatch payload match what the build jobs check out. # @@ -159,7 +192,9 @@ jobs: if [ "$EVENT_NAME" = "pull_request" ]; then echo "-> **Build only** -- pull request; artifacts are not published." elif [ "$SHOULD_PUBLISH" = "true" ]; then - echo "-> **Will publish** at " + SUFFIX="" + [ "$BRANCH_INPUT" = "$DEFAULT_BRANCH" ] && SUFFIX=" (default branch; deployed at root)" + echo "-> **Will publish** at <$SITE_URL>$SUFFIX" echo "" echo "_Note: the URL only resolves if \`${BRANCH_INPUT}\` is also configured in steel-website's \`BRANCH_CONFIG\` (\`deploy.yml\`). If not, the aggregator will receive the dispatch but drop the artifact._" else @@ -190,15 +225,8 @@ jobs: - name: Build MkDocs documentation env: - BRANCH: ${{ needs.check-should-publish.outputs.branch }} - SHOULD_PUBLISH: ${{ needs.check-should-publish.outputs.should_publish }} + SITE_URL: ${{ needs.check-should-publish.outputs.site_url }} run: | - if [ "$SHOULD_PUBLISH" = "true" ]; then - export SITE_URL="https://steel.ethereum.foundation/docs/execution-specs/${BRANCH}/" - else - export SITE_URL="https://example.com/docs/${BRANCH}/" - fi - echo "Building MkDocs with SITE_URL=$SITE_URL" just docs From 90e74f1cbf166d050bdf1d2043beb18acfd05820 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Tue, 16 Jun 2026 14:16:45 +0200 Subject: [PATCH 025/233] bug(spec-specs, tests): EIP-8037 strict block-gas inclusion rule (#2892) --- src/ethereum/forks/amsterdam/fork.py | 17 +-- .../test_block_2d_gas_accounting.py | 121 ++++++++++++++++++ .../test_state_gas_reservoir.py | 97 ++++++-------- 3 files changed, 166 insertions(+), 69 deletions(-) diff --git a/src/ethereum/forks/amsterdam/fork.py b/src/ethereum/forks/amsterdam/fork.py index ec654585291..f6c4afaba21 100644 --- a/src/ethereum/forks/amsterdam/fork.py +++ b/src/ethereum/forks/amsterdam/fork.py @@ -83,7 +83,6 @@ TX_MAX_GAS_LIMIT, BlobTransaction, FeeMarketCapableTransaction, - IntrinsicGasCost, LegacyTransaction, SetCodeTransaction, Transaction, @@ -496,7 +495,6 @@ def check_transaction( block_output: vm.BlockOutput, tx: Transaction, tx_state: TransactionState, - intrinsic: IntrinsicGasCost, ) -> Tuple[Address, Uint, Tuple[VersionedHash, ...], U64]: """ Check if the transaction is includable in the block. @@ -511,9 +509,6 @@ def check_transaction( The transaction. tx_state : The transaction state tracker. - intrinsic : - The transaction's intrinsic gas cost, split into regular and - state components. Returns ------- @@ -569,16 +564,11 @@ def check_transaction( ) blob_gas_available = MAX_BLOB_GAS_PER_BLOCK - block_output.blob_gas_used - # Worst-case regular contribution: tx.gas minus the portion that - # must go to intrinsic state gas, capped at TX_MAX_GAS_LIMIT. - worst_case_regular = min(TX_MAX_GAS_LIMIT, tx.gas - intrinsic.state) - if worst_case_regular > regular_gas_available: + # EIP-8037 per-dimension inclusion check. + if min(TX_MAX_GAS_LIMIT, tx.gas) > regular_gas_available: raise GasUsedExceedsLimitError("regular gas used exceeds limit") - # Worst-case state contribution: tx.gas minus the portion that - # must go to intrinsic regular gas. - worst_case_state = tx.gas - intrinsic.regular - if worst_case_state > state_gas_available: + if tx.gas > state_gas_available: raise GasUsedExceedsLimitError("state gas used exceeds limit") tx_blob_gas_used = calculate_total_blob_gas(tx) @@ -1014,7 +1004,6 @@ def process_transaction( block_output=block_output, tx=tx, tx_state=tx_state, - intrinsic=intrinsic, ) sender_account = get_account(tx_state, sender) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py index a657275e98d..17f6556c4e9 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py @@ -11,21 +11,27 @@ import pytest from execution_testing import ( + AccessList, Account, + Address, Alloc, + AuthorizationTuple, Block, BlockchainTestFiller, Bytecode, Environment, Fork, + Hash, Header, Op, Storage, Transaction, TransactionException, TransactionReceipt, + add_kzg_version, ) +from ...cancun.eip4844_blobs.spec import Spec as EIP4844_Spec from .spec import ref_spec_8037 REFERENCE_SPEC_GIT_PATH = ref_spec_8037.git_path @@ -477,6 +483,121 @@ def test_multi_block_dimension_flip( ) +@pytest.mark.parametrize( + "tx_gas_delta, expected_exception", + [ + pytest.param(0, None, id="gas_equal"), + pytest.param( + 1, + TransactionException.GAS_ALLOWANCE_EXCEEDED, + id="gas_one_above", + marks=pytest.mark.exception_test, + ), + pytest.param( + 2, + TransactionException.GAS_ALLOWANCE_EXCEEDED, + id="gas_two_above", + marks=pytest.mark.exception_test, + ), + ], +) +@pytest.mark.parametrize( + "block_gas_limit", + [ + pytest.param(0x0FFFFFD, id="bgl_0x0fffffd"), + pytest.param(0x01FFFFE, id="bgl_0x01ffffe"), + ], +) +@pytest.mark.parametrize( + "tx_type, contract_creation", + [ + pytest.param(0, False, id="type_0_call"), + pytest.param(0, True, id="type_0_create"), + pytest.param(1, False, id="type_1_call"), + pytest.param(1, True, id="type_1_create"), + pytest.param(2, False, id="type_2_call"), + pytest.param(2, True, id="type_2_create"), + pytest.param(3, False, id="type_3_blob"), + pytest.param(4, False, id="type_4_set_code"), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_tx_gas_limit_block_boundary( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + tx_type: int, + contract_creation: bool, + block_gas_limit: int, + tx_gas_delta: int, + expected_exception: TransactionException | None, +) -> None: + """ + Reject tx whose ``gas_limit`` exceeds the block ``gas_limit``. + + EIP-8037 inclusion rule: ``min(TX_MAX_GAS_LIMIT, tx.gas) <= + regular_gas_available`` and ``tx.gas <= state_gas_available``. + At block start both budgets equal ``block_gas_limit``. + """ + gas_limit = block_gas_limit + tx_gas_delta + gas_price = 10 + sender = pre.fund_eoa(amount=gas_limit * gas_price + 10**18) + + to = None if contract_creation else pre.fund_eoa(amount=0) + access_list = None + authorization_list = None + blob_versioned_hashes = None + extra_fee_args: dict = {} + if tx_type == 1: + access_list = [AccessList(address=Address(1), storage_keys=[Hash(0)])] + elif tx_type == 2: + access_list = [] + elif tx_type == 3: + blob_versioned_hashes = add_kzg_version( + [Hash(1)], EIP4844_Spec.BLOB_COMMITMENT_VERSION_KZG + ) + extra_fee_args["max_fee_per_blob_gas"] = 1 + elif tx_type == 4: + authorization_list = [ + AuthorizationTuple( + signer=pre.fund_eoa(amount=0), address=Address(1) + ) + ] + + if tx_type in (0, 1): + fee_args: dict = {"gas_price": gas_price} + else: + fee_args = { + "max_fee_per_gas": gas_price, + "max_priority_fee_per_gas": 0, + } + fee_args.update(extra_fee_args) + + tx = Transaction( + ty=tx_type, + sender=sender, + to=to, + gas_limit=gas_limit, + access_list=access_list, + authorization_list=authorization_list, + blob_versioned_hashes=blob_versioned_hashes, + error=expected_exception, + **fee_args, + ) + + blockchain_test( + genesis_environment=Environment(gas_limit=block_gas_limit), + pre=pre, + blocks=[ + Block( + txs=[tx], + gas_limit=block_gas_limit, + exception=expected_exception, + ) + ], + post={}, + ) + + @pytest.mark.parametrize( "delta", [ diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_reservoir.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_reservoir.py index 11d0de0ac94..1da756d4a2f 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_reservoir.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_reservoir.py @@ -266,10 +266,9 @@ def test_block_state_gas_limit_boundary( Verify the per-tx state check at the strict-greater-than boundary. tx1 consumes `tx1_state` via cold SSTOREs. tx2 is sized so that - its worst-case state contribution `tx.gas - intrinsic_regular` - equals `state_available` (delta=0, accepted because the check is - strict `>`) or exceeds it by 1 (delta=1, rejected with - `GAS_ALLOWANCE_EXCEEDED`). + its worst-case state contribution `tx.gas` equals `state_available` + (delta=0, accepted because the check is strict `>`) or exceeds it + by 1 (delta=1, rejected with `GAS_ALLOWANCE_EXCEEDED`). The regular check is asserted to pass so rejection on delta=1 is pinned to the state dimension. @@ -291,11 +290,10 @@ def test_block_state_gas_limit_boundary( tx1_regular = intrinsic_cost() + tx1_code.gas_cost(fork) - tx1_state tx1_gas = gas_limit_cap + tx1_state - # tx2: worst-case state contribution = tx.gas - intrinsic_regular. + # tx2: worst-case state contribution = tx.gas (strict EIP rule). # Plain call, so intrinsic_state is zero. - tx2_intrinsic_regular = intrinsic_cost() state_available = block_gas_limit - tx1_state - tx2_gas = tx2_intrinsic_regular + state_available + delta + tx2_gas = state_available + delta # Pin the rejection (when delta > 0) to the state check: the # regular check must not fire. @@ -335,22 +333,21 @@ def test_block_state_gas_limit_boundary( ) +@pytest.mark.exception_test @pytest.mark.valid_from("EIP8037") -def test_creation_tx_regular_check_subtracts_intrinsic_state( +def test_creation_tx_regular_check_uses_full_tx_gas( blockchain_test: BlockchainTestFiller, pre: Alloc, fork: Fork, ) -> None: """ - Verify the regular check subtracts `intrinsic.state` from tx.gas. - - The EIP regular check is - `min(TX_MAX, tx.gas - intrinsic.state) > regular_available`. For a - creation tx, `intrinsic.state = GAS_NEW_ACCOUNT`. This test sizes a - creation tx whose raw `tx.gas` exceeds `regular_available` but - `tx.gas - intrinsic.state` fits; it must be accepted. The old - formula `min(TX_MAX, tx.gas)` would reject the same tx, proving - the subtraction is honored. + Verify the regular check uses the full `tx.gas` (no subtraction). + + The EIP regular check is `min(TX_MAX, tx.gas) > regular_available`. + For a creation tx, `intrinsic.state = GAS_NEW_ACCOUNT`. This test + sizes a creation tx whose raw `tx.gas` exceeds `regular_available` + while `tx.gas - intrinsic.state` would fit; it must be rejected. A + formula subtracting `intrinsic.state` would have wrongly accepted. """ gas_limit_cap = fork.transaction_gas_limit_cap() assert gas_limit_cap is not None @@ -364,10 +361,10 @@ def test_creation_tx_regular_check_subtracts_intrinsic_state( ) - fork.transaction_intrinsic_state_gas(contract_creation=True) # Tight boundary: after the filler consumes gas_limit_cap, the - # remaining regular is exactly intrinsic_regular + 1. The old + # remaining regular is exactly intrinsic_regular + 1. The strict # formula `min(TX_MAX, tx.gas)` rejects (tx.gas = intrinsic_total - # > intrinsic_regular + 1); the new formula `min(TX_MAX, tx.gas - # - intrinsic.state)` accepts (equals intrinsic_regular). + # > intrinsic_regular + 1); a formula subtracting `intrinsic.state` + # would accept (tx.gas - intrinsic.state == intrinsic_regular). block_gas_limit = gas_limit_cap + intrinsic_regular + 1 intrinsic_state = fork.transaction_intrinsic_state_gas( @@ -383,10 +380,10 @@ def test_creation_tx_regular_check_subtracts_intrinsic_state( remaining_regular = block_gas_limit - gas_limit_cap assert create_tx_gas > remaining_regular, ( - "old formula must reject to prove new formula differs" + "strict formula must reject: full tx.gas exceeds remaining regular" ) assert create_tx_gas - intrinsic_state <= remaining_regular, ( - "new formula must accept" + "a subtracting formula would have accepted" ) filler_tx = Transaction( @@ -398,6 +395,7 @@ def test_creation_tx_regular_check_subtracts_intrinsic_state( to=None, gas_limit=create_tx_gas, sender=pre.fund_eoa(), + error=TransactionException.GAS_ALLOWANCE_EXCEEDED, ) blockchain_test( @@ -407,6 +405,7 @@ def test_creation_tx_regular_check_subtracts_intrinsic_state( Block( txs=[filler_tx, create_tx], gas_limit=block_gas_limit, + exception=TransactionException.GAS_ALLOWANCE_EXCEEDED, ) ], post={}, @@ -421,19 +420,18 @@ def test_single_tx_state_check_exceeds_block_limit( fork: Fork, ) -> None: """ - Verify a single tx is rejected when its state contribution exceeds - the entire block gas limit. + Verify a single tx is rejected when its gas limit exceeds the + entire block gas limit in the state dimension. - No prior txs needed. A tx whose tx.gas - intrinsic_regular exceeds - block_gas_limit must be rejected at inclusion. + No prior txs needed. The state check uses the full `tx.gas`, so a + tx whose `tx.gas` exceeds `block_gas_limit` must be rejected at + inclusion. """ gas_limit_cap = fork.transaction_gas_limit_cap() assert gas_limit_cap is not None - intrinsic_cost = fork.transaction_intrinsic_cost_calculator() - intrinsic_regular = intrinsic_cost() block_gas_limit = gas_limit_cap + 100 - tx_gas = block_gas_limit + intrinsic_regular + 1 + tx_gas = block_gas_limit + 1 tx = Transaction( to=pre.deploy_contract(code=Op.STOP), @@ -466,15 +464,11 @@ def test_creation_tx_state_check_exceeded( """ Verify a creation tx is rejected by the state check. - A creation tx has non-zero intrinsic_state (new account) AND - intrinsic_regular (base + CREATE cost). Both formulas are - exercised: the regular check subtracts intrinsic_state, the state - check subtracts intrinsic_regular. - - A filler tx consumes state budget. The creation tx's state - contribution (tx.gas - intrinsic_regular) exceeds the remaining - state budget while its regular contribution - (tx.gas - intrinsic_state) fits the regular budget. + A creation tx (`to=None`) goes through the per-dimension inclusion + check like any other tx. A filler tx consumes state budget; the + creation tx's `tx.gas` then exceeds the remaining state budget by + one while its regular contribution still fits, pinning the + rejection to the state dimension. """ gas_limit_cap = fork.transaction_gas_limit_cap() assert gas_limit_cap is not None @@ -482,11 +476,6 @@ def test_creation_tx_state_check_exceeded( block_gas_limit = 100_000_000 intrinsic_cost = fork.transaction_intrinsic_cost_calculator() - create_intrinsic_total = intrinsic_cost(contract_creation=True) - create_intrinsic_state = fork.transaction_intrinsic_state_gas( - contract_creation=True, - ) - create_intrinsic_regular = create_intrinsic_total - create_intrinsic_state num_sstores = 50 tx1_code = Bytecode() @@ -499,14 +488,12 @@ def test_creation_tx_state_check_exceeded( tx1_gas = gas_limit_cap + tx1_state state_available = block_gas_limit - tx1_state - # tx2 state contribution = state_available + 1 → rejected - tx2_gas = create_intrinsic_regular + state_available + 1 + # tx2: full tx.gas exceeds state_available by 1, so rejected. + tx2_gas = state_available + 1 # Regular check must pass so rejection is pinned to state. regular_available = block_gas_limit - tx1_regular - assert min(gas_limit_cap, tx2_gas - create_intrinsic_state) < ( - regular_available - ) + assert min(gas_limit_cap, tx2_gas) < regular_available tx1 = Transaction( to=tx1_contract, @@ -635,17 +622,17 @@ def test_block_2d_gas_valid_when_cumulative_exceeds_limit( assert tx_state > tx_regular block_gas_used = tx_state - # num_txs sized so `one_d_bound > block_gas_limit > two_d_bound`: - # per-dimension maxes fit (accepted under 2D-max) but the 1D sum - # exceeds the limit (would be rejected by a summing client). - num_txs = block_gas_limit // block_gas_used + env = Environment(gas_limit=block_gas_limit) + tx_limit = tx_gas_used + 1000 + + # Strict rule counts full `tx.gas` per dimension; state is the + # binding one (tx_state > tx_regular), so every `tx_limit` must + # fit the remaining state gas. + num_txs = (block_gas_limit - tx_limit) // tx_state + 1 two_d_bound = num_txs * block_gas_used one_d_bound = num_txs * tx_gas_used assert two_d_bound <= block_gas_limit < one_d_bound - env = Environment(gas_limit=block_gas_limit) - tx_limit = tx_gas_used + 1000 - txs = [] post = {} for _ in range(num_txs): From e35d103be5007a0b98eb1a69af1c584a6e79d862 Mon Sep 17 00:00:00 2001 From: spencer Date: Tue, 16 Jun 2026 18:29:24 +0100 Subject: [PATCH 026/233] feat(tests, spec-specs): raise EIP-7954 max code size to 64KiB (#2987) Co-authored-by: danceratopz --- .../forks/forks/eips/amsterdam/eip_7954.py | 12 ++++++------ .../execution_testing/tools/tools_code/generators.py | 11 +++++++---- src/ethereum/forks/amsterdam/vm/interpreter.py | 2 +- .../eip7954_increase_max_contract_size/spec.py | 2 +- .../eip7954_increase_max_contract_size/test_cases.md | 4 ++-- .../test_fork_transition.py | 8 ++++++-- .../stRandom2/test_random_statetest646.py | 6 ++++++ 7 files changed, 29 insertions(+), 16 deletions(-) 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 index b5c23ce2752..29b8ad22dd6 100644 --- 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 @@ -1,8 +1,8 @@ """ EIP-7954: Increase Maximum Contract Size. -Raise the maximum contract code size from 24KiB to 32KiB and initcode size from -48KiB to 64KiB. +Raise the maximum contract code size from 24KiB to 64KiB and initcode size from +48KiB to 128KiB. https://eips.ethereum.org/EIPS/eip-7954 """ @@ -15,10 +15,10 @@ class EIP7954(BaseFork): @classmethod def max_code_size(cls) -> int: - """Max contract code size is 32 KiB.""" - return 32 * 1024 + """Max contract code size is 64 KiB.""" + return 64 * 1024 @classmethod def max_initcode_size(cls) -> int: - """Max initcode size is 64 KiB.""" - return 64 * 1024 + """Max initcode size is 128 KiB.""" + return 128 * 1024 diff --git a/packages/testing/src/execution_testing/tools/tools_code/generators.py b/packages/testing/src/execution_testing/tools/tools_code/generators.py index 9d15d425e59..3a5c79a0e07 100644 --- a/packages/testing/src/execution_testing/tools/tools_code/generators.py +++ b/packages/testing/src/execution_testing/tools/tools_code/generators.py @@ -53,8 +53,11 @@ def __new__( initcode = initcode_prefix code_length = len(deploy_code) - # PUSH2: length= - initcode += Op.PUSH2(code_length) + # PUSHN: length=. PUSH2 by default, widening to a + # larger PUSH only when the deploy code exceeds 64KiB - 1 bytes. + push_length_size = max(2, (code_length.bit_length() + 7) // 8) + push_length = getattr(Op, f"PUSH{push_length_size}") + initcode += push_length(code_length) # PUSH1: offset=0 initcode += Op.PUSH1(0) @@ -62,8 +65,8 @@ def __new__( # DUP2 initcode += Op.DUP2 - # PUSH1: initcode_length=11 + len(initcode_prefix_bytes) (constant) - no_prefix_length = 0x0B + # PUSH1: initcode_length=9 + push_length_size + initcode_prefix_bytes + no_prefix_length = 0x09 + push_length_size assert no_prefix_length + len(initcode_prefix) <= 0xFF, ( "initcode prefix too long" ) diff --git a/src/ethereum/forks/amsterdam/vm/interpreter.py b/src/ethereum/forks/amsterdam/vm/interpreter.py index b8eb6b968db..bb01dad813f 100644 --- a/src/ethereum/forks/amsterdam/vm/interpreter.py +++ b/src/ethereum/forks/amsterdam/vm/interpreter.py @@ -68,7 +68,7 @@ from .runtime import get_valid_jump_destinations STACK_DEPTH_LIMIT = Uint(1024) -MAX_CODE_SIZE = 0x8000 +MAX_CODE_SIZE = 0x10000 MAX_INIT_CODE_SIZE = 2 * MAX_CODE_SIZE diff --git a/tests/amsterdam/eip7954_increase_max_contract_size/spec.py b/tests/amsterdam/eip7954_increase_max_contract_size/spec.py index 7dac2d83625..bef566b2240 100644 --- a/tests/amsterdam/eip7954_increase_max_contract_size/spec.py +++ b/tests/amsterdam/eip7954_increase_max_contract_size/spec.py @@ -13,5 +13,5 @@ class ReferenceSpec: ref_spec_7954 = ReferenceSpec( git_path="EIPS/eip-7954.md", - version="b1f5bf8f70ba9306400f5e13313f781c35acc860", + version="1dc9bc870f864d7ad1095fc73ba8ca098d02c732", ) diff --git a/tests/amsterdam/eip7954_increase_max_contract_size/test_cases.md b/tests/amsterdam/eip7954_increase_max_contract_size/test_cases.md index 68d665d0c82..27ab07655c5 100644 --- a/tests/amsterdam/eip7954_increase_max_contract_size/test_cases.md +++ b/tests/amsterdam/eip7954_increase_max_contract_size/test_cases.md @@ -7,12 +7,12 @@ | `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_warm_after_failed_create_over_max_code_size` | A failed CREATE/CREATE2 over max code size leaves the would-be address warm | A creator runs CREATE/CREATE2 whose initcode returns `MAX_CODE_SIZE + 1` bytes; a checker then measures the gas of a `BALANCE` on that address. | The address is warm: the post-RETURN size-check rejection still leaves it in the access list. | ✅ Completed | +| `test_max_code_size_fork_transition` | New `MAX_CODE_SIZE` activates exactly at the fork boundary | Before and after the fork, deploy a contract one byte over the parent fork's max code size (valid under the new limit; its initcode stays within both forks' initcode limits). | Pre-fork: deployment fails at code deposit (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 | 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 10931708eeb..90aabfa5560 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 @@ -38,7 +38,9 @@ def test_max_code_size_fork_transition( fork: TransitionFork, ) -> None: """Ensure the new max code size limit activates at the fork boundary.""" - code_size = fork.transitions_to().max_code_size() + parent = fork.transitions_from() + assert parent is not None, "Parent fork must be defined for this test" + code_size = parent.max_code_size() + 1 deploy_code = Op.JUMPDEST * code_size initcode = Initcode(deploy_code=deploy_code) @@ -87,7 +89,9 @@ def test_max_code_size_via_create_fork_transition( create_opcode: Op, ) -> None: """Ensure the new max code size limit activates at the fork via opcodes.""" - code_size = fork.transitions_to().max_code_size() + parent = fork.transitions_from() + assert parent is not None, "Parent fork must be defined for this test" + code_size = parent.max_code_size() + 1 deploy_code = Op.JUMPDEST * code_size initcode = Initcode(deploy_code=deploy_code) initcode_bytes = bytes(initcode) diff --git a/tests/ported_static/stRandom2/test_random_statetest646.py b/tests/ported_static/stRandom2/test_random_statetest646.py index 112c3e9e5ee..868470e3bce 100644 --- a/tests/ported_static/stRandom2/test_random_statetest646.py +++ b/tests/ported_static/stRandom2/test_random_statetest646.py @@ -28,6 +28,7 @@ ["state_tests/stRandom2/randomStatetest646Filler.json"], ) @pytest.mark.valid_from("Cancun") +@pytest.mark.valid_before("EIP7954") @pytest.mark.pre_alloc_mutable def test_random_statetest646( state_test: StateTestFiller, @@ -96,6 +97,11 @@ def test_random_statetest646( value=0x5684B90A, ) + # Capped at EIP7954: the 0x13FFA-byte CREATE initcode exceeds + # MAX_INITCODE_SIZE only before the limit is raised, so the inner CREATE + # reverts the frame and the created address never persists. EIP-7954's + # raised limit (where this initcode is valid) is covered by the dedicated + # tests in tests/amsterdam/eip7954_increase_max_contract_size. post = { sender: Account(storage={}, code=b"", nonce=1), compute_create_address( From afbb3258fc25b44dc98174121997fb249e22e1d1 Mon Sep 17 00:00:00 2001 From: danceratopz Date: Tue, 16 Jun 2026 14:22:09 +0200 Subject: [PATCH 027/233] feat(test-forks): enable filling for TangerineWhistle and SpuriousDragon Remove `ignore=True` from `TangerineWhistle` and `SpuriousDragon` so they are included in `get_deployed_forks()` and filled. Both have distinct EVM rulesets (`TANGERINE`/`SPURIOUS`) and full EELS `t8n` support but were never filled, leaving `valid_from("TangerineWhistle")` tests starting at Byzantium. Update the `valid_until` marker test for the two added deployed forks. --- .../pytest_commands/plugins/forks/tests/test_markers.py | 7 ++++++- .../testing/src/execution_testing/forks/forks/forks.py | 2 -- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/forks/tests/test_markers.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/forks/tests/test_markers.py index 34e84cb1b7e..d6fd07fdff9 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/forks/tests/test_markers.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/forks/tests/test_markers.py @@ -26,7 +26,12 @@ def test_case(state_test): valid_until='"Cancun"', ), [], - {"passed": 10, "failed": 0, "skipped": 0, "errors": 0}, + # All deployed forks from Frontier through Cancun, except + # Constantinople (filled as ConstantinopleFix): Frontier, + # Homestead, TangerineWhistle, SpuriousDragon, Byzantium, + # ConstantinopleFix, Istanbul, Berlin, London, Paris, Shanghai, + # Cancun = 12 forks. + {"passed": 12, "failed": 0, "skipped": 0, "errors": 0}, id="valid_until", ), pytest.param( diff --git a/packages/testing/src/execution_testing/forks/forks/forks.py b/packages/testing/src/execution_testing/forks/forks/forks.py index cd2777c88a0..1360b0da715 100644 --- a/packages/testing/src/execution_testing/forks/forks/forks.py +++ b/packages/testing/src/execution_testing/forks/forks/forks.py @@ -1347,7 +1347,6 @@ class DAOFork( class TangerineWhistle( DAOFork, - ignore=True, ruleset_name="TANGERINE", ): """TangerineWhistle fork (EIP-150).""" @@ -1360,7 +1359,6 @@ class SpuriousDragon( eips.EIP161, eips.EIP155, TangerineWhistle, - ignore=True, ruleset_name="SPURIOUS", ): """SpuriousDragon fork.""" From b314d18ec625660398343494f290df16a5fc315a Mon Sep 17 00:00:00 2001 From: danceratopz Date: Tue, 16 Jun 2026 14:22:09 +0200 Subject: [PATCH 028/233] chore(ci): rebalance fill fork ranges for the enabled forks Split `Frontier`->`Shanghai` + `Cancun` into `Frontier`->`Paris` + `Shanghai`->`Cancun` so the two newly filled forks do not overload the `pre-cancun` job. Keeps the same runner count in both the `fill` matrix (`test.yaml`) and the release split (`fork-ranges.yaml`). --- .github/configs/fork-ranges.yaml | 8 ++++---- .github/workflows/test.yaml | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/configs/fork-ranges.yaml b/.github/configs/fork-ranges.yaml index feb5ed48e38..ef3acf4818e 100644 --- a/.github/configs/fork-ranges.yaml +++ b/.github/configs/fork-ranges.yaml @@ -1,11 +1,11 @@ # Shared fork ranges for splitting multi-fork releases across parallel runners. # Features using --until are automatically split using applicable ranges. # Features using --fork (single fork) are never split. -- label: pre-cancun +- label: pre-shanghai from: Frontier - until: Shanghai -- label: cancun - from: Cancun + until: Paris +- label: shanghai-cancun + from: Shanghai until: Cancun - label: prague from: Prague diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index b328615180e..723ef6c95a4 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -81,11 +81,11 @@ jobs: fail-fast: false matrix: include: - - label: pre-cancun + - label: pre-shanghai from_fork: Frontier - until_fork: Shanghai - - label: cancun - from_fork: Cancun + until_fork: Paris + - label: shanghai-cancun + from_fork: Shanghai until_fork: Cancun - label: prague from_fork: Prague From c8b2f08b369ec719bfb56ba078df759d1d8da5a2 Mon Sep 17 00:00:00 2001 From: Sam Wilson Date: Tue, 9 Jun 2026 17:51:13 -0400 Subject: [PATCH 029/233] feat(tests): pick most strict valid_from mark when multiple appear --- .../pytest_commands/plugins/forks/forks.py | 48 +++++++++++++++++-- .../forks/tests/test_bad_validity_markers.py | 31 ------------ .../plugins/forks/tests/test_markers.py | 13 +++++ 3 files changed, 56 insertions(+), 36 deletions(-) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/forks/forks.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/forks/forks.py index b10bc17c5ec..ca56fce5c7f 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/forks/forks.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/forks/forks.py @@ -15,6 +15,7 @@ Iterable, Iterator, List, + Self, Set, Tuple, Type, @@ -801,12 +802,14 @@ def get_all_validity_markers( for marker in markers: for marker_name in ALL_VALIDITY_MARKERS: if marker.name == marker_name: - if marker_name in markers_dict: - raise Exception( - f"Too many '{marker_name}' markers applied to test" - ) cls = ALL_VALIDITY_MARKERS[marker.name] - markers_dict[marker_name] = cls(mark=marker) + new_marker = cls(mark=marker) + try: + existing_marker = markers_dict[marker_name] + except KeyError: + markers_dict[marker_name] = new_marker + else: + existing_marker.update(new_marker) for cls in ALL_VALIDITY_MARKERS.values(): if cls.flag and cls.marker_name not in markers_dict: @@ -915,6 +918,26 @@ def _process_with_marker_args( """ pass + def update(self, other: Self) -> None: + """ + Update `self` to be the more strict of `self` or `other`. + + For example: + + >>> first = ValidFrom("Frontier") + >>> second = ValidFrom("Osaka") + >>> first.update(second) + >>> print(first) + ValidFrom("Osaka") + + If `self` cannot be updated (no merging is possible/implemented), + raises an exception. + """ + del other + raise Exception( + f"Too many '{self.marker_name}' markers applied to test" + ) + class ValidFrom(ValidityMarker): """ @@ -950,6 +973,21 @@ def _process_with_marker_args( resulting_set |= {f for f in ALL_FORKS if f >= fork} return resulting_set + def update(self, other: Self) -> None: + """Replace `self` with `other` if `other` is more restrictive.""" + if self.mark is None: + self.mark = other.mark + return + + if other.mark is None: + return + + ours = len(self._process_with_marker_args(*self.mark.args)) + theirs = len(other._process_with_marker_args(*other.mark.args)) + + if theirs < ours: + self.mark = other.mark + class ValidUntil(ValidityMarker): """ diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/forks/tests/test_bad_validity_markers.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/forks/tests/test_bad_validity_markers.py index 6c3093a60ba..f063d0dd1c3 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/forks/tests/test_bad_validity_markers.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/forks/tests/test_bad_validity_markers.py @@ -7,19 +7,6 @@ invalid_merge_marker = "Marge" # codespell:ignore marge invalid_validity_marker_test_cases = ( - ( - "too_many_valid_from_markers", - ( - """ - import pytest - @pytest.mark.valid_from("Paris") - @pytest.mark.valid_from("Paris") - def test_case(state_test): - assert 0 - """, - "Too many 'valid_from' markers applied to test", - ), - ), ( "too_many_valid_until_markers", ( @@ -248,24 +235,6 @@ def test_invalid_validity_markers( param_level_marker_error_test_cases = ( - ( - "param_level_valid_from_with_function_level_valid_from", - ( - """ - import pytest - @pytest.mark.parametrize( - "value", - [ - pytest.param(True, marks=pytest.mark.valid_from("Paris")), - ], - ) - @pytest.mark.valid_from("Berlin") - def test_case(state_test, value): - assert 1 - """, - "Too many 'valid_from' markers applied to test", - ), - ), ( "param_level_valid_until_with_function_level_valid_until", ( diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/forks/tests/test_markers.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/forks/tests/test_markers.py index d6fd07fdff9..696d5770d30 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/forks/tests/test_markers.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/forks/tests/test_markers.py @@ -21,6 +21,19 @@ def test_case(state_test): @pytest.mark.parametrize( "test_function,pytest_args,outcomes", [ + pytest.param( + """ +import pytest +@pytest.mark.valid_from("Paris") +@pytest.mark.valid_from("Berlin") +@pytest.mark.state_test_only +def test_case(state_test): + pass +""", + [], + {"passed": 5, "failed": 0, "skipped": 0, "errors": 0}, + id="two_valid_from", + ), pytest.param( generate_test( valid_until='"Cancun"', From 45fb08e4925d39e26cdcd3f2c6eb63d57edea00e Mon Sep 17 00:00:00 2001 From: Sam Wilson Date: Fri, 22 May 2026 16:45:37 -0400 Subject: [PATCH 030/233] chore(specs): refactor code deployment functions --- src/ethereum/forks/amsterdam/state_tracker.py | 28 ++++------- .../forks/amsterdam/vm/instructions/system.py | 7 +-- .../forks/amsterdam/vm/interpreter.py | 12 ++--- .../forks/arrow_glacier/state_tracker.py | 28 ++++------- .../arrow_glacier/vm/instructions/system.py | 7 +-- .../forks/arrow_glacier/vm/interpreter.py | 12 ++--- src/ethereum/forks/berlin/state_tracker.py | 28 ++++------- .../forks/berlin/vm/instructions/system.py | 7 +-- src/ethereum/forks/berlin/vm/interpreter.py | 12 ++--- src/ethereum/forks/bpo1/state_tracker.py | 28 ++++------- .../forks/bpo1/vm/instructions/system.py | 7 +-- src/ethereum/forks/bpo1/vm/interpreter.py | 12 ++--- src/ethereum/forks/bpo2/state_tracker.py | 28 ++++------- .../forks/bpo2/vm/instructions/system.py | 7 +-- src/ethereum/forks/bpo2/vm/interpreter.py | 12 ++--- src/ethereum/forks/bpo3/state_tracker.py | 28 ++++------- .../forks/bpo3/vm/instructions/system.py | 7 +-- src/ethereum/forks/bpo3/vm/interpreter.py | 12 ++--- src/ethereum/forks/bpo4/state_tracker.py | 28 ++++------- .../forks/bpo4/vm/instructions/system.py | 7 +-- src/ethereum/forks/bpo4/vm/interpreter.py | 12 ++--- src/ethereum/forks/bpo5/state_tracker.py | 28 ++++------- .../forks/bpo5/vm/instructions/system.py | 7 +-- src/ethereum/forks/bpo5/vm/interpreter.py | 12 ++--- src/ethereum/forks/byzantium/state_tracker.py | 28 ++++------- .../forks/byzantium/vm/instructions/system.py | 7 +-- .../forks/byzantium/vm/interpreter.py | 12 ++--- src/ethereum/forks/cancun/state_tracker.py | 28 ++++------- .../forks/cancun/vm/instructions/system.py | 7 +-- src/ethereum/forks/cancun/vm/interpreter.py | 12 ++--- .../forks/constantinople/state_tracker.py | 28 ++++------- .../constantinople/vm/instructions/system.py | 7 +-- .../forks/constantinople/vm/interpreter.py | 12 ++--- src/ethereum/forks/dao_fork/state_tracker.py | 28 ++++------- .../forks/dao_fork/vm/instructions/system.py | 7 +-- src/ethereum/forks/dao_fork/vm/interpreter.py | 12 ++--- src/ethereum/forks/frontier/state_tracker.py | 28 ++++------- .../forks/frontier/vm/instructions/system.py | 7 +-- src/ethereum/forks/frontier/vm/interpreter.py | 12 ++--- .../forks/gray_glacier/state_tracker.py | 28 ++++------- .../gray_glacier/vm/instructions/system.py | 7 +-- .../forks/gray_glacier/vm/interpreter.py | 12 ++--- src/ethereum/forks/homestead/state_tracker.py | 28 ++++------- .../forks/homestead/vm/instructions/system.py | 7 +-- .../forks/homestead/vm/interpreter.py | 12 ++--- src/ethereum/forks/istanbul/state_tracker.py | 28 ++++------- .../forks/istanbul/vm/instructions/system.py | 7 +-- src/ethereum/forks/istanbul/vm/interpreter.py | 12 ++--- src/ethereum/forks/london/state_tracker.py | 28 ++++------- .../forks/london/vm/instructions/system.py | 7 +-- src/ethereum/forks/london/vm/interpreter.py | 12 ++--- .../forks/muir_glacier/state_tracker.py | 28 ++++------- .../muir_glacier/vm/instructions/system.py | 7 +-- .../forks/muir_glacier/vm/interpreter.py | 12 ++--- src/ethereum/forks/osaka/state_tracker.py | 28 ++++------- .../forks/osaka/vm/instructions/system.py | 7 +-- src/ethereum/forks/osaka/vm/interpreter.py | 12 ++--- src/ethereum/forks/paris/state_tracker.py | 28 ++++------- .../forks/paris/vm/instructions/system.py | 7 +-- src/ethereum/forks/paris/vm/interpreter.py | 12 ++--- src/ethereum/forks/prague/state_tracker.py | 28 ++++------- .../forks/prague/vm/instructions/system.py | 7 +-- src/ethereum/forks/prague/vm/interpreter.py | 12 ++--- src/ethereum/forks/shanghai/state_tracker.py | 28 ++++------- .../forks/shanghai/vm/instructions/system.py | 7 +-- src/ethereum/forks/shanghai/vm/interpreter.py | 12 ++--- .../forks/spurious_dragon/state_tracker.py | 28 ++++------- .../spurious_dragon/vm/instructions/system.py | 7 +-- .../forks/spurious_dragon/vm/interpreter.py | 12 ++--- .../forks/tangerine_whistle/state_tracker.py | 28 ++++------- .../vm/instructions/system.py | 7 +-- .../forks/tangerine_whistle/vm/interpreter.py | 12 ++--- .../test_initcollision.py | 50 +++++++++++++++---- 73 files changed, 401 insertions(+), 777 deletions(-) diff --git a/src/ethereum/forks/amsterdam/state_tracker.py b/src/ethereum/forks/amsterdam/state_tracker.py index 2e208ee764f..9312d7c5231 100644 --- a/src/ethereum/forks/amsterdam/state_tracker.py +++ b/src/ethereum/forks/amsterdam/state_tracker.py @@ -280,28 +280,18 @@ def account_exists(tx_state: TransactionState, address: Address) -> bool: return get_account_optional(tx_state, address) is not None -def account_has_code_or_nonce( - tx_state: TransactionState, address: Address -) -> bool: +def account_deployable(tx_state: TransactionState, address: Address) -> bool: """ - Check if an account has non-zero nonce or non-empty code. - - Parameters - ---------- - tx_state : - The transaction state. - address : - Address of the account that needs to be checked. - - Returns - ------- - has_code_or_nonce : ``bool`` - True if the account has non-zero nonce or non-empty code, - False otherwise. - + Check if an account's code can be written to. """ account = get_account(tx_state, address) - return account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH + if account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH: + return False + + if account_has_storage(tx_state, address): + return False + + return True def account_has_storage(tx_state: TransactionState, address: Address) -> bool: diff --git a/src/ethereum/forks/amsterdam/vm/instructions/system.py b/src/ethereum/forks/amsterdam/vm/instructions/system.py index c573ea743fb..39fa98ebf0c 100644 --- a/src/ethereum/forks/amsterdam/vm/instructions/system.py +++ b/src/ethereum/forks/amsterdam/vm/instructions/system.py @@ -21,8 +21,7 @@ from ethereum.utils.numeric import ceil32 from ...state_tracker import ( - account_has_code_or_nonce, - account_has_storage, + account_deployable, get_account, get_code, increment_nonce, @@ -122,9 +121,7 @@ def generic_create( evm.accessed_addresses.add(contract_address) - if account_has_code_or_nonce( - tx_state, contract_address - ) or account_has_storage(tx_state, contract_address): + if not account_deployable(tx_state, contract_address): increment_nonce(tx_state, evm.message.current_target) evm.regular_gas_used += create_message_gas evm.state_gas_left += create_message_state_gas_reservoir diff --git a/src/ethereum/forks/amsterdam/vm/interpreter.py b/src/ethereum/forks/amsterdam/vm/interpreter.py index bb01dad813f..2ef5719ce34 100644 --- a/src/ethereum/forks/amsterdam/vm/interpreter.py +++ b/src/ethereum/forks/amsterdam/vm/interpreter.py @@ -33,8 +33,7 @@ from ..blocks import Log from ..state_tracker import ( - account_has_code_or_nonce, - account_has_storage, + account_deployable, copy_tx_state, destroy_storage, get_account, @@ -126,10 +125,9 @@ def process_message_call(message: Message) -> MessageCallOutput: refund_counter = U256(0) state_refund = Uint(0) if message.target == Bytes0(b""): - is_collision = account_has_code_or_nonce( - tx_state, message.current_target - ) or account_has_storage(tx_state, message.current_target) - if is_collision: + if account_deployable(tx_state, message.current_target): + evm = process_create_message(message) + else: return MessageCallOutput( gas_left=Uint(0), refund_counter=U256(0), @@ -142,8 +140,6 @@ def process_message_call(message: Message) -> MessageCallOutput: state_gas_used=0, state_refund=Uint(0), ) - else: - evm = process_create_message(message) else: if message.tx_env.authorizations != (): state_refund += set_delegation(message) diff --git a/src/ethereum/forks/arrow_glacier/state_tracker.py b/src/ethereum/forks/arrow_glacier/state_tracker.py index 24362ecff98..dd7a3c3bb8c 100644 --- a/src/ethereum/forks/arrow_glacier/state_tracker.py +++ b/src/ethereum/forks/arrow_glacier/state_tracker.py @@ -276,28 +276,18 @@ def account_exists(tx_state: TransactionState, address: Address) -> bool: return get_account_optional(tx_state, address) is not None -def account_has_code_or_nonce( - tx_state: TransactionState, address: Address -) -> bool: +def account_deployable(tx_state: TransactionState, address: Address) -> bool: """ - Check if an account has non-zero nonce or non-empty code. - - Parameters - ---------- - tx_state : - The transaction state. - address : - Address of the account that needs to be checked. - - Returns - ------- - has_code_or_nonce : ``bool`` - True if the account has non-zero nonce or non-empty code, - False otherwise. - + Check if an account's code can be written to. """ account = get_account(tx_state, address) - return account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH + if account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH: + return False + + if account_has_storage(tx_state, address): + return False + + return True def account_has_storage(tx_state: TransactionState, address: Address) -> bool: diff --git a/src/ethereum/forks/arrow_glacier/vm/instructions/system.py b/src/ethereum/forks/arrow_glacier/vm/instructions/system.py index b3870ddb774..716e6fe7f88 100644 --- a/src/ethereum/forks/arrow_glacier/vm/instructions/system.py +++ b/src/ethereum/forks/arrow_glacier/vm/instructions/system.py @@ -21,9 +21,8 @@ from ethereum.utils.numeric import ceil32 from ...state_tracker import ( + account_deployable, account_exists_and_is_empty, - account_has_code_or_nonce, - account_has_storage, get_account, get_code, increment_nonce, @@ -91,9 +90,7 @@ def generic_create( evm.accessed_addresses.add(contract_address) - if account_has_code_or_nonce( - evm.message.tx_env.state, contract_address - ) or account_has_storage(evm.message.tx_env.state, contract_address): + if not account_deployable(evm.message.tx_env.state, contract_address): increment_nonce(evm.message.tx_env.state, evm.message.current_target) push(evm.stack, U256(0)) return diff --git a/src/ethereum/forks/arrow_glacier/vm/interpreter.py b/src/ethereum/forks/arrow_glacier/vm/interpreter.py index 282e2bdfcf4..cc4c75495a5 100644 --- a/src/ethereum/forks/arrow_glacier/vm/interpreter.py +++ b/src/ethereum/forks/arrow_glacier/vm/interpreter.py @@ -32,9 +32,8 @@ from ..blocks import Log from ..state_tracker import ( + account_deployable, account_exists_and_is_empty, - account_has_code_or_nonce, - account_has_storage, copy_tx_state, destroy_storage, increment_nonce, @@ -107,10 +106,9 @@ def process_message_call(message: Message) -> MessageCallOutput: tx_state = message.tx_env.state refund_counter = U256(0) if message.target == Bytes0(b""): - is_collision = account_has_code_or_nonce( - tx_state, message.current_target - ) or account_has_storage(tx_state, message.current_target) - if is_collision: + if account_deployable(tx_state, message.current_target): + evm = process_create_message(message) + else: return MessageCallOutput( gas_left=Uint(0), refund_counter=U256(0), @@ -119,8 +117,6 @@ def process_message_call(message: Message) -> MessageCallOutput: touched_accounts=set(), error=AddressCollision(), ) - else: - evm = process_create_message(message) else: evm = process_message(message) if account_exists_and_is_empty(tx_state, Address(message.target)): diff --git a/src/ethereum/forks/berlin/state_tracker.py b/src/ethereum/forks/berlin/state_tracker.py index 24362ecff98..dd7a3c3bb8c 100644 --- a/src/ethereum/forks/berlin/state_tracker.py +++ b/src/ethereum/forks/berlin/state_tracker.py @@ -276,28 +276,18 @@ def account_exists(tx_state: TransactionState, address: Address) -> bool: return get_account_optional(tx_state, address) is not None -def account_has_code_or_nonce( - tx_state: TransactionState, address: Address -) -> bool: +def account_deployable(tx_state: TransactionState, address: Address) -> bool: """ - Check if an account has non-zero nonce or non-empty code. - - Parameters - ---------- - tx_state : - The transaction state. - address : - Address of the account that needs to be checked. - - Returns - ------- - has_code_or_nonce : ``bool`` - True if the account has non-zero nonce or non-empty code, - False otherwise. - + Check if an account's code can be written to. """ account = get_account(tx_state, address) - return account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH + if account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH: + return False + + if account_has_storage(tx_state, address): + return False + + return True def account_has_storage(tx_state: TransactionState, address: Address) -> bool: diff --git a/src/ethereum/forks/berlin/vm/instructions/system.py b/src/ethereum/forks/berlin/vm/instructions/system.py index b94c7ebfb4b..c564c9d14b1 100644 --- a/src/ethereum/forks/berlin/vm/instructions/system.py +++ b/src/ethereum/forks/berlin/vm/instructions/system.py @@ -21,9 +21,8 @@ from ethereum.utils.numeric import ceil32 from ...state_tracker import ( + account_deployable, account_exists_and_is_empty, - account_has_code_or_nonce, - account_has_storage, get_account, get_code, increment_nonce, @@ -91,9 +90,7 @@ def generic_create( evm.accessed_addresses.add(contract_address) - if account_has_code_or_nonce( - evm.message.tx_env.state, contract_address - ) or account_has_storage(evm.message.tx_env.state, contract_address): + if not account_deployable(evm.message.tx_env.state, contract_address): increment_nonce(evm.message.tx_env.state, evm.message.current_target) push(evm.stack, U256(0)) return diff --git a/src/ethereum/forks/berlin/vm/interpreter.py b/src/ethereum/forks/berlin/vm/interpreter.py index eea97094a78..4e2949acb3b 100644 --- a/src/ethereum/forks/berlin/vm/interpreter.py +++ b/src/ethereum/forks/berlin/vm/interpreter.py @@ -32,9 +32,8 @@ from ..blocks import Log from ..state_tracker import ( + account_deployable, account_exists_and_is_empty, - account_has_code_or_nonce, - account_has_storage, copy_tx_state, destroy_storage, increment_nonce, @@ -106,10 +105,9 @@ def process_message_call(message: Message) -> MessageCallOutput: tx_state = message.tx_env.state refund_counter = U256(0) if message.target == Bytes0(b""): - is_collision = account_has_code_or_nonce( - tx_state, message.current_target - ) or account_has_storage(tx_state, message.current_target) - if is_collision: + if account_deployable(tx_state, message.current_target): + evm = process_create_message(message) + else: return MessageCallOutput( gas_left=Uint(0), refund_counter=U256(0), @@ -118,8 +116,6 @@ def process_message_call(message: Message) -> MessageCallOutput: touched_accounts=set(), error=AddressCollision(), ) - else: - evm = process_create_message(message) else: evm = process_message(message) if account_exists_and_is_empty(tx_state, Address(message.target)): diff --git a/src/ethereum/forks/bpo1/state_tracker.py b/src/ethereum/forks/bpo1/state_tracker.py index 67ceaa86321..8ce889a833b 100644 --- a/src/ethereum/forks/bpo1/state_tracker.py +++ b/src/ethereum/forks/bpo1/state_tracker.py @@ -264,28 +264,18 @@ def account_exists(tx_state: TransactionState, address: Address) -> bool: return get_account_optional(tx_state, address) is not None -def account_has_code_or_nonce( - tx_state: TransactionState, address: Address -) -> bool: +def account_deployable(tx_state: TransactionState, address: Address) -> bool: """ - Check if an account has non-zero nonce or non-empty code. - - Parameters - ---------- - tx_state : - The transaction state. - address : - Address of the account that needs to be checked. - - Returns - ------- - has_code_or_nonce : ``bool`` - True if the account has non-zero nonce or non-empty code, - False otherwise. - + Check if an account's code can be written to. """ account = get_account(tx_state, address) - return account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH + if account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH: + return False + + if account_has_storage(tx_state, address): + return False + + return True def account_has_storage(tx_state: TransactionState, address: Address) -> bool: diff --git a/src/ethereum/forks/bpo1/vm/instructions/system.py b/src/ethereum/forks/bpo1/vm/instructions/system.py index f8f8f29e100..7ff29c92b88 100644 --- a/src/ethereum/forks/bpo1/vm/instructions/system.py +++ b/src/ethereum/forks/bpo1/vm/instructions/system.py @@ -21,8 +21,7 @@ from ethereum.utils.numeric import ceil32 from ...state_tracker import ( - account_has_code_or_nonce, - account_has_storage, + account_deployable, get_account, increment_nonce, is_account_alive, @@ -98,9 +97,7 @@ def generic_create( evm.accessed_addresses.add(contract_address) - if account_has_code_or_nonce( - evm.message.tx_env.state, contract_address - ) or account_has_storage(evm.message.tx_env.state, contract_address): + if not account_deployable(evm.message.tx_env.state, contract_address): increment_nonce(evm.message.tx_env.state, evm.message.current_target) push(evm.stack, U256(0)) return diff --git a/src/ethereum/forks/bpo1/vm/interpreter.py b/src/ethereum/forks/bpo1/vm/interpreter.py index 64ffe56e7b8..88661ff8f5c 100644 --- a/src/ethereum/forks/bpo1/vm/interpreter.py +++ b/src/ethereum/forks/bpo1/vm/interpreter.py @@ -32,8 +32,7 @@ from ..blocks import Log from ..state_tracker import ( - account_has_code_or_nonce, - account_has_storage, + account_deployable, copy_tx_state, destroy_storage, get_account, @@ -109,10 +108,9 @@ def process_message_call(message: Message) -> MessageCallOutput: tx_state = message.tx_env.state refund_counter = U256(0) if message.target == Bytes0(b""): - is_collision = account_has_code_or_nonce( - tx_state, message.current_target - ) or account_has_storage(tx_state, message.current_target) - if is_collision: + if account_deployable(tx_state, message.current_target): + evm = process_create_message(message) + else: return MessageCallOutput( gas_left=Uint(0), refund_counter=U256(0), @@ -121,8 +119,6 @@ def process_message_call(message: Message) -> MessageCallOutput: error=AddressCollision(), return_data=Bytes(b""), ) - else: - evm = process_create_message(message) else: if message.tx_env.authorizations != (): refund_counter += set_delegation(message) diff --git a/src/ethereum/forks/bpo2/state_tracker.py b/src/ethereum/forks/bpo2/state_tracker.py index 67ceaa86321..8ce889a833b 100644 --- a/src/ethereum/forks/bpo2/state_tracker.py +++ b/src/ethereum/forks/bpo2/state_tracker.py @@ -264,28 +264,18 @@ def account_exists(tx_state: TransactionState, address: Address) -> bool: return get_account_optional(tx_state, address) is not None -def account_has_code_or_nonce( - tx_state: TransactionState, address: Address -) -> bool: +def account_deployable(tx_state: TransactionState, address: Address) -> bool: """ - Check if an account has non-zero nonce or non-empty code. - - Parameters - ---------- - tx_state : - The transaction state. - address : - Address of the account that needs to be checked. - - Returns - ------- - has_code_or_nonce : ``bool`` - True if the account has non-zero nonce or non-empty code, - False otherwise. - + Check if an account's code can be written to. """ account = get_account(tx_state, address) - return account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH + if account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH: + return False + + if account_has_storage(tx_state, address): + return False + + return True def account_has_storage(tx_state: TransactionState, address: Address) -> bool: diff --git a/src/ethereum/forks/bpo2/vm/instructions/system.py b/src/ethereum/forks/bpo2/vm/instructions/system.py index 30db9d8309f..ba2e2141562 100644 --- a/src/ethereum/forks/bpo2/vm/instructions/system.py +++ b/src/ethereum/forks/bpo2/vm/instructions/system.py @@ -21,8 +21,7 @@ from ethereum.utils.numeric import ceil32 from ...state_tracker import ( - account_has_code_or_nonce, - account_has_storage, + account_deployable, get_account, increment_nonce, is_account_alive, @@ -98,9 +97,7 @@ def generic_create( evm.accessed_addresses.add(contract_address) - if account_has_code_or_nonce( - evm.message.tx_env.state, contract_address - ) or account_has_storage(evm.message.tx_env.state, contract_address): + if not account_deployable(evm.message.tx_env.state, contract_address): increment_nonce(evm.message.tx_env.state, evm.message.current_target) push(evm.stack, U256(0)) return diff --git a/src/ethereum/forks/bpo2/vm/interpreter.py b/src/ethereum/forks/bpo2/vm/interpreter.py index 5be8a929f02..0e22563be4a 100644 --- a/src/ethereum/forks/bpo2/vm/interpreter.py +++ b/src/ethereum/forks/bpo2/vm/interpreter.py @@ -32,8 +32,7 @@ from ..blocks import Log from ..state_tracker import ( - account_has_code_or_nonce, - account_has_storage, + account_deployable, copy_tx_state, destroy_storage, get_account, @@ -109,10 +108,9 @@ def process_message_call(message: Message) -> MessageCallOutput: tx_state = message.tx_env.state refund_counter = U256(0) if message.target == Bytes0(b""): - is_collision = account_has_code_or_nonce( - tx_state, message.current_target - ) or account_has_storage(tx_state, message.current_target) - if is_collision: + if account_deployable(tx_state, message.current_target): + evm = process_create_message(message) + else: return MessageCallOutput( gas_left=Uint(0), refund_counter=U256(0), @@ -121,8 +119,6 @@ def process_message_call(message: Message) -> MessageCallOutput: error=AddressCollision(), return_data=Bytes(b""), ) - else: - evm = process_create_message(message) else: if message.tx_env.authorizations != (): refund_counter += set_delegation(message) diff --git a/src/ethereum/forks/bpo3/state_tracker.py b/src/ethereum/forks/bpo3/state_tracker.py index 67ceaa86321..8ce889a833b 100644 --- a/src/ethereum/forks/bpo3/state_tracker.py +++ b/src/ethereum/forks/bpo3/state_tracker.py @@ -264,28 +264,18 @@ def account_exists(tx_state: TransactionState, address: Address) -> bool: return get_account_optional(tx_state, address) is not None -def account_has_code_or_nonce( - tx_state: TransactionState, address: Address -) -> bool: +def account_deployable(tx_state: TransactionState, address: Address) -> bool: """ - Check if an account has non-zero nonce or non-empty code. - - Parameters - ---------- - tx_state : - The transaction state. - address : - Address of the account that needs to be checked. - - Returns - ------- - has_code_or_nonce : ``bool`` - True if the account has non-zero nonce or non-empty code, - False otherwise. - + Check if an account's code can be written to. """ account = get_account(tx_state, address) - return account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH + if account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH: + return False + + if account_has_storage(tx_state, address): + return False + + return True def account_has_storage(tx_state: TransactionState, address: Address) -> bool: diff --git a/src/ethereum/forks/bpo3/vm/instructions/system.py b/src/ethereum/forks/bpo3/vm/instructions/system.py index 30db9d8309f..ba2e2141562 100644 --- a/src/ethereum/forks/bpo3/vm/instructions/system.py +++ b/src/ethereum/forks/bpo3/vm/instructions/system.py @@ -21,8 +21,7 @@ from ethereum.utils.numeric import ceil32 from ...state_tracker import ( - account_has_code_or_nonce, - account_has_storage, + account_deployable, get_account, increment_nonce, is_account_alive, @@ -98,9 +97,7 @@ def generic_create( evm.accessed_addresses.add(contract_address) - if account_has_code_or_nonce( - evm.message.tx_env.state, contract_address - ) or account_has_storage(evm.message.tx_env.state, contract_address): + if not account_deployable(evm.message.tx_env.state, contract_address): increment_nonce(evm.message.tx_env.state, evm.message.current_target) push(evm.stack, U256(0)) return diff --git a/src/ethereum/forks/bpo3/vm/interpreter.py b/src/ethereum/forks/bpo3/vm/interpreter.py index b7995f3c6fc..4a596d66370 100644 --- a/src/ethereum/forks/bpo3/vm/interpreter.py +++ b/src/ethereum/forks/bpo3/vm/interpreter.py @@ -32,8 +32,7 @@ from ..blocks import Log from ..state_tracker import ( - account_has_code_or_nonce, - account_has_storage, + account_deployable, copy_tx_state, destroy_storage, get_account, @@ -109,10 +108,9 @@ def process_message_call(message: Message) -> MessageCallOutput: tx_state = message.tx_env.state refund_counter = U256(0) if message.target == Bytes0(b""): - is_collision = account_has_code_or_nonce( - tx_state, message.current_target - ) or account_has_storage(tx_state, message.current_target) - if is_collision: + if account_deployable(tx_state, message.current_target): + evm = process_create_message(message) + else: return MessageCallOutput( gas_left=Uint(0), refund_counter=U256(0), @@ -121,8 +119,6 @@ def process_message_call(message: Message) -> MessageCallOutput: error=AddressCollision(), return_data=Bytes(b""), ) - else: - evm = process_create_message(message) else: if message.tx_env.authorizations != (): refund_counter += set_delegation(message) diff --git a/src/ethereum/forks/bpo4/state_tracker.py b/src/ethereum/forks/bpo4/state_tracker.py index 67ceaa86321..8ce889a833b 100644 --- a/src/ethereum/forks/bpo4/state_tracker.py +++ b/src/ethereum/forks/bpo4/state_tracker.py @@ -264,28 +264,18 @@ def account_exists(tx_state: TransactionState, address: Address) -> bool: return get_account_optional(tx_state, address) is not None -def account_has_code_or_nonce( - tx_state: TransactionState, address: Address -) -> bool: +def account_deployable(tx_state: TransactionState, address: Address) -> bool: """ - Check if an account has non-zero nonce or non-empty code. - - Parameters - ---------- - tx_state : - The transaction state. - address : - Address of the account that needs to be checked. - - Returns - ------- - has_code_or_nonce : ``bool`` - True if the account has non-zero nonce or non-empty code, - False otherwise. - + Check if an account's code can be written to. """ account = get_account(tx_state, address) - return account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH + if account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH: + return False + + if account_has_storage(tx_state, address): + return False + + return True def account_has_storage(tx_state: TransactionState, address: Address) -> bool: diff --git a/src/ethereum/forks/bpo4/vm/instructions/system.py b/src/ethereum/forks/bpo4/vm/instructions/system.py index f8f8f29e100..7ff29c92b88 100644 --- a/src/ethereum/forks/bpo4/vm/instructions/system.py +++ b/src/ethereum/forks/bpo4/vm/instructions/system.py @@ -21,8 +21,7 @@ from ethereum.utils.numeric import ceil32 from ...state_tracker import ( - account_has_code_or_nonce, - account_has_storage, + account_deployable, get_account, increment_nonce, is_account_alive, @@ -98,9 +97,7 @@ def generic_create( evm.accessed_addresses.add(contract_address) - if account_has_code_or_nonce( - evm.message.tx_env.state, contract_address - ) or account_has_storage(evm.message.tx_env.state, contract_address): + if not account_deployable(evm.message.tx_env.state, contract_address): increment_nonce(evm.message.tx_env.state, evm.message.current_target) push(evm.stack, U256(0)) return diff --git a/src/ethereum/forks/bpo4/vm/interpreter.py b/src/ethereum/forks/bpo4/vm/interpreter.py index a778ad9af53..d0758480bb5 100644 --- a/src/ethereum/forks/bpo4/vm/interpreter.py +++ b/src/ethereum/forks/bpo4/vm/interpreter.py @@ -32,8 +32,7 @@ from ..blocks import Log from ..state_tracker import ( - account_has_code_or_nonce, - account_has_storage, + account_deployable, copy_tx_state, destroy_storage, get_account, @@ -109,10 +108,9 @@ def process_message_call(message: Message) -> MessageCallOutput: tx_state = message.tx_env.state refund_counter = U256(0) if message.target == Bytes0(b""): - is_collision = account_has_code_or_nonce( - tx_state, message.current_target - ) or account_has_storage(tx_state, message.current_target) - if is_collision: + if account_deployable(tx_state, message.current_target): + evm = process_create_message(message) + else: return MessageCallOutput( gas_left=Uint(0), refund_counter=U256(0), @@ -121,8 +119,6 @@ def process_message_call(message: Message) -> MessageCallOutput: error=AddressCollision(), return_data=Bytes(b""), ) - else: - evm = process_create_message(message) else: if message.tx_env.authorizations != (): refund_counter += set_delegation(message) diff --git a/src/ethereum/forks/bpo5/state_tracker.py b/src/ethereum/forks/bpo5/state_tracker.py index 67ceaa86321..8ce889a833b 100644 --- a/src/ethereum/forks/bpo5/state_tracker.py +++ b/src/ethereum/forks/bpo5/state_tracker.py @@ -264,28 +264,18 @@ def account_exists(tx_state: TransactionState, address: Address) -> bool: return get_account_optional(tx_state, address) is not None -def account_has_code_or_nonce( - tx_state: TransactionState, address: Address -) -> bool: +def account_deployable(tx_state: TransactionState, address: Address) -> bool: """ - Check if an account has non-zero nonce or non-empty code. - - Parameters - ---------- - tx_state : - The transaction state. - address : - Address of the account that needs to be checked. - - Returns - ------- - has_code_or_nonce : ``bool`` - True if the account has non-zero nonce or non-empty code, - False otherwise. - + Check if an account's code can be written to. """ account = get_account(tx_state, address) - return account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH + if account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH: + return False + + if account_has_storage(tx_state, address): + return False + + return True def account_has_storage(tx_state: TransactionState, address: Address) -> bool: diff --git a/src/ethereum/forks/bpo5/vm/instructions/system.py b/src/ethereum/forks/bpo5/vm/instructions/system.py index f8f8f29e100..7ff29c92b88 100644 --- a/src/ethereum/forks/bpo5/vm/instructions/system.py +++ b/src/ethereum/forks/bpo5/vm/instructions/system.py @@ -21,8 +21,7 @@ from ethereum.utils.numeric import ceil32 from ...state_tracker import ( - account_has_code_or_nonce, - account_has_storage, + account_deployable, get_account, increment_nonce, is_account_alive, @@ -98,9 +97,7 @@ def generic_create( evm.accessed_addresses.add(contract_address) - if account_has_code_or_nonce( - evm.message.tx_env.state, contract_address - ) or account_has_storage(evm.message.tx_env.state, contract_address): + if not account_deployable(evm.message.tx_env.state, contract_address): increment_nonce(evm.message.tx_env.state, evm.message.current_target) push(evm.stack, U256(0)) return diff --git a/src/ethereum/forks/bpo5/vm/interpreter.py b/src/ethereum/forks/bpo5/vm/interpreter.py index b94bab8b33b..25033fe9fa0 100644 --- a/src/ethereum/forks/bpo5/vm/interpreter.py +++ b/src/ethereum/forks/bpo5/vm/interpreter.py @@ -32,8 +32,7 @@ from ..blocks import Log from ..state_tracker import ( - account_has_code_or_nonce, - account_has_storage, + account_deployable, copy_tx_state, destroy_storage, get_account, @@ -109,10 +108,9 @@ def process_message_call(message: Message) -> MessageCallOutput: tx_state = message.tx_env.state refund_counter = U256(0) if message.target == Bytes0(b""): - is_collision = account_has_code_or_nonce( - tx_state, message.current_target - ) or account_has_storage(tx_state, message.current_target) - if is_collision: + if account_deployable(tx_state, message.current_target): + evm = process_create_message(message) + else: return MessageCallOutput( gas_left=Uint(0), refund_counter=U256(0), @@ -121,8 +119,6 @@ def process_message_call(message: Message) -> MessageCallOutput: error=AddressCollision(), return_data=Bytes(b""), ) - else: - evm = process_create_message(message) else: if message.tx_env.authorizations != (): refund_counter += set_delegation(message) diff --git a/src/ethereum/forks/byzantium/state_tracker.py b/src/ethereum/forks/byzantium/state_tracker.py index 24362ecff98..dd7a3c3bb8c 100644 --- a/src/ethereum/forks/byzantium/state_tracker.py +++ b/src/ethereum/forks/byzantium/state_tracker.py @@ -276,28 +276,18 @@ def account_exists(tx_state: TransactionState, address: Address) -> bool: return get_account_optional(tx_state, address) is not None -def account_has_code_or_nonce( - tx_state: TransactionState, address: Address -) -> bool: +def account_deployable(tx_state: TransactionState, address: Address) -> bool: """ - Check if an account has non-zero nonce or non-empty code. - - Parameters - ---------- - tx_state : - The transaction state. - address : - Address of the account that needs to be checked. - - Returns - ------- - has_code_or_nonce : ``bool`` - True if the account has non-zero nonce or non-empty code, - False otherwise. - + Check if an account's code can be written to. """ account = get_account(tx_state, address) - return account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH + if account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH: + return False + + if account_has_storage(tx_state, address): + return False + + return True def account_has_storage(tx_state: TransactionState, address: Address) -> bool: diff --git a/src/ethereum/forks/byzantium/vm/instructions/system.py b/src/ethereum/forks/byzantium/vm/instructions/system.py index 477d466d325..1e18971104b 100644 --- a/src/ethereum/forks/byzantium/vm/instructions/system.py +++ b/src/ethereum/forks/byzantium/vm/instructions/system.py @@ -20,9 +20,8 @@ from ethereum.state import Address from ...state_tracker import ( + account_deployable, account_exists_and_is_empty, - account_has_code_or_nonce, - account_has_storage, get_account, get_code, increment_nonce, @@ -98,9 +97,7 @@ def create(evm: Evm) -> None: ): push(evm.stack, U256(0)) evm.gas_left += create_message_gas - elif account_has_code_or_nonce( - evm.message.tx_env.state, contract_address - ) or account_has_storage(evm.message.tx_env.state, contract_address): + elif not account_deployable(evm.message.tx_env.state, contract_address): increment_nonce(evm.message.tx_env.state, evm.message.current_target) push(evm.stack, U256(0)) else: diff --git a/src/ethereum/forks/byzantium/vm/interpreter.py b/src/ethereum/forks/byzantium/vm/interpreter.py index 5544b8a489e..c571cebbad3 100644 --- a/src/ethereum/forks/byzantium/vm/interpreter.py +++ b/src/ethereum/forks/byzantium/vm/interpreter.py @@ -32,9 +32,8 @@ from ..blocks import Log from ..state_tracker import ( + account_deployable, account_exists_and_is_empty, - account_has_code_or_nonce, - account_has_storage, copy_tx_state, destroy_storage, increment_nonce, @@ -105,10 +104,9 @@ def process_message_call(message: Message) -> MessageCallOutput: tx_state = message.tx_env.state refund_counter = U256(0) if message.target == Bytes0(b""): - is_collision = account_has_code_or_nonce( - tx_state, message.current_target - ) or account_has_storage(tx_state, message.current_target) - if is_collision: + if account_deployable(tx_state, message.current_target): + evm = process_create_message(message) + else: return MessageCallOutput( gas_left=Uint(0), refund_counter=U256(0), @@ -117,8 +115,6 @@ def process_message_call(message: Message) -> MessageCallOutput: touched_accounts=set(), error=AddressCollision(), ) - else: - evm = process_create_message(message) else: evm = process_message(message) if account_exists_and_is_empty(tx_state, Address(message.target)): diff --git a/src/ethereum/forks/cancun/state_tracker.py b/src/ethereum/forks/cancun/state_tracker.py index 67ceaa86321..8ce889a833b 100644 --- a/src/ethereum/forks/cancun/state_tracker.py +++ b/src/ethereum/forks/cancun/state_tracker.py @@ -264,28 +264,18 @@ def account_exists(tx_state: TransactionState, address: Address) -> bool: return get_account_optional(tx_state, address) is not None -def account_has_code_or_nonce( - tx_state: TransactionState, address: Address -) -> bool: +def account_deployable(tx_state: TransactionState, address: Address) -> bool: """ - Check if an account has non-zero nonce or non-empty code. - - Parameters - ---------- - tx_state : - The transaction state. - address : - Address of the account that needs to be checked. - - Returns - ------- - has_code_or_nonce : ``bool`` - True if the account has non-zero nonce or non-empty code, - False otherwise. - + Check if an account's code can be written to. """ account = get_account(tx_state, address) - return account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH + if account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH: + return False + + if account_has_storage(tx_state, address): + return False + + return True def account_has_storage(tx_state: TransactionState, address: Address) -> bool: diff --git a/src/ethereum/forks/cancun/vm/instructions/system.py b/src/ethereum/forks/cancun/vm/instructions/system.py index c2d0c4c7f3e..3d88783f399 100644 --- a/src/ethereum/forks/cancun/vm/instructions/system.py +++ b/src/ethereum/forks/cancun/vm/instructions/system.py @@ -21,8 +21,7 @@ from ethereum.utils.numeric import ceil32 from ...state_tracker import ( - account_has_code_or_nonce, - account_has_storage, + account_deployable, get_account, get_code, increment_nonce, @@ -98,9 +97,7 @@ def generic_create( evm.accessed_addresses.add(contract_address) - if account_has_code_or_nonce( - evm.message.tx_env.state, contract_address - ) or account_has_storage(evm.message.tx_env.state, contract_address): + if not account_deployable(evm.message.tx_env.state, contract_address): increment_nonce(evm.message.tx_env.state, evm.message.current_target) push(evm.stack, U256(0)) return diff --git a/src/ethereum/forks/cancun/vm/interpreter.py b/src/ethereum/forks/cancun/vm/interpreter.py index bb0687a666e..068246056e8 100644 --- a/src/ethereum/forks/cancun/vm/interpreter.py +++ b/src/ethereum/forks/cancun/vm/interpreter.py @@ -32,8 +32,7 @@ from ..blocks import Log from ..state_tracker import ( - account_has_code_or_nonce, - account_has_storage, + account_deployable, copy_tx_state, destroy_storage, increment_nonce, @@ -104,10 +103,9 @@ def process_message_call(message: Message) -> MessageCallOutput: tx_state = message.tx_env.state refund_counter = U256(0) if message.target == Bytes0(b""): - is_collision = account_has_code_or_nonce( - tx_state, message.current_target - ) or account_has_storage(tx_state, message.current_target) - if is_collision: + if account_deployable(tx_state, message.current_target): + evm = process_create_message(message) + else: return MessageCallOutput( gas_left=Uint(0), refund_counter=U256(0), @@ -115,8 +113,6 @@ def process_message_call(message: Message) -> MessageCallOutput: accounts_to_delete=set(), error=AddressCollision(), ) - else: - evm = process_create_message(message) else: evm = process_message(message) diff --git a/src/ethereum/forks/constantinople/state_tracker.py b/src/ethereum/forks/constantinople/state_tracker.py index 24362ecff98..dd7a3c3bb8c 100644 --- a/src/ethereum/forks/constantinople/state_tracker.py +++ b/src/ethereum/forks/constantinople/state_tracker.py @@ -276,28 +276,18 @@ def account_exists(tx_state: TransactionState, address: Address) -> bool: return get_account_optional(tx_state, address) is not None -def account_has_code_or_nonce( - tx_state: TransactionState, address: Address -) -> bool: +def account_deployable(tx_state: TransactionState, address: Address) -> bool: """ - Check if an account has non-zero nonce or non-empty code. - - Parameters - ---------- - tx_state : - The transaction state. - address : - Address of the account that needs to be checked. - - Returns - ------- - has_code_or_nonce : ``bool`` - True if the account has non-zero nonce or non-empty code, - False otherwise. - + Check if an account's code can be written to. """ account = get_account(tx_state, address) - return account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH + if account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH: + return False + + if account_has_storage(tx_state, address): + return False + + return True def account_has_storage(tx_state: TransactionState, address: Address) -> bool: diff --git a/src/ethereum/forks/constantinople/vm/instructions/system.py b/src/ethereum/forks/constantinople/vm/instructions/system.py index 1f4a1741743..07b55b972f6 100644 --- a/src/ethereum/forks/constantinople/vm/instructions/system.py +++ b/src/ethereum/forks/constantinople/vm/instructions/system.py @@ -21,9 +21,8 @@ from ethereum.utils.numeric import ceil32 from ...state_tracker import ( + account_deployable, account_exists_and_is_empty, - account_has_code_or_nonce, - account_has_storage, get_account, get_code, increment_nonce, @@ -89,9 +88,7 @@ def generic_create( push(evm.stack, U256(0)) return - if account_has_code_or_nonce( - evm.message.tx_env.state, contract_address - ) or account_has_storage(evm.message.tx_env.state, contract_address): + if not account_deployable(evm.message.tx_env.state, contract_address): increment_nonce(evm.message.tx_env.state, evm.message.current_target) push(evm.stack, U256(0)) return diff --git a/src/ethereum/forks/constantinople/vm/interpreter.py b/src/ethereum/forks/constantinople/vm/interpreter.py index 4796f0ff997..974307736d6 100644 --- a/src/ethereum/forks/constantinople/vm/interpreter.py +++ b/src/ethereum/forks/constantinople/vm/interpreter.py @@ -32,9 +32,8 @@ from ..blocks import Log from ..state_tracker import ( + account_deployable, account_exists_and_is_empty, - account_has_code_or_nonce, - account_has_storage, copy_tx_state, destroy_storage, increment_nonce, @@ -105,10 +104,9 @@ def process_message_call(message: Message) -> MessageCallOutput: tx_state = message.tx_env.state refund_counter = U256(0) if message.target == Bytes0(b""): - is_collision = account_has_code_or_nonce( - tx_state, message.current_target - ) or account_has_storage(tx_state, message.current_target) - if is_collision: + if account_deployable(tx_state, message.current_target): + evm = process_create_message(message) + else: return MessageCallOutput( gas_left=Uint(0), refund_counter=U256(0), @@ -117,8 +115,6 @@ def process_message_call(message: Message) -> MessageCallOutput: touched_accounts=set(), error=AddressCollision(), ) - else: - evm = process_create_message(message) else: evm = process_message(message) if account_exists_and_is_empty(tx_state, Address(message.target)): diff --git a/src/ethereum/forks/dao_fork/state_tracker.py b/src/ethereum/forks/dao_fork/state_tracker.py index 24362ecff98..dd7a3c3bb8c 100644 --- a/src/ethereum/forks/dao_fork/state_tracker.py +++ b/src/ethereum/forks/dao_fork/state_tracker.py @@ -276,28 +276,18 @@ def account_exists(tx_state: TransactionState, address: Address) -> bool: return get_account_optional(tx_state, address) is not None -def account_has_code_or_nonce( - tx_state: TransactionState, address: Address -) -> bool: +def account_deployable(tx_state: TransactionState, address: Address) -> bool: """ - Check if an account has non-zero nonce or non-empty code. - - Parameters - ---------- - tx_state : - The transaction state. - address : - Address of the account that needs to be checked. - - Returns - ------- - has_code_or_nonce : ``bool`` - True if the account has non-zero nonce or non-empty code, - False otherwise. - + Check if an account's code can be written to. """ account = get_account(tx_state, address) - return account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH + if account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH: + return False + + if account_has_storage(tx_state, address): + return False + + return True def account_has_storage(tx_state: TransactionState, address: Address) -> bool: diff --git a/src/ethereum/forks/dao_fork/vm/instructions/system.py b/src/ethereum/forks/dao_fork/vm/instructions/system.py index b13f23b1be8..95f33ef3066 100644 --- a/src/ethereum/forks/dao_fork/vm/instructions/system.py +++ b/src/ethereum/forks/dao_fork/vm/instructions/system.py @@ -20,8 +20,7 @@ from ethereum.state import Address from ...state_tracker import ( - account_has_code_or_nonce, - account_has_storage, + account_deployable, get_account, get_code, increment_nonce, @@ -92,9 +91,7 @@ def create(evm: Evm) -> None: ): push(evm.stack, U256(0)) evm.gas_left += create_message_gas - elif account_has_code_or_nonce( - evm.message.tx_env.state, contract_address - ) or account_has_storage(evm.message.tx_env.state, contract_address): + elif not account_deployable(evm.message.tx_env.state, contract_address): increment_nonce(evm.message.tx_env.state, evm.message.current_target) push(evm.stack, U256(0)) else: diff --git a/src/ethereum/forks/dao_fork/vm/interpreter.py b/src/ethereum/forks/dao_fork/vm/interpreter.py index bdd65600275..9c771ed7b16 100644 --- a/src/ethereum/forks/dao_fork/vm/interpreter.py +++ b/src/ethereum/forks/dao_fork/vm/interpreter.py @@ -32,8 +32,7 @@ from ..blocks import Log from ..state_tracker import ( - account_has_code_or_nonce, - account_has_storage, + account_deployable, copy_tx_state, destroy_storage, move_ether, @@ -98,10 +97,9 @@ def process_message_call(message: Message) -> MessageCallOutput: tx_state = message.tx_env.state refund_counter = U256(0) if message.target == Bytes0(b""): - is_collision = account_has_code_or_nonce( - tx_state, message.current_target - ) or account_has_storage(tx_state, message.current_target) - if is_collision: + if account_deployable(tx_state, message.current_target): + evm = process_create_message(message) + else: return MessageCallOutput( gas_left=Uint(0), refund_counter=U256(0), @@ -109,8 +107,6 @@ def process_message_call(message: Message) -> MessageCallOutput: accounts_to_delete=set(), error=AddressCollision(), ) - else: - evm = process_create_message(message) else: evm = process_message(message) diff --git a/src/ethereum/forks/frontier/state_tracker.py b/src/ethereum/forks/frontier/state_tracker.py index 24362ecff98..dd7a3c3bb8c 100644 --- a/src/ethereum/forks/frontier/state_tracker.py +++ b/src/ethereum/forks/frontier/state_tracker.py @@ -276,28 +276,18 @@ def account_exists(tx_state: TransactionState, address: Address) -> bool: return get_account_optional(tx_state, address) is not None -def account_has_code_or_nonce( - tx_state: TransactionState, address: Address -) -> bool: +def account_deployable(tx_state: TransactionState, address: Address) -> bool: """ - Check if an account has non-zero nonce or non-empty code. - - Parameters - ---------- - tx_state : - The transaction state. - address : - Address of the account that needs to be checked. - - Returns - ------- - has_code_or_nonce : ``bool`` - True if the account has non-zero nonce or non-empty code, - False otherwise. - + Check if an account's code can be written to. """ account = get_account(tx_state, address) - return account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH + if account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH: + return False + + if account_has_storage(tx_state, address): + return False + + return True def account_has_storage(tx_state: TransactionState, address: Address) -> bool: diff --git a/src/ethereum/forks/frontier/vm/instructions/system.py b/src/ethereum/forks/frontier/vm/instructions/system.py index 47dd034fc16..80c99b325c9 100644 --- a/src/ethereum/forks/frontier/vm/instructions/system.py +++ b/src/ethereum/forks/frontier/vm/instructions/system.py @@ -20,8 +20,7 @@ from ethereum.state import Address from ...state_tracker import ( - account_has_code_or_nonce, - account_has_storage, + account_deployable, get_account, get_code, increment_nonce, @@ -92,9 +91,7 @@ def create(evm: Evm) -> None: ): push(evm.stack, U256(0)) evm.gas_left += create_message_gas - elif account_has_code_or_nonce( - evm.message.tx_env.state, contract_address - ) or account_has_storage(evm.message.tx_env.state, contract_address): + elif not account_deployable(evm.message.tx_env.state, contract_address): increment_nonce(evm.message.tx_env.state, evm.message.current_target) push(evm.stack, U256(0)) else: diff --git a/src/ethereum/forks/frontier/vm/interpreter.py b/src/ethereum/forks/frontier/vm/interpreter.py index 00a7c52c0db..447ee999b0f 100644 --- a/src/ethereum/forks/frontier/vm/interpreter.py +++ b/src/ethereum/forks/frontier/vm/interpreter.py @@ -32,8 +32,7 @@ from ..blocks import Log from ..state_tracker import ( - account_has_code_or_nonce, - account_has_storage, + account_deployable, copy_tx_state, destroy_storage, move_ether, @@ -98,10 +97,9 @@ def process_message_call(message: Message) -> MessageCallOutput: tx_state = message.tx_env.state refund_counter = U256(0) if message.target == Bytes0(b""): - is_collision = account_has_code_or_nonce( - tx_state, message.current_target - ) or account_has_storage(tx_state, message.current_target) - if is_collision: + if account_deployable(tx_state, message.current_target): + evm = process_create_message(message) + else: return MessageCallOutput( gas_left=Uint(0), refund_counter=U256(0), @@ -109,8 +107,6 @@ def process_message_call(message: Message) -> MessageCallOutput: accounts_to_delete=set(), error=AddressCollision(), ) - else: - evm = process_create_message(message) else: evm = process_message(message) diff --git a/src/ethereum/forks/gray_glacier/state_tracker.py b/src/ethereum/forks/gray_glacier/state_tracker.py index 24362ecff98..dd7a3c3bb8c 100644 --- a/src/ethereum/forks/gray_glacier/state_tracker.py +++ b/src/ethereum/forks/gray_glacier/state_tracker.py @@ -276,28 +276,18 @@ def account_exists(tx_state: TransactionState, address: Address) -> bool: return get_account_optional(tx_state, address) is not None -def account_has_code_or_nonce( - tx_state: TransactionState, address: Address -) -> bool: +def account_deployable(tx_state: TransactionState, address: Address) -> bool: """ - Check if an account has non-zero nonce or non-empty code. - - Parameters - ---------- - tx_state : - The transaction state. - address : - Address of the account that needs to be checked. - - Returns - ------- - has_code_or_nonce : ``bool`` - True if the account has non-zero nonce or non-empty code, - False otherwise. - + Check if an account's code can be written to. """ account = get_account(tx_state, address) - return account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH + if account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH: + return False + + if account_has_storage(tx_state, address): + return False + + return True def account_has_storage(tx_state: TransactionState, address: Address) -> bool: diff --git a/src/ethereum/forks/gray_glacier/vm/instructions/system.py b/src/ethereum/forks/gray_glacier/vm/instructions/system.py index 05d4957faa8..4fc230e6f0c 100644 --- a/src/ethereum/forks/gray_glacier/vm/instructions/system.py +++ b/src/ethereum/forks/gray_glacier/vm/instructions/system.py @@ -21,9 +21,8 @@ from ethereum.utils.numeric import ceil32 from ...state_tracker import ( + account_deployable, account_exists_and_is_empty, - account_has_code_or_nonce, - account_has_storage, get_account, get_code, increment_nonce, @@ -91,9 +90,7 @@ def generic_create( evm.accessed_addresses.add(contract_address) - if account_has_code_or_nonce( - evm.message.tx_env.state, contract_address - ) or account_has_storage(evm.message.tx_env.state, contract_address): + if not account_deployable(evm.message.tx_env.state, contract_address): increment_nonce(evm.message.tx_env.state, evm.message.current_target) push(evm.stack, U256(0)) return diff --git a/src/ethereum/forks/gray_glacier/vm/interpreter.py b/src/ethereum/forks/gray_glacier/vm/interpreter.py index cb5b6a39d2c..cad00071b6b 100644 --- a/src/ethereum/forks/gray_glacier/vm/interpreter.py +++ b/src/ethereum/forks/gray_glacier/vm/interpreter.py @@ -32,9 +32,8 @@ from ..blocks import Log from ..state_tracker import ( + account_deployable, account_exists_and_is_empty, - account_has_code_or_nonce, - account_has_storage, copy_tx_state, destroy_storage, increment_nonce, @@ -107,10 +106,9 @@ def process_message_call(message: Message) -> MessageCallOutput: tx_state = message.tx_env.state refund_counter = U256(0) if message.target == Bytes0(b""): - is_collision = account_has_code_or_nonce( - tx_state, message.current_target - ) or account_has_storage(tx_state, message.current_target) - if is_collision: + if account_deployable(tx_state, message.current_target): + evm = process_create_message(message) + else: return MessageCallOutput( gas_left=Uint(0), refund_counter=U256(0), @@ -119,8 +117,6 @@ def process_message_call(message: Message) -> MessageCallOutput: touched_accounts=set(), error=AddressCollision(), ) - else: - evm = process_create_message(message) else: evm = process_message(message) if account_exists_and_is_empty(tx_state, Address(message.target)): diff --git a/src/ethereum/forks/homestead/state_tracker.py b/src/ethereum/forks/homestead/state_tracker.py index 24362ecff98..dd7a3c3bb8c 100644 --- a/src/ethereum/forks/homestead/state_tracker.py +++ b/src/ethereum/forks/homestead/state_tracker.py @@ -276,28 +276,18 @@ def account_exists(tx_state: TransactionState, address: Address) -> bool: return get_account_optional(tx_state, address) is not None -def account_has_code_or_nonce( - tx_state: TransactionState, address: Address -) -> bool: +def account_deployable(tx_state: TransactionState, address: Address) -> bool: """ - Check if an account has non-zero nonce or non-empty code. - - Parameters - ---------- - tx_state : - The transaction state. - address : - Address of the account that needs to be checked. - - Returns - ------- - has_code_or_nonce : ``bool`` - True if the account has non-zero nonce or non-empty code, - False otherwise. - + Check if an account's code can be written to. """ account = get_account(tx_state, address) - return account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH + if account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH: + return False + + if account_has_storage(tx_state, address): + return False + + return True def account_has_storage(tx_state: TransactionState, address: Address) -> bool: diff --git a/src/ethereum/forks/homestead/vm/instructions/system.py b/src/ethereum/forks/homestead/vm/instructions/system.py index b13f23b1be8..95f33ef3066 100644 --- a/src/ethereum/forks/homestead/vm/instructions/system.py +++ b/src/ethereum/forks/homestead/vm/instructions/system.py @@ -20,8 +20,7 @@ from ethereum.state import Address from ...state_tracker import ( - account_has_code_or_nonce, - account_has_storage, + account_deployable, get_account, get_code, increment_nonce, @@ -92,9 +91,7 @@ def create(evm: Evm) -> None: ): push(evm.stack, U256(0)) evm.gas_left += create_message_gas - elif account_has_code_or_nonce( - evm.message.tx_env.state, contract_address - ) or account_has_storage(evm.message.tx_env.state, contract_address): + elif not account_deployable(evm.message.tx_env.state, contract_address): increment_nonce(evm.message.tx_env.state, evm.message.current_target) push(evm.stack, U256(0)) else: diff --git a/src/ethereum/forks/homestead/vm/interpreter.py b/src/ethereum/forks/homestead/vm/interpreter.py index 21445abf5c8..9b3bf9cd0ee 100644 --- a/src/ethereum/forks/homestead/vm/interpreter.py +++ b/src/ethereum/forks/homestead/vm/interpreter.py @@ -32,8 +32,7 @@ from ..blocks import Log from ..state_tracker import ( - account_has_code_or_nonce, - account_has_storage, + account_deployable, copy_tx_state, destroy_storage, move_ether, @@ -98,10 +97,9 @@ def process_message_call(message: Message) -> MessageCallOutput: tx_state = message.tx_env.state refund_counter = U256(0) if message.target == Bytes0(b""): - is_collision = account_has_code_or_nonce( - tx_state, message.current_target - ) or account_has_storage(tx_state, message.current_target) - if is_collision: + if account_deployable(tx_state, message.current_target): + evm = process_create_message(message) + else: return MessageCallOutput( gas_left=Uint(0), refund_counter=U256(0), @@ -109,8 +107,6 @@ def process_message_call(message: Message) -> MessageCallOutput: accounts_to_delete=set(), error=AddressCollision(), ) - else: - evm = process_create_message(message) else: evm = process_message(message) diff --git a/src/ethereum/forks/istanbul/state_tracker.py b/src/ethereum/forks/istanbul/state_tracker.py index 24362ecff98..dd7a3c3bb8c 100644 --- a/src/ethereum/forks/istanbul/state_tracker.py +++ b/src/ethereum/forks/istanbul/state_tracker.py @@ -276,28 +276,18 @@ def account_exists(tx_state: TransactionState, address: Address) -> bool: return get_account_optional(tx_state, address) is not None -def account_has_code_or_nonce( - tx_state: TransactionState, address: Address -) -> bool: +def account_deployable(tx_state: TransactionState, address: Address) -> bool: """ - Check if an account has non-zero nonce or non-empty code. - - Parameters - ---------- - tx_state : - The transaction state. - address : - Address of the account that needs to be checked. - - Returns - ------- - has_code_or_nonce : ``bool`` - True if the account has non-zero nonce or non-empty code, - False otherwise. - + Check if an account's code can be written to. """ account = get_account(tx_state, address) - return account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH + if account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH: + return False + + if account_has_storage(tx_state, address): + return False + + return True def account_has_storage(tx_state: TransactionState, address: Address) -> bool: diff --git a/src/ethereum/forks/istanbul/vm/instructions/system.py b/src/ethereum/forks/istanbul/vm/instructions/system.py index 3ecdf779332..1817f86c57b 100644 --- a/src/ethereum/forks/istanbul/vm/instructions/system.py +++ b/src/ethereum/forks/istanbul/vm/instructions/system.py @@ -21,9 +21,8 @@ from ethereum.utils.numeric import ceil32 from ...state_tracker import ( + account_deployable, account_exists_and_is_empty, - account_has_code_or_nonce, - account_has_storage, get_account, get_code, increment_nonce, @@ -89,9 +88,7 @@ def generic_create( push(evm.stack, U256(0)) return - if account_has_code_or_nonce( - evm.message.tx_env.state, contract_address - ) or account_has_storage(evm.message.tx_env.state, contract_address): + if not account_deployable(evm.message.tx_env.state, contract_address): increment_nonce(evm.message.tx_env.state, evm.message.current_target) push(evm.stack, U256(0)) return diff --git a/src/ethereum/forks/istanbul/vm/interpreter.py b/src/ethereum/forks/istanbul/vm/interpreter.py index ed7b86a45d9..6bbfc9a6136 100644 --- a/src/ethereum/forks/istanbul/vm/interpreter.py +++ b/src/ethereum/forks/istanbul/vm/interpreter.py @@ -32,9 +32,8 @@ from ..blocks import Log from ..state_tracker import ( + account_deployable, account_exists_and_is_empty, - account_has_code_or_nonce, - account_has_storage, copy_tx_state, destroy_storage, increment_nonce, @@ -106,10 +105,9 @@ def process_message_call(message: Message) -> MessageCallOutput: tx_state = message.tx_env.state refund_counter = U256(0) if message.target == Bytes0(b""): - is_collision = account_has_code_or_nonce( - tx_state, message.current_target - ) or account_has_storage(tx_state, message.current_target) - if is_collision: + if account_deployable(tx_state, message.current_target): + evm = process_create_message(message) + else: return MessageCallOutput( gas_left=Uint(0), refund_counter=U256(0), @@ -118,8 +116,6 @@ def process_message_call(message: Message) -> MessageCallOutput: touched_accounts=set(), error=AddressCollision(), ) - else: - evm = process_create_message(message) else: evm = process_message(message) if account_exists_and_is_empty(tx_state, Address(message.target)): diff --git a/src/ethereum/forks/london/state_tracker.py b/src/ethereum/forks/london/state_tracker.py index 24362ecff98..dd7a3c3bb8c 100644 --- a/src/ethereum/forks/london/state_tracker.py +++ b/src/ethereum/forks/london/state_tracker.py @@ -276,28 +276,18 @@ def account_exists(tx_state: TransactionState, address: Address) -> bool: return get_account_optional(tx_state, address) is not None -def account_has_code_or_nonce( - tx_state: TransactionState, address: Address -) -> bool: +def account_deployable(tx_state: TransactionState, address: Address) -> bool: """ - Check if an account has non-zero nonce or non-empty code. - - Parameters - ---------- - tx_state : - The transaction state. - address : - Address of the account that needs to be checked. - - Returns - ------- - has_code_or_nonce : ``bool`` - True if the account has non-zero nonce or non-empty code, - False otherwise. - + Check if an account's code can be written to. """ account = get_account(tx_state, address) - return account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH + if account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH: + return False + + if account_has_storage(tx_state, address): + return False + + return True def account_has_storage(tx_state: TransactionState, address: Address) -> bool: diff --git a/src/ethereum/forks/london/vm/instructions/system.py b/src/ethereum/forks/london/vm/instructions/system.py index 05d4957faa8..4fc230e6f0c 100644 --- a/src/ethereum/forks/london/vm/instructions/system.py +++ b/src/ethereum/forks/london/vm/instructions/system.py @@ -21,9 +21,8 @@ from ethereum.utils.numeric import ceil32 from ...state_tracker import ( + account_deployable, account_exists_and_is_empty, - account_has_code_or_nonce, - account_has_storage, get_account, get_code, increment_nonce, @@ -91,9 +90,7 @@ def generic_create( evm.accessed_addresses.add(contract_address) - if account_has_code_or_nonce( - evm.message.tx_env.state, contract_address - ) or account_has_storage(evm.message.tx_env.state, contract_address): + if not account_deployable(evm.message.tx_env.state, contract_address): increment_nonce(evm.message.tx_env.state, evm.message.current_target) push(evm.stack, U256(0)) return diff --git a/src/ethereum/forks/london/vm/interpreter.py b/src/ethereum/forks/london/vm/interpreter.py index 1b42b4da68a..def18b44ce3 100644 --- a/src/ethereum/forks/london/vm/interpreter.py +++ b/src/ethereum/forks/london/vm/interpreter.py @@ -32,9 +32,8 @@ from ..blocks import Log from ..state_tracker import ( + account_deployable, account_exists_and_is_empty, - account_has_code_or_nonce, - account_has_storage, copy_tx_state, destroy_storage, increment_nonce, @@ -107,10 +106,9 @@ def process_message_call(message: Message) -> MessageCallOutput: tx_state = message.tx_env.state refund_counter = U256(0) if message.target == Bytes0(b""): - is_collision = account_has_code_or_nonce( - tx_state, message.current_target - ) or account_has_storage(tx_state, message.current_target) - if is_collision: + if account_deployable(tx_state, message.current_target): + evm = process_create_message(message) + else: return MessageCallOutput( gas_left=Uint(0), refund_counter=U256(0), @@ -119,8 +117,6 @@ def process_message_call(message: Message) -> MessageCallOutput: touched_accounts=set(), error=AddressCollision(), ) - else: - evm = process_create_message(message) else: evm = process_message(message) if account_exists_and_is_empty(tx_state, Address(message.target)): diff --git a/src/ethereum/forks/muir_glacier/state_tracker.py b/src/ethereum/forks/muir_glacier/state_tracker.py index 24362ecff98..dd7a3c3bb8c 100644 --- a/src/ethereum/forks/muir_glacier/state_tracker.py +++ b/src/ethereum/forks/muir_glacier/state_tracker.py @@ -276,28 +276,18 @@ def account_exists(tx_state: TransactionState, address: Address) -> bool: return get_account_optional(tx_state, address) is not None -def account_has_code_or_nonce( - tx_state: TransactionState, address: Address -) -> bool: +def account_deployable(tx_state: TransactionState, address: Address) -> bool: """ - Check if an account has non-zero nonce or non-empty code. - - Parameters - ---------- - tx_state : - The transaction state. - address : - Address of the account that needs to be checked. - - Returns - ------- - has_code_or_nonce : ``bool`` - True if the account has non-zero nonce or non-empty code, - False otherwise. - + Check if an account's code can be written to. """ account = get_account(tx_state, address) - return account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH + if account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH: + return False + + if account_has_storage(tx_state, address): + return False + + return True def account_has_storage(tx_state: TransactionState, address: Address) -> bool: diff --git a/src/ethereum/forks/muir_glacier/vm/instructions/system.py b/src/ethereum/forks/muir_glacier/vm/instructions/system.py index 3ecdf779332..1817f86c57b 100644 --- a/src/ethereum/forks/muir_glacier/vm/instructions/system.py +++ b/src/ethereum/forks/muir_glacier/vm/instructions/system.py @@ -21,9 +21,8 @@ from ethereum.utils.numeric import ceil32 from ...state_tracker import ( + account_deployable, account_exists_and_is_empty, - account_has_code_or_nonce, - account_has_storage, get_account, get_code, increment_nonce, @@ -89,9 +88,7 @@ def generic_create( push(evm.stack, U256(0)) return - if account_has_code_or_nonce( - evm.message.tx_env.state, contract_address - ) or account_has_storage(evm.message.tx_env.state, contract_address): + if not account_deployable(evm.message.tx_env.state, contract_address): increment_nonce(evm.message.tx_env.state, evm.message.current_target) push(evm.stack, U256(0)) return diff --git a/src/ethereum/forks/muir_glacier/vm/interpreter.py b/src/ethereum/forks/muir_glacier/vm/interpreter.py index 12aef672173..7534e790759 100644 --- a/src/ethereum/forks/muir_glacier/vm/interpreter.py +++ b/src/ethereum/forks/muir_glacier/vm/interpreter.py @@ -32,9 +32,8 @@ from ..blocks import Log from ..state_tracker import ( + account_deployable, account_exists_and_is_empty, - account_has_code_or_nonce, - account_has_storage, copy_tx_state, destroy_storage, increment_nonce, @@ -106,10 +105,9 @@ def process_message_call(message: Message) -> MessageCallOutput: tx_state = message.tx_env.state refund_counter = U256(0) if message.target == Bytes0(b""): - is_collision = account_has_code_or_nonce( - tx_state, message.current_target - ) or account_has_storage(tx_state, message.current_target) - if is_collision: + if account_deployable(tx_state, message.current_target): + evm = process_create_message(message) + else: return MessageCallOutput( gas_left=Uint(0), refund_counter=U256(0), @@ -118,8 +116,6 @@ def process_message_call(message: Message) -> MessageCallOutput: touched_accounts=set(), error=AddressCollision(), ) - else: - evm = process_create_message(message) else: evm = process_message(message) if account_exists_and_is_empty(tx_state, Address(message.target)): diff --git a/src/ethereum/forks/osaka/state_tracker.py b/src/ethereum/forks/osaka/state_tracker.py index 67ceaa86321..8ce889a833b 100644 --- a/src/ethereum/forks/osaka/state_tracker.py +++ b/src/ethereum/forks/osaka/state_tracker.py @@ -264,28 +264,18 @@ def account_exists(tx_state: TransactionState, address: Address) -> bool: return get_account_optional(tx_state, address) is not None -def account_has_code_or_nonce( - tx_state: TransactionState, address: Address -) -> bool: +def account_deployable(tx_state: TransactionState, address: Address) -> bool: """ - Check if an account has non-zero nonce or non-empty code. - - Parameters - ---------- - tx_state : - The transaction state. - address : - Address of the account that needs to be checked. - - Returns - ------- - has_code_or_nonce : ``bool`` - True if the account has non-zero nonce or non-empty code, - False otherwise. - + Check if an account's code can be written to. """ account = get_account(tx_state, address) - return account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH + if account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH: + return False + + if account_has_storage(tx_state, address): + return False + + return True def account_has_storage(tx_state: TransactionState, address: Address) -> bool: diff --git a/src/ethereum/forks/osaka/vm/instructions/system.py b/src/ethereum/forks/osaka/vm/instructions/system.py index 5774be6002c..e8b1b2de1f6 100644 --- a/src/ethereum/forks/osaka/vm/instructions/system.py +++ b/src/ethereum/forks/osaka/vm/instructions/system.py @@ -21,8 +21,7 @@ from ethereum.utils.numeric import ceil32 from ...state_tracker import ( - account_has_code_or_nonce, - account_has_storage, + account_deployable, get_account, increment_nonce, is_account_alive, @@ -98,9 +97,7 @@ def generic_create( evm.accessed_addresses.add(contract_address) - if account_has_code_or_nonce( - evm.message.tx_env.state, contract_address - ) or account_has_storage(evm.message.tx_env.state, contract_address): + if not account_deployable(evm.message.tx_env.state, contract_address): increment_nonce(evm.message.tx_env.state, evm.message.current_target) push(evm.stack, U256(0)) return diff --git a/src/ethereum/forks/osaka/vm/interpreter.py b/src/ethereum/forks/osaka/vm/interpreter.py index a9edd1c28d2..f2f731b2dd9 100644 --- a/src/ethereum/forks/osaka/vm/interpreter.py +++ b/src/ethereum/forks/osaka/vm/interpreter.py @@ -32,8 +32,7 @@ from ..blocks import Log from ..state_tracker import ( - account_has_code_or_nonce, - account_has_storage, + account_deployable, copy_tx_state, destroy_storage, get_account, @@ -109,10 +108,9 @@ def process_message_call(message: Message) -> MessageCallOutput: tx_state = message.tx_env.state refund_counter = U256(0) if message.target == Bytes0(b""): - is_collision = account_has_code_or_nonce( - tx_state, message.current_target - ) or account_has_storage(tx_state, message.current_target) - if is_collision: + if account_deployable(tx_state, message.current_target): + evm = process_create_message(message) + else: return MessageCallOutput( gas_left=Uint(0), refund_counter=U256(0), @@ -121,8 +119,6 @@ def process_message_call(message: Message) -> MessageCallOutput: error=AddressCollision(), return_data=Bytes(b""), ) - else: - evm = process_create_message(message) else: if message.tx_env.authorizations != (): refund_counter += set_delegation(message) diff --git a/src/ethereum/forks/paris/state_tracker.py b/src/ethereum/forks/paris/state_tracker.py index a62bed0c09f..964acb0682a 100644 --- a/src/ethereum/forks/paris/state_tracker.py +++ b/src/ethereum/forks/paris/state_tracker.py @@ -276,28 +276,18 @@ def account_exists(tx_state: TransactionState, address: Address) -> bool: return get_account_optional(tx_state, address) is not None -def account_has_code_or_nonce( - tx_state: TransactionState, address: Address -) -> bool: +def account_deployable(tx_state: TransactionState, address: Address) -> bool: """ - Check if an account has non-zero nonce or non-empty code. - - Parameters - ---------- - tx_state : - The transaction state. - address : - Address of the account that needs to be checked. - - Returns - ------- - has_code_or_nonce : ``bool`` - True if the account has non-zero nonce or non-empty code, - False otherwise. - + Check if an account's code can be written to. """ account = get_account(tx_state, address) - return account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH + if account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH: + return False + + if account_has_storage(tx_state, address): + return False + + return True def account_has_storage(tx_state: TransactionState, address: Address) -> bool: diff --git a/src/ethereum/forks/paris/vm/instructions/system.py b/src/ethereum/forks/paris/vm/instructions/system.py index 3bff288ecf3..8c472300023 100644 --- a/src/ethereum/forks/paris/vm/instructions/system.py +++ b/src/ethereum/forks/paris/vm/instructions/system.py @@ -21,8 +21,7 @@ from ethereum.utils.numeric import ceil32 from ...state_tracker import ( - account_has_code_or_nonce, - account_has_storage, + account_deployable, get_account, get_code, increment_nonce, @@ -90,9 +89,7 @@ def generic_create( evm.accessed_addresses.add(contract_address) - if account_has_code_or_nonce( - evm.message.tx_env.state, contract_address - ) or account_has_storage(evm.message.tx_env.state, contract_address): + if not account_deployable(evm.message.tx_env.state, contract_address): increment_nonce(evm.message.tx_env.state, evm.message.current_target) push(evm.stack, U256(0)) return diff --git a/src/ethereum/forks/paris/vm/interpreter.py b/src/ethereum/forks/paris/vm/interpreter.py index f7b51029535..456b43ff142 100644 --- a/src/ethereum/forks/paris/vm/interpreter.py +++ b/src/ethereum/forks/paris/vm/interpreter.py @@ -32,8 +32,7 @@ from ..blocks import Log from ..state_tracker import ( - account_has_code_or_nonce, - account_has_storage, + account_deployable, copy_tx_state, destroy_storage, increment_nonce, @@ -103,10 +102,9 @@ def process_message_call(message: Message) -> MessageCallOutput: tx_state = message.tx_env.state refund_counter = U256(0) if message.target == Bytes0(b""): - is_collision = account_has_code_or_nonce( - tx_state, message.current_target - ) or account_has_storage(tx_state, message.current_target) - if is_collision: + if account_deployable(tx_state, message.current_target): + evm = process_create_message(message) + else: return MessageCallOutput( gas_left=Uint(0), refund_counter=U256(0), @@ -114,8 +112,6 @@ def process_message_call(message: Message) -> MessageCallOutput: accounts_to_delete=set(), error=AddressCollision(), ) - else: - evm = process_create_message(message) else: evm = process_message(message) diff --git a/src/ethereum/forks/prague/state_tracker.py b/src/ethereum/forks/prague/state_tracker.py index 67ceaa86321..8ce889a833b 100644 --- a/src/ethereum/forks/prague/state_tracker.py +++ b/src/ethereum/forks/prague/state_tracker.py @@ -264,28 +264,18 @@ def account_exists(tx_state: TransactionState, address: Address) -> bool: return get_account_optional(tx_state, address) is not None -def account_has_code_or_nonce( - tx_state: TransactionState, address: Address -) -> bool: +def account_deployable(tx_state: TransactionState, address: Address) -> bool: """ - Check if an account has non-zero nonce or non-empty code. - - Parameters - ---------- - tx_state : - The transaction state. - address : - Address of the account that needs to be checked. - - Returns - ------- - has_code_or_nonce : ``bool`` - True if the account has non-zero nonce or non-empty code, - False otherwise. - + Check if an account's code can be written to. """ account = get_account(tx_state, address) - return account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH + if account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH: + return False + + if account_has_storage(tx_state, address): + return False + + return True def account_has_storage(tx_state: TransactionState, address: Address) -> bool: diff --git a/src/ethereum/forks/prague/vm/instructions/system.py b/src/ethereum/forks/prague/vm/instructions/system.py index 30db9d8309f..ba2e2141562 100644 --- a/src/ethereum/forks/prague/vm/instructions/system.py +++ b/src/ethereum/forks/prague/vm/instructions/system.py @@ -21,8 +21,7 @@ from ethereum.utils.numeric import ceil32 from ...state_tracker import ( - account_has_code_or_nonce, - account_has_storage, + account_deployable, get_account, increment_nonce, is_account_alive, @@ -98,9 +97,7 @@ def generic_create( evm.accessed_addresses.add(contract_address) - if account_has_code_or_nonce( - evm.message.tx_env.state, contract_address - ) or account_has_storage(evm.message.tx_env.state, contract_address): + if not account_deployable(evm.message.tx_env.state, contract_address): increment_nonce(evm.message.tx_env.state, evm.message.current_target) push(evm.stack, U256(0)) return diff --git a/src/ethereum/forks/prague/vm/interpreter.py b/src/ethereum/forks/prague/vm/interpreter.py index c52a6768ad9..d6ed8dd4bc6 100644 --- a/src/ethereum/forks/prague/vm/interpreter.py +++ b/src/ethereum/forks/prague/vm/interpreter.py @@ -32,8 +32,7 @@ from ..blocks import Log from ..state_tracker import ( - account_has_code_or_nonce, - account_has_storage, + account_deployable, copy_tx_state, destroy_storage, get_account, @@ -109,10 +108,9 @@ def process_message_call(message: Message) -> MessageCallOutput: tx_state = message.tx_env.state refund_counter = U256(0) if message.target == Bytes0(b""): - is_collision = account_has_code_or_nonce( - tx_state, message.current_target - ) or account_has_storage(tx_state, message.current_target) - if is_collision: + if account_deployable(tx_state, message.current_target): + evm = process_create_message(message) + else: return MessageCallOutput( gas_left=Uint(0), refund_counter=U256(0), @@ -121,8 +119,6 @@ def process_message_call(message: Message) -> MessageCallOutput: error=AddressCollision(), return_data=Bytes(b""), ) - else: - evm = process_create_message(message) else: if message.tx_env.authorizations != (): refund_counter += set_delegation(message) diff --git a/src/ethereum/forks/shanghai/state_tracker.py b/src/ethereum/forks/shanghai/state_tracker.py index a62bed0c09f..964acb0682a 100644 --- a/src/ethereum/forks/shanghai/state_tracker.py +++ b/src/ethereum/forks/shanghai/state_tracker.py @@ -276,28 +276,18 @@ def account_exists(tx_state: TransactionState, address: Address) -> bool: return get_account_optional(tx_state, address) is not None -def account_has_code_or_nonce( - tx_state: TransactionState, address: Address -) -> bool: +def account_deployable(tx_state: TransactionState, address: Address) -> bool: """ - Check if an account has non-zero nonce or non-empty code. - - Parameters - ---------- - tx_state : - The transaction state. - address : - Address of the account that needs to be checked. - - Returns - ------- - has_code_or_nonce : ``bool`` - True if the account has non-zero nonce or non-empty code, - False otherwise. - + Check if an account's code can be written to. """ account = get_account(tx_state, address) - return account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH + if account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH: + return False + + if account_has_storage(tx_state, address): + return False + + return True def account_has_storage(tx_state: TransactionState, address: Address) -> bool: diff --git a/src/ethereum/forks/shanghai/vm/instructions/system.py b/src/ethereum/forks/shanghai/vm/instructions/system.py index c703c775df0..4043a94b595 100644 --- a/src/ethereum/forks/shanghai/vm/instructions/system.py +++ b/src/ethereum/forks/shanghai/vm/instructions/system.py @@ -21,8 +21,7 @@ from ethereum.utils.numeric import ceil32 from ...state_tracker import ( - account_has_code_or_nonce, - account_has_storage, + account_deployable, get_account, get_code, increment_nonce, @@ -97,9 +96,7 @@ def generic_create( evm.accessed_addresses.add(contract_address) - if account_has_code_or_nonce( - evm.message.tx_env.state, contract_address - ) or account_has_storage(evm.message.tx_env.state, contract_address): + if not account_deployable(evm.message.tx_env.state, contract_address): increment_nonce(evm.message.tx_env.state, evm.message.current_target) push(evm.stack, U256(0)) return diff --git a/src/ethereum/forks/shanghai/vm/interpreter.py b/src/ethereum/forks/shanghai/vm/interpreter.py index be7fbda75c2..30f6a5051a2 100644 --- a/src/ethereum/forks/shanghai/vm/interpreter.py +++ b/src/ethereum/forks/shanghai/vm/interpreter.py @@ -32,8 +32,7 @@ from ..blocks import Log from ..state_tracker import ( - account_has_code_or_nonce, - account_has_storage, + account_deployable, copy_tx_state, destroy_storage, increment_nonce, @@ -104,10 +103,9 @@ def process_message_call(message: Message) -> MessageCallOutput: tx_state = message.tx_env.state refund_counter = U256(0) if message.target == Bytes0(b""): - is_collision = account_has_code_or_nonce( - tx_state, message.current_target - ) or account_has_storage(tx_state, message.current_target) - if is_collision: + if account_deployable(tx_state, message.current_target): + evm = process_create_message(message) + else: return MessageCallOutput( gas_left=Uint(0), refund_counter=U256(0), @@ -115,8 +113,6 @@ def process_message_call(message: Message) -> MessageCallOutput: accounts_to_delete=set(), error=AddressCollision(), ) - else: - evm = process_create_message(message) else: evm = process_message(message) diff --git a/src/ethereum/forks/spurious_dragon/state_tracker.py b/src/ethereum/forks/spurious_dragon/state_tracker.py index 24362ecff98..dd7a3c3bb8c 100644 --- a/src/ethereum/forks/spurious_dragon/state_tracker.py +++ b/src/ethereum/forks/spurious_dragon/state_tracker.py @@ -276,28 +276,18 @@ def account_exists(tx_state: TransactionState, address: Address) -> bool: return get_account_optional(tx_state, address) is not None -def account_has_code_or_nonce( - tx_state: TransactionState, address: Address -) -> bool: +def account_deployable(tx_state: TransactionState, address: Address) -> bool: """ - Check if an account has non-zero nonce or non-empty code. - - Parameters - ---------- - tx_state : - The transaction state. - address : - Address of the account that needs to be checked. - - Returns - ------- - has_code_or_nonce : ``bool`` - True if the account has non-zero nonce or non-empty code, - False otherwise. - + Check if an account's code can be written to. """ account = get_account(tx_state, address) - return account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH + if account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH: + return False + + if account_has_storage(tx_state, address): + return False + + return True def account_has_storage(tx_state: TransactionState, address: Address) -> bool: diff --git a/src/ethereum/forks/spurious_dragon/vm/instructions/system.py b/src/ethereum/forks/spurious_dragon/vm/instructions/system.py index d12feed9990..1ad26f6f366 100644 --- a/src/ethereum/forks/spurious_dragon/vm/instructions/system.py +++ b/src/ethereum/forks/spurious_dragon/vm/instructions/system.py @@ -20,9 +20,8 @@ from ethereum.state import Address from ...state_tracker import ( + account_deployable, account_exists_and_is_empty, - account_has_code_or_nonce, - account_has_storage, get_account, get_code, increment_nonce, @@ -95,9 +94,7 @@ def create(evm: Evm) -> None: ): push(evm.stack, U256(0)) evm.gas_left += create_message_gas - elif account_has_code_or_nonce( - evm.message.tx_env.state, contract_address - ) or account_has_storage(evm.message.tx_env.state, contract_address): + elif not account_deployable(evm.message.tx_env.state, contract_address): increment_nonce(evm.message.tx_env.state, evm.message.current_target) push(evm.stack, U256(0)) else: diff --git a/src/ethereum/forks/spurious_dragon/vm/interpreter.py b/src/ethereum/forks/spurious_dragon/vm/interpreter.py index 622014f0047..2c46cb978dc 100644 --- a/src/ethereum/forks/spurious_dragon/vm/interpreter.py +++ b/src/ethereum/forks/spurious_dragon/vm/interpreter.py @@ -32,9 +32,8 @@ from ..blocks import Log from ..state_tracker import ( + account_deployable, account_exists_and_is_empty, - account_has_code_or_nonce, - account_has_storage, copy_tx_state, destroy_storage, increment_nonce, @@ -104,10 +103,9 @@ def process_message_call(message: Message) -> MessageCallOutput: tx_state = message.tx_env.state refund_counter = U256(0) if message.target == Bytes0(b""): - is_collision = account_has_code_or_nonce( - tx_state, message.current_target - ) or account_has_storage(tx_state, message.current_target) - if is_collision: + if account_deployable(tx_state, message.current_target): + evm = process_create_message(message) + else: return MessageCallOutput( gas_left=Uint(0), refund_counter=U256(0), @@ -116,8 +114,6 @@ def process_message_call(message: Message) -> MessageCallOutput: touched_accounts=set(), error=AddressCollision(), ) - else: - evm = process_create_message(message) else: evm = process_message(message) if account_exists_and_is_empty(tx_state, Address(message.target)): diff --git a/src/ethereum/forks/tangerine_whistle/state_tracker.py b/src/ethereum/forks/tangerine_whistle/state_tracker.py index 24362ecff98..dd7a3c3bb8c 100644 --- a/src/ethereum/forks/tangerine_whistle/state_tracker.py +++ b/src/ethereum/forks/tangerine_whistle/state_tracker.py @@ -276,28 +276,18 @@ def account_exists(tx_state: TransactionState, address: Address) -> bool: return get_account_optional(tx_state, address) is not None -def account_has_code_or_nonce( - tx_state: TransactionState, address: Address -) -> bool: +def account_deployable(tx_state: TransactionState, address: Address) -> bool: """ - Check if an account has non-zero nonce or non-empty code. - - Parameters - ---------- - tx_state : - The transaction state. - address : - Address of the account that needs to be checked. - - Returns - ------- - has_code_or_nonce : ``bool`` - True if the account has non-zero nonce or non-empty code, - False otherwise. - + Check if an account's code can be written to. """ account = get_account(tx_state, address) - return account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH + if account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH: + return False + + if account_has_storage(tx_state, address): + return False + + return True def account_has_storage(tx_state: TransactionState, address: Address) -> bool: diff --git a/src/ethereum/forks/tangerine_whistle/vm/instructions/system.py b/src/ethereum/forks/tangerine_whistle/vm/instructions/system.py index 8f30a357288..a70030f6ea9 100644 --- a/src/ethereum/forks/tangerine_whistle/vm/instructions/system.py +++ b/src/ethereum/forks/tangerine_whistle/vm/instructions/system.py @@ -20,9 +20,8 @@ from ethereum.state import Address from ...state_tracker import ( + account_deployable, account_exists, - account_has_code_or_nonce, - account_has_storage, get_account, get_code, increment_nonce, @@ -94,9 +93,7 @@ def create(evm: Evm) -> None: ): push(evm.stack, U256(0)) evm.gas_left += create_message_gas - elif account_has_code_or_nonce( - evm.message.tx_env.state, contract_address - ) or account_has_storage(evm.message.tx_env.state, contract_address): + elif not account_deployable(evm.message.tx_env.state, contract_address): increment_nonce(evm.message.tx_env.state, evm.message.current_target) push(evm.stack, U256(0)) else: diff --git a/src/ethereum/forks/tangerine_whistle/vm/interpreter.py b/src/ethereum/forks/tangerine_whistle/vm/interpreter.py index 476a569731c..b62f04c0e07 100644 --- a/src/ethereum/forks/tangerine_whistle/vm/interpreter.py +++ b/src/ethereum/forks/tangerine_whistle/vm/interpreter.py @@ -32,8 +32,7 @@ from ..blocks import Log from ..state_tracker import ( - account_has_code_or_nonce, - account_has_storage, + account_deployable, copy_tx_state, destroy_storage, move_ether, @@ -98,10 +97,9 @@ def process_message_call(message: Message) -> MessageCallOutput: tx_state = message.tx_env.state refund_counter = U256(0) if message.target == Bytes0(b""): - is_collision = account_has_code_or_nonce( - tx_state, message.current_target - ) or account_has_storage(tx_state, message.current_target) - if is_collision: + if account_deployable(tx_state, message.current_target): + evm = process_create_message(message) + else: return MessageCallOutput( gas_left=Uint(0), refund_counter=U256(0), @@ -109,8 +107,6 @@ def process_message_call(message: Message) -> MessageCallOutput: accounts_to_delete=set(), error=AddressCollision(), ) - else: - evm = process_create_message(message) else: evm = process_message(message) diff --git a/tests/paris/eip7610_create_collision/test_initcollision.py b/tests/paris/eip7610_create_collision/test_initcollision.py index 544a7442d44..081b24e9cf2 100644 --- a/tests/paris/eip7610_create_collision/test_initcollision.py +++ b/tests/paris/eip7610_create_collision/test_initcollision.py @@ -22,7 +22,7 @@ REFERENCE_SPEC_VERSION = "80ef48d0bbb5a4939ade51caaaac57b5df6acd4e" pytestmark = [ - pytest.mark.valid_from("Paris"), + pytest.mark.valid_from("Frontier"), pytest.mark.ported_from( [ "https://github.com/ethereum/tests/blob/v13.3/src/GeneralStateTestsFiller/stSStoreTest/InitCollisionFiller.json", @@ -81,6 +81,7 @@ def test_init_collision_create_tx( ty=tx_type, to=None, data=initcode, + protected=False, ) created_contract_address = tx.created_contract @@ -113,7 +114,15 @@ def test_init_collision_create_tx( ) -@pytest.mark.parametrize("opcode", [Op.CREATE, Op.CREATE2]) +@pytest.mark.parametrize( + "opcode", + [ + Op.CREATE, + pytest.param( + Op.CREATE2, marks=pytest.mark.valid_from("Constantinople") + ), + ], +) def test_init_collision_create_opcode( state_test: StateTestFiller, pre: Alloc, @@ -129,14 +138,37 @@ def test_init_collision_create_opcode( """ assert len(initcode) <= 32 contract_creator_code = ( + # Reverts if and only if contract creation fails. In Frontier/Homestead + # this runs out of gas, and every other fork jumps to a non-JUMPDEST. Op.MSTORE(0, Op.PUSH32(bytes(initcode).ljust(32, b"\0"))) - + Op.SSTORE(0x01, opcode(value=0, offset=0, size=len(initcode))) + + Op.JUMPI( + condition=Op.ISZERO(opcode(value=0, offset=0, size=len(initcode))), + pc=0, + ) + Op.STOP ) - contract_creator_address = pre.deploy_contract( - contract_creator_code, - storage={0x01: 0x01}, + contract_creator_address = pre.deploy_contract(contract_creator_code) + + gas_limiter_code = ( + # Calls the contract creator, reserving some gas to SSTORE the result. + Op.SSTORE( + 0x01, + Op.CALL( + gas=Op.SUB(Op.GAS, 50_000), + address=contract_creator_address, + value=0, + args_offset=0, + args_size=0, + ret_offset=0, + ret_size=0, + ), + ) + ) + gas_limiter_address = pre.deploy_contract( + gas_limiter_code, + storage={0x01: 0x02}, ) + created_contract_address = compute_create_address( address=contract_creator_address, nonce=1, @@ -147,8 +179,8 @@ def test_init_collision_create_opcode( tx = Transaction( sender=pre.fund_eoa(), - to=contract_creator_address, - data=initcode, + to=gas_limiter_address, + protected=False, ) pre[created_contract_address] = Account( @@ -164,7 +196,7 @@ def test_init_collision_create_opcode( created_contract_address: Account( storage={0x01: 0x01}, ), - contract_creator_address: Account(storage={0x01: 0x00}), + gas_limiter_address: Account(storage={0x01: 0x00}), }, tx=tx, ) From 7ccf64998512a589e64442f6f795f340c6603cba Mon Sep 17 00:00:00 2001 From: Sam Wilson <57262657+SamWilsn@users.noreply.github.com> Date: Wed, 17 Jun 2026 04:53:51 -0400 Subject: [PATCH 031/233] fix(ci): include json-loader in coverage reports (#2975) --- .github/workflows/test.yaml | 6 ++++++ Justfile | 9 ++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 723ef6c95a4..df94030cace 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -145,6 +145,12 @@ jobs: run: just json-loader env: PYTEST_XDIST_AUTO_NUM_WORKERS: auto + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 + with: + files: .just/json-loader/coverage.xml + flags: unittests + token: ${{ secrets.CODECOV_TOKEN }} test-tests: runs-on: [self-hosted-ghr, size-xl-x64] diff --git a/Justfile b/Justfile index 477268369b2..6da3a1eabc6 100644 --- a/Justfile +++ b/Justfile @@ -163,10 +163,17 @@ json-loader *args: --output="tests/json_loader/fixtures" \ --cov-config=pyproject.toml \ --cov=ethereum \ - --cov-fail-under=80 + --cov-branch \ + --cov-report=term \ + --cov-fail-under=85 uv run pytest \ -m "not slow" \ -n auto --maxprocesses 6 --dist=loadfile \ + --cov-config=pyproject.toml \ + --cov=ethereum \ + --cov-branch \ + --cov-report=term \ + --cov-report "xml:{{ output_dir }}/json-loader/coverage.xml" \ --basetemp="{{ output_dir }}/json-loader/tmp" \ "$@" \ tests/json_loader From c00006fb09a3975ded1906fee368d6c4402df553 Mon Sep 17 00:00:00 2001 From: danceratopz Date: Wed, 17 Jun 2026 12:19:27 +0200 Subject: [PATCH 032/233] feat(tests): add EIP-7954 jumpdest test past the old code-size limit (#2993) Co-authored-by: marioevz <11726710+marioevz@users.noreply.github.com> --- .../test_cases.md | 1 + .../test_max_code_size.py | 62 +++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/tests/amsterdam/eip7954_increase_max_contract_size/test_cases.md b/tests/amsterdam/eip7954_increase_max_contract_size/test_cases.md index 27ab07655c5..bb6c676639b 100644 --- a/tests/amsterdam/eip7954_increase_max_contract_size/test_cases.md +++ b/tests/amsterdam/eip7954_increase_max_contract_size/test_cases.md @@ -10,6 +10,7 @@ | `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_high_jumpdest` | Enforce JUMP destination validity and code execution past the old size limits | Deploy a `MAX_CODE_SIZE` contract that jumps near the new limit (far beyond the old 24 KiB code and 48 KiB initcode limits), to a real `JUMPDEST` or to a `PUSH1` byte, and call it via a caller that records the call result. | Valid `JUMPDEST`: jump succeeds, the contract executes at the high offset and stores a sentinel. Non-`JUMPDEST`: jump is rejected, call fails, nothing is stored. | ✅ 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_warm_after_failed_create_over_max_code_size` | A failed CREATE/CREATE2 over max code size leaves the would-be address warm | A creator runs CREATE/CREATE2 whose initcode returns `MAX_CODE_SIZE + 1` bytes; a checker then measures the gas of a `BALANCE` on that address. | The address is warm: the post-RETURN size-check rejection still leaves it in the access list. | ✅ Completed | | `test_max_code_size_fork_transition` | New `MAX_CODE_SIZE` activates exactly at the fork boundary | Before and after the fork, deploy a contract one byte over the parent fork's max code size (valid under the new limit; its initcode stays within both forks' initcode limits). | Pre-fork: deployment fails at code deposit (exceeds old limit). Post-fork: deployment succeeds. | ✅ Completed | 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 44bcc33e84b..c1a2ff5f13f 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 @@ -349,3 +349,65 @@ def test_warm_after_failed_create_over_max_code_size( } state_test(pre=pre, tx=tx, post=post) + + +@pytest.mark.parametrize( + "valid_jumpdest", + [ + pytest.param(True, id="valid_high_jumpdest"), + pytest.param(False, id="invalid_high_dest"), + ], +) +def test_max_code_size_high_jumpdest( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + valid_jumpdest: bool, +) -> None: + """ + Ensure jump destination validity is enforced past the old size limits. + + Deploy a `MAX_CODE_SIZE` contract that stores a sentinel and then jumps + near the new limit, far beyond the old 24 KiB code and 48 KiB initcode + limits, then call it through a caller that records the call's success: + + - ``valid_high_jumpdest``: the target byte is a real ``JUMPDEST``, so the + jump succeeds, the frame returns, and the sentinel store is kept. + - ``invalid_high_dest``: the target byte is a ``STOP`` (not a + ``JUMPDEST``), so the jump is rejected, the frame reverts, and the + sentinel store is discarded. + + A client whose jumpdest analysis or code execution does not cover the + full new code range fails one of the two cases. No existing test + executes a contract at a program counter beyond the old limit. + """ + if valid_jumpdest: + tail = Op.JUMPDEST + else: + # A bare STOP, not a JUMPDEST: jumping here is invalid. A client that + # wrongly accepts it halts normally and keeps the prefix store (1). + tail = Op.STOP + + dest = fork.max_code_size() - len(tail) + push_size = (dest.bit_length() + 7) // 8 + push_op = getattr(Op, f"PUSH{push_size}") + prefix = Op.SSTORE(0, 1) + push_op(dest) + Op.JUMP + target_code = prefix + Op.INVALID * (dest - len(prefix)) + tail + assert len(target_code) == fork.max_code_size() + + target = pre.deploy_contract(target_code) + caller = pre.deploy_contract( + Op.SSTORE(0, Op.CALL(gas=Op.GAS, address=target)) + Op.STOP + ) + + tx = Transaction(sender=pre.fund_eoa(), to=caller) + + # Valid: jump completes, call succeeds (1), and the store is kept. + # Invalid: jump reverts, call fails (0), and nothing is stored. + stored = 1 if valid_jumpdest else 0 + post = { + caller: Account(storage={0: stored}), + target: Account(storage={0: stored}), + } + + state_test(pre=pre, tx=tx, post=post) From 4d841001c84d18d32b570f4052303ad2d8fe968a Mon Sep 17 00:00:00 2001 From: raxhvl <10168946+raxhvl@users.noreply.github.com> Date: Wed, 17 Jun 2026 11:43:18 +0000 Subject: [PATCH 033/233] feat(tests): disallow empty change set for storage slot BALs (#2945) --- .../test_types/block_access_list/modifiers.py | 36 ++++++++++ .../test_block_access_lists_invalid.py | 67 +++++++++++++++++++ .../test_cases.md | 1 + 3 files changed, 104 insertions(+) diff --git a/packages/testing/src/execution_testing/test_types/block_access_list/modifiers.py b/packages/testing/src/execution_testing/test_types/block_access_list/modifiers.py index 00903266251..2777f5c5e27 100644 --- a/packages/testing/src/execution_testing/test_types/block_access_list/modifiers.py +++ b/packages/testing/src/execution_testing/test_types/block_access_list/modifiers.py @@ -481,6 +481,41 @@ def transform(bal: BlockAccessList) -> BlockAccessList: return transform +def append_empty_slot( + address: Address, slot: int +) -> Callable[[BlockAccessList], BlockAccessList]: + """ + Append an empty BalStorageSlot (no changes) to an account's + storage_changes. Used by invalid-BAL tests to simulate a malformed + entry where a slot is recorded as changed but carries no actual change. + """ + + def transform(bal: BlockAccessList) -> BlockAccessList: + from . import BalStorageSlot + + found_address = False + new_root = [] + for account_change in bal.root: + if account_change.address == address: + found_address = True + new_account = account_change.model_copy(deep=True) + new_account.storage_changes.append( + BalStorageSlot(slot=slot, slot_changes=[]) + ) + new_root.append(new_account) + else: + new_root.append(account_change) + + if not found_address: + raise ValueError( + f"Address {address} not found in BAL to append empty slot" + ) + + return BlockAccessList(root=new_root) + + return transform + + def duplicate_account( address: Address, ) -> Callable[[BlockAccessList], BlockAccessList]: @@ -779,6 +814,7 @@ def transform(bal: BlockAccessList) -> BlockAccessList: "append_account", "append_change", "append_storage", + "append_empty_slot", "duplicate_account", "reverse_accounts", "keep_only", diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_invalid.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_invalid.py index 9ae157823fc..33585b2a3cc 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_invalid.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_invalid.py @@ -36,6 +36,7 @@ from execution_testing.test_types.block_access_list.modifiers import ( append_account, append_change, + append_empty_slot, append_storage, duplicate_account, duplicate_balance_change, @@ -1029,6 +1030,72 @@ def test_bal_invalid_extraneous_entries( ) +@pytest.mark.valid_from("Amsterdam") +@pytest.mark.exception_test +@pytest.mark.parametrize( + "pre_storage,oracle_expectation,slot_to_inject", + [ + pytest.param( + {}, + BalAccountExpectation( + storage_changes=[ + BalStorageSlot( + slot=0, + slot_changes=[ + BalStorageChange( + block_access_index=1, post_value=0x42 + ) + ], + ) + ], + ), + 1, + id="unrelated_slot", + ), + pytest.param( + {0: 0x42}, + BalAccountExpectation(storage_reads=[0]), + 0, + id="demoted_noop", + ), + ], +) +def test_bal_invalid_empty_slot_changes( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + pre_storage: dict, + oracle_expectation: BalAccountExpectation, + slot_to_inject: int, +) -> None: + """Reject BAL containing a SlotChanges with an empty slot_changes list.""" + alice = pre.fund_eoa() + oracle = pre.deploy_contract(code=Op.SSTORE(0, 0x42), storage=pre_storage) + tx = Transaction(sender=alice, to=oracle, gas_limit=1_000_000) + + blockchain_test( + pre=pre, + post=pre, + blocks=[ + Block( + txs=[tx], + exception=BlockException.INVALID_BLOCK_ACCESS_LIST, + expected_block_access_list=BlockAccessListExpectation( + account_expectations={ + alice: BalAccountExpectation( + nonce_changes=[ + BalNonceChange( + block_access_index=1, post_nonce=1 + ) + ], + ), + oracle: oracle_expectation, + } + ).modify(append_empty_slot(oracle, slot=slot_to_inject)), + ) + ], + ) + + @pytest.mark.valid_from("Amsterdam") @pytest.mark.exception_test @pytest.mark.parametrize( diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_cases.md b/tests/amsterdam/eip7928_block_level_access_lists/test_cases.md index 10ce020ccaa..67b7e6a4e8e 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_cases.md +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_cases.md @@ -158,6 +158,7 @@ | `test_bal_7002_request_invalid` | Ensure BAL correctly handles invalid withdrawal request scenarios | Parameterized test with 8 invalid scenarios: (1) insufficient_fee (fee=0), (2) calldata_too_short (55 bytes), (3) calldata_too_long (57 bytes), (4) oog (insufficient gas), (5-7) invalid_call_type (DELEGATECALL/STATICCALL/CALLCODE), (8) contract_reverts. Tests both EOA and contract-based withdrawal requests. | BAL **MUST** include sender with `nonce_changes` at `block_access_index=1`. BAL **MUST** include system contract with `storage_reads` for slots: excess (slot 0), count (slot 1), head (slot 2), tail (slot 3). System contract **MUST NOT** have `storage_changes` (transaction failed, no queue modification). | ✅ Completed | | `test_bal_invalid_extraneous_entries` | Verify clients reject blocks with any type of extraneous BAL entries | Alice sends 100 wei to Oracle contract (which reads storage slot 0). Charlie is uninvolved in this transaction. A valid BAL is created containing nonce change for Alice, balance change and storage read for Oracle. The BAL is corrupted by adding various extraneous entries: (1) extra_nonce, (2) extra_balance, (3) extra_code, (4) extra_storage_write_touched (slot 0 - already read), (5) extra_storage_write_untouched (slot 1 - not accessed), (6) extra_storage_write_uninvolved_account (Charlie - uninvolved account), (7) extra_account_access (Charlie), (8) extra_storage_read (slot 999). Each tested at block_access_index 1 (same tx), 2 (system tx), 3 (out of bounds). | Block **MUST** be rejected with `INVALID_BLOCK_ACCESS_LIST` exception. Clients **MUST** detect any extraneous entries in BAL. | ✅ Completed | | `test_bal_invalid_duplicate_entries` | Verify clients reject blocks where BAL violates uniqueness constraints | Oracle writes storage, reads storage, and CREATEs a contract. BAL is corrupted with duplicate entries: (1) duplicate_nonce_change, (2) duplicate_balance_change, (3) duplicate_code_change, (4) duplicate_storage_slot, (5) duplicate_storage_read, (6) duplicate_slot_change, (7) storage_key_in_both_changes_and_reads. | Block **MUST** be rejected with `INVALID_BLOCK_ACCESS_LIST` exception. Each `block_access_index` must appear at most once per change list, each storage key at most once in `storage_changes` and `storage_reads`, and no key in both. | ✅ Completed | +| `test_bal_invalid_empty_slot_changes` | Verify clients reject BAL containing a storage slot entry with no changes | Parametrized: (1) `unrelated_slot`: Oracle writes one slot; BAL is corrupted to include a second, unrelated slot with no changes. (2) `demoted_noop`: Oracle writes a slot back to its existing value (no-op), so the slot is recorded as a read; BAL is corrupted to also record the same slot as a change with no changes. | Block **MUST** be rejected with `INVALID_BLOCK_ACCESS_LIST` exception. A storage slot in `storage_changes` **MUST** have at least one recorded change; a slot accessed without any change belongs in `storage_reads`. | ✅ Completed | | `test_bal_invalid_missing_withdrawal_account` | Verify clients reject blocks where BAL is missing an account modified only by a withdrawal | Alice sends 5 wei to Bob (1 transaction). Charlie receives 10 gwei withdrawal. BAL modifier removes Charlie's entry entirely. | Block **MUST** be rejected with `INVALID_BLOCK_ACCESS_LIST` exception. Clients **MUST** detect that Charlie's balance was modified by the withdrawal but has no corresponding BAL entry. | ✅ Completed | | `test_bal_invalid_missing_withdrawal_account_empty_block` | Verify clients reject blocks where BAL is missing a withdrawal-modified account in an empty block | Charlie receives 10 gwei withdrawal in block with no transactions. BAL modifier removes Charlie's entry entirely. | Block **MUST** be rejected with `INVALID_BLOCK_ACCESS_LIST` exception. Clients **MUST** detect withdrawal-modified accounts even when no transactions are present. | ✅ Completed | | `test_bal_invalid_hash_mismatch` | Verify clients reject blocks where the BAL hash in the header does not match the actual BAL content | Alice sends value to Bob. BAL content is valid but header hash is overridden to a wrong value via `rlp_modifier`. Unlike other invalid BAL tests (which corrupt BAL content with matching hash), this keeps the BAL valid but injects a wrong header hash. | Block **MUST** be rejected with `INVALID_BAL_HASH` or `INVALID_BLOCK_HASH` exception. Clients **MUST** re-derive the BAL from block execution and compare its hash to the header, not just verify the BAL content is self-consistent. | ✅ Completed | From d32554218f4dadc65ced5de1fe1598dfc7c8bc2b Mon Sep 17 00:00:00 2001 From: spencer Date: Wed, 17 Jun 2026 16:39:23 +0100 Subject: [PATCH 034/233] chore(ci): rename bal release feature to glamsterdam-devnet (#2997) --- .github/configs/feature.yaml | 2 +- .github/scripts/tests/test_release_scripts.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/configs/feature.yaml b/.github/configs/feature.yaml index cf6a92748d9..e5a7e51d7bc 100644 --- a/.github/configs/feature.yaml +++ b/.github/configs/feature.yaml @@ -13,7 +13,7 @@ benchmark_fast: fill-params: --fork=Osaka --generate-all-formats --gas-benchmark-values 100 ./tests/benchmark/compute feature_only: true -bal: +glamsterdam-devnet: evm-type: eels fill-params: --fork=Amsterdam feature_only: true diff --git a/.github/scripts/tests/test_release_scripts.py b/.github/scripts/tests/test_release_scripts.py index bd3acc17e8d..6e24864bfed 100644 --- a/.github/scripts/tests/test_release_scripts.py +++ b/.github/scripts/tests/test_release_scripts.py @@ -70,12 +70,12 @@ def test_unsplit_feature_produces_single_entry(self): def test_feature_only_can_be_requested_explicitly(self): """Verify feature_only entries work when named directly.""" - result = run_script(BUILD_MATRIX_SCRIPT, "bal") + result = run_script(BUILD_MATRIX_SCRIPT, "glamsterdam-devnet") assert result.returncode == 0 out = parse_matrix_output(result.stdout) matrix = json.loads(out["build_matrix"]) assert len(matrix) == 1 - assert matrix[0]["feature"] == "bal" + assert matrix[0]["feature"] == "glamsterdam-devnet" assert out["combine_labels"] == "" def test_unknown_feature_fails(self): From 5cc6a81d5961e73620700e26b0a253807f72abc0 Mon Sep 17 00:00:00 2001 From: Sam Wilson Date: Wed, 17 Jun 2026 19:33:33 -0400 Subject: [PATCH 035/233] refactor(specs): factor out auth validity --- .../forks/amsterdam/vm/eoa_delegation.py | 66 ++++++++++++------- src/ethereum/forks/bpo1/vm/eoa_delegation.py | 64 +++++++++++------- src/ethereum/forks/bpo2/vm/eoa_delegation.py | 64 +++++++++++------- src/ethereum/forks/bpo3/vm/eoa_delegation.py | 64 +++++++++++------- src/ethereum/forks/bpo4/vm/eoa_delegation.py | 64 +++++++++++------- src/ethereum/forks/bpo5/vm/eoa_delegation.py | 64 +++++++++++------- src/ethereum/forks/osaka/vm/eoa_delegation.py | 64 +++++++++++------- .../forks/prague/vm/eoa_delegation.py | 64 +++++++++++------- 8 files changed, 337 insertions(+), 177 deletions(-) diff --git a/src/ethereum/forks/amsterdam/vm/eoa_delegation.py b/src/ethereum/forks/amsterdam/vm/eoa_delegation.py index 8f2a3e81609..563a8c430d0 100644 --- a/src/ethereum/forks/amsterdam/vm/eoa_delegation.py +++ b/src/ethereum/forks/amsterdam/vm/eoa_delegation.py @@ -10,7 +10,7 @@ from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import keccak256 from ethereum.exceptions import InvalidBlock, InvalidSignatureError -from ethereum.state import EMPTY_CODE_HASH, Address +from ethereum.state import EMPTY_CODE_HASH, Account, Address from ..fork_types import Authorization from ..state_tracker import ( @@ -157,6 +157,43 @@ def calculate_delegation_cost( return True, delegated_address, delegation_gas_cost +def validate_authorization( + message: Message, auth: Authorization +) -> None | Tuple[Address, Account]: + """ + Check if the given `Authorization` is valid against the current state. + + Returns the `authority` address and its `Account`, or `None` if the + validation was unsuccessful. + """ + tx_state = message.tx_env.state + + if auth.chain_id not in (message.block_env.chain_id, U256(0)): + return None + + if auth.nonce >= U64.MAX_VALUE: + return None + + try: + authority = recover_authority(auth) + except InvalidSignatureError: + return None + + message.accessed_addresses.add(authority) + + authority_account = get_account(tx_state, authority) + authority_code = get_code(tx_state, authority_account.code_hash) + + if authority_code and not is_valid_delegation(authority_code): + return None + + authority_nonce = authority_account.nonce + if authority_nonce != auth.nonce: + return None + + return (authority, authority_account) + + def set_delegation(message: Message) -> Uint: """ Set the delegation code for the authorities in the message. @@ -180,28 +217,11 @@ def set_delegation(message: Message) -> Uint: tx_state = message.tx_env.state state_refund = Uint(0) for auth in message.tx_env.authorizations: - if auth.chain_id not in (message.block_env.chain_id, U256(0)): - continue - - if auth.nonce >= U64.MAX_VALUE: - continue - - try: - authority = recover_authority(auth) - except InvalidSignatureError: - continue - - message.accessed_addresses.add(authority) - - authority_account = get_account(tx_state, authority) - authority_code = get_code(tx_state, authority_account.code_hash) - - if authority_code and not is_valid_delegation(authority_code): - continue - - authority_nonce = authority_account.nonce - if authority_nonce != auth.nonce: - continue + match validate_authorization(message, auth): + case None: + continue + case (authority, authority_account): + pass if account_exists(tx_state, authority): refund = StateGasCosts.NEW_ACCOUNT diff --git a/src/ethereum/forks/bpo1/vm/eoa_delegation.py b/src/ethereum/forks/bpo1/vm/eoa_delegation.py index 1ba552f8dd6..564b165bbee 100644 --- a/src/ethereum/forks/bpo1/vm/eoa_delegation.py +++ b/src/ethereum/forks/bpo1/vm/eoa_delegation.py @@ -155,6 +155,43 @@ def access_delegation( return True, address, code, access_gas_cost +def validate_authorization( + message: Message, auth: Authorization +) -> None | Address: + """ + Check if the given `Authorization` is valid against the current state. + + Returns the `authority` address or `None` if the validation was + unsuccessful. + """ + tx_state = message.tx_env.state + + if auth.chain_id not in (message.block_env.chain_id, U256(0)): + return None + + if auth.nonce >= U64.MAX_VALUE: + return None + + try: + authority = recover_authority(auth) + except InvalidSignatureError: + return None + + message.accessed_addresses.add(authority) + + authority_account = get_account(tx_state, authority) + authority_code = get_code(tx_state, authority_account.code_hash) + + if authority_code and not is_valid_delegation(authority_code): + return None + + authority_nonce = authority_account.nonce + if authority_nonce != auth.nonce: + return None + + return authority + + def set_delegation(message: Message) -> U256: """ Set the delegation code for the authorities in the message. @@ -173,28 +210,11 @@ def set_delegation(message: Message) -> U256: tx_state = message.tx_env.state refund_counter = U256(0) for auth in message.tx_env.authorizations: - if auth.chain_id not in (message.block_env.chain_id, U256(0)): - continue - - if auth.nonce >= U64.MAX_VALUE: - continue - - try: - authority = recover_authority(auth) - except InvalidSignatureError: - continue - - message.accessed_addresses.add(authority) - - authority_account = get_account(tx_state, authority) - authority_code = get_code(tx_state, authority_account.code_hash) - - if authority_code and not is_valid_delegation(authority_code): - continue - - authority_nonce = authority_account.nonce - if authority_nonce != auth.nonce: - continue + match validate_authorization(message, auth): + case None: + continue + case authority: + pass if account_exists(tx_state, authority): refund_counter += U256( diff --git a/src/ethereum/forks/bpo2/vm/eoa_delegation.py b/src/ethereum/forks/bpo2/vm/eoa_delegation.py index 1ba552f8dd6..564b165bbee 100644 --- a/src/ethereum/forks/bpo2/vm/eoa_delegation.py +++ b/src/ethereum/forks/bpo2/vm/eoa_delegation.py @@ -155,6 +155,43 @@ def access_delegation( return True, address, code, access_gas_cost +def validate_authorization( + message: Message, auth: Authorization +) -> None | Address: + """ + Check if the given `Authorization` is valid against the current state. + + Returns the `authority` address or `None` if the validation was + unsuccessful. + """ + tx_state = message.tx_env.state + + if auth.chain_id not in (message.block_env.chain_id, U256(0)): + return None + + if auth.nonce >= U64.MAX_VALUE: + return None + + try: + authority = recover_authority(auth) + except InvalidSignatureError: + return None + + message.accessed_addresses.add(authority) + + authority_account = get_account(tx_state, authority) + authority_code = get_code(tx_state, authority_account.code_hash) + + if authority_code and not is_valid_delegation(authority_code): + return None + + authority_nonce = authority_account.nonce + if authority_nonce != auth.nonce: + return None + + return authority + + def set_delegation(message: Message) -> U256: """ Set the delegation code for the authorities in the message. @@ -173,28 +210,11 @@ def set_delegation(message: Message) -> U256: tx_state = message.tx_env.state refund_counter = U256(0) for auth in message.tx_env.authorizations: - if auth.chain_id not in (message.block_env.chain_id, U256(0)): - continue - - if auth.nonce >= U64.MAX_VALUE: - continue - - try: - authority = recover_authority(auth) - except InvalidSignatureError: - continue - - message.accessed_addresses.add(authority) - - authority_account = get_account(tx_state, authority) - authority_code = get_code(tx_state, authority_account.code_hash) - - if authority_code and not is_valid_delegation(authority_code): - continue - - authority_nonce = authority_account.nonce - if authority_nonce != auth.nonce: - continue + match validate_authorization(message, auth): + case None: + continue + case authority: + pass if account_exists(tx_state, authority): refund_counter += U256( diff --git a/src/ethereum/forks/bpo3/vm/eoa_delegation.py b/src/ethereum/forks/bpo3/vm/eoa_delegation.py index 1ba552f8dd6..564b165bbee 100644 --- a/src/ethereum/forks/bpo3/vm/eoa_delegation.py +++ b/src/ethereum/forks/bpo3/vm/eoa_delegation.py @@ -155,6 +155,43 @@ def access_delegation( return True, address, code, access_gas_cost +def validate_authorization( + message: Message, auth: Authorization +) -> None | Address: + """ + Check if the given `Authorization` is valid against the current state. + + Returns the `authority` address or `None` if the validation was + unsuccessful. + """ + tx_state = message.tx_env.state + + if auth.chain_id not in (message.block_env.chain_id, U256(0)): + return None + + if auth.nonce >= U64.MAX_VALUE: + return None + + try: + authority = recover_authority(auth) + except InvalidSignatureError: + return None + + message.accessed_addresses.add(authority) + + authority_account = get_account(tx_state, authority) + authority_code = get_code(tx_state, authority_account.code_hash) + + if authority_code and not is_valid_delegation(authority_code): + return None + + authority_nonce = authority_account.nonce + if authority_nonce != auth.nonce: + return None + + return authority + + def set_delegation(message: Message) -> U256: """ Set the delegation code for the authorities in the message. @@ -173,28 +210,11 @@ def set_delegation(message: Message) -> U256: tx_state = message.tx_env.state refund_counter = U256(0) for auth in message.tx_env.authorizations: - if auth.chain_id not in (message.block_env.chain_id, U256(0)): - continue - - if auth.nonce >= U64.MAX_VALUE: - continue - - try: - authority = recover_authority(auth) - except InvalidSignatureError: - continue - - message.accessed_addresses.add(authority) - - authority_account = get_account(tx_state, authority) - authority_code = get_code(tx_state, authority_account.code_hash) - - if authority_code and not is_valid_delegation(authority_code): - continue - - authority_nonce = authority_account.nonce - if authority_nonce != auth.nonce: - continue + match validate_authorization(message, auth): + case None: + continue + case authority: + pass if account_exists(tx_state, authority): refund_counter += U256( diff --git a/src/ethereum/forks/bpo4/vm/eoa_delegation.py b/src/ethereum/forks/bpo4/vm/eoa_delegation.py index 1ba552f8dd6..564b165bbee 100644 --- a/src/ethereum/forks/bpo4/vm/eoa_delegation.py +++ b/src/ethereum/forks/bpo4/vm/eoa_delegation.py @@ -155,6 +155,43 @@ def access_delegation( return True, address, code, access_gas_cost +def validate_authorization( + message: Message, auth: Authorization +) -> None | Address: + """ + Check if the given `Authorization` is valid against the current state. + + Returns the `authority` address or `None` if the validation was + unsuccessful. + """ + tx_state = message.tx_env.state + + if auth.chain_id not in (message.block_env.chain_id, U256(0)): + return None + + if auth.nonce >= U64.MAX_VALUE: + return None + + try: + authority = recover_authority(auth) + except InvalidSignatureError: + return None + + message.accessed_addresses.add(authority) + + authority_account = get_account(tx_state, authority) + authority_code = get_code(tx_state, authority_account.code_hash) + + if authority_code and not is_valid_delegation(authority_code): + return None + + authority_nonce = authority_account.nonce + if authority_nonce != auth.nonce: + return None + + return authority + + def set_delegation(message: Message) -> U256: """ Set the delegation code for the authorities in the message. @@ -173,28 +210,11 @@ def set_delegation(message: Message) -> U256: tx_state = message.tx_env.state refund_counter = U256(0) for auth in message.tx_env.authorizations: - if auth.chain_id not in (message.block_env.chain_id, U256(0)): - continue - - if auth.nonce >= U64.MAX_VALUE: - continue - - try: - authority = recover_authority(auth) - except InvalidSignatureError: - continue - - message.accessed_addresses.add(authority) - - authority_account = get_account(tx_state, authority) - authority_code = get_code(tx_state, authority_account.code_hash) - - if authority_code and not is_valid_delegation(authority_code): - continue - - authority_nonce = authority_account.nonce - if authority_nonce != auth.nonce: - continue + match validate_authorization(message, auth): + case None: + continue + case authority: + pass if account_exists(tx_state, authority): refund_counter += U256( diff --git a/src/ethereum/forks/bpo5/vm/eoa_delegation.py b/src/ethereum/forks/bpo5/vm/eoa_delegation.py index 1ba552f8dd6..564b165bbee 100644 --- a/src/ethereum/forks/bpo5/vm/eoa_delegation.py +++ b/src/ethereum/forks/bpo5/vm/eoa_delegation.py @@ -155,6 +155,43 @@ def access_delegation( return True, address, code, access_gas_cost +def validate_authorization( + message: Message, auth: Authorization +) -> None | Address: + """ + Check if the given `Authorization` is valid against the current state. + + Returns the `authority` address or `None` if the validation was + unsuccessful. + """ + tx_state = message.tx_env.state + + if auth.chain_id not in (message.block_env.chain_id, U256(0)): + return None + + if auth.nonce >= U64.MAX_VALUE: + return None + + try: + authority = recover_authority(auth) + except InvalidSignatureError: + return None + + message.accessed_addresses.add(authority) + + authority_account = get_account(tx_state, authority) + authority_code = get_code(tx_state, authority_account.code_hash) + + if authority_code and not is_valid_delegation(authority_code): + return None + + authority_nonce = authority_account.nonce + if authority_nonce != auth.nonce: + return None + + return authority + + def set_delegation(message: Message) -> U256: """ Set the delegation code for the authorities in the message. @@ -173,28 +210,11 @@ def set_delegation(message: Message) -> U256: tx_state = message.tx_env.state refund_counter = U256(0) for auth in message.tx_env.authorizations: - if auth.chain_id not in (message.block_env.chain_id, U256(0)): - continue - - if auth.nonce >= U64.MAX_VALUE: - continue - - try: - authority = recover_authority(auth) - except InvalidSignatureError: - continue - - message.accessed_addresses.add(authority) - - authority_account = get_account(tx_state, authority) - authority_code = get_code(tx_state, authority_account.code_hash) - - if authority_code and not is_valid_delegation(authority_code): - continue - - authority_nonce = authority_account.nonce - if authority_nonce != auth.nonce: - continue + match validate_authorization(message, auth): + case None: + continue + case authority: + pass if account_exists(tx_state, authority): refund_counter += U256( diff --git a/src/ethereum/forks/osaka/vm/eoa_delegation.py b/src/ethereum/forks/osaka/vm/eoa_delegation.py index 62193fe0819..bb67bb57c2f 100644 --- a/src/ethereum/forks/osaka/vm/eoa_delegation.py +++ b/src/ethereum/forks/osaka/vm/eoa_delegation.py @@ -156,6 +156,43 @@ def access_delegation( return True, address, code, access_gas_cost +def validate_authorization( + message: Message, auth: Authorization +) -> None | Address: + """ + Check if the given `Authorization` is valid against the current state. + + Returns the `authority` address or `None` if the validation was + unsuccessful. + """ + tx_state = message.tx_env.state + + if auth.chain_id not in (message.block_env.chain_id, U256(0)): + return None + + if auth.nonce >= U64.MAX_VALUE: + return None + + try: + authority = recover_authority(auth) + except InvalidSignatureError: + return None + + message.accessed_addresses.add(authority) + + authority_account = get_account(tx_state, authority) + authority_code = get_code(tx_state, authority_account.code_hash) + + if authority_code and not is_valid_delegation(authority_code): + return None + + authority_nonce = authority_account.nonce + if authority_nonce != auth.nonce: + return None + + return authority + + def set_delegation(message: Message) -> U256: """ Set the delegation code for the authorities in the message. @@ -174,28 +211,11 @@ def set_delegation(message: Message) -> U256: tx_state = message.tx_env.state refund_counter = U256(0) for auth in message.tx_env.authorizations: - if auth.chain_id not in (message.block_env.chain_id, U256(0)): - continue - - if auth.nonce >= U64.MAX_VALUE: - continue - - try: - authority = recover_authority(auth) - except InvalidSignatureError: - continue - - message.accessed_addresses.add(authority) - - authority_account = get_account(tx_state, authority) - authority_code = get_code(tx_state, authority_account.code_hash) - - if authority_code and not is_valid_delegation(authority_code): - continue - - authority_nonce = authority_account.nonce - if authority_nonce != auth.nonce: - continue + match validate_authorization(message, auth): + case None: + continue + case authority: + pass if account_exists(tx_state, authority): refund_counter += U256( diff --git a/src/ethereum/forks/prague/vm/eoa_delegation.py b/src/ethereum/forks/prague/vm/eoa_delegation.py index 1ba552f8dd6..564b165bbee 100644 --- a/src/ethereum/forks/prague/vm/eoa_delegation.py +++ b/src/ethereum/forks/prague/vm/eoa_delegation.py @@ -155,6 +155,43 @@ def access_delegation( return True, address, code, access_gas_cost +def validate_authorization( + message: Message, auth: Authorization +) -> None | Address: + """ + Check if the given `Authorization` is valid against the current state. + + Returns the `authority` address or `None` if the validation was + unsuccessful. + """ + tx_state = message.tx_env.state + + if auth.chain_id not in (message.block_env.chain_id, U256(0)): + return None + + if auth.nonce >= U64.MAX_VALUE: + return None + + try: + authority = recover_authority(auth) + except InvalidSignatureError: + return None + + message.accessed_addresses.add(authority) + + authority_account = get_account(tx_state, authority) + authority_code = get_code(tx_state, authority_account.code_hash) + + if authority_code and not is_valid_delegation(authority_code): + return None + + authority_nonce = authority_account.nonce + if authority_nonce != auth.nonce: + return None + + return authority + + def set_delegation(message: Message) -> U256: """ Set the delegation code for the authorities in the message. @@ -173,28 +210,11 @@ def set_delegation(message: Message) -> U256: tx_state = message.tx_env.state refund_counter = U256(0) for auth in message.tx_env.authorizations: - if auth.chain_id not in (message.block_env.chain_id, U256(0)): - continue - - if auth.nonce >= U64.MAX_VALUE: - continue - - try: - authority = recover_authority(auth) - except InvalidSignatureError: - continue - - message.accessed_addresses.add(authority) - - authority_account = get_account(tx_state, authority) - authority_code = get_code(tx_state, authority_account.code_hash) - - if authority_code and not is_valid_delegation(authority_code): - continue - - authority_nonce = authority_account.nonce - if authority_nonce != auth.nonce: - continue + match validate_authorization(message, auth): + case None: + continue + case authority: + pass if account_exists(tx_state, authority): refund_counter += U256( From c9a7c8dbbd327fc428755fdc611e3bf6b63c95b1 Mon Sep 17 00:00:00 2001 From: Sam Wilson Date: Wed, 17 Jun 2026 13:46:18 -0400 Subject: [PATCH 036/233] chore(specs): clean is_valid_delegation --- src/ethereum/forks/amsterdam/vm/eoa_delegation.py | 6 ++---- src/ethereum/forks/bpo1/vm/eoa_delegation.py | 6 ++---- src/ethereum/forks/bpo2/vm/eoa_delegation.py | 6 ++---- src/ethereum/forks/bpo3/vm/eoa_delegation.py | 6 ++---- src/ethereum/forks/bpo4/vm/eoa_delegation.py | 6 ++---- src/ethereum/forks/bpo5/vm/eoa_delegation.py | 6 ++---- src/ethereum/forks/osaka/vm/eoa_delegation.py | 6 ++---- src/ethereum/forks/prague/vm/eoa_delegation.py | 6 ++---- 8 files changed, 16 insertions(+), 32 deletions(-) diff --git a/src/ethereum/forks/amsterdam/vm/eoa_delegation.py b/src/ethereum/forks/amsterdam/vm/eoa_delegation.py index 563a8c430d0..8237225c713 100644 --- a/src/ethereum/forks/amsterdam/vm/eoa_delegation.py +++ b/src/ethereum/forks/amsterdam/vm/eoa_delegation.py @@ -50,12 +50,10 @@ def is_valid_delegation(code: bytes) -> bool: False otherwise. """ - if ( + return ( len(code) == EOA_DELEGATED_CODE_LENGTH and code[:EOA_DELEGATION_MARKER_LENGTH] == EOA_DELEGATION_MARKER - ): - return True - return False + ) def get_delegated_code_address(code: bytes) -> Optional[Address]: diff --git a/src/ethereum/forks/bpo1/vm/eoa_delegation.py b/src/ethereum/forks/bpo1/vm/eoa_delegation.py index 564b165bbee..e6c66328baa 100644 --- a/src/ethereum/forks/bpo1/vm/eoa_delegation.py +++ b/src/ethereum/forks/bpo1/vm/eoa_delegation.py @@ -49,12 +49,10 @@ def is_valid_delegation(code: bytes) -> bool: False otherwise. """ - if ( + return ( len(code) == EOA_DELEGATED_CODE_LENGTH and code[:EOA_DELEGATION_MARKER_LENGTH] == EOA_DELEGATION_MARKER - ): - return True - return False + ) def get_delegated_code_address(code: bytes) -> Optional[Address]: diff --git a/src/ethereum/forks/bpo2/vm/eoa_delegation.py b/src/ethereum/forks/bpo2/vm/eoa_delegation.py index 564b165bbee..e6c66328baa 100644 --- a/src/ethereum/forks/bpo2/vm/eoa_delegation.py +++ b/src/ethereum/forks/bpo2/vm/eoa_delegation.py @@ -49,12 +49,10 @@ def is_valid_delegation(code: bytes) -> bool: False otherwise. """ - if ( + return ( len(code) == EOA_DELEGATED_CODE_LENGTH and code[:EOA_DELEGATION_MARKER_LENGTH] == EOA_DELEGATION_MARKER - ): - return True - return False + ) def get_delegated_code_address(code: bytes) -> Optional[Address]: diff --git a/src/ethereum/forks/bpo3/vm/eoa_delegation.py b/src/ethereum/forks/bpo3/vm/eoa_delegation.py index 564b165bbee..e6c66328baa 100644 --- a/src/ethereum/forks/bpo3/vm/eoa_delegation.py +++ b/src/ethereum/forks/bpo3/vm/eoa_delegation.py @@ -49,12 +49,10 @@ def is_valid_delegation(code: bytes) -> bool: False otherwise. """ - if ( + return ( len(code) == EOA_DELEGATED_CODE_LENGTH and code[:EOA_DELEGATION_MARKER_LENGTH] == EOA_DELEGATION_MARKER - ): - return True - return False + ) def get_delegated_code_address(code: bytes) -> Optional[Address]: diff --git a/src/ethereum/forks/bpo4/vm/eoa_delegation.py b/src/ethereum/forks/bpo4/vm/eoa_delegation.py index 564b165bbee..e6c66328baa 100644 --- a/src/ethereum/forks/bpo4/vm/eoa_delegation.py +++ b/src/ethereum/forks/bpo4/vm/eoa_delegation.py @@ -49,12 +49,10 @@ def is_valid_delegation(code: bytes) -> bool: False otherwise. """ - if ( + return ( len(code) == EOA_DELEGATED_CODE_LENGTH and code[:EOA_DELEGATION_MARKER_LENGTH] == EOA_DELEGATION_MARKER - ): - return True - return False + ) def get_delegated_code_address(code: bytes) -> Optional[Address]: diff --git a/src/ethereum/forks/bpo5/vm/eoa_delegation.py b/src/ethereum/forks/bpo5/vm/eoa_delegation.py index 564b165bbee..e6c66328baa 100644 --- a/src/ethereum/forks/bpo5/vm/eoa_delegation.py +++ b/src/ethereum/forks/bpo5/vm/eoa_delegation.py @@ -49,12 +49,10 @@ def is_valid_delegation(code: bytes) -> bool: False otherwise. """ - if ( + return ( len(code) == EOA_DELEGATED_CODE_LENGTH and code[:EOA_DELEGATION_MARKER_LENGTH] == EOA_DELEGATION_MARKER - ): - return True - return False + ) def get_delegated_code_address(code: bytes) -> Optional[Address]: diff --git a/src/ethereum/forks/osaka/vm/eoa_delegation.py b/src/ethereum/forks/osaka/vm/eoa_delegation.py index bb67bb57c2f..99b29941b57 100644 --- a/src/ethereum/forks/osaka/vm/eoa_delegation.py +++ b/src/ethereum/forks/osaka/vm/eoa_delegation.py @@ -49,12 +49,10 @@ def is_valid_delegation(code: bytes) -> bool: False otherwise. """ - if ( + return ( len(code) == EOA_DELEGATED_CODE_LENGTH and code[:EOA_DELEGATION_MARKER_LENGTH] == EOA_DELEGATION_MARKER - ): - return True - return False + ) def get_delegated_code_address(code: bytes) -> Optional[Address]: diff --git a/src/ethereum/forks/prague/vm/eoa_delegation.py b/src/ethereum/forks/prague/vm/eoa_delegation.py index 564b165bbee..e6c66328baa 100644 --- a/src/ethereum/forks/prague/vm/eoa_delegation.py +++ b/src/ethereum/forks/prague/vm/eoa_delegation.py @@ -49,12 +49,10 @@ def is_valid_delegation(code: bytes) -> bool: False otherwise. """ - if ( + return ( len(code) == EOA_DELEGATED_CODE_LENGTH and code[:EOA_DELEGATION_MARKER_LENGTH] == EOA_DELEGATION_MARKER - ): - return True - return False + ) def get_delegated_code_address(code: bytes) -> Optional[Address]: From fcd46ca03fd4ec098274bf8e7929c800922a0fe3 Mon Sep 17 00:00:00 2001 From: danceratopz Date: Thu, 18 Jun 2026 10:09:11 +0200 Subject: [PATCH 037/233] chore(tooling): update write-test skill for automatic tx gas-limit (#2995) Co-authored-by: raxhvl <10168946+raxhvl@users.noreply.github.com> --- .claude/commands/write-test.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.claude/commands/write-test.md b/.claude/commands/write-test.md index cbc8db3b601..f8ef60cdede 100644 --- a/.claude/commands/write-test.md +++ b/.claude/commands/write-test.md @@ -45,6 +45,12 @@ Conventions and patterns for writing consensus tests. Run this skill before writ - `fork.gas_costs()` returns `GasCosts` dataclass with constants like `G_WARM_SLOAD`, `G_COLD_ACCOUNT_ACCESS`, `G_BASE`, etc. - `fork.transaction_intrinsic_cost_calculator()` for computing tx intrinsic gas +## Transactions + +- Rule: omit `gas_limit`. It auto-fills so the transaction executes in full without running out of gas. +- Exception: set `gas_limit` explicitly for gas-sensitive tests (intrinsic-gas boundaries, OOG, code-deposit limits, or gas metering). +- Anti-pattern: the `gas_limit=fork.transaction_gas_limit_cap()` boilerplate is now redundant. + ## Exception Testing - Pass `error=TransactionException.INTRINSIC_GAS_TOO_LOW` to `Transaction` From 5f8c109f75d51bca8b0dd04750c80bcea4fc995f Mon Sep 17 00:00:00 2001 From: danceratopz Date: Thu, 18 Jun 2026 10:14:47 +0200 Subject: [PATCH 038/233] feat(tests): add max-code-size jumpdest test for immediate bytes (#2998) Add `test_max_code_size_jumpdest_in_immediate`, which places a `0x5B` as the last byte of a `MAX_CODE_SIZE` contract, right after an immediate-carrying opcode, and jumps to it: - `PUSH1`: The `0x5B` is push data, always skipped, so the jump is rejected. - `DUPN`/`SWAPN`/`EXCHANGE`: Per EIP-8024 the `0x5B` is an invalid immediate, kept at an instruction boundary, so it stays a valid `JUMPDEST` and the jump is accepted. Exercises the immediate-skipping branches of jumpdest analysis well past the old 24 KiB code and 48 KiB initcode limits. --- .../test_cases.md | 1 + .../test_max_code_size.py | 62 +++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/tests/amsterdam/eip7954_increase_max_contract_size/test_cases.md b/tests/amsterdam/eip7954_increase_max_contract_size/test_cases.md index bb6c676639b..786f01340da 100644 --- a/tests/amsterdam/eip7954_increase_max_contract_size/test_cases.md +++ b/tests/amsterdam/eip7954_increase_max_contract_size/test_cases.md @@ -11,6 +11,7 @@ | `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_high_jumpdest` | Enforce JUMP destination validity and code execution past the old size limits | Deploy a `MAX_CODE_SIZE` contract that jumps near the new limit (far beyond the old 24 KiB code and 48 KiB initcode limits), to a real `JUMPDEST` or to a `PUSH1` byte, and call it via a caller that records the call result. | Valid `JUMPDEST`: jump succeeds, the contract executes at the high offset and stores a sentinel. Non-`JUMPDEST`: jump is rejected, call fails, nothing is stored. | ✅ Completed | +| `test_max_code_size_jumpdest_in_immediate` | Verify jumpdest analysis classifies a `0x5B` immediate byte at max code size | Place a `0x5B` as the last byte of a `MAX_CODE_SIZE` contract, right after a `PUSH1`/`DUPN`/`SWAPN`/`EXCHANGE`, and jump to it. | `PUSH1`: `0x5B` is data, skipped, jump rejected. `DUPN`/`SWAPN`/`EXCHANGE`: per EIP-8024 `0x5B` is an invalid immediate, kept as a `JUMPDEST`, jump accepted. | ✅ 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_warm_after_failed_create_over_max_code_size` | A failed CREATE/CREATE2 over max code size leaves the would-be address warm | A creator runs CREATE/CREATE2 whose initcode returns `MAX_CODE_SIZE + 1` bytes; a checker then measures the gas of a `BALANCE` on that address. | The address is warm: the post-RETURN size-check rejection still leaves it in the access list. | ✅ Completed | | `test_max_code_size_fork_transition` | New `MAX_CODE_SIZE` activates exactly at the fork boundary | Before and after the fork, deploy a contract one byte over the parent fork's max code size (valid under the new limit; its initcode stays within both forks' initcode limits). | Pre-fork: deployment fails at code deposit (exceeds old limit). Post-fork: deployment succeeds. | ✅ Completed | 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 c1a2ff5f13f..5fbee4e337d 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 @@ -8,6 +8,7 @@ from execution_testing import ( Account, Alloc, + Bytecode, CodeGasMeasure, Fork, Initcode, @@ -411,3 +412,64 @@ def test_max_code_size_high_jumpdest( } state_test(pre=pre, tx=tx, post=post) + + +@pytest.mark.parametrize( + "tail,accepted", + [ + pytest.param(Op.PUSH1(0x5B), False, id="push1_data_rejected"), + pytest.param(Op.DUPN[b"\x5b"], True, id="dupn_immediate_accepted"), + pytest.param(Op.SWAPN[b"\x5b"], True, id="swapn_immediate_accepted"), + pytest.param( + Op.EXCHANGE[b"\x5b"], True, id="exchange_immediate_accepted" + ), + ], +) +def test_max_code_size_jumpdest_in_immediate( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + tail: Bytecode, + accepted: bool, +) -> None: + """ + Ensure jumpdest analysis classifies a `0x5B` immediate byte correctly at + the new max code size. + + A `0x5B` sits as the last byte of a `MAX_CODE_SIZE` contract, right after + an immediate-carrying opcode, and the contract jumps to it: + + - ``push1_data_rejected``: the `0x5B` is `PUSH` data, always skipped by + the analysis, so it is not a `JUMPDEST` and the jump is rejected. + - ``dupn``/``swapn``/``exchange``: per EIP-8024 `0x5B` is an *invalid* + immediate for these opcodes, so it is not skipped and stays a valid + `JUMPDEST`, and the jump is accepted. + + Exercises the immediate-skipping branches of jumpdest analysis well past + the old 24 KiB code and 48 KiB initcode limits. + """ + jump_target = fork.max_code_size() - 1 # the 0x5B is the last byte + push_size = (jump_target.bit_length() + 7) // 8 + push_op = getattr(Op, f"PUSH{push_size}") + prefix = Op.SSTORE(0, 1) + push_op(jump_target) + Op.JUMP + filler_len = fork.max_code_size() - len(prefix) - len(tail) + target_code = prefix + Op.INVALID * filler_len + tail + assert len(target_code) == fork.max_code_size() + assert bytes(target_code)[jump_target] == 0x5B + + target = pre.deploy_contract(target_code) + caller = pre.deploy_contract( + Op.SSTORE(0, Op.CALL(gas=Op.GAS, address=target)) + Op.STOP + ) + + tx = Transaction(sender=pre.fund_eoa(), to=caller) + + # Accepted: jump completes, the call succeeds (1), the store is kept. + # Rejected: jump reverts, the call fails (0), nothing is stored. + stored = 1 if accepted else 0 + post = { + caller: Account(storage={0: stored}), + target: Account(storage={0: stored}), + } + + state_test(pre=pre, tx=tx, post=post) From a8876ca0182e0043ffcfff01033692e36b0f0cb5 Mon Sep 17 00:00:00 2001 From: spencer Date: Thu, 18 Jun 2026 15:38:29 +0100 Subject: [PATCH 039/233] feat(spec-specs, tests): update EIP-8037 to use source based refunds (#2999) Co-authored-by: kclowes --- src/ethereum/forks/amsterdam/fork.py | 13 +- src/ethereum/forks/amsterdam/vm/__init__.py | 62 +++-- src/ethereum/forks/amsterdam/vm/gas.py | 1 + .../forks/amsterdam/vm/interpreter.py | 5 +- .../test_block_access_lists_opcodes.py | 17 +- .../test_state_gas_call.py | 67 ++--- .../test_state_gas_create.py | 94 ++++--- .../test_state_gas_reservoir.py | 259 ++++++++---------- .../test_state_gas_sstore.py | 94 ++++--- .../test_mcopy_memory_expansion.py | 7 +- 10 files changed, 310 insertions(+), 309 deletions(-) diff --git a/src/ethereum/forks/amsterdam/fork.py b/src/ethereum/forks/amsterdam/fork.py index f6c4afaba21..6506a15ca49 100644 --- a/src/ethereum/forks/amsterdam/fork.py +++ b/src/ethereum/forks/amsterdam/fork.py @@ -1066,15 +1066,10 @@ def process_transaction( tx_output = process_message_call(message) - if tx_output.error is not None: - tx_output.state_gas_left = Uint( - int(tx_output.state_gas_left) + tx_output.state_gas_used - ) - tx_output.state_gas_used = 0 - if isinstance(tx.to, Bytes0): - new_account_refund = StateGasCosts.NEW_ACCOUNT - tx_output.state_gas_left += new_account_refund - tx_output.state_refund += new_account_refund + if tx_output.error is not None and isinstance(tx.to, Bytes0): + new_account_refund = StateGasCosts.NEW_ACCOUNT + tx_output.state_gas_left += new_account_refund + tx_output.state_refund += new_account_refund tx_gas_used_before_refund = ( tx.gas - tx_output.gas_left - tx_output.state_gas_left diff --git a/src/ethereum/forks/amsterdam/vm/__init__.py b/src/ethereum/forks/amsterdam/vm/__init__.py index 3c12b81e474..d49759578e7 100644 --- a/src/ethereum/forks/amsterdam/vm/__init__.py +++ b/src/ethereum/forks/amsterdam/vm/__init__.py @@ -186,18 +186,17 @@ class Evm: accessed_storage_keys: Set[Tuple[Address, Bytes32]] regular_gas_used: Uint = Uint(0) state_gas_used: int = 0 - """ - State gas that has been consumed by this execution frame and its - children. - - `state_gas_used` may go negative when the refund matches an - ancestor's charge (e.g. an `SSTORE` clearing a slot a parent set). - """ + state_gas_spilled: Uint = Uint(0) def credit_state_gas_refund(evm: Evm, amount: Uint) -> None: """ - Credit an inline state gas refund to the local frame's reservoir. + Credit a state gas refund to the local frame, in LIFO order. + + State-gas charges draw from the reservoir first and from `gas_left` + last, so refills credit the pool charged last first: `gas_left` up + to `state_gas_spilled`, then the reservoir. This restores the + exact pools the charge drew from, so the two never drift. Parameters ---------- @@ -207,7 +206,10 @@ def credit_state_gas_refund(evm: Evm, amount: Uint) -> None: The refund amount to credit. """ - evm.state_gas_left += amount + from_gas_left = min(amount, evm.state_gas_spilled) + evm.gas_left += from_gas_left + evm.state_gas_spilled -= from_gas_left + evm.state_gas_left += amount - from_gas_left evm.state_gas_used -= int(amount) @@ -225,6 +227,7 @@ def incorporate_child_on_success(evm: Evm, child_evm: Evm) -> None: """ evm.gas_left += child_evm.gas_left evm.state_gas_left += child_evm.state_gas_left + evm.state_gas_spilled += child_evm.state_gas_spilled evm.logs += child_evm.logs evm.refund_counter += child_evm.refund_counter evm.accounts_to_delete.update(child_evm.accounts_to_delete) @@ -234,6 +237,30 @@ def incorporate_child_on_success(evm: Evm, child_evm: Evm) -> None: evm.state_gas_used += child_evm.state_gas_used +def refill_frame_state_gas(evm: Evm) -> None: + """ + Roll back the frame's state gas in LIFO order on revert or halt. + + The frame's state changes are undone, so the state gas it consumed + is credited back to `gas_left` first and then to the reservoir, + restoring the pools the charges drew from. + + Parameters + ---------- + evm : + The frame whose state gas is rolled back. + + """ + evm.gas_left += evm.state_gas_spilled + evm.state_gas_left = Uint( + int(evm.state_gas_left) + + evm.state_gas_used + - int(evm.state_gas_spilled) + ) + evm.state_gas_used = 0 + evm.state_gas_spilled = Uint(0) + + def incorporate_child_on_error( evm: Evm, child_evm: Evm, @@ -241,12 +268,11 @@ def incorporate_child_on_error( """ Incorporate the state of an unsuccessful `child_evm` into the parent `evm`. - State is rolled back, restoring all state gas to the parent's - reservoir via the `state_gas_left + state_gas_used` invariant. The - child's `state_gas_used` is not inherited (only the success path - propagates it), satisfying the EIP-8037 revert rule that - `execution_state_gas_used` decreases by the child's charged state - gas. Inline refunds roll back with their matching charges. + The child rolls back its own state gas via `refill_frame_state_gas` + before returning (on both reverts and exceptional halts), so its + `gas_left` and reservoir already reflect the LIFO refill and its + `state_gas_used` is zero. The parent therefore only reabsorbs the + child's `gas_left` and reservoir. Parameters ---------- @@ -257,11 +283,7 @@ def incorporate_child_on_error( """ evm.gas_left += child_evm.gas_left - evm.state_gas_left = Uint( - int(evm.state_gas_left) - + child_evm.state_gas_used - + int(child_evm.state_gas_left) - ) + evm.state_gas_left += child_evm.state_gas_left evm.regular_gas_used += child_evm.regular_gas_used diff --git a/src/ethereum/forks/amsterdam/vm/gas.py b/src/ethereum/forks/amsterdam/vm/gas.py index c9eabffa4a9..7ce827a2407 100644 --- a/src/ethereum/forks/amsterdam/vm/gas.py +++ b/src/ethereum/forks/amsterdam/vm/gas.py @@ -311,6 +311,7 @@ def charge_state_gas(evm: Evm, amount: Uint) -> None: remainder = amount - evm.state_gas_left evm.state_gas_left = Uint(0) evm.gas_left -= remainder + evm.state_gas_spilled += remainder else: raise OutOfGasError diff --git a/src/ethereum/forks/amsterdam/vm/interpreter.py b/src/ethereum/forks/amsterdam/vm/interpreter.py index 2ef5719ce34..71ecb2d2bcc 100644 --- a/src/ethereum/forks/amsterdam/vm/interpreter.py +++ b/src/ethereum/forks/amsterdam/vm/interpreter.py @@ -53,7 +53,7 @@ charge_state_gas, ) from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS -from . import Evm, emit_transfer_log +from . import Evm, emit_transfer_log, refill_frame_state_gas from .exceptions import ( AddressCollision, ExceptionalHalt, @@ -241,6 +241,7 @@ def process_create_message(message: Message) -> Evm: charge_state_gas(evm, code_deposit_state_gas) except ExceptionalHalt as error: restore_tx_state(tx_state, snapshot) + refill_frame_state_gas(evm) evm.regular_gas_used += evm.gas_left evm.gas_left = Uint(0) evm.output = b"" @@ -329,12 +330,14 @@ def process_message(message: Message) -> Evm: except ExceptionalHalt as error: evm_trace(evm, OpException(error)) + refill_frame_state_gas(evm) evm.regular_gas_used += evm.gas_left evm.gas_left = Uint(0) evm.output = b"" evm.error = error except Revert as error: evm_trace(evm, OpException(error)) + refill_frame_state_gas(evm) evm.error = error if evm.error: diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py index 658decefdde..010f891fccb 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py @@ -3288,7 +3288,16 @@ def test_bal_create_and_oog( init_code_size=len(init_code_bytes), ) factory_sstore = Op.SSTORE(0x00, 1) - factory_code = factory_mstore + factory_create + factory_sstore + oog_sink_memory_size = 10000 * 32 + factory_oog_sink = Op.MSTORE( + oog_sink_memory_size - 32, + 0, + old_memory_size=32, + new_memory_size=oog_sink_memory_size, + ) + factory_code = ( + factory_mstore + factory_create + factory_oog_sink + factory_sstore + ) factory = pre.deploy_contract( code=factory_code, @@ -3313,10 +3322,11 @@ def test_bal_create_and_oog( gas_limit = intrinsic_cost + create_static_cost - 1 elif oog_boundary == OutOfGasBoundary.OOG_AFTER_TARGET_ACCESS: # Exactly the CREATE static cost — address accessed, child - # frame gets 0 gas, CREATE fails, parent OOGs at next opcode + # frame gets 0 gas, CREATE fails, sink forces OOG after access gas_limit = intrinsic_cost + create_static_cost else: - # Full success: static cost + child frame (63/64 rule) + SSTORE + # Full success: static cost + child frame (63/64 rule) + + # SSTORE + gas sink. child_gas = init_code.gas_cost(fork) remaining_needed = (child_gas * 64 + 62) // 63 gas_limit = ( @@ -3324,6 +3334,7 @@ def test_bal_create_and_oog( + create_static_cost + remaining_needed + factory_sstore.gas_cost(fork) + + factory_oog_sink.gas_cost(fork) ) tx = Transaction( diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py index d90bfbcb63b..0afbf769a4c 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py @@ -206,11 +206,13 @@ def test_reservoir_restored_after_child_spill_and_revert( Test all state gas recovered when child spills then reverts. The child performs two SSTOREs (zero-to-nonzero) but only one - SSTORE's worth of state gas fits in the reservoir — the second + SSTORE's worth of state gas fits in the reservoir, so the second spills into `gas_left`. The child then REVERTs. Because state - changes are rolled back, all state gas (reservoir + spill) is - restored to the parent's reservoir. The parent can then perform - two SSTOREs using only the recovered reservoir. + changes are rolled back, the state gas is refilled LIFO: the + spilled portion returns to `gas_left` and the reservoir-funded + portion restores the reservoir to its start value. The parent + then performs two SSTOREs, drawing one from the restored + reservoir and spilling the other from the recovered `gas_left`. """ sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) @@ -224,8 +226,8 @@ def test_reservoir_restored_after_child_spill_and_revert( parent = pre.deploy_contract( code=( Op.POP(Op.CALL(gas=500_000, address=child)) - # All state gas recovered (reservoir + spill), parent - # can perform two SSTOREs from the recovered reservoir + # State gas recovered LIFO: the spilled SSTORE returns to + # gas_left, the other restores the reservoir + Op.SSTORE(parent_storage.store_next(1), 1) + Op.SSTORE(parent_storage.store_next(1), 1) ), @@ -335,10 +337,11 @@ def test_sequential_calls_reservoir_restored_between_reverts( """ Test reservoir restored across sequential child reverts. - Parent calls child1 which spills and reverts, then calls child2 - which also uses state gas from the restored reservoir. Both - child failures restore the reservoir, so the parent can use it - for its own SSTORE at the end. + Parent calls child1, which uses the reservoir for an SSTORE and + reverts, restoring the reservoir. It then calls child2, which + reuses the restored reservoir and reverts, restoring it again. + The parent then performs its own SSTORE from the restored + reservoir. """ sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) @@ -1282,7 +1285,7 @@ def test_call_value_to_pre_existing_selfdestructed_account( ], ) @pytest.mark.valid_from("EIP8037") -def test_top_level_halt_refunds_total_state_gas( +def test_top_level_halt_burns_spilled_state_gas( blockchain_test: BlockchainTestFiller, pre: Alloc, fork: Fork, @@ -1290,22 +1293,24 @@ def test_top_level_halt_refunds_total_state_gas( reservoir_delta: int, ) -> None: """ - Verify a top-level halt refunds the total state-gas consumed - (reservoir-portion + spilled-portion) regardless of child failure - mode. The parent calls a child that either reverts or halts, then - INVALIDs at the top level. - - Per the updated EIP, both child failure modes propagate the full - `state_gas_used` back through `incorporate_child_on_error`, and - the top-level halt no longer overrides it. The tx-level error - handler then folds the residual into the reservoir, so - `state_gas_left_end = max(reservoir, child_charge)` and - `tx_gas_used = tx.gas - state_gas_left_end`: - - - `reservoir < child_charge` (one_short): spill is refunded too, - `tx_gas_used = gas_limit_cap - (child_charge - reservoir)`. - - `reservoir >= child_charge`: no spill, `tx_gas_used = - gas_limit_cap`. + Verify a top-level halt burns the spilled state gas, so only the + start reservoir survives. The parent calls a child that reverts or + halts, then INVALIDs at the top level. + + Under LIFO refills a frame's spilled state gas refills to + `gas_left`, which the halt then zeros. Only the reservoir-funded + portion survives, equal to the reservoir at frame start. + + That start value equals the sized reservoir R, so for every child + failure mode and `reservoir_delta`: + + `state_gas_left_end = R`, + `tx_gas_used = tx.gas - R = gas_limit_cap`. + + With the reservoir one short (`reservoir_delta == -1`) the child's + SSTORE spills one unit from `gas_left`, which is refilled then + burned by the halt. So `tx_gas_used` stays `gas_limit_cap`. The + old behavior refunded the spill, giving `gas_limit_cap - 1`. """ gas_limit_cap = fork.transaction_gas_limit_cap() assert gas_limit_cap is not None @@ -1331,10 +1336,10 @@ def test_top_level_halt_refunds_total_state_gas( sender=pre.fund_eoa(), ) - # Policy A halt: state_gas counters preserved through the child - # halt/revert, parent halt, and tx-level fold. - # state_gas_left_end = max(reservoir, sstore_state_gas). - state_gas_left_end = max(reservoir, sstore_state_gas) + # LIFO refills: the spill refills to `gas_left` and is burned by + # the halt. Only the sized reservoir survives, so + # `tx_gas_used = gas_limit_cap`. + state_gas_left_end = reservoir expected_gas_used = tx_gas - state_gas_left_end blockchain_test( diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py index 54d0cc76bfe..ed0be4e1d9f 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py @@ -635,24 +635,27 @@ def test_parent_state_gas_after_child_failure( """ Test parent state-gas pools after CREATE child failure. - A factory invokes CREATE whose initcode performs an SSTORE - (charging state gas) then either REVERTs or hits INVALID. The - factory's own SSTORE after the failed CREATE acts as the - discriminator that the parent's state-gas accounting (reservoir - and gas_left) is in the expected state. + A factory runs CREATE whose initcode does an SSTORE, then either + REVERTs or hits INVALID. The factory's own SSTORE after the failed + CREATE checks the parent's reservoir and gas_left are correct. + + Under EIP-8037 state-gas refunds are LIFO. Gas spilled from + gas_left refunds to gas_left, only the reservoir-funded portion + returns to the reservoir. Four scenarios cover the gas-pool state space: - - `with_reservoir x revert`: child state gas (new account + - initcode SSTORE) is fully refunded to the parent reservoir on - REVERT. - - `with_reservoir x halt`: HALT resets the child frame to - `(0, R0_child)`; only the reservoir-portion entering the - initcode is returned, any spilled gas stays burned. - - `no_reservoir x revert`: child state gas refunded forms a - fresh reservoir even though `R0_parent` started at 0. - - `no_reservoir x halt`: no phantom reservoir may form; the - factory's post-CREATE SSTORE must spill from gas_left. + - `with_reservoir x revert`: child state gas refills LIFO. The + reservoir-funded portion returns to the parent reservoir, any + spill to the parent gas_left. + - `with_reservoir x halt`: halt refills the child frame LIFO then + burns its gas_left. Only the child's start reservoir survives. + - `no_reservoir x revert`: child state gas spilled wholly from + gas_left, so the LIFO refill returns it there. No phantom + reservoir forms. + - `no_reservoir x halt`: no phantom reservoir forms. The spilled + child state gas is burned with the child gas_left and the + factory's post-CREATE SSTORE spills from gas_left. """ gas_limit_cap = fork.transaction_gas_limit_cap() assert gas_limit_cap is not None @@ -728,21 +731,21 @@ def test_parent_state_gas_after_child_failure( initcode_regular_revert = initcode.gas_cost(fork) - sstore_state_gas if failure_op == Op.INVALID: - # Simulate runtime gas accounting for HALT using fork helpers: - # 1. Initial regular pool capped by transaction_gas_limit_cap; + # Simulate runtime gas for HALT under EIP-8037 LIFO refills: + # 1. Regular pool capped by transaction_gas_limit_cap. The # remainder forms the state reservoir. - # 2. CREATE op charges new_account state gas (from reservoir - # first, spilled to gas_left otherwise). - # 3. 63/64 retention rule: parent retains gas_left // 64. - # 4. INVALID burns all forwarded regular gas in the child. - # Per the updated EIP, child halt preserves its state-gas - # counters and `incorporate_child_on_error` refunds the - # full child charge — including any spilled portion — to - # the parent's reservoir. - # 5. CREATE failure refunds new_account state gas to the - # parent's state pool (account creation rolled back). - # 6. Factory's post-CREATE SSTORE charges sstore_state_gas - # (state pool first, spilled to gas_left otherwise). + # 2. CREATE charges new_account state gas, reservoir first + # then spilled to gas_left and tracked. + # 3. 63/64 retention: parent keeps gas_left // 64. The + # reservoir is forwarded to the child frame. + # 4. Child initcode SSTORE charges sstore_state_gas, child + # reservoir first then spilled to child gas_left. + # 5. INVALID refills the child frame LIFO then burns its + # gas_left. Only the child's start reservoir survives. + # 6. CREATE failure refills new_account LIFO: the spill to + # parent gas_left, the rest to the parent reservoir. + # 7. Factory post-CREATE SSTORE charges sstore_state_gas, + # reservoir first then spilled to gas_left. execution_gas = gas_limit - intrinsic_cost regular_budget = gas_limit_cap - intrinsic_cost sim_gas_left = min(regular_budget, execution_gas) @@ -751,26 +754,31 @@ def test_parent_state_gas_after_child_failure( sim_gas_left -= factory_pre_create_regular sim_gas_left -= gas_costs.OPCODE_CREATE_BASE + init_code_word_cost - if sim_state_gas_left >= new_account_state_gas: - sim_state_gas_left -= new_account_state_gas - else: - sim_gas_left -= new_account_state_gas - sim_state_gas_left - sim_state_gas_left = 0 + # CREATE new_account state gas: reservoir first, spill tracked. + new_account_from_reservoir = min( + sim_state_gas_left, new_account_state_gas + ) + new_account_spill = new_account_state_gas - new_account_from_reservoir + sim_state_gas_left -= new_account_from_reservoir + sim_gas_left -= new_account_spill - # `child_reservoir` is what the parent forwards to the child. - # Under Policy A halt, incorporate refunds child.state_gas_used - # + child.state_gas_left = max(sstore, child_reservoir) back to - # the parent. The simulator already implicitly retains - # `child_reservoir` in `sim_state_gas_left`, so the additional - # Policy A refund versus the Policy B "burn the spill" rule is - # `max(0, sstore_state_gas - child_reservoir)`. + # 63/64 retention: parent keeps gas_left // 64. The reservoir + # is forwarded to the child frame and survives on halt. child_reservoir = sim_state_gas_left sim_gas_left = sim_gas_left // 64 - sim_state_gas_left += max(0, sstore_state_gas - child_reservoir) - sim_state_gas_left += new_account_state_gas + + # INVALID burns child gas_left, including any spilled SSTORE + # state gas. Only the forwarded reservoir survives. + sim_state_gas_left = child_reservoir + + # CREATE failure refills new_account LIFO: spilled portion to + # gas_left, reservoir-funded portion to the reservoir. + sim_gas_left += new_account_spill + sim_state_gas_left += new_account_from_reservoir sim_gas_left -= factory_post_create_regular + # Factory post-CREATE SSTORE: reservoir first, spill otherwise. if sim_state_gas_left >= sstore_state_gas: sim_state_gas_left -= sstore_state_gas else: diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_reservoir.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_reservoir.py index 1da756d4a2f..b949c150b00 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_reservoir.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_reservoir.py @@ -931,73 +931,13 @@ def test_subcall_failure_does_not_zero_top_level_state_gas( @pytest.mark.parametrize( - "failure_mode", + "spill_source", [ - pytest.param("revert", id="revert"), - pytest.param("halt", id="halt"), + pytest.param("own", id="own_spill"), + pytest.param("propagated", id="propagated_spill"), + pytest.param("both", id="own_and_propagated_spill"), ], ) -@pytest.mark.valid_from("EIP8037") -def test_top_level_failure_spilled_state_gas( - state_test: StateTestFiller, - pre: Alloc, - fork: Fork, - failure_mode: str, -) -> None: - """ - Verify the top-level failure handling for state gas that spilled - from the reservoir into `gas_left`. - - When the reservoir is smaller than the state gas charge, the - overflow spills and is drawn from `gas_left`. Both failure - modes refund the full `state_gas_used` (reservoir-portion + - spilled-portion) to the reservoir per the updated EIP. They - differ only in `gas_left` handling: - - - REVERT preserves `gas_left`; sender billed only the regular - component. - - Exceptional halt zeros `gas_left` (existing EVM rule); sender - pays for everything except the state-gas refund. - """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) - intrinsic_cost = fork.transaction_intrinsic_cost_calculator()() - - if failure_mode == "revert": - code = Op.SSTORE(0, 1) + Op.REVERT(0, 0) - else: - code = Op.SSTORE(0, 1) + Op.INVALID - contract = pre.deploy_contract(code=code) - - # Reservoir sized to cover only half the SSTORE state gas; the - # other half spills into gas_left. - tx_gas = gas_limit_cap + sstore_state_gas // 2 - - if failure_mode == "revert": - # gas_left preserved; full state_gas_used refunded to - # reservoir → sender billed only the regular component. - expected_cumulative = ( - intrinsic_cost + code.gas_cost(fork) - sstore_state_gas - ) - else: - # gas_left burned; full state_gas_used (reservoir-portion + - # spilled-portion) refunded via reservoir. - # tx_gas_used = tx_gas - 0 - sstore_state_gas. - expected_cumulative = tx_gas - sstore_state_gas - - tx = Transaction( - to=contract, - state_gas_reservoir=sstore_state_gas // 2, - sender=pre.fund_eoa(), - expected_receipt=TransactionReceipt( - cumulative_gas_used=expected_cumulative, - ), - ) - - state_test(pre=pre, post={contract: Account(storage={})}, tx=tx) - - @pytest.mark.parametrize( "failure_mode", [ @@ -1006,76 +946,83 @@ def test_top_level_failure_spilled_state_gas( ], ) @pytest.mark.valid_from("EIP8037") -def test_top_level_failure_propagated_state_gas( +def test_top_level_failure_spilled_state_gas( state_test: StateTestFiller, pre: Alloc, fork: Fork, failure_mode: str, + spill_source: str, ) -> None: """ - Verify the top-level failure handling for state gas propagated - from a successful subcall. - - The parent calls a child that runs SSTORE and returns. The - child's `state_gas_used` is folded into the parent frame via the - success path so the parent's reservoir is empty and its - `state_gas_used` carries the SSTORE charge. - - Per the updated EIP both failure modes refund the full propagated - `state_gas_used` (reservoir-portion + spilled-portion) to the - reservoir. They differ only in `gas_left` handling: - - - REVERT preserves `gas_left`; sender billed only the regular - component. - - Exceptional halt zeros `gas_left`; sender pays for everything - except the state-gas refund. + Verify top-level failure handling for spilled state gas, whether + the spill is charged in the frame itself, propagated from a + successful subcall, or both. + + The reservoir covers half an SSTORE's state gas, so each SSTORE + charge spills into `gas_left`. A successful child propagates its + `state_gas_spilled` into the parent, accumulating with the parent's + own spill. Refunds are LIFO, so the spilled portion returns to + `gas_left` and only the reservoir-funded portion to the reservoir. + + - REVERT preserves `gas_left`, so all state gas is refunded and the + sender pays only the regular component. + - Halt refills LIFO then zeros `gas_left`, so the spill is burned + and only the start reservoir survives. """ gas_limit_cap = fork.transaction_gas_limit_cap() assert gas_limit_cap is not None sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) intrinsic_cost = fork.transaction_intrinsic_cost_calculator()() + terminator = Op.REVERT(0, 0) if failure_mode == "revert" else Op.INVALID + has_child = spill_source in ("propagated", "both") child_code = Op.SSTORE(0, 1) - child = pre.deploy_contract(code=child_code) - if failure_mode == "revert": - parent_code = Op.POP(Op.CALL(gas=Op.GAS, address=child)) + Op.REVERT( - 0, 0 - ) - else: - parent_code = Op.POP(Op.CALL(gas=Op.GAS, address=child)) + Op.INVALID + + parent_code = Bytecode() + if spill_source in ("own", "both"): + parent_code += Op.SSTORE(0, 1) + child = None + if has_child: + child = pre.deploy_contract(code=child_code) + parent_code += Op.POP(Op.CALL(gas=Op.GAS, address=child)) + parent_code += terminator parent = pre.deploy_contract(code=parent_code) - # Reservoir sized to half the SSTORE state gas so the child's - # charge drains the reservoir AND spills into gas_left. The halt - # path then exercises a non-trivial spill case rather than the - # degenerate no-spill case. - tx_gas = gas_limit_cap + sstore_state_gas // 2 + # Reservoir covers half an SSTORE's state gas, so every SSTORE + # spills into gas_left. + reservoir = sstore_state_gas // 2 + tx_gas = gas_limit_cap + reservoir + total_state = sstore_state_gas * ( + (1 if spill_source in ("own", "both") else 0) + (1 if has_child else 0) + ) if failure_mode == "revert": - # gas_left preserved; full propagated state_gas_used refunded - # → sender billed only the regular component. + # gas_left preserved, all state gas refunded, so the sender + # pays only the regular component. expected_cumulative = ( - intrinsic_cost - + parent_code.gas_cost(fork) - + child_code.gas_cost(fork) - - sstore_state_gas + intrinsic_cost + parent_code.gas_cost(fork) - total_state ) + if has_child: + expected_cumulative += child_code.gas_cost(fork) else: - # gas_left burned; full propagated state_gas_used (reservoir - # + spill) refunded via reservoir. - # tx_gas_used = tx_gas - 0 - sstore_state_gas. - expected_cumulative = tx_gas - sstore_state_gas + # gas_left burned after LIFO refill. The spill returns to + # gas_left and is consumed, so only the start reservoir + # survives. + expected_cumulative = tx_gas - reservoir tx = Transaction( to=parent, - state_gas_reservoir=sstore_state_gas // 2, + state_gas_reservoir=reservoir, sender=pre.fund_eoa(), expected_receipt=TransactionReceipt( cumulative_gas_used=expected_cumulative, ), ) - state_test(pre=pre, post={child: Account(storage={})}, tx=tx) + post = {parent: Account(storage={})} + if child is not None: + post[child] = Account(storage={}) + state_test(pre=pre, post=post, tx=tx) def _build_call_chain( @@ -1118,10 +1065,9 @@ def _build_create_chain( then terminates with `terminator`. The deepest level's initcode just executes its body and terminates. - Each CREATE pre-charges `STATE_NEW × cpsb` of state-gas on the - parent frame, which is what makes this chain exercise the - credit-on-failure path that distinguishes Policy A from Policy B - for top-level halt. + Each CREATE pre-charges `STATE_NEW * cpsb` of state gas on the + parent frame, which makes this chain exercise the LIFO + refill-on-failure path for top-level halt. """ remaining_frame_bodies = frame_bodies[:] # Deepest level is just body + terminator (runs as initcode of @@ -1133,8 +1079,8 @@ def _build_create_chain( inner_bytes = bytes(inner_initcode) inner_size = len(inner_bytes) # Pad to 32-byte alignment so Om.MSTORE uses the cheap - # PUSH32+MSTORE path on the trailing chunk; CREATE reads - # only `size` bytes so the trailing zeros are ignored. + # PUSH32+MSTORE path on the trailing chunk. CREATE reads + # only `size` bytes, so the trailing zeros are ignored. padded = inner_bytes + b"\x00" * ((-inner_size) % 32) code = ( remaining_frame_bodies.pop() @@ -1270,21 +1216,23 @@ def test_nested_failure_resets_to_tx_reservoir( so the cascade reaches the top. Axes: - - `failure_mode`: REVERT vs HALT (top-level gas_left semantics - differ; state-gas refund must agree per the updated EIP). - - `spill_mode`: `no_spill` sizes the reservoir to cover all - state-gas charges. `spill` shrinks it so charges drain into - gas_left, exercising the spill-refund-on-halt rule. - - `frame_op`: `call` chains via CALL (no per-frame pre-charge). - `create` chains via CREATE (each level pre-charges - `STATE_BYTES_PER_NEW_ACCOUNT × cpsb`, exercising - credit-on-failure interleaved with the spill). - - Per the updated EIP, every state-gas charge — body charges, - spilled portions, and CREATE pre-charges — is refunded to the - top-level reservoir on either revert or halt. So the user pays - `tx_gas - max(reservoir, total_state_charges)` on halt and only - regular charges + intrinsic on revert, regardless of axes. + - `failure_mode`: REVERT vs HALT. Top-level gas_left semantics + differ, but state gas refund must agree per the updated EIP. + - `spill_mode`: `no_spill` sizes the reservoir to cover all state + gas charges. `spill` shrinks it so charges drain into gas_left, + exercising the spill-refund-on-halt rule. + - `frame_op`: `call` chains via CALL with no per-frame pre-charge. + `create` chains via CREATE, where each level pre-charges + `STATE_BYTES_PER_NEW_ACCOUNT * cpsb` and exercises + credit-on-failure interleaved with the spill. + + Refunds are LIFO. On REVERT every state gas charge (body charges, + spilled portions, and CREATE pre-charges) is refilled, the spill + landing back in `gas_left`, so the user pays only regular charges + plus intrinsic. On HALT the LIFO refill returns spilled state gas + to `gas_left`, which is then zeroed, so only the start reservoir + survives and the user pays `tx_gas - reservoir = gas_limit_cap`, + regardless of spill axis or CREATE pre-charges. Two assertions cross-check the gas accounting: - `cumulative_gas_used` (receipt) pins `tx.gas - gas_left - @@ -1320,20 +1268,19 @@ def test_nested_failure_resets_to_tx_reservoir( top, frame_codes = _build_create_chain(pre, frame_bodies, terminator) sum_regular = sum(code.regular_cost(fork) for code in frame_codes) - spill = max(0, total_state_charges - reservoir) if failure_mode == "halt": - # Policy A (updated EIP): all state-gas — body charges, spilled - # portions, and CREATE pre-charges (returned via credit) — folds - # into state_gas_left at tx end. gas_left is zeroed by halt. - state_gas_at_end = max(reservoir, total_state_charges) - expected_cumulative = tx_gas - state_gas_at_end - # Header: block_regular = gas_limit_cap - spill (spilled - # state-gas drained gas_left but is no longer reclassified to - # regular under Policy A); block_state ≈ 0 for plain CALLs. - expected_header_gas_used = gas_limit_cap - spill + # LIFO refill returns spilled state gas (and spilled CREATE + # pre-charges) to gas_left, which halt then zeros. Only the + # start reservoir survives. + expected_cumulative = tx_gas - reservoir + assert expected_cumulative == gas_limit_cap + # Header: all gas_left (including the refilled spill) is + # consumed as regular. Block state gas is zero for plain + # frames. + expected_header_gas_used = gas_limit_cap elif failure_mode == "revert": - # Revert preserves gas_left; full state-gas refund. - # User pays only regular costs + intrinsic. + # Revert preserves gas_left, full state gas refund, so the + # user pays only regular costs plus intrinsic. expected_cumulative = intrinsic_cost + sum_regular # Header reflects the regular-vs-state attribution directly: # state_gas_used is zeroed by the tx error handler, so only @@ -1394,26 +1341,36 @@ def test_nested_state_gas_refund_consumed_at_depth( consume_at: str, ) -> None: """ - Verify state-gas refund credits propagate through a CALL chain so - they can be consumed at any depth. - - Refund sources: SSTORE `0→1→0`, CREATE collision, CREATE initcode - revert (all credit deepest's reservoir), and a SetCode auth on an - `existing_leaf` authority (credits the top reservoir at message - entry). - - A probe CALL sized one short of covering an SSTORE on full spill - runs either at the refund-source frame or back at the top after - the chain returns; it succeeds only when its frame holds enough - reservoir, so a missing or mis-propagated credit OOGs it. + Verify how state gas refund credits route under LIFO refills. + + Refund sources SSTORE `0->1->0`, CREATE collision, and CREATE + initcode revert all refund LIFO, so the credit returns to + `gas_left`, not the reservoir. A SetCode auth on an `existing_leaf` + authority still credits the reservoir directly at message entry. + + A probe CALL sized one short of covering an SSTORE forwards a fixed + gas to a sub-call, so it can only observe the reservoir, never the + `gas_left` refund. It therefore succeeds only for the auth scenario + and fails (stores 0) for the SSTORE/CREATE scenarios whose refund + lands in `gas_left`. """ is_auth_scenario = refund_scenario == "auth_existing_leaf" probe_address = pre.deploy_contract(code=Op.SSTORE(0, 1)) probe_gas = Op.SSTORE(0, 1).gas_cost(fork) - 1 consumer_storage = Storage() + # The probe forwards a fixed gas and can only see the reservoir, + # so it succeeds (CALL returns 1) only when the refund credited the + # reservoir, the auth scenario. Otherwise the LIFO refund lands in + # gas_left, the sub-call OOGs, and CALL returns 0. + if is_auth_scenario: + probe_label = "auth_reservoir_probe_must_succeed" + probe_result = 1 + else: + probe_label = "gas_left_refund_probe_must_fail" + probe_result = 0 consume_op = Op.SSTORE( - consumer_storage.store_next(1, "probe_must_succeed"), + consumer_storage.store_next(probe_result, probe_label), Op.CALL(gas=probe_gas, address=probe_address), ) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_sstore.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_sstore.py index d8b25968d91..50e404d149d 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_sstore.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_sstore.py @@ -870,19 +870,19 @@ def test_sstore_restoration_sub_frame_revert( call_opcode: Op, ) -> None: """ - Verify 0 to x to 0 reservoir refund returns to the caller on - sub-frame REVERT. - - The sub-call performs 0 to x to 0 then REVERTs. Since both the - set-charge and its refund roll back together, the - `state_gas_used + state_gas_left` sum reflects the unconsumed - reservoir and is returned to the caller via - `incorporate_child_on_error`. A single-SSTORE probe sized to OOG - by 1 succeeds, confirming the caller's reservoir was replenished. + Verify a sub-frame REVERT does not inflate the caller's reservoir + under source-based (LIFO) refills. + + The sub-call does 0 to x to 0 then REVERTs. The set spilled its + state gas from `gas_left`, so the refill at x to 0 returns it to + `gas_left`, not the reservoir. On REVERT the state gas refills to + the parent's `gas_left`, so the reservoir stays at 0. A probe sized + to OOG by 1 then fails, since its fixed forwarded gas cannot reach + the `gas_left` refund. """ gas_costs = fork.gas_costs() - # Probe SSTORE(0, 1): 2 pushes + cold storage write + state gas - 1, - # so it OOGs by 1 when the reservoir is 0 and succeeds otherwise. + # Probe SSTORE(0, 1): 2 pushes + cold write + state gas - 1. OOGs by + # 1 when the reservoir is 0, as forwarded gas misses gas_left. probe_gas = ( 2 * gas_costs.VERY_LOW + gas_costs.COLD_STORAGE_WRITE @@ -894,11 +894,10 @@ def test_sstore_restoration_sub_frame_revert( child = pre.deploy_contract(code=child_code) probe = pre.deploy_contract(code=Op.SSTORE(0, 1)) - # Forward all remaining gas so the child completes both SSTOREs - # and REVERT without a hard-coded budget. + # Forward all gas so the child does both SSTOREs and REVERT. caller_storage = Storage() caller_code = Op.POP(call_opcode(gas=Op.GAS, address=child)) + Op.SSTORE( - caller_storage.store_next(1, "probe_must_succeed"), + caller_storage.store_next(0, "probe_must_fail"), Op.CALL(gas=probe_gas, address=probe), ) caller = pre.deploy_contract(code=caller_code) @@ -925,20 +924,21 @@ def test_sstore_restoration_ancestor_revert( call_opcode: Op, ) -> None: """ - Verify the SSTORE 0 to x to 0 refund returns to the caller when an - ancestor frame (not the applying frame itself) reverts. - - Inner frame applies the refund and returns successfully; its - `state_gas_left` (inflated by the refund) propagates to middle - via `incorporate_child_on_success`. Middle then REVERTs; the - refunded reservoir flows back to the caller via - `incorporate_child_on_error`, so the caller's reservoir is - replenished by `sstore_state_gas`. + Verify an ancestor REVERT does not inflate the caller's reservoir + under source-based (LIFO) refills. + + Inner's set spills its state gas from `gas_left`. The refill at + x to 0 returns it to `gas_left`, and inner's + `state_gas_spilled` propagates to middle on success. Middle + then REVERTs, refilling the spilled state gas to the caller's + `gas_left`, not the reservoir. The reservoir stays at 0, so a probe + sized to OOG by 1 fails, since its fixed forwarded gas cannot reach + the `gas_left` refund. """ gas_costs = fork.gas_costs() intrinsic_cost = fork.transaction_intrinsic_cost_calculator()() - # Probe SSTORE(0, 1): 2 pushes + cold storage write + state gas - 1, - # so it OOGs by 1 when the reservoir is 0 and succeeds otherwise. + # Probe SSTORE(0, 1): 2 pushes + cold write + state gas - 1. OOGs by + # 1 when the reservoir is 0, as forwarded gas misses gas_left. probe_gas = ( 2 * gas_costs.VERY_LOW + gas_costs.COLD_STORAGE_WRITE @@ -969,22 +969,27 @@ def test_sstore_restoration_ancestor_revert( caller_storage = Storage() caller_code = Op.POP(call_opcode(gas=Op.GAS, address=middle)) + Op.SSTORE( - caller_storage.store_next(1, "probe_must_succeed"), + caller_storage.store_next(0, "probe_must_fail"), Op.CALL(gas=probe_gas, address=probe), ) caller = pre.deploy_contract(code=caller_code) - # Block state gas commits: probe's SSTORE-set and caller's outer - # SSTORE-set; inner's set+clear cancel before middle reverts and - # don't propagate. Header gas_used is max(regular, state). + # Block state gas commits only the caller's outer SSTORE-set. The + # probe OOGs and inner's set+clear cancel before middle reverts. + # The probe's CALL burns its forwarded budget on the OOG, less the + # cold-call surcharge already in the caller's static regular cost. + # Header gas_used is max(regular, state). + probe_burned = ( + probe_gas - gas_costs.COLD_ACCOUNT_ACCESS - 2 * gas_costs.WARM_ACCESS + ) expected_regular = ( intrinsic_cost + caller_code.regular_cost(fork) + middle_code.regular_cost(fork) + inner_code.regular_cost(fork) - + probe_code.regular_cost(fork) + + probe_burned ) - expected_state = 2 * Op.SSTORE(new_value=1).state_cost(fork) + expected_state = Op.SSTORE(new_value=1).state_cost(fork) expected_gas_used = max(expected_regular, expected_state) # gas_limit at the cap means the caller's reservoir starts at 0. @@ -1109,21 +1114,20 @@ def test_sstore_restoration_create_init_revert( create_opcode: Op, ) -> None: """ - Verify reservoir refunds return to the caller when CREATE init - code REVERTs inside a sub-frame that also REVERTs. - - Wrapping the CREATE in an outer reverting frame isolates the - rollback concern from the legitimate CREATE silent-failure refund - (`create_account_state_gas` credited to the frame executing the - CREATE opcode). When the outer frame reverts, the refunded - reservoir flows back to the caller via - `incorporate_child_on_error`, replenishing the caller's - reservoir by at least `sstore_state_gas`. A single-SSTORE probe - sized to OOG by 1 succeeds, confirming the propagation. + Verify a reverting CREATE sub-frame does not inflate the caller's + reservoir under source-based (LIFO) refills. + + The init code spills its state gas from `gas_left`, does 0 to x to 0 + and REVERTs. The CREATE is wrapped in an outer frame that also + REVERTs. Each refill returns the spilled state gas to `gas_left`, + and the reverts refill it to the caller's `gas_left`, not the + reservoir. The reservoir stays at 0, so a probe sized to OOG by 1 + fails, since its fixed forwarded gas cannot reach the `gas_left` + refund. """ gas_costs = fork.gas_costs() - # Probe SSTORE(0, 1): 2 pushes + cold storage write + state gas - 1, - # so it OOGs by 1 when the reservoir is 0 and succeeds otherwise. + # Probe SSTORE(0, 1): 2 pushes + cold write + state gas - 1. OOGs by + # 1 when the reservoir is 0, as forwarded gas misses gas_left. probe_gas = ( 2 * gas_costs.VERY_LOW + gas_costs.COLD_STORAGE_WRITE @@ -1157,7 +1161,7 @@ def test_sstore_restoration_create_init_revert( code=( Op.POP(Op.CALL(gas=Op.GAS, address=inner)) + Op.SSTORE( - caller_storage.store_next(1, "probe_must_succeed"), + caller_storage.store_next(0, "probe_must_fail"), Op.CALL(gas=probe_gas, address=probe), ) ), diff --git a/tests/cancun/eip5656_mcopy/test_mcopy_memory_expansion.py b/tests/cancun/eip5656_mcopy/test_mcopy_memory_expansion.py index 0502a645563..23f32896ed1 100644 --- a/tests/cancun/eip5656_mcopy/test_mcopy_memory_expansion.py +++ b/tests/cancun/eip5656_mcopy/test_mcopy_memory_expansion.py @@ -128,19 +128,14 @@ def tx( # noqa: D103 initial_memory: bytes, tx_gas_limit: int, tx_access_list: List[AccessList], - successful: bool, - fork: Fork, ) -> Transaction: - expected_gas = tx_gas_limit - if not successful and fork.is_eip_enabled(8037): - expected_gas -= Op.SSTORE(new_value=1).state_cost(fork) return Transaction( sender=sender, to=caller_address, access_list=tx_access_list, data=initial_memory, gas_limit=tx_gas_limit, - expected_receipt=TransactionReceipt(cumulative_gas_used=expected_gas), + expected_receipt=TransactionReceipt(cumulative_gas_used=tx_gas_limit), ) From 350e1764dd79522f29ce98cb7ccd65d741d8be8c Mon Sep 17 00:00:00 2001 From: spencer Date: Thu, 18 Jun 2026 16:12:55 +0100 Subject: [PATCH 040/233] feat(spec-specs, tests): refund EIP-8037 account creation for existing create targets (#3002) Co-authored-by: kclowes --- src/ethereum/forks/amsterdam/fork.py | 4 +- .../forks/amsterdam/vm/instructions/system.py | 4 + .../forks/amsterdam/vm/interpreter.py | 8 + .../test_block_access_lists_opcodes.py | 141 +++++++++++ .../test_state_gas_create.py | 222 +++++++++++++++++- 5 files changed, 374 insertions(+), 5 deletions(-) diff --git a/src/ethereum/forks/amsterdam/fork.py b/src/ethereum/forks/amsterdam/fork.py index 6506a15ca49..30798e96063 100644 --- a/src/ethereum/forks/amsterdam/fork.py +++ b/src/ethereum/forks/amsterdam/fork.py @@ -1066,7 +1066,9 @@ def process_transaction( tx_output = process_message_call(message) - if tx_output.error is not None and isinstance(tx.to, Bytes0): + if isinstance(tx.to, Bytes0) and ( + tx_output.error is not None or tx_output.created_target_alive + ): new_account_refund = StateGasCosts.NEW_ACCOUNT tx_output.state_gas_left += new_account_refund tx_output.state_refund += new_account_refund diff --git a/src/ethereum/forks/amsterdam/vm/instructions/system.py b/src/ethereum/forks/amsterdam/vm/instructions/system.py index 39fa98ebf0c..03b0a247c71 100644 --- a/src/ethereum/forks/amsterdam/vm/instructions/system.py +++ b/src/ethereum/forks/amsterdam/vm/instructions/system.py @@ -130,6 +130,8 @@ def generic_create( push(evm.stack, U256(0)) return + target_alive = is_account_alive(tx_state, contract_address) + increment_nonce(tx_state, evm.message.current_target) child_message = Message( @@ -162,6 +164,8 @@ def generic_create( push(evm.stack, U256(0)) else: incorporate_child_on_success(evm, child_evm) + if target_alive: + credit_state_gas_refund(evm, StateGasCosts.NEW_ACCOUNT) evm.return_data = b"" push(evm.stack, U256.from_be_bytes(child_evm.message.current_target)) diff --git a/src/ethereum/forks/amsterdam/vm/interpreter.py b/src/ethereum/forks/amsterdam/vm/interpreter.py index 71ecb2d2bcc..2fe53a3982d 100644 --- a/src/ethereum/forks/amsterdam/vm/interpreter.py +++ b/src/ethereum/forks/amsterdam/vm/interpreter.py @@ -39,6 +39,7 @@ get_account, get_code, increment_nonce, + is_account_alive, mark_account_created, move_ether, restore_tx_state, @@ -91,6 +92,8 @@ class MessageCallOutput: authorities that already existed in state. Subtracted from `tx_state_gas` in block accounting so `block.gas_used` matches the receipt `cumulative_gas_used`. + 10. `created_target_alive`: Whether a top-level creation + transaction targeted an already-existent account. """ gas_left: Uint @@ -103,6 +106,7 @@ class MessageCallOutput: regular_gas_used: Uint state_gas_used: int state_refund: Uint + created_target_alive: bool def process_message_call(message: Message) -> MessageCallOutput: @@ -124,8 +128,10 @@ def process_message_call(message: Message) -> MessageCallOutput: tx_state = message.tx_env.state refund_counter = U256(0) state_refund = Uint(0) + target_alive = False if message.target == Bytes0(b""): if account_deployable(tx_state, message.current_target): + target_alive = is_account_alive(tx_state, message.current_target) evm = process_create_message(message) else: return MessageCallOutput( @@ -139,6 +145,7 @@ def process_message_call(message: Message) -> MessageCallOutput: regular_gas_used=message.gas, state_gas_used=0, state_refund=Uint(0), + created_target_alive=False, ) else: if message.tx_env.authorizations != (): @@ -180,6 +187,7 @@ def process_message_call(message: Message) -> MessageCallOutput: regular_gas_used=evm.regular_gas_used, state_gas_used=evm.state_gas_used, state_refund=state_refund, + created_target_alive=target_alive, ) diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py index 010f891fccb..737ba9e4c41 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py @@ -3528,6 +3528,147 @@ def test_bal_create_early_failure( ) +@pytest.mark.with_all_create_opcodes +@pytest.mark.parametrize("creation_outcome", ["pre_frame_failure", "success"]) +def test_bal_create_existing_target( + pre: Alloc, + blockchain_test: BlockchainTestFiller, + create_opcode: Op, + creation_outcome: str, +) -> None: + """ + Test BAL for CREATE/CREATE2 into a pre-existing balance-only target. + + Under EIP-8037 the account-creation charge is unconditional, so the + target's existence is never read to decide it. On a pre-frame failure + (insufficient endowment) the pre-existing target is never accessed and + stays absent from the BAL; on success it appears with the deployed + nonce and code. + """ + alice = pre.fund_eoa() + + init_code = Initcode(deploy_code=Op.STOP) + init_code_bytes = bytes(init_code) + + if creation_outcome == "pre_frame_failure": + factory_balance, endowment = 50, 100 + else: + factory_balance, endowment = 0, 0 + + factory_code = ( + Op.MSTORE(0, Op.PUSH32(init_code_bytes)) + + Op.SSTORE( + 0x00, + Op.GT( + create_opcode( + value=endowment, + offset=32 - len(init_code_bytes), + size=len(init_code_bytes), + ), + 0, + ), + ) + + Op.STOP + ) + + factory = pre.deploy_contract( + code=factory_code, + balance=factory_balance, + storage={0x00: 0xDEAD}, + ) + + target = compute_create_address( + address=factory, + nonce=1, + salt=0, + initcode=init_code_bytes, + opcode=create_opcode, + ) + # Pre-existing balance-only leaf (balance, no code, zero nonce). + pre.fund_address(target, amount=1) + + tx = Transaction(sender=alice, to=factory) + + if creation_outcome == "pre_frame_failure": + expected_bal = BlockAccessListExpectation( + account_expectations={ + alice: BalAccountExpectation( + nonce_changes=[ + BalNonceChange(block_access_index=1, post_nonce=1) + ], + ), + factory: BalAccountExpectation( + nonce_changes=[], + storage_changes=[ + BalStorageSlot( + slot=0x00, + slot_changes=[ + BalStorageChange( + block_access_index=1, post_value=0 + ) + ], + ) + ], + ), + # Never accessed despite pre-existing: absent from the BAL. + target: None, + } + ) + post = { + alice: Account(nonce=1), + factory: Account( + nonce=1, balance=factory_balance, storage={0x00: 0} + ), + target: Account(balance=1), + } + else: + expected_bal = BlockAccessListExpectation( + account_expectations={ + alice: BalAccountExpectation( + nonce_changes=[ + BalNonceChange(block_access_index=1, post_nonce=1) + ], + ), + factory: BalAccountExpectation( + nonce_changes=[ + BalNonceChange(block_access_index=1, post_nonce=2) + ], + storage_changes=[ + BalStorageSlot( + slot=0x00, + slot_changes=[ + BalStorageChange( + block_access_index=1, post_value=1 + ) + ], + ) + ], + ), + target: BalAccountExpectation( + nonce_changes=[ + BalNonceChange(block_access_index=1, post_nonce=1) + ], + code_changes=[ + BalCodeChange( + block_access_index=1, new_code=bytes(Op.STOP) + ) + ], + ), + } + ) + post = { + alice: Account(nonce=1), + factory: Account(nonce=2, storage={0x00: 1}), + target: Account(nonce=1, balance=1, code=Op.STOP), + } + + blockchain_test( + pre=pre, + blocks=[Block(txs=[tx], expected_block_access_list=expected_bal)], + post=post, + ) + + @pytest.mark.with_all_create_opcodes @pytest.mark.parametrize( "storage_op", diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py index ed0be4e1d9f..2c9e4e362a1 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py @@ -1210,11 +1210,20 @@ def test_code_deposit_halt_discards_initcode_state_gas( ) +@pytest.mark.parametrize( + "target", + [ + pytest.param("new", id="new_account"), + pytest.param("existing", id="existing_account"), + ], +) +@pytest.mark.pre_alloc_mutable() @pytest.mark.valid_from("EIP8037") def test_create_tx_header_gas_used( blockchain_test: BlockchainTestFiller, pre: Alloc, fork: Fork, + target: str, ) -> None: """ Verify block header gas_used for a successful CREATE transaction. @@ -1223,22 +1232,45 @@ def test_create_tx_header_gas_used( exact gas_used from first principles and verify against the block header. Catches bugs where clients report gas_limit instead of actual consumed gas. + + For a fresh target the NEW_ACCOUNT state gas is charged and + dominates the regular gas, so gas_used == NEW_ACCOUNT. For a + pre-existing balance-only leaf the NEW_ACCOUNT charge is refunded, + so net state gas is zero and the regular intrinsic gas dominates. + The expected value subtracts NEW_ACCOUNT and so fails if the + refund regresses. """ gas_costs = fork.gas_costs() initcode = Op.STOP create_state_gas = fork.create_state_gas(code_size=1) + if target == "existing": + sender = pre.fund_eoa(nonce=0) + contract_address = compute_create_address(address=sender, nonce=0) + # Balance-only leaf: alive and deployable, so the creation + # succeeds and the intrinsic NEW_ACCOUNT charge is refunded. + pre.fund_address(contract_address, amount=1) + else: + sender = pre.fund_eoa() + tx = Transaction( to=None, data=initcode, state_gas_reservoir=create_state_gas, - sender=pre.fund_eoa(), + sender=sender, ) # block_gas_used = max(block_regular, block_state) - # For a minimal CREATE tx deploying Op.STOP (1 byte), - # state gas (new account) dominates regular gas. - expected_gas_used = gas_costs.NEW_ACCOUNT + if target == "existing": + intrinsic_cost = fork.transaction_intrinsic_cost_calculator() + intrinsic_total = intrinsic_cost( + calldata=bytes(initcode), contract_creation=True + ) + expected_gas_used = intrinsic_total - gas_costs.NEW_ACCOUNT + else: + # For a minimal CREATE tx deploying Op.STOP (1 byte), + # state gas (new account) dominates regular gas. + expected_gas_used = gas_costs.NEW_ACCOUNT blockchain_test( pre=pre, @@ -1837,6 +1869,117 @@ def test_create_code_deposit_oog_refunds_state_gas( state_test(pre=pre, post={factory: Account(storage=storage)}, tx=tx) +@pytest.mark.parametrize( + "reservoir_covers", + [ + pytest.param(True, id="charge_from_reservoir"), + pytest.param(False, id="charge_spills_from_gas_left"), + ], +) +@pytest.mark.with_all_create_opcodes() +@pytest.mark.valid_from("EIP8037") +def test_create_account_charge_reduces_child_gas( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + create_opcode: Op, + reservoir_covers: bool, +) -> None: + """ + Verify the early NEW_ACCOUNT charge reduces forwarded child gas. + + `generic_create` charges NEW_ACCOUNT before computing the child's + 63/64 share. When the reservoir covers the charge `gas_left` is + untouched and the child receives the full share. When the reservoir + is empty the charge spills NEW_ACCOUNT from `gas_left` first, so the + child receives `NEW_ACCOUNT * 63 / 64` less. The init code burns a + fixed amount sized between the two shares, so it deploys when the + charge comes from the reservoir and runs out of gas when it spills. + The target is a pre-existing balance-only leaf, the EIP-8037 + success-refund path that the old conditional charge skipped. + """ + new_account = fork.gas_costs().NEW_ACCOUNT + memory_gas = fork.memory_expansion_gas_calculator() + + # Factory `gas_left` at the NEW_ACCOUNT charge. Three times + # NEW_ACCOUNT gives a wide discrimination window and a large + # absolute child share. + gas_at_charge = 3 * new_account + full_share = gas_at_charge - gas_at_charge // 64 + spilled = gas_at_charge - new_account + reduced_share = spilled - spilled // 64 + # Burn the middle of `(reduced_share, full_share]` for robustness. + target_burn = (full_share + reduced_share) // 2 + + # Init code burns `target_burn` regular gas via one MSTORE memory + # expansion, then deploys empty code (zero code deposit). Invert + # `words * MEMORY_PER_WORD + words ** 2 // 512 = target_mem` to size + # the sink offset from gas rather than a magic number. + init_static = (Op.MSTORE(0, 0) + Op.RETURN(0, 0)).gas_cost(fork) + target_mem = target_burn - init_static + # Memory cost is monotonic in word count, so binary search the + # largest word count whose expansion stays within `target_mem`. + low, high = 1, target_mem + while low < high: + mid = (low + high + 1) // 2 + if int(memory_gas(new_bytes=mid * 32)) <= target_mem: + low = mid + else: + high = mid - 1 + words = low + sink_offset = (words - 1) * 32 + child_burn = init_static + int(memory_gas(new_bytes=words * 32)) + assert reduced_share < child_burn <= full_share + + init_code = Op.MSTORE(sink_offset, 0) + Op.RETURN(0, 0) + mstore_value, size = init_code_at_high_bytes(init_code) + create_call = ( + create_opcode(value=0, offset=0, size=size, salt=0) + if create_opcode == Op.CREATE2 + else create_opcode(value=0, offset=0, size=size) + ) + + storage = Storage() + expected = 1 if reservoir_covers else 0 + factory = pre.deploy_contract( + code=Op.MSTORE(0, mstore_value) + + Op.SSTORE( + storage.store_next(expected, "child_succeeds"), + Op.GT(create_call, 0), + ), + ) + + # Pre-existing balance-only target: the success-refund path. Under + # the old conditional approach this alive target skips NEW_ACCOUNT, + # so the child gets the full share and fits in both cases. + if create_opcode == Op.CREATE2: + create_address = compute_create2_address( + address=factory, salt=0, initcode=bytes(init_code) + ) + else: + create_address = compute_create_address(address=factory, nonce=1) + pre.fund_address(create_address, amount=1) + + # Regular gas the factory spends before the NEW_ACCOUNT charge: the + # initcode setup MSTORE plus the create opcode regular portion + # (`gas_cost` folds NEW_ACCOUNT into the create op, so strip it). + setup = Op.MSTORE(0, mstore_value) + pre_charge_regular = ( + setup.gas_cost(fork) + create_call.gas_cost(fork) - new_account + ) + forwarded_gas = gas_at_charge + pre_charge_regular + caller = pre.deploy_contract( + code=Op.CALL(gas=forwarded_gas, address=factory) + ) + tx = Transaction( + to=caller, + state_gas_reservoir=new_account if reservoir_covers else 0, + sender=pre.fund_eoa(), + ) + + state_test(pre=pre, post={factory: Account(storage=storage)}, tx=tx) + + @pytest.mark.parametrize( "init_code", [ @@ -2568,3 +2711,74 @@ def test_create_collision_burned_gas_counted_in_block_regular( ], post={}, ) + + +@pytest.mark.parametrize( + "target", + [ + pytest.param("new", id="new_account"), + pytest.param("existing", id="existing_account"), + ], +) +@pytest.mark.with_all_create_opcodes() +@pytest.mark.valid_from("EIP8037") +def test_create_account_creation_charge( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + create_opcode: Op, + target: str, +) -> None: + """ + Verify NEW_ACCOUNT is charged for a new account and refunded for a + pre-existing balance-only leaf. + + Empty init code means zero code deposit, so NEW_ACCOUNT is the only + create state cost. A fresh target is charged it; a pre-existing + balance-only target (balance, no code, zero nonce) refunds it on + success. The probe SSTORE both confirms the create succeeded and + makes state gas dominate, so gas_used drops by exactly NEW_ACCOUNT + when refunded. + """ + new_account = fork.gas_costs().NEW_ACCOUNT + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + mstore_value, size = init_code_at_high_bytes(Op.STOP) + create_call = ( + create_opcode(value=0, offset=0, size=size, salt=0) + if create_opcode == Op.CREATE2 + else create_opcode(value=0, offset=0, size=size) + ) + + storage = Storage() + factory = pre.deploy_contract( + code=Op.MSTORE(0, mstore_value) + + Op.SSTORE( + storage.store_next(1, "create_succeeds"), Op.GT(create_call, 0) + ) + ) + + # Factory deployed via deploy_contract starts at nonce 1. + if create_opcode == Op.CREATE2: + create_address = compute_create2_address( + address=factory, salt=0, initcode=bytes(Op.STOP) + ) + else: + create_address = compute_create_address(address=factory, nonce=1) + if target == "existing": + pre.fund_address(create_address, amount=1) + + tx = Transaction( + to=factory, + state_gas_reservoir=new_account + sstore_state_gas, + sender=pre.fund_eoa(), + ) + + # State gas dominates regular: a new account adds NEW_ACCOUNT on top + # of the probe SSTORE, a pre-existing target refunds it. + expected = sstore_state_gas + (new_account if target == "new" else 0) + state_test( + pre=pre, + tx=tx, + post={factory: Account(storage=storage)}, + blockchain_test_header_verify=Header(gas_used=expected), + ) From 8e958d6918310fc0ae04b85a5791cd4cd0a7ba29 Mon Sep 17 00:00:00 2001 From: Bhargava Shastry Date: Thu, 18 Jun 2026 18:42:04 +0200 Subject: [PATCH 041/233] feat(tests): EIP-8037 state-gas refund on failed CREATE2 with init storage (#3011) Co-authored-by: spencer-tb --- docs/writing_tests/post_mortems.md | 46 +++++++++++++++ .../test_state_gas_create.py | 56 +++++++++++++++++++ 2 files changed, 102 insertions(+) diff --git a/docs/writing_tests/post_mortems.md b/docs/writing_tests/post_mortems.md index 6e51f3aa7b3..91a49612681 100644 --- a/docs/writing_tests/post_mortems.md +++ b/docs/writing_tests/post_mortems.md @@ -72,6 +72,52 @@ None required - the existing framework supported writing these tests. --- +## 2026-06 - CREATE2 Failed Deposit Storage State-Gas Refund - Amsterdam + +### Description + +A consensus divergence was found via goevmlab differential fuzzing in +go-ethereum's Amsterdam (bal-devnet-7) EIP-8037 implementation: when a `CREATE2` +whose init code writes new storage slots fails its code deposit — either because +the deposited code is rejected by EIP-3541, or because the EIP-8037 code-deposit +state gas cannot be paid — the create frame reverts, but only the new-account +state-creation gas is refunded; the init's storage-slot state-creation gas +(`STATE_BYTES_PER_STORAGE_SET * COST_PER_STATE_BYTE` per slot) is not. The +transaction over-reports gas used (by `num_slots * 97920`), so the sender and +coinbase balances — and the post-state root — diverge from the reference spec +and from revm/nethermind/besu/erigon/ethrex. + +### Root Cause Analysis + +- State-creation gas charged inside a `CREATE`/`CREATE2` init frame must be fully + reverted when the create fails, for both the new account and any storage slots + the init wrote. The existing `eip8037` suite covered the create-init storage + charge on the success path and same-tx slot-reset refunds, but never isolated + the refund of storage-slot state gas on a create *failure*. +- The new-account state-gas refund on failure was already correct, which masked + the missing storage-slot refund: a failing create with no init storage agrees + across clients, so only the combination "failing create + init storage" + exposes it. +- Differential fuzzing (goevmlab) surfaced it where direct enumeration had not. + +### Steps Taken To Avoid Recurrence + +- Added a parametric regression test over the failure mechanism (EIP-3541 reject + and code-deposit OOG) and the number of init storage slots (`0`, `1`, `3`). The + `slots=0` case is a negative control (account-creation refund only) that must + not diverge; the `slots>=1` cases isolate the storage-slot state-gas refund on + create failure and scale the discrepancy with the slot count. + +### Implemented Test Case + +- `tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py::test_create2_failed_deposit_refunds_storage_state_gas` + +### Framework/Documentation Changes + +None required - the existing framework supported writing this test. + +--- + ## TEMPLATE ## Date - Title - Fork diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py index 2c9e4e362a1..0047d2cf3d6 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py @@ -1869,6 +1869,62 @@ def test_create_code_deposit_oog_refunds_state_gas( state_test(pre=pre, post={factory: Account(storage=storage)}, tx=tx) +@pytest.mark.parametrize("slots", [0, 1, 3]) +@pytest.mark.parametrize("fail_mode", ["eip3541", "oog_deposit"]) +@pytest.mark.valid_from("EIP8037") +def test_create2_failed_deposit_refunds_storage_state_gas( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + slots: int, + fail_mode: str, +) -> None: + """ + Test a failed CREATE2 deposit refunds the init's storage-slot state gas. + + Total gas used is independent of `slots`, so a client that drops the + slot refund diverges for `slots >= 1`; `slots == 0` is the negative + control. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + + # init: write `slots` new storage slots, then trigger a deposit failure + init_code = Bytecode() + for i in range(slots): + init_code += Op.SSTORE(i, i + 1) + if fail_mode == "eip3541": + # return 0xEF -> EIP-3541 rejects the deposited code + init_code += Op.MSTORE8(0, 0xEF) + Op.RETURN(0, 1) + else: + # return max-size code: the code-deposit state gas cannot be paid + init_code += Op.RETURN(0, fork.max_code_size()) + mstore_value, size = init_code_at_high_bytes(init_code) + + storage = Storage() + factory = pre.deploy_contract( + code=( + Op.MSTORE(0, mstore_value) + + Op.SSTORE( + storage.store_next(0, "create2_failed"), + Op.CREATE2(value=0, offset=0, size=size, salt=0), + ) + ), + ) + + tx = Transaction( + to=factory, + gas_limit=gas_limit_cap, + sender=pre.fund_eoa(), + ) + + state_test( + pre=pre, + post={factory: Account(storage=storage)}, + tx=tx, + ) + + @pytest.mark.parametrize( "reservoir_covers", [ From babe45cfb406abed78a01413e0ab5361a3f6c753 Mon Sep 17 00:00:00 2001 From: danceratopz Date: Fri, 19 Jun 2026 08:49:53 +0200 Subject: [PATCH 042/233] chore(ci,tooling): speed up json-loader job (#3010) --- .github/workflows/test.yaml | 2 ++ Justfile | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index df94030cace..29d4d393aee 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -140,6 +140,8 @@ jobs: with: submodules: recursive - uses: ./.github/actions/setup-uv + with: + python-version: "3.14" - uses: ./.github/actions/setup-env - name: Fill and run json-loader tests run: just json-loader diff --git a/Justfile b/Justfile index 6da3a1eabc6..3b25cc77a55 100644 --- a/Justfile +++ b/Justfile @@ -165,15 +165,17 @@ json-loader *args: --cov=ethereum \ --cov-branch \ --cov-report=term \ + --durations=50 \ --cov-fail-under=85 uv run pytest \ -m "not slow" \ - -n auto --maxprocesses 6 --dist=loadfile \ + -n {{ xdist_workers }} --dist=loadfile \ --cov-config=pyproject.toml \ --cov=ethereum \ --cov-branch \ --cov-report=term \ --cov-report "xml:{{ output_dir }}/json-loader/coverage.xml" \ + --durations=50 \ --basetemp="{{ output_dir }}/json-loader/tmp" \ "$@" \ tests/json_loader From ab00b3c48b17c0cfe344796e972741819ac77d0f Mon Sep 17 00:00:00 2001 From: danceratopz Date: Fri, 19 Jun 2026 09:19:52 +0200 Subject: [PATCH 043/233] chore(deps,tooling): move ruff to testing package deps for gentest --- packages/testing/pyproject.toml | 3 ++- uv.lock | 6 ++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/testing/pyproject.toml b/packages/testing/pyproject.toml index 6aacab447a7..38b19f71926 100644 --- a/packages/testing/pyproject.toml +++ b/packages/testing/pyproject.toml @@ -38,6 +38,7 @@ dependencies = [ "semver>=3.0.1,<4", "pydantic>=2.12.3,<3", "rich>=13.7.0,<15", + "ruff==0.13.2", "filelock>=3.15.1,<4", "ethereum-types>=0.4.1,<0.5", "pyyaml>=6.0.2,<7", @@ -66,7 +67,7 @@ test = [ "pytest-cov>=4.1.0,<5", ] lint = [ - "ruff==0.13.2", + # ruff is included in package deps for gentest "mypy==1.20.0", "types-requests>=2.31,<2.33", ] diff --git a/uv.lock b/uv.lock index 0c7c3b5edc1..3a5fc74c119 100644 --- a/uv.lock +++ b/uv.lock @@ -1080,6 +1080,7 @@ dependencies = [ { name = "requests" }, { name = "requests-unixsocket2" }, { name = "rich" }, + { name = "ruff" }, { name = "semver" }, { name = "tenacity" }, { name = "trie" }, @@ -1091,12 +1092,10 @@ dependencies = [ dev = [ { name = "mypy" }, { name = "pytest-cov" }, - { name = "ruff" }, { name = "types-requests" }, ] lint = [ { name = "mypy" }, - { name = "ruff" }, { name = "types-requests" }, ] test = [ @@ -1134,6 +1133,7 @@ requires-dist = [ { name = "requests", specifier = ">=2.31.0,<3" }, { name = "requests-unixsocket2", specifier = ">=0.4.0" }, { name = "rich", specifier = ">=13.7.0,<15" }, + { name = "ruff", specifier = "==0.13.2" }, { name = "semver", specifier = ">=3.0.1,<4" }, { name = "tenacity", specifier = ">=9.0.0,<10" }, { name = "trie", specifier = ">=3.1.0,<4" }, @@ -1145,12 +1145,10 @@ requires-dist = [ dev = [ { name = "mypy", specifier = "==1.20.0" }, { name = "pytest-cov", specifier = ">=4.1.0,<5" }, - { name = "ruff", specifier = "==0.13.2" }, { name = "types-requests", specifier = ">=2.31,<2.33" }, ] lint = [ { name = "mypy", specifier = "==1.20.0" }, - { name = "ruff", specifier = "==0.13.2" }, { name = "types-requests", specifier = ">=2.31,<2.33" }, ] test = [{ name = "pytest-cov", specifier = ">=4.1.0,<5" }] From b7033394dd34d44ce3045b66e487ee155dea6974 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Fri, 19 Jun 2026 19:56:54 -0300 Subject: [PATCH 044/233] feat(tests): add EIP-211 return data buffer tests for SELFDESTRUCT (#3029) * feat(tests): add EIP-211 return data buffer tests for SELFDESTRUCT behavior * Apply suggestions from code review Co-authored-by: Mario Vega --------- Co-authored-by: Mario Vega --- .../byzantium/eip211_return_data/__init__.py | 1 + tests/byzantium/eip211_return_data/spec.py | 17 ++++++ .../eip211_return_data/test_selfdestruct.py | 53 +++++++++++++++++++ 3 files changed, 71 insertions(+) create mode 100644 tests/byzantium/eip211_return_data/__init__.py create mode 100644 tests/byzantium/eip211_return_data/spec.py create mode 100644 tests/byzantium/eip211_return_data/test_selfdestruct.py diff --git a/tests/byzantium/eip211_return_data/__init__.py b/tests/byzantium/eip211_return_data/__init__.py new file mode 100644 index 00000000000..610ca7b6322 --- /dev/null +++ b/tests/byzantium/eip211_return_data/__init__.py @@ -0,0 +1 @@ +"""Tests for EIP-211 return data buffer behavior.""" diff --git a/tests/byzantium/eip211_return_data/spec.py b/tests/byzantium/eip211_return_data/spec.py new file mode 100644 index 00000000000..fb0d55403a7 --- /dev/null +++ b/tests/byzantium/eip211_return_data/spec.py @@ -0,0 +1,17 @@ +"""Defines EIP-211 specification reference.""" + +from dataclasses import dataclass + + +@dataclass(frozen=True) +class ReferenceSpec: + """Defines the reference spec version and git path.""" + + git_path: str + version: str + + +ref_spec_211 = ReferenceSpec( + git_path="EIPS/eip-211.md", + version="N/A", +) diff --git a/tests/byzantium/eip211_return_data/test_selfdestruct.py b/tests/byzantium/eip211_return_data/test_selfdestruct.py new file mode 100644 index 00000000000..7636ddbe5fd --- /dev/null +++ b/tests/byzantium/eip211_return_data/test_selfdestruct.py @@ -0,0 +1,53 @@ +"""Test SELFDESTRUCT return data buffer behavior.""" + +import pytest +from execution_testing import ( + Account, + Alloc, + Op, + StateTestFiller, + Transaction, +) + +from .spec import ref_spec_211 + +REFERENCE_SPEC_GIT_PATH = ref_spec_211.git_path +REFERENCE_SPEC_VERSION = ref_spec_211.version + + +@pytest.mark.valid_from("Byzantium") +def test_selfdestruct_clears_return_data( + state_test: StateTestFiller, + pre: Alloc, +) -> None: + """ + Test SELFDESTRUCT returns empty data after an inner call returns data. + + The selfdestructing contract first performs a call that leaves 32 bytes in + its own return-data buffer. The outer caller must still observe empty + return data from the selfdestructing call. + """ + returns_32 = pre.deploy_contract( + code=Op.MSTORE(0, 0x112233) + Op.RETURN(0, 32) + ) + + selfdestructs = pre.deploy_contract( + code=Op.POP(Op.CALL(address=returns_32, ret_size=0)) + + Op.SELFDESTRUCT(Op.CALLER) + ) + + caller = pre.deploy_contract( + code=Op.SSTORE( + 0, + Op.CALL(address=selfdestructs, ret_size=0), + ) + + Op.SSTORE(1, Op.RETURNDATASIZE) + ) + + tx = Transaction(sender=pre.fund_eoa(), to=caller) + + state_test( + pre=pre, + tx=tx, + post={caller: Account(storage={0: 1, 1: 0})}, + ) From 8f41f2801625f972ada0f3662e09bc19b7039e3b Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Mon, 22 Jun 2026 10:09:28 -0600 Subject: [PATCH 045/233] refactor(test-forks): Introduce `minimum_block_gas_limit` (#2994) --- .../src/execution_testing/forks/base_fork.py | 6 ++ .../forks/forks/eips/amsterdam/eip_7928.py | 13 --- .../forks/forks/eips/cancun/eip_4788.py | 6 ++ .../forks/forks/eips/prague/eip_2935.py | 6 ++ .../forks/forks/eips/prague/eip_7002.py | 6 ++ .../forks/forks/eips/prague/eip_7251.py | 6 ++ .../execution_testing/forks/forks/forks.py | 16 ++++ .../test_fork_transition.py | 5 +- .../test_block_2d_gas_accounting.py | 32 ++++--- tests/frontier/validation/test_header.py | 86 ++++++++++++------- ...static_internal_call_hitting_gas_limit2.py | 3 +- 11 files changed, 123 insertions(+), 62 deletions(-) diff --git a/packages/testing/src/execution_testing/forks/base_fork.py b/packages/testing/src/execution_testing/forks/base_fork.py index ff4cd70e7f5..6449ced32d4 100644 --- a/packages/testing/src/execution_testing/forks/base_fork.py +++ b/packages/testing/src/execution_testing/forks/base_fork.py @@ -455,6 +455,12 @@ def gas_costs(cls) -> GasCosts: """Return dataclass with the gas costs constants for the fork.""" pass + @classmethod + @abstractmethod + def minimum_block_gas_limit(cls) -> int: + """Return the minimum block gas limit for it to be considered valid.""" + pass + @classmethod @abstractmethod def opcode_gas_map( diff --git a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_7928.py b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_7928.py index d4b1b9a6e17..1eacd844b21 100644 --- a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_7928.py +++ b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_7928.py @@ -39,19 +39,6 @@ def gas_costs(cls) -> GasCosts: BLOCK_ACCESS_LIST_ITEM=2000, ) - @classmethod - def empty_block_bal_item_count(cls) -> int: - """ - Return the BAL item count for an empty EIP-7928 block. - - Four system contracts produce 15 items: - EIP-4788 beacon roots: 1 address + 1 write + 1 read = 3 - EIP-2935 history storage: 1 address + 1 write = 2 - EIP-7002 withdrawal requests: 1 address + 4 reads = 5 - EIP-7251 consolidation requests: 1 address + 4 reads = 5 - """ - return 15 - @classmethod def engine_execution_payload_block_access_list(cls) -> bool: """ diff --git a/packages/testing/src/execution_testing/forks/forks/eips/cancun/eip_4788.py b/packages/testing/src/execution_testing/forks/forks/eips/cancun/eip_4788.py index c247691b1f5..87c8763f16e 100644 --- a/packages/testing/src/execution_testing/forks/forks/eips/cancun/eip_4788.py +++ b/packages/testing/src/execution_testing/forks/forks/eips/cancun/eip_4788.py @@ -18,6 +18,12 @@ class EIP4788(BaseFork): """EIP-4788 class.""" + @classmethod + def empty_block_bal_item_count(cls) -> int: + """Add block-level access list elements for an empty block.""" + # Beacon roots contract: 1 address + 1 write + 1 read = 3 + return super(EIP4788, cls).empty_block_bal_item_count() + 3 + @classmethod def header_beacon_root_required(cls) -> bool: """Parent beacon block root is required.""" diff --git a/packages/testing/src/execution_testing/forks/forks/eips/prague/eip_2935.py b/packages/testing/src/execution_testing/forks/forks/eips/prague/eip_2935.py index e9c996fff24..aa759fa8509 100644 --- a/packages/testing/src/execution_testing/forks/forks/eips/prague/eip_2935.py +++ b/packages/testing/src/execution_testing/forks/forks/eips/prague/eip_2935.py @@ -25,6 +25,12 @@ class EIP2935(BaseFork): """EIP-2935 class.""" + @classmethod + def empty_block_bal_item_count(cls) -> int: + """Add block-level access list elements for an empty block.""" + # History contract: 1 address + 1 write = 2 + return super(EIP2935, cls).empty_block_bal_item_count() + 2 + @classmethod def system_contracts(cls) -> List[Address]: """Add the history storage contract.""" diff --git a/packages/testing/src/execution_testing/forks/forks/eips/prague/eip_7002.py b/packages/testing/src/execution_testing/forks/forks/eips/prague/eip_7002.py index 92118d50a1b..c5cba51883d 100644 --- a/packages/testing/src/execution_testing/forks/forks/eips/prague/eip_7002.py +++ b/packages/testing/src/execution_testing/forks/forks/eips/prague/eip_7002.py @@ -27,6 +27,12 @@ class EIP7002(BaseFork): """EIP-7002 class.""" + @classmethod + def empty_block_bal_item_count(cls) -> int: + """Add block-level access list elements for an empty block.""" + # Withdrawals contract: 1 address + 4 reads = 5 + return super(EIP7002, cls).empty_block_bal_item_count() + 5 + @classmethod def system_contracts(cls) -> List[Address]: """Add the withdrawal request predeploy contract.""" diff --git a/packages/testing/src/execution_testing/forks/forks/eips/prague/eip_7251.py b/packages/testing/src/execution_testing/forks/forks/eips/prague/eip_7251.py index 56605c5073c..7ed005481da 100644 --- a/packages/testing/src/execution_testing/forks/forks/eips/prague/eip_7251.py +++ b/packages/testing/src/execution_testing/forks/forks/eips/prague/eip_7251.py @@ -26,6 +26,12 @@ class EIP7251(BaseFork): """EIP-7251 class.""" + @classmethod + def empty_block_bal_item_count(cls) -> int: + """Add block-level access list elements for an empty block.""" + # Consolidations contract: 1 address + 4 reads = 5 + return super(EIP7251, cls).empty_block_bal_item_count() + 5 + @classmethod def system_contracts(cls) -> List[Address]: """Add the consolidation request predeploy contract.""" diff --git a/packages/testing/src/execution_testing/forks/forks/forks.py b/packages/testing/src/execution_testing/forks/forks/forks.py index 1360b0da715..04fc4838045 100644 --- a/packages/testing/src/execution_testing/forks/forks/forks.py +++ b/packages/testing/src/execution_testing/forks/forks/forks.py @@ -323,6 +323,22 @@ def wrapper(opcode: OpcodeBase) -> int: return wrapper + @classmethod + def minimum_block_gas_limit(cls) -> int: + """ + Return the minimum gas limit for the block to be considered valid. + """ + minimum_block_gas_limit = 5_000 + bal_minimum_block_gas_limit = 0 + if cls.header_bal_hash_required(): + # The block gas limit is influenced by the minimum amount of + # block level access elements the system contracts contain. + bal_minimum_block_gas_limit = ( + cls.empty_block_bal_item_count() + * cls.gas_costs().BLOCK_ACCESS_LIST_ITEM + ) + return max(minimum_block_gas_limit, bal_minimum_block_gas_limit) + @classmethod def opcode_gas_map( cls, diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_fork_transition.py b/tests/amsterdam/eip7928_block_level_access_lists/test_fork_transition.py index 28fea46d463..008011f5108 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_fork_transition.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_fork_transition.py @@ -185,10 +185,7 @@ def test_fork_transition_bal_size_constraint( `BLOCK_ACCESS_LIST_GAS_LIMIT_EXCEEDED`. """ amsterdam = fork.transitions_to() - min_gas_limit = ( - amsterdam.empty_block_bal_item_count() - * amsterdam.gas_costs().BLOCK_ACCESS_LIST_ITEM - ) + min_gas_limit = amsterdam.minimum_block_gas_limit() over_budget_gas_limit = min_gas_limit - 1 pre_fork_block = Block( diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py index 17f6556c4e9..ebd7e58bed8 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py @@ -624,22 +624,26 @@ def test_tx_inclusion_at_regular_gas_block_limit_small( assert gas_limit_cap is not None intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() - block_gas_limit = intrinsic_gas * 2 + filler_tx_count = (fork.minimum_block_gas_limit() // intrinsic_gas) + 1 + block_gas_limit = intrinsic_gas * (filler_tx_count + 1) - filler = pre.deploy_contract(code=Op.STOP) - filler_tx = Transaction( - to=filler, - gas_limit=intrinsic_gas, - sender=pre.fund_eoa(), - ) + dest_contract = pre.deploy_contract(code=Op.STOP) + filler_sender = pre.fund_eoa() + filler_txs = [ + Transaction( + to=dest_contract, + gas_limit=intrinsic_gas, + sender=filler_sender, + ) + for _ in range(filler_tx_count) + ] - second_gas_limit = intrinsic_gas + delta - assert second_gas_limit < gas_limit_cap + excess_tx_gas_limit = intrinsic_gas + delta + assert excess_tx_gas_limit < gas_limit_cap error = TransactionException.GAS_ALLOWANCE_EXCEEDED if delta else None - second = pre.deploy_contract(code=Op.STOP) - second_tx = Transaction( - to=second, - gas_limit=second_gas_limit, + excess_tx = Transaction( + to=dest_contract, + gas_limit=excess_tx_gas_limit, sender=pre.fund_eoa(), error=error, ) @@ -649,7 +653,7 @@ def test_tx_inclusion_at_regular_gas_block_limit_small( pre=pre, blocks=[ Block( - txs=[filler_tx, second_tx], + txs=filler_txs + [excess_tx], gas_limit=block_gas_limit, exception=error, ) diff --git a/tests/frontier/validation/test_header.py b/tests/frontier/validation/test_header.py index f53ef2e43be..f4d33cb46da 100644 --- a/tests/frontier/validation/test_header.py +++ b/tests/frontier/validation/test_header.py @@ -1,35 +1,50 @@ """Test the block header validations applied from Frontier.""" +from typing import Generator + import pytest -from execution_testing import Fork -from execution_testing.base_types.base_types import ZeroPaddedHexNumber -from execution_testing.base_types.composite_types import Alloc -from execution_testing.exceptions.exceptions import BlockException -from execution_testing.specs.blockchain import ( +from execution_testing import ( + Alloc, Block, BlockchainTestFiller, + BlockException, + Environment, + Fork, Header, + ParameterSet, ) -from execution_testing.test_types.block_types import Environment - - -@pytest.mark.parametrize( - "gas_limit", - [ - pytest.param(0, marks=pytest.mark.exception_test), - pytest.param(1, marks=pytest.mark.exception_test), - pytest.param(4999, marks=pytest.mark.exception_test), - pytest.param(5000, marks=pytest.mark.valid_before("EIP7928")), - pytest.param( - 5000, - marks=[ - pytest.mark.valid_from("EIP7928"), - pytest.mark.exception_test, - ], - ), - ], -) -def test_gas_limit_below_minimum( +from execution_testing.base_types import ZeroPaddedHexNumber +from execution_testing.forks import Frontier + +# Protocol minimum block gas limit, enforced since Frontier. +PROTOCOL_GAS_LIMIT_FLOOR = Frontier.minimum_block_gas_limit() + + +def gas_limit_cases_by_fork( + fork: Fork, +) -> Generator[ParameterSet, None, None]: + """Yield gas limit cases around the fork's minimum block gas limit.""" + minimum_block_gas_limit = fork.minimum_block_gas_limit() + yield pytest.param( + 0, + id="zero", + marks=pytest.mark.exception_test, + ) + yield pytest.param( + 1, + id="one", + marks=pytest.mark.exception_test, + ) + yield pytest.param( + minimum_block_gas_limit - 1, + id="minimum_minus_one", + marks=pytest.mark.exception_test, + ) + yield pytest.param(minimum_block_gas_limit, id="minimum") + + +@pytest.mark.parametrize_by_fork("gas_limit", gas_limit_cases_by_fork) +def test_block_gas_limit_below_minimum( blockchain_test: BlockchainTestFiller, pre: Alloc, gas_limit: int, @@ -40,14 +55,25 @@ def test_gas_limit_below_minimum( Tests that a block with a gas limit below the minimum throws an error. """ modified_fields = {"gas_limit": gas_limit} - env.gas_limit = ZeroPaddedHexNumber(5000) + minimum_block_gas_limit = fork.minimum_block_gas_limit() + env.gas_limit = ZeroPaddedHexNumber(minimum_block_gas_limit) block = Block(txs=[]) - if gas_limit < 5000: + if gas_limit < minimum_block_gas_limit: block.rlp_modifier = Header(**modified_fields) - block.exception = BlockException.INVALID_GASLIMIT - elif fork.is_eip_enabled(7928): - block.exception = BlockException.BLOCK_ACCESS_LIST_GAS_LIMIT_EXCEEDED + if gas_limit < PROTOCOL_GAS_LIMIT_FLOOR: + block.exception = ( + [ + BlockException.INVALID_GASLIMIT, + BlockException.BLOCK_ACCESS_LIST_GAS_LIMIT_EXCEEDED, + ] + if fork.is_eip_enabled(7928) + else BlockException.INVALID_GASLIMIT + ) + else: + block.exception = ( + BlockException.BLOCK_ACCESS_LIST_GAS_LIMIT_EXCEEDED + ) blockchain_test(pre=pre, post={}, blocks=[block], genesis_environment=env) diff --git a/tests/ported_static/stStaticCall/test_static_internal_call_hitting_gas_limit2.py b/tests/ported_static/stStaticCall/test_static_internal_call_hitting_gas_limit2.py index 420d8654ec1..a057f8bef9b 100644 --- a/tests/ported_static/stStaticCall/test_static_internal_call_hitting_gas_limit2.py +++ b/tests/ported_static/stStaticCall/test_static_internal_call_hitting_gas_limit2.py @@ -3,6 +3,8 @@ Ported from: state_tests/stStaticCall/static_InternalCallHittingGasLimit2Filler.json + +@manually-enhanced: Do not overwrite. """ import pytest @@ -43,7 +45,6 @@ def test_static_internal_call_hitting_gas_limit2( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=47766, ) # Source: lll From d844821c06a1f354390f8b49c6879df0d93d27b2 Mon Sep 17 00:00:00 2001 From: kclowes Date: Mon, 22 Jun 2026 10:15:22 -0600 Subject: [PATCH 046/233] feat(spec, spec-tests): 8037 Calldata floor accounting alignment (#3005) * feat: 8037 - Apply changes from EIP-8037 spec change #11706 that don't conflict with #11807 * chore(tests): correct misleading EIP-8037 CALL insufficient-balance test * test: fix EIP-8037 existing CALL target coverage * fix: future-poof gas costs, use actual nonexistent account * refactor: use Storage object --------- Co-authored-by: spencer-tb Co-authored-by: Bhargava Shastry --- src/ethereum/forks/amsterdam/fork.py | 6 +- .../forks/amsterdam/vm/instructions/system.py | 12 +- .../test_gas_accounting.py | 64 ++++--- .../test_state_gas_call.py | 164 +++++++++++++----- 4 files changed, 169 insertions(+), 77 deletions(-) diff --git a/src/ethereum/forks/amsterdam/fork.py b/src/ethereum/forks/amsterdam/fork.py index 30798e96063..e72f40d8633 100644 --- a/src/ethereum/forks/amsterdam/fork.py +++ b/src/ethereum/forks/amsterdam/fork.py @@ -1118,15 +1118,13 @@ def process_transaction( all_logs = tx_output.logs + tuple(finalization_logs) - tx_regular_gas = tx_env.intrinsic_regular_gas + tx_output.regular_gas_used tx_state_gas = ( int(tx_env.intrinsic_state_gas) + tx_output.state_gas_used - int(tx_output.state_refund) ) - block_output.block_gas_used += max( - tx_regular_gas, intrinsic.calldata_floor - ) + tx_regular_gas = tx_gas_used_before_refund - Uint(max(0, tx_state_gas)) + block_output.block_gas_used += tx_regular_gas block_output.block_state_gas_used += Uint(max(0, tx_state_gas)) block_output.blob_gas_used += tx_blob_gas_used diff --git a/src/ethereum/forks/amsterdam/vm/instructions/system.py b/src/ethereum/forks/amsterdam/vm/instructions/system.py index 03b0a247c71..cf9e27934c8 100644 --- a/src/ethereum/forks/amsterdam/vm/instructions/system.py +++ b/src/ethereum/forks/amsterdam/vm/instructions/system.py @@ -329,6 +329,7 @@ class GenericCall: memory_output_size: U256 code: Bytes disable_precompiles: bool + new_account_charged: bool = False def generic_call(evm: Evm, params: GenericCall) -> None: @@ -342,6 +343,8 @@ def generic_call(evm: Evm, params: GenericCall) -> None: if evm.message.depth + Uint(1) > STACK_DEPTH_LIMIT: evm.gas_left += params.gas evm.state_gas_left += params.state_gas_reservoir + if params.new_account_charged: + credit_state_gas_refund(evm, StateGasCosts.NEW_ACCOUNT) push(evm.stack, U256(0)) return @@ -376,6 +379,8 @@ def generic_call(evm: Evm, params: GenericCall) -> None: if child_evm.error: incorporate_child_on_error(evm, child_evm) + if params.new_account_charged: + credit_state_gas_refund(evm, StateGasCosts.NEW_ACCOUNT) evm.return_data = child_evm.output push(evm.stack, U256(0)) else: @@ -461,7 +466,9 @@ def call(evm: Evm) -> None: code = get_code(tx_state, code_hash) charge_gas(evm, extra_gas + extend_memory.cost) - if value != 0 and not is_account_alive(tx_state, to): + has_value = value != 0 + new_account_charged = has_value and not is_account_alive(tx_state, to) + if new_account_charged: charge_state_gas(evm, StateGasCosts.NEW_ACCOUNT) message_call_gas = calculate_message_call_gas( @@ -487,6 +494,8 @@ def call(evm: Evm) -> None: evm.return_data = b"" evm.gas_left += message_call_gas.sub_call evm.state_gas_left += call_state_gas_reservoir + if new_account_charged: + credit_state_gas_refund(evm, StateGasCosts.NEW_ACCOUNT) else: generic_call( evm, @@ -505,6 +514,7 @@ def call(evm: Evm) -> None: memory_output_size=memory_output_size, code=code, disable_precompiles=is_delegated, + new_account_charged=new_account_charged, ), ) diff --git a/tests/amsterdam/eip7778_block_gas_accounting_without_refunds/test_gas_accounting.py b/tests/amsterdam/eip7778_block_gas_accounting_without_refunds/test_gas_accounting.py index 001d273ea53..cc9fcee86ea 100644 --- a/tests/amsterdam/eip7778_block_gas_accounting_without_refunds/test_gas_accounting.py +++ b/tests/amsterdam/eip7778_block_gas_accounting_without_refunds/test_gas_accounting.py @@ -219,7 +219,7 @@ def build_refund_tx( ) @pytest.mark.with_all_refund_types() @pytest.mark.execute(pytest.mark.skip(reason="Requires specific gas price")) -@pytest.mark.valid_from("EIP7778") +@pytest.mark.valid_from("EIP8037") def test_simple_gas_accounting( blockchain_test: BlockchainTestFiller, pre: Alloc, @@ -248,7 +248,7 @@ def test_simple_gas_accounting( ) # EIP-8037: block gas_used = max(block_regular_gas, block_state_gas) - block_regular = max(gas_used_pre_refund, call_data_floor_cost) + block_regular = gas_used_pre_refund refund_tx_block_gas_used = max(block_regular, tx_state_gas) blockchain_test( @@ -293,7 +293,7 @@ def test_simple_gas_accounting( ) @pytest.mark.with_all_refund_types() @pytest.mark.execute(pytest.mark.skip(reason="Requires specific gas price")) -@pytest.mark.valid_from("EIP7778") +@pytest.mark.valid_from("EIP8037") def test_multi_transaction_gas_accounting( blockchain_test: BlockchainTestFiller, pre: Alloc, @@ -352,13 +352,17 @@ def test_multi_transaction_gas_accounting( exceed_block_gas_limit=exceed_block_gas_limit, ) refund_tx_gas_used = max(gas_used_post_refund, call_data_floor_cost) - refund_tx_block_regular = max(gas_used_pre_refund, call_data_floor_cost) extra_tx_sender = pre.fund_eoa() extra_tx_calldata = b"\xff" if extra_tx_data_floor else b"" extra_tx_intrinsic_gas_cost = intrinsic_cost_calc( calldata=extra_tx_calldata ) + # Block regular gas uses actual charge, not the tx-level floor. + extra_tx_block_gas = intrinsic_cost_calc( + calldata=extra_tx_calldata, + return_cost_deducted_prior_execution=True, + ) extra_tx = Transaction( to=stop_address, @@ -368,20 +372,28 @@ def test_multi_transaction_gas_accounting( expected_receipt={ "gas_used": refund_tx_gas_used + extra_tx_intrinsic_gas_cost, }, - error=TransactionException.GAS_ALLOWANCE_EXCEEDED - if exceed_block_gas_limit - else None, + error=( + TransactionException.GAS_ALLOWANCE_EXCEEDED + if exceed_block_gas_limit + else None + ), ) # EIP-8037: block_gas_used = max(sum_regular, sum_state) # Extra tx has no state gas, so its state gas contribution = 0 - block_regular = refund_tx_block_regular + extra_tx_intrinsic_gas_cost + block_regular = gas_used_pre_refund + extra_tx_block_gas block_state = tx_state_gas total_block_gas_used = max(block_regular, block_state) + # The block gas_limit must accommodate extra_tx's full gas_limit (which + # may be floor-inclusive) even though block gas_used uses the lower actual + # charge. For exceed_block_gas_limit=True we set the limit below + # total_block_gas_used to test that the extra_tx fails. if exceed_block_gas_limit: environment_gas_limit = total_block_gas_used - 1 else: - environment_gas_limit = total_block_gas_used + environment_gas_limit = ( + gas_used_pre_refund + extra_tx_intrinsic_gas_cost + ) txs = [refund_tx, extra_tx] @@ -450,7 +462,7 @@ class CallDataTestType(Enum): "interval that DATA_FLOOR_BETWEEN needs is empty" ), ) -@pytest.mark.valid_from("EIP7778") +@pytest.mark.valid_from("EIP8037") def test_varying_calldata_costs( blockchain_test: BlockchainTestFiller, pre: Alloc, @@ -553,7 +565,7 @@ def test_varying_calldata_costs( ) # EIP-8037: block gas_used = max(block_regular_gas, block_state_gas) - block_regular = max(call_data_floor_cost, gas_used_pre_refund) + block_regular = gas_used_pre_refund refund_tx_block_gas_used = max(block_regular, tx_state_gas) blockchain_test( @@ -605,7 +617,7 @@ def test_multiple_refund_types_in_one_tx( ) # EIP-8037: block gas_used = max(block_regular_gas, block_state_gas) - block_regular = max(gas_used_pre_refund, call_data_floor_cost) + block_regular = gas_used_pre_refund refund_tx_block_gas_used = max(block_regular, tx_state_gas) blockchain_test( @@ -621,24 +633,24 @@ def test_multiple_refund_types_in_one_tx( @pytest.mark.execute(pytest.mark.skip(reason="Requires specific gas price")) -@pytest.mark.valid_from("EIP7778") +@pytest.mark.valid_from("EIP8037") def test_mixed_gas_regimes( blockchain_test: BlockchainTestFiller, pre: Alloc, fork: Fork, ) -> None: """ - Lock in `block.gas_used == sum_i max(pre_refund_i, floor_i)` across a - block where each tx hits a different EIP-7778 regime. + Lock in block-level gas accounting across a block where each tx hits a + different regime. tx1: SSTORE-set fresh slot (no refund, pre_refund > floor). tx2: SSTORE-clear x10 (normal refund, refund not clipped to floor). - tx3: 1000 zero-byte calldata to STOP (floor binds upward). + tx3: 1000 zero-byte calldata to STOP (floor binds upward for fee only). - The 2-tx `test_multi_transaction_gas_accounting` covers a refund tx - plus a minimal extra tx but never combines a refund-bearing tx with - a floor-binding tx in the same block. Per-tx sender balance is also - asserted to lock in that the floor-binding tx pays + After EIP-8037's calldata-floor alignment, the floor only affects tx-level + fee calculation (tx_gas_used = max(post_refund, floor)); block regular gas + uses pre-refund gas minus state gas, with no floor applied. Per-tx sender + balance is also asserted to lock in that the floor-binding tx pays `floor * gas_price`, not `pre_refund * gas_price`. """ intrinsic_cost_calc = fork.transaction_intrinsic_cost_calculator() @@ -714,25 +726,27 @@ def test_mixed_gas_regimes( ) tx3_floor = data_floor_calc(data=tx3_data) assert tx3_floor > tx3_pre_refund, "tx3: floor must bind upward" - tx3_contribution = max(tx3_pre_refund, tx3_floor) + tx3_fee_gas = max(tx3_pre_refund, tx3_floor) + # Block regular gas uses pre-refund only; floor is tx-level only. + tx3_block_contribution = tx3_pre_refund tx3 = Transaction( to=tx3_target, - gas_limit=tx3_contribution, + gas_limit=tx3_fee_gas, sender=tx3_sender, data=tx3_data, # TODO: gas_used in expected_receipt is ignored by # verify_transaction_receipt; only cumulative_gas_used is # checked. To be fixed by #2855. - expected_receipt={"gas_used": tx3_contribution}, + expected_receipt={"gas_used": tx3_fee_gas}, ) tx3_gas_price = tx3.gas_price if tx3.gas_price else tx3.max_fee_per_gas assert tx3_gas_price is not None post[tx3_sender] = Account( - balance=initial_fund - tx3_contribution * tx3_gas_price + balance=initial_fund - tx3_fee_gas * tx3_gas_price ) total_gas_used = ( - tx1_block_contribution + tx2_contribution + tx3_contribution + tx1_block_contribution + tx2_contribution + tx3_block_contribution ) blockchain_test( diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py index 0afbf769a4c..6825b531638 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py @@ -469,7 +469,7 @@ def test_call_value_transfer_existing_account_no_state_gas( create new state, so no state gas is charged. """ # Existing target account - target = pre.fund_eoa(amount=0) + target = pre.fund_eoa(amount=1) parent_storage = Storage() parent = pre.deploy_contract( @@ -488,7 +488,10 @@ def test_call_value_transfer_existing_account_no_state_gas( sender=pre.fund_eoa(), ) - post = {parent: Account(storage=parent_storage)} + post = { + parent: Account(balance=0, storage=parent_storage), + target: Account(balance=2), + } state_test(pre=pre, post=post, tx=tx) @@ -555,13 +558,13 @@ def test_delegatecall_reservoir_passing( """ sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) - # Library code that writes to slot 0 — runs in parent's context + parent_storage = Storage() + # Library code runs in parent's context — slot is reserved on + # parent_storage so the post check uses the same source of truth. library = pre.deploy_contract( - code=Op.SSTORE(0, 1), + code=Op.SSTORE(parent_storage.store_next(1, "delegated"), 1), ) - parent_storage = Storage() - parent_storage[0] = 1 # Expect slot 0 = 1 after delegatecall parent = pre.deploy_contract( code=(Op.DELEGATECALL(gas=100_000, address=library)), ) @@ -656,40 +659,26 @@ def test_gas_opcode_excludes_reservoir( state_test(pre=pre, post=post, tx=tx) -@pytest.mark.parametrize( - "target_exists", - [ - pytest.param(True, id="existing_account"), - pytest.param(False, id="new_account"), - ], -) @pytest.mark.valid_from("EIP8037") def test_call_insufficient_balance_returns_reservoir( state_test: StateTestFiller, pre: Alloc, fork: Fork, - target_exists: bool, ) -> None: """ - Test CALL with insufficient balance returns reservoir to parent. - - When a CALL transfers value but the caller has insufficient balance, - the call fails before any state gas is charged for the target - account. Both gas_left and state_gas_left are returned to the - parent frame. The parent can still use the reservoir for a - subsequent SSTORE. + Test CALL with insufficient balance returns the reservoir to parent. + + A value-bearing CALL to an existing account fails the balance check + before entering the child frame; gas_left and state_gas_left are + returned to the parent, which can still use the reservoir for a + later SSTORE. The new-account variant (where NEW_ACCOUNT is charged + then refilled on the same failure) is pinned by + test_call_insufficient_balance_refunds_new_account_state_gas. """ - gas_costs = fork.gas_costs() sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) - target: int | Address - if target_exists: - target = pre.deploy_contract(code=Op.STOP) - reservoir = sstore_state_gas - else: - target = 0xDEAD - # New account needs new-account state gas too - reservoir = sstore_state_gas + gas_costs.NEW_ACCOUNT + target = pre.deploy_contract(code=Op.STOP) + reservoir = sstore_state_gas storage = Storage() contract = pre.deploy_contract( @@ -819,7 +808,7 @@ def test_call_pre_charged_costs_excluded_from_forwarding( child_code = Op.SSTORE(child_storage.store_next(1, "child_ran"), 1) child = pre.deploy_contract(child_code) - child_regular_gas = 2 * gas_costs.VERY_LOW + gas_costs.COLD_STORAGE_WRITE + child_regular_gas = child_code.regular_cost(fork) # Memory expansion triggered by ret_size on the wrapper's CALL ret_size = 512 * 32 # 512 words @@ -1410,7 +1399,6 @@ def test_create_oog_during_state_gas_charge( SSTORE is forwarded only its regular stipend, so it succeeds only if the refund landed in the reservoir (not in `gas_left`). """ - gas_costs = fork.gas_costs() sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) init_code = Op.STOP @@ -1430,11 +1418,11 @@ def test_create_oog_during_state_gas_charge( ), ) - grandchild = pre.deploy_contract(code=Op.SSTORE(0, 1)) + grandchild_storage = Storage() + grandchild_code = Op.SSTORE(grandchild_storage.store_next(1, "ran"), 1) + grandchild = pre.deploy_contract(code=grandchild_code) - push_cost = 2 * gas_costs.VERY_LOW - sstore_regular = gas_costs.COLD_STORAGE_WRITE - grandchild_stipend = push_cost + sstore_regular + grandchild_stipend = grandchild_code.regular_cost(fork) parent = pre.deploy_contract( code=( @@ -1451,7 +1439,7 @@ def test_create_oog_during_state_gas_charge( state_test( pre=pre, - post={grandchild: Account(storage={0: 1})}, + post={grandchild: Account(storage=grandchild_storage)}, tx=tx, ) @@ -1523,7 +1511,8 @@ def test_child_failure_refunds_state_gas_to_reservoir_not_gas_left( gas_costs = fork.gas_costs() sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) - probe = pre.deploy_contract(code=Op.SSTORE(0, 1)) + probe_storage = Storage() + probe_code = Op.SSTORE(probe_storage.store_next(1, "probe_ran"), 1) if charge_via == "sstore": child_code: Bytecode = Op.SSTORE(0, 1) + Op.REVERT(0, 0) @@ -1538,13 +1527,8 @@ def test_child_failure_refunds_state_gas_to_reservoir_not_gas_left( child_state_charge = gas_costs.NEW_ACCOUNT child = pre.deploy_contract(code=child_code, balance=child_balance) - - # Tight stipend: just enough regular gas for the probe's SSTORE - # opcode plus its two stack pushes, leaving no slack to absorb a - # state-gas spill. - push_cost = 2 * gas_costs.VERY_LOW - sstore_regular = gas_costs.COLD_STORAGE_WRITE - probe_stipend = push_cost + sstore_regular + probe = pre.deploy_contract(probe_code) + probe_stipend = probe_code.regular_cost(fork) parent = pre.deploy_contract( code=( @@ -1567,9 +1551,9 @@ def test_child_failure_refunds_state_gas_to_reservoir_not_gas_left( # context, so the probe's SSTORE lands on `parent` instead of # `probe`. if call_opcode == Op.DELEGATECALL: - post: dict = {parent: Account(storage={0: 1})} + post: dict = {parent: Account(storage=probe_storage)} else: - post = {probe: Account(storage={0: 1})} + post = {probe: Account(storage=probe_storage)} state_test( pre=pre, @@ -1577,3 +1561,89 @@ def test_child_failure_refunds_state_gas_to_reservoir_not_gas_left( tx=tx, blockchain_test_header_verify=Header(gas_used=sstore_state_gas), ) + + +@pytest.mark.valid_from("EIP8037") +def test_call_insufficient_balance_refunds_new_account_state_gas( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Refill NEW_ACCOUNT state gas on a value CALL that fails the balance + check before the child frame. + """ + gas_costs = fork.gas_costs() + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + new_account_state_gas = gas_costs.NEW_ACCOUNT + + probe_storage = Storage() + probe_code = Op.SSTORE(probe_storage.store_next(1, "probe_ran"), 1) + probe = pre.deploy_contract(probe_code) + + probe_stipend = probe_code.regular_cost(fork) + + non_existent_account = pre.nonexistent_account() + + parent = pre.deploy_contract( + code=( + Op.POP(Op.CALL(gas=Op.GAS, address=non_existent_account, value=1)) + + Op.POP(Op.CALL(gas=probe_stipend, address=probe)) + ), + balance=0, + ) + + assert new_account_state_gas >= sstore_state_gas + reservoir = new_account_state_gas + + tx = Transaction( + to=parent, + state_gas_reservoir=reservoir, + sender=pre.fund_eoa(), + ) + + post = {probe: Account(storage=probe_storage)} + state_test(pre=pre, post=post, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_call_value_precompile_halt_refunds_new_account_state_gas( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Refill NEW_ACCOUNT state gas on a value CALL to an unfunded + precompile that halts in the child frame. + """ + gas_costs = fork.gas_costs() + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + new_account_state_gas = gas_costs.NEW_ACCOUNT + + probe_storage = Storage() + probe_code = Op.SSTORE(probe_storage.store_next(1, "probe_ran"), 1) + probe = pre.deploy_contract(probe_code) + + probe_stipend = probe_code.regular_cost(fork) + + ecpairing = 0x08 + + parent = pre.deploy_contract( + code=( + Op.POP(Op.CALL(1, ecpairing, 1, 0, 0, 0, 0)) + + Op.POP(Op.CALL(gas=probe_stipend, address=probe)) + ), + balance=1, + ) + + assert new_account_state_gas >= sstore_state_gas + reservoir = new_account_state_gas + + tx = Transaction( + to=parent, + state_gas_reservoir=reservoir, + sender=pre.fund_eoa(), + ) + + post = {probe: Account(storage=probe_storage)} + state_test(pre=pre, post=post, tx=tx) From b14bc8160da274f9949cc81101c9248084024d4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Tue, 23 Jun 2026 09:33:07 +0200 Subject: [PATCH 047/233] feat(tests): EIP-8037 code-deposit state gas exact-fit boundary (#3031) * feat(tests): EIP-8037 code-deposit state gas exact-fit boundary Pin the code-deposit state gas charge at its exact-fit gas boundary. A CREATE transaction deploys code via RETURN(0, code_size); after the init code returns, code deposit charges keccak regular gas from gas_left then code_size * COST_PER_STATE_BYTE state gas, reservoir first and spilling into gas_left. test_code_deposit_state_gas_exact_fit_boundary sets the transaction gas so the deposit charge lands exactly at the available gas (the contract deploys) or one gas short (the deposit halts, NEW_ACCOUNT is refilled, and no code is deployed). The reservoir_funded case uses code_size = MAX_CODE_SIZE so the deposit exceeds the EIP-7825 cap and is drawn reservoir first (the shortfall reduces the reservoir; an over-cap halt bills exactly the cap); the gas_left_spill case uses an in-cap gas limit so the deposit spills wholly from gas_left. The scaling tests vary the size but assert success only, leaving this boundary unpinned. --- .../test_state_gas_create.py | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py index 0047d2cf3d6..9f1a366ebbf 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py @@ -233,6 +233,90 @@ def test_code_deposit_state_gas_scales_with_size( state_test(pre=pre, post=post, tx=tx) +@pytest.mark.parametrize( + ("funding", "gas_delta"), + [ + pytest.param("reservoir", 0, id="reservoir_success"), + pytest.param("reservoir", -1, id="reservoir_oog"), + pytest.param("spill", 0, id="spill_success"), + pytest.param("spill", -1, id="spill_oog"), + ], +) +@EIPChecklist.GasCostChanges.Test.OutOfGas() +@pytest.mark.valid_from("EIP8037") +def test_code_deposit_state_gas_exact_fit_boundary( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + funding: str, + gas_delta: int, +) -> None: + """ + Pin the code-deposit state gas at its exact-fit boundary. + + A CREATE tx deploys ``code_size`` bytes with ``gas_limit`` set so the + deposit lands exactly at the available gas (deploys) or one gas short + (halts: state restored, NEW_ACCOUNT refilled, no code). The two + regimes pin the halt billing: over-cap ``reservoir`` rolls the + reservoir back so the sender pays the cap; in-cap ``spill`` burns + ``gas_left`` and bills ``gas_limit - NEW_ACCOUNT``. The scaling + tests assert success only. + """ + gas_costs = fork.gas_costs() + cap = fork.transaction_gas_limit_cap() + assert cap is not None + + code_size = fork.max_code_size() if funding == "reservoir" else 1000 + + words = (code_size + 31) // 32 + memory_gas = gas_costs.MEMORY_PER_WORD * words + words * words // 512 + init_code = Op.RETURN(0, code_size) + init_exec_regular = init_code.regular_cost(fork) + memory_gas + keccak_gas = gas_costs.OPCODE_KECCAK256_PER_WORD * words + deposit_state_gas = fork.code_deposit_state_gas(code_size=code_size) + + intrinsic_total = fork.transaction_intrinsic_cost_calculator()( + calldata=bytes(init_code), + contract_creation=True, + return_cost_deducted_prior_execution=True, + ) + exact_fit_gas = ( + intrinsic_total + init_exec_regular + keccak_gas + deposit_state_gas + ) + if funding == "reservoir": + assert exact_fit_gas > cap + else: + assert exact_fit_gas <= cap + + sender = pre.fund_eoa() + created = compute_create_address(address=sender, nonce=0) + gas_limit = exact_fit_gas + gas_delta + + post: dict + if gas_delta == 0: + receipt_gas_used = exact_fit_gas + post = {created: Account(code=b"\x00" * code_size)} + else: + receipt_gas_used = ( + cap + if funding == "reservoir" + else gas_limit - gas_costs.NEW_ACCOUNT + ) + post = {created: Account.NONEXISTENT} + + tx = Transaction( + to=None, + data=init_code, + gas_limit=gas_limit, + sender=sender, + expected_receipt=TransactionReceipt( + cumulative_gas_used=receipt_gas_used + ), + ) + + state_test(pre=pre, post=post, tx=tx) + + @pytest.mark.valid_from("EIP8037") def test_repeated_create_same_code_charges_each_account( state_test: StateTestFiller, From c81abf9b35fd1d4215ea44bca74594e652c904b0 Mon Sep 17 00:00:00 2001 From: Sam Wilson Date: Wed, 17 Jun 2026 16:11:17 -0400 Subject: [PATCH 048/233] chore(ci): update mypy --- Justfile | 4 +- packages/testing/pyproject.toml | 2 +- .../base_types/base_types.py | 4 +- .../execution_testing/base_types/pydantic.py | 2 +- .../cli/gentest/test_providers.py | 2 +- .../logging/tests/test_logging.py | 4 +- .../src/execution_testing/specs/base.py | 2 +- pyproject.toml | 18 +- .../compute/instruction/test_storage.py | 2 + tests/json_loader/helpers/fixtures.py | 2 +- tests/prague/eip7702_set_code_tx/test_gas.py | 7 +- uv.lock | 268 ++++++++++-------- 12 files changed, 189 insertions(+), 128 deletions(-) diff --git a/Justfile b/Justfile index 3b25cc77a55..d464b884949 100644 --- a/Justfile +++ b/Justfile @@ -130,7 +130,7 @@ fill *args: [group('integration tests')] fill-pypy *args: @mkdir -p "{{ output_dir }}/fill-pypy/tmp" "{{ output_dir }}/fill-pypy/logs" - uv run --python pypy3.11 fill \ + uv run --python pypy3.11 --no-dev --group test fill \ --skip-index \ --output="{{ output_dir }}/fill-pypy/fixtures" \ --no-html \ @@ -197,7 +197,7 @@ test-tests *args: [group('unit tests')] test-tests-pypy *args: @mkdir -p "{{ output_dir }}/test-tests-pypy/tmp" - cd packages/testing && uv run --python pypy3.11 pytest \ + cd packages/testing && uv run --python pypy3.11 --no-dev --group test pytest \ -n auto --maxprocesses 6 \ --basetemp="{{ output_dir }}/test-tests-pypy/tmp" \ --ignore=src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_benchmarking.py \ diff --git a/packages/testing/pyproject.toml b/packages/testing/pyproject.toml index 38b19f71926..afd2e37ab02 100644 --- a/packages/testing/pyproject.toml +++ b/packages/testing/pyproject.toml @@ -68,7 +68,7 @@ test = [ ] lint = [ # ruff is included in package deps for gentest - "mypy==1.20.0", + "mypy==2.1.0", "types-requests>=2.31,<2.33", ] dev = [ diff --git a/packages/testing/src/execution_testing/base_types/base_types.py b/packages/testing/src/execution_testing/base_types/base_types.py index b42fb315485..c81b61ef8f3 100644 --- a/packages/testing/src/execution_testing/base_types/base_types.py +++ b/packages/testing/src/execution_testing/base_types/base_types.py @@ -308,7 +308,7 @@ def hex(self) -> str: @classmethod def __get_pydantic_core_schema__( - cls: Type[Self], source_type: Any, handler: GetCoreSchemaHandler + cls, source_type: Any, handler: GetCoreSchemaHandler ) -> PlainValidatorFunctionSchema: """ Call the class constructor without info and appends the serialization @@ -390,7 +390,7 @@ def __ne__(self, other: object) -> bool: @classmethod def __get_pydantic_core_schema__( - cls: Type[Self], source_type: Any, handler: GetCoreSchemaHandler + cls, source_type: Any, handler: GetCoreSchemaHandler ) -> PlainValidatorFunctionSchema: """ Call the class constructor without info and appends the serialization diff --git a/packages/testing/src/execution_testing/base_types/pydantic.py b/packages/testing/src/execution_testing/base_types/pydantic.py index dee04ced28c..e36e7d96d98 100644 --- a/packages/testing/src/execution_testing/base_types/pydantic.py +++ b/packages/testing/src/execution_testing/base_types/pydantic.py @@ -28,7 +28,7 @@ class EthereumTestRootModel( class CopyValidateModel(EthereumTestBaseModel): """Model that supports copying with validation.""" - def copy(self: Self, **kwargs: Any) -> Self: + def copy(self, **kwargs: Any) -> Self: """ Create a copy of the model with the updated fields that are validated. """ diff --git a/packages/testing/src/execution_testing/cli/gentest/test_providers.py b/packages/testing/src/execution_testing/cli/gentest/test_providers.py index 9bd373bb191..8401c1394ca 100644 --- a/packages/testing/src/execution_testing/cli/gentest/test_providers.py +++ b/packages/testing/src/execution_testing/cli/gentest/test_providers.py @@ -44,7 +44,7 @@ class BlockchainTestProvider(BaseModel): def _get_environment_kwargs(self) -> str: env_str = "" pad = " " - for field, value in self.block.dict().items(): + for field, value in self.block.model_dump().items(): env_str += ( f'{pad}{field}="{value}",\n' if field == "coinbase" diff --git a/packages/testing/src/execution_testing/logging/tests/test_logging.py b/packages/testing/src/execution_testing/logging/tests/test_logging.py index eb5d479e51e..87fe76ac89c 100644 --- a/packages/testing/src/execution_testing/logging/tests/test_logging.py +++ b/packages/testing/src/execution_testing/logging/tests/test_logging.py @@ -32,8 +32,8 @@ def test_custom_levels_registered(self) -> None: """Test that custom log levels are properly registered.""" assert logging.getLevelName(VERBOSE_LEVEL) == "VERBOSE" assert logging.getLevelName(FAIL_LEVEL) == "FAIL" - assert logging.getLevelName("VERBOSE") == VERBOSE_LEVEL - assert logging.getLevelName("FAIL") == FAIL_LEVEL + assert logging.getLevelName("VERBOSE") == VERBOSE_LEVEL # type: ignore[deprecated] + assert logging.getLevelName("FAIL") == FAIL_LEVEL # type: ignore[deprecated] def test_get_logger(self) -> None: """Test that get_logger returns a properly typed logger.""" diff --git a/packages/testing/src/execution_testing/specs/base.py b/packages/testing/src/execution_testing/specs/base.py index 8aee027f097..49cf517ff7b 100644 --- a/packages/testing/src/execution_testing/specs/base.py +++ b/packages/testing/src/execution_testing/specs/base.py @@ -170,7 +170,7 @@ def __pydantic_init_subclass__(cls, **kwargs: Any) -> None: @classmethod def from_test( - cls: Type[Self], + cls, *, base_test: "BaseTest", **kwargs: Any, diff --git a/pyproject.toml b/pyproject.toml index c436bfe5ff8..8edf9df95a5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -214,7 +214,7 @@ test = [ ] lint = [ "codespell==2.4.1", - "mypy==1.20.0", + "mypy==2.1.0", "ruff==0.13.2", "vulture==2.14.0", "types-requests>=2.31,<2.33", @@ -494,6 +494,22 @@ warn_redundant_casts = true ignore_missing_imports = false mypy_path = ["src", "packages/testing/src", "packages/testing/stubs"] files = ["src", "tests", "packages"] +enable_error_code = [ + "truthy-iterable", + "unused-awaitable", + "redundant-self", + "unused-ignore", + "unimported-reveal", + "exhaustive-match", + "deprecated", + + #"mutable-override", + #"truthy-bool", + #"explicit-override", + #"ignore-without-code", + #"possibly-undefined", + #"redundant-expr", +] exclude = [ "^\\.cache/", "^\\.devcontainer/", diff --git a/tests/benchmark/compute/instruction/test_storage.py b/tests/benchmark/compute/instruction/test_storage.py index bb2814a9252..98f7dfd89eb 100644 --- a/tests/benchmark/compute/instruction/test_storage.py +++ b/tests/benchmark/compute/instruction/test_storage.py @@ -180,6 +180,8 @@ def create_benchmark_executor( current_value=original, new_value=2**256 - 1, ) + case _: + raise ValueError # [index, num] loop_condition = ( diff --git a/tests/json_loader/helpers/fixtures.py b/tests/json_loader/helpers/fixtures.py index 38c6b2f3088..ea82bec67ec 100644 --- a/tests/json_loader/helpers/fixtures.py +++ b/tests/json_loader/helpers/fixtures.py @@ -90,7 +90,7 @@ def clear_data_cache(self) -> None: del self.data def collect( - self: Self, + self, ) -> Generator[Item | Collector, None, None]: """Collect test cases from a single JSON fixtures file.""" try: diff --git a/tests/prague/eip7702_set_code_tx/test_gas.py b/tests/prague/eip7702_set_code_tx/test_gas.py index 1c0e111ecf8..3321a893a63 100644 --- a/tests/prague/eip7702_set_code_tx/test_gas.py +++ b/tests/prague/eip7702_set_code_tx/test_gas.py @@ -434,9 +434,10 @@ def authorize_to_address( return pre.fund_eoa(1) case AddressType.CONTRACT: return pre.deploy_contract(Op.STOP) - raise ValueError( - f"Unsupported authorization address case: {request.param}" - ) + case _: + raise ValueError( + f"Unsupported authorization address case: {request.param}" + ) @pytest.fixture() diff --git a/uv.lock b/uv.lock index 3a5fc74c119..9694dd767d5 100644 --- a/uv.lock +++ b/uv.lock @@ -2,7 +2,8 @@ version = 1 revision = 3 requires-python = ">=3.11" resolution-markers = [ - "python_full_version >= '3.13'", + "python_full_version >= '3.15'", + "python_full_version >= '3.13' and python_full_version < '3.15'", "python_full_version < '3.13'", ] @@ -36,6 +37,46 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c9/7f/09065fd9e27da0eda08b4d6897f1c13535066174cc023af248fc2a8d5e5a/asn1crypto-1.5.1-py2.py3-none-any.whl", hash = "sha256:db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67", size = 105045, upload-time = "2022-03-15T14:46:51.055Z" }, ] +[[package]] +name = "ast-serialize" +version = "0.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/81/9d/09e27731bd5864a9ce04e3244074e674bb8936bf62b45e0357248717adac/ast_serialize-0.5.0.tar.gz", hash = "sha256:5880091bfe6f4f986f22866375c2e884843e7a0b6343ae41aeea659613d879b6", size = 61157, upload-time = "2026-05-17T17:48:29.429Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/9a/13dde51ba9e15f8b97957ab7cb0120d0e381524d651c6bd630b9c359227f/ast_serialize-0.5.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8f5c14f169eb0972c0c21bada5358b23d6047c76583b005234f865b11f1fa00a", size = 1183520, upload-time = "2026-05-17T17:47:30.831Z" }, + { url = "https://files.pythonhosted.org/packages/37/de/5a7f0a9fe68944f536632a5af84676739c7d2582be42deb082634bf3a754/ast_serialize-0.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7d1a2de9de5be04652f0ed60738356ef94f66db37924a9499fffe98dc491aa0b", size = 1175779, upload-time = "2026-05-17T17:47:32.551Z" }, + { url = "https://files.pythonhosted.org/packages/9c/81/0bb853e76e4f6e9a1855d569003c59e19ffac45f7079d91505d1bb212f92/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be5173fb66f9b49026d9d5a2ff0fc7c7009077107c0eb285b2d60fdf1fe10bd1", size = 1233750, upload-time = "2026-05-17T17:47:34.731Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d3/4cf705beeccc08754d0bbda99aefff26110e209b9a07ac8a6b60eec48531/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f8015cd071ac1339924ee2b8098c93e00e155f30a16f40ec9816fcf84f4753f6", size = 1235942, upload-time = "2026-05-17T17:47:36.287Z" }, + { url = "https://files.pythonhosted.org/packages/26/c8/ee097e437ea27dd2b8b227865c875492b585650a5802a22d82b304c8201b/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5499e8797edff2a9186aa313ed382c6b422e798e9332d9953badcee6e69a88f2", size = 1442517, upload-time = "2026-05-17T17:47:38.17Z" }, + { url = "https://files.pythonhosted.org/packages/ff/bd/68063442838f1ba68ec72b5436430bc75b3bb17a1a3c3063f09b0c05ae2b/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6848f2a093fb5548751a9a09bff8fcd229e2bbeb0e3331f391b6ae6d26cd9903", size = 1254081, upload-time = "2026-05-17T17:47:39.826Z" }, + { url = "https://files.pythonhosted.org/packages/50/e2/1e520793bc6a4e4524a6ab022391e827825eaa0c3811828bfdc6852eca26/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:832d4c998e0b091fd60a6d6bceee535483c4d490de9ba85003af835225719261", size = 1259910, upload-time = "2026-05-17T17:47:41.369Z" }, + { url = "https://files.pythonhosted.org/packages/4e/e1/49b60f467979979cfe6913b43948ff25bca971ad0591d181812f163a988e/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:16db7c62ec0b8efe1d7afd283a388d8f74f2605d56032e5a37747d2de8dba027", size = 1250678, upload-time = "2026-05-17T17:47:43.702Z" }, + { url = "https://files.pythonhosted.org/packages/74/ba/66ab9555de6275677566f6574e5ef6c29cb185ea866f643bc06f8280a8ee/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:baf5eb061eb5bccade4128ad42da33787d72f6013809cd1b590376ece8b3c937", size = 1301603, upload-time = "2026-05-17T17:47:46.256Z" }, + { url = "https://files.pythonhosted.org/packages/66/42/6aca9b9abc710014b2be9059689e5dd1679339e78f567ffb4d255a9e2050/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:104e4a35bd7c124173c41760ef9aaea17ddb3f86c65cb643671d59afbe3ee94c", size = 1410332, upload-time = "2026-05-17T17:47:47.899Z" }, + { url = "https://files.pythonhosted.org/packages/47/68/2f76594432a22581ecf878b5e75a9b8601c24b2241cf0bbeb1e21fcf370c/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:36be371028fc1675acb38a331bde160dbab7ff907fdf00b67eb6911aa106951b", size = 1509979, upload-time = "2026-05-17T17:47:50.942Z" }, + { url = "https://files.pythonhosted.org/packages/40/ac/a93c9b58292653f6c595752f677a08e608f903b710594909e9231a389b3b/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:061ee58bdb52341c8201a6df41182a977736bae3b7ded87ca7176ca25a8a47ab", size = 1505002, upload-time = "2026-05-17T17:47:54.093Z" }, + { url = "https://files.pythonhosted.org/packages/14/2e/b278f68c497ee2f1d1576cbbef8db5281cd4a5f2db040537592ac9c8862e/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b15219e9cdc9f53f6f4cb51c009203507228226148c05c5e8fe451c28b435eb3", size = 1456231, upload-time = "2026-05-17T17:47:56.311Z" }, + { url = "https://files.pythonhosted.org/packages/0b/43/419be1c566a4c504cd8fd60ce2f84e790f295495c0f327cfaeadf3d51012/ast_serialize-0.5.0-cp314-cp314t-win32.whl", hash = "sha256:842d1c004bb466c7df036f95fabef789570541922b10976b12f5592a69cf0b38", size = 1058668, upload-time = "2026-05-17T17:47:58.305Z" }, + { url = "https://files.pythonhosted.org/packages/03/6f/c9d4d549295ed05111aeb8853232d1afd9d0a179fddb01eeffbb3a4a6842/ast_serialize-0.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b0c06d760909b095cc466356dfccd05a1c7233a6ca191c020dca2c6a6f16c24c", size = 1101075, upload-time = "2026-05-17T17:48:00.35Z" }, + { url = "https://files.pythonhosted.org/packages/d0/8e/d00c5ab30c58222e07d62956fca86c59d91b9ad32997e633c38b526623a3/ast_serialize-0.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:787baedb0262cc49e8ce37cc15c00ae818e46a165a3b36f5e21ed174998104cb", size = 1075347, upload-time = "2026-05-17T17:48:01.753Z" }, + { url = "https://files.pythonhosted.org/packages/e0/9e/dc2530acb3a60dc6e46d65abf27d1d9f86721694757906a148d90a6860de/ast_serialize-0.5.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:0668aa9459cfa8c9c49ddd2163ebcf43088ba045ef7492af6fe22e0098303101", size = 1191380, upload-time = "2026-05-17T17:48:03.738Z" }, + { url = "https://files.pythonhosted.org/packages/26/0a/bd3d18a582f273d6c843d16bb9e22e9e16365ff7991e92f18f798e9f1224/ast_serialize-0.5.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:bf683d6363edf2b39eed6b6d4fe22d34b6203867a67e27134d9e2a2680c4bc4a", size = 1183879, upload-time = "2026-05-17T17:48:05.463Z" }, + { url = "https://files.pythonhosted.org/packages/40/ae/1f919100f8620887af58fcc381c61a1f218cdf89c6e155f87b213e61010a/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc22cf0c9be65e71cf88fda130af60d61eb4a79370ad4cfe7900d48a4aa2211", size = 1244529, upload-time = "2026-05-17T17:48:07.008Z" }, + { url = "https://files.pythonhosted.org/packages/c6/ca/6376559dcce707cdbc1d0d9a13c8d3baaaa501e949ce0ebdc4230cd881aa/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f66173891548c9f2726bf27957b41cabce12fa679dc6da505ddbde4d4b3b31cf", size = 1240560, upload-time = "2026-05-17T17:48:08.46Z" }, + { url = "https://files.pythonhosted.org/packages/35/b2/a620e206b5aeb7efbf2710336df57d457cffbb3991076bbcc1147ef9abd4/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e42d729ef2be96a14efbad355093284739e3670ece3e534f82cc8832790911d9", size = 1451172, upload-time = "2026-05-17T17:48:09.922Z" }, + { url = "https://files.pythonhosted.org/packages/fa/e0/4ad5c04c24a40481b2935ce9a0ccdb6023dc8b667167d06ae530cc3512f2/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b725026bafa801dbd7310eb13a75f0a2e370e7e51b2cb225f9d21fcfadf919ee", size = 1265072, upload-time = "2026-05-17T17:48:11.469Z" }, + { url = "https://files.pythonhosted.org/packages/b2/71/4d1d479aa56d0101c40e17720c3d6ac2af7269ea0487a80b18e7bfd1a5b7/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b54f60c1d78767a53b67eaa663f0dfac3afe606aa07f1301572f588b73d64809", size = 1270488, upload-time = "2026-05-17T17:48:13.575Z" }, + { url = "https://files.pythonhosted.org/packages/6d/4f/0de1bbe06f6edef9fde4ed12ca8e7b3ec7e6e2bd4e672c5af487f7957665/ast_serialize-0.5.0-cp39-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:27d51654fc240a1e87e742d353d98eb45b75f62f129086b3596ab53df2ac2a43", size = 1260702, upload-time = "2026-05-17T17:48:15.141Z" }, + { url = "https://files.pythonhosted.org/packages/75/61/e00872439cfdddcc3c1b6cdaa6e5d904ba8e26a18807c67c4e14409d0ca8/ast_serialize-0.5.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c36237c46dd1674542f2109740ea5ea485a169bf1431939ada0434e17934", size = 1311182, upload-time = "2026-05-17T17:48:16.779Z" }, + { url = "https://files.pythonhosted.org/packages/76/8e/699a5b955f7926956c95e9e1d74132acad73c2fe7a426f94da89123c20aa/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1943db345233cc7194a470f13afa9c59772c0b123dea0c9414c4d4ca54369759", size = 1421410, upload-time = "2026-05-17T17:48:18.527Z" }, + { url = "https://files.pythonhosted.org/packages/a9/ae/d5b7626874478997adc7a29ab28accf21e596fb590c944290401dfd0b29e/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:df1c00022cbbcb064bfaa505aa9c9295362443ce5dacb459d1331d3da353f887", size = 1516587, upload-time = "2026-05-17T17:48:20.133Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ce/b59e02a82d9c4244d64cde502e0b00e83e38816abe19155ceb5437402c7f/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:cae65289fc456fde04af979a2be09302ef5d8ab92ef23e596d6746dc267ada27", size = 1515171, upload-time = "2026-05-17T17:48:21.921Z" }, + { url = "https://files.pythonhosted.org/packages/8b/38/d8d90042747d05aa08d4efcf1c99035a5f670a6bf4c214d31644392afbca/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:239a4c354e8d676e9d94631d1d4a64edc6b266f86ff3a5a80aedd344f342c01d", size = 1464668, upload-time = "2026-05-17T17:48:23.544Z" }, + { url = "https://files.pythonhosted.org/packages/dd/51/5b840c4df7334104cecffa28f23904fe81ca89ca223d2450e288de39fd3c/ast_serialize-0.5.0-cp39-abi3-win32.whl", hash = "sha256:143a4ef63285a075871908fda3672dc21864b83a8ec3ee12304aa3e4c5387b9a", size = 1068311, upload-time = "2026-05-17T17:48:25.027Z" }, + { url = "https://files.pythonhosted.org/packages/41/11/ca5672c7d491825bc4cd6702dea106a6b60d928707712ec257c7833ae476/ast_serialize-0.5.0-cp39-abi3-win_amd64.whl", hash = "sha256:cf25572c526add400f26a4750dc6ce0c3bb93fc1f75e7ae0cad4ce4f2cd5c590", size = 1108931, upload-time = "2026-05-17T17:48:26.591Z" }, + { url = "https://files.pythonhosted.org/packages/45/19/cc8bd127d28a43da249aa955cfd164cf8fd534e79e42cea96c4854d72fd0/ast_serialize-0.5.0-cp39-abi3-win_arm64.whl", hash = "sha256:92a31c9c20d25a076edaeec76b128a3535d74a24f340b9a8a7e96c9b86dc9642", size = 1081181, upload-time = "2026-05-17T17:48:28.122Z" }, +] + [[package]] name = "attrs" version = "25.4.0" @@ -987,7 +1028,7 @@ dev = [ { name = "mkdocs-material-extensions", specifier = ">=1.1.1,<2" }, { name = "mkdocstrings", specifier = ">=0.21.2,<1" }, { name = "mkdocstrings-python", specifier = ">=1.0.0,<2" }, - { name = "mypy", specifier = "==1.20.0" }, + { name = "mypy", specifier = "==2.1.0" }, { name = "pillow", specifier = ">=12,<13" }, { name = "psutil", specifier = ">=7.2.2" }, { name = "pyflakes", specifier = ">=3.0" }, @@ -1009,7 +1050,7 @@ doc = [ ] lint = [ { name = "codespell", specifier = "==2.4.1" }, - { name = "mypy", specifier = "==1.20.0" }, + { name = "mypy", specifier = "==2.1.0" }, { name = "ruff", specifier = "==0.13.2" }, { name = "types-requests", specifier = ">=2.31,<2.33" }, { name = "vulture", specifier = "==2.14.0" }, @@ -1143,12 +1184,12 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ - { name = "mypy", specifier = "==1.20.0" }, + { name = "mypy", specifier = "==2.1.0" }, { name = "pytest-cov", specifier = ">=4.1.0,<5" }, { name = "types-requests", specifier = ">=2.31,<2.33" }, ] lint = [ - { name = "mypy", specifier = "==1.20.0" }, + { name = "mypy", specifier = "==2.1.0" }, { name = "types-requests", specifier = ">=2.31,<2.33" }, ] test = [{ name = "pytest-cov", specifier = ">=4.1.0,<5" }] @@ -1487,75 +1528,75 @@ wheels = [ [[package]] name = "librt" -version = "0.8.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/56/9c/b4b0c54d84da4a94b37bd44151e46d5e583c9534c7e02250b961b1b6d8a8/librt-0.8.1.tar.gz", hash = "sha256:be46a14693955b3bd96014ccbdb8339ee8c9346fbe11c1b78901b55125f14c73", size = 177471, upload-time = "2026-02-17T16:13:06.101Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/01/0e748af5e4fee180cf7cd12bd12b0513ad23b045dccb2a83191bde82d168/librt-0.8.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:681dc2451d6d846794a828c16c22dc452d924e9f700a485b7ecb887a30aad1fd", size = 65315, upload-time = "2026-02-17T16:11:25.152Z" }, - { url = "https://files.pythonhosted.org/packages/9d/4d/7184806efda571887c798d573ca4134c80ac8642dcdd32f12c31b939c595/librt-0.8.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3b4350b13cc0e6f5bec8fa7caf29a8fb8cdc051a3bae45cfbfd7ce64f009965", size = 68021, upload-time = "2026-02-17T16:11:26.129Z" }, - { url = "https://files.pythonhosted.org/packages/ae/88/c3c52d2a5d5101f28d3dc89298444626e7874aa904eed498464c2af17627/librt-0.8.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ac1e7817fd0ed3d14fd7c5df91daed84c48e4c2a11ee99c0547f9f62fdae13da", size = 194500, upload-time = "2026-02-17T16:11:27.177Z" }, - { url = "https://files.pythonhosted.org/packages/d6/5d/6fb0a25b6a8906e85b2c3b87bee1d6ed31510be7605b06772f9374ca5cb3/librt-0.8.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:747328be0c5b7075cde86a0e09d7a9196029800ba75a1689332348e998fb85c0", size = 205622, upload-time = "2026-02-17T16:11:28.242Z" }, - { url = "https://files.pythonhosted.org/packages/b2/a6/8006ae81227105476a45691f5831499e4d936b1c049b0c1feb17c11b02d1/librt-0.8.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0af2bd2bc204fa27f3d6711d0f360e6b8c684a035206257a81673ab924aa11e", size = 218304, upload-time = "2026-02-17T16:11:29.344Z" }, - { url = "https://files.pythonhosted.org/packages/ee/19/60e07886ad16670aae57ef44dada41912c90906a6fe9f2b9abac21374748/librt-0.8.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d480de377f5b687b6b1bc0c0407426da556e2a757633cc7e4d2e1a057aa688f3", size = 211493, upload-time = "2026-02-17T16:11:30.445Z" }, - { url = "https://files.pythonhosted.org/packages/9c/cf/f666c89d0e861d05600438213feeb818c7514d3315bae3648b1fc145d2b6/librt-0.8.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d0ee06b5b5291f609ddb37b9750985b27bc567791bc87c76a569b3feed8481ac", size = 219129, upload-time = "2026-02-17T16:11:32.021Z" }, - { url = "https://files.pythonhosted.org/packages/8f/ef/f1bea01e40b4a879364c031476c82a0dc69ce068daad67ab96302fed2d45/librt-0.8.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9e2c6f77b9ad48ce5603b83b7da9ee3e36b3ab425353f695cba13200c5d96596", size = 213113, upload-time = "2026-02-17T16:11:33.192Z" }, - { url = "https://files.pythonhosted.org/packages/9b/80/cdab544370cc6bc1b72ea369525f547a59e6938ef6863a11ab3cd24759af/librt-0.8.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:439352ba9373f11cb8e1933da194dcc6206daf779ff8df0ed69c5e39113e6a99", size = 212269, upload-time = "2026-02-17T16:11:34.373Z" }, - { url = "https://files.pythonhosted.org/packages/9d/9c/48d6ed8dac595654f15eceab2035131c136d1ae9a1e3548e777bb6dbb95d/librt-0.8.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:82210adabbc331dbb65d7868b105185464ef13f56f7f76688565ad79f648b0fe", size = 234673, upload-time = "2026-02-17T16:11:36.063Z" }, - { url = "https://files.pythonhosted.org/packages/16/01/35b68b1db517f27a01be4467593292eb5315def8900afad29fabf56304ba/librt-0.8.1-cp311-cp311-win32.whl", hash = "sha256:52c224e14614b750c0a6d97368e16804a98c684657c7518752c356834fff83bb", size = 54597, upload-time = "2026-02-17T16:11:37.544Z" }, - { url = "https://files.pythonhosted.org/packages/71/02/796fe8f02822235966693f257bf2c79f40e11337337a657a8cfebba5febc/librt-0.8.1-cp311-cp311-win_amd64.whl", hash = "sha256:c00e5c884f528c9932d278d5c9cbbea38a6b81eb62c02e06ae53751a83a4d52b", size = 61733, upload-time = "2026-02-17T16:11:38.691Z" }, - { url = "https://files.pythonhosted.org/packages/28/ad/232e13d61f879a42a4e7117d65e4984bb28371a34bb6fb9ca54ec2c8f54e/librt-0.8.1-cp311-cp311-win_arm64.whl", hash = "sha256:f7cdf7f26c2286ffb02e46d7bac56c94655540b26347673bea15fa52a6af17e9", size = 52273, upload-time = "2026-02-17T16:11:40.308Z" }, - { url = "https://files.pythonhosted.org/packages/95/21/d39b0a87ac52fc98f621fb6f8060efb017a767ebbbac2f99fbcbc9ddc0d7/librt-0.8.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a28f2612ab566b17f3698b0da021ff9960610301607c9a5e8eaca62f5e1c350a", size = 66516, upload-time = "2026-02-17T16:11:41.604Z" }, - { url = "https://files.pythonhosted.org/packages/69/f1/46375e71441c43e8ae335905e069f1c54febee63a146278bcee8782c84fd/librt-0.8.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:60a78b694c9aee2a0f1aaeaa7d101cf713e92e8423a941d2897f4fa37908dab9", size = 68634, upload-time = "2026-02-17T16:11:43.268Z" }, - { url = "https://files.pythonhosted.org/packages/0a/33/c510de7f93bf1fa19e13423a606d8189a02624a800710f6e6a0a0f0784b3/librt-0.8.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:758509ea3f1eba2a57558e7e98f4659d0ea7670bff49673b0dde18a3c7e6c0eb", size = 198941, upload-time = "2026-02-17T16:11:44.28Z" }, - { url = "https://files.pythonhosted.org/packages/dd/36/e725903416409a533d92398e88ce665476f275081d0d7d42f9c4951999e5/librt-0.8.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:039b9f2c506bd0ab0f8725aa5ba339c6f0cd19d3b514b50d134789809c24285d", size = 209991, upload-time = "2026-02-17T16:11:45.462Z" }, - { url = "https://files.pythonhosted.org/packages/30/7a/8d908a152e1875c9f8eac96c97a480df425e657cdb47854b9efaa4998889/librt-0.8.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bb54f1205a3a6ab41a6fd71dfcdcbd278670d3a90ca502a30d9da583105b6f7", size = 224476, upload-time = "2026-02-17T16:11:46.542Z" }, - { url = "https://files.pythonhosted.org/packages/a8/b8/a22c34f2c485b8903a06f3fe3315341fe6876ef3599792344669db98fcff/librt-0.8.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:05bd41cdee35b0c59c259f870f6da532a2c5ca57db95b5f23689fcb5c9e42440", size = 217518, upload-time = "2026-02-17T16:11:47.746Z" }, - { url = "https://files.pythonhosted.org/packages/79/6f/5c6fea00357e4f82ba44f81dbfb027921f1ab10e320d4a64e1c408d035d9/librt-0.8.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adfab487facf03f0d0857b8710cf82d0704a309d8ffc33b03d9302b4c64e91a9", size = 225116, upload-time = "2026-02-17T16:11:49.298Z" }, - { url = "https://files.pythonhosted.org/packages/f2/a0/95ced4e7b1267fe1e2720a111685bcddf0e781f7e9e0ce59d751c44dcfe5/librt-0.8.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:153188fe98a72f206042be10a2c6026139852805215ed9539186312d50a8e972", size = 217751, upload-time = "2026-02-17T16:11:50.49Z" }, - { url = "https://files.pythonhosted.org/packages/93/c2/0517281cb4d4101c27ab59472924e67f55e375bc46bedae94ac6dc6e1902/librt-0.8.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dd3c41254ee98604b08bd5b3af5bf0a89740d4ee0711de95b65166bf44091921", size = 218378, upload-time = "2026-02-17T16:11:51.783Z" }, - { url = "https://files.pythonhosted.org/packages/43/e8/37b3ac108e8976888e559a7b227d0ceac03c384cfd3e7a1c2ee248dbae79/librt-0.8.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e0d138c7ae532908cbb342162b2611dbd4d90c941cd25ab82084aaf71d2c0bd0", size = 241199, upload-time = "2026-02-17T16:11:53.561Z" }, - { url = "https://files.pythonhosted.org/packages/4b/5b/35812d041c53967fedf551a39399271bbe4257e681236a2cf1a69c8e7fa1/librt-0.8.1-cp312-cp312-win32.whl", hash = "sha256:43353b943613c5d9c49a25aaffdba46f888ec354e71e3529a00cca3f04d66a7a", size = 54917, upload-time = "2026-02-17T16:11:54.758Z" }, - { url = "https://files.pythonhosted.org/packages/de/d1/fa5d5331b862b9775aaf2a100f5ef86854e5d4407f71bddf102f4421e034/librt-0.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:ff8baf1f8d3f4b6b7257fcb75a501f2a5499d0dda57645baa09d4d0d34b19444", size = 62017, upload-time = "2026-02-17T16:11:55.748Z" }, - { url = "https://files.pythonhosted.org/packages/c7/7c/c614252f9acda59b01a66e2ddfd243ed1c7e1deab0293332dfbccf862808/librt-0.8.1-cp312-cp312-win_arm64.whl", hash = "sha256:0f2ae3725904f7377e11cc37722d5d401e8b3d5851fb9273d7f4fe04f6b3d37d", size = 52441, upload-time = "2026-02-17T16:11:56.801Z" }, - { url = "https://files.pythonhosted.org/packages/c5/3c/f614c8e4eaac7cbf2bbdf9528790b21d89e277ee20d57dc6e559c626105f/librt-0.8.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7e6bad1cd94f6764e1e21950542f818a09316645337fd5ab9a7acc45d99a8f35", size = 66529, upload-time = "2026-02-17T16:11:57.809Z" }, - { url = "https://files.pythonhosted.org/packages/ab/96/5836544a45100ae411eda07d29e3d99448e5258b6e9c8059deb92945f5c2/librt-0.8.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cf450f498c30af55551ba4f66b9123b7185362ec8b625a773b3d39aa1a717583", size = 68669, upload-time = "2026-02-17T16:11:58.843Z" }, - { url = "https://files.pythonhosted.org/packages/06/53/f0b992b57af6d5531bf4677d75c44f095f2366a1741fb695ee462ae04b05/librt-0.8.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:eca45e982fa074090057132e30585a7e8674e9e885d402eae85633e9f449ce6c", size = 199279, upload-time = "2026-02-17T16:11:59.862Z" }, - { url = "https://files.pythonhosted.org/packages/f3/ad/4848cc16e268d14280d8168aee4f31cea92bbd2b79ce33d3e166f2b4e4fc/librt-0.8.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c3811485fccfda840861905b8c70bba5ec094e02825598bb9d4ca3936857a04", size = 210288, upload-time = "2026-02-17T16:12:00.954Z" }, - { url = "https://files.pythonhosted.org/packages/52/05/27fdc2e95de26273d83b96742d8d3b7345f2ea2bdbd2405cc504644f2096/librt-0.8.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e4af413908f77294605e28cfd98063f54b2c790561383971d2f52d113d9c363", size = 224809, upload-time = "2026-02-17T16:12:02.108Z" }, - { url = "https://files.pythonhosted.org/packages/7a/d0/78200a45ba3240cb042bc597d6f2accba9193a2c57d0356268cbbe2d0925/librt-0.8.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5212a5bd7fae98dae95710032902edcd2ec4dc994e883294f75c857b83f9aba0", size = 218075, upload-time = "2026-02-17T16:12:03.631Z" }, - { url = "https://files.pythonhosted.org/packages/af/72/a210839fa74c90474897124c064ffca07f8d4b347b6574d309686aae7ca6/librt-0.8.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e692aa2d1d604e6ca12d35e51fdc36f4cda6345e28e36374579f7ef3611b3012", size = 225486, upload-time = "2026-02-17T16:12:04.725Z" }, - { url = "https://files.pythonhosted.org/packages/a3/c1/a03cc63722339ddbf087485f253493e2b013039f5b707e8e6016141130fa/librt-0.8.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4be2a5c926b9770c9e08e717f05737a269b9d0ebc5d2f0060f0fe3fe9ce47acb", size = 218219, upload-time = "2026-02-17T16:12:05.828Z" }, - { url = "https://files.pythonhosted.org/packages/58/f5/fff6108af0acf941c6f274a946aea0e484bd10cd2dc37610287ce49388c5/librt-0.8.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fd1a720332ea335ceb544cf0a03f81df92abd4bb887679fd1e460976b0e6214b", size = 218750, upload-time = "2026-02-17T16:12:07.09Z" }, - { url = "https://files.pythonhosted.org/packages/71/67/5a387bfef30ec1e4b4f30562c8586566faf87e47d696768c19feb49e3646/librt-0.8.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2af9e01e0ef80d95ae3c720be101227edae5f2fe7e3dc63d8857fadfc5a1d", size = 241624, upload-time = "2026-02-17T16:12:08.43Z" }, - { url = "https://files.pythonhosted.org/packages/d4/be/24f8502db11d405232ac1162eb98069ca49c3306c1d75c6ccc61d9af8789/librt-0.8.1-cp313-cp313-win32.whl", hash = "sha256:086a32dbb71336627e78cc1d6ee305a68d038ef7d4c39aaff41ae8c9aa46e91a", size = 54969, upload-time = "2026-02-17T16:12:09.633Z" }, - { url = "https://files.pythonhosted.org/packages/5c/73/c9fdf6cb2a529c1a092ce769a12d88c8cca991194dfe641b6af12fa964d2/librt-0.8.1-cp313-cp313-win_amd64.whl", hash = "sha256:e11769a1dbda4da7b00a76cfffa67aa47cfa66921d2724539eee4b9ede780b79", size = 62000, upload-time = "2026-02-17T16:12:10.632Z" }, - { url = "https://files.pythonhosted.org/packages/d3/97/68f80ca3ac4924f250cdfa6e20142a803e5e50fca96ef5148c52ee8c10ea/librt-0.8.1-cp313-cp313-win_arm64.whl", hash = "sha256:924817ab3141aca17893386ee13261f1d100d1ef410d70afe4389f2359fea4f0", size = 52495, upload-time = "2026-02-17T16:12:11.633Z" }, - { url = "https://files.pythonhosted.org/packages/c9/6a/907ef6800f7bca71b525a05f1839b21f708c09043b1c6aa77b6b827b3996/librt-0.8.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6cfa7fe54fd4d1f47130017351a959fe5804bda7a0bc7e07a2cdbc3fdd28d34f", size = 66081, upload-time = "2026-02-17T16:12:12.766Z" }, - { url = "https://files.pythonhosted.org/packages/1b/18/25e991cd5640c9fb0f8d91b18797b29066b792f17bf8493da183bf5caabe/librt-0.8.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:228c2409c079f8c11fb2e5d7b277077f694cb93443eb760e00b3b83cb8b3176c", size = 68309, upload-time = "2026-02-17T16:12:13.756Z" }, - { url = "https://files.pythonhosted.org/packages/a4/36/46820d03f058cfb5a9de5940640ba03165ed8aded69e0733c417bb04df34/librt-0.8.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7aae78ab5e3206181780e56912d1b9bb9f90a7249ce12f0e8bf531d0462dd0fc", size = 196804, upload-time = "2026-02-17T16:12:14.818Z" }, - { url = "https://files.pythonhosted.org/packages/59/18/5dd0d3b87b8ff9c061849fbdb347758d1f724b9a82241aa908e0ec54ccd0/librt-0.8.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:172d57ec04346b047ca6af181e1ea4858086c80bdf455f61994c4aa6fc3f866c", size = 206907, upload-time = "2026-02-17T16:12:16.513Z" }, - { url = "https://files.pythonhosted.org/packages/d1/96/ef04902aad1424fd7299b62d1890e803e6ab4018c3044dca5922319c4b97/librt-0.8.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b1977c4ea97ce5eb7755a78fae68d87e4102e4aaf54985e8b56806849cc06a3", size = 221217, upload-time = "2026-02-17T16:12:17.906Z" }, - { url = "https://files.pythonhosted.org/packages/6d/ff/7e01f2dda84a8f5d280637a2e5827210a8acca9a567a54507ef1c75b342d/librt-0.8.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:10c42e1f6fd06733ef65ae7bebce2872bcafd8d6e6b0a08fe0a05a23b044fb14", size = 214622, upload-time = "2026-02-17T16:12:19.108Z" }, - { url = "https://files.pythonhosted.org/packages/1e/8c/5b093d08a13946034fed57619742f790faf77058558b14ca36a6e331161e/librt-0.8.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4c8dfa264b9193c4ee19113c985c95f876fae5e51f731494fc4e0cf594990ba7", size = 221987, upload-time = "2026-02-17T16:12:20.331Z" }, - { url = "https://files.pythonhosted.org/packages/d3/cc/86b0b3b151d40920ad45a94ce0171dec1aebba8a9d72bb3fa00c73ab25dd/librt-0.8.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:01170b6729a438f0dedc4a26ed342e3dc4f02d1000b4b19f980e1877f0c297e6", size = 215132, upload-time = "2026-02-17T16:12:21.54Z" }, - { url = "https://files.pythonhosted.org/packages/fc/be/8588164a46edf1e69858d952654e216a9a91174688eeefb9efbb38a9c799/librt-0.8.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:7b02679a0d783bdae30d443025b94465d8c3dc512f32f5b5031f93f57ac32071", size = 215195, upload-time = "2026-02-17T16:12:23.073Z" }, - { url = "https://files.pythonhosted.org/packages/f5/f2/0b9279bea735c734d69344ecfe056c1ba211694a72df10f568745c899c76/librt-0.8.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:190b109bb69592a3401fe1ffdea41a2e73370ace2ffdc4a0e8e2b39cdea81b78", size = 237946, upload-time = "2026-02-17T16:12:24.275Z" }, - { url = "https://files.pythonhosted.org/packages/e9/cc/5f2a34fbc8aeb35314a3641f9956fa9051a947424652fad9882be7a97949/librt-0.8.1-cp314-cp314-win32.whl", hash = "sha256:e70a57ecf89a0f64c24e37f38d3fe217a58169d2fe6ed6d70554964042474023", size = 50689, upload-time = "2026-02-17T16:12:25.766Z" }, - { url = "https://files.pythonhosted.org/packages/a0/76/cd4d010ab2147339ca2b93e959c3686e964edc6de66ddacc935c325883d7/librt-0.8.1-cp314-cp314-win_amd64.whl", hash = "sha256:7e2f3edca35664499fbb36e4770650c4bd4a08abc1f4458eab9df4ec56389730", size = 57875, upload-time = "2026-02-17T16:12:27.465Z" }, - { url = "https://files.pythonhosted.org/packages/84/0f/2143cb3c3ca48bd3379dcd11817163ca50781927c4537345d608b5045998/librt-0.8.1-cp314-cp314-win_arm64.whl", hash = "sha256:0d2f82168e55ddefd27c01c654ce52379c0750ddc31ee86b4b266bcf4d65f2a3", size = 48058, upload-time = "2026-02-17T16:12:28.556Z" }, - { url = "https://files.pythonhosted.org/packages/d2/0e/9b23a87e37baf00311c3efe6b48d6b6c168c29902dfc3f04c338372fd7db/librt-0.8.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2c74a2da57a094bd48d03fa5d196da83d2815678385d2978657499063709abe1", size = 68313, upload-time = "2026-02-17T16:12:29.659Z" }, - { url = "https://files.pythonhosted.org/packages/db/9a/859c41e5a4f1c84200a7d2b92f586aa27133c8243b6cac9926f6e54d01b9/librt-0.8.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a355d99c4c0d8e5b770313b8b247411ed40949ca44e33e46a4789b9293a907ee", size = 70994, upload-time = "2026-02-17T16:12:31.516Z" }, - { url = "https://files.pythonhosted.org/packages/4c/28/10605366ee599ed34223ac2bf66404c6fb59399f47108215d16d5ad751a8/librt-0.8.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2eb345e8b33fb748227409c9f1233d4df354d6e54091f0e8fc53acdb2ffedeb7", size = 220770, upload-time = "2026-02-17T16:12:33.294Z" }, - { url = "https://files.pythonhosted.org/packages/af/8d/16ed8fd452dafae9c48d17a6bc1ee3e818fd40ef718d149a8eff2c9f4ea2/librt-0.8.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9be2f15e53ce4e83cc08adc29b26fb5978db62ef2a366fbdf716c8a6c8901040", size = 235409, upload-time = "2026-02-17T16:12:35.443Z" }, - { url = "https://files.pythonhosted.org/packages/89/1b/7bdf3e49349c134b25db816e4a3db6b94a47ac69d7d46b1e682c2c4949be/librt-0.8.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:785ae29c1f5c6e7c2cde2c7c0e148147f4503da3abc5d44d482068da5322fd9e", size = 246473, upload-time = "2026-02-17T16:12:36.656Z" }, - { url = "https://files.pythonhosted.org/packages/4e/8a/91fab8e4fd2a24930a17188c7af5380eb27b203d72101c9cc000dbdfd95a/librt-0.8.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1d3a7da44baf692f0c6aeb5b2a09c5e6fc7a703bca9ffa337ddd2e2da53f7732", size = 238866, upload-time = "2026-02-17T16:12:37.849Z" }, - { url = "https://files.pythonhosted.org/packages/b9/e0/c45a098843fc7c07e18a7f8a24ca8496aecbf7bdcd54980c6ca1aaa79a8e/librt-0.8.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5fc48998000cbc39ec0d5311312dda93ecf92b39aaf184c5e817d5d440b29624", size = 250248, upload-time = "2026-02-17T16:12:39.445Z" }, - { url = "https://files.pythonhosted.org/packages/82/30/07627de23036640c952cce0c1fe78972e77d7d2f8fd54fa5ef4554ff4a56/librt-0.8.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e96baa6820280077a78244b2e06e416480ed859bbd8e5d641cf5742919d8beb4", size = 240629, upload-time = "2026-02-17T16:12:40.889Z" }, - { url = "https://files.pythonhosted.org/packages/fb/c1/55bfe1ee3542eba055616f9098eaf6eddb966efb0ca0f44eaa4aba327307/librt-0.8.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:31362dbfe297b23590530007062c32c6f6176f6099646bb2c95ab1b00a57c382", size = 239615, upload-time = "2026-02-17T16:12:42.446Z" }, - { url = "https://files.pythonhosted.org/packages/2b/39/191d3d28abc26c9099b19852e6c99f7f6d400b82fa5a4e80291bd3803e19/librt-0.8.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cc3656283d11540ab0ea01978378e73e10002145117055e03722417aeab30994", size = 263001, upload-time = "2026-02-17T16:12:43.627Z" }, - { url = "https://files.pythonhosted.org/packages/b9/eb/7697f60fbe7042ab4e88f4ee6af496b7f222fffb0a4e3593ef1f29f81652/librt-0.8.1-cp314-cp314t-win32.whl", hash = "sha256:738f08021b3142c2918c03692608baed43bc51144c29e35807682f8070ee2a3a", size = 51328, upload-time = "2026-02-17T16:12:45.148Z" }, - { url = "https://files.pythonhosted.org/packages/7c/72/34bf2eb7a15414a23e5e70ecb9440c1d3179f393d9349338a91e2781c0fb/librt-0.8.1-cp314-cp314t-win_amd64.whl", hash = "sha256:89815a22daf9c51884fb5dbe4f1ef65ee6a146e0b6a8df05f753e2e4a9359bf4", size = 58722, upload-time = "2026-02-17T16:12:46.85Z" }, - { url = "https://files.pythonhosted.org/packages/b2/c8/d148e041732d631fc76036f8b30fae4e77b027a1e95b7a84bb522481a940/librt-0.8.1-cp314-cp314t-win_arm64.whl", hash = "sha256:bf512a71a23504ed08103a13c941f763db13fb11177beb3d9244c98c29fb4a61", size = 48755, upload-time = "2026-02-17T16:12:47.943Z" }, +version = "0.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/40/08/9e7f6b5d2b5bed6ad055cdd5925f192bb403a51280f86b56554d9d0699a2/librt-0.11.0.tar.gz", hash = "sha256:075dc3ef4458a278e0195cbf6ac9d38808d9b906c5a6c7f7f79c3888276a3fb1", size = 200139, upload-time = "2026-05-10T18:17:25.138Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/87/2bf31fe17587b29e3f93ec31421e2b1e1c3e349b8bf6c7c313dbad1d5340/librt-0.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:93d95bd45b7d58343d8b90d904450a545144eec19a002511163426f8ab1fae29", size = 141092, upload-time = "2026-05-10T18:15:34.795Z" }, + { url = "https://files.pythonhosted.org/packages/cf/08/5c5bf772920b7ebac6e32bc91a643e0ab3870199c0b542356d3baa83970a/librt-0.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ee278c769a713638cdacd4c0436d72156e75df3ebc0166ab2b9dc43acc386c9", size = 142035, upload-time = "2026-05-10T18:15:36.242Z" }, + { url = "https://files.pythonhosted.org/packages/06/20/662a03d254e5b000d838e8b345d83303ddb768c080fd488e40634c0fa66b/librt-0.11.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f230cb1cbc9faaa616f9a678f530ebcf186e414b6bcbd88b960e4ba1b92428d5", size = 475022, upload-time = "2026-05-10T18:15:37.56Z" }, + { url = "https://files.pythonhosted.org/packages/de/f3/aa81523e45184c6ec23dc7f63263362ec55f80a09d424c012359ecbe7e35/librt-0.11.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:5d63c855d86938d9de93e265c9bd8c705b51ec494de5738340ee93767a686e4b", size = 467273, upload-time = "2026-05-10T18:15:39.182Z" }, + { url = "https://files.pythonhosted.org/packages/6b/6f/59c74b560ca8853834d5501d589c8a2519f4184f273a085ffd0f37a1cc47/librt-0.11.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:993f028be9e96a08d31df3479ac80d99be374d17f3b78e4796b3fd3c913d4e89", size = 497083, upload-time = "2026-05-10T18:15:40.634Z" }, + { url = "https://files.pythonhosted.org/packages/fe/7b/5aa4d2c9600a719401160bf7055417df0b2a47439b9d88286ce45e56b65f/librt-0.11.0-cp311-cp311-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:258d73a0aa66a055e65b2e4d1b8cdb23b9d132c5bb915d9547d804fcaed116cc", size = 489139, upload-time = "2026-05-10T18:15:41.934Z" }, + { url = "https://files.pythonhosted.org/packages/d6/31/9143803d7da6856a69153785768c4936864430eec0fd9461c3ea527d9922/librt-0.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0827efe7854718f04aaddf6496e96960a956e676fe1d0f04eb41511fd8ad06d5", size = 508442, upload-time = "2026-05-10T18:15:43.206Z" }, + { url = "https://files.pythonhosted.org/packages/2f/5a/bce08184488426bda4ccc2c4964ac048c8f68ae89bd7120082eef4233cfd/librt-0.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7753e57d6e12d019c0d8786f1c09c709f4c3fcc57c3887b24e36e6c06ec938b7", size = 514230, upload-time = "2026-05-10T18:15:44.761Z" }, + { url = "https://files.pythonhosted.org/packages/89/8c/bb5e213d254b7505a0e658da199d8ab719086632ce09eef311ab27976523/librt-0.11.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:11bd19822431cc21af9f27374e7ae2e58103c7d98bda823536a6c47f6bb2bb3d", size = 494231, upload-time = "2026-05-10T18:15:46.308Z" }, + { url = "https://files.pythonhosted.org/packages/9d/fb/541cdad5b1ab1300398c74c4c9a497b88e5074c21b1244c8f49731d3a284/librt-0.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:22bdf239b219d3993761a148ffa134b19e52e9989c84f845d5d7b71d70a17412", size = 537585, upload-time = "2026-05-10T18:15:47.629Z" }, + { url = "https://files.pythonhosted.org/packages/8f/f2/464bb69295c320cb06bddb4f14a4ec67934ee14b2bffb12b19fb7ab287ba/librt-0.11.0-cp311-cp311-win32.whl", hash = "sha256:46c60b61e308eb535fbd6fa622b1ee1bb2815691c1ad9c98bf7b84952ec3bc8d", size = 100509, upload-time = "2026-05-10T18:15:49.157Z" }, + { url = "https://files.pythonhosted.org/packages/6d/e7/a17ee1788f9e4fbf548c19f4afa07c92089b9e24fef6cb2410863781ef4c/librt-0.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:902e546ff044f579ff1c953ff5fce97b636fe9e3943996b2177710c6ef076f73", size = 118628, upload-time = "2026-05-10T18:15:50.345Z" }, + { url = "https://files.pythonhosted.org/packages/cc/c7/6c766214f9f9903bcfcfbef97d807af8d8f5aa3502d247858ab17582d212/librt-0.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:65ac3bc20f78aa0ee5ae84baa68917f89fef4af63e941084dd019a0d0e749f0c", size = 103122, upload-time = "2026-05-10T18:15:52.068Z" }, + { url = "https://files.pythonhosted.org/packages/8b/d0/07c77e067f0838949b43bd89232c29d72efebb9d2801a9750184eb706b71/librt-0.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b87504f1690a23b9a2cca841191a04f83895d4fc2dd04df91d82b1a04ca2ad46", size = 144147, upload-time = "2026-05-10T18:15:53.227Z" }, + { url = "https://files.pythonhosted.org/packages/7a/24/8493538fa4f62f982686398a5b8f68008138a75086abdea19ade64bf4255/librt-0.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40071fc5fe0ce8daa6de616702314a01e1250711682b0523d6ab8d4525910cb3", size = 143614, upload-time = "2026-05-10T18:15:54.657Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1e/f8bad050810d9171f34a1648ed910e56814c2ba61639f2bd53c6377ae24b/librt-0.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:137e79445c896a0ea7b265f52d23954e05b64222ee1af69e2cb34219067cbb67", size = 485538, upload-time = "2026-05-10T18:15:56.117Z" }, + { url = "https://files.pythonhosted.org/packages/c0/fe/3594ebfbaf03084ba4b120c9ba5c3183fd938a48725e9bbe6ff0a5159ad8/librt-0.11.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:cca6644054e78746d8d4ef238681f9c34ff8b584fe6b988ecebb8db3b15e622a", size = 479623, upload-time = "2026-05-10T18:15:57.544Z" }, + { url = "https://files.pythonhosted.org/packages/b0/da/5d1876984b3746c85dbd219dbfcb73c85f54ee263fd32e5b2a632ec14571/librt-0.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5b0eea49f5562861ee8d757a32ef7d559c1d35be2aaaa1ec28941d74c9ffc8a", size = 513082, upload-time = "2026-05-10T18:15:58.805Z" }, + { url = "https://files.pythonhosted.org/packages/19/6e/55bdf5d5ca00c3e18430690bf2c953d8d3ffd3c337418173d33dec985dc9/librt-0.11.0-cp312-cp312-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0d1029d7e1ae1a7e647ed6fb5df8c4ce2dffefb7a9f5fd1376a4554d96dac09f", size = 508105, upload-time = "2026-05-10T18:16:00.2Z" }, + { url = "https://files.pythonhosted.org/packages/07/10/f1f23a7c595ee90ece4d35c851e5d104b1311a887ed1b4ac4c35bbd13da8/librt-0.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bc3ce6b33c5828d9e80592011a5c584cb2ce86edbc4088405f70da47dc1d1b3b", size = 522268, upload-time = "2026-05-10T18:16:01.708Z" }, + { url = "https://files.pythonhosted.org/packages/b6/02/5720f5697a7f54b78b3aefbe20df3a48cedcff1276618c4aa481177942ed/librt-0.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:936c5995f3514a42111f20099397d8177c79b4d7e70961e396c6f5a0a3566766", size = 527348, upload-time = "2026-05-10T18:16:03.496Z" }, + { url = "https://files.pythonhosted.org/packages/50/db/b4a47c6f91db4ff76348a0b3dd0cc65e090a078b765a810a62ff9434c3d3/librt-0.11.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9bc0ca6ad9381cbe8e4aa6e5726e4c80c78115a6e9723c599ed1d73e092bc49d", size = 516294, upload-time = "2026-05-10T18:16:05.173Z" }, + { url = "https://files.pythonhosted.org/packages/9e/58/9384b2f4eb1ed1d273d40948a7c5c4b2360213b402ef3be4641c06299f9c/librt-0.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:070aa8c26c0a74774317a72df8851facc7f0f012a5b406557ac56992d92e1ec8", size = 553608, upload-time = "2026-05-10T18:16:06.839Z" }, + { url = "https://files.pythonhosted.org/packages/21/7b/5aa8848a7c6a9278c79375146da1812e695754ceec5f005e6043461a7315/librt-0.11.0-cp312-cp312-win32.whl", hash = "sha256:6bf14feb84b05ae945277395451998c89c54d0def4070eb5c08de544930b245a", size = 101879, upload-time = "2026-05-10T18:16:08.103Z" }, + { url = "https://files.pythonhosted.org/packages/37/33/8a745436944947575b584231750a41417de1a38cf6a2e9251d1065651c09/librt-0.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:75672f0bc524ede266287d532d7923dbce94c7514ad07627bac3d0c6d92cc4d9", size = 119831, upload-time = "2026-05-10T18:16:09.174Z" }, + { url = "https://files.pythonhosted.org/packages/59/67/a6739ac96e28b7855808bdb0370e250606104a859750d209e5a0716fe7ab/librt-0.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:2f10cf143e4a9bb0f4f5af568a00df94a2d69ef41c2579584454bb0fe5cc642c", size = 103470, upload-time = "2026-05-10T18:16:10.369Z" }, + { url = "https://files.pythonhosted.org/packages/82/61/e59168d4d0bf2bf90f4f0caf7a001bfc60254c3af4586013b04dc3ef517b/librt-0.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:78dc31f7fdfe9c9d0eb0e8f42d139db230e826415bbcabd9f0e9faaaee909894", size = 144119, upload-time = "2026-05-10T18:16:11.771Z" }, + { url = "https://files.pythonhosted.org/packages/61/fd/caa1d60b12f7dd79ccea23054e06eeaebe266a5f52c40a6b651069200ce5/librt-0.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fa475675db22290c3158e1d42326d0f5a65f04f44a0e68c3630a25b53560fb9c", size = 143565, upload-time = "2026-05-10T18:16:13.334Z" }, + { url = "https://files.pythonhosted.org/packages/b8/a9/dc744f5c2b4978d48db970be29f22716d3413d28b14ad99740817315cf2c/librt-0.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:621db29691044bdeda22e789e482e1b0f3a985d90e3426c9c6d17606416205ea", size = 485395, upload-time = "2026-05-10T18:16:14.729Z" }, + { url = "https://files.pythonhosted.org/packages/8f/21/7f8e97a1e4dae952a5a95948f6f8507a173bc1e669f54340bba6ca1ca31b/librt-0.11.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:a9010e2ed5b3a9e158c5fd966b3ab7e834bb3d3aacc8f66c91dd4b57a3799230", size = 479383, upload-time = "2026-05-10T18:16:16.321Z" }, + { url = "https://files.pythonhosted.org/packages/a6/6d/d8ee9c114bebf2c50e29ec2aa940826fccb62a645c3e4c18760987d0e16d/librt-0.11.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c39513d8b7477a2e1ed8c43fc21c524e8d5a0f8d4e8b7b074dbdbe7820a08e2", size = 513010, upload-time = "2026-05-10T18:16:17.647Z" }, + { url = "https://files.pythonhosted.org/packages/f0/43/0b5708af2bd30a46400e72ba6bdaa8f066f15fb9a688527e34220e8d6c06/librt-0.11.0-cp313-cp313-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7aef3cf1d5af86e770ab04bfd993dfc4ae8b8c17f66fb77dd4a7d50de7bbb1a3", size = 508433, upload-time = "2026-05-10T18:16:19.309Z" }, + { url = "https://files.pythonhosted.org/packages/4a/50/356187247d09013490481033183b3532b58acf8028bcb34b2b56a375c9b2/librt-0.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:557183ddc36babe46b27dd60facbd5adb4492181a5be887587d57cda6e092f21", size = 522595, upload-time = "2026-05-10T18:16:20.642Z" }, + { url = "https://files.pythonhosted.org/packages/40/e7/c6ac4240899c7f3248079d5a9900debe0dadb3fdeaf856684c987105ba47/librt-0.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:83d3e1f72bd42f6c5c0b7daec530c3f829bd02db42c70b8ddf0c2d90a2459930", size = 527255, upload-time = "2026-05-10T18:16:22.352Z" }, + { url = "https://files.pythonhosted.org/packages/eb/b5/a81322dbeedeeaf9c1ee6f001734d28a09d8383ac9e6779bc24bbd0743c6/librt-0.11.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:4ce1f21fbe589bc1afd7872dece84fb0e1144f794a288e58a10d2c54a55c43be", size = 516847, upload-time = "2026-05-10T18:16:23.627Z" }, + { url = "https://files.pythonhosted.org/packages/ae/66/6e6323787d592b55204a42595ff1102da5115601b53a7e9ddebc889a6da5/librt-0.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b09f7044ea2b64c9da42fd3d335666518cfd1c6e8a182c95da73d0214b41e", size = 553920, upload-time = "2026-05-10T18:16:25.025Z" }, + { url = "https://files.pythonhosted.org/packages/9c/21/623f8ca230857102066d9ca8c6c1734995908c4d0d1bee7bb2ef0021cb33/librt-0.11.0-cp313-cp313-win32.whl", hash = "sha256:78fddc31cd4d3caa897ad5d31f856b1faadc9474021ad6cb182b9018793e254e", size = 101898, upload-time = "2026-05-10T18:16:26.649Z" }, + { url = "https://files.pythonhosted.org/packages/b3/1d/b4ebd44dd723f768469007515cb92251e0ae286c94c140f374801140fa74/librt-0.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:8ca8aa88751a775870b764e93bad5135385f563cb8dcee399abf034ea4d3cb47", size = 119812, upload-time = "2026-05-10T18:16:27.859Z" }, + { url = "https://files.pythonhosted.org/packages/3b/e4/b2f4ca7965ca373b491cdb4bc25cdb30c1649ca81a8782056a83850292a9/librt-0.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:96f044bb325fd9cf1a723015638c219e9143f0dfbc0ca54c565df2b7fc748b44", size = 103448, upload-time = "2026-05-10T18:16:29.066Z" }, + { url = "https://files.pythonhosted.org/packages/29/eb/dbce197da4e227779e56b5735f2decc3eb36e55a1cdbf1bd65d6639d76c1/librt-0.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4a017a95e5837dc15a8c5661d60e05daa96b90908b1aa6b7acdf443cd25c8ebd", size = 143345, upload-time = "2026-05-10T18:16:30.674Z" }, + { url = "https://files.pythonhosted.org/packages/76/a3/254bebd0c11c8ba684018efb8006ff22e466abce445215cca6c778e7d9de/librt-0.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b1ecbd9819deccc39b7542bf4d2a740d8a620694d39989e58661d3763458f8d4", size = 143131, upload-time = "2026-05-10T18:16:32.037Z" }, + { url = "https://files.pythonhosted.org/packages/f1/3f/f77d6122d21ac7bf6ae8a7dfced1bd2a7ac545d3273ebdcaf8042f6d619f/librt-0.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7da327dacd7be8f8ec36547373550744a3cc0e536d54665cd83f8bcd961200e8", size = 477024, upload-time = "2026-05-10T18:16:33.493Z" }, + { url = "https://files.pythonhosted.org/packages/ac/0a/2c996dadebaa7d9bbbd43ef2d4f3e66b6da545f838a41694ef6172cebec8/librt-0.11.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:0dc56b1f8d06e60db362cc3fdae206681817f86ce4725d34511473487f12a34b", size = 474221, upload-time = "2026-05-10T18:16:34.864Z" }, + { url = "https://files.pythonhosted.org/packages/0a/7e/f5d92af8486b8272c23b3e686b46ff72d89c8169585eb61eef01a2ac7147/librt-0.11.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05fb8fb2ab90e21c8d12ea240d744ad514da9baf381ebfa70d91d20d21713175", size = 505174, upload-time = "2026-05-10T18:16:36.705Z" }, + { url = "https://files.pythonhosted.org/packages/af/1a/cb0734fe86398eb33193ab753b7326255c74cac5eb09e76b9b16536e7adb/librt-0.11.0-cp314-cp314-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cae74872be221df4374d10fec61f93ed1513b9546ea84f2c0bf73ab3e9bd0b03", size = 497216, upload-time = "2026-05-10T18:16:38.418Z" }, + { url = "https://files.pythonhosted.org/packages/18/06/094820f91558b66e29943c0ec41c9914f460f48dd51fc503c3101e10842d/librt-0.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:32bcc918c0148eb7e3d57385125bac7e5f9e4359d05f07448b09f6f778c2f31c", size = 513921, upload-time = "2026-05-10T18:16:39.848Z" }, + { url = "https://files.pythonhosted.org/packages/0b/c2/00de9018871a282f530cacb457d5ec0428f6ac7e6fedde9aff7468d9fb04/librt-0.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:f9743fc99135d5f78d2454435615f6dec0473ca507c26ce9d92b10b562a280d3", size = 520850, upload-time = "2026-05-10T18:16:41.471Z" }, + { url = "https://files.pythonhosted.org/packages/51/9d/64631832348fd1834fb3a61b996434edddaaf25a31d03b0a76273159d2cf/librt-0.11.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5ba067f4aadae8fda802d91d2124c90c42195ff32d9161d3549e6d05cfe26f96", size = 504237, upload-time = "2026-05-10T18:16:43.15Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ec/ae5525eb16edc827a044e7bb8777a455ff95d4bca9379e7e6bddd7383647/librt-0.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:de3bf945454d032f9e390b85c4072e0a0570bf825421c8be0e71209fa65e1abe", size = 546261, upload-time = "2026-05-10T18:16:44.408Z" }, + { url = "https://files.pythonhosted.org/packages/5a/09/adce371f27ca039411da9659f7430fcc2ba6cd0c7b3e4467a0f091be7fa9/librt-0.11.0-cp314-cp314-win32.whl", hash = "sha256:d2277a05f6dcb9fd13db9566aac4fabd68c3ea1ea46ee5567d4eef8efa495a2f", size = 96965, upload-time = "2026-05-10T18:16:46.039Z" }, + { url = "https://files.pythonhosted.org/packages/d6/ee/8ac720d98548f173c7ce2e632a7ca94673f74cacd5c8162a84af5b35958a/librt-0.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:ab73e8db5e3f564d812c1f5c3a175930a5f9bc96ccb5e3b22a34d7858b401cf7", size = 115151, upload-time = "2026-05-10T18:16:47.133Z" }, + { url = "https://files.pythonhosted.org/packages/94/20/c900cf14efeb09b6bef2b2dff20779f73464b97fd58d1c6bccc379588ae3/librt-0.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:aea3caa317752e3a466fa8af45d91ee0ea8c7fdd96e42b0a8dd9b76a7931eba1", size = 98850, upload-time = "2026-05-10T18:16:48.597Z" }, + { url = "https://files.pythonhosted.org/packages/0c/71/944bfe4b64e12abffcd3c15e1cce07f72f3d55655083786285f4dedeb532/librt-0.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d1b36540d7aaf9b9101b3a6f376c8d8e9f7a9aec93ed05918f2c69d493ffef72", size = 151138, upload-time = "2026-05-10T18:16:49.839Z" }, + { url = "https://files.pythonhosted.org/packages/b6/10/99e64a5c86989357fda078c8143c533389585f6473b7439172dd8f3b3b2d/librt-0.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:efbb343ab2ce3540f4ecbe6315d677ed70f37cd9a72b1e58066c918ca83acbaa", size = 151976, upload-time = "2026-05-10T18:16:51.062Z" }, + { url = "https://files.pythonhosted.org/packages/21/31/5072ad880946d83e5ea4147d6d018c78eefce85b77819b19bdd0ee229435/librt-0.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa0dd688aab3f7914d3e6e5e3554978e0383312fb8e771d84be008a35b9ee548", size = 557927, upload-time = "2026-05-10T18:16:52.632Z" }, + { url = "https://files.pythonhosted.org/packages/5e/8d/70b5fb7cfbab60edbe7381614ab985da58e144fbf465c86d44c95f43cdca/librt-0.11.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:f5fb36b8c6c63fdcbb1d526d94c0d1331610d43f4118cc1beb4efef4f3faacb2", size = 539698, upload-time = "2026-05-10T18:16:53.934Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a3/ba3495a0b3edbd24a4cae0d1d3c64f39a9fc45d06e812101289b50c1a619/librt-0.11.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4a9a237d13addb93715b6fee74023d5ee3469b53fce527626c0e088aa585805f", size = 577162, upload-time = "2026-05-10T18:16:55.589Z" }, + { url = "https://files.pythonhosted.org/packages/f7/db/36e25fb81f99937ff1b96612a1dc9fd66f039cb9cc3aee12c01fac31aab9/librt-0.11.0-cp314-cp314t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5ddd17bd87b2c56ddd60e546a7984a2e64c4e8eab92fb4cf3830a48ad5469d51", size = 566494, upload-time = "2026-05-10T18:16:56.975Z" }, + { url = "https://files.pythonhosted.org/packages/33/0d/3f622b47f0b013eeb9cf4cc07ae9bfe378d832a4eec998b2b209fe84244d/librt-0.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bd43992b4473d42f12ff9e68326079f0696d9d4e6000e8f39a0238d482ba6ee2", size = 596858, upload-time = "2026-05-10T18:16:58.374Z" }, + { url = "https://files.pythonhosted.org/packages/a9/02/71b90bc93039c46a2000651f6ad60122b114c8f54c4ad306e0e96f5b75ad/librt-0.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:f8e3e8056dd674e279741485e2e512d6e9a751c7455809d0114e6ebf8d781085", size = 590318, upload-time = "2026-05-10T18:16:59.676Z" }, + { url = "https://files.pythonhosted.org/packages/04/04/418cb3f75621e2b761fb1ab0f017f4d70a1a72a6e7c74ee4f7e8d198c2f3/librt-0.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:c1f708d8ae9c56cf38a903c44297243d2ec83fd82b396b977e0144a3e76217e3", size = 575115, upload-time = "2026-05-10T18:17:01.007Z" }, + { url = "https://files.pythonhosted.org/packages/cc/2c/5a2183ac58dd911f26b5d7e7d7d8f1d87fcecdddd99d6c12169a258ff62c/librt-0.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0add982e0e7b9fc14cf4b33789d5f13f66581889b88c2f58099f6ce8f92617bd", size = 617918, upload-time = "2026-05-10T18:17:02.682Z" }, + { url = "https://files.pythonhosted.org/packages/15/1f/dc6771a52592a4451be6effa200cbfc9cec61e4393d3033d81a9d307961d/librt-0.11.0-cp314-cp314t-win32.whl", hash = "sha256:2b481d846ac894c4e8403c5fd0e87c5d11d6499e404b474602508a224ff531c8", size = 103562, upload-time = "2026-05-10T18:17:03.99Z" }, + { url = "https://files.pythonhosted.org/packages/62/4a/7d1415567027286a75ba1093ec4aca11f073e0f559c530cf3e0a757ad55c/librt-0.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:28edb433edde181112a908c78907af28f964eabc15f4dd16c9d66c834302677c", size = 124327, upload-time = "2026-05-10T18:17:05.465Z" }, + { url = "https://files.pythonhosted.org/packages/ce/62/b40b382fa0c66fee1478073eb8db352a4a6beda4a1adccf1df911d8c289c/librt-0.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dee008f20b542e3cd162ba338a7f9ec0f6d23d395f66fe8aeeec3c9d067ea253", size = 102572, upload-time = "2026-05-10T18:17:06.809Z" }, ] [[package]] @@ -1974,52 +2015,53 @@ wheels = [ [[package]] name = "mypy" -version = "1.20.0" +version = "2.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "ast-serialize" }, { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, { name = "mypy-extensions" }, { name = "pathspec" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f8/5c/b0089fe7fef0a994ae5ee07029ced0526082c6cfaaa4c10d40a10e33b097/mypy-1.20.0.tar.gz", hash = "sha256:eb96c84efcc33f0b5e0e04beacf00129dd963b67226b01c00b9dfc8affb464c3", size = 3815028, upload-time = "2026-03-31T16:55:14.959Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6e/1c/74cb1d9993236910286865679d1c616b136b2eae468493aa939431eda410/mypy-1.20.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4525e7010b1b38334516181c5b81e16180b8e149e6684cee5a727c78186b4e3b", size = 14343972, upload-time = "2026-03-31T16:49:04.887Z" }, - { url = "https://files.pythonhosted.org/packages/d5/0d/01399515eca280386e308cf57901e68d3a52af18691941b773b3380c1df8/mypy-1.20.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a17c5d0bdcca61ce24a35beb828a2d0d323d3fcf387d7512206888c900193367", size = 13225007, upload-time = "2026-03-31T16:50:08.151Z" }, - { url = "https://files.pythonhosted.org/packages/56/ac/b4ba5094fb2d7fe9d2037cd8d18bbe02bcf68fd22ab9ff013f55e57ba095/mypy-1.20.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f75ff57defcd0f1d6e006d721ccdec6c88d4f6a7816eb92f1c4890d979d9ee62", size = 13663752, upload-time = "2026-03-31T16:49:26.064Z" }, - { url = "https://files.pythonhosted.org/packages/db/a7/460678d3cf7da252d2288dad0c602294b6ec22a91932ec368cc11e44bb6e/mypy-1.20.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b503ab55a836136b619b5fc21c8803d810c5b87551af8600b72eecafb0059cb0", size = 14532265, upload-time = "2026-03-31T16:53:55.077Z" }, - { url = "https://files.pythonhosted.org/packages/a3/3e/051cca8166cf0438ae3ea80e0e7c030d7a8ab98dffc93f80a1aa3f23c1a2/mypy-1.20.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1973868d2adbb4584a3835780b27436f06d1dc606af5be09f187aaa25be1070f", size = 14768476, upload-time = "2026-03-31T16:50:34.587Z" }, - { url = "https://files.pythonhosted.org/packages/be/66/8e02ec184f852ed5c4abb805583305db475930854e09964b55e107cdcbc4/mypy-1.20.0-cp311-cp311-win_amd64.whl", hash = "sha256:2fcedb16d456106e545b2bfd7ef9d24e70b38ec252d2a629823a4d07ebcdb69e", size = 10818226, upload-time = "2026-03-31T16:53:15.624Z" }, - { url = "https://files.pythonhosted.org/packages/13/4b/383ad1924b28f41e4879a74151e7a5451123330d45652da359f9183bcd45/mypy-1.20.0-cp311-cp311-win_arm64.whl", hash = "sha256:379edf079ce44ac8d2805bcf9b3dd7340d4f97aad3a5e0ebabbf9d125b84b442", size = 9750091, upload-time = "2026-03-31T16:54:12.162Z" }, - { url = "https://files.pythonhosted.org/packages/be/dd/3afa29b58c2e57c79116ed55d700721c3c3b15955e2b6251dd165d377c0e/mypy-1.20.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:002b613ae19f4ac7d18b7e168ffe1cb9013b37c57f7411984abbd3b817b0a214", size = 14509525, upload-time = "2026-03-31T16:55:01.824Z" }, - { url = "https://files.pythonhosted.org/packages/54/eb/227b516ab8cad9f2a13c5e7a98d28cd6aa75e9c83e82776ae6c1c4c046c7/mypy-1.20.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9336b5e6712f4adaf5afc3203a99a40b379049104349d747eb3e5a3aa23ac2e", size = 13326469, upload-time = "2026-03-31T16:51:41.23Z" }, - { url = "https://files.pythonhosted.org/packages/57/d4/1ddb799860c1b5ac6117ec307b965f65deeb47044395ff01ab793248a591/mypy-1.20.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f13b3e41bce9d257eded794c0f12878af3129d80aacd8a3ee0dee51f3a978651", size = 13705953, upload-time = "2026-03-31T16:48:55.69Z" }, - { url = "https://files.pythonhosted.org/packages/c5/b7/54a720f565a87b893182a2a393370289ae7149e4715859e10e1c05e49154/mypy-1.20.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9804c3ad27f78e54e58b32e7cb532d128b43dbfb9f3f9f06262b821a0f6bd3f5", size = 14710363, upload-time = "2026-03-31T16:53:26.948Z" }, - { url = "https://files.pythonhosted.org/packages/b2/2a/74810274848d061f8a8ea4ac23aaad43bd3d8c1882457999c2e568341c57/mypy-1.20.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:697f102c5c1d526bdd761a69f17c6070f9892eebcb94b1a5963d679288c09e78", size = 14947005, upload-time = "2026-03-31T16:50:17.591Z" }, - { url = "https://files.pythonhosted.org/packages/77/91/21b8ba75f958bcda75690951ce6fa6b7138b03471618959529d74b8544e2/mypy-1.20.0-cp312-cp312-win_amd64.whl", hash = "sha256:0ecd63f75fdd30327e4ad8b5704bd6d91fc6c1b2e029f8ee14705e1207212489", size = 10880616, upload-time = "2026-03-31T16:52:19.986Z" }, - { url = "https://files.pythonhosted.org/packages/8a/15/3d8198ef97c1ca03aea010cce4f1d4f3bc5d9849e8c0140111ca2ead9fdd/mypy-1.20.0-cp312-cp312-win_arm64.whl", hash = "sha256:f194db59657c58593a3c47c6dfd7bad4ef4ac12dbc94d01b3a95521f78177e33", size = 9813091, upload-time = "2026-03-31T16:53:44.385Z" }, - { url = "https://files.pythonhosted.org/packages/d6/a7/f64ea7bd592fa431cb597418b6dec4a47f7d0c36325fec7ac67bc8402b94/mypy-1.20.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b20c8b0fd5877abdf402e79a3af987053de07e6fb208c18df6659f708b535134", size = 14485344, upload-time = "2026-03-31T16:49:16.78Z" }, - { url = "https://files.pythonhosted.org/packages/bb/72/8927d84cfc90c6abea6e96663576e2e417589347eb538749a464c4c218a0/mypy-1.20.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:367e5c993ba34d5054d11937d0485ad6dfc60ba760fa326c01090fc256adf15c", size = 13327400, upload-time = "2026-03-31T16:53:08.02Z" }, - { url = "https://files.pythonhosted.org/packages/ab/4a/11ab99f9afa41aa350178d24a7d2da17043228ea10f6456523f64b5a6cf6/mypy-1.20.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f799d9db89fc00446f03281f84a221e50018fc40113a3ba9864b132895619ebe", size = 13706384, upload-time = "2026-03-31T16:52:28.577Z" }, - { url = "https://files.pythonhosted.org/packages/42/79/694ca73979cfb3535ebfe78733844cd5aff2e63304f59bf90585110d975a/mypy-1.20.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:555658c611099455b2da507582ea20d2043dfdfe7f5ad0add472b1c6238b433f", size = 14700378, upload-time = "2026-03-31T16:48:45.527Z" }, - { url = "https://files.pythonhosted.org/packages/84/24/a022ccab3a46e3d2cdf2e0e260648633640eb396c7e75d5a42818a8d3971/mypy-1.20.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:efe8d70949c3023698c3fca1e94527e7e790a361ab8116f90d11221421cd8726", size = 14932170, upload-time = "2026-03-31T16:49:36.038Z" }, - { url = "https://files.pythonhosted.org/packages/d8/9b/549228d88f574d04117e736f55958bd4908f980f9f5700a07aeb85df005b/mypy-1.20.0-cp313-cp313-win_amd64.whl", hash = "sha256:f49590891d2c2f8a9de15614e32e459a794bcba84693c2394291a2038bbaaa69", size = 10888526, upload-time = "2026-03-31T16:50:59.827Z" }, - { url = "https://files.pythonhosted.org/packages/91/17/15095c0e54a8bc04d22d4ff06b2139d5f142c2e87520b4e39010c4862771/mypy-1.20.0-cp313-cp313-win_arm64.whl", hash = "sha256:76a70bf840495729be47510856b978f1b0ec7d08f257ca38c9d932720bf6b43e", size = 9816456, upload-time = "2026-03-31T16:49:59.537Z" }, - { url = "https://files.pythonhosted.org/packages/4e/0e/6ca4a84cbed9e62384bc0b2974c90395ece5ed672393e553996501625fc5/mypy-1.20.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:0f42dfaab7ec1baff3b383ad7af562ab0de573c5f6edb44b2dab016082b89948", size = 14483331, upload-time = "2026-03-31T16:52:57.999Z" }, - { url = "https://files.pythonhosted.org/packages/7d/c5/5fe9d8a729dd9605064691816243ae6c49fde0bd28f6e5e17f6a24203c43/mypy-1.20.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:31b5dbb55293c1bd27c0fc813a0d2bb5ceef9d65ac5afa2e58f829dab7921fd5", size = 13342047, upload-time = "2026-03-31T16:54:21.555Z" }, - { url = "https://files.pythonhosted.org/packages/4c/33/e18bcfa338ca4e6b2771c85d4c5203e627d0c69d9de5c1a2cf2ba13320ba/mypy-1.20.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49d11c6f573a5a08f77fad13faff2139f6d0730ebed2cfa9b3d2702671dd7188", size = 13719585, upload-time = "2026-03-31T16:51:53.89Z" }, - { url = "https://files.pythonhosted.org/packages/6b/8d/93491ff7b79419edc7eabf95cb3b3f7490e2e574b2855c7c7e7394ff933f/mypy-1.20.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d3243c406773185144527f83be0e0aefc7bf4601b0b2b956665608bf7c98a83", size = 14685075, upload-time = "2026-03-31T16:54:04.464Z" }, - { url = "https://files.pythonhosted.org/packages/b5/9d/d924b38a4923f8d164bf2b4ec98bf13beaf6e10a5348b4b137eadae40a6e/mypy-1.20.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a79c1eba7ac4209f2d850f0edd0a2f8bba88cbfdfefe6fb76a19e9d4fe5e71a2", size = 14919141, upload-time = "2026-03-31T16:54:51.785Z" }, - { url = "https://files.pythonhosted.org/packages/59/98/1da9977016678c0b99d43afe52ed00bb3c1a0c4c995d3e6acca1a6ebb9b4/mypy-1.20.0-cp314-cp314-win_amd64.whl", hash = "sha256:00e047c74d3ec6e71a2eb88e9ea551a2edb90c21f993aefa9e0d2a898e0bb732", size = 11050925, upload-time = "2026-03-31T16:51:30.758Z" }, - { url = "https://files.pythonhosted.org/packages/5e/e3/ba0b7a3143e49a9c4f5967dde6ea4bf8e0b10ecbbcca69af84027160ee89/mypy-1.20.0-cp314-cp314-win_arm64.whl", hash = "sha256:931a7630bba591593dcf6e97224a21ff80fb357e7982628d25e3c618e7f598ef", size = 10001089, upload-time = "2026-03-31T16:49:43.632Z" }, - { url = "https://files.pythonhosted.org/packages/12/28/e617e67b3be9d213cda7277913269c874eb26472489f95d09d89765ce2d8/mypy-1.20.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:26c8b52627b6552f47ff11adb4e1509605f094e29815323e487fc0053ebe93d1", size = 15534710, upload-time = "2026-03-31T16:52:12.506Z" }, - { url = "https://files.pythonhosted.org/packages/6e/0c/3b5f2d3e45dc7169b811adce8451679d9430399d03b168f9b0489f43adaa/mypy-1.20.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:39362cdb4ba5f916e7976fccecaab1ba3a83e35f60fa68b64e9a70e221bb2436", size = 14393013, upload-time = "2026-03-31T16:54:41.186Z" }, - { url = "https://files.pythonhosted.org/packages/a3/49/edc8b0aa145cc09c1c74f7ce2858eead9329931dcbbb26e2ad40906daa4e/mypy-1.20.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:34506397dbf40c15dc567635d18a21d33827e9ab29014fb83d292a8f4f8953b6", size = 15047240, upload-time = "2026-03-31T16:54:31.955Z" }, - { url = "https://files.pythonhosted.org/packages/42/37/a946bb416e37a57fa752b3100fd5ede0e28df94f92366d1716555d47c454/mypy-1.20.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:555493c44a4f5a1b58d611a43333e71a9981c6dbe26270377b6f8174126a0526", size = 15858565, upload-time = "2026-03-31T16:53:36.997Z" }, - { url = "https://files.pythonhosted.org/packages/2f/99/7690b5b5b552db1bd4ff362e4c0eb3107b98d680835e65823fbe888c8b78/mypy-1.20.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2721f0ce49cb74a38f00c50da67cb7d36317b5eda38877a49614dc018e91c787", size = 16087874, upload-time = "2026-03-31T16:52:48.313Z" }, - { url = "https://files.pythonhosted.org/packages/aa/76/53e893a498138066acd28192b77495c9357e5a58cc4be753182846b43315/mypy-1.20.0-cp314-cp314t-win_amd64.whl", hash = "sha256:47781555a7aa5fedcc2d16bcd72e0dc83eb272c10dd657f9fb3f9cc08e2e6abb", size = 12572380, upload-time = "2026-03-31T16:49:52.454Z" }, - { url = "https://files.pythonhosted.org/packages/76/9c/6dbdae21f01b7aacddc2c0bbf3c5557aa547827fdf271770fe1e521e7093/mypy-1.20.0-cp314-cp314t-win_arm64.whl", hash = "sha256:c70380fe5d64010f79fb863b9081c7004dd65225d2277333c219d93a10dad4dd", size = 10381174, upload-time = "2026-03-31T16:51:20.179Z" }, - { url = "https://files.pythonhosted.org/packages/21/66/4d734961ce167f0fd8380769b3b7c06dbdd6ff54c2190f3f2ecd22528158/mypy-1.20.0-py3-none-any.whl", hash = "sha256:a6e0641147cbfa7e4e94efdb95c2dab1aff8cfc159ded13e07f308ddccc8c48e", size = 2636365, upload-time = "2026-03-31T16:51:44.911Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/82/15/cca9d88503549ed6fedeaa1d448cdddd542ee8a490232d732e278036fbf2/mypy-2.1.0.tar.gz", hash = "sha256:81e76ad12c2d804512e9b13240d1588316531bfba07558286078bfbce9613633", size = 3898359, upload-time = "2026-05-11T18:37:36.237Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/a1/639f3024794a2a15899cb90707fe02e044c4412794c39c5769fd3df2e2ef/mypy-2.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a683016b16fe2f572dc04c72be7ee0504ac1605a265d0200f5cea695fb788f41", size = 14691685, upload-time = "2026-05-11T18:33:27.973Z" }, + { url = "https://files.pythonhosted.org/packages/3b/08/9a585dea4325f20d8b80dc78623fa50d1fd2173b710f6237afd6ba6ab39b/mypy-2.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1a293c534adb55271fef24a26da04b855540a8c13cc07bc5917b9fd2c394f2ca", size = 13555165, upload-time = "2026-05-11T18:32:16.107Z" }, + { url = "https://files.pythonhosted.org/packages/81/dc/7c42cc9c6cb01e8eb09961f1f738741d3e9c7e9d5c5b30ec69222625cd5f/mypy-2.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7406f4d048e71e576f5356d317e5b0a9e666dfd966bd99f9d14ca06e1a341538", size = 13994376, upload-time = "2026-05-11T18:32:39.256Z" }, + { url = "https://files.pythonhosted.org/packages/d4/fa/285946c33bce716e082c11dfeee9ee196eaf1f5042efb3581a31f9f205e4/mypy-2.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e0210d626fc8b31ccc90233754c7bc90e1f43205e85d96387f7db1285b55c398", size = 14864618, upload-time = "2026-05-11T18:34:49.765Z" }, + { url = "https://files.pythonhosted.org/packages/2b/83/82397f48af6c27e295d57979ded8490c9829040152cf7571b2f026aeb9a0/mypy-2.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3712c20deed54e814eaaa825603bada8ea1c390670a397c95b98405347acc563", size = 15102063, upload-time = "2026-05-11T18:34:05.855Z" }, + { url = "https://files.pythonhosted.org/packages/40/68/b02dec39057b88eb03dc0aa854732e26e8361f34f9d0e20c7614967d1eba/mypy-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fcaa0e479066e31f7cceb6a3bea39cb22b2ff51a6b2f24f193d19179ba17c389", size = 11060564, upload-time = "2026-05-11T18:35:36.494Z" }, + { url = "https://files.pythonhosted.org/packages/cf/a8/ea3dcbef31f99b634f2ee23bb0321cbc8c1b388b76a861eb849f13c347dc/mypy-2.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:0b1a5260c95aa443083f9ed3592662941951bca3d4ca224a5dc517c38b7cf666", size = 9966983, upload-time = "2026-05-11T18:37:14.139Z" }, + { url = "https://files.pythonhosted.org/packages/95/b1/55861beb5c339b44f9a2ba92df9e2cb1eeb4ae1eee674cdf7772c797778b/mypy-2.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:244358bf1c0da7722230bce60683d52e8e9fd030554926f15b747a84efb5b3af", size = 14874381, upload-time = "2026-05-11T18:37:31.784Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b3/b7f770114b7d0ac92d0f76e8d93c2780844a70488a90e91821927850da86/mypy-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4ec7c57657493c7a75534df2751c8ae2cda383c16ecc55d2106c54476b1b16f6", size = 13665501, upload-time = "2026-05-11T18:34:23.063Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f3/8ae2037967e2126689a0c11d99e2b707134a565191e92c60ca2572aec60a/mypy-2.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8161b6ff4392410023224f0969d17db93e1e154bc3e4ba62598e720723ae211", size = 14045750, upload-time = "2026-05-11T18:31:48.151Z" }, + { url = "https://files.pythonhosted.org/packages/a0/32/615eb5911859e43d054941b0d0a7d06cfa2870eba86529cf385b052b111c/mypy-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf03e12003084a67395184d3eb8cbd6a489dc3655b5664b28c210a9e2403ab0b", size = 15061630, upload-time = "2026-05-11T18:37:06.898Z" }, + { url = "https://files.pythonhosted.org/packages/d4/03/4eafbfff8bfab1b87082741eae6e6a624028c984e6708b73bce2a8570c9d/mypy-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:20509760fd791c51579d573153407d226385ec1f8bcce55d730b354f3336bc22", size = 15288831, upload-time = "2026-05-11T18:31:18.07Z" }, + { url = "https://files.pythonhosted.org/packages/99/ee/919661478e5891a3c96e549c036e467e64563ab85995b10c53c8358e16a3/mypy-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:6753d0c1fdd6b1a23b9e4f283ce80b2153b724adcb2653b20b85a8a28ac6436b", size = 11135228, upload-time = "2026-05-11T18:34:31.23Z" }, + { url = "https://files.pythonhosted.org/packages/24/0a/6a12b9782ca0831a553192f351679f4548abc9d19a7cc93bb7feb02084c7/mypy-2.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:98ebb6589bb3b6d0c6f0c459d53ca55b8091fbc13d277c4041c885392e8195e8", size = 10040684, upload-time = "2026-05-11T18:36:48.199Z" }, + { url = "https://files.pythonhosted.org/packages/6e/dd/c7191469c777f07689c032a8f7326e393ea34c92d6d76eb7ce5ba57ea66d/mypy-2.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35aac3bb114e03888f535d5eb51b8bafbb3266586b599da1940f9b1be3ec5bd5", size = 14852174, upload-time = "2026-05-11T18:31:38.929Z" }, + { url = "https://files.pythonhosted.org/packages/55/8c/aed55408879043d72bb9135f4d0d19a02b886dd569631e113e3d2706cb8d/mypy-2.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8de55a8c861f2a49331f807be98d90caeceeef520bde13d43a160207f8af613e", size = 13651542, upload-time = "2026-05-11T18:36:04.636Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8e/f371a824b1f1fa8ea6e3dbb8703d232977d572be2329554a3bc4d960302f/mypy-2.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5fdf2941a07434af755837d9880f7d7d25f1dacb1af9dcd4b9b66f2220a3024e", size = 14033929, upload-time = "2026-05-11T18:35:55.742Z" }, + { url = "https://files.pythonhosted.org/packages/94/21/f54be870d6dd53a82c674407e0f8eed7174b05ec78d42e5abd7b42e84fd5/mypy-2.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e195b817c13f02352a9c124301f9f30f078405444679b6753c1b96b6eed37285", size = 15039200, upload-time = "2026-05-11T18:33:10.281Z" }, + { url = "https://files.pythonhosted.org/packages/17/99/bf21748626a40ce59fd29a39386ab46afec88b7bd2f0fa6c3a97c995523f/mypy-2.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5431d42af987ebd92ba2f71d45c85ed41d8e6ca9f5fd209a69f68f707d2469e5", size = 15272690, upload-time = "2026-05-11T18:32:07.205Z" }, + { url = "https://files.pythonhosted.org/packages/d6/d7/9e90d2cf47100bea550ed2bc7b0d4de3a62181d84d5e37da0003e8462637/mypy-2.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:767fe8c66dc3e01e19e1737d4c38ebefead16125e1b8e58ad421903b376f5c65", size = 11147435, upload-time = "2026-05-11T18:33:56.477Z" }, + { url = "https://files.pythonhosted.org/packages/ec/46/e5c449e858798e35ffc90946282a27c62a77be743fe17480e4977374eb91/mypy-2.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:ecfe70d43775ab99562ab128ce49854a362044c9f894961f68f898c23cb7429d", size = 10035052, upload-time = "2026-05-11T18:32:30.049Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ca/b279a672e874aedd5498ae25f722dacc8aa86bbffb939b3f97cbb1cf6686/mypy-2.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7354c5a7f69d9345c3d6e69921d57088eea3ddeeb6b20d34c1b3855b02c36ec2", size = 14848422, upload-time = "2026-05-11T18:35:45.984Z" }, + { url = "https://files.pythonhosted.org/packages/27/e6/3efe56c631d959b9b4454e208b0ac4b7f4f58b404c89f8bec7b49efdfc21/mypy-2.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:49890d4f76ac9e06ec117f9e09f3174da70a620a0c300953d8595c926e80947f", size = 13677374, upload-time = "2026-05-11T18:36:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/84/7f/8107ea87a44fd1f1b59882442f033c9c3488c127201b1d1d15f1cbd6022e/mypy-2.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:761be68e023ef5d94678772396a8af1220030f80837a3afd8d0aef3b419666f4", size = 14055743, upload-time = "2026-05-11T18:35:18.361Z" }, + { url = "https://files.pythonhosted.org/packages/51/4d/b6d34db183133b83761b9199a82d31557cdbb70a380d8c3b3438e11882a3/mypy-2.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c90345fc182dc363b891350457ec69c35140858538f38b4540845afcc32b1aef", size = 15020937, upload-time = "2026-05-11T18:34:59.618Z" }, + { url = "https://files.pythonhosted.org/packages/ff/d7/f08360c691d758acb02f45022c34d98b92892f4ea756644e1000d4b9f3d8/mypy-2.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b84802e7b5a6daf1f5e15bc9fcd7ddae77be13981ffab037f1c67bb84d67d135", size = 15253371, upload-time = "2026-05-11T18:36:41.081Z" }, + { url = "https://files.pythonhosted.org/packages/67/1b/09460a13719530a19bce27bd3bc8449e83569dd2ba7faf51c9c3c30c0b61/mypy-2.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:022c771234936ceac541ebaf836fe9e2abeb3f5e09aff21588fe543ff006fe21", size = 11326429, upload-time = "2026-05-11T18:34:13.526Z" }, + { url = "https://files.pythonhosted.org/packages/40/62/75dbf0f82f7b6680340efc614af29dd0b3c17b8a4f1cd09b8bd2fd6bc814/mypy-2.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:498207db725cec88829a6a5c2fc771205fd043719ef98bc49aba8fb9fc4e6d57", size = 10218799, upload-time = "2026-05-11T18:32:23.491Z" }, + { url = "https://files.pythonhosted.org/packages/b2/66/caca04ed7d972fb6eb6dd1ccd6df1de5c38fae8c5b3dc1c4e8e0d85ee6b9/mypy-2.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7d5e5cad0efeba72b93cd17490cc0d69c5ac9ca132994fe3fb0314808aeeb83e", size = 15923458, upload-time = "2026-05-11T18:35:28.64Z" }, + { url = "https://files.pythonhosted.org/packages/ed/52/2d90cbe49d014b13ed7ff337930c30bad35893fe38a1e4641e756bb62191/mypy-2.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ff715050c127d724fd260a2e666e7747fdd83511c0c47d449d98238970aef780", size = 14757697, upload-time = "2026-05-11T18:36:14.208Z" }, + { url = "https://files.pythonhosted.org/packages/ac/37/d98f4a14e081b238992d0ed96b6d39c7cc0148c9699eb71eaa68629665ea/mypy-2.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:82208da9e09414d520e912d3e462d454854bed0810b71540bb016dcbca7308fd", size = 15405638, upload-time = "2026-05-11T18:33:48.249Z" }, + { url = "https://files.pythonhosted.org/packages/a3/c2/15c46613b24a84fad2aea1248bf9619b99c2767ae9071fe224c179a0b7d4/mypy-2.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e79ebc1b904b84f0310dff7469655a9c36c7a68bddb37bdd42b67a332df61d08", size = 16215852, upload-time = "2026-05-11T18:32:50.296Z" }, + { url = "https://files.pythonhosted.org/packages/5c/90/9c16a57f482c76d25f6379762b56bbf65c711d8158cf271fb2802cfb0640/mypy-2.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e583edc957cfb0deb142079162ae826f58449b116c1d442f2d91c69d9fced081", size = 16452695, upload-time = "2026-05-11T18:33:38.182Z" }, + { url = "https://files.pythonhosted.org/packages/0f/4c/215a4eeb63cacc5f17f516691ea7285d11e249802b942476bff15922a314/mypy-2.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b33b6cd332695bba180d55e717a79d3038e479a2c49cc5eb3d53603409b9a5d7", size = 12866622, upload-time = "2026-05-11T18:34:39.945Z" }, + { url = "https://files.pythonhosted.org/packages/4b/50/1043e1db5f455ffe4c9ab22747cd8ca2bc492b1e4f4e21b130a44ee2b217/mypy-2.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:4f910fe825376a7b66ef7ca8c98e5a149e8cd64c19ae71d84047a74ee060d4e6", size = 10610798, upload-time = "2026-05-11T18:36:31.444Z" }, + { url = "https://files.pythonhosted.org/packages/0d/2a/13ca1f292f6db1b98ff495ef3467736b331621c5917cad984b7043e7348d/mypy-2.1.0-py3-none-any.whl", hash = "sha256:a663814603a5c563fb87a4f96fb473eeb30d1f5a4885afcf44f9db000a366289", size = 2693302, upload-time = "2026-05-11T18:31:29.246Z" }, ] [[package]] From 9d117a0c89d96a67913eb5d30db7f50808557ab3 Mon Sep 17 00:00:00 2001 From: Sam Wilson Date: Wed, 17 Jun 2026 09:41:19 -0400 Subject: [PATCH 049/233] fix(specs): check chain id --- .../client_clis/clis/execution_specs.py | 4 ++ src/ethereum/forks/amsterdam/exceptions.py | 16 ++++- src/ethereum/forks/amsterdam/fork.py | 11 ++- src/ethereum/forks/amsterdam/transactions.py | 30 +++++++-- .../forks/arrow_glacier/exceptions.py | 16 ++++- src/ethereum/forks/arrow_glacier/fork.py | 11 ++- .../forks/arrow_glacier/transactions.py | 30 +++++++-- src/ethereum/forks/berlin/exceptions.py | 16 +++++ src/ethereum/forks/berlin/fork.py | 11 ++- src/ethereum/forks/berlin/transactions.py | 30 +++++++-- src/ethereum/forks/bpo1/exceptions.py | 16 ++++- src/ethereum/forks/bpo1/fork.py | 11 ++- src/ethereum/forks/bpo1/transactions.py | 30 +++++++-- src/ethereum/forks/bpo2/exceptions.py | 16 ++++- src/ethereum/forks/bpo2/fork.py | 11 ++- src/ethereum/forks/bpo2/transactions.py | 30 +++++++-- src/ethereum/forks/bpo3/exceptions.py | 16 ++++- src/ethereum/forks/bpo3/fork.py | 11 ++- src/ethereum/forks/bpo3/transactions.py | 30 +++++++-- src/ethereum/forks/bpo4/exceptions.py | 16 ++++- src/ethereum/forks/bpo4/fork.py | 11 ++- src/ethereum/forks/bpo4/transactions.py | 30 +++++++-- src/ethereum/forks/bpo5/exceptions.py | 16 ++++- src/ethereum/forks/bpo5/fork.py | 11 ++- src/ethereum/forks/bpo5/transactions.py | 30 +++++++-- src/ethereum/forks/byzantium/exceptions.py | 21 ++++++ src/ethereum/forks/byzantium/fork.py | 11 ++- src/ethereum/forks/byzantium/transactions.py | 23 +++++-- src/ethereum/forks/cancun/exceptions.py | 16 ++++- src/ethereum/forks/cancun/fork.py | 11 ++- src/ethereum/forks/cancun/transactions.py | 30 +++++++-- .../forks/constantinople/exceptions.py | 21 ++++++ src/ethereum/forks/constantinople/fork.py | 11 ++- .../forks/constantinople/transactions.py | 23 +++++-- src/ethereum/forks/gray_glacier/exceptions.py | 16 ++++- src/ethereum/forks/gray_glacier/fork.py | 11 ++- .../forks/gray_glacier/transactions.py | 30 +++++++-- src/ethereum/forks/istanbul/exceptions.py | 21 ++++++ src/ethereum/forks/istanbul/fork.py | 11 ++- src/ethereum/forks/istanbul/transactions.py | 23 +++++-- src/ethereum/forks/london/exceptions.py | 16 ++++- src/ethereum/forks/london/fork.py | 11 ++- src/ethereum/forks/london/transactions.py | 30 +++++++-- src/ethereum/forks/muir_glacier/exceptions.py | 21 ++++++ src/ethereum/forks/muir_glacier/fork.py | 11 ++- .../forks/muir_glacier/transactions.py | 23 +++++-- src/ethereum/forks/osaka/exceptions.py | 16 ++++- src/ethereum/forks/osaka/fork.py | 11 ++- src/ethereum/forks/osaka/transactions.py | 30 +++++++-- src/ethereum/forks/paris/exceptions.py | 16 ++++- src/ethereum/forks/paris/fork.py | 11 ++- src/ethereum/forks/paris/transactions.py | 30 +++++++-- src/ethereum/forks/prague/exceptions.py | 16 ++++- src/ethereum/forks/prague/fork.py | 11 ++- src/ethereum/forks/prague/transactions.py | 30 +++++++-- src/ethereum/forks/shanghai/exceptions.py | 16 ++++- src/ethereum/forks/shanghai/fork.py | 11 ++- src/ethereum/forks/shanghai/transactions.py | 30 +++++++-- .../forks/spurious_dragon/exceptions.py | 21 ++++++ src/ethereum/forks/spurious_dragon/fork.py | 11 ++- .../forks/spurious_dragon/transactions.py | 23 +++++-- tests/frontier/validation/test_transaction.py | 59 ++++++++++++++++ .../eip1559_fee_market_change/test_tx_type.py | 67 ++++++++++++++++++- 63 files changed, 1110 insertions(+), 150 deletions(-) create mode 100644 src/ethereum/forks/byzantium/exceptions.py create mode 100644 src/ethereum/forks/constantinople/exceptions.py create mode 100644 src/ethereum/forks/istanbul/exceptions.py create mode 100644 src/ethereum/forks/muir_glacier/exceptions.py create mode 100644 src/ethereum/forks/spurious_dragon/exceptions.py diff --git a/packages/testing/src/execution_testing/client_clis/clis/execution_specs.py b/packages/testing/src/execution_testing/client_clis/clis/execution_specs.py index 55434840aeb..be3b9939a1a 100644 --- a/packages/testing/src/execution_testing/client_clis/clis/execution_specs.py +++ b/packages/testing/src/execution_testing/client_clis/clis/execution_specs.py @@ -204,6 +204,10 @@ class ExecutionSpecsExceptionMapper(ExceptionMapper): TransactionException.INTRINSIC_GAS_BELOW_FLOOR_GAS_COST: ( "InsufficientTransactionGasError" ), + TransactionException.INVALID_SIGNATURE_VRS: ( + "InvalidSignatureError('bad" + ), + TransactionException.INVALID_CHAINID: ("WrongChainId"), TransactionException.INITCODE_SIZE_EXCEEDED: "InitCodeTooLargeError", TransactionException.PRIORITY_GREATER_THAN_MAX_FEE_PER_GAS: ( "PriorityFeeGreaterThanMaxFeeError" diff --git a/src/ethereum/forks/amsterdam/exceptions.py b/src/ethereum/forks/amsterdam/exceptions.py index 1c6c14cc33d..6ef5651cfa8 100644 --- a/src/ethereum/forks/amsterdam/exceptions.py +++ b/src/ethereum/forks/amsterdam/exceptions.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Final -from ethereum_types.numeric import Uint +from ethereum_types.numeric import U64, Uint from ethereum.exceptions import InvalidBlock, InvalidTransaction @@ -12,6 +12,20 @@ from .transactions import Transaction +class WrongChainIdError(InvalidTransaction): + """ + Chain identifier from a transaction does not match the executing chain. See + [EIP-155]. + + [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + """ + + def __init__(self, expected: U64, actual: U64): + super().__init__(f"expected chain_id `{expected}` but got `{actual}`") + self.expected = expected + self.actual = actual + + class TransactionTypeError(InvalidTransaction): """ Unknown [EIP-2718] transaction type byte. diff --git a/src/ethereum/forks/amsterdam/fork.py b/src/ethereum/forks/amsterdam/fork.py index e72f40d8633..722463959f2 100644 --- a/src/ethereum/forks/amsterdam/fork.py +++ b/src/ethereum/forks/amsterdam/fork.py @@ -58,6 +58,7 @@ NoBlobDataError, PriorityFeeGreaterThanMaxFeeError, TransactionTypeContractCreationError, + WrongChainIdError, ) from .fork_types import Authorization, BlockAccessIndex, VersionedHash from .requests import ( @@ -86,6 +87,7 @@ LegacyTransaction, SetCodeTransaction, Transaction, + chain_id, decode_transaction, encode_transaction, get_transaction_hash, @@ -575,7 +577,14 @@ def check_transaction( if tx_blob_gas_used > blob_gas_available: raise BlobGasLimitExceededError("blob gas limit exceeded") - sender_address = recover_sender(block_env.chain_id, tx) + tx_chain_id = chain_id(tx) + if tx_chain_id is not None and tx_chain_id != block_env.chain_id: + raise WrongChainIdError( + expected=block_env.chain_id, + actual=tx_chain_id, + ) + + sender_address = recover_sender(tx) sender_account = get_account(tx_state, sender_address) if isinstance(tx, FeeMarketCapableTransaction): diff --git a/src/ethereum/forks/amsterdam/transactions.py b/src/ethereum/forks/amsterdam/transactions.py index 598f3552710..fd0cc7b1566 100644 --- a/src/ethereum/forks/amsterdam/transactions.py +++ b/src/ethereum/forks/amsterdam/transactions.py @@ -735,7 +735,25 @@ def count_tokens_in_data(data: bytes) -> Uint: return num_zeros + num_non_zeros * Uint(4) -def recover_sender(chain_id: U64, tx: Transaction) -> Address: +def chain_id(tx: Transaction) -> None | U64: + """ + Extract the chain identifier from a transaction. See [EIP-155]. + + [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + """ + if isinstance(tx, LegacyTransaction): + if tx.v == 27 or tx.v == 28: + return None + + if tx.v < U256(35): + raise InvalidSignatureError("bad v") + + return U64((tx.v - U256(35)) >> U256(1)) + else: + return tx.chain_id + + +def recover_sender(tx: Transaction) -> Address: """ Extracts the sender address from a transaction. @@ -762,14 +780,14 @@ def recover_sender(chain_id: U64, tx: Transaction) -> Address: r, s, v - U256(27), signing_hash_pre155(tx) ) else: - chain_id_x2 = U256(chain_id) * U256(2) - if v != U256(35) + chain_id_x2 and v != U256(36) + chain_id_x2: - raise InvalidSignatureError("bad v") + assert v >= U256(35), "call chain_id before recover_sender" + tx_chain_id = U64((v - U256(35)) >> U256(1)) + v = (v - U256(35)) & U256(1) public_key = secp256k1_recover( r, s, - v - U256(35) - chain_id_x2, - signing_hash_155(tx, chain_id), + v, + signing_hash_155(tx, tx_chain_id), ) elif isinstance(tx, AccessListTransaction): if tx.y_parity not in (U256(0), U256(1)): diff --git a/src/ethereum/forks/arrow_glacier/exceptions.py b/src/ethereum/forks/arrow_glacier/exceptions.py index 59968e94e27..906dc947e1b 100644 --- a/src/ethereum/forks/arrow_glacier/exceptions.py +++ b/src/ethereum/forks/arrow_glacier/exceptions.py @@ -4,11 +4,25 @@ from typing import Final -from ethereum_types.numeric import Uint +from ethereum_types.numeric import U64, Uint from ethereum.exceptions import InvalidTransaction +class WrongChainIdError(InvalidTransaction): + """ + Chain identifier from a transaction does not match the executing chain. See + [EIP-155]. + + [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + """ + + def __init__(self, expected: U64, actual: U64): + super().__init__(f"expected chain_id `{expected}` but got `{actual}`") + self.expected = expected + self.actual = actual + + class TransactionTypeError(InvalidTransaction): """ Unknown [EIP-2718] transaction type byte. diff --git a/src/ethereum/forks/arrow_glacier/fork.py b/src/ethereum/forks/arrow_glacier/fork.py index eed78cbdfb2..484d474c983 100644 --- a/src/ethereum/forks/arrow_glacier/fork.py +++ b/src/ethereum/forks/arrow_glacier/fork.py @@ -42,6 +42,7 @@ from .exceptions import ( InsufficientMaxFeePerGasError, PriorityFeeGreaterThanMaxFeeError, + WrongChainIdError, ) from .state_tracker import ( BlockState, @@ -61,6 +62,7 @@ FeeMarketTransaction, LegacyTransaction, Transaction, + chain_id, decode_transaction, encode_transaction, get_transaction_hash, @@ -490,7 +492,14 @@ def check_transaction( gas_available = block_env.block_gas_limit - block_output.block_gas_used if tx.gas > gas_available: raise GasUsedExceedsLimitError("gas used exceeds limit") - sender_address = recover_sender(block_env.chain_id, tx) + tx_chain_id = chain_id(tx) + if tx_chain_id is not None and tx_chain_id != block_env.chain_id: + raise WrongChainIdError( + expected=block_env.chain_id, + actual=tx_chain_id, + ) + + sender_address = recover_sender(tx) sender_account = get_account(tx_state, sender_address) if isinstance(tx, FeeMarketTransaction): diff --git a/src/ethereum/forks/arrow_glacier/transactions.py b/src/ethereum/forks/arrow_glacier/transactions.py index 902128e634c..b0b5bc9fa04 100644 --- a/src/ethereum/forks/arrow_glacier/transactions.py +++ b/src/ethereum/forks/arrow_glacier/transactions.py @@ -376,7 +376,25 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: return GasCosts.TX_BASE + data_cost + create_cost + access_list_cost -def recover_sender(chain_id: U64, tx: Transaction) -> Address: +def chain_id(tx: Transaction) -> None | U64: + """ + Extract the chain identifier from a transaction. See [EIP-155]. + + [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + """ + if isinstance(tx, LegacyTransaction): + if tx.v == 27 or tx.v == 28: + return None + + if tx.v < U256(35): + raise InvalidSignatureError("bad v") + + return U64((tx.v - U256(35)) >> U256(1)) + else: + return tx.chain_id + + +def recover_sender(tx: Transaction) -> Address: """ Extracts the sender address from a transaction. @@ -403,14 +421,14 @@ def recover_sender(chain_id: U64, tx: Transaction) -> Address: r, s, v - U256(27), signing_hash_pre155(tx) ) else: - chain_id_x2 = U256(chain_id) * U256(2) - if v != U256(35) + chain_id_x2 and v != U256(36) + chain_id_x2: - raise InvalidSignatureError("bad v") + assert v >= U256(35), "call chain_id before recover_sender" + tx_chain_id = U64((v - U256(35)) >> U256(1)) + v = (v - U256(35)) & U256(1) public_key = secp256k1_recover( r, s, - v - U256(35) - chain_id_x2, - signing_hash_155(tx, chain_id), + v, + signing_hash_155(tx, tx_chain_id), ) elif isinstance(tx, AccessListTransaction): if tx.y_parity not in (U256(0), U256(1)): diff --git a/src/ethereum/forks/berlin/exceptions.py b/src/ethereum/forks/berlin/exceptions.py index 5781a2c1c35..c451cad48c9 100644 --- a/src/ethereum/forks/berlin/exceptions.py +++ b/src/ethereum/forks/berlin/exceptions.py @@ -4,9 +4,25 @@ from typing import Final +from ethereum_types.numeric import U64 + from ethereum.exceptions import InvalidTransaction +class WrongChainIdError(InvalidTransaction): + """ + Chain identifier from a transaction does not match the executing chain. See + [EIP-155]. + + [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + """ + + def __init__(self, expected: U64, actual: U64): + super().__init__(f"expected chain_id `{expected}` but got `{actual}`") + self.expected = expected + self.actual = actual + + class TransactionTypeError(InvalidTransaction): """ Unknown [EIP-2718] transaction type byte. diff --git a/src/ethereum/forks/berlin/fork.py b/src/ethereum/forks/berlin/fork.py index 69a176907ec..14f72b34c54 100644 --- a/src/ethereum/forks/berlin/fork.py +++ b/src/ethereum/forks/berlin/fork.py @@ -39,6 +39,7 @@ from . import vm from .blocks import Block, Header, Log, Receipt, encode_receipt from .bloom import logs_bloom +from .exceptions import WrongChainIdError from .state_tracker import ( BlockState, TransactionState, @@ -56,6 +57,7 @@ AccessListTransaction, LegacyTransaction, Transaction, + chain_id, decode_transaction, encode_transaction, get_transaction_hash, @@ -403,7 +405,14 @@ def check_transaction( gas_available = block_env.block_gas_limit - block_output.block_gas_used if tx.gas > gas_available: raise GasUsedExceedsLimitError("gas used exceeds limit") - sender_address = recover_sender(block_env.chain_id, tx) + tx_chain_id = chain_id(tx) + if tx_chain_id is not None and tx_chain_id != block_env.chain_id: + raise WrongChainIdError( + expected=block_env.chain_id, + actual=tx_chain_id, + ) + + sender_address = recover_sender(tx) sender_account = get_account(tx_state, sender_address) max_gas_fee = tx.gas * tx.gas_price diff --git a/src/ethereum/forks/berlin/transactions.py b/src/ethereum/forks/berlin/transactions.py index 1c4bf3ca2f0..a36ed5e5da1 100644 --- a/src/ethereum/forks/berlin/transactions.py +++ b/src/ethereum/forks/berlin/transactions.py @@ -292,7 +292,25 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: return GasCosts.TX_BASE + data_cost + create_cost + access_list_cost -def recover_sender(chain_id: U64, tx: Transaction) -> Address: +def chain_id(tx: Transaction) -> None | U64: + """ + Extract the chain identifier from a transaction. See [EIP-155]. + + [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + """ + if isinstance(tx, LegacyTransaction): + if tx.v == 27 or tx.v == 28: + return None + + if tx.v < U256(35): + raise InvalidSignatureError("bad v") + + return U64((tx.v - U256(35)) >> U256(1)) + else: + return tx.chain_id + + +def recover_sender(tx: Transaction) -> Address: """ Extracts the sender address from a transaction. @@ -319,14 +337,14 @@ def recover_sender(chain_id: U64, tx: Transaction) -> Address: r, s, v - U256(27), signing_hash_pre155(tx) ) else: - chain_id_x2 = U256(chain_id) * U256(2) - if v != U256(35) + chain_id_x2 and v != U256(36) + chain_id_x2: - raise InvalidSignatureError("bad v") + assert v >= U256(35), "call chain_id before recover_sender" + tx_chain_id = U64((v - U256(35)) >> U256(1)) + v = (v - U256(35)) & U256(1) public_key = secp256k1_recover( r, s, - v - U256(35) - chain_id_x2, - signing_hash_155(tx, chain_id), + v, + signing_hash_155(tx, tx_chain_id), ) elif isinstance(tx, AccessListTransaction): if tx.y_parity not in (U256(0), U256(1)): diff --git a/src/ethereum/forks/bpo1/exceptions.py b/src/ethereum/forks/bpo1/exceptions.py index 3074a1f738f..8d409eaaa02 100644 --- a/src/ethereum/forks/bpo1/exceptions.py +++ b/src/ethereum/forks/bpo1/exceptions.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Final -from ethereum_types.numeric import Uint +from ethereum_types.numeric import U64, Uint from ethereum.exceptions import InvalidTransaction @@ -12,6 +12,20 @@ from .transactions import Transaction +class WrongChainIdError(InvalidTransaction): + """ + Chain identifier from a transaction does not match the executing chain. See + [EIP-155]. + + [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + """ + + def __init__(self, expected: U64, actual: U64): + super().__init__(f"expected chain_id `{expected}` but got `{actual}`") + self.expected = expected + self.actual = actual + + class TransactionTypeError(InvalidTransaction): """ Unknown [EIP-2718] transaction type byte. diff --git a/src/ethereum/forks/bpo1/fork.py b/src/ethereum/forks/bpo1/fork.py index a256625deb7..c2c43c631dc 100644 --- a/src/ethereum/forks/bpo1/fork.py +++ b/src/ethereum/forks/bpo1/fork.py @@ -48,6 +48,7 @@ NoBlobDataError, PriorityFeeGreaterThanMaxFeeError, TransactionTypeContractCreationError, + WrongChainIdError, ) from .fork_types import Authorization, VersionedHash from .requests import ( @@ -75,6 +76,7 @@ LegacyTransaction, SetCodeTransaction, Transaction, + chain_id, decode_transaction, encode_transaction, get_transaction_hash, @@ -483,7 +485,14 @@ def check_transaction( if tx_blob_gas_used > blob_gas_available: raise BlobGasLimitExceededError("blob gas limit exceeded") - sender_address = recover_sender(block_env.chain_id, tx) + tx_chain_id = chain_id(tx) + if tx_chain_id is not None and tx_chain_id != block_env.chain_id: + raise WrongChainIdError( + expected=block_env.chain_id, + actual=tx_chain_id, + ) + + sender_address = recover_sender(tx) sender_account = get_account(tx_state, sender_address) if isinstance(tx, FeeMarketCapableTransaction): diff --git a/src/ethereum/forks/bpo1/transactions.py b/src/ethereum/forks/bpo1/transactions.py index acd3e8b07c3..ad60842ee81 100644 --- a/src/ethereum/forks/bpo1/transactions.py +++ b/src/ethereum/forks/bpo1/transactions.py @@ -651,7 +651,25 @@ def calculate_intrinsic_cost(tx: Transaction) -> IntrinsicGasCost: ) -def recover_sender(chain_id: U64, tx: Transaction) -> Address: +def chain_id(tx: Transaction) -> None | U64: + """ + Extract the chain identifier from a transaction. See [EIP-155]. + + [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + """ + if isinstance(tx, LegacyTransaction): + if tx.v == 27 or tx.v == 28: + return None + + if tx.v < U256(35): + raise InvalidSignatureError("bad v") + + return U64((tx.v - U256(35)) >> U256(1)) + else: + return tx.chain_id + + +def recover_sender(tx: Transaction) -> Address: """ Extracts the sender address from a transaction. @@ -678,14 +696,14 @@ def recover_sender(chain_id: U64, tx: Transaction) -> Address: r, s, v - U256(27), signing_hash_pre155(tx) ) else: - chain_id_x2 = U256(chain_id) * U256(2) - if v != U256(35) + chain_id_x2 and v != U256(36) + chain_id_x2: - raise InvalidSignatureError("bad v") + assert v >= U256(35), "call chain_id before recover_sender" + tx_chain_id = U64((v - U256(35)) >> U256(1)) + v = (v - U256(35)) & U256(1) public_key = secp256k1_recover( r, s, - v - U256(35) - chain_id_x2, - signing_hash_155(tx, chain_id), + v, + signing_hash_155(tx, tx_chain_id), ) elif isinstance(tx, AccessListTransaction): if tx.y_parity not in (U256(0), U256(1)): diff --git a/src/ethereum/forks/bpo2/exceptions.py b/src/ethereum/forks/bpo2/exceptions.py index 3074a1f738f..8d409eaaa02 100644 --- a/src/ethereum/forks/bpo2/exceptions.py +++ b/src/ethereum/forks/bpo2/exceptions.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Final -from ethereum_types.numeric import Uint +from ethereum_types.numeric import U64, Uint from ethereum.exceptions import InvalidTransaction @@ -12,6 +12,20 @@ from .transactions import Transaction +class WrongChainIdError(InvalidTransaction): + """ + Chain identifier from a transaction does not match the executing chain. See + [EIP-155]. + + [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + """ + + def __init__(self, expected: U64, actual: U64): + super().__init__(f"expected chain_id `{expected}` but got `{actual}`") + self.expected = expected + self.actual = actual + + class TransactionTypeError(InvalidTransaction): """ Unknown [EIP-2718] transaction type byte. diff --git a/src/ethereum/forks/bpo2/fork.py b/src/ethereum/forks/bpo2/fork.py index a256625deb7..c2c43c631dc 100644 --- a/src/ethereum/forks/bpo2/fork.py +++ b/src/ethereum/forks/bpo2/fork.py @@ -48,6 +48,7 @@ NoBlobDataError, PriorityFeeGreaterThanMaxFeeError, TransactionTypeContractCreationError, + WrongChainIdError, ) from .fork_types import Authorization, VersionedHash from .requests import ( @@ -75,6 +76,7 @@ LegacyTransaction, SetCodeTransaction, Transaction, + chain_id, decode_transaction, encode_transaction, get_transaction_hash, @@ -483,7 +485,14 @@ def check_transaction( if tx_blob_gas_used > blob_gas_available: raise BlobGasLimitExceededError("blob gas limit exceeded") - sender_address = recover_sender(block_env.chain_id, tx) + tx_chain_id = chain_id(tx) + if tx_chain_id is not None and tx_chain_id != block_env.chain_id: + raise WrongChainIdError( + expected=block_env.chain_id, + actual=tx_chain_id, + ) + + sender_address = recover_sender(tx) sender_account = get_account(tx_state, sender_address) if isinstance(tx, FeeMarketCapableTransaction): diff --git a/src/ethereum/forks/bpo2/transactions.py b/src/ethereum/forks/bpo2/transactions.py index 983dcefa419..569d6867270 100644 --- a/src/ethereum/forks/bpo2/transactions.py +++ b/src/ethereum/forks/bpo2/transactions.py @@ -651,7 +651,25 @@ def calculate_intrinsic_cost(tx: Transaction) -> IntrinsicGasCost: ) -def recover_sender(chain_id: U64, tx: Transaction) -> Address: +def chain_id(tx: Transaction) -> None | U64: + """ + Extract the chain identifier from a transaction. See [EIP-155]. + + [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + """ + if isinstance(tx, LegacyTransaction): + if tx.v == 27 or tx.v == 28: + return None + + if tx.v < U256(35): + raise InvalidSignatureError("bad v") + + return U64((tx.v - U256(35)) >> U256(1)) + else: + return tx.chain_id + + +def recover_sender(tx: Transaction) -> Address: """ Extracts the sender address from a transaction. @@ -678,14 +696,14 @@ def recover_sender(chain_id: U64, tx: Transaction) -> Address: r, s, v - U256(27), signing_hash_pre155(tx) ) else: - chain_id_x2 = U256(chain_id) * U256(2) - if v != U256(35) + chain_id_x2 and v != U256(36) + chain_id_x2: - raise InvalidSignatureError("bad v") + assert v >= U256(35), "call chain_id before recover_sender" + tx_chain_id = U64((v - U256(35)) >> U256(1)) + v = (v - U256(35)) & U256(1) public_key = secp256k1_recover( r, s, - v - U256(35) - chain_id_x2, - signing_hash_155(tx, chain_id), + v, + signing_hash_155(tx, tx_chain_id), ) elif isinstance(tx, AccessListTransaction): if tx.y_parity not in (U256(0), U256(1)): diff --git a/src/ethereum/forks/bpo3/exceptions.py b/src/ethereum/forks/bpo3/exceptions.py index 3074a1f738f..8d409eaaa02 100644 --- a/src/ethereum/forks/bpo3/exceptions.py +++ b/src/ethereum/forks/bpo3/exceptions.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Final -from ethereum_types.numeric import Uint +from ethereum_types.numeric import U64, Uint from ethereum.exceptions import InvalidTransaction @@ -12,6 +12,20 @@ from .transactions import Transaction +class WrongChainIdError(InvalidTransaction): + """ + Chain identifier from a transaction does not match the executing chain. See + [EIP-155]. + + [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + """ + + def __init__(self, expected: U64, actual: U64): + super().__init__(f"expected chain_id `{expected}` but got `{actual}`") + self.expected = expected + self.actual = actual + + class TransactionTypeError(InvalidTransaction): """ Unknown [EIP-2718] transaction type byte. diff --git a/src/ethereum/forks/bpo3/fork.py b/src/ethereum/forks/bpo3/fork.py index a256625deb7..c2c43c631dc 100644 --- a/src/ethereum/forks/bpo3/fork.py +++ b/src/ethereum/forks/bpo3/fork.py @@ -48,6 +48,7 @@ NoBlobDataError, PriorityFeeGreaterThanMaxFeeError, TransactionTypeContractCreationError, + WrongChainIdError, ) from .fork_types import Authorization, VersionedHash from .requests import ( @@ -75,6 +76,7 @@ LegacyTransaction, SetCodeTransaction, Transaction, + chain_id, decode_transaction, encode_transaction, get_transaction_hash, @@ -483,7 +485,14 @@ def check_transaction( if tx_blob_gas_used > blob_gas_available: raise BlobGasLimitExceededError("blob gas limit exceeded") - sender_address = recover_sender(block_env.chain_id, tx) + tx_chain_id = chain_id(tx) + if tx_chain_id is not None and tx_chain_id != block_env.chain_id: + raise WrongChainIdError( + expected=block_env.chain_id, + actual=tx_chain_id, + ) + + sender_address = recover_sender(tx) sender_account = get_account(tx_state, sender_address) if isinstance(tx, FeeMarketCapableTransaction): diff --git a/src/ethereum/forks/bpo3/transactions.py b/src/ethereum/forks/bpo3/transactions.py index 6592faf2ea9..258364835f8 100644 --- a/src/ethereum/forks/bpo3/transactions.py +++ b/src/ethereum/forks/bpo3/transactions.py @@ -651,7 +651,25 @@ def calculate_intrinsic_cost(tx: Transaction) -> IntrinsicGasCost: ) -def recover_sender(chain_id: U64, tx: Transaction) -> Address: +def chain_id(tx: Transaction) -> None | U64: + """ + Extract the chain identifier from a transaction. See [EIP-155]. + + [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + """ + if isinstance(tx, LegacyTransaction): + if tx.v == 27 or tx.v == 28: + return None + + if tx.v < U256(35): + raise InvalidSignatureError("bad v") + + return U64((tx.v - U256(35)) >> U256(1)) + else: + return tx.chain_id + + +def recover_sender(tx: Transaction) -> Address: """ Extracts the sender address from a transaction. @@ -678,14 +696,14 @@ def recover_sender(chain_id: U64, tx: Transaction) -> Address: r, s, v - U256(27), signing_hash_pre155(tx) ) else: - chain_id_x2 = U256(chain_id) * U256(2) - if v != U256(35) + chain_id_x2 and v != U256(36) + chain_id_x2: - raise InvalidSignatureError("bad v") + assert v >= U256(35), "call chain_id before recover_sender" + tx_chain_id = U64((v - U256(35)) >> U256(1)) + v = (v - U256(35)) & U256(1) public_key = secp256k1_recover( r, s, - v - U256(35) - chain_id_x2, - signing_hash_155(tx, chain_id), + v, + signing_hash_155(tx, tx_chain_id), ) elif isinstance(tx, AccessListTransaction): if tx.y_parity not in (U256(0), U256(1)): diff --git a/src/ethereum/forks/bpo4/exceptions.py b/src/ethereum/forks/bpo4/exceptions.py index 3074a1f738f..8d409eaaa02 100644 --- a/src/ethereum/forks/bpo4/exceptions.py +++ b/src/ethereum/forks/bpo4/exceptions.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Final -from ethereum_types.numeric import Uint +from ethereum_types.numeric import U64, Uint from ethereum.exceptions import InvalidTransaction @@ -12,6 +12,20 @@ from .transactions import Transaction +class WrongChainIdError(InvalidTransaction): + """ + Chain identifier from a transaction does not match the executing chain. See + [EIP-155]. + + [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + """ + + def __init__(self, expected: U64, actual: U64): + super().__init__(f"expected chain_id `{expected}` but got `{actual}`") + self.expected = expected + self.actual = actual + + class TransactionTypeError(InvalidTransaction): """ Unknown [EIP-2718] transaction type byte. diff --git a/src/ethereum/forks/bpo4/fork.py b/src/ethereum/forks/bpo4/fork.py index a256625deb7..c2c43c631dc 100644 --- a/src/ethereum/forks/bpo4/fork.py +++ b/src/ethereum/forks/bpo4/fork.py @@ -48,6 +48,7 @@ NoBlobDataError, PriorityFeeGreaterThanMaxFeeError, TransactionTypeContractCreationError, + WrongChainIdError, ) from .fork_types import Authorization, VersionedHash from .requests import ( @@ -75,6 +76,7 @@ LegacyTransaction, SetCodeTransaction, Transaction, + chain_id, decode_transaction, encode_transaction, get_transaction_hash, @@ -483,7 +485,14 @@ def check_transaction( if tx_blob_gas_used > blob_gas_available: raise BlobGasLimitExceededError("blob gas limit exceeded") - sender_address = recover_sender(block_env.chain_id, tx) + tx_chain_id = chain_id(tx) + if tx_chain_id is not None and tx_chain_id != block_env.chain_id: + raise WrongChainIdError( + expected=block_env.chain_id, + actual=tx_chain_id, + ) + + sender_address = recover_sender(tx) sender_account = get_account(tx_state, sender_address) if isinstance(tx, FeeMarketCapableTransaction): diff --git a/src/ethereum/forks/bpo4/transactions.py b/src/ethereum/forks/bpo4/transactions.py index 1a916ab853c..5e86fc63f13 100644 --- a/src/ethereum/forks/bpo4/transactions.py +++ b/src/ethereum/forks/bpo4/transactions.py @@ -651,7 +651,25 @@ def calculate_intrinsic_cost(tx: Transaction) -> IntrinsicGasCost: ) -def recover_sender(chain_id: U64, tx: Transaction) -> Address: +def chain_id(tx: Transaction) -> None | U64: + """ + Extract the chain identifier from a transaction. See [EIP-155]. + + [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + """ + if isinstance(tx, LegacyTransaction): + if tx.v == 27 or tx.v == 28: + return None + + if tx.v < U256(35): + raise InvalidSignatureError("bad v") + + return U64((tx.v - U256(35)) >> U256(1)) + else: + return tx.chain_id + + +def recover_sender(tx: Transaction) -> Address: """ Extracts the sender address from a transaction. @@ -678,14 +696,14 @@ def recover_sender(chain_id: U64, tx: Transaction) -> Address: r, s, v - U256(27), signing_hash_pre155(tx) ) else: - chain_id_x2 = U256(chain_id) * U256(2) - if v != U256(35) + chain_id_x2 and v != U256(36) + chain_id_x2: - raise InvalidSignatureError("bad v") + assert v >= U256(35), "call chain_id before recover_sender" + tx_chain_id = U64((v - U256(35)) >> U256(1)) + v = (v - U256(35)) & U256(1) public_key = secp256k1_recover( r, s, - v - U256(35) - chain_id_x2, - signing_hash_155(tx, chain_id), + v, + signing_hash_155(tx, tx_chain_id), ) elif isinstance(tx, AccessListTransaction): if tx.y_parity not in (U256(0), U256(1)): diff --git a/src/ethereum/forks/bpo5/exceptions.py b/src/ethereum/forks/bpo5/exceptions.py index 3074a1f738f..8d409eaaa02 100644 --- a/src/ethereum/forks/bpo5/exceptions.py +++ b/src/ethereum/forks/bpo5/exceptions.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Final -from ethereum_types.numeric import Uint +from ethereum_types.numeric import U64, Uint from ethereum.exceptions import InvalidTransaction @@ -12,6 +12,20 @@ from .transactions import Transaction +class WrongChainIdError(InvalidTransaction): + """ + Chain identifier from a transaction does not match the executing chain. See + [EIP-155]. + + [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + """ + + def __init__(self, expected: U64, actual: U64): + super().__init__(f"expected chain_id `{expected}` but got `{actual}`") + self.expected = expected + self.actual = actual + + class TransactionTypeError(InvalidTransaction): """ Unknown [EIP-2718] transaction type byte. diff --git a/src/ethereum/forks/bpo5/fork.py b/src/ethereum/forks/bpo5/fork.py index a256625deb7..c2c43c631dc 100644 --- a/src/ethereum/forks/bpo5/fork.py +++ b/src/ethereum/forks/bpo5/fork.py @@ -48,6 +48,7 @@ NoBlobDataError, PriorityFeeGreaterThanMaxFeeError, TransactionTypeContractCreationError, + WrongChainIdError, ) from .fork_types import Authorization, VersionedHash from .requests import ( @@ -75,6 +76,7 @@ LegacyTransaction, SetCodeTransaction, Transaction, + chain_id, decode_transaction, encode_transaction, get_transaction_hash, @@ -483,7 +485,14 @@ def check_transaction( if tx_blob_gas_used > blob_gas_available: raise BlobGasLimitExceededError("blob gas limit exceeded") - sender_address = recover_sender(block_env.chain_id, tx) + tx_chain_id = chain_id(tx) + if tx_chain_id is not None and tx_chain_id != block_env.chain_id: + raise WrongChainIdError( + expected=block_env.chain_id, + actual=tx_chain_id, + ) + + sender_address = recover_sender(tx) sender_account = get_account(tx_state, sender_address) if isinstance(tx, FeeMarketCapableTransaction): diff --git a/src/ethereum/forks/bpo5/transactions.py b/src/ethereum/forks/bpo5/transactions.py index 8c765b0fd5d..2aded367166 100644 --- a/src/ethereum/forks/bpo5/transactions.py +++ b/src/ethereum/forks/bpo5/transactions.py @@ -651,7 +651,25 @@ def calculate_intrinsic_cost(tx: Transaction) -> IntrinsicGasCost: ) -def recover_sender(chain_id: U64, tx: Transaction) -> Address: +def chain_id(tx: Transaction) -> None | U64: + """ + Extract the chain identifier from a transaction. See [EIP-155]. + + [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + """ + if isinstance(tx, LegacyTransaction): + if tx.v == 27 or tx.v == 28: + return None + + if tx.v < U256(35): + raise InvalidSignatureError("bad v") + + return U64((tx.v - U256(35)) >> U256(1)) + else: + return tx.chain_id + + +def recover_sender(tx: Transaction) -> Address: """ Extracts the sender address from a transaction. @@ -678,14 +696,14 @@ def recover_sender(chain_id: U64, tx: Transaction) -> Address: r, s, v - U256(27), signing_hash_pre155(tx) ) else: - chain_id_x2 = U256(chain_id) * U256(2) - if v != U256(35) + chain_id_x2 and v != U256(36) + chain_id_x2: - raise InvalidSignatureError("bad v") + assert v >= U256(35), "call chain_id before recover_sender" + tx_chain_id = U64((v - U256(35)) >> U256(1)) + v = (v - U256(35)) & U256(1) public_key = secp256k1_recover( r, s, - v - U256(35) - chain_id_x2, - signing_hash_155(tx, chain_id), + v, + signing_hash_155(tx, tx_chain_id), ) elif isinstance(tx, AccessListTransaction): if tx.y_parity not in (U256(0), U256(1)): diff --git a/src/ethereum/forks/byzantium/exceptions.py b/src/ethereum/forks/byzantium/exceptions.py new file mode 100644 index 00000000000..1ba2a07726c --- /dev/null +++ b/src/ethereum/forks/byzantium/exceptions.py @@ -0,0 +1,21 @@ +""" +Exceptions specific to this fork. +""" + +from ethereum_types.numeric import U64 + +from ethereum.exceptions import InvalidTransaction + + +class WrongChainIdError(InvalidTransaction): + """ + Chain identifier from a transaction does not match the executing chain. See + [EIP-155]. + + [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + """ + + def __init__(self, expected: U64, actual: U64): + super().__init__(f"expected chain_id `{expected}` but got `{actual}`") + self.expected = expected + self.actual = actual diff --git a/src/ethereum/forks/byzantium/fork.py b/src/ethereum/forks/byzantium/fork.py index 1c705256b96..a83087f4f0a 100644 --- a/src/ethereum/forks/byzantium/fork.py +++ b/src/ethereum/forks/byzantium/fork.py @@ -38,6 +38,7 @@ from . import vm from .blocks import Block, Header, Log, Receipt from .bloom import logs_bloom +from .exceptions import WrongChainIdError from .state_tracker import ( BlockState, TransactionState, @@ -53,6 +54,7 @@ ) from .transactions import ( Transaction, + chain_id, get_transaction_hash, recover_sender, validate_transaction, @@ -398,7 +400,14 @@ def check_transaction( gas_available = block_env.block_gas_limit - block_output.block_gas_used if tx.gas > gas_available: raise GasUsedExceedsLimitError("gas used exceeds limit") - sender_address = recover_sender(block_env.chain_id, tx) + tx_chain_id = chain_id(tx) + if tx_chain_id is not None and tx_chain_id != block_env.chain_id: + raise WrongChainIdError( + expected=block_env.chain_id, + actual=tx_chain_id, + ) + + sender_address = recover_sender(tx) sender_account = get_account(tx_state, sender_address) max_gas_fee = tx.gas * tx.gas_price diff --git a/src/ethereum/forks/byzantium/transactions.py b/src/ethereum/forks/byzantium/transactions.py index 8272ebea080..f599f94e062 100644 --- a/src/ethereum/forks/byzantium/transactions.py +++ b/src/ethereum/forks/byzantium/transactions.py @@ -147,7 +147,20 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: return GasCosts.TX_BASE + data_cost + create_cost -def recover_sender(chain_id: U64, tx: Transaction) -> Address: +def chain_id(tx: Transaction) -> None | U64: + """ + Extract the chain identifier from a transaction. See [EIP-155]. + + [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + """ + if tx.v == 27 or tx.v == 28: + return None + if tx.v < U256(35): + raise InvalidSignatureError("bad v") + return U64((tx.v - U256(35)) >> U256(1)) + + +def recover_sender(tx: Transaction) -> Address: """ Extracts the sender address from a transaction. @@ -172,11 +185,11 @@ def recover_sender(chain_id: U64, tx: Transaction) -> Address: r, s, v - U256(27), signing_hash_pre155(tx) ) else: - chain_id_x2 = U256(chain_id) * U256(2) - if v != U256(35) + chain_id_x2 and v != U256(36) + chain_id_x2: - raise InvalidSignatureError("bad v") + assert v >= U256(35), "call chain_id before recover_sender" + tx_chain_id = U64((v - U256(35)) >> U256(1)) + v = (v - U256(35)) & U256(1) public_key = secp256k1_recover( - r, s, v - U256(35) - chain_id_x2, signing_hash_155(tx, chain_id) + r, s, v, signing_hash_155(tx, tx_chain_id) ) return Address(keccak256(public_key)[12:32]) diff --git a/src/ethereum/forks/cancun/exceptions.py b/src/ethereum/forks/cancun/exceptions.py index 5b46b805561..3256a5a7fcf 100644 --- a/src/ethereum/forks/cancun/exceptions.py +++ b/src/ethereum/forks/cancun/exceptions.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Final -from ethereum_types.numeric import Uint +from ethereum_types.numeric import U64, Uint from ethereum.exceptions import InvalidTransaction @@ -12,6 +12,20 @@ from .transactions import Transaction +class WrongChainIdError(InvalidTransaction): + """ + Chain identifier from a transaction does not match the executing chain. See + [EIP-155]. + + [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + """ + + def __init__(self, expected: U64, actual: U64): + super().__init__(f"expected chain_id `{expected}` but got `{actual}`") + self.expected = expected + self.actual = actual + + class TransactionTypeError(InvalidTransaction): """ Unknown [EIP-2718] transaction type byte. diff --git a/src/ethereum/forks/cancun/fork.py b/src/ethereum/forks/cancun/fork.py index 753912fd9d5..67e3b0197d0 100644 --- a/src/ethereum/forks/cancun/fork.py +++ b/src/ethereum/forks/cancun/fork.py @@ -46,6 +46,7 @@ NoBlobDataError, PriorityFeeGreaterThanMaxFeeError, TransactionTypeContractCreationError, + WrongChainIdError, ) from .fork_types import VersionedHash from .state_tracker import ( @@ -67,6 +68,7 @@ FeeMarketTransaction, LegacyTransaction, Transaction, + chain_id, decode_transaction, encode_transaction, get_transaction_hash, @@ -446,7 +448,14 @@ def check_transaction( if tx_blob_gas_used > blob_gas_available: raise BlobGasLimitExceededError("blob gas limit exceeded") - sender_address = recover_sender(block_env.chain_id, tx) + tx_chain_id = chain_id(tx) + if tx_chain_id is not None and tx_chain_id != block_env.chain_id: + raise WrongChainIdError( + expected=block_env.chain_id, + actual=tx_chain_id, + ) + + sender_address = recover_sender(tx) sender_account = get_account(tx_state, sender_address) if isinstance(tx, FeeMarketCapableTransaction): diff --git a/src/ethereum/forks/cancun/transactions.py b/src/ethereum/forks/cancun/transactions.py index a0d80d8c67a..d65da512655 100644 --- a/src/ethereum/forks/cancun/transactions.py +++ b/src/ethereum/forks/cancun/transactions.py @@ -500,7 +500,25 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: return GasCosts.TX_BASE + data_cost + create_cost + access_list_cost -def recover_sender(chain_id: U64, tx: Transaction) -> Address: +def chain_id(tx: Transaction) -> None | U64: + """ + Extract the chain identifier from a transaction. See [EIP-155]. + + [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + """ + if isinstance(tx, LegacyTransaction): + if tx.v == 27 or tx.v == 28: + return None + + if tx.v < U256(35): + raise InvalidSignatureError("bad v") + + return U64((tx.v - U256(35)) >> U256(1)) + else: + return tx.chain_id + + +def recover_sender(tx: Transaction) -> Address: """ Extracts the sender address from a transaction. @@ -527,14 +545,14 @@ def recover_sender(chain_id: U64, tx: Transaction) -> Address: r, s, v - U256(27), signing_hash_pre155(tx) ) else: - chain_id_x2 = U256(chain_id) * U256(2) - if v != U256(35) + chain_id_x2 and v != U256(36) + chain_id_x2: - raise InvalidSignatureError("bad v") + assert v >= U256(35), "call chain_id before recover_sender" + tx_chain_id = U64((v - U256(35)) >> U256(1)) + v = (v - U256(35)) & U256(1) public_key = secp256k1_recover( r, s, - v - U256(35) - chain_id_x2, - signing_hash_155(tx, chain_id), + v, + signing_hash_155(tx, tx_chain_id), ) elif isinstance(tx, AccessListTransaction): if tx.y_parity not in (U256(0), U256(1)): diff --git a/src/ethereum/forks/constantinople/exceptions.py b/src/ethereum/forks/constantinople/exceptions.py new file mode 100644 index 00000000000..1ba2a07726c --- /dev/null +++ b/src/ethereum/forks/constantinople/exceptions.py @@ -0,0 +1,21 @@ +""" +Exceptions specific to this fork. +""" + +from ethereum_types.numeric import U64 + +from ethereum.exceptions import InvalidTransaction + + +class WrongChainIdError(InvalidTransaction): + """ + Chain identifier from a transaction does not match the executing chain. See + [EIP-155]. + + [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + """ + + def __init__(self, expected: U64, actual: U64): + super().__init__(f"expected chain_id `{expected}` but got `{actual}`") + self.expected = expected + self.actual = actual diff --git a/src/ethereum/forks/constantinople/fork.py b/src/ethereum/forks/constantinople/fork.py index 209fe029e46..62654a56386 100644 --- a/src/ethereum/forks/constantinople/fork.py +++ b/src/ethereum/forks/constantinople/fork.py @@ -38,6 +38,7 @@ from . import vm from .blocks import Block, Header, Log, Receipt from .bloom import logs_bloom +from .exceptions import WrongChainIdError from .state_tracker import ( BlockState, TransactionState, @@ -53,6 +54,7 @@ ) from .transactions import ( Transaction, + chain_id, get_transaction_hash, recover_sender, validate_transaction, @@ -398,7 +400,14 @@ def check_transaction( gas_available = block_env.block_gas_limit - block_output.block_gas_used if tx.gas > gas_available: raise GasUsedExceedsLimitError("gas used exceeds limit") - sender_address = recover_sender(block_env.chain_id, tx) + tx_chain_id = chain_id(tx) + if tx_chain_id is not None and tx_chain_id != block_env.chain_id: + raise WrongChainIdError( + expected=block_env.chain_id, + actual=tx_chain_id, + ) + + sender_address = recover_sender(tx) sender_account = get_account(tx_state, sender_address) max_gas_fee = tx.gas * tx.gas_price diff --git a/src/ethereum/forks/constantinople/transactions.py b/src/ethereum/forks/constantinople/transactions.py index 8272ebea080..f599f94e062 100644 --- a/src/ethereum/forks/constantinople/transactions.py +++ b/src/ethereum/forks/constantinople/transactions.py @@ -147,7 +147,20 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: return GasCosts.TX_BASE + data_cost + create_cost -def recover_sender(chain_id: U64, tx: Transaction) -> Address: +def chain_id(tx: Transaction) -> None | U64: + """ + Extract the chain identifier from a transaction. See [EIP-155]. + + [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + """ + if tx.v == 27 or tx.v == 28: + return None + if tx.v < U256(35): + raise InvalidSignatureError("bad v") + return U64((tx.v - U256(35)) >> U256(1)) + + +def recover_sender(tx: Transaction) -> Address: """ Extracts the sender address from a transaction. @@ -172,11 +185,11 @@ def recover_sender(chain_id: U64, tx: Transaction) -> Address: r, s, v - U256(27), signing_hash_pre155(tx) ) else: - chain_id_x2 = U256(chain_id) * U256(2) - if v != U256(35) + chain_id_x2 and v != U256(36) + chain_id_x2: - raise InvalidSignatureError("bad v") + assert v >= U256(35), "call chain_id before recover_sender" + tx_chain_id = U64((v - U256(35)) >> U256(1)) + v = (v - U256(35)) & U256(1) public_key = secp256k1_recover( - r, s, v - U256(35) - chain_id_x2, signing_hash_155(tx, chain_id) + r, s, v, signing_hash_155(tx, tx_chain_id) ) return Address(keccak256(public_key)[12:32]) diff --git a/src/ethereum/forks/gray_glacier/exceptions.py b/src/ethereum/forks/gray_glacier/exceptions.py index 59968e94e27..906dc947e1b 100644 --- a/src/ethereum/forks/gray_glacier/exceptions.py +++ b/src/ethereum/forks/gray_glacier/exceptions.py @@ -4,11 +4,25 @@ from typing import Final -from ethereum_types.numeric import Uint +from ethereum_types.numeric import U64, Uint from ethereum.exceptions import InvalidTransaction +class WrongChainIdError(InvalidTransaction): + """ + Chain identifier from a transaction does not match the executing chain. See + [EIP-155]. + + [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + """ + + def __init__(self, expected: U64, actual: U64): + super().__init__(f"expected chain_id `{expected}` but got `{actual}`") + self.expected = expected + self.actual = actual + + class TransactionTypeError(InvalidTransaction): """ Unknown [EIP-2718] transaction type byte. diff --git a/src/ethereum/forks/gray_glacier/fork.py b/src/ethereum/forks/gray_glacier/fork.py index 78a88531754..fca169f4123 100644 --- a/src/ethereum/forks/gray_glacier/fork.py +++ b/src/ethereum/forks/gray_glacier/fork.py @@ -42,6 +42,7 @@ from .exceptions import ( InsufficientMaxFeePerGasError, PriorityFeeGreaterThanMaxFeeError, + WrongChainIdError, ) from .state_tracker import ( BlockState, @@ -61,6 +62,7 @@ FeeMarketTransaction, LegacyTransaction, Transaction, + chain_id, decode_transaction, encode_transaction, get_transaction_hash, @@ -490,7 +492,14 @@ def check_transaction( gas_available = block_env.block_gas_limit - block_output.block_gas_used if tx.gas > gas_available: raise GasUsedExceedsLimitError("gas used exceeds limit") - sender_address = recover_sender(block_env.chain_id, tx) + tx_chain_id = chain_id(tx) + if tx_chain_id is not None and tx_chain_id != block_env.chain_id: + raise WrongChainIdError( + expected=block_env.chain_id, + actual=tx_chain_id, + ) + + sender_address = recover_sender(tx) sender_account = get_account(tx_state, sender_address) if isinstance(tx, FeeMarketTransaction): diff --git a/src/ethereum/forks/gray_glacier/transactions.py b/src/ethereum/forks/gray_glacier/transactions.py index 902128e634c..b0b5bc9fa04 100644 --- a/src/ethereum/forks/gray_glacier/transactions.py +++ b/src/ethereum/forks/gray_glacier/transactions.py @@ -376,7 +376,25 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: return GasCosts.TX_BASE + data_cost + create_cost + access_list_cost -def recover_sender(chain_id: U64, tx: Transaction) -> Address: +def chain_id(tx: Transaction) -> None | U64: + """ + Extract the chain identifier from a transaction. See [EIP-155]. + + [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + """ + if isinstance(tx, LegacyTransaction): + if tx.v == 27 or tx.v == 28: + return None + + if tx.v < U256(35): + raise InvalidSignatureError("bad v") + + return U64((tx.v - U256(35)) >> U256(1)) + else: + return tx.chain_id + + +def recover_sender(tx: Transaction) -> Address: """ Extracts the sender address from a transaction. @@ -403,14 +421,14 @@ def recover_sender(chain_id: U64, tx: Transaction) -> Address: r, s, v - U256(27), signing_hash_pre155(tx) ) else: - chain_id_x2 = U256(chain_id) * U256(2) - if v != U256(35) + chain_id_x2 and v != U256(36) + chain_id_x2: - raise InvalidSignatureError("bad v") + assert v >= U256(35), "call chain_id before recover_sender" + tx_chain_id = U64((v - U256(35)) >> U256(1)) + v = (v - U256(35)) & U256(1) public_key = secp256k1_recover( r, s, - v - U256(35) - chain_id_x2, - signing_hash_155(tx, chain_id), + v, + signing_hash_155(tx, tx_chain_id), ) elif isinstance(tx, AccessListTransaction): if tx.y_parity not in (U256(0), U256(1)): diff --git a/src/ethereum/forks/istanbul/exceptions.py b/src/ethereum/forks/istanbul/exceptions.py new file mode 100644 index 00000000000..1ba2a07726c --- /dev/null +++ b/src/ethereum/forks/istanbul/exceptions.py @@ -0,0 +1,21 @@ +""" +Exceptions specific to this fork. +""" + +from ethereum_types.numeric import U64 + +from ethereum.exceptions import InvalidTransaction + + +class WrongChainIdError(InvalidTransaction): + """ + Chain identifier from a transaction does not match the executing chain. See + [EIP-155]. + + [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + """ + + def __init__(self, expected: U64, actual: U64): + super().__init__(f"expected chain_id `{expected}` but got `{actual}`") + self.expected = expected + self.actual = actual diff --git a/src/ethereum/forks/istanbul/fork.py b/src/ethereum/forks/istanbul/fork.py index 5d5dd2686d1..da84266e3f9 100644 --- a/src/ethereum/forks/istanbul/fork.py +++ b/src/ethereum/forks/istanbul/fork.py @@ -38,6 +38,7 @@ from . import vm from .blocks import Block, Header, Log, Receipt from .bloom import logs_bloom +from .exceptions import WrongChainIdError from .state_tracker import ( BlockState, TransactionState, @@ -53,6 +54,7 @@ ) from .transactions import ( Transaction, + chain_id, get_transaction_hash, recover_sender, validate_transaction, @@ -398,7 +400,14 @@ def check_transaction( gas_available = block_env.block_gas_limit - block_output.block_gas_used if tx.gas > gas_available: raise GasUsedExceedsLimitError("gas used exceeds limit") - sender_address = recover_sender(block_env.chain_id, tx) + tx_chain_id = chain_id(tx) + if tx_chain_id is not None and tx_chain_id != block_env.chain_id: + raise WrongChainIdError( + expected=block_env.chain_id, + actual=tx_chain_id, + ) + + sender_address = recover_sender(tx) sender_account = get_account(tx_state, sender_address) max_gas_fee = tx.gas * tx.gas_price diff --git a/src/ethereum/forks/istanbul/transactions.py b/src/ethereum/forks/istanbul/transactions.py index 8272ebea080..f599f94e062 100644 --- a/src/ethereum/forks/istanbul/transactions.py +++ b/src/ethereum/forks/istanbul/transactions.py @@ -147,7 +147,20 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: return GasCosts.TX_BASE + data_cost + create_cost -def recover_sender(chain_id: U64, tx: Transaction) -> Address: +def chain_id(tx: Transaction) -> None | U64: + """ + Extract the chain identifier from a transaction. See [EIP-155]. + + [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + """ + if tx.v == 27 or tx.v == 28: + return None + if tx.v < U256(35): + raise InvalidSignatureError("bad v") + return U64((tx.v - U256(35)) >> U256(1)) + + +def recover_sender(tx: Transaction) -> Address: """ Extracts the sender address from a transaction. @@ -172,11 +185,11 @@ def recover_sender(chain_id: U64, tx: Transaction) -> Address: r, s, v - U256(27), signing_hash_pre155(tx) ) else: - chain_id_x2 = U256(chain_id) * U256(2) - if v != U256(35) + chain_id_x2 and v != U256(36) + chain_id_x2: - raise InvalidSignatureError("bad v") + assert v >= U256(35), "call chain_id before recover_sender" + tx_chain_id = U64((v - U256(35)) >> U256(1)) + v = (v - U256(35)) & U256(1) public_key = secp256k1_recover( - r, s, v - U256(35) - chain_id_x2, signing_hash_155(tx, chain_id) + r, s, v, signing_hash_155(tx, tx_chain_id) ) return Address(keccak256(public_key)[12:32]) diff --git a/src/ethereum/forks/london/exceptions.py b/src/ethereum/forks/london/exceptions.py index 59968e94e27..906dc947e1b 100644 --- a/src/ethereum/forks/london/exceptions.py +++ b/src/ethereum/forks/london/exceptions.py @@ -4,11 +4,25 @@ from typing import Final -from ethereum_types.numeric import Uint +from ethereum_types.numeric import U64, Uint from ethereum.exceptions import InvalidTransaction +class WrongChainIdError(InvalidTransaction): + """ + Chain identifier from a transaction does not match the executing chain. See + [EIP-155]. + + [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + """ + + def __init__(self, expected: U64, actual: U64): + super().__init__(f"expected chain_id `{expected}` but got `{actual}`") + self.expected = expected + self.actual = actual + + class TransactionTypeError(InvalidTransaction): """ Unknown [EIP-2718] transaction type byte. diff --git a/src/ethereum/forks/london/fork.py b/src/ethereum/forks/london/fork.py index d5ce75bd533..c457b885c6d 100644 --- a/src/ethereum/forks/london/fork.py +++ b/src/ethereum/forks/london/fork.py @@ -43,6 +43,7 @@ from .exceptions import ( InsufficientMaxFeePerGasError, PriorityFeeGreaterThanMaxFeeError, + WrongChainIdError, ) from .state_tracker import ( BlockState, @@ -62,6 +63,7 @@ FeeMarketTransaction, LegacyTransaction, Transaction, + chain_id, decode_transaction, encode_transaction, get_transaction_hash, @@ -499,7 +501,14 @@ def check_transaction( gas_available = block_env.block_gas_limit - block_output.block_gas_used if tx.gas > gas_available: raise GasUsedExceedsLimitError("gas used exceeds limit") - sender_address = recover_sender(block_env.chain_id, tx) + tx_chain_id = chain_id(tx) + if tx_chain_id is not None and tx_chain_id != block_env.chain_id: + raise WrongChainIdError( + expected=block_env.chain_id, + actual=tx_chain_id, + ) + + sender_address = recover_sender(tx) sender_account = get_account(tx_state, sender_address) if isinstance(tx, FeeMarketTransaction): diff --git a/src/ethereum/forks/london/transactions.py b/src/ethereum/forks/london/transactions.py index 902128e634c..b0b5bc9fa04 100644 --- a/src/ethereum/forks/london/transactions.py +++ b/src/ethereum/forks/london/transactions.py @@ -376,7 +376,25 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: return GasCosts.TX_BASE + data_cost + create_cost + access_list_cost -def recover_sender(chain_id: U64, tx: Transaction) -> Address: +def chain_id(tx: Transaction) -> None | U64: + """ + Extract the chain identifier from a transaction. See [EIP-155]. + + [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + """ + if isinstance(tx, LegacyTransaction): + if tx.v == 27 or tx.v == 28: + return None + + if tx.v < U256(35): + raise InvalidSignatureError("bad v") + + return U64((tx.v - U256(35)) >> U256(1)) + else: + return tx.chain_id + + +def recover_sender(tx: Transaction) -> Address: """ Extracts the sender address from a transaction. @@ -403,14 +421,14 @@ def recover_sender(chain_id: U64, tx: Transaction) -> Address: r, s, v - U256(27), signing_hash_pre155(tx) ) else: - chain_id_x2 = U256(chain_id) * U256(2) - if v != U256(35) + chain_id_x2 and v != U256(36) + chain_id_x2: - raise InvalidSignatureError("bad v") + assert v >= U256(35), "call chain_id before recover_sender" + tx_chain_id = U64((v - U256(35)) >> U256(1)) + v = (v - U256(35)) & U256(1) public_key = secp256k1_recover( r, s, - v - U256(35) - chain_id_x2, - signing_hash_155(tx, chain_id), + v, + signing_hash_155(tx, tx_chain_id), ) elif isinstance(tx, AccessListTransaction): if tx.y_parity not in (U256(0), U256(1)): diff --git a/src/ethereum/forks/muir_glacier/exceptions.py b/src/ethereum/forks/muir_glacier/exceptions.py new file mode 100644 index 00000000000..1ba2a07726c --- /dev/null +++ b/src/ethereum/forks/muir_glacier/exceptions.py @@ -0,0 +1,21 @@ +""" +Exceptions specific to this fork. +""" + +from ethereum_types.numeric import U64 + +from ethereum.exceptions import InvalidTransaction + + +class WrongChainIdError(InvalidTransaction): + """ + Chain identifier from a transaction does not match the executing chain. See + [EIP-155]. + + [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + """ + + def __init__(self, expected: U64, actual: U64): + super().__init__(f"expected chain_id `{expected}` but got `{actual}`") + self.expected = expected + self.actual = actual diff --git a/src/ethereum/forks/muir_glacier/fork.py b/src/ethereum/forks/muir_glacier/fork.py index 0b6ada29506..e64fcf08654 100644 --- a/src/ethereum/forks/muir_glacier/fork.py +++ b/src/ethereum/forks/muir_glacier/fork.py @@ -38,6 +38,7 @@ from . import vm from .blocks import Block, Header, Log, Receipt from .bloom import logs_bloom +from .exceptions import WrongChainIdError from .state_tracker import ( BlockState, TransactionState, @@ -53,6 +54,7 @@ ) from .transactions import ( Transaction, + chain_id, get_transaction_hash, recover_sender, validate_transaction, @@ -398,7 +400,14 @@ def check_transaction( gas_available = block_env.block_gas_limit - block_output.block_gas_used if tx.gas > gas_available: raise GasUsedExceedsLimitError("gas used exceeds limit") - sender_address = recover_sender(block_env.chain_id, tx) + tx_chain_id = chain_id(tx) + if tx_chain_id is not None and tx_chain_id != block_env.chain_id: + raise WrongChainIdError( + expected=block_env.chain_id, + actual=tx_chain_id, + ) + + sender_address = recover_sender(tx) sender_account = get_account(tx_state, sender_address) max_gas_fee = tx.gas * tx.gas_price diff --git a/src/ethereum/forks/muir_glacier/transactions.py b/src/ethereum/forks/muir_glacier/transactions.py index 8272ebea080..f599f94e062 100644 --- a/src/ethereum/forks/muir_glacier/transactions.py +++ b/src/ethereum/forks/muir_glacier/transactions.py @@ -147,7 +147,20 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: return GasCosts.TX_BASE + data_cost + create_cost -def recover_sender(chain_id: U64, tx: Transaction) -> Address: +def chain_id(tx: Transaction) -> None | U64: + """ + Extract the chain identifier from a transaction. See [EIP-155]. + + [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + """ + if tx.v == 27 or tx.v == 28: + return None + if tx.v < U256(35): + raise InvalidSignatureError("bad v") + return U64((tx.v - U256(35)) >> U256(1)) + + +def recover_sender(tx: Transaction) -> Address: """ Extracts the sender address from a transaction. @@ -172,11 +185,11 @@ def recover_sender(chain_id: U64, tx: Transaction) -> Address: r, s, v - U256(27), signing_hash_pre155(tx) ) else: - chain_id_x2 = U256(chain_id) * U256(2) - if v != U256(35) + chain_id_x2 and v != U256(36) + chain_id_x2: - raise InvalidSignatureError("bad v") + assert v >= U256(35), "call chain_id before recover_sender" + tx_chain_id = U64((v - U256(35)) >> U256(1)) + v = (v - U256(35)) & U256(1) public_key = secp256k1_recover( - r, s, v - U256(35) - chain_id_x2, signing_hash_155(tx, chain_id) + r, s, v, signing_hash_155(tx, tx_chain_id) ) return Address(keccak256(public_key)[12:32]) diff --git a/src/ethereum/forks/osaka/exceptions.py b/src/ethereum/forks/osaka/exceptions.py index 3074a1f738f..8d409eaaa02 100644 --- a/src/ethereum/forks/osaka/exceptions.py +++ b/src/ethereum/forks/osaka/exceptions.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Final -from ethereum_types.numeric import Uint +from ethereum_types.numeric import U64, Uint from ethereum.exceptions import InvalidTransaction @@ -12,6 +12,20 @@ from .transactions import Transaction +class WrongChainIdError(InvalidTransaction): + """ + Chain identifier from a transaction does not match the executing chain. See + [EIP-155]. + + [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + """ + + def __init__(self, expected: U64, actual: U64): + super().__init__(f"expected chain_id `{expected}` but got `{actual}`") + self.expected = expected + self.actual = actual + + class TransactionTypeError(InvalidTransaction): """ Unknown [EIP-2718] transaction type byte. diff --git a/src/ethereum/forks/osaka/fork.py b/src/ethereum/forks/osaka/fork.py index a256625deb7..c2c43c631dc 100644 --- a/src/ethereum/forks/osaka/fork.py +++ b/src/ethereum/forks/osaka/fork.py @@ -48,6 +48,7 @@ NoBlobDataError, PriorityFeeGreaterThanMaxFeeError, TransactionTypeContractCreationError, + WrongChainIdError, ) from .fork_types import Authorization, VersionedHash from .requests import ( @@ -75,6 +76,7 @@ LegacyTransaction, SetCodeTransaction, Transaction, + chain_id, decode_transaction, encode_transaction, get_transaction_hash, @@ -483,7 +485,14 @@ def check_transaction( if tx_blob_gas_used > blob_gas_available: raise BlobGasLimitExceededError("blob gas limit exceeded") - sender_address = recover_sender(block_env.chain_id, tx) + tx_chain_id = chain_id(tx) + if tx_chain_id is not None and tx_chain_id != block_env.chain_id: + raise WrongChainIdError( + expected=block_env.chain_id, + actual=tx_chain_id, + ) + + sender_address = recover_sender(tx) sender_account = get_account(tx_state, sender_address) if isinstance(tx, FeeMarketCapableTransaction): diff --git a/src/ethereum/forks/osaka/transactions.py b/src/ethereum/forks/osaka/transactions.py index b0a90152275..58b4f03c253 100644 --- a/src/ethereum/forks/osaka/transactions.py +++ b/src/ethereum/forks/osaka/transactions.py @@ -655,7 +655,25 @@ def calculate_intrinsic_cost(tx: Transaction) -> IntrinsicGasCost: ) -def recover_sender(chain_id: U64, tx: Transaction) -> Address: +def chain_id(tx: Transaction) -> None | U64: + """ + Extract the chain identifier from a transaction. See [EIP-155]. + + [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + """ + if isinstance(tx, LegacyTransaction): + if tx.v == 27 or tx.v == 28: + return None + + if tx.v < U256(35): + raise InvalidSignatureError("bad v") + + return U64((tx.v - U256(35)) >> U256(1)) + else: + return tx.chain_id + + +def recover_sender(tx: Transaction) -> Address: """ Extracts the sender address from a transaction. @@ -682,14 +700,14 @@ def recover_sender(chain_id: U64, tx: Transaction) -> Address: r, s, v - U256(27), signing_hash_pre155(tx) ) else: - chain_id_x2 = U256(chain_id) * U256(2) - if v != U256(35) + chain_id_x2 and v != U256(36) + chain_id_x2: - raise InvalidSignatureError("bad v") + assert v >= U256(35), "call chain_id before recover_sender" + tx_chain_id = U64((v - U256(35)) >> U256(1)) + v = (v - U256(35)) & U256(1) public_key = secp256k1_recover( r, s, - v - U256(35) - chain_id_x2, - signing_hash_155(tx, chain_id), + v, + signing_hash_155(tx, tx_chain_id), ) elif isinstance(tx, AccessListTransaction): if tx.y_parity not in (U256(0), U256(1)): diff --git a/src/ethereum/forks/paris/exceptions.py b/src/ethereum/forks/paris/exceptions.py index 59968e94e27..906dc947e1b 100644 --- a/src/ethereum/forks/paris/exceptions.py +++ b/src/ethereum/forks/paris/exceptions.py @@ -4,11 +4,25 @@ from typing import Final -from ethereum_types.numeric import Uint +from ethereum_types.numeric import U64, Uint from ethereum.exceptions import InvalidTransaction +class WrongChainIdError(InvalidTransaction): + """ + Chain identifier from a transaction does not match the executing chain. See + [EIP-155]. + + [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + """ + + def __init__(self, expected: U64, actual: U64): + super().__init__(f"expected chain_id `{expected}` but got `{actual}`") + self.expected = expected + self.actual = actual + + class TransactionTypeError(InvalidTransaction): """ Unknown [EIP-2718] transaction type byte. diff --git a/src/ethereum/forks/paris/fork.py b/src/ethereum/forks/paris/fork.py index a5609b3353c..318cbdf2163 100644 --- a/src/ethereum/forks/paris/fork.py +++ b/src/ethereum/forks/paris/fork.py @@ -41,6 +41,7 @@ from .exceptions import ( InsufficientMaxFeePerGasError, PriorityFeeGreaterThanMaxFeeError, + WrongChainIdError, ) from .state_tracker import ( BlockState, @@ -58,6 +59,7 @@ FeeMarketTransaction, LegacyTransaction, Transaction, + chain_id, decode_transaction, encode_transaction, get_transaction_hash, @@ -389,7 +391,14 @@ def check_transaction( gas_available = block_env.block_gas_limit - block_output.block_gas_used if tx.gas > gas_available: raise GasUsedExceedsLimitError("gas used exceeds limit") - sender_address = recover_sender(block_env.chain_id, tx) + tx_chain_id = chain_id(tx) + if tx_chain_id is not None and tx_chain_id != block_env.chain_id: + raise WrongChainIdError( + expected=block_env.chain_id, + actual=tx_chain_id, + ) + + sender_address = recover_sender(tx) sender_account = get_account(tx_state, sender_address) if isinstance(tx, FeeMarketTransaction): diff --git a/src/ethereum/forks/paris/transactions.py b/src/ethereum/forks/paris/transactions.py index 902128e634c..b0b5bc9fa04 100644 --- a/src/ethereum/forks/paris/transactions.py +++ b/src/ethereum/forks/paris/transactions.py @@ -376,7 +376,25 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: return GasCosts.TX_BASE + data_cost + create_cost + access_list_cost -def recover_sender(chain_id: U64, tx: Transaction) -> Address: +def chain_id(tx: Transaction) -> None | U64: + """ + Extract the chain identifier from a transaction. See [EIP-155]. + + [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + """ + if isinstance(tx, LegacyTransaction): + if tx.v == 27 or tx.v == 28: + return None + + if tx.v < U256(35): + raise InvalidSignatureError("bad v") + + return U64((tx.v - U256(35)) >> U256(1)) + else: + return tx.chain_id + + +def recover_sender(tx: Transaction) -> Address: """ Extracts the sender address from a transaction. @@ -403,14 +421,14 @@ def recover_sender(chain_id: U64, tx: Transaction) -> Address: r, s, v - U256(27), signing_hash_pre155(tx) ) else: - chain_id_x2 = U256(chain_id) * U256(2) - if v != U256(35) + chain_id_x2 and v != U256(36) + chain_id_x2: - raise InvalidSignatureError("bad v") + assert v >= U256(35), "call chain_id before recover_sender" + tx_chain_id = U64((v - U256(35)) >> U256(1)) + v = (v - U256(35)) & U256(1) public_key = secp256k1_recover( r, s, - v - U256(35) - chain_id_x2, - signing_hash_155(tx, chain_id), + v, + signing_hash_155(tx, tx_chain_id), ) elif isinstance(tx, AccessListTransaction): if tx.y_parity not in (U256(0), U256(1)): diff --git a/src/ethereum/forks/prague/exceptions.py b/src/ethereum/forks/prague/exceptions.py index 898d96bebaf..18d22306281 100644 --- a/src/ethereum/forks/prague/exceptions.py +++ b/src/ethereum/forks/prague/exceptions.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Final -from ethereum_types.numeric import Uint +from ethereum_types.numeric import U64, Uint from ethereum.exceptions import InvalidTransaction @@ -12,6 +12,20 @@ from .transactions import Transaction +class WrongChainIdError(InvalidTransaction): + """ + Chain identifier from a transaction does not match the executing chain. See + [EIP-155]. + + [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + """ + + def __init__(self, expected: U64, actual: U64): + super().__init__(f"expected chain_id `{expected}` but got `{actual}`") + self.expected = expected + self.actual = actual + + class TransactionTypeError(InvalidTransaction): """ Unknown [EIP-2718] transaction type byte. diff --git a/src/ethereum/forks/prague/fork.py b/src/ethereum/forks/prague/fork.py index f48695cf5c6..f5c4f81a6f8 100644 --- a/src/ethereum/forks/prague/fork.py +++ b/src/ethereum/forks/prague/fork.py @@ -47,6 +47,7 @@ NoBlobDataError, PriorityFeeGreaterThanMaxFeeError, TransactionTypeContractCreationError, + WrongChainIdError, ) from .fork_types import Authorization, VersionedHash from .requests import ( @@ -74,6 +75,7 @@ LegacyTransaction, SetCodeTransaction, Transaction, + chain_id, decode_transaction, encode_transaction, get_transaction_hash, @@ -471,7 +473,14 @@ def check_transaction( if tx_blob_gas_used > blob_gas_available: raise BlobGasLimitExceededError("blob gas limit exceeded") - sender_address = recover_sender(block_env.chain_id, tx) + tx_chain_id = chain_id(tx) + if tx_chain_id is not None and tx_chain_id != block_env.chain_id: + raise WrongChainIdError( + expected=block_env.chain_id, + actual=tx_chain_id, + ) + + sender_address = recover_sender(tx) sender_account = get_account(tx_state, sender_address) if isinstance(tx, FeeMarketCapableTransaction): diff --git a/src/ethereum/forks/prague/transactions.py b/src/ethereum/forks/prague/transactions.py index ed549ec1d39..2c5dd7d1b1f 100644 --- a/src/ethereum/forks/prague/transactions.py +++ b/src/ethereum/forks/prague/transactions.py @@ -646,7 +646,25 @@ def calculate_intrinsic_cost(tx: Transaction) -> IntrinsicGasCost: ) -def recover_sender(chain_id: U64, tx: Transaction) -> Address: +def chain_id(tx: Transaction) -> None | U64: + """ + Extract the chain identifier from a transaction. See [EIP-155]. + + [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + """ + if isinstance(tx, LegacyTransaction): + if tx.v == 27 or tx.v == 28: + return None + + if tx.v < U256(35): + raise InvalidSignatureError("bad v") + + return U64((tx.v - U256(35)) >> U256(1)) + else: + return tx.chain_id + + +def recover_sender(tx: Transaction) -> Address: """ Extracts the sender address from a transaction. @@ -673,14 +691,14 @@ def recover_sender(chain_id: U64, tx: Transaction) -> Address: r, s, v - U256(27), signing_hash_pre155(tx) ) else: - chain_id_x2 = U256(chain_id) * U256(2) - if v != U256(35) + chain_id_x2 and v != U256(36) + chain_id_x2: - raise InvalidSignatureError("bad v") + assert v >= U256(35), "call chain_id before recover_sender" + tx_chain_id = U64((v - U256(35)) >> U256(1)) + v = (v - U256(35)) & U256(1) public_key = secp256k1_recover( r, s, - v - U256(35) - chain_id_x2, - signing_hash_155(tx, chain_id), + v, + signing_hash_155(tx, tx_chain_id), ) elif isinstance(tx, AccessListTransaction): if tx.y_parity not in (U256(0), U256(1)): diff --git a/src/ethereum/forks/shanghai/exceptions.py b/src/ethereum/forks/shanghai/exceptions.py index 8dc6c4d0e1c..3929980023b 100644 --- a/src/ethereum/forks/shanghai/exceptions.py +++ b/src/ethereum/forks/shanghai/exceptions.py @@ -4,11 +4,25 @@ from typing import Final -from ethereum_types.numeric import Uint +from ethereum_types.numeric import U64, Uint from ethereum.exceptions import InvalidTransaction +class WrongChainIdError(InvalidTransaction): + """ + Chain identifier from a transaction does not match the executing chain. See + [EIP-155]. + + [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + """ + + def __init__(self, expected: U64, actual: U64): + super().__init__(f"expected chain_id `{expected}` but got `{actual}`") + self.expected = expected + self.actual = actual + + class TransactionTypeError(InvalidTransaction): """ Unknown [EIP-2718] transaction type byte. diff --git a/src/ethereum/forks/shanghai/fork.py b/src/ethereum/forks/shanghai/fork.py index 73a34017d55..c038d947014 100644 --- a/src/ethereum/forks/shanghai/fork.py +++ b/src/ethereum/forks/shanghai/fork.py @@ -41,6 +41,7 @@ from .exceptions import ( InsufficientMaxFeePerGasError, PriorityFeeGreaterThanMaxFeeError, + WrongChainIdError, ) from .state_tracker import ( BlockState, @@ -58,6 +59,7 @@ FeeMarketTransaction, LegacyTransaction, Transaction, + chain_id, decode_transaction, encode_transaction, get_transaction_hash, @@ -393,7 +395,14 @@ def check_transaction( gas_available = block_env.block_gas_limit - block_output.block_gas_used if tx.gas > gas_available: raise GasUsedExceedsLimitError("gas used exceeds limit") - sender_address = recover_sender(block_env.chain_id, tx) + tx_chain_id = chain_id(tx) + if tx_chain_id is not None and tx_chain_id != block_env.chain_id: + raise WrongChainIdError( + expected=block_env.chain_id, + actual=tx_chain_id, + ) + + sender_address = recover_sender(tx) sender_account = get_account(tx_state, sender_address) if isinstance(tx, FeeMarketTransaction): diff --git a/src/ethereum/forks/shanghai/transactions.py b/src/ethereum/forks/shanghai/transactions.py index 2b76014505b..b76058eaf39 100644 --- a/src/ethereum/forks/shanghai/transactions.py +++ b/src/ethereum/forks/shanghai/transactions.py @@ -386,7 +386,25 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: return GasCosts.TX_BASE + data_cost + create_cost + access_list_cost -def recover_sender(chain_id: U64, tx: Transaction) -> Address: +def chain_id(tx: Transaction) -> None | U64: + """ + Extract the chain identifier from a transaction. See [EIP-155]. + + [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + """ + if isinstance(tx, LegacyTransaction): + if tx.v == 27 or tx.v == 28: + return None + + if tx.v < U256(35): + raise InvalidSignatureError("bad v") + + return U64((tx.v - U256(35)) >> U256(1)) + else: + return tx.chain_id + + +def recover_sender(tx: Transaction) -> Address: """ Extracts the sender address from a transaction. @@ -413,14 +431,14 @@ def recover_sender(chain_id: U64, tx: Transaction) -> Address: r, s, v - U256(27), signing_hash_pre155(tx) ) else: - chain_id_x2 = U256(chain_id) * U256(2) - if v != U256(35) + chain_id_x2 and v != U256(36) + chain_id_x2: - raise InvalidSignatureError("bad v") + assert v >= U256(35), "call chain_id before recover_sender" + tx_chain_id = U64((v - U256(35)) >> U256(1)) + v = (v - U256(35)) & U256(1) public_key = secp256k1_recover( r, s, - v - U256(35) - chain_id_x2, - signing_hash_155(tx, chain_id), + v, + signing_hash_155(tx, tx_chain_id), ) elif isinstance(tx, AccessListTransaction): if tx.y_parity not in (U256(0), U256(1)): diff --git a/src/ethereum/forks/spurious_dragon/exceptions.py b/src/ethereum/forks/spurious_dragon/exceptions.py new file mode 100644 index 00000000000..1ba2a07726c --- /dev/null +++ b/src/ethereum/forks/spurious_dragon/exceptions.py @@ -0,0 +1,21 @@ +""" +Exceptions specific to this fork. +""" + +from ethereum_types.numeric import U64 + +from ethereum.exceptions import InvalidTransaction + + +class WrongChainIdError(InvalidTransaction): + """ + Chain identifier from a transaction does not match the executing chain. See + [EIP-155]. + + [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + """ + + def __init__(self, expected: U64, actual: U64): + super().__init__(f"expected chain_id `{expected}` but got `{actual}`") + self.expected = expected + self.actual = actual diff --git a/src/ethereum/forks/spurious_dragon/fork.py b/src/ethereum/forks/spurious_dragon/fork.py index b82b9fc12bc..01c8b269d16 100644 --- a/src/ethereum/forks/spurious_dragon/fork.py +++ b/src/ethereum/forks/spurious_dragon/fork.py @@ -38,6 +38,7 @@ from . import vm from .blocks import Block, Header, Log, Receipt from .bloom import logs_bloom +from .exceptions import WrongChainIdError from .state_tracker import ( BlockState, TransactionState, @@ -53,6 +54,7 @@ ) from .transactions import ( Transaction, + chain_id, get_transaction_hash, recover_sender, validate_transaction, @@ -394,7 +396,14 @@ def check_transaction( gas_available = block_env.block_gas_limit - block_output.block_gas_used if tx.gas > gas_available: raise GasUsedExceedsLimitError("gas used exceeds limit") - sender_address = recover_sender(block_env.chain_id, tx) + tx_chain_id = chain_id(tx) + if tx_chain_id is not None and tx_chain_id != block_env.chain_id: + raise WrongChainIdError( + expected=block_env.chain_id, + actual=tx_chain_id, + ) + + sender_address = recover_sender(tx) sender_account = get_account(tx_state, sender_address) max_gas_fee = tx.gas * tx.gas_price diff --git a/src/ethereum/forks/spurious_dragon/transactions.py b/src/ethereum/forks/spurious_dragon/transactions.py index 8272ebea080..f599f94e062 100644 --- a/src/ethereum/forks/spurious_dragon/transactions.py +++ b/src/ethereum/forks/spurious_dragon/transactions.py @@ -147,7 +147,20 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint: return GasCosts.TX_BASE + data_cost + create_cost -def recover_sender(chain_id: U64, tx: Transaction) -> Address: +def chain_id(tx: Transaction) -> None | U64: + """ + Extract the chain identifier from a transaction. See [EIP-155]. + + [EIP-155]: https://eips.ethereum.org/EIPS/eip-155 + """ + if tx.v == 27 or tx.v == 28: + return None + if tx.v < U256(35): + raise InvalidSignatureError("bad v") + return U64((tx.v - U256(35)) >> U256(1)) + + +def recover_sender(tx: Transaction) -> Address: """ Extracts the sender address from a transaction. @@ -172,11 +185,11 @@ def recover_sender(chain_id: U64, tx: Transaction) -> Address: r, s, v - U256(27), signing_hash_pre155(tx) ) else: - chain_id_x2 = U256(chain_id) * U256(2) - if v != U256(35) + chain_id_x2 and v != U256(36) + chain_id_x2: - raise InvalidSignatureError("bad v") + assert v >= U256(35), "call chain_id before recover_sender" + tx_chain_id = U64((v - U256(35)) >> U256(1)) + v = (v - U256(35)) & U256(1) public_key = secp256k1_recover( - r, s, v - U256(35) - chain_id_x2, signing_hash_155(tx, chain_id) + r, s, v, signing_hash_155(tx, tx_chain_id) ) return Address(keccak256(public_key)[12:32]) diff --git a/tests/frontier/validation/test_transaction.py b/tests/frontier/validation/test_transaction.py index 3e3f487317f..39613ae4ec6 100644 --- a/tests/frontier/validation/test_transaction.py +++ b/tests/frontier/validation/test_transaction.py @@ -8,6 +8,7 @@ StateTestFiller, Storage, Transaction, + add_kzg_version, ) from execution_testing.base_types.base_types import ZeroPaddedHexNumber from execution_testing.exceptions.exceptions import TransactionException @@ -196,3 +197,61 @@ def test_sender_balance_insufficient_state_test( post={contract: Account(storage=storage)}, tx=tx, ) + + +SECP256K1N = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 + + +@pytest.mark.valid_from("Frontier") +@pytest.mark.exception_test +@pytest.mark.eels_base_coverage +@pytest.mark.with_all_tx_types +@pytest.mark.parametrize( + ("v", "r", "s"), + [ + # Other than 27/28, anything less than 35 for v is invalid. + (34, 1, 1), + # Equal to or above these values are invalid. + (27, SECP256K1N, 1), + pytest.param(27, 1, SECP256K1N, id="s=SECP256K1N"), + pytest.param( + 27, + 1, + (SECP256K1N // 2) + 1, + id="s=SECP256K1N//2+1", + marks=pytest.mark.valid_from("Homestead"), + ), + ], +) +def test_bad_v_r_s( + state_test: StateTestFiller, + pre: Alloc, + tx_type: int, + v: int, + r: int, + s: int, +) -> None: + """ + The v/y_parity component of a signature must be 35 or greater (if it isn't + 27/28). + """ + to = pre.fund_eoa(0xDEADBEEE) + + blob_versioned_hashes = add_kzg_version([0], 1) if tx_type == 3 else None + tx = Transaction( + sender=pre.fund_eoa(), + to=to, + error=TransactionException.INVALID_SIGNATURE_VRS, + ty=tx_type, + blob_versioned_hashes=blob_versioned_hashes, + value=1, + v=v, + r=r, + s=s, + ) + + state_test( + pre=pre, + post={to: Account(balance=0xDEADBEEE)}, + tx=tx, + ) diff --git a/tests/london/eip1559_fee_market_change/test_tx_type.py b/tests/london/eip1559_fee_market_change/test_tx_type.py index c8ade2fb6b6..6a831d0203a 100644 --- a/tests/london/eip1559_fee_market_change/test_tx_type.py +++ b/tests/london/eip1559_fee_market_change/test_tx_type.py @@ -1,18 +1,21 @@ """Test the tx type validation for EIP-1559.""" -from typing import Generator +from typing import Final, Generator, Sequence import pytest from execution_testing import ( Account, Alloc, + ChainConfig, Fork, ParameterSet, StateTestFiller, Transaction, TransactionException, + TransactionType, ) from execution_testing import Opcodes as Op +from execution_testing.base_types import Hash from .spec import ref_spec_1559 @@ -70,3 +73,65 @@ def test_eip1559_tx_validity( post[sender] = pre[sender] # type: ignore state_test(pre=pre, post=post, tx=tx) + + +TX_TYPES: Final[Sequence[object]] = [ + pytest.param(TransactionType.LEGACY, None), + pytest.param( + TransactionType.ACCESS_LIST, + None, + marks=[pytest.mark.valid_from("Berlin")], + ), + pytest.param( + TransactionType.BASE_FEE, + None, + marks=[pytest.mark.valid_from("London")], + ), + pytest.param( + TransactionType.BLOB_TRANSACTION, + [0], + marks=[pytest.mark.valid_from("Cancun")], + ), + pytest.param( + TransactionType.SET_CODE, + None, + marks=[pytest.mark.valid_from("Prague")], + ), +] + +if len(TX_TYPES) != len(TransactionType): + raise Exception("missing tx type") + + +@pytest.mark.valid_from("SpuriousDragon") +@pytest.mark.exception_test +@pytest.mark.parametrize(("tx_type", "blob_versioned_hashes"), TX_TYPES) +def test_invalid_chain_id( + state_test: StateTestFiller, + pre: Alloc, + chain_config: ChainConfig, + tx_type: int, + blob_versioned_hashes: None | Sequence[Hash], +) -> None: + """ + Test that a transaction with a different chain id is not valid. + """ + to = pre.fund_eoa(0xDEADBEEE) + + tx = Transaction( + sender=pre.fund_eoa(), + value=1, + chain_id=chain_config.chain_id + 1, + ty=tx_type, + to=to, + error=TransactionException.INVALID_CHAINID, + blob_versioned_hashes=blob_versioned_hashes, + ) + + state_test( + pre=pre, + tx=tx, + post={ + to: Account(balance=0xDEADBEEE), + }, + ) From fd6e6b8d4d5d226a7a9f7a1579b4ae707b0c107c Mon Sep 17 00:00:00 2001 From: Sam Wilson Date: Wed, 17 Jun 2026 10:25:09 -0400 Subject: [PATCH 050/233] fix(tests): add exception map for invalid chain ids Co-authored-by: Mario Vega Zavala --- .../client_clis/clis/besu.py | 3 ++ .../client_clis/clis/erigon.py | 1 + .../client_clis/clis/ethrex.py | 3 ++ .../client_clis/clis/geth.py | 1 + .../client_clis/clis/nethermind.py | 3 ++ .../client_clis/clis/reth.py | 1 + .../eip1559_fee_market_change/test_tx_type.py | 37 ++----------------- 7 files changed, 16 insertions(+), 33 deletions(-) diff --git a/packages/testing/src/execution_testing/client_clis/clis/besu.py b/packages/testing/src/execution_testing/client_clis/clis/besu.py index ce0789cd399..6325b830ef9 100644 --- a/packages/testing/src/execution_testing/client_clis/clis/besu.py +++ b/packages/testing/src/execution_testing/client_clis/clis/besu.py @@ -326,6 +326,9 @@ class BesuExceptionMapper(ExceptionMapper): "transaction invalid max priority fee per gas cannot be greater " "than max fee per gas" ), + TransactionException.INVALID_CHAINID: ( + "transaction invalid transaction was meant for chain id" + ), TransactionException.TYPE_3_TX_INVALID_BLOB_VERSIONED_HASH: ( "Invalid versionedHash" ), diff --git a/packages/testing/src/execution_testing/client_clis/clis/erigon.py b/packages/testing/src/execution_testing/client_clis/clis/erigon.py index e0a77b5f71a..3ef96449490 100644 --- a/packages/testing/src/execution_testing/client_clis/clis/erigon.py +++ b/packages/testing/src/execution_testing/client_clis/clis/erigon.py @@ -53,6 +53,7 @@ class ErigonExceptionMapper(ExceptionMapper): TransactionException.NONCE_MISMATCH_TOO_LOW: "nonce too low", TransactionException.NONCE_MISMATCH_TOO_HIGH: "nonce too high", TransactionException.GAS_ALLOWANCE_EXCEEDED: "gas limit reached", + TransactionException.INVALID_CHAINID: "invalid chain id for signer", TransactionException.TYPE_3_TX_PRE_FORK: ( "blob txn is not supported by signer" ), diff --git a/packages/testing/src/execution_testing/client_clis/clis/ethrex.py b/packages/testing/src/execution_testing/client_clis/clis/ethrex.py index d85a1e7ce86..cde6576a6d7 100644 --- a/packages/testing/src/execution_testing/client_clis/clis/ethrex.py +++ b/packages/testing/src/execution_testing/client_clis/clis/ethrex.py @@ -17,6 +17,9 @@ class EthrexExceptionMapper(ExceptionMapper): TransactionException.TYPE_3_TX_MAX_BLOB_GAS_ALLOWANCE_EXCEEDED: ( "Exceeded MAX_BLOB_GAS_PER_BLOCK" ), + TransactionException.INVALID_CHAINID: ( + "Transaction has invalid chain id" + ), BlockException.INVALID_DEPOSIT_EVENT_LAYOUT: ( "Invalid deposit request layout" ), diff --git a/packages/testing/src/execution_testing/client_clis/clis/geth.py b/packages/testing/src/execution_testing/client_clis/clis/geth.py index 25f0e0244e9..1a9c46c720d 100644 --- a/packages/testing/src/execution_testing/client_clis/clis/geth.py +++ b/packages/testing/src/execution_testing/client_clis/clis/geth.py @@ -54,6 +54,7 @@ class GethExceptionMapper(ExceptionMapper): TransactionException.PRIORITY_GREATER_THAN_MAX_FEE_PER_GAS: ( "max priority fee per gas higher than max fee per gas" ), + TransactionException.INVALID_CHAINID: "invalid chain id for signer", TransactionException.TYPE_1_TX_PRE_FORK: ( "transaction type not supported" ), diff --git a/packages/testing/src/execution_testing/client_clis/clis/nethermind.py b/packages/testing/src/execution_testing/client_clis/clis/nethermind.py index 929fc139cbb..bb3687ef970 100644 --- a/packages/testing/src/execution_testing/client_clis/clis/nethermind.py +++ b/packages/testing/src/execution_testing/client_clis/clis/nethermind.py @@ -413,6 +413,9 @@ class NethermindExceptionMapper(ExceptionMapper): ), TransactionException.NONCE_MISMATCH_TOO_LOW: (r"nonce too low"), TransactionException.NONCE_MISMATCH_TOO_HIGH: (r"nonce too high"), + TransactionException.INVALID_CHAINID: ( + r"InvalidTxChainId|Signature is invalid." + ), TransactionException.TYPE_3_TX_WITH_FULL_BLOBS: ( r"Transaction \d+ is not valid" ), diff --git a/packages/testing/src/execution_testing/client_clis/clis/reth.py b/packages/testing/src/execution_testing/client_clis/clis/reth.py index c2193fcb996..d3a77fdedce 100644 --- a/packages/testing/src/execution_testing/client_clis/clis/reth.py +++ b/packages/testing/src/execution_testing/client_clis/clis/reth.py @@ -27,6 +27,7 @@ class RethExceptionMapper(ExceptionMapper): TransactionException.GASLIMIT_PRICE_PRODUCT_OVERFLOW: "overflow", TransactionException.TYPE_3_TX_CONTRACT_CREATION: "unexpected length", TransactionException.TYPE_3_TX_WITH_FULL_BLOBS: "unexpected list", + TransactionException.INVALID_CHAINID: "invalid chain ID", TransactionException.TYPE_3_TX_INVALID_BLOB_VERSIONED_HASH: ( "blob version not supported" ), diff --git a/tests/london/eip1559_fee_market_change/test_tx_type.py b/tests/london/eip1559_fee_market_change/test_tx_type.py index 6a831d0203a..a9dae2eb0bc 100644 --- a/tests/london/eip1559_fee_market_change/test_tx_type.py +++ b/tests/london/eip1559_fee_market_change/test_tx_type.py @@ -1,6 +1,6 @@ """Test the tx type validation for EIP-1559.""" -from typing import Final, Generator, Sequence +from typing import Generator import pytest from execution_testing import ( @@ -12,10 +12,9 @@ StateTestFiller, Transaction, TransactionException, - TransactionType, + add_kzg_version, ) from execution_testing import Opcodes as Op -from execution_testing.base_types import Hash from .spec import ref_spec_1559 @@ -75,49 +74,21 @@ def test_eip1559_tx_validity( state_test(pre=pre, post=post, tx=tx) -TX_TYPES: Final[Sequence[object]] = [ - pytest.param(TransactionType.LEGACY, None), - pytest.param( - TransactionType.ACCESS_LIST, - None, - marks=[pytest.mark.valid_from("Berlin")], - ), - pytest.param( - TransactionType.BASE_FEE, - None, - marks=[pytest.mark.valid_from("London")], - ), - pytest.param( - TransactionType.BLOB_TRANSACTION, - [0], - marks=[pytest.mark.valid_from("Cancun")], - ), - pytest.param( - TransactionType.SET_CODE, - None, - marks=[pytest.mark.valid_from("Prague")], - ), -] - -if len(TX_TYPES) != len(TransactionType): - raise Exception("missing tx type") - - @pytest.mark.valid_from("SpuriousDragon") @pytest.mark.exception_test -@pytest.mark.parametrize(("tx_type", "blob_versioned_hashes"), TX_TYPES) +@pytest.mark.with_all_tx_types def test_invalid_chain_id( state_test: StateTestFiller, pre: Alloc, chain_config: ChainConfig, tx_type: int, - blob_versioned_hashes: None | Sequence[Hash], ) -> None: """ Test that a transaction with a different chain id is not valid. """ to = pre.fund_eoa(0xDEADBEEE) + blob_versioned_hashes = add_kzg_version([0], 1) if tx_type == 3 else None tx = Transaction( sender=pre.fund_eoa(), value=1, From 8dca7a70340c09d897cd05497254ee99bd1f110b Mon Sep 17 00:00:00 2001 From: Leo Lara Date: Thu, 25 Jun 2026 03:59:25 +0700 Subject: [PATCH 051/233] chore(tests): reduce Amsterdam ported_static skip list (480 to 258) (#2996) * chore(tests): unskip ported_static cases now passing on Amsterdam PR #2969 made the transaction gas limit automatic. On EIP-8037 forks the implicit limit now carries a state-gas reservoir, so tests that leave the transaction gas limit unset receive enough headroom and stop running out of gas under the two-dimensional gas model. Filling tests/ported_static/ for Amsterdam without the skip list shows 33 of these entries pass across all fixture variants. Remove them from amsterdam_skip_list.txt and recompute the per-section counts and the total, from 480 down to 447. Two sections become empty and are dropped, stCallDelegateCodesHomestead and stRecursiveCreate. Most of the cleared cases are the _suicide_end family. They set only the environment gas limit and rely on the implicit transaction gas limit, so the reservoir restores their pre-Amsterdam behaviour. * chore(tests): drop obsolete ported_static skip entries on Amsterdam These 10 entries match no test collected on Amsterdam, so the conftest never skips anything for them. They are dead weight in the list. Eight are size-limit tests under stCodeSizeLimit and stEIP3860_limitmeterinitcode. They carry valid_before("EIP7954"), and Amsterdam includes EIP-7954, so pytest deselects them and they never reach the skip step. Both sections empty out and are dropped. Two are stCreateTest/test_create_address_warm_after_fail cases pinned to a create-code-too-big-v1 parametrization that no longer exists after the test was reparametrized. Recompute the section counts and the total, from 447 down to 437. * chore(tests): unset incidental tx gas_limit on 69 ported_static files PR #2969 computes the transaction gas limit automatically and adds a state-gas reservoir on EIP-8037 forks. These 69 files set an explicit transaction gas_limit that was incidental: it sufficed on older forks, but on Amsterdam the state-gas component pushed the transaction over its budget and broke the test. Leaving the gas limit unset gives each transaction the implicit limit plus reservoir, which restores the recorded full-execution behaviour. Drop the transaction gas_limit from these files, together with the helpers it orphaned (tx_gas lists, an intrinsic-gas computation) and three now-unused fork-conditional bumps that the unset limit supersedes. Filling them across every valid fork passes all variants with no residual failures, so remove their 116 entries from amsterdam_skip_list.txt. Total 437 down to 321. * chore(tests): unset g1 gas_limit on Amsterdam for no_src_account_create test_no_src_account_create and its 1559 variant assert an INSUFFICIENT_ACCOUNT_FUNDS rejection at gas index g1 (210000). On Amsterdam EIP-8037 raises the creation intrinsic gas above that budget, so the transaction is rejected for gas before the funds check and the assertion fails. Make the g1 budget fork-conditional: leave the gas limit unset on EIP-8037 forks, where the implicit limit plus reservoir clears the intrinsic and the intended funds rejection fires again. Earlier forks keep the original 210000. Filling across every valid fork passes all variants, so remove the 16 g1 entries from amsterdam_skip_list.txt. Total 321 down to 305. * chore(tests): fork-conditional gas unset for 8 more ported_static files Extend the per-parametrization fix from no_src_account_create to eight more mixed files. Each sets the skip-listed gas slot to None on EIP-8037, so that parametrization gets the implicit limit plus reservoir, while the other slots and earlier forks keep their explicit gas. These slots recorded a full-execution result that broke on Amsterdam when the state-gas component pushed the transaction over its budget. The stZeroKnowledge point_mul tests run out of gas because they SSTORE the precompile result, which EIP-8037 makes more expensive; the precompile call itself is unchanged, so unsetting restores the recorded success state. Filling across every valid fork passes all variants with no failures, so remove the 47 entries from amsterdam_skip_list.txt. Total 305 down to 258. * chore(tests): Port and remove failing stZeroKnowledge tests --------- Co-authored-by: marioevz --- tests/byzantium/eip196_ec_add_mul/spec.py | 6 + .../byzantium/eip196_ec_add_mul/test_ecadd.py | 179 +- .../byzantium/eip196_ec_add_mul/test_ecmul.py | 194 ++ tests/ported_static/amsterdam_skip_list.txt | 276 +-- .../test_callcode_lose_gas_oog.py | 2 +- ...st_callcallcodecallcode_011_oogm_before.py | 1 - .../stCreate2/test_create2call_precompiles.py | 2 - .../test_create2check_fields_in_initcode.py | 2 - .../test_create_collision_results.py | 2 - .../test_create_collision_to_empty2.py | 5 +- ...test_create_oo_gafter_init_code_revert2.py | 2 - .../test_delegatecode_dynamic_code.py | 1 - .../test_coinbase_warm_account_call_gas.py | 2 - .../stEIP3855_push0/test_push0.py | 2 - .../stMemoryStressTest/test_return_bounds.py | 2 +- .../stMemoryTest/test_calldatacopy_dejavu2.py | 1 - .../test_call_sha256_1_nonzero_value.py | 1 - .../test_ecrecover_short_buff.py | 1 - ...eturndatasize_after_successful_callcode.py | 1 - .../test_subcall_return_more_then_expected.py | 1 - .../test_loop_calls_then_revert.py | 1 - .../test_revert_opcode_multiple_sub_calls.py | 7 +- ..._change_from_external_call_in_init_code.py | 10 - .../stSelfBalance/test_self_balance.py | 1 - .../test_self_balance_equals_balance.py | 1 - .../test_self_balance_gas_cost.py | 1 - .../stSolidityTest/test_test_overflow.py | 1 - .../test_test_structures_and_variabless.py | 1 - ...more_gas_on_depth2_then_transaction_has.py | 2 - ...ic_call_contract_to_create_contract_oog.py | 1 - .../test_static_call_recursive_bomb3.py | 1 - ...test_static_call_sha256_1_nonzero_value.py | 1 - .../test_static_callcall_00_ooge_1.py | 2 - .../test_static_callcallcode_01_ooge_2.py | 2 - ...st_static_callcallcodecallcode_011_ooge.py | 2 - ..._static_callcallcodecallcode_011_ooge_2.py | 2 - ...tic_callcallcodecallcode_011_oogm_after.py | 2 - ...ic_callcallcodecallcode_011_oogm_after2.py | 2 - ...c_callcallcodecallcode_011_oogm_after_1.py | 2 - ...c_callcallcodecallcode_011_oogm_after_2.py | 2 - ...ic_callcallcodecallcode_011_oogm_before.py | 2 - ...c_callcallcodecallcode_011_oogm_before2.py | 2 - .../test_static_callcodecall_10_ooge.py | 2 - .../test_static_callcodecall_10_ooge_2.py | 2 - .../test_static_callcodecallcall_100_ooge.py | 2 - .../test_static_callcodecallcall_100_ooge2.py | 2 - ...tatic_callcodecallcall_100_oogm_after_3.py | 2 - ...static_callcodecallcall_100_oogm_before.py | 2 - ...tatic_callcodecallcall_100_oogm_before2.py | 2 - ..._static_callcodecallcallcode_101_ooge_2.py | 1 - ...tic_callcodecallcallcode_101_oogm_after.py | 1 - ...ic_callcodecallcallcode_101_oogm_after2.py | 2 - ...ic_callcodecallcallcode_101_oogm_before.py | 1 - ...c_callcodecallcallcode_101_oogm_before2.py | 2 - ...st_static_callcodecallcodecall_110_ooge.py | 1 - ...t_static_callcodecallcodecall_110_ooge2.py | 2 - ...tic_callcodecallcodecall_110_oogm_after.py | 1 - ...ic_callcodecallcodecall_110_oogm_after2.py | 2 - ...c_callcodecallcodecall_110_oogm_after_2.py | 1 - ...c_callcodecallcodecall_110_oogm_after_3.py | 1 - ...ic_callcodecallcodecall_110_oogm_before.py | 1 - ...c_callcodecallcodecall_110_oogm_before2.py | 2 - .../test_static_calldelcode_01_ooge.py | 2 - .../test_static_check_opcodes4.py | 2 +- .../test_static_check_opcodes5.py | 2 +- .../test_static_revert_opcode_calls.py | 2 - ...code_to_precompile_from_called_contract.py | 2 - ...precompile_from_contract_initialization.py | 2 - ...callcode_to_precompile_from_transaction.py | 2 - ...call_to_precompile_from_called_contract.py | 1 - ...precompile_from_contract_initialization.py | 1 - ...gatecall_to_precompile_from_transaction.py | 1 - .../test_ab_acalls_suicide0.py | 1 - .../stSystemOperationsTest/test_call10.py | 1 - ..._name_registrator_address_too_big_right.py | 1 - .../test_no_src_account_create.py | 5 +- .../test_no_src_account_create1559.py | 5 +- .../test_day_limit_construction_partial.py | 1 - .../test_wallet_construction_partial.py | 1 - .../stZeroKnowledge/test_point_mul_add.py | 640 ------ .../stZeroKnowledge/test_point_mul_add2.py | 1985 ----------------- .../test_exp_power256_of256.py | 1 - .../vmArithmeticTest/test_two_ops.py | 1 - 83 files changed, 398 insertions(+), 3024 deletions(-) delete mode 100644 tests/ported_static/stZeroKnowledge/test_point_mul_add.py delete mode 100644 tests/ported_static/stZeroKnowledge/test_point_mul_add2.py diff --git a/tests/byzantium/eip196_ec_add_mul/spec.py b/tests/byzantium/eip196_ec_add_mul/spec.py index fc6b3d39720..ee241b2d0ad 100644 --- a/tests/byzantium/eip196_ec_add_mul/spec.py +++ b/tests/byzantium/eip196_ec_add_mul/spec.py @@ -156,6 +156,12 @@ class Spec: 0x269D2516BF8C4F5798CC1267162E59ADD561E5537A328FE0F28A252FA287A72A, ) + # Sample point used by the legacy pointMulAdd2 tests + SAMPLE_G1 = PointG1( + 0x0CCBEC17235F5B9CC5E42F3DF6364A76ECDD0101DDDA8FC5DC0BA0B59C0E5628, + 0x069EF5E376C0A1EA82F9DFC2E0001A7F385D655EEF9A6F976C7A5D2C493EA3AD, + ) + # Invalid point: S1 with a different x coordinate (not on curve) S1_INVALID = PointG1( 0x0F25919BCB43D5A57391564615C9E70A992B10EAFA4DB109709649CF48C50DD2, diff --git a/tests/byzantium/eip196_ec_add_mul/test_ecadd.py b/tests/byzantium/eip196_ec_add_mul/test_ecadd.py index 6655695f21f..e2834736294 100644 --- a/tests/byzantium/eip196_ec_add_mul/test_ecadd.py +++ b/tests/byzantium/eip196_ec_add_mul/test_ecadd.py @@ -1,12 +1,7 @@ """Tests the ecadd precompiled contract.""" import pytest -from execution_testing import ( - Alloc, - Environment, - StateTestFiller, - Transaction, -) +from execution_testing import Alloc, StateTestFiller, Transaction from .spec import PointG1, Spec, ref_spec_196 @@ -137,6 +132,162 @@ Spec.S1, id="single_s1", ), + # Ported from pointMulAdd / pointMulAdd2 (ECADD vs ECMUL) + pytest.param( + Spec.S1x2 + Spec.S1, + Spec.S1x3, + id="s1x2_plus_s1", + ), + pytest.param( + PointG1(Spec.S1x3.x, Spec.P - Spec.S1x3.y) + Spec.S1x3, + Spec.INF_G1, + id="neg_s1x3_plus_s1x3", + ), + pytest.param( + PointG1(Spec.S1x3.x, Spec.P - Spec.S1x3.y) + + PointG1(Spec.S1x3.x, Spec.P - Spec.S1x3.y), + PointG1( + 0x255E468453D7636CC1563E43F7521755F95E6C56043C7321B4AE04E772945FB0, + 0x225C5F1623620FD84BFBAB2D861A9D1E570F7727C540F403085998EBAF407C4, + ), + id="neg_s1x3_doubled", + ), + pytest.param( + PointG1(Spec.S1x3.x, Spec.P - Spec.S1x3.y) + Spec.INF_G1, + PointG1(Spec.S1x3.x, Spec.P - Spec.S1x3.y), + id="neg_s1x3_plus_inf", + ), + pytest.param( + Spec.S1x2 + Spec.INF_G1, + Spec.S1x2, + id="s1x2_plus_inf", + ), + pytest.param( + Spec.G1 + PointG1(Spec.G1.x, Spec.P - Spec.G1.y), + Spec.INF_G1, + id="generator_plus_neg_generator", + ), + pytest.param( + PointG1(Spec.G1.x, Spec.P - Spec.G1.y) + + PointG1(Spec.G1.x, Spec.P - Spec.G1.y), + PointG1(Spec.G1x2.x, Spec.P - Spec.G1x2.y), + id="neg_generator_doubled", + ), + pytest.param( + PointG1(Spec.G1x2.x, Spec.P - Spec.G1x2.y) + + PointG1(Spec.G1.x, Spec.P - Spec.G1.y), + PointG1( + 0x769BF9AC56BEA3FF40232BCB1B6BD159315D84715B8E679F2D355961915ABF0, + 0x5ACB4B400E90C0063006A39F478F3E865E306DD5CD56F356E2E8CD8FE7EDAE6, + ), + id="neg_g1x2_plus_neg_generator", + ), + pytest.param( + PointG1(Spec.G1.x, Spec.P - Spec.G1.y) + Spec.INF_G1, + PointG1(Spec.G1.x, Spec.P - Spec.G1.y), + id="neg_generator_plus_inf", + ), + pytest.param( + Spec.SAMPLE_G1 + PointG1(Spec.G1.x, Spec.P - Spec.G1.y), + PointG1( + 0x113AECCECDAF57CD8C0AACE591774949DCDAF892555FA86726FA7E679B89C067, + 0xBFFBA84127A19ABDE488A8251A9A3FCE33B34A76F96AAFB11AB4A6CEF3E9979, + ), + id="sample_plus_neg_generator", + ), + pytest.param( + Spec.SAMPLE_G1 + Spec.SAMPLE_G1, + PointG1( + 0x1FD3B816D9951DCB9AA9797D25E51A865987703AE83CD69C4658679F0350AE2B, + 0x29CE3D80A74DDC13784BEB25CA9FBFD048A3265A32C6F38B92060C5093A0E7A7, + ), + id="sample_doubled", + ), + pytest.param( + Spec.SAMPLE_G1 + Spec.INF_G1, + Spec.SAMPLE_G1, + id="sample_plus_inf", + ), + pytest.param( + PointG1(Spec.G1x2_256_1.x, Spec.P - Spec.G1x2_256_1.y) + + PointG1(Spec.G1.x, Spec.P - Spec.G1.y), + PointG1( + 0x1D78954C630B3895FBBFAFAC1294F2C0158879FDC70BFE18222890E7BFB66FBA, + 0x101C3346E98B136A7078AEBD427DCED763722D77E3D7985342E0BFFCC6EA4D56, + ), + id="neg_g1x2_256_1_plus_neg_generator", + ), + pytest.param( + PointG1(Spec.G1x2_256_1.x, Spec.P - Spec.G1x2_256_1.y) + + PointG1(Spec.G1x2_256_1.x, Spec.P - Spec.G1x2_256_1.y), + PointG1( + 0x2FA739D4CDE056D8FD75427345CBB34159856E06A4FFAD64159C4773F23FBF4B, + 0x1EED5D5325C31FC89DD541A13D7F63B981FAE8D4BF78A6B08A38A601FCFEA97B, + ), + id="neg_g1x2_256_1_doubled", + ), + pytest.param( + PointG1(Spec.G1x2_256_1.x, Spec.P - Spec.G1x2_256_1.y) + + Spec.INF_G1, + PointG1(Spec.G1x2_256_1.x, Spec.P - Spec.G1x2_256_1.y), + id="neg_g1x2_256_1_plus_inf", + ), + pytest.param( + Spec.G1x2 + Spec.G1, + PointG1( + 0x769BF9AC56BEA3FF40232BCB1B6BD159315D84715B8E679F2D355961915ABF0, + 0x2AB799BEE0489429554FDB7C8D086475319E63B40B9C5B57CDF1FF3DD9FE2261, + ), + id="g1x2_plus_generator", + ), + pytest.param( + PointG1(Spec.G1.x, Spec.P - Spec.G1.y) + Spec.G1, + Spec.INF_G1, + id="neg_generator_plus_generator", + ), + pytest.param( + PointG1(Spec.SAMPLE_G1.x, Spec.P - Spec.SAMPLE_G1.y) + Spec.G1, + PointG1( + 0x113AECCECDAF57CD8C0AACE591774949DCDAF892555FA86726FA7E679B89C067, + 0x246493EECEB7867DDA07BB342FD7B460B44635E9F8DB1F922A7541A9E93E63CE, + ), + id="neg_sample_plus_generator", + ), + pytest.param( + PointG1(Spec.SAMPLE_G1.x, Spec.P - Spec.SAMPLE_G1.y) + + PointG1(Spec.SAMPLE_G1.x, Spec.P - Spec.SAMPLE_G1.y), + PointG1( + 0x1FD3B816D9951DCB9AA9797D25E51A865987703AE83CD69C4658679F0350AE2B, + 0x69610F239E3C41640045A90B6E1988D4EDE443735AAD701AA1A7FC644DC15A0, + ), + id="neg_sample_doubled", + ), + pytest.param( + PointG1(Spec.SAMPLE_G1.x, Spec.P - Spec.SAMPLE_G1.y) + Spec.INF_G1, + PointG1(Spec.SAMPLE_G1.x, Spec.P - Spec.SAMPLE_G1.y), + id="neg_sample_plus_inf", + ), + pytest.param( + Spec.G1x2_256_1 + Spec.G1, + PointG1( + 0x1D78954C630B3895FBBFAFAC1294F2C0158879FDC70BFE18222890E7BFB66FBA, + 0x20481B2BF7A68CBF47D796F93F038986340F3D19849A3239F93FCC1A1192AFF1, + ), + id="g1x2_256_1_plus_generator", + ), + pytest.param( + Spec.G1x2_256_1 + Spec.G1x2_256_1, + PointG1( + 0x2FA739D4CDE056D8FD75427345CBB34159856E06A4FFAD64159C4773F23FBF4B, + 0x1176F11FBB6E80611A7B04154401F4A4158681BCA8F923DCB1E7E614DB7E53CC, + ), + id="g1x2_256_1_doubled", + ), + pytest.param( + Spec.G1x2_256_1 + Spec.INF_G1, + Spec.G1x2_256_1, + id="g1x2_256_1_plus_inf", + ), ], ) @pytest.mark.ported_from( @@ -171,6 +322,8 @@ "https://github.com/ethereum/tests/blob/v13.3/src/GeneralStateTestsFiller/stZeroKnowledge2/ecadd_1145-3932_1145-4651_25000_192Filler.json", "https://github.com/ethereum/tests/blob/v13.3/src/GeneralStateTestsFiller/stZeroKnowledge2/ecadd_1145-3932_2969-1336_21000_128Filler.json", "https://github.com/ethereum/tests/blob/v13.3/src/GeneralStateTestsFiller/stZeroKnowledge2/ecadd_1145-3932_2969-1336_25000_128Filler.json", + "https://github.com/ethereum/tests/blob/v13.3/src/GeneralStateTestsFiller/stZeroKnowledge/pointMulAddFiller.json", + "https://github.com/ethereum/tests/blob/v13.3/src/GeneralStateTestsFiller/stZeroKnowledge/pointMulAdd2Filler.json", ], pr=[ "https://github.com/ethereum/execution-specs/pull/1935", @@ -184,12 +337,7 @@ def test_valid( tx: Transaction, ) -> None: """Test the valid inputs to the ECADD precompile.""" - state_test( - env=Environment(), - pre=pre, - tx=tx, - post=post, - ) + state_test(pre=pre, tx=tx, post=post) @pytest.mark.parametrize( @@ -313,9 +461,4 @@ def test_invalid( tx: Transaction, ) -> None: """Test the invalid inputs to the ECADD precompile.""" - state_test( - env=Environment(), - pre=pre, - tx=tx, - post=post, - ) + state_test(pre=pre, tx=tx, post=post) diff --git a/tests/byzantium/eip196_ec_add_mul/test_ecmul.py b/tests/byzantium/eip196_ec_add_mul/test_ecmul.py index b8fbb1b938e..be68f384508 100644 --- a/tests/byzantium/eip196_ec_add_mul/test_ecmul.py +++ b/tests/byzantium/eip196_ec_add_mul/test_ecmul.py @@ -239,6 +239,198 @@ Spec.INF_G1, id="generator_no_scalar", ), + # Ported from pointMulAdd / pointMulAdd2 (ECADD vs ECMUL) + pytest.param( + Spec.S1 + Scalar(2), + Spec.S1x2, + id="s1_times_two", + ), + pytest.param( + Spec.S1 + Scalar(3), + Spec.S1x3, + id="s1_times_three", + ), + pytest.param( + Spec.S1x3 + Scalar(0), + Spec.INF_G1, + id="s1x3_times_zero", + ), + pytest.param( + PointG1(Spec.S1x3.x, Spec.P - Spec.S1x3.y) + Scalar(2), + PointG1( + 0x255E468453D7636CC1563E43F7521755F95E6C56043C7321B4AE04E772945FB0, + 0x225C5F1623620FD84BFBAB2D861A9D1E570F7727C540F403085998EBAF407C4, + ), + id="neg_s1x3_times_two", + ), + pytest.param( + Spec.S1x3 + Scalar(Spec.N - 1), + PointG1(Spec.S1x3.x, Spec.P - Spec.S1x3.y), + id="s1x3_times_group_order_minus_one", + ), + pytest.param( + Spec.S1x3 + Scalar(Spec.N - 2), + PointG1( + 0x255E468453D7636CC1563E43F7521755F95E6C56043C7321B4AE04E772945FB0, + 0x225C5F1623620FD84BFBAB2D861A9D1E570F7727C540F403085998EBAF407C4, + ), + id="s1x3_times_group_order_minus_two", + ), + pytest.param( + Spec.S1x2 + Scalar(1), + Spec.S1x2, + id="s1x2_times_one", + ), + pytest.param( + Spec.INF_G1 + Scalar(3), + Spec.INF_G1, + id="inf_times_three", + ), + pytest.param( + Spec.INF_G1 + Scalar(Spec.N - 2), + Spec.INF_G1, + id="inf_times_group_order_minus_two", + ), + pytest.param( + Spec.INF_G1 + Scalar(Spec.P - 1), + Spec.INF_G1, + id="inf_times_field_modulus_minus_one", + ), + pytest.param( + Spec.INF_G1 + Scalar(Spec.P - 2), + Spec.INF_G1, + id="inf_times_field_modulus_minus_two", + ), + pytest.param( + Spec.INF_G1 + Scalar(2**256 - 2), + Spec.INF_G1, + id="inf_times_2_pow_256_minus_2", + ), + pytest.param( + PointG1(Spec.G1.x, Spec.P - Spec.G1.y) + Scalar(2), + PointG1(Spec.G1x2.x, Spec.P - Spec.G1x2.y), + id="neg_generator_times_two", + ), + pytest.param( + PointG1(Spec.G1.x, Spec.P - Spec.G1.y) + Scalar(3), + PointG1( + 0x769BF9AC56BEA3FF40232BCB1B6BD159315D84715B8E679F2D355961915ABF0, + 0x5ACB4B400E90C0063006A39F478F3E865E306DD5CD56F356E2E8CD8FE7EDAE6, + ), + id="neg_generator_times_three", + ), + pytest.param( + PointG1(Spec.G1.x, Spec.P - Spec.G1.y) + Scalar(0), + Spec.INF_G1, + id="neg_generator_times_zero", + ), + pytest.param( + PointG1(Spec.G1.x, Spec.P - Spec.G1.y) + Scalar(Spec.N - 1), + Spec.G1, + id="neg_generator_times_group_order_minus_one", + ), + pytest.param( + PointG1(Spec.G1.x, Spec.P - Spec.G1.y) + Scalar(Spec.N - 2), + Spec.G1x2, + id="neg_generator_times_group_order_minus_two", + ), + pytest.param( + PointG1(Spec.G1.x, Spec.P - Spec.G1.y) + Scalar(1), + PointG1(Spec.G1.x, Spec.P - Spec.G1.y), + id="neg_generator_times_one", + ), + pytest.param( + Spec.SAMPLE_G1 + Scalar(2), + PointG1( + 0x1FD3B816D9951DCB9AA9797D25E51A865987703AE83CD69C4658679F0350AE2B, + 0x29CE3D80A74DDC13784BEB25CA9FBFD048A3265A32C6F38B92060C5093A0E7A7, + ), + id="sample_times_two", + ), + pytest.param( + PointG1(Spec.G1.x, Spec.P - Spec.G1.y) + Scalar(Spec.P - 1), + Spec.SAMPLE_G1, + id="neg_generator_times_field_modulus_minus_one", + ), + pytest.param( + PointG1(Spec.G1.x, Spec.P - Spec.G1.y) + Scalar(Spec.P - 2), + PointG1( + 0x2C15ED1902E189486AB6B625AA982510AEF6246B21A1E1BCEA382DA4D735E8BA, + 0x2103E58CBD2FA8081763442AB46C26A9B8051E9B049C3948C8D7D0E139C5E3F, + ), + id="neg_generator_times_field_modulus_minus_two", + ), + pytest.param( + PointG1(Spec.G1x2_256_1.x, Spec.P - Spec.G1x2_256_1.y) + Scalar(2), + PointG1( + 0x2FA739D4CDE056D8FD75427345CBB34159856E06A4FFAD64159C4773F23FBF4B, + 0x1EED5D5325C31FC89DD541A13D7F63B981FAE8D4BF78A6B08A38A601FCFEA97B, + ), + id="neg_g1x2_256_1_times_two", + ), + pytest.param( + PointG1(Spec.G1.x, Spec.P - Spec.G1.y) + Scalar(2**256 - 1), + PointG1(Spec.G1x2_256_1.x, Spec.P - Spec.G1x2_256_1.y), + id="neg_generator_times_2_pow_256_minus_1", + ), + pytest.param( + PointG1(Spec.G1.x, Spec.P - Spec.G1.y) + Scalar(2**256 - 2), + PointG1( + 0x8E2142845DB159BD105879A109FE7A6F254ED3DDAE0E9CD8A2AEAE05E5F647B, + 0x221108EE615499D2E0A1113CA1A858A34E055F9DA2D30E6E6AB392B049944A92, + ), + id="neg_generator_times_2_pow_256_minus_2", + ), + pytest.param( + Spec.G1 + Scalar(3), + PointG1( + 0x769BF9AC56BEA3FF40232BCB1B6BD159315D84715B8E679F2D355961915ABF0, + 0x2AB799BEE0489429554FDB7C8D086475319E63B40B9C5B57CDF1FF3DD9FE2261, + ), + id="generator_times_three", + ), + pytest.param( + Spec.G1 + Scalar(Spec.N - 2), + PointG1(Spec.G1x2.x, Spec.P - Spec.G1x2.y), + id="generator_times_group_order_minus_two", + ), + pytest.param( + PointG1(Spec.SAMPLE_G1.x, Spec.P - Spec.SAMPLE_G1.y) + Scalar(2), + PointG1( + 0x1FD3B816D9951DCB9AA9797D25E51A865987703AE83CD69C4658679F0350AE2B, + 0x69610F239E3C41640045A90B6E1988D4EDE443735AAD701AA1A7FC644DC15A0, + ), + id="neg_sample_times_two", + ), + pytest.param( + Spec.G1 + Scalar(Spec.P - 1), + PointG1(Spec.SAMPLE_G1.x, Spec.P - Spec.SAMPLE_G1.y), + id="generator_times_field_modulus_minus_one", + ), + pytest.param( + Spec.G1 + Scalar(Spec.P - 2), + PointG1( + 0x2C15ED1902E189486AB6B625AA982510AEF6246B21A1E1BCEA382DA4D735E8BA, + 0x2E54101A155EA5A936DA1173D63A95F2FC0118A7B82806F8AF930F08C4E09F08, + ), + id="generator_times_field_modulus_minus_two", + ), + pytest.param( + Spec.G1x2_256_1 + Scalar(2), + PointG1( + 0x2FA739D4CDE056D8FD75427345CBB34159856E06A4FFAD64159C4773F23FBF4B, + 0x1176F11FBB6E80611A7B04154401F4A4158681BCA8F923DCB1E7E614DB7E53CC, + ), + id="g1x2_256_1_times_two", + ), + pytest.param( + Spec.G1 + Scalar(2**256 - 2), + PointG1( + 0x8E2142845DB159BD105879A109FE7A6F254ED3DDAE0E9CD8A2AEAE05E5F647B, + 0xE5345847FDD0656D7AF3479DFD8FFBA497C0AF3C59EBC1ED16CF9668EE8B2B5, + ), + id="generator_times_2_pow_256_minus_2", + ), ], ) @pytest.mark.ported_from( @@ -280,6 +472,8 @@ "https://github.com/ethereum/legacytests/tree/master/Cancun/GeneralStateTests/stZeroKnowledge2/ecmul_1-2_0_21000_96Filler.json", "https://github.com/ethereum/legacytests/tree/master/Cancun/GeneralStateTests/stZeroKnowledge2/ecmul_1-2_1_21000_128Filler.json", "https://github.com/ethereum/legacytests/tree/master/Cancun/GeneralStateTests/stZeroKnowledge2/ecmul_1-2_1_21000_96Filler.json", + "https://github.com/ethereum/tests/blob/v13.3/src/GeneralStateTestsFiller/stZeroKnowledge/pointMulAddFiller.json", + "https://github.com/ethereum/tests/blob/v13.3/src/GeneralStateTestsFiller/stZeroKnowledge/pointMulAdd2Filler.json", ], pr=["https://github.com/ethereum/execution-specs/pull/2403"], ) diff --git a/tests/ported_static/amsterdam_skip_list.txt b/tests/ported_static/amsterdam_skip_list.txt index 0a07bbec654..4423a666f35 100644 --- a/tests/ported_static/amsterdam_skip_list.txt +++ b/tests/ported_static/amsterdam_skip_list.txt @@ -8,7 +8,7 @@ # Entries are substring-matched against each pytest nodeid (after # stripping the fixture-format suffix in conftest.py). # -# Total entries: 480 +# Total entries: 258 # stAttackTest (1) stAttackTest/test_crashing_transaction.py::test_crashing_transaction[fork_Amsterdam] @@ -19,72 +19,33 @@ stBadOpcode/test_measure_gas.py::test_measure_gas[fork_Amsterdam-CREATE] stBadOpcode/test_operation_diff_gas.py::test_operation_diff_gas[fork_Amsterdam-CREATE2] stBadOpcode/test_operation_diff_gas.py::test_operation_diff_gas[fork_Amsterdam-CREATE] -# stCallCodes (9) -stCallCodes/test_callcall_00_suicide_end.py::test_callcall_00_suicide_end[fork_Amsterdam] -stCallCodes/test_callcallcall_000_suicide_end.py::test_callcallcall_000_suicide_end[fork_Amsterdam] -stCallCodes/test_callcallcodecall_010_suicide_end.py::test_callcallcodecall_010_suicide_end[fork_Amsterdam] +# stCallCodes (3) stCallCodes/test_callcode_in_initcode_to_existing_contract.py::test_callcode_in_initcode_to_existing_contract[fork_Amsterdam-d0] stCallCodes/test_callcode_in_initcode_to_existing_contract.py::test_callcode_in_initcode_to_existing_contract[fork_Amsterdam-d1] stCallCodes/test_callcode_in_initcode_to_existing_contract_with_value_transfer.py::test_callcode_in_initcode_to_existing_contract_with_value_transfer[fork_Amsterdam] -stCallCodes/test_callcodecall_10_suicide_end.py::test_callcodecall_10_suicide_end[fork_Amsterdam] -stCallCodes/test_callcodecallcall_100_suicide_end.py::test_callcodecallcall_100_suicide_end[fork_Amsterdam] -stCallCodes/test_callcodecallcodecall_110_suicide_end.py::test_callcodecallcodecall_110_suicide_end[fork_Amsterdam] -# stCallCreateCallCodeTest (12) +# stCallCreateCallCodeTest (11) stCallCreateCallCodeTest/test_call1024_oog.py::test_call1024_oog[fork_Amsterdam--g0] stCallCreateCallCodeTest/test_call1024_oog.py::test_call1024_oog[fork_Amsterdam--g1] stCallCreateCallCodeTest/test_call1024_oog.py::test_call1024_oog[fork_Amsterdam--g2] stCallCreateCallCodeTest/test_call1024_oog.py::test_call1024_oog[fork_Amsterdam--g3] stCallCreateCallCodeTest/test_callcode1024_oog.py::test_callcode1024_oog[fork_Amsterdam--g0] stCallCreateCallCodeTest/test_callcode1024_oog.py::test_callcode1024_oog[fork_Amsterdam--g1] -stCallCreateCallCodeTest/test_callcode_lose_gas_oog.py::test_callcode_lose_gas_oog[fork_Amsterdam--g2] stCallCreateCallCodeTest/test_contract_creation_make_call_that_ask_more_gas_then_transaction_provided.py::test_contract_creation_make_call_that_ask_more_gas_then_transaction_provided[fork_Amsterdam--g0] stCallCreateCallCodeTest/test_contract_creation_make_call_that_ask_more_gas_then_transaction_provided.py::test_contract_creation_make_call_that_ask_more_gas_then_transaction_provided[fork_Amsterdam--g1] stCallCreateCallCodeTest/test_create_name_registrator_per_txs_not_enough_gas.py::test_create_name_registrator_per_txs_not_enough_gas[fork_Amsterdam--g0] stCallCreateCallCodeTest/test_create_name_registrator_per_txs_not_enough_gas.py::test_create_name_registrator_per_txs_not_enough_gas[fork_Amsterdam--g1] stCallCreateCallCodeTest/test_create_name_registrator_pre_store1_not_enough_gas.py::test_create_name_registrator_pre_store1_not_enough_gas[fork_Amsterdam] -# stCallDelegateCodesCallCodeHomestead (10) +# stCallDelegateCodesCallCodeHomestead (1) stCallDelegateCodesCallCodeHomestead/test_callcallcallcode_001_suicide_end.py::test_callcallcallcode_001_suicide_end[fork_Amsterdam] -stCallDelegateCodesCallCodeHomestead/test_callcallcode_01_suicide_end.py::test_callcallcode_01_suicide_end[fork_Amsterdam] -stCallDelegateCodesCallCodeHomestead/test_callcallcodecall_010_suicide_end.py::test_callcallcodecall_010_suicide_end[fork_Amsterdam] -stCallDelegateCodesCallCodeHomestead/test_callcallcodecallcode_011_oogm_before.py::test_callcallcodecallcode_011_oogm_before[fork_Amsterdam] -stCallDelegateCodesCallCodeHomestead/test_callcallcodecallcode_011_suicide_end.py::test_callcallcodecallcode_011_suicide_end[fork_Amsterdam] -stCallDelegateCodesCallCodeHomestead/test_callcodecall_10_suicide_end.py::test_callcodecall_10_suicide_end[fork_Amsterdam] -stCallDelegateCodesCallCodeHomestead/test_callcodecallcall_100_suicide_end.py::test_callcodecallcall_100_suicide_end[fork_Amsterdam] -stCallDelegateCodesCallCodeHomestead/test_callcodecallcallcode_101_suicide_end.py::test_callcodecallcallcode_101_suicide_end[fork_Amsterdam] -stCallDelegateCodesCallCodeHomestead/test_callcodecallcode_11_suicide_end.py::test_callcodecallcode_11_suicide_end[fork_Amsterdam] -stCallDelegateCodesCallCodeHomestead/test_callcodecallcodecall_110_suicide_end.py::test_callcodecallcodecall_110_suicide_end[fork_Amsterdam] -# stCallDelegateCodesHomestead (10) -stCallDelegateCodesHomestead/test_callcallcallcode_001_suicide_end.py::test_callcallcallcode_001_suicide_end[fork_Amsterdam] -stCallDelegateCodesHomestead/test_callcallcode_01_suicide_end.py::test_callcallcode_01_suicide_end[fork_Amsterdam] -stCallDelegateCodesHomestead/test_callcallcodecall_010_suicide_end.py::test_callcallcodecall_010_suicide_end[fork_Amsterdam] -stCallDelegateCodesHomestead/test_callcallcodecallcode_011_suicide_end.py::test_callcallcodecallcode_011_suicide_end[fork_Amsterdam] -stCallDelegateCodesHomestead/test_callcodecall_10_suicide_end.py::test_callcodecall_10_suicide_end[fork_Amsterdam] -stCallDelegateCodesHomestead/test_callcodecallcall_100_suicide_end.py::test_callcodecallcall_100_suicide_end[fork_Amsterdam] -stCallDelegateCodesHomestead/test_callcodecallcallcode_101_suicide_end.py::test_callcodecallcallcode_101_suicide_end[fork_Amsterdam] -stCallDelegateCodesHomestead/test_callcodecallcode_11_suicide_end.py::test_callcodecallcode_11_suicide_end[fork_Amsterdam] -stCallDelegateCodesHomestead/test_callcodecallcodecall_110_suicide_end.py::test_callcodecallcodecall_110_suicide_end[fork_Amsterdam] -stCallDelegateCodesHomestead/test_callcodecallcodecallcode_111_suicide_end.py::test_callcodecallcodecallcode_111_suicide_end[fork_Amsterdam] - -# stCodeSizeLimit (2) -stCodeSizeLimit/test_create2_code_size_limit.py::test_create2_code_size_limit[fork_Amsterdam-valid] -stCodeSizeLimit/test_create_code_size_limit.py::test_create_code_size_limit[fork_Amsterdam-valid] - -# stCreate2 (38) +# stCreate2 (31) stCreate2/test_create2_oo_gafter_init_code_revert2.py::test_create2_oo_gafter_init_code_revert2[fork_Amsterdam] stCreate2/test_create2_oog_from_call_refunds.py::test_create2_oog_from_call_refunds[fork_Amsterdam-SStore_CallCode_Refund_NoOoG] stCreate2/test_create2_oog_from_call_refunds.py::test_create2_oog_from_call_refunds[fork_Amsterdam-SStore_Create2_Refund_NoOoG] stCreate2/test_create2_oog_from_call_refunds.py::test_create2_oog_from_call_refunds[fork_Amsterdam-SStore_Create_Refund_NoOoG] stCreate2/test_create2_oog_from_call_refunds.py::test_create2_oog_from_call_refunds[fork_Amsterdam-SStore_DelegateCall_Refund_NoOoG] -stCreate2/test_create2call_precompiles.py::test_create2call_precompiles[fork_Amsterdam-d7] -stCreate2/test_create2check_fields_in_initcode.py::test_create2check_fields_in_initcode[fork_Amsterdam-d0] -stCreate2/test_create2check_fields_in_initcode.py::test_create2check_fields_in_initcode[fork_Amsterdam-d1] -stCreate2/test_create2check_fields_in_initcode.py::test_create2check_fields_in_initcode[fork_Amsterdam-d2] -stCreate2/test_create2check_fields_in_initcode.py::test_create2check_fields_in_initcode[fork_Amsterdam-d4] -stCreate2/test_create2check_fields_in_initcode.py::test_create2check_fields_in_initcode[fork_Amsterdam-d5] -stCreate2/test_create2check_fields_in_initcode.py::test_create2check_fields_in_initcode[fork_Amsterdam-d6] stCreate2/test_create2collision_selfdestructed_oog.py::test_create2collision_selfdestructed_oog[fork_Amsterdam-d0] stCreate2/test_create2collision_selfdestructed_oog.py::test_create2collision_selfdestructed_oog[fork_Amsterdam-d1] stCreate2/test_create2collision_selfdestructed_oog.py::test_create2collision_selfdestructed_oog[fork_Amsterdam-d2] @@ -112,31 +73,20 @@ stCreate2/test_revert_depth_create_address_collision_berlin.py::test_revert_dept stCreate2/test_revert_depth_create_address_collision_berlin.py::test_revert_depth_create_address_collision_berlin[fork_Amsterdam-d1-g1-v0] stCreate2/test_revert_depth_create_address_collision_berlin.py::test_revert_depth_create_address_collision_berlin[fork_Amsterdam-d1-g1-v1] -# stCreateTest (52) +# stCreateTest (40) stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create-0xef-v1] -stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create-code-too-big-v1] stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create-contructor-revert-v1] -stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create-high-nonce-v0] stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create-high-nonce-v1] stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create-invalid-opcode-v1] stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create-ok-v1] -stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create-oog-constructor-v0] stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create-oog-constructor-v1] -stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create-oog-post-constr-v0] stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create-oog-post-constr-v1] stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create2-0xef-v1] -stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create2-code-too-big-v1] stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create2-contructor-revert-v1] stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create2-invalid-opcode-v1] stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create2-ok-v1] -stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create2-oog-constructor-v0] stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create2-oog-constructor-v1] -stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create2-oog-post-constr-v0] stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create2-oog-post-constr-v1] -stCreateTest/test_create_collision_results.py::test_create_collision_results[fork_Amsterdam-d0] -stCreateTest/test_create_collision_results.py::test_create_collision_results[fork_Amsterdam-d1] -stCreateTest/test_create_collision_to_empty2.py::test_create_collision_to_empty2[fork_Amsterdam-d0-g0-v0] -stCreateTest/test_create_collision_to_empty2.py::test_create_collision_to_empty2[fork_Amsterdam-d0-g0-v1] stCreateTest/test_create_e_contract_create_ne_contract_in_init_oog_tr.py::test_create_e_contract_create_ne_contract_in_init_oog_tr[fork_Amsterdam--g0] stCreateTest/test_create_e_contract_create_ne_contract_in_init_oog_tr.py::test_create_e_contract_create_ne_contract_in_init_oog_tr[fork_Amsterdam--g1] stCreateTest/test_create_e_contract_then_call_to_non_existent_acc.py::test_create_e_contract_then_call_to_non_existent_acc[fork_Amsterdam] @@ -148,7 +98,6 @@ stCreateTest/test_create_empty_contract_with_storage.py::test_create_empty_contr stCreateTest/test_create_empty_contract_with_storage_and_call_it_0wei.py::test_create_empty_contract_with_storage_and_call_it_0wei[fork_Amsterdam] stCreateTest/test_create_empty_contract_with_storage_and_call_it_1wei.py::test_create_empty_contract_with_storage_and_call_it_1wei[fork_Amsterdam] stCreateTest/test_create_oo_gafter_init_code_returndata_size.py::test_create_oo_gafter_init_code_returndata_size[fork_Amsterdam] -stCreateTest/test_create_oo_gafter_init_code_revert2.py::test_create_oo_gafter_init_code_revert2[fork_Amsterdam-d0] stCreateTest/test_create_oog_from_call_refunds.py::test_create_oog_from_call_refunds[fork_Amsterdam-SStore_Create2_Refund_NoOoG] stCreateTest/test_create_oog_from_call_refunds.py::test_create_oog_from_call_refunds[fork_Amsterdam-SStore_Create_Refund_NoOoG] stCreateTest/test_create_oog_from_call_refunds.py::test_create_oog_from_call_refunds[fork_Amsterdam-SStore_Refund_NoOoG2] @@ -166,14 +115,13 @@ stCreateTest/test_transaction_collision_to_empty_but_code.py::test_transaction_c stCreateTest/test_transaction_collision_to_empty_but_nonce.py::test_transaction_collision_to_empty_but_nonce[fork_Amsterdam--g1-v0] stCreateTest/test_transaction_collision_to_empty_but_nonce.py::test_transaction_collision_to_empty_but_nonce[fork_Amsterdam--g1-v1] -# stDelegatecallTestHomestead (7) +# stDelegatecallTestHomestead (6) stDelegatecallTestHomestead/test_call1024_oog.py::test_call1024_oog[fork_Amsterdam--g0] stDelegatecallTestHomestead/test_call1024_oog.py::test_call1024_oog[fork_Amsterdam--g1] stDelegatecallTestHomestead/test_deleagate_call_after_value_transfer.py::test_deleagate_call_after_value_transfer[fork_Amsterdam] stDelegatecallTestHomestead/test_delegatecall1024_oog.py::test_delegatecall1024_oog[fork_Amsterdam] stDelegatecallTestHomestead/test_delegatecall_emptycontract.py::test_delegatecall_emptycontract[fork_Amsterdam] stDelegatecallTestHomestead/test_delegatecall_in_initcode_to_existing_contract.py::test_delegatecall_in_initcode_to_existing_contract[fork_Amsterdam] -stDelegatecallTestHomestead/test_delegatecode_dynamic_code.py::test_delegatecode_dynamic_code[fork_Amsterdam] # stEIP150Specific (7) stEIP150Specific/test_call_ask_more_gas_on_depth2_then_transaction_has.py::test_call_ask_more_gas_on_depth2_then_transaction_has[fork_Amsterdam] @@ -220,30 +168,11 @@ stEIP1559/test_sender_balance.py::test_sender_balance[fork_Amsterdam] # stEIP158Specific (1) stEIP158Specific/test_exp_empty.py::test_exp_empty[fork_Amsterdam] -# stEIP3651_warmcoinbase (8) -stEIP3651_warmcoinbase/test_coinbase_warm_account_call_gas.py::test_coinbase_warm_account_call_gas[fork_Amsterdam-d0] -stEIP3651_warmcoinbase/test_coinbase_warm_account_call_gas.py::test_coinbase_warm_account_call_gas[fork_Amsterdam-d1] -stEIP3651_warmcoinbase/test_coinbase_warm_account_call_gas.py::test_coinbase_warm_account_call_gas[fork_Amsterdam-d2] -stEIP3651_warmcoinbase/test_coinbase_warm_account_call_gas.py::test_coinbase_warm_account_call_gas[fork_Amsterdam-d3] -stEIP3651_warmcoinbase/test_coinbase_warm_account_call_gas.py::test_coinbase_warm_account_call_gas[fork_Amsterdam-d4] -stEIP3651_warmcoinbase/test_coinbase_warm_account_call_gas.py::test_coinbase_warm_account_call_gas[fork_Amsterdam-d5] -stEIP3651_warmcoinbase/test_coinbase_warm_account_call_gas.py::test_coinbase_warm_account_call_gas[fork_Amsterdam-d6] -stEIP3651_warmcoinbase/test_coinbase_warm_account_call_gas.py::test_coinbase_warm_account_call_gas[fork_Amsterdam-d7] - -# stEIP3855_push0 (4) -stEIP3855_push0/test_push0.py::test_push0[fork_Amsterdam-1025_push0] +# stEIP3855_push0 (3) stEIP3855_push0/test_push0_gas.py::test_push0_gas[fork_Amsterdam] stEIP3855_push0/test_push0_gas2.py::test_push0_gas2[fork_Amsterdam-use_push0] stEIP3855_push0/test_push0_gas2.py::test_push0_gas2[fork_Amsterdam-use_push1_00] -# stEIP3860_limitmeterinitcode (6) -stEIP3860_limitmeterinitcode/test_create2_init_code_size_limit.py::test_create2_init_code_size_limit[fork_Amsterdam-invalid] -stEIP3860_limitmeterinitcode/test_create2_init_code_size_limit.py::test_create2_init_code_size_limit[fork_Amsterdam-valid] -stEIP3860_limitmeterinitcode/test_create_init_code_size_limit.py::test_create_init_code_size_limit[fork_Amsterdam-invalid] -stEIP3860_limitmeterinitcode/test_create_init_code_size_limit.py::test_create_init_code_size_limit[fork_Amsterdam-valid] -stEIP3860_limitmeterinitcode/test_creation_tx_init_code_size_limit.py::test_creation_tx_init_code_size_limit[fork_Amsterdam-invalid] -stEIP3860_limitmeterinitcode/test_creation_tx_init_code_size_limit.py::test_creation_tx_init_code_size_limit[fork_Amsterdam-valid] - # stEIP5656_MCOPY (55) stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src0_size0-g0] stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src0_size0-g1] @@ -319,12 +248,8 @@ stMemExpandingEIP150Calls/test_call_goes_oog_on_second_level_with_mem_expanding_ stMemExpandingEIP150Calls/test_create_and_gas_inside_create_with_mem_expanding_calls.py::test_create_and_gas_inside_create_with_mem_expanding_calls[fork_Amsterdam] stMemExpandingEIP150Calls/test_new_gas_price_for_codes_with_mem_expanding_calls.py::test_new_gas_price_for_codes_with_mem_expanding_calls[fork_Amsterdam] -# stMemoryStressTest (1) -stMemoryStressTest/test_return_bounds.py::test_return_bounds[fork_Amsterdam--g1] - -# stMemoryTest (5) +# stMemoryTest (4) stMemoryTest/test_call_data_copy_offset.py::test_call_data_copy_offset[fork_Amsterdam] -stMemoryTest/test_calldatacopy_dejavu2.py::test_calldatacopy_dejavu2[fork_Amsterdam] stMemoryTest/test_code_copy_offset.py::test_code_copy_offset[fork_Amsterdam] stMemoryTest/test_oog.py::test_oog[fork_Amsterdam-success14] stMemoryTest/test_oog.py::test_oog[fork_Amsterdam-success15] @@ -341,13 +266,6 @@ stNonZeroCallsTest/test_non_zero_value_delegatecall_to_empty_paris.py::test_non_ stNonZeroCallsTest/test_non_zero_value_delegatecall_to_non_non_zero_balance.py::test_non_zero_value_delegatecall_to_non_non_zero_balance[fork_Amsterdam] stNonZeroCallsTest/test_non_zero_value_delegatecall_to_one_storage_key_paris.py::test_non_zero_value_delegatecall_to_one_storage_key_paris[fork_Amsterdam] -# stPreCompiledContracts2 (2) -stPreCompiledContracts2/test_call_sha256_1_nonzero_value.py::test_call_sha256_1_nonzero_value[fork_Amsterdam] -stPreCompiledContracts2/test_ecrecover_short_buff.py::test_ecrecover_short_buff[fork_Amsterdam] - -# stRecursiveCreate (1) -stRecursiveCreate/test_recursive_create.py::test_recursive_create[fork_Amsterdam] - # stRefundTest (7) stRefundTest/test_refund50_2.py::test_refund50_2[fork_Amsterdam] stRefundTest/test_refund50percent_cap.py::test_refund50percent_cap[fork_Amsterdam] @@ -357,19 +275,11 @@ stRefundTest/test_refund_suicide50procent_cap.py::test_refund_suicide50procent_c stRefundTest/test_refund_suicide50procent_cap.py::test_refund_suicide50procent_cap[fork_Amsterdam-d1] stRefundTest/test_refund_tx_to_suicide.py::test_refund_tx_to_suicide[fork_Amsterdam] -# stReturnDataTest (2) -stReturnDataTest/test_returndatasize_after_successful_callcode.py::test_returndatasize_after_successful_callcode[fork_Amsterdam] -stReturnDataTest/test_subcall_return_more_then_expected.py::test_subcall_return_more_then_expected[fork_Amsterdam] - -# stRevertTest (28) +# stRevertTest (12) stRevertTest/test_loop_calls_depth_then_revert.py::test_loop_calls_depth_then_revert[fork_Amsterdam] -stRevertTest/test_loop_calls_then_revert.py::test_loop_calls_then_revert[fork_Amsterdam] stRevertTest/test_loop_delegate_calls_depth_then_revert.py::test_loop_delegate_calls_depth_then_revert[fork_Amsterdam] -stRevertTest/test_revert_depth2.py::test_revert_depth2[fork_Amsterdam--g1] stRevertTest/test_revert_depth_create_address_collision.py::test_revert_depth_create_address_collision[fork_Amsterdam-d0-g1-v0] stRevertTest/test_revert_depth_create_address_collision.py::test_revert_depth_create_address_collision[fork_Amsterdam-d0-g1-v1] -stRevertTest/test_revert_depth_create_address_collision.py::test_revert_depth_create_address_collision[fork_Amsterdam-d1-g1-v0] -stRevertTest/test_revert_depth_create_address_collision.py::test_revert_depth_create_address_collision[fork_Amsterdam-d1-g1-v1] stRevertTest/test_revert_depth_create_oog.py::test_revert_depth_create_oog[fork_Amsterdam-d0-g1-v0] stRevertTest/test_revert_depth_create_oog.py::test_revert_depth_create_oog[fork_Amsterdam-d0-g1-v1] stRevertTest/test_revert_depth_create_oog.py::test_revert_depth_create_oog[fork_Amsterdam-d1-g1-v0] @@ -378,192 +288,36 @@ stRevertTest/test_revert_opcode_in_calls_on_non_empty_return_data.py::test_rever stRevertTest/test_revert_opcode_in_calls_on_non_empty_return_data.py::test_revert_opcode_in_calls_on_non_empty_return_data[fork_Amsterdam-d1-g0] stRevertTest/test_revert_opcode_in_calls_on_non_empty_return_data.py::test_revert_opcode_in_calls_on_non_empty_return_data[fork_Amsterdam-d2-g0] stRevertTest/test_revert_opcode_in_calls_on_non_empty_return_data.py::test_revert_opcode_in_calls_on_non_empty_return_data[fork_Amsterdam-d3-g0] -stRevertTest/test_revert_opcode_multiple_sub_calls.py::test_revert_opcode_multiple_sub_calls[fork_Amsterdam-d0-g0-v0] -stRevertTest/test_revert_opcode_multiple_sub_calls.py::test_revert_opcode_multiple_sub_calls[fork_Amsterdam-d0-g0-v1] -stRevertTest/test_revert_opcode_multiple_sub_calls.py::test_revert_opcode_multiple_sub_calls[fork_Amsterdam-d0-g2-v0] -stRevertTest/test_revert_opcode_multiple_sub_calls.py::test_revert_opcode_multiple_sub_calls[fork_Amsterdam-d0-g2-v1] -stRevertTest/test_revert_opcode_multiple_sub_calls.py::test_revert_opcode_multiple_sub_calls[fork_Amsterdam-d1-g0-v0] -stRevertTest/test_revert_opcode_multiple_sub_calls.py::test_revert_opcode_multiple_sub_calls[fork_Amsterdam-d1-g0-v1] -stRevertTest/test_revert_opcode_multiple_sub_calls.py::test_revert_opcode_multiple_sub_calls[fork_Amsterdam-d2-g0-v0] -stRevertTest/test_revert_opcode_multiple_sub_calls.py::test_revert_opcode_multiple_sub_calls[fork_Amsterdam-d2-g0-v1] -stRevertTest/test_revert_opcode_multiple_sub_calls.py::test_revert_opcode_multiple_sub_calls[fork_Amsterdam-d3-g0-v0] -stRevertTest/test_revert_opcode_multiple_sub_calls.py::test_revert_opcode_multiple_sub_calls[fork_Amsterdam-d3-g0-v1] -stRevertTest/test_revert_opcode_multiple_sub_calls.py::test_revert_opcode_multiple_sub_calls[fork_Amsterdam-d3-g2-v0] -stRevertTest/test_revert_opcode_multiple_sub_calls.py::test_revert_opcode_multiple_sub_calls[fork_Amsterdam-d3-g2-v1] -# stSStoreTest (10) -stSStoreTest/test_sstore_change_from_external_call_in_init_code.py::test_sstore_change_from_external_call_in_init_code[fork_Amsterdam-d0] -stSStoreTest/test_sstore_change_from_external_call_in_init_code.py::test_sstore_change_from_external_call_in_init_code[fork_Amsterdam-d1] -stSStoreTest/test_sstore_change_from_external_call_in_init_code.py::test_sstore_change_from_external_call_in_init_code[fork_Amsterdam-d3] -stSStoreTest/test_sstore_change_from_external_call_in_init_code.py::test_sstore_change_from_external_call_in_init_code[fork_Amsterdam-d4] -stSStoreTest/test_sstore_change_from_external_call_in_init_code.py::test_sstore_change_from_external_call_in_init_code[fork_Amsterdam-d5] -stSStoreTest/test_sstore_change_from_external_call_in_init_code.py::test_sstore_change_from_external_call_in_init_code[fork_Amsterdam-d7] +# stSStoreTest (4) stSStoreTest/test_sstore_gas.py::test_sstore_gas[fork_Amsterdam] stSStoreTest/test_sstore_gas_left.py::test_sstore_gas_left[fork_Amsterdam-d2] stSStoreTest/test_sstore_gas_left.py::test_sstore_gas_left[fork_Amsterdam-d5] stSStoreTest/test_sstore_gas_left.py::test_sstore_gas_left[fork_Amsterdam-d8] -# stSelfBalance (3) -stSelfBalance/test_self_balance.py::test_self_balance[fork_Amsterdam] -stSelfBalance/test_self_balance_equals_balance.py::test_self_balance_equals_balance[fork_Amsterdam] -stSelfBalance/test_self_balance_gas_cost.py::test_self_balance_gas_cost[fork_Amsterdam] - -# stSolidityTest (5) +# stSolidityTest (3) stSolidityTest/test_recursive_create_contracts.py::test_recursive_create_contracts[fork_Amsterdam] stSolidityTest/test_test_contract_interaction.py::test_test_contract_interaction[fork_Amsterdam] stSolidityTest/test_test_contract_suicide.py::test_test_contract_suicide[fork_Amsterdam] -stSolidityTest/test_test_overflow.py::test_test_overflow[fork_Amsterdam] -stSolidityTest/test_test_structures_and_variabless.py::test_test_structures_and_variabless[fork_Amsterdam] # stSpecialTest (1) stSpecialTest/test_make_money.py::test_make_money[fork_Amsterdam] -# stStaticCall (81) -stStaticCall/test_static_call_ask_more_gas_on_depth2_then_transaction_has.py::test_static_call_ask_more_gas_on_depth2_then_transaction_has[fork_Amsterdam-d0] -stStaticCall/test_static_call_contract_to_create_contract_oog.py::test_static_call_contract_to_create_contract_oog[fork_Amsterdam--v1] -stStaticCall/test_static_call_recursive_bomb3.py::test_static_call_recursive_bomb3[fork_Amsterdam] -stStaticCall/test_static_call_sha256_1_nonzero_value.py::test_static_call_sha256_1_nonzero_value[fork_Amsterdam] +# stStaticCall (4) stStaticCall/test_static_call_value_inherit_from_call.py::test_static_call_value_inherit_from_call[fork_Amsterdam] -stStaticCall/test_static_callcall_00_ooge_1.py::test_static_callcall_00_ooge_1[fork_Amsterdam-d0] -stStaticCall/test_static_callcall_00_ooge_1.py::test_static_callcall_00_ooge_1[fork_Amsterdam-d1] -stStaticCall/test_static_callcallcode_01_ooge_2.py::test_static_callcallcode_01_ooge_2[fork_Amsterdam-d0] -stStaticCall/test_static_callcallcode_01_ooge_2.py::test_static_callcallcode_01_ooge_2[fork_Amsterdam-d1] -stStaticCall/test_static_callcallcodecallcode_011_ooge.py::test_static_callcallcodecallcode_011_ooge[fork_Amsterdam-d0] -stStaticCall/test_static_callcallcodecallcode_011_ooge.py::test_static_callcallcodecallcode_011_ooge[fork_Amsterdam-d1] -stStaticCall/test_static_callcallcodecallcode_011_ooge_2.py::test_static_callcallcodecallcode_011_ooge_2[fork_Amsterdam-d0] -stStaticCall/test_static_callcallcodecallcode_011_ooge_2.py::test_static_callcallcodecallcode_011_ooge_2[fork_Amsterdam-d1] -stStaticCall/test_static_callcallcodecallcode_011_oogm_after.py::test_static_callcallcodecallcode_011_oogm_after[fork_Amsterdam-d0] -stStaticCall/test_static_callcallcodecallcode_011_oogm_after.py::test_static_callcallcodecallcode_011_oogm_after[fork_Amsterdam-d1] -stStaticCall/test_static_callcallcodecallcode_011_oogm_after2.py::test_static_callcallcodecallcode_011_oogm_after2[fork_Amsterdam-d0] -stStaticCall/test_static_callcallcodecallcode_011_oogm_after2.py::test_static_callcallcodecallcode_011_oogm_after2[fork_Amsterdam-d1] -stStaticCall/test_static_callcallcodecallcode_011_oogm_after_1.py::test_static_callcallcodecallcode_011_oogm_after_1[fork_Amsterdam-d0] -stStaticCall/test_static_callcallcodecallcode_011_oogm_after_1.py::test_static_callcallcodecallcode_011_oogm_after_1[fork_Amsterdam-d1] -stStaticCall/test_static_callcallcodecallcode_011_oogm_after_2.py::test_static_callcallcodecallcode_011_oogm_after_2[fork_Amsterdam-d0] -stStaticCall/test_static_callcallcodecallcode_011_oogm_after_2.py::test_static_callcallcodecallcode_011_oogm_after_2[fork_Amsterdam-d1] -stStaticCall/test_static_callcallcodecallcode_011_oogm_before.py::test_static_callcallcodecallcode_011_oogm_before[fork_Amsterdam-d0] -stStaticCall/test_static_callcallcodecallcode_011_oogm_before.py::test_static_callcallcodecallcode_011_oogm_before[fork_Amsterdam-d1] -stStaticCall/test_static_callcallcodecallcode_011_oogm_before2.py::test_static_callcallcodecallcode_011_oogm_before2[fork_Amsterdam-d0] -stStaticCall/test_static_callcallcodecallcode_011_oogm_before2.py::test_static_callcallcodecallcode_011_oogm_before2[fork_Amsterdam-d1] -stStaticCall/test_static_callcallcodecallcode_011_oogm_before2.py::test_static_callcallcodecallcode_011_oogm_before2[fork_Amsterdam-d2] -stStaticCall/test_static_callcodecall_10_ooge.py::test_static_callcodecall_10_ooge[fork_Amsterdam-d0] -stStaticCall/test_static_callcodecall_10_ooge.py::test_static_callcodecall_10_ooge[fork_Amsterdam-d1] -stStaticCall/test_static_callcodecall_10_ooge_2.py::test_static_callcodecall_10_ooge_2[fork_Amsterdam-d0] -stStaticCall/test_static_callcodecall_10_ooge_2.py::test_static_callcodecall_10_ooge_2[fork_Amsterdam-d1] -stStaticCall/test_static_callcodecallcall_100_ooge.py::test_static_callcodecallcall_100_ooge[fork_Amsterdam-d0] -stStaticCall/test_static_callcodecallcall_100_ooge.py::test_static_callcodecallcall_100_ooge[fork_Amsterdam-d1] -stStaticCall/test_static_callcodecallcall_100_ooge2.py::test_static_callcodecallcall_100_ooge2[fork_Amsterdam-d0] -stStaticCall/test_static_callcodecallcall_100_ooge2.py::test_static_callcodecallcall_100_ooge2[fork_Amsterdam-d1] -stStaticCall/test_static_callcodecallcall_100_oogm_after_3.py::test_static_callcodecallcall_100_oogm_after_3[fork_Amsterdam--v0] -stStaticCall/test_static_callcodecallcall_100_oogm_after_3.py::test_static_callcodecallcall_100_oogm_after_3[fork_Amsterdam--v1] -stStaticCall/test_static_callcodecallcall_100_oogm_before.py::test_static_callcodecallcall_100_oogm_before[fork_Amsterdam-d0] -stStaticCall/test_static_callcodecallcall_100_oogm_before.py::test_static_callcodecallcall_100_oogm_before[fork_Amsterdam-d1] -stStaticCall/test_static_callcodecallcall_100_oogm_before2.py::test_static_callcodecallcall_100_oogm_before2[fork_Amsterdam-d0-v0] -stStaticCall/test_static_callcodecallcall_100_oogm_before2.py::test_static_callcodecallcall_100_oogm_before2[fork_Amsterdam-d0-v1] -stStaticCall/test_static_callcodecallcall_100_oogm_before2.py::test_static_callcodecallcall_100_oogm_before2[fork_Amsterdam-d1-v0] -stStaticCall/test_static_callcodecallcall_100_oogm_before2.py::test_static_callcodecallcall_100_oogm_before2[fork_Amsterdam-d1-v1] -stStaticCall/test_static_callcodecallcallcode_101_ooge_2.py::test_static_callcodecallcallcode_101_ooge_2[fork_Amsterdam] -stStaticCall/test_static_callcodecallcallcode_101_oogm_after.py::test_static_callcodecallcallcode_101_oogm_after[fork_Amsterdam] -stStaticCall/test_static_callcodecallcallcode_101_oogm_after2.py::test_static_callcodecallcallcode_101_oogm_after2[fork_Amsterdam--v0] -stStaticCall/test_static_callcodecallcallcode_101_oogm_after2.py::test_static_callcodecallcallcode_101_oogm_after2[fork_Amsterdam--v1] -stStaticCall/test_static_callcodecallcallcode_101_oogm_before.py::test_static_callcodecallcallcode_101_oogm_before[fork_Amsterdam] -stStaticCall/test_static_callcodecallcallcode_101_oogm_before2.py::test_static_callcodecallcallcode_101_oogm_before2[fork_Amsterdam--v0] -stStaticCall/test_static_callcodecallcallcode_101_oogm_before2.py::test_static_callcodecallcallcode_101_oogm_before2[fork_Amsterdam--v1] -stStaticCall/test_static_callcodecallcodecall_110_ooge.py::test_static_callcodecallcodecall_110_ooge[fork_Amsterdam] -stStaticCall/test_static_callcodecallcodecall_110_ooge2.py::test_static_callcodecallcodecall_110_ooge2[fork_Amsterdam--v0] -stStaticCall/test_static_callcodecallcodecall_110_ooge2.py::test_static_callcodecallcodecall_110_ooge2[fork_Amsterdam--v1] -stStaticCall/test_static_callcodecallcodecall_110_ooge2.py::test_static_callcodecallcodecall_110_ooge2[fork_Amsterdam--v2] -stStaticCall/test_static_callcodecallcodecall_110_oogm_after.py::test_static_callcodecallcodecall_110_oogm_after[fork_Amsterdam] -stStaticCall/test_static_callcodecallcodecall_110_oogm_after2.py::test_static_callcodecallcodecall_110_oogm_after2[fork_Amsterdam--v0] -stStaticCall/test_static_callcodecallcodecall_110_oogm_after2.py::test_static_callcodecallcodecall_110_oogm_after2[fork_Amsterdam--v1] -stStaticCall/test_static_callcodecallcodecall_110_oogm_after2.py::test_static_callcodecallcodecall_110_oogm_after2[fork_Amsterdam--v2] -stStaticCall/test_static_callcodecallcodecall_110_oogm_after_2.py::test_static_callcodecallcodecall_110_oogm_after_2[fork_Amsterdam] -stStaticCall/test_static_callcodecallcodecall_110_oogm_after_3.py::test_static_callcodecallcodecall_110_oogm_after_3[fork_Amsterdam] -stStaticCall/test_static_callcodecallcodecall_110_oogm_before.py::test_static_callcodecallcodecall_110_oogm_before[fork_Amsterdam] -stStaticCall/test_static_callcodecallcodecall_110_oogm_before2.py::test_static_callcodecallcodecall_110_oogm_before2[fork_Amsterdam--v0] -stStaticCall/test_static_callcodecallcodecall_110_oogm_before2.py::test_static_callcodecallcodecall_110_oogm_before2[fork_Amsterdam--v1] -stStaticCall/test_static_callcodecallcodecall_110_oogm_before2.py::test_static_callcodecallcodecall_110_oogm_before2[fork_Amsterdam--v2] -stStaticCall/test_static_calldelcode_01_ooge.py::test_static_calldelcode_01_ooge[fork_Amsterdam-d0] -stStaticCall/test_static_calldelcode_01_ooge.py::test_static_calldelcode_01_ooge[fork_Amsterdam-d1] -stStaticCall/test_static_check_opcodes4.py::test_static_check_opcodes4[fork_Amsterdam--g1-v0] -stStaticCall/test_static_check_opcodes4.py::test_static_check_opcodes4[fork_Amsterdam--g1-v1] -stStaticCall/test_static_check_opcodes5.py::test_static_check_opcodes5[fork_Amsterdam-d0-g1-v0] -stStaticCall/test_static_check_opcodes5.py::test_static_check_opcodes5[fork_Amsterdam-d0-g1-v1] -stStaticCall/test_static_check_opcodes5.py::test_static_check_opcodes5[fork_Amsterdam-d1-g1-v0] -stStaticCall/test_static_check_opcodes5.py::test_static_check_opcodes5[fork_Amsterdam-d1-g1-v1] -stStaticCall/test_static_check_opcodes5.py::test_static_check_opcodes5[fork_Amsterdam-d2-g1-v0] -stStaticCall/test_static_check_opcodes5.py::test_static_check_opcodes5[fork_Amsterdam-d2-g1-v1] -stStaticCall/test_static_check_opcodes5.py::test_static_check_opcodes5[fork_Amsterdam-d3-g1-v0] -stStaticCall/test_static_check_opcodes5.py::test_static_check_opcodes5[fork_Amsterdam-d3-g1-v1] -stStaticCall/test_static_check_opcodes5.py::test_static_check_opcodes5[fork_Amsterdam-d4-g1-v0] -stStaticCall/test_static_check_opcodes5.py::test_static_check_opcodes5[fork_Amsterdam-d4-g1-v1] stStaticCall/test_static_create_empty_contract_and_call_it_0wei.py::test_static_create_empty_contract_and_call_it_0wei[fork_Amsterdam] stStaticCall/test_static_create_empty_contract_with_storage_and_call_it_0wei.py::test_static_create_empty_contract_with_storage_and_call_it_0wei[fork_Amsterdam] stStaticCall/test_static_execute_call_that_ask_fore_gas_then_trabsaction_has.py::test_static_execute_call_that_ask_fore_gas_then_trabsaction_has[fork_Amsterdam-d0] -stStaticCall/test_static_revert_opcode_calls.py::test_static_revert_opcode_calls[fork_Amsterdam--g1] -# stStaticFlagEnabled (6) -stStaticFlagEnabled/test_callcode_to_precompile_from_called_contract.py::test_callcode_to_precompile_from_called_contract[fork_Amsterdam] -stStaticFlagEnabled/test_callcode_to_precompile_from_contract_initialization.py::test_callcode_to_precompile_from_contract_initialization[fork_Amsterdam] -stStaticFlagEnabled/test_callcode_to_precompile_from_transaction.py::test_callcode_to_precompile_from_transaction[fork_Amsterdam] -stStaticFlagEnabled/test_delegatecall_to_precompile_from_called_contract.py::test_delegatecall_to_precompile_from_called_contract[fork_Amsterdam] -stStaticFlagEnabled/test_delegatecall_to_precompile_from_contract_initialization.py::test_delegatecall_to_precompile_from_contract_initialization[fork_Amsterdam] -stStaticFlagEnabled/test_delegatecall_to_precompile_from_transaction.py::test_delegatecall_to_precompile_from_transaction[fork_Amsterdam] - -# stSystemOperationsTest (8) +# stSystemOperationsTest (5) stSystemOperationsTest/test_ab_acalls0.py::test_ab_acalls0[fork_Amsterdam] stSystemOperationsTest/test_ab_acalls3.py::test_ab_acalls3[fork_Amsterdam] -stSystemOperationsTest/test_ab_acalls_suicide0.py::test_ab_acalls_suicide0[fork_Amsterdam] -stSystemOperationsTest/test_call10.py::test_call10[fork_Amsterdam] stSystemOperationsTest/test_call_recursive_bomb3.py::test_call_recursive_bomb3[fork_Amsterdam] -stSystemOperationsTest/test_call_to_name_registrator_address_too_big_right.py::test_call_to_name_registrator_address_too_big_right[fork_Amsterdam] stSystemOperationsTest/test_double_selfdestruct_touch_paris.py::test_double_selfdestruct_touch_paris[fork_Amsterdam--v1] stSystemOperationsTest/test_double_selfdestruct_touch_paris.py::test_double_selfdestruct_touch_paris[fork_Amsterdam--v2] -# stTransactionTest (21) -stTransactionTest/test_no_src_account_create.py::test_no_src_account_create[fork_Amsterdam-d0-g1-v0] -stTransactionTest/test_no_src_account_create.py::test_no_src_account_create[fork_Amsterdam-d0-g1-v1] -stTransactionTest/test_no_src_account_create.py::test_no_src_account_create[fork_Amsterdam-d1-g1-v0] -stTransactionTest/test_no_src_account_create.py::test_no_src_account_create[fork_Amsterdam-d1-g1-v1] -stTransactionTest/test_no_src_account_create.py::test_no_src_account_create[fork_Amsterdam-d2-g1-v0] -stTransactionTest/test_no_src_account_create.py::test_no_src_account_create[fork_Amsterdam-d2-g1-v1] -stTransactionTest/test_no_src_account_create.py::test_no_src_account_create[fork_Amsterdam-d3-g1-v0] -stTransactionTest/test_no_src_account_create.py::test_no_src_account_create[fork_Amsterdam-d3-g1-v1] -stTransactionTest/test_no_src_account_create.py::test_no_src_account_create[fork_Amsterdam-d4-g1-v0] -stTransactionTest/test_no_src_account_create.py::test_no_src_account_create[fork_Amsterdam-d4-g1-v1] -stTransactionTest/test_no_src_account_create1559.py::test_no_src_account_create1559[fork_Amsterdam-d0-g1-v0] -stTransactionTest/test_no_src_account_create1559.py::test_no_src_account_create1559[fork_Amsterdam-d0-g1-v1] -stTransactionTest/test_no_src_account_create1559.py::test_no_src_account_create1559[fork_Amsterdam-d1-g1-v0] -stTransactionTest/test_no_src_account_create1559.py::test_no_src_account_create1559[fork_Amsterdam-d1-g1-v1] -stTransactionTest/test_no_src_account_create1559.py::test_no_src_account_create1559[fork_Amsterdam-d2-g1-v0] -stTransactionTest/test_no_src_account_create1559.py::test_no_src_account_create1559[fork_Amsterdam-d2-g1-v1] +# stTransactionTest (4) stTransactionTest/test_opcodes_transaction_init.py::test_opcodes_transaction_init[fork_Amsterdam-d120] stTransactionTest/test_opcodes_transaction_init.py::test_opcodes_transaction_init[fork_Amsterdam-side_effects] stTransactionTest/test_store_gas_on_create.py::test_store_gas_on_create[fork_Amsterdam] stTransactionTest/test_suicides_and_internal_call_suicides_success.py::test_suicides_and_internal_call_suicides_success[fork_Amsterdam-d1] -vmArithmeticTest/test_exp_power256_of256.py::test_exp_power256_of256[fork_Amsterdam] - -# stWalletTest (2) -stWalletTest/test_day_limit_construction_partial.py::test_day_limit_construction_partial[fork_Amsterdam] -stWalletTest/test_wallet_construction_partial.py::test_wallet_construction_partial[fork_Amsterdam] - -# stZeroKnowledge (20) -stZeroKnowledge/test_point_mul_add.py::test_point_mul_add[fork_Amsterdam-d2-g3] -stZeroKnowledge/test_point_mul_add.py::test_point_mul_add[fork_Amsterdam-d7-g3] -stZeroKnowledge/test_point_mul_add.py::test_point_mul_add[fork_Amsterdam-d8-g3] -stZeroKnowledge/test_point_mul_add2.py::test_point_mul_add2[fork_Amsterdam-d0-g3] -stZeroKnowledge/test_point_mul_add2.py::test_point_mul_add2[fork_Amsterdam-d1-g3] -stZeroKnowledge/test_point_mul_add2.py::test_point_mul_add2[fork_Amsterdam-d12-g3] -stZeroKnowledge/test_point_mul_add2.py::test_point_mul_add2[fork_Amsterdam-d17-g3] -stZeroKnowledge/test_point_mul_add2.py::test_point_mul_add2[fork_Amsterdam-d2-g3] -stZeroKnowledge/test_point_mul_add2.py::test_point_mul_add2[fork_Amsterdam-d21-g3] -stZeroKnowledge/test_point_mul_add2.py::test_point_mul_add2[fork_Amsterdam-d26-g3] -stZeroKnowledge/test_point_mul_add2.py::test_point_mul_add2[fork_Amsterdam-d3-g3] -stZeroKnowledge/test_point_mul_add2.py::test_point_mul_add2[fork_Amsterdam-d30-g3] -stZeroKnowledge/test_point_mul_add2.py::test_point_mul_add2[fork_Amsterdam-d34-g3] -stZeroKnowledge/test_point_mul_add2.py::test_point_mul_add2[fork_Amsterdam-d4-g3] -stZeroKnowledge/test_point_mul_add2.py::test_point_mul_add2[fork_Amsterdam-d5-g3] -stZeroKnowledge/test_point_mul_add2.py::test_point_mul_add2[fork_Amsterdam-d6-g3] -stZeroKnowledge/test_point_mul_add2.py::test_point_mul_add2[fork_Amsterdam-d7-g3] -stZeroKnowledge/test_point_mul_add2.py::test_point_mul_add2[fork_Amsterdam-d8-g3] -stZeroKnowledge/test_point_mul_add2.py::test_point_mul_add2[fork_Amsterdam-d9-g3] -vmArithmeticTest/test_two_ops.py::test_two_ops[fork_Amsterdam] diff --git a/tests/ported_static/stCallCreateCallCodeTest/test_callcode_lose_gas_oog.py b/tests/ported_static/stCallCreateCallCodeTest/test_callcode_lose_gas_oog.py index 8ed143eb578..a75fb2e2479 100644 --- a/tests/ported_static/stCallCreateCallCodeTest/test_callcode_lose_gas_oog.py +++ b/tests/ported_static/stCallCreateCallCodeTest/test_callcode_lose_gas_oog.py @@ -123,7 +123,7 @@ def test_callcode_lose_gas_oog( tx_data = [ Bytes(""), ] - tx_gas = [166262, 156262, 170000] + tx_gas = [166262, 156262, None if fork.is_eip_enabled(8037) else 170000] tx_value = [10] tx = Transaction( diff --git a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcodecallcode_011_oogm_before.py b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcodecallcode_011_oogm_before.py index f720fd6f4c2..0cca6713282 100644 --- a/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcodecallcode_011_oogm_before.py +++ b/tests/ported_static/stCallDelegateCodesCallCodeHomestead/test_callcallcodecallcode_011_oogm_before.py @@ -111,7 +111,6 @@ def test_callcallcodecallcode_011_oogm_before( sender=sender, to=target, data=Bytes(""), - gas_limit=172000, ) post = { diff --git a/tests/ported_static/stCreate2/test_create2call_precompiles.py b/tests/ported_static/stCreate2/test_create2call_precompiles.py index 4fe7a1ba9e5..c187bf94f9e 100644 --- a/tests/ported_static/stCreate2/test_create2call_precompiles.py +++ b/tests/ported_static/stCreate2/test_create2call_precompiles.py @@ -524,14 +524,12 @@ def test_create2call_precompiles( ) + Op.STOP * 2, ] - tx_gas = [15000000] tx_value = [1] tx = Transaction( sender=sender, to=None, data=tx_data[d], - gas_limit=tx_gas[g], value=tx_value[v], error=_exc, ) diff --git a/tests/ported_static/stCreate2/test_create2check_fields_in_initcode.py b/tests/ported_static/stCreate2/test_create2check_fields_in_initcode.py index 4f883160226..da4c2cabd35 100644 --- a/tests/ported_static/stCreate2/test_create2check_fields_in_initcode.py +++ b/tests/ported_static/stCreate2/test_create2check_fields_in_initcode.py @@ -472,13 +472,11 @@ def test_create2check_fields_in_initcode( Hash(contract_6, left_padding=True), Hash(contract_8, left_padding=True), ] - tx_gas = [600000] tx = Transaction( sender=sender, to=contract_0, data=tx_data[d], - gas_limit=tx_gas[g], error=_exc, ) diff --git a/tests/ported_static/stCreateTest/test_create_collision_results.py b/tests/ported_static/stCreateTest/test_create_collision_results.py index 148c283bfb7..bb10874ad2c 100644 --- a/tests/ported_static/stCreateTest/test_create_collision_results.py +++ b/tests/ported_static/stCreateTest/test_create_collision_results.py @@ -240,13 +240,11 @@ def test_create_collision_results( Bytes("01"), Bytes("02"), ] - tx_gas = [16777216] tx = Transaction( sender=sender, to=contract_2, data=tx_data[d], - gas_limit=tx_gas[g], ) post = { diff --git a/tests/ported_static/stCreateTest/test_create_collision_to_empty2.py b/tests/ported_static/stCreateTest/test_create_collision_to_empty2.py index 811c004c64b..06f61cbd121 100644 --- a/tests/ported_static/stCreateTest/test_create_collision_to_empty2.py +++ b/tests/ported_static/stCreateTest/test_create_collision_to_empty2.py @@ -258,7 +258,10 @@ def test_create_collision_to_empty2( # `OPCODE_CREATE_BASE` from 32_000 to 9_000, so reduce the # original 54_000 budget by the same delta to track the cliff. create_base_delta = 32000 - fork.gas_costs().OPCODE_CREATE_BASE - tx_gas = [600000, 54000 - create_base_delta] + tx_gas = [ + None if fork.is_eip_enabled(8037) else 600000, + 54000 - create_base_delta, + ] tx_value = [0, 1] tx = Transaction( diff --git a/tests/ported_static/stCreateTest/test_create_oo_gafter_init_code_revert2.py b/tests/ported_static/stCreateTest/test_create_oo_gafter_init_code_revert2.py index b6178a31982..b0099e00d23 100644 --- a/tests/ported_static/stCreateTest/test_create_oo_gafter_init_code_revert2.py +++ b/tests/ported_static/stCreateTest/test_create_oo_gafter_init_code_revert2.py @@ -186,13 +186,11 @@ def test_create_oo_gafter_init_code_revert2( Hash(contract_1, left_padding=True), Hash(contract_2, left_padding=True), ] - tx_gas = [175000] tx = Transaction( sender=sender, to=contract_0, data=tx_data[d], - gas_limit=tx_gas[g], error=_exc, ) diff --git a/tests/ported_static/stDelegatecallTestHomestead/test_delegatecode_dynamic_code.py b/tests/ported_static/stDelegatecallTestHomestead/test_delegatecode_dynamic_code.py index 13cf2e5404a..c4bc870fc4b 100644 --- a/tests/ported_static/stDelegatecallTestHomestead/test_delegatecode_dynamic_code.py +++ b/tests/ported_static/stDelegatecallTestHomestead/test_delegatecode_dynamic_code.py @@ -77,7 +77,6 @@ def test_delegatecode_dynamic_code( sender=sender, to=contract_0, data=Bytes(""), - gas_limit=453081, ) post = { diff --git a/tests/ported_static/stEIP3651_warmcoinbase/test_coinbase_warm_account_call_gas.py b/tests/ported_static/stEIP3651_warmcoinbase/test_coinbase_warm_account_call_gas.py index 6bdb6c49c1a..7e8250c3ad0 100644 --- a/tests/ported_static/stEIP3651_warmcoinbase/test_coinbase_warm_account_call_gas.py +++ b/tests/ported_static/stEIP3651_warmcoinbase/test_coinbase_warm_account_call_gas.py @@ -270,13 +270,11 @@ def test_coinbase_warm_account_call_gas( Bytes("693c6139") + Hash(0x6), Bytes("693c6139") + Hash(0x7), ] - tx_gas = [80000] tx = Transaction( sender=sender, to=target, data=tx_data[d], - gas_limit=tx_gas[g], nonce=1, ) diff --git a/tests/ported_static/stEIP3855_push0/test_push0.py b/tests/ported_static/stEIP3855_push0/test_push0.py index 45fdb4aa609..9bc2c28c7de 100644 --- a/tests/ported_static/stEIP3855_push0/test_push0.py +++ b/tests/ported_static/stEIP3855_push0/test_push0.py @@ -275,13 +275,11 @@ def test_push0( contract_5, contract_7, ] - tx_gas = [700000] tx = Transaction( sender=sender, to=contract_0, data=tx_data[d], - gas_limit=tx_gas[g], error=_exc, ) diff --git a/tests/ported_static/stMemoryStressTest/test_return_bounds.py b/tests/ported_static/stMemoryStressTest/test_return_bounds.py index ae8816c1e31..8918cd9d9d0 100644 --- a/tests/ported_static/stMemoryStressTest/test_return_bounds.py +++ b/tests/ported_static/stMemoryStressTest/test_return_bounds.py @@ -429,7 +429,7 @@ def test_return_bounds( tx_data = [ Bytes(""), ] - tx_gas = [150000, 500000, 15000000] + tx_gas = [150000, None if fork.is_eip_enabled(8037) else 500000, 15000000] tx_value = [1] tx = Transaction( diff --git a/tests/ported_static/stMemoryTest/test_calldatacopy_dejavu2.py b/tests/ported_static/stMemoryTest/test_calldatacopy_dejavu2.py index 3bddb40d23d..11b98c19a46 100644 --- a/tests/ported_static/stMemoryTest/test_calldatacopy_dejavu2.py +++ b/tests/ported_static/stMemoryTest/test_calldatacopy_dejavu2.py @@ -64,7 +64,6 @@ def test_calldatacopy_dejavu2( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, value=10, ) diff --git a/tests/ported_static/stPreCompiledContracts2/test_call_sha256_1_nonzero_value.py b/tests/ported_static/stPreCompiledContracts2/test_call_sha256_1_nonzero_value.py index 9acde20fa83..465999d54a6 100644 --- a/tests/ported_static/stPreCompiledContracts2/test_call_sha256_1_nonzero_value.py +++ b/tests/ported_static/stPreCompiledContracts2/test_call_sha256_1_nonzero_value.py @@ -75,7 +75,6 @@ def test_call_sha256_1_nonzero_value( sender=sender, to=target, data=Bytes(""), - gas_limit=365224, value=0x186A0, ) diff --git a/tests/ported_static/stPreCompiledContracts2/test_ecrecover_short_buff.py b/tests/ported_static/stPreCompiledContracts2/test_ecrecover_short_buff.py index cffcc678170..0d6b1fcae22 100644 --- a/tests/ported_static/stPreCompiledContracts2/test_ecrecover_short_buff.py +++ b/tests/ported_static/stPreCompiledContracts2/test_ecrecover_short_buff.py @@ -155,7 +155,6 @@ def test_ecrecover_short_buff( sender=sender, to=contract_0, data=Bytes("00"), - gas_limit=7400000, value=0x186A0, nonce=1, ) diff --git a/tests/ported_static/stReturnDataTest/test_returndatasize_after_successful_callcode.py b/tests/ported_static/stReturnDataTest/test_returndatasize_after_successful_callcode.py index 3aed1ab7ffa..71ed00ddf6c 100644 --- a/tests/ported_static/stReturnDataTest/test_returndatasize_after_successful_callcode.py +++ b/tests/ported_static/stReturnDataTest/test_returndatasize_after_successful_callcode.py @@ -80,7 +80,6 @@ def test_returndatasize_after_successful_callcode( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, ) post = {target: Account(storage={0: 6})} diff --git a/tests/ported_static/stReturnDataTest/test_subcall_return_more_then_expected.py b/tests/ported_static/stReturnDataTest/test_subcall_return_more_then_expected.py index 1f2141adffe..50a5e19297b 100644 --- a/tests/ported_static/stReturnDataTest/test_subcall_return_more_then_expected.py +++ b/tests/ported_static/stReturnDataTest/test_subcall_return_more_then_expected.py @@ -228,7 +228,6 @@ def test_subcall_return_more_then_expected( sender=sender, to=target, data=Bytes(""), - gas_limit=400000, value=1, ) diff --git a/tests/ported_static/stRevertTest/test_loop_calls_then_revert.py b/tests/ported_static/stRevertTest/test_loop_calls_then_revert.py index c64f15352a7..416336db74d 100644 --- a/tests/ported_static/stRevertTest/test_loop_calls_then_revert.py +++ b/tests/ported_static/stRevertTest/test_loop_calls_then_revert.py @@ -75,7 +75,6 @@ def test_loop_calls_then_revert( sender=sender, to=target, data=Bytes(""), - gas_limit=10000000, ) post = { diff --git a/tests/ported_static/stRevertTest/test_revert_opcode_multiple_sub_calls.py b/tests/ported_static/stRevertTest/test_revert_opcode_multiple_sub_calls.py index 416bab3330a..b91a3a76eb5 100644 --- a/tests/ported_static/stRevertTest/test_revert_opcode_multiple_sub_calls.py +++ b/tests/ported_static/stRevertTest/test_revert_opcode_multiple_sub_calls.py @@ -584,7 +584,12 @@ def test_revert_opcode_multiple_sub_calls( Hash(addr_3, left_padding=True), Hash(addr_4, left_padding=True), ] - tx_gas = [800000, 126200, 160000, 50000] + tx_gas = [ + None if fork.is_eip_enabled(8037) else 800000, + 126200, + None if fork.is_eip_enabled(8037) else 160000, + 50000, + ] tx_value = [0, 10] tx = Transaction( diff --git a/tests/ported_static/stSStoreTest/test_sstore_change_from_external_call_in_init_code.py b/tests/ported_static/stSStoreTest/test_sstore_change_from_external_call_in_init_code.py index 71d0e703e00..b73e6cd6c83 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_change_from_external_call_in_init_code.py +++ b/tests/ported_static/stSStoreTest/test_sstore_change_from_external_call_in_init_code.py @@ -561,21 +561,11 @@ def test_sstore_change_from_external_call_in_init_code( ) + Op.STOP, ] - # Fork-aware gas budget: contract-creation intrinsic from the - # fork's calculator, plus the bytecode's own gas cost (which - # already includes the gas forwarded to inner CALLs via opcode - # metadata). - intrinsic = fork.transaction_intrinsic_cost_calculator()( - calldata=tx_data[d], - contract_creation=True, - ) - tx_gas = [intrinsic + tx_data[d].gas_cost(fork)] tx = Transaction( sender=sender, to=None, data=tx_data[d], - gas_limit=tx_gas[g], error=_exc, ) diff --git a/tests/ported_static/stSelfBalance/test_self_balance.py b/tests/ported_static/stSelfBalance/test_self_balance.py index cbaba3c11c2..65d17a6164c 100644 --- a/tests/ported_static/stSelfBalance/test_self_balance.py +++ b/tests/ported_static/stSelfBalance/test_self_balance.py @@ -54,7 +54,6 @@ def test_self_balance( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, ) post = {target: Account(storage={1: 500})} diff --git a/tests/ported_static/stSelfBalance/test_self_balance_equals_balance.py b/tests/ported_static/stSelfBalance/test_self_balance_equals_balance.py index 0bd75d31ebc..d43b1979d21 100644 --- a/tests/ported_static/stSelfBalance/test_self_balance_equals_balance.py +++ b/tests/ported_static/stSelfBalance/test_self_balance_equals_balance.py @@ -58,7 +58,6 @@ def test_self_balance_equals_balance( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, ) post = {target: Account(storage={1: 1})} diff --git a/tests/ported_static/stSelfBalance/test_self_balance_gas_cost.py b/tests/ported_static/stSelfBalance/test_self_balance_gas_cost.py index 99909d00883..d6d09dbefbe 100644 --- a/tests/ported_static/stSelfBalance/test_self_balance_gas_cost.py +++ b/tests/ported_static/stSelfBalance/test_self_balance_gas_cost.py @@ -63,7 +63,6 @@ def test_self_balance_gas_cost( sender=sender, to=target, data=Bytes(""), - gas_limit=100000, ) post = {target: Account(storage={1: 5})} diff --git a/tests/ported_static/stSolidityTest/test_test_overflow.py b/tests/ported_static/stSolidityTest/test_test_overflow.py index 2343732d411..a51db794ecd 100644 --- a/tests/ported_static/stSolidityTest/test_test_overflow.py +++ b/tests/ported_static/stSolidityTest/test_test_overflow.py @@ -158,7 +158,6 @@ def test_test_overflow( sender=sender, to=target, data=Bytes("c0406226"), - gas_limit=100000, ) post = {target: Account(storage={0: 1})} diff --git a/tests/ported_static/stSolidityTest/test_test_structures_and_variabless.py b/tests/ported_static/stSolidityTest/test_test_structures_and_variabless.py index 594bbf9fd21..b8d96e19a12 100644 --- a/tests/ported_static/stSolidityTest/test_test_structures_and_variabless.py +++ b/tests/ported_static/stSolidityTest/test_test_structures_and_variabless.py @@ -230,7 +230,6 @@ def test_test_structures_and_variabless( sender=sender, to=target, data=Bytes("c0406226"), - gas_limit=350000, value=100, ) diff --git a/tests/ported_static/stStaticCall/test_static_call_ask_more_gas_on_depth2_then_transaction_has.py b/tests/ported_static/stStaticCall/test_static_call_ask_more_gas_on_depth2_then_transaction_has.py index 1ff95945afb..d47a87e4f8f 100644 --- a/tests/ported_static/stStaticCall/test_static_call_ask_more_gas_on_depth2_then_transaction_has.py +++ b/tests/ported_static/stStaticCall/test_static_call_ask_more_gas_on_depth2_then_transaction_has.py @@ -211,13 +211,11 @@ def test_static_call_ask_more_gas_on_depth2_then_transaction_has( Hash(addr, left_padding=True), Hash(addr_4, left_padding=True), ] - tx_gas = [600000] tx = Transaction( sender=sender, to=target, data=tx_data[d], - gas_limit=tx_gas[g], error=_exc, ) diff --git a/tests/ported_static/stStaticCall/test_static_call_contract_to_create_contract_oog.py b/tests/ported_static/stStaticCall/test_static_call_contract_to_create_contract_oog.py index 0c32de25970..a331700c24f 100644 --- a/tests/ported_static/stStaticCall/test_static_call_contract_to_create_contract_oog.py +++ b/tests/ported_static/stStaticCall/test_static_call_contract_to_create_contract_oog.py @@ -133,7 +133,6 @@ def test_static_call_contract_to_create_contract_oog( sender=sender, to=contract_0, data=tx_data[d], - gas_limit=tx_gas[g], value=tx_value[v], error=_exc, ) diff --git a/tests/ported_static/stStaticCall/test_static_call_recursive_bomb3.py b/tests/ported_static/stStaticCall/test_static_call_recursive_bomb3.py index 352dacad6d4..86ae8c506de 100644 --- a/tests/ported_static/stStaticCall/test_static_call_recursive_bomb3.py +++ b/tests/ported_static/stStaticCall/test_static_call_recursive_bomb3.py @@ -85,7 +85,6 @@ def test_static_call_recursive_bomb3( sender=sender, to=target, data=Bytes(""), - gas_limit=1000000, value=0x186A0, ) diff --git a/tests/ported_static/stStaticCall/test_static_call_sha256_1_nonzero_value.py b/tests/ported_static/stStaticCall/test_static_call_sha256_1_nonzero_value.py index 6bd0f39acaa..86648626d51 100644 --- a/tests/ported_static/stStaticCall/test_static_call_sha256_1_nonzero_value.py +++ b/tests/ported_static/stStaticCall/test_static_call_sha256_1_nonzero_value.py @@ -93,7 +93,6 @@ def test_static_call_sha256_1_nonzero_value( sender=sender, to=target, data=Bytes(""), - gas_limit=365224, value=0x186A0, ) diff --git a/tests/ported_static/stStaticCall/test_static_callcall_00_ooge_1.py b/tests/ported_static/stStaticCall/test_static_callcall_00_ooge_1.py index b1f3f3deb06..6a9d35e6368 100644 --- a/tests/ported_static/stStaticCall/test_static_callcall_00_ooge_1.py +++ b/tests/ported_static/stStaticCall/test_static_callcall_00_ooge_1.py @@ -219,13 +219,11 @@ def test_static_callcall_00_ooge_1( Hash(addr, left_padding=True), Hash(addr_4, left_padding=True), ] - tx_gas = [380066] tx = Transaction( sender=sender, to=target, data=tx_data[d], - gas_limit=tx_gas[g], error=_exc, ) diff --git a/tests/ported_static/stStaticCall/test_static_callcallcode_01_ooge_2.py b/tests/ported_static/stStaticCall/test_static_callcallcode_01_ooge_2.py index 118b967f0dd..4387a2bd1b3 100644 --- a/tests/ported_static/stStaticCall/test_static_callcallcode_01_ooge_2.py +++ b/tests/ported_static/stStaticCall/test_static_callcallcode_01_ooge_2.py @@ -132,13 +132,11 @@ def test_static_callcallcode_01_ooge_2( Hash(addr_2, left_padding=True), Hash(addr_3, left_padding=True), ] - tx_gas = [172000] tx = Transaction( sender=sender, to=target, data=tx_data[d], - gas_limit=tx_gas[g], ) post = {target: Account(storage={0: 1, 1: 1})} diff --git a/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_011_ooge.py b/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_011_ooge.py index 255691213b7..33693d1c5eb 100644 --- a/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_011_ooge.py +++ b/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_011_ooge.py @@ -147,13 +147,11 @@ def test_static_callcallcodecallcode_011_ooge( Hash(addr_3, left_padding=True), Hash(addr_4, left_padding=True), ] - tx_gas = [172000] tx = Transaction( sender=sender, to=target, data=tx_data[d], - gas_limit=tx_gas[g], ) post = {target: Account(storage={0: 1, 1: 1})} diff --git a/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_011_ooge_2.py b/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_011_ooge_2.py index 9b73ceaab58..26f1bd02f04 100644 --- a/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_011_ooge_2.py +++ b/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_011_ooge_2.py @@ -179,13 +179,11 @@ def test_static_callcallcodecallcode_011_ooge_2( Hash(addr_2, left_padding=True), Hash(addr_3, left_padding=True), ] - tx_gas = [172000] tx = Transaction( sender=sender, to=target, data=tx_data[d], - gas_limit=tx_gas[g], error=_exc, ) diff --git a/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_011_oogm_after.py b/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_011_oogm_after.py index f2dd613dedf..f35c3612ed7 100644 --- a/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_011_oogm_after.py +++ b/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_011_oogm_after.py @@ -164,13 +164,11 @@ def test_static_callcallcodecallcode_011_oogm_after( Hash(addr, left_padding=True), Hash(addr_2, left_padding=True), ] - tx_gas = [172000] tx = Transaction( sender=sender, to=target, data=tx_data[d], - gas_limit=tx_gas[g], ) post = {target: Account(storage={0: 0, 1: 1})} diff --git a/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_011_oogm_after2.py b/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_011_oogm_after2.py index 91e9a676fb2..4d8657cb848 100644 --- a/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_011_oogm_after2.py +++ b/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_011_oogm_after2.py @@ -161,13 +161,11 @@ def test_static_callcallcodecallcode_011_oogm_after2( Hash(addr, left_padding=True), Hash(addr_2, left_padding=True), ] - tx_gas = [172000] tx = Transaction( sender=sender, to=target, data=tx_data[d], - gas_limit=tx_gas[g], ) post = {target: Account(storage={0: 0, 1: 1})} diff --git a/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_011_oogm_after_1.py b/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_011_oogm_after_1.py index bfbc6735e35..1cfff633430 100644 --- a/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_011_oogm_after_1.py +++ b/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_011_oogm_after_1.py @@ -164,13 +164,11 @@ def test_static_callcallcodecallcode_011_oogm_after_1( Hash(addr, left_padding=True), Hash(addr_2, left_padding=True), ] - tx_gas = [172000] tx = Transaction( sender=sender, to=target, data=tx_data[d], - gas_limit=tx_gas[g], ) post = {target: Account(storage={0: 0, 1: 1})} diff --git a/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_011_oogm_after_2.py b/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_011_oogm_after_2.py index a8854ec54cc..4c6559ee3cd 100644 --- a/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_011_oogm_after_2.py +++ b/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_011_oogm_after_2.py @@ -167,13 +167,11 @@ def test_static_callcallcodecallcode_011_oogm_after_2( Hash(addr, left_padding=True), Hash(addr_2, left_padding=True), ] - tx_gas = [172000] tx = Transaction( sender=sender, to=target, data=tx_data[d], - gas_limit=tx_gas[g], ) post = {target: Account(storage={0: 0, 1: 1})} diff --git a/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_011_oogm_before.py b/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_011_oogm_before.py index 9c95f6777cf..56359c1b095 100644 --- a/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_011_oogm_before.py +++ b/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_011_oogm_before.py @@ -165,13 +165,11 @@ def test_static_callcallcodecallcode_011_oogm_before( Hash(addr_2, left_padding=True), Hash(addr_3, left_padding=True), ] - tx_gas = [172000] tx = Transaction( sender=sender, to=target, data=tx_data[d], - gas_limit=tx_gas[g], ) post = {target: Account(storage={0: 1, 1: 1})} diff --git a/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_011_oogm_before2.py b/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_011_oogm_before2.py index b0ff081f608..8d3516646cf 100644 --- a/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_011_oogm_before2.py +++ b/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_011_oogm_before2.py @@ -213,13 +213,11 @@ def test_static_callcallcodecallcode_011_oogm_before2( Hash(addr_3, left_padding=True), Hash(addr_4, left_padding=True), ] - tx_gas = [172000] tx = Transaction( sender=sender, to=target, data=tx_data[d], - gas_limit=tx_gas[g], error=_exc, ) diff --git a/tests/ported_static/stStaticCall/test_static_callcodecall_10_ooge.py b/tests/ported_static/stStaticCall/test_static_callcodecall_10_ooge.py index 52635e65517..1cac6ce2c65 100644 --- a/tests/ported_static/stStaticCall/test_static_callcodecall_10_ooge.py +++ b/tests/ported_static/stStaticCall/test_static_callcodecall_10_ooge.py @@ -129,13 +129,11 @@ def test_static_callcodecall_10_ooge( Hash(addr_2, left_padding=True), Hash(addr_3, left_padding=True), ] - tx_gas = [172000] tx = Transaction( sender=sender, to=target, data=tx_data[d], - gas_limit=tx_gas[g], ) post = {target: Account(storage={0: 1, 1: 1})} diff --git a/tests/ported_static/stStaticCall/test_static_callcodecall_10_ooge_2.py b/tests/ported_static/stStaticCall/test_static_callcodecall_10_ooge_2.py index a81f4ac172c..a8672c78552 100644 --- a/tests/ported_static/stStaticCall/test_static_callcodecall_10_ooge_2.py +++ b/tests/ported_static/stStaticCall/test_static_callcodecall_10_ooge_2.py @@ -134,13 +134,11 @@ def test_static_callcodecall_10_ooge_2( Hash(addr_2, left_padding=True), Hash(addr_3, left_padding=True), ] - tx_gas = [172000] tx = Transaction( sender=sender, to=target, data=tx_data[d], - gas_limit=tx_gas[g], ) post = { diff --git a/tests/ported_static/stStaticCall/test_static_callcodecallcall_100_ooge.py b/tests/ported_static/stStaticCall/test_static_callcodecallcall_100_ooge.py index 2a349847b34..072c438d4b1 100644 --- a/tests/ported_static/stStaticCall/test_static_callcodecallcall_100_ooge.py +++ b/tests/ported_static/stStaticCall/test_static_callcodecallcall_100_ooge.py @@ -151,13 +151,11 @@ def test_static_callcodecallcall_100_ooge( Hash(addr_3, left_padding=True), Hash(addr_4, left_padding=True), ] - tx_gas = [172000] tx = Transaction( sender=sender, to=target, data=tx_data[d], - gas_limit=tx_gas[g], ) post = {target: Account(storage={0: 1, 1: 1})} diff --git a/tests/ported_static/stStaticCall/test_static_callcodecallcall_100_ooge2.py b/tests/ported_static/stStaticCall/test_static_callcodecallcall_100_ooge2.py index ddc828b6da4..d16e1cb0838 100644 --- a/tests/ported_static/stStaticCall/test_static_callcodecallcall_100_ooge2.py +++ b/tests/ported_static/stStaticCall/test_static_callcodecallcall_100_ooge2.py @@ -146,13 +146,11 @@ def test_static_callcodecallcall_100_ooge2( Hash(addr_3, left_padding=True), Hash(addr_4, left_padding=True), ] - tx_gas = [172000] tx = Transaction( sender=sender, to=target, data=tx_data[d], - gas_limit=tx_gas[g], ) post = {target: Account(storage={0: 1, 1: 1})} diff --git a/tests/ported_static/stStaticCall/test_static_callcodecallcall_100_oogm_after_3.py b/tests/ported_static/stStaticCall/test_static_callcodecallcall_100_oogm_after_3.py index c4003605e60..23f939bb74d 100644 --- a/tests/ported_static/stStaticCall/test_static_callcodecallcall_100_oogm_after_3.py +++ b/tests/ported_static/stStaticCall/test_static_callcodecallcall_100_oogm_after_3.py @@ -159,14 +159,12 @@ def test_static_callcodecallcall_100_oogm_after_3( tx_data = [ Bytes(""), ] - tx_gas = [172000] tx_value = [0, 1] tx = Transaction( sender=sender, to=target, data=tx_data[d], - gas_limit=tx_gas[g], value=tx_value[v], error=_exc, ) diff --git a/tests/ported_static/stStaticCall/test_static_callcodecallcall_100_oogm_before.py b/tests/ported_static/stStaticCall/test_static_callcodecallcall_100_oogm_before.py index 894c008a900..80aa5dffc1a 100644 --- a/tests/ported_static/stStaticCall/test_static_callcodecallcall_100_oogm_before.py +++ b/tests/ported_static/stStaticCall/test_static_callcodecallcall_100_oogm_before.py @@ -161,13 +161,11 @@ def test_static_callcodecallcall_100_oogm_before( Hash(addr_2, left_padding=True), Hash(addr_3, left_padding=True), ] - tx_gas = [172000] tx = Transaction( sender=sender, to=target, data=tx_data[d], - gas_limit=tx_gas[g], ) post = {target: Account(storage={0: 1, 1: 1})} diff --git a/tests/ported_static/stStaticCall/test_static_callcodecallcall_100_oogm_before2.py b/tests/ported_static/stStaticCall/test_static_callcodecallcall_100_oogm_before2.py index 791b780df05..64a7e3dd3b2 100644 --- a/tests/ported_static/stStaticCall/test_static_callcodecallcall_100_oogm_before2.py +++ b/tests/ported_static/stStaticCall/test_static_callcodecallcall_100_oogm_before2.py @@ -169,14 +169,12 @@ def test_static_callcodecallcall_100_oogm_before2( Hash(addr_2, left_padding=True), Hash(addr_3, left_padding=True), ] - tx_gas = [172000] tx_value = [0, 1] tx = Transaction( sender=sender, to=target, data=tx_data[d], - gas_limit=tx_gas[g], value=tx_value[v], ) diff --git a/tests/ported_static/stStaticCall/test_static_callcodecallcallcode_101_ooge_2.py b/tests/ported_static/stStaticCall/test_static_callcodecallcallcode_101_ooge_2.py index bd879aec938..33445534448 100644 --- a/tests/ported_static/stStaticCall/test_static_callcodecallcallcode_101_ooge_2.py +++ b/tests/ported_static/stStaticCall/test_static_callcodecallcallcode_101_ooge_2.py @@ -114,7 +114,6 @@ def test_static_callcodecallcallcode_101_ooge_2( sender=sender, to=target, data=Bytes(""), - gas_limit=172000, ) post = {target: Account(storage={0: 1, 1: 1})} diff --git a/tests/ported_static/stStaticCall/test_static_callcodecallcallcode_101_oogm_after.py b/tests/ported_static/stStaticCall/test_static_callcodecallcallcode_101_oogm_after.py index 80bbe4d391a..eb4ef9fb520 100644 --- a/tests/ported_static/stStaticCall/test_static_callcodecallcallcode_101_oogm_after.py +++ b/tests/ported_static/stStaticCall/test_static_callcodecallcallcode_101_oogm_after.py @@ -119,7 +119,6 @@ def test_static_callcodecallcallcode_101_oogm_after( sender=sender, to=target, data=Bytes(""), - gas_limit=172000, ) post = {target: Account(storage={0: 0, 1: 1})} diff --git a/tests/ported_static/stStaticCall/test_static_callcodecallcallcode_101_oogm_after2.py b/tests/ported_static/stStaticCall/test_static_callcodecallcallcode_101_oogm_after2.py index 22aa6944777..d74d78f27ca 100644 --- a/tests/ported_static/stStaticCall/test_static_callcodecallcallcode_101_oogm_after2.py +++ b/tests/ported_static/stStaticCall/test_static_callcodecallcallcode_101_oogm_after2.py @@ -143,14 +143,12 @@ def test_static_callcodecallcallcode_101_oogm_after2( tx_data = [ Bytes(""), ] - tx_gas = [172000] tx_value = [0, 1] tx = Transaction( sender=sender, to=contract_0, data=tx_data[d], - gas_limit=tx_gas[g], value=tx_value[v], ) diff --git a/tests/ported_static/stStaticCall/test_static_callcodecallcallcode_101_oogm_before.py b/tests/ported_static/stStaticCall/test_static_callcodecallcallcode_101_oogm_before.py index 5360979ac9c..433a1c911ee 100644 --- a/tests/ported_static/stStaticCall/test_static_callcodecallcallcode_101_oogm_before.py +++ b/tests/ported_static/stStaticCall/test_static_callcodecallcallcode_101_oogm_before.py @@ -116,7 +116,6 @@ def test_static_callcodecallcallcode_101_oogm_before( sender=sender, to=target, data=Bytes(""), - gas_limit=172000, ) post = {target: Account(storage={0: 1, 1: 1})} diff --git a/tests/ported_static/stStaticCall/test_static_callcodecallcallcode_101_oogm_before2.py b/tests/ported_static/stStaticCall/test_static_callcodecallcallcode_101_oogm_before2.py index ec321a4558a..b0140877121 100644 --- a/tests/ported_static/stStaticCall/test_static_callcodecallcallcode_101_oogm_before2.py +++ b/tests/ported_static/stStaticCall/test_static_callcodecallcallcode_101_oogm_before2.py @@ -139,14 +139,12 @@ def test_static_callcodecallcallcode_101_oogm_before2( tx_data = [ Bytes(""), ] - tx_gas = [172000] tx_value = [0, 1] tx = Transaction( sender=sender, to=target, data=tx_data[d], - gas_limit=tx_gas[g], value=tx_value[v], ) diff --git a/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_110_ooge.py b/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_110_ooge.py index fb9b691d681..83602d3f93a 100644 --- a/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_110_ooge.py +++ b/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_110_ooge.py @@ -112,7 +112,6 @@ def test_static_callcodecallcodecall_110_ooge( sender=sender, to=target, data=Bytes(""), - gas_limit=172000, ) post = {target: Account(storage={0: 1, 1: 1})} diff --git a/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_110_ooge2.py b/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_110_ooge2.py index 7c371ac945e..3f568e1b407 100644 --- a/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_110_ooge2.py +++ b/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_110_ooge2.py @@ -141,14 +141,12 @@ def test_static_callcodecallcodecall_110_ooge2( tx_data = [ Bytes(""), ] - tx_gas = [172000] tx_value = [0, 1, 2] tx = Transaction( sender=sender, to=target, data=tx_data[d], - gas_limit=tx_gas[g], value=tx_value[v], ) diff --git a/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_110_oogm_after.py b/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_110_oogm_after.py index b121cac4afb..5ebc302095a 100644 --- a/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_110_oogm_after.py +++ b/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_110_oogm_after.py @@ -114,7 +114,6 @@ def test_static_callcodecallcodecall_110_oogm_after( sender=sender, to=target, data=Bytes(""), - gas_limit=172000, ) post = {target: Account(storage={0: 0, 1: 1})} diff --git a/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_110_oogm_after2.py b/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_110_oogm_after2.py index 6bfd84549df..06c28b78bc4 100644 --- a/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_110_oogm_after2.py +++ b/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_110_oogm_after2.py @@ -143,14 +143,12 @@ def test_static_callcodecallcodecall_110_oogm_after2( tx_data = [ Bytes(""), ] - tx_gas = [172000] tx_value = [0, 1, 2] tx = Transaction( sender=sender, to=target, data=tx_data[d], - gas_limit=tx_gas[g], value=tx_value[v], ) diff --git a/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_110_oogm_after_2.py b/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_110_oogm_after_2.py index 90496314497..15539b15784 100644 --- a/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_110_oogm_after_2.py +++ b/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_110_oogm_after_2.py @@ -114,7 +114,6 @@ def test_static_callcodecallcodecall_110_oogm_after_2( sender=sender, to=target, data=Bytes(""), - gas_limit=172000, ) post = {target: Account(storage={0: 0, 1: 1})} diff --git a/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_110_oogm_after_3.py b/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_110_oogm_after_3.py index cbc5beb1ba3..3d562c72148 100644 --- a/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_110_oogm_after_3.py +++ b/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_110_oogm_after_3.py @@ -116,7 +116,6 @@ def test_static_callcodecallcodecall_110_oogm_after_3( sender=sender, to=target, data=Bytes(""), - gas_limit=172000, ) post = {target: Account(storage={0: 0, 1: 1})} diff --git a/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_110_oogm_before.py b/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_110_oogm_before.py index 139114b474a..0d55896f985 100644 --- a/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_110_oogm_before.py +++ b/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_110_oogm_before.py @@ -112,7 +112,6 @@ def test_static_callcodecallcodecall_110_oogm_before( sender=sender, to=target, data=Bytes(""), - gas_limit=172000, ) post = {target: Account(storage={0: 1, 1: 1})} diff --git a/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_110_oogm_before2.py b/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_110_oogm_before2.py index 972eaf216e2..69377cd05b2 100644 --- a/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_110_oogm_before2.py +++ b/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_110_oogm_before2.py @@ -141,14 +141,12 @@ def test_static_callcodecallcodecall_110_oogm_before2( tx_data = [ Bytes(""), ] - tx_gas = [172000] tx_value = [0, 1, 2] tx = Transaction( sender=sender, to=target, data=tx_data[d], - gas_limit=tx_gas[g], value=tx_value[v], ) diff --git a/tests/ported_static/stStaticCall/test_static_calldelcode_01_ooge.py b/tests/ported_static/stStaticCall/test_static_calldelcode_01_ooge.py index 5e85db2b185..bf4aa29155e 100644 --- a/tests/ported_static/stStaticCall/test_static_calldelcode_01_ooge.py +++ b/tests/ported_static/stStaticCall/test_static_calldelcode_01_ooge.py @@ -131,13 +131,11 @@ def test_static_calldelcode_01_ooge( Hash(addr_2, left_padding=True), Hash(addr_3, left_padding=True), ] - tx_gas = [172000] tx = Transaction( sender=sender, to=target, data=tx_data[d], - gas_limit=tx_gas[g], ) post = {target: Account(storage={0: 1, 1: 1})} diff --git a/tests/ported_static/stStaticCall/test_static_check_opcodes4.py b/tests/ported_static/stStaticCall/test_static_check_opcodes4.py index d212aec2a60..8c4a09aef3a 100644 --- a/tests/ported_static/stStaticCall/test_static_check_opcodes4.py +++ b/tests/ported_static/stStaticCall/test_static_check_opcodes4.py @@ -254,7 +254,7 @@ def test_static_check_opcodes4( tx_data = [ Bytes(""), ] - tx_gas = [50000, 335000] + tx_gas = [50000, None if fork.is_eip_enabled(8037) else 335000] tx_value = [0, 100] tx = Transaction( diff --git a/tests/ported_static/stStaticCall/test_static_check_opcodes5.py b/tests/ported_static/stStaticCall/test_static_check_opcodes5.py index 74c6fd24a52..ff557a77c24 100644 --- a/tests/ported_static/stStaticCall/test_static_check_opcodes5.py +++ b/tests/ported_static/stStaticCall/test_static_check_opcodes5.py @@ -552,7 +552,7 @@ def test_static_check_opcodes5( Hash(addr_4, left_padding=True), Hash(addr_5, left_padding=True), ] - tx_gas = [50000, 335000] + tx_gas = [50000, None if fork.is_eip_enabled(8037) else 335000] tx_value = [0, 100] tx = Transaction( diff --git a/tests/ported_static/stStaticCall/test_static_revert_opcode_calls.py b/tests/ported_static/stStaticCall/test_static_revert_opcode_calls.py index 80b13c69048..7de006e80b7 100644 --- a/tests/ported_static/stStaticCall/test_static_revert_opcode_calls.py +++ b/tests/ported_static/stStaticCall/test_static_revert_opcode_calls.py @@ -96,13 +96,11 @@ def test_static_revert_opcode_calls( tx_data = [ Bytes(""), ] - tx_gas = [460000, 88000] tx = Transaction( sender=sender, to=target, data=tx_data[d], - gas_limit=tx_gas[g], ) post = {target: Account(storage={1: 1})} diff --git a/tests/ported_static/stStaticFlagEnabled/test_callcode_to_precompile_from_called_contract.py b/tests/ported_static/stStaticFlagEnabled/test_callcode_to_precompile_from_called_contract.py index 75dcfd80640..d7083f62a5f 100644 --- a/tests/ported_static/stStaticFlagEnabled/test_callcode_to_precompile_from_called_contract.py +++ b/tests/ported_static/stStaticFlagEnabled/test_callcode_to_precompile_from_called_contract.py @@ -22,7 +22,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -618,7 +617,6 @@ def test_callcode_to_precompile_from_called_contract( sender=sender, to=contract_0, data=Bytes(""), - gas_limit=6000000 if fork >= Amsterdam else 4000000, value=100, ) diff --git a/tests/ported_static/stStaticFlagEnabled/test_callcode_to_precompile_from_contract_initialization.py b/tests/ported_static/stStaticFlagEnabled/test_callcode_to_precompile_from_contract_initialization.py index 8385f94ee28..fe7c501f17d 100644 --- a/tests/ported_static/stStaticFlagEnabled/test_callcode_to_precompile_from_contract_initialization.py +++ b/tests/ported_static/stStaticFlagEnabled/test_callcode_to_precompile_from_contract_initialization.py @@ -22,7 +22,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -518,7 +517,6 @@ def test_callcode_to_precompile_from_contract_initialization( data=Bytes( "7ffeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeed60005562012020620a00006000600073a0000000000000000000000000000000000000005afa507ffeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeed600155620a000051610a0055620b000051610b0055620a010051610a0155620b010051610b0155620a020051610a0255620b020051610b0255620a030051610a0355620b030051610b0355620a040051610a0455620b040051610b0455620a050051610a0555620b050051610b0555620a060051610a0655620b060051610b0655620a070051610a0755620b070051610b0755620a080051610a0855620b080051610b0855620a090051610a0955620b090051610b0955620a100051610a1055620b100051610b1055620a110051610a1155620b110051610b1155620a120051610a1255620b120051610b1255620a130051610a1355620b130051610b1355620a140051610a1455620b140051610b1455620a150051610a1555620b150051610b1555620a160051610a1655620b160051610b1655620a170051610a1755620b170051610b1755620a180051610a1855620b180051610b1855620a190051610a1955620b190051610b1955620a200051610a2055620b200051610b205500" # noqa: E501 ), - gas_limit=6000000 if fork >= Amsterdam else 4000000, value=100, ) diff --git a/tests/ported_static/stStaticFlagEnabled/test_callcode_to_precompile_from_transaction.py b/tests/ported_static/stStaticFlagEnabled/test_callcode_to_precompile_from_transaction.py index 21124fa254f..0af77fb08e3 100644 --- a/tests/ported_static/stStaticFlagEnabled/test_callcode_to_precompile_from_transaction.py +++ b/tests/ported_static/stStaticFlagEnabled/test_callcode_to_precompile_from_transaction.py @@ -21,7 +21,6 @@ StateTestFiller, Transaction, ) -from execution_testing.forks import Amsterdam from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -581,7 +580,6 @@ def test_callcode_to_precompile_from_transaction( sender=sender, to=contract_0, data=Bytes(""), - gas_limit=6000000 if fork >= Amsterdam else 4000000, value=100, ) diff --git a/tests/ported_static/stStaticFlagEnabled/test_delegatecall_to_precompile_from_called_contract.py b/tests/ported_static/stStaticFlagEnabled/test_delegatecall_to_precompile_from_called_contract.py index 8dcdce66aaf..39e1a5b852e 100644 --- a/tests/ported_static/stStaticFlagEnabled/test_delegatecall_to_precompile_from_called_contract.py +++ b/tests/ported_static/stStaticFlagEnabled/test_delegatecall_to_precompile_from_called_contract.py @@ -452,7 +452,6 @@ def test_delegatecall_to_precompile_from_called_contract( sender=sender, to=contract_0, data=Bytes(""), - gas_limit=4000000, value=100, ) diff --git a/tests/ported_static/stStaticFlagEnabled/test_delegatecall_to_precompile_from_contract_initialization.py b/tests/ported_static/stStaticFlagEnabled/test_delegatecall_to_precompile_from_contract_initialization.py index df7fecef5c6..95295711d41 100644 --- a/tests/ported_static/stStaticFlagEnabled/test_delegatecall_to_precompile_from_contract_initialization.py +++ b/tests/ported_static/stStaticFlagEnabled/test_delegatecall_to_precompile_from_contract_initialization.py @@ -385,7 +385,6 @@ def test_delegatecall_to_precompile_from_contract_initialization( data=Bytes( "7ffeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeed60005562012020620a00006000600073a0000000000000000000000000000000000000005afa507ffeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeed600155620a000051610a0055620a110051610a1155620a010051610a0155620a120051610a1255620a020051610a0255620a130051610a1355620a030051610a0355620a140051610a1455620a040051610a0455620a150051610a1555620a050051610a0555620a160051610a1655620a060051610a0655620a170051610a1755620a070051610a0755620a180051610a1855620a080051610a0855620a190051610a1955620a090051610a0955620a200051610a2055620a100051610a105500" # noqa: E501 ), - gas_limit=4000000, value=100, ) diff --git a/tests/ported_static/stStaticFlagEnabled/test_delegatecall_to_precompile_from_transaction.py b/tests/ported_static/stStaticFlagEnabled/test_delegatecall_to_precompile_from_transaction.py index 427cc8978b5..4c4d0565624 100644 --- a/tests/ported_static/stStaticFlagEnabled/test_delegatecall_to_precompile_from_transaction.py +++ b/tests/ported_static/stStaticFlagEnabled/test_delegatecall_to_precompile_from_transaction.py @@ -415,7 +415,6 @@ def test_delegatecall_to_precompile_from_transaction( sender=sender, to=contract_0, data=Bytes(""), - gas_limit=4000000, value=100, ) diff --git a/tests/ported_static/stSystemOperationsTest/test_ab_acalls_suicide0.py b/tests/ported_static/stSystemOperationsTest/test_ab_acalls_suicide0.py index a8876a5fd6a..ccf258d6303 100644 --- a/tests/ported_static/stSystemOperationsTest/test_ab_acalls_suicide0.py +++ b/tests/ported_static/stSystemOperationsTest/test_ab_acalls_suicide0.py @@ -92,7 +92,6 @@ def test_ab_acalls_suicide0( sender=sender, to=target, data=Bytes(""), - gas_limit=10000000, value=0x186A0, ) diff --git a/tests/ported_static/stSystemOperationsTest/test_call10.py b/tests/ported_static/stSystemOperationsTest/test_call10.py index 9ae1eb3184d..6e125fec2c7 100644 --- a/tests/ported_static/stSystemOperationsTest/test_call10.py +++ b/tests/ported_static/stSystemOperationsTest/test_call10.py @@ -75,7 +75,6 @@ def test_call10( sender=sender, to=target, data=Bytes(""), - gas_limit=200000, value=10, ) diff --git a/tests/ported_static/stSystemOperationsTest/test_call_to_name_registrator_address_too_big_right.py b/tests/ported_static/stSystemOperationsTest/test_call_to_name_registrator_address_too_big_right.py index 2d9970a1547..57b2d4249c9 100644 --- a/tests/ported_static/stSystemOperationsTest/test_call_to_name_registrator_address_too_big_right.py +++ b/tests/ported_static/stSystemOperationsTest/test_call_to_name_registrator_address_too_big_right.py @@ -92,7 +92,6 @@ def test_call_to_name_registrator_address_too_big_right( sender=sender, to=target, data=Bytes(""), - gas_limit=300000, value=0x186A0, ) diff --git a/tests/ported_static/stTransactionTest/test_no_src_account_create.py b/tests/ported_static/stTransactionTest/test_no_src_account_create.py index eb70f1d3636..fd82a83d143 100644 --- a/tests/ported_static/stTransactionTest/test_no_src_account_create.py +++ b/tests/ported_static/stTransactionTest/test_no_src_account_create.py @@ -379,7 +379,10 @@ def test_no_src_account_create( Op.STOP, Op.STOP, ] - tx_gas = [21000, 210000, 0] + # EIP-8037 raises the creation intrinsic gas above 210000, which + # rejects the transaction for gas before the intended insufficient + # funds check. Leave the gas limit unset on Amsterdam. + tx_gas = [21000, None if fork.is_eip_enabled(8037) else 210000, 0] tx_value = [0, 1] tx_access_lists: dict[int, list] = { 2: [], diff --git a/tests/ported_static/stTransactionTest/test_no_src_account_create1559.py b/tests/ported_static/stTransactionTest/test_no_src_account_create1559.py index 8438ccb8900..46f293d3b49 100644 --- a/tests/ported_static/stTransactionTest/test_no_src_account_create1559.py +++ b/tests/ported_static/stTransactionTest/test_no_src_account_create1559.py @@ -236,7 +236,10 @@ def test_no_src_account_create1559( Op.STOP, Op.STOP, ] - tx_gas = [21000, 210000, 0] + # EIP-8037 raises the creation intrinsic gas above 210000, which + # rejects the transaction for gas before the intended insufficient + # funds check. Leave the gas limit unset on Amsterdam. + tx_gas = [21000, None if fork.is_eip_enabled(8037) else 210000, 0] tx_value = [0, 1] tx_access_lists: dict[int, list] = { 0: [], diff --git a/tests/ported_static/stWalletTest/test_day_limit_construction_partial.py b/tests/ported_static/stWalletTest/test_day_limit_construction_partial.py index 0cd3bb580e1..3b761567555 100644 --- a/tests/ported_static/stWalletTest/test_day_limit_construction_partial.py +++ b/tests/ported_static/stWalletTest/test_day_limit_construction_partial.py @@ -54,7 +54,6 @@ def test_day_limit_construction_partial( data=Bytes( "606060409081526001600081815581805533600160a060020a0316600381905581526101026020529190912055620151804204610107556109b4806100456000396000f300606060405236156100985760e060020a6000350463173825d9811461009a5780632f54bf6e146100f65780634123cb6b1461011a5780635c52c2f5146101235780637065cb4814610154578063746c917114610188578063b20d30a914610191578063b75c7dc6146101c5578063ba51a6df146101f5578063c2cf732614610229578063f00d4b5d14610269578063f1736d86146102a2575b005b6100986004356000600036436040518084848082843750505090910190815260405190819003602001902090506105b9815b600160a060020a0333166000908152610102602052604081205481808083811415610719576108b0565b6102ac6004355b600160a060020a0316600090815261010260205260408120541190565b6102ac60015481565b6100986000364360405180848480828437505050909101908152604051908190036020019020905061070b816100cc565b61009860043560003643604051808484808284375050509091019081526040519081900360200190209050610531816100cc565b6102ac60005481565b610098600435600036436040518084848082843750505090910190815260405190819003602001902090506106ff816100cc565b610098600435600160a060020a03331660009081526101026020526040812054908080838114156102be57610340565b61009860043560003643604051808484808284375050509091019081526040519081900360200190209050610678816100cc565b6102ac600435602435600082815261010360209081526040808320600160a060020a0385168452610102909252822054829081818114156106d1576106f5565b6100986004356024356000600036436040518084848082843750505090910190815260405190819003602001902090506103ca816100cc565b6102ac6101055481565b60408051918252519081900360200190f35b5050506000828152610103602052604081206001810154600284900a92908316819011156103405781546001838101805492909101845590849003905560408051600160a060020a03331681526020810187905281517fc7fb647e59b18047309aa15aad418e5d7ca96d173ad704f1031a2c3d7591734b929181900390910190a15b5050505050565b600160a060020a038316600283610100811015610002570155600160a060020a0384811660008181526101026020908152604080832083905593871680835291849020869055835192835282015281517fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c929181900390910190a15b505b505050565b156103c3576103d8836100fd565b156103e357506103c5565b600160a060020a03841660009081526101026020526040812054925082141561040c57506103c5565b6103475b6101045460005b8181101561085f576101048054829081101561000257600091825260008051602061099483398151915201541461048a5761010480546101039160009184908110156100025760008051602061099483398151915201548252506020919091526040812081815560018101829055600201555b600101610417565b60018054810190819055600160a060020a038316906002906101008110156100025790900160005081905550600160005054610102600050600084600160a060020a03168152602001908152602001600020600050819055507f994a936646fe87ffe4f1e469d3d6aa417d6b855598397f323de5b449f765f0c3826040518082600160a060020a0316815260200191505060405180910390a15b505b50565b1561052c5761053f826100fd565b1561054a575061052e565b610552610410565b60015460fa90106105675761056561057c565b505b60015460fa9010610492575061052e565b6106365b600060015b600154811015610899575b600154811080156105ac5750600281610100811015610002570154600014155b156108b95760010161058c565b156103c557600160a060020a0383166000908152610102602052604081205492508214156105e7575061052c565b6001600160005054036000600050541115610602575061052c565b600060028361010081101561000257508301819055600160a060020a03841681526101026020526040812055610578610410565b5060408051600160a060020a038516815290517f58619076adf5bb0943d100ef88d52d7c3fd691b19d3a9071b555b651fbf418da9181900360200190a1505050565b1561052c5760015482111561068d575061052e565b600082905561069a610410565b6040805183815290517facbdb084c721332ac59f9b8e392196c9eb0e4932862da8eb9beaf0dad4f550da9181900360200190a15050565b506001830154600282900a908116600014156106f057600094506106f5565b600194505b5050505092915050565b1561052c575061010555565b1561052e5760006101065550565b60008681526101036020526040812080549094509092508214156107a2578154835560018381018390556101048054918201808255828015829011610771578183600052602060002091820191016107719190610885565b5050506002840181905561010480548892908110156100025760009190915260008051602061099483398151915201555b506001820154600284900a908116600014156108b05760408051600160a060020a03331681526020810188905281517fe1c52dc63b719ade82e8bea94cc41a0d5d28e4aaf536adb5e9cccc9ff8c1aeda929181900390910190a182546001901161089d57600086815261010360205260409020600201546101048054909190811015610002576040600090812060008051602061099483398151915292909201819055808255600180830182905560029092015595506108b09050565b61010480546000808355919091526103c590600080516020610994833981519152908101905b808211156108995760008155600101610885565b5090565b8254600019018355600183018054821790555b50505050919050565b5b600180541180156108dc57506001546002906101008110156100025701546000145b156108f057600180546000190190556108ba565b600154811080156109135750600154600290610100811015610002570154600014155b801561092d57506002816101008110156100025701546000145b1561098e57600154600290610100811015610002578101549082610100811015610002578101919091558190610102906000908361010081101561000257810154825260209290925260408120929092556001546101008110156100025701555b61058156004c0be60200faa20559308cb7b5a1bb3255c16cb1cab91f525b5ae7a03d02fabe" # noqa: E501 ), - gas_limit=817082, value=100, nonce=1, ) diff --git a/tests/ported_static/stWalletTest/test_wallet_construction_partial.py b/tests/ported_static/stWalletTest/test_wallet_construction_partial.py index 90aaebc9d2f..0974f1f81d2 100644 --- a/tests/ported_static/stWalletTest/test_wallet_construction_partial.py +++ b/tests/ported_static/stWalletTest/test_wallet_construction_partial.py @@ -54,7 +54,6 @@ def test_wallet_construction_partial( data=Bytes( "6060604052604051602080611014833960806040818152925160016000818155818055600160a060020a03331660038190558152610102909452938320939093556201518042046101075582917f102d25c49d33fcdb8976a3f2744e0785c98d9e43b88364859e6aec4ae82eff5c91a250610f958061007f6000396000f300606060405236156100b95760e060020a6000350463173825d9811461010b5780632f54bf6e146101675780634123cb6b1461018f5780635c52c2f5146101985780637065cb48146101c9578063746c9171146101fd578063797af62714610206578063b20d30a914610219578063b61d27f61461024d578063b75c7dc61461026e578063ba51a6df1461029e578063c2cf7326146102d2578063cbf0b0c014610312578063f00d4b5d14610346578063f1736d861461037f575b61038960003411156101095760408051600160a060020a033316815234602082015281517fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c929181900390910190a15b565b610389600435600060003643604051808484808284375050509091019081526040519081900360200190209050610693815b600160a060020a0333166000908152610102602052604081205481808083811415610c1357610d6c565b61038b6004355b600160a060020a03811660009081526101026020526040812054115b919050565b61038b60015481565b610389600036436040518084848082843750505090910190815260405190819003602001902090506107e58161013d565b6103896004356000364360405180848480828437505050909101908152604051908190036020019020905061060b8161013d565b61038b60005481565b61038b6004355b600081610a4b8161013d565b610389600435600036436040518084848082843750505090910190815260405190819003602001902090506107d98161013d565b61038b6004803590602480359160443591820191013560006108043361016e565b610389600435600160a060020a033316600090815261010260205260408120549080808381141561039d5761041f565b610389600435600036436040518084848082843750505090910190815260405190819003602001902090506107528161013d565b61038b600435602435600082815261010360209081526040808320600160a060020a0385168452610102909252822054829081818114156107ab576107cf565b610389600435600036436040518084848082843750505090910190815260405190819003602001902090506107f38161013d565b6103896004356024356000600036436040518084848082843750505090910190815260405190819003602001902090506104ac8161013d565b61038b6101055481565b005b60408051918252519081900360200190f35b5050506000828152610103602052604081206001810154600284900a929083168190111561041f5781546001838101805492909101845590849003905560408051600160a060020a03331681526020810187905281517fc7fb647e59b18047309aa15aad418e5d7ca96d173ad704f1031a2c3d7591734b929181900390910190a15b5050505050565b600160a060020a03831660028361010081101561000257508301819055600160a060020a03851660008181526101026020908152604080832083905584835291829020869055815192835282019290925281517fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c929181900390910190a15b505b505050565b156104a5576104ba8361016e565b156104c557506104a7565b600160a060020a0384166000908152610102602052604081205492508214156104ee57506104a7565b6104265b6101045460005b81811015610eba57610104805461010891600091849081101561000257600080516020610f7583398151915201548252506020918252604081208054600160a060020a0319168155600181018290556002810180548382559083528383209193610f3f92601f9290920104810190610a33565b60018054810190819055600160a060020a038316906002906101008110156100025790900160005081905550600160005054610102600050600084600160a060020a03168152602001908152602001600020600050819055507f994a936646fe87ffe4f1e469d3d6aa417d6b855598397f323de5b449f765f0c3826040518082600160a060020a0316815260200191505060405180910390a15b505b50565b15610606576106198261016e565b156106245750610608565b61062c6104f2565b60015460fa90106106415761063f610656565b505b60015460fa901061056c5750610608565b6107105b600060015b600154811015610a47575b600154811080156106865750600281610100811015610002570154600014155b15610d7557600101610666565b156104a757600160a060020a0383166000908152610102602052604081205492508214156106c15750610606565b60016001600050540360006000505411156106dc5750610606565b600060028361010081101561000257508301819055600160a060020a038416815261010260205260408120556106526104f2565b5060408051600160a060020a038516815290517f58619076adf5bb0943d100ef88d52d7c3fd691b19d3a9071b555b651fbf418da9181900360200190a1505050565b15610606576001548211156107675750610608565b60008290556107746104f2565b6040805183815290517facbdb084c721332ac59f9b8e392196c9eb0e4932862da8eb9beaf0dad4f550da9181900360200190a15050565b506001830154600282900a908116600014156107ca57600094506107cf565b600194505b5050505092915050565b15610606575061010555565b156106085760006101065550565b156106065781600160a060020a0316ff5b15610a2357610818846000610e4f3361016e565b156108d4577f92ca3a80853e6663fa31fa10b99225f18d4902939b4c53a9caae9043f6efd00433858786866040518086600160a060020a0316815260200185815260200184600160a060020a031681526020018060200182810382528484828181526020019250808284378201915050965050505050505060405180910390a184600160a060020a03168484846040518083838082843750505090810191506000908083038185876185025a03f15060009350610a2392505050565b6000364360405180848480828437505050909101908152604051908190036020019020915061090490508161020d565b158015610927575060008181526101086020526040812054600160a060020a0316145b15610a235760008181526101086020908152604082208054600160a060020a03191688178155600181018790556002018054858255818452928290209092601f01919091048101908490868215610a2b579182015b82811115610a2b57823582600050559160200191906001019061097c565b50600050507f1733cbb53659d713b79580f79f3f9ff215f78a7c7aa45890f3b89fc5cddfbf328133868887876040518087815260200186600160a060020a0316815260200185815260200184600160a060020a03168152602001806020018281038252848482818152602001925080828437820191505097505050505050505060405180910390a15b949350505050565b5061099a9291505b80821115610a475760008155600101610a33565b5090565b15610c005760008381526101086020526040812054600160a060020a031614610c0057604080516000918220805460018201546002929092018054600160a060020a0392909216949293909291819084908015610acd57820191906000526020600020905b815481529060010190602001808311610ab057829003601f168201915b50509250505060006040518083038185876185025a03f1505050600084815261010860209081526040805181842080546001820154600160a060020a033381811686529685018c905294840181905293166060830181905260a06080840181815260029390930180549185018290527fe7c957c06e9a662c1a6c77366179f5b702b97651dc28eee7d5bf1dff6e40bb4a985095968b969294929390929160c083019085908015610ba257820191906000526020600020905b815481529060010190602001808311610b8557829003601f168201915b505097505050505050505060405180910390a160008381526101086020908152604082208054600160a060020a031916815560018101839055600281018054848255908452828420919392610c0692601f9290920104810190610a33565b50919050565b505050600191505061018a565b6000868152610103602052604081208054909450909250821415610c9c578154835560018381018390556101048054918201808255828015829011610c6b57818360005260206000209182019101610c6b9190610a33565b50505060028401819055610104805488929081101561000257600091909152600080516020610f7583398151915201555b506001820154600284900a90811660001415610d6c5760408051600160a060020a03331681526020810188905281517fe1c52dc63b719ade82e8bea94cc41a0d5d28e4aaf536adb5e9cccc9ff8c1aeda929181900390910190a1825460019011610d59576000868152610103602052604090206002015461010480549091908110156100025760406000908120600080516020610f758339815191529290920181905580825560018083018290556002909201559550610d6c9050565b8254600019018355600183018054821790555b50505050919050565b5b60018054118015610d9857506001546002906101008110156100025701546000145b15610dac5760018054600019019055610d76565b60015481108015610dcf5750600154600290610100811015610002570154600014155b8015610de957506002816101008110156100025701546000145b15610e4a57600154600290610100811015610002578101549082610100811015610002578101919091558190610102906000908361010081101561000257810154825260209290925260408120929092556001546101008110156100025701555b61065b565b1561018a5761010754610e655b62015180420490565b1115610e7e57600061010655610e79610e5c565b610107555b6101065480830110801590610e9c5750610106546101055490830111155b15610eb25750610106805482019055600161018a565b50600061018a565b6106066101045460005b81811015610f4a5761010480548290811015610002576000918252600080516020610f75833981519152015414610f3757610104805461010391600091849081101561000257600080516020610f7583398151915201548252506020919091526040812081815560018101829055600201555b600101610ec4565b5050506001016104f9565b61010480546000808355919091526104a790600080516020610f7583398151915290810190610a3356004c0be60200faa20559308cb7b5a1bb3255c16cb1cab91f525b5ae7a03d02fabe" # noqa: E501 ), - gas_limit=2225022, value=100, nonce=1, ) diff --git a/tests/ported_static/stZeroKnowledge/test_point_mul_add.py b/tests/ported_static/stZeroKnowledge/test_point_mul_add.py deleted file mode 100644 index 4a32c3ba08c..00000000000 --- a/tests/ported_static/stZeroKnowledge/test_point_mul_add.py +++ /dev/null @@ -1,640 +0,0 @@ -""" -Test_point_mul_add. - -Ported from: -state_tests/stZeroKnowledge/pointMulAddFiller.json -""" - -import pytest -from execution_testing import ( - EOA, - Account, - Address, - Alloc, - Environment, - Hash, - StateTestFiller, - Transaction, -) -from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( - resolve_expect_post, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - ["state_tests/stZeroKnowledge/pointMulAddFiller.json"], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.parametrize( - "d, g, v", - [ - pytest.param( - 0, - 0, - 0, - id="d0-g0", - ), - pytest.param( - 0, - 1, - 0, - id="d0-g1", - ), - pytest.param( - 0, - 2, - 0, - id="d0-g2", - ), - pytest.param( - 0, - 3, - 0, - id="d0-g3", - ), - pytest.param( - 1, - 0, - 0, - id="d1-g0", - ), - pytest.param( - 1, - 1, - 0, - id="d1-g1", - ), - pytest.param( - 1, - 2, - 0, - id="d1-g2", - ), - pytest.param( - 1, - 3, - 0, - id="d1-g3", - ), - pytest.param( - 2, - 0, - 0, - id="d2-g0", - ), - pytest.param( - 2, - 1, - 0, - id="d2-g1", - ), - pytest.param( - 2, - 2, - 0, - id="d2-g2", - ), - pytest.param( - 2, - 3, - 0, - id="d2-g3", - ), - pytest.param( - 3, - 0, - 0, - id="d3-g0", - ), - pytest.param( - 3, - 1, - 0, - id="d3-g1", - ), - pytest.param( - 3, - 2, - 0, - id="d3-g2", - ), - pytest.param( - 3, - 3, - 0, - id="d3-g3", - ), - pytest.param( - 4, - 0, - 0, - id="d4-g0", - ), - pytest.param( - 4, - 1, - 0, - id="d4-g1", - ), - pytest.param( - 4, - 2, - 0, - id="d4-g2", - ), - pytest.param( - 4, - 3, - 0, - id="d4-g3", - ), - pytest.param( - 5, - 0, - 0, - id="d5-g0", - ), - pytest.param( - 5, - 1, - 0, - id="d5-g1", - ), - pytest.param( - 5, - 2, - 0, - id="d5-g2", - ), - pytest.param( - 5, - 3, - 0, - id="d5-g3", - ), - pytest.param( - 6, - 0, - 0, - id="d6-g0", - ), - pytest.param( - 6, - 1, - 0, - id="d6-g1", - ), - pytest.param( - 6, - 2, - 0, - id="d6-g2", - ), - pytest.param( - 6, - 3, - 0, - id="d6-g3", - ), - pytest.param( - 7, - 0, - 0, - id="d7-g0", - ), - pytest.param( - 7, - 1, - 0, - id="d7-g1", - ), - pytest.param( - 7, - 2, - 0, - id="d7-g2", - ), - pytest.param( - 7, - 3, - 0, - id="d7-g3", - ), - pytest.param( - 8, - 0, - 0, - id="d8-g0", - ), - pytest.param( - 8, - 1, - 0, - id="d8-g1", - ), - pytest.param( - 8, - 2, - 0, - id="d8-g2", - ), - pytest.param( - 8, - 3, - 0, - id="d8-g3", - ), - ], -) -@pytest.mark.pre_alloc_mutable -def test_point_mul_add( - state_test: StateTestFiller, - pre: Alloc, - fork: Fork, - d: int, - g: int, - v: int, -) -> None: - """Test_point_mul_add.""" - coinbase = Address(0x68795C4AA09D6F4ED3E5DEDDF8C2AD3049A601DA) - contract_0 = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B) - sender = EOA( - key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8 - ) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - ) - - pre[sender] = Account(balance=0xDE0B6B3A7640000, nonce=1) - # Source: lll - # {(MSTORE 0 (CALLDATALOAD 0)) (MSTORE 32 (CALLDATALOAD 32)) (MSTORE 64 (CALLDATALOAD 64)) (MSTORE 96 (CALLDATALOAD 96)) (MSTORE 128 (CALLDATALOAD 128)) (MSTORE 160 (CALLDATALOAD 160)) (MSTORE 192 (CALLDATALOAD 192)) [[0]](CALLCODE 500000 6 0 0 128 300 64) [[1]](CALLCODE 500000 7 0 128 96 400 64) [[10]] (MLOAD 300) [[11]] (MLOAD 332) [[20]] (MLOAD 400) [[21]] (MLOAD 432) [[2]] (EQ (SLOAD 10) (SLOAD 20)) [[3]] (EQ (SLOAD 11) (SLOAD 21))} # noqa: E501 - contract_0 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.CALLDATALOAD(offset=0x0)) - + Op.MSTORE(offset=0x20, value=Op.CALLDATALOAD(offset=0x20)) - + Op.MSTORE(offset=0x40, value=Op.CALLDATALOAD(offset=0x40)) - + Op.MSTORE(offset=0x60, value=Op.CALLDATALOAD(offset=0x60)) - + Op.MSTORE(offset=0x80, value=Op.CALLDATALOAD(offset=0x80)) - + Op.MSTORE(offset=0xA0, value=Op.CALLDATALOAD(offset=0xA0)) - + Op.MSTORE(offset=0xC0, value=Op.CALLDATALOAD(offset=0xC0)) - + Op.SSTORE( - key=0x0, - value=Op.CALLCODE( - gas=0x7A120, - address=0x6, - value=0x0, - args_offset=0x0, - args_size=0x80, - ret_offset=0x12C, - ret_size=0x40, - ), - ) - + Op.SSTORE( - key=0x1, - value=Op.CALLCODE( - gas=0x7A120, - address=0x7, - value=0x0, - args_offset=0x80, - args_size=0x60, - ret_offset=0x190, - ret_size=0x40, - ), - ) - + Op.SSTORE(key=0xA, value=Op.MLOAD(offset=0x12C)) - + Op.SSTORE(key=0xB, value=Op.MLOAD(offset=0x14C)) - + Op.SSTORE(key=0x14, value=Op.MLOAD(offset=0x190)) - + Op.SSTORE(key=0x15, value=Op.MLOAD(offset=0x1B0)) - + Op.SSTORE( - key=0x2, value=Op.EQ(Op.SLOAD(key=0xA), Op.SLOAD(key=0x14)) - ) - + Op.SSTORE( - key=0x3, value=Op.EQ(Op.SLOAD(key=0xB), Op.SLOAD(key=0x15)) - ) - + Op.STOP, - nonce=0, - address=Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B), # noqa: E501 - ) - - expect_entries_: list[dict] = [ - { - "indexes": {"data": [0], "gas": 0, "value": -1}, - "network": [">=Cancun"], - "result": { - contract_0: Account( - storage={ - 0: 1, - 1: 1, - 2: 1, - 3: 1, - 10: 0x1DE49A4B0233273BBA8146AF82042D004F2085EC982397DB0D97DA17204CC286, # noqa: E501 - 11: 0x217327FFC463919BEF80CC166D09C6172639D8589799928761BCD9F22C903D4, # noqa: E501 - 20: 0x1DE49A4B0233273BBA8146AF82042D004F2085EC982397DB0D97DA17204CC286, # noqa: E501 - 21: 0x217327FFC463919BEF80CC166D09C6172639D8589799928761BCD9F22C903D4, # noqa: E501 - }, - ), - }, - }, - { - "indexes": {"data": [1], "gas": 0, "value": -1}, - "network": [">=Cancun"], - "result": { - contract_0: Account( - storage={ - 0: 1, - 1: 1, - 2: 1, - 3: 1, - 10: 0x1F4D1D80177B1377743D1901F70D7389BE7F7A35A35BFD234A8AAEE615B88C49, # noqa: E501 - 11: 0x18683193AE021A2F8920FED186CDE5D9B1365116865281CCF884C1F28B1DF8F, # noqa: E501 - 20: 0x1F4D1D80177B1377743D1901F70D7389BE7F7A35A35BFD234A8AAEE615B88C49, # noqa: E501 - 21: 0x18683193AE021A2F8920FED186CDE5D9B1365116865281CCF884C1F28B1DF8F, # noqa: E501 - }, - ), - }, - }, - { - "indexes": {"data": [2], "gas": 0, "value": -1}, - "network": [">=Cancun"], - "result": {contract_0: Account(storage={0: 1, 1: 1, 2: 1, 3: 1})}, - }, - { - "indexes": {"data": [3], "gas": 0, "value": -1}, - "network": [">=Cancun"], - "result": { - contract_0: Account( - storage={ - 0: 1, - 1: 1, - 2: 1, - 3: 1, - 10: 0x255E468453D7636CC1563E43F7521755F95E6C56043C7321B4AE04E772945FB0, # noqa: E501 - 11: 0x225C5F1623620FD84BFBAB2D861A9D1E570F7727C540F403085998EBAF407C4, # noqa: E501 - 20: 0x255E468453D7636CC1563E43F7521755F95E6C56043C7321B4AE04E772945FB0, # noqa: E501 - 21: 0x225C5F1623620FD84BFBAB2D861A9D1E570F7727C540F403085998EBAF407C4, # noqa: E501 - }, - ), - }, - }, - { - "indexes": {"data": [4], "gas": 0, "value": -1}, - "network": [">=Cancun"], - "result": { - contract_0: Account( - storage={ - 0: 1, - 1: 1, - 2: 1, - 3: 1, - 10: 0x1F4D1D80177B1377743D1901F70D7389BE7F7A35A35BFD234A8AAEE615B88C49, # noqa: E501 - 11: 0x2EDDCB59A6517E86BFBE35C9691479FFFC6E0580000CA2706C983FF7AFCB1DB8, # noqa: E501 - 20: 0x1F4D1D80177B1377743D1901F70D7389BE7F7A35A35BFD234A8AAEE615B88C49, # noqa: E501 - 21: 0x2EDDCB59A6517E86BFBE35C9691479FFFC6E0580000CA2706C983FF7AFCB1DB8, # noqa: E501 - }, - ), - }, - }, - { - "indexes": {"data": [5], "gas": 0, "value": -1}, - "network": [">=Cancun"], - "result": { - contract_0: Account( - storage={ - 0: 1, - 1: 1, - 2: 1, - 3: 1, - 10: 0x255E468453D7636CC1563E43F7521755F95E6C56043C7321B4AE04E772945FB0, # noqa: E501 - 11: 0x225C5F1623620FD84BFBAB2D861A9D1E570F7727C540F403085998EBAF407C4, # noqa: E501 - 20: 0x255E468453D7636CC1563E43F7521755F95E6C56043C7321B4AE04E772945FB0, # noqa: E501 - 21: 0x225C5F1623620FD84BFBAB2D861A9D1E570F7727C540F403085998EBAF407C4, # noqa: E501 - }, - ), - }, - }, - { - "indexes": {"data": [6], "gas": 0, "value": -1}, - "network": [">=Cancun"], - "result": { - contract_0: Account( - storage={ - 0: 1, - 1: 1, - 2: 1, - 3: 1, - 10: 0x1DE49A4B0233273BBA8146AF82042D004F2085EC982397DB0D97DA17204CC286, # noqa: E501 - 11: 0x217327FFC463919BEF80CC166D09C6172639D8589799928761BCD9F22C903D4, # noqa: E501 - 20: 0x1DE49A4B0233273BBA8146AF82042D004F2085EC982397DB0D97DA17204CC286, # noqa: E501 - 21: 0x217327FFC463919BEF80CC166D09C6172639D8589799928761BCD9F22C903D4, # noqa: E501 - }, - ), - }, - }, - { - "indexes": {"data": [7], "gas": [0, 3], "value": -1}, - "network": [">=Cancun"], - "result": { - contract_0: Account( - storage={ - 0: 1, - 1: 1, - 10: 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD3, # noqa: E501 - 11: 0x15ED738C0E0A7C92E7845F96B2AE9C0A68A6A449E3538FC7FF3EBF7A5A18A2C4, # noqa: E501 - 20: 1, - 21: 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45, # noqa: E501 - }, - ), - }, - }, - { - "indexes": {"data": [8], "gas": 0, "value": -1}, - "network": [">=Cancun"], - "result": {contract_0: Account(storage={0: 1, 1: 1, 2: 1, 3: 1})}, - }, - { - "indexes": {"data": -1, "gas": [1, 2], "value": -1}, - "network": [">=Cancun"], - "result": {contract_0: Account(storage={})}, - }, - { - "indexes": {"data": [0, 1, 3, 4, 5, 6], "gas": [3], "value": -1}, - "network": [">=Cancun"], - "result": {contract_0: Account(storage={})}, - }, - { - "indexes": {"data": [8, 2], "gas": [3], "value": -1}, - "network": [">=Cancun"], - "result": {contract_0: Account(storage={0: 1, 1: 1, 2: 1, 3: 1})}, - }, - ] - - post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork) - - tx_data = [ - Hash(0xF25929BCB43D5A57391564615C9E70A992B10EAFA4DB109709649CF48C50DD2) - + Hash( - 0x16DA2F5CB6BE7A0AA72C440C53C9BBDFEC6C36C7D515536431B3A865468ACBBA - ) - + Hash( - 0xF25929BCB43D5A57391564615C9E70A992B10EAFA4DB109709649CF48C50DD2 - ) - + Hash( - 0x16DA2F5CB6BE7A0AA72C440C53C9BBDFEC6C36C7D515536431B3A865468ACBBA - ) - + Hash( - 0xF25929BCB43D5A57391564615C9E70A992B10EAFA4DB109709649CF48C50DD2 - ) - + Hash( - 0x16DA2F5CB6BE7A0AA72C440C53C9BBDFEC6C36C7D515536431B3A865468ACBBA - ) - + Hash(0x2), - Hash( - 0x1DE49A4B0233273BBA8146AF82042D004F2085EC982397DB0D97DA17204CC286 - ) - + Hash( - 0x217327FFC463919BEF80CC166D09C6172639D8589799928761BCD9F22C903D4 - ) - + Hash( - 0xF25929BCB43D5A57391564615C9E70A992B10EAFA4DB109709649CF48C50DD2 - ) - + Hash( - 0x16DA2F5CB6BE7A0AA72C440C53C9BBDFEC6C36C7D515536431B3A865468ACBBA - ) - + Hash( - 0xF25929BCB43D5A57391564615C9E70A992B10EAFA4DB109709649CF48C50DD2 - ) - + Hash( - 0x16DA2F5CB6BE7A0AA72C440C53C9BBDFEC6C36C7D515536431B3A865468ACBBA - ) - + Hash(0x3), - Hash( - 0x1F4D1D80177B1377743D1901F70D7389BE7F7A35A35BFD234A8AAEE615B88C49 - ) - + Hash( - 0x2EDDCB59A6517E86BFBE35C9691479FFFC6E0580000CA2706C983FF7AFCB1DB8 - ) - + Hash( - 0x1F4D1D80177B1377743D1901F70D7389BE7F7A35A35BFD234A8AAEE615B88C49 - ) - + Hash( - 0x18683193AE021A2F8920FED186CDE5D9B1365116865281CCF884C1F28B1DF8F - ) - + Hash( - 0x1F4D1D80177B1377743D1901F70D7389BE7F7A35A35BFD234A8AAEE615B88C49 - ) - + Hash( - 0x18683193AE021A2F8920FED186CDE5D9B1365116865281CCF884C1F28B1DF8F - ) - + Hash(0x0), - Hash( - 0x1F4D1D80177B1377743D1901F70D7389BE7F7A35A35BFD234A8AAEE615B88C49 - ) - + Hash( - 0x2EDDCB59A6517E86BFBE35C9691479FFFC6E0580000CA2706C983FF7AFCB1DB8 - ) - + Hash( - 0x1F4D1D80177B1377743D1901F70D7389BE7F7A35A35BFD234A8AAEE615B88C49 - ) - + Hash( - 0x2EDDCB59A6517E86BFBE35C9691479FFFC6E0580000CA2706C983FF7AFCB1DB8 - ) - + Hash( - 0x1F4D1D80177B1377743D1901F70D7389BE7F7A35A35BFD234A8AAEE615B88C49 - ) - + Hash( - 0x2EDDCB59A6517E86BFBE35C9691479FFFC6E0580000CA2706C983FF7AFCB1DB8 - ) - + Hash(0x2), - Hash( - 0x1F4D1D80177B1377743D1901F70D7389BE7F7A35A35BFD234A8AAEE615B88C49 - ) - + Hash( - 0x2EDDCB59A6517E86BFBE35C9691479FFFC6E0580000CA2706C983FF7AFCB1DB8 - ) - + Hash(0x0) - + Hash(0x0) - + Hash( - 0x1F4D1D80177B1377743D1901F70D7389BE7F7A35A35BFD234A8AAEE615B88C49 - ) - + Hash( - 0x18683193AE021A2F8920FED186CDE5D9B1365116865281CCF884C1F28B1DF8F - ) - + Hash( - 0x30644E72E131A029B85045B68181585D2833E84879B9709143E1F593F0000000 - ), - Hash( - 0x1F4D1D80177B1377743D1901F70D7389BE7F7A35A35BFD234A8AAEE615B88C49 - ) - + Hash( - 0x2EDDCB59A6517E86BFBE35C9691479FFFC6E0580000CA2706C983FF7AFCB1DB8 - ) - + Hash( - 0x1F4D1D80177B1377743D1901F70D7389BE7F7A35A35BFD234A8AAEE615B88C49 - ) - + Hash( - 0x2EDDCB59A6517E86BFBE35C9691479FFFC6E0580000CA2706C983FF7AFCB1DB8 - ) - + Hash( - 0x1F4D1D80177B1377743D1901F70D7389BE7F7A35A35BFD234A8AAEE615B88C49 - ) - + Hash( - 0x18683193AE021A2F8920FED186CDE5D9B1365116865281CCF884C1F28B1DF8F - ) - + Hash( - 0x30644E72E131A029B85045B68181585D2833E84879B9709143E1F593EFFFFFFF - ), - Hash( - 0x1DE49A4B0233273BBA8146AF82042D004F2085EC982397DB0D97DA17204CC286 - ) - + Hash( - 0x217327FFC463919BEF80CC166D09C6172639D8589799928761BCD9F22C903D4 - ) - + Hash(0x0) - + Hash(0x0) - + Hash( - 0x1DE49A4B0233273BBA8146AF82042D004F2085EC982397DB0D97DA17204CC286 - ) - + Hash( - 0x217327FFC463919BEF80CC166D09C6172639D8589799928761BCD9F22C903D4 - ) - + Hash(0x1), - Hash(0x1) - + Hash(0x2) - + Hash(0x1) - + Hash(0x2) - + Hash(0x1) - + Hash(0x2) - + Hash( - 0x30644E72E131A029B85045B68181585D2833E84879B9709143E1F593F0000000 - ), - Hash(0x1) - + Hash(0x2) - + Hash(0x1) - + Hash( - 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45 - ) - + Hash(0x1) - + Hash(0x2) - + Hash(0x0), - ] - tx_gas = [2000000, 90000, 110000, 192000] - - tx = Transaction( - sender=sender, - to=contract_0, - data=tx_data[d], - gas_limit=tx_gas[g], - nonce=1, - error=_exc, - ) - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stZeroKnowledge/test_point_mul_add2.py b/tests/ported_static/stZeroKnowledge/test_point_mul_add2.py deleted file mode 100644 index d97f92d87f5..00000000000 --- a/tests/ported_static/stZeroKnowledge/test_point_mul_add2.py +++ /dev/null @@ -1,1985 +0,0 @@ -""" -Test_point_mul_add2. - -Ported from: -state_tests/stZeroKnowledge/pointMulAdd2Filler.json -""" - -import pytest -from execution_testing import ( - EOA, - Account, - Address, - Alloc, - Environment, - Hash, - StateTestFiller, - Transaction, -) -from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( - resolve_expect_post, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - ["state_tests/stZeroKnowledge/pointMulAdd2Filler.json"], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.parametrize( - "d, g, v", - [ - pytest.param( - 0, - 0, - 0, - id="d0-g0", - ), - pytest.param( - 0, - 1, - 0, - id="d0-g1", - ), - pytest.param( - 0, - 2, - 0, - id="d0-g2", - ), - pytest.param( - 0, - 3, - 0, - id="d0-g3", - ), - pytest.param( - 1, - 0, - 0, - id="d1-g0", - ), - pytest.param( - 1, - 1, - 0, - id="d1-g1", - ), - pytest.param( - 1, - 2, - 0, - id="d1-g2", - ), - pytest.param( - 1, - 3, - 0, - id="d1-g3", - ), - pytest.param( - 2, - 0, - 0, - id="d2-g0", - ), - pytest.param( - 2, - 1, - 0, - id="d2-g1", - ), - pytest.param( - 2, - 2, - 0, - id="d2-g2", - ), - pytest.param( - 2, - 3, - 0, - id="d2-g3", - ), - pytest.param( - 3, - 0, - 0, - id="d3-g0", - ), - pytest.param( - 3, - 1, - 0, - id="d3-g1", - ), - pytest.param( - 3, - 2, - 0, - id="d3-g2", - ), - pytest.param( - 3, - 3, - 0, - id="d3-g3", - ), - pytest.param( - 4, - 0, - 0, - id="d4-g0", - ), - pytest.param( - 4, - 1, - 0, - id="d4-g1", - ), - pytest.param( - 4, - 2, - 0, - id="d4-g2", - ), - pytest.param( - 4, - 3, - 0, - id="d4-g3", - ), - pytest.param( - 5, - 0, - 0, - id="d5-g0", - ), - pytest.param( - 5, - 1, - 0, - id="d5-g1", - ), - pytest.param( - 5, - 2, - 0, - id="d5-g2", - ), - pytest.param( - 5, - 3, - 0, - id="d5-g3", - ), - pytest.param( - 6, - 0, - 0, - id="d6-g0", - ), - pytest.param( - 6, - 1, - 0, - id="d6-g1", - ), - pytest.param( - 6, - 2, - 0, - id="d6-g2", - ), - pytest.param( - 6, - 3, - 0, - id="d6-g3", - ), - pytest.param( - 7, - 0, - 0, - id="d7-g0", - ), - pytest.param( - 7, - 1, - 0, - id="d7-g1", - ), - pytest.param( - 7, - 2, - 0, - id="d7-g2", - ), - pytest.param( - 7, - 3, - 0, - id="d7-g3", - ), - pytest.param( - 8, - 0, - 0, - id="d8-g0", - ), - pytest.param( - 8, - 1, - 0, - id="d8-g1", - ), - pytest.param( - 8, - 2, - 0, - id="d8-g2", - ), - pytest.param( - 8, - 3, - 0, - id="d8-g3", - ), - pytest.param( - 9, - 0, - 0, - id="d9-g0", - ), - pytest.param( - 9, - 1, - 0, - id="d9-g1", - ), - pytest.param( - 9, - 2, - 0, - id="d9-g2", - ), - pytest.param( - 9, - 3, - 0, - id="d9-g3", - ), - pytest.param( - 10, - 0, - 0, - id="d10-g0", - ), - pytest.param( - 10, - 1, - 0, - id="d10-g1", - ), - pytest.param( - 10, - 2, - 0, - id="d10-g2", - ), - pytest.param( - 10, - 3, - 0, - id="d10-g3", - ), - pytest.param( - 11, - 0, - 0, - id="d11-g0", - ), - pytest.param( - 11, - 1, - 0, - id="d11-g1", - ), - pytest.param( - 11, - 2, - 0, - id="d11-g2", - ), - pytest.param( - 11, - 3, - 0, - id="d11-g3", - ), - pytest.param( - 12, - 0, - 0, - id="d12-g0", - ), - pytest.param( - 12, - 1, - 0, - id="d12-g1", - ), - pytest.param( - 12, - 2, - 0, - id="d12-g2", - ), - pytest.param( - 12, - 3, - 0, - id="d12-g3", - ), - pytest.param( - 13, - 0, - 0, - id="d13-g0", - ), - pytest.param( - 13, - 1, - 0, - id="d13-g1", - ), - pytest.param( - 13, - 2, - 0, - id="d13-g2", - ), - pytest.param( - 13, - 3, - 0, - id="d13-g3", - ), - pytest.param( - 14, - 0, - 0, - id="d14-g0", - ), - pytest.param( - 14, - 1, - 0, - id="d14-g1", - ), - pytest.param( - 14, - 2, - 0, - id="d14-g2", - ), - pytest.param( - 14, - 3, - 0, - id="d14-g3", - ), - pytest.param( - 15, - 0, - 0, - id="d15-g0", - ), - pytest.param( - 15, - 1, - 0, - id="d15-g1", - ), - pytest.param( - 15, - 2, - 0, - id="d15-g2", - ), - pytest.param( - 15, - 3, - 0, - id="d15-g3", - ), - pytest.param( - 16, - 0, - 0, - id="d16-g0", - ), - pytest.param( - 16, - 1, - 0, - id="d16-g1", - ), - pytest.param( - 16, - 2, - 0, - id="d16-g2", - ), - pytest.param( - 16, - 3, - 0, - id="d16-g3", - ), - pytest.param( - 17, - 0, - 0, - id="d17-g0", - ), - pytest.param( - 17, - 1, - 0, - id="d17-g1", - ), - pytest.param( - 17, - 2, - 0, - id="d17-g2", - ), - pytest.param( - 17, - 3, - 0, - id="d17-g3", - ), - pytest.param( - 18, - 0, - 0, - id="d18-g0", - ), - pytest.param( - 18, - 1, - 0, - id="d18-g1", - ), - pytest.param( - 18, - 2, - 0, - id="d18-g2", - ), - pytest.param( - 18, - 3, - 0, - id="d18-g3", - ), - pytest.param( - 19, - 0, - 0, - id="d19-g0", - ), - pytest.param( - 19, - 1, - 0, - id="d19-g1", - ), - pytest.param( - 19, - 2, - 0, - id="d19-g2", - ), - pytest.param( - 19, - 3, - 0, - id="d19-g3", - ), - pytest.param( - 20, - 0, - 0, - id="d20-g0", - ), - pytest.param( - 20, - 1, - 0, - id="d20-g1", - ), - pytest.param( - 20, - 2, - 0, - id="d20-g2", - ), - pytest.param( - 20, - 3, - 0, - id="d20-g3", - ), - pytest.param( - 21, - 0, - 0, - id="d21-g0", - ), - pytest.param( - 21, - 1, - 0, - id="d21-g1", - ), - pytest.param( - 21, - 2, - 0, - id="d21-g2", - ), - pytest.param( - 21, - 3, - 0, - id="d21-g3", - ), - pytest.param( - 22, - 0, - 0, - id="d22-g0", - ), - pytest.param( - 22, - 1, - 0, - id="d22-g1", - ), - pytest.param( - 22, - 2, - 0, - id="d22-g2", - ), - pytest.param( - 22, - 3, - 0, - id="d22-g3", - ), - pytest.param( - 23, - 0, - 0, - id="d23-g0", - ), - pytest.param( - 23, - 1, - 0, - id="d23-g1", - ), - pytest.param( - 23, - 2, - 0, - id="d23-g2", - ), - pytest.param( - 23, - 3, - 0, - id="d23-g3", - ), - pytest.param( - 24, - 0, - 0, - id="d24-g0", - ), - pytest.param( - 24, - 1, - 0, - id="d24-g1", - ), - pytest.param( - 24, - 2, - 0, - id="d24-g2", - ), - pytest.param( - 24, - 3, - 0, - id="d24-g3", - ), - pytest.param( - 25, - 0, - 0, - id="d25-g0", - ), - pytest.param( - 25, - 1, - 0, - id="d25-g1", - ), - pytest.param( - 25, - 2, - 0, - id="d25-g2", - ), - pytest.param( - 25, - 3, - 0, - id="d25-g3", - ), - pytest.param( - 26, - 0, - 0, - id="d26-g0", - ), - pytest.param( - 26, - 1, - 0, - id="d26-g1", - ), - pytest.param( - 26, - 2, - 0, - id="d26-g2", - ), - pytest.param( - 26, - 3, - 0, - id="d26-g3", - ), - pytest.param( - 27, - 0, - 0, - id="d27-g0", - ), - pytest.param( - 27, - 1, - 0, - id="d27-g1", - ), - pytest.param( - 27, - 2, - 0, - id="d27-g2", - ), - pytest.param( - 27, - 3, - 0, - id="d27-g3", - ), - pytest.param( - 28, - 0, - 0, - id="d28-g0", - ), - pytest.param( - 28, - 1, - 0, - id="d28-g1", - ), - pytest.param( - 28, - 2, - 0, - id="d28-g2", - ), - pytest.param( - 28, - 3, - 0, - id="d28-g3", - ), - pytest.param( - 29, - 0, - 0, - id="d29-g0", - ), - pytest.param( - 29, - 1, - 0, - id="d29-g1", - ), - pytest.param( - 29, - 2, - 0, - id="d29-g2", - ), - pytest.param( - 29, - 3, - 0, - id="d29-g3", - ), - pytest.param( - 30, - 0, - 0, - id="d30-g0", - ), - pytest.param( - 30, - 1, - 0, - id="d30-g1", - ), - pytest.param( - 30, - 2, - 0, - id="d30-g2", - ), - pytest.param( - 30, - 3, - 0, - id="d30-g3", - ), - pytest.param( - 31, - 0, - 0, - id="d31-g0", - ), - pytest.param( - 31, - 1, - 0, - id="d31-g1", - ), - pytest.param( - 31, - 2, - 0, - id="d31-g2", - ), - pytest.param( - 31, - 3, - 0, - id="d31-g3", - ), - pytest.param( - 32, - 0, - 0, - id="d32-g0", - ), - pytest.param( - 32, - 1, - 0, - id="d32-g1", - ), - pytest.param( - 32, - 2, - 0, - id="d32-g2", - ), - pytest.param( - 32, - 3, - 0, - id="d32-g3", - ), - pytest.param( - 33, - 0, - 0, - id="d33-g0", - ), - pytest.param( - 33, - 1, - 0, - id="d33-g1", - ), - pytest.param( - 33, - 2, - 0, - id="d33-g2", - ), - pytest.param( - 33, - 3, - 0, - id="d33-g3", - ), - pytest.param( - 34, - 0, - 0, - id="d34-g0", - ), - pytest.param( - 34, - 1, - 0, - id="d34-g1", - ), - pytest.param( - 34, - 2, - 0, - id="d34-g2", - ), - pytest.param( - 34, - 3, - 0, - id="d34-g3", - ), - pytest.param( - 35, - 0, - 0, - id="d35-g0", - ), - pytest.param( - 35, - 1, - 0, - id="d35-g1", - ), - pytest.param( - 35, - 2, - 0, - id="d35-g2", - ), - pytest.param( - 35, - 3, - 0, - id="d35-g3", - ), - pytest.param( - 36, - 0, - 0, - id="d36-g0", - ), - pytest.param( - 36, - 1, - 0, - id="d36-g1", - ), - pytest.param( - 36, - 2, - 0, - id="d36-g2", - ), - pytest.param( - 36, - 3, - 0, - id="d36-g3", - ), - pytest.param( - 37, - 0, - 0, - id="d37-g0", - ), - pytest.param( - 37, - 1, - 0, - id="d37-g1", - ), - pytest.param( - 37, - 2, - 0, - id="d37-g2", - ), - pytest.param( - 37, - 3, - 0, - id="d37-g3", - ), - ], -) -@pytest.mark.pre_alloc_mutable -def test_point_mul_add2( - state_test: StateTestFiller, - pre: Alloc, - fork: Fork, - d: int, - g: int, - v: int, -) -> None: - """Test_point_mul_add2.""" - coinbase = Address(0x68795C4AA09D6F4ED3E5DEDDF8C2AD3049A601DA) - contract_0 = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B) - sender = EOA( - key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8 - ) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - ) - - pre[sender] = Account(balance=0xDE0B6B3A7640000, nonce=1) - # Source: lll - # {(MSTORE 0 (CALLDATALOAD 0)) (MSTORE 32 (CALLDATALOAD 32)) (MSTORE 64 (CALLDATALOAD 64)) (MSTORE 96 (CALLDATALOAD 96)) (MSTORE 128 (CALLDATALOAD 128)) (MSTORE 160 (CALLDATALOAD 160)) (MSTORE 192 (CALLDATALOAD 192)) [[0]](CALLCODE 500000 6 0 0 128 300 64) [[1]](CALLCODE 500000 7 0 128 96 400 64) [[10]] (MLOAD 300) [[11]] (MLOAD 332) [[20]] (MLOAD 400) [[21]] (MLOAD 432) [[2]] (EQ (SLOAD 10) (SLOAD 20)) [[3]] (EQ (SLOAD 11) (SLOAD 21))} # noqa: E501 - contract_0 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.CALLDATALOAD(offset=0x0)) - + Op.MSTORE(offset=0x20, value=Op.CALLDATALOAD(offset=0x20)) - + Op.MSTORE(offset=0x40, value=Op.CALLDATALOAD(offset=0x40)) - + Op.MSTORE(offset=0x60, value=Op.CALLDATALOAD(offset=0x60)) - + Op.MSTORE(offset=0x80, value=Op.CALLDATALOAD(offset=0x80)) - + Op.MSTORE(offset=0xA0, value=Op.CALLDATALOAD(offset=0xA0)) - + Op.MSTORE(offset=0xC0, value=Op.CALLDATALOAD(offset=0xC0)) - + Op.SSTORE( - key=0x0, - value=Op.CALLCODE( - gas=0x7A120, - address=0x6, - value=0x0, - args_offset=0x0, - args_size=0x80, - ret_offset=0x12C, - ret_size=0x40, - ), - ) - + Op.SSTORE( - key=0x1, - value=Op.CALLCODE( - gas=0x7A120, - address=0x7, - value=0x0, - args_offset=0x80, - args_size=0x60, - ret_offset=0x190, - ret_size=0x40, - ), - ) - + Op.SSTORE(key=0xA, value=Op.MLOAD(offset=0x12C)) - + Op.SSTORE(key=0xB, value=Op.MLOAD(offset=0x14C)) - + Op.SSTORE(key=0x14, value=Op.MLOAD(offset=0x190)) - + Op.SSTORE(key=0x15, value=Op.MLOAD(offset=0x1B0)) - + Op.SSTORE( - key=0x2, value=Op.EQ(Op.SLOAD(key=0xA), Op.SLOAD(key=0x14)) - ) - + Op.SSTORE( - key=0x3, value=Op.EQ(Op.SLOAD(key=0xB), Op.SLOAD(key=0x15)) - ) - + Op.STOP, - nonce=0, - address=Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B), # noqa: E501 - ) - - expect_entries_: list[dict] = [ - { - "indexes": { - "data": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 26], - "gas": [0], - "value": -1, - }, - "network": [">=Cancun"], - "result": {contract_0: Account(storage={0: 1, 1: 1, 2: 1, 3: 1})}, - }, - { - "indexes": {"data": [10], "gas": 0, "value": -1}, - "network": [">=Cancun"], - "result": { - contract_0: Account( - storage={ - 0: 1, - 1: 1, - 2: 1, - 3: 1, - 10: 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD3, # noqa: E501 - 11: 0x1A76DAE6D3272396D0CBE61FCED2BC532EDAC647851E3AC53CE1CC9C7E645A83, # noqa: E501 - 20: 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD3, # noqa: E501 - 21: 0x1A76DAE6D3272396D0CBE61FCED2BC532EDAC647851E3AC53CE1CC9C7E645A83, # noqa: E501 - }, - ), - }, - }, - { - "indexes": {"data": [11], "gas": 0, "value": -1}, - "network": [">=Cancun"], - "result": { - contract_0: Account( - storage={ - 0: 1, - 1: 1, - 2: 1, - 3: 1, - 10: 0x769BF9AC56BEA3FF40232BCB1B6BD159315D84715B8E679F2D355961915ABF0, # noqa: E501 - 11: 0x5ACB4B400E90C0063006A39F478F3E865E306DD5CD56F356E2E8CD8FE7EDAE6, # noqa: E501 - 20: 0x769BF9AC56BEA3FF40232BCB1B6BD159315D84715B8E679F2D355961915ABF0, # noqa: E501 - 21: 0x5ACB4B400E90C0063006A39F478F3E865E306DD5CD56F356E2E8CD8FE7EDAE6, # noqa: E501 - }, - ), - }, - }, - { - "indexes": {"data": [13, 15], "gas": 0, "value": -1}, - "network": [">=Cancun"], - "result": { - contract_0: Account( - storage={ - 0: 1, - 1: 1, - 2: 1, - 3: 1, - 10: 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD3, # noqa: E501 - 11: 0x15ED738C0E0A7C92E7845F96B2AE9C0A68A6A449E3538FC7FF3EBF7A5A18A2C4, # noqa: E501 - 20: 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD3, # noqa: E501 - 21: 0x15ED738C0E0A7C92E7845F96B2AE9C0A68A6A449E3538FC7FF3EBF7A5A18A2C4, # noqa: E501 - }, - ), - }, - }, - { - "indexes": {"data": [14], "gas": 0, "value": -1}, - "network": [">=Cancun"], - "result": { - contract_0: Account( - storage={ - 0: 1, - 1: 1, - 2: 1, - 3: 1, - 10: 1, - 11: 2, - 20: 1, - 21: 2, - }, - ), - }, - }, - { - "indexes": {"data": [16], "gas": 0, "value": -1}, - "network": [">=Cancun"], - "result": { - contract_0: Account( - storage={ - 0: 1, - 1: 1, - 2: 1, - 3: 1, - 10: 1, - 11: 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45, # noqa: E501 - 20: 1, - 21: 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45, # noqa: E501 - }, - ), - }, - }, - { - "indexes": {"data": [17], "gas": 0, "value": -1}, - "network": [">=Cancun"], - "result": { - contract_0: Account( - storage={ - 0: 1, - 1: 1, - 10: 0x113AECCECDAF57CD8C0AACE591774949DCDAF892555FA86726FA7E679B89C067, # noqa: E501 - 11: 0xBFFBA84127A19ABDE488A8251A9A3FCE33B34A76F96AAFB11AB4A6CEF3E9979, # noqa: E501 - }, - ), - }, - }, - { - "indexes": {"data": [18], "gas": 0, "value": -1}, - "network": [">=Cancun"], - "result": { - contract_0: Account( - storage={ - 0: 1, - 1: 1, - 2: 1, - 3: 1, - 10: 0x1FD3B816D9951DCB9AA9797D25E51A865987703AE83CD69C4658679F0350AE2B, # noqa: E501 - 11: 0x29CE3D80A74DDC13784BEB25CA9FBFD048A3265A32C6F38B92060C5093A0E7A7, # noqa: E501 - 20: 0x1FD3B816D9951DCB9AA9797D25E51A865987703AE83CD69C4658679F0350AE2B, # noqa: E501 - 21: 0x29CE3D80A74DDC13784BEB25CA9FBFD048A3265A32C6F38B92060C5093A0E7A7, # noqa: E501 - }, - ), - }, - }, - { - "indexes": {"data": [19], "gas": 0, "value": -1}, - "network": [">=Cancun"], - "result": { - contract_0: Account( - storage={ - 0: 1, - 1: 1, - 2: 1, - 3: 1, - 10: 0xCCBEC17235F5B9CC5E42F3DF6364A76ECDD0101DDDA8FC5DC0BA0B59C0E5628, # noqa: E501 - 11: 0x69EF5E376C0A1EA82F9DFC2E0001A7F385D655EEF9A6F976C7A5D2C493EA3AD, # noqa: E501 - 20: 0xCCBEC17235F5B9CC5E42F3DF6364A76ECDD0101DDDA8FC5DC0BA0B59C0E5628, # noqa: E501 - 21: 0x69EF5E376C0A1EA82F9DFC2E0001A7F385D655EEF9A6F976C7A5D2C493EA3AD, # noqa: E501 - }, - ), - }, - }, - { - "indexes": {"data": [20], "gas": 0, "value": -1}, - "network": [">=Cancun"], - "result": { - contract_0: Account( - storage={ - 0: 1, - 1: 1, - 10: 0x1FD3B816D9951DCB9AA9797D25E51A865987703AE83CD69C4658679F0350AE2B, # noqa: E501 - 11: 0x29CE3D80A74DDC13784BEB25CA9FBFD048A3265A32C6F38B92060C5093A0E7A7, # noqa: E501 - 20: 0x2C15ED1902E189486AB6B625AA982510AEF6246B21A1E1BCEA382DA4D735E8BA, # noqa: E501 - 21: 0x2103E58CBD2FA8081763442AB46C26A9B8051E9B049C3948C8D7D0E139C5E3F, # noqa: E501 - }, - ), - }, - }, - { - "indexes": {"data": [21], "gas": [0, 3], "value": -1}, - "network": [">=Cancun"], - "result": { - contract_0: Account( - storage={ - 0: 1, - 1: 1, - 10: 0x1D78954C630B3895FBBFAFAC1294F2C0158879FDC70BFE18222890E7BFB66FBA, # noqa: E501 - 11: 0x101C3346E98B136A7078AEBD427DCED763722D77E3D7985342E0BFFCC6EA4D56, # noqa: E501 - }, - ), - }, - }, - { - "indexes": {"data": [22], "gas": 0, "value": -1}, - "network": [">=Cancun"], - "result": { - contract_0: Account( - storage={ - 0: 1, - 1: 1, - 2: 1, - 3: 1, - 10: 0x2FA739D4CDE056D8FD75427345CBB34159856E06A4FFAD64159C4773F23FBF4B, # noqa: E501 - 11: 0x1EED5D5325C31FC89DD541A13D7F63B981FAE8D4BF78A6B08A38A601FCFEA97B, # noqa: E501 - 20: 0x2FA739D4CDE056D8FD75427345CBB34159856E06A4FFAD64159C4773F23FBF4B, # noqa: E501 - 21: 0x1EED5D5325C31FC89DD541A13D7F63B981FAE8D4BF78A6B08A38A601FCFEA97B, # noqa: E501 - }, - ), - }, - }, - { - "indexes": {"data": [23], "gas": 0, "value": -1}, - "network": [">=Cancun"], - "result": { - contract_0: Account( - storage={ - 0: 1, - 1: 1, - 2: 1, - 3: 1, - 10: 0x2F588CFFE99DB877A4434B598AB28F81E0522910EA52B45F0ADAA772B2D5D352, # noqa: E501 - 11: 0x1D701EC9E3FCA50E84777F0F68CAFF5BFF48CF6A6BD4428462AE9366CF0582B0, # noqa: E501 - 20: 0x2F588CFFE99DB877A4434B598AB28F81E0522910EA52B45F0ADAA772B2D5D352, # noqa: E501 - 21: 0x1D701EC9E3FCA50E84777F0F68CAFF5BFF48CF6A6BD4428462AE9366CF0582B0, # noqa: E501 - }, - ), - }, - }, - { - "indexes": {"data": [24], "gas": 0, "value": -1}, - "network": [">=Cancun"], - "result": { - contract_0: Account( - storage={ - 0: 1, - 1: 1, - 10: 0x2FA739D4CDE056D8FD75427345CBB34159856E06A4FFAD64159C4773F23FBF4B, # noqa: E501 - 11: 0x1EED5D5325C31FC89DD541A13D7F63B981FAE8D4BF78A6B08A38A601FCFEA97B, # noqa: E501 - 20: 0x8E2142845DB159BD105879A109FE7A6F254ED3DDAE0E9CD8A2AEAE05E5F647B, # noqa: E501 - 21: 0x221108EE615499D2E0A1113CA1A858A34E055F9DA2D30E6E6AB392B049944A92, # noqa: E501 - }, - ), - }, - }, - { - "indexes": {"data": [25], "gas": 0, "value": -1}, - "network": [">=Cancun"], - "result": { - contract_0: Account( - storage={ - 0: 1, - 1: 1, - 2: 1, - 3: 1, - 10: 0x769BF9AC56BEA3FF40232BCB1B6BD159315D84715B8E679F2D355961915ABF0, # noqa: E501 - 11: 0x2AB799BEE0489429554FDB7C8D086475319E63B40B9C5B57CDF1FF3DD9FE2261, # noqa: E501 - 20: 0x769BF9AC56BEA3FF40232BCB1B6BD159315D84715B8E679F2D355961915ABF0, # noqa: E501 - 21: 0x2AB799BEE0489429554FDB7C8D086475319E63B40B9C5B57CDF1FF3DD9FE2261, # noqa: E501 - }, - ), - }, - }, - { - "indexes": {"data": [27], "gas": 0, "value": -1}, - "network": [">=Cancun"], - "result": { - contract_0: Account( - storage={ - 0: 1, - 1: 1, - 2: 1, - 3: 1, - 10: 1, - 11: 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45, # noqa: E501 - 20: 1, - 21: 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45, # noqa: E501 - }, - ), - }, - }, - { - "indexes": {"data": [28], "gas": 0, "value": -1}, - "network": [">=Cancun"], - "result": { - contract_0: Account( - storage={ - 0: 1, - 1: 1, - 2: 1, - 3: 1, - 10: 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD3, # noqa: E501 - 11: 0x1A76DAE6D3272396D0CBE61FCED2BC532EDAC647851E3AC53CE1CC9C7E645A83, # noqa: E501 - 20: 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD3, # noqa: E501 - 21: 0x1A76DAE6D3272396D0CBE61FCED2BC532EDAC647851E3AC53CE1CC9C7E645A83, # noqa: E501 - }, - ), - }, - }, - { - "indexes": {"data": [29], "gas": 0, "value": -1}, - "network": [">=Cancun"], - "result": { - contract_0: Account( - storage={ - 0: 1, - 1: 1, - 2: 1, - 3: 1, - 10: 1, - 11: 2, - 20: 1, - 21: 2, - }, - ), - }, - }, - { - "indexes": {"data": [30], "gas": [0, 3], "value": -1}, - "network": [">=Cancun"], - "result": { - contract_0: Account( - storage={ - 0: 1, - 1: 1, - 10: 0x113AECCECDAF57CD8C0AACE591774949DCDAF892555FA86726FA7E679B89C067, # noqa: E501 - 11: 0x246493EECEB7867DDA07BB342FD7B460B44635E9F8DB1F922A7541A9E93E63CE, # noqa: E501 - }, - ), - }, - }, - { - "indexes": {"data": [31], "gas": 0, "value": -1}, - "network": [">=Cancun"], - "result": { - contract_0: Account( - storage={ - 0: 1, - 1: 1, - 2: 1, - 3: 1, - 10: 0x1FD3B816D9951DCB9AA9797D25E51A865987703AE83CD69C4658679F0350AE2B, # noqa: E501 - 11: 0x69610F239E3C41640045A90B6E1988D4EDE443735AAD701AA1A7FC644DC15A0, # noqa: E501 - 20: 0x1FD3B816D9951DCB9AA9797D25E51A865987703AE83CD69C4658679F0350AE2B, # noqa: E501 - 21: 0x69610F239E3C41640045A90B6E1988D4EDE443735AAD701AA1A7FC644DC15A0, # noqa: E501 - }, - ), - }, - }, - { - "indexes": {"data": [32], "gas": 0, "value": -1}, - "network": [">=Cancun"], - "result": { - contract_0: Account( - storage={ - 0: 1, - 1: 1, - 2: 1, - 3: 1, - 10: 0xCCBEC17235F5B9CC5E42F3DF6364A76ECDD0101DDDA8FC5DC0BA0B59C0E5628, # noqa: E501 - 11: 0x29C5588F6A70FE3F355665F3A1813DDE5F24053278D75AF5CFA62EEA8F3E599A, # noqa: E501 - 20: 0xCCBEC17235F5B9CC5E42F3DF6364A76ECDD0101DDDA8FC5DC0BA0B59C0E5628, # noqa: E501 - 21: 0x29C5588F6A70FE3F355665F3A1813DDE5F24053278D75AF5CFA62EEA8F3E599A, # noqa: E501 - }, - ), - }, - }, - { - "indexes": {"data": [33], "gas": [0], "value": -1}, - "network": [">=Cancun"], - "result": { - contract_0: Account( - storage={ - 0: 1, - 1: 1, - 10: 0x1FD3B816D9951DCB9AA9797D25E51A865987703AE83CD69C4658679F0350AE2B, # noqa: E501 - 11: 0x69610F239E3C41640045A90B6E1988D4EDE443735AAD701AA1A7FC644DC15A0, # noqa: E501 - 20: 0x2C15ED1902E189486AB6B625AA982510AEF6246B21A1E1BCEA382DA4D735E8BA, # noqa: E501 - 21: 0x2E54101A155EA5A936DA1173D63A95F2FC0118A7B82806F8AF930F08C4E09F08, # noqa: E501 - }, - ), - }, - }, - { - "indexes": {"data": [34], "gas": [0, 3], "value": -1}, - "network": [">=Cancun"], - "result": { - contract_0: Account( - storage={ - 0: 1, - 1: 1, - 10: 0x1D78954C630B3895FBBFAFAC1294F2C0158879FDC70BFE18222890E7BFB66FBA, # noqa: E501 - 11: 0x20481B2BF7A68CBF47D796F93F038986340F3D19849A3239F93FCC1A1192AFF1, # noqa: E501 - }, - ), - }, - }, - { - "indexes": {"data": [35], "gas": 0, "value": -1}, - "network": [">=Cancun"], - "result": { - contract_0: Account( - storage={ - 0: 1, - 1: 1, - 2: 1, - 3: 1, - 10: 0x2FA739D4CDE056D8FD75427345CBB34159856E06A4FFAD64159C4773F23FBF4B, # noqa: E501 - 11: 0x1176F11FBB6E80611A7B04154401F4A4158681BCA8F923DCB1E7E614DB7E53CC, # noqa: E501 - 20: 0x2FA739D4CDE056D8FD75427345CBB34159856E06A4FFAD64159C4773F23FBF4B, # noqa: E501 - 21: 0x1176F11FBB6E80611A7B04154401F4A4158681BCA8F923DCB1E7E614DB7E53CC, # noqa: E501 - }, - ), - }, - }, - { - "indexes": {"data": [36], "gas": 0, "value": -1}, - "network": [">=Cancun"], - "result": { - contract_0: Account( - storage={ - 0: 1, - 1: 1, - 2: 1, - 3: 1, - 10: 0x2F588CFFE99DB877A4434B598AB28F81E0522910EA52B45F0ADAA772B2D5D352, # noqa: E501 - 11: 0x12F42FA8FD34FB1B33D8C6A718B6590198389B26FC9D8808D971F8B009777A97, # noqa: E501 - 20: 0x2F588CFFE99DB877A4434B598AB28F81E0522910EA52B45F0ADAA772B2D5D352, # noqa: E501 - 21: 0x12F42FA8FD34FB1B33D8C6A718B6590198389B26FC9D8808D971F8B009777A97, # noqa: E501 - }, - ), - }, - }, - { - "indexes": {"data": [37], "gas": [0], "value": -1}, - "network": [">=Cancun"], - "result": { - contract_0: Account( - storage={ - 0: 1, - 1: 1, - 10: 0x2FA739D4CDE056D8FD75427345CBB34159856E06A4FFAD64159C4773F23FBF4B, # noqa: E501 - 11: 0x1176F11FBB6E80611A7B04154401F4A4158681BCA8F923DCB1E7E614DB7E53CC, # noqa: E501 - 20: 0x8E2142845DB159BD105879A109FE7A6F254ED3DDAE0E9CD8A2AEAE05E5F647B, # noqa: E501 - 21: 0xE5345847FDD0656D7AF3479DFD8FFBA497C0AF3C59EBC1ED16CF9668EE8B2B5, # noqa: E501 - }, - ), - }, - }, - { - "indexes": {"data": -1, "gas": [1, 2], "value": -1}, - "network": [">=Cancun"], - "result": {contract_0: Account(storage={})}, - }, - { - "indexes": { - "data": [ - 32, - 35, - 36, - 10, - 11, - 13, - 14, - 15, - 16, - 18, - 19, - 22, - 23, - 25, - 27, - 28, - 29, - ], - "gas": [3], - "value": -1, - }, - "network": [">=Cancun"], - "result": {contract_0: Account(storage={})}, - }, - { - "indexes": { - "data": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 26], - "gas": [3], - "value": -1, - }, - "network": [">=Cancun"], - "result": {contract_0: Account(storage={0: 1, 1: 1, 2: 1, 3: 1})}, - }, - { - "indexes": {"data": [17], "gas": [3], "value": -1}, - "network": [">=Cancun"], - "result": { - contract_0: Account( - storage={ - 0: 1, - 1: 1, - 10: 0x113AECCECDAF57CD8C0AACE591774949DCDAF892555FA86726FA7E679B89C067, # noqa: E501 - 11: 0xBFFBA84127A19ABDE488A8251A9A3FCE33B34A76F96AAFB11AB4A6CEF3E9979, # noqa: E501 - }, - ), - }, - }, - { - "indexes": {"data": [33, 37, 20, 24, 31], "gas": [3], "value": -1}, - "network": [">=Cancun"], - "result": {contract_0: Account(storage={0: 0})}, - }, - ] - - post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork) - - tx_data = [ - Hash(0x0) - + Hash(0x0) - + Hash(0x0) - + Hash(0x0) - + Hash(0x0) - + Hash(0x0) - + Hash(0x2), - Hash(0x0) - + Hash(0x0) - + Hash(0x0) - + Hash(0x0) - + Hash(0x0) - + Hash(0x0) - + Hash(0x3), - Hash(0x0) - + Hash(0x0) - + Hash(0x0) - + Hash(0x0) - + Hash(0x0) - + Hash(0x0) - + Hash(0x0), - Hash(0x0) - + Hash(0x0) - + Hash(0x0) - + Hash(0x0) - + Hash(0x0) - + Hash(0x0) - + Hash( - 0x30644E72E131A029B85045B68181585D2833E84879B9709143E1F593F0000000 - ), - Hash(0x0) - + Hash(0x0) - + Hash(0x0) - + Hash(0x0) - + Hash(0x0) - + Hash(0x0) - + Hash( - 0x30644E72E131A029B85045B68181585D2833E84879B9709143E1F593EFFFFFFF - ), - Hash(0x0) - + Hash(0x0) - + Hash(0x0) - + Hash(0x0) - + Hash(0x0) - + Hash(0x0) - + Hash(0x1), - Hash(0x0) - + Hash(0x0) - + Hash(0x0) - + Hash(0x0) - + Hash(0x0) - + Hash(0x0) - + Hash( - 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD46 - ), - Hash(0x0) - + Hash(0x0) - + Hash(0x0) - + Hash(0x0) - + Hash(0x0) - + Hash(0x0) - + Hash( - 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45 - ), - Hash(0x0) - + Hash(0x0) - + Hash(0x0) - + Hash(0x0) - + Hash(0x0) - + Hash(0x0) - + Hash( - 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - ), - Hash(0x0) - + Hash(0x0) - + Hash(0x0) - + Hash(0x0) - + Hash(0x0) - + Hash(0x0) - + Hash( - 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE - ), - Hash(0x1) - + Hash( - 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45 - ) - + Hash(0x1) - + Hash( - 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45 - ) - + Hash(0x1) - + Hash( - 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45 - ) - + Hash(0x2), - Hash(0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD3) - + Hash( - 0x1A76DAE6D3272396D0CBE61FCED2BC532EDAC647851E3AC53CE1CC9C7E645A83 - ) - + Hash(0x1) - + Hash( - 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45 - ) - + Hash(0x1) - + Hash( - 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45 - ) - + Hash(0x3), - Hash(0x1) - + Hash(0x2) - + Hash(0x1) - + Hash( - 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45 - ) - + Hash(0x1) - + Hash( - 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45 - ) - + Hash(0x0), - Hash(0x1) - + Hash(0x2) - + Hash(0x1) - + Hash(0x2) - + Hash(0x1) - + Hash(0x2) - + Hash(0x2), - Hash(0x1) - + Hash(0x2) - + Hash(0x0) - + Hash(0x0) - + Hash(0x1) - + Hash( - 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45 - ) - + Hash( - 0x30644E72E131A029B85045B68181585D2833E84879B9709143E1F593F0000000 - ), - Hash(0x1) - + Hash(0x2) - + Hash(0x1) - + Hash(0x2) - + Hash(0x1) - + Hash( - 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45 - ) - + Hash( - 0x30644E72E131A029B85045B68181585D2833E84879B9709143E1F593EFFFFFFF - ), - Hash(0x1) - + Hash( - 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45 - ) - + Hash(0x0) - + Hash(0x0) - + Hash(0x1) - + Hash( - 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45 - ) - + Hash(0x1), - Hash(0xCCBEC17235F5B9CC5E42F3DF6364A76ECDD0101DDDA8FC5DC0BA0B59C0E5628) - + Hash( - 0x69EF5E376C0A1EA82F9DFC2E0001A7F385D655EEF9A6F976C7A5D2C493EA3AD - ) - + Hash(0x1) - + Hash( - 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45 - ) - + Hash(0x1) - + Hash( - 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45 - ) - + Hash(0x0), - Hash(0xCCBEC17235F5B9CC5E42F3DF6364A76ECDD0101DDDA8FC5DC0BA0B59C0E5628) - + Hash( - 0x69EF5E376C0A1EA82F9DFC2E0001A7F385D655EEF9A6F976C7A5D2C493EA3AD - ) - + Hash( - 0xCCBEC17235F5B9CC5E42F3DF6364A76ECDD0101DDDA8FC5DC0BA0B59C0E5628 - ) - + Hash( - 0x69EF5E376C0A1EA82F9DFC2E0001A7F385D655EEF9A6F976C7A5D2C493EA3AD - ) - + Hash( - 0xCCBEC17235F5B9CC5E42F3DF6364A76ECDD0101DDDA8FC5DC0BA0B59C0E5628 - ) - + Hash( - 0x69EF5E376C0A1EA82F9DFC2E0001A7F385D655EEF9A6F976C7A5D2C493EA3AD - ) - + Hash(0x2), - Hash(0xCCBEC17235F5B9CC5E42F3DF6364A76ECDD0101DDDA8FC5DC0BA0B59C0E5628) - + Hash( - 0x69EF5E376C0A1EA82F9DFC2E0001A7F385D655EEF9A6F976C7A5D2C493EA3AD - ) - + Hash(0x0) - + Hash(0x0) - + Hash(0x1) - + Hash( - 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45 - ) - + Hash( - 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD46 - ), - Hash(0xCCBEC17235F5B9CC5E42F3DF6364A76ECDD0101DDDA8FC5DC0BA0B59C0E5628) - + Hash( - 0x69EF5E376C0A1EA82F9DFC2E0001A7F385D655EEF9A6F976C7A5D2C493EA3AD - ) - + Hash( - 0xCCBEC17235F5B9CC5E42F3DF6364A76ECDD0101DDDA8FC5DC0BA0B59C0E5628 - ) - + Hash( - 0x69EF5E376C0A1EA82F9DFC2E0001A7F385D655EEF9A6F976C7A5D2C493EA3AD - ) - + Hash(0x1) - + Hash( - 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45 - ) - + Hash( - 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45 - ), - Hash( - 0x2F588CFFE99DB877A4434B598AB28F81E0522910EA52B45F0ADAA772B2D5D352 - ) - + Hash( - 0x1D701EC9E3FCA50E84777F0F68CAFF5BFF48CF6A6BD4428462AE9366CF0582B0 - ) - + Hash(0x1) - + Hash( - 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45 - ) - + Hash(0x1) - + Hash( - 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45 - ) - + Hash(0x0), - Hash( - 0x2F588CFFE99DB877A4434B598AB28F81E0522910EA52B45F0ADAA772B2D5D352 - ) - + Hash( - 0x1D701EC9E3FCA50E84777F0F68CAFF5BFF48CF6A6BD4428462AE9366CF0582B0 - ) - + Hash( - 0x2F588CFFE99DB877A4434B598AB28F81E0522910EA52B45F0ADAA772B2D5D352 - ) - + Hash( - 0x1D701EC9E3FCA50E84777F0F68CAFF5BFF48CF6A6BD4428462AE9366CF0582B0 - ) - + Hash( - 0x2F588CFFE99DB877A4434B598AB28F81E0522910EA52B45F0ADAA772B2D5D352 - ) - + Hash( - 0x1D701EC9E3FCA50E84777F0F68CAFF5BFF48CF6A6BD4428462AE9366CF0582B0 - ) - + Hash(0x2), - Hash( - 0x2F588CFFE99DB877A4434B598AB28F81E0522910EA52B45F0ADAA772B2D5D352 - ) - + Hash( - 0x1D701EC9E3FCA50E84777F0F68CAFF5BFF48CF6A6BD4428462AE9366CF0582B0 - ) - + Hash(0x0) - + Hash(0x0) - + Hash(0x1) - + Hash( - 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45 - ) - + Hash( - 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - ), - Hash( - 0x2F588CFFE99DB877A4434B598AB28F81E0522910EA52B45F0ADAA772B2D5D352 - ) - + Hash( - 0x1D701EC9E3FCA50E84777F0F68CAFF5BFF48CF6A6BD4428462AE9366CF0582B0 - ) - + Hash( - 0x2F588CFFE99DB877A4434B598AB28F81E0522910EA52B45F0ADAA772B2D5D352 - ) - + Hash( - 0x1D701EC9E3FCA50E84777F0F68CAFF5BFF48CF6A6BD4428462AE9366CF0582B0 - ) - + Hash(0x1) - + Hash( - 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45 - ) - + Hash( - 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE - ), - Hash(0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD3) - + Hash( - 0x15ED738C0E0A7C92E7845F96B2AE9C0A68A6A449E3538FC7FF3EBF7A5A18A2C4 - ) - + Hash(0x1) - + Hash(0x2) - + Hash(0x1) - + Hash(0x2) - + Hash(0x3), - Hash(0x1) - + Hash( - 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45 - ) - + Hash(0x1) - + Hash(0x2) - + Hash(0x1) - + Hash(0x2) - + Hash(0x0), - Hash(0x1) - + Hash( - 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45 - ) - + Hash(0x0) - + Hash(0x0) - + Hash(0x1) - + Hash(0x2) - + Hash( - 0x30644E72E131A029B85045B68181585D2833E84879B9709143E1F593F0000000 - ), - Hash(0x1) - + Hash( - 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45 - ) - + Hash(0x1) - + Hash( - 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45 - ) - + Hash(0x1) - + Hash(0x2) - + Hash( - 0x30644E72E131A029B85045B68181585D2833E84879B9709143E1F593EFFFFFFF - ), - Hash(0x1) - + Hash(0x2) - + Hash(0x0) - + Hash(0x0) - + Hash(0x1) - + Hash(0x2) - + Hash(0x1), - Hash(0xCCBEC17235F5B9CC5E42F3DF6364A76ECDD0101DDDA8FC5DC0BA0B59C0E5628) - + Hash( - 0x29C5588F6A70FE3F355665F3A1813DDE5F24053278D75AF5CFA62EEA8F3E599A - ) - + Hash(0x1) - + Hash(0x2) - + Hash(0x1) - + Hash(0x2) - + Hash(0x0), - Hash(0xCCBEC17235F5B9CC5E42F3DF6364A76ECDD0101DDDA8FC5DC0BA0B59C0E5628) - + Hash( - 0x29C5588F6A70FE3F355665F3A1813DDE5F24053278D75AF5CFA62EEA8F3E599A - ) - + Hash( - 0xCCBEC17235F5B9CC5E42F3DF6364A76ECDD0101DDDA8FC5DC0BA0B59C0E5628 - ) - + Hash( - 0x29C5588F6A70FE3F355665F3A1813DDE5F24053278D75AF5CFA62EEA8F3E599A - ) - + Hash( - 0xCCBEC17235F5B9CC5E42F3DF6364A76ECDD0101DDDA8FC5DC0BA0B59C0E5628 - ) - + Hash( - 0x29C5588F6A70FE3F355665F3A1813DDE5F24053278D75AF5CFA62EEA8F3E599A - ) - + Hash(0x2), - Hash(0xCCBEC17235F5B9CC5E42F3DF6364A76ECDD0101DDDA8FC5DC0BA0B59C0E5628) - + Hash( - 0x29C5588F6A70FE3F355665F3A1813DDE5F24053278D75AF5CFA62EEA8F3E599A - ) - + Hash(0x0) - + Hash(0x0) - + Hash(0x1) - + Hash(0x2) - + Hash( - 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD46 - ), - Hash(0xCCBEC17235F5B9CC5E42F3DF6364A76ECDD0101DDDA8FC5DC0BA0B59C0E5628) - + Hash( - 0x29C5588F6A70FE3F355665F3A1813DDE5F24053278D75AF5CFA62EEA8F3E599A - ) - + Hash( - 0xCCBEC17235F5B9CC5E42F3DF6364A76ECDD0101DDDA8FC5DC0BA0B59C0E5628 - ) - + Hash( - 0x29C5588F6A70FE3F355665F3A1813DDE5F24053278D75AF5CFA62EEA8F3E599A - ) - + Hash(0x1) - + Hash(0x2) - + Hash( - 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45 - ), - Hash( - 0x2F588CFFE99DB877A4434B598AB28F81E0522910EA52B45F0ADAA772B2D5D352 - ) - + Hash( - 0x12F42FA8FD34FB1B33D8C6A718B6590198389B26FC9D8808D971F8B009777A97 - ) - + Hash(0x1) - + Hash(0x2) - + Hash(0x1) - + Hash(0x2) - + Hash(0x0), - Hash( - 0x2F588CFFE99DB877A4434B598AB28F81E0522910EA52B45F0ADAA772B2D5D352 - ) - + Hash( - 0x12F42FA8FD34FB1B33D8C6A718B6590198389B26FC9D8808D971F8B009777A97 - ) - + Hash( - 0x2F588CFFE99DB877A4434B598AB28F81E0522910EA52B45F0ADAA772B2D5D352 - ) - + Hash( - 0x12F42FA8FD34FB1B33D8C6A718B6590198389B26FC9D8808D971F8B009777A97 - ) - + Hash( - 0x2F588CFFE99DB877A4434B598AB28F81E0522910EA52B45F0ADAA772B2D5D352 - ) - + Hash( - 0x12F42FA8FD34FB1B33D8C6A718B6590198389B26FC9D8808D971F8B009777A97 - ) - + Hash(0x2), - Hash( - 0x2F588CFFE99DB877A4434B598AB28F81E0522910EA52B45F0ADAA772B2D5D352 - ) - + Hash( - 0x12F42FA8FD34FB1B33D8C6A718B6590198389B26FC9D8808D971F8B009777A97 - ) - + Hash(0x0) - + Hash(0x0) - + Hash(0x1) - + Hash(0x2) - + Hash( - 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - ), - Hash( - 0x2F588CFFE99DB877A4434B598AB28F81E0522910EA52B45F0ADAA772B2D5D352 - ) - + Hash( - 0x12F42FA8FD34FB1B33D8C6A718B6590198389B26FC9D8808D971F8B009777A97 - ) - + Hash( - 0x2F588CFFE99DB877A4434B598AB28F81E0522910EA52B45F0ADAA772B2D5D352 - ) - + Hash( - 0x12F42FA8FD34FB1B33D8C6A718B6590198389B26FC9D8808D971F8B009777A97 - ) - + Hash(0x1) - + Hash(0x2) - + Hash( - 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE - ), - ] - tx_gas = [2000000, 90000, 110000, 150000] - - tx = Transaction( - sender=sender, - to=contract_0, - data=tx_data[d], - gas_limit=tx_gas[g], - nonce=1, - error=_exc, - ) - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/vmArithmeticTest/test_exp_power256_of256.py b/tests/ported_static/vmArithmeticTest/test_exp_power256_of256.py index 3a4d820a4be..accbc8a2290 100644 --- a/tests/ported_static/vmArithmeticTest/test_exp_power256_of256.py +++ b/tests/ported_static/vmArithmeticTest/test_exp_power256_of256.py @@ -1281,7 +1281,6 @@ def test_exp_power256_of256( sender=sender, to=target, data=Bytes("693c6139") + Hash(0x0), - gas_limit=16777216, value=1, ) diff --git a/tests/ported_static/vmArithmeticTest/test_two_ops.py b/tests/ported_static/vmArithmeticTest/test_two_ops.py index db7aeac6d17..41e08c2067e 100644 --- a/tests/ported_static/vmArithmeticTest/test_two_ops.py +++ b/tests/ported_static/vmArithmeticTest/test_two_ops.py @@ -1655,7 +1655,6 @@ def test_two_ops( sender=sender, to=target, data=Bytes("00"), - gas_limit=16777216, value=1, ) From 617917ade562374cf249f1d8a1a32385cf13e311 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Faruk=20Irmak?= Date: Thu, 25 Jun 2026 07:41:55 +0300 Subject: [PATCH 052/233] feat(tests): CREATE/CREATE2 and CALL clear return data on failed pre-checks (#3032) * feat(tests): CREATE/CREATE2 and CALL clear return data on failed pre-checks Entering a CREATE or CALL must reset the return-data buffer unconditionally, including the pre-checks that abort before the callee/initcode runs. The existing CREATE/CREATE2 return-data tests always execute the initcode (RETURN or REVERT) and the CALL tests always enter the callee, so the early-return pre-check paths are uncovered. A client that resets the buffer only after a pre-check leaves stale return data from a preceding CALL observable via RETURNDATASIZE/RETURNDATACOPY. Add EIP-211 state tests under byzantium/eip211_return_data, alongside the existing test_selfdestruct_clears_return_data: - test_create: a CALL returning 32 bytes followed by a CREATE/CREATE2 with value exceeding the creator's balance (failing the balance pre-check before initcode). CREATE is valid from Byzantium, CREATE2 from Constantinople. - test_call: a CALL returning 32 bytes followed by a CALL with value exceeding the caller's balance (failing the balance pre-check before entering the callee). Both assert RETURNDATASIZE is 0 after the failed create/call. The 1024 call-stack depth pre-check is intentionally not tested: since EIP-150's 63/64 gas-forwarding rule, a call chain runs out of gas before reaching depth 1024, so that branch is effectively unreachable. Co-Authored-By: Claude Opus 4.8 (1M context) * refactor: create/call failure cleans return buffer --------- Co-authored-by: Claude Opus 4.8 (1M context) Co-authored-by: LouisTsai --- .../byzantium/eip211_return_data/test_call.py | 84 +++++++++++++++++ .../eip211_return_data/test_create.py | 94 +++++++++++++++++++ 2 files changed, 178 insertions(+) create mode 100644 tests/byzantium/eip211_return_data/test_call.py create mode 100644 tests/byzantium/eip211_return_data/test_create.py diff --git a/tests/byzantium/eip211_return_data/test_call.py b/tests/byzantium/eip211_return_data/test_call.py new file mode 100644 index 00000000000..fea905060d3 --- /dev/null +++ b/tests/byzantium/eip211_return_data/test_call.py @@ -0,0 +1,84 @@ +"""Test CALL return data buffer behavior on pre-check failure.""" + +import pytest +from execution_testing import ( + Account, + Alloc, + Op, + StateTestFiller, + Storage, + Transaction, +) + +from .spec import ref_spec_211 + +REFERENCE_SPEC_GIT_PATH = ref_spec_211.git_path +REFERENCE_SPEC_VERSION = ref_spec_211.version + + +@pytest.mark.valid_from("Byzantium") +def test_call_clears_return_data_on_insufficient_balance( + pre: Alloc, + state_test: StateTestFiller, +) -> None: + """ + Test that a CALL clears the return-data buffer even when the call fails the + insufficient-balance pre-check and the callee is never entered. + + A CALL whose value exceeds the caller's balance is a "light" failure: it + pushes 0 and never executes the callee, but per EIP-211 it must still reset + the return-data buffer. A client that skips the reset on this early-return + path would leave stale return data from a preceding CALL observable via + RETURNDATASIZE/RETURNDATACOPY. + + (The other CALL pre-check -- the 1024 call-stack depth limit -- is not + exercised here: since EIP-150's 63/64 gas-forwarding rule, a call chain + runs out of gas long before reaching depth 1024, so that branch is + effectively unreachable.) + + Storage layout: + slot N = RETURNDATASIZE after the funded CALL (expected 32) + slot N+1 = RETURNDATASIZE after the failing CALL (expected 0) + slot N+2 = the failing CALL result (expected 0, failure) + """ + storage = Storage() + + # Callee returns 32 bytes, so the caller's return-data buffer is 32 bytes. + callee = pre.deploy_contract( + code=Op.MSTORE(0, 0x11223344) + Op.RETURN(0, 32), + ) + + init_balance = 1 + + # Caller has balance 1, so a CALL with value 2 fails the balance pre-check + # before entering the callee. + caller = pre.deploy_contract( + balance=init_balance, + code=( + Op.CALL(gas=Op.GAS, address=callee, ret_size=32) + + Op.SSTORE( + storage.store_next(32, "rds_after_call"), Op.RETURNDATASIZE + ) + + Op.SSTORE( + storage.store_next(0, "failed_call_result"), + Op.CALL(gas=Op.GAS, address=callee, value=init_balance + 1), + ) + + Op.SSTORE( + storage.store_next(0, "rds_after_failed_call"), + Op.RETURNDATASIZE, + ) + + Op.STOP + ), + storage=dict.fromkeys(storage, 0xFF), + ) + + tx = Transaction( + sender=pre.fund_eoa(), + to=caller, + ) + + state_test( + pre=pre, + post={caller: Account(storage=storage)}, + tx=tx, + ) diff --git a/tests/byzantium/eip211_return_data/test_create.py b/tests/byzantium/eip211_return_data/test_create.py new file mode 100644 index 00000000000..317086293cd --- /dev/null +++ b/tests/byzantium/eip211_return_data/test_create.py @@ -0,0 +1,94 @@ +"""Test CREATE/CREATE2 return data buffer behavior on pre-check failure.""" + +import pytest +from execution_testing import ( + Account, + Alloc, + Op, + StateTestFiller, + Storage, + Transaction, +) + +from .spec import ref_spec_211 + +REFERENCE_SPEC_GIT_PATH = ref_spec_211.git_path +REFERENCE_SPEC_VERSION = ref_spec_211.version + + +@pytest.mark.valid_from("Byzantium") +@pytest.mark.parametrize( + "create_opcode", + [ + pytest.param(Op.CREATE, id="CREATE"), + pytest.param( + Op.CREATE2, + id="CREATE2", + marks=pytest.mark.valid_from("Constantinople"), + ), + ], +) +def test_create_clears_return_data_on_insufficient_balance( + create_opcode: Op, + pre: Alloc, + state_test: StateTestFiller, +) -> None: + """ + Test that entering CREATE/CREATE2 clears the return-data buffer even when + the create fails a pre-execution check (insufficient balance) and the + initcode never runs. + + Existing return-data tests for CREATE/CREATE2 always execute the initcode + (which RETURNs or REVERTs), covering only the post-initcode path. Entering + a CREATE must reset the return-data buffer unconditionally, including the + depth/nonce/balance pre-checks that abort before any initcode runs. A + client that clears the buffer only after those pre-checks would leave stale + return data from a preceding CALL via RETURNDATASIZE/RETURNDATACOPY. + + The caller performs a CALL that leaves 32 bytes of return data, then a + create with value exceeding its balance (failing the balance pre-check + before initcode), and asserts RETURNDATASIZE is 0 afterward. + + Storage layout: + slot N = RETURNDATASIZE after the CALL (expected 32) + slot N+1 = RETURNDATASIZE after the create (expected 0) + slot N+2 = the create result address (expected 0, i.e. failure) + """ + storage = Storage() + + # Callee returns 32 bytes, so the caller's return-data buffer is 32 bytes. + callee = pre.deploy_contract( + code=Op.MSTORE(0, 0x11223344) + Op.RETURN(0, 32), + ) + + init_balance = 1 + create_op = create_opcode(value=init_balance + 1) + + # Caller has balance 1, so a create with value 2 fails the balance + # pre-check before executing any initcode. + caller = pre.deploy_contract( + balance=init_balance, + code=( + Op.CALL(gas=Op.GAS, address=callee, ret_size=32) + + Op.SSTORE( + storage.store_next(32, "rds_after_call"), Op.RETURNDATASIZE + ) + + Op.SSTORE(storage.store_next(0, "create_result"), create_op) + + Op.SSTORE( + storage.store_next(0, "rds_after_create"), Op.RETURNDATASIZE + ) + + Op.STOP + ), + storage=dict.fromkeys(storage, 0xFF), + ) + + tx = Transaction( + sender=pre.fund_eoa(), + to=caller, + ) + + state_test( + pre=pre, + post={caller: Account(storage=storage)}, + tx=tx, + ) From fc4ac8cd1768e5ecc9ecf6dca586910f67746597 Mon Sep 17 00:00:00 2001 From: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Date: Thu, 25 Jun 2026 02:29:59 -0300 Subject: [PATCH 053/233] fix(clients): map ethrex empty-change-set BAL rejection to INVALID_BLOCK_ACCESS_LIST (#3046) ethrex correctly rejects an EIP-7928 block whose BAL contains a SlotChanges with an empty slot_changes list, returning INVALID with the message "Block access list storage_changes slot for account has an empty change set". The EthrexExceptionMapper already recognizes the sibling validate_ordering() messages (not-in-strictly-ascending-order, storage_changes-and-storage_reads) but was missing this one, so consume's strict exception matching reports test_bal_invalid_empty_slot_changes [unrelated_slot|demoted_noop] as failing even though ethrex's consensus behavior is correct. Add the missing alternative to the INVALID_BLOCK_ACCESS_LIST regex. --- .../testing/src/execution_testing/client_clis/clis/ethrex.py | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/testing/src/execution_testing/client_clis/clis/ethrex.py b/packages/testing/src/execution_testing/client_clis/clis/ethrex.py index cde6576a6d7..bb8ec00ec7e 100644 --- a/packages/testing/src/execution_testing/client_clis/clis/ethrex.py +++ b/packages/testing/src/execution_testing/client_clis/clis/ethrex.py @@ -176,6 +176,7 @@ class EthrexExceptionMapper(ExceptionMapper): r"exceeding max valid index \d+|" r"Failed to RLP decode BAL|" r"Block access list .+ not in strictly ascending order.*|" + r"Block access list .+ has an empty change set|" r"BAL validation failed for (tx \d+|system_tx|withdrawal): .*|" r"BAL validation failed: .*|" r"absent from BAL|" From 916604a4c06aad83ae33c0401d9192d1f11efde7 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Wed, 24 Jun 2026 23:33:37 -0600 Subject: [PATCH 054/233] refactor(test-tools): Remove `CodeGasMeasure` footgun (stop) (#3040) * refactor(test-tools): Remove `CodeGasMeasure` footgun (stop) * fix(tests): Fix CLZ test --- .../execution_testing/tools/tools_code/generators.py | 3 --- .../test_warm_status_revert.py | 1 - tests/berlin/eip2930_access_list/test_acl.py | 1 - tests/frontier/opcodes/test_call.py | 3 --- .../test_count_leading_zeros.py | 11 ++++------- tests/prague/eip7702_set_code_tx/test_gas.py | 1 - tests/prague/eip7702_set_code_tx/test_set_code_txs.py | 2 -- 7 files changed, 4 insertions(+), 18 deletions(-) diff --git a/packages/testing/src/execution_testing/tools/tools_code/generators.py b/packages/testing/src/execution_testing/tools/tools_code/generators.py index 3a5c79a0e07..ae9026c7fa3 100644 --- a/packages/testing/src/execution_testing/tools/tools_code/generators.py +++ b/packages/testing/src/execution_testing/tools/tools_code/generators.py @@ -161,7 +161,6 @@ def __new__( overhead_cost: int = 0, extra_stack_items: int = 0, sstore_key: int | Bytes = 0, - stop: bool = True, ) -> Self: """Assemble the bytecode that measures gas usage.""" res = Op.GAS + code + Op.GAS @@ -180,8 +179,6 @@ def __new__( + Op.SWAP1 + Op.SSTORE(sstore_key, Op.SUB) ) - if stop: - res += Op.STOP instance = super().__new__(cls, res) instance.code = code diff --git a/tests/berlin/eip2929_gas_cost_increases/test_warm_status_revert.py b/tests/berlin/eip2929_gas_cost_increases/test_warm_status_revert.py index 3925cd89958..97a36987279 100644 --- a/tests/berlin/eip2929_gas_cost_increases/test_warm_status_revert.py +++ b/tests/berlin/eip2929_gas_cost_increases/test_warm_status_revert.py @@ -49,7 +49,6 @@ def test_storage_warm_status_reverted_by_subcall( overhead_cost=sload_push_cost, extra_stack_items=1, sstore_key=1, - stop=False, ) # Also verify storage[0] value (should still be 1). diff --git a/tests/berlin/eip2930_access_list/test_acl.py b/tests/berlin/eip2930_access_list/test_acl.py index f8cec0ed3f5..226f842bc6c 100644 --- a/tests/berlin/eip2930_access_list/test_acl.py +++ b/tests/berlin/eip2930_access_list/test_acl.py @@ -288,7 +288,6 @@ def test_repeated_address_acl( overhead_cost=sload_push_cost, extra_stack_items=1, # SLOAD pushes 1 item to the stack sstore_key=0, - stop=False, # Because it's the first CodeGasMeasure ) sload1_measure = CodeGasMeasure( diff --git a/tests/frontier/opcodes/test_call.py b/tests/frontier/opcodes/test_call.py index 53233ee9188..49311749446 100644 --- a/tests/frontier/opcodes/test_call.py +++ b/tests/frontier/opcodes/test_call.py @@ -42,7 +42,6 @@ def test_call_large_offset_mstore( overhead_cost=call_push_cost, extra_stack_items=1, # Because CALL pushes 1 item to the stack sstore_key=0, - stop=False, # Because it's the first CodeGasMeasure ) mstore_measure = CodeGasMeasure( code=Op.MSTORE(offset=mem_offset, value=1), @@ -110,8 +109,6 @@ def test_call_memory_expands_on_early_revert( # Because CALL pushes 1 item to the stack extra_stack_items=1, sstore_key=0, - # Because it's the first CodeGasMeasure - stop=False, ) mstore_measure = CodeGasMeasure( # Low offset for not expanding memory diff --git a/tests/osaka/eip7939_count_leading_zeros/test_count_leading_zeros.py b/tests/osaka/eip7939_count_leading_zeros/test_count_leading_zeros.py index 7b294c31b1b..be4ce614a3b 100644 --- a/tests/osaka/eip7939_count_leading_zeros/test_count_leading_zeros.py +++ b/tests/osaka/eip7939_count_leading_zeros/test_count_leading_zeros.py @@ -130,13 +130,10 @@ def test_clz_gas_cost( ) -> None: """Test CLZ opcode gas cost.""" contract_address = pre.deploy_contract( - Op.SSTORE( - 0, - CodeGasMeasure( - code=Op.CLZ(Op.PUSH1(1)), - extra_stack_items=1, - overhead_cost=Op.PUSH1.gas_cost(fork), - ), + CodeGasMeasure( + code=Op.CLZ(Op.PUSH1(1)), + extra_stack_items=1, + overhead_cost=Op.PUSH1.gas_cost(fork), ), storage={"0x00": "0xdeadbeef"}, ) diff --git a/tests/prague/eip7702_set_code_tx/test_gas.py b/tests/prague/eip7702_set_code_tx/test_gas.py index 3321a893a63..431b4166add 100644 --- a/tests/prague/eip7702_set_code_tx/test_gas.py +++ b/tests/prague/eip7702_set_code_tx/test_gas.py @@ -1100,7 +1100,6 @@ def test_account_warming( overhead_cost=overhead_cost, extra_stack_items=1, sstore_key=check_address, - stop=False, ) for check_address in addresses_to_check ) diff --git a/tests/prague/eip7702_set_code_tx/test_set_code_txs.py b/tests/prague/eip7702_set_code_tx/test_set_code_txs.py index 293cbe16b49..e4be5bd2071 100644 --- a/tests/prague/eip7702_set_code_tx/test_set_code_txs.py +++ b/tests/prague/eip7702_set_code_tx/test_set_code_txs.py @@ -1498,14 +1498,12 @@ def test_set_code_address_and_authority_warm_state( overhead_cost=overhead_cost, extra_stack_items=1, sstore_key=slot_set_code_to_warm_state, - stop=False, ) code_gas_measure_authority = CodeGasMeasure( code=call_opcode(address=auth_signer), overhead_cost=overhead_cost, extra_stack_items=1, sstore_key=slot_authority_warm_state, - stop=False, ) callee_code = Bytecode() From a9cc76b8c1c93459627a6146e419dcb193c12e9d Mon Sep 17 00:00:00 2001 From: danceratopz Date: Thu, 25 Jun 2026 07:38:50 +0200 Subject: [PATCH 055/233] chore(tooling): clarify state_test preference in write-test skill (#3035) * chore(tooling): clarify state_test preference in write-test skill Strengthen the test-type guidance in the `write-test` skill so agents stop reaching for `blockchain_test` to wrap a single transaction. Make explicit that block-header checks (commonly `gas_used`), receipt logs, and 2D regular/state gas are all expressible from a `state_test` via `blockchain_test_header_verify`, the transaction's `expected_receipt`, and `state_gas_reservoir` respectively, so needing one of those is not a reason to use `blockchain_test`. * chore(tooling): prefer the tx receipt for gas checks in write-test skill The skill recommended `blockchain_test_header_verify=Header(gas_used=...)` to assert a transaction's gas usage from a `state_test`. The transaction's `expected_receipt=TransactionReceipt(cumulative_gas_used=...)` expresses the same check bound to the transaction, so point the gas guidance there and leave `blockchain_test_header_verify` for other block-header fields. The change follows the review that checks gas used through the transaction receipt rather than the block header: https://github.com/CPerezz/execution-specs/pull/3 --- .claude/commands/write-test.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.claude/commands/write-test.md b/.claude/commands/write-test.md index f8ef60cdede..3895d81e0ef 100644 --- a/.claude/commands/write-test.md +++ b/.claude/commands/write-test.md @@ -6,8 +6,9 @@ Conventions and patterns for writing consensus tests. Run this skill before writ - All test imports come from `execution_testing` — it is the public API - Core fixtures: `pre: Alloc` (pre-state builder), `state_test: StateTestFiller`, `blockchain_test: BlockchainTestFiller`, `fork: Fork` -- Prefer `state_test` for single-tx tests (simpler, avoids block-building false positives); `fill` auto-generates a `blockchain_test` from every `state_test` -- Use `blockchain_test` only for multi-block scenarios +- Rule: use `state_test` for single-transaction tests; `fill` auto-derives a `blockchain_test` from each, so no coverage is lost. +- Exception: use `blockchain_test` when the test needs more than one transaction (a `state_test` holds exactly one) or more than one block (e.g. transaction-ordering or fork-transition tests). +- Anti-pattern: wrapping one transaction in a `Block` to reach `blockchain_test`. A `state_test` can assert the transaction's gas used and receipt logs (the tx's `expected_receipt=TransactionReceipt(cumulative_gas_used=...)`), reserve state gas (the tx's `state_gas_reservoir=`), and other block-header fields (`blockchain_test_header_verify=Header(...)`) without it. ## Pre-State Setup From 3f888bc71f2d30d48b6f5a92c618ab283a6c98d5 Mon Sep 17 00:00:00 2001 From: danceratopz Date: Thu, 25 Jun 2026 09:08:13 +0200 Subject: [PATCH 056/233] fix(test-cli): raise a clear error when `gentest` can't find `ruff` (#3024) --- .../cli/gentest/source_code_generator.py | 47 +++++++++++-------- .../cli/gentest/tests/test_cli.py | 8 +++- 2 files changed, 34 insertions(+), 21 deletions(-) diff --git a/packages/testing/src/execution_testing/cli/gentest/source_code_generator.py b/packages/testing/src/execution_testing/cli/gentest/source_code_generator.py index ecbed9150e1..10f6807c663 100644 --- a/packages/testing/src/execution_testing/cli/gentest/source_code_generator.py +++ b/packages/testing/src/execution_testing/cli/gentest/source_code_generator.py @@ -53,10 +53,10 @@ def get_test_source(provider: Provider, template_path: str) -> str: def format_code(code: str) -> str: """ - Format the provided Python code using the Black code formatter. + Format the provided Python code using the ruff formatter. This function writes the given code to a temporary Python file, formats it - using the Black formatter, and returns the formatted code as a string. + using ruff, and returns the formatted code as a string. Args: code (str): The Python code to be formatted. @@ -84,24 +84,33 @@ def format_code(code: str) -> str: # Call ruff to format the file config_path = AppConfig().ROOT_DIR.parent / "pyproject.toml" - result = subprocess.run( - [ - str(formatter_path), - "format", - str(input_file_path), - "--no-cache", - "--config", - str(config_path), - ], - capture_output=True, - text=True, - ) - if result.returncode != 0: - raise Exception( - f"Error formatting code using formatter '{formatter_path}': " - f"returncode={result.returncode}, stdout={result.stdout!r}, " - f"stderr={result.stderr!r}" + try: + subprocess.run( + [ + str(formatter_path), + "format", + str(input_file_path), + "--no-cache", + "--config", + str(config_path), + ], + capture_output=True, + text=True, + check=True, ) + except FileNotFoundError as e: + raise FileNotFoundError( + f"Could not run the 'ruff' formatter at '{formatter_path}'. " + f"gentest requires 'ruff' to format generated tests; ensure " + f"the development environment is installed, e.g. with " + f"'uv sync'." + ) from e + except subprocess.CalledProcessError as e: + raise RuntimeError( + f"Error formatting code using formatter '{formatter_path}': " + f"returncode={e.returncode}, stdout={e.stdout!r}, " + f"stderr={e.stderr!r}" + ) from e # Return the formatted source code return input_file_path.read_text() diff --git a/packages/testing/src/execution_testing/cli/gentest/tests/test_cli.py b/packages/testing/src/execution_testing/cli/gentest/tests/test_cli.py index 92118cac441..430f024dcbc 100644 --- a/packages/testing/src/execution_testing/cli/gentest/tests/test_cli.py +++ b/packages/testing/src/execution_testing/cli/gentest/tests/test_cli.py @@ -139,10 +139,14 @@ def get_mock_context(self: StateTestProvider) -> dict: monkeypatch.setattr(StateTestProvider, "get_context", get_mock_context) ## Generate ## + # catch_exceptions=False lets any error from gentest propagate with its + # full traceback instead of being hidden behind a non-zero exit code. gentest_result = runner.invoke( - generate, [transaction_hash, generated_py_file] + generate, + [transaction_hash, generated_py_file], + catch_exceptions=False, ) - assert gentest_result.exit_code == 0 + assert gentest_result.exit_code == 0, gentest_result.output ## Fill ## with open(generated_py_file, "r") as f: From f200fed30c2c7f2ef4aeac5a4248af40447451ae Mon Sep 17 00:00:00 2001 From: danceratopz Date: Tue, 30 Jun 2026 17:12:09 +0200 Subject: [PATCH 057/233] refactor(test-benchmark): speed-up the keccak max-permutations search (#3060) --- .../compute/instruction/test_keccak.py | 42 +++++++++++++++---- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/tests/benchmark/compute/instruction/test_keccak.py b/tests/benchmark/compute/instruction/test_keccak.py index 07ec6ee8133..374639337cd 100644 --- a/tests/benchmark/compute/instruction/test_keccak.py +++ b/tests/benchmark/compute/instruction/test_keccak.py @@ -31,16 +31,46 @@ def test_keccak_max_permutations( mem_exp_gas_calculator = fork.memory_expansion_gas_calculator() + def attack_block_for(input_length: int) -> Bytecode: + """Return the keccak attack block hashing `input_length` bytes.""" + return Op.POP(Op.SHA3(Op.PUSH0, Op.DUP1, data_size=input_length)) + + # Only SHA3's per-word cost varies with the input size, so the attack + # block's per-call gas is affine in the keccak word count. Precompute + # the size-independent base once instead of rebuilding the bytecode and + # recomputing its gas each iteration (the search's bottleneck); the + # discovered `optimal_input_length`, and so the fixture, is unchanged. + base_iteration_gas_cost = attack_block_for(0).gas_cost(fork) + keccak_word_gas_cost = fork.gas_costs().OPCODE_KECCAK256_PER_WORD + + def per_call_gas_cost(input_length: int) -> int: + """Return the per-call gas of the keccak attack block.""" + word_count = (input_length + 31) // 32 + return base_iteration_gas_cost + keccak_word_gas_cost * word_count + + # The input lengths examined by the discovery search below. + search_lengths = range(1, 1_000_000, 32) + + # Guard the affine model: if a future fork reprices keccak non-linearly, + # fail here instead of silently discovering a different optimum. The + # samples straddle the 32-byte word boundary and span the search range. + for sample_length in (1, 31, 32, 33, KECCAK_RATE, search_lengths[-1]): + assert per_call_gas_cost(sample_length) == attack_block_for( + sample_length + ).gas_cost(fork), ( + "keccak gas is no longer affine in the input word count; the " + "analytic search optimization is invalid for this fork" + ) + # Discover the optimal input size to maximize keccak-permutations, # not to maximize keccak calls. # The complication of the discovery arises from # the non-linear gas cost of memory expansion. max_keccak_perm_per_block = 0 optimal_input_length = 0 - for i in range(1, 1_000_000, 32): - # Iteration cost disregarding memory expansion - iteration_bytecode = Op.POP(Op.SHA3(Op.PUSH0, Op.DUP1, data_size=i)) - iteration_gas_cost = iteration_bytecode.gas_cost(fork) + for i in search_lengths: + # Iteration cost disregarding memory expansion. + iteration_gas_cost = per_call_gas_cost(i) # From the available gas, we subtract the mem expansion costs # considering we know the current input size length i. available_gas_after_expansion = max( @@ -61,9 +91,7 @@ def test_keccak_max_permutations( target_opcode=Op.SHA3, code_generator=JumpLoopGenerator( setup=Op.PUSH20[optimal_input_length], - attack_block=Op.POP( - Op.SHA3(Op.PUSH0, Op.DUP1, data_size=optimal_input_length) - ), + attack_block=attack_block_for(optimal_input_length), ), ) From f1221b2332ed86ecf40e45195d54eb1531f0b271 Mon Sep 17 00:00:00 2001 From: danceratopz Date: Tue, 30 Jun 2026 17:57:01 +0200 Subject: [PATCH 058/233] chore(ci): speed up bench-gas by filling only the `blockchain_test` format (#3057) --- Justfile | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/Justfile b/Justfile index d464b884949..41c54cfcc3d 100644 --- a/Justfile +++ b/Justfile @@ -221,31 +221,46 @@ test-ci-scripts *args: # --- Benchmarks --- -# Fill benchmark tests with --gas-benchmark-values, then verify with EELS +# Smoke-test benchmark tests: fill blockchain_test fixtures, then verify against EELS. [group('benchmark tests')] bench-gas *args: @mkdir -p "{{ output_dir }}/bench-gas/tmp" "{{ output_dir }}/bench-gas/logs" - @echo "==> Step 1/2: Filling benchmark fixtures with configured EVM (EVM_BIN={{ evm_bin }})" + @echo "==> Step 1/3: Generating pre-alloc groups (smoke-tests the BlockchainEngineX path)" uv run fill \ + --generate-pre-alloc-groups \ --evm-bin="{{ evm_bin }}" \ --gas-benchmark-values 1 \ - --generate-all-formats \ --fork Osaka \ -m "not slow" \ -n auto --maxprocesses 10 --dist=loadgroup \ + --output="{{ output_dir }}/bench-gas/pre-alloc" \ + --basetemp="{{ output_dir }}/bench-gas/tmp" \ + --log-to "{{ output_dir }}/bench-gas/logs" \ + --clean \ + "$@" \ + tests/benchmark/compute + @echo "==> Step 2/3: Filling blockchain_test fixtures with configured EVM (EVM_BIN={{ evm_bin }})" + uv run fill \ + --evm-bin="{{ evm_bin }}" \ + --gas-benchmark-values 1 \ + --fork Osaka \ + -m "blockchain_test and (not derived_test) and (not slow)" \ + -n auto --maxprocesses 10 --dist=loadgroup \ + --durations=20 \ --output="{{ output_dir }}/bench-gas/fixtures" \ --basetemp="{{ output_dir }}/bench-gas/tmp" \ --log-to "{{ output_dir }}/bench-gas/logs" \ --clean \ "$@" \ tests/benchmark/compute - @echo "==> Step 2/2: Running filled fixtures against EELS via json_loader" + @echo "==> Step 3/3: Running filled fixtures against EELS via json_loader" @rm -rf tests/json_loader/bench_gas_fixtures ln -sfn "{{ output_dir }}/bench-gas/fixtures" tests/json_loader/bench_gas_fixtures - cd tests/json_loader && uv run --python pypy3.11 pytest \ + cd tests/json_loader && uv run --python pypy3.11 --no-dev --group test pytest \ --fork Osaka \ --allow-post-state-hash \ -n auto --maxprocesses 10 --dist=loadfile \ + --durations=20 \ --basetemp="{{ output_dir }}/bench-gas/json-loader-tmp" \ bench_gas_fixtures From 4fb72f2caf6704fc4e16835af4b069fd5827a810 Mon Sep 17 00:00:00 2001 From: Skas <108791624+Skanislav@users.noreply.github.com> Date: Tue, 30 Jun 2026 20:18:37 +0200 Subject: [PATCH 059/233] refactor(specs): 8037 `StateGas`, `RegularGas` and `StateGasPerByte` (#3037) Co-authored-by: spencer --- src/ethereum/forks/amsterdam/fork.py | 2 +- src/ethereum/forks/amsterdam/fork_types.py | 34 +++++++++++++++++-- src/ethereum/forks/amsterdam/transactions.py | 21 +++++++----- src/ethereum/forks/amsterdam/vm/__init__.py | 4 +-- src/ethereum/forks/amsterdam/vm/gas.py | 13 ++++--- .../amsterdam/vm/instructions/storage.py | 3 +- .../forks/amsterdam/vm/instructions/system.py | 3 +- 7 files changed, 60 insertions(+), 20 deletions(-) diff --git a/src/ethereum/forks/amsterdam/fork.py b/src/ethereum/forks/amsterdam/fork.py index 722463959f2..6ac9e968e52 100644 --- a/src/ethereum/forks/amsterdam/fork.py +++ b/src/ethereum/forks/amsterdam/fork.py @@ -1001,7 +1001,7 @@ def process_transaction( intrinsic = validate_transaction(tx) - intrinsic_gas = intrinsic.regular + intrinsic.state + intrinsic_gas = Uint(intrinsic.regular) + Uint(intrinsic.state) ( sender, diff --git a/src/ethereum/forks/amsterdam/fork_types.py b/src/ethereum/forks/amsterdam/fork_types.py index 3fb1d66d057..52f0c8f8234 100644 --- a/src/ethereum/forks/amsterdam/fork_types.py +++ b/src/ethereum/forks/amsterdam/fork_types.py @@ -12,12 +12,12 @@ """ from dataclasses import dataclass -from typing import final +from typing import NewType, final from ethereum_rlp import rlp from ethereum_types.bytes import Bytes, Bytes256 from ethereum_types.frozen import slotted_freezable -from ethereum_types.numeric import U8, U32, U64, U256 +from ethereum_types.numeric import U8, U32, U64, U256, Uint from ethereum.crypto.hash import Hash32 from ethereum.state import Account, Address @@ -34,6 +34,36 @@ Bloom = Bytes256 +RegularGas = NewType("RegularGas", Uint) + +StateGas = NewType("StateGas", Uint) + + +@final +@slotted_freezable +@dataclass +class StateGasPerByte: + """ + State gas charged per byte of state growth, per [EIP-8037]. + + A rate, not an amount: deliberately not a `Uint`, since adding a rate to + a gas amount is meaningless. Multiplying it by a byte count, in either + operand order, yields a `StateGas`. + + [EIP-8037]: https://eips.ethereum.org/EIPS/eip-8037 + """ + + rate: Uint + + def __mul__(self, num_bytes: Uint) -> StateGas: + """Return the state gas for `num_bytes` charged at this rate.""" + return StateGas(self.rate * num_bytes) + + def __rmul__(self, num_bytes: Uint) -> StateGas: + """Return the state gas for `num_bytes` charged at this rate.""" + return StateGas(self.rate * num_bytes) + + def encode_account(raw_account_data: Account, storage_root: Bytes) -> Bytes: """ Encode `Account` dataclass. diff --git a/src/ethereum/forks/amsterdam/transactions.py b/src/ethereum/forks/amsterdam/transactions.py index fd0cc7b1566..b05711d28b4 100644 --- a/src/ethereum/forks/amsterdam/transactions.py +++ b/src/ethereum/forks/amsterdam/transactions.py @@ -25,7 +25,12 @@ InitCodeTooLargeError, TransactionTypeError, ) -from .fork_types import Authorization, VersionedHash +from .fork_types import ( + Authorization, + RegularGas, + StateGas, + VersionedHash, +) @final @@ -33,10 +38,10 @@ class IntrinsicGasCost: """Intrinsic gas costs for a transaction, split by gas type.""" - regular: Uint + regular: RegularGas """Regular execution gas (calldata, base cost, access list, etc.).""" - state: Uint + state: StateGas """ State growth gas (account creation, storage set, authorization) per [EIP-8037]. @@ -44,7 +49,7 @@ class IntrinsicGasCost: [EIP-8037]: https://eips.ethereum.org/EIPS/eip-8037 """ - calldata_floor: Uint + calldata_floor: RegularGas """ Minimum gas cost based on calldata size per [EIP-7623]. @@ -605,7 +610,7 @@ def validate_transaction(tx: Transaction) -> IntrinsicGasCost: from .vm.interpreter import MAX_INIT_CODE_SIZE intrinsic = calculate_intrinsic_cost(tx) - intrinsic_gas = intrinsic.regular + intrinsic.state + intrinsic_gas = Uint(intrinsic.regular) + Uint(intrinsic.state) if intrinsic_gas > tx.gas: raise InsufficientTransactionGasError("Insufficient intrinsic gas") if intrinsic.calldata_floor > tx.gas: @@ -717,9 +722,9 @@ def calculate_intrinsic_cost(tx: Transaction) -> IntrinsicGasCost: intrinsic_state_gas = create_state_gas + auth_state_gas return IntrinsicGasCost( - regular=intrinsic_regular_gas, - state=intrinsic_state_gas, - calldata_floor=data_floor_gas_cost, + regular=RegularGas(intrinsic_regular_gas), + state=StateGas(intrinsic_state_gas), + calldata_floor=RegularGas(data_floor_gas_cost), ) diff --git a/src/ethereum/forks/amsterdam/vm/__init__.py b/src/ethereum/forks/amsterdam/vm/__init__.py index d49759578e7..48d0b3eab14 100644 --- a/src/ethereum/forks/amsterdam/vm/__init__.py +++ b/src/ethereum/forks/amsterdam/vm/__init__.py @@ -26,7 +26,7 @@ from ..block_access_lists import BlockAccessList, BlockAccessListBuilder from ..blocks import Log, Receipt, Withdrawal -from ..fork_types import Authorization, VersionedHash +from ..fork_types import Authorization, StateGas, VersionedHash from ..state_tracker import BlockState, TransactionState from ..transactions import LegacyTransaction @@ -189,7 +189,7 @@ class Evm: state_gas_spilled: Uint = Uint(0) -def credit_state_gas_refund(evm: Evm, amount: Uint) -> None: +def credit_state_gas_refund(evm: Evm, amount: StateGas) -> None: """ Credit a state gas refund to the local frame, in LIFO order. diff --git a/src/ethereum/forks/amsterdam/vm/gas.py b/src/ethereum/forks/amsterdam/vm/gas.py index 7ce827a2407..c967572d5e1 100644 --- a/src/ethereum/forks/amsterdam/vm/gas.py +++ b/src/ethereum/forks/amsterdam/vm/gas.py @@ -21,6 +21,7 @@ from ethereum.utils.numeric import ceil32, taylor_exponential from ..blocks import Header +from ..fork_types import StateGas, StateGasPerByte from ..transactions import BlobTransaction, Transaction from . import Evm from .exceptions import OutOfGasError @@ -36,17 +37,19 @@ class StateGasCosts: state-byte counts that convert into gas via `COST_PER_STATE_BYTE`. """ - COST_PER_STATE_BYTE: Final[Uint] = Uint(1530) + COST_PER_STATE_BYTE: Final[StateGasPerByte] = StateGasPerByte(Uint(1530)) STATE_BYTES_PER_NEW_ACCOUNT: Final[Uint] = Uint(120) STATE_BYTES_PER_STORAGE_SET: Final[Uint] = Uint(64) STATE_BYTES_PER_AUTH_BASE: Final[Uint] = Uint(23) - STORAGE_SET: Final[Uint] = ( + STORAGE_SET: Final[StateGas] = ( STATE_BYTES_PER_STORAGE_SET * COST_PER_STATE_BYTE ) - NEW_ACCOUNT: Final[Uint] = ( + NEW_ACCOUNT: Final[StateGas] = ( STATE_BYTES_PER_NEW_ACCOUNT * COST_PER_STATE_BYTE ) - AUTH_BASE: Final[Uint] = STATE_BYTES_PER_AUTH_BASE * COST_PER_STATE_BYTE + AUTH_BASE: Final[StateGas] = ( + STATE_BYTES_PER_AUTH_BASE * COST_PER_STATE_BYTE + ) # These values may be patched at runtime by a future gas repricing utility @@ -290,7 +293,7 @@ def charge_gas(evm: Evm, amount: Uint) -> None: evm.regular_gas_used += amount -def charge_state_gas(evm: Evm, amount: Uint) -> None: +def charge_state_gas(evm: Evm, amount: StateGas) -> None: """ Subtracts `amount` from the state gas reservoir, then from `evm.gas_left` when the reservoir is empty. Records state gas usage. diff --git a/src/ethereum/forks/amsterdam/vm/instructions/storage.py b/src/ethereum/forks/amsterdam/vm/instructions/storage.py index aa8d869a456..4e864b8ec71 100644 --- a/src/ethereum/forks/amsterdam/vm/instructions/storage.py +++ b/src/ethereum/forks/amsterdam/vm/instructions/storage.py @@ -13,6 +13,7 @@ from ethereum_types.numeric import Uint +from ...fork_types import StateGas from ...state_tracker import ( get_storage, get_storage_original, @@ -90,7 +91,7 @@ def sstore(evm: Evm) -> None: current_value = get_storage(tx_state, evm.message.current_target, key) gas_cost = Uint(0) - state_gas = Uint(0) + state_gas = StateGas(Uint(0)) if (evm.message.current_target, key) not in evm.accessed_storage_keys: evm.accessed_storage_keys.add((evm.message.current_target, key)) diff --git a/src/ethereum/forks/amsterdam/vm/instructions/system.py b/src/ethereum/forks/amsterdam/vm/instructions/system.py index cf9e27934c8..fd173a30adf 100644 --- a/src/ethereum/forks/amsterdam/vm/instructions/system.py +++ b/src/ethereum/forks/amsterdam/vm/instructions/system.py @@ -20,6 +20,7 @@ from ethereum.state import Address from ethereum.utils.numeric import ceil32 +from ...fork_types import StateGas from ...state_tracker import ( account_deployable, get_account, @@ -668,7 +669,7 @@ def selfdestruct(evm: Evm) -> None: if is_cold_access: evm.accessed_addresses.add(beneficiary) - state_gas = Uint(0) + state_gas = StateGas(Uint(0)) if ( not is_account_alive(tx_state, beneficiary) and get_account(tx_state, evm.message.current_target).balance != 0 From 63f20af493acc2eb71f7542ce8bfe250e465c70c Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Tue, 30 Jun 2026 12:49:45 -0600 Subject: [PATCH 060/233] chore(tests,test-forks): Address #2990 review comments (pkgutil, type: ignore, Spec dataclass) (#3018) * refactor(test-forks): Use pkgutil to load contract binaries * refactor(test-forks): Remove pre_allocation_blockchain type: ignore comments * chore(tests): Remove `dataclass` from `Spec` classes --- .../src/execution_testing/forks/bytecode.py | 19 +++++++++++++++++++ .../forks/forks/eips/cancun/eip_4788.py | 5 +++-- .../forks/forks/eips/prague/eip_2935.py | 15 +++++++-------- .../forks/forks/eips/prague/eip_6110.py | 15 +++++++-------- .../forks/forks/eips/prague/eip_7002.py | 13 ++++++------- .../forks/forks/eips/prague/eip_7251.py | 13 ++++++------- .../eip7708_eth_transfer_logs/spec.py | 1 - .../spec.py | 1 - tests/amsterdam/eip7843_slotnum/spec.py | 1 - .../eip7928_block_level_access_lists/spec.py | 1 - .../spec.py | 1 - .../eip7981_increase_access_list_cost/spec.py | 1 - .../eip8024_dupn_swapn_exchange/spec.py | 1 - .../spec.py | 1 - tests/berlin/eip2930_access_list/spec.py | 1 - tests/byzantium/eip196_ec_add_mul/spec.py | 1 - tests/byzantium/eip197_ec_pairing/spec.py | 1 - tests/cancun/eip1153_tstore/spec.py | 1 - tests/cancun/eip4788_beacon_root/spec.py | 1 - tests/cancun/eip4844_blobs/spec.py | 1 - tests/constantinople/eip1014_create2/spec.py | 1 - .../eip145_bitwise_shift/spec.py | 1 - tests/frontier/identity_precompile/spec.py | 3 --- tests/frontier/precompiles/spec.py | 1 - tests/istanbul/eip152_blake2/spec.py | 1 - tests/osaka/eip7594_peerdas/spec.py | 1 - .../eip7825_transaction_gas_limit_cap/spec.py | 1 - .../osaka/eip7883_modexp_gas_increase/spec.py | 1 - .../osaka/eip7918_blob_reserve_price/spec.py | 1 - tests/osaka/eip7934_block_rlp_limit/spec.py | 1 - .../osaka/eip7939_count_leading_zeros/spec.py | 1 - .../eip7951_p256verify_precompiles/spec.py | 1 - .../eip2537_bls_12_381_precompiles/spec.py | 1 - .../spec.py | 1 - tests/prague/eip6110_deposits/spec.py | 1 - .../spec.py | 1 - tests/prague/eip7251_consolidations/spec.py | 1 - .../eip7623_increase_calldata_cost/spec.py | 1 - tests/prague/eip7702_set_code_tx/spec.py | 1 - tests/shanghai/eip3860_initcode/spec.py | 1 - 40 files changed, 48 insertions(+), 68 deletions(-) create mode 100644 packages/testing/src/execution_testing/forks/bytecode.py diff --git a/packages/testing/src/execution_testing/forks/bytecode.py b/packages/testing/src/execution_testing/forks/bytecode.py new file mode 100644 index 00000000000..3d93fae5dad --- /dev/null +++ b/packages/testing/src/execution_testing/forks/bytecode.py @@ -0,0 +1,19 @@ +"""Helper to load predeploy contract bytecode bundled as package data.""" + +import pkgutil + + +def load_contract_bytecode(module_name: str, filename: str) -> bytes: + """ + Load predeploy contract bytecode bundled as package data. + + `module_name` is the importing module's `__name__`; the bytecode is read + from a `contracts/` resource located alongside that module. + """ + resource = f"contracts/{filename}" + bytecode = pkgutil.get_data(module_name, resource) + if bytecode is None: + raise FileNotFoundError( + f"Unable to read bytecode `{resource}` from `{module_name}`" + ) + return bytecode diff --git a/packages/testing/src/execution_testing/forks/forks/eips/cancun/eip_4788.py b/packages/testing/src/execution_testing/forks/forks/eips/cancun/eip_4788.py index 87c8763f16e..c2b9db08b5f 100644 --- a/packages/testing/src/execution_testing/forks/forks/eips/cancun/eip_4788.py +++ b/packages/testing/src/execution_testing/forks/forks/eips/cancun/eip_4788.py @@ -49,8 +49,9 @@ def pre_allocation_blockchain(cls) -> Mapping: "57602036146024575f5ffd5b5f35801560495762001fff810690" "815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd" "5b62001fff42064281555f359062001fff015500", - } - } | super(EIP4788, cls).pre_allocation_blockchain() # type: ignore + }, + **super(EIP4788, cls).pre_allocation_blockchain(), + } @classmethod def engine_new_payload_beacon_root(cls) -> bool: diff --git a/packages/testing/src/execution_testing/forks/forks/eips/prague/eip_2935.py b/packages/testing/src/execution_testing/forks/forks/eips/prague/eip_2935.py index aa759fa8509..d8a04e90e68 100644 --- a/packages/testing/src/execution_testing/forks/forks/eips/prague/eip_2935.py +++ b/packages/testing/src/execution_testing/forks/forks/eips/prague/eip_2935.py @@ -7,19 +7,17 @@ https://eips.ethereum.org/EIPS/eip-2935 """ -from os.path import realpath -from pathlib import Path from typing import List, Mapping from execution_testing.base_types import Address from ....base_fork import BaseFork +from ....bytecode import load_contract_bytecode -BYTECODE_FILE = ( - Path(realpath(__file__)).parent / "contracts" / "history_contract.bin" -) HISTORY_STORAGE_ADDRESS = 0x0000F90827F1C53A10CB7A02335B175320002935 -HISTORY_STORAGE_BYTECODE = BYTECODE_FILE.read_bytes() +HISTORY_STORAGE_BYTECODE = load_contract_bytecode( + __name__, "history_contract.bin" +) class EIP2935(BaseFork): @@ -48,5 +46,6 @@ def pre_allocation_blockchain(cls) -> Mapping: HISTORY_STORAGE_ADDRESS: { "nonce": 1, "code": HISTORY_STORAGE_BYTECODE, - } - } | super(EIP2935, cls).pre_allocation_blockchain() # type: ignore + }, + **super(EIP2935, cls).pre_allocation_blockchain(), + } diff --git a/packages/testing/src/execution_testing/forks/forks/eips/prague/eip_6110.py b/packages/testing/src/execution_testing/forks/forks/eips/prague/eip_6110.py index e84a0ac2b1a..02082d90b50 100644 --- a/packages/testing/src/execution_testing/forks/forks/eips/prague/eip_6110.py +++ b/packages/testing/src/execution_testing/forks/forks/eips/prague/eip_6110.py @@ -8,19 +8,17 @@ """ from hashlib import sha256 -from os.path import realpath -from pathlib import Path from typing import List, Mapping from execution_testing.base_types import Address from ....base_fork import BaseFork +from ....bytecode import load_contract_bytecode -BYTECODE_FILE = ( - Path(realpath(__file__)).parent / "contracts" / "deposit_contract.bin" -) DEPOSIT_CONTRACT_ADDRESS = 0x00000000219AB540356CBB839CBE05303D7705FA -DEPOSIT_CONTRACT_BYTECODE = BYTECODE_FILE.read_bytes() +DEPOSIT_CONTRACT_BYTECODE = load_contract_bytecode( + __name__, "deposit_contract.bin" +) class EIP6110(BaseFork): @@ -54,5 +52,6 @@ def pre_allocation_blockchain(cls) -> Mapping: "nonce": 1, "code": DEPOSIT_CONTRACT_BYTECODE, "storage": storage, - } - } | super(EIP6110, cls).pre_allocation_blockchain() # type: ignore + }, + **super(EIP6110, cls).pre_allocation_blockchain(), + } diff --git a/packages/testing/src/execution_testing/forks/forks/eips/prague/eip_7002.py b/packages/testing/src/execution_testing/forks/forks/eips/prague/eip_7002.py index c5cba51883d..45f7f61058d 100644 --- a/packages/testing/src/execution_testing/forks/forks/eips/prague/eip_7002.py +++ b/packages/testing/src/execution_testing/forks/forks/eips/prague/eip_7002.py @@ -7,21 +7,19 @@ https://eips.ethereum.org/EIPS/eip-7002 """ -from os.path import realpath -from pathlib import Path from typing import List, Mapping from execution_testing.base_types import Address from ....base_fork import BaseFork +from ....bytecode import load_contract_bytecode -BYTECODE_FILE = ( - Path(realpath(__file__)).parent / "contracts" / "withdrawal_request.bin" -) WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS = ( 0x00000961EF480EB55E80D19AD83579A64C007002 ) -WITHDRAWAL_REQUEST_PREDEPLOY_BYTECODE = BYTECODE_FILE.read_bytes() +WITHDRAWAL_REQUEST_PREDEPLOY_BYTECODE = load_contract_bytecode( + __name__, "withdrawal_request.bin" +) class EIP7002(BaseFork): @@ -51,4 +49,5 @@ def pre_allocation_blockchain(cls) -> Mapping: "nonce": 1, "code": WITHDRAWAL_REQUEST_PREDEPLOY_BYTECODE, }, - } | super(EIP7002, cls).pre_allocation_blockchain() # type: ignore + **super(EIP7002, cls).pre_allocation_blockchain(), + } diff --git a/packages/testing/src/execution_testing/forks/forks/eips/prague/eip_7251.py b/packages/testing/src/execution_testing/forks/forks/eips/prague/eip_7251.py index 7ed005481da..c5046985790 100644 --- a/packages/testing/src/execution_testing/forks/forks/eips/prague/eip_7251.py +++ b/packages/testing/src/execution_testing/forks/forks/eips/prague/eip_7251.py @@ -6,21 +6,19 @@ https://eips.ethereum.org/EIPS/eip-7251 """ -from os.path import realpath -from pathlib import Path from typing import List, Mapping from execution_testing.base_types import Address from ....base_fork import BaseFork +from ....bytecode import load_contract_bytecode -BYTECODE_FILE = ( - Path(realpath(__file__)).parent / "contracts" / "consolidation_request.bin" -) CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS = ( 0x0000BBDDC7CE488642FB579F8B00F3A590007251 ) -CONSOLIDATION_REQUEST_PREDEPLOY_BYTECODE = BYTECODE_FILE.read_bytes() +CONSOLIDATION_REQUEST_PREDEPLOY_BYTECODE = load_contract_bytecode( + __name__, "consolidation_request.bin" +) class EIP7251(BaseFork): @@ -50,4 +48,5 @@ def pre_allocation_blockchain(cls) -> Mapping: "nonce": 1, "code": CONSOLIDATION_REQUEST_PREDEPLOY_BYTECODE, }, - } | super(EIP7251, cls).pre_allocation_blockchain() # type: ignore + **super(EIP7251, cls).pre_allocation_blockchain(), + } diff --git a/tests/amsterdam/eip7708_eth_transfer_logs/spec.py b/tests/amsterdam/eip7708_eth_transfer_logs/spec.py index 9d08cb17eaf..ec58c8d7187 100644 --- a/tests/amsterdam/eip7708_eth_transfer_logs/spec.py +++ b/tests/amsterdam/eip7708_eth_transfer_logs/spec.py @@ -18,7 +18,6 @@ class ReferenceSpec: ) -@dataclass(frozen=True) class Spec: """ Parameters from the EIP-7708 specifications as defined at diff --git a/tests/amsterdam/eip7778_block_gas_accounting_without_refunds/spec.py b/tests/amsterdam/eip7778_block_gas_accounting_without_refunds/spec.py index ed60bde819c..4219a5fd98d 100644 --- a/tests/amsterdam/eip7778_block_gas_accounting_without_refunds/spec.py +++ b/tests/amsterdam/eip7778_block_gas_accounting_without_refunds/spec.py @@ -16,7 +16,6 @@ class ReferenceSpec: ) -@dataclass(frozen=True) class Spec: """ Parameters from the EIP-7778 specifications as defined at diff --git a/tests/amsterdam/eip7843_slotnum/spec.py b/tests/amsterdam/eip7843_slotnum/spec.py index a96172631f4..db53e40f62f 100644 --- a/tests/amsterdam/eip7843_slotnum/spec.py +++ b/tests/amsterdam/eip7843_slotnum/spec.py @@ -17,6 +17,5 @@ class ReferenceSpec: ) -@dataclass(frozen=True) class Spec: """Constants and parameters from EIP-7843.""" diff --git a/tests/amsterdam/eip7928_block_level_access_lists/spec.py b/tests/amsterdam/eip7928_block_level_access_lists/spec.py index 2eac6b26b3b..f094e07f29c 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/spec.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/spec.py @@ -17,7 +17,6 @@ class ReferenceSpec: ) -@dataclass(frozen=True) class Spec: """Constants and parameters from EIP-7928.""" diff --git a/tests/amsterdam/eip7976_increase_calldata_floor_cost/spec.py b/tests/amsterdam/eip7976_increase_calldata_floor_cost/spec.py index 209153c0d2a..df03e7071f8 100644 --- a/tests/amsterdam/eip7976_increase_calldata_floor_cost/spec.py +++ b/tests/amsterdam/eip7976_increase_calldata_floor_cost/spec.py @@ -17,7 +17,6 @@ class ReferenceSpec: # Constants -@dataclass(frozen=True) class Spec: """ Parameters from the EIP-7976 specifications as defined at diff --git a/tests/amsterdam/eip7981_increase_access_list_cost/spec.py b/tests/amsterdam/eip7981_increase_access_list_cost/spec.py index 30ff97e4fcc..92f945e1bc1 100644 --- a/tests/amsterdam/eip7981_increase_access_list_cost/spec.py +++ b/tests/amsterdam/eip7981_increase_access_list_cost/spec.py @@ -17,7 +17,6 @@ class ReferenceSpec: # Constants -@dataclass(frozen=True) class Spec: """ Parameters from the EIP-7981 specifications as defined at diff --git a/tests/amsterdam/eip8024_dupn_swapn_exchange/spec.py b/tests/amsterdam/eip8024_dupn_swapn_exchange/spec.py index 5f0037c67d8..8a1c911ee4c 100644 --- a/tests/amsterdam/eip8024_dupn_swapn_exchange/spec.py +++ b/tests/amsterdam/eip8024_dupn_swapn_exchange/spec.py @@ -23,7 +23,6 @@ class ReferenceSpec: ) -@dataclass(frozen=True) class Spec: """Constants and parameters from EIP-8024.""" diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/spec.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/spec.py index f11954e2c2f..c5fbeb36071 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/spec.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/spec.py @@ -29,7 +29,6 @@ class ReferenceSpec: ) -@dataclass(frozen=True) class Spec: """ Constants and helpers for the EIP-8037 State Creation Gas Cost diff --git a/tests/berlin/eip2930_access_list/spec.py b/tests/berlin/eip2930_access_list/spec.py index c43745b195c..f0597662ffe 100644 --- a/tests/berlin/eip2930_access_list/spec.py +++ b/tests/berlin/eip2930_access_list/spec.py @@ -17,7 +17,6 @@ class ReferenceSpec: # Constants -@dataclass(frozen=True) class Spec: """ Parameters from the EIP-2930 specifications as defined at diff --git a/tests/byzantium/eip196_ec_add_mul/spec.py b/tests/byzantium/eip196_ec_add_mul/spec.py index ee241b2d0ad..2210b7ad1b1 100644 --- a/tests/byzantium/eip196_ec_add_mul/spec.py +++ b/tests/byzantium/eip196_ec_add_mul/spec.py @@ -45,7 +45,6 @@ def __bytes__(self) -> bytes: return FP(self.x) + FP(self.y) -@dataclass(frozen=True) class Spec: """ Parameters from the EIP-196 specification (https://eips.ethereum.org/EIPS/eip-196) diff --git a/tests/byzantium/eip197_ec_pairing/spec.py b/tests/byzantium/eip197_ec_pairing/spec.py index 9f5597e8f01..797b1882989 100644 --- a/tests/byzantium/eip197_ec_pairing/spec.py +++ b/tests/byzantium/eip197_ec_pairing/spec.py @@ -25,7 +25,6 @@ def __bytes__(self) -> bytes: return FP(self.x[0]) + FP(self.x[1]) + FP(self.y[0]) + FP(self.y[1]) -@dataclass(frozen=True) class Spec: """ Parameters from the EIP-197 specification diff --git a/tests/cancun/eip1153_tstore/spec.py b/tests/cancun/eip1153_tstore/spec.py index 9ab7653a4b1..17de777f5d0 100644 --- a/tests/cancun/eip1153_tstore/spec.py +++ b/tests/cancun/eip1153_tstore/spec.py @@ -16,7 +16,6 @@ class ReferenceSpec: ) -@dataclass(frozen=True) class Spec: """ Parameters from the EIP-1153 specifications as defined at diff --git a/tests/cancun/eip4788_beacon_root/spec.py b/tests/cancun/eip4788_beacon_root/spec.py index 586c7dc9edb..3e223009cef 100644 --- a/tests/cancun/eip4788_beacon_root/spec.py +++ b/tests/cancun/eip4788_beacon_root/spec.py @@ -19,7 +19,6 @@ class ReferenceSpec: # Constants -@dataclass(frozen=True) class Spec: """ Parameters from the EIP-4788 specifications as defined at diff --git a/tests/cancun/eip4844_blobs/spec.py b/tests/cancun/eip4844_blobs/spec.py index a30a0da24d9..d20c2dc2926 100644 --- a/tests/cancun/eip4844_blobs/spec.py +++ b/tests/cancun/eip4844_blobs/spec.py @@ -53,7 +53,6 @@ class ReferenceSpec: # Constants -@dataclass(frozen=True) class Spec: """ Parameters from the EIP-4844 specifications as defined at diff --git a/tests/constantinople/eip1014_create2/spec.py b/tests/constantinople/eip1014_create2/spec.py index 893d0485f36..52c11feb04d 100644 --- a/tests/constantinople/eip1014_create2/spec.py +++ b/tests/constantinople/eip1014_create2/spec.py @@ -16,7 +16,6 @@ class ReferenceSpec: ) -@dataclass(frozen=True) class Spec: """ Parameters from the EIP-1014 specifications as defined at diff --git a/tests/constantinople/eip145_bitwise_shift/spec.py b/tests/constantinople/eip145_bitwise_shift/spec.py index b6931c814c7..e4d06cee1ea 100644 --- a/tests/constantinople/eip145_bitwise_shift/spec.py +++ b/tests/constantinople/eip145_bitwise_shift/spec.py @@ -16,7 +16,6 @@ class ReferenceSpec: ) -@dataclass(frozen=True) class Spec: """ Parameters from the EIP-145 specifications as defined at diff --git a/tests/frontier/identity_precompile/spec.py b/tests/frontier/identity_precompile/spec.py index 0d9fce9c5a2..f59c9614bf1 100644 --- a/tests/frontier/identity_precompile/spec.py +++ b/tests/frontier/identity_precompile/spec.py @@ -1,11 +1,8 @@ """Defines spec constants for the IDENTITY precompile.""" -from dataclasses import dataclass - from execution_testing import Address -@dataclass(frozen=True) class Spec: """Parameters for the IDENTITY precompile (frontier).""" diff --git a/tests/frontier/precompiles/spec.py b/tests/frontier/precompiles/spec.py index 68ecdf7e572..437b3806e86 100644 --- a/tests/frontier/precompiles/spec.py +++ b/tests/frontier/precompiles/spec.py @@ -25,7 +25,6 @@ def __bytes__(self) -> bytes: ) -@dataclass(frozen=True) class Spec: """Parameters for the frontier precompiles.""" diff --git a/tests/istanbul/eip152_blake2/spec.py b/tests/istanbul/eip152_blake2/spec.py index 412bf0fdd69..204b4379089 100644 --- a/tests/istanbul/eip152_blake2/spec.py +++ b/tests/istanbul/eip152_blake2/spec.py @@ -17,7 +17,6 @@ class ReferenceSpec: # Constants -@dataclass(frozen=True) class Spec: """ Parameters from the EIP-152 specifications as defined at diff --git a/tests/osaka/eip7594_peerdas/spec.py b/tests/osaka/eip7594_peerdas/spec.py index 58c7ca97076..4d9eb114645 100644 --- a/tests/osaka/eip7594_peerdas/spec.py +++ b/tests/osaka/eip7594_peerdas/spec.py @@ -16,7 +16,6 @@ class ReferenceSpec: ) -@dataclass(frozen=True) class Spec: """ Parameters from the EIP-7594 specifications as defined at diff --git a/tests/osaka/eip7825_transaction_gas_limit_cap/spec.py b/tests/osaka/eip7825_transaction_gas_limit_cap/spec.py index 0f3d012a2c8..d699e2e5092 100644 --- a/tests/osaka/eip7825_transaction_gas_limit_cap/spec.py +++ b/tests/osaka/eip7825_transaction_gas_limit_cap/spec.py @@ -17,7 +17,6 @@ class ReferenceSpec: ) -@dataclass(frozen=True) class Spec: """ Constants and helpers for the EIP-7825 Transaction Gas Limit Cap tests. diff --git a/tests/osaka/eip7883_modexp_gas_increase/spec.py b/tests/osaka/eip7883_modexp_gas_increase/spec.py index 68ff44e5241..74b72d1b8d6 100644 --- a/tests/osaka/eip7883_modexp_gas_increase/spec.py +++ b/tests/osaka/eip7883_modexp_gas_increase/spec.py @@ -26,7 +26,6 @@ def ceiling_division(a: int, b: int) -> int: return -(a // -b) -@dataclass(frozen=True) class Spec: """Constants and helpers for the ModExp gas cost calculation.""" diff --git a/tests/osaka/eip7918_blob_reserve_price/spec.py b/tests/osaka/eip7918_blob_reserve_price/spec.py index 902365faf84..961f1b374c3 100644 --- a/tests/osaka/eip7918_blob_reserve_price/spec.py +++ b/tests/osaka/eip7918_blob_reserve_price/spec.py @@ -21,7 +21,6 @@ class ReferenceSpec: ) -@dataclass(frozen=True) class Spec(EIP4844Spec): """ Parameters from the EIP-7918 specifications. Extends EIP-4844 spec with the diff --git a/tests/osaka/eip7934_block_rlp_limit/spec.py b/tests/osaka/eip7934_block_rlp_limit/spec.py index e4a227493ec..cdf469ab0a1 100644 --- a/tests/osaka/eip7934_block_rlp_limit/spec.py +++ b/tests/osaka/eip7934_block_rlp_limit/spec.py @@ -16,7 +16,6 @@ class ReferenceSpec: ) -@dataclass(frozen=True) class Spec: """ Parameters from the EIP-7934 specifications as defined at diff --git a/tests/osaka/eip7939_count_leading_zeros/spec.py b/tests/osaka/eip7939_count_leading_zeros/spec.py index 11e693fae21..3062c46d6fa 100644 --- a/tests/osaka/eip7939_count_leading_zeros/spec.py +++ b/tests/osaka/eip7939_count_leading_zeros/spec.py @@ -16,7 +16,6 @@ class ReferenceSpec: ) -@dataclass(frozen=True) class Spec: """Constants and helpers for the CLZ opcode.""" diff --git a/tests/osaka/eip7951_p256verify_precompiles/spec.py b/tests/osaka/eip7951_p256verify_precompiles/spec.py index 6f73c8a1b90..7c00812d480 100644 --- a/tests/osaka/eip7951_p256verify_precompiles/spec.py +++ b/tests/osaka/eip7951_p256verify_precompiles/spec.py @@ -65,7 +65,6 @@ class H(FieldElement): pass -@dataclass(frozen=True) class Spec: """ Parameters from the EIP-7951 specifications as defined at diff --git a/tests/prague/eip2537_bls_12_381_precompiles/spec.py b/tests/prague/eip2537_bls_12_381_precompiles/spec.py index a8bb7a3431c..0314637e4b3 100644 --- a/tests/prague/eip2537_bls_12_381_precompiles/spec.py +++ b/tests/prague/eip2537_bls_12_381_precompiles/spec.py @@ -95,7 +95,6 @@ def __bytes__(self) -> bytes: return self.x.to_bytes(32, byteorder="big") -@dataclass(frozen=True) class Spec: """ Parameters from the EIP-2537 specifications as defined at diff --git a/tests/prague/eip2935_historical_block_hashes_from_state/spec.py b/tests/prague/eip2935_historical_block_hashes_from_state/spec.py index 07148283b34..f56db8f72fb 100644 --- a/tests/prague/eip2935_historical_block_hashes_from_state/spec.py +++ b/tests/prague/eip2935_historical_block_hashes_from_state/spec.py @@ -16,7 +16,6 @@ class ReferenceSpec: ) -@dataclass(frozen=True) class Spec: """ Parameters from the EIP-2935 specifications as defined at diff --git a/tests/prague/eip6110_deposits/spec.py b/tests/prague/eip6110_deposits/spec.py index f01b22032c8..d36a66d8a2c 100644 --- a/tests/prague/eip6110_deposits/spec.py +++ b/tests/prague/eip6110_deposits/spec.py @@ -16,7 +16,6 @@ class ReferenceSpec: ) -@dataclass(frozen=True) class Spec: """ Parameters from the EIP-6110 specifications as defined at diff --git a/tests/prague/eip7002_el_triggerable_withdrawals/spec.py b/tests/prague/eip7002_el_triggerable_withdrawals/spec.py index 3372a444fe5..689a95d8277 100644 --- a/tests/prague/eip7002_el_triggerable_withdrawals/spec.py +++ b/tests/prague/eip7002_el_triggerable_withdrawals/spec.py @@ -23,7 +23,6 @@ class ReferenceSpec: # Constants -@dataclass(frozen=True) class Spec: """ Parameters from the EIP-7002 specifications as defined at diff --git a/tests/prague/eip7251_consolidations/spec.py b/tests/prague/eip7251_consolidations/spec.py index 765a9e59c16..cf116dd3633 100644 --- a/tests/prague/eip7251_consolidations/spec.py +++ b/tests/prague/eip7251_consolidations/spec.py @@ -19,7 +19,6 @@ class ReferenceSpec: # Constants -@dataclass(frozen=True) class Spec: """ Parameters from the EIP-7251 specifications as defined at diff --git a/tests/prague/eip7623_increase_calldata_cost/spec.py b/tests/prague/eip7623_increase_calldata_cost/spec.py index 6527e5c69c5..f77d56640f0 100644 --- a/tests/prague/eip7623_increase_calldata_cost/spec.py +++ b/tests/prague/eip7623_increase_calldata_cost/spec.py @@ -17,7 +17,6 @@ class ReferenceSpec: # Constants -@dataclass(frozen=True) class Spec: """ Parameters from the EIP-7623 specifications as defined at diff --git a/tests/prague/eip7702_set_code_tx/spec.py b/tests/prague/eip7702_set_code_tx/spec.py index 8f1eaac1fab..aa9dc50eeeb 100644 --- a/tests/prague/eip7702_set_code_tx/spec.py +++ b/tests/prague/eip7702_set_code_tx/spec.py @@ -18,7 +18,6 @@ class ReferenceSpec: ) -@dataclass(frozen=True) class Spec: """ Parameters from the EIP-7702 specifications as defined at diff --git a/tests/shanghai/eip3860_initcode/spec.py b/tests/shanghai/eip3860_initcode/spec.py index 63c58147f20..87535434cdb 100644 --- a/tests/shanghai/eip3860_initcode/spec.py +++ b/tests/shanghai/eip3860_initcode/spec.py @@ -16,7 +16,6 @@ class ReferenceSpec: ) -@dataclass(frozen=True) class Spec: """ Define parameters from the EIP-3860 specifications. From 26f47861dfbbd6b33d6a050ece5dae0ee4611285 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Tue, 30 Jun 2026 14:04:08 -0600 Subject: [PATCH 061/233] refactor(tests): generalize system-contract request test helpers (EIP-6110/7002/7251/7685) (#2989) * feat(test-types): Add unit test to `Transaction(gas_limit=None)` behavior * Reset: Incomplete refactor * refactor(tests): Remove `Spec.get_fee` from many tests * refactor(tests): Coalesce conftest for system contract tests * refactor(tests): Refactor multi-request tests * fix(tests): Failing amsterdam test * refactor(tests): Use REQUEST_TYPES in 7702 test * fix(test-types): Review comments Co-authored-by: LouisTsai --------- Co-authored-by: LouisTsai --- .../testing/src/execution_testing/__init__.py | 12 + .../execution_testing/test_types/__init__.py | 14 + .../system_contract_request_types.py | 391 +++++++++++++ .../tests/test_implicit_gas_limit.py | 39 ++ .../test_block_access_lists_eip7002.py | 49 +- .../test_block_access_lists_eip7251.py | 14 +- .../system_contract_request_fixtures.py | 172 ++++++ tests/prague/eip6110_deposits/conftest.py | 18 +- tests/prague/eip6110_deposits/helpers.py | 221 +------- .../prague/eip6110_deposits/test_deposits.py | 231 ++------ .../test_deposits_out_of_gas.py | 218 ++++++++ .../eip6110_deposits/test_eip_mainnet.py | 5 +- .../conftest.py | 139 +---- .../helpers.py | 296 +--------- .../test_contract_deployment.py | 1 - .../test_eip_mainnet.py | 10 +- .../test_modified_withdrawal_contract.py | 8 +- .../test_withdrawal_requests.py | 230 ++------ .../test_withdrawal_requests_during_fork.py | 16 +- .../test_withdrawal_requests_out_of_gas.py | 165 ++++++ .../prague/eip7251_consolidations/conftest.py | 139 +---- .../prague/eip7251_consolidations/helpers.py | 299 +--------- .../test_consolidations.py | 234 ++------ .../test_consolidations_during_fork.py | 16 +- .../test_consolidations_out_of_gas.py | 160 ++++++ .../test_contract_deployment.py | 1 - .../test_eip_mainnet.py | 10 +- .../test_modified_consolidation_contract.py | 8 +- .../conftest.py | 58 +- .../test_multi_type_requests.py | 524 ++++-------------- .../eip7702_set_code_tx/test_set_code_txs.py | 76 +-- 31 files changed, 1613 insertions(+), 2161 deletions(-) create mode 100644 packages/testing/src/execution_testing/test_types/system_contract_request_types.py create mode 100644 tests/common/system_contract_request_fixtures.py create mode 100644 tests/prague/eip6110_deposits/test_deposits_out_of_gas.py create mode 100644 tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests_out_of_gas.py create mode 100644 tests/prague/eip7251_consolidations/test_consolidations_out_of_gas.py diff --git a/packages/testing/src/execution_testing/__init__.py b/packages/testing/src/execution_testing/__init__.py index e8a5b2e3211..6f3289afd26 100644 --- a/packages/testing/src/execution_testing/__init__.py +++ b/packages/testing/src/execution_testing/__init__.py @@ -67,9 +67,14 @@ ConsolidationRequest, DepositRequest, Environment, + FeeSystemContractRequest, NetworkWrappedTransaction, Removable, Requests, + SystemContractInteractionBase, + SystemContractInteractionContract, + SystemContractInteractionTransaction, + SystemContractRequest, TestParameterGroup, TestPhaseManager, Transaction, @@ -84,6 +89,7 @@ compute_create_address, compute_deterministic_create2_address, keccak256, + relay_contract_code, ) from .tools import ( CalldataCase, @@ -164,6 +170,7 @@ "EngineAPIError", "Environment", "EOA", + "FeeSystemContractRequest", "FixedIterationsBytecode", "FixtureCollector", "Fork", @@ -193,6 +200,10 @@ "StateTestFiller", "Storage", "Switch", + "SystemContractInteractionBase", + "SystemContractInteractionContract", + "SystemContractInteractionTransaction", + "SystemContractRequest", "TestAddress", "TestAddress2", "TestParameterGroup", @@ -226,4 +237,5 @@ "generate_system_contract_deploy_test", "generate_system_contract_error_test", "keccak256", + "relay_contract_code", ) diff --git a/packages/testing/src/execution_testing/test_types/__init__.py b/packages/testing/src/execution_testing/test_types/__init__.py index b9029ceb3ad..ad0df23da36 100644 --- a/packages/testing/src/execution_testing/test_types/__init__.py +++ b/packages/testing/src/execution_testing/test_types/__init__.py @@ -40,6 +40,14 @@ Requests, WithdrawalRequest, ) +from .system_contract_request_types import ( + FeeSystemContractRequest, + SystemContractInteractionBase, + SystemContractInteractionContract, + SystemContractInteractionTransaction, + SystemContractRequest, + relay_contract_code, +) from .transaction_types import ( AuthorizationTuple, NetworkWrappedTransaction, @@ -73,9 +81,14 @@ "Environment", "EnvironmentDefaults", "EOA", + "FeeSystemContractRequest", "NetworkWrappedTransaction", "Removable", "Requests", + "SystemContractInteractionBase", + "SystemContractInteractionContract", + "SystemContractInteractionTransaction", + "SystemContractRequest", "TestParameterGroup", "TestPhase", "TestPhaseManager", @@ -95,4 +108,5 @@ "contract_address_from_hash", "eoa_from_hash", "keccak256", + "relay_contract_code", ) diff --git a/packages/testing/src/execution_testing/test_types/system_contract_request_types.py b/packages/testing/src/execution_testing/test_types/system_contract_request_types.py new file mode 100644 index 00000000000..a21a48d581c --- /dev/null +++ b/packages/testing/src/execution_testing/test_types/system_contract_request_types.py @@ -0,0 +1,391 @@ +""" +Test-side descriptors for execution-layer requests triggered via system +contracts. + +A `SystemContractRequest` is a `RequestBase` (it serializes to the on-chain +request bytes) that also carries the calldata, value and validity needed to +drive and verify a request from a test. The interaction classes +(`SystemContractInteractionTransaction` / `SystemContractInteractionContract`) +operate on any `SystemContractRequest`, so a single interaction can even mix +request types in one transaction. +""" + +from abc import abstractmethod +from dataclasses import dataclass, field, replace +from typing import Any, Callable, ClassVar, List, Self, Sequence + +from execution_testing.base_types import Address, CamelModel +from execution_testing.forks.forks.helpers import fake_exponential +from execution_testing.vm import Bytecode, Op + +from .account_types import EOA, Alloc +from .request_types import RequestBase +from .transaction_types import Transaction + + +class SystemContractRequest(RequestBase, CamelModel): + """ + Test descriptor for a request triggered by calling a system contract. + + Holds the fields and interface shared by all request types; the concrete + serialized fields (and the `RequestBase.__bytes__` / `type`) are provided + by each subclass. + """ + + valid: bool = True + """Whether the request is expected to be valid and therefore included.""" + calldata_modifier: Callable[[bytes], bytes] = lambda x: x + """Calldata modifier function applied when building the calldata.""" + + interaction_contract_address: ClassVar[Address] + """Address of the system contract that processes the request.""" + + @property + @abstractmethod + def value(self) -> int: + """Value (in wei) of the call that triggers the request.""" + ... + + @property + @abstractmethod + def calldata(self) -> bytes: + """Calldata of the call that triggers the request.""" + ... + + @abstractmethod + def with_source_address(self, source_address: Address) -> Self: + """Return a copy of the request with its source address set.""" + ... + + @classmethod + @abstractmethod + def from_index(cls, index: int, fee: int | None = None) -> Self: + """Build a request from a sequential index, paying `fee`.""" + ... + + +class FeeSystemContractRequest(SystemContractRequest): + """ + A `SystemContractRequest` whose triggering call must pay a fee that grows + with the per-block excess request count, following the `fake_exponential` + dynamic shared by EIP-7002, EIP-7251 (and future system contracts). + + Subclasses set `min_fee`, `update_fraction` and `target_per_block`, and + implement `from_index` to build a request from a sequential index. + """ + + fee: int = 0 + """Fee (in wei) paid to the system contract to enqueue the request.""" + + min_fee: ClassVar[int] + """Minimum fee, charged when there is no excess.""" + update_fraction: ClassVar[int] + """Controls how quickly the fee grows with the excess request count.""" + target_per_block: ClassVar[int] + """Target requests per block; excess above this raises the fee.""" + max_per_block: ClassVar[int] + """Maximum number of requests dequeued into a single block.""" + + def model_post_init(self, __context: Any) -> None: + """Default an unset fee to the base fee (the fee at zero excess).""" + super().model_post_init(__context) + if "fee" not in self.model_fields_set: + self.fee = type(self).get_fee(0) + + @property + def value(self) -> int: + """The value of the triggering call is the fee.""" + return self.fee + + @classmethod + def get_fee(cls, excess: int) -> int: + """Return the fee charged for the given excess request count.""" + return fake_exponential(cls.min_fee, excess, cls.update_fraction) + + @classmethod + def get_excess(cls, previous_excess: int, count: int) -> int: + """Return the new excess after a block processing `count` requests.""" + return max(0, previous_excess + count - cls.target_per_block) + + @classmethod + def get_n_fee_increments(cls, n: int) -> List[int]: + """Get the first N excess request counts that increase the fee.""" + excess_request_counts: List[int] = [] + last_fee = 1 + i = 0 + while len(excess_request_counts) < n: + fee = cls.get_fee(i) + if fee > last_fee: + excess_request_counts.append(i) + last_fee = fee + i += 1 + return excess_request_counts + + @classmethod + def get_n_fee_increment_blocks( + cls, n: int + ) -> List[List["SystemContractInteractionContract"]]: + """ + Return N blocks such that each subsequent block has an increasing fee + for the requests. + + Each block contains the number of requests required to reach the next + fee increment (plus the per-block target), built from sequential + indices via `from_index` and wrapped in a relay-contract interaction. + """ + blocks = [] + previous_excess = 0 + request_index = 0 + previous_fee = 0 + for required_excess_requests in cls.get_n_fee_increments(n): + requests_required = ( + required_excess_requests + + cls.target_per_block + - previous_excess + ) + fee = cls.get_fee(previous_excess) + assert fee > previous_fee + blocks.append( + [ + SystemContractInteractionContract( + requests=[ + cls.from_index(i, fee) + for i in range( + request_index, + request_index + requests_required, + ) + ], + ) + ], + ) + previous_fee = fee + request_index += requests_required + previous_excess = required_excess_requests + + return blocks + + +def relay_contract_code( + requests: Sequence[SystemContractRequest], + *, + call_type: Op, + extra_code: Bytecode, + gas_limits: List[int | None] | None = None, +) -> Bytecode: + """ + Build the code of a relay contract that issues each request by calling its + system contract with the request's calldata. + + The contract reads the concatenated request calldata from its own calldata, + copies each request payload into memory and issues `call_type` to the + corresponding system contract. + + `gas_limits` is an optional list, aligned with `requests`, that overrides + the gas forwarded to each inner call. It is only used by the out-of-gas + test functions; a `None` entry (or omitting the list) forwards all + available gas via `Op.GAS`. + """ + if gas_limits is not None: + assert len(gas_limits) == len(requests), ( + "gas_limits must be aligned with requests" + ) + code = Bytecode() + current_offset = 0 + for i, r in enumerate(requests): + gas_limit = gas_limits[i] if gas_limits is not None else None + value_arg = [r.value] if call_type in (Op.CALL, Op.CALLCODE) else [] + code += Op.CALLDATACOPY(0, current_offset, len(r.calldata)) + Op.POP( + call_type( + Op.GAS if gas_limit is None else gas_limit, + r.interaction_contract_address, + *value_arg, + 0, + len(r.calldata), + 0, + 0, + ) + ) + current_offset += len(r.calldata) + return code + extra_code + + +@dataclass(kw_only=True, frozen=True) +class SystemContractInteractionBase: + """Base class for all types of request transactions we want to test.""" + + sender_account: EOA | None = None + """Account that sends the transaction.""" + requests: Sequence[SystemContractRequest] + """Requests to be included in the block.""" + gas_limits: List[int | None] | None = None + """ + Optional per-request gas overrides, aligned with `requests`. Only set by + the out-of-gas test functions; left `None` for normal tests so the + automatic transaction gas-limit (and full inner-call gas) applies. + """ + + @property + def request_source_address(self) -> Address | None: + """Address recorded as the source of the requests.""" + raise NotImplementedError + + def transactions(self) -> List[Transaction]: + """Return the transactions that trigger the requests.""" + raise NotImplementedError + + def update_pre(self, pre: Alloc) -> Self: + """ + Allocate accounts/contracts in `pre` and return a new instance with + the allocated state populated. Does not mutate `self`, so the + parametrize value remains pristine across fixture format runs. + """ + raise NotImplementedError + + def valid_requests( + self, current_minimum_fee: int | None = None + ) -> List[SystemContractRequest]: + """ + Return the list of requests that should be included in the block. + + `current_minimum_fee` filters out requests whose value is below it + (e.g. the per-block fee). When `None`, no fee filter is applied and + every request marked `valid` is returned, trusting the caller to + ensure each request's value is sufficient. + """ + source = self.request_source_address + assert source is not None, "Source address not initialized" + return [ + r.with_source_address(source) + for r in self.requests + if r.valid + and (current_minimum_fee is None or r.value >= current_minimum_fee) + ] + + +@dataclass(kw_only=True, frozen=True) +class SystemContractInteractionTransaction(SystemContractInteractionBase): + """ + Describe requests originated from an externally owned account, one + transaction per request. + """ + + @property + def request_source_address(self) -> Address | None: + """The sender account is the source of the requests.""" + return self.sender_account + + def transactions(self) -> List[Transaction]: + """Return one transaction per request.""" + assert self.sender_account is not None, ( + "Sender account not initialized" + ) + txs: List[Transaction] = [] + for i, request in enumerate(self.requests): + gas_limit = ( + self.gas_limits[i] if self.gas_limits is not None else None + ) + txs.append( + Transaction( + gas_limit=gas_limit, + to=request.interaction_contract_address, + value=request.value, + data=request.calldata, + sender=self.sender_account, + ) + ) + return txs + + def update_pre(self, pre: Alloc) -> Self: + """Return a copy of self with `sender_account` populated.""" + return replace(self, sender_account=pre.fund_eoa()) + + +@dataclass(kw_only=True, frozen=True) +class SystemContractInteractionContract(SystemContractInteractionBase): + """Describe requests originated from a relay contract.""" + + tx_value: int = 0 + """Value to send with the transaction.""" + + contract_balance: int | None = None + """ + Balance of the relay contract that sends the requests. `None` (the + default) funds the contract with the sum of the request values, which is + always enough to forward each request's value. + """ + contract_address: Address | None = None + """Address of the relay contract that sends the requests.""" + entry_address: Address | None = None + """Address to send the transaction to.""" + + call_type: Op = field(default_factory=lambda: Op.CALL) + """Type of call to be made to the system contract.""" + call_depth: int = 2 + """Frame depth of the system contract when it processes the requests.""" + extra_code: Bytecode = field(default_factory=Bytecode) + """Extra code to be included in the relay contract.""" + + @property + def request_source_address(self) -> Address | None: + """The relay contract is the source of the requests.""" + return self.contract_address + + @property + def contract_code(self) -> Bytecode: + """Code used by the relay contract.""" + return relay_contract_code( + self.requests, + call_type=self.call_type, + extra_code=self.extra_code, + gas_limits=self.gas_limits, + ) + + def transactions(self) -> List[Transaction]: + """Return the single transaction that drives the relay contract.""" + assert self.entry_address is not None, "Entry address not initialized" + return [ + Transaction( + to=self.entry_address, + value=self.tx_value, + data=b"".join(r.calldata for r in self.requests), + sender=self.sender_account, + ) + ] + + def update_pre(self, pre: Alloc) -> Self: + """ + Return a copy of self with the allocated sender/contract/entry + addresses populated. + """ + sender_account = pre.fund_eoa() + contract_balance = ( + self.contract_balance + if self.contract_balance is not None + else sum(r.value for r in self.requests) + ) + contract_address = pre.deploy_contract( + code=self.contract_code, balance=contract_balance + ) + entry_address = contract_address + if self.call_depth > 2: + for _ in range(1, self.call_depth - 1): + entry_address = pre.deploy_contract( + code=Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) + + Op.POP( + Op.CALL( + Op.GAS, + entry_address, + 0, + 0, + Op.CALLDATASIZE, + 0, + 0, + ) + ), + ) + return replace( + self, + sender_account=sender_account, + contract_address=contract_address, + entry_address=entry_address, + ) diff --git a/packages/testing/src/execution_testing/test_types/tests/test_implicit_gas_limit.py b/packages/testing/src/execution_testing/test_types/tests/test_implicit_gas_limit.py index 979212f70de..899b15c2154 100644 --- a/packages/testing/src/execution_testing/test_types/tests/test_implicit_gas_limit.py +++ b/packages/testing/src/execution_testing/test_types/tests/test_implicit_gas_limit.py @@ -44,6 +44,45 @@ def calculate_max_transaction_gas_limit( ) +class TestTreatNoneGasLimitAsUnset: + """ + Test that an explicit `None` gas limit is dropped at construction time, + leaving the field unset and defaulted. + + Callers that build transactions programmatically (e.g. the system-contract + request helpers) rely on `gas_limit=None` being equivalent to omitting the + argument: the field stays out of `model_fields_set` so the implicit + gas-limit machinery resolves it, rather than treating it as explicit. + """ + + def test_none_defaults_to_21000(self) -> None: + """`gas_limit=None` defaults the field to the 21,000 base cost.""" + assert isinstance(Transaction(gas_limit=None).gas_limit, int) + + def test_none_not_in_model_fields_set(self) -> None: + """`gas_limit=None` leaves the field unset (implicit).""" + assert "gas_limit" not in Transaction(gas_limit=None).model_fields_set + + def test_none_matches_omitted(self) -> None: + """`gas_limit=None` is indistinguishable from omitting it.""" + explicit_none = Transaction(gas_limit=None) + omitted = Transaction() + assert explicit_none.gas_limit == omitted.gas_limit + assert explicit_none.model_fields_set == omitted.model_fields_set + + def test_explicit_value_is_set(self) -> None: + """An explicit integer gas limit remains in `model_fields_set`.""" + tx = Transaction(gas_limit=21_000) + assert "gas_limit" in tx.model_fields_set + + @pytest.mark.parametrize("alias", ["gas_limit", "gasLimit", "gas"]) + def test_none_dropped_for_all_aliases(self, alias: str) -> None: + """A `None` value is dropped regardless of the field alias used.""" + tx = Transaction(**{alias: None}) + assert tx.gas_limit == 21_000 + assert "gas_limit" not in tx.model_fields_set + + class TestSetGasLimit: """Test `Transaction.set_gas_limit` resolution of unset limits.""" diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip7002.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip7002.py index bf9384224e6..8eb5f8addfa 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip7002.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip7002.py @@ -16,14 +16,14 @@ BlockAccessListExpectation, BlockchainTestFiller, Op, + SystemContractInteractionBase, + SystemContractInteractionContract, + SystemContractInteractionTransaction, Transaction, ) from ...prague.eip7002_el_triggerable_withdrawals.helpers import ( WithdrawalRequest, - WithdrawalRequestContract, - WithdrawalRequestInteractionBase, - WithdrawalRequestTransaction, ) from ...prague.eip7002_el_triggerable_withdrawals.spec import Spec as Spec7002 from .spec import ref_spec_7928 @@ -527,14 +527,15 @@ def test_bal_7002_request_from_contract( fee = Spec7002.get_fee(0) # Create withdrawal request interaction using Prague helper - interaction = WithdrawalRequestContract( - requests=[ - WithdrawalRequest( - validator_pubkey=0x01, - amount=0, - fee=fee, - ) - ], + withdrawal_requests = [ + WithdrawalRequest( + validator_pubkey=0x01, + amount=0, + fee=fee, + ) + ] + interaction = SystemContractInteractionContract( + requests=withdrawal_requests, contract_balance=fee, ) @@ -546,7 +547,7 @@ def test_bal_7002_request_from_contract( # Build queue storage slots with contract as source queue_writes, queue_reads = _build_queue_storage_slots( - [relay_contract], prepared.requests + [relay_contract], withdrawal_requests ) block = Block( @@ -624,7 +625,7 @@ def test_bal_7002_request_from_contract( "interaction", [ pytest.param( - WithdrawalRequestTransaction( + SystemContractInteractionTransaction( requests=[ WithdrawalRequest( validator_pubkey=0x01, @@ -637,7 +638,7 @@ def test_bal_7002_request_from_contract( id="insufficient_fee", ), pytest.param( - WithdrawalRequestTransaction( + SystemContractInteractionTransaction( requests=[ WithdrawalRequest( validator_pubkey=0x01, @@ -653,7 +654,7 @@ def test_bal_7002_request_from_contract( id="calldata_too_short", ), pytest.param( - WithdrawalRequestTransaction( + SystemContractInteractionTransaction( requests=[ WithdrawalRequest( validator_pubkey=0x01, @@ -669,21 +670,21 @@ def test_bal_7002_request_from_contract( id="calldata_too_long", ), pytest.param( - WithdrawalRequestTransaction( + SystemContractInteractionTransaction( requests=[ WithdrawalRequest( validator_pubkey=0x01, amount=0, fee=Spec7002.get_fee(0), - gas_limit=25_000, # Insufficient gas valid=False, ) - ] + ], + gas_limits=[25_000], # Insufficient gas ), id="oog", ), pytest.param( - WithdrawalRequestContract( + SystemContractInteractionContract( requests=[ WithdrawalRequest( validator_pubkey=0x01, @@ -697,7 +698,7 @@ def test_bal_7002_request_from_contract( id="invalid_call_type_delegatecall", ), pytest.param( - WithdrawalRequestContract( + SystemContractInteractionContract( requests=[ WithdrawalRequest( validator_pubkey=0x01, @@ -711,7 +712,7 @@ def test_bal_7002_request_from_contract( id="invalid_call_type_staticcall", ), pytest.param( - WithdrawalRequestContract( + SystemContractInteractionContract( requests=[ WithdrawalRequest( validator_pubkey=0x01, @@ -725,7 +726,7 @@ def test_bal_7002_request_from_contract( id="invalid_call_type_callcode", ), pytest.param( - WithdrawalRequestContract( + SystemContractInteractionContract( requests=[ WithdrawalRequest( validator_pubkey=0x01, @@ -743,7 +744,7 @@ def test_bal_7002_request_from_contract( def test_bal_7002_request_invalid( pre: Alloc, blockchain_test: BlockchainTestFiller, - interaction: WithdrawalRequestInteractionBase, + interaction: SystemContractInteractionBase, ) -> None: """ Ensure BAL correctly handles invalid withdrawal request scenarios. @@ -798,7 +799,7 @@ def test_bal_7002_request_invalid( } # Add relay contract to post-state for contract scenarios - if isinstance(prepared, WithdrawalRequestContract): + if isinstance(prepared, SystemContractInteractionContract): post[prepared.contract_address] = Account() blockchain_test( diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip7251.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip7251.py index 35494f4207f..e7e26d85d71 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip7251.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip7251.py @@ -13,12 +13,10 @@ BlockAccessListExpectation, BlockchainTestFiller, Environment, + SystemContractInteractionTransaction, ) -from tests.prague.eip7251_consolidations.helpers import ( - ConsolidationRequest, - ConsolidationRequestTransaction, -) +from tests.prague.eip7251_consolidations.helpers import ConsolidationRequest from tests.prague.eip7251_consolidations.spec import Spec, ref_spec_7251 REFERENCE_SPEC_GIT_PATH = ref_spec_7251.git_path @@ -49,7 +47,7 @@ [ pytest.param( [ - ConsolidationRequestTransaction( + SystemContractInteractionTransaction( requests=[ ConsolidationRequest( source_pubkey=0x01, @@ -63,7 +61,7 @@ ), pytest.param( [ - ConsolidationRequestTransaction( + SystemContractInteractionTransaction( requests=[ ConsolidationRequest( source_pubkey=i * 2 + 1, @@ -78,7 +76,7 @@ ), pytest.param( [ - ConsolidationRequestTransaction( + SystemContractInteractionTransaction( requests=[ ConsolidationRequest( source_pubkey=i * 2 + 1, @@ -96,7 +94,7 @@ def test_bal_system_dequeue_consolidations_eip7251( blockchain_test: BlockchainTestFiller, pre: Alloc, - blocks_consolidation_requests: List[ConsolidationRequestTransaction], + blocks_consolidation_requests: List[SystemContractInteractionTransaction], ) -> None: """Test making a consolidation request to the beacon chain.""" txs = [] diff --git a/tests/common/system_contract_request_fixtures.py b/tests/common/system_contract_request_fixtures.py new file mode 100644 index 00000000000..d60537bf577 --- /dev/null +++ b/tests/common/system_contract_request_fixtures.py @@ -0,0 +1,172 @@ +""" +Shared pytest fixtures for system-contract request tests (EIP-6110, EIP-7002, +EIP-7251, and future forks). + +These fixtures are request-type agnostic and track inclusion independently per +request type. For `FeeSystemContractRequest` types (e.g. withdrawals and +consolidations) they read the per-block dequeue cap (`max_per_block`), the fee +curve (`get_fee`) and the excess update (`get_excess`), tracking the excess +request count per type. Fee-less requests (e.g. deposits) are always included +with no per-block cap. A block may therefore mix request types, each accounted +for with its own rules. +""" + +from collections import defaultdict +from itertools import zip_longest +from typing import Dict, List, Type + +import pytest +from execution_testing import ( + Alloc, + Block, + FeeSystemContractRequest, + Fork, + Header, + Requests, + SystemContractInteractionBase, + SystemContractRequest, + TransitionFork, +) + +RequestType = Type[SystemContractRequest] + + +@pytest.fixture +def prepared_system_contract_interactions_per_block( + pre: Alloc, + system_contract_interactions_per_block: List[ + List[SystemContractInteractionBase] + ], +) -> List[List[SystemContractInteractionBase]]: + """ + Allocate accounts/contracts for each interaction in `pre` and return copies + with the allocated state populated. The parametrize value + `system_contract_interactions_per_block` is not mutated, so it stays + pristine across fixture format runs. + """ + return [ + [r.update_pre(pre) for r in block_interactions] + for block_interactions in system_contract_interactions_per_block + ] + + +@pytest.fixture +def included_requests( + prepared_system_contract_interactions_per_block: List[ + List[SystemContractInteractionBase] + ], +) -> List[List[SystemContractRequest]]: + """ + Return the requests that should be included in each block, tracking the + excess request count independently per request type. + """ + excess: Dict[RequestType, int] = defaultdict(int) + carry_over: Dict[RequestType, List[SystemContractRequest]] = defaultdict( + list + ) + seen_types: List[RequestType] = [] + per_block_included: List[List[SystemContractRequest]] = [] + + for block_interactions in prepared_system_contract_interactions_per_block: + # Group this block's valid requests by type. Fee requests are kept only + # if they meet their type's current (per-block) fee; fee-less requests + # (e.g. deposits) are always included. + current: Dict[RequestType, List[SystemContractRequest]] = defaultdict( + list + ) + for interaction in block_interactions: + for request in interaction.valid_requests(): + request_type = type(request) + if request_type not in seen_types: + seen_types.append(request_type) + if isinstance(request, FeeSystemContractRequest): + minimum_fee = type(request).get_fee(excess[request_type]) + if request.value < minimum_fee: + continue + current[request_type].append(request) + + block_included: List[SystemContractRequest] = [] + for request_type in seen_types: + pending = carry_over[request_type] + current[request_type] + if issubclass(request_type, FeeSystemContractRequest): + cap = request_type.max_per_block + block_included += pending[:cap] + carry_over[request_type] = pending[cap:] + excess[request_type] = request_type.get_excess( + excess[request_type], len(current[request_type]) + ) + else: + # Fee-less requests (e.g. deposits) have no per-block cap. + block_included += pending + carry_over[request_type] = [] + per_block_included.append(block_included) + + # Keep adding blocks until every type's queue is drained. Only fee requests + # (which are capped per block) can ever carry over. + while any(carry_over[request_type] for request_type in seen_types): + block_included = [] + for request_type in seen_types: + if not issubclass(request_type, FeeSystemContractRequest): + continue + queue = carry_over[request_type] + cap = request_type.max_per_block + block_included += queue[:cap] + carry_over[request_type] = queue[cap:] + per_block_included.append(block_included) + + return per_block_included + + +@pytest.fixture +def timestamp() -> int: + """Return the timestamp for the first block.""" + return 1 + + +@pytest.fixture +def blocks( + fork: Fork | TransitionFork, + prepared_system_contract_interactions_per_block: List[ + List[SystemContractInteractionBase] + ], + included_requests: List[List[SystemContractRequest]], + timestamp: int, +) -> List[Block]: + """Return the list of blocks that should be included in the test.""" + blocks: List[Block] = [] + + for block_interactions, block_included_requests in zip_longest( # type: ignore + prepared_system_contract_interactions_per_block, + included_requests, + fillvalue=[], + ): + block_fork = fork.fork_at( + block_number=len(blocks) + 1, + timestamp=timestamp, + ) + header_verify: Header | None = None + if block_fork.header_requests_required(): + header_verify = Header( + requests_hash=Requests( + *block_included_requests, + ) + ) + else: + assert not block_included_requests + blocks.append( + Block( + txs=sum((r.transactions() for r in block_interactions), []), + header_verify=header_verify, + timestamp=timestamp, + ) + ) + timestamp += 1 + + return blocks + [ + # Add an empty block at the end to verify that no more requests are + # included. + Block( + header_verify=Header(requests_hash=Requests()), + timestamp=timestamp, + ) + ] diff --git a/tests/prague/eip6110_deposits/conftest.py b/tests/prague/eip6110_deposits/conftest.py index 2f26cde8ddb..6162db86bb6 100644 --- a/tests/prague/eip6110_deposits/conftest.py +++ b/tests/prague/eip6110_deposits/conftest.py @@ -10,17 +10,19 @@ Fork, Header, Requests, + SystemContractInteractionBase, + SystemContractRequest, Transaction, ) from execution_testing.base_types import HexNumber -from .helpers import DepositInteractionBase, DepositRequest +from .helpers import DepositRequest @pytest.fixture def prepared_requests( - pre: Alloc, requests: List[DepositInteractionBase] -) -> List[DepositInteractionBase]: + pre: Alloc, requests: List[SystemContractInteractionBase] +) -> List[SystemContractInteractionBase]: """ Allocate accounts/contracts for each request in `pre` and return copies with the allocated state populated. The parametrize value `requests` is @@ -32,7 +34,7 @@ def prepared_requests( @pytest.fixture def txs( fork: Fork, - prepared_requests: List[DepositInteractionBase], + prepared_requests: List[SystemContractInteractionBase], ) -> List[Transaction]: """List of transactions to include in the block.""" floor_cost = fork.transaction_data_floor_cost_calculator() @@ -66,12 +68,12 @@ def exception() -> BlockException | None: @pytest.fixture def included_requests( - prepared_requests: List[DepositInteractionBase], -) -> List[DepositRequest]: + prepared_requests: List[SystemContractInteractionBase], +) -> List[SystemContractRequest]: """ Return the list of deposit requests that should be included in each block. """ - valid_requests: List[DepositRequest] = [] + valid_requests: List[SystemContractRequest] = [] for d in prepared_requests: valid_requests += d.valid_requests(10**18) @@ -82,7 +84,7 @@ def included_requests( @pytest.fixture def blocks( fork: Fork, - included_requests: List[DepositRequest], + included_requests: List[SystemContractRequest], block_body_override_requests: List[DepositRequest] | None, txs: List[Transaction], exception: BlockException | None, diff --git a/tests/prague/eip6110_deposits/helpers.py b/tests/prague/eip6110_deposits/helpers.py index dcccf62cba2..570744c1a2a 100644 --- a/tests/prague/eip6110_deposits/helpers.py +++ b/tests/prague/eip6110_deposits/helpers.py @@ -1,19 +1,10 @@ """Helpers for the EIP-6110 deposit tests.""" -from dataclasses import dataclass, field, replace from functools import cached_property from hashlib import sha256 as sha256_hashlib -from typing import Callable, ClassVar, List, Self +from typing import ClassVar, Self -from execution_testing import ( - EOA, - Address, - Alloc, - Bytecode, - Hash, - Op, - Transaction, -) +from execution_testing import Address, Hash, SystemContractRequest from execution_testing import DepositRequest as DepositRequestBase from .spec import Spec @@ -80,15 +71,9 @@ def write_bytes(data: bytes, size: int) -> None: return bytes(result) -class DepositRequest(DepositRequestBase): +class DepositRequest(DepositRequestBase, SystemContractRequest): """Deposit request descriptor.""" - valid: bool = True - """Whether the deposit request is valid or not.""" - gas_limit: int | None = None - """Gas limit for the call.""" - calldata_modifier: Callable[[bytes], bytes] = lambda x: x - """Calldata modifier function.""" extra_wei: int = 0 """ Extra amount in wei to be sent with the deposit. If this value modulo 10**9 @@ -100,7 +85,7 @@ class DepositRequest(DepositRequestBase): Spec.DEPOSIT_CONTRACT_ADDRESS ) - @cached_property + @property def value(self) -> int: """ Return the value of the deposit transaction, equal to the amount in @@ -126,7 +111,7 @@ def deposit_data_root(self) -> Hash: amount_signature_root = sha256(amount_bytes, signature_root) return Hash(sha256(pubkey_withdrawal_root, amount_signature_root)) - @cached_property + @property def calldata(self) -> bytes: """ Return the calldata needed to call the beacon chain deposit contract @@ -213,190 +198,14 @@ def with_source_address(self, source_address: Address) -> "DepositRequest": del source_address return self.copy() - -@dataclass(kw_only=True, frozen=True) -class DepositInteractionBase: - """Base class for all types of deposit transactions we want to test.""" - - sender_account: EOA | None = None - """Account that sends the transaction.""" - requests: List[DepositRequest] - """Deposit request to be included in the block.""" - - def transactions(self) -> List[Transaction]: - """Return a transaction for the deposit request.""" - raise NotImplementedError - - def update_pre(self, pre: Alloc) -> Self: - """ - Allocate accounts/contracts in `pre` and return a new instance with - the allocated state populated. Does not mutate `self`, so the - parametrize value remains pristine across fixture format runs. - """ - raise NotImplementedError - - def valid_requests(self, current_minimum_fee: int) -> List[DepositRequest]: - """ - Return the list of deposit requests that should be included in the - block. - """ - raise NotImplementedError - - -@dataclass(kw_only=True, frozen=True) -class DepositTransaction(DepositInteractionBase): - """ - Class used to describe a deposit originated from an externally owned - account. - """ - - def transactions(self) -> List[Transaction]: - """Return a transaction for the deposit request.""" - assert self.sender_account is not None, ( - "Sender account not initialized" + @classmethod + def from_index(cls, index: int, fee: int | None = None) -> Self: + """Build a request from a sequential index, paying `fee`.""" + assert fee is None, f"Deposit requests do not require any fee: {fee}" + return cls( + pubkey=(index * 3), + withdrawal_credentials=(index * 3) + 1, + amount=1_000_000_000, + signature=(index * 3) + 2, + index=index, ) - txs: List[Transaction] = [] - for request in self.requests: - gas_limit = request.gas_limit - if gas_limit is not None: - tx = Transaction( - gas_limit=request.gas_limit, - to=request.interaction_contract_address, - value=request.value, - data=request.calldata, - sender=self.sender_account, - ) - else: - tx = Transaction( - to=request.interaction_contract_address, - value=request.value, - data=request.calldata, - sender=self.sender_account, - ) - txs.append(tx) - return txs - - def update_pre(self, pre: Alloc) -> Self: - """Return a copy of self with `sender_account` populated.""" - return replace(self, sender_account=pre.fund_eoa()) - - def valid_requests(self, current_minimum_fee: int) -> List[DepositRequest]: - """ - Return the list of deposit requests that should be included in the - block. - """ - return [ - request - for request in self.requests - if request.valid and request.value >= current_minimum_fee - ] - - -@dataclass(kw_only=True, frozen=True) -class DepositContract(DepositInteractionBase): - """Class used to describe a deposit originated from a contract.""" - - tx_gas_limit: int | None = None - """Gas limit for the transaction. `None` uses the implicit gas limit.""" - tx_value: int = 0 - """Value to send with the transaction.""" - - contract_balance: int = 32_000_000_000_000_000_000 * 100 - """Balance of the contract that sends the deposit requests.""" - contract_address: Address | None = None - """Address of the contract that sends the deposit requests.""" - entry_address: Address | None = None - """Address to send the transaction to.""" - - call_type: Op = field(default_factory=lambda: Op.CALL) - """Type of call to be made to the deposit contract.""" - call_depth: int = 2 - """ - Frame depth of the beacon chain deposit contract when it executes the - deposit requests. - """ - extra_code: Bytecode = field(default_factory=Bytecode) - """ - Extra code to be included in the contract that sends the deposit requests. - """ - - @property - def contract_code(self) -> Bytecode: - """Contract code used by the relay contract.""" - code = Bytecode() - current_offset = 0 - for r in self.requests: - value_arg = ( - [r.value] if self.call_type in (Op.CALL, Op.CALLCODE) else [] - ) - code += Op.CALLDATACOPY( - 0, current_offset, len(r.calldata) - ) + Op.POP( - self.call_type( - Op.GAS if r.gas_limit is None else r.gas_limit, - r.interaction_contract_address, - *value_arg, - 0, - len(r.calldata), - 0, - 0, - ) - ) - current_offset += len(r.calldata) - return code + self.extra_code - - def transactions(self) -> List[Transaction]: - """Return a transaction for the deposit request.""" - return [ - Transaction( - gas_limit=self.tx_gas_limit, - to=self.entry_address, - value=self.tx_value, - data=b"".join(r.calldata for r in self.requests), - sender=self.sender_account, - ) - ] - - def update_pre(self, pre: Alloc) -> Self: - """ - Return a copy of self with the allocated sender/contract/entry - addresses populated. - """ - sender_account = pre.fund_eoa() - contract_address = pre.deploy_contract( - code=self.contract_code, balance=self.contract_balance - ) - entry_address = contract_address - if self.call_depth > 2: - for _ in range(1, self.call_depth - 1): - entry_address = pre.deploy_contract( - code=Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) - + Op.POP( - Op.CALL( - Op.GAS, - entry_address, - 0, - 0, - Op.CALLDATASIZE, - 0, - 0, - ) - ), - ) - return replace( - self, - sender_account=sender_account, - contract_address=contract_address, - entry_address=entry_address, - ) - - def valid_requests(self, current_minimum_fee: int) -> List[DepositRequest]: - """ - Return the list of deposit requests that should be included in the - block. - """ - return [ - d - for d in self.requests - if d.valid and d.value >= current_minimum_fee - ] diff --git a/tests/prague/eip6110_deposits/test_deposits.py b/tests/prague/eip6110_deposits/test_deposits.py index 82f42cd9fdf..6b5508bfbe3 100644 --- a/tests/prague/eip6110_deposits/test_deposits.py +++ b/tests/prague/eip6110_deposits/test_deposits.py @@ -15,9 +15,11 @@ BlockException, Macros, Op, + SystemContractInteractionContract, + SystemContractInteractionTransaction, ) -from .helpers import DepositContract, DepositRequest, DepositTransaction +from .helpers import DepositRequest from .spec import ref_spec_6110 REFERENCE_SPEC_GIT_PATH = ref_spec_6110.git_path @@ -31,7 +33,7 @@ [ pytest.param( [ - DepositTransaction( + SystemContractInteractionTransaction( requests=[ DepositRequest( pubkey=0x01, @@ -47,7 +49,7 @@ ), pytest.param( [ - DepositTransaction( + SystemContractInteractionTransaction( requests=[ DepositRequest( pubkey=0x01, @@ -63,7 +65,7 @@ ), pytest.param( [ - DepositTransaction( + SystemContractInteractionTransaction( requests=[ DepositRequest( pubkey=0x01, @@ -86,7 +88,7 @@ ), pytest.param( [ - DepositTransaction( + SystemContractInteractionTransaction( requests=[ DepositRequest( pubkey=0x01, @@ -103,7 +105,7 @@ ), pytest.param( [ - DepositTransaction( + SystemContractInteractionTransaction( requests=[ DepositRequest( pubkey=0x01, @@ -114,7 +116,7 @@ ) ], ), - DepositTransaction( + SystemContractInteractionTransaction( requests=[ DepositRequest( pubkey=0x01, @@ -130,7 +132,7 @@ ), pytest.param( [ - DepositTransaction( + SystemContractInteractionTransaction( requests=[ DepositRequest( pubkey=0x01, @@ -153,7 +155,7 @@ ), pytest.param( [ - DepositTransaction( + SystemContractInteractionTransaction( requests=[ DepositRequest( pubkey=0x01, @@ -176,61 +178,7 @@ ), pytest.param( [ - DepositTransaction( - requests=[ - DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=32_000_000_000, - signature=0x03, - index=0x0, - # From traces, gas used by the first tx is 82,718 - # so reduce by one here - gas_limit=0x1431D, - valid=False, - ), - DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=32_000_000_000, - signature=0x03, - index=0x0, - ), - ], - ), - ], - id="multiple_deposit_from_same_eoa_first_oog", - ), - pytest.param( - [ - DepositTransaction( - requests=[ - DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=32_000_000_000, - signature=0x03, - index=0x0, - ), - DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=32_000_000_000, - signature=0x03, - index=0x0, - # From traces, gas used by the second tx is 68,594, - # reduce by one here - gas_limit=0x10BF1, - valid=False, - ), - ], - ), - ], - id="multiple_deposit_from_same_eoa_last_oog", - ), - pytest.param( - [ - DepositTransaction( + SystemContractInteractionTransaction( requests=[ DepositRequest( pubkey=0x01, @@ -248,7 +196,7 @@ ), pytest.param( [ - DepositContract( + SystemContractInteractionContract( requests=[ DepositRequest( pubkey=0x01, @@ -267,7 +215,7 @@ ), pytest.param( [ - DepositContract( + SystemContractInteractionContract( requests=[ DepositRequest( pubkey=0x01, @@ -283,7 +231,7 @@ ), pytest.param( [ - DepositContract( + SystemContractInteractionContract( requests=[ DepositRequest( pubkey=0x01, @@ -306,7 +254,7 @@ ), pytest.param( [ - DepositContract( + SystemContractInteractionContract( requests=[ DepositRequest( pubkey=0x01, @@ -324,7 +272,7 @@ ), pytest.param( [ - DepositContract( + SystemContractInteractionContract( requests=[ DepositRequest( pubkey=0x01, @@ -348,7 +296,7 @@ ), pytest.param( [ - DepositContract( + SystemContractInteractionContract( requests=[ DepositRequest( pubkey=0x01, @@ -372,57 +320,7 @@ ), pytest.param( [ - DepositContract( - requests=[ - DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=1_000_000_000, - signature=0x03, - gas_limit=100, - index=0x0, - valid=False, - ), - DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=1_000_000_000, - signature=0x03, - index=0x0, - ), - ], - ), - ], - id="multiple_deposits_from_contract_first_oog", - ), - pytest.param( - [ - DepositContract( - requests=[ - DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=1_000_000_000, - signature=0x03, - index=0x0, - ), - DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=1_000_000_000, - signature=0x03, - index=0x0, - gas_limit=100, - valid=False, - ), - ], - ), - ], - id="multiple_deposits_from_contract_last_oog", - ), - pytest.param( - [ - DepositContract( + SystemContractInteractionContract( requests=[ DepositRequest( pubkey=0x01, @@ -448,7 +346,7 @@ ), pytest.param( [ - DepositContract( + SystemContractInteractionContract( requests=[ DepositRequest( pubkey=0x01, @@ -474,27 +372,7 @@ ), pytest.param( [ - DepositContract( - requests=[ - DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=1_000_000_000, - signature=0x03, - index=i, - valid=False, - ) - for i in range(450) - ], - tx_gas_limit=10_000_000, - ), - ], - id="many_deposits_from_contract_oog", - marks=pytest.mark.slow, - ), - pytest.param( - [ - DepositContract( + SystemContractInteractionContract( requests=[ DepositRequest( pubkey=0x01, @@ -505,7 +383,7 @@ ), ], ), - DepositTransaction( + SystemContractInteractionTransaction( requests=[ DepositRequest( pubkey=0x01, @@ -521,7 +399,7 @@ ), pytest.param( [ - DepositTransaction( + SystemContractInteractionTransaction( requests=[ DepositRequest( pubkey=0x01, @@ -532,7 +410,7 @@ ) ], ), - DepositContract( + SystemContractInteractionContract( requests=[ DepositRequest( pubkey=0x01, @@ -548,7 +426,7 @@ ), pytest.param( [ - DepositTransaction( + SystemContractInteractionTransaction( requests=[ DepositRequest( pubkey=0x01, @@ -559,7 +437,7 @@ ) ], ), - DepositContract( + SystemContractInteractionContract( requests=[ DepositRequest( pubkey=0x01, @@ -570,7 +448,7 @@ ), ], ), - DepositTransaction( + SystemContractInteractionTransaction( requests=[ DepositRequest( pubkey=0x01, @@ -586,7 +464,7 @@ ), pytest.param( [ - DepositContract( + SystemContractInteractionContract( requests=[ DepositRequest( pubkey=0x01, @@ -597,7 +475,7 @@ ), ], ), - DepositTransaction( + SystemContractInteractionTransaction( requests=[ DepositRequest( pubkey=0x01, @@ -608,7 +486,7 @@ ) ], ), - DepositContract( + SystemContractInteractionContract( requests=[ DepositRequest( pubkey=0x01, @@ -624,7 +502,7 @@ ), pytest.param( [ - DepositContract( + SystemContractInteractionContract( requests=[ DepositRequest( pubkey=0x01, @@ -642,7 +520,7 @@ ), pytest.param( [ - DepositContract( + SystemContractInteractionContract( requests=[ DepositRequest( pubkey=0x01, @@ -660,7 +538,7 @@ ), pytest.param( [ - DepositContract( + SystemContractInteractionContract( requests=[ DepositRequest( pubkey=0x01, @@ -678,7 +556,7 @@ ), pytest.param( [ - DepositContract( + SystemContractInteractionContract( requests=[ DepositRequest( pubkey=0x01, @@ -693,10 +571,11 @@ ], id="single_deposit_from_contract_call_depth_3", ), - # TODO: Update tx_gas_limit for EIP-8037 state creation gas costs. + # TODO: Provide a higher transaction gas limit for EIP-8037 state + # creation gas costs to extend this test past EIP8037. pytest.param( [ - DepositContract( + SystemContractInteractionContract( requests=[ DepositRequest( pubkey=0x01, @@ -714,7 +593,7 @@ ), pytest.param( [ - DepositTransaction( + SystemContractInteractionTransaction( requests=[ DepositRequest( pubkey=0x01, @@ -730,7 +609,7 @@ ), pytest.param( [ - DepositTransaction( + SystemContractInteractionTransaction( requests=[ DepositRequest( pubkey=0x01, @@ -748,7 +627,7 @@ ), pytest.param( [ - DepositTransaction( + SystemContractInteractionTransaction( requests=[ DepositRequest( pubkey=0x01, @@ -766,7 +645,7 @@ ), pytest.param( [ - DepositContract( + SystemContractInteractionContract( requests=[ DepositRequest( pubkey=0x01, @@ -784,7 +663,7 @@ ), pytest.param( [ - DepositContract( + SystemContractInteractionContract( requests=[ DepositRequest( pubkey=0x01, @@ -802,7 +681,7 @@ ), pytest.param( [ - DepositTransaction( + SystemContractInteractionTransaction( requests=[ DepositRequest( pubkey=0x01, @@ -826,7 +705,7 @@ ), pytest.param( [ - DepositContract( + SystemContractInteractionContract( requests=[ DepositRequest( pubkey=0x01, @@ -847,7 +726,7 @@ ), pytest.param( [ - DepositContract( + SystemContractInteractionContract( requests=[ DepositRequest( pubkey=0x01, @@ -865,7 +744,7 @@ ), pytest.param( [ - DepositContract( + SystemContractInteractionContract( requests=[ DepositRequest( pubkey=0x01, @@ -884,7 +763,7 @@ ), pytest.param( [ - DepositContract( + SystemContractInteractionContract( requests=[ DepositRequest( pubkey=0x01, @@ -943,7 +822,7 @@ def test_deposit( ), pytest.param( [ - DepositTransaction( + SystemContractInteractionTransaction( requests=[ DepositRequest( pubkey=0x01, @@ -961,7 +840,7 @@ def test_deposit( ), pytest.param( [ - DepositTransaction( + SystemContractInteractionTransaction( requests=[ DepositRequest( pubkey=0x01, @@ -987,7 +866,7 @@ def test_deposit( ), pytest.param( [ - DepositTransaction( + SystemContractInteractionTransaction( requests=[ DepositRequest( pubkey=0x01, @@ -1013,7 +892,7 @@ def test_deposit( ), pytest.param( [ - DepositTransaction( + SystemContractInteractionTransaction( requests=[ DepositRequest( pubkey=0x01, @@ -1039,7 +918,7 @@ def test_deposit( ), pytest.param( [ - DepositTransaction( + SystemContractInteractionTransaction( requests=[ DepositRequest( pubkey=0x01, @@ -1065,7 +944,7 @@ def test_deposit( ), pytest.param( [ - DepositTransaction( + SystemContractInteractionTransaction( requests=[ DepositRequest( pubkey=0x01, @@ -1091,7 +970,7 @@ def test_deposit( ), pytest.param( [ - DepositTransaction( + SystemContractInteractionTransaction( requests=[ DepositRequest( pubkey=0x01, @@ -1131,7 +1010,7 @@ def test_deposit( ), pytest.param( [ - DepositTransaction( + SystemContractInteractionTransaction( requests=[ DepositRequest( pubkey=0x01, diff --git a/tests/prague/eip6110_deposits/test_deposits_out_of_gas.py b/tests/prague/eip6110_deposits/test_deposits_out_of_gas.py new file mode 100644 index 00000000000..78c241ba3e4 --- /dev/null +++ b/tests/prague/eip6110_deposits/test_deposits_out_of_gas.py @@ -0,0 +1,218 @@ +""" +Out-of-gas deposit tests. + +Tests that deposit requests whose triggering call runs out of gas are not +included in the block, for +[EIP-6110: Supply validator deposits on chain](https://eips.ethereum.org/EIPS/eip-6110). + +The gas limits are supplied via the interaction helpers (per-request +`gas_limits` or directly on the prepared transaction) rather than being baked +into the deposit request descriptor, keeping the gas concern isolated to these +dedicated tests. +""" + +from typing import List + +import pytest +from execution_testing import ( + Alloc, + Block, + BlockchainTestFiller, + Fork, + Header, + Requests, + SystemContractInteractionContract, + SystemContractInteractionTransaction, +) +from execution_testing.base_types import HexNumber + +from .helpers import DepositRequest +from .spec import ref_spec_6110 + +REFERENCE_SPEC_GIT_PATH = ref_spec_6110.git_path +REFERENCE_SPEC_VERSION = ref_spec_6110.version + +pytestmark = pytest.mark.valid_from("Prague") + + +@pytest.mark.parametrize( + "requests", + [ + pytest.param( + [ + SystemContractInteractionTransaction( + requests=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + valid=False, + ), + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + ), + ], + # From traces, gas used by the first tx is 82,718 + # so reduce by one here + gas_limits=[0x1431D, None], + ), + ], + id="multiple_deposit_from_same_eoa_first_oog", + ), + pytest.param( + [ + SystemContractInteractionTransaction( + requests=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + ), + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=32_000_000_000, + signature=0x03, + index=0x0, + valid=False, + ), + ], + # From traces, gas used by the second tx is 68,594, + # reduce by one here + gas_limits=[None, 0x10BF1], + ), + ], + id="multiple_deposit_from_same_eoa_last_oog", + ), + pytest.param( + [ + SystemContractInteractionContract( + requests=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=1_000_000_000, + signature=0x03, + index=0x0, + valid=False, + ), + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=1_000_000_000, + signature=0x03, + index=0x0, + ), + ], + # Starve the first inner call of gas + gas_limits=[100, None], + ), + ], + id="multiple_deposits_from_contract_first_oog", + ), + pytest.param( + [ + SystemContractInteractionContract( + requests=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=1_000_000_000, + signature=0x03, + index=0x0, + ), + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=1_000_000_000, + signature=0x03, + index=0x0, + valid=False, + ), + ], + # Starve the last inner call of gas + gas_limits=[None, 100], + ), + ], + id="multiple_deposits_from_contract_last_oog", + ), + ], +) +@pytest.mark.slow() +def test_deposit_out_of_gas( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + blocks: List[Block], +) -> None: + """ + Test that a deposit request whose triggering call runs out of gas is not + included, while the other requests in the block are. + + The gas limits are supplied per-request via the interaction's `gas_limits` + list rather than being baked into the deposit request descriptor, keeping + the gas concern isolated to these dedicated tests. + """ + blockchain_test( + pre=pre, + post={}, + blocks=blocks, + ) + + +@pytest.mark.slow() +def test_deposit_from_contract_transaction_out_of_gas( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test that a relay-contract transaction with an insufficient gas limit runs + out of gas, so none of the deposit requests it would trigger are included. + + The transaction gas limit is applied directly to the prepared transaction + rather than through the request helper, keeping the gas concern isolated to + this dedicated test. + """ + deposit_contract = SystemContractInteractionContract( + requests=[ + DepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=1_000_000_000, + signature=0x03, + index=i, + valid=False, + ) + for i in range(450) + ], + ).update_pre(pre) + + # A 10M gas limit is far too little to process all 450 deposits, so the + # transaction runs out of gas and emits no deposit requests. The limit is + # raised to the fork's calldata floor when that is higher (EIP-7623 / + # EIP-7976), so the transaction stays valid rather than being rejected for + # being below the floor; even then it is nowhere near enough to execute. + txs = deposit_contract.transactions() + floor_cost = fork.transaction_data_floor_cost_calculator() + txs[0].gas_limit = HexNumber( + max(10_000_000, floor_cost(data=txs[0].data) + 1) + ) + + blockchain_test( + pre=pre, + post={}, + blocks=[ + Block( + txs=txs, + header_verify=Header(requests_hash=Requests()), + ) + ], + ) diff --git a/tests/prague/eip6110_deposits/test_eip_mainnet.py b/tests/prague/eip6110_deposits/test_eip_mainnet.py index a481afc16dd..b33865860ca 100644 --- a/tests/prague/eip6110_deposits/test_eip_mainnet.py +++ b/tests/prague/eip6110_deposits/test_eip_mainnet.py @@ -9,9 +9,10 @@ Alloc, Block, BlockchainTestFiller, + SystemContractInteractionTransaction, ) -from .helpers import DepositRequest, DepositTransaction +from .helpers import DepositRequest from .spec import ref_spec_6110 REFERENCE_SPEC_GIT_PATH = ref_spec_6110.git_path @@ -25,7 +26,7 @@ [ pytest.param( [ - DepositTransaction( + SystemContractInteractionTransaction( # TODO: Use a real public key to allow recovery of # the funds. requests=[ diff --git a/tests/prague/eip7002_el_triggerable_withdrawals/conftest.py b/tests/prague/eip7002_el_triggerable_withdrawals/conftest.py index ffcfd918794..aad25c1e5ea 100644 --- a/tests/prague/eip7002_el_triggerable_withdrawals/conftest.py +++ b/tests/prague/eip7002_el_triggerable_withdrawals/conftest.py @@ -1,137 +1,8 @@ """Fixtures for the EIP-7002 withdrawal tests.""" -from itertools import zip_longest -from typing import List - -import pytest -from execution_testing import ( - Alloc, - Block, - Fork, - Header, - Requests, - TransitionFork, +from ...common.system_contract_request_fixtures import ( + blocks, # noqa: F401 + included_requests, # noqa: F401 + prepared_system_contract_interactions_per_block, # noqa: F401 + timestamp, # noqa: F401 ) - -from .helpers import WithdrawalRequest, WithdrawalRequestInteractionBase -from .spec import Spec - - -@pytest.fixture -def prepared_blocks_withdrawal_requests( - pre: Alloc, - blocks_withdrawal_requests: List[List[WithdrawalRequestInteractionBase]], -) -> List[List[WithdrawalRequestInteractionBase]]: - """ - Allocate accounts/contracts for each interaction in `pre` and return - copies with the allocated state populated. The parametrize value - `blocks_withdrawal_requests` is not mutated, so it stays pristine across - fixture format runs. - """ - return [ - [r.update_pre(pre) for r in block_requests] - for block_requests in blocks_withdrawal_requests - ] - - -@pytest.fixture -def included_requests( - prepared_blocks_withdrawal_requests: List[ - List[WithdrawalRequestInteractionBase] - ], -) -> List[List[WithdrawalRequest]]: - """ - Return the list of withdrawal requests that should be included in each - block. - """ - excess_withdrawal_requests = 0 - carry_over_requests: List[WithdrawalRequest] = [] - per_block_included_requests: List[List[WithdrawalRequest]] = [] - for block_withdrawal_requests in prepared_blocks_withdrawal_requests: - # Get fee for the current block - current_minimum_fee = Spec.get_fee(excess_withdrawal_requests) - - # With the fee, get the valid withdrawal requests for the current block - current_block_requests = [] - for w in block_withdrawal_requests: - current_block_requests += w.valid_requests(current_minimum_fee) - - # Get the withdrawal requests that should be included in the block - pending_requests = carry_over_requests + current_block_requests - per_block_included_requests.append( - pending_requests[: Spec.MAX_WITHDRAWAL_REQUESTS_PER_BLOCK] - ) - carry_over_requests = pending_requests[ - Spec.MAX_WITHDRAWAL_REQUESTS_PER_BLOCK : - ] - - # Update the excess withdrawal requests - excess_withdrawal_requests = Spec.get_excess_withdrawal_requests( - excess_withdrawal_requests, - len(current_block_requests), - ) - while carry_over_requests: - # Keep adding blocks until all withdrawal requests are included - per_block_included_requests.append( - carry_over_requests[: Spec.MAX_WITHDRAWAL_REQUESTS_PER_BLOCK] - ) - carry_over_requests = carry_over_requests[ - Spec.MAX_WITHDRAWAL_REQUESTS_PER_BLOCK : - ] - - return per_block_included_requests - - -@pytest.fixture -def timestamp() -> int: - """Return the timestamp for the first block.""" - return 1 - - -@pytest.fixture -def blocks( - fork: Fork | TransitionFork, - prepared_blocks_withdrawal_requests: List[ - List[WithdrawalRequestInteractionBase] - ], - included_requests: List[List[WithdrawalRequest]], - timestamp: int, -) -> List[Block]: - """Return the list of blocks that should be included in the test.""" - blocks: List[Block] = [] - - for block_requests, block_included_requests in zip_longest( # type: ignore - prepared_blocks_withdrawal_requests, - included_requests, - fillvalue=[], - ): - block_fork = fork.fork_at( - block_number=len(blocks) + 1, - timestamp=timestamp, - ) - header_verify: Header | None = None - if block_fork.header_requests_required(): - header_verify = Header( - requests_hash=Requests( - *block_included_requests, - ) - ) - else: - assert not block_included_requests - blocks.append( - Block( - txs=sum((r.transactions() for r in block_requests), []), - header_verify=header_verify, - timestamp=timestamp, - ) - ) - timestamp += 1 - - return blocks + [ - # Add an empty block at the end to verify that no more withdrawal - # requests are included - Block( - header_verify=Header(requests_hash=Requests()), - timestamp=timestamp, - ) - ] diff --git a/tests/prague/eip7002_el_triggerable_withdrawals/helpers.py b/tests/prague/eip7002_el_triggerable_withdrawals/helpers.py index 5eb36d1de2b..31268da9522 100644 --- a/tests/prague/eip7002_el_triggerable_withdrawals/helpers.py +++ b/tests/prague/eip7002_el_triggerable_withdrawals/helpers.py @@ -1,18 +1,8 @@ """Helpers for the EIP-7002 withdrawal tests.""" -from dataclasses import dataclass, field, replace -from functools import cached_property -from itertools import count -from typing import Callable, ClassVar, List, Self +from typing import ClassVar, Self -from execution_testing import ( - EOA, - Address, - Alloc, - Bytecode, - Op, - Transaction, -) +from execution_testing import Address, FeeSystemContractRequest from execution_testing import ( WithdrawalRequest as WithdrawalRequestBase, ) @@ -20,36 +10,20 @@ from .spec import Spec -class WithdrawalRequest(WithdrawalRequestBase): +class WithdrawalRequest(WithdrawalRequestBase, FeeSystemContractRequest): """Class used to describe a withdrawal request in a test.""" - fee: int = 0 - """ - Fee to be paid to the system contract for the withdrawal request. This is - different from `amount` which is the amount of gwei to be withdrawn on the - beacon chain. - - """ - valid: bool = True - """Whether the withdrawal request is valid or not.""" - gas_limit: int | None = None - """Gas limit for the call.""" - calldata_modifier: Callable[[bytes], bytes] = lambda x: x - """Calldata modifier function.""" - interaction_contract_address: ClassVar[Address] = Address( Spec.WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS ) + min_fee: ClassVar[int] = Spec.MIN_WITHDRAWAL_REQUEST_FEE + update_fraction: ClassVar[int] = ( + Spec.WITHDRAWAL_REQUEST_FEE_UPDATE_FRACTION + ) + target_per_block: ClassVar[int] = Spec.TARGET_WITHDRAWAL_REQUESTS_PER_BLOCK + max_per_block: ClassVar[int] = Spec.MAX_WITHDRAWAL_REQUESTS_PER_BLOCK @property - def value(self) -> int: - """ - Return the value of the call to the withdrawal request contract, equal - to the fee to be paid. - """ - return self.fee - - @cached_property def calldata(self) -> bytes: """ Return the calldata needed to call the withdrawal request contract and @@ -68,249 +42,9 @@ def with_source_address( """ return self.copy(source_address=source_address) - -@dataclass(kw_only=True, frozen=True) -class WithdrawalRequestInteractionBase: - """Base class for all types of withdrawal transactions we want to test.""" - - sender_account: EOA | None = None - """Account that will send the transaction.""" - requests: List[WithdrawalRequest] - """Withdrawal request to be included in the block.""" - - def transactions(self) -> List[Transaction]: - """Return a transaction for the withdrawal request.""" - raise NotImplementedError - - def update_pre(self, pre: Alloc) -> Self: - """ - Allocate accounts/contracts in `pre` and return a new instance with - the allocated state populated. Does not mutate `self`, so the - parametrize value remains pristine across fixture format runs. - """ - raise NotImplementedError - - def valid_requests( - self, current_minimum_fee: int - ) -> List[WithdrawalRequest]: - """ - Return the list of withdrawal requests that should be valid in the - block. - """ - raise NotImplementedError - - -@dataclass(kw_only=True, frozen=True) -class WithdrawalRequestTransaction(WithdrawalRequestInteractionBase): - """ - Class used to describe a withdrawal request originated from an externally - owned account. - """ - - def transactions(self) -> List[Transaction]: - """Return a transaction for the withdrawal request.""" - assert self.sender_account is not None, ( - "Sender account not initialized" - ) - txs: List[Transaction] = [] - for request in self.requests: - gas_limit = request.gas_limit - if gas_limit is not None: - tx = Transaction( - gas_limit=request.gas_limit, - to=request.interaction_contract_address, - value=request.value, - data=request.calldata, - sender=self.sender_account, - ) - else: - tx = Transaction( - to=request.interaction_contract_address, - value=request.value, - data=request.calldata, - sender=self.sender_account, - ) - txs.append(tx) - return txs - - def update_pre(self, pre: Alloc) -> Self: - """Return a copy of self with `sender_account` populated.""" - return replace(self, sender_account=pre.fund_eoa()) - - def valid_requests( - self, current_minimum_fee: int - ) -> List[WithdrawalRequest]: - """Return the list of withdrawal requests that are valid.""" - assert self.sender_account is not None, ( - "Sender account not initialized" - ) - return [ - request.with_source_address(self.sender_account) - for request in self.requests - if request.valid and request.fee >= current_minimum_fee - ] - - -@dataclass(kw_only=True, frozen=True) -class WithdrawalRequestContract(WithdrawalRequestInteractionBase): - """Class used to describe a withdrawal originated from a contract.""" - - contract_balance: int = 1_000_000_000_000_000_000 - """ - Balance of the contract that will make the call to the pre-deploy contract. - """ - contract_address: Address | None = None - """ - Address of the contract that will make the call to the pre-deploy contract. - """ - entry_address: Address | None = None - """Address to send the transaction to.""" - - call_type: Op = field(default_factory=lambda: Op.CALL) - """Type of call to be used to make the withdrawal request.""" - call_depth: int = 2 - """Frame depth of the pre-deploy contract when it executes the call.""" - extra_code: Bytecode = field(default_factory=Bytecode) - """Extra code to be added to the contract code.""" - - @property - def contract_code(self) -> Bytecode: - """Contract code used by the relay contract.""" - code = Bytecode() - current_offset = 0 - for r in self.requests: - value_arg = ( - [r.value] if self.call_type in (Op.CALL, Op.CALLCODE) else [] - ) - code += Op.CALLDATACOPY( - 0, current_offset, len(r.calldata) - ) + Op.POP( - self.call_type( - Op.GAS if r.gas_limit is None else r.gas_limit, - r.interaction_contract_address, - *value_arg, - 0, - len(r.calldata), - 0, - 0, - ) - ) - current_offset += len(r.calldata) - return code + self.extra_code - - def transactions(self) -> List[Transaction]: - """Return a transaction for the withdrawal request.""" - assert self.entry_address is not None, "Entry address not initialized" - return [ - Transaction( - to=self.entry_address, - data=b"".join(r.calldata for r in self.requests), - sender=self.sender_account, - ) - ] - - def update_pre(self, pre: Alloc) -> Self: - """ - Return a copy of self with the allocated sender/contract/entry - addresses populated. - """ - sender_account = pre.fund_eoa() - contract_address = pre.deploy_contract( - code=self.contract_code, balance=self.contract_balance - ) - entry_address = contract_address - if self.call_depth > 2: - for _ in range(1, self.call_depth - 1): - entry_address = pre.deploy_contract( - code=Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) - + Op.POP( - Op.CALL( - Op.GAS, - entry_address, - 0, - 0, - Op.CALLDATASIZE, - 0, - 0, - ) - ) - ) - return replace( - self, - sender_account=sender_account, - contract_address=contract_address, - entry_address=entry_address, - ) - - def valid_requests( - self, current_minimum_fee: int - ) -> List[WithdrawalRequest]: - """Return the list of withdrawal requests that are valid.""" - assert self.contract_address is not None, ( - "Contract address not initialized" - ) - return [ - r.with_source_address(self.contract_address) - for r in self.requests - if r.valid and r.value >= current_minimum_fee - ] - - -def get_n_fee_increments(n: int) -> List[int]: - """Get the first N excess withdrawal requests that increase the fee.""" - excess_withdrawal_requests_counts = [] - last_fee = 1 - for i in count(0): - if Spec.get_fee(i) > last_fee: - excess_withdrawal_requests_counts.append(i) - last_fee = Spec.get_fee(i) - if len(excess_withdrawal_requests_counts) == n: - break - return excess_withdrawal_requests_counts - - -def get_n_fee_increment_blocks( - n: int, -) -> List[List[WithdrawalRequestContract]]: - """ - Return N blocks that should be included in the test such that each - subsequent block has an increasing fee for the withdrawal requests. - - This is done by calculating the number of withdrawals required to reach the - next fee increment and creating a block with that number of withdrawal - requests plus the number of withdrawals required to reach the target. - """ - blocks = [] - previous_excess = 0 - withdrawal_index = 0 - previous_fee = 0 - for required_excess_withdrawals in get_n_fee_increments(n): - withdrawals_required = ( - required_excess_withdrawals - + Spec.TARGET_WITHDRAWAL_REQUESTS_PER_BLOCK - - previous_excess - ) - fee = Spec.get_fee(previous_excess) - assert fee > previous_fee - blocks.append( - [ - WithdrawalRequestContract( - requests=[ - WithdrawalRequest( - validator_pubkey=i, - amount=0, - fee=fee, - ) - for i in range( - withdrawal_index, - withdrawal_index + withdrawals_required, - ) - ], - ) - ], - ) - previous_fee = fee - withdrawal_index += withdrawals_required - previous_excess = required_excess_withdrawals - - return blocks + @classmethod + def from_index(cls, index: int, fee: int | None = None) -> Self: + """Build a withdrawal request from a sequential index.""" + if fee is None: + fee = cls.get_fee(0) + return cls(validator_pubkey=index, amount=0, fee=fee) diff --git a/tests/prague/eip7002_el_triggerable_withdrawals/test_contract_deployment.py b/tests/prague/eip7002_el_triggerable_withdrawals/test_contract_deployment.py index 03c65ff2ecd..1330eea842c 100644 --- a/tests/prague/eip7002_el_triggerable_withdrawals/test_contract_deployment.py +++ b/tests/prague/eip7002_el_triggerable_withdrawals/test_contract_deployment.py @@ -43,7 +43,6 @@ def test_system_contract_deployment( withdrawal_request = WithdrawalRequest( validator_pubkey=0x01, amount=1, - fee=Spec.get_fee(0), source_address=sender, ) intrinsic_gas_calculator = ( diff --git a/tests/prague/eip7002_el_triggerable_withdrawals/test_eip_mainnet.py b/tests/prague/eip7002_el_triggerable_withdrawals/test_eip_mainnet.py index a230a2ddca0..956db33bdac 100644 --- a/tests/prague/eip7002_el_triggerable_withdrawals/test_eip_mainnet.py +++ b/tests/prague/eip7002_el_triggerable_withdrawals/test_eip_mainnet.py @@ -9,10 +9,11 @@ Alloc, Block, BlockchainTestFiller, + SystemContractInteractionTransaction, ) -from .helpers import WithdrawalRequest, WithdrawalRequestTransaction -from .spec import Spec, ref_spec_7002 +from .helpers import WithdrawalRequest +from .spec import ref_spec_7002 REFERENCE_SPEC_GIT_PATH = ref_spec_7002.git_path REFERENCE_SPEC_VERSION = ref_spec_7002.version @@ -21,17 +22,16 @@ @pytest.mark.parametrize( - "blocks_withdrawal_requests", + "system_contract_interactions_per_block", [ pytest.param( [ [ - WithdrawalRequestTransaction( + SystemContractInteractionTransaction( requests=[ WithdrawalRequest( validator_pubkey=0x01, amount=0, - fee=Spec.get_fee(0), ) ], ), diff --git a/tests/prague/eip7002_el_triggerable_withdrawals/test_modified_withdrawal_contract.py b/tests/prague/eip7002_el_triggerable_withdrawals/test_modified_withdrawal_contract.py index 7a5aeb5cb11..f218318274f 100644 --- a/tests/prague/eip7002_el_triggerable_withdrawals/test_modified_withdrawal_contract.py +++ b/tests/prague/eip7002_el_triggerable_withdrawals/test_modified_withdrawal_contract.py @@ -14,15 +14,13 @@ Bytecode, Op, Requests, + SystemContractInteractionTransaction, Transaction, generate_system_contract_error_test, ) from execution_testing import Macros as Om -from .helpers import ( - WithdrawalRequest, - WithdrawalRequestTransaction, -) +from .helpers import WithdrawalRequest from .spec import Spec as Spec_EIP7002 from .spec import ref_spec_7002 @@ -120,7 +118,7 @@ def test_extra_withdrawals( # given a list of withdrawal requests construct a withdrawal request # transaction - withdrawal_request_transaction = WithdrawalRequestTransaction( + withdrawal_request_transaction = SystemContractInteractionTransaction( requests=requests_list ) # prepare withdrawal senders diff --git a/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests.py b/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests.py index 2b13ec75d54..eb4a09690ab 100644 --- a/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests.py +++ b/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests.py @@ -17,17 +17,14 @@ Macros, Op, Requests, + SystemContractInteractionBase, + SystemContractInteractionContract, + SystemContractInteractionTransaction, TestAddress, TestAddress2, ) -from .helpers import ( - WithdrawalRequest, - WithdrawalRequestContract, - WithdrawalRequestInteractionBase, - WithdrawalRequestTransaction, - get_n_fee_increment_blocks, -) +from .helpers import WithdrawalRequest from .spec import Spec, ref_spec_7002 REFERENCE_SPEC_GIT_PATH = ref_spec_7002.git_path @@ -37,17 +34,16 @@ @pytest.mark.parametrize( - "blocks_withdrawal_requests", + "system_contract_interactions_per_block", [ pytest.param( [ [ - WithdrawalRequestTransaction( + SystemContractInteractionTransaction( requests=[ WithdrawalRequest( validator_pubkey=0x01, amount=0, - fee=Spec.get_fee(0), ) ], ), @@ -58,7 +54,7 @@ pytest.param( [ [ - WithdrawalRequestTransaction( + SystemContractInteractionTransaction( requests=[ WithdrawalRequest( validator_pubkey=0x01, @@ -75,12 +71,11 @@ pytest.param( [ [ - WithdrawalRequestTransaction( + SystemContractInteractionTransaction( requests=[ WithdrawalRequest( validator_pubkey=0x01, amount=0, - fee=Spec.get_fee(0), calldata_modifier=lambda x: x[:-1], valid=False, ) @@ -93,12 +88,11 @@ pytest.param( [ [ - WithdrawalRequestTransaction( + SystemContractInteractionTransaction( requests=[ WithdrawalRequest( validator_pubkey=0x01, amount=0, - fee=Spec.get_fee(0), calldata_modifier=lambda x: x + b"\x00", valid=False, ) @@ -111,17 +105,15 @@ pytest.param( [ [ - WithdrawalRequestTransaction( + SystemContractInteractionTransaction( requests=[ WithdrawalRequest( validator_pubkey=0x01, amount=0, - fee=Spec.get_fee(0), ), WithdrawalRequest( validator_pubkey=0x02, amount=Spec.MAX_AMOUNT - 1, - fee=Spec.get_fee(0), ), ], ), @@ -132,21 +124,19 @@ pytest.param( [ [ - WithdrawalRequestTransaction( + SystemContractInteractionTransaction( requests=[ WithdrawalRequest( validator_pubkey=0x01, amount=0, - fee=Spec.get_fee(0), ) ], ), - WithdrawalRequestTransaction( + SystemContractInteractionTransaction( requests=[ WithdrawalRequest( validator_pubkey=0x02, amount=Spec.MAX_AMOUNT - 1, - fee=Spec.get_fee(0), ) ], ), @@ -157,12 +147,11 @@ pytest.param( [ [ - WithdrawalRequestTransaction( + SystemContractInteractionTransaction( requests=[ WithdrawalRequest( validator_pubkey=i + 1, amount=0 if i % 2 == 0 else Spec.MAX_AMOUNT, - fee=Spec.get_fee(0), ) for i in range( Spec.MAX_WITHDRAWAL_REQUESTS_PER_BLOCK @@ -176,7 +165,7 @@ pytest.param( [ [ - WithdrawalRequestTransaction( + SystemContractInteractionTransaction( requests=[ WithdrawalRequest( validator_pubkey=0x01, @@ -186,7 +175,6 @@ WithdrawalRequest( validator_pubkey=0x02, amount=Spec.MAX_AMOUNT - 1, - fee=Spec.get_fee(0), ), ] ), @@ -197,12 +185,11 @@ pytest.param( [ [ - WithdrawalRequestTransaction( + SystemContractInteractionTransaction( requests=[ WithdrawalRequest( validator_pubkey=0x01, amount=0, - fee=Spec.get_fee(0), ), WithdrawalRequest( validator_pubkey=0x02, @@ -218,60 +205,11 @@ pytest.param( [ [ - WithdrawalRequestTransaction( - requests=[ - WithdrawalRequest( - validator_pubkey=0x01, - amount=0, - fee=Spec.get_fee(0), - # Value obtained from trace minus one - gas_limit=114_247 - 1, - valid=False, - ), - WithdrawalRequest( - validator_pubkey=0x02, - amount=0, - fee=Spec.get_fee(0), - ), - ] - ), - ], - ], - id="single_block_multiple_withdrawal_request_first_oog", - ), - pytest.param( - [ - [ - WithdrawalRequestTransaction( - requests=[ - WithdrawalRequest( - validator_pubkey=0x01, - amount=0, - fee=Spec.get_fee(0), - ), - WithdrawalRequest( - validator_pubkey=0x02, - amount=0, - fee=Spec.get_fee(0), - # Value obtained from trace minus one - gas_limit=80_047 - 1, - valid=False, - ), - ] - ), - ], - ], - id="single_block_multiple_withdrawal_request_last_oog", - ), - pytest.param( - [ - [ - WithdrawalRequestTransaction( + SystemContractInteractionTransaction( requests=[ WithdrawalRequest( validator_pubkey=i + 1, amount=0 if i % 2 == 0 else Spec.MAX_AMOUNT, - fee=Spec.get_fee(0), ) for i in range( Spec.MAX_WITHDRAWAL_REQUESTS_PER_BLOCK * 2 @@ -285,12 +223,11 @@ pytest.param( [ [ - WithdrawalRequestContract( + SystemContractInteractionContract( requests=[ WithdrawalRequest( validator_pubkey=0x01, amount=0, - fee=Spec.get_fee(0), ), ] ), @@ -301,12 +238,11 @@ pytest.param( [ [ - WithdrawalRequestContract( + SystemContractInteractionContract( requests=[ WithdrawalRequest( validator_pubkey=0x01, amount=0, - fee=Spec.get_fee(0), ), ], call_depth=3, @@ -318,12 +254,11 @@ pytest.param( [ [ - WithdrawalRequestContract( + SystemContractInteractionContract( requests=[ WithdrawalRequest( validator_pubkey=0x01, amount=0, - fee=Spec.get_fee(0), ), ], call_depth=264, @@ -335,14 +270,13 @@ pytest.param( [ [ - WithdrawalRequestContract( + SystemContractInteractionContract( requests=[ WithdrawalRequest( validator_pubkey=i + 1, amount=Spec.MAX_AMOUNT - 1 if i % 2 == 0 else 0, - fee=Spec.get_fee(0), ) for i in range( Spec.MAX_WITHDRAWAL_REQUESTS_PER_BLOCK @@ -356,7 +290,7 @@ pytest.param( [ [ - WithdrawalRequestContract( + SystemContractInteractionContract( requests=[ WithdrawalRequest( validator_pubkey=1, @@ -370,7 +304,6 @@ amount=Spec.MAX_AMOUNT - 1 if i % 2 == 0 else 0, - fee=Spec.get_fee(0), ) for i in range( 1, Spec.MAX_WITHDRAWAL_REQUESTS_PER_BLOCK @@ -384,14 +317,13 @@ pytest.param( [ [ - WithdrawalRequestContract( + SystemContractInteractionContract( requests=[ WithdrawalRequest( validator_pubkey=i + 1, amount=Spec.MAX_AMOUNT - 1 if i % 2 == 0 else 0, - fee=Spec.get_fee(0), ) for i in range( Spec.MAX_WITHDRAWAL_REQUESTS_PER_BLOCK - 1 @@ -421,76 +353,13 @@ pytest.param( [ [ - WithdrawalRequestContract( - requests=[ - WithdrawalRequest( - validator_pubkey=1, - amount=Spec.MAX_AMOUNT - 1, - gas_limit=100, - fee=Spec.get_fee(0), - valid=False, - ) - ] - + [ - WithdrawalRequest( - validator_pubkey=i + 1, - amount=Spec.MAX_AMOUNT - 1 - if i % 2 == 0 - else 0, - fee=Spec.get_fee(0), - valid=True, - ) - for i in range( - 1, Spec.MAX_WITHDRAWAL_REQUESTS_PER_BLOCK - ) - ], - ), - ], - ], - id="single_block_multiple_withdrawal_requests_from_contract_first_oog", - ), - pytest.param( - [ - [ - WithdrawalRequestContract( - requests=[ - WithdrawalRequest( - validator_pubkey=i + 1, - amount=Spec.MAX_AMOUNT - 1 - if i % 2 == 0 - else 0, - fee=Spec.get_fee(0), - valid=True, - ) - for i in range( - Spec.MAX_WITHDRAWAL_REQUESTS_PER_BLOCK - ) - ] - + [ - WithdrawalRequest( - validator_pubkey=Spec.MAX_WITHDRAWAL_REQUESTS_PER_BLOCK, - amount=Spec.MAX_AMOUNT - 1, - gas_limit=100, - fee=Spec.get_fee(0), - valid=False, - ) - ], - ), - ], - ], - id="single_block_multiple_withdrawal_requests_from_contract_last_oog", - ), - pytest.param( - [ - [ - WithdrawalRequestContract( + SystemContractInteractionContract( requests=[ WithdrawalRequest( validator_pubkey=i + 1, amount=Spec.MAX_AMOUNT - 1 if i % 2 == 0 else 0, - fee=Spec.get_fee(0), valid=False, ) for i in range( @@ -506,14 +375,13 @@ pytest.param( [ [ - WithdrawalRequestContract( + SystemContractInteractionContract( requests=[ WithdrawalRequest( validator_pubkey=i + 1, amount=Spec.MAX_AMOUNT - 1 if i % 2 == 0 else 0, - fee=Spec.get_fee(0), valid=False, ) for i in range( @@ -528,40 +396,37 @@ ), pytest.param( # Test the first 50 fee increments - get_n_fee_increment_blocks(50), + WithdrawalRequest.get_n_fee_increment_blocks(50), id="multiple_block_fee_increments", ), pytest.param( [ [ - WithdrawalRequestContract( + SystemContractInteractionContract( requests=[ WithdrawalRequest( validator_pubkey=0x01, amount=0, - fee=Spec.get_fee(0), valid=False, ) ], call_type=Op.DELEGATECALL, ), - WithdrawalRequestContract( + SystemContractInteractionContract( requests=[ WithdrawalRequest( validator_pubkey=0x01, amount=0, - fee=Spec.get_fee(0), valid=False, ) ], call_type=Op.STATICCALL, ), - WithdrawalRequestContract( + SystemContractInteractionContract( requests=[ WithdrawalRequest( validator_pubkey=0x01, amount=0, - fee=Spec.get_fee(0), valid=False, ) ], @@ -574,36 +439,33 @@ pytest.param( [ [ - WithdrawalRequestContract( + SystemContractInteractionContract( requests=[ WithdrawalRequest( validator_pubkey=0x01, amount=0, - fee=Spec.get_fee(0), valid=False, ) ], call_type=Op.DELEGATECALL, call_depth=3, ), - WithdrawalRequestContract( + SystemContractInteractionContract( requests=[ WithdrawalRequest( validator_pubkey=0x01, amount=0, - fee=Spec.get_fee(0), valid=False, ) ], call_type=Op.STATICCALL, call_depth=3, ), - WithdrawalRequestContract( + SystemContractInteractionContract( requests=[ WithdrawalRequest( validator_pubkey=0x01, amount=0, - fee=Spec.get_fee(0), valid=False, ) ], @@ -617,36 +479,33 @@ pytest.param( [ [ - WithdrawalRequestContract( + SystemContractInteractionContract( requests=[ WithdrawalRequest( validator_pubkey=0x01, amount=0, - fee=Spec.get_fee(0), valid=False, ) ], call_type=Op.DELEGATECALL, call_depth=1024, ), - WithdrawalRequestContract( + SystemContractInteractionContract( requests=[ WithdrawalRequest( validator_pubkey=0x01, amount=0, - fee=Spec.get_fee(0), valid=False, ) ], call_type=Op.STATICCALL, call_depth=1024, ), - WithdrawalRequestContract( + SystemContractInteractionContract( requests=[ WithdrawalRequest( validator_pubkey=0x01, amount=0, - fee=Spec.get_fee(0), valid=False, ) ], @@ -690,12 +549,11 @@ def test_withdrawal_requests( ), pytest.param( [ - WithdrawalRequestTransaction( + SystemContractInteractionTransaction( requests=[ WithdrawalRequest( validator_pubkey=0x01, amount=0, - fee=Spec.get_fee(0), ), ] ), @@ -706,12 +564,11 @@ def test_withdrawal_requests( ), pytest.param( [ - WithdrawalRequestTransaction( + SystemContractInteractionTransaction( requests=[ WithdrawalRequest( validator_pubkey=0x01, amount=0, - fee=Spec.get_fee(0), ), ] ), @@ -728,12 +585,11 @@ def test_withdrawal_requests( ), pytest.param( [ - WithdrawalRequestTransaction( + SystemContractInteractionTransaction( requests=[ WithdrawalRequest( validator_pubkey=0x01, amount=0, - fee=Spec.get_fee(0), ) ], ), @@ -750,12 +606,11 @@ def test_withdrawal_requests( ), pytest.param( [ - WithdrawalRequestTransaction( + SystemContractInteractionTransaction( requests=[ WithdrawalRequest( validator_pubkey=0x01, amount=0, - fee=Spec.get_fee(0), ) ], ), @@ -772,17 +627,15 @@ def test_withdrawal_requests( ), pytest.param( [ - WithdrawalRequestTransaction( + SystemContractInteractionTransaction( requests=[ WithdrawalRequest( validator_pubkey=0x01, amount=0, - fee=Spec.get_fee(0), ), WithdrawalRequest( validator_pubkey=0x02, amount=0, - fee=Spec.get_fee(0), ), ], ), @@ -804,12 +657,11 @@ def test_withdrawal_requests( ), pytest.param( [ - WithdrawalRequestTransaction( + SystemContractInteractionTransaction( requests=[ WithdrawalRequest( validator_pubkey=0x01, amount=0, - fee=Spec.get_fee(0), ) ], ), @@ -836,7 +688,7 @@ def test_withdrawal_requests_negative( pre: Alloc, fork: Fork, blockchain_test: BlockchainTestFiller, - requests: List[WithdrawalRequestInteractionBase], + requests: List[SystemContractInteractionBase], block_body_override_requests: List[WithdrawalRequest], exception: BlockException, ) -> None: diff --git a/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests_during_fork.py b/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests_during_fork.py index 2f8118690ed..ca9c2099aea 100644 --- a/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests_during_fork.py +++ b/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests_during_fork.py @@ -14,10 +14,11 @@ Block, BlockchainTestFiller, Environment, + SystemContractInteractionTransaction, Transaction, ) -from .helpers import WithdrawalRequest, WithdrawalRequestTransaction +from .helpers import WithdrawalRequest from .spec import Spec, ref_spec_7002 REFERENCE_SPEC_GIT_PATH = ref_spec_7002.git_path @@ -29,18 +30,18 @@ @pytest.mark.parametrize( - "blocks_withdrawal_requests", + "system_contract_interactions_per_block", [ pytest.param( [ [], # No withdrawal requests, but we deploy the contract [ - WithdrawalRequestTransaction( + SystemContractInteractionTransaction( requests=[ WithdrawalRequest( validator_pubkey=0x01, amount=0, - fee=Spec.get_fee(10), + fee=WithdrawalRequest.get_fee(10), # Pre-fork withdrawal request valid=False, ) @@ -48,12 +49,12 @@ ), ], [ - WithdrawalRequestTransaction( + SystemContractInteractionTransaction( requests=[ WithdrawalRequest( validator_pubkey=0x02, amount=0, - fee=Spec.get_fee(10), + fee=WithdrawalRequest.get_fee(10), # First post-fork withdrawal request, will not # be included because the inhibitor is cleared # at the end of the block @@ -63,12 +64,11 @@ ), ], [ - WithdrawalRequestTransaction( + SystemContractInteractionTransaction( requests=[ WithdrawalRequest( validator_pubkey=0x03, amount=0, - fee=Spec.get_fee(0), # First withdrawal that is valid valid=True, ) diff --git a/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests_out_of_gas.py b/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests_out_of_gas.py new file mode 100644 index 00000000000..2f0cef4a07c --- /dev/null +++ b/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests_out_of_gas.py @@ -0,0 +1,165 @@ +""" +Out-of-gas withdrawal request tests. + +Tests that withdrawal requests whose triggering call runs out of gas are not +included in the block, for +[EIP-7002: Execution layer triggerable withdrawals](https://eips.ethereum.org/EIPS/eip-7002). + +The gas limits are supplied per-request via the interaction's `gas_limits` +list rather than being baked into the withdrawal request descriptor, keeping +the gas concern isolated to these dedicated tests. +""" + +from typing import List + +import pytest +from execution_testing import ( + Alloc, + Block, + BlockchainTestFiller, + Environment, + SystemContractInteractionContract, + SystemContractInteractionTransaction, +) + +from .helpers import WithdrawalRequest +from .spec import Spec, ref_spec_7002 + +REFERENCE_SPEC_GIT_PATH = ref_spec_7002.git_path +REFERENCE_SPEC_VERSION = ref_spec_7002.version + +pytestmark = pytest.mark.valid_from("Prague") + + +@pytest.mark.parametrize( + "system_contract_interactions_per_block", + [ + pytest.param( + [ + [ + SystemContractInteractionTransaction( + requests=[ + WithdrawalRequest( + validator_pubkey=0x01, + amount=0, + valid=False, + ), + WithdrawalRequest( + validator_pubkey=0x02, + amount=0, + ), + ], + # Value obtained from trace minus one + gas_limits=[114_247 - 1, None], + ), + ], + ], + id="single_block_multiple_withdrawal_request_first_oog", + ), + pytest.param( + [ + [ + SystemContractInteractionTransaction( + requests=[ + WithdrawalRequest( + validator_pubkey=0x01, + amount=0, + ), + WithdrawalRequest( + validator_pubkey=0x02, + amount=0, + valid=False, + ), + ], + # Value obtained from trace minus one + gas_limits=[None, 80_047 - 1], + ), + ], + ], + id="single_block_multiple_withdrawal_request_last_oog", + ), + pytest.param( + [ + [ + SystemContractInteractionContract( + requests=[ + WithdrawalRequest( + validator_pubkey=1, + amount=Spec.MAX_AMOUNT - 1, + valid=False, + ) + ] + + [ + WithdrawalRequest( + validator_pubkey=i + 1, + amount=Spec.MAX_AMOUNT - 1 + if i % 2 == 0 + else 0, + valid=True, + ) + for i in range( + 1, Spec.MAX_WITHDRAWAL_REQUESTS_PER_BLOCK + ) + ], + # Starve the first inner call of gas + gas_limits=[100] + + [None] + * (Spec.MAX_WITHDRAWAL_REQUESTS_PER_BLOCK - 1), + ), + ], + ], + id="single_block_multiple_withdrawal_requests_from_contract_first_oog", + ), + pytest.param( + [ + [ + SystemContractInteractionContract( + requests=[ + WithdrawalRequest( + validator_pubkey=i + 1, + amount=Spec.MAX_AMOUNT - 1 + if i % 2 == 0 + else 0, + valid=True, + ) + for i in range( + Spec.MAX_WITHDRAWAL_REQUESTS_PER_BLOCK + ) + ] + + [ + WithdrawalRequest( + validator_pubkey=Spec.MAX_WITHDRAWAL_REQUESTS_PER_BLOCK, + amount=Spec.MAX_AMOUNT - 1, + valid=False, + ) + ], + # Starve the last inner call of gas + gas_limits=[None] + * Spec.MAX_WITHDRAWAL_REQUESTS_PER_BLOCK + + [100], + ), + ], + ], + id="single_block_multiple_withdrawal_requests_from_contract_last_oog", + ), + ], +) +def test_withdrawal_requests_out_of_gas( + blockchain_test: BlockchainTestFiller, + blocks: List[Block], + pre: Alloc, +) -> None: + """ + Test that a withdrawal request whose triggering call runs out of gas is + not included, while the other requests in the block are. + + The gas limits are supplied per-request via the interaction's `gas_limits` + list rather than being baked into the withdrawal request descriptor, + keeping the gas concern isolated to these dedicated tests. + """ + blockchain_test( + genesis_environment=Environment(), + pre=pre, + post={}, + blocks=blocks, + ) diff --git a/tests/prague/eip7251_consolidations/conftest.py b/tests/prague/eip7251_consolidations/conftest.py index 27604e746a2..0c390b3a418 100644 --- a/tests/prague/eip7251_consolidations/conftest.py +++ b/tests/prague/eip7251_consolidations/conftest.py @@ -1,137 +1,8 @@ """Fixtures for the EIP-7251 consolidations tests.""" -from itertools import zip_longest -from typing import List - -import pytest -from execution_testing import ( - Alloc, - Block, - Fork, - Header, - Requests, - TransitionFork, +from ...common.system_contract_request_fixtures import ( + blocks, # noqa: F401 + included_requests, # noqa: F401 + prepared_system_contract_interactions_per_block, # noqa: F401 + timestamp, # noqa: F401 ) - -from .helpers import ConsolidationRequest, ConsolidationRequestInteractionBase -from .spec import Spec - - -@pytest.fixture -def prepared_blocks_consolidation_requests( - pre: Alloc, - blocks_consolidation_requests: List[ - List[ConsolidationRequestInteractionBase] - ], -) -> List[List[ConsolidationRequestInteractionBase]]: - """ - Allocate accounts/contracts for each interaction in `pre` and return - copies with the allocated state populated. The parametrize value - `blocks_consolidation_requests` is not mutated, so it stays pristine - across fixture format runs. - """ - return [ - [r.update_pre(pre) for r in block_requests] - for block_requests in blocks_consolidation_requests - ] - - -@pytest.fixture -def included_requests( - prepared_blocks_consolidation_requests: List[ - List[ConsolidationRequestInteractionBase] - ], -) -> List[List[ConsolidationRequest]]: - """ - Return the list of consolidation requests that should be included in each - block. - """ - excess_consolidation_requests = 0 - carry_over_requests: List[ConsolidationRequest] = [] - per_block_included_requests: List[List[ConsolidationRequest]] = [] - for block_consolidation_requests in prepared_blocks_consolidation_requests: - # Get fee for the current block - current_minimum_fee = Spec.get_fee(excess_consolidation_requests) - - # With the fee, get the valid consolidation requests for the current - # block - current_block_requests = [] - for w in block_consolidation_requests: - current_block_requests += w.valid_requests(current_minimum_fee) - - # Get the consolidation requests that should be included in the block - pending_requests = carry_over_requests + current_block_requests - per_block_included_requests.append( - pending_requests[: Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK] - ) - carry_over_requests = pending_requests[ - Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK : - ] - - # Update the excess consolidation requests - excess_consolidation_requests = Spec.get_excess_consolidation_requests( - excess_consolidation_requests, - len(current_block_requests), - ) - - while carry_over_requests: - # Keep adding blocks until all consolidation requests are included - per_block_included_requests.append( - carry_over_requests[: Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK] - ) - carry_over_requests = carry_over_requests[ - Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK : - ] - - return per_block_included_requests - - -@pytest.fixture -def timestamp() -> int: - """Return the timestamp for the first block.""" - return 1 - - -@pytest.fixture -def blocks( - fork: Fork | TransitionFork, - prepared_blocks_consolidation_requests: List[ - List[ConsolidationRequestInteractionBase] - ], - included_requests: List[List[ConsolidationRequest]], - timestamp: int, -) -> List[Block]: - """Return the list of blocks that should be included in the test.""" - blocks: List[Block] = [] - - for block_requests, block_included_requests in zip_longest( # type: ignore - prepared_blocks_consolidation_requests, - included_requests, - fillvalue=[], - ): - active_fork = fork.fork_at( - block_number=len(blocks) + 1, timestamp=timestamp - ) - header_verify: Header | None = None - if active_fork.header_requests_required(): - header_verify = Header( - requests_hash=Requests(*block_included_requests) - ) - else: - assert not block_included_requests - blocks.append( - Block( - txs=sum((r.transactions() for r in block_requests), []), - header_verify=header_verify, - timestamp=timestamp, - ) - ) - timestamp += 1 - - return blocks + [ - Block( - header_verify=Header(requests_hash=Requests()), - timestamp=timestamp, - ) - ] # Add an empty block at the end to verify that no more consolidation - # requests are included diff --git a/tests/prague/eip7251_consolidations/helpers.py b/tests/prague/eip7251_consolidations/helpers.py index 6de1b365abb..cdfcfe828cd 100644 --- a/tests/prague/eip7251_consolidations/helpers.py +++ b/tests/prague/eip7251_consolidations/helpers.py @@ -1,18 +1,8 @@ """Helpers for the EIP-7251 consolidation tests.""" -from dataclasses import dataclass, field, replace -from functools import cached_property -from itertools import count -from typing import Callable, ClassVar, List, Self +from typing import ClassVar, Self -from execution_testing import ( - EOA, - Address, - Alloc, - Bytecode, - Op, - Transaction, -) +from execution_testing import Address, FeeSystemContractRequest from execution_testing import ( ConsolidationRequest as ConsolidationRequestBase, ) @@ -20,31 +10,22 @@ from .spec import Spec -class ConsolidationRequest(ConsolidationRequestBase): +class ConsolidationRequest(ConsolidationRequestBase, FeeSystemContractRequest): """Class used to describe a consolidation request in a test.""" - fee: int = 0 - """Fee to be paid to the system contract for the consolidation request.""" - valid: bool = True - """Whether the consolidation request is valid or not.""" - gas_limit: int | None = None - """Gas limit for the call.""" - calldata_modifier: Callable[[bytes], bytes] = lambda x: x - """Calldata modifier function.""" - interaction_contract_address: ClassVar[Address] = Address( Spec.CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS ) + min_fee: ClassVar[int] = Spec.MIN_CONSOLIDATION_REQUEST_FEE + update_fraction: ClassVar[int] = ( + Spec.CONSOLIDATION_REQUEST_FEE_UPDATE_FRACTION + ) + target_per_block: ClassVar[int] = ( + Spec.TARGET_CONSOLIDATION_REQUESTS_PER_BLOCK + ) + max_per_block: ClassVar[int] = Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK @property - def value(self) -> int: - """ - Return the value of the call to the consolidation request contract, - equal to the fee to be paid. - """ - return self.fee - - @cached_property def calldata(self) -> bytes: """ Return the calldata needed to call the consolidation request contract @@ -61,253 +42,13 @@ def with_source_address( """ return self.copy(source_address=source_address) - -@dataclass(kw_only=True, frozen=True) -class ConsolidationRequestInteractionBase: - """ - Base class for all types of consolidation transactions we want to test. - """ - - sender_account: EOA | None = None - """Account that will send the transaction.""" - requests: List[ConsolidationRequest] - """Consolidation requests to be included in the block.""" - - def transactions(self) -> List[Transaction]: - """Return a transaction for the consolidation request.""" - raise NotImplementedError - - def update_pre(self, pre: Alloc) -> Self: - """ - Allocate accounts/contracts in `pre` and return a new instance with - the allocated state populated. Does not mutate `self`, so the - parametrize value remains pristine across fixture format runs. - """ - raise NotImplementedError - - def valid_requests( - self, current_minimum_fee: int - ) -> List[ConsolidationRequest]: - """ - Return the list of consolidation requests that should be valid in the - block. - """ - raise NotImplementedError - - -@dataclass(kw_only=True, frozen=True) -class ConsolidationRequestTransaction(ConsolidationRequestInteractionBase): - """ - Class to describe a consolidation request originated from an externally - owned account. - """ - - def transactions(self) -> List[Transaction]: - """Return a transaction for the consolidation request.""" - assert self.sender_account is not None, ( - "Sender account not initialized" - ) - txs: List[Transaction] = [] - for request in self.requests: - gas_limit = request.gas_limit - if gas_limit is not None: - tx = Transaction( - gas_limit=gas_limit, - to=request.interaction_contract_address, - value=request.value, - data=request.calldata, - sender=self.sender_account, - ) - else: - tx = Transaction( - to=request.interaction_contract_address, - value=request.value, - data=request.calldata, - sender=self.sender_account, - ) - txs.append(tx) - return txs - - def update_pre(self, pre: Alloc) -> Self: - """Return a copy of self with `sender_account` populated.""" - return replace(self, sender_account=pre.fund_eoa()) - - def valid_requests( - self, current_minimum_fee: int - ) -> List[ConsolidationRequest]: - """Return the list of consolidation requests that are valid.""" - assert self.sender_account is not None, ( - "Sender account not initialized" + @classmethod + def from_index(cls, index: int, fee: int | None = None) -> Self: + """Build a consolidation request from a sequential index.""" + if fee is None: + fee = cls.get_fee(0) + return cls( + source_pubkey=index * 2, + target_pubkey=index * 2 + 1, + fee=fee, ) - return [ - request.with_source_address(self.sender_account) - for request in self.requests - if request.valid and request.fee >= current_minimum_fee - ] - - -@dataclass(kw_only=True, frozen=True) -class ConsolidationRequestContract(ConsolidationRequestInteractionBase): - """Class used to describe a consolidation originated from a contract.""" - - contract_balance: int = 1_000_000_000_000_000_000 - """ - Balance of the contract that will make the call to the pre-deploy contract. - """ - contract_address: Address | None = None - """ - Address of the contract that will make the call to the pre-deploy contract. - """ - entry_address: Address | None = None - """Address to send the transaction to.""" - - call_type: Op = field(default_factory=lambda: Op.CALL) - """Type of call to be used to make the consolidation request.""" - call_depth: int = 2 - """Frame depth of the pre-deploy contract when it executes the call.""" - extra_code: Bytecode = field(default_factory=Bytecode) - """Extra code to be added to the contract code.""" - - @property - def contract_code(self) -> Bytecode: - """Contract code used by the relay contract.""" - code = Bytecode() - current_offset = 0 - for r in self.requests: - value_arg = ( - [r.value] if self.call_type in (Op.CALL, Op.CALLCODE) else [] - ) - code += Op.CALLDATACOPY( - 0, current_offset, len(r.calldata) - ) + Op.POP( - self.call_type( - Op.GAS if r.gas_limit is None else r.gas_limit, - r.interaction_contract_address, - *value_arg, - 0, - len(r.calldata), - 0, - 0, - ) - ) - current_offset += len(r.calldata) - return code + self.extra_code - - def transactions(self) -> List[Transaction]: - """Return a transaction for the consolidation request.""" - assert self.entry_address is not None, "Entry address not initialized" - return [ - Transaction( - to=self.entry_address, - value=0, - data=b"".join(r.calldata for r in self.requests), - sender=self.sender_account, - ) - ] - - def update_pre(self, pre: Alloc) -> Self: - """ - Return a copy of self with the allocated sender/contract/entry - addresses populated. - """ - sender_account = pre.fund_eoa() - contract_address = pre.deploy_contract( - code=self.contract_code, balance=self.contract_balance - ) - entry_address = contract_address - if self.call_depth > 2: - for _ in range(1, self.call_depth - 1): - entry_address = pre.deploy_contract( - code=Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) - + Op.POP( - Op.CALL( - Op.GAS, - entry_address, - 0, - 0, - Op.CALLDATASIZE, - 0, - 0, - ) - ) - ) - return replace( - self, - sender_account=sender_account, - contract_address=contract_address, - entry_address=entry_address, - ) - - def valid_requests( - self, current_minimum_fee: int - ) -> List[ConsolidationRequest]: - """Return the list of consolidation requests that are valid.""" - assert self.contract_address is not None, ( - "Contract address not initialized" - ) - return [ - r.with_source_address(self.contract_address) - for r in self.requests - if r.valid and r.value >= current_minimum_fee - ] - - -def get_n_fee_increments(n: int) -> List[int]: - """Get the first N excess consolidation requests that increase the fee.""" - excess_consolidation_requests_counts = [] - last_fee = 1 - for i in count(0): - if Spec.get_fee(i) > last_fee: - excess_consolidation_requests_counts.append(i) - last_fee = Spec.get_fee(i) - if len(excess_consolidation_requests_counts) == n: - break - return excess_consolidation_requests_counts - - -def get_n_fee_increment_blocks( - n: int, -) -> List[List[ConsolidationRequestContract]]: - """ - Return N blocks that should be included in the test such that each - subsequent block has an increasing fee for the consolidation requests. - - This is done by calculating the number of consolidations required to reach - the next fee increment and creating a block with that number of - consolidation requests plus the number of consolidations required to reach - the target. - """ - blocks = [] - previous_excess = 0 - consolidation_index = 0 - previous_fee = 0 - for required_excess_consolidations in get_n_fee_increments(n): - consolidations_required = ( - required_excess_consolidations - + Spec.TARGET_CONSOLIDATION_REQUESTS_PER_BLOCK - - previous_excess - ) - fee = Spec.get_fee(previous_excess) - assert fee > previous_fee - blocks.append( - [ - ConsolidationRequestContract( - requests=[ - ConsolidationRequest( - source_pubkey=i * 2, - target_pubkey=i * 2 + 1, - fee=fee, - ) - for i in range( - consolidation_index, - consolidation_index + consolidations_required, - ) - ], - ) - ], - ) - previous_fee = fee - consolidation_index += consolidations_required - previous_excess = required_excess_consolidations - - return blocks diff --git a/tests/prague/eip7251_consolidations/test_consolidations.py b/tests/prague/eip7251_consolidations/test_consolidations.py index a9d7af6b380..6964b9b246d 100644 --- a/tests/prague/eip7251_consolidations/test_consolidations.py +++ b/tests/prague/eip7251_consolidations/test_consolidations.py @@ -17,17 +17,14 @@ Macros, Op, Requests, + SystemContractInteractionBase, + SystemContractInteractionContract, + SystemContractInteractionTransaction, TestAddress, TestAddress2, ) -from .helpers import ( - ConsolidationRequest, - ConsolidationRequestContract, - ConsolidationRequestInteractionBase, - ConsolidationRequestTransaction, - get_n_fee_increment_blocks, -) +from .helpers import ConsolidationRequest from .spec import Spec, ref_spec_7251 REFERENCE_SPEC_GIT_PATH = ref_spec_7251.git_path @@ -37,17 +34,16 @@ @pytest.mark.parametrize( - "blocks_consolidation_requests", + "system_contract_interactions_per_block", [ pytest.param( [ [ - ConsolidationRequestTransaction( + SystemContractInteractionTransaction( requests=[ ConsolidationRequest( source_pubkey=0x01, target_pubkey=0x02, - fee=Spec.get_fee(0), ) ], ), @@ -58,12 +54,11 @@ pytest.param( [ [ - ConsolidationRequestTransaction( + SystemContractInteractionTransaction( requests=[ ConsolidationRequest( source_pubkey=0x01, target_pubkey=0x01, - fee=Spec.get_fee(0), ) ], ), @@ -74,12 +69,11 @@ pytest.param( [ [ - ConsolidationRequestTransaction( + SystemContractInteractionTransaction( requests=[ ConsolidationRequest( source_pubkey=-1, target_pubkey=-2, - fee=Spec.get_fee(0), ) ], ), @@ -90,7 +84,7 @@ pytest.param( [ [ - ConsolidationRequestTransaction( + SystemContractInteractionTransaction( requests=[ ConsolidationRequest( source_pubkey=0x01, @@ -107,12 +101,11 @@ pytest.param( [ [ - ConsolidationRequestTransaction( + SystemContractInteractionTransaction( requests=[ ConsolidationRequest( source_pubkey=0x01, target_pubkey=0x02, - fee=Spec.get_fee(0), calldata_modifier=lambda x: x[:-1], valid=False, ) @@ -125,12 +118,11 @@ pytest.param( [ [ - ConsolidationRequestTransaction( + SystemContractInteractionTransaction( requests=[ ConsolidationRequest( source_pubkey=0x01, target_pubkey=0x02, - fee=Spec.get_fee(0), calldata_modifier=lambda x: x + b"\x00", valid=False, ) @@ -143,17 +135,15 @@ pytest.param( [ [ - ConsolidationRequestTransaction( + SystemContractInteractionTransaction( requests=[ ConsolidationRequest( source_pubkey=0x01, target_pubkey=0x02, - fee=Spec.get_fee(0), ), ConsolidationRequest( source_pubkey=0x03, target_pubkey=0x04, - fee=Spec.get_fee(0), ), ], ), @@ -164,21 +154,19 @@ pytest.param( [ [ - ConsolidationRequestTransaction( + SystemContractInteractionTransaction( requests=[ ConsolidationRequest( source_pubkey=0x01, target_pubkey=0x02, - fee=Spec.get_fee(0), ) ], ), - ConsolidationRequestTransaction( + SystemContractInteractionTransaction( requests=[ ConsolidationRequest( source_pubkey=0x03, target_pubkey=0x04, - fee=Spec.get_fee(0), ) ], ), @@ -189,12 +177,11 @@ pytest.param( [ [ - ConsolidationRequestTransaction( + SystemContractInteractionTransaction( requests=[ ConsolidationRequest( source_pubkey=i * 2, target_pubkey=i * 2 + 1, - fee=Spec.get_fee(0), ) for i in range( Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK @@ -208,7 +195,7 @@ pytest.param( [ [ - ConsolidationRequestTransaction( + SystemContractInteractionTransaction( requests=[ ConsolidationRequest( source_pubkey=0x01, @@ -218,7 +205,6 @@ ConsolidationRequest( source_pubkey=0x03, target_pubkey=0x04, - fee=Spec.get_fee(0), ), ] ), @@ -229,12 +215,11 @@ pytest.param( [ [ - ConsolidationRequestTransaction( + SystemContractInteractionTransaction( requests=[ ConsolidationRequest( source_pubkey=0x01, target_pubkey=0x02, - fee=Spec.get_fee(0), ), ConsolidationRequest( source_pubkey=0x03, @@ -250,58 +235,11 @@ pytest.param( [ [ - ConsolidationRequestTransaction( - requests=[ - ConsolidationRequest( - source_pubkey=0x01, - target_pubkey=0x02, - fee=Spec.get_fee(0), - gas_limit=136_534 - 1, - valid=False, - ), - ConsolidationRequest( - source_pubkey=0x03, - target_pubkey=0x04, - fee=Spec.get_fee(0), - ), - ] - ), - ], - ], - id="single_block_multiple_consolidation_request_first_oog", - ), - pytest.param( - [ - [ - ConsolidationRequestTransaction( - requests=[ - ConsolidationRequest( - source_pubkey=0x01, - target_pubkey=0x02, - fee=Spec.get_fee(0), - ), - ConsolidationRequest( - source_pubkey=0x03, - target_pubkey=0x04, - fee=Spec.get_fee(0), - gas_limit=102_334 - 1, - valid=False, - ), - ] - ), - ], - ], - id="single_block_multiple_consolidation_request_last_oog", - ), - pytest.param( - [ - [ - ConsolidationRequestTransaction( + SystemContractInteractionTransaction( requests=[ ConsolidationRequest( source_pubkey=i * 2, target_pubkey=i * 2 + 1, - fee=Spec.get_fee(0), ) for i in range( Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK * 5 @@ -315,12 +253,11 @@ pytest.param( [ [ - ConsolidationRequestContract( + SystemContractInteractionContract( requests=[ ConsolidationRequest( source_pubkey=0x01, target_pubkey=0x02, - fee=Spec.get_fee(0), ), ] ), @@ -331,12 +268,11 @@ pytest.param( [ [ - ConsolidationRequestContract( + SystemContractInteractionContract( requests=[ ConsolidationRequest( source_pubkey=i * 2, target_pubkey=i * 2 + 1, - fee=Spec.get_fee(0), ) for i in range( Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK * 5 @@ -350,12 +286,11 @@ pytest.param( [ [ - ConsolidationRequestContract( + SystemContractInteractionContract( requests=[ ConsolidationRequest( source_pubkey=i * 2, target_pubkey=i * 2 + 1, - fee=Spec.get_fee(0), ) for i in range( Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK * 5 @@ -370,12 +305,11 @@ pytest.param( [ [ - ConsolidationRequestContract( + SystemContractInteractionContract( requests=[ ConsolidationRequest( source_pubkey=i * 2, target_pubkey=i * 2 + 1, - fee=Spec.get_fee(0), ) for i in range( Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK * 5 @@ -390,7 +324,7 @@ pytest.param( [ [ - ConsolidationRequestContract( + SystemContractInteractionContract( requests=[ ConsolidationRequest( source_pubkey=0x00, @@ -402,7 +336,6 @@ ConsolidationRequest( source_pubkey=i * 2, target_pubkey=i * 2 + 1, - fee=Spec.get_fee(0), ) for i in range( 1, @@ -417,12 +350,11 @@ pytest.param( [ [ - ConsolidationRequestContract( + SystemContractInteractionContract( requests=[ ConsolidationRequest( source_pubkey=i * 2, target_pubkey=i * 2 + 1, - fee=Spec.get_fee(0), ) for i in range( Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK * 5 @@ -443,71 +375,11 @@ pytest.param( [ [ - ConsolidationRequestContract( - requests=[ - ConsolidationRequest( - source_pubkey=-1, - target_pubkey=-2, - gas_limit=100, - fee=Spec.get_fee(0), - valid=False, - ) - ] - + [ - ConsolidationRequest( - source_pubkey=i * 2, - target_pubkey=i * 2 + 1, - fee=Spec.get_fee(0), - valid=True, - ) - for i in range( - 1, - Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK * 5, - ) - ], - ), - ], - ], - id="single_block_multiple_consolidation_requests_from_contract_first_oog", - ), - pytest.param( - [ - [ - ConsolidationRequestContract( - requests=[ - ConsolidationRequest( - source_pubkey=i * 2, - target_pubkey=i * 2 + 1, - fee=Spec.get_fee(0), - valid=True, - ) - for i in range( - Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK * 5 - ) - ] - + [ - ConsolidationRequest( - source_pubkey=-1, - target_pubkey=-2, - gas_limit=100, - fee=Spec.get_fee(0), - valid=False, - ) - ], - ), - ], - ], - id="single_block_multiple_consolidation_requests_from_contract_last_oog", - ), - pytest.param( - [ - [ - ConsolidationRequestContract( + SystemContractInteractionContract( requests=[ ConsolidationRequest( source_pubkey=i * 2, target_pubkey=i * 2 + 1, - fee=Spec.get_fee(0), valid=False, ) for i in range( @@ -523,12 +395,11 @@ pytest.param( [ [ - ConsolidationRequestContract( + SystemContractInteractionContract( requests=[ ConsolidationRequest( source_pubkey=i * 2, target_pubkey=i * 2 + 1, - fee=Spec.get_fee(0), valid=False, ) for i in range( @@ -543,40 +414,37 @@ ), pytest.param( # Test the first 50 fee increments - get_n_fee_increment_blocks(50), + ConsolidationRequest.get_n_fee_increment_blocks(50), id="multiple_block_fee_increments", ), pytest.param( [ [ - ConsolidationRequestContract( + SystemContractInteractionContract( requests=[ ConsolidationRequest( source_pubkey=0x01, target_pubkey=0x02, - fee=Spec.get_fee(0), valid=False, ) ], call_type=Op.DELEGATECALL, ), - ConsolidationRequestContract( + SystemContractInteractionContract( requests=[ ConsolidationRequest( source_pubkey=0x01, target_pubkey=0x02, - fee=Spec.get_fee(0), valid=False, ) ], call_type=Op.STATICCALL, ), - ConsolidationRequestContract( + SystemContractInteractionContract( requests=[ ConsolidationRequest( source_pubkey=0x01, target_pubkey=0x02, - fee=Spec.get_fee(0), valid=False, ) ], @@ -589,36 +457,33 @@ pytest.param( [ [ - ConsolidationRequestContract( + SystemContractInteractionContract( requests=[ ConsolidationRequest( source_pubkey=0x01, target_pubkey=0x02, - fee=Spec.get_fee(0), valid=False, ) ], call_type=Op.DELEGATECALL, call_depth=3, ), - ConsolidationRequestContract( + SystemContractInteractionContract( requests=[ ConsolidationRequest( source_pubkey=0x01, target_pubkey=0x02, - fee=Spec.get_fee(0), valid=False, ) ], call_type=Op.STATICCALL, call_depth=3, ), - ConsolidationRequestContract( + SystemContractInteractionContract( requests=[ ConsolidationRequest( source_pubkey=0x01, target_pubkey=0x02, - fee=Spec.get_fee(0), valid=False, ) ], @@ -632,36 +497,33 @@ pytest.param( [ [ - ConsolidationRequestContract( + SystemContractInteractionContract( requests=[ ConsolidationRequest( source_pubkey=0x01, target_pubkey=0x02, - fee=Spec.get_fee(0), valid=False, ) ], call_type=Op.DELEGATECALL, call_depth=1024, ), - ConsolidationRequestContract( + SystemContractInteractionContract( requests=[ ConsolidationRequest( source_pubkey=0x01, target_pubkey=0x02, - fee=Spec.get_fee(0), valid=False, ) ], call_type=Op.STATICCALL, call_depth=1024, ), - ConsolidationRequestContract( + SystemContractInteractionContract( requests=[ ConsolidationRequest( source_pubkey=0x01, target_pubkey=0x02, - fee=Spec.get_fee(0), valid=False, ) ], @@ -705,12 +567,11 @@ def test_consolidation_requests( ), pytest.param( [ - ConsolidationRequestTransaction( + SystemContractInteractionTransaction( requests=[ ConsolidationRequest( source_pubkey=0x01, target_pubkey=0x02, - fee=Spec.get_fee(0), ), ] ), @@ -721,12 +582,11 @@ def test_consolidation_requests( ), pytest.param( [ - ConsolidationRequestTransaction( + SystemContractInteractionTransaction( requests=[ ConsolidationRequest( source_pubkey=0x01, target_pubkey=0x02, - fee=Spec.get_fee(0), ), ] ), @@ -743,12 +603,11 @@ def test_consolidation_requests( ), pytest.param( [ - ConsolidationRequestTransaction( + SystemContractInteractionTransaction( requests=[ ConsolidationRequest( source_pubkey=0x01, target_pubkey=0x02, - fee=Spec.get_fee(0), ), ] ), @@ -765,12 +624,11 @@ def test_consolidation_requests( ), pytest.param( [ - ConsolidationRequestTransaction( + SystemContractInteractionTransaction( requests=[ ConsolidationRequest( source_pubkey=0x01, target_pubkey=0x02, - fee=Spec.get_fee(0), ), ] ), @@ -787,12 +645,11 @@ def test_consolidation_requests( ), pytest.param( [ - ConsolidationRequestTransaction( + SystemContractInteractionTransaction( requests=[ ConsolidationRequest( source_pubkey=0x01, target_pubkey=0x02, - fee=Spec.get_fee(0), ) ], ), @@ -809,17 +666,15 @@ def test_consolidation_requests( ), pytest.param( [ - ConsolidationRequestTransaction( + SystemContractInteractionTransaction( requests=[ ConsolidationRequest( source_pubkey=0x01, target_pubkey=0x02, - fee=Spec.get_fee(0), ), ConsolidationRequest( source_pubkey=0x03, target_pubkey=0x04, - fee=Spec.get_fee(0), ), ], ), @@ -841,12 +696,11 @@ def test_consolidation_requests( ), pytest.param( [ - ConsolidationRequestTransaction( + SystemContractInteractionTransaction( requests=[ ConsolidationRequest( source_pubkey=0x01, target_pubkey=0x02, - fee=Spec.get_fee(0), ) ], ), @@ -873,7 +727,7 @@ def test_consolidation_requests_negative( pre: Alloc, fork: Fork, blockchain_test: BlockchainTestFiller, - requests: List[ConsolidationRequestInteractionBase], + requests: List[SystemContractInteractionBase], block_body_override_requests: List[ConsolidationRequest], exception: BlockException, ) -> None: diff --git a/tests/prague/eip7251_consolidations/test_consolidations_during_fork.py b/tests/prague/eip7251_consolidations/test_consolidations_during_fork.py index 3a883580737..3eeb467ae6f 100644 --- a/tests/prague/eip7251_consolidations/test_consolidations_during_fork.py +++ b/tests/prague/eip7251_consolidations/test_consolidations_during_fork.py @@ -14,10 +14,11 @@ Block, BlockchainTestFiller, Environment, + SystemContractInteractionTransaction, Transaction, ) -from .helpers import ConsolidationRequest, ConsolidationRequestTransaction +from .helpers import ConsolidationRequest from .spec import Spec, ref_spec_7251 REFERENCE_SPEC_GIT_PATH = ref_spec_7251.git_path @@ -29,18 +30,18 @@ @pytest.mark.parametrize( - "blocks_consolidation_requests", + "system_contract_interactions_per_block", [ pytest.param( [ [], # No consolidation requests, but we deploy the contract [ - ConsolidationRequestTransaction( + SystemContractInteractionTransaction( requests=[ ConsolidationRequest( source_pubkey=0x01, target_pubkey=0x02, - fee=Spec.get_fee(10), + fee=ConsolidationRequest.get_fee(10), # Pre-fork consolidation request valid=False, ) @@ -48,12 +49,12 @@ ), ], [ - ConsolidationRequestTransaction( + SystemContractInteractionTransaction( requests=[ ConsolidationRequest( source_pubkey=0x03, target_pubkey=0x04, - fee=Spec.get_fee(10), + fee=ConsolidationRequest.get_fee(10), # First post-fork consolidation request, will # not be included because the inhibitor is # cleared at the end of the block @@ -63,12 +64,11 @@ ), ], [ - ConsolidationRequestTransaction( + SystemContractInteractionTransaction( requests=[ ConsolidationRequest( source_pubkey=0x05, target_pubkey=0x06, - fee=Spec.get_fee(0), # First consolidation that is valid valid=True, ) diff --git a/tests/prague/eip7251_consolidations/test_consolidations_out_of_gas.py b/tests/prague/eip7251_consolidations/test_consolidations_out_of_gas.py new file mode 100644 index 00000000000..e141cc7edff --- /dev/null +++ b/tests/prague/eip7251_consolidations/test_consolidations_out_of_gas.py @@ -0,0 +1,160 @@ +""" +Out-of-gas consolidation request tests. + +Tests that consolidation requests whose triggering call runs out of gas are +not included in the block, for +[EIP-7251: Increase the MAX_EFFECTIVE_BALANCE](https://eips.ethereum.org/EIPS/eip-7251). + +The gas limits are supplied per-request via the interaction's `gas_limits` +list rather than being baked into the consolidation request descriptor, +keeping the gas concern isolated to these dedicated tests. +""" + +from typing import List + +import pytest +from execution_testing import ( + Alloc, + Block, + BlockchainTestFiller, + Environment, + SystemContractInteractionContract, + SystemContractInteractionTransaction, +) + +from .helpers import ConsolidationRequest +from .spec import Spec, ref_spec_7251 + +REFERENCE_SPEC_GIT_PATH = ref_spec_7251.git_path +REFERENCE_SPEC_VERSION = ref_spec_7251.version + +pytestmark = pytest.mark.valid_from("Prague") + + +@pytest.mark.parametrize( + "system_contract_interactions_per_block", + [ + pytest.param( + [ + [ + SystemContractInteractionTransaction( + requests=[ + ConsolidationRequest( + source_pubkey=0x01, + target_pubkey=0x02, + valid=False, + ), + ConsolidationRequest( + source_pubkey=0x03, + target_pubkey=0x04, + ), + ], + gas_limits=[136_534 - 1, None], + ), + ], + ], + id="single_block_multiple_consolidation_request_first_oog", + ), + pytest.param( + [ + [ + SystemContractInteractionTransaction( + requests=[ + ConsolidationRequest( + source_pubkey=0x01, + target_pubkey=0x02, + ), + ConsolidationRequest( + source_pubkey=0x03, + target_pubkey=0x04, + valid=False, + ), + ], + gas_limits=[None, 102_334 - 1], + ), + ], + ], + id="single_block_multiple_consolidation_request_last_oog", + ), + pytest.param( + [ + [ + SystemContractInteractionContract( + requests=[ + ConsolidationRequest( + source_pubkey=-1, + target_pubkey=-2, + valid=False, + ) + ] + + [ + ConsolidationRequest( + source_pubkey=i * 2, + target_pubkey=i * 2 + 1, + valid=True, + ) + for i in range( + 1, + Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK * 5, + ) + ], + # Starve the first inner call of gas + gas_limits=[100] + + [None] + * (Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK * 5 - 1), + ), + ], + ], + id="single_block_multiple_consolidation_requests_from_contract_first_oog", + ), + pytest.param( + [ + [ + SystemContractInteractionContract( + requests=[ + ConsolidationRequest( + source_pubkey=i * 2, + target_pubkey=i * 2 + 1, + valid=True, + ) + for i in range( + Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK * 5 + ) + ] + + [ + ConsolidationRequest( + source_pubkey=-1, + target_pubkey=-2, + valid=False, + ) + ], + # Starve the last inner call of gas + gas_limits=[None] + * (Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK * 5) + + [100], + ), + ], + ], + id="single_block_multiple_consolidation_requests_from_contract_last_oog", + ), + ], +) +def test_consolidation_requests_out_of_gas( + blockchain_test: BlockchainTestFiller, + blocks: List[Block], + pre: Alloc, +) -> None: + """ + Test that a consolidation request whose triggering call runs out of gas is + not included, while the other requests in the block are. + + The gas limits are supplied per-request via the interaction's `gas_limits` + list rather than being baked into the consolidation request descriptor, + keeping the gas concern isolated to these dedicated tests. + """ + blockchain_test( + genesis_environment=Environment(), + pre=pre, + post={}, + blocks=blocks, + ) diff --git a/tests/prague/eip7251_consolidations/test_contract_deployment.py b/tests/prague/eip7251_consolidations/test_contract_deployment.py index 71268fe4eab..25e2d42a321 100644 --- a/tests/prague/eip7251_consolidations/test_contract_deployment.py +++ b/tests/prague/eip7251_consolidations/test_contract_deployment.py @@ -43,7 +43,6 @@ def test_system_contract_deployment( consolidation_request = ConsolidationRequest( source_pubkey=0x01, target_pubkey=0x02, - fee=Spec.get_fee(0), source_address=sender, ) intrinsic_gas_calculator = ( diff --git a/tests/prague/eip7251_consolidations/test_eip_mainnet.py b/tests/prague/eip7251_consolidations/test_eip_mainnet.py index 1f1ea7f43f6..30f78c022ee 100644 --- a/tests/prague/eip7251_consolidations/test_eip_mainnet.py +++ b/tests/prague/eip7251_consolidations/test_eip_mainnet.py @@ -9,10 +9,11 @@ Alloc, Block, BlockchainTestFiller, + SystemContractInteractionTransaction, ) -from .helpers import ConsolidationRequest, ConsolidationRequestTransaction -from .spec import Spec, ref_spec_7251 +from .helpers import ConsolidationRequest +from .spec import ref_spec_7251 REFERENCE_SPEC_GIT_PATH = ref_spec_7251.git_path REFERENCE_SPEC_VERSION = ref_spec_7251.version @@ -21,17 +22,16 @@ @pytest.mark.parametrize( - "blocks_consolidation_requests", + "system_contract_interactions_per_block", [ pytest.param( [ [ - ConsolidationRequestTransaction( + SystemContractInteractionTransaction( requests=[ ConsolidationRequest( source_pubkey=0x01, target_pubkey=0x02, - fee=Spec.get_fee(0), ) ], ), diff --git a/tests/prague/eip7251_consolidations/test_modified_consolidation_contract.py b/tests/prague/eip7251_consolidations/test_modified_consolidation_contract.py index 82453a74d51..81def366386 100644 --- a/tests/prague/eip7251_consolidations/test_modified_consolidation_contract.py +++ b/tests/prague/eip7251_consolidations/test_modified_consolidation_contract.py @@ -14,15 +14,13 @@ Bytecode, Op, Requests, + SystemContractInteractionTransaction, Transaction, generate_system_contract_error_test, ) from execution_testing import Macros as Om -from .helpers import ( - ConsolidationRequest, - ConsolidationRequestTransaction, -) +from .helpers import ConsolidationRequest from .spec import Spec as Spec_EIP7251 from .spec import ref_spec_7251 @@ -121,7 +119,7 @@ def test_extra_consolidations( # given a list of consolidation requests construct a consolidation request # transaction - consolidation_request_transaction = ConsolidationRequestTransaction( + consolidation_request_transaction = SystemContractInteractionTransaction( requests=requests_list ) # prepare consolidation senders diff --git a/tests/prague/eip7685_general_purpose_el_requests/conftest.py b/tests/prague/eip7685_general_purpose_el_requests/conftest.py index 79bef284f42..61152e8104b 100644 --- a/tests/prague/eip7685_general_purpose_el_requests/conftest.py +++ b/tests/prague/eip7685_general_purpose_el_requests/conftest.py @@ -11,17 +11,30 @@ EngineAPIError, Header, Requests, + SystemContractInteractionBase, + SystemContractRequest, ) -from ..eip6110_deposits.helpers import DepositInteractionBase, DepositRequest -from ..eip7002_el_triggerable_withdrawals.helpers import ( - WithdrawalRequest, - WithdrawalRequestInteractionBase, -) -from ..eip7251_consolidations.helpers import ( - ConsolidationRequest, - ConsolidationRequestInteractionBase, +from ...common.system_contract_request_fixtures import ( + blocks, # noqa: F401 + included_requests, # noqa: F401 + prepared_system_contract_interactions_per_block, # noqa: F401 + timestamp, # noqa: F401 ) +from ..eip6110_deposits.helpers import DepositRequest +from ..eip7002_el_triggerable_withdrawals.helpers import WithdrawalRequest +from ..eip7251_consolidations.helpers import ConsolidationRequest + + +@pytest.fixture +def system_contract_interactions_per_block( + requests: List[SystemContractInteractionBase], +) -> List[List[SystemContractInteractionBase]]: + """ + Adapt the flat `requests` parametrization (one block's interactions) to the + per-block shape consumed by the shared request fixtures (`blocks` etc.). + """ + return [requests] @pytest.fixture @@ -78,33 +91,24 @@ def is_monotonically_increasing(requests: List[bytes]) -> bool: @pytest.fixture -def blocks( +def override_blocks( pre: Alloc, - requests: List[ - DepositInteractionBase - | WithdrawalRequestInteractionBase - | ConsolidationRequestInteractionBase - ], + requests: List[SystemContractInteractionBase], block_body_override_requests: List[Bytes | SupportsBytes] | None, correct_requests_hash_in_header: bool, exception: BlockException | None, engine_api_error_code: EngineAPIError | None, ) -> List[Block]: - """List of blocks that comprise the test.""" - valid_requests_list: List[ - DepositRequest | WithdrawalRequest | ConsolidationRequest - ] = [] - # Single block therefore base fee - withdrawal_request_fee = 1 - consolidation_request_fee = 1 + """ + Single block whose request body / header can be overridden, used by the + negative tests to inject invalid requests and expect a block exception. + """ + valid_requests_list: List[SystemContractRequest] = [] + # Every request here is constructed with a sufficient value, so no fee + # filter is needed: each interaction returns all of its `valid` requests. prepared = [r.update_pre(pre) for r in requests] for r in prepared: - if isinstance(r, DepositInteractionBase): - valid_requests_list += r.valid_requests(10**18) - elif isinstance(r, WithdrawalRequestInteractionBase): - valid_requests_list += r.valid_requests(withdrawal_request_fee) - elif isinstance(r, ConsolidationRequestInteractionBase): - valid_requests_list += r.valid_requests(consolidation_request_fee) + valid_requests_list += r.valid_requests() valid_requests = Requests(*valid_requests_list) diff --git a/tests/prague/eip7685_general_purpose_el_requests/test_multi_type_requests.py b/tests/prague/eip7685_general_purpose_el_requests/test_multi_type_requests.py index b9d3bcf61e1..3761b818d80 100644 --- a/tests/prague/eip7685_general_purpose_el_requests/test_multi_type_requests.py +++ b/tests/prague/eip7685_general_purpose_el_requests/test_multi_type_requests.py @@ -10,44 +10,25 @@ import pytest from execution_testing import ( - EOA, - Account, - Address, Alloc, Block, BlockchainTestFiller, BlockException, - Bytecode, Bytes, Environment, + FeeSystemContractRequest, Fork, - Header, - Op, ParameterSet, Requests, - Storage, + SystemContractInteractionContract, + SystemContractInteractionTransaction, + SystemContractRequest, TestAddress, - Transaction, ) -from ..eip6110_deposits.helpers import ( - DepositContract, - DepositRequest, - DepositTransaction, -) -from ..eip6110_deposits.spec import Spec as Spec_EIP6110 -from ..eip7002_el_triggerable_withdrawals.helpers import ( - WithdrawalRequest, - WithdrawalRequestContract, - WithdrawalRequestTransaction, -) -from ..eip7002_el_triggerable_withdrawals.spec import Spec as Spec_EIP7002 -from ..eip7251_consolidations.helpers import ( - ConsolidationRequest, - ConsolidationRequestContract, - ConsolidationRequestTransaction, -) -from ..eip7251_consolidations.spec import Spec as Spec_EIP7251 +from ..eip6110_deposits.helpers import DepositRequest +from ..eip7002_el_triggerable_withdrawals.helpers import WithdrawalRequest +from ..eip7251_consolidations.helpers import ConsolidationRequest from .spec import ref_spec_7685 REFERENCE_SPEC_GIT_PATH: str = ref_spec_7685.git_path @@ -56,286 +37,109 @@ pytestmark: pytest.MarkDecorator = pytest.mark.valid_from("Prague") -def single_deposit(i: int) -> DepositRequest: # noqa: D103 - return DepositRequest( - pubkey=(i * 3), - withdrawal_credentials=(i * 3) + 1, - amount=32_000_000_000, - signature=(i * 3) + 2, - index=i, - ) - - -def single_deposit_from_eoa(i: int) -> DepositTransaction: # noqa: D103 - return DepositTransaction(requests=[single_deposit(i)]) +# All request types under test, in ascending request-type order. Adding a new +# request type here makes the permutations and parametrizations pick it up. +# Required for future forks to add new request types to this dictionary. +REQUEST_TYPES: List[type[SystemContractRequest]] = [ + DepositRequest, + WithdrawalRequest, + ConsolidationRequest, +] +REQUEST_TYPE_BY_ADDRESS = { + rt.interaction_contract_address: rt for rt in REQUEST_TYPES +} +# Number of requests used for request types that have no per-block cap (e.g. +# deposits), to exercise "many in a single block". +UNCAPPED_REQUEST_SAMPLE = 18 -def single_deposit_from_contract(i: int) -> DepositContract: # noqa: D103 - return DepositContract(requests=[single_deposit(i)]) +def request_type_to_id_str(ty: type[SystemContractRequest]) -> str: + """Return an id-friendly string from the system contract request.""" + return ty.__name__.removesuffix("Request").lower() -def single_withdrawal(i: int) -> WithdrawalRequest: # noqa: D103 - return WithdrawalRequest( - validator_pubkey=i + 1, - amount=0, - fee=1, +def request_types_from_fork(fork: Fork) -> List[type[SystemContractRequest]]: + """Return the types of system contract requests for a given fork.""" + assert len(REQUEST_TYPES) > fork.max_request_type(), ( + f"Request type {fork.max_request_type()} not in REQUEST_TYPES. " + "Test needs update" ) + return REQUEST_TYPES[: fork.max_request_type() + 1] -def single_withdrawal_from_eoa(i: int) -> WithdrawalRequestTransaction: # noqa: D103 - return WithdrawalRequestTransaction(requests=[single_withdrawal(i)]) - - -def single_withdrawal_from_contract(i: int) -> WithdrawalRequestContract: # noqa: D103 - return WithdrawalRequestContract(requests=[single_withdrawal(i)]) - - -def single_consolidation(i: int) -> ConsolidationRequest: # noqa: D103 - return ConsolidationRequest( - source_pubkey=(i * 2), - target_pubkey=(i * 2) + 1, - fee=1, +def eoa_interaction( + request_type: type[SystemContractRequest], i: int = 0 +) -> SystemContractInteractionTransaction: + """Build an EOA-originated interaction for a single request.""" + return SystemContractInteractionTransaction( + requests=[request_type.from_index(i)] ) -def single_consolidation_from_eoa(i: int) -> ConsolidationRequestTransaction: # noqa: D103 - return ConsolidationRequestTransaction(requests=[single_consolidation(i)]) - +def contract_interaction( + request_type: type[SystemContractRequest], i: int = 0 +) -> SystemContractInteractionContract: + """Build a relay-contract-originated interaction for a single request.""" + return SystemContractInteractionContract( + requests=[request_type.from_index(i)] + ) -def single_consolidation_from_contract(i: int) -> ConsolidationRequestContract: # noqa: D103 - return ConsolidationRequestContract(requests=[single_consolidation(i)]) +def get_fork_permutations(fork: Fork) -> Generator[ParameterSet, None, None]: + """Get request permutations for a given fork.""" + request_types = request_types_from_fork(fork) -def get_permutations(n: int = 3) -> Generator[ParameterSet, None, None]: - """Return possible permutations of the requests from an EOA.""" - requests: list = [ - ( - "deposit", - single_deposit(0), - ), - ( - "withdrawal", - single_withdrawal(0), - ), - ( - "consolidation", - single_consolidation(0), - ), - ] - for perm in permutations(requests, n): - yield pytest.param( - [p[1] for p in perm], id="+".join([p[0] for p in perm]) + # EOA permutations + for perm in permutations(request_types): + perm_id = "+".join( + [f"{request_type_to_id_str(rt)}_from_eoa" for rt in perm] ) + yield pytest.param([eoa_interaction(rt) for rt in perm], id=perm_id) - -def get_eoa_permutations(n: int = 3) -> Generator[ParameterSet, None, None]: - """Return possible permutations of the requests from an EOA.""" - requests: list = [ - ( - "deposit_from_eoa", - single_deposit_from_eoa(0), - ), - ( - "withdrawal_from_eoa", - single_withdrawal_from_eoa(0), - ), - ( - "consolidation_from_eoa", - single_consolidation_from_eoa(0), - ), - ] - for perm in permutations(requests, n): + # Contract permutations + for perm in permutations(request_types): + perm_id = "+".join( + [f"{request_type_to_id_str(rt)}_from_contract" for rt in perm] + ) yield pytest.param( - [p[1] for p in perm], id="+".join([p[0] for p in perm]) + [contract_interaction(rt) for rt in perm], id=perm_id ) - -def get_contract_permutations( - n: int = 3, -) -> Generator[ParameterSet, None, None]: - """Return possible permutations of the requests from a contract.""" - requests: list = [ - ( - "deposit_from_contract", - single_deposit_from_contract(0), - ), - ( - "withdrawal_from_contract", - single_withdrawal_from_contract(0), - ), - ( - "consolidation_from_contract", - single_consolidation_from_contract(0), - ), - ] - for perm in permutations(requests, n): + # Multiple request types from same transaction + for perm in permutations(request_types): yield pytest.param( - [p[1] for p in perm], id="+".join([p[0] for p in perm]) + [ + SystemContractInteractionContract( + requests=[rt.from_index(0) for rt in perm] + ) + ], + id="+".join(request_type_to_id_str(rt) for rt in perm) + + "_from_same_tx", + ) + # Empty requests + yield pytest.param([], id="empty_requests") + + # One more than the per-block cap of each request type, so the surplus of + # capped types carries over to a following block. Uncapped types (e.g. + # deposits) have no cap, so an arbitrary sample count is used and all are + # included in the same block. + over_cap_interactions: List[SystemContractInteractionContract] = [] + ids: List[str] = [] + for rt in request_types: + if issubclass(rt, FeeSystemContractRequest): + cap = rt.max_per_block + else: + cap = UNCAPPED_REQUEST_SAMPLE + over_cap_interactions.append( + SystemContractInteractionContract( + requests=[rt.from_index(i) for i in range(cap + 1)] + ) ) + ids.append(f"{request_type_to_id_str(rt)}_over_cap") + yield pytest.param(over_cap_interactions, id="+".join(ids)) -@pytest.mark.parametrize( - "requests", - [ - *get_eoa_permutations(), - *get_contract_permutations(), - pytest.param( - [ - single_deposit_from_eoa(0), - single_withdrawal_from_eoa(0), - single_deposit_from_contract(1), - ], - id="deposit_from_eoa+withdrawal_from_eoa+deposit_from_contract", - ), - pytest.param( - [ - single_deposit_from_eoa(0), - single_consolidation_from_eoa(0), - single_deposit_from_contract(1), - ], - id="deposit_from_eoa+consolidation_from_eoa+deposit_from_contract", - ), - pytest.param( - [ - single_consolidation_from_eoa(0), - single_deposit_from_eoa(0), - single_consolidation_from_contract(1), - ], - id="consolidation_from_eoa+deposit_from_eoa+consolidation_from_contract", - ), - pytest.param( - [ - single_consolidation_from_eoa(0), - single_withdrawal_from_eoa(0), - single_consolidation_from_contract(1), - ], - id="consolidation_from_eoa+withdrawal_from_eoa+consolidation_from_contract", - ), - pytest.param( - [ - single_withdrawal_from_eoa(0), - single_consolidation_from_eoa(0), - single_withdrawal_from_contract(1), - ], - id="withdrawal_from_eoa+consolidation_from_eoa+withdrawal_from_contract", - ), - pytest.param( - [ - single_withdrawal_from_eoa(0), - single_deposit_from_eoa(0), - single_withdrawal_from_contract(1), - ], - id="withdrawal_from_eoa+deposit_from_eoa+withdrawal_from_contract", - ), - pytest.param( - [], - id="empty_requests", - ), - # contract: consolidation + withdrawal - pytest.param( - [ - single_withdrawal_from_eoa(0), - single_consolidation_from_contract(0), - single_withdrawal_from_contract(1), - ], - id="withdrawal_from_eoa+consolidation_from_contract+withdrawal_from_contract", - ), - pytest.param( - [ - single_deposit_from_eoa(0), - single_consolidation_from_contract(0), - single_withdrawal_from_contract(0), - ], - id="deposit_from_eoa+consolidation_from_contract+withdrawal_from_contract", - ), - pytest.param( - [ - single_consolidation_from_eoa(0), - single_consolidation_from_contract(1), - single_withdrawal_from_contract(0), - ], - id="consolidation_from_eoa+consolidation_from_contract+withdrawal_from_contract", - ), - # contract: consolidation + deposit - pytest.param( - [ - single_withdrawal_from_eoa(0), - single_consolidation_from_contract(0), - single_deposit_from_contract(0), - ], - id="withdrawal_from_eoa+consolidation_from_contract+deposit_from_contract", - ), - pytest.param( - [ - single_deposit_from_eoa(0), - single_consolidation_from_contract(0), - single_deposit_from_contract(1), - ], - id="deposit_from_eoa+consolidation_from_contract+deposit_from_contract", - ), - pytest.param( - [ - single_consolidation_from_eoa(0), - single_consolidation_from_contract(1), - single_deposit_from_contract(0), - ], - id="consolidation_from_eoa+consolidation_from_contract+deposit_from_contract", - ), - # contract: withdrawal + deposit - pytest.param( - [ - single_withdrawal_from_eoa(0), - single_withdrawal_from_contract(1), - single_deposit_from_contract(0), - ], - id="withdrawal_from_eoa+withdrawal_from_contract+deposit_from_contract", - ), - pytest.param( - [ - single_deposit_from_eoa(0), - single_withdrawal_from_contract(0), - single_deposit_from_contract(1), - ], - id="deposit_from_eoa+withdrawal_from_contract+deposit_from_contract", - ), - pytest.param( - [ - single_consolidation_from_eoa(0), - single_withdrawal_from_contract(0), - single_deposit_from_contract(0), - ], - id="consolidation_from_eoa+withdrawal_from_contract+deposit_from_contract", - ), - # testing upper limits of each request type per slot if it exists - pytest.param( - [ - single_consolidation_from_contract(0), - single_consolidation_from_contract(1), - # the following performs single_withdrawal_from_contract(0) to - # (16) - *[ - single_withdrawal_from_contract(i) - for i in range( - 0, - 16, - ) - ], - # single_withdrawal_from_contract(16) not allowed cuz only 16 - # MAX WITHDRAWALS PER BLOCK (EIP-7002) - # the following performs single_deposit_from_contract(0) to - # (18) - *[ - single_deposit_from_contract(i) - for i in range( - 0, - 18, - ) - ], - ], - id="max_withdrawals_per_slot+max_consolidations_per_slot+unlimited_deposits_per_slot", - ), - ], -) +@pytest.mark.parametrize_by_fork("requests", get_fork_permutations) @pytest.mark.eels_base_coverage def test_valid_multi_type_requests( blockchain_test: BlockchainTestFiller, @@ -343,8 +147,8 @@ def test_valid_multi_type_requests( blocks: List[Block], ) -> None: """ - Test making a deposit to the beacon chain deposit contract and a withdrawal - in the same block. + Test valid combinations of every request type in the same block, from + EOAs and from relay contracts, including per-type maximums. """ blockchain_test( genesis_environment=Environment(), @@ -354,100 +158,6 @@ def test_valid_multi_type_requests( ) -@pytest.mark.parametrize("requests", [*get_permutations()]) -def test_valid_multi_type_request_from_same_tx( - blockchain_test: BlockchainTestFiller, - pre: Alloc, - requests: List[DepositRequest | WithdrawalRequest | ConsolidationRequest], - fork: Fork, -) -> None: - """ - Test making a deposit to the beacon chain deposit contract and a withdrawal - in the same tx. - """ - withdrawal_request_fee: int = 1 - consolidation_request_fee: int = 1 - - calldata: bytes = b"" - contract_code: Bytecode = Bytecode() - total_value: int = 0 - storage: Storage = Storage() - - for request in requests: - calldata_start: int = len(calldata) - current_calldata: bytes = request.calldata - calldata += current_calldata - - contract_code += Op.CALLDATACOPY( - 0, calldata_start, len(current_calldata) - ) - - call_contract_address: int = 0 - value: int = 0 - if isinstance(request, DepositRequest): - call_contract_address = Spec_EIP6110.DEPOSIT_CONTRACT_ADDRESS - value = request.value - elif isinstance(request, WithdrawalRequest): - call_contract_address = ( - Spec_EIP7002.WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS - ) - value = withdrawal_request_fee - elif isinstance(request, ConsolidationRequest): - call_contract_address = ( - Spec_EIP7251.CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS - ) - value = consolidation_request_fee - - total_value += value - - contract_code += Op.SSTORE( - storage.store_next(1), - Op.CALL( - address=call_contract_address, - value=value, - args_offset=0, - args_size=len(current_calldata), - ), - ) - - sender: EOA = pre.fund_eoa() - contract_address: Address = pre.deploy_contract( - code=contract_code, - ) - - tx: Transaction = Transaction( - to=contract_address, - value=total_value, - data=calldata, - sender=sender, - ) - - blockchain_test( - genesis_environment=Environment(), - pre=pre, - post={ - contract_address: Account( - storage=storage, - ) - }, - blocks=[ - Block( - txs=[tx], - header_verify=Header( - requests_hash=Requests( - *[ - request.with_source_address(contract_address) - for request in sorted( - requests, key=lambda r: r.type - ) - ], - ) - ), - ) - ], - ) - - def invalid_requests_block_combinations( *, correct_requests_hash_in_header: bool, @@ -455,43 +165,29 @@ def invalid_requests_block_combinations( """ Return a list of invalid request combinations for the given fork. - In the event of a new request type, the `all_request_types` dictionary - should be updated with the new request type and its corresponding - request-generating transaction. + Combinations are derived from `REQUEST_TYPES` for the fork, so a new + request type is picked up by adding it there. The hand-crafted + "incorrect order" cases remain valid for more types but are not + exhaustive, so revisit them when adding a type. Returned parameters are: requests, block_body_override_requests, exception """ def func(fork: Fork) -> List[ParameterSet]: - assert fork.max_request_type() == 2, ( - "Test update is needed for new request types" - ) + request_types = request_types_from_fork(fork) + # Per type: the EOA interaction that triggers it, and the bare request + # (source-addressed) used to build the block body. Source addressing is + # a no-op for fee-less requests (e.g. deposits) whose bytes omit it. all_request_types: Dict[ str, - Tuple[ - DepositTransaction - | WithdrawalRequestTransaction - | ConsolidationRequestTransaction, - DepositRequest | WithdrawalRequest | ConsolidationRequest, - ], + Tuple[SystemContractInteractionTransaction, SystemContractRequest], ] = { - "deposit": ( - single_deposit_from_eoa(0), # eoa_request - single_deposit(0), # block_request - ), - "withdrawal": ( - single_withdrawal_from_eoa(0), # eoa_request - single_withdrawal(0).with_source_address( - TestAddress - ), # block_request - ), - "consolidation": ( - single_consolidation_from_eoa(0), # eoa_request - single_consolidation(0).with_source_address( - TestAddress - ), # block_request - ), + request_type_to_id_str(request_type): ( + eoa_interaction(request_type, 0), + request_type.from_index(0).with_source_address(TestAddress), + ) + for request_type in request_types } expected_exceptions: List[BlockException] = [ @@ -559,9 +255,7 @@ def func(fork: Fork) -> List[ParameterSet]: *[r[1] for r in all_request_types.values()] ).requests_list # Requests automatically adds the type byte correct_order_transactions: List[ - DepositTransaction - | WithdrawalRequestTransaction - | ConsolidationRequestTransaction + SystemContractInteractionTransaction ] = [r[0] for r in all_request_types.values()] # Send first element to the end @@ -667,7 +361,7 @@ def func(fork: Fork) -> List[ParameterSet]: def test_invalid_multi_type_requests( blockchain_test: BlockchainTestFiller, pre: Alloc, - blocks: List[Block], + override_blocks: List[Block], ) -> None: """ Negative testing for all request types in the same block. @@ -682,7 +376,7 @@ def test_invalid_multi_type_requests( genesis_environment=Environment(), pre=pre, post={}, - blocks=blocks, + blocks=override_blocks, ) @@ -696,7 +390,7 @@ def test_invalid_multi_type_requests( def test_invalid_multi_type_requests_engine( blockchain_test: BlockchainTestFiller, pre: Alloc, - blocks: List[Block], + override_blocks: List[Block], ) -> None: """ Negative testing for all request types in the same block with incorrect @@ -724,5 +418,5 @@ def test_invalid_multi_type_requests_engine( genesis_environment=Environment(), pre=pre, post={}, - blocks=blocks, + blocks=override_blocks, ) diff --git a/tests/prague/eip7702_set_code_tx/test_set_code_txs.py b/tests/prague/eip7702_set_code_tx/test_set_code_txs.py index e4be5bd2071..a083c9035e0 100644 --- a/tests/prague/eip7702_set_code_tx/test_set_code_txs.py +++ b/tests/prague/eip7702_set_code_tx/test_set_code_txs.py @@ -50,11 +50,9 @@ from execution_testing.base_types import HexNumber from ...cancun.eip4844_blobs.spec import Spec as Spec4844 -from ..eip6110_deposits.helpers import DepositRequest -from ..eip7002_el_triggerable_withdrawals.helpers import WithdrawalRequest -from ..eip7002_el_triggerable_withdrawals.spec import Spec as Spec7002 -from ..eip7251_consolidations.helpers import ConsolidationRequest -from ..eip7251_consolidations.spec import Spec as Spec7251 +from ..eip7685_general_purpose_el_requests.test_multi_type_requests import ( + REQUEST_TYPE_BY_ADDRESS, +) from .helpers import AddressType from .spec import Spec, ref_spec_7702 @@ -3169,7 +3167,6 @@ def deposit_contract_initial_storage() -> Storage: def test_set_code_to_system_contract( blockchain_test: BlockchainTestFiller, pre: Alloc, - fork: Fork, system_contract: int, call_opcode: Op, ) -> None: @@ -3204,50 +3201,29 @@ def test_set_code_to_system_contract( auth_signer = pre.fund_eoa(auth_account_start_balance) # Fabricate the payload for the system contract - match system_contract: - case Address(0x000F3DF6D732807EF1319FB7B8BB8522D0BEAC02): # EIP-4788 - caller_payload = Hash(1) - caller_code_storage[call_return_data_size_slot] = 32 - case Address(0x00000000219AB540356CBB839CBE05303D7705FA): # EIP-6110 - # Fabricate a valid deposit request to the set-code account - deposit_request = DepositRequest( - pubkey=0x01, - withdrawal_credentials=0x02, - amount=1_000_000_000, - signature=0x03, - index=0x0, - ) - caller_payload = deposit_request.calldata - call_value = deposit_request.value - case Address(Spec7002.WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS): - # Fabricate a valid withdrawal request to the set-code account - withdrawal_request = WithdrawalRequest( - source_address=0x01, - validator_pubkey=0x02, - amount=0x03, - fee=0x01, - ) - caller_payload = withdrawal_request.calldata - call_value = withdrawal_request.value - case Address(Spec7251.CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS): - # Fabricate a valid consolidation request to the set-code account - consolidation_request = ConsolidationRequest( - source_address=0x01, - source_pubkey=0x02, - target_pubkey=0x03, - fee=0x01, - ) - caller_payload = consolidation_request.calldata - call_value = consolidation_request.value - case Address(0x0000F90827F1C53A10CB7A02335B175320002935): # EIP-2935 - # This payload is used to identify the number of blocks to be - # subtracted from the latest block number - caller_payload = Hash(1) - caller_code_storage[call_return_data_size_slot] = 32 - case _: - raise ValueError( - f"Not implemented system contract: {system_contract}" - ) + if Address(system_contract) in REQUEST_TYPE_BY_ADDRESS: + rt = REQUEST_TYPE_BY_ADDRESS[Address(system_contract)] + request = rt.from_index(0) + caller_payload = request.calldata + call_value = request.value + else: + match system_contract: + case Address( + 0x000F3DF6D732807EF1319FB7B8BB8522D0BEAC02 + ): # EIP-4788 + caller_payload = Hash(1) + caller_code_storage[call_return_data_size_slot] = 32 + case Address( + 0x0000F90827F1C53A10CB7A02335B175320002935 + ): # EIP-2935 + # This payload is used to identify the number of blocks to be + # subtracted from the latest block number + caller_payload = Hash(1) + caller_code_storage[call_return_data_size_slot] = 32 + case _: + raise ValueError( + f"Not implemented system contract: {system_contract}" + ) # Setup the code to call the system contract match system_contract: From e135c2880962382da356177d81b1536bb76c94a3 Mon Sep 17 00:00:00 2001 From: danceratopz Date: Wed, 1 Jul 2026 08:43:38 +0200 Subject: [PATCH 062/233] chore(tooling): fix fill-tests skill and evm-bin help (#3075) * chore(tooling): update fill-tests skill benchmark and evm-bin notes Document `--include-benchmark` and `tests/benchmark/...` path targeting as the ways to collect benchmark tests, replacing the outdated `-m benchmark` guidance. Note that `--evm-bin` defaults to the in-repo EELS Python spec, and drop the static-tests section since `tests/static/` no longer exists. * chore(test-fill): fix `--evm-bin` help default The default is the in-repo EELS Python spec (`src/ethereum/`), not `ethereum-spec-evm-resolver`. --- .claude/commands/fill-tests.md | 18 ++++-------------- .../pytest_commands/plugins/filler/filler.py | 3 ++- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/.claude/commands/fill-tests.md b/.claude/commands/fill-tests.md index e1f3db7967a..46fbc0262c9 100644 --- a/.claude/commands/fill-tests.md +++ b/.claude/commands/fill-tests.md @@ -19,7 +19,7 @@ uv run fill --collect-only tests/ # Dry run: list tests wit - `-k "pattern"` — filter tests by name pattern - `-m "marker"` — filter by pytest marker (e.g. `-m state_test`, `-m blockchain_test`) - `-n auto --maxprocesses N` — parallel execution (use `--dist=loadgroup`) -- `--evm-bin PATH` — specify t8n tool (default: `ethereum-spec-evm-resolver`) +- `--evm-bin PATH` — t8n tool; defaults to the in-repo EELS Python spec (`src/ethereum/`) - `--verify-fixtures` — verify generated fixtures against geth blocktest - `--generate-all-formats` — generate all fixture formats (2-phase) @@ -37,19 +37,9 @@ uv run fill --collect-only tests/ # Dry run: list tests wit ## Benchmark Tests -- Must use `-m benchmark` — benchmark tests are excluded by default -- Require evmone as backend: `--evm-bin=evmone-t8n` -- Default benchmark fork is Prague (set in `tests/benchmark/conftest.py`) -- Gas values mode: `--gas-benchmark-values 1,10,100` (values in millions of gas) -- Fixed opcode count mode: `--fixed-opcode-count 1,10,100` (values in thousands) -- These two modes are **mutually exclusive** -- Use `--generate-pre-alloc-groups` for stateful benchmarks - -## Static Tests (Legacy) - -- `uv run fill --fill-static-tests tests/static/` — fills YAML/JSON fillers from `ethereum/tests` -- Legacy only — do NOT add new static fillers. Use Python tests instead -- Useful to check if spec changes broke how legacy tests fill +- Excluded from a broad `tests/` run: include them by targeting a `tests/benchmark/...` path, or add `--include-benchmark` when also collecting `tests/`. +- Pick a mode (mutually exclusive): `--gas-benchmark-values 1,10,100` (millions of gas) or `--fixed-opcode-count 1,10,100` (thousands). These parametrize the tests, e.g. `...[fork_Prague-blockchain_test-benchmark-gas-value_1M]`. +- Backend is optional: omitting `--evm-bin` runs the slow in-repo EELS Python spec; `--evm-bin=evmone-t8n` or `--evm-bin=evm` (geth, used by `just bench-gas`) are faster. ## Fixture Formats diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py index 639631de181..ffcced6e476 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py @@ -433,7 +433,8 @@ def pytest_addoption(parser: pytest.Parser) -> None: default=None, help=( "Path to an evm executable (or name of an executable in the " - "PATH) that provides `t8n`. Default: `ethereum-spec-evm-resolver`." + "PATH) that provides `t8n`. Defaults to the in-repo EELS " + "Python spec (`src/ethereum/`)." ), ) evm_group.addoption( From ba26c0d408854e7f0f8cf6a88ebbd9dee2bd3abc Mon Sep 17 00:00:00 2001 From: danceratopz Date: Wed, 1 Jul 2026 08:51:15 +0200 Subject: [PATCH 063/233] fix(tooling): honor exported `DOCC_SKIP_DIFFS` in `docs-spec` recipe (#3074) * fix(tooling): Honor exported `DOCC_SKIP_DIFFS` in `docs-spec` recipe The `docs-spec` recipe declared `$DOCC_SKIP_DIFFS=""`, whose empty parameter default shadowed the environment variable exported by the `Build Spec Docs` CI job. As a result `docc` never saw the flag and always rendered the per-fork diffs, so pull-request and non-default-branch builds discovered all 993 diff pairs and took ~20 min instead of ~8 min. Default the parameter to `env_var_or_default("DOCC_SKIP_DIFFS", "")` so an exported value is honored, while `just docs-spec-fast` (which passes `1` positionally) and the plain local default keep working. --- .github/workflows/docs-build.yaml | 1 + Justfile | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docs-build.yaml b/.github/workflows/docs-build.yaml index 5b7c60a7fed..b49c2c518a9 100644 --- a/.github/workflows/docs-build.yaml +++ b/.github/workflows/docs-build.yaml @@ -36,6 +36,7 @@ on: - "mkdocs.yml" - "uv.lock" - "pyproject.toml" + - "Justfile" - ".github/workflows/docs-build.yaml" - ".github/configs/docs-branches.yaml" diff --git a/Justfile b/Justfile index 41c54cfcc3d..c3b7de95a5c 100644 --- a/Justfile +++ b/Justfile @@ -308,7 +308,7 @@ export DYLD_FALLBACK_LIBRARY_PATH := if os() == "macos" { "/opt/homebrew/lib" } # Generate documentation for EELS using docc [group('docs')] -docs-spec $DOCC_SKIP_DIFFS="": +docs-spec $DOCC_SKIP_DIFFS=env_var_or_default("DOCC_SKIP_DIFFS", ""): uv run docc --output "{{ output_dir }}/docs-spec" uv run python -c 'import pathlib; print("documentation available under file://{0}".format(pathlib.Path(r"{{ output_dir }}") / "docs-spec" / "index.html"))' From 5442d28004690bbf49a65f7bdc99dfda62591bba Mon Sep 17 00:00:00 2001 From: Jack CC Date: Wed, 1 Jul 2026 17:08:48 +0800 Subject: [PATCH 064/233] chore(ci): update PR template (#3054) Co-authored-by: danceratopz --- .github/PULL_REQUEST_TEMPLATE.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index d0e5e1a21b7..89068893dbd 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -9,16 +9,19 @@ N/A. ## ✅ Checklist -- [ ] All: Ran fast static checks to avoid unnecessary CI fails, see also [Code Standards](https://eest.ethereum.org/main/getting_started/code_standards/) and [Enabling Pre-commit Checks](https://eest.ethereum.org/main/dev/precommit/): +- [ ] All: Ran fast static checks to avoid unnecessary CI fails, see also [Code Standards](https://steel.ethereum.foundation/docs/execution-specs/getting_started/code_standards/) and [Verifying Changes](https://steel.ethereum.foundation/docs/execution-specs/getting_started/verifying_changes/): ```console just static ``` -- [ ] All: PR title adheres to the [repo standard](https://eest.ethereum.org/main/getting_started/contributing/?h=contri#commit-messages-issue-and-pr-titles) - it will be used as the squash commit message and should start `type(scope):`. +- [ ] All: PR title have the form `():`, where `` and `` come from an approrpriate `C-`, respectively `A-`, label. The title should match the a target squash commit message. - [ ] All: Considered updating the online docs in the [./docs/](/ethereum/execution-specs/blob/HEAD/docs/) directory. - [ ] All: Set appropriate labels for the changes (only maintainers can apply labels). -- [ ] Tests: Ran `mkdocs serve` locally and verified the auto-generated docs for new tests in the [Test Case Reference](https://eest.ethereum.org/main/tests/) are correctly formatted. - [ ] Tests: For PRs implementing a missed test case, update the [post-mortem document](/ethereum/execution-specs/blob/HEAD/docs/writing_tests/post_mortems.md) to add an entry the list. -- [ ] Ported Tests: All converted JSON/YML tests from [ethereum/tests](/ethereum/tests) or [tests/static](/ethereum/execution-specs/blob/HEAD/tests/static) have been assigned `@ported_from` marker. +- [ ] Ported Tests: Add the following docstring to manually enhanced tests from `./tests/ported_static/`: + ```text + @manually-enhanced: Do not overwrite. Post-state expectations corrected + manually (see PR #2784). + ```` #### Cute Animal Picture From 27174ca81b09dee4d41db23b40305cd1bb70f5bf Mon Sep 17 00:00:00 2001 From: Jack CC Date: Wed, 1 Jul 2026 18:14:53 +0800 Subject: [PATCH 065/233] fix(spec-tests): register JSON fixture fork marker (#3053) --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 8edf9df95a5..99ed678edc7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -292,6 +292,7 @@ markers = [ "json_state_tests: marks tests as json_state_tests (deselect with '-m \"not json_state_tests\"')", "vm_test: marks tests as vm_test (deselect with '-m \"not vm_test\"')", "eels_base_coverage: Minimized subset selected to preserve high EELS line-coverage parity (select with '-m eels_base_coverage')", + "fork: marks JSON fixture tests by fork", "repricing: marks tests for gas repricing analysis", "stub_parametrize: parametrize test from address stubs by prefix", ] From 6b35e9095eab919aa40ecdff6260f0313a627aee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Wed, 1 Jul 2026 14:01:20 +0200 Subject: [PATCH 066/233] feat(tests): extend block-gas inclusion boundary test to Osaka (#3076) --- .../test_block_2d_gas_accounting.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py index ebd7e58bed8..0c2d604d20e 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py @@ -605,7 +605,10 @@ def test_tx_gas_limit_block_boundary( pytest.param(1, id="exceeds", marks=pytest.mark.exception_test), ], ) -@pytest.mark.valid_from("EIP8037") +# Cumulative block-gas inclusion is a pre-existing rule, not an +# EIP-8037 novelty. Floor is Osaka only because the gas-cap guard +# below relies on EIP-7825's transaction_gas_limit_cap(). +@pytest.mark.valid_from("Osaka") def test_tx_inclusion_at_regular_gas_block_limit_small( blockchain_test: BlockchainTestFiller, pre: Alloc, @@ -656,6 +659,9 @@ def test_tx_inclusion_at_regular_gas_block_limit_small( txs=filler_txs + [excess_tx], gas_limit=block_gas_limit, exception=error, + header_verify=Header(gas_used=block_gas_limit) + if not error + else None, ) ], post={}, From 9920b6e67cb0a26305b2c6a9578aabe0b74399cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Wed, 1 Jul 2026 14:30:26 +0200 Subject: [PATCH 067/233] refactor(spec-specs): derive EIP-8037 state gas used from reservoir/spill (#3027) --- src/ethereum/forks/amsterdam/vm/__init__.py | 39 +++++++++++++------ src/ethereum/forks/amsterdam/vm/gas.py | 6 +-- .../forks/amsterdam/vm/interpreter.py | 9 ++++- 3 files changed, 37 insertions(+), 17 deletions(-) diff --git a/src/ethereum/forks/amsterdam/vm/__init__.py b/src/ethereum/forks/amsterdam/vm/__init__.py index 48d0b3eab14..b7f890bbc73 100644 --- a/src/ethereum/forks/amsterdam/vm/__init__.py +++ b/src/ethereum/forks/amsterdam/vm/__init__.py @@ -185,7 +185,6 @@ class Evm: accessed_addresses: Set[Address] accessed_storage_keys: Set[Tuple[Address, Bytes32]] regular_gas_used: Uint = Uint(0) - state_gas_used: int = 0 state_gas_spilled: Uint = Uint(0) @@ -210,7 +209,6 @@ def credit_state_gas_refund(evm: Evm, amount: StateGas) -> None: evm.gas_left += from_gas_left evm.state_gas_spilled -= from_gas_left evm.state_gas_left += amount - from_gas_left - evm.state_gas_used -= int(amount) def incorporate_child_on_success(evm: Evm, child_evm: Evm) -> None: @@ -234,7 +232,6 @@ def incorporate_child_on_success(evm: Evm, child_evm: Evm) -> None: evm.accessed_addresses.update(child_evm.accessed_addresses) evm.accessed_storage_keys.update(child_evm.accessed_storage_keys) evm.regular_gas_used += child_evm.regular_gas_used - evm.state_gas_used += child_evm.state_gas_used def refill_frame_state_gas(evm: Evm) -> None: @@ -252,15 +249,34 @@ def refill_frame_state_gas(evm: Evm) -> None: """ evm.gas_left += evm.state_gas_spilled - evm.state_gas_left = Uint( - int(evm.state_gas_left) - + evm.state_gas_used - - int(evm.state_gas_spilled) - ) - evm.state_gas_used = 0 + evm.state_gas_left = evm.message.state_gas_reservoir evm.state_gas_spilled = Uint(0) +def frame_state_gas_used(evm: Evm) -> int: + """ + Return the net state gas consumed by a finished frame. + + Equal to the reservoir drawn down ([`state_gas_reservoir`][sgr] at entry + minus the reservoir now) plus [`state_gas_spilled`][sgs]. May be negative + when refunds exceed charges. + + Parameters + ---------- + evm : + The finished frame. + + [sgr]: ref:ethereum.forks.amsterdam.vm.Message.state_gas_reservoir + [sgs]: ref:ethereum.forks.amsterdam.vm.Evm.state_gas_spilled + + """ + return ( + int(evm.message.state_gas_reservoir) + - int(evm.state_gas_left) + + int(evm.state_gas_spilled) + ) + + def incorporate_child_on_error( evm: Evm, child_evm: Evm, @@ -270,9 +286,8 @@ def incorporate_child_on_error( The child rolls back its own state gas via `refill_frame_state_gas` before returning (on both reverts and exceptional halts), so its - `gas_left` and reservoir already reflect the LIFO refill and its - `state_gas_used` is zero. The parent therefore only reabsorbs the - child's `gas_left` and reservoir. + `gas_left` and reservoir already reflect the LIFO refill. The parent + therefore only reabsorbs the child's `gas_left` and reservoir. Parameters ---------- diff --git a/src/ethereum/forks/amsterdam/vm/gas.py b/src/ethereum/forks/amsterdam/vm/gas.py index c967572d5e1..156d7db6ec8 100644 --- a/src/ethereum/forks/amsterdam/vm/gas.py +++ b/src/ethereum/forks/amsterdam/vm/gas.py @@ -296,7 +296,7 @@ def charge_gas(evm: Evm, amount: Uint) -> None: def charge_state_gas(evm: Evm, amount: StateGas) -> None: """ Subtracts `amount` from the state gas reservoir, then from - `evm.gas_left` when the reservoir is empty. Records state gas usage. + `evm.gas_left` when the reservoir is empty, tracking any [spill]. Parameters ---------- @@ -305,6 +305,8 @@ def charge_state_gas(evm: Evm, amount: StateGas) -> None: amount : The amount of state gas the current operation requires. + [spill]: ref:ethereum.forks.amsterdam.vm.Evm.state_gas_spilled + """ evm_trace(evm, StateGasAndRefund(int(amount))) @@ -318,8 +320,6 @@ def charge_state_gas(evm: Evm, amount: StateGas) -> None: else: raise OutOfGasError - evm.state_gas_used += int(amount) - def calculate_memory_gas_cost(size_in_bytes: Uint) -> Uint: """ diff --git a/src/ethereum/forks/amsterdam/vm/interpreter.py b/src/ethereum/forks/amsterdam/vm/interpreter.py index 2fe53a3982d..5df1dccc8a0 100644 --- a/src/ethereum/forks/amsterdam/vm/interpreter.py +++ b/src/ethereum/forks/amsterdam/vm/interpreter.py @@ -54,7 +54,12 @@ charge_state_gas, ) from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS -from . import Evm, emit_transfer_log, refill_frame_state_gas +from . import ( + Evm, + emit_transfer_log, + frame_state_gas_used, + refill_frame_state_gas, +) from .exceptions import ( AddressCollision, ExceptionalHalt, @@ -185,7 +190,7 @@ def process_message_call(message: Message) -> MessageCallOutput: return_data=evm.output, state_gas_left=evm.state_gas_left, regular_gas_used=evm.regular_gas_used, - state_gas_used=evm.state_gas_used, + state_gas_used=frame_state_gas_used(evm), state_refund=state_refund, created_target_alive=target_alive, ) From af05361ff79f00ec341c375f61b3ccdd6019b4d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Wed, 1 Jul 2026 17:18:56 +0200 Subject: [PATCH 068/233] feat(tests): EIP-8037 SELFDESTRUCT new-account state gas spill and refill (#3069) --- .../test_state_gas_selfdestruct.py | 103 ++++++++++-------- 1 file changed, 58 insertions(+), 45 deletions(-) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_selfdestruct.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_selfdestruct.py index 5e5ee91ea08..82c465cd67a 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_selfdestruct.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_selfdestruct.py @@ -33,37 +33,46 @@ REFERENCE_SPEC_VERSION = ref_spec_8037.version +@pytest.mark.parametrize("funding", ["reservoir", "spill"]) @pytest.mark.valid_from("EIP8037") -def test_selfdestruct_new_beneficiary_charges_state_gas( +def test_selfdestruct_new_beneficiary_state_gas( state_test: StateTestFiller, pre: Alloc, fork: Fork, + funding: str, ) -> None: """ - Test SELFDESTRUCT to non-existent beneficiary charges state gas. + Test SELFDESTRUCT to a non-existent beneficiary bills NEW_ACCOUNT. - When the beneficiary does not exist and the originator has nonzero - balance, SELFDESTRUCT charges new-account state gas for - creating the new beneficiary account. + A contract with nonzero balance self-destructs to a non-alive + beneficiary, charging new-account state gas. The charge is billed + identically whether drawn from the reservoir (out-of-cap tx) or + spilled into `gas_left` (in-cap tx): the block bills NEW_ACCOUNT in + the state dimension and the beneficiary is created. """ - gas_costs = fork.gas_costs() - new_account_state_gas = gas_costs.NEW_ACCOUNT - - # Non-existent beneficiary + new_account_state_gas = fork.gas_costs().NEW_ACCOUNT beneficiary = 0xDEAD contract = pre.deploy_contract( - code=Op.SELFDESTRUCT(beneficiary), - balance=1, + code=Op.SELFDESTRUCT(beneficiary), balance=1 ) - tx = Transaction( to=contract, - state_gas_reservoir=new_account_state_gas, sender=pre.fund_eoa(), + state_gas_reservoir=( + new_account_state_gas if funding == "reservoir" else 0 + ), ) - state_test(pre=pre, post={}, tx=tx) + state_test( + pre=pre, + post={ + beneficiary: Account(balance=1), + contract: Account(balance=0), + }, + tx=tx, + blockchain_test_header_verify=Header(gas_used=new_account_state_gas), + ) @pytest.mark.valid_from("EIP8037") @@ -122,37 +131,6 @@ def test_selfdestruct_zero_balance_no_state_gas( state_test(pre=pre, post={}, tx=tx) -@pytest.mark.valid_from("EIP8037") -def test_selfdestruct_state_gas_from_reservoir( - state_test: StateTestFiller, - pre: Alloc, - fork: Fork, -) -> None: - """ - Test SELFDESTRUCT state gas drawn from reservoir. - - Provide gas above TX_MAX_GAS_LIMIT so the new account state gas - for the non-existent beneficiary is drawn from the reservoir. - """ - gas_costs = fork.gas_costs() - new_account_state_gas = gas_costs.NEW_ACCOUNT - - beneficiary = 0xDEAD - - contract = pre.deploy_contract( - code=Op.SELFDESTRUCT(beneficiary), - balance=1, - ) - - tx = Transaction( - to=contract, - state_gas_reservoir=new_account_state_gas, - sender=pre.fund_eoa(), - ) - - state_test(pre=pre, post={}, tx=tx) - - @pytest.mark.valid_from("EIP8037") def test_selfdestruct_to_self_in_create_tx( state_test: StateTestFiller, @@ -238,6 +216,41 @@ def test_selfdestruct_new_beneficiary_header_gas_used( ) +@pytest.mark.valid_from("EIP8037") +def test_selfdestruct_state_gas_refilled_on_ancestor_revert( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify SELFDESTRUCT state gas is refilled when an ancestor reverts. + + The inner frame spills the NEW_ACCOUNT charge and self-destructs + successfully, then the caller reverts: the beneficiary creation + rolls back and the spilled charge is refilled, so only regular gas + is billed. + """ + beneficiary = 0xDEAD + inner_code = Op.SELFDESTRUCT(beneficiary) + inner = pre.deploy_contract(code=inner_code, balance=1) + caller_code = Op.POP(Op.CALL(gas=Op.GAS, address=inner)) + Op.REVERT(0, 0) + caller = pre.deploy_contract(code=caller_code) + + expected_regular = ( + fork.transaction_intrinsic_cost_calculator()() + + caller_code.gas_cost(fork) + + inner_code.gas_cost(fork) + ) + tx = Transaction(to=caller, sender=pre.fund_eoa()) + + state_test( + pre=pre, + post={beneficiary: Account.NONEXISTENT, inner: Account(balance=1)}, + tx=tx, + blockchain_test_header_verify=Header(gas_used=expected_regular), + ) + + @pytest.mark.parametrize( "num_slots", [ From 7c634a428c65c7081c0008b54d55dded971e5055 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Wed, 1 Jul 2026 17:19:07 +0200 Subject: [PATCH 069/233] feat(tests): EIP-8037 base fee follows the bottleneck gas dimension (#3039) --- .../test_block_2d_gas_accounting.py | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py index 0c2d604d20e..78f793db4c9 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py @@ -786,3 +786,102 @@ def test_receipt_cumulative_differs_from_header_gas_used( ], post=post, ) + + +@pytest.mark.parametrize("dominant_dimension", ["state", "regular"]) +@pytest.mark.valid_from("EIP8037") +def test_base_fee_per_gas_follows_dominant_dimension( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, + dominant_dimension: str, +) -> None: + """ + Verify the child block's base fee follows the bottleneck dimension. + + Block 1 exceeds the gas target on one dimension only: state, via + SSTORE-set txs that spill, or regular, via STOP txs. Its header + gas_used = max(regular, state) is then set by that dimension alone, + which lifts empty block 2's base fee under the EIP-1559 update. + """ + genesis_base_fee = 10**9 + max_fee_per_gas = 10**10 + gas_limit = 600_000 + target = gas_limit // fork.base_fee_elasticity_multiplier() + + txs: list[Transaction] = [] + post: dict = {} + if dominant_dimension == "state": + num_txs = 5 + tx_regular, tx_state = sstore_tx_gas(fork) + block_regular = num_txs * tx_regular + block_state = num_txs * tx_state + tx_gas_limit = tx_regular + tx_state + assert block_state > target > block_regular + else: + num_txs = 15 + tx_gas_limit = fork.transaction_intrinsic_cost_calculator()() + block_regular = num_txs * tx_gas_limit + block_state = 0 + stop_contract = pre.deploy_contract(code=Op.STOP) + assert block_regular > target > block_state + + for _ in range(num_txs): + if dominant_dimension == "state": + storage = Storage() + contract = pre.deploy_contract( + code=Op.SSTORE(storage.store_next(1), 1) + Op.STOP, + ) + post[contract] = Account(storage=storage) + else: + contract = stop_contract + txs.append( + Transaction( + to=contract, + gas_limit=tx_gas_limit, + max_fee_per_gas=max_fee_per_gas, + max_priority_fee_per_gas=0, + sender=pre.fund_eoa(), + ) + ) + + block_1_gas_used = max(block_regular, block_state) + base_fee_calc = fork.base_fee_per_gas_calculator() + block_1_base_fee = base_fee_calc( + parent_base_fee_per_gas=genesis_base_fee, + parent_gas_used=0, + parent_gas_limit=gas_limit, + ) + block_2_base_fee = base_fee_calc( + parent_base_fee_per_gas=block_1_base_fee, + parent_gas_used=block_1_gas_used, + parent_gas_limit=gas_limit, + ) + assert block_2_base_fee > block_1_base_fee + + blockchain_test( + genesis_environment=Environment( + gas_limit=gas_limit, + base_fee_per_gas=genesis_base_fee, + ), + pre=pre, + blocks=[ + Block( + txs=txs, + gas_limit=gas_limit, + header_verify=Header( + gas_used=block_1_gas_used, + base_fee_per_gas=block_1_base_fee, + ), + ), + Block( + txs=[], + gas_limit=gas_limit, + header_verify=Header( + gas_used=0, + base_fee_per_gas=block_2_base_fee, + ), + ), + ], + post=post, + ) From a3659742a5d774791b117cc359daea1e76f314de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Wed, 1 Jul 2026 17:21:48 +0200 Subject: [PATCH 070/233] feat(tests): EIP-8037 CALL new-account state gas exact-fit boundary (#3067) --- .../test_state_gas_call.py | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py index 6825b531638..22fd9a29f87 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py @@ -28,6 +28,7 @@ StateTestFiller, Storage, Transaction, + TransactionReceipt, compute_create2_address, compute_create_address, ) @@ -1480,6 +1481,52 @@ def test_call_new_account_no_regular_account_creation_cost( state_test(pre=pre, post={target: Account(balance=1)}, tx=tx) +@pytest.mark.parametrize( + "gas_delta", + [pytest.param(0, id="exact_fit"), pytest.param(-1, id="one_short")], +) +@pytest.mark.valid_from("EIP8037") +def test_call_new_account_state_gas_boundary( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + gas_delta: int, +) -> None: + """ + Pin the CALL new-account state charge at its exact-fit spill + boundary. At `exact_fit` the charge just fits and the target is + materialized; one gas short the caller frame goes out of gas, so + nothing is created and the value transfer is rolled back. + """ + gas_costs = fork.gas_costs() + target = 0xDEAD + caller_code = Op.CALL(gas=0, address=target, value=1) + Op.STOP + caller = pre.deploy_contract(code=caller_code, balance=1) + + exact_fit = ( + fork.transaction_intrinsic_cost_calculator()() + + caller_code.gas_cost(fork) + + gas_costs.CALL_VALUE + + gas_costs.NEW_ACCOUNT + ) + post: dict + if gas_delta == 0: + gas_used = exact_fit - gas_costs.CALL_STIPEND + post = {target: Account(balance=1), caller: Account(balance=0)} + else: + gas_used = exact_fit + gas_delta + post = {target: Account.NONEXISTENT, caller: Account(balance=1)} + + tx = Transaction( + to=caller, + gas_limit=exact_fit + gas_delta, + sender=pre.fund_eoa(), + expected_receipt=TransactionReceipt(cumulative_gas_used=gas_used), + ) + + state_test(pre=pre, post=post, tx=tx) + + @pytest.mark.parametrize( "call_opcode,charge_via", [ From 8bc63b4edf2291648a48228f7aa9a27289bad24f Mon Sep 17 00:00:00 2001 From: spencer Date: Wed, 1 Jul 2026 16:45:40 +0100 Subject: [PATCH 071/233] feat(ci): overhaul fixture releases (#2888) Co-authored-by: danceratopz --- .github/actions/build-evm-base/action.yaml | 67 ++++---- .../build-evm-client/ethjs/action.yaml | 37 ----- .github/actions/build-fixtures/action.yaml | 17 +- .github/configs/evm-impl.yaml | 15 -- .github/configs/evm.yaml | 24 ++- .github/configs/feature.yaml | 18 +- .github/configs/fork-ranges.yaml | 5 +- .github/scripts/create_release_tarball.py | 6 +- .github/scripts/generate_build_matrix.py | 117 +++++++++++-- .github/scripts/get_release_props.py | 10 +- .github/scripts/merge_index_files.py | 2 +- .github/scripts/tests/test_release_scripts.py | 94 +++++++++-- ...ure_feature.yaml => release_fixtures.yaml} | 156 ++++++++++++------ docs/dev/releasing_tests.md | 110 ++++++++++++ docs/navigation.md | 3 +- docs/running_tests/releases.md | 148 +++++++++++------ 16 files changed, 594 insertions(+), 235 deletions(-) delete mode 100644 .github/actions/build-evm-client/ethjs/action.yaml delete mode 100644 .github/configs/evm-impl.yaml rename .github/workflows/{release_fixture_feature.yaml => release_fixtures.yaml} (53%) create mode 100644 docs/dev/releasing_tests.md diff --git a/.github/actions/build-evm-base/action.yaml b/.github/actions/build-evm-base/action.yaml index 7a5496259b0..d8ed5780d98 100644 --- a/.github/actions/build-evm-base/action.yaml +++ b/.github/actions/build-evm-base/action.yaml @@ -2,48 +2,61 @@ name: 'Build EVM' description: 'Resolves and builds the requested EVM binary by name' inputs: type: - description: 'Type of EVM binary to build' + description: 'Type of EVM binary to build (key in .github/configs/evm.yaml)' required: true - default: 'main' + repo_override: + description: 'Override the repo from evm.yaml (e.g. ethereum/go-ethereum)' + required: false + default: '' + ref_override: + description: 'Override the ref/branch/commit from evm.yaml' + required: false + default: '' outputs: impl: description: "Implementation of EVM binary to build" value: ${{ steps.config-evm-reader.outputs.impl }} repo: description: "Repository to use to build the EVM binary" - value: ${{ steps.config-evm-reader.outputs.repo }} + value: ${{ steps.resolved.outputs.repo }} ref: description: "Reference to branch, commit, or tag to use to build the EVM binary" - value: ${{ steps.config-evm-reader.outputs.ref }} + value: ${{ steps.resolved.outputs.ref }} evm-bin: description: "Binary name of the evm tool to use" - value: ${{ steps.config-evm-impl-config-reader.outputs.evm-bin }} - x-dist: - description: "Binary name of the evm tool to use" - value: ${{ steps.config-evm-impl-config-reader.outputs.x-dist }} + value: ${{ steps.config-evm-reader.outputs.evm-bin }} + xdist: + description: "Number of parallel pytest-xdist workers to use" + value: ${{ steps.config-evm-reader.outputs.xdist }} runs: using: "composite" steps: - - name: Get the selected EVM version from the .github/configs/evm.yaml + - name: Get the selected EVM configuration from .github/configs/evm.yaml id: config-evm-reader shell: bash run: | awk "/^${{ inputs.type }}:/{flag=1; next} /^[[:alnum:]]/{flag=0} flag" ./.github/configs/evm.yaml \ | sed 's/ //g' | sed 's/:/=/g' >> "$GITHUB_OUTPUT" - - name: Get the EVM implementation configuration from .github/configs/evm-impl-config.yaml - id: config-evm-impl-config-reader + - name: Apply repo/ref overrides + id: resolved shell: bash + env: + DEFAULT_REPO: ${{ steps.config-evm-reader.outputs.repo }} + DEFAULT_REF: ${{ steps.config-evm-reader.outputs.ref }} + REPO_OVERRIDE: ${{ inputs.repo_override }} + REF_OVERRIDE: ${{ inputs.ref_override }} run: | - awk "/^${{ steps.config-evm-reader.outputs.impl }}:/{flag=1; next} /^[[:alnum:]]/{flag=0} flag" ./.github/configs/evm-impl.yaml \ - | sed 's/ //g' | sed 's/:/=/g' >> "$GITHUB_OUTPUT" + echo "repo=${REPO_OVERRIDE:-$DEFAULT_REPO}" >> "$GITHUB_OUTPUT" + echo "ref=${REF_OVERRIDE:-$DEFAULT_REF}" >> "$GITHUB_OUTPUT" - name: Print Variables for the selected EVM type shell: bash run: | + echo "Type: ${{ inputs.type }}" echo "Implementation: ${{ steps.config-evm-reader.outputs.impl }}" - echo "Repository: ${{ steps.config-evm-reader.outputs.repo }}" - echo "Reference: ${{ steps.config-evm-reader.outputs.ref }}" - echo "EVM Binary: ${{ steps.config-evm-impl-config-reader.outputs.evm-bin }}" - echo "X-Dist parameter: ${{ steps.config-evm-impl-config-reader.outputs.x-dist }}" + echo "Repository: ${{ steps.resolved.outputs.repo }}" + echo "Reference: ${{ steps.resolved.outputs.ref }}" + echo "EVM Binary: ${{ steps.config-evm-reader.outputs.evm-bin }}" + echo "X-Dist parameter: ${{ steps.config-evm-reader.outputs.xdist }}" - name: Skip building for EELS if: steps.config-evm-reader.outputs.impl == 'eels' shell: bash @@ -52,25 +65,19 @@ runs: if: steps.config-evm-reader.outputs.impl == 'geth' uses: ./.github/actions/build-evm-client/geth with: - repo: ${{ steps.config-evm-reader.outputs.repo }} - ref: ${{ steps.config-evm-reader.outputs.ref }} + repo: ${{ steps.resolved.outputs.repo }} + ref: ${{ steps.resolved.outputs.ref }} - name: Build the EVM using EVMONE action if: steps.config-evm-reader.outputs.impl == 'evmone' uses: ./.github/actions/build-evm-client/evmone with: - repo: ${{ steps.config-evm-reader.outputs.repo }} - ref: ${{ steps.config-evm-reader.outputs.ref }} - # `targets` in the evm.yaml must be an inline array to not interfere with `config-evm-reader`'s parsing + repo: ${{ steps.resolved.outputs.repo }} + ref: ${{ steps.resolved.outputs.ref }} + # `targets` in evm.yaml must be an inline array to not interfere with `config-evm-reader`'s parsing targets: ${{ join(fromJSON(steps.config-evm-reader.outputs.targets), ' ') }} - name: Build the EVM using Besu action if: steps.config-evm-reader.outputs.impl == 'besu' uses: ./.github/actions/build-evm-client/besu with: - repo: ${{ steps.config-evm-reader.outputs.repo }} - ref: ${{ steps.config-evm-reader.outputs.ref }} - - name: Build the EVM using EthJS action - if: steps.config-evm-reader.outputs.impl == 'ethjs' - uses: ./.github/actions/build-evm-client/ethjs - with: - repo: ${{ steps.config-evm-reader.outputs.repo }} - ref: ${{ steps.config-evm-reader.outputs.ref }} \ No newline at end of file + repo: ${{ steps.resolved.outputs.repo }} + ref: ${{ steps.resolved.outputs.ref }} diff --git a/.github/actions/build-evm-client/ethjs/action.yaml b/.github/actions/build-evm-client/ethjs/action.yaml deleted file mode 100644 index 2bc21dd7bf5..00000000000 --- a/.github/actions/build-evm-client/ethjs/action.yaml +++ /dev/null @@ -1,37 +0,0 @@ -name: 'Build EthereumJS monorepo' -description: 'Builds the EthereumJS monorepo' -inputs: - repo: - description: 'Source repository to use to build EthereumJS' - required: true - default: 'ethereumjs/ethereumjs-monorepo' - ref: - description: 'Reference to branch, commit, or tag to use to build EthereumJS' - required: true - default: 'master' -runs: - using: "composite" - steps: - - name: Checkout EthereumJS monorepo - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - repository: ${{ inputs.repo }} - ref: ${{ inputs.ref }} - path: ethereumjs - - - name: Setup node - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 - with: - node-version: 18 - - - name: Build monorepo - shell: bash - run: | - cd $GITHUB_WORKSPACE/ethereumjs - npm ci - - - name: Add t8ntool to $PATH - shell: bash - run: | - echo $GITHUB_WORKSPACE/ethereumjs/packages/vm/test/t8n/ >> $GITHUB_PATH - echo $GITHUB_WORKSPACE/ethereumjs/node_modules/.bin >> $GITHUB_PATH \ No newline at end of file diff --git a/.github/actions/build-fixtures/action.yaml b/.github/actions/build-fixtures/action.yaml index 0c2f197e216..719c3221a75 100644 --- a/.github/actions/build-fixtures/action.yaml +++ b/.github/actions/build-fixtures/action.yaml @@ -13,6 +13,15 @@ inputs: split_label: description: "Label for this fork-range split. Empty for unsplit builds." default: "" + evm: + description: "Override the evm impl. Defaults to the feature's evm-type." + default: "" + evm_repo: + description: "Override the t8n tool repo (e.g. ethereum/go-ethereum)" + default: "" + evm_ref: + description: "Override the t8n tool branch / tag / commit" + default: "" runs: using: "composite" steps: @@ -32,7 +41,9 @@ runs: - uses: ./.github/actions/build-evm-base id: evm-builder with: - type: ${{ steps.properties.outputs.evm-type }} + type: ${{ inputs.evm != '' && inputs.evm || steps.properties.outputs.evm-type }} + repo_override: ${{ inputs.evm_repo }} + ref_override: ${{ inputs.evm_ref }} - name: Install pigz for parallel tarball compression if: inputs.split_label == '' shell: bash @@ -53,9 +64,9 @@ runs: # Allow exit code 5 (NO_TESTS_COLLECTED) for fork ranges with no tests. EXIT_CODE=0 if [ "${{ steps.evm-builder.outputs.impl }}" = "eels" ]; then - uv run fill -n ${{ steps.evm-builder.outputs.x-dist }} ${{ steps.properties.outputs.fill-params }} $FORK_ARGS $OUTPUT_ARG --build-name ${{ inputs.release_name }} --no-html --durations=100 --log-level=DEBUG || EXIT_CODE=$? + uv run fill -n ${{ steps.evm-builder.outputs.xdist }} ${{ steps.properties.outputs.fill-params }} $FORK_ARGS $OUTPUT_ARG --build-name ${{ inputs.release_name }} --no-html --durations=100 --log-level=DEBUG || EXIT_CODE=$? else - uv run fill -n ${{ steps.evm-builder.outputs.x-dist }} --evm-bin=${{ steps.evm-builder.outputs.evm-bin }} ${{ steps.properties.outputs.fill-params }} $FORK_ARGS $OUTPUT_ARG --build-name ${{ inputs.release_name }} --no-html --durations=100 --log-level=DEBUG || EXIT_CODE=$? + uv run fill -n ${{ steps.evm-builder.outputs.xdist }} --evm-bin=${{ steps.evm-builder.outputs.evm-bin }} ${{ steps.properties.outputs.fill-params }} $FORK_ARGS $OUTPUT_ARG --build-name ${{ inputs.release_name }} --no-html --durations=100 --log-level=DEBUG || EXIT_CODE=$? fi if [ "$EXIT_CODE" -ne 0 ] && [ "$EXIT_CODE" -ne 5 ]; then exit "$EXIT_CODE" diff --git a/.github/configs/evm-impl.yaml b/.github/configs/evm-impl.yaml deleted file mode 100644 index 4a70077a9e1..00000000000 --- a/.github/configs/evm-impl.yaml +++ /dev/null @@ -1,15 +0,0 @@ -eels: - evm-bin: null - x-dist: auto -geth: - evm-bin: evm - x-dist: auto -evmone: - evm-bin: evmone-t8n - x-dist: auto -besu: - evm-bin: evmtool - x-dist: 0 -ethjs: - evm-bin: ethereumjs-t8ntool.sh - x-dist: auto diff --git a/.github/configs/evm.yaml b/.github/configs/evm.yaml index c295d43bb86..98269b5c38f 100644 --- a/.github/configs/evm.yaml +++ b/.github/configs/evm.yaml @@ -1,13 +1,31 @@ +benchmark: + impl: geth + repo: ethereum/go-ethereum + ref: master + evm-bin: evm + xdist: auto eels: impl: eels repo: null ref: null -static: + evm-bin: null + xdist: auto +evmone: impl: evmone repo: ethereum/evmone ref: master targets: ["evmone-t8n"] -benchmark: + evm-bin: evmone-t8n + xdist: auto +geth: impl: geth repo: ethereum/go-ethereum - ref: master \ No newline at end of file + ref: master + evm-bin: evm + xdist: auto +besu: + impl: besu + repo: hyperledger/besu + ref: main + evm-bin: evmtool + xdist: 0 diff --git a/.github/configs/feature.yaml b/.github/configs/feature.yaml index e5a7e51d7bc..39297ec6e84 100644 --- a/.github/configs/feature.yaml +++ b/.github/configs/feature.yaml @@ -1,7 +1,13 @@ -# Unless filling for special features, all features should fill for previous forks (starting from Frontier) too -mainnet: +# Release feature definitions consumed by the `release_fixtures` workflow. +# +# Top-level keys are feature names used verbatim in the release tag +# (`tests-@vX.Y.Z`), except `tests`, which tags as `tests@vX.Y.Z`. +# Any `-devnet` input resolves to the shared `devnet` entry but keeps +# its name in the tag; the devnet number lives in the version (X), not the +# feature name, so this file needs no edits for new devnets. +tests: evm-type: eels - fill-params: --until=BPO2 --generate-all-formats + fill-params: --until=BPO4 --generate-all-formats benchmark: evm-type: benchmark @@ -13,7 +19,7 @@ benchmark_fast: fill-params: --fork=Osaka --generate-all-formats --gas-benchmark-values 100 ./tests/benchmark/compute feature_only: true -glamsterdam-devnet: +# Shared entry for all `-devnet` releases; matched by `-devnet` suffix. +devnet: evm-type: eels - fill-params: --fork=Amsterdam - feature_only: true + fill-params: --until=Amsterdam diff --git a/.github/configs/fork-ranges.yaml b/.github/configs/fork-ranges.yaml index ef3acf4818e..e0e725f016c 100644 --- a/.github/configs/fork-ranges.yaml +++ b/.github/configs/fork-ranges.yaml @@ -12,10 +12,7 @@ until: Prague - label: osaka from: Osaka - until: Osaka -- label: bpo - from: BPO1 - until: BPO2 + until: BPO5 - label: amsterdam from: Amsterdam until: Amsterdam diff --git a/.github/scripts/create_release_tarball.py b/.github/scripts/create_release_tarball.py index 1f4e7d47a2f..c63b3fe7c60 100644 --- a/.github/scripts/create_release_tarball.py +++ b/.github/scripts/create_release_tarball.py @@ -7,11 +7,11 @@ """ Create a release tarball from a merged fixture directory. -Archive all ``.json`` and ``.ini`` files under a ``fixtures/`` prefix, +Archive all `.json` and `.ini` files under a `fixtures/` prefix, matching the structure produced by -``execution_testing.cli.pytest_commands.plugins.shared.fixture_output``. +`execution_testing.cli.pytest_commands.plugins.shared.fixture_output`. -Use ``pigz`` for parallel compression when available, otherwise fall +Use `pigz` for parallel compression when available, otherwise fall back to Python's built-in gzip. """ diff --git a/.github/scripts/generate_build_matrix.py b/.github/scripts/generate_build_matrix.py index de8b3b5144a..02fde3087dc 100644 --- a/.github/scripts/generate_build_matrix.py +++ b/.github/scripts/generate_build_matrix.py @@ -7,26 +7,37 @@ # ] # /// """ -Generate the build matrix for release fixture workflows. +Validate release inputs and generate the build matrix for release +fixture workflows. -Read `.github/configs/feature.yaml` and emit a flat JSON build matrix -suitable for ``strategy.matrix`` in GitHub Actions. +Usage: `generate_build_matrix.py [branch]`. -Features whose ``fill-params`` contain ``--until`` are split across the +First validate the dispatch inputs (see `validate_inputs`), then read +`.github/configs/feature.yaml` and emit a flat JSON build matrix suitable +for `strategy.matrix` in GitHub Actions. + +Features whose `fill-params` contain `--until` are split across the shared fork ranges defined in `.github/configs/fork-ranges.yaml`. -Features using ``--fork`` (single fork) produce a single unsplit entry. +Features using `--fork` (single fork) produce a single unsplit entry. """ import json import re import sys from pathlib import Path +from typing import NoReturn import yaml FEATURE_CONFIG = Path(".github/configs/feature.yaml") FORK_RANGES_CONFIG = Path(".github/configs/fork-ranges.yaml") +VERSION_RE = re.compile(r"^v[0-9]+\.[0-9]+\.[0-9]+$") + +# Devnet release branches follow `devnets//`, e.g. +# `devnets/bal/7` or `devnets/glamsterdam/6`; `` is the devnet number. +DEVNET_BRANCH_RE = re.compile(r"^devnets/[^/]+/([0-9]+)$") + # Canonical fork ordering used to filter fork ranges per feature. FORK_ORDER = [ "Frontier", @@ -49,6 +60,9 @@ "Osaka", "BPO1", "BPO2", + "BPO3", + "BPO4", + "BPO5", "Amsterdam", ] @@ -61,11 +75,70 @@ def load_config(path: Path) -> dict: return yaml.safe_load(f) +def fail(message: str) -> NoReturn: + """Print an error to stderr and exit non-zero.""" + print(f"Error: {message}", file=sys.stderr) + sys.exit(1) + + +def validate_inputs(feature: str, version: str, branch: str) -> None: + """ + Validate the release dispatch inputs before building a matrix. + + Centralize the feature/version checks here so they are unit-testable + rather than living as inline bash in the release workflow. + + For `-devnet` releases the major version (`X` of `vX.Y.Z`) + must equal the devnet number encoded in the release branch, so a + `bal-devnet` release from `devnets/bal/7` must be tagged `v7.*.*`. + """ + if not feature: + fail("feature name is empty") + if not VERSION_RE.match(version): + fail(f"version '{version}' must match vX.Y.Z (e.g. v20.0.0)") + + # A bare `devnet` has no friendly `-` prefix to tag with. + if feature in ("devnet", "-devnet"): + fail("devnet releases require a - prefix, e.g. bal-devnet") + + # `-devnet-`: the devnet index belongs in the version (X of + # vX.Y.Z), not in the feature name. + if "-devnet-" in feature: + suggested_feature, _, suggested_index = feature.rpartition("-") + fail( + "devnet index must go in 'version', not the feature name; " + f"did you mean feature={suggested_feature} " + f"version=v{suggested_index}.0.0?" + ) + + if feature.endswith("-devnet"): + if not branch: + fail( + "devnet releases require a 'branch' input, " + "e.g. branch=devnets/bal/7" + ) + match = DEVNET_BRANCH_RE.match(branch) + if not match: + fail( + f"could not parse a devnet number from branch '{branch}' " + "(expected devnets//, e.g. devnets/bal/7)" + ) + devnet_number = int(match.group(1)) + major = int(version.lstrip("v").split(".")[0]) + if major != devnet_number: + minor_patch = version.split(".", 1)[1] + fail( + f"version major (v{major}) must equal the devnet number " + f"({devnet_number}) from branch '{branch}'; " + f"did you mean version=v{devnet_number}.{minor_patch}?" + ) + + def parse_until_fork(fill_params: str) -> str | None: """ - Extract the ``--until`` value from fill-params. + Extract the `--until` value from fill-params. - Return ``None`` when ``--fork`` is used instead (single-fork + Return `None` when `--fork` is used instead (single-fork feature that should not be split). """ if re.search(r"--fork\b", fill_params): @@ -76,9 +149,9 @@ def parse_until_fork(fill_params: str) -> str | None: def applicable_ranges(fork_ranges: list[dict], until_fork: str) -> list[dict]: """ - Return fork ranges whose ``from`` is at or before *until_fork*. + Return fork ranges whose `from` is at or before *until_fork*. - Clamp the last applicable range's ``until`` to *until_fork* so we + Clamp the last applicable range's `until` to *until_fork* so we never fill beyond the feature's declared boundary. """ limit = FORK_INDEX[until_fork] @@ -130,26 +203,38 @@ def build_matrix( def main() -> None: - """Entry point.""" - if len(sys.argv) != 2: + """Validate the inputs and print the build matrix to stdout.""" + args = sys.argv[1:] + if len(args) < 2: print( - "Usage: generate_build_matrix.py ", + "Usage: generate_build_matrix.py [branch]", file=sys.stderr, ) sys.exit(1) + name = args[0] + version = args[1] + branch = args[2] if len(args) > 2 else "" + + validate_inputs(name, version, branch) + config = load_config(FEATURE_CONFIG) fork_ranges = load_config(FORK_RANGES_CONFIG) or [] - name = sys.argv[1] - if name not in config or not isinstance(config[name], dict): + # `-devnet` releases (e.g. bal-devnet) share the `devnet` entry, + # while keeping their friendly name in the matrix and artifact outputs. + lookup = ( + "devnet" if name.endswith("-devnet") and "devnet" in config else name + ) + + if lookup not in config or not isinstance(config[lookup], dict): print( - f"Error: feature '{name}' not found in {FEATURE_CONFIG}.", + f"Error: feature '{lookup}' not found in {FEATURE_CONFIG}.", file=sys.stderr, ) sys.exit(1) - build, labels = build_matrix(config[name], name, fork_ranges) + build, labels = build_matrix(config[lookup], name, fork_ranges) print(f"build_matrix={json.dumps(build)}") print(f"feature_name={name}") diff --git a/.github/scripts/get_release_props.py b/.github/scripts/get_release_props.py index 577979c31bf..f8505675ed3 100644 --- a/.github/scripts/get_release_props.py +++ b/.github/scripts/get_release_props.py @@ -22,8 +22,14 @@ def get_release_props(release: str) -> None: with open(RELEASE_PROPS_FILE) as f: data = yaml.safe_load(f) if release not in data: - print(f"Error: Release {release} not found in {RELEASE_PROPS_FILE}.") - sys.exit(1) + # `-devnet` releases (e.g. bal-devnet) share the `devnet` entry. + if release.endswith("-devnet") and "devnet" in data: + release = "devnet" + else: + print( + f"Error: Release {release} not found in {RELEASE_PROPS_FILE}." + ) + sys.exit(1) print("\n".join(f"{key}={value}" for key, value in data[release].items())) diff --git a/.github/scripts/merge_index_files.py b/.github/scripts/merge_index_files.py index ea41b299a10..4f5e31e3ca0 100644 --- a/.github/scripts/merge_index_files.py +++ b/.github/scripts/merge_index_files.py @@ -3,7 +3,7 @@ Merge multiple .meta/index.json files from split fixture builds. Accept fixture directories as arguments, load each directory's -``.meta/index.json``, merge them via ``IndexFile.merge()``, and write +`.meta/index.json`, merge them via `IndexFile.merge()`, and write the result to the specified output path. """ diff --git a/.github/scripts/tests/test_release_scripts.py b/.github/scripts/tests/test_release_scripts.py index 6e24864bfed..a74c4c67c08 100644 --- a/.github/scripts/tests/test_release_scripts.py +++ b/.github/scripts/tests/test_release_scripts.py @@ -1,7 +1,7 @@ """ Test the CI release helper scripts. -Each test invokes the script via ``uv run`` to validate the actual CLI +Each test invokes the script via `uv run` to validate the actual CLI interface, matching how GitHub Actions calls them. """ @@ -43,12 +43,12 @@ class TestGenerateBuildMatrix: def test_split_feature_produces_entries_per_range(self): """Verify a split feature expands into one entry per range.""" - result = run_script(BUILD_MATRIX_SCRIPT, "mainnet") + result = run_script(BUILD_MATRIX_SCRIPT, "tests", "v24.0.0") assert result.returncode == 0 out = parse_matrix_output(result.stdout) matrix = json.loads(out["build_matrix"]) assert len(matrix) > 1 - assert out["feature_name"] == "mainnet" + assert out["feature_name"] == "tests" assert out["combine_labels"] != "" labels = [e["label"] for e in matrix] assert all(lbl != "" for lbl in labels) @@ -57,7 +57,7 @@ def test_split_feature_produces_entries_per_range(self): def test_unsplit_feature_produces_single_entry(self): """Verify a feature without fork-ranges produces one entry.""" - result = run_script(BUILD_MATRIX_SCRIPT, "benchmark") + result = run_script(BUILD_MATRIX_SCRIPT, "benchmark", "v24.0.0") assert result.returncode == 0 out = parse_matrix_output(result.stdout) matrix = json.loads(out["build_matrix"]) @@ -68,19 +68,21 @@ def test_unsplit_feature_produces_single_entry(self): assert matrix[0]["from_fork"] == "" assert matrix[0]["until_fork"] == "" - def test_feature_only_can_be_requested_explicitly(self): - """Verify feature_only entries work when named directly.""" - result = run_script(BUILD_MATRIX_SCRIPT, "glamsterdam-devnet") + def test_devnet_name_resolves_to_shared_feature(self): + """Verify a -devnet name resolves to the devnet feature.""" + result = run_script( + BUILD_MATRIX_SCRIPT, "bal-devnet", "v7.0.0", "devnets/bal/7" + ) assert result.returncode == 0 out = parse_matrix_output(result.stdout) matrix = json.loads(out["build_matrix"]) - assert len(matrix) == 1 - assert matrix[0]["feature"] == "glamsterdam-devnet" - assert out["combine_labels"] == "" + assert out["feature_name"] == "bal-devnet" + # Entries keep the friendly name, not the shared "devnet" key. + assert all(e["feature"] == "bal-devnet" for e in matrix) def test_unknown_feature_fails(self): """Verify error exit for unknown feature name.""" - result = run_script(BUILD_MATRIX_SCRIPT, "nonexistent") + result = run_script(BUILD_MATRIX_SCRIPT, "nonexistent", "v1.0.0") assert result.returncode == 1 assert "not found" in result.stderr @@ -92,7 +94,7 @@ def test_no_args_fails(self): def test_output_is_valid_github_actions_format(self): """Verify output lines are key=value for GITHUB_OUTPUT.""" - result = run_script(BUILD_MATRIX_SCRIPT, "mainnet") + result = run_script(BUILD_MATRIX_SCRIPT, "tests", "v24.0.0") assert result.returncode == 0 lines = result.stdout.strip().splitlines() assert len(lines) == 3 @@ -101,6 +103,74 @@ def test_output_is_valid_github_actions_format(self): assert lines[2].startswith("combine_labels=") +class TestValidateInputs: + """Test the release input validation in generate_build_matrix.py.""" + + def test_bad_version_fails(self): + """Verify a non vX.Y.Z version is rejected.""" + result = run_script(BUILD_MATRIX_SCRIPT, "tests", "24.0.0") + assert result.returncode == 1 + assert "must match vX.Y.Z" in result.stderr + + def test_empty_feature_fails(self): + """Verify an empty feature name is rejected.""" + result = run_script(BUILD_MATRIX_SCRIPT, "", "v1.0.0") + assert result.returncode == 1 + assert "feature name is empty" in result.stderr + + def test_bare_devnet_fails(self): + """Verify a bare `devnet` feature name is rejected.""" + result = run_script( + BUILD_MATRIX_SCRIPT, "devnet", "v7.0.0", "devnets/bal/7" + ) + assert result.returncode == 1 + assert "require a - prefix" in result.stderr + + def test_devnet_index_in_feature_name_fails(self): + """Verify `-devnet-` is rejected with a suggestion.""" + result = run_script( + BUILD_MATRIX_SCRIPT, "bal-devnet-7", "v7.0.0", "devnets/bal/7" + ) + assert result.returncode == 1 + assert "did you mean feature=bal-devnet version=v7.0.0" in ( + result.stderr + ) + + def test_devnet_without_branch_fails(self): + """Verify a `-devnet` release requires a branch.""" + result = run_script(BUILD_MATRIX_SCRIPT, "bal-devnet", "v7.0.0") + assert result.returncode == 1 + assert "require a 'branch' input" in result.stderr + + def test_devnet_branch_wrong_shape_fails(self): + """Verify a branch outside `devnets//` is rejected.""" + result = run_script( + BUILD_MATRIX_SCRIPT, "bal-devnet", "v7.0.0", "bal-devnet-7" + ) + assert result.returncode == 1 + assert "could not parse a devnet number" in result.stderr + + def test_devnet_major_must_match_branch_number(self): + """Verify the major version must equal the branch devnet number.""" + result = run_script( + BUILD_MATRIX_SCRIPT, "bal-devnet", "v3.0.0", "devnets/bal/7" + ) + assert result.returncode == 1 + assert "must equal the devnet number" in result.stderr + + def test_devnet_matching_major_passes(self): + """Verify a major equal to the branch devnet number passes.""" + result = run_script( + BUILD_MATRIX_SCRIPT, + "glamsterdam-devnet", + "v6.0.0", + "devnets/glamsterdam/6", + ) + assert result.returncode == 0 + out = parse_matrix_output(result.stdout) + assert out["feature_name"] == "glamsterdam-devnet" + + class TestCreateReleaseTarball: """Test create_release_tarball.py.""" diff --git a/.github/workflows/release_fixture_feature.yaml b/.github/workflows/release_fixtures.yaml similarity index 53% rename from .github/workflows/release_fixture_feature.yaml rename to .github/workflows/release_fixtures.yaml index 2f333ed6ff9..7ac44ac4f66 100644 --- a/.github/workflows/release_fixture_feature.yaml +++ b/.github/workflows/release_fixtures.yaml @@ -1,10 +1,32 @@ name: Create Fixture Release on: - push: - tags: - - "tests-*@v*" workflow_dispatch: + inputs: + feature: + description: "Feature name, e.g. tests, benchmark, bal-devnet" + required: true + type: string + version: + description: "Release version, e.g. v20.0.0" + required: true + type: string + branch: + description: "Branch to release from, e.g. devnets/bal/7 (required for *-devnet features)" + required: false + type: string + evm: + description: "Override the evm impl (e.g. geth, evmone). Defaults to the feature's evm-type." + required: false + type: string + evm_repo: + description: "Override the t8n tool repo (e.g. ethereum/go-ethereum)" + required: false + type: string + evm_ref: + description: "Override the t8n tool branch / tag / commit" + required: false + type: string jobs: setup: @@ -13,19 +35,37 @@ jobs: build_matrix: ${{ steps.matrix.outputs.build_matrix }} feature_name: ${{ steps.matrix.outputs.feature_name }} combine_labels: ${{ steps.matrix.outputs.combine_labels }} + target_sha: ${{ steps.target_sha.outputs.sha }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: false + ref: ${{ inputs.branch }} + - name: Resolve target SHA + id: target_sha + shell: bash + run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" - uses: ./.github/actions/setup-uv - - name: Generate build matrix + - name: Validate input and generate build matrix id: matrix shell: bash + env: + INPUT_FEATURE: ${{ inputs.feature }} + INPUT_VERSION: ${{ inputs.version }} + INPUT_BRANCH: ${{ inputs.branch }} + INPUT_EVM: ${{ inputs.evm }} run: | - FEATURE_PREFIX="${GITHUB_REF_NAME//@*/}" - FEATURE_NAME="${FEATURE_PREFIX#tests-}" - uv run -q .github/scripts/generate_build_matrix.py "$FEATURE_NAME" >> "$GITHUB_OUTPUT" + # An `evm` override must name a key in evm.yaml; the feature, + # version and devnet-branch validation lives in (and is unit-tested + # via) generate_build_matrix.py. + if [ -n "$INPUT_EVM" ] && ! grep -qE "^${INPUT_EVM}:" .github/configs/evm.yaml; then + echo "::error::evm '$INPUT_EVM' is not a key in .github/configs/evm.yaml" + exit 1 + fi + + uv run -q .github/scripts/generate_build_matrix.py \ + "$INPUT_FEATURE" "$INPUT_VERSION" "$INPUT_BRANCH" >> "$GITHUB_OUTPUT" build: name: fill (${{ matrix.label || matrix.feature }}) @@ -40,26 +80,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: true - - - name: Fetch lllc - uses: ./.github/actions/fetch-binary - with: - version: v1.0.0 - repo_owner: felix314159 - repo_name: lllc-custom - remote_name: lllc - binary_name: lllc - expected_sha256: 865a0d5379acb3b5471337b5dcf686a2dd71587c6b65b9da6c963de627e0b300 - - - name: Fetch Solidity - uses: ./.github/actions/fetch-binary - with: - version: v0.8.24 - repo_owner: ethereum - repo_name: solidity - remote_name: solc-static-linux - binary_name: solc - expected_sha256: fb03a29a517452b9f12bcf459ef37d0a543765bb3bbc911e70a87d6a37c30d5f + ref: ${{ needs.setup.outputs.target_sha }} - uses: ./.github/actions/build-fixtures with: @@ -67,6 +88,9 @@ jobs: from_fork: ${{ matrix.from_fork }} until_fork: ${{ matrix.until_fork }} split_label: ${{ matrix.label }} + evm: ${{ inputs.evm }} + evm_repo: ${{ inputs.evm_repo }} + evm_ref: ${{ inputs.evm_ref }} combine: name: combine (${{ needs.setup.outputs.feature_name }}) @@ -77,6 +101,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: false + ref: ${{ needs.setup.outputs.target_sha }} - uses: ./.github/actions/setup-uv - name: Install pigz run: sudo apt-get install -y pigz @@ -112,19 +137,28 @@ jobs: - name: Free disk space run: rm -rf split_artifacts/ - name: Create release tarball + id: tarball shell: bash + env: + FEATURE_NAME: ${{ needs.setup.outputs.feature_name }} run: | - uv run -q .github/scripts/create_release_tarball.py combined fixtures_${{ needs.setup.outputs.feature_name }}.tar.gz + if [ "$FEATURE_NAME" = "tests" ]; then + TARBALL="fixtures.tar.gz" + else + TARBALL="fixtures_${FEATURE_NAME}.tar.gz" + fi + uv run -q .github/scripts/create_release_tarball.py combined "$TARBALL" + echo "path=$TARBALL" >> "$GITHUB_OUTPUT" - name: Upload combined fixture tarball uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: fixtures_${{ needs.setup.outputs.feature_name }} - path: fixtures_${{ needs.setup.outputs.feature_name }}.tar.gz + path: ${{ steps.tarball.outputs.path }} release: runs-on: ubuntu-latest needs: [setup, build, combine] - if: always() && needs.build.result == 'success' && (needs.combine.result == 'success' || needs.combine.result == 'skipped') && startsWith(github.ref, 'refs/tags/tests-') + if: always() && needs.build.result == 'success' && (needs.combine.result == 'success' || needs.combine.result == 'skipped') permissions: contents: write steps: @@ -132,6 +166,7 @@ jobs: with: submodules: false fetch-depth: 0 + ref: ${{ needs.setup.outputs.target_sha }} - name: Download release artifacts shell: bash @@ -142,38 +177,49 @@ jobs: env: GH_TOKEN: ${{ github.token }} - - name: Draft release on EELS (canonical) + - name: Draft release + shell: bash + env: + INPUT_FEATURE: ${{ inputs.feature }} + INPUT_VERSION: ${{ inputs.version }} + TARGET_SHA: ${{ needs.setup.outputs.target_sha }} + GH_TOKEN: ${{ github.token }} run: | - FEATURE_PREFIX="${TAG_NAME%%@*}" + RELEASE_NAME="${INPUT_FEATURE}@${INPUT_VERSION}" + # Git tags namespace fixture releases under `tests-@`, except + # the default `tests` feature which tags as `tests@vX.Y.Z` (no doubled + # prefix). The release title matches the git tag. + if [ "$INPUT_FEATURE" = "tests" ]; then + TAG_NAME="$RELEASE_NAME" + TAG_GLOB="tests@v*" + else + TAG_NAME="tests-${RELEASE_NAME}" + TAG_GLOB="tests-${INPUT_FEATURE}@v*" + fi + + # Use the prior release of the same feature as the notes baseline. PREV_TAG=$( - git tag --list "${FEATURE_PREFIX}@v*" --sort=-v:refname \ + git tag --list "$TAG_GLOB" --sort=-v:refname \ | grep -v "^${TAG_NAME}$" \ | head -n 1 \ || true ) - RELEASE_ARGS=(--draft --generate-notes) - if [ "$FEATURE_NAME" != "mainnet" ]; then - RELEASE_ARGS+=(--prerelease) - fi + + # Pin the tag to the single SHA resolved in `setup` so the tag + # lands on the same commit every job built from. + TARGET="$TARGET_SHA" + + # All fixture releases are drafted as prereleases; publish manually. + RELEASE_ARGS=( + --draft + --prerelease + --generate-notes + --target "$TARGET" + --title "$TAG_NAME" + ) if [ -n "$PREV_TAG" ]; then RELEASE_ARGS+=(--notes-start-tag "$PREV_TAG") fi - gh release create "$TAG_NAME" "${RELEASE_ARGS[@]}" ./artifacts/**/*.tar.gz - env: - TAG_NAME: ${{ github.ref_name }} - FEATURE_NAME: ${{ needs.setup.outputs.feature_name }} - GH_TOKEN: ${{ github.token }} - - name: Draft release on EEST (mirror) - run: | - EEST_TAG="${TAG_NAME#tests-}" - RELEASE_ARGS=(--repo ethereum/execution-spec-tests --draft) - if [ "$FEATURE_NAME" != "mainnet" ]; then - RELEASE_ARGS+=(--prerelease) - fi - RELEASE_ARGS+=(--notes "This release is mirrored from [ethereum/execution-specs ${TAG_NAME}](https://github.com/ethereum/execution-specs/releases/tag/${TAG_NAME}).") - gh release create "$EEST_TAG" "${RELEASE_ARGS[@]}" ./artifacts/**/*.tar.gz - env: - TAG_NAME: ${{ github.ref_name }} - FEATURE_NAME: ${{ needs.setup.outputs.feature_name }} - GH_TOKEN: ${{ secrets.EEST_RELEASE_TOKEN }} + # Creates the tag on the target commit and the release on success. + gh release create "$TAG_NAME" "${RELEASE_ARGS[@]}" ./artifacts/**/*.tar.gz diff --git a/docs/dev/releasing_tests.md b/docs/dev/releasing_tests.md new file mode 100644 index 00000000000..0259159c0f8 --- /dev/null +++ b/docs/dev/releasing_tests.md @@ -0,0 +1,110 @@ +# Releasing Test Fixtures + +This page covers the mechanics of cutting a test fixture release. For the release types, +their versioning, and consumption guidance, see +[EELS Fixture Releases](../running_tests/releases.md). + +Fixture releases are produced by manually dispatching the +[`release_fixtures.yaml`](https://github.com/ethereum/execution-specs/blob/master/.github/workflows/release_fixtures.yaml) +workflow. There is no tag to push by hand. The workflow builds the fixtures and, only on +success, creates the tag and the (draft) GitHub release. + +```bash +gh workflow run release_fixtures.yaml -f feature= -f version=vX.Y.Z [-f branch=] +``` + +## Inputs + +| Input | Required | Description | +| ---------- | ----------------- | ---------------------------------------------------------------------------------------------------- | +| `feature` | yes | Feature name, e.g. `tests`, `benchmark`, or a `-devnet` name. | +| `version` | yes | Release version `vX.Y.Z` (validated against `^v[0-9]+\.[0-9]+\.[0-9]+$`). Tagged as `tests-@` (the `tests` feature tags as `tests@`). | +| `branch` | devnet only | Branch to build and release from. Optional for non-devnet features; **required** for devnet releases. | +| `evm` | no | Override the evm impl (e.g. `geth`, `evmone`). Defaults to the feature's `evm-type` in `feature.yaml`. | +| `evm_repo` | no | Override the t8n tool repo (e.g. `ethereum/go-ethereum`). | +| `evm_ref` | no | Override the t8n tool branch / tag / commit. | + +`` must be a key in +[`.github/configs/feature.yaml`](https://github.com/ethereum/execution-specs/blob/master/.github/configs/feature.yaml) +(e.g. `tests`, `benchmark`), or a `-devnet` name that resolves to the shared `devnet` +feature. + +Input validation runs in +[`generate_build_matrix.py`](https://github.com/ethereum/execution-specs/blob/master/.github/scripts/generate_build_matrix.py) +(unit-tested) before any fixtures are built, and fails fast on: + +- an empty `feature` or a `version` that is not `vX.Y.Z`; +- a bare `devnet` feature name (must carry a `-` prefix, e.g. `bal-devnet`); +- a `-devnet-` feature name — the devnet index belongs in the `version` major, not + the feature name (so `feature=bal-devnet-7` is rejected in favour of + `feature=bal-devnet version=v7.0.0`); +- a `*-devnet` release missing a `branch`, a `branch` outside the `devnets//` shape + (e.g. `devnets/bal/7`), or a `version` major that does not equal the devnet number `` in + the branch (so `feature=bal-devnet branch=devnets/bal/7` must use `version=v7.*.*`). + +## Devnet releases + +Devnet releases must use a `-devnet` feature name (e.g. `feature=bal-devnet`) and must +specify the branch to release from. Devnet branches follow the `devnets//` scheme +(e.g. `devnets/bal/7`), and the `version` major must match the devnet number `` in the +branch: + +```bash +gh workflow run release_fixtures.yaml -f feature=bal-devnet -f version=v7.0.0 -f branch=devnets/bal/7 +``` + +## What the workflow produces + +On success the workflow: + +1. Builds `fixtures_.tar.gz` (the `tests` feature builds `fixtures.tar.gz`) for the + resolved feature (per its `evm-type` and `fill-params` in `feature.yaml`). +2. Creates the git tag `tests-@vX.Y.Z` (the `tests` feature tags as `tests@vX.Y.Z`, + no doubled prefix) on the released commit (the SHA resolved once from the `branch` HEAD when + given, otherwise the dispatch commit). +3. Publishes a **draft pre-release** to + [`ethereum/execution-specs`](https://github.com/ethereum/execution-specs/releases), titled + the same as the git tag, with the fixture tarball(s) attached. + +| Example dispatch | Git tag | Release title | Artifact | +| ---------------- | ------- | ------------- | -------- | +| `feature=tests version=v24.0.0` | `tests@v24.0.0` | `tests@v24.0.0` | `fixtures.tar.gz` | +| `feature=bal-devnet version=v7.0.0 branch=devnets/bal/7` | `tests-bal-devnet@v7.0.0` | `tests-bal-devnet@v7.0.0` | `fixtures_bal-devnet.tar.gz` | + +The release is created as a draft; review and publish it from the GitHub releases page. + +## Cutting a release + +1. **Pick the next version** per the + [Versioning Scheme](../running_tests/releases.md#versioning-scheme) for the feature you're + releasing (e.g. the next `tests` release after `tests@v24.1.0` is `tests@v24.1.1` for a + non-breaking/new-tests bump, or `tests@v24.2.0` for a consensus-breaking spec change). +2. **Dispatch the workflow** from the + [Actions tab](https://github.com/ethereum/execution-specs/actions/workflows/release_fixtures.yaml) + or via the CLI: + + ```bash + gh workflow run release_fixtures.yaml -f feature=tests -f version=v24.1.1 + # devnet releases additionally require the branch (major must match its number): + gh workflow run release_fixtures.yaml -f feature=bal-devnet -f version=v7.0.0 -f branch=devnets/bal/7 + ``` + +3. **Wait for the build to succeed.** On success the workflow creates the + `tests-@vX.Y.Z` tag on the target commit and drafts the GitHub release with the + fixture tarball attached. If any job fails, no tag or release is created — fix the cause + and re-dispatch. +4. **Review and publish the draft.** Open the draft on the + [releases page](https://github.com/ethereum/execution-specs/releases), check the + auto-generated notes (anchored at the prior release on the same feature via + `--notes-start-tag`), and click *Publish release* when ready. + +!!! tip "Release features opt into all fixture formats via `feature.yaml`" + Tarball output (`.tar.gz`) does not by itself include the pre-allocation group formats + (`BlockchainEngineXFixture`, `BlockchainEngineStatefulFixture`). A release feature + requests them by adding `--generate-all-formats` to its `fill-params` in + `.github/configs/feature.yaml`: + ```console + # .tar.gz no longer auto-enables all formats (changed in #2702); request + # them explicitly with --generate-all-formats + uv run fill --generate-all-formats --output=fixtures.tar.gz tests/ + ``` diff --git a/docs/navigation.md b/docs/navigation.md index 20bf2f8cd3b..db82611d077 100644 --- a/docs/navigation.md +++ b/docs/navigation.md @@ -46,7 +46,7 @@ * [Filling Stateful Benchmark Fixtures](filling_tests/fill_stateful.md) * [Running Tests](running_tests/index.md) * [Methods of Running Tests](running_tests/running.md) - * [EEST Fixture Releases](running_tests/releases.md) + * [EELS Fixture Releases](running_tests/releases.md) * [Fuzzer Bridge](writing_tests/fuzzer_bridge.md) * [Test Fixture Specifications](running_tests/test_formats/index.md) * [State Tests](running_tests/test_formats/state_test.md) @@ -83,6 +83,7 @@ * [Running Github Actions Locally](dev/test_actions_locally.md) * [Dependencies and Packaging](dev/deps_and_packaging.md) * [Releasing](dev/releasing.md) + * [Releasing Test Fixtures](dev/releasing_tests.md) * [Library Reference](library/index.md) * [EEST CLI Tools](library/cli/index.md) * [eest](library/cli/eest.md) diff --git a/docs/running_tests/releases.md b/docs/running_tests/releases.md index 9907c64a81c..7d82e68821a 100644 --- a/docs/running_tests/releases.md +++ b/docs/running_tests/releases.md @@ -1,22 +1,89 @@ -# EEST Fixture Releases - -## Formats and Release Layout - -@ethereum/execution-specs releases contain JSON test fixtures in various formats. Note that transaction type tests are executed directly from Python source using the [`execute`](./execute/index.md) command. +# EELS Fixture Releases + +Test fixtures are published as feature-scoped releases on the +[`ethereum/execution-specs`](https://github.com/ethereum/execution-specs/releases) +repository: `tests@vX.Y.Z`, `-devnet@vX.Y.Z`, and `benchmark@vX.Y.Z`. Each release is +a self-contained `.tar.gz` of JSON fixtures that execution clients consume in CI. + +This page describes the release types, their versioning, the fixture formats they contain, +and how to consume them. To cut a new release, see +[Releasing Test Fixtures](../dev/releasing_tests.md). + +!!! note "Fixture releases vs. the spec-package `vX.Y.Z` tags" + `ethereum/execution-specs` also publishes Python spec package releases tagged + `vX.Y.Z` (e.g. [`v2.20.0`](https://github.com/ethereum/execution-specs/releases/tag/v2.20.0)). + Those contain no test fixtures, only the executable specification package. + Fixture releases are the feature-scoped tags described on this page, and are never + attached to the `vX.Y.Z` package tags. Every fixture tag starts with `tests` + (`tests@vX.Y.Z`, or `tests-@vX.Y.Z` for the other features), which is the + quickest way to tell the two apart on the releases page. + +## Test Release Types + +Fixtures are released as independent types. Each type has its own tag namespace, artifact, +and cadence. + +| Type | Release name | Artifact | Scope | Built from | +| --------- | ---------------------- | ------------------------------- | ------------------------------------------------------------------------------ | ----------------------- | +| Tests | `tests@vX.Y.Z` | `fixtures.tar.gz` | All forks, all tests (eventually including `ethereum/tests` state tests) | latest `forks/*` branch | +| Devnet | `-devnet@vX.Y.Z` | `fixtures_-devnet.tar.gz` | All forks, all tests, for an upcoming-fork feature under active devnet testing | the devnet branch | +| Benchmark | `benchmark@vX.Y.Z` | `fixtures_benchmark.tar.gz` | EVM benchmarking tests | latest `forks/*` branch | + +- "Tests" releases track clients' production branches and are tagged frequently (roughly + once or twice a week). They are the "must pass" release for mainnet CI, and supersede the + old `fixtures_stable` / `fixtures_develop` artifacts. +- "Devnet" releases target a specific feature under active development (e.g. `bal-devnet`). + They are advisory/non-blocking and may not yet cover every EIP; see the corresponding + release notes for the coverage provided. +- "Benchmark" (and, in future, zkEVM) releases are produced separately for their + specialized consumers. + +## Versioning Scheme + +Each release has a name of the form `@v..` (e.g. `bal-devnet@v7.0.0`), +which is the identifier `consume cache` accepts. The git tag, and the matching GitHub release +title, add a `tests-` prefix to keep fixture releases separate from the spec-package `vX.Y.Z` +tags, so the release `bal-devnet@v7.0.0` is tagged `tests-bal-devnet@v7.0.0`. The default +`tests` feature is the exception, tagging as `tests@v..` directly with no doubled +prefix. + +`X` identifies the fork or devnet a release targets; `Y` and `Z` order changes within that +target: + +| Component | Tests | Devnet | Benchmark | +| --------- | --------------------------------------------------- | ---------------------------------------------------- | ------------------------------- | +| `X` | Fork number | Devnet number | Fork number (mirrors target) | +| `Y` | Consensus-breaking spec change targeting fork `X` | Consensus-breaking spec change targeting devnet `X` | Mirrors the targeted feature | +| `Z` | Non-breaking change (refactor), new/modified tests | Non-breaking change (refactor), new/modified tests | Moves freely at its own pace | + +A client targeting fork/devnet `X` should take the release with major == `X`, the latest +minor, and ideally the latest patch. The major alone tells you whether a release is +relevant to you, and a bump in `Y` (e.g. `v7.0.0` to `v7.1.0`) signals a consensus-breaking +spec change in your target, so read the release notes before adopting it. + +This also lets two devnets of the same feature be maintained in parallel (e.g. `v3.0.1` +alongside `v7.0.0`) without ambiguity, the same way `2.x` and `3.x` coexist under semver. + +## Fixture Formats + +Fixture releases contain JSON test fixtures in various formats. Note that transaction type +tests are executed directly from Python source using the [`execute`](./execute/index.md) +command. | Format | Consumed by the client | Location in `.tar.gz` release | | -------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- | | [State Tests](./test_formats/state_test.md) | - directly via a `statetest`-like command
(e.g., [go-ethereum/cmd/evm/staterunner.go](https://github.com/ethereum/go-ethereum/blob/4bb097b7ffc32256791e55ff16ca50ef83c4609b/cmd/evm/staterunner.go)) | `./fixtures/state_tests/` | | [Blockchain Tests](./test_formats/blockchain_test.md) | - directly via a `blocktest`-like command
(e.g., [go-ethereum/cmd/evm/blockrunner.go](https://github.com/ethereum/go-ethereum/blob/4bb097b7ffc32256791e55ff16ca50ef83c4609b/cmd/evm/blockrunner.go))
- using the [eels/consume-rlp Simulator](./running.md#rlp) via block import | `./fixtures/blockchain_tests/` | | [Blockchain Engine Tests](./test_formats/blockchain_test_engine.md) | - using the [eels/consume-engine Simulator](./running.md#engine) and the Engine API | `./fixtures/blockchain_tests_engine/` | +| [Blockchain Engine X Tests](./test_formats/blockchain_test_engine_x.md) | - using the [eels/consume-enginex Simulator](./running.md#enginex) and the Engine API, reusing a client per pre-allocation group | `./fixtures/blockchain_tests_engine_x/` | | [Transaction Tests](./test_formats/transaction_test.md) | - using a new simulator coming soon | None; executed directly from Python source,
using a release tag | -| Blob Transaction Tests | - using the [eels/execute-blobs Simulator](./execute/hive.md#the-eelsexecute-blobs-simulator) and | None; executed directly from Python source,
using a release tag | +| Blob Transaction Tests | - using the [eels/execute-blobs Simulator](./execute/hive.md#the-eelsexecute-blobs-simulator) | None; executed directly from Python source,
using a release tag | ## Fixture Output Directory Structure -Inside each format directory, fixtures are grouped by **target fork**. +Inside each format directory, fixtures are grouped by target fork. -The top-level subdirectory identifies the fork **under test**. Below it, +The top-level subdirectory identifies the fork under test. Below it, fixtures mirror the `./tests/` source layout: each directory corresponds to the fork where the functionality was originally introduced. Because tests declare `valid_from`, a single target fork directory contains @@ -77,51 +144,38 @@ fixtures/ └── benchmark/compute/... ``` -## Release URLs and Tarballs - -### Versioning Scheme - -EEST framework and test sources and fixture releases are tagged use a semantic versioning scheme, `>v..` as following: - -- ``: An existing fixture format has changed (potentially breaking change). Action must be taken by client teams to ensure smooth upgrade to the new format. -- ``: Additional coverage (new tests, or a new format) have been added to the release. -- ``: A bug-fix release; an error in the tests or framework has been patched. - -Please see below for an explanation of the optional `` that is used in pre-releases. - -### Standard Releases +## Pinning Guidance -Releases are published on the @ethereum/execution-specs [releases](https://github.com/ethereum/execution-specs/releases) page. Standard releases are tagged using the format `vX.Y.Z` (they don't have a ``). +Mapped to a typical client CI setup: -For standard releases, two tarballs are available: +- **Blocking gate (current + past forks)**: Pin a specific `tests@vX.Y.Z` for reproducible, + no-rug-pull CI on your `master`/production branch, or follow the latest `tests` release if + a moving target is acceptable. This supersedes the old `fixtures_develop` / `fixtures_stable` + artifacts. +- **Non-blocking gate (next fork)**: Use the current `-devnet@vX.Y.Z` release for the + upcoming fork's active devnet (e.g. `bal-devnet@vX.Y.Z`). Treat it as advisory, since devnet + coverage changes rapidly and should not block merges. -| Release Artifact | Fork/feature scope | -| ------------------------- | ----------------------------------------------------------------------- | -| `fixtures_stable.tar.gz` | Tests for all forks up to and including the last deployed ("stable") mainnet fork ("must pass") | -| `fixtures_develop.tar.gz` | Tests for all forks up to and including the last development fork | +!!! note "Devnet vs. tests overlap" + Devnet releases are filled for all forks/tests, so they overlap with the `tests` release. + If your blocking gate already runs a `tests` release, the devnet gate re-runs that shared + coverage. Deduplicating that overlap is a consumer-side concern handled when + resolving/consuming releases. -I.e., `fixtures_develop` are a superset of `fixtures_stable`. +## Downloading Releases -!!! tip "Release features opt into all fixture formats via `feature.yaml`" - Tarball output (`.tar.gz`) does not by itself include the pre-allocation group formats (`BlockchainEngineXFixture`, `BlockchainEngineStatefulFixture`). A release feature requests them by adding `--generate-all-formats` to its `fill-params` in `.github/configs/feature.yaml`: - ```console - uv run fill --generate-all-formats --output=fixtures_stable.tar.gz tests/ - ``` +The [`consume cache`](./consume/cache.md) command resolves EELS release and pre-release tags +to release URLs and downloads them. For example: -### Pre-Release and Devnet Releases - -Intermediate releases that target specific subsets of features or tests under active development are published at @ethereum/execution-specs [releases](https://github.com/ethereum/execution-specs/releases). - -These releases are tagged using the format `@vX.Y.Z`. - - -Examples: - -- [`fusaka-devnet-1@v1.0.0`](https://github.com/ethereum/execution-spec-tests/releases/tag/fusaka-devnet-1%40v1.0.0) - this fixture release contains tests adhering to the [Fusaka Devnet 1 spec](https://notes.ethereum.org/@ethpandaops/fusaka-devnet-1). -- [`benchmark@v0.0.3`](https://github.com/ethereum/execution-spec-tests/releases/tag/benchmark%40v0.0.3) - this fixture release contains tests specifically aimed at benchmarking EVMs. +```bash +uv run consume cache --input=tests@latest +uv run consume cache --input=bal-devnet@v7.0.0 +``` -Devnet releases should be treated as WIP and may not yet contain full test coverage (or even coverage for all EIPs). The coverage provided by these releases is detailed in the corresponding release notes. +Raw tarballs can also be fetched directly with the GitHub CLI: -### Help Downloading Releases +```bash +gh release download tests-bal-devnet@v7.0.0 --repo ethereum/execution-specs --pattern '*.tar.gz' +``` -The [`consume cache`](./consume/cache.md) command can be used to resolve EEST release and pre-release tags to release URLs and download them. +To create a release, see [Releasing Test Fixtures](../dev/releasing_tests.md). From 2d68578e587cf9e2b5c1af2f61ff4c22f646b184 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=94=A1=E4=BD=B3=E8=AA=A0=20Louis=20Tsai?= <72684086+LouisTsai-Csie@users.noreply.github.com> Date: Thu, 2 Jul 2026 03:05:23 +0800 Subject: [PATCH 072/233] feat(spec,tests): implement EIP-7997 (#3079) * feat(spec, spec-specs): EIP-7997 (#2802) * feat(spec, specs): EIP-7997 - Spec and basic tests * feat(spec-specs): EIP 7997 - More tests * bugfix(specs): Add logic to add 0x12 as a special contract outside the precompile range * feat(tests): 7997 - add a check at the transition, adds tests for prewarming, CALLCODE, and giving it a balance via SELFDESTRUCT * fix: address PR feedback * feat(spec): Use Arachnid create2 contract * feat(specs, spec-test): Remove unneeded code * feat(spec-tests): Remove unneeded tests * feat(test-specs): Add sstore metadata for gas pricing * fix(test-specs): Remove sstore metadata * feat(test-specs): Add Arachnid contract to system contracts * feat(test-specs): Add a BAL test, large initcode test, and an ef prefix test * fix(test-specs): remove unneeded pre_alloc_mutable decorators * Add Amsterdam config to test_execute_eth_config * fix: Uses storage.store_next and mark valid_from EIP7997 instead of Amsterdam * fix: don't hardcode gas values, call_opcode parametrization * fix: Remove Factory predeploy from system contracts * fix: Remove explicit set of gas_limit * lint * Whitespace * feat(tests): verify EIP-7997 factory nonce persists across fork-transition (#3077) Co-authored-by: Barnabas Busa * fix(tests): Review comments --------- Co-authored-by: kclowes Co-authored-by: Barnabas Busa Co-authored-by: marioevz --- .../forks/forks/eips/amsterdam/eip_7997.py | 57 ++ src/ethereum/forks/amsterdam/__init__.py | 5 +- .../__init__.py | 1 + .../spec.py | 31 + .../test_eip_mainnet.py | 62 ++ .../test_factory.py | 750 ++++++++++++++++++ .../test_fork_transition.py | 106 +++ 7 files changed, 1011 insertions(+), 1 deletion(-) create mode 100644 packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_7997.py create mode 100644 tests/amsterdam/eip7997_deterministic_factory_predeploy/__init__.py create mode 100644 tests/amsterdam/eip7997_deterministic_factory_predeploy/spec.py create mode 100644 tests/amsterdam/eip7997_deterministic_factory_predeploy/test_eip_mainnet.py create mode 100644 tests/amsterdam/eip7997_deterministic_factory_predeploy/test_factory.py create mode 100644 tests/amsterdam/eip7997_deterministic_factory_predeploy/test_fork_transition.py diff --git a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_7997.py b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_7997.py new file mode 100644 index 00000000000..f84c3efd71b --- /dev/null +++ b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_7997.py @@ -0,0 +1,57 @@ +""" +EIP-7997: Deterministic Factory Predeploy. + +Predeploy the Arachnid `CREATE2` factory at +`0x4e59b44847b379578588920ca78fbf26c0b4956c` so deterministic +deployments are available across chains without bootstrapping +transactions. + +https://eips.ethereum.org/EIPS/eip-7997 +""" + +from typing import Mapping + +from execution_testing.base_types import Address + +from ....base_fork import BaseFork + +DETERMINISTIC_FACTORY_PREDEPLOY_ADDRESS = ( + 0x4E59B44847B379578588920CA78FBF26C0B4956C +) +DETERMINISTIC_FACTORY_PREDEPLOY_BYTECODE = bytes.fromhex( + "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0" + "3601600081602082378035828234f58015156039578182fd" + "5b8082525050506014600cf3" +) + + +class EIP7997(BaseFork): + """EIP-7997 class.""" + + @classmethod + def deterministic_factory_predeploy_address(cls) -> Address | None: + """Return the EIP-7997 deterministic factory predeploy address.""" + return Address( + DETERMINISTIC_FACTORY_PREDEPLOY_ADDRESS, + label="DETERMINISTIC_FACTORY_PREDEPLOY_ADDRESS", + ) + + @classmethod + def pre_allocation(cls) -> Mapping: + """Pre-allocate the deterministic factory predeploy.""" + return { + DETERMINISTIC_FACTORY_PREDEPLOY_ADDRESS: { + "nonce": 1, + "code": DETERMINISTIC_FACTORY_PREDEPLOY_BYTECODE, + } + } | super(EIP7997, cls).pre_allocation() # type: ignore + + @classmethod + def pre_allocation_blockchain(cls) -> Mapping: + """Pre-allocate the deterministic factory predeploy.""" + return { + DETERMINISTIC_FACTORY_PREDEPLOY_ADDRESS: { + "nonce": 1, + "code": DETERMINISTIC_FACTORY_PREDEPLOY_BYTECODE, + } + } | super(EIP7997, cls).pre_allocation_blockchain() # type: ignore diff --git a/src/ethereum/forks/amsterdam/__init__.py b/src/ethereum/forks/amsterdam/__init__.py index e47bb43c1a3..3d13dd37dfa 100644 --- a/src/ethereum/forks/amsterdam/__init__.py +++ b/src/ethereum/forks/amsterdam/__init__.py @@ -1,16 +1,19 @@ """ -The Amsterdam fork ([EIP-7773]) includes block-level access lists. +The Amsterdam fork ([EIP-7773]) includes block-level access lists and the +deterministic ``CREATE2`` factory predeploy. ### Changes - [EIP-7928: Block-Level Access Lists][EIP-7928] - [EIP-7954: Increase Maximum Contract Size][EIP-7954] +- [EIP-7997: Deterministic Factory Predeploy][EIP-7997] ### 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 +[EIP-7997]: https://eips.ethereum.org/EIPS/eip-7997 """ from ethereum.fork_criteria import ForkCriteria, Unscheduled diff --git a/tests/amsterdam/eip7997_deterministic_factory_predeploy/__init__.py b/tests/amsterdam/eip7997_deterministic_factory_predeploy/__init__.py new file mode 100644 index 00000000000..ed408ffee14 --- /dev/null +++ b/tests/amsterdam/eip7997_deterministic_factory_predeploy/__init__.py @@ -0,0 +1 @@ +"""Tests for EIP-7997: Deterministic Factory Predeploy.""" diff --git a/tests/amsterdam/eip7997_deterministic_factory_predeploy/spec.py b/tests/amsterdam/eip7997_deterministic_factory_predeploy/spec.py new file mode 100644 index 00000000000..1fa0619a7ed --- /dev/null +++ b/tests/amsterdam/eip7997_deterministic_factory_predeploy/spec.py @@ -0,0 +1,31 @@ +"""Reference spec for [EIP-7997: Deterministic Factory Predeploy](https://eips.ethereum.org/EIPS/eip-7997).""" + +from dataclasses import dataclass + +from execution_testing import Bytes + + +@dataclass(frozen=True) +class ReferenceSpec: + """Reference specification.""" + + git_path: str + version: str + + +ref_spec_7997 = ReferenceSpec( + git_path="EIPS/eip-7997.md", + version="a0a7f5adb491fc6ad4b008f307899c30f348db22", +) + + +@dataclass(frozen=True) +class Spec: + """Constants from EIP-7997.""" + + FACTORY_ADDRESS: int = 0x4E59B44847B379578588920CA78FBF26C0B4956C + FACTORY_BYTECODE: Bytes = Bytes( + "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0" + "3601600081602082378035828234f58015156039578182fd" + "5b8082525050506014600cf3" + ) diff --git a/tests/amsterdam/eip7997_deterministic_factory_predeploy/test_eip_mainnet.py b/tests/amsterdam/eip7997_deterministic_factory_predeploy/test_eip_mainnet.py new file mode 100644 index 00000000000..33fdae13f77 --- /dev/null +++ b/tests/amsterdam/eip7997_deterministic_factory_predeploy/test_eip_mainnet.py @@ -0,0 +1,62 @@ +""" +abstract: Crafted tests for mainnet of +[EIP-7997: Deterministic Factory Predeploy](https://eips.ethereum.org/EIPS/eip-7997). +""" # noqa: E501 + +import pytest +from execution_testing import ( + Account, + Alloc, + Op, + StateTestFiller, + Storage, + Transaction, +) + +from .spec import Spec, ref_spec_7997 + +REFERENCE_SPEC_GIT_PATH = ref_spec_7997.git_path +REFERENCE_SPEC_VERSION = ref_spec_7997.version + +pytestmark = [pytest.mark.valid_at("EIP7997"), pytest.mark.mainnet] + +FACTORY = Spec.FACTORY_ADDRESS + + +def test_eip_7997( + state_test: StateTestFiller, + pre: Alloc, +) -> None: + """ + The factory bytecode is present at the canonical Arachnid factory + address with nonce 1. Verifies EVM-observable views of + the predeploy via `EXTCODESIZE`, `EXTCODEHASH` and `EXTCODECOPY`. + """ + storage = Storage() + extcodesize_slot = storage.store_next( + len(Spec.FACTORY_BYTECODE), "extcodesize" + ) + extcodehash_slot = storage.store_next( + Spec.FACTORY_BYTECODE.keccak256(), "extcodehash" + ) + extcodecopy_hash_slot = storage.store_next( + Spec.FACTORY_BYTECODE.keccak256(), "extcodecopy_hash" + ) + caller = pre.deploy_contract( + Op.SSTORE(extcodesize_slot, Op.EXTCODESIZE(FACTORY)) + + Op.SSTORE(extcodehash_slot, Op.EXTCODEHASH(FACTORY)) + + Op.EXTCODECOPY(FACTORY, 0, 0, Op.EXTCODESIZE(FACTORY)) + + Op.SSTORE(extcodecopy_hash_slot, Op.SHA3(0, Op.EXTCODESIZE(FACTORY))) + + Op.STOP, + ) + state_test( + pre=pre, + tx=Transaction( + sender=pre.fund_eoa(), + to=caller, + ), + post={ + FACTORY: Account(code=Spec.FACTORY_BYTECODE), + caller: Account(storage=storage), + }, + ) diff --git a/tests/amsterdam/eip7997_deterministic_factory_predeploy/test_factory.py b/tests/amsterdam/eip7997_deterministic_factory_predeploy/test_factory.py new file mode 100644 index 00000000000..bd4f9c3031d --- /dev/null +++ b/tests/amsterdam/eip7997_deterministic_factory_predeploy/test_factory.py @@ -0,0 +1,750 @@ +""" +Tests for [EIP-7997: Deterministic Factory Predeploy](https://eips.ethereum.org/EIPS/eip-7997). + +The factory (the Arachnid deterministic deployment proxy) interprets +calldata as `salt (32) || initcode` and invokes `CREATE2` with the call +value forwarded. It returns the created address (20 bytes) on success +and reverts on `CREATE2` failure. With calldata shorter than 32 bytes, +the factory's `CALLDATASIZE - 32` underflow triggers a copy of nearly +2^256 bytes and reverts via OOG. +""" + +import pytest +from execution_testing import ( + Account, + Address, + Alloc, + AuthorizationTuple, + BalAccountExpectation, + BalCodeChange, + BalNonceChange, + BlockAccessListExpectation, + Bytes, + Fork, + Hash, + Initcode, + Op, + StateTestFiller, + Storage, + Transaction, + compute_create2_address, + keccak256, +) + +from ...prague.eip7702_set_code_tx.spec import Spec as Spec7702 +from .spec import Spec, ref_spec_7997 + +REFERENCE_SPEC_GIT_PATH = ref_spec_7997.git_path +REFERENCE_SPEC_VERSION = ref_spec_7997.version + +pytestmark = pytest.mark.valid_from("EIP7997") + +FACTORY = Spec.FACTORY_ADDRESS + + +def test_factory_predeploy_account( + state_test: StateTestFiller, + pre: Alloc, +) -> None: + """ + The factory bytecode is present at the canonical Arachnid factory + address with nonce 1 and balance 0. Verifies EVM-observable views of + the predeploy via `EXTCODESIZE`, `EXTCODEHASH`, `EXTCODECOPY` + + `SHA3`, and `BALANCE`. + """ + storage = Storage() + extcodesize_slot = storage.store_next( + len(Spec.FACTORY_BYTECODE), "extcodesize" + ) + extcodehash_slot = storage.store_next( + keccak256(Spec.FACTORY_BYTECODE), "extcodehash" + ) + extcodecopy_hash_slot = storage.store_next( + keccak256(Spec.FACTORY_BYTECODE), "extcodecopy_hash" + ) + balance_slot = storage.store_next(0, "balance") + caller = pre.deploy_contract( + Op.SSTORE(extcodesize_slot, Op.EXTCODESIZE(FACTORY)) + + Op.SSTORE(extcodehash_slot, Op.EXTCODEHASH(FACTORY)) + + Op.EXTCODECOPY(FACTORY, 0, 0, Op.EXTCODESIZE(FACTORY)) + + Op.SSTORE(extcodecopy_hash_slot, Op.SHA3(0, Op.EXTCODESIZE(FACTORY))) + + Op.SSTORE(balance_slot, Op.BALANCE(FACTORY)) + + Op.STOP, + ) + state_test( + pre=pre, + tx=Transaction( + sender=pre.fund_eoa(), + to=caller, + ), + post={ + FACTORY: Account( + nonce=1, + balance=0, + code=Spec.FACTORY_BYTECODE, + ), + caller: Account(storage=storage), + }, + ) + + +@pytest.mark.parametrize( + "forwarded_value", + [ + pytest.param(0, id="no_value"), + pytest.param(1, id="with_value"), + ], +) +def test_factory_deploys_contract( + state_test: StateTestFiller, + pre: Alloc, + forwarded_value: int, +) -> None: + """ + Calling the factory with `salt || initcode` deploys a contract at the + expected `CREATE2` address and returns that address. When the call + forwards a non-zero value, the deployed contract receives that + balance. + """ + salt = 0x42 + runtime_code = Op.PUSH1(0x01) + Op.PUSH1(0x00) + Op.RETURN + initcode = Initcode(deploy_code=runtime_code) + expected_address = compute_create2_address(FACTORY, salt, initcode) + + storage = Storage() + caller = pre.deploy_contract( + Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) + + Op.SSTORE( + storage.store_next(1, "factory_call_success"), + Op.CALL( + gas=Op.GAS, + address=FACTORY, + value=forwarded_value, + args_offset=0, + args_size=Op.CALLDATASIZE, + ret_offset=12, + ret_size=20, + ), + ) + + Op.SSTORE( + storage.store_next(expected_address, "returned_address"), + Op.MLOAD(0), + ) + + Op.STOP, + balance=forwarded_value, + ) + + state_test( + pre=pre, + tx=Transaction( + sender=pre.fund_eoa(), + to=caller, + data=Hash(salt) + bytes(initcode), + ), + post={ + caller: Account(storage=storage, balance=0), + expected_address: Account( + nonce=1, + balance=forwarded_value, + code=bytes(runtime_code), + ), + }, + ) + + +def test_factory_address_collision_reverts( + state_test: StateTestFiller, + pre: Alloc, +) -> None: + """ + A second deployment to the same `CREATE2` target reverts. `CREATE2` + fails when the destination already has code, returns 0, and the factory + reverts with the (empty) creation-frame return data. + """ + salt = 0x77 + runtime_code = Op.STOP + initcode = Initcode(deploy_code=runtime_code) + target = compute_create2_address(FACTORY, salt, initcode) + + storage = Storage() + caller = pre.deploy_contract( + Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) + + Op.SSTORE( + storage.store_next(1, "first_call_success"), + Op.CALL( + gas=Op.GAS, + address=FACTORY, + value=0, + args_offset=0, + args_size=Op.CALLDATASIZE, + ret_offset=0x100, + ret_size=32, + ), + ) + + Op.SSTORE( + storage.store_next(0, "second_call_failed"), + Op.CALL( + gas=Op.GAS, + address=FACTORY, + value=0, + args_offset=0, + args_size=Op.CALLDATASIZE, + ret_offset=0x100, + ret_size=32, + ), + ) + + Op.STOP, + ) + + state_test( + pre=pre, + tx=Transaction( + sender=pre.fund_eoa(), + to=caller, + data=Hash(salt) + bytes(initcode), + ), + post={ + caller: Account(storage=storage), + target: Account(nonce=1, code=bytes(runtime_code)), + }, + ) + + +def test_factory_different_salts_produce_different_addresses( + state_test: StateTestFiller, + pre: Alloc, +) -> None: + """ + Two calls to the factory with the same initcode but different salts + must deploy at distinct, salt-derived addresses, proving the salt is + actually plumbed through to `CREATE2`. + """ + salt_a = 0x11 + salt_b = 0x22 + runtime_code = Op.PUSH1(0x01) + Op.PUSH1(0x00) + Op.RETURN + initcode = Initcode(deploy_code=runtime_code) + addr_a = compute_create2_address(FACTORY, salt_a, initcode) + addr_b = compute_create2_address(FACTORY, salt_b, initcode) + assert addr_a != addr_b + + initcode_offset = 32 + args_size = initcode_offset + len(bytes(initcode)) + + storage = Storage() + salt_a_call_slot = storage.store_next(1, "salt_a_call_success") + salt_a_addr_slot = storage.store_next(addr_a, "salt_a_address") + salt_b_call_slot = storage.store_next(1, "salt_b_call_success") + salt_b_addr_slot = storage.store_next(addr_b, "salt_b_address") + + caller = pre.deploy_contract( + Op.CALLDATACOPY(initcode_offset, 0, Op.CALLDATASIZE) + + Op.MSTORE(0, salt_a) + + Op.SSTORE( + salt_a_call_slot, + Op.CALL( + gas=Op.GAS, + address=FACTORY, + value=0, + args_offset=0, + args_size=args_size, + ret_offset=0x20C, + ret_size=20, + ), + ) + + Op.SSTORE(salt_a_addr_slot, Op.MLOAD(0x200)) + + Op.MSTORE(0, salt_b) + + Op.SSTORE( + salt_b_call_slot, + Op.CALL( + gas=Op.GAS, + address=FACTORY, + value=0, + args_offset=0, + args_size=args_size, + ret_offset=0x20C, + ret_size=20, + ), + ) + + Op.SSTORE(salt_b_addr_slot, Op.MLOAD(0x200)) + + Op.STOP, + ) + + state_test( + pre=pre, + tx=Transaction( + sender=pre.fund_eoa(), + to=caller, + data=bytes(initcode), + ), + post={ + caller: Account(storage=storage), + addr_a: Account(nonce=1, code=bytes(runtime_code)), + addr_b: Account(nonce=1, code=bytes(runtime_code)), + }, + ) + + +def test_factory_direct_eoa_call( + state_test: StateTestFiller, + pre: Alloc, +) -> None: + """ + A transaction sent directly to the factory address (no relay contract) + deploys at the expected `CREATE2` address. + """ + salt = 0xCAFE + runtime_code = Op.PUSH1(0x01) + Op.PUSH1(0x00) + Op.RETURN + initcode = Initcode(deploy_code=runtime_code) + expected_address = compute_create2_address(FACTORY, salt, initcode) + + state_test( + pre=pre, + tx=Transaction( + sender=pre.fund_eoa(), + to=FACTORY, + data=Hash(salt) + bytes(initcode), + ), + post={ + expected_address: Account(nonce=1, code=bytes(runtime_code)), + }, + ) + + +def test_factory_staticcall_reverts( + state_test: StateTestFiller, + pre: Alloc, +) -> None: + """ + Calling the factory via `STATICCALL` fails because `CREATE2` requires a + writable context. No contract is deployed. + """ + salt = 0x33 + runtime_code = Op.STOP + initcode = Initcode(deploy_code=runtime_code) + expected_address = compute_create2_address(FACTORY, salt, initcode) + + storage = Storage() + caller = pre.deploy_contract( + Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) + + Op.SSTORE( + storage.store_next(0, "staticcall_failed"), + Op.STATICCALL( + gas=Op.GAS, + address=FACTORY, + args_offset=0, + args_size=Op.CALLDATASIZE, + ret_offset=0x100, + ret_size=32, + ), + ) + + Op.STOP, + ) + + state_test( + pre=pre, + tx=Transaction( + sender=pre.fund_eoa(), + to=caller, + data=Hash(salt) + bytes(initcode), + ), + post={ + caller: Account(storage=storage), + expected_address: Account.NONEXISTENT, + }, + ) + + +@pytest.mark.parametrize("call_opcode", [Op.DELEGATECALL, Op.CALLCODE]) +def test_factory_in_caller_context( + state_test: StateTestFiller, + pre: Alloc, + call_opcode: Op, +) -> None: + """ + Under `DELEGATECALL` or `CALLCODE`, the factory's bytecode runs in the + caller's context, so `CREATE2`'s deployer is the caller — not the + factory. The contract is deployed at the address derived from the + caller, and the factory-derived address is empty. + """ + salt = 0x44 + runtime_code = Op.STOP + initcode = Initcode(deploy_code=runtime_code) + factory_derived = compute_create2_address(FACTORY, salt, initcode) + + call_op = call_opcode( + gas=Op.GAS, + address=FACTORY, + args_offset=0, + args_size=Op.CALLDATASIZE, + ret_offset=0x10C, + ret_size=20, + ) + + storage = Storage() + call_success_slot = storage.store_next(1, "delegated_call_success") + derived_addr_slot = storage.store_next(0, "caller_derived_address") + + caller = pre.deploy_contract( + Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) + + Op.SSTORE(call_success_slot, call_op) + + Op.SSTORE(derived_addr_slot, Op.MLOAD(0x100)) + + Op.STOP, + ) + caller_derived = compute_create2_address(caller, salt, initcode) + storage[derived_addr_slot] = caller_derived + + state_test( + pre=pre, + tx=Transaction( + sender=pre.fund_eoa(), + to=caller, + data=Hash(salt) + bytes(initcode), + ), + post={ + caller: Account(storage=storage), + caller_derived: Account(nonce=1, code=bytes(runtime_code)), + factory_derived: Account.NONEXISTENT, + }, + ) + + +def test_factory_deploys_to_pre_funded_address( + state_test: StateTestFiller, + pre: Alloc, +) -> None: + """ + `CREATE2` to an address that has only a balance (no code, no storage, + nonce 0) succeeds and preserves the existing balance. + """ + salt = 0x66 + runtime_code = Op.STOP + initcode = Initcode(deploy_code=runtime_code) + expected_address = compute_create2_address(FACTORY, salt, initcode) + pre_balance = 1 + + storage = Storage() + caller = pre.deploy_contract( + Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) + + Op.POP( + Op.CALL( + gas=Op.GAS, + address=expected_address, + value=pre_balance, + ) + ) + + Op.SSTORE( + storage.store_next(1, "factory_call_success"), + Op.CALL( + gas=Op.GAS, + address=FACTORY, + value=0, + args_offset=0, + args_size=Op.CALLDATASIZE, + ret_offset=0x100, + ret_size=32, + ), + ) + + Op.STOP, + balance=pre_balance, + ) + + state_test( + pre=pre, + tx=Transaction( + sender=pre.fund_eoa(), + to=caller, + data=Hash(salt) + bytes(initcode), + ), + post={ + caller: Account(storage=storage), + expected_address: Account( + nonce=1, + balance=pre_balance, + code=bytes(runtime_code), + ), + }, + ) + + +def test_factory_receives_balance_via_selfdestruct( + state_test: StateTestFiller, + pre: Alloc, +) -> None: + """ + `SELFDESTRUCT` to the factory transfers the originator's balance to + the factory address. The factory's other state is untouched: same + nonce, same code. Calling the factory after the transfer still works. + + Tests that the factory address has no special handling under + `SELFDESTRUCT` — it behaves like any other contract beneficiary. + """ + forwarded_value = 1 + + sd_actor = pre.deploy_contract( + Op.SELFDESTRUCT(FACTORY), + balance=forwarded_value, + ) + + salt = 0x88 + runtime_code = Op.STOP + initcode = Initcode(deploy_code=runtime_code) + expected_address = compute_create2_address(FACTORY, salt, initcode) + + storage = Storage() + caller = pre.deploy_contract( + Op.POP(Op.CALL(gas=Op.GAS, address=sd_actor)) + + Op.SSTORE( + storage.store_next(forwarded_value, "factory_balance_after_sd"), + Op.BALANCE(FACTORY), + ) + + Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) + + Op.SSTORE( + storage.store_next(1, "factory_call_success"), + Op.CALL( + gas=Op.GAS, + address=FACTORY, + value=0, + args_offset=0, + args_size=Op.CALLDATASIZE, + ret_offset=0x100, + ret_size=32, + ), + ) + + Op.STOP, + ) + + state_test( + pre=pre, + tx=Transaction( + sender=pre.fund_eoa(), + to=caller, + data=Hash(salt) + bytes(initcode), + ), + post={ + caller: Account(storage=storage), + FACTORY: Account( + nonce=2, + balance=forwarded_value, + code=Spec.FACTORY_BYTECODE, + ), + expected_address: Account( + nonce=1, + code=bytes(runtime_code), + ), + }, + ) + + +def test_factory_via_eip7702_delegation( + state_test: StateTestFiller, + pre: Alloc, +) -> None: + """ + An EOA delegates its code to the factory via an EIP-7702 + authorization. When the EOA is then called with `salt || initcode`, + the factory bytecode runs in the EOA's context, so `CREATE2` treats + the EOA as the deployer. The deterministic address therefore + derives from the EOA, not from the factory. + """ + auth_signer = pre.fund_eoa() + auth_signer_nonce = auth_signer.nonce + + salt = 0x42 + runtime_code = Op.PUSH1(0x01) + Op.PUSH1(0x00) + Op.RETURN + initcode = Initcode(deploy_code=runtime_code) + expected_address = compute_create2_address(auth_signer, salt, initcode) + + caller = pre.deploy_contract( + Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) + + Op.POP( + Op.CALL( + gas=Op.GAS, + address=auth_signer, + value=0, + args_offset=0, + args_size=Op.CALLDATASIZE, + ret_offset=0x100, + ret_size=20, + ), + ) + + Op.STOP, + ) + + state_test( + pre=pre, + tx=Transaction( + sender=pre.fund_eoa(), + to=caller, + data=Hash(salt) + bytes(initcode), + authorization_list=[ + AuthorizationTuple( + address=Address(FACTORY), + nonce=auth_signer_nonce, + signer=auth_signer, + ), + ], + ), + post={ + auth_signer: Account( + nonce=auth_signer_nonce + 2, + code=Spec7702.delegation_designation(Address(FACTORY)), + ), + expected_address: Account(nonce=1, code=bytes(runtime_code)), + FACTORY: Account( + nonce=1, + balance=0, + code=Spec.FACTORY_BYTECODE, + ), + }, + ) + + +def test_factory_rejects_ef_prefix_deployment( + state_test: StateTestFiller, + pre: Alloc, +) -> None: + """ + EIP-3541: deploying code that begins with `0xEF` is rejected. The + factory's `CREATE2` fails when the initcode would return such code; + the factory reverts and no contract is deployed. + """ + salt = 0x3541 + deploy_code = Bytes(b"\xef\x00") + initcode = Initcode(deploy_code=deploy_code) + expected_address = compute_create2_address(FACTORY, salt, initcode) + + storage = Storage() + caller = pre.deploy_contract( + Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) + + Op.SSTORE( + storage.store_next(0, "factory_call_failed"), + Op.CALL( + gas=Op.GAS, + address=FACTORY, + value=0, + args_offset=0, + args_size=Op.CALLDATASIZE, + ), + ) + + Op.STOP, + ) + + state_test( + pre=pre, + tx=Transaction( + sender=pre.fund_eoa(), + to=caller, + data=Hash(salt) + bytes(initcode), + ), + post={ + caller: Account(storage=storage), + expected_address: Account.NONEXISTENT, + }, + ) + + +def test_factory_rejects_oversized_initcode( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + EIP-3860: initcode larger than the fork's max initcode size is + rejected. The factory's `CREATE2` fails for oversized initcode and + the factory reverts. + """ + salt = 0x55 + initcode = Initcode( + deploy_code=Op.STOP, + initcode_length=fork.max_initcode_size() + 1, + ) + expected_address = compute_create2_address(FACTORY, salt, initcode) + + storage = Storage() + caller = pre.deploy_contract( + Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) + + Op.SSTORE( + storage.store_next(0, "factory_call_failed"), + Op.CALL( + gas=Op.GAS, + address=FACTORY, + value=0, + args_offset=0, + args_size=Op.CALLDATASIZE, + ), + ) + + Op.STOP, + ) + + state_test( + pre=pre, + tx=Transaction( + sender=pre.fund_eoa(), + to=caller, + data=Hash(salt) + bytes(initcode), + ), + post={ + caller: Account(storage=storage), + expected_address: Account.NONEXISTENT, + }, + ) + + +def test_factory_block_access_list( + state_test: StateTestFiller, + pre: Alloc, +) -> None: + """ + EIP-7928: a factory deployment is captured in the block-level + access list. The factory's nonce bump from `CREATE2` and the + deployed contract's `nonce`/`code` initialization both appear + under their respective accounts. + """ + salt = 0x42 + runtime_code = Op.PUSH1(0x01) + Op.PUSH1(0x00) + Op.RETURN + initcode = Initcode(deploy_code=runtime_code) + expected_address = compute_create2_address(FACTORY, salt, initcode) + + sender = pre.fund_eoa() + + state_test( + pre=pre, + tx=Transaction( + sender=sender, + to=Address(FACTORY), + data=Hash(salt) + bytes(initcode), + ), + post={ + FACTORY: Account( + nonce=2, + balance=0, + code=Spec.FACTORY_BYTECODE, + ), + expected_address: Account(nonce=1, code=bytes(runtime_code)), + }, + expected_block_access_list=BlockAccessListExpectation( + account_expectations={ + sender: BalAccountExpectation( + nonce_changes=[ + BalNonceChange(block_access_index=1, post_nonce=1), + ], + ), + Address(FACTORY): BalAccountExpectation( + nonce_changes=[ + BalNonceChange(block_access_index=1, post_nonce=2), + ], + ), + expected_address: BalAccountExpectation( + nonce_changes=[ + BalNonceChange(block_access_index=1, post_nonce=1), + ], + code_changes=[ + BalCodeChange( + block_access_index=1, + new_code=bytes(runtime_code), + ), + ], + ), + }, + ), + ) diff --git a/tests/amsterdam/eip7997_deterministic_factory_predeploy/test_fork_transition.py b/tests/amsterdam/eip7997_deterministic_factory_predeploy/test_fork_transition.py new file mode 100644 index 00000000000..b124a0c5884 --- /dev/null +++ b/tests/amsterdam/eip7997_deterministic_factory_predeploy/test_fork_transition.py @@ -0,0 +1,106 @@ +""" +Fork-transition tests for +[EIP-7997: Deterministic Factory Predeploy](https://eips.ethereum.org/EIPS/eip-7997). +""" + +import pytest +from execution_testing import ( + Account, + Address, + Alloc, + Block, + BlockchainTestFiller, + Hash, + Initcode, + Op, + Transaction, + compute_create2_address, +) +from execution_testing.test_types.block_access_list.account_changes import ( + BalNonceChange, +) +from execution_testing.test_types.block_access_list.expectations import ( + BalAccountExpectation, + BlockAccessListExpectation, +) + +from .spec import Spec, ref_spec_7997 + +REFERENCE_SPEC_GIT_PATH = ref_spec_7997.git_path +REFERENCE_SPEC_VERSION = ref_spec_7997.version + +FORK_TIMESTAMP = 15_000 + + +@pytest.mark.valid_at_transition_to("Amsterdam") +@pytest.mark.pre_alloc_mutable +@pytest.mark.parametrize("pre_fork_nonce", [1, 2, 32]) +def test_factory_deploys_across_transition( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + pre_fork_nonce: int, +) -> None: + """ + A pre-existing factory keeps deploying contracts across the Amsterdam + transition, with its nonce accruing normally. + + Asserting that final nonce is what catches the glamsterdam-devnet-6 bug: a + client that re-injects EIP-7997 at the transition resets the already-used + factory back to nonce 1, diverging the post-state root. Deployment success + alone cannot catch it, since the `CREATE2` address does not depend on the + factory nonce. + """ + factory = pre.deploy_contract( + code=Spec.FACTORY_BYTECODE, + address=Address(Spec.FACTORY_ADDRESS), + nonce=pre_fork_nonce, + ) + sender = pre.fund_eoa() + + runtime_code = Op.RETURN(0, 1) + initcode = Initcode(deploy_code=runtime_code) + + timestamps = [FORK_TIMESTAMP - 1, FORK_TIMESTAMP, FORK_TIMESTAMP + 1] + + blocks = [] + deployed = {} + for i, timestamp in enumerate(timestamps): + blocks.append( + Block( + timestamp=timestamp, + txs=[ + Transaction( + sender=sender, + to=factory, + data=Hash(timestamp) + bytes(initcode), + ) + ], + expected_block_access_list=BlockAccessListExpectation( + account_expectations={ + factory: BalAccountExpectation( + nonce_changes=[ + BalNonceChange( + block_access_index=1, + post_nonce=pre_fork_nonce + i + 1, + ) + ], + ), + } + ), + ) + ) + deployed[compute_create2_address(factory, timestamp, initcode)] = ( + Account(nonce=1, code=bytes(runtime_code)) + ) + + blockchain_test( + pre=pre, + blocks=blocks, + post={ + **deployed, + factory: Account( + nonce=pre_fork_nonce + len(timestamps), + code=Spec.FACTORY_BYTECODE, + ), + }, + ) From 279ae53eb85917ecdd093a6dfb2beb2e0521de7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=94=A1=E4=BD=B3=E8=AA=A0=20Louis=20Tsai?= <72684086+LouisTsai-Csie@users.noreply.github.com> Date: Thu, 2 Jul 2026 16:08:47 +0800 Subject: [PATCH 073/233] feat(spec,tests): Implement EIP-8246 (#3078) * feat(spec-spec, tests): Implement eip-8246 and testing scenario (#2842) * feat: implement eip 8246 * tests: enhance eip 8246 coverage * refactor: resolve failing selfdestruct scenario * chore: fix linting issue * fix(specs): Remove all references to burn logs * fix(tests): Fix remaining tests * chore: rename function --------- Co-authored-by: marioevz * chore: remove dup statement --------- Co-authored-by: marioevz --- .../forks/forks/eips/amsterdam/eip_8246.py | 13 + src/ethereum/forks/amsterdam/__init__.py | 2 + src/ethereum/forks/amsterdam/fork.py | 32 +- src/ethereum/forks/amsterdam/state_tracker.py | 31 +- src/ethereum/forks/amsterdam/vm/__init__.py | 35 - .../forks/amsterdam/vm/instructions/system.py | 8 +- .../eip7708_eth_transfer_logs/spec.py | 12 - .../test_burn_logs.py | 912 ------------------ .../test_fork_transition.py | 115 +-- .../test_block_access_lists_opcodes.py | 14 +- .../test_state_gas_call.py | 27 +- .../eip8246_selfdestruct_no_burn/__init__.py | 1 + .../eip8246_selfdestruct_no_burn/spec.py | 17 + .../test_selfdestruct_no_burn.py | 227 +++++ .../eip6780_selfdestruct/test_selfdestruct.py | 87 +- .../test_selfdestruct_revert.py | 46 +- .../create/test_create_suicide_during_init.py | 12 +- .../security/test_selfdestruct_balance_bug.py | 19 +- .../stCreate2/test_create2_suicide.py | 27 +- ..._transaction_create_suicide_in_initcode.py | 9 +- .../test_eip150_selfdestruct.py | 55 +- 21 files changed, 502 insertions(+), 1199 deletions(-) create mode 100644 packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8246.py delete mode 100644 tests/amsterdam/eip7708_eth_transfer_logs/test_burn_logs.py create mode 100644 tests/amsterdam/eip8246_selfdestruct_no_burn/__init__.py create mode 100644 tests/amsterdam/eip8246_selfdestruct_no_burn/spec.py create mode 100644 tests/amsterdam/eip8246_selfdestruct_no_burn/test_selfdestruct_no_burn.py diff --git a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8246.py b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8246.py new file mode 100644 index 00000000000..f6b746c4c6f --- /dev/null +++ b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8246.py @@ -0,0 +1,13 @@ +""" +EIP-8246: Remove SELFDESTRUCT balance burn. + +https://eips.ethereum.org/EIPS/eip-8246 +""" + +from ....base_fork import BaseFork + + +class EIP8246(BaseFork): + """EIP-8246 class.""" + + pass diff --git a/src/ethereum/forks/amsterdam/__init__.py b/src/ethereum/forks/amsterdam/__init__.py index 3d13dd37dfa..36057c1a383 100644 --- a/src/ethereum/forks/amsterdam/__init__.py +++ b/src/ethereum/forks/amsterdam/__init__.py @@ -7,6 +7,7 @@ - [EIP-7928: Block-Level Access Lists][EIP-7928] - [EIP-7954: Increase Maximum Contract Size][EIP-7954] - [EIP-7997: Deterministic Factory Predeploy][EIP-7997] +- [EIP-8246: Remove SELFDESTRUCT balance burn][EIP-8246] ### Releases @@ -14,6 +15,7 @@ [EIP-7928]: https://eips.ethereum.org/EIPS/eip-7928 [EIP-7954]: https://eips.ethereum.org/EIPS/eip-7954 [EIP-7997]: https://eips.ethereum.org/EIPS/eip-7997 +[EIP-8246]: https://eips.ethereum.org/EIPS/eip-8246 """ from ethereum.fork_criteria import ForkCriteria, Unscheduled diff --git a/src/ethereum/forks/amsterdam/fork.py b/src/ethereum/forks/amsterdam/fork.py index 6ac9e968e52..6fe9cf29de0 100644 --- a/src/ethereum/forks/amsterdam/fork.py +++ b/src/ethereum/forks/amsterdam/fork.py @@ -37,7 +37,6 @@ State, apply_changes_to_state, ) -from ethereum.utils.byte import left_pad_zero_bytes from . import vm from .block_access_lists import ( @@ -71,8 +70,8 @@ from .state_tracker import ( BlockState, TransactionState, + clear_account_preserving_balance, create_ether, - destroy_account, extract_block_diff, get_account, get_code, @@ -1107,26 +1106,6 @@ def process_transaction( # transfer miner fees create_ether(tx_state, block_env.coinbase, U256(transaction_fee)) - # EIP-7708: Emit burn logs for balances held by accounts marked for - # deletion AFTER miner fee transfer. - finalization_logs: List[Log] = [] - for address in sorted(tx_output.accounts_to_delete): - balance = get_account(tx_state, address).balance - if balance > U256(0): - padded_address = left_pad_zero_bytes(address, 32) - finalization_logs.append( - Log( - address=vm.SYSTEM_ADDRESS, - topics=( - vm.BURN_TOPIC, - Hash32(padded_address), - ), - data=balance.to_be_bytes32(), - ) - ) - - all_logs = tx_output.logs + tuple(finalization_logs) - tx_state_gas = ( int(tx_env.intrinsic_state_gas) + tx_output.state_gas_used @@ -1139,10 +1118,7 @@ def process_transaction( block_output.cumulative_gas_used += tx_gas_used receipt = make_receipt( - tx, - tx_output.error, - block_output.cumulative_gas_used, - all_logs, + tx, tx_output.error, block_output.cumulative_gas_used, tx_output.logs ) receipt_key = rlp.encode(Uint(index)) @@ -1154,10 +1130,10 @@ def process_transaction( receipt, ) - block_output.block_logs += all_logs + block_output.block_logs += tx_output.logs for address in tx_output.accounts_to_delete: - destroy_account(tx_state, address) + clear_account_preserving_balance(tx_state, address) incorporate_tx_into_block(tx_state, block_env.block_access_list_builder) diff --git a/src/ethereum/forks/amsterdam/state_tracker.py b/src/ethereum/forks/amsterdam/state_tracker.py index 9312d7c5231..cda7bbf53ea 100644 --- a/src/ethereum/forks/amsterdam/state_tracker.py +++ b/src/ethereum/forks/amsterdam/state_tracker.py @@ -423,10 +423,9 @@ def destroy_account(tx_state: TransactionState, address: Address) -> None: """ Completely remove the account at ``address`` and all of its storage. - This function is made available exclusively for the ``SELFDESTRUCT`` - opcode. It is expected that ``SELFDESTRUCT`` will be disabled in a - future hardfork and this function will be removed. Only supports same - transaction destruction. + Invoked by ``modify_state`` (and the coinbase fee-credit path) to + clean up an account that has become empty (zero nonce, empty + code, and zero balance) so it does not appear in the post-state. Parameters ---------- @@ -440,6 +439,30 @@ def destroy_account(tx_state: TransactionState, address: Address) -> None: set_account(tx_state, address, None) +def clear_account_preserving_balance( + tx_state: TransactionState, address: Address +) -> None: + """ + Clear an account's nonce, code, and storage while preserving its + balance. + + Parameters + ---------- + tx_state : + The transaction state. + address : + Address of the account to modify. + + """ + + def clear_account(account: Account) -> None: + account.nonce = Uint(0) + account.code_hash = EMPTY_CODE_HASH + + destroy_storage(tx_state, address) + modify_state(tx_state, address, clear_account) + + def destroy_storage(tx_state: TransactionState, address: Address) -> None: """ Completely remove the storage at ``address``. diff --git a/src/ethereum/forks/amsterdam/vm/__init__.py b/src/ethereum/forks/amsterdam/vm/__init__.py index b7f890bbc73..1f54d2b3c64 100644 --- a/src/ethereum/forks/amsterdam/vm/__init__.py +++ b/src/ethereum/forks/amsterdam/vm/__init__.py @@ -32,7 +32,6 @@ __all__ = ("Environment", "Evm", "Message") TRANSFER_TOPIC = keccak256(b"Transfer(address,address,uint256)") -BURN_TOPIC = keccak256(b"Burn(address,uint256)") SYSTEM_ADDRESS = Address( bytes.fromhex("fffffffffffffffffffffffffffffffffffffffe") ) @@ -339,37 +338,3 @@ def emit_transfer_log( ) evm.logs = evm.logs + (log_entry,) - - -def emit_burn_log( - evm: Evm, - account: Address, - amount: U256, -) -> None: - """ - Emit a LOG2 for ETH burn per EIP-7708. - - Parameters - ---------- - evm : - The state of the ethereum virtual machine - account : - The account address whose ETH is being burned - amount : - The amount of ETH being burned - - """ - if amount == 0: - return - - padded_account = left_pad_zero_bytes(account, 32) - log_entry = Log( - address=SYSTEM_ADDRESS, - topics=( - BURN_TOPIC, - Hash32(padded_account), - ), - data=amount.to_be_bytes32(), - ) - - evm.logs = evm.logs + (log_entry,) diff --git a/src/ethereum/forks/amsterdam/vm/instructions/system.py b/src/ethereum/forks/amsterdam/vm/instructions/system.py index fd173a30adf..185bc277fd5 100644 --- a/src/ethereum/forks/amsterdam/vm/instructions/system.py +++ b/src/ethereum/forks/amsterdam/vm/instructions/system.py @@ -28,7 +28,6 @@ increment_nonce, is_account_alive, move_ether, - set_account_balance, ) from ...utils.address import ( compute_contract_address, @@ -43,7 +42,6 @@ Evm, Message, credit_state_gas_refund, - emit_burn_log, emit_transfer_log, incorporate_child_on_error, incorporate_child_on_success, @@ -689,15 +687,11 @@ def selfdestruct(evm: Evm) -> None: move_ether(tx_state, originator, beneficiary, originator_balance) # Emit transfer or burn log - if originator in tx_state.created_accounts and beneficiary == originator: - emit_burn_log(evm, originator, originator_balance) - elif beneficiary != originator: + if beneficiary != originator: emit_transfer_log(evm, originator, beneficiary, originator_balance) # Register account for deletion iff created in same transaction if originator in tx_state.created_accounts: - # If beneficiary and originator are the same then the ether is burnt. - set_account_balance(tx_state, originator, U256(0)) evm.accounts_to_delete.add(originator) # HALT the execution diff --git a/tests/amsterdam/eip7708_eth_transfer_logs/spec.py b/tests/amsterdam/eip7708_eth_transfer_logs/spec.py index ec58c8d7187..54088c5217c 100644 --- a/tests/amsterdam/eip7708_eth_transfer_logs/spec.py +++ b/tests/amsterdam/eip7708_eth_transfer_logs/spec.py @@ -46,15 +46,3 @@ def transfer_log( ], data=Bytes(amount.to_bytes(32, "big")), ) - - -def burn_log(contract_address: Address, amount: int | None) -> TransactionLog: - """Create an expected Burn log for EIP-7708.""" - return TransactionLog( - address=Spec.SYSTEM_ADDRESS, - topics=[ - Spec.BURN_TOPIC, - Hash(bytes(contract_address).rjust(32, b"\x00")), - ], - data=Bytes(amount.to_bytes(32, "big")) if amount is not None else None, - ) diff --git a/tests/amsterdam/eip7708_eth_transfer_logs/test_burn_logs.py b/tests/amsterdam/eip7708_eth_transfer_logs/test_burn_logs.py deleted file mode 100644 index 4485939b8d7..00000000000 --- a/tests/amsterdam/eip7708_eth_transfer_logs/test_burn_logs.py +++ /dev/null @@ -1,912 +0,0 @@ -""" -Tests for EIP-7708 Burn logs. - -Tests for the Burn(address,uint256) log emitted when: -- SELFDESTRUCT to self with nonzero balance -- Account created and destroyed in the same transaction -""" - -import pytest -from execution_testing import ( - EOA, - Account, - Address, - Alloc, - Block, - BlockchainTestFiller, - Bytecode, - Conditional, - Environment, - Fork, - Header, - Initcode, - Op, - Opcodes, - StateTestFiller, - Transaction, - TransactionReceipt, - compute_create_address, -) -from execution_testing import ( - Macros as Om, -) - -from .spec import burn_log, ref_spec_7708, transfer_log - -REFERENCE_SPEC_GIT_PATH = ref_spec_7708.git_path -REFERENCE_SPEC_VERSION = ref_spec_7708.version - -pytestmark = pytest.mark.valid_from("EIP7708") - - -def test_selfdestruct_to_self_pre_existing_no_log( - state_test: StateTestFiller, - env: Environment, - pre: Alloc, - sender: EOA, -) -> None: - """ - Test that selfdestruct-to-self emits NO log for pre-existing contracts. - - Burn log only emitted when created and destroyed in same tx. - """ - contract_balance = 2000 - - contract_code = Op.SELFDESTRUCT(Op.ADDRESS) - contract = pre.deploy_contract(contract_code, balance=contract_balance) - - tx = Transaction( - sender=sender, - to=contract, - value=0, - expected_receipt=TransactionReceipt(logs=[]), - ) - - # Contract keeps its balance (not destroyed since not created in same tx) - state_test( - env=env, - pre=pre, - post={contract: Account(balance=contract_balance)}, - tx=tx, - ) - - -@pytest.mark.parametrize( - "contract_balance", - [ - pytest.param(2000, id="with_balance"), - pytest.param(0, id="zero_balance"), - ], -) -@pytest.mark.with_all_create_opcodes -def test_selfdestruct_to_self_same_tx( - state_test: StateTestFiller, - env: Environment, - pre: Alloc, - sender: EOA, - contract_balance: int, - create_opcode: Op, -) -> None: - """ - Test selfdestruct-to-self for same-tx created contracts. - - - With balance, Burn log emitted (burns ETH). - - No balance, no logs expected. - """ - initcode = Op.SELFDESTRUCT(Op.ADDRESS) - initcode_bytes = bytes(initcode) - initcode_len = len(initcode_bytes) - - factory_code = Op.MSTORE( - 0, Op.PUSH32(initcode_bytes.rjust(32, b"\x00")) - ) + create_opcode( - value=Op.CALLVALUE, offset=32 - initcode_len, size=initcode_len - ) - - factory = pre.deploy_contract(factory_code) - created_address = compute_create_address( - address=factory, - nonce=1, - salt=0, - initcode=initcode_bytes, - opcode=create_opcode, - ) - - if contract_balance > 0: - expected_logs = [ - transfer_log(sender, factory, contract_balance), - transfer_log(factory, created_address, contract_balance), - burn_log(created_address, contract_balance), - ] - else: - expected_logs = [] - - tx = Transaction( - sender=sender, - to=factory, - value=contract_balance, - expected_receipt=TransactionReceipt(logs=expected_logs), - ) - - state_test(env=env, pre=pre, post={}, tx=tx) - - -@pytest.mark.parametrize( - "contract_balance", - [ - pytest.param(2000, id="with_balance"), - pytest.param(0, id="zero_balance"), - ], -) -@pytest.mark.with_all_create_opcodes -def test_selfdestruct_to_different_address_same_tx( - state_test: StateTestFiller, - env: Environment, - pre: Alloc, - sender: EOA, - contract_balance: int, - create_opcode: Op, -) -> None: - """ - Test same-tx selfdestruct to different address. - - With balance: Transfer log emitted. Zero balance: no logs. - """ - beneficiary = pre.deploy_contract(Op.STOP) - - initcode = Op.SELFDESTRUCT(beneficiary) - initcode_bytes = bytes(initcode) - initcode_len = len(initcode_bytes) - - factory_code = Op.MSTORE( - 0, Op.PUSH32(initcode_bytes.rjust(32, b"\x00")) - ) + create_opcode( - value=Op.CALLVALUE, offset=32 - initcode_len, size=initcode_len - ) - - factory = pre.deploy_contract(factory_code) - created_address = compute_create_address( - address=factory, - nonce=1, - salt=0, - initcode=initcode_bytes, - opcode=create_opcode, - ) - - if contract_balance > 0: - expected_logs = [ - transfer_log(sender, factory, contract_balance), - transfer_log(factory, created_address, contract_balance), - transfer_log(created_address, beneficiary, contract_balance), - ] - post = {beneficiary: Account(balance=contract_balance)} - else: - expected_logs = [] - post = {} - - tx = Transaction( - sender=sender, - to=factory, - value=contract_balance, - expected_receipt=TransactionReceipt(logs=expected_logs), - ) - - state_test(env=env, pre=pre, post=post, tx=tx) - - -@pytest.mark.parametrize( - "to_self", - [ - pytest.param(True, id="to_self"), - pytest.param(False, id="to_other"), - ], -) -@pytest.mark.parametrize( - "call_twice,second_call_value", - [ - pytest.param(True, 1, id="call_twice_with_value"), - pytest.param(True, 0, id="call_twice"), - pytest.param(False, 0, id="call_once"), - ], -) -@pytest.mark.parametrize( - "transfer_during_create", - [ - pytest.param(True, id="transfer_during_create"), - pytest.param(False, id="transfer_during_call"), - ], -) -def test_selfdestruct_same_tx_via_call( - state_test: StateTestFiller, - env: Environment, - pre: Alloc, - sender: EOA, - to_self: bool, - call_twice: bool, - second_call_value: int, - transfer_during_create: bool, -) -> None: - """ - Test selfdestruct via CREATE-then-CALL (not initcode selfdestruct). - - Factory CREATEs contract with runtime code, then CALLs the contract that - was just created to trigger SELFDESTRUCT (depending on - `transfer_during_create`, the value of the contract is transferred during - the CREATE or CALL opcodes). Contract is still in created_accounts. - - Depending on `call_twice`, the contract can be called twice during the - same call frame where it was created. - """ - contract_balance = 2000 - beneficiary = pre.deploy_contract(Op.STOP) - - if to_self: - runtime_code = Op.SELFDESTRUCT(Op.ADDRESS) - else: - runtime_code = Op.SELFDESTRUCT(beneficiary) - - initcode = Initcode(deploy_code=runtime_code) - initcode_len = len(initcode) - - if transfer_during_create: - create_value = contract_balance - first_call_value = 0 - else: - create_value = 0 - first_call_value = contract_balance - - factory_code = ( - Om.MSTORE(initcode, 0) - + Op.SSTORE( - 0, Op.CREATE(value=create_value, offset=0, size=initcode_len) - ) - + Op.SSTORE( - 1, - Op.CALL(gas=100_000, address=Op.SLOAD(0), value=first_call_value), - ) - ) - if call_twice: - factory_code += Op.SSTORE( - 2, - Op.CALL(gas=100_000, address=Op.SLOAD(0), value=second_call_value), - ) - - factory = pre.deploy_contract( - factory_code, balance=contract_balance + second_call_value - ) - created_address = compute_create_address(address=factory, nonce=1) - - factory_storage = { - 0: created_address, - 1: 1, - } - if call_twice: - factory_storage[2] = 1 - - if to_self: - expected_logs = [ - transfer_log(factory, created_address, contract_balance), - burn_log(created_address, contract_balance), - ] - if call_twice and second_call_value > 0: - expected_logs += [ - transfer_log(factory, created_address, second_call_value), - burn_log(created_address, second_call_value), - ] - post = {factory: Account(storage=factory_storage)} - else: - expected_logs = [ - transfer_log(factory, created_address, contract_balance), - transfer_log(created_address, beneficiary, contract_balance), - ] - if call_twice and second_call_value > 0: - expected_logs += [ - transfer_log(factory, created_address, second_call_value), - transfer_log(created_address, beneficiary, second_call_value), - ] - post = { - beneficiary: Account(balance=contract_balance + second_call_value), - factory: Account(storage=factory_storage), - } - - tx = Transaction( - sender=sender, - to=factory, - expected_receipt=TransactionReceipt(logs=expected_logs), - ) - - state_test(env=env, pre=pre, post=post, tx=tx) - - -@pytest.mark.parametrize( - "payer_code,eth_transferred", - [ - pytest.param( - Op.SELFDESTRUCT(Op.CALLDATALOAD(0)), - True, - id="via_selfdestruct", - ), - pytest.param( - Op.CALL( - gas=50_000, - address=Op.CALLDATALOAD(0), - value=Op.BALANCE(Op.ADDRESS), - ), - True, - id="via_call", - ), - pytest.param( - Op.CALL( - gas=50_000, - address=Op.CALLDATALOAD(0), - value=Op.BALANCE(Op.ADDRESS), - ) - + Op.REVERT(0, 0), - False, - id="via_call_revert", - ), - ], -) -@pytest.mark.parametrize( - "to_self", - [ - pytest.param(False, id="to_beneficiary"), - pytest.param(True, id="to_self"), - ], -) -def test_finalization_burn_logs( - state_test: StateTestFiller, - env: Environment, - pre: Alloc, - sender: EOA, - payer_code: Bytecode, - eth_transferred: bool, - to_self: bool, -) -> None: - """ - Test Burn logs at finalization for post-selfdestruct balance. - - X contracts (x1, x2, x3) selfdestruct, then receive ETH via payer contracts - (p1, p2, p3). At finalization, X contracts emit Burn logs for their - in lexicographical address order (only if they received ETH). - - When to_self=True, X contracts SELFDESTRUCT to themselves (burning ETH - with LOG2). When to_self=False, X contracts SELFDESTRUCT to a beneficiary - (Transfer LOG3). - """ - beneficiary = pre.deploy_contract(Op.STOP) - - # Pre-compute factory address and created contract addresses - # so we can call them in reverse sorted order to prove finalization - # logs are sorted by address, not by call order - factory_address = compute_create_address( - address=sender, nonce=sender.nonce - ) - x1 = compute_create_address(address=factory_address, nonce=1) - x2 = compute_create_address(address=factory_address, nonce=2) - x3 = compute_create_address(address=factory_address, nonce=3) - - # sort() + call in REVERSE order to prove finalization - # lexicographical sorting - sorted_addrs = sorted([x1, x2, x3]) - reverse_sorted = list(reversed(sorted_addrs)) - - # Runtime: selfdestruct on first call, STOP on subsequent calls - target: Address | Opcodes = Op.ADDRESS if to_self else beneficiary - runtime = Conditional( - condition=Op.ISZERO(Op.TLOAD(0)), - if_true=Op.TSTORE(0, 1) + Op.SELFDESTRUCT(target), - if_false=Op.STOP, - ) - initcode = Initcode(deploy_code=runtime) - initcode_len = len(initcode) - - # Payer contracts (p1, p2, p3) will send ETH to created contracts - p1 = pre.deploy_contract(payer_code, balance=100) - p2 = pre.deploy_contract(payer_code, balance=200) - p3 = pre.deploy_contract(payer_code, balance=300) - - # Call p1/p2/p3 targeting addresses in REVERSE sorted order - # This proves finalization logs are sorted by address, not call order - factory_code = ( - Om.MSTORE(initcode, 0) - # Create x1, x2, x3 - + Op.TSTORE(0, Op.CREATE(value=1000, offset=0, size=initcode_len)) - + Op.TSTORE(1, Op.CREATE(value=2000, offset=0, size=initcode_len)) - + Op.TSTORE(2, Op.CREATE(value=3000, offset=0, size=initcode_len)) - # Call x1, x2, x3 to trigger SELFDESTRUCT - + Op.CALL(gas=100_000, address=Op.TLOAD(0), value=0) - + Op.CALL(gas=100_000, address=Op.TLOAD(1), value=0) - + Op.CALL(gas=100_000, address=Op.TLOAD(2), value=0) - # p1/p2/p3 send ETH in REVERSE sorted address order - + Op.MSTORE(0, reverse_sorted[0]) - + Op.CALL(gas=100_000, address=p1, args_offset=0, args_size=32) - + Op.MSTORE(0, reverse_sorted[1]) - + Op.CALL(gas=100_000, address=p2, args_offset=0, args_size=32) - + Op.MSTORE(0, reverse_sorted[2]) - + Op.CALL(gas=100_000, address=p3, args_offset=0, args_size=32) - ) - - factory_balance = 1000 + 2000 + 3000 - pre.fund_address(factory_address, factory_balance) - - # Amounts based on reverse call order: - # p1→reverse[0], p2→reverse[1], p3→reverse[2] - amounts = { - reverse_sorted[0]: 100, - reverse_sorted[1]: 200, - reverse_sorted[2]: 300, - } - - # Execution logs: - # 1. CREATE x1, x2, x3 → LOG3 Transfer (factory → created) - # 2. CALL x1, x2, x3 → LOG3 or LOG2 depending on `to_self` - # 3. p1/p2/p3 send to reverse_sorted order - execution_logs = [ - transfer_log(factory_address, x1, 1000), - transfer_log(factory_address, x2, 2000), - transfer_log(factory_address, x3, 3000), - ] - - if to_self: - # SELFDESTRUCT to self burns ETH → LOG2 Burn - execution_logs.extend( - [ - burn_log(x1, 1000), - burn_log(x2, 2000), - burn_log(x3, 3000), - ] - ) - beneficiary_balance = 0 - else: - # SELFDESTRUCT to beneficiary → LOG3 Transfer - execution_logs.extend( - [ - transfer_log(x1, beneficiary, 1000), - transfer_log(x2, beneficiary, 2000), - transfer_log(x3, beneficiary, 3000), - ] - ) - beneficiary_balance = factory_balance - - if not eth_transferred: - # Reverted CALLs emit no logs, no ETH transferred, no finalization logs - finalization_logs = [] - post = { - x1: Account.NONEXISTENT, - x2: Account.NONEXISTENT, - x3: Account.NONEXISTENT, - beneficiary: Account(balance=beneficiary_balance), - p1: Account(balance=100), - p2: Account(balance=200), - p3: Account(balance=300), - } - else: - # p1/p2/p3 send ETH in reverse sorted order - execution_logs.extend( - [ - transfer_log(p1, reverse_sorted[0], 100), - transfer_log(p2, reverse_sorted[1], 200), - transfer_log(p3, reverse_sorted[2], 300), - ] - ) - # Finalization logs emitted in SORTED address order (not call order) - finalization_logs = [ - burn_log(addr, amounts[addr]) for addr in sorted_addrs - ] - post = { - x1: Account.NONEXISTENT, - x2: Account.NONEXISTENT, - x3: Account.NONEXISTENT, - beneficiary: Account(balance=beneficiary_balance), - p1: Account(balance=0), - p2: Account(balance=0), - p3: Account(balance=0), - } - - tx = Transaction( - sender=sender, - to=None, - data=factory_code, - expected_receipt=TransactionReceipt( - logs=execution_logs + finalization_logs - ), - ) - - state_test(env=env, pre=pre, post=post, tx=tx) - - -@pytest.mark.parametrize( - "num_accounts", - [ - pytest.param(2, id="two_accounts"), - pytest.param(5, id="five_accounts"), - ], -) -def test_finalization_burn_logs_multi_account_ordering( - state_test: StateTestFiller, - env: Environment, - pre: Alloc, - sender: EOA, - fork: Fork, - num_accounts: int, -) -> None: - """ - Verify finalization burn logs are sorted lexicographically by address - when multiple accounts are marked for deletion in the same transaction. - - N accounts are created and SELFDESTRUCT'd in the same tx, then each - is funded by a dedicated payer contract called in REVERSE sorted - address order with a distinct nonzero amount. Every destroyed account - ends with a distinct nonzero balance at finalization, so a Burn log - is emitted for each. The resulting sequence of finalization burn logs - must appear in ascending address order regardless of call order. - """ - beneficiary = pre.deploy_contract(Op.STOP) - - factory_address = compute_create_address( - address=sender, nonce=sender.nonce - ) - created_addrs = [ - compute_create_address(address=factory_address, nonce=i + 1) - for i in range(num_accounts) - ] - sorted_addrs = sorted(created_addrs) - reverse_sorted = list(reversed(sorted_addrs)) - - # Each created contract is CALLed exactly once (to trigger SELFDESTRUCT); - # payers then forward via their own SELFDESTRUCT, so the created - # contracts are never re-invoked — no call-once guard is needed. - runtime = Op.SELFDESTRUCT(beneficiary) - initcode = Initcode(deploy_code=runtime) - initcode_len = len(initcode) - - create_balances = [1000 * (i + 1) for i in range(num_accounts)] - factory_balance = sum(create_balances) - pre.fund_address(factory_address, factory_balance) - - payer_code = Op.SELFDESTRUCT(Op.CALLDATALOAD(0)) - funding_amounts = [100 * (i + 1) for i in range(num_accounts)] - payers = [ - pre.deploy_contract(payer_code, balance=funding_amounts[i]) - for i in range(num_accounts) - ] - - factory_code: Bytecode = Om.MSTORE(initcode, 0) - for i in range(num_accounts): - factory_code += Op.TSTORE( - i, - Op.CREATE(value=create_balances[i], offset=0, size=initcode_len), - ) - for i in range(num_accounts): - factory_code += Op.CALL(gas=Op.GAS, address=Op.TLOAD(i), value=0) - for i in range(num_accounts): - factory_code += Op.MSTORE(0, reverse_sorted[i]) - factory_code += Op.CALL( - gas=Op.GAS, - address=payers[i], - args_offset=0, - args_size=32, - ) - - execution_logs = [ - transfer_log(factory_address, addr, create_balances[i]) - for i, addr in enumerate(created_addrs) - ] - execution_logs.extend( - transfer_log(addr, beneficiary, create_balances[i]) - for i, addr in enumerate(created_addrs) - ) - execution_logs.extend( - transfer_log(payers[i], reverse_sorted[i], funding_amounts[i]) - for i in range(num_accounts) - ) - - amount_by_addr = dict(zip(reverse_sorted, funding_amounts, strict=True)) - finalization_logs = [ - burn_log(addr, amount_by_addr[addr]) for addr in sorted_addrs - ] - - tx = Transaction( - sender=sender, - to=None, - data=factory_code, - expected_receipt=TransactionReceipt( - logs=execution_logs + finalization_logs - ), - ) - - post: dict[Address, Account | None] = dict.fromkeys( - created_addrs, Account.NONEXISTENT - ) - post[beneficiary] = Account(balance=factory_balance) - for payer in payers: - post[payer] = Account(balance=0) - - state_test(env=env, pre=pre, post=post, tx=tx) - - -@pytest.mark.parametrize( - "num_transfers", - [ - pytest.param(2, id="two_transfers"), - pytest.param(5, id="five_transfers"), - ], -) -def test_finalization_burn_log_single_account_multiple_transfers( - state_test: StateTestFiller, - env: Environment, - pre: Alloc, - sender: EOA, - fork: Fork, - num_transfers: int, -) -> None: - """ - Verify finalization emits a single Burn log summing multiple ETH transfers - to one to-be-destructed account. - - A single account is created and SELFDESTRUCT'd in the same tx, then N - payer contracts each send a distinct nonzero amount to it. Exactly ONE - Burn log MUST be emitted at finalization with the combined residual - balance, a client emitting one log per transfer would fail. - """ - beneficiary = pre.deploy_contract(Op.STOP) - - factory_address = compute_create_address( - address=sender, nonce=sender.nonce - ) - x = compute_create_address(address=factory_address, nonce=1) - - # x is only CALLed once (to trigger SELFDESTRUCT); payers forward via - # their own SELFDESTRUCT, so no call-once guard is needed. - runtime = Op.SELFDESTRUCT(beneficiary) - initcode = Initcode(deploy_code=runtime) - initcode_len = len(initcode) - - create_balance = 1000 - pre.fund_address(factory_address, create_balance) - - # N payer contracts, each sending a distinct nonzero amount to x - payer_code = Op.SELFDESTRUCT(x) - funding_amounts = [100 * (i + 1) for i in range(num_transfers)] - payers = [ - pre.deploy_contract(payer_code, balance=funding_amounts[i]) - for i in range(num_transfers) - ] - - # Factory creates x, triggers its SELFDESTRUCT, then calls each payer with - # x as the beneficiary so each payer's balance is forwarded to x. - factory_code: Bytecode = ( - Om.MSTORE(initcode, 0) - + Op.SSTORE( - 0, Op.CREATE(value=create_balance, offset=0, size=initcode_len) - ) - + Op.SSTORE( - 1, - Op.CALL(gas=Op.GAS, address=Op.SLOAD(0), value=0), - ) - ) - for i in range(num_transfers): - factory_code += Op.SSTORE( - 2 + i, - Op.CALL( - gas=Op.GAS, - address=payers[i], - args_offset=0, - args_size=32, - ), - ) - - execution_logs = [ - transfer_log(factory_address, x, create_balance), - transfer_log(x, beneficiary, create_balance), - ] - execution_logs.extend( - transfer_log(payers[i], x, funding_amounts[i]) - for i in range(num_transfers) - ) - - # Exactly one burn log with the SUM of transferred amounts - total_residual = sum(funding_amounts) - finalization_logs = [burn_log(x, total_residual)] - - tx = Transaction( - sender=sender, - to=None, - data=factory_code, - expected_receipt=TransactionReceipt( - logs=execution_logs + finalization_logs - ), - ) - - factory_storage = {0: x, 1: 1} - for i in range(num_transfers): - factory_storage[2 + i] = 1 - - post: dict[Address, Account | None] = { - x: Account.NONEXISTENT, - beneficiary: Account(balance=create_balance), - factory_address: Account(storage=factory_storage), - } - for payer in payers: - post[payer] = Account(balance=0) - - state_test(env=env, pre=pre, post=post, tx=tx) - - -@pytest.mark.parametrize( - "funded_after_selfdestruct", - [ - pytest.param(True, id="funded_after_selfdestruct"), - pytest.param(False, id="miner_fee_only"), - ], -) -def test_selfdestruct_finalization_after_priority_fee( - blockchain_test: BlockchainTestFiller, - pre: Alloc, - fork: Fork, - funded_after_selfdestruct: bool, -) -> None: - """ - Verify finalization burn logs are emitted after priority fee payment. - - Sets coinbase to a contract that self-destructs in the same tx. The - finalization burn log includes the priority fee, proving finalization - happens after fee payment per EIP-7708. - - funded_after_selfdestruct: - - if True: payer sends ETH, finalization = funding + priority_fee - - if False: no payer, finalization = priority_fee only - """ - genesis_base_fee = 7 - env = Environment(base_fee_per_gas=genesis_base_fee) - contract_balance = 1000 - funding_amount = 10_000 if funded_after_selfdestruct else 0 - - sender = pre.fund_eoa() - - factory_address = compute_create_address(address=sender, nonce=0) - created_address = compute_create_address(address=factory_address, nonce=1) - coinbase = created_address # coinbase == self-destructed contract - - # inner contract: simple SELFDESTRUCT to self - runtime_code = ( - Op.SELFDESTRUCT( - Op.ADDRESS, - # Gas accounting - address_warm=True, - account_new=False, - self_destructed_account=True, - self_destructed_account_code_deposit=len( - Op.SELFDESTRUCT(address=Op.ADDRESS) - ), - ) - if fork.is_eip_enabled(8037) - else Op.SELFDESTRUCT( - Op.ADDRESS, - # Gas accounting - address_warm=True, - account_new=False, - ) - ) - initcode = Initcode(deploy_code=runtime_code) - initcode_len = len(initcode) - - gas_costs = fork.gas_costs() - mem_after_mstore = ((initcode_len + 31) // 32) * 32 - - # The base factory code: CREATE + CALL to trigger selfdestruct - call_gas = 100_000 - if fork.is_eip_enabled(8037): - call_gas = 500_000 - factory_code = Om.MSTORE( - initcode, 0, new_memory_size=mem_after_mstore - ) + Op.CALL( - gas=call_gas, - address=Op.CREATE( - value=contract_balance, - offset=0, - size=initcode_len, - init_code_size=initcode_len, - ), - address_warm=True, - ) - - # optionally add payer call to fund coinbase after selfdestruct - payer = None - payer_runtime_gas = 0 - if funded_after_selfdestruct: - payer_code = Op.SELFDESTRUCT(Op.CALLDATALOAD(0)) - payer = pre.deploy_contract(payer_code, balance=funding_amount) - factory_code += Op.MSTORE(0, created_address) - factory_code += Op.CALL( - gas=call_gas, address=payer, args_offset=0, args_size=32 - ) - payer_runtime_gas = Op.SELFDESTRUCT( - Op.CALLDATALOAD(0), address_warm=True, account_new=False - ).gas_cost(fork) - - pre.fund_address(factory_address, contract_balance) - - # prio fee calc - gas_price = 10 - base_fee = fork.base_fee_per_gas_calculator()( - parent_base_fee_per_gas=genesis_base_fee, - parent_gas_used=0, - parent_gas_limit=env.gas_limit, - ) - priority_fee_per_gas = gas_price - base_fee - - intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( - calldata=bytes(factory_code), - contract_creation=True, - ) - factory_gas = factory_code.gas_cost(fork) - initcode_exec_gas = initcode.execution_gas(fork) - code_deposit_gas = len(runtime_code) * gas_costs.CODE_DEPOSIT_PER_BYTE - inner_runtime_gas = runtime_code.gas_cost(fork) - - gas_used = ( - intrinsic_gas - + factory_gas - + initcode_exec_gas - + code_deposit_gas - + inner_runtime_gas - + payer_runtime_gas - ) - - inner_runtime_refund = runtime_code.refund(fork) - gas_refunds = inner_runtime_refund - discount = min( - gas_refunds, - gas_used // 5, # max discount EIP-3529 - ) - priority_fee = priority_fee_per_gas * (gas_used - discount) - - # Finalization burn log proves coinbase received priority fee before log - finalization_balance: int | None = funding_amount + priority_fee - - expected_logs = [ - transfer_log(factory_address, created_address, contract_balance), - burn_log(created_address, contract_balance), - ] - - # if funded after selfdestruct, expect transfer log from payer - if funded_after_selfdestruct: - assert payer is not None - expected_logs.append( - transfer_log(payer, created_address, funding_amount) - ) - - # finalization burn log - if fork.is_eip_enabled(8037): - # TODO: Fix calculation of the exact expected gas usage - finalization_balance = None - expected_logs.append(burn_log(created_address, finalization_balance)) - tx = Transaction( - sender=sender, - to=None, - value=0, - data=factory_code, - gas_price=gas_price, - expected_receipt=TransactionReceipt(logs=expected_logs), - ) - - post: dict[Address, Account | None] = { - created_address: Account.NONEXISTENT, - } - if payer is not None: - post[payer] = Account(balance=0) - - blockchain_test( - pre=pre, - blocks=[ - Block( - txs=[tx], - fee_recipient=coinbase, - header_verify=Header(base_fee_per_gas=base_fee), - ) - ], - post=post, - genesis_environment=env, - ) diff --git a/tests/amsterdam/eip7708_eth_transfer_logs/test_fork_transition.py b/tests/amsterdam/eip7708_eth_transfer_logs/test_fork_transition.py index cfba6e930b8..2d79177e1c4 100644 --- a/tests/amsterdam/eip7708_eth_transfer_logs/test_fork_transition.py +++ b/tests/amsterdam/eip7708_eth_transfer_logs/test_fork_transition.py @@ -11,129 +11,16 @@ Alloc, Block, BlockchainTestFiller, - Op, Transaction, TransactionReceipt, - compute_create_address, ) -from .spec import burn_log, ref_spec_7708, transfer_log +from .spec import ref_spec_7708, transfer_log REFERENCE_SPEC_GIT_PATH = ref_spec_7708.git_path REFERENCE_SPEC_VERSION = ref_spec_7708.version -@pytest.mark.parametrize( - "same_tx,to_self", - [ - pytest.param(True, True, id="same_tx_to_self"), - pytest.param(False, True, id="pre_existing_to_self"), - pytest.param(False, False, id="pre_existing_to_other"), - ], -) -@pytest.mark.valid_at_transition_to("EIP7708") -def test_burn_log_at_fork_transition( - blockchain_test: BlockchainTestFiller, - pre: Alloc, - same_tx: bool, - to_self: bool, -) -> None: - """ - Test burn log emission across the EIP-7708 fork transition. - - same_tx_to_self: Factory CREATEs and selfdestructs to self in one tx. - At/after Amsterdam emits a CREATE transfer log + Burn log. - - pre_existing_to_self: Pre-existing contract selfdestructs to self. - No logs at any fork — SELFDESTRUCT to same account emits nothing. - - pre_existing_to_other: Pre-existing contract selfdestructs to a different - account. At/after Amsterdam emits a Transfer log. - """ - sender = pre.fund_eoa() - contract_balance = 1000 - - if same_tx: - initcode = Op.SELFDESTRUCT(Op.ADDRESS) - initcode_bytes = bytes(initcode) - initcode_len = len(initcode_bytes) - - factory_code = Op.MSTORE( - 0, Op.PUSH32(initcode_bytes.rjust(32, b"\x00")) - ) + Op.CREATE( - value=contract_balance, offset=32 - initcode_len, size=initcode_len - ) - - factory = pre.deploy_contract( - factory_code, balance=contract_balance * 3 - ) - created = [ - compute_create_address(address=factory, nonce=n) - for n in range(1, 4) - ] - targets = [factory] * 3 - - expected_logs = [ - [], - [ - transfer_log(factory, created[1], contract_balance), - burn_log(created[1], contract_balance), - ], - [ - transfer_log(factory, created[2], contract_balance), - burn_log(created[2], contract_balance), - ], - ] - post: dict = { - sender: Account(nonce=3), - created[0]: Account.NONEXISTENT, - created[1]: Account.NONEXISTENT, - created[2]: Account.NONEXISTENT, - } - elif to_self: - targets = [ - pre.deploy_contract( - Op.SELFDESTRUCT(Op.ADDRESS), balance=contract_balance - ) - for _ in range(3) - ] - expected_logs = [[], [], []] - post = {sender: Account(nonce=3)} - else: - beneficiary = pre.nonexistent_account() - targets = [ - pre.deploy_contract( - Op.SELFDESTRUCT(beneficiary), balance=contract_balance - ) - for _ in range(3) - ] - expected_logs = [ - [], - [transfer_log(targets[1], beneficiary, contract_balance)], - [transfer_log(targets[2], beneficiary, contract_balance)], - ] - post = { - sender: Account(nonce=3), - beneficiary: Account(balance=contract_balance * 3), - } - - blocks = [ - Block( - timestamp=ts, - txs=[ - Transaction( - to=targets[i], - sender=sender, - expected_receipt=TransactionReceipt(logs=expected_logs[i]), - ) - ], - ) - for i, ts in enumerate([14_999, 15_000, 15_001]) - ] - - blockchain_test(pre=pre, blocks=blocks, post=post) - - @pytest.mark.valid_at_transition_to("EIP7708") def test_transfer_log_fork_transition( blockchain_test: BlockchainTestFiller, pre: Alloc diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py index 737ba9e4c41..00f639bd6ff 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py @@ -2838,13 +2838,18 @@ def test_bal_create_selfdestruct_to_self_with_call( ), # Created address: ephemeral (created and destroyed same tx) # - storage_reads for slot 0x01 (aborted write becomes read) - # - NO nonce/code/storage/balance changes + # - NO nonce/code/storage changes + # - Balance remains per eip-8246 created_address: BalAccountExpectation( storage_reads=[0x01], storage_changes=[], nonce_changes=[], code_changes=[], - balance_changes=[], + balance_changes=[ + BalBalanceChange( + block_access_index=1, post_balance=endowment + ) + ], ), } ), @@ -2857,8 +2862,9 @@ def test_bal_create_selfdestruct_to_self_with_call( alice: Account(nonce=1), factory: Account(nonce=2, balance=factory_balance - endowment), oracle: Account(storage={0x01: 0x42}), - # Created address doesn't exist - destroyed in same tx - created_address: Account.NONEXISTENT, + created_address: Account( + balance=endowment, nonce=0, code=b"", storage={} + ), }, ) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py index 22fd9a29f87..c3e3528389f 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py @@ -29,7 +29,6 @@ Storage, Transaction, TransactionReceipt, - compute_create2_address, compute_create_address, ) from execution_testing.checklists import EIPChecklist @@ -1093,19 +1092,13 @@ def test_call_value_to_self_destructed_burns_value( ), balance=initial_balance, ) - # CREATE/CREATE2 address depends on the opcode, but for both the - # orchestrator's nonce after the deploy is 1 at the time of the - # CREATE. Using compute_create_address for CREATE is correct; for - # CREATE2 the deterministic address depends on salt and initcode. - # Use a salt of 0 and the initcode built above for CREATE2. - if create_opcode == Op.CREATE2: - created_address = compute_create2_address( - address=orchestrator, - salt=0, - initcode=bytes(inner_code), - ) - else: - created_address = compute_create_address(address=orchestrator, nonce=1) + created_address = compute_create_address( + address=orchestrator, + nonce=1, + salt=0, + initcode=bytes(inner_code), + opcode=create_opcode, + ) tx = Transaction( to=orchestrator, @@ -1113,11 +1106,15 @@ def test_call_value_to_self_destructed_burns_value( sender=pre.fund_eoa(), ) + created_address_account = Account.NONEXISTENT + if fork.is_eip_enabled(8246): + created_address_account = Account(balance=call_value * 2) + blockchain_test( pre=pre, blocks=[Block(txs=[tx])], post={ - created_address: Account.NONEXISTENT, + created_address: created_address_account, orchestrator: Account(balance=0), }, ) diff --git a/tests/amsterdam/eip8246_selfdestruct_no_burn/__init__.py b/tests/amsterdam/eip8246_selfdestruct_no_burn/__init__.py new file mode 100644 index 00000000000..53e52f58738 --- /dev/null +++ b/tests/amsterdam/eip8246_selfdestruct_no_burn/__init__.py @@ -0,0 +1 @@ +"""Tests for [EIP-8246: Remove SELFDESTRUCT Burn](https://eips.ethereum.org/EIPS/eip-8246).""" diff --git a/tests/amsterdam/eip8246_selfdestruct_no_burn/spec.py b/tests/amsterdam/eip8246_selfdestruct_no_burn/spec.py new file mode 100644 index 00000000000..d27c0b3f36a --- /dev/null +++ b/tests/amsterdam/eip8246_selfdestruct_no_burn/spec.py @@ -0,0 +1,17 @@ +"""Reference spec for [EIP-8246](https://eips.ethereum.org/EIPS/eip-8246).""" + +from dataclasses import dataclass + + +@dataclass(frozen=True) +class ReferenceSpec: + """Reference specification.""" + + git_path: str + version: str + + +ref_spec_8246 = ReferenceSpec( + git_path="EIPS/eip-8246.md", + version="3b30ff829e5e698f1c6f69427111d194b80af38d", +) diff --git a/tests/amsterdam/eip8246_selfdestruct_no_burn/test_selfdestruct_no_burn.py b/tests/amsterdam/eip8246_selfdestruct_no_burn/test_selfdestruct_no_burn.py new file mode 100644 index 00000000000..3ba7cc5fa6e --- /dev/null +++ b/tests/amsterdam/eip8246_selfdestruct_no_burn/test_selfdestruct_no_burn.py @@ -0,0 +1,227 @@ +"""Tests for [EIP-8246: Remove SELFDESTRUCT balance burn](https://eips.ethereum.org/EIPS/eip-8246).""" + +import pytest +from execution_testing import ( + Account, + Address, + Alloc, + Block, + BlockchainTestFiller, + Bytecode, + Hash, + Op, + Storage, + Transaction, + compute_create_address, + keccak256, +) + +from .spec import ref_spec_8246 + +REFERENCE_SPEC_GIT_PATH = ref_spec_8246.git_path +REFERENCE_SPEC_VERSION = ref_spec_8246.version + +pytestmark = pytest.mark.valid_from("EIP8246") + + +@pytest.mark.parametrize("initial_balance", [0, 1]) +@pytest.mark.parametrize("create_opcode", [Op.CREATE, Op.CREATE2]) +@pytest.mark.parametrize("post_send_count", [0, 1, 3]) +@pytest.mark.parametrize( + "post_send_opcode", [Op.CALL, Op.CALLCODE, Op.SELFDESTRUCT] +) +@pytest.mark.parametrize( + "initial_storage", + [ + pytest.param(False, id="no_storage"), + pytest.param(True, id="with_storage"), + ], +) +@pytest.mark.parametrize( + "transfer_target, transfer_drains_victim", + [ + pytest.param(Op.ADDRESS, False, id="self"), + pytest.param(0x01, True, id="precompile"), + pytest.param( + Address(keccak256(b"eip-8246-eoa-target")[-20:]), + True, + id="eoa", + ), + ], +) +@pytest.mark.parametrize( + "exit_op, execution_success", + [ + pytest.param(Op.STOP, True, id="success"), + pytest.param(Op.REVERT(0, 0), False, id="revert"), + pytest.param(Op.MSTORE(2**32, 0), False, id="oog"), + ], +) +def test_selfdestructing_initcode_preserves_balance( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + initial_balance: int, + post_send_count: int, + create_opcode: Op, + post_send_opcode: Op, + initial_storage: bool, + transfer_target: Op, + transfer_drains_victim: bool, + exit_op: Op, + execution_success: bool, +) -> None: + """ + Same-tx SELFDESTRUCT preserves the victim's balance per EIP-8246. + + Test flow: + selfdestruct_tx + tx.to = entry_contract + └─ CALL selfdestruct_contract_factory + └─ initcode runs: + [optional] SSTORE(slot, value) + SELFDESTRUCT(transfer_target) # registers victim + └─ selfdestruct_contract_factory exits via STOP | REVERT | OOG + └─ N * post-send to victim (CALL | CALLCODE | donor.SELFDESTRUCT) + + tx finalize + - victim balance-only per EIP-8246, + - or NONEXISTENT if EIP-161 cleans up a zero-balance account + + probe_tx + tx.to = probe_contract + └─ STORAGE [0] = BALANCE(victim) + STORAGE [1] = EXTCODEHASH(victim) + STORAGE [2] = EXTCODESIZE(victim) + STORAGE [3] = SHA3(EXTCODECOPY(victim, 0, 0, size)) + """ + # Selfdestruct target contract template. + # Optionally initializes storage to test clearing. + storage_init = Op.SSTORE(0, 1) if initial_storage else Bytecode() + selfdestruct_initcode = storage_init + Op.SELFDESTRUCT(transfer_target) + + selfdestruct_template = pre.deploy_contract(code=selfdestruct_initcode) + + # Build selfdestruct target contract via CREATE/CREATE2 + salt = 0 + if create_opcode == Op.CREATE2: + create_call = create_opcode( + value=initial_balance, + size=len(selfdestruct_initcode), + salt=salt, + ) + else: + create_call = create_opcode( + value=initial_balance, + size=len(selfdestruct_initcode), + ) + + # Selfdestruct target contract factory + # Exits via STOP/REVERT/OOG for different scenario + selfdestruct_contract_factory = pre.deploy_contract( + code=Op.EXTCODECOPY( + address=selfdestruct_template, size=len(selfdestruct_initcode) + ) + + Op.POP(create_call) + + exit_op + ) + + victim = compute_create_address( + address=selfdestruct_contract_factory, + opcode=create_opcode, + nonce=1, + salt=salt, + initcode=selfdestruct_initcode, + ) + + # Post value sending to the victim + # Ensure the ether transfer is not burned after eip-8246. + post_send_value = 1 + if post_send_opcode == Op.SELFDESTRUCT: + donor = pre.deploy_contract(code=Op.SELFDESTRUCT(victim)) + post_send = Op.POP( + Op.CALL(gas=Op.GAS, address=donor, value=post_send_value) + ) + else: + post_send = Op.POP( + post_send_opcode(gas=Op.GAS, address=victim, value=post_send_value) + ) + + entry_contract = pre.deploy_contract( + code=Op.POP( + Op.CALL( + gas=Op.GAS, + address=selfdestruct_contract_factory, + value=initial_balance, + ) + ) + + post_send * post_send_count + ) + + total_balance = initial_balance + post_send_count * post_send_value + + sender = pre.fund_eoa() + selfdestruct_tx = Transaction( + sender=sender, to=entry_contract, value=total_balance + ) + + # Balance verification + # retained: + # selfdestruct-to-self retains balance + # selfdestruct-to-others drains balance if not revert / OOG + # delivered: post-sends count except for CALLCODE + retained = 0 if transfer_drains_victim else initial_balance + delivered = ( + 0 + if post_send_opcode == Op.CALLCODE + else post_send_count * post_send_value + ) + + expected_balance = retained + delivered if execution_success else delivered + victim_alive = expected_balance > 0 + + probe_storage = Storage() + probe_code = ( + Op.SSTORE( + probe_storage.store_next(expected_balance), + Op.BALANCE(Op.CALLDATALOAD(0)), + ) + + Op.SSTORE( + probe_storage.store_next(keccak256(b"") if victim_alive else 0), + Op.EXTCODEHASH(Op.CALLDATALOAD(0)), + ) + + Op.SSTORE( + probe_storage.store_next(0), + Op.EXTCODESIZE(Op.CALLDATALOAD(0)), + ) + + Op.EXTCODECOPY( + Op.CALLDATALOAD(0), 0, 0, Op.EXTCODESIZE(Op.CALLDATALOAD(0)) + ) + + Op.SSTORE( + probe_storage.store_next(keccak256(b"")), + Op.SHA3(0, Op.EXTCODESIZE(Op.CALLDATALOAD(0))), + ) + + Op.STOP + ) + + probe_contract = pre.deploy_contract( + code=probe_code, storage=probe_storage.canary() + ) + + probe_tx = Transaction( + sender=sender, to=probe_contract, data=Hash(victim, left_padding=True) + ) + + blockchain_test( + pre=pre, + post={ + victim: ( + Account.NONEXISTENT + if not victim_alive + else Account( + balance=expected_balance, nonce=0, code=b"", storage={} + ) + ), + probe_contract: Account(storage=probe_storage), + }, + blocks=[Block(txs=[selfdestruct_tx, probe_tx])], + ) diff --git a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py index d37cfbb4490..a78ddccb011 100644 --- a/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py +++ b/tests/cancun/eip6780_selfdestruct/test_selfdestruct.py @@ -29,10 +29,7 @@ ) from execution_testing.forks import Cancun -from tests.amsterdam.eip7708_eth_transfer_logs.spec import ( - burn_log, - transfer_log, -) +from tests.amsterdam.eip7708_eth_transfer_logs.spec import transfer_log REFERENCE_SPEC_GIT_PATH = "EIPS/eip-6780.md" REFERENCE_SPEC_VERSION = "1b6a0e94cc47e859b9866e570391cf37dc55059a" @@ -326,14 +323,7 @@ def test_create_selfdestruct_same_tx( # SELFDESTRUCT emits a Transfer log to a different address, or a Burn # log when sending to self (contract was created in this tx). if selfdestruct_contract_current_balance > 0: - if sendall_recipient == selfdestruct_contract_address: - expected_logs_after_tx_value.append( - burn_log( - selfdestruct_contract_address, - selfdestruct_contract_current_balance, - ) - ) - else: + if sendall_recipient != selfdestruct_contract_address: expected_logs_after_tx_value.append( transfer_log( selfdestruct_contract_address, @@ -347,14 +337,15 @@ def test_create_selfdestruct_same_tx( sendall_final_balances[sendall_recipient] += ( selfdestruct_contract_current_balance ) - - # Self-destructing contract must always have zero balance after the - # call because the self-destruct always happens in the same transaction - # in this test - selfdestruct_contract_current_balance = 0 + selfdestruct_contract_current_balance = 0 + elif not fork.is_eip_enabled(8246): + # per EIP-8246 + selfdestruct_contract_current_balance = 0 entry_code += Op.SSTORE( - entry_code_storage.store_next(0), + entry_code_storage.store_next( + selfdestruct_contract_current_balance + ), Op.BALANCE(selfdestruct_contract_address), ) @@ -392,7 +383,16 @@ def test_create_selfdestruct_same_tx( for address, balance in sendall_final_balances.items(): post[address] = Account(balance=balance, storage={0: 1}) - post[selfdestruct_contract_address] = Account.NONEXISTENT # type: ignore + if fork.is_eip_enabled(8246) and selfdestruct_contract_current_balance > 0: + # per EIP-8246 + post[selfdestruct_contract_address] = Account( + balance=selfdestruct_contract_current_balance, + nonce=0, + code=b"", + storage={}, + ) + else: + post[selfdestruct_contract_address] = Account.NONEXISTENT # type: ignore if fork.is_eip_enabled(7708): expected_logs = [] @@ -485,6 +485,7 @@ def test_self_destructing_initcode( # Call the self-destructing contract multiple times as required, increasing # the wei sent each time entry_code_balance = 0 + selfdestruct_contract_address_remaining_balance = 0 for i in range(call_times): entry_code += Op.SSTORE( entry_code_storage.store_next(1), @@ -500,6 +501,11 @@ def test_self_destructing_initcode( ) entry_code_balance += i + if fork.is_eip_enabled(8246): + # After the first call, this call value will become stuck since + # EIP-8246 disables self-destruct burns. + selfdestruct_contract_address_remaining_balance += i + entry_code += Op.SSTORE( entry_code_storage.store_next(entry_code_balance), Op.BALANCE(selfdestruct_contract_address), @@ -531,14 +537,16 @@ def test_self_destructing_initcode( entry_code_address: Account( storage=entry_code_storage, ), - selfdestruct_contract_address: Account.NONEXISTENT, # type: ignore + selfdestruct_contract_address: Account.NONEXISTENT + if selfdestruct_contract_address_remaining_balance == 0 + else Account(balance=selfdestruct_contract_address_remaining_balance), # type: ignore sendall_recipient_addresses[0]: Account( balance=sendall_amount, storage={0: 1} ), } + expected_logs = [] if fork.is_eip_enabled(7708): - expected_logs = [] # tx value transfer: sender -> entry_code_address (created contract) if entry_code_balance > 0: expected_logs.append( @@ -561,13 +569,7 @@ def test_self_destructing_initcode( entry_code_address, selfdestruct_contract_address, i ) ) - # At finalization the (destroyed) contract has the accumulated - # post-SELFDESTRUCT balance, which is burned. - if entry_code_balance > 0: - expected_logs.append( - burn_log(selfdestruct_contract_address, entry_code_balance) - ) - tx.expected_receipt = TransactionReceipt(logs=expected_logs) + tx.expected_receipt = TransactionReceipt(logs=expected_logs) state_test(pre=pre, post=post, tx=tx) @@ -758,15 +760,8 @@ def test_recreate_self_destructed_contract_different_txs( if i == 0 and selfdestruct_contract_initial_balance > 0: if ( sendall_recipient_addresses[0] - == selfdestruct_contract_address + != selfdestruct_contract_address ): - tx_logs.append( - burn_log( - selfdestruct_contract_address, - selfdestruct_contract_initial_balance, - ) - ) - else: tx_logs.append( transfer_log( selfdestruct_contract_address, @@ -789,9 +784,25 @@ def test_recreate_self_destructed_contract_different_txs( entry_code_address: Account( storage=entry_code_storage, ), - selfdestruct_contract_address: Account.NONEXISTENT, # type: ignore } - if sendall_recipient_addresses[0] != selfdestruct_contract_address: + self_target = ( + sendall_recipient_addresses[0] == selfdestruct_contract_address + ) + if ( + fork.is_eip_enabled(8246) + and self_target + and selfdestruct_contract_initial_balance > 0 + ): + # per EIP-8246 + post[selfdestruct_contract_address] = Account( + balance=selfdestruct_contract_initial_balance, + nonce=0, + code=b"", + storage={}, + ) + else: + post[selfdestruct_contract_address] = Account.NONEXISTENT # type: ignore + if not self_target: post[sendall_recipient_addresses[0]] = Account( balance=sendall_amount, storage={0: 1} ) diff --git a/tests/cancun/eip6780_selfdestruct/test_selfdestruct_revert.py b/tests/cancun/eip6780_selfdestruct/test_selfdestruct_revert.py index f9f27484a9e..125de382a17 100644 --- a/tests/cancun/eip6780_selfdestruct/test_selfdestruct_revert.py +++ b/tests/cancun/eip6780_selfdestruct/test_selfdestruct_revert.py @@ -389,7 +389,7 @@ def test_selfdestruct_created_in_same_tx_with_revert( # noqa SC200 0, # ret length ) - post: Dict[Address, Account] = { + post: Dict[Address, Account | None] = { entry_code_address: Account( code="0x", storage=Storage( @@ -408,7 +408,15 @@ def test_selfdestruct_created_in_same_tx_with_revert( # noqa SC200 } if selfdestruct_on_outer_call > 0: - post[selfdestruct_with_transfer_contract_address] = Account.NONEXISTENT # type: ignore + if selfdestruct_on_outer_call == 1 and fork.is_eip_enabled(8246): + # per EIP-8246 + post[selfdestruct_with_transfer_contract_address] = Account( + balance=1, + ) + else: + post[selfdestruct_with_transfer_contract_address] = ( + Account.NONEXISTENT + ) post[selfdestruct_recipient_address] = Account( balance=1 if selfdestruct_on_outer_call == 1 else 2, ) @@ -426,7 +434,7 @@ def test_selfdestruct_created_in_same_tx_with_revert( # noqa SC200 } ), ) - post[selfdestruct_recipient_address] = Account.NONEXISTENT # type: ignore + post[selfdestruct_recipient_address] = Account.NONEXISTENT tx = Transaction( data=entry_code, @@ -439,15 +447,29 @@ def test_selfdestruct_created_in_same_tx_with_revert( # noqa SC200 account_expectations = {} if selfdestruct_on_outer_call > 0: - account_expectations[ - selfdestruct_with_transfer_contract_address - ] = BalAccountExpectation( - storage_reads=[0, 1], # Storage was accessed - nonce_changes=[], - balance_changes=[], - code_changes=[], - storage_changes=[], - ) + if selfdestruct_on_outer_call == 1 and fork.is_eip_enabled(8246): + # per EIP-8246 + account_expectations[ + selfdestruct_with_transfer_contract_address + ] = BalAccountExpectation( + storage_reads=[0, 1], # Storage was accessed + nonce_changes=[], + balance_changes=[ + BalBalanceChange(block_access_index=1, post_balance=1) + ], + code_changes=[], + storage_changes=[], + ) + else: + account_expectations[ + selfdestruct_with_transfer_contract_address + ] = BalAccountExpectation( + storage_reads=[0, 1], # Storage was accessed + nonce_changes=[], + balance_changes=[], + code_changes=[], + storage_changes=[], + ) account_expectations[selfdestruct_recipient_address] = ( BalAccountExpectation( balance_changes=[ diff --git a/tests/frontier/create/test_create_suicide_during_init.py b/tests/frontier/create/test_create_suicide_during_init.py index 88ce37f0b31..e0976833aea 100644 --- a/tests/frontier/create/test_create_suicide_during_init.py +++ b/tests/frontier/create/test_create_suicide_during_init.py @@ -84,7 +84,7 @@ def test_create_suicide_during_transaction_create( expected_create_address = compute_create_address( address=sender if transaction_create else contract_deploy, - nonce=1 if transaction_create else 0, + nonce=0 if transaction_create else 1, initcode=contract_initcode, opcode=create_opcode, ) @@ -98,6 +98,10 @@ def test_create_suicide_during_transaction_create( protected=fork.supports_protected_txs(), ) + # per EIP-8246 + selfdestruct_to_self_preserves_balance = ( + fork.is_eip_enabled(8246) and operation == Operation.SUICIDE_TO_ITSELF + ) post = { contract_success: Account(storage={1: 1}), self_destruct_destination: Account( @@ -107,6 +111,10 @@ def test_create_suicide_during_transaction_create( contract_after_suicide: Account( storage={1: 0} ), # suicide eats all gas - expected_create_address: Account.NONEXISTENT, + expected_create_address: ( + Account(balance=tx_value, nonce=0, code=b"", storage={}) + if selfdestruct_to_self_preserves_balance + else Account.NONEXISTENT + ), } state_test(env=Environment(), pre=pre, post=post, tx=tx) diff --git a/tests/paris/security/test_selfdestruct_balance_bug.py b/tests/paris/security/test_selfdestruct_balance_bug.py index 719f78d3c93..1ff82ed412e 100644 --- a/tests/paris/security/test_selfdestruct_balance_bug.py +++ b/tests/paris/security/test_selfdestruct_balance_bug.py @@ -19,6 +19,7 @@ Block, BlockchainTestFiller, CalldataCase, + Fork, Initcode, Op, Switch, @@ -29,7 +30,7 @@ @pytest.mark.valid_from("Constantinople") def test_tx_selfdestruct_balance_bug( - blockchain_test: BlockchainTestFiller, pre: Alloc + blockchain_test: BlockchainTestFiller, pre: Alloc, fork: Fork ) -> None: """ Test that the vulnerability is not present by checking the balance of the @@ -125,15 +126,19 @@ def test_tx_selfdestruct_balance_bug( ), ] + # per EIP-8246 + if fork.is_eip_enabled(8246): + probe_1_balance = 4 + probe_2_balance = 9 + else: + probe_1_balance = 0 + probe_2_balance = 5 + post = { # Check call from caller has succeeded. cc_address: Account(nonce=2, storage={0xCA1101: 1}), - # Check balance of 0xaa after tx 1 is 0 wei, i.e self-destructed. - # Vulnerable versions should return 1 wei. - balance_address_1: Account(storage={0xBA1AA: 0}), - # Check that 0xaa exists and balance after tx 2 is 5 wei. - # Vulnerable versions should return 6 wei. - balance_address_2: Account(storage={0xBA1AA: 5}), + balance_address_1: Account(storage={0xBA1AA: probe_1_balance}), + balance_address_2: Account(storage={0xBA1AA: probe_2_balance}), aa_location: Account(storage={0: 0}), } diff --git a/tests/ported_static/stCreate2/test_create2_suicide.py b/tests/ported_static/stCreate2/test_create2_suicide.py index 41d417f8456..c2f522daf2a 100644 --- a/tests/ported_static/stCreate2/test_create2_suicide.py +++ b/tests/ported_static/stCreate2/test_create2_suicide.py @@ -189,7 +189,20 @@ def test_create2_suicide( }, { "indexes": {"data": [6, 7], "gas": -1, "value": -1}, - "network": [">=Cancun"], + "network": [">=Cancun=Amsterdam"], "result": { compute_create_address(address=sender, nonce=0): Account( balance=9, nonce=2 @@ -199,6 +212,18 @@ def test_create2_suicide( ): Account.NONEXISTENT, }, }, + { + "indexes": {"data": [7], "gas": -1, "value": -1}, + "network": [">=Amsterdam"], + "result": { + compute_create_address(address=sender, nonce=0): Account( + balance=9, nonce=2 + ), + Address(0x6CD0E5133771823DA00D4CB545EC8CDAB0E38203): Account( + balance=1, nonce=0, code=b"", storage={} + ), + }, + }, { "indexes": {"data": [8, 9], "gas": -1, "value": -1}, "network": [">=Cancun"], diff --git a/tests/ported_static/stInitCodeTest/test_transaction_create_suicide_in_initcode.py b/tests/ported_static/stInitCodeTest/test_transaction_create_suicide_in_initcode.py index a938f5a0fc8..4ed3a8341d8 100644 --- a/tests/ported_static/stInitCodeTest/test_transaction_create_suicide_in_initcode.py +++ b/tests/ported_static/stInitCodeTest/test_transaction_create_suicide_in_initcode.py @@ -50,6 +50,7 @@ def test_transaction_create_suicide_in_initcode( pre[coinbase] = Account(balance=0, nonce=1) + tx_value = 1 tx = Transaction( sender=sender, to=None, @@ -58,8 +59,14 @@ def test_transaction_create_suicide_in_initcode( value=1, ) + # per EIP-8246 + created_address = compute_create_address(address=sender, nonce=0) post = { - compute_create_address(address=sender, nonce=0): Account.NONEXISTENT, + created_address: ( + Account(balance=tx_value, nonce=0, code=b"", storage={}) + if fork.is_eip_enabled(8246) + else Account.NONEXISTENT + ), sender: Account(nonce=1), } diff --git a/tests/tangerine_whistle/eip150_operation_gas_costs/test_eip150_selfdestruct.py b/tests/tangerine_whistle/eip150_operation_gas_costs/test_eip150_selfdestruct.py index 49cc095d44c..b91b57d4260 100644 --- a/tests/tangerine_whistle/eip150_operation_gas_costs/test_eip150_selfdestruct.py +++ b/tests/tangerine_whistle/eip150_operation_gas_costs/test_eip150_selfdestruct.py @@ -1009,8 +1009,18 @@ def test_selfdestruct_to_self( if fork.is_eip_enabled(7928): if same_tx: if is_success: - # Created and destroyed in same tx - no net changes for victim - victim_expectation = BalAccountExpectation.empty() + # per EIP-8246 + if fork.is_eip_enabled(8246) and originator_balance > 0: + victim_expectation = BalAccountExpectation( + balance_changes=[ + BalBalanceChange( + block_access_index=1, + post_balance=originator_balance, + ) + ], + ) + else: + victim_expectation = BalAccountExpectation.empty() else: # OOG: CREATE succeeded but SELFDESTRUCT failed victim_expectation = BalAccountExpectation( @@ -1090,8 +1100,19 @@ def test_selfdestruct_to_self( victim: Account(balance=originator_balance, code=victim_code), } else: - contract_destroyed = fork < Cancun or same_tx - if contract_destroyed: + # per EIP-8246 + if fork.is_eip_enabled(8246) and same_tx and originator_balance > 0: + post = { + alice: Account(nonce=1), + caller: Account(nonce=caller_nonce), + victim: Account( + balance=originator_balance, + nonce=0, + code=b"", + storage={}, + ), + } + elif fork < Cancun or same_tx: post = { alice: Account(nonce=1), caller: Account(nonce=caller_nonce), @@ -1171,6 +1192,19 @@ def test_initcode_selfdestruct_to_self( BalBalanceChange(block_access_index=1, post_balance=0) ) + # per EIP-8246 + if fork.is_eip_enabled(8246) and originator_balance > 0: + victim_initcode_expectation = BalAccountExpectation( + balance_changes=[ + BalBalanceChange( + block_access_index=1, + post_balance=originator_balance, + ) + ], + ) + else: + victim_initcode_expectation = BalAccountExpectation.empty() + expected_bal = BlockAccessListExpectation( account_expectations={ alice: BalAccountExpectation( @@ -1179,15 +1213,22 @@ def test_initcode_selfdestruct_to_self( ], ), caller: caller_expectation, - victim: BalAccountExpectation.empty(), + victim: victim_initcode_expectation, } ) - # Contract was created and destroyed in same tx + # per EIP-8246 + victim_post: Account | None + if fork.is_eip_enabled(8246) and originator_balance > 0: + victim_post = Account( + balance=originator_balance, nonce=0, code=b"", storage={} + ) + else: + victim_post = Account.NONEXISTENT post: dict = { alice: Account(nonce=1), caller: Account(nonce=2), - victim: Account.NONEXISTENT, + victim: victim_post, } blockchain_test( From b6956dbe42a64d143569d5e9b20a0e7f7b9d0ac9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Thu, 2 Jul 2026 11:06:13 +0200 Subject: [PATCH 074/233] chore(test-client-clis): migrate evmone t8n to the `evmone t8n` subcommand (#3063) --- .claude/commands/fill-tests.md | 2 +- .../build-evm-client/evmone/action.yaml | 2 +- .github/configs/evm.yaml | 6 +++--- docs/filling_tests/debugging_t8n_tools.md | 6 +++--- docs/filling_tests/transition_tool_support.md | 2 +- .../client_clis/clis/evmone.py | 20 +++++++++---------- .../client_clis/tests/test_transition_tool.py | 6 +++--- .../client_clis/transition_tool.py | 9 ++++++--- 8 files changed, 27 insertions(+), 26 deletions(-) diff --git a/.claude/commands/fill-tests.md b/.claude/commands/fill-tests.md index 46fbc0262c9..909ee26fe3a 100644 --- a/.claude/commands/fill-tests.md +++ b/.claude/commands/fill-tests.md @@ -39,7 +39,7 @@ uv run fill --collect-only tests/ # Dry run: list tests wit - Excluded from a broad `tests/` run: include them by targeting a `tests/benchmark/...` path, or add `--include-benchmark` when also collecting `tests/`. - Pick a mode (mutually exclusive): `--gas-benchmark-values 1,10,100` (millions of gas) or `--fixed-opcode-count 1,10,100` (thousands). These parametrize the tests, e.g. `...[fork_Prague-blockchain_test-benchmark-gas-value_1M]`. -- Backend is optional: omitting `--evm-bin` runs the slow in-repo EELS Python spec; `--evm-bin=evmone-t8n` or `--evm-bin=evm` (geth, used by `just bench-gas`) are faster. +- Backend is optional: omitting `--evm-bin` runs the slow in-repo EELS Python spec; `--evm-bin=evmone` or `--evm-bin=evm` (geth, used by `just bench-gas`) are faster. ## Fixture Formats diff --git a/.github/actions/build-evm-client/evmone/action.yaml b/.github/actions/build-evm-client/evmone/action.yaml index eb6d8b0a3f2..f6cd072756c 100644 --- a/.github/actions/build-evm-client/evmone/action.yaml +++ b/.github/actions/build-evm-client/evmone/action.yaml @@ -64,7 +64,7 @@ runs: run: | mkdir -p $GITHUB_WORKSPACE/bin cd $GITHUB_WORKSPACE/evmone - cmake -S . -B build -DEVMONE_TESTING=ON -DEVMONE_PRECOMPILES_SILKPRE=1 + cmake -S . -B build cmake --build build --parallel --target ${{ inputs.targets }} - name: Add evmone bin to PATH shell: bash diff --git a/.github/configs/evm.yaml b/.github/configs/evm.yaml index 98269b5c38f..621ad39623d 100644 --- a/.github/configs/evm.yaml +++ b/.github/configs/evm.yaml @@ -14,10 +14,10 @@ evmone: impl: evmone repo: ethereum/evmone ref: master - targets: ["evmone-t8n"] - evm-bin: evmone-t8n + evm-bin: evmone xdist: auto -geth: + targets: ["evmone-cli"] +benchmark: impl: geth repo: ethereum/go-ethereum ref: master diff --git a/docs/filling_tests/debugging_t8n_tools.md b/docs/filling_tests/debugging_t8n_tools.md index 2352bec641d..bba71b2e23d 100644 --- a/docs/filling_tests/debugging_t8n_tools.md +++ b/docs/filling_tests/debugging_t8n_tools.md @@ -102,7 +102,7 @@ For example, running: ```console fill tests/berlin/eip2930_access_list/ --fork Berlin -m blockchain_test \ --evm-dump-dir==/tmp/evm-dump \ - --evm-bin=../evmone/build/bin/evmone-t8n \ + --evm-bin=../evmone/build/bin/evmone \ --verify-fixtures-bin=../go-ethereum/build/bin/evm \ --verify-fixtures ``` @@ -156,7 +156,7 @@ where the `verify_fixtures.sh` script can be used to reproduce the `evm blocktes 4. Explicitly set two different `evm` binaries to execute the `t8n` and `blocktest` commands; write debug data to the specified `--evm-dump-dir`: ```console - fill --evm-bin=../evmone/build/bin/evmone-t8n \ + fill --evm-bin=../evmone/build/bin/evmone \ --verify-fixtures-bin=../go-ethereum/build/bin/evm \ --evm-dump-dir=/tmp/evm-dump ``` @@ -164,7 +164,7 @@ where the `verify_fixtures.sh` script can be used to reproduce the `evm blocktes 5. Additionally use `--single-fixture-per-file` to improve the granularity of the reporting of the `evm blocktest` command by writing the fixture generated by each parametrized test case to its own file. ```console - fill --evm-bin=../evmone/build/bin/evmone-t8n \ + fill --evm-bin=../evmone/build/bin/evmone \ --verify-fixtures-bin=../go-ethereum/build/bin/evm \ --evm-dump-dir=/tmp/evm-dump \ --single-fixture-per-file diff --git a/docs/filling_tests/transition_tool_support.md b/docs/filling_tests/transition_tool_support.md index f151f05a7d6..5a404f5dd07 100644 --- a/docs/filling_tests/transition_tool_support.md +++ b/docs/filling_tests/transition_tool_support.md @@ -4,7 +4,7 @@ The following transition tools are supported by the framework: | Client | `t8n` Tool | Tracing Support | | -------| ---------- | --------------- | -| [ethereum/evmone](https://github.com/ethereum/evmone) | `evmone-t8n` | Yes | +| [ethereum/evmone](https://github.com/ethereum/evmone) | `evmone t8n` | Yes | | [ethereum/execution-specs](https://github.com/ethereum/execution-specs) | [`ethereum-spec-evm t8n`](https://github.com/ethereum/execution-specs/tree/a48e0b381d5225a6c3de2d06cd9ee7ae0b6ca9bb/src/ethereum_spec_tools/evm_tools/t8n) | Yes | | [ethereumjs](https://github.com/ethereumjs/ethereumjs-monorepo) | [`ethereumjs-t8ntool.sh`](https://github.com/ethereumjs/ethereumjs-monorepo/tree/master/packages/vm/test/t8n) | No | | [ethereum/go-ethereum](https://github.com/ethereum/go-ethereum) | [`evm t8n`](https://github.com/ethereum/go-ethereum/tree/master/cmd/evm) | Yes | diff --git a/packages/testing/src/execution_testing/client_clis/clis/evmone.py b/packages/testing/src/execution_testing/client_clis/clis/evmone.py index 8157d29f938..7a6cfe0f917 100644 --- a/packages/testing/src/execution_testing/client_clis/clis/evmone.py +++ b/packages/testing/src/execution_testing/client_clis/clis/evmone.py @@ -34,10 +34,14 @@ class EvmOneTransitionTool(TransitionTool): - """Evmone `evmone-t8n` Transition tool interface wrapper class.""" - - default_binary = Path("evmone-t8n") - detect_binary_pattern = re.compile(r"^evmone-t8n\b") + """Evmone `evmone t8n` Transition tool interface wrapper class.""" + + default_binary = Path("evmone") + # Match the `evmone` binary's version banner (`evmone `) while + # excluding the sibling `evmone-statetest` / `evmone-blockchaintest` tools. + detect_binary_pattern = re.compile(r"^evmone\b(?!-)") + version_flag = "--version" + subcommand = "t8n" t8n_use_stream = False binary: Path @@ -46,12 +50,6 @@ class EvmOneTransitionTool(TransitionTool): supports_opcode_count: ClassVar[bool] = True supports_blob_params: ClassVar[bool] = True - # evmone uses space-separated fork names for some forks - fork_name_map: ClassVar[Dict[str, str]] = { - "TangerineWhistle": "Tangerine Whistle", - "SpuriousDragon": "Spurious Dragon", - } - def __init__( self, *, @@ -67,7 +65,7 @@ def __init__( def is_fork_supported(self, fork: Fork) -> bool: """ - Return True if the fork is supported by the tool. Currently, evmone-t8n + Return True if the fork is supported by the tool. Currently, evmone provides no way to determine supported forks. """ del fork diff --git a/packages/testing/src/execution_testing/client_clis/tests/test_transition_tool.py b/packages/testing/src/execution_testing/client_clis/tests/test_transition_tool.py index fa919bdbf42..c6f91382478 100644 --- a/packages/testing/src/execution_testing/client_clis/tests/test_transition_tool.py +++ b/packages/testing/src/execution_testing/client_clis/tests/test_transition_tool.py @@ -44,9 +44,9 @@ def test_default_tool() -> None: GethTransitionTool, ), ( - Path("evmone-t8n"), - "evmone-t8n", - "evmone-t8n 0.11.0-dev+commit.93997506", + Path("evmone"), + "evmone", + "evmone 0.22.0", EvmOneTransitionTool, ), pytest.param( diff --git a/packages/testing/src/execution_testing/client_clis/transition_tool.py b/packages/testing/src/execution_testing/client_clis/transition_tool.py index 93d1d7e17d5..2e75fcb7fbe 100644 --- a/packages/testing/src/execution_testing/client_clis/transition_tool.py +++ b/packages/testing/src/execution_testing/client_clis/transition_tool.py @@ -414,9 +414,12 @@ def _evaluate_filesystem( ) fork_name = self.fork_name_map.get(fork_name, fork_name) - # Construct args for evmone-t8n binary - args = [ - str(self.binary), + # Prepend the binary and its t8n subcommand if it uses one (e.g. + # evmone's `t8n`), as construct_args_stream does, then the t8n flags. + args = [str(self.binary)] + if self.subcommand: + args.append(self.subcommand) + args += [ "--state.fork", fork_name, "--input.alloc", From 9c938836b9e50aba1ea920c978d49d0b188d6273 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Thu, 2 Jul 2026 13:45:22 +0200 Subject: [PATCH 075/233] feat(tests): EIP-8037 spilled NEW_ACCOUNT gas consumed on caller halt (#3061) --- .../test_state_gas_call.py | 140 +++++++++++++++++- 1 file changed, 136 insertions(+), 4 deletions(-) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py index c3e3528389f..a30fdb28391 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py @@ -2,10 +2,12 @@ Test CALL state gas reservoir passing under EIP-8037. The full state gas reservoir is passed to child call frames with no -63/64 rule. On child success, remaining state gas returns to the -parent. On child revert or exceptional halt, all state gas, both -reservoir and any that spilled into `gas_left`, is restored to the -parent's reservoir (only CPU gas is consumed for the failed frame). +63/64 rule. On child success, remaining state gas returns to the parent. +On revert, the frame's state gas is refilled in LIFO order: the portion +that spilled into `gas_left` returns there and the reservoir-funded +portion restores the reservoir. An exceptional halt likewise resets the +reservoir to its start-of-frame value, but the spilled portion stays +consumed as regular gas with the rest of `gas_left`. All CALL-family opcodes (CALL, DELEGATECALL, STATICCALL) pass the full reservoir to child frames. @@ -1691,3 +1693,133 @@ def test_call_value_precompile_halt_refunds_new_account_state_gas( post = {probe: Account(storage=probe_storage)} state_test(pre=pre, post=post, tx=tx) + + +@pytest.mark.parametrize("target_kind", ["new_account", "precompile"]) +@pytest.mark.parametrize("reservoir", ["in_cap", "over_cap"]) +@pytest.mark.valid_from("EIP8037") +def test_call_value_new_account_state_gas_consumed_on_caller_halt( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + reservoir: str, + target_kind: str, +) -> None: + """ + Consume a spilled NEW_ACCOUNT charge when the caller exceptionally halts. + + The caller value-CALLs a zero-balance `target`, charging `NEW_ACCOUNT` + state gas in its own frame; with an empty reservoir the charge spills into + `gas_left`. Two child outcomes share identical accounting: a plain new + account, where the CALL materializes it and succeeds, and the bn256 + pairing precompile forwarded only the value stipend, where the CALL fails + in the child and the charge is refilled to `gas_left` in LIFO order. The + caller then hits `INVALID`; the halt burns all of `gas_left`, including + the spilled charge, and resets the reservoir to its start-of-frame value. + The sender pays the full regular budget: the whole `gas_limit` in-cap, or + the EIP-7825 gas cap over-cap (the restored reservoir is refunded). The + value transfer is rolled back, leaving `target` absent and the caller + balance intact. + + Both `target_kind` variants assert the same totals by design; the + child-failure refill itself is pinned by the probe in + `test_call_value_precompile_halt_refunds_new_account_state_gas`. + """ + value = 1 + # gas=0 forwards only the value stipend: ignored by the empty account + # (CALL succeeds), far below the precompile base cost (CALL fails). + target = ( + Address(0x08) + if target_kind == "precompile" + else pre.nonexistent_account() + ) + caller = pre.deploy_contract( + code=Op.CALL(gas=0, address=target, value=value) + Op.INVALID, + balance=value, + ) + sender = pre.fund_eoa() + + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + + if reservoir == "over_cap": + # The excess over the EIP-7825 cap becomes the reservoir. + gas_limit = gas_limit_cap + fork.gas_costs().NEW_ACCOUNT // 2 + expected_gas_used = gas_limit_cap + else: + gas_limit = 1_000_000 + expected_gas_used = gas_limit + + tx = Transaction( + to=caller, + sender=sender, + gas_limit=gas_limit, + expected_receipt=TransactionReceipt( + cumulative_gas_used=expected_gas_used + ), + ) + + post = { + caller: Account(balance=value), + target: Account.NONEXISTENT, + } + state_test(pre=pre, post=post, tx=tx) + + +@pytest.mark.parametrize("reservoir", ["in_cap", "over_cap"]) +@pytest.mark.valid_from("EIP8037") +def test_call_value_new_account_state_gas_returned_on_caller_revert( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + reservoir: str, +) -> None: + """ + Return a spilled NEW_ACCOUNT charge when the caller cleanly reverts. + + Same value CALL to the absent account `target` as the halt case, but the + caller ends with `REVERT`. A revert refills the frame state gas in LIFO + order: the spilled portion returns to `gas_left` and the reservoir-funded + portion restores the reservoir, both refunded to the sender. The sender + pays only the regular execution gas, the same value in-cap and over-cap, + and the value transfer is rolled back. + """ + value = 1 + target = pre.nonexistent_account() + caller_code = Op.CALL(gas=0, address=target, value=value) + Op.REVERT(0, 0) + caller = pre.deploy_contract(code=caller_code, balance=value) + sender = pre.fund_eoa() + + gas_costs = fork.gas_costs() + # Only regular execution is billed: the spilled and reservoir-funded parts + # of the NEW_ACCOUNT charge are both refunded, so the cost matches in-cap + # and over-cap. `gas_cost` covers the pushes and cold access; the value + # transfer is added on top and the empty child returns its stipend unused. + expected_gas_used = ( + fork.transaction_intrinsic_cost_calculator()() + + caller_code.gas_cost(fork) + + gas_costs.CALL_VALUE + - gas_costs.CALL_STIPEND + ) + receipt = TransactionReceipt(cumulative_gas_used=expected_gas_used) + + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + gas_limit = ( + gas_limit_cap + gas_costs.NEW_ACCOUNT // 2 + if reservoir == "over_cap" + else 1_000_000 + ) + + tx = Transaction( + to=caller, + sender=sender, + gas_limit=gas_limit, + expected_receipt=receipt, + ) + + post = { + caller: Account(balance=value), + target: Account.NONEXISTENT, + } + state_test(pre=pre, post=post, tx=tx) From 3b3840adfb3658aa24e6cee9f2ccba6edbae5ad5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Thu, 2 Jul 2026 14:18:38 +0200 Subject: [PATCH 076/233] feat(tests): EIP-8037 CREATE-onto-alive refunds NEW_ACCOUNT to gas_left (#3073) --- .../test_state_gas_create.py | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py index 9f1a366ebbf..8cea2d84600 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py @@ -2282,6 +2282,44 @@ def test_create_tx_collision_refunds_reservoir( ) +@pytest.mark.valid_from("EIP8037") +def test_create_onto_alive_refunds_to_gas_left( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify a refunded CREATE NEW_ACCOUNT charge returns to gas_left. + + A CREATE2 onto an already-alive (pre-funded, code-less) address + spills the NEW_ACCOUNT charge from the empty reservoir into + gas_left, succeeds, and refunds it. `gas_limit` leaves exactly + `NEW_ACCOUNT` after the create, so the following SSTORE runs only + if the refund returned to gas_left (LIFO) rather than the reservoir. + """ + salt = 0 + create = Op.POP(Op.CREATE2(0, 0, 0, salt)) + storage = Storage() + contract = pre.deploy_contract( + code=create + Op.SSTORE(storage.store_next(1), 1) + ) + target = compute_create2_address(address=contract, salt=salt, initcode=b"") + pre.fund_address(target, amount=1) + + gas_limit = ( + fork.transaction_intrinsic_cost_calculator()() + + create.regular_cost(fork) + + fork.gas_costs().NEW_ACCOUNT + ) + tx = Transaction(to=contract, gas_limit=gas_limit, sender=pre.fund_eoa()) + + post = { + contract: Account(storage=storage), + target: Account(nonce=1, balance=1), + } + state_test(pre=pre, post=post, tx=tx) + + @pytest.mark.parametrize( "initcode_size_delta", [ From fdc800afd7a2c02fd78816ba70a7b22c6545f04a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Thu, 2 Jul 2026 14:18:50 +0200 Subject: [PATCH 077/233] feat(tests): EIP-8037 SSTORE-set state charge exact-fit spill boundary (#3072) --- .../test_state_gas_pricing.py | 44 ++++++++++++------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_pricing.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_pricing.py index 8522effa94b..46091384d00 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_pricing.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_pricing.py @@ -23,6 +23,7 @@ AuthorizationTuple, Environment, Fork, + Header, Op, StateTestFiller, Storage, @@ -158,28 +159,33 @@ def test_charge_spills_to_gas_left( state_test(pre=pre, post=post, tx=tx) +@pytest.mark.parametrize( + "gas_delta", + [pytest.param(0, id="exact_fit"), pytest.param(-1, id="one_short")], +) @EIPChecklist.GasCostChanges.Test.OutOfGas() @pytest.mark.valid_from("EIP8037") -def test_charge_oog_both_pools_insufficient( +def test_charge_spill_boundary( state_test: StateTestFiller, pre: Alloc, fork: Fork, + gas_delta: int, ) -> None: """ - Test OOG when both reservoir and gas_left are insufficient. + Test the SSTORE-set state charge at its exact-fit spill boundary. - Provide just enough gas for intrinsic + SSTORE regular gas but - not enough for the state gas charge. Neither the reservoir (empty - at TX_MAX_GAS_LIMIT) nor gas_left can cover the cost. + With an empty reservoir (in-cap tx) the full state charge spills + into gas_left. Sized to exactly the charge the SSTORE succeeds and + the block bills it as state gas; one gas short, neither pool can + cover the charge and the frame runs out of gas with the slot unset. """ - gas_costs = fork.gas_costs() - contract = pre.deploy_contract( - code=Op.SSTORE(0, 1), - ) + code = Op.SSTORE(0, 1) + contract = pre.deploy_contract(code=code) - # Tight gas: intrinsic + SSTORE regular gas only - intrinsic_cost = fork.transaction_intrinsic_cost_calculator() - gas_limit = intrinsic_cost() + gas_costs.COLD_STORAGE_WRITE + intrinsic = fork.transaction_intrinsic_cost_calculator()() + regular = code.regular_cost(fork) + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + gas_limit = intrinsic + regular + sstore_state_gas + gas_delta tx = Transaction( to=contract, @@ -187,9 +193,17 @@ def test_charge_oog_both_pools_insufficient( sender=pre.fund_eoa(), ) - # OOG — storage unchanged - post = {contract: Account(storage={0: 0})} - state_test(pre=pre, post=post, tx=tx) + header = Header( + gas_used=max(intrinsic + regular, sstore_state_gas) + if gas_delta == 0 + else gas_limit + ) + state_test( + pre=pre, + post={contract: Account(storage={0: 1 if gas_delta == 0 else 0})}, + tx=tx, + blockchain_test_header_verify=header, + ) @EIPChecklist.GasRefundsChanges.Test.RefundCalculation() From b2f7bd94c4caed46dfb4661dca4b3491430e05dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Thu, 2 Jul 2026 14:22:30 +0200 Subject: [PATCH 078/233] feat(tests): EIP-8037 reject tx exceeding remaining block state gas (#3081) --- .../test_block_2d_gas_accounting.py | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py index 78f793db4c9..962e26fbdec 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py @@ -885,3 +885,78 @@ def test_base_fee_per_gas_follows_dominant_dimension( ], post=post, ) + + +@pytest.mark.parametrize( + "delta", + [ + pytest.param(0, id="exact_fit"), + pytest.param(1, id="exceeded", marks=pytest.mark.exception_test), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_cumulative_block_state_gas_boundary( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, + delta: int, +) -> None: + """ + Probe the block state-gas inclusion gate with spill-funded usage. + + tx1 gets no state-gas reservoir (small gas_limit), so its SSTORE-set + state gas reaches block_state_gas_used only via spillover, and its + gas_limit exactly fills the block. tx2's gas_limit is the remaining + state budget plus delta, below both the per-tx cap and the remaining + regular budget, so only the state gate can reject it: delta=0 must + be accepted (strict >) and delta=1 rejected. + test_block_state_gas_limit_boundary covers this gate with a + reservoir-funded tx1 and an above-cap tx2. + """ + n = 6 + intrinsic = fork.transaction_intrinsic_cost_calculator()() + sstore_code = ( + sum((Op.SSTORE(i, 1) for i in range(n)), Bytecode()) + Op.STOP + ) + tx1_regular = intrinsic + sstore_code.regular_cost(fork) + tx1_state = sstore_code.state_cost(fork) + # tx1 exactly fills the block; the leftover state budget is tx1_regular. + block_gas_limit = tx1_regular + tx1_state + # tx2 stays within the remaining regular budget, so only the state + # dimension can reject it. + assert tx1_regular + 1 <= block_gas_limit - tx1_regular + + sstore_contract = pre.deploy_contract(code=sstore_code) + stop_contract = pre.deploy_contract(code=Op.STOP) + + error = TransactionException.GAS_ALLOWANCE_EXCEEDED if delta else None + tx1 = Transaction( + to=sstore_contract, gas_limit=block_gas_limit, sender=pre.fund_eoa() + ) + tx2 = Transaction( + to=stop_contract, + gas_limit=tx1_regular + delta, + sender=pre.fund_eoa(), + error=error, + ) + + post: dict = {} + header_verify: Header | None = None + if not delta: + post = {sstore_contract: Account(storage=dict.fromkeys(range(n), 1))} + header_verify = Header( + gas_used=max(tx1_regular + intrinsic, tx1_state) + ) + + blockchain_test( + genesis_environment=Environment(gas_limit=block_gas_limit), + pre=pre, + blocks=[ + Block( + txs=[tx1, tx2], + exception=error, + header_verify=header_verify, + ) + ], + post=post, + ) From 06800859a7ef5ed33d3b75cf502aedcad7b7fedd Mon Sep 17 00:00:00 2001 From: spencer Date: Thu, 2 Jul 2026 15:17:36 +0100 Subject: [PATCH 079/233] chore(test-consume): resolve fixture release tag for consume cache (#3085) Co-authored-by: danceratopz --- docs/dev/logging.md | 2 +- docs/running_tests/consume/cache.md | 104 +- docs/running_tests/hive/common_options.md | 14 +- docs/running_tests/hive/dev_mode.md | 2 +- docs/running_tests/releases.md | 1 + .../plugins/consume/consume.py | 8 +- .../plugins/consume/releases.py | 164 +- .../consume/tests/release_information.json | 2939 +---------------- .../tests/test_fixtures_source_input_types.py | 18 +- .../plugins/consume/tests/test_releases.py | 123 +- .../src/execution_testing/config/app.py | 13 +- 11 files changed, 424 insertions(+), 2964 deletions(-) diff --git a/docs/dev/logging.md b/docs/dev/logging.md index 346dfcf2431..2c65f4bd850 100644 --- a/docs/dev/logging.md +++ b/docs/dev/logging.md @@ -69,7 +69,7 @@ logger.fail("Test failure or similar issue") You can adjust the log level when running pytest with the `--eest-log-level` option: ```bash -consume engine --input=latest@stable --eest-log-level=VERBOSE -s --sim.limit=".*chainid.*" +consume engine --input=tests@v20.0.0 --eest-log-level=VERBOSE -s --sim.limit=".*chainid.*" ``` The argument accepts both log level names (e.g., "DEBUG", "VERBOSE", "INFO") and numeric values. diff --git a/docs/running_tests/consume/cache.md b/docs/running_tests/consume/cache.md index c0a054bed7b..9bb5e5af471 100644 --- a/docs/running_tests/consume/cache.md +++ b/docs/running_tests/consume/cache.md @@ -3,14 +3,14 @@ The `consume cache` command can be used to resolve, download and cache fixture releases: ```console -consume cache --input=stable@v4.5.0 +consume cache --input=tests@v20.0.0 ``` All `consume` subcommands have an `--input` argument, which implements the same functionality as `consume cache` to download and cache fixtures, respectively obtain downloaded fixtures from the cache. ## Example: Two-liner to Download the Latest Fixture Release -Releases can be downloaded using EEST tooling without (manually) cloning and installing the @ethereum/execution-specs tools as following: +Releases can be downloaded without (manually) cloning and installing the @ethereum/execution-specs tools as following: 1. Install `uv` (a fast, rust-based Python package manager): @@ -18,35 +18,32 @@ Releases can be downloaded using EEST tooling without (manually) cloning and ins curl -LsSf https://astral.sh/uv/install.sh | sh ``` -2. Run EEST's `consume cache` command via `uv` and request the latest ["stable" fixture release](../releases.md): +2. Run the `consume cache` command via `uv` and request the latest [mainnet `tests` release](../releases.md): ```console uvx --from "git+https://github.com/ethereum/execution-specs.git#subdirectory=packages/testing" \ - consume cache --input=stable@latest + consume cache --input=latest ``` - - Expected output, as of `v4.5.0`: + + Expected output, as of `tests@v20.0.0`: ```console - Built ethereum-execution-testing @ git+https://github.com/ethereum/execution-specs.git@a48e0b381d5225a6c3de2d06cd9ee7ae0b6ca9bb#subdirectory=packages/testing - Installed 70 packages in 15ms - - Path: /home/dtopz/.cache/ethereum-execution-spec-tests/cached_downloads/ethereum/execution-spec-tests/v5.4.0/fixtures_stable/fixtures - Input: https://github.com/ethereum/execution-spec-tests/releases/download/v5.4.0/fixtures_stable.tar.gz - Release page: https://github.com/ethereum/execution-spec-tests/releases/tag/v5.4.0 + Path: /home/dtopz/.cache/ethereum-execution-spec-tests/cached_downloads/ethereum/execution-specs/tests%40v20.0.0/fixtures/fixtures + Input: https://github.com/ethereum/execution-specs/releases/download/tests%40v20.0.0/fixtures.tar.gz + Release page: https://github.com/ethereum/execution-specs/releases/tag/tests%40v20.0.0 ``` - **Note:** Use direct URLs to avoid GitHub API calls (better for CI environments). Version specifiers like `stable@latest` will always use the GitHub API to resolve versions. More details on the arguments to `--input` are provided below. + **Note:** Use direct URLs to avoid GitHub API calls (better for CI environments). Version specifiers like `tests@latest` will always use the GitHub API to resolve versions. More details on the arguments to `--input` are provided below. - **Explanation:** `uv` creates a local Python virtual environment in `~/.cache/uv/`, installs EEST and executes the `consume cache` command to resolve and download the release, which gets cached at `~/.cache/ethereum-execution-spec-tests`. Subsequent commands will use the cached version of the fixtures. + **Explanation:** `uv` creates a local Python virtual environment in `~/.cache/uv/`, installs the testing package and executes the `consume cache` command to resolve and download the release, which gets cached at `~/.cache/ethereum-execution-spec-tests`. Subsequent commands will use the cached version of the fixtures. ## The `--input` Flag to Specify Fixtures All `consume` sub-commands take an `--input=||` flag to specify which fixtures should be used for the command, `` may be: 1. **A local directory**: Fixtures from your local file system. -2. **A release specification**: An EEST release tag or "release specification" `stable@latest`, `fusaka-devnet-1@v1.0.0`, etc. +2. **A release specification**: A fixture release tag or "release specification" `tests@latest`, `bal-devnet@v7.0.0`, etc. 3. **A URLs**: A full URL to a custom hosted release or a Github release. ### Release Specifications @@ -55,13 +52,15 @@ A release specification has the format `@`. **Supported release names:** -- `stable`: Latest stable fork release. -- `develop`: Latest development fork release. -- Custom release names: e.g., `pectra-devnet-4`, `eip7692`. +- `tests`: The mainnet release, all tests for all forks up to and including the latest mainnet fork. A bare `latest` or `vX.Y.Z` input is shorthand for `tests@latest`, respectively `tests@vX.Y.Z`. +- `-devnet`: Devnet releases, e.g. `bal-devnet`, `glamsterdam-devnet`. +- Other features: e.g. `benchmark`, `zkevm`. + +Any release name is also accepted with its `tests-` git tag prefix, e.g. `tests-bal@v7.3.2`. **Supported version formats:** -- `latest`: Most recent release for the specified name. +- `latest`: Highest version for the specified name (publish time only breaks ties). - `v1.2.3`: Specific semantic version. ### Examples @@ -69,26 +68,25 @@ A release specification has the format `@`. Examples using a release specification: ```bash -# Latest standard, full stable release (all forks up to and including the latest deployed mainnet fork) -uv run consume engine --input stable@latest - -# Latest standard, full development release (all forks up to and including the latest development fork) -uv run consume rlp --input develop@latest - -# Standard, full releases by tag -uv run consume engine --input stable@v4.1.0 -uv run consume rlp --input develop@v4.2.1 - -# Pre-release tags -uv run consume cache --input pectra-devnet-6@v1.0.0 -uv run consume direct --input eip7692@latest --bin ../go-ethereum/build/bin/evm +# Latest mainnet (tests) release +uv run consume engine --input latest +uv run consume rlp --input tests@latest + +# Mainnet release by version +uv run consume engine --input v20.0.0 +uv run consume rlp --input tests@v20.0.0 + +# Feature releases, with or without the tests- tag prefix +uv run consume cache --input bal-devnet@v7.0.0 +uv run consume cache --input glamsterdam-devnet@latest +uv run consume direct --input tests-bal@v7.3.2 --bin ../go-ethereum/build/bin/evm ``` Examples using a URL, the target must be a `.tar.gz`: ```bash # GitHub release URL -uv run consume engine --input https://github.com/ethereum/execution-spec-tests/releases/download/v4.1.0/fixtures_develop.tar.gz +uv run consume engine --input https://github.com/ethereum/execution-specs/releases/download/tests%40v20.0.0/fixtures.tar.gz # Direct archive URL uv run consume rlp --input https://example.com/custom-fixtures.tar.gz @@ -109,13 +107,13 @@ All remote fixture sources are automatically cached to avoid repeated downloads: You can override this location with the `--cache-folder` flag: ```bash -uv run consume cache --input stable@latest --cache-folder /path/to/custom/cache +uv run consume cache --input latest --cache-folder /path/to/custom/cache ``` Or extract directly to a specific directory (bypasses cache structure): ```bash -uv run consume cache --input fusaka-devnet-2@v1.1.0 --extract-to ./benchmark-fixtures +uv run consume cache --input bal-devnet@v7.0.0 --extract-to ./devnet-fixtures ``` **Cache structure:** @@ -124,25 +122,15 @@ uv run consume cache --input fusaka-devnet-2@v1.1.0 --extract-to ./benchmark-fix ❯ tree ~/.cache/ethereum-execution-spec-tests/ -L 5 /home/dtopz/.cache/ethereum-execution-spec-tests/ ├── cached_downloads -│   ├── ethereum -│   │   └── execution-spec-tests -│   │   ├── pectra-devnet-5%40v1.0.0 -│   │   │   └── fixtures_pectra-devnet-5 -│   │   ├── pectra-devnet-6%40v1.0.0 -│   │   │   └── fixtures_pectra-devnet-6 -│   │   ├── v4.0.0 -│   │   │   └── fixtures_develop -│   │   ├── v4.1.0 -│   │   │   └── fixtures_develop -│   │   ├── v4.2.0 -│   │   │   ├── fixtures_develop -│   │   │   ├── fixtures_eip7692 -│   │   │   └── fixtures_stable -│   │   ├── v4.3.0 -│   │   │   └── fixtures_develop -│   │   └── v4.5.0 -│   │   └── fixtures_stable -│   └── other +│ ├── ethereum +│ │ └── execution-specs +│ │ ├── tests%40v20.0.0 +│ │ │ └── fixtures +│ │ ├── tests-bal%40v7.3.2 +│ │ │ └── fixtures_bal +│ │ └── tests-glamsterdam-devnet%40v6.1.0 +│ │ └── fixtures_glamsterdam-devnet +│ └── other └── release_information.json ``` @@ -155,7 +143,7 @@ The [`fill` command](../../filling_tests/index.md) generates a JSON file ` "FixturesSource": """ - Create a fixture source from a release spec (e.g., develop@latest). + Create a fixture source from a release spec (e.g., tests@latest). """ if cache_folder is None: cache_folder = CACHED_DOWNLOADS_DIRECTORY @@ -376,8 +376,10 @@ def pytest_addoption(parser: pytest.Parser) -> None: # noqa: D103 "Specify the JSON test fixtures source. Can be a local " "directory, a URL pointing to a fixtures.tar.gz archive, a " "release name and version in the form of `NAME@v1.2.3` " - "(`stable` and `develop` are valid release names, and `latest` " - "is a valid version), or the special keyword 'stdin'. " + "(e.g. `tests@v20.0.0` or `bal-devnet@v7.0.0`, with or " + "without the `tests-` tag prefix, and `latest` is a valid " + "version), a bare `latest` or `vX.Y.Z` which resolves the " + "mainnet `tests` release, or the special keyword 'stdin'. " f"Defaults to the following local directory: '{default_input()}'." ), ) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/releases.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/releases.py index 556fa7fe2c3..f74f68b382e 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/releases.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/releases.py @@ -42,6 +42,16 @@ def __init__(self, release_string: str): super().__init__(f"Asset not found: {release_string}") +TESTS_FEATURE_NAME = "tests" + +BARE_VERSION_RE = re.compile(r"^v\d+\.\d+\.\d+$") + +# TODO: Legacy EEST `stable`/`develop` releases (bare `vX.Y.Z` git tags on +# the archived ethereum/execution-spec-tests repo) remain resolvable so +# existing consumers don't break; remove after 2026-08 (see #3085). +LEGACY_FEATURE_NAMES = {"stable", "develop"} + + @dataclass(kw_only=True) class ReleaseTag: """A descriptor for a release.""" @@ -55,13 +65,20 @@ def from_string(cls, release_string: str) -> "ReleaseTag": Create a release descriptor from a string. The release source can be in the format `tag_name@version` or just - `tag_name`. + `tag_name`. A bare `latest` or `vX.Y.Z` resolves to the mainnet + `tests` release. """ version: str | None if "@" in release_string: tag_name, version = release_string.split("@") if version == "" or version.lower() == "latest": version = None + elif release_string.lower() == "latest": + tag_name = TESTS_FEATURE_NAME + version = None + elif BARE_VERSION_RE.match(release_string): + tag_name = TESTS_FEATURE_NAME + version = release_string else: tag_name = release_string version = None @@ -70,28 +87,52 @@ def from_string(cls, release_string: str) -> "ReleaseTag": @staticmethod def is_release_string(release_string: str) -> bool: """Check if the release string is in the correct format.""" - return "@" in release_string + return ( + "@" in release_string + or release_string.lower() == "latest" + or BARE_VERSION_RE.match(release_string) is not None + ) + + @property + def feature_name(self) -> str: + """Get the feature name, without the `tests-` git tag prefix.""" + return self.tag_name.removeprefix("tests-") - def __eq__(self, value: object) -> bool: + def matches_tag(self, tag: str) -> bool: """ - Check if the release descriptor matches the string value. + Check whether a release git tag matches this descriptor. - Returns True if the value is the same as the tag name or the tag name - and version. + Fixture releases are tagged `tests-@vX.Y.Z`, except the + default `tests` feature which tags as `tests@vX.Y.Z`. Both the + friendly feature name (`bal-devnet@v7.0.0`) and the full tag + (`tests-bal-devnet@v7.0.0`) are accepted as input. """ - assert isinstance(value, str), f"Expected a string, but got: {value}" + if self.feature_name in LEGACY_FEATURE_NAMES: + # Legacy releases tag as bare `vX.Y.Z`; the asset name check + # in `ReleaseInformation.__contains__` selects the feature. + if self.version is not None: + return tag == self.version + return BARE_VERSION_RE.match(tag) is not None if self.version is not None: - # normal release, e.g., stable@v4.0.0 - normal_release_match = value == self.version - # pre release, e.g., pectra-devnet-6@v1.0.0 - pre_release_match = value == f"{self.tag_name}@{self.version}" - return normal_release_match or pre_release_match - return value.startswith(self.tag_name) + return tag in ( + f"{self.tag_name}@{self.version}", + f"tests-{self.feature_name}@{self.version}", + ) + return tag.startswith( + (f"{self.tag_name}@", f"tests-{self.feature_name}@") + ) @property def asset_name(self) -> str: - """Get the asset name.""" - return f"fixtures_{self.tag_name}.tar.gz" + """ + Get the asset name for this feature. + + The default `tests` feature ships a plain `fixtures.tar.gz`; every + other feature ships `fixtures_.tar.gz`. + """ + if self.feature_name == TESTS_FEATURE_NAME: + return "fixtures.tar.gz" + return f"fixtures_{self.feature_name}.tar.gz" class Asset(BaseModel): @@ -129,12 +170,12 @@ class ReleaseInformation(BaseModel): def __contains__(self, release_descriptor: ReleaseTag) -> bool: """Check if the release information contains the release descriptor.""" - if release_descriptor.version is not None: - return release_descriptor == self.tag_name - for asset in self.assets.root: - if asset.name == release_descriptor.asset_name: - return True - return False + # Require the expected asset too, so a matching tag whose fixture + # tarball is missing is skipped rather than resolved. + return release_descriptor.matches_tag(self.tag_name) and any( + asset.name == release_descriptor.asset_name + for asset in self.assets.root + ) def get_asset(self, release_descriptor: ReleaseTag) -> Asset: """Get the asset URL.""" @@ -176,23 +217,31 @@ def parse_release_information( release_information: List, ) -> List[ReleaseInformation]: """Parse the release information from the Github API.""" - return Releases.model_validate(release_information).root + # Skip drafts (only visible with maintainer credentials): they have no + # `published_at` and their assets are not downloadable. + published = [ + release + for release in release_information + if not release.get("draft", False) + ] + return Releases.model_validate(published).root def download_release_information( destination_file: Path | None, ) -> List[ReleaseInformation]: """ - Download all releases from the GitHub API, handling pagination properly. + Download recent releases from the GitHub API, following pagination. - GitHub's API returns releases in pages of 30 by default. This function - follows the pagination links to ensure we get every release, which is - crucial for finding older versions or latest releases. + Request pages of 100 releases (the API maximum) and follow the + pagination links up to `max_pages` pages, so resolution sees the 200 + most recent releases per repo. Older releases fall outside this + window and cannot be resolved. """ all_releases = [] for repo in SUPPORTED_REPOS: current_url: str | None = ( - f"https://api.github.com/repos/{repo}/releases" + f"https://api.github.com/repos/{repo}/releases?per_page=100" ) max_pages = 2 while current_url and max_pages > 0: @@ -225,15 +274,48 @@ def parse_release_information_from_file( return parse_release_information(release_information) +RELEASE_VERSION_RE = re.compile(r"@v(\d+)\.(\d+)\.(\d+)") + + +def find_release( + release_string: str, release_information: List[ReleaseInformation] +) -> ReleaseInformation: + """ + Find the release matching the release descriptor string. + + When multiple releases match (a `latest` version), return the highest + version, tie-broken by publish time, so a patch published on an older + release line never wins over a newer line. + """ + release_descriptor = ReleaseTag.from_string(release_string) + matches = [ + release + for release in release_information + if release_descriptor in release + ] + if not matches: + raise NoSuchReleaseError(release_string) + + def sort_key( + release: ReleaseInformation, + ) -> tuple[tuple[int, ...], datetime]: + version = RELEASE_VERSION_RE.search(release.tag_name) + numbers = ( + tuple(int(number) for number in version.groups()) + if version + else (0, 0, 0) + ) + return (numbers, release.published_at) + + return max(matches, key=sort_key) + + def get_release_url_from_release_information( release_string: str, release_information: List[ReleaseInformation] ) -> str: """Get the URL for a specific release.""" - release_descriptor = ReleaseTag.from_string(release_string) - for release in release_information: - if release_descriptor in release: - return release.get_asset(release_descriptor).url - raise NoSuchReleaseError(release_string) + release = find_release(release_string, release_information) + return release.get_asset(ReleaseTag.from_string(release_string)).url def get_release_page_url(release_string: str) -> str: @@ -241,11 +323,11 @@ def get_release_page_url(release_string: str) -> str: Return the GitHub Release page URL for a specific release descriptor. This function can handle: - - A standard release string (e.g., "eip7692@latest") from - execution-spec-tests only. + - A release string (e.g., "tests@latest" or "bal-devnet@v7.0.0") from + any repo in `SUPPORTED_REPOS`. - A direct asset download link (e.g., - "https://github.com/ethereum/execution-spec-tests/releases/ - download/v4.0.0/fixtures_eip7692.tar.gz"). + "https://github.com/ethereum/execution-specs/releases/ + download/tests%40v20.0.0/fixtures.tar.gz"). """ release_information = get_release_information() @@ -263,14 +345,8 @@ def get_release_page_url(release_string: str) -> str: ) # Case 2: Otherwise, treat it as a release descriptor (e.g., - # "eip7692@latest") - release_descriptor = ReleaseTag.from_string(release_string) - for release in release_information: - if release_descriptor in release: - return release.url - - # If nothing matched, raise - raise NoSuchReleaseError(release_string) + # "tests@latest") + return find_release(release_string, release_information).url def get_release_information() -> List[ReleaseInformation]: diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/tests/release_information.json b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/tests/release_information.json index 8223f696ee8..5a869951c58 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/tests/release_information.json +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/tests/release_information.json @@ -1,2890 +1,179 @@ [ { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/192140551", - "assets_url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/192140551/assets", - "upload_url": "https://uploads.github.com/repos/ethereum/execution-spec-tests/releases/192140551/assets{?name,label}", - "html_url": "https://github.com/ethereum/execution-spec-tests/releases/tag/pectra-devnet-5%40v1.0.0", - "id": 192140551, - "author": { - "login": "marioevz", - "id": 11726710, - "node_id": "MDQ6VXNlcjExNzI2NzEw", - "avatar_url": "https://avatars.githubusercontent.com/u/11726710?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/marioevz", - "html_url": "https://github.com/marioevz", - "followers_url": "https://api.github.com/users/marioevz/followers", - "following_url": "https://api.github.com/users/marioevz/following{/other_user}", - "gists_url": "https://api.github.com/users/marioevz/gists{/gist_id}", - "starred_url": "https://api.github.com/users/marioevz/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/marioevz/subscriptions", - "organizations_url": "https://api.github.com/users/marioevz/orgs", - "repos_url": "https://api.github.com/users/marioevz/repos", - "events_url": "https://api.github.com/users/marioevz/events{/privacy}", - "received_events_url": "https://api.github.com/users/marioevz/received_events", - "type": "User", - "user_view_type": "public", - "site_admin": false - }, - "node_id": "RE_kwDOIQGLK84Lc9UH", - "tag_name": "pectra-devnet-5@v1.0.0", - "target_commitish": "main", - "name": "pectra-devnet-5@v1.0.0", - "draft": false, - "prerelease": true, - "created_at": "2024-12-23T13:58:57Z", - "published_at": "2024-12-23T17:47:33Z", + "html_url": "https://github.com/ethereum/execution-spec-tests/releases/tag/v4.5.0", + "id": 900012, + "tag_name": "v4.5.0", + "name": "v4.5.0", + "created_at": "2025-02-01T10:00:00Z", + "published_at": "2025-02-01T10:00:00Z", "assets": [ { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/assets/215394109", - "id": 215394109, - "node_id": "RA_kwDOIQGLK84M1qc9", - "name": "fixtures_pectra-devnet-5.tar.gz", - "label": null, - "uploader": { - "login": "marioevz", - "id": 11726710, - "node_id": "MDQ6VXNlcjExNzI2NzEw", - "avatar_url": "https://avatars.githubusercontent.com/u/11726710?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/marioevz", - "html_url": "https://github.com/marioevz", - "followers_url": "https://api.github.com/users/marioevz/followers", - "following_url": "https://api.github.com/users/marioevz/following{/other_user}", - "gists_url": "https://api.github.com/users/marioevz/gists{/gist_id}", - "starred_url": "https://api.github.com/users/marioevz/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/marioevz/subscriptions", - "organizations_url": "https://api.github.com/users/marioevz/orgs", - "repos_url": "https://api.github.com/users/marioevz/repos", - "events_url": "https://api.github.com/users/marioevz/events{/privacy}", - "received_events_url": "https://api.github.com/users/marioevz/received_events", - "type": "User", - "user_view_type": "public", - "site_admin": false - }, - "content_type": "application/gzip", - "state": "uploaded", - "size": 53020980, - "download_count": 10, - "created_at": "2024-12-23T17:37:55Z", - "updated_at": "2024-12-23T17:38:02Z", - "browser_download_url": "https://github.com/ethereum/execution-spec-tests/releases/download/pectra-devnet-5%40v1.0.0/fixtures_pectra-devnet-5.tar.gz" - } - ], - "tarball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/tarball/pectra-devnet-5@v1.0.0", - "zipball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/zipball/pectra-devnet-5@v1.0.0", - "body": "First EEST pre-release for Pectra Devnet-5.\r\n\r\n## Execution Layer EIP List for pectra-devnet-5\r\n\r\nThe list below links the specific commit versions of the EIPs included in devnet-5 and in this release: \r\n\r\n- [EIP-2537: Precompile for BLS12-381 curve operations](https://github.com/ethereum/EIPs/blob/e8ce6c1d95a6901505fcf2d4ada4245feb69ea7e/EIPS/eip-2537.md)\r\n- [EIP-2935: Save historical block hashes in state](https://github.com/lightclient/EIPs/blob/4d485ae63022f60824bafdc715c049b8510d76eb/EIPS/eip-2935.md)\r\n- [EIP-6110: Supply validator deposits on chain](https://github.com/ethereum/EIPs/blob/e8ce6c1d95a6901505fcf2d4ada4245feb69ea7e/EIPS/eip-6110.md)\r\n- [EIP-7002: Execution layer triggerable withdrawals](https://github.com/ethereum/EIPs/blob/e8ce6c1d95a6901505fcf2d4ada4245feb69ea7e/EIPS/eip-7002.md)\r\n- [EIP-7251: Increase the MAX_EFFECTIVE_BALANCE](https://github.com/ethereum/EIPs/blob/e8ce6c1d95a6901505fcf2d4ada4245feb69ea7e/EIPS/eip-7251.md)\r\n- [EIP-7623: Increase calldata cost](https://github.com/ethereum/EIPs/blob/e8ce6c1d95a6901505fcf2d4ada4245feb69ea7e/EIPS/eip-7623.md) - :exclamation: new EIP\r\n- [EIP-7685: General purpose execution layer requests](https://github.com/ethereum/EIPs/blob/e8ce6c1d95a6901505fcf2d4ada4245feb69ea7e/EIPS/eip-7685.md)\r\n- [EIP-7691: Blob throughput increase](https://github.com/ethereum/EIPs/blob/e8ce6c1d95a6901505fcf2d4ada4245feb69ea7e/EIPS/eip-7691.md) :exclamation: new EIP\r\n- [EIP-7702: Set EOA account code for one transaction](https://github.com/ethereum/EIPs/blob/e8ce6c1d95a6901505fcf2d4ada4245feb69ea7e/EIPS/eip-7702.md)\r\n\r\n## Breaking Changes\r\n\r\n#### Transaction Tests\r\n\r\nNew test format is included in this release called [Transaction Tests](https://eest.ethereum.org/main/consuming_tests/transaction_test/).\r\n\r\nThe fixtures of this type are included in folder `./fixtures/transaction_tests/`.\r\n\r\n### Important Notes\r\nNone" - }, - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/190023984", - "assets_url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/190023984/assets", - "upload_url": "https://uploads.github.com/repos/ethereum/execution-spec-tests/releases/190023984/assets{?name,label}", - "html_url": "https://github.com/ethereum/execution-spec-tests/releases/tag/verkle%40v0.0.9-alpha-1", - "id": 190023984, - "author": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "node_id": "RE_kwDOIQGLK84LU4kw", - "tag_name": "verkle@v0.0.9-alpha-1", - "target_commitish": "main", - "name": "verkle@v0.0.9-alpha-1", - "draft": false, - "prerelease": true, - "created_at": "2024-12-10T16:03:56Z", - "published_at": "2024-12-10T18:01:25Z", - "assets": [ - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/assets/212429636", - "id": 212429636, - "node_id": "RA_kwDOIQGLK84MqWtE", - "name": "fixtures_verkle-conversion-stride-0.tar.gz", - "label": "", - "uploader": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "content_type": "application/gzip", - "state": "uploaded", - "size": 380857, - "download_count": 1, - "created_at": "2024-12-10T17:59:15Z", - "updated_at": "2024-12-10T17:59:15Z", - "browser_download_url": "https://github.com/ethereum/execution-spec-tests/releases/download/verkle%40v0.0.9-alpha-1/fixtures_verkle-conversion-stride-0.tar.gz" - }, - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/assets/212429637", - "id": 212429637, - "node_id": "RA_kwDOIQGLK84MqWtF", - "name": "fixtures_verkle-genesis.tar.gz", - "label": "", - "uploader": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "content_type": "application/gzip", - "state": "uploaded", - "size": 3161837, - "download_count": 5, - "created_at": "2024-12-10T17:59:15Z", - "updated_at": "2024-12-10T17:59:15Z", - "browser_download_url": "https://github.com/ethereum/execution-spec-tests/releases/download/verkle%40v0.0.9-alpha-1/fixtures_verkle-genesis.tar.gz" - } - ], - "tarball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/tarball/verkle@v0.0.9-alpha-1", - "zipball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/zipball/verkle@v0.0.9-alpha-1", - "body": "**Full Changelog**: https://github.com/ethereum/execution-spec-tests/compare/verkle@v0.0.8...verkle@v0.0.9-alpha-1" - }, - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/188166169", - "assets_url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/188166169/assets", - "upload_url": "https://uploads.github.com/repos/ethereum/execution-spec-tests/releases/188166169/assets{?name,label}", - "html_url": "https://github.com/ethereum/execution-spec-tests/releases/tag/eip7692%40v2.1.0", - "id": 188166169, - "author": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "node_id": "RE_kwDOIQGLK84LNzAZ", - "tag_name": "eip7692@v2.1.0", - "target_commitish": "main", - "name": "eip7692@v2.1.0", - "draft": false, - "prerelease": true, - "created_at": "2024-11-27T13:42:33Z", - "published_at": "2024-11-29T10:15:52Z", - "assets": [ - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/assets/209848063", - "id": 209848063, - "node_id": "RA_kwDOIQGLK84Mggb_", - "name": "fixtures_eip7692.tar.gz", - "label": "", - "uploader": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "content_type": "application/gzip", - "state": "uploaded", - "size": 35294190, - "download_count": 126, - "created_at": "2024-11-29T09:32:05Z", - "updated_at": "2024-11-29T09:32:06Z", - "browser_download_url": "https://github.com/ethereum/execution-spec-tests/releases/download/eip7692%40v2.1.0/fixtures_eip7692.tar.gz" - } - ], - "tarball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/tarball/eip7692@v2.1.0", - "zipball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/zipball/eip7692@v2.1.0", - "body": "## What's Changed (Only EOF-relevant changes listed)\r\n\r\n* new(tests): EOF - EIP-4750: Stack validation in CALLF by @shemnon in https://github.com/ethereum/execution-spec-tests/pull/889\r\n* new(tests): EOF - EIP-5450: RJUMP* vs CALLF tests by @pdobacz in https://github.com/ethereum/execution-spec-tests/pull/833\r\n* bug(tests) - CALLF rule #4 applies to return stack, not operand stack by @shemnon in https://github.com/ethereum/execution-spec-tests/pull/907\r\n* feat(exceptions,specs): class to verify exception strings by @winsvega in https://github.com/ethereum/execution-spec-tests/pull/795\r\n* new(tests): basic EOF execution tests by @chfast in https://github.com/ethereum/execution-spec-tests/pull/912\r\n* new(tests): EOF - EIP-4200: migrate remaining RJUMP* execution tests by @chfast in https://github.com/ethereum/execution-spec-tests/pull/916\r\n* refactor(tests): EOF - EIP-4750: parametrize CALLF execution tests by @chfast in https://github.com/ethereum/execution-spec-tests/pull/913\r\n* new(cli): Introduce eofwrap tool by @pdobacz in https://github.com/ethereum/execution-spec-tests/pull/896\r\n* new(tests): EOF - EIP-6206: Add stack overflow by rule check to JUMPF by @shemnon in https://github.com/ethereum/execution-spec-tests/pull/902\r\n* new(tests): Explicit test for EXTDELEGATECALL value cost by @pdobacz in https://github.com/ethereum/execution-spec-tests/pull/911\r\n* new(tests): EOF - EIP-4750: add fibonacci and factorial tests for CALLF by @chfast in https://github.com/ethereum/execution-spec-tests/pull/915\r\n* new(tests): EOF - EIP-7692: migrate `CALLF` execution tests by @chfast in https://github.com/ethereum/execution-spec-tests/pull/914\r\n* new(tests): EOF - EIP-4200 EIP-6206 RJUMPI with JUMPF by @pdobacz in https://github.com/ethereum/execution-spec-tests/pull/928\r\n* feat(forks): Add gas costs functions by @marioevz in https://github.com/ethereum/execution-spec-tests/pull/779\r\n* new(tests): EOF - EIP-3540: validation of opcodes by @chfast in https://github.com/ethereum/execution-spec-tests/pull/932\r\n* feat(docs): add prague-devnet-5 link; add EOF EIP links/info by @danceratopz in https://github.com/ethereum/execution-spec-tests/pull/957\r\n* feat(ci,eof): include eofwrap in EOF prerelease by @pdobacz in https://github.com/ethereum/execution-spec-tests/pull/962\r\n\r\n## New Contributors\r\n* @MaximeDavin made their first contribution in https://github.com/ethereum/execution-spec-tests/pull/949\r\n\r\n**Full Changelog**: https://github.com/ethereum/execution-spec-tests/compare/eip7692@v2.0.0...eip7692@v2.1.0", - "reactions": { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/188166169/reactions", - "total_count": 1, - "+1": 0, - "-1": 0, - "laugh": 0, - "hooray": 0, - "confused": 0, - "heart": 0, - "rocket": 1, - "eyes": 0 - }, - "mentions_count": 7 - }, - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/186894334", - "assets_url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/186894334/assets", - "upload_url": "https://uploads.github.com/repos/ethereum/execution-spec-tests/releases/186894334/assets{?name,label}", - "html_url": "https://github.com/ethereum/execution-spec-tests/releases/tag/verkle%40v0.0.8", - "id": 186894334, - "author": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "node_id": "RE_kwDOIQGLK84LI8f-", - "tag_name": "verkle@v0.0.8", - "target_commitish": "main", - "name": "verkle@v0.0.8", - "draft": false, - "prerelease": true, - "created_at": "2024-11-22T11:08:22Z", - "published_at": "2024-11-22T13:21:15Z", - "assets": [ - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/assets/208335927", - "id": 208335927, - "node_id": "RA_kwDOIQGLK84MavQ3", - "name": "fixtures_verkle-conversion-stride-0.tar.gz", - "label": "", - "uploader": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "content_type": "application/gzip", - "state": "uploaded", - "size": 380504, - "download_count": 2, - "created_at": "2024-11-22T13:03:52Z", - "updated_at": "2024-11-22T13:03:53Z", - "browser_download_url": "https://github.com/ethereum/execution-spec-tests/releases/download/verkle%40v0.0.8/fixtures_verkle-conversion-stride-0.tar.gz" - }, - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/assets/208335928", - "id": 208335928, - "node_id": "RA_kwDOIQGLK84MavQ4", - "name": "fixtures_verkle-genesis.tar.gz", - "label": "", - "uploader": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "content_type": "application/gzip", - "state": "uploaded", - "size": 3160592, - "download_count": 15, - "created_at": "2024-11-22T13:03:52Z", - "updated_at": "2024-11-22T13:03:53Z", - "browser_download_url": "https://github.com/ethereum/execution-spec-tests/releases/download/verkle%40v0.0.8/fixtures_verkle-genesis.tar.gz" - } - ], - "tarball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/tarball/verkle@v0.0.8", - "zipball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/zipball/verkle@v0.0.8", - "body": "## What's Changed\r\nThis release includes extra tests that reproduce bugs found in some EL clients in the new devnet7:\r\n* verkle: add extra SSTORE test by @jsign in https://github.com/ethereum/execution-spec-tests/pull/936\r\n* verkle: add contract creation failure scenario by @jsign in https://github.com/ethereum/execution-spec-tests/pull/944\r\n\r\nPlease see PR descriptions to understand better what new tests try to cover.\r\n\r\n**Full Changelog**: https://github.com/ethereum/execution-spec-tests/compare/verkle@v0.0.7...verkle@v0.0.8", - "mentions_count": 1 - }, - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/183128740", - "assets_url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/183128740/assets", - "upload_url": "https://uploads.github.com/repos/ethereum/execution-spec-tests/releases/183128740/assets{?name,label}", - "html_url": "https://github.com/ethereum/execution-spec-tests/releases/tag/verkle%40v0.0.7-alpha-8", - "id": 183128740, - "author": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "node_id": "RE_kwDOIQGLK84K6lKk", - "tag_name": "verkle@v0.0.7-alpha-8", - "target_commitish": "main", - "name": "verkle@v0.0.7-alpha-8", - "draft": false, - "prerelease": true, - "created_at": "2024-11-01T14:13:01Z", - "published_at": "2024-11-01T16:12:50Z", - "assets": [ - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/assets/203411146", - "id": 203411146, - "node_id": "RA_kwDOIQGLK84MH87K", - "name": "fixtures_verkle-conversion-stride-0.tar.gz", - "label": "", - "uploader": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "content_type": "application/gzip", - "state": "uploaded", - "size": 380886, - "download_count": 3, - "created_at": "2024-11-01T16:11:16Z", - "updated_at": "2024-11-01T16:11:16Z", - "browser_download_url": "https://github.com/ethereum/execution-spec-tests/releases/download/verkle%40v0.0.7-alpha-8/fixtures_verkle-conversion-stride-0.tar.gz" - }, - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/assets/203411145", - "id": 203411145, - "node_id": "RA_kwDOIQGLK84MH87J", - "name": "fixtures_verkle-genesis.tar.gz", - "label": "", - "uploader": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "content_type": "application/gzip", - "state": "uploaded", - "size": 3158358, - "download_count": 9, - "created_at": "2024-11-01T16:11:16Z", - "updated_at": "2024-11-01T16:11:16Z", - "browser_download_url": "https://github.com/ethereum/execution-spec-tests/releases/download/verkle%40v0.0.7-alpha-8/fixtures_verkle-genesis.tar.gz" - } - ], - "tarball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/tarball/verkle@v0.0.7-alpha-8", - "zipball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/zipball/verkle@v0.0.7-alpha-8", - "body": "## What's Changed\r\n* feat(verkle): add parent root to witness by @spencer-tb in https://github.com/ethereum/execution-spec-tests/pull/910\r\n* verkle: parent state root field renaming by @jsign in https://github.com/ethereum/execution-spec-tests/pull/934\r\n\r\n\r\n**Full Changelog**: https://github.com/ethereum/execution-spec-tests/compare/verkle@v0.0.6...verkle@v0.0.7-alpha-8", - "mentions_count": 2 - }, - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/183375211", - "assets_url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/183375211/assets", - "upload_url": "https://uploads.github.com/repos/ethereum/execution-spec-tests/releases/183375211/assets{?name,label}", - "html_url": "https://github.com/ethereum/execution-spec-tests/releases/tag/verkle%40v0.0.7", - "id": 183375211, - "author": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "node_id": "RE_kwDOIQGLK84K7hVr", - "tag_name": "verkle@v0.0.7", - "target_commitish": "main", - "name": "verkle@v0.0.7", - "draft": false, - "prerelease": true, - "created_at": "2024-11-01T16:27:53Z", - "published_at": "2024-11-04T13:53:03Z", - "assets": [ - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/assets/204012832", - "id": 204012832, - "node_id": "RA_kwDOIQGLK84MKP0g", - "name": "fixtures_verkle-conversion-stride-0.tar.gz", - "label": "", - "uploader": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "content_type": "application/gzip", - "state": "uploaded", - "size": 380554, - "download_count": 8, - "created_at": "2024-11-04T13:47:54Z", - "updated_at": "2024-11-04T13:47:54Z", - "browser_download_url": "https://github.com/ethereum/execution-spec-tests/releases/download/verkle%40v0.0.7/fixtures_verkle-conversion-stride-0.tar.gz" - }, - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/assets/204012833", - "id": 204012833, - "node_id": "RA_kwDOIQGLK84MKP0h", - "name": "fixtures_verkle-genesis.tar.gz", - "label": "", - "uploader": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "content_type": "application/gzip", - "state": "uploaded", - "size": 3157499, - "download_count": 9, - "created_at": "2024-11-04T13:47:54Z", - "updated_at": "2024-11-04T13:47:54Z", - "browser_download_url": "https://github.com/ethereum/execution-spec-tests/releases/download/verkle%40v0.0.7/fixtures_verkle-genesis.tar.gz" - } - ], - "tarball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/tarball/verkle@v0.0.7", - "zipball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/zipball/verkle@v0.0.7", - "body": "⚠️ **Note these tests are up to date with the latest devnet-7 spec!**\r\n\r\nA reasonable order of steps for client teams to start running tests can be found below, starting with the genesis tests from: `fixtures_verkle-genesis.tar.gz`\r\n- Run the EIP-6800 tests for a good first check, `fixtures/verkle/eip6800_genesis_verkle_tree/` within the extracted the fixture genesis tar.\r\n - Followed by EIP-4762 tests: `fixtures/verkle/eip4762_verkle_gas_witness/`.\r\n - And EIP-7709 tests: `fixtures/verkle/eip7709_blockhash_witness/`.\r\n- Run \"backfilled\" tests, all previous fork tests filled for Verkle, i.e all tests excluding`fixtures/verkle/*`.\r\n- (Optional) Run the `fixtures_verkle-conversion-stride-0.tar.gz` tests, as these contain basic pre-fork tests to make sure nothing is broken on the fork before Verkle.\r\n\r\nChanges:\r\n* The `parentStateRoot` field was added to the witness.\r\n* In backported tests, if the test is running in Overlay Tree mode, we won't generate a witness (since it doesn't make sense).\r\n* **Important note:** the backported tests under `tests/cancun/eip6780_selfdestruct` might fail for some clients. This is expected since there's an ongoing discussion on how to resolve a spec issue.\r\n\r\n## 🐘 Verkle Genesis Test Fixtures\r\n\r\nContains verkle specific test vectors from https://github.com/ethereum/execution-spec-tests/pull/659 including all existing EEST test cases filled for a verkle configured fork. Note these tests assume the MPT to VKT conversion has completed where we start at the Verkle fork.\r\n\r\nPlease use `fixtures_verkle-genesis.tar.gz`!\r\n\r\n### Generating Genesis Fixtures\r\n\r\nUsing the geth evm binary from this [commit](https://github.com/gballet/go-ethereum/commit/a00d50aa1590d7bed660723e2aa2fc7e58d64559), fill with the following command:\r\n```\r\nfill --fork Verkle --evm-bin= -n auto -m blockchain_test\r\n```\r\n\r\n## 🔁 Verkle Conversion Test Fixtures - 0 Stride\r\n\r\nThese aim to verify a basic fork transition from Shanghai to Verkle. 0 stride denotes that the initial MPT remains frozen. Thus the MPT is not being converted to a VKT within these tests. The intention is to check that **only blocks after the transition** update the VKT, isolating VKT fork transition issues without touching MPT stride conversion logic.\r\n\r\nTest cases additionally include Shanghai genesis tests to assert that the fork before Verkle is not broken.\r\n\r\nPlease use `fixtures_verkle-conversion-stride-0.tar.gz`!\r\n\r\n### Generating Conversion Fixtures\r\n\r\nUsing the geth evm binary from this [commit](https://github.com/gballet/go-ethereum/commit/a00d50aa1590d7bed660723e2aa2fc7e58d64559), fill with the following command:\r\n```\r\nfill --from Shanghai --until EIP6800Transition --evm-bin= -n auto -m blockchain_test\r\n```" - }, - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/181327020", - "assets_url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/181327020/assets", - "upload_url": "https://uploads.github.com/repos/ethereum/execution-spec-tests/releases/181327020/assets{?name,label}", - "html_url": "https://github.com/ethereum/execution-spec-tests/releases/tag/verkle%40v0.0.6", - "id": 181327020, - "author": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "node_id": "RE_kwDOIQGLK84KztSs", - "tag_name": "verkle@v0.0.6", - "target_commitish": "main", - "name": "verkle@v0.0.6", - "draft": false, - "prerelease": true, - "created_at": "2024-10-21T17:37:28Z", - "published_at": "2024-10-22T22:29:59Z", - "assets": [ - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/assets/201018032", - "id": 201018032, - "node_id": "RA_kwDOIQGLK84L-0qw", - "name": "fixtures_verkle-conversion-stride-0.tar.gz", - "label": "", - "uploader": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "content_type": "application/gzip", - "state": "uploaded", - "size": 859314, - "download_count": 12, - "created_at": "2024-10-22T22:24:59Z", - "updated_at": "2024-10-22T22:24:59Z", - "browser_download_url": "https://github.com/ethereum/execution-spec-tests/releases/download/verkle%40v0.0.6/fixtures_verkle-conversion-stride-0.tar.gz" - }, - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/assets/201018029", - "id": 201018029, - "node_id": "RA_kwDOIQGLK84L-0qt", - "name": "fixtures_verkle-genesis.tar.gz", - "label": "", - "uploader": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "content_type": "application/gzip", - "state": "uploaded", - "size": 3140809, - "download_count": 17, - "created_at": "2024-10-22T22:24:59Z", - "updated_at": "2024-10-22T22:24:59Z", - "browser_download_url": "https://github.com/ethereum/execution-spec-tests/releases/download/verkle%40v0.0.6/fixtures_verkle-genesis.tar.gz" - } - ], - "tarball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/tarball/verkle@v0.0.6", - "zipball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/zipball/verkle@v0.0.6", - "body": "⚠️ **Note these tests are up to date with the latest devnet-7 spec!**\r\n\r\n**This release is filled with a specific geth branch [`gballet/jsign-witness-fix`](https://github.com/gballet/go-ethereum/pull/495) aligned with devnet-7. It contains updates and fixes for the past genesis tests due to the addition of witness checks within the testing framework. These verify the correct behaviour of the witness for specific test cases if defined.**\r\n\r\nA reasonable order of steps for client teams to start running tests can be found below, starting with the genesis tests from: `fixtures_verkle-genesis.tar.gz`\r\n- Run the EIP-6800 tests for a good first check, `fixtures/verkle/eip6800_genesis_verkle_tree/` within the extracted the fixture genesis tar.\r\n - Followed by EIP-4762 tests: `fixtures/verkle/eip4762_verkle_gas_witness/`.\r\n - And EIP-7709 tests: `fixtures/verkle/eip7709_blockhash_witness/`.\r\n- Run \"backfilled\" tests, all previous fork tests filled for Verkle, i.e all tests excluding`fixtures/verkle/*`.\r\n- (Optional) Run the `fixtures_verkle-conversion-stride-0.tar.gz` tests, as these contain basic pre-fork tests to make sure nothing is broken on the fork before Verkle.\r\n\r\nChanges:\r\n* A SELFDESTRUCT test targeting insufficient gas case was improved.\r\n* There was a bug in Geth that generated incorrect filling for backported tests (i.e: Shanghai ones).\r\n\r\n## 🐘 Verkle Genesis Test Fixtures\r\n\r\nContains verkle specific test vectors from https://github.com/ethereum/execution-spec-tests/pull/659 including all existing EEST test cases filled for a verkle configured fork. Note these tests assume the MPT to VKT conversion has completed where we start at the Verkle fork.\r\n\r\nPlease use `fixtures_verkle-genesis.tar.gz`!\r\n\r\n### Generating Genesis Fixtures\r\n\r\nUsing the geth evm binary from this [commit](https://github.com/gballet/go-ethereum/commit/a00d50aa1590d7bed660723e2aa2fc7e58d64559), fill with the following command:\r\n```\r\nfill --fork Verkle --evm-bin= -n auto -m blockchain_test\r\n```\r\n\r\n## 🔁 Verkle Conversion Test Fixtures - 0 Stride\r\n\r\nThese aim to verify a basic fork transition from Shanghai to Verkle. 0 stride denotes that the initial MPT remains frozen. Thus the MPT is not being converted to a VKT within these tests. The intention is to check that **only blocks after the transition** update the VKT, isolating VKT fork transition issues without touching MPT stride conversion logic.\r\n\r\nTest cases additionally include Shanghai genesis tests to assert that the fork before Verkle is not broken.\r\n\r\nPlease use `fixtures_verkle-conversion-stride-0.tar.gz`!\r\n\r\n### Generating Conversion Fixtures\r\n\r\nUsing the geth evm binary from this [commit](https://github.com/gballet/go-ethereum/commit/a00d50aa1590d7bed660723e2aa2fc7e58d64559), fill with the following command:\r\n```\r\nfill --from Shanghai --until EIP6800Transition --evm-bin= -n auto -m blockchain_test\r\n```\r\n" - }, - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/180476466", - "assets_url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/180476466/assets", - "upload_url": "https://uploads.github.com/repos/ethereum/execution-spec-tests/releases/180476466/assets{?name,label}", - "html_url": "https://github.com/ethereum/execution-spec-tests/releases/tag/verkle%40v0.0.5", - "id": 180476466, - "author": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "node_id": "RE_kwDOIQGLK84Kwdoy", - "tag_name": "verkle@v0.0.5", - "target_commitish": "main", - "name": "verkle@v0.0.5", - "draft": false, - "prerelease": true, - "created_at": "2024-10-16T13:14:57Z", - "published_at": "2024-10-17T15:11:23Z", - "assets": [ - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/assets/199780731", - "id": 199780731, - "node_id": "RA_kwDOIQGLK84L6Gl7", - "name": "fixtures_verkle-conversion-stride-0.tar.gz", - "label": "", - "uploader": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "content_type": "application/gzip", - "state": "uploaded", - "size": 859332, - "download_count": 11, - "created_at": "2024-10-17T14:55:52Z", - "updated_at": "2024-10-17T14:55:52Z", - "browser_download_url": "https://github.com/ethereum/execution-spec-tests/releases/download/verkle%40v0.0.5/fixtures_verkle-conversion-stride-0.tar.gz" - }, - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/assets/199780732", - "id": 199780732, - "node_id": "RA_kwDOIQGLK84L6Gl8", - "name": "fixtures_verkle-genesis.tar.gz", - "label": "", - "uploader": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "content_type": "application/gzip", - "state": "uploaded", - "size": 3140759, - "download_count": 45, - "created_at": "2024-10-17T14:55:52Z", - "updated_at": "2024-10-17T14:55:52Z", - "browser_download_url": "https://github.com/ethereum/execution-spec-tests/releases/download/verkle%40v0.0.5/fixtures_verkle-genesis.tar.gz" - } - ], - "tarball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/tarball/verkle@v0.0.5", - "zipball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/zipball/verkle@v0.0.5", - "body": "⚠️ **Note these tests are up to date with the latest devnet-7 spec!**\r\n\r\n**This release is filled with a specific geth branch [`gballet/jsign-witness-fix`](https://github.com/gballet/go-ethereum/pull/495) aligned with devnet-7. It contains updates and fixes for the past genesis tests due to the addition of witness checks within the testing framework. These verify the correct behaviour of the witness for specific test cases if defined.**\r\n\r\nA reasonable order of steps for client teams to start running tests can be found below, starting with the genesis tests from: `fixtures_verkle-genesis.tar.gz`\r\n- Run the EIP-6800 tests for a good first check, `fixtures/verkle/eip6800_genesis_verkle_tree/` within the extracted the fixture genesis tar.\r\n - Followed by EIP-4762 tests: `fixtures/verkle/eip4762_verkle_gas_witness/`.\r\n - And EIP-7709 tests: `fixtures/verkle/eip7709_blockhash_witness/`.\r\n- Run \"backfilled\" tests, all previous fork tests filled for Verkle, i.e all tests excluding`fixtures/verkle/*`.\r\n- (Optional) Run the `fixtures_verkle-conversion-stride-0.tar.gz` tests, as these contain basic pre-fork tests to make sure nothing is broken on the fork before Verkle.\r\n\r\nChanges:\r\n* new(tests): eip-4762 *CALL with insufficient gas by @jsign in https://github.com/ethereum/execution-spec-tests/pull/867\r\n* feat(verkle): add two-way exhaustive witness checks by @spencer-tb in https://github.com/ethereum/execution-spec-tests/pull/879\r\n* eip4762: enable calls with insufficient gas tests again by @jsign in https://github.com/ethereum/execution-spec-tests/pull/880\r\n* new(tests): eip-4762 contract creations with insufficient gas by @jsign in https://github.com/ethereum/execution-spec-tests/pull/873\r\n* new(tests): eip-4762 EXTCODEHASH with insufficient gas by @jsign in https://github.com/ethereum/execution-spec-tests/pull/874\r\n* new(tests): eip-4762 (EXT)CODECOPY with insufficient gas by @jsign in https://github.com/ethereum/execution-spec-tests/pull/868\r\n* new(tests): eip-4762 SELFDESTRUCT with insufficient gas by @jsign in https://github.com/ethereum/execution-spec-tests/pull/875\r\n* eip4762: SSTORE/SLOAD insufficient gas tests by @jsign in https://github.com/ethereum/execution-spec-tests/pull/884\r\n* Filling fixes by @jsign in https://github.com/ethereum/execution-spec-tests/pull/885\r\n**Full Changelog**: https://github.com/ethereum/execution-spec-tests/compare/verkle@v0.0.3...verkle@v0.0.4\r\n\r\n## 🐘 Verkle Genesis Test Fixtures\r\n\r\nContains verkle specific test vectors from https://github.com/ethereum/execution-spec-tests/pull/659 including all existing EEST test cases filled for a verkle configured fork. Note these tests assume the MPT to VKT conversion has completed where we start at the Verkle fork.\r\n\r\nPlease use `fixtures_verkle-genesis.tar.gz`!\r\n\r\n### Generating Genesis Fixtures\r\n\r\nUsing the geth evm binary from this [commit](https://github.com/gballet/go-ethereum/commit/a00d50aa1590d7bed660723e2aa2fc7e58d64559), fill with the following command:\r\n```\r\nfill --fork Verkle --evm-bin= -n auto -m blockchain_test\r\n```\r\n\r\n## 🔁 Verkle Conversion Test Fixtures - 0 Stride\r\n\r\nThese aim to verify a basic fork transition from Shanghai to Verkle. 0 stride denotes that the initial MPT remains frozen. Thus the MPT is not being converted to a VKT within these tests. The intention is to check that **only blocks after the transition** update the VKT, isolating VKT fork transition issues without touching MPT stride conversion logic.\r\n\r\nTest cases additionally include Shanghai genesis tests to assert that the fork before Verkle is not broken.\r\n\r\nPlease use `fixtures_verkle-conversion-stride-0.tar.gz`!\r\n\r\n### Generating Conversion Fixtures\r\n\r\nUsing the geth evm binary from this [commit](https://github.com/gballet/go-ethereum/commit/a00d50aa1590d7bed660723e2aa2fc7e58d64559), fill with the following command:\r\n```\r\nfill --from Shanghai --until EIP6800Transition --evm-bin= -n auto -m blockchain_test\r\n```\r\n", - "mentions_count": 2 - }, - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/180106863", - "assets_url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/180106863/assets", - "upload_url": "https://uploads.github.com/repos/ethereum/execution-spec-tests/releases/180106863/assets{?name,label}", - "html_url": "https://github.com/ethereum/execution-spec-tests/releases/tag/eip7692%40v2.0.0", - "id": 180106863, - "author": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "node_id": "RE_kwDOIQGLK84KvDZv", - "tag_name": "eip7692@v2.0.0", - "target_commitish": "main", - "name": "eip7692@v2.0.0", - "draft": false, - "prerelease": true, - "created_at": "2024-10-15T20:35:40Z", - "published_at": "2024-10-15T21:34:53Z", - "assets": [ - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/assets/199345084", - "id": 199345084, - "node_id": "RA_kwDOIQGLK84L4cO8", - "name": "fixtures_eip7692-osaka.tar.gz", - "label": "", - "uploader": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "content_type": "application/gzip", - "state": "uploaded", - "size": 29598138, - "download_count": 85, - "created_at": "2024-10-15T21:01:23Z", - "updated_at": "2024-10-15T21:01:24Z", - "browser_download_url": "https://github.com/ethereum/execution-spec-tests/releases/download/eip7692%40v2.0.0/fixtures_eip7692-osaka.tar.gz" - }, - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/assets/199345083", - "id": 199345083, - "node_id": "RA_kwDOIQGLK84L4cO7", - "name": "fixtures_eip7692.tar.gz", - "label": "", - "uploader": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "content_type": "application/gzip", - "state": "uploaded", - "size": 3005939, - "download_count": 5, - "created_at": "2024-10-15T21:01:23Z", - "updated_at": "2024-10-15T21:01:24Z", - "browser_download_url": "https://github.com/ethereum/execution-spec-tests/releases/download/eip7692%40v2.0.0/fixtures_eip7692.tar.gz" - } - ], - "tarball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/tarball/eip7692@v2.0.0", - "zipball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/zipball/eip7692@v2.0.0", - "body": "First EIP-7692 release filled for the `Osaka` fork.\r\n\r\nFilled tests in `tests/prague` and `tests/osaka` folders.\r\n\r\nPrague fork follows **devnet-3 specification** (see https://github.com/ethereum/execution-spec-tests/releases/tag/pectra-devnet-3%40v1.5.0 for details).\r\n\r\n## What's Changed (Only EOF-relevant changes listed)\r\n* fix(tests): fix TSTORE EOF variant test by @shemnon in https://github.com/ethereum/execution-spec-tests/pull/831\r\n* new(tests): EOF - EIP-6206: clarify \"non-returning instruction\" by @pdobacz in https://github.com/ethereum/execution-spec-tests/pull/837\r\n* feat(docs,tests): add links to the online test case docs in the EOF tracker by @danceratopz in https://github.com/ethereum/execution-spec-tests/pull/838\r\n* refactor(tests): unify EOF return code constants by @shemnon in https://github.com/ethereum/execution-spec-tests/pull/834\r\n* new(tests): EOF validation tests of stack height with double RJUMPI by @chfast in https://github.com/ethereum/execution-spec-tests/pull/851\r\n* new(tests): EOF - EIP-4750: unreachable code sections by @chfast in https://github.com/ethereum/execution-spec-tests/pull/856\r\n* fix(fw): EOF - Fix EXCHANGE's data_portion_length by @pdobacz in https://github.com/ethereum/execution-spec-tests/pull/849\r\n* new(tests): EIP-7069 and EIP-7620 - failures and context vars by @pdobacz in https://github.com/ethereum/execution-spec-tests/pull/836\r\n* feat(forks,tests): Osaka by @marioevz in https://github.com/ethereum/execution-spec-tests/pull/869\r\n* fix(github): Fix `eip7692-osaka` to also fill `tests/prague` by @marioevz in https://github.com/ethereum/execution-spec-tests/pull/897\r\n\r\n**Full Changelog**: https://github.com/ethereum/execution-spec-tests/compare/eip7692@v1.1.1...eip7692@v2.0.0", - "mentions_count": 5 - }, - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/179885084", - "assets_url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/179885084/assets", - "upload_url": "https://uploads.github.com/repos/ethereum/execution-spec-tests/releases/179885084/assets{?name,label}", - "html_url": "https://github.com/ethereum/execution-spec-tests/releases/tag/pectra-devnet-4%40v1.0.1", - "id": 179885084, - "author": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "node_id": "RE_kwDOIQGLK84KuNQc", - "tag_name": "pectra-devnet-4@v1.0.1", - "target_commitish": "main", - "name": "pectra-devnet-4@v1.0.1", - "draft": false, - "prerelease": true, - "created_at": "2024-10-14T17:38:34Z", - "published_at": "2024-10-14T20:32:44Z", - "assets": [ - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/assets/199069956", - "id": 199069956, - "node_id": "RA_kwDOIQGLK84L3ZEE", - "name": "fixtures_pectra-devnet-4.tar.gz", - "label": "", - "uploader": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "content_type": "application/gzip", - "state": "uploaded", - "size": 20080350, - "download_count": 5047, - "created_at": "2024-10-14T19:47:27Z", - "updated_at": "2024-10-14T19:47:28Z", - "browser_download_url": "https://github.com/ethereum/execution-spec-tests/releases/download/pectra-devnet-4%40v1.0.1/fixtures_pectra-devnet-4.tar.gz" - } - ], - "tarball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/tarball/pectra-devnet-4@v1.0.1", - "zipball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/zipball/pectra-devnet-4@v1.0.1", - "body": "Second EEST pre-release for Pectra Devnet-4. Fixes a small issue on some negative tests that override the expected requests with an empty requests hash but the block does indeed contain transactions that trigger at least one request.\r\n\r\nFor a full description of the fixtures included please check [pectra-devnet-4@v1.0.0](https://github.com/ethereum/execution-spec-tests/releases/tag/pectra-devnet-4%40v1.0.0) release notes.\r\n\r\n**Full Changelog**: https://github.com/ethereum/execution-spec-tests/compare/pectra-devnet-4@v1.0.0...pectra-devnet-4@v1.0.1\r\n\r\n**Test Case Documentation**: https://eest.ethereum.org/pectra-devnet-4@v1.0.1/tests/prague/", - "reactions": { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/179885084/reactions", - "total_count": 2, - "+1": 0, - "-1": 0, - "laugh": 0, - "hooray": 0, - "confused": 0, - "heart": 0, - "rocket": 2, - "eyes": 0 - } - }, - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/179659615", - "assets_url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/179659615/assets", - "upload_url": "https://uploads.github.com/repos/ethereum/execution-spec-tests/releases/179659615/assets{?name,label}", - "html_url": "https://github.com/ethereum/execution-spec-tests/releases/tag/pectra-devnet-4%40v1.0.0", - "id": 179659615, - "author": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "node_id": "RE_kwDOIQGLK84KtWNf", - "tag_name": "pectra-devnet-4@v1.0.0", - "target_commitish": "main", - "name": "pectra-devnet-4@v1.0.0", - "draft": false, - "prerelease": true, - "created_at": "2024-10-13T14:08:37Z", - "published_at": "2024-10-14T04:00:43Z", - "assets": [ - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/assets/198798555", - "id": 198798555, - "node_id": "RA_kwDOIQGLK84L2Wzb", - "name": "fixtures_pectra-devnet-4.tar.gz", - "label": "", - "uploader": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "content_type": "application/gzip", - "state": "uploaded", - "size": 20080066, - "download_count": 11, - "created_at": "2024-10-13T16:16:57Z", - "updated_at": "2024-10-13T16:16:58Z", - "browser_download_url": "https://github.com/ethereum/execution-spec-tests/releases/download/pectra-devnet-4%40v1.0.0/fixtures_pectra-devnet-4.tar.gz" - } - ], - "tarball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/tarball/pectra-devnet-4@v1.0.0", - "zipball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/zipball/pectra-devnet-4@v1.0.0", - "body": "First EEST pre-release for Pectra Devnet-4.\r\n\r\n## Included Specification PRs\r\n\r\n- [x] https://github.com/ethereum/EIPs/pull/8889\r\n- [x] https://github.com/ethereum/EIPs/pull/8890\r\n- [x] https://github.com/lightclient/sys-asm/pull/20\r\n- [x] https://github.com/ethereum/EIPs/pull/8845\r\n- [x] https://github.com/ethereum/EIPs/pull/8929\r\n- [x] https://github.com/ethereum/EIPs/pull/8948\r\n- [x] https://github.com/ethereum/EIPs/pull/8950\r\n- [x] https://github.com/ethereum/EIPs/pull/8857\r\n- [x] https://github.com/ethereum/EIPs/pull/8856\r\n- [x] https://github.com/ethereum/EIPs/pull/8855\r\n- [x] https://github.com/ethereum/EIPs/pull/8854\r\n- [x] https://github.com/ethereum/execution-apis/pull/591\r\n- [x] https://github.com/ethereum/EIPs/pull/8934 (Also updates EIP-7251)\r\n- [x] https://github.com/ethereum/EIPs/pull/8938\r\n\r\n## New Tests\r\n\r\n- EIP-7685: Invalid request type in block\r\n- EIP-7002, EIP-7251: Add tests for system contracts execution pre-fork.\r\n- EIP-7702: Add deploy delegation-like contract test\r\n\r\n## Breaking Changes\r\n\r\n#### Blockchain Fixtures Changes\r\n\r\n- `blockHeader.requests_root` field has been renamed to `requests_hash` in the `blockchain_test` fixture type.\r\n- `FixtureBlockBase` and `FixtureExecutionPayload` fields `deposit_requests`, `withdrawal_requests` and `consolidation_requests` are replaced by a single field `requests` which contains a list of hex strings, each element represents the bytes of a flattened request.\r\n- Fourth parameter has been added to `FixtureEngineNewPayload.params` which represents the flattened requests.\r\n\r\n### Important Notes\r\n- EIP-2935 slow tests (256+ blocks) have been skipped.\r\n\r\n## Included EIP Versions\r\n\r\n- [EIP-2537: Precompile for BLS12-381 curve operations](https://github.com/ethereum/EIPs/blob/9ccf12ceb3979bf0b31ad82a54a0470845c38c2d/EIPS/eip-2537.md)\r\n- [EIP-2935: Save historical block hashes in state](https://github.com/ethereum/EIPs/blob/45587bd019487b0bd59bec941bc0e2d708811cc8/EIPS/eip-2935.md)\r\n- [EIP-6110: Supply validator deposits on chain](https://github.com/ethereum/EIPs/blob/f88a24b00b0ad92d5ba640f8427ab5001fc453ad/EIPS/eip-6110.md)\r\n- [EIP-7002: Execution layer triggerable exits](https://github.com/ethereum/EIPs/blob/a7fb2260ae2ea39bdd31886832c9e45452d0e76a/EIPS/eip-7002.md)\r\n- [EIP-7251: Increase the MAX_EFFECTIVE_BALANCE](https://github.com/ethereum/EIPs/blob/a7fb2260ae2ea39bdd31886832c9e45452d0e76a/EIPS/eip-7251.md)\r\n- [EIP-7685: General purpose execution layer requests](https://github.com/ethereum/EIPs/blob/a7fb2260ae2ea39bdd31886832c9e45452d0e76a/EIPS/eip-7685.md)\r\n- [EIP-7702: Set EOA account code for one transaction](https://github.com/ethereum/EIPs/blob/a7fb2260ae2ea39bdd31886832c9e45452d0e76a/EIPS/eip-7702.md)", - "reactions": { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/179659615/reactions", - "total_count": 1, - "+1": 0, - "-1": 0, - "laugh": 0, - "hooray": 0, - "confused": 0, - "heart": 0, - "rocket": 1, - "eyes": 0 - } - }, - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/177899690", - "assets_url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/177899690/assets", - "upload_url": "https://uploads.github.com/repos/ethereum/execution-spec-tests/releases/177899690/assets{?name,label}", - "html_url": "https://github.com/ethereum/execution-spec-tests/releases/tag/verkle%40v0.0.4", - "id": 177899690, - "author": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "node_id": "RE_kwDOIQGLK84Kmoiq", - "tag_name": "verkle@v0.0.4", - "target_commitish": "main", - "name": "verkle@v0.0.4", - "draft": false, - "prerelease": true, - "created_at": "2024-10-01T19:34:38Z", - "published_at": "2024-10-01T22:30:31Z", - "assets": [ - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/assets/196226600", - "id": 196226600, - "node_id": "RA_kwDOIQGLK84Lsi4o", - "name": "fixtures_verkle-conversion-stride-0.tar.gz", - "label": "", - "uploader": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "content_type": "application/gzip", - "state": "uploaded", - "size": 850808, - "download_count": 31, - "created_at": "2024-10-01T22:05:30Z", - "updated_at": "2024-10-01T22:05:30Z", - "browser_download_url": "https://github.com/ethereum/execution-spec-tests/releases/download/verkle%40v0.0.4/fixtures_verkle-conversion-stride-0.tar.gz" - }, - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/assets/196226599", - "id": 196226599, - "node_id": "RA_kwDOIQGLK84Lsi4n", - "name": "fixtures_verkle-genesis.tar.gz", - "label": "", - "uploader": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "content_type": "application/gzip", - "state": "uploaded", - "size": 3073513, - "download_count": 37, - "created_at": "2024-10-01T22:05:30Z", - "updated_at": "2024-10-01T22:05:30Z", - "browser_download_url": "https://github.com/ethereum/execution-spec-tests/releases/download/verkle%40v0.0.4/fixtures_verkle-genesis.tar.gz" - } - ], - "tarball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/tarball/verkle@v0.0.4", - "zipball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/zipball/verkle@v0.0.4", - "body": "⚠️ **Note these tests are up to date with the latest devnet-7 spec!**\r\n\r\n**This release is filled with a specific geth branch [`gballet/jsign-witness-fix`](https://github.com/gballet/go-ethereum/pull/495) aligned with devnet-7. It contains updates and fixes for the past genesis tests due to the addition of witness checks within the testing framework. These verify the correct behaviour of the witness for specific test cases if defined.**\r\n\r\nA reasonable order of steps for client teams to start running tests can be found below, starting with the genesis tests from: `fixtures_verkle-genesis.tar.gz`\r\n- Run the EIP-6800 tests for a good first check, `fixtures/verkle/eip6800_genesis_verkle_tree/` within the extracted the fixture genesis tar.\r\n - Followed by EIP-4762 tests: `fixtures/verkle/eip4762_verkle_gas_witness/`.\r\n - And EIP-7709 tests: `fixtures/verkle/eip7709_blockhash_witness/`.\r\n- Run \"backfilled\" tests, all previous fork tests filled for Verkle, i.e all tests excluding`fixtures/verkle/*`.\r\n- (Optional) Run the `fixtures_verkle-conversion-stride-0.tar.gz` tests, as these contain basic pre-fork tests to make sure nothing is broken on the fork before Verkle.\r\n\r\n**Full Changelog**: https://github.com/ethereum/execution-spec-tests/compare/verkle@v0.0.3...verkle@v0.0.4\r\n\r\n## 🐘 Verkle Genesis Test Fixtures\r\n\r\nContains verkle specific test vectors from https://github.com/ethereum/execution-spec-tests/pull/659 including all existing EEST test cases filled for a verkle configured fork. Note these tests assume the MPT to VKT conversion has completed where we start at the Verkle fork.\r\n\r\nPlease use `fixtures_verkle-genesis.tar.gz`!\r\n\r\n### Generating Genesis Fixtures\r\n\r\nUsing the geth evm binary from this [commit](https://github.com/gballet/go-ethereum/commit/a00d50aa1590d7bed660723e2aa2fc7e58d64559), fill with the following command:\r\n```\r\nfill --fork Verkle --evm-bin= -n auto -m blockchain_test\r\n```\r\n\r\n## 🔁 Verkle Conversion Test Fixtures - 0 Stride\r\n\r\nThese aim to verify a basic fork transition from Shanghai to Verkle. 0 stride denotes that the initial MPT remains frozen. Thus the MPT is not being converted to a VKT within these tests. The intention is to check that **only blocks after the transition** update the VKT, isolating VKT fork transition issues without touching MPT stride conversion logic.\r\n\r\nTest cases additionally include Shanghai genesis tests to assert that the fork before Verkle is not broken.\r\n\r\nPlease use `fixtures_verkle-conversion-stride-0.tar.gz`!\r\n\r\n### Generating Conversion Fixtures\r\n\r\nUsing the geth evm binary from this [commit](https://github.com/gballet/go-ethereum/commit/a00d50aa1590d7bed660723e2aa2fc7e58d64559), fill with the following command:\r\n```\r\nfill --from Shanghai --until EIP6800Transition --evm-bin= -n auto -m blockchain_test\r\n```\r\n\r\n" - }, - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/176456969", - "assets_url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/176456969/assets", - "upload_url": "https://uploads.github.com/repos/ethereum/execution-spec-tests/releases/176456969/assets{?name,label}", - "html_url": "https://github.com/ethereum/execution-spec-tests/releases/tag/eip7692%40v1.1.1", - "id": 176456969, - "author": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "node_id": "RE_kwDOIQGLK84KhIUJ", - "tag_name": "eip7692@v1.1.1", - "target_commitish": "main", - "name": "eip7692@v1.1.1", - "draft": false, - "prerelease": true, - "created_at": "2024-09-23T16:06:58Z", - "published_at": "2024-09-23T19:28:51Z", - "assets": [ - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/assets/194417872", - "id": 194417872, - "node_id": "RA_kwDOIQGLK84LlpTQ", - "name": "fixtures_eip7692-prague.tar.gz", - "label": "", - "uploader": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "content_type": "application/gzip", - "state": "uploaded", - "size": 29388774, - "download_count": 14, - "created_at": "2024-09-23T17:45:29Z", - "updated_at": "2024-09-23T17:45:31Z", - "browser_download_url": "https://github.com/ethereum/execution-spec-tests/releases/download/eip7692%40v1.1.1/fixtures_eip7692-prague.tar.gz" - }, - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/assets/194417871", - "id": 194417871, - "node_id": "RA_kwDOIQGLK84LlpTP", - "name": "fixtures_eip7692.tar.gz", - "label": "", - "uploader": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "content_type": "application/gzip", - "state": "uploaded", - "size": 2947021, - "download_count": 155, - "created_at": "2024-09-23T17:45:29Z", - "updated_at": "2024-09-23T17:45:30Z", - "browser_download_url": "https://github.com/ethereum/execution-spec-tests/releases/download/eip7692%40v1.1.1/fixtures_eip7692.tar.gz" - } - ], - "tarball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/tarball/eip7692@v1.1.1", - "zipball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/zipball/eip7692@v1.1.1", - "body": "Filled using Besu commit https://github.com/besu-eth/besu/commit/0d6395515890280c29ee2402c03b1ee81bde3bab\r\n\r\n## What's Changed\r\n* new(tests): EOF - EIP-7620 EOFCREATE gas testing by @pdobacz in https://github.com/ethereum/execution-spec-tests/pull/785\r\n\r\n**Full Changelog**: https://github.com/ethereum/execution-spec-tests/compare/eip7692@v1.1.0...eip7692@v1.1.1", - "mentions_count": 1 - }, - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/175961554", - "assets_url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/175961554/assets", - "upload_url": "https://uploads.github.com/repos/ethereum/execution-spec-tests/releases/175961554/assets{?name,label}", - "html_url": "https://github.com/ethereum/execution-spec-tests/releases/tag/eip7692%40v1.1.0", - "id": 175961554, - "author": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "node_id": "RE_kwDOIQGLK84KfPXS", - "tag_name": "eip7692@v1.1.0", - "target_commitish": "main", - "name": "eip7692@v1.1.0", - "draft": false, - "prerelease": true, - "created_at": "2024-09-19T18:04:52Z", - "published_at": "2024-09-19T18:41:46Z", - "assets": [ - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/assets/193660497", - "id": 193660497, - "node_id": "RA_kwDOIQGLK84LiwZR", - "name": "fixtures_eip7692-prague.tar.gz", - "label": "", - "uploader": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "content_type": "application/gzip", - "state": "uploaded", - "size": 28430232, - "download_count": 10, - "created_at": "2024-09-19T18:23:47Z", - "updated_at": "2024-09-19T18:23:48Z", - "browser_download_url": "https://github.com/ethereum/execution-spec-tests/releases/download/eip7692%40v1.1.0/fixtures_eip7692-prague.tar.gz" - }, - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/assets/193660498", - "id": 193660498, - "node_id": "RA_kwDOIQGLK84LiwZS", - "name": "fixtures_eip7692.tar.gz", - "label": "", - "uploader": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "content_type": "application/gzip", - "state": "uploaded", - "size": 2734819, - "download_count": 31, - "created_at": "2024-09-19T18:23:47Z", - "updated_at": "2024-09-19T18:23:47Z", - "browser_download_url": "https://github.com/ethereum/execution-spec-tests/releases/download/eip7692%40v1.1.0/fixtures_eip7692.tar.gz" - } - ], - "tarball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/tarball/eip7692@v1.1.0", - "zipball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/zipball/eip7692@v1.1.0", - "body": "Contains several EIP-7702 Devnet-3 tests parametrized to use EOF.\r\n\r\n## What's Changed\r\n* chore(tests): update EOF tests tracker by @chfast in https://github.com/ethereum/execution-spec-tests/pull/791\r\n* new(tests): EOF: more fuzzing discovered tests by @shemnon in https://github.com/ethereum/execution-spec-tests/pull/789\r\n* fix(fw): DATALOAD pushed_stack_items by @pdobacz in https://github.com/ethereum/execution-spec-tests/pull/784\r\n* new(tests): EOF: tests for invalid non-returning sections by @chfast in https://github.com/ethereum/execution-spec-tests/pull/794\r\n* new(tests): EOF - EIP-7069 - expand EXT*CALL gas testing by @pdobacz in https://github.com/ethereum/execution-spec-tests/pull/771\r\n* fix(tests): EOF - Remove duplicate container tests, automatically check for duplicates by @marioevz in https://github.com/ethereum/execution-spec-tests/pull/800\r\n* fix(fw): max stack height calculation in __add__ by @pdobacz in https://github.com/ethereum/execution-spec-tests/pull/810\r\n* new(test): EIP-7702 + EIP-1153: test that TransientStorage stays at correct address by @jochem-brouwer in https://github.com/ethereum/execution-spec-tests/pull/799\r\n* new(tests): TSTORE: ensure transient storage is cleared after transactions by @jochem-brouwer in https://github.com/ethereum/execution-spec-tests/pull/798\r\n* new(tests): EOF - EIP-7620: EOFCREATE referencing the same subcontainer twice by @MariusVanDerWijden in https://github.com/ethereum/execution-spec-tests/pull/809\r\n* new(tests): EOF - EIP-7620: Dangling data in subcontainer test by @shemnon in https://github.com/ethereum/execution-spec-tests/pull/812\r\n* fix(fw): EOF - Accept `initcode_prefix` on EOF `Container.Init` by @marioevz in https://github.com/ethereum/execution-spec-tests/pull/819\r\n* fix(tests): Fix Existing EOF + EIP-7702 Tests by @marioevz in https://github.com/ethereum/execution-spec-tests/pull/821\r\n\r\n\r\n**Full Changelog**: https://github.com/ethereum/execution-spec-tests/compare/eip7692@v1.0.9...eip7692@v1.1.0", - "mentions_count": 6 - }, - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/174363281", - "assets_url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/174363281/assets", - "upload_url": "https://uploads.github.com/repos/ethereum/execution-spec-tests/releases/174363281/assets{?name,label}", - "html_url": "https://github.com/ethereum/execution-spec-tests/releases/tag/pectra-devnet-3%40v1.5.0", - "id": 174363281, - "author": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "node_id": "RE_kwDOIQGLK84KZJKR", - "tag_name": "pectra-devnet-3@v1.5.0", - "target_commitish": "main", - "name": "pectra-devnet-3@v1.5.0", - "draft": false, - "prerelease": true, - "created_at": "2024-09-10T14:20:58Z", - "published_at": "2024-09-10T15:14:09Z", - "assets": [ - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/assets/191744130", - "id": 191744130, - "node_id": "RA_kwDOIQGLK84LbciC", - "name": "fixtures_pectra-devnet-3.tar.gz", - "label": "", - "uploader": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "content_type": "application/gzip", - "state": "uploaded", - "size": 18524987, - "download_count": 4146, - "created_at": "2024-09-10T15:12:25Z", - "updated_at": "2024-09-10T15:12:26Z", - "browser_download_url": "https://github.com/ethereum/execution-spec-tests/releases/download/pectra-devnet-3%40v1.5.0/fixtures_pectra-devnet-3.tar.gz" - } - ], - "tarball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/tarball/pectra-devnet-3@v1.5.0", - "zipball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/zipball/pectra-devnet-3@v1.5.0", - "body": "Adds `tests/prague/eip7702_set_code_tx/test_set_code_txs.py::test_contract_creating_set_code_transaction` which is the contract creating type-4 transaction test.\r\n\r\n### Important Notes\r\n- EIP-2935 slow tests (256+ blocks) have been skipped.\r\n\r\n## Included EIPs\r\n\r\n- [EIP-2537: Precompile for BLS12-381 curve operations](https://github.com/ethereum/EIPs/blob/032c46504b10568039ba300969010e77c795f43a/EIPS/eip-2537.md)\r\n- [EIP-2935: Save historical block hashes in state](https://github.com/ethereum/EIPs/blob/45587bd019487b0bd59bec941bc0e2d708811cc8/EIPS/eip-2935.md)\r\n- [EIP-6110: Supply validator deposits on chain](https://github.com/ethereum/EIPs/blob/699cb15fc5ab7812d44266013876d9de81d05742/EIPS/eip-6110.md)\r\n- [EIP-7002: Execution layer triggerable exits](https://github.com/ethereum/EIPs/blob/f3620fabfa303fd84ee84522c744ae4a4da65cdf/EIPS/eip-7002.md)\r\n- [EIP-7251: Increase the MAX_EFFECTIVE_BALANCE](https://github.com/ethereum/EIPs/blob/bc91716ef2863c3bcab0d4d8ff013e24bf4999c2/EIPS/eip-7251.md)\r\n- [EIP-7685: General purpose execution layer requests](https://github.com/ethereum/EIPs/blob/b27b3f1c1b8252d178c0a7d4bcf6480c8bcb2159/EIPS/eip-7685.md)\r\n- [EIP-7702: Set EOA account code for one transaction](https://github.com/ethereum/EIPs/blob/d87a570a5baedff7b0a71d79de3ff4f14cee0990/EIPS/eip-7702.md)\r\n\r\n## Missing EIPs\r\n- [EIP-7692: EVM Object Format (EOFv1) Meta](https://github.com/ethereum/EIPs/blob/ad9bf2bd83c1018f0a892e03ad3b524cc441257e/EIPS/eip-7692.md)\r\n**Full Changelog**: https://github.com/ethereum/execution-spec-tests/compare/pectra-devnet-3@v1.4.0...pectra-devnet-3@v1.5.0", - "reactions": { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/174363281/reactions", - "total_count": 1, - "+1": 0, - "-1": 0, - "laugh": 0, - "hooray": 0, - "confused": 0, - "heart": 1, - "rocket": 0, - "eyes": 0 - } - }, - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/173852359", - "assets_url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/173852359/assets", - "upload_url": "https://uploads.github.com/repos/ethereum/execution-spec-tests/releases/173852359/assets{?name,label}", - "html_url": "https://github.com/ethereum/execution-spec-tests/releases/tag/eip7692%40v1.0.9", - "id": 173852359, - "author": { - "login": "marioevz", - "id": 11726710, - "node_id": "MDQ6VXNlcjExNzI2NzEw", - "avatar_url": "https://avatars.githubusercontent.com/u/11726710?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/marioevz", - "html_url": "https://github.com/marioevz", - "followers_url": "https://api.github.com/users/marioevz/followers", - "following_url": "https://api.github.com/users/marioevz/following{/other_user}", - "gists_url": "https://api.github.com/users/marioevz/gists{/gist_id}", - "starred_url": "https://api.github.com/users/marioevz/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/marioevz/subscriptions", - "organizations_url": "https://api.github.com/users/marioevz/orgs", - "repos_url": "https://api.github.com/users/marioevz/repos", - "events_url": "https://api.github.com/users/marioevz/events{/privacy}", - "received_events_url": "https://api.github.com/users/marioevz/received_events", - "type": "User", - "user_view_type": "public", - "site_admin": false - }, - "node_id": "RE_kwDOIQGLK84KXMbH", - "tag_name": "eip7692@v1.0.9", - "target_commitish": "main", - "name": "eip7692@v1.0.9", - "draft": false, - "prerelease": true, - "created_at": "2024-09-05T20:24:52Z", - "published_at": "2024-09-06T14:10:28Z", - "assets": [ - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/assets/190869157", - "id": 190869157, - "node_id": "RA_kwDOIQGLK84LYG6l", - "name": "fixtures_eip7692.tar.gz", - "label": null, - "uploader": { - "login": "marioevz", - "id": 11726710, - "node_id": "MDQ6VXNlcjExNzI2NzEw", - "avatar_url": "https://avatars.githubusercontent.com/u/11726710?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/marioevz", - "html_url": "https://github.com/marioevz", - "followers_url": "https://api.github.com/users/marioevz/followers", - "following_url": "https://api.github.com/users/marioevz/following{/other_user}", - "gists_url": "https://api.github.com/users/marioevz/gists{/gist_id}", - "starred_url": "https://api.github.com/users/marioevz/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/marioevz/subscriptions", - "organizations_url": "https://api.github.com/users/marioevz/orgs", - "repos_url": "https://api.github.com/users/marioevz/repos", - "events_url": "https://api.github.com/users/marioevz/events{/privacy}", - "received_events_url": "https://api.github.com/users/marioevz/received_events", - "type": "User", - "user_view_type": "public", - "site_admin": false - }, - "content_type": "application/gzip", - "state": "uploaded", - "size": 2694137, - "download_count": 147, - "created_at": "2024-09-06T14:08:16Z", - "updated_at": "2024-09-06T14:08:17Z", - "browser_download_url": "https://github.com/ethereum/execution-spec-tests/releases/download/eip7692%40v1.0.9/fixtures_eip7692.tar.gz" - } - ], - "tarball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/tarball/eip7692@v1.0.9", - "zipball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/zipball/eip7692@v1.0.9", - "body": "## Important Notes\r\n\r\nEIP-7702 breaks Prague+EOF tests at the moment, therefore this release does not contain them, but they should be expected at a later release.\r\n\r\n## What's Changed\r\n* fix(docs): Add some more cases to EOF tracker by @gumb0 in https://github.com/ethereum/execution-spec-tests/pull/747\r\n* new(tests): EOF - EIP-3540: Migrate validation tests: EIP3540/validInvalidFiller.yml by @chfast in https://github.com/ethereum/execution-spec-tests/pull/598\r\n* new(tests): EOF - EIP-7620: tests for msg.depth and static flag by @pdobacz in https://github.com/ethereum/execution-spec-tests/pull/732\r\n* new(tests): EOF - EIP-7069: Call Gas Testing for EXT*CALL by @shemnon in https://github.com/ethereum/execution-spec-tests/pull/713\r\n* new(tests): EIP-7069 - EXTCALL with balance and other by @pdobacz in https://github.com/ethereum/execution-spec-tests/pull/755\r\n* new(tests): EOF - EIP-3540: Test types with 128 inputs by @gurukamath in https://github.com/ethereum/execution-spec-tests/pull/749\r\n* new(tests): EOF - EIP-3540: out of order container section by @chfast in https://github.com/ethereum/execution-spec-tests/pull/741\r\n* chore: simplify python project config by @danceratopz in https://github.com/ethereum/execution-spec-tests/pull/764\r\n* feat(tests): Add multiple exception support to EOF tests by @shemnon in https://github.com/ethereum/execution-spec-tests/pull/759\r\n* new(tests): EOF - EIP-7620: migrate \"embedded container\" tests by @chfast in https://github.com/ethereum/execution-spec-tests/pull/763\r\n* new(tests): EIP-5656/7692 - use new marker to EOF-ize MCOPY test (2) by @pdobacz in https://github.com/ethereum/execution-spec-tests/pull/754\r\n* feat(tests): EOF - EIP-3540/EIP-4200: Move and rename oritests by @winsvega in https://github.com/ethereum/execution-spec-tests/pull/731\r\n* new(tests): EOF - Tests from Fuzzing by @shemnon in https://github.com/ethereum/execution-spec-tests/pull/756\r\n* feat(docs/tests): EOF: Update tracker, add unimplemented tests by @marioevz in https://github.com/ethereum/execution-spec-tests/pull/773\r\n* new(tests): EOF: Validate EOF only opcodes are invalid in legacy by @shemnon in https://github.com/ethereum/execution-spec-tests/pull/768\r\n\r\n**Full Changelog**: https://github.com/ethereum/execution-spec-tests/compare/eip7692@v1.0.8...eip7692@v1.0.9", - "mentions_count": 8 - }, - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/172420698", - "assets_url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/172420698/assets", - "upload_url": "https://uploads.github.com/repos/ethereum/execution-spec-tests/releases/172420698/assets{?name,label}", - "html_url": "https://github.com/ethereum/execution-spec-tests/releases/tag/pectra-devnet-3%40v1.4.0", - "id": 172420698, - "author": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "node_id": "RE_kwDOIQGLK84KRu5a", - "tag_name": "pectra-devnet-3@v1.4.0", - "target_commitish": "main", - "name": "pectra-devnet-3@v1.4.0", - "draft": false, - "prerelease": true, - "created_at": "2024-08-28T17:35:59Z", - "published_at": "2024-08-28T18:35:05Z", - "assets": [ - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/assets/188915076", - "id": 188915076, - "node_id": "RA_kwDOIQGLK84LQp2E", - "name": "fixtures_pectra-devnet-3.tar.gz", - "label": "", - "uploader": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "content_type": "application/gzip", - "state": "uploaded", - "size": 18516050, - "download_count": 117, - "created_at": "2024-08-28T18:29:17Z", - "updated_at": "2024-08-28T18:29:18Z", - "browser_download_url": "https://github.com/ethereum/execution-spec-tests/releases/download/pectra-devnet-3%40v1.4.0/fixtures_pectra-devnet-3.tar.gz" - } - ], - "tarball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/tarball/pectra-devnet-3@v1.4.0", - "zipball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/zipball/pectra-devnet-3@v1.4.0", - "body": "Fixes to `test_set_code_using_invalid_signatures` where the test expected an invalid transaction in some cases where only an invalid authorization list element was expected (but a valid transaction): https://github.com/ethereum/execution-spec-tests/commit/e70d831d30a3e924ef6946b518efb18e10ca684a\r\n### Important Notes\r\n- EIP-2935 slow tests (256+ blocks) have been skipped.\r\n\r\n## Included EIPs\r\n\r\n- [EIP-2537: Precompile for BLS12-381 curve operations](https://github.com/ethereum/EIPs/blob/032c46504b10568039ba300969010e77c795f43a/EIPS/eip-2537.md)\r\n- [EIP-2935: Save historical block hashes in state](https://github.com/ethereum/EIPs/blob/45587bd019487b0bd59bec941bc0e2d708811cc8/EIPS/eip-2935.md)\r\n- [EIP-6110: Supply validator deposits on chain](https://github.com/ethereum/EIPs/blob/699cb15fc5ab7812d44266013876d9de81d05742/EIPS/eip-6110.md)\r\n- [EIP-7002: Execution layer triggerable exits](https://github.com/ethereum/EIPs/blob/f3620fabfa303fd84ee84522c744ae4a4da65cdf/EIPS/eip-7002.md)\r\n- [EIP-7251: Increase the MAX_EFFECTIVE_BALANCE](https://github.com/ethereum/EIPs/blob/bc91716ef2863c3bcab0d4d8ff013e24bf4999c2/EIPS/eip-7251.md)\r\n- [EIP-7685: General purpose execution layer requests](https://github.com/ethereum/EIPs/blob/b27b3f1c1b8252d178c0a7d4bcf6480c8bcb2159/EIPS/eip-7685.md)\r\n- [EIP-7702: Set EOA account code for one transaction](https://github.com/ethereum/EIPs/blob/d87a570a5baedff7b0a71d79de3ff4f14cee0990/EIPS/eip-7702.md)\r\n\r\n## Missing EIPs\r\n- [EIP-7692: EVM Object Format (EOFv1) Meta](https://github.com/ethereum/EIPs/blob/ad9bf2bd83c1018f0a892e03ad3b524cc441257e/EIPS/eip-7692.md)\r\n\r\n**Full Changelog**: https://github.com/ethereum/execution-spec-tests/compare/pectra-devnet-3@v1.3.0...pectra-devnet-3@v1.4.0" - }, - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/172389706", - "assets_url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/172389706/assets", - "upload_url": "https://uploads.github.com/repos/ethereum/execution-spec-tests/releases/172389706/assets{?name,label}", - "html_url": "https://github.com/ethereum/execution-spec-tests/releases/tag/pectra-devnet-3%40v1.3.0", - "id": 172389706, - "author": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "node_id": "RE_kwDOIQGLK84KRnVK", - "tag_name": "pectra-devnet-3@v1.3.0", - "target_commitish": "main", - "name": "pectra-devnet-3@v1.3.0", - "draft": false, - "prerelease": true, - "created_at": "2024-08-27T18:27:56Z", - "published_at": "2024-08-28T15:27:28Z", - "assets": [ - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/assets/188881070", - "id": 188881070, - "node_id": "RA_kwDOIQGLK84LQhiu", - "name": "fixtures_pectra-devnet-3.tar.gz", - "label": "", - "uploader": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "content_type": "application/gzip", - "state": "uploaded", - "size": 18509265, - "download_count": 7, - "created_at": "2024-08-28T15:22:25Z", - "updated_at": "2024-08-28T15:22:25Z", - "browser_download_url": "https://github.com/ethereum/execution-spec-tests/releases/download/pectra-devnet-3%40v1.3.0/fixtures_pectra-devnet-3.tar.gz" - } - ], - "tarball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/tarball/pectra-devnet-3@v1.3.0", - "zipball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/zipball/pectra-devnet-3@v1.3.0", - "body": "This is a hotfix release that contains no new tests but fixes an issue during filling where the system account (`0xfffffffffffffffffffffffffffffffffffffffe`) had its nonce increased in blockchain tests.\r\n\r\n### Important Notes\r\n- EIP-2935 slow tests (256+ blocks) have been skipped.\r\n\r\n## Included EIPs\r\n\r\n- [EIP-2537: Precompile for BLS12-381 curve operations](https://github.com/ethereum/EIPs/blob/032c46504b10568039ba300969010e77c795f43a/EIPS/eip-2537.md)\r\n- [EIP-2935: Save historical block hashes in state](https://github.com/ethereum/EIPs/blob/45587bd019487b0bd59bec941bc0e2d708811cc8/EIPS/eip-2935.md)\r\n- [EIP-6110: Supply validator deposits on chain](https://github.com/ethereum/EIPs/blob/699cb15fc5ab7812d44266013876d9de81d05742/EIPS/eip-6110.md)\r\n- [EIP-7002: Execution layer triggerable exits](https://github.com/ethereum/EIPs/blob/f3620fabfa303fd84ee84522c744ae4a4da65cdf/EIPS/eip-7002.md)\r\n- [EIP-7251: Increase the MAX_EFFECTIVE_BALANCE](https://github.com/ethereum/EIPs/blob/bc91716ef2863c3bcab0d4d8ff013e24bf4999c2/EIPS/eip-7251.md)\r\n- [EIP-7685: General purpose execution layer requests](https://github.com/ethereum/EIPs/blob/b27b3f1c1b8252d178c0a7d4bcf6480c8bcb2159/EIPS/eip-7685.md)\r\n- [EIP-7702: Set EOA account code for one transaction](https://github.com/ethereum/EIPs/blob/d87a570a5baedff7b0a71d79de3ff4f14cee0990/EIPS/eip-7702.md)\r\n\r\n## Missing EIPs\r\n- [EIP-7692: EVM Object Format (EOFv1) Meta](https://github.com/ethereum/EIPs/blob/ad9bf2bd83c1018f0a892e03ad3b524cc441257e/EIPS/eip-7692.md)\r\n\r\n**Full Changelog**: https://github.com/ethereum/execution-spec-tests/compare/eip7692@v1.0.8...pectra-devnet-3@v1.3.0" - }, - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/172219357", - "assets_url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/172219357/assets", - "upload_url": "https://uploads.github.com/repos/ethereum/execution-spec-tests/releases/172219357/assets{?name,label}", - "html_url": "https://github.com/ethereum/execution-spec-tests/releases/tag/pectra-devnet-3%40v1.2.0", - "id": 172219357, - "author": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "node_id": "RE_kwDOIQGLK84KQ9vd", - "tag_name": "pectra-devnet-3@v1.2.0", - "target_commitish": "main", - "name": "pectra-devnet-3@v1.2.0", - "draft": false, - "prerelease": true, - "created_at": "2024-08-27T18:27:56Z", - "published_at": "2024-08-27T19:27:24Z", - "assets": [ - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/assets/188681921", - "id": 188681921, - "node_id": "RA_kwDOIQGLK84LPw7B", - "name": "fixtures_pectra-devnet-3.tar.gz", - "label": "", - "uploader": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "content_type": "application/gzip", - "state": "uploaded", - "size": 18529117, - "download_count": 13, - "created_at": "2024-08-27T19:18:48Z", - "updated_at": "2024-08-27T19:18:48Z", - "browser_download_url": "https://github.com/ethereum/execution-spec-tests/releases/download/pectra-devnet-3%40v1.2.0/fixtures_pectra-devnet-3.tar.gz" - } - ], - "tarball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/tarball/pectra-devnet-3@v1.2.0", - "zipball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/zipball/pectra-devnet-3@v1.2.0", - "body": "Third release for Pectra Devnet-3, filled again using EthereumJS transition tool implementation which includes fixes to the state root expected in state tests (thanks again @jochem-brouwer!).\r\n\r\n### Fixes\r\n- In previous release, in the state tests, the system contracts' addresses were touched even though the contracts were not present in the pre-alloc.\r\n\r\n### Important Notes\r\n- EIP-2935 slow tests (256+ blocks) have been skipped.\r\n\r\n## Included EIPs\r\n\r\n- [EIP-2537: Precompile for BLS12-381 curve operations](https://github.com/ethereum/EIPs/blob/032c46504b10568039ba300969010e77c795f43a/EIPS/eip-2537.md)\r\n- [EIP-2935: Save historical block hashes in state](https://github.com/ethereum/EIPs/blob/45587bd019487b0bd59bec941bc0e2d708811cc8/EIPS/eip-2935.md)\r\n- [EIP-6110: Supply validator deposits on chain](https://github.com/ethereum/EIPs/blob/699cb15fc5ab7812d44266013876d9de81d05742/EIPS/eip-6110.md)\r\n- [EIP-7002: Execution layer triggerable exits](https://github.com/ethereum/EIPs/blob/f3620fabfa303fd84ee84522c744ae4a4da65cdf/EIPS/eip-7002.md)\r\n- [EIP-7251: Increase the MAX_EFFECTIVE_BALANCE](https://github.com/ethereum/EIPs/blob/bc91716ef2863c3bcab0d4d8ff013e24bf4999c2/EIPS/eip-7251.md)\r\n- [EIP-7685: General purpose execution layer requests](https://github.com/ethereum/EIPs/blob/b27b3f1c1b8252d178c0a7d4bcf6480c8bcb2159/EIPS/eip-7685.md)\r\n- [EIP-7702: Set EOA account code for one transaction](https://github.com/ethereum/EIPs/blob/d87a570a5baedff7b0a71d79de3ff4f14cee0990/EIPS/eip-7702.md)\r\n\r\n## Missing EIPs\r\n- [EIP-7692: EVM Object Format (EOFv1) Meta](https://github.com/ethereum/EIPs/blob/ad9bf2bd83c1018f0a892e03ad3b524cc441257e/EIPS/eip-7692.md)\r\n\r\n**Full Changelog**: https://github.com/ethereum/execution-spec-tests/compare/pectra-devnet-3@v1.1.0...pectra-devnet-3@v1.2.0", - "mentions_count": 1 - }, - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/171954734", - "assets_url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/171954734/assets", - "upload_url": "https://uploads.github.com/repos/ethereum/execution-spec-tests/releases/171954734/assets{?name,label}", - "html_url": "https://github.com/ethereum/execution-spec-tests/releases/tag/verkle%40v0.0.3", - "id": 171954734, - "author": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "node_id": "RE_kwDOIQGLK84KP9Iu", - "tag_name": "verkle@v0.0.3", - "target_commitish": "main", - "name": "verkle@v0.0.3", - "draft": false, - "prerelease": true, - "created_at": "2024-08-26T13:37:20Z", - "published_at": "2024-08-26T14:29:03Z", - "assets": [ - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/assets/188383335", - "id": 188383335, - "node_id": "RA_kwDOIQGLK84LOoBn", - "name": "fixtures_verkle-conversion-stride-0.tar.gz", - "label": "", - "uploader": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, + "browser_download_url": "https://github.com/ethereum/execution-spec-tests/releases/download/v4.5.0/fixtures_stable.tar.gz", + "id": 9000120, + "name": "fixtures_stable.tar.gz", "content_type": "application/gzip", - "state": "uploaded", - "size": 809677, - "download_count": 82, - "created_at": "2024-08-26T14:00:11Z", - "updated_at": "2024-08-26T14:00:11Z", - "browser_download_url": "https://github.com/ethereum/execution-spec-tests/releases/download/verkle%40v0.0.3/fixtures_verkle-conversion-stride-0.tar.gz" + "size": 1000000 }, { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/assets/188383334", - "id": 188383334, - "node_id": "RA_kwDOIQGLK84LOoBm", - "name": "fixtures_verkle-genesis.tar.gz", - "label": "", - "uploader": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, + "browser_download_url": "https://github.com/ethereum/execution-spec-tests/releases/download/v4.5.0/fixtures_develop.tar.gz", + "id": 9000121, + "name": "fixtures_develop.tar.gz", "content_type": "application/gzip", - "state": "uploaded", - "size": 891973, - "download_count": 87, - "created_at": "2024-08-26T14:00:11Z", - "updated_at": "2024-08-26T14:00:11Z", - "browser_download_url": "https://github.com/ethereum/execution-spec-tests/releases/download/verkle%40v0.0.3/fixtures_verkle-genesis.tar.gz" + "size": 1000000 } - ], - "tarball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/tarball/verkle@v0.0.3", - "zipball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/zipball/verkle@v0.0.3", - "body": "⚠️ **Note these tests are up to date with the latest devnet-7 spec!**\r\n\r\n**This release is equivalent to [verkle@v0.0.2](https://github.com/ethereum/execution-spec-tests/releases/tag/verkle%40v0.0.2) but filled with the latest geth [`gballet/kaustinen-with-shapella`](https://github.com/gballet/go-ethereum/tree/kaustinen-with-shapella) branch aligned with devnet-7.**\r\n\r\nFrom the [VIC no 23 testing slides](https://hackmd.io/@jsign/verkle-testing#/4), a reasonable order of steps for client teams to start running tests can be found below, starting with the genesis tests from: `fixtures_verkle-genesis.tar.gz`\r\n- Run EIP-6800 tests (~23) for a good first check, `fixtures/verkle/eip6800_genesis_verkle_tree/` within the extracted the fixture genesis tar.\r\n- Run \"backfilled\" tests (~300), all tests excluding`fixtures/verkle/*`.\r\n- Wait for the next release to run additional tests after framework witness assertions verify the remaining test cases.\r\n\r\n**Full Changelog**: https://github.com/ethereum/execution-spec-tests/compare/verkle@v0.0.2...verkle@v0.0.3\r\n\r\n## 🌪️ Fixture Format Changes\r\n\r\nAll fixtures now contain a block witness although currently without the parent state root.\r\n```python\r\nclass Witness(CamelModel):\r\n state_diff: StateDiff\r\n verkle_proof: VerkleProof\r\n```\r\nClient test consumers can now utilize this to compare there computed block witness against the witness contained within the fixtures (computed from geth's t8n).\r\n\r\nFor more information on our witness definition please adhere to [`src/ethereum_test_types/verkle/types.py`](https://github.com/jsign/execution-spec-tests/blob/jsign-verkle-rebased-mainnet/src/ethereum_test_types/verkle/types.py).\r\n\r\nAdditionally the post state is removed. Future fixture releases will contain the post state as a VKT.\r\n\r\n## 🐘 Verkle Genesis Test Fixtures\r\n\r\nContains verkle specific test vectors from https://github.com/ethereum/execution-spec-tests/pull/659 including all existing EEST test cases filled for a verkle configured fork. Note these tests assume the MPT to VKT conversion has completed where we start at the Verkle fork.\r\n\r\nPlease use `fixtures_verkle-genesis.tar.gz`!\r\n\r\n### Generating Genesis Fixtures\r\n\r\nUsing the geth evm binary from this [commit](https://github.com/gballet/go-ethereum/commit/df132604b24104bcfcf838bf30495a279dd799f8), fill with the following command:\r\n```\r\nfill --fork Verkle --evm-bin= -n auto -m blockchain_test\r\n```\r\n\r\n## 🔁 Verkle Conversion Test Fixtures - 0 Stride\r\n\r\nContains an improvement to the initial set of transition [tests](https://github.com/ethereum/execution-spec-tests/releases/tag/eip6800%40v0.0.1).\r\n\r\nThese aim to verify a basic fork transition from Shanghai to Verkle. 0 stride denotes that the initial MPT remains frozen. Thus the MPT is not being converted to a VKT within these tests. The intention is to check that **only blocks after the transition** update the VKT, isolating VKT fork transition issues without touching MPT stride conversion logic.\r\n\r\nTest cases additionally include Shanghai genesis tests to assert that the fork before Verkle is not broken.\r\n\r\nThe next release will contain conversion tests with some stride enabled to dynamically validate the MPT conversion.\r\n\r\nPlease use `fixtures_verkle-conversion-stride-0.tar.gz`!\r\n\r\n### Generating Conversion Fixtures\r\n\r\nUsing the geth evm binary from this [commit](https://github.com/gballet/go-ethereum/commit/df132604b24104bcfcf838bf30495a279dd799f8), fill with the following command:\r\n```\r\nfill --from Shanghai --until EIP6800Transition --evm-bin= -n auto -m blockchain_test\r\n```\r\n\r\n\r\n", - "reactions": { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/171954734/reactions", - "total_count": 1, - "+1": 0, - "-1": 0, - "laugh": 0, - "hooray": 0, - "confused": 0, - "heart": 0, - "rocket": 1, - "eyes": 0 - } + ] }, { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/171714751", - "assets_url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/171714751/assets", - "upload_url": "https://uploads.github.com/repos/ethereum/execution-spec-tests/releases/171714751/assets{?name,label}", - "html_url": "https://github.com/ethereum/execution-spec-tests/releases/tag/pectra-devnet-3%40v1.1.0", - "id": 171714751, - "author": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "node_id": "RE_kwDOIQGLK84KPCi_", - "tag_name": "pectra-devnet-3@v1.1.0", - "target_commitish": "main", - "name": "pectra-devnet-3@v1.1.0", - "draft": false, - "prerelease": true, - "created_at": "2024-08-23T16:53:12Z", - "published_at": "2024-08-23T17:49:42Z", + "html_url": "https://github.com/ethereum/execution-specs/releases/tag/tests%40v20.0.0", + "id": 900003, + "tag_name": "tests@v20.0.0", + "name": "tests@v20.0.0", + "created_at": "2026-07-10T10:00:00Z", + "published_at": "2026-07-10T10:00:00Z", "assets": [ { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/assets/187907663", - "id": 187907663, - "node_id": "RA_kwDOIQGLK84LMz5P", - "name": "fixtures_pectra-devnet-3.tar.gz", - "label": "", - "uploader": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, + "browser_download_url": "https://github.com/ethereum/execution-specs/releases/download/tests%40v20.0.0/fixtures.tar.gz", + "id": 9000030, + "name": "fixtures.tar.gz", "content_type": "application/gzip", - "state": "uploaded", - "size": 18636284, - "download_count": 8, - "created_at": "2024-08-23T17:44:22Z", - "updated_at": "2024-08-23T17:44:23Z", - "browser_download_url": "https://github.com/ethereum/execution-spec-tests/releases/download/pectra-devnet-3%40v1.1.0/fixtures_pectra-devnet-3.tar.gz" + "size": 1000000 } - ], - "tarball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/tarball/pectra-devnet-3@v1.1.0", - "zipball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/zipball/pectra-devnet-3@v1.1.0", - "body": "Second release for Pectra Devnet-3, filled again using EthereumJS transition tool implementation which included gas accounting fixes (thanks again @jochem-brouwer!).\r\n\r\n### Fixes\r\n- Added re-authorization gas costs tests, i.e. existing set-code in account receives a new delegation (https://github.com/ethereum/execution-spec-tests/commit/c27e01079951553ade21d5cf2ce705a8917f9865)\r\n- Fixed warm account costs to consider cost to access the delegated-to account (https://github.com/ethereum/execution-spec-tests/commit/59ec505058ef3cc93e3657d2a7453a643ef6e439)\r\n\r\n### Important Notes\r\n- EIP-2935 slow tests (256+ blocks) have been skipped.\r\n\r\n## Included EIPs\r\n\r\n- [EIP-2537: Precompile for BLS12-381 curve operations](https://github.com/ethereum/EIPs/blob/032c46504b10568039ba300969010e77c795f43a/EIPS/eip-2537.md)\r\n- [EIP-2935: Save historical block hashes in state](https://github.com/ethereum/EIPs/blob/45587bd019487b0bd59bec941bc0e2d708811cc8/EIPS/eip-2935.md)\r\n- [EIP-6110: Supply validator deposits on chain](https://github.com/ethereum/EIPs/blob/699cb15fc5ab7812d44266013876d9de81d05742/EIPS/eip-6110.md)\r\n- [EIP-7002: Execution layer triggerable exits](https://github.com/ethereum/EIPs/blob/f3620fabfa303fd84ee84522c744ae4a4da65cdf/EIPS/eip-7002.md)\r\n- [EIP-7251: Increase the MAX_EFFECTIVE_BALANCE](https://github.com/ethereum/EIPs/blob/bc91716ef2863c3bcab0d4d8ff013e24bf4999c2/EIPS/eip-7251.md)\r\n- [EIP-7685: General purpose execution layer requests](https://github.com/ethereum/EIPs/blob/b27b3f1c1b8252d178c0a7d4bcf6480c8bcb2159/EIPS/eip-7685.md)\r\n- [EIP-7702: Set EOA account code for one transaction](https://github.com/ethereum/EIPs/blob/d87a570a5baedff7b0a71d79de3ff4f14cee0990/EIPS/eip-7702.md)\r\n\r\n## Missing EIPs\r\n- [EIP-7692: EVM Object Format (EOFv1) Meta](https://github.com/ethereum/EIPs/blob/ad9bf2bd83c1018f0a892e03ad3b524cc441257e/EIPS/eip-7692.md)\r\n\r\n**Full Changelog**: https://github.com/ethereum/execution-spec-tests/compare/pectra-devnet-3@v1.0.0...pectra-devnet-3@v1.1.0", - "reactions": { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/171714751/reactions", - "total_count": 2, - "+1": 0, - "-1": 0, - "laugh": 0, - "hooray": 0, - "confused": 0, - "heart": 0, - "rocket": 2, - "eyes": 0 - }, - "mentions_count": 1 + ] }, { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/171316015", - "assets_url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/171316015/assets", - "upload_url": "https://uploads.github.com/repos/ethereum/execution-spec-tests/releases/171316015/assets{?name,label}", - "html_url": "https://github.com/ethereum/execution-spec-tests/releases/tag/pectra-devnet-3%40v1.0.0", - "id": 171316015, - "author": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "node_id": "RE_kwDOIQGLK84KNhMv", - "tag_name": "pectra-devnet-3@v1.0.0", - "target_commitish": "main", - "name": "pectra-devnet-3@v1.0.0", - "draft": false, - "prerelease": true, - "created_at": "2024-08-21T16:11:07Z", - "published_at": "2024-08-21T17:01:28Z", + "html_url": "https://github.com/ethereum/execution-specs/releases/tag/tests-bal-devnet%40v8.0.0", + "id": 900004, + "tag_name": "tests-bal-devnet@v8.0.0", + "name": "tests-bal-devnet@v8.0.0", + "created_at": "2026-07-20T10:00:00Z", + "published_at": "2026-07-20T10:00:00Z", "assets": [ { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/assets/187443215", - "id": 187443215, - "node_id": "RA_kwDOIQGLK84LLCgP", - "name": "fixtures_pectra-devnet-3.tar.gz", - "label": "", - "uploader": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, + "browser_download_url": "https://github.com/ethereum/execution-specs/releases/download/tests-bal-devnet%40v8.0.0/fixtures_bal-devnet.tar.gz", + "id": 9000040, + "name": "fixtures_bal-devnet.tar.gz", "content_type": "application/gzip", - "state": "uploaded", - "size": 16580317, - "download_count": 10, - "created_at": "2024-08-21T16:50:53Z", - "updated_at": "2024-08-21T16:50:53Z", - "browser_download_url": "https://github.com/ethereum/execution-spec-tests/releases/download/pectra-devnet-3%40v1.0.0/fixtures_pectra-devnet-3.tar.gz" + "size": 1000000 } - ], - "tarball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/tarball/pectra-devnet-3@v1.0.0", - "zipball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/zipball/pectra-devnet-3@v1.0.0", - "body": "First release for Pectra Devnet-3, filled using EthereumJS transition tool implementation (thanks @jochem-brouwer!).\r\n\r\n\r\n### Important Notes\r\n- EIP-2537 tests have been skipped for this release but will be available in a later release.\r\n- EIP-2935 slow tests (256+ blocks) have been skipped too.\r\n\r\n## Included EIPs\r\n\r\n- ~~[EIP-2537: Precompile for BLS12-381 curve operations](https://github.com/ethereum/EIPs/blob/032c46504b10568039ba300969010e77c795f43a/EIPS/eip-2537.md)~~ TESTS SKIPPED ONLY FOR THIS RELEASE, EIP STILL ENABLED IN DEVNET-3\r\n- [EIP-2935: Save historical block hashes in state](https://github.com/ethereum/EIPs/blob/45587bd019487b0bd59bec941bc0e2d708811cc8/EIPS/eip-2935.md)\r\n- [EIP-6110: Supply validator deposits on chain](https://github.com/ethereum/EIPs/blob/699cb15fc5ab7812d44266013876d9de81d05742/EIPS/eip-6110.md)\r\n- [EIP-7002: Execution layer triggerable exits](https://github.com/ethereum/EIPs/blob/f3620fabfa303fd84ee84522c744ae4a4da65cdf/EIPS/eip-7002.md)\r\n- [EIP-7251: Increase the MAX_EFFECTIVE_BALANCE](https://github.com/ethereum/EIPs/blob/bc91716ef2863c3bcab0d4d8ff013e24bf4999c2/EIPS/eip-7251.md)\r\n- [EIP-7685: General purpose execution layer requests](https://github.com/ethereum/EIPs/blob/b27b3f1c1b8252d178c0a7d4bcf6480c8bcb2159/EIPS/eip-7685.md)\r\n- [EIP-7702: Set EOA account code for one transaction](https://github.com/ethereum/EIPs/blob/d87a570a5baedff7b0a71d79de3ff4f14cee0990/EIPS/eip-7702.md)\r\n\r\n## Missing EIPs\r\n- [EIP-7692: EVM Object Format (EOFv1) Meta](https://github.com/ethereum/EIPs/blob/ad9bf2bd83c1018f0a892e03ad3b524cc441257e/EIPS/eip-7692.md)\r\n\r\n**Full Changelog**: https://github.com/ethereum/execution-spec-tests/compare/v3.0.0...pectra-devnet-3@v1.0.0", - "reactions": { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/171316015/reactions", - "total_count": 4, - "+1": 0, - "-1": 0, - "laugh": 0, - "hooray": 0, - "confused": 0, - "heart": 0, - "rocket": 4, - "eyes": 0 - }, - "mentions_count": 1 + ] }, { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/170379891", - "assets_url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/170379891/assets", - "upload_url": "https://uploads.github.com/repos/ethereum/execution-spec-tests/releases/170379891/assets{?name,label}", - "html_url": "https://github.com/ethereum/execution-spec-tests/releases/tag/eip7692%40v1.0.8", - "id": 170379891, - "author": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "node_id": "RE_kwDOIQGLK84KJ8pz", - "tag_name": "eip7692@v1.0.8", - "target_commitish": "main", - "name": "eip7692@v1.0.8", - "draft": false, - "prerelease": true, - "created_at": "2024-08-13T23:41:40Z", - "published_at": "2024-08-15T16:17:27Z", + "html_url": "https://github.com/ethereum/execution-specs/releases/tag/tests-bal%40v7.3.1", + "id": 900006, + "tag_name": "tests-bal@v7.3.1", + "name": "tests-bal@v7.3.1", + "created_at": "2026-06-12T11:49:23Z", + "published_at": "2026-06-12T11:49:23Z", "assets": [ { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/assets/186158058", - "id": 186158058, - "node_id": "RA_kwDOIQGLK84LGIvq", - "name": "fixtures_eip7692-prague.tar.gz", - "label": "", - "uploader": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "content_type": "application/gzip", - "state": "uploaded", - "size": 11910702, - "download_count": 10, - "created_at": "2024-08-15T15:00:11Z", - "updated_at": "2024-08-15T15:00:12Z", - "browser_download_url": "https://github.com/ethereum/execution-spec-tests/releases/download/eip7692%40v1.0.8/fixtures_eip7692-prague.tar.gz" - }, - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/assets/186158059", - "id": 186158059, - "node_id": "RA_kwDOIQGLK84LGIvr", - "name": "fixtures_eip7692.tar.gz", - "label": "", - "uploader": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, + "browser_download_url": "https://github.com/ethereum/execution-specs/releases/download/tests-bal%40v7.3.1/fixtures_bal.tar.gz", + "id": 9000060, + "name": "fixtures_bal.tar.gz", "content_type": "application/gzip", - "state": "uploaded", - "size": 2495738, - "download_count": 220, - "created_at": "2024-08-15T15:00:11Z", - "updated_at": "2024-08-15T15:00:11Z", - "browser_download_url": "https://github.com/ethereum/execution-spec-tests/releases/download/eip7692%40v1.0.8/fixtures_eip7692.tar.gz" + "size": 1000000 } - ], - "tarball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/tarball/eip7692@v1.0.8", - "zipball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/zipball/eip7692@v1.0.8", - "body": "## What's Changed\r\n* new(tests): EOF - EIP-3540: Expand section size testing by @pdobacz in https://github.com/ethereum/execution-spec-tests/pull/705\r\n* new(tests) Deep and wide EOF subcontainers by @shemnon in https://github.com/ethereum/execution-spec-tests/pull/718\r\n* fix(docs): Add more cases to EOF tracker by @gumb0 in https://github.com/ethereum/execution-spec-tests/pull/723\r\n* new(tests): EOF - EIP-7069: Add tests by @pdobacz in https://github.com/ethereum/execution-spec-tests/pull/722\r\n* fix(fixtures): Fix index generation for EOF tests by @marioevz in https://github.com/ethereum/execution-spec-tests/pull/728\r\n* fix(docs): Add some execution cases to EOF test tracker by @gumb0 in https://github.com/ethereum/execution-spec-tests/pull/727\r\n* feat(fw,forks,tests): Add EVM code type marker by @marioevz in https://github.com/ethereum/execution-spec-tests/pull/610\r\n* new(tests): EOF - EIP-7069: Add tests, part 2. by @pdobacz in https://github.com/ethereum/execution-spec-tests/pull/730\r\n* feat(fw): add optional `Container.expected_bytecode` by @chfast in https://github.com/ethereum/execution-spec-tests/pull/737\r\n* new(tests): migrate \"valid\" EOFCREATE validation by @chfast in https://github.com/ethereum/execution-spec-tests/pull/738\r\n* fix(docs): Add stack validation cases to EOF tracker by @gumb0 in https://github.com/ethereum/execution-spec-tests/pull/735\r\n* new(tests): EOF - EIP-3540: migrate tests for truncated sections by @chfast in https://github.com/ethereum/execution-spec-tests/pull/740\r\n* feat(plugins,forks,github): Allow dual-feature (Prague+Cancun) build on EOF releases by @marioevz in https://github.com/ethereum/execution-spec-tests/pull/743\r\n* fix(docs): Add execution cases from evmone-generated tests to EOF tracker by @gumb0 in https://github.com/ethereum/execution-spec-tests/pull/742\r\n\r\n\r\n**Full Changelog**: https://github.com/ethereum/execution-spec-tests/compare/v3.0.0...eip7692@v1.0.8", - "mentions_count": 5 + ] }, { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/169640052", - "assets_url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/169640052/assets", - "upload_url": "https://uploads.github.com/repos/ethereum/execution-spec-tests/releases/169640052/assets{?name,label}", - "html_url": "https://github.com/ethereum/execution-spec-tests/releases/tag/verkle%40v0.0.2", - "id": 169640052, - "author": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "node_id": "RE_kwDOIQGLK84KHIB0", - "tag_name": "verkle@v0.0.2", - "target_commitish": "main", - "name": "verkle@v0.0.2", - "draft": false, - "prerelease": true, - "created_at": "2024-08-10T18:46:36Z", - "published_at": "2024-08-10T19:21:58Z", + "html_url": "https://github.com/ethereum/execution-specs/releases/tag/tests-bal%40v7.3.2", + "id": 900005, + "tag_name": "tests-bal@v7.3.2", + "name": "tests-bal@v7.3.2", + "created_at": "2026-06-15T12:35:39Z", + "published_at": "2026-06-15T12:35:39Z", "assets": [ { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/assets/185158406", - "id": 185158406, - "node_id": "RA_kwDOIQGLK84LCUsG", - "name": "fixtures_verkle-conversion-stride-0.tar.gz", - "label": "", - "uploader": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "content_type": "application/gzip", - "state": "uploaded", - "size": 1153328, - "download_count": 2, - "created_at": "2024-08-10T19:08:40Z", - "updated_at": "2024-08-10T19:08:40Z", - "browser_download_url": "https://github.com/ethereum/execution-spec-tests/releases/download/verkle%40v0.0.2/fixtures_verkle-conversion-stride-0.tar.gz" - }, - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/assets/185158405", - "id": 185158405, - "node_id": "RA_kwDOIQGLK84LCUsF", - "name": "fixtures_verkle-genesis.tar.gz", - "label": "", - "uploader": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, + "browser_download_url": "https://github.com/ethereum/execution-specs/releases/download/tests-bal%40v7.3.2/fixtures_bal.tar.gz", + "id": 9000050, + "name": "fixtures_bal.tar.gz", "content_type": "application/gzip", - "state": "uploaded", - "size": 941999, - "download_count": 1, - "created_at": "2024-08-10T19:08:40Z", - "updated_at": "2024-08-10T19:08:40Z", - "browser_download_url": "https://github.com/ethereum/execution-spec-tests/releases/download/verkle%40v0.0.2/fixtures_verkle-genesis.tar.gz" + "size": 1000000 } - ], - "tarball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/tarball/verkle@v0.0.2", - "zipball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/zipball/verkle@v0.0.2", - "body": "\r\n⚠️ **Note these tests are up to date with the devnet-6 spec!**\r\n\r\n**This release is equivalent to [verkle@v0.0.1](https://github.com/ethereum/execution-spec-tests/releases/tag/verkle%40v0.0.1) but with the addition of Shanghai genesis tests within the conversion fixture set.**\r\n\r\n**Full Changelog**: https://github.com/ethereum/execution-spec-tests/compare/verkle@v0.0.1...verkle@v0.0.2\r\n\r\n## 🌪️ Fixture Format Changes\r\n\r\nAll fixtures now contain a block witness although currently without the parent state root.\r\n```python\r\nclass Witness(CamelModel):\r\n state_diff: StateDiff\r\n verkle_proof: VerkleProof\r\n```\r\nClient test consumers can now utilize this to compare there computed block witness against the witness present within the fixtures (computed from geth's t8n).\r\n\r\nFor more information on our witness definition please adhere to [`src/ethereum_test_types/verkle/types.py`](https://github.com/jsign/execution-spec-tests/blob/jsign-verkle-rebased-mainnet/src/ethereum_test_types/verkle/types.py).\r\n\r\nAdditionally the post state is removed. Future fixture releases will contain the post state as a VKT.\r\n\r\n## 🐘 Verkle Genesis Test Fixtures\r\n\r\nContains verkle specific test vectors from https://github.com/ethereum/execution-spec-tests/pull/659 including all existing EEST test cases filled for a verkle configured fork. Note these tests assume the MPT to VKT conversion has completed where we start at the Verkle fork.\r\n\r\nPlease use `fixtures_verkle-genesis.tar.gz`!\r\n\r\n### Generating Genesis Fixtures\r\n\r\nUsing the geth evm binary from this [commit](https://github.com/gballet/go-ethereum/pull/466/commits/47addd7be52f2e07743aa2f4710236f463c5afdf), fill with the following command:\r\n```\r\nfill --fork Verkle --evm-bin=/evm -n auto -m blockchain_test\r\n```\r\n\r\n## 🔁 Verkle Conversion Test Fixtures - 0 Stride\r\n\r\nContains an improvement to the initial set of transition [tests](https://github.com/ethereum/execution-spec-tests/releases/tag/eip6800%40v0.0.1).\r\n\r\nThese aim to verify a basic fork transition from Shanghai to Verkle. 0 stride denotes that the initial MPT remains frozen. Thus the MPT is not being converted to a VKT within these tests. The intention is to check that **only blocks after the transition** update the VKT, isolating VKT fork transition issues without touching MPT stride conversion logic.\r\n\r\nTest cases additionally include Shanghai genesis tests to assert that the fork before Verkle is not broken.\r\n\r\nThe next release will contain conversion tests with some stride enabled to dynamically validate the MPT conversion.\r\n\r\nPlease use `fixtures_verkle-conversion-stride-0.tar.gz`!\r\n\r\n### Generating Conversion Fixtures\r\n\r\nUsing the geth evm binary from this [commit](https://github.com/gballet/go-ethereum/pull/466/commits/47addd7be52f2e07743aa2f4710236f463c5afdf), fill with the following command:\r\n```\r\nfill --from Shanghai --until EIP6800Transition --evm-bin=/evm -n auto -m blockchain_test\r\n```\r\n", - "reactions": { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/169640052/reactions", - "total_count": 1, - "+1": 0, - "-1": 0, - "laugh": 0, - "hooray": 0, - "confused": 0, - "heart": 0, - "rocket": 1, - "eyes": 0 - } + ] }, { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/169420898", - "assets_url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/169420898/assets", - "upload_url": "https://uploads.github.com/repos/ethereum/execution-spec-tests/releases/169420898/assets{?name,label}", - "html_url": "https://github.com/ethereum/execution-spec-tests/releases/tag/verkle%40v0.0.1", - "id": 169420898, - "author": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "node_id": "RE_kwDOIQGLK84KGShi", - "tag_name": "verkle@v0.0.1", - "target_commitish": "main", - "name": "verkle@v0.0.1", - "draft": false, - "prerelease": true, - "created_at": "2024-08-08T22:23:18Z", - "published_at": "2024-08-08T23:32:32Z", + "html_url": "https://github.com/ethereum/execution-specs/releases/tag/tests-benchmark%40v0.0.9", + "id": 900007, + "tag_name": "tests-benchmark@v0.0.9", + "name": "tests-benchmark@v0.0.9", + "created_at": "2026-05-05T05:24:00Z", + "published_at": "2026-05-05T05:24:00Z", "assets": [ { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/assets/184817013", - "id": 184817013, - "node_id": "RA_kwDOIQGLK84LBBV1", - "name": "fixtures_verkle-conversion-stride-0.tar.gz", - "label": "", - "uploader": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, + "browser_download_url": "https://github.com/ethereum/execution-specs/releases/download/tests-benchmark%40v0.0.9/fixtures_benchmark.tar.gz", + "id": 9000070, + "name": "fixtures_benchmark.tar.gz", "content_type": "application/gzip", - "state": "uploaded", - "size": 908777, - "download_count": 2, - "created_at": "2024-08-08T22:45:40Z", - "updated_at": "2024-08-08T22:45:40Z", - "browser_download_url": "https://github.com/ethereum/execution-spec-tests/releases/download/verkle%40v0.0.1/fixtures_verkle-conversion-stride-0.tar.gz" - }, - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/assets/184817014", - "id": 184817014, - "node_id": "RA_kwDOIQGLK84LBBV2", - "name": "fixtures_verkle-genesis.tar.gz", - "label": "", - "uploader": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "content_type": "application/gzip", - "state": "uploaded", - "size": 941979, - "download_count": 2, - "created_at": "2024-08-08T22:45:40Z", - "updated_at": "2024-08-08T22:45:41Z", - "browser_download_url": "https://github.com/ethereum/execution-spec-tests/releases/download/verkle%40v0.0.1/fixtures_verkle-genesis.tar.gz" + "size": 1000000 } - ], - "tarball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/tarball/verkle@v0.0.1", - "zipball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/zipball/verkle@v0.0.1", - "body": "⚠️ **Note these tests are up to date with the devnet-6 spec!**\r\n\r\n## 🌪️ Fixture Format Changes\r\n\r\nAll fixtures now contain a block witness although currently without the parent state root.\r\n```python\r\nclass Witness(CamelModel):\r\n state_diff: StateDiff\r\n verkle_proof: VerkleProof\r\n```\r\nClient test consumers can now utilize this to compare there computed block witness against the witness present within the fixtures (computed from geth's t8n).\r\n\r\nFor more information on our witness definition please adhere to [`src/ethereum_test_types/verkle/types.py`](https://github.com/jsign/execution-spec-tests/blob/jsign-verkle-rebased-mainnet/src/ethereum_test_types/verkle/types.py).\r\n\r\nAdditionally the post state is removed. Future fixture releases will contain the post state as a VKT.\r\n\r\n## 🐘 Verkle Genesis Test Fixtures\r\n\r\nContains verkle specific test vectors from https://github.com/ethereum/execution-spec-tests/pull/659 including all existing EEST test cases filled for a verkle configured fork. Note these tests assume the MPT to VKT conversion has completed where we start at the Verkle fork.\r\n\r\nPlease use `fixtures_verkle-genesis.tar.gz`!\r\n\r\n### Generating Genesis Fixtures\r\n\r\nUsing the geth evm binary from this [commit](https://github.com/gballet/go-ethereum/pull/466/commits/47addd7be52f2e07743aa2f4710236f463c5afdf), fill with the following command:\r\n```\r\nfill --fork Verkle --evm-bin=/evm -n auto -m blockchain_test\r\n```\r\n\r\n## 🔁 Verkle Conversion Test Fixtures - 0 Stride\r\n\r\nContains an improvement to the initial set of transition [tests](https://github.com/ethereum/execution-spec-tests/releases/tag/eip6800%40v0.0.1).\r\n\r\nThese aim to verify a basic fork transition from Shanghai to Verkle. 0 stride denotes that the initial MPT remains frozen. Thus the MPT is not being converted to a VKT within these tests. The intention is to check that **only blocks after the transition** update the VKT, isolating VKT fork transition issues without touching MPT stride conversion logic.\r\n\r\nThe next release will contain conversion tests with some stride enabled to dynamically validate the MPT conversion.\r\n\r\nPlease use `fixtures_verkle-conversion-stride-0.tar.gz`!\r\n\r\n### Generating Conversion Fixtures\r\n\r\nUsing the geth evm binary from this [commit](https://github.com/gballet/go-ethereum/pull/466/commits/47addd7be52f2e07743aa2f4710236f463c5afdf), fill with the following command:\r\n```\r\nfill --fork EIP6800Transition --evm-bin=/evm -n auto -m blockchain_test\r\n```\r\n" + ] }, { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/166538302", - "assets_url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/166538302/assets", - "upload_url": "https://uploads.github.com/repos/ethereum/execution-spec-tests/releases/166538302/assets{?name,label}", - "html_url": "https://github.com/ethereum/execution-spec-tests/releases/tag/v3.0.0", - "id": 166538302, - "author": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "node_id": "RE_kwDOIQGLK84J7Sw-", - "tag_name": "v3.0.0", - "target_commitish": "main", - "name": " Petřín (v3.0.0)", - "draft": false, - "prerelease": false, - "created_at": "2024-07-23T18:34:24Z", - "published_at": "2024-07-23T23:43:55Z", + "html_url": "https://github.com/ethereum/execution-specs/releases/tag/tests-glamsterdam-devnet%40v6.0.0", + "id": 900009, + "tag_name": "tests-glamsterdam-devnet@v6.0.0", + "name": "tests-glamsterdam-devnet@v6.0.0", + "created_at": "2026-06-19T19:04:11Z", + "published_at": "2026-06-19T19:04:11Z", "assets": [ { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/assets/183049828", - "id": 183049828, - "node_id": "RA_kwDOIQGLK84K6R5k", - "name": "fixtures_develop.tar.gz", - "label": null, - "uploader": { - "login": "spencer-tb", - "id": 60348173, - "node_id": "MDQ6VXNlcjYwMzQ4MTcz", - "avatar_url": "https://avatars.githubusercontent.com/u/60348173?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/spencer-tb", - "html_url": "https://github.com/spencer-tb", - "followers_url": "https://api.github.com/users/spencer-tb/followers", - "following_url": "https://api.github.com/users/spencer-tb/following{/other_user}", - "gists_url": "https://api.github.com/users/spencer-tb/gists{/gist_id}", - "starred_url": "https://api.github.com/users/spencer-tb/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/spencer-tb/subscriptions", - "organizations_url": "https://api.github.com/users/spencer-tb/orgs", - "repos_url": "https://api.github.com/users/spencer-tb/repos", - "events_url": "https://api.github.com/users/spencer-tb/events{/privacy}", - "received_events_url": "https://api.github.com/users/spencer-tb/received_events", - "type": "User", - "user_view_type": "public", - "site_admin": false - }, - "content_type": "application/x-gzip", - "state": "uploaded", - "size": 21746751, - "download_count": 3396, - "created_at": "2024-07-31T20:47:41Z", - "updated_at": "2024-07-31T20:48:01Z", - "browser_download_url": "https://github.com/ethereum/execution-spec-tests/releases/download/v3.0.0/fixtures_develop.tar.gz" - }, - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/assets/181430588", - "id": 181430588, - "node_id": "RA_kwDOIQGLK84K0Gk8", - "name": "fixtures_eip7692.tar.gz", - "label": "", - "uploader": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "content_type": "application/gzip", - "state": "uploaded", - "size": 2075015, - "download_count": 8, - "created_at": "2024-07-23T23:38:17Z", - "updated_at": "2024-07-23T23:38:17Z", - "browser_download_url": "https://github.com/ethereum/execution-spec-tests/releases/download/v3.0.0/fixtures_eip7692.tar.gz" - }, - { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/assets/181430589", - "id": 181430589, - "node_id": "RA_kwDOIQGLK84K0Gk9", - "name": "fixtures_stable.tar.gz", - "label": "", - "uploader": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, + "browser_download_url": "https://github.com/ethereum/execution-specs/releases/download/tests-glamsterdam-devnet%40v6.0.0/fixtures_glamsterdam-devnet.tar.gz", + "id": 9000090, + "name": "fixtures_glamsterdam-devnet.tar.gz", "content_type": "application/gzip", - "state": "uploaded", - "size": 2847936, - "download_count": 24258, - "created_at": "2024-07-23T23:38:17Z", - "updated_at": "2024-07-23T23:38:17Z", - "browser_download_url": "https://github.com/ethereum/execution-spec-tests/releases/download/v3.0.0/fixtures_stable.tar.gz" + "size": 1000000 } - ], - "tarball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/tarball/v3.0.0", - "zipball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/zipball/v3.0.0", - "body": "EEST's Petřín release adds many improvements and additions. Please read the breaking changes!\r\n\r\nA notable key package `ethereum_test_tools` is now fragmented into several fine grained packages that are suitable for use within other Python based repositories.\r\n\r\nThe consume simulator pytest plugin now contains 4 fixture runners:\r\n- `consume direct`: provides a pytest wrapper to execute multiple fixtures against client evm `statetest/blocktest` runners.\r\n- `consume rlp`: using hive as a back-end, executes fixture block rlps against fully instantiated clients, verifying the last block hash is expected.\r\n- `consume engine`: with a hive back-end, sends `engine_newPayloadVX` calls against fully instantiated clients validating each call response.\r\n- `consume all`: provides a wrapper surrounding all commands to execute consume direct, rlp and engine at once for the specified fixtures.\r\n\r\nWith a refined consume suite, testers can now `fill` and `consume` the generated fixtures extremely quickly to validate both the generated fixture and client implementation. This removes the cumbersome requirement of updating hive when verifying new fixtures.\r\n\r\nTo align the [execution-apis](https://github.com/ethereum/execution-apis/) engine specification with the `consume engine` plugin, the `blockchain_test_hive` fixture is renamed to `blockchain_test_engine` to align more with the sentiment of the spec. Similarly, the fixture format of the latter is changed to match the engine new payload `\"params\"` field defined in the spec.\r\n\r\n---\r\n\r\n### 💥 Breaking Changes\r\n\r\n- Cancun is now the latest deployed fork, and the development fork is now Prague ([#489](https://github.com/ethereum/execution-spec-tests/pull/489)).\r\n- Stable fixtures artifact `fixtures.tar.gz` has been renamed to `fixtures_stable.tar.gz` ([#573](https://github.com/ethereum/execution-spec-tests/pull/573))\r\n- The \"Blockchain Test Hive\" fixture format has been renamed to \"Blockchain Test Engine\" and updated to more closely resemble the `engine_newPayload` format in the `execution-apis` specification (https://github.com/ethereum/execution-apis/blob/main/src/engine/prague.md#request) and now contains a single `\"params\"` field instead of multiple fields for each parameter ([#687](https://github.com/ethereum/execution-spec-tests/pull/687)).\r\n- Output folder for fixtures has been renamed from \"blockchain_tests_hive\" to \"blockchain_tests_engine\" ([#687](https://github.com/ethereum/execution-spec-tests/pull/687)).\r\n\r\n### 🧪 Test Cases\r\n\r\n- ✨ Add tests for eof container's section bytes position smart fuzzing ([#592](https://github.com/ethereum/execution-spec-tests/pull/592)).\r\n- ✨ Add `test_create_selfdestruct_same_tx_increased_nonce` which tests self-destructing a contract with a nonce > 1 ([#478](https://github.com/ethereum/execution-spec-tests/pull/478)).\r\n- ✨ Add `test_double_kill` and `test_recreate` which test resurrection of accounts killed with `SELFDESTRUCT` ([#488](https://github.com/ethereum/execution-spec-tests/pull/488)).\r\n- ✨ Add eof example valid invalid tests from ori, fetch EOF Container implementation ([#535](https://github.com/ethereum/execution-spec-tests/pull/535)).\r\n- ✨ Add tests for [EIP-2537: Precompile for BLS12-381 curve operations](https://eips.ethereum.org/EIPS/eip-2537) ([#499](https://github.com/ethereum/execution-spec-tests/pull/499)).\r\n- ✨ [EIP-663](https://eips.ethereum.org/EIPS/eip-663): Add `test_dupn.py` and `test_swapn.py` ([#502](https://github.com/ethereum/execution-spec-tests/pull/502)).\r\n- ✨ Add tests for [EIP-6110: Supply validator deposits on chain](https://eips.ethereum.org/EIPS/eip-6110) ([#530](https://github.com/ethereum/execution-spec-tests/pull/530)).\r\n- ✨ Add tests for [EIP-7002: Execution layer triggerable withdrawals](https://eips.ethereum.org/EIPS/eip-7002) ([#530](https://github.com/ethereum/execution-spec-tests/pull/530)).\r\n- ✨ Add tests for [EIP-7685: General purpose execution layer requests](https://eips.ethereum.org/EIPS/eip-7685) ([#530](https://github.com/ethereum/execution-spec-tests/pull/530)).\r\n- ✨ Add tests for [EIP-2935: Serve historical block hashes from state](https://eips.ethereum.org/EIPS/eip-2935) ([#564](https://github.com/ethereum/execution-spec-tests/pull/564), [#585](https://github.com/ethereum/execution-spec-tests/pull/585)).\r\n- ✨ Add tests for [EIP-4200: EOF - Static relative jumps](https://eips.ethereum.org/EIPS/eip-4200) ([#581](https://github.com/ethereum/execution-spec-tests/pull/581), [#666](https://github.com/ethereum/execution-spec-tests/pull/666)).\r\n- ✨ Add tests for [EIP-7069: EOF - Revamped CALL instructions](https://eips.ethereum.org/EIPS/eip-7069) ([#595](https://github.com/ethereum/execution-spec-tests/pull/595)).\r\n- 🐞 Fix typos in self-destruct collision test from erroneous pytest parametrization ([#608](https://github.com/ethereum/execution-spec-tests/pull/608)).\r\n- ✨ Add tests for [EIP-3540: EOF - EVM Object Format v1](https://eips.ethereum.org/EIPS/eip-3540) ([#634](https://github.com/ethereum/execution-spec-tests/pull/634), [#668](https://github.com/ethereum/execution-spec-tests/pull/668)).\r\n- 🔀 Update EIP-7002 tests to match spec changes in [ethereum/execution-apis#549](https://github.com/ethereum/execution-apis/pull/549) ([#600](https://github.com/ethereum/execution-spec-tests/pull/600))\r\n- ✨ Convert a few eip1153 tests from ethereum/tests repo into .py ([#440](https://github.com/ethereum/execution-spec-tests/pull/440)).\r\n- ✨ Add tests for [EIP-7480: EOF - Data section access instructions](https://eips.ethereum.org/EIPS/eip-7480) ([#518](https://github.com/ethereum/execution-spec-tests/pull/518), [#664](https://github.com/ethereum/execution-spec-tests/pull/664)).\r\n- ✨ Add tests for subcontainer kind validation from [EIP-7620: EOF Contract Creation](https://eips.ethereum.org/EIPS/eip-7620) for the cases with deeply nested containers and non-first code sections ([#676](https://github.com/ethereum/execution-spec-tests/pull/676)).\r\n- ✨ Add tests for runtime stack overflow at CALLF instruction from [EIP-4750: EOF - Functions](https://eips.ethereum.org/EIPS/eip-4750) ([#678](https://github.com/ethereum/execution-spec-tests/pull/678)).\r\n- ✨ Add tests for runtime stack overflow at JUMPF instruction from [EIP-6206: EOF - JUMPF and non-returning functions](https://eips.ethereum.org/EIPS/eip-6206) ([#690](https://github.com/ethereum/execution-spec-tests/pull/690)).\r\n- ✨ Add tests for [EIP-7251: Increase the MAX_EFFECTIVE_BALANCE](https://eips.ethereum.org/EIPS/eip-7251) ([#642](https://github.com/ethereum/execution-spec-tests/pull/642))\r\n- ✨ Add tests for Devnet-1 version of [EIP-7702: Set EOA account code](https://eips.ethereum.org/EIPS/eip-7702) ([#621](https://github.com/ethereum/execution-spec-tests/pull/621))\r\n\r\n### 🛠️ Framework\r\n\r\n- 🐞 Fix incorrect `!=` operator for `FixedSizeBytes` ([#477](https://github.com/ethereum/execution-spec-tests/pull/477)).\r\n- ✨ Add Macro enum that represents byte sequence of Op instructions ([#457](https://github.com/ethereum/execution-spec-tests/pull/457))\r\n- ✨ Number of parameters used to call opcodes (to generate bytecode) is now checked ([#492](https://github.com/ethereum/execution-spec-tests/pull/492)).\r\n- ✨ Libraries have been refactored to use `pydantic` for type checking in most test types ([#486](https://github.com/ethereum/execution-spec-tests/pull/486), [#501](https://github.com/ethereum/execution-spec-tests/pull/501), [#508](https://github.com/ethereum/execution-spec-tests/pull/508)).\r\n- ✨ Opcodes are now subscriptable and it's used to define the data portion of the opcode: `Op.PUSH1(1) == Op.PUSH1[1] == b\"\\x60\\x01\"` ([#513](https://github.com/ethereum/execution-spec-tests/pull/513))\r\n- ✨ Added EOF fixture format ([#512](https://github.com/ethereum/execution-spec-tests/pull/512)).\r\n- ✨ Verify filled EOF fixtures using `evmone-eofparse` during `fill` execution ([#519](https://github.com/ethereum/execution-spec-tests/pull/519)).\r\n- ✨ Added `--traces` support when running with Hyperledger Besu ([#511](https://github.com/ethereum/execution-spec-tests/pull/511)).\r\n- ✨ Use pytest's \"short\" traceback style (`--tb=short`) for failure summaries in the test report for more compact terminal output ([#542](https://github.com/ethereum/execution-spec-tests/pull/542)).\r\n- ✨ The `fill` command now generates HTML test reports with links to the JSON fixtures and debug information ([#537](https://github.com/ethereum/execution-spec-tests/pull/537)).\r\n- ✨ Add an Ethereum RPC client class for use with consume commands ([#556](https://github.com/ethereum/execution-spec-tests/pull/556)).\r\n- ✨ Add a \"slow\" pytest marker, in order to be able to limit the filled tests until release ([#562](https://github.com/ethereum/execution-spec-tests/pull/562)).\r\n- ✨ Add a CLI tool that generates blockchain tests as Python from a transaction hash ([#470](https://github.com/ethereum/execution-spec-tests/pull/470), [#576](https://github.com/ethereum/execution-spec-tests/pull/576)).\r\n- ✨ Add more Transaction and Block exceptions from existing ethereum/tests repo ([#572](https://github.com/ethereum/execution-spec-tests/pull/572)).\r\n- ✨ Add \"description\" and \"url\" fields containing test case documentation and a source code permalink to fixtures during `fill` and use them in `consume`-generated Hive test reports ([#579](https://github.com/ethereum/execution-spec-tests/pull/579)).\r\n- ✨ Add git workflow evmone coverage script for any new lines mentioned in converted_ethereum_tests.txt ([#503](https://github.com/ethereum/execution-spec-tests/pull/503)).\r\n- ✨ Add a new covariant marker `with_all_contract_creating_tx_types` that allows automatic parametrization of a test with all contract-creating transaction types at the current executing fork ([#602](https://github.com/ethereum/execution-spec-tests/pull/602)).\r\n- ✨ Tests are now encouraged to declare a `pre: Alloc` parameter to get the pre-allocation object for the test, and use `pre.deploy_contract` and `pre.fund_eoa` to deploy contracts and fund accounts respectively, instead of declaring the `pre` as a dictionary or modifying its contents directly (see the [state test tutorial](https://eest.ethereum.org/main/tutorials/state_transition/) for an updated example) ([#584](https://github.com/ethereum/execution-spec-tests/pull/584)).\r\n- ✨ Enable loading of [ethereum/tests/BlockchainTests](https://github.com/ethereum/tests/tree/develop/BlockchainTests) ([#596](https://github.com/ethereum/execution-spec-tests/pull/596)).\r\n- 🔀 Refactor `gentest` to use `ethereum_test_tools.rpc.rpc` by adding to `get_transaction_by_hash`, `debug_trace_call` to `EthRPC` ([#568](https://github.com/ethereum/execution-spec-tests/pull/568)).\r\n- ✨ Write a properties file to the output directory and enable direct generation of a fixture tarball from `fill` via `--output=fixtures.tgz`([#627](https://github.com/ethereum/execution-spec-tests/pull/627)).\r\n- 🔀 `ethereum_test_tools` library has been split into multiple libraries ([#645](https://github.com/ethereum/execution-spec-tests/pull/645)).\r\n- ✨ Add the consume engine simulator and refactor the consume simulator suite. ([#691](https://github.com/ethereum/execution-spec-tests/pull/691)).\r\n\r\n### 📋 Misc\r\n\r\n- 🐞 Fix CI by using Golang 1.21 in Github Actions to build geth ([#484](https://github.com/ethereum/execution-spec-tests/pull/484)).\r\n- 💥 \"Merge\" has been renamed to \"Paris\" in the \"network\" field of the Blockchain tests, and in the \"post\" field of the State tests ([#480](https://github.com/ethereum/execution-spec-tests/pull/480)).\r\n- ✨ Port entry point scripts to use [click](https://click.palletsprojects.com) and add tests ([#483](https://github.com/ethereum/execution-spec-tests/pull/483)).\r\n- 💥 As part of the pydantic conversion, the fixtures have the following (possibly breaking) changes ([#486](https://github.com/ethereum/execution-spec-tests/pull/486)):\r\n - State test field `transaction` now uses the proper zero-padded hex number format for fields `maxPriorityFeePerGas`, `maxFeePerGas`, and `maxFeePerBlobGas`\r\n - Fixtures' hashes (in the `_info` field) are now calculated by removing the \"_info\" field entirely instead of it being set to an empty dict.\r\n- 🐞 Relax minor and patch dependency requirements to avoid conflicting package dependencies ([#510](https://github.com/ethereum/execution-spec-tests/pull/510)).\r\n- 🔀 Update all CI actions to use their respective Node.js 20 versions, ahead of their Node.js 16 version deprecations ([#527](https://github.com/ethereum/execution-spec-tests/pull/527)).\r\n- ✨ Releases now contain a `fixtures_eip7692.tar.gz` which contains all EOF fixtures ([#573](https://github.com/ethereum/execution-spec-tests/pull/573)).\r\n- ✨ Use `solc-select` for tox when running locally and within CI ([#604](https://github.com/ethereum/execution-spec-tests/pull/604)).\r\n\r\n\r\n## New Contributors\r\n* @raxhvl made their first contribution in https://github.com/ethereum/execution-spec-tests/pull/482\r\n* @yperbasis made their first contribution in https://github.com/ethereum/execution-spec-tests/pull/488\r\n* @redistay made their first contribution in https://github.com/ethereum/execution-spec-tests/pull/493\r\n* @hanghuge made their first contribution in https://github.com/ethereum/execution-spec-tests/pull/495\r\n* @gumb0 made their first contribution in https://github.com/ethereum/execution-spec-tests/pull/559\r\n* @artemd24 made their first contribution in https://github.com/ethereum/execution-spec-tests/pull/568\r\n* @pdobacz made their first contribution in https://github.com/ethereum/execution-spec-tests/pull/614\r\n* @raymondnguyen8 made their first contribution in https://github.com/ethereum/execution-spec-tests/pull/632\r\n\r\n**Full Changelog**: https://github.com/ethereum/execution-spec-tests/compare/v2.1.1...v3.0.0", - "reactions": { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/166538302/reactions", - "total_count": 2, - "+1": 0, - "-1": 0, - "laugh": 0, - "hooray": 0, - "confused": 0, - "heart": 0, - "rocket": 2, - "eyes": 0 - }, - "mentions_count": 8 + ] }, { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/166305534", - "assets_url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/166305534/assets", - "upload_url": "https://uploads.github.com/repos/ethereum/execution-spec-tests/releases/166305534/assets{?name,label}", - "html_url": "https://github.com/ethereum/execution-spec-tests/releases/tag/eip7692%40v1.0.7", - "id": 166305534, - "author": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "node_id": "RE_kwDOIQGLK84J6Z7-", - "tag_name": "eip7692@v1.0.7", - "target_commitish": "main", - "name": "eip7692@v1.0.7", - "draft": false, - "prerelease": true, - "created_at": "2024-07-19T21:16:06Z", - "published_at": "2024-07-19T21:33:40Z", + "html_url": "https://github.com/ethereum/execution-specs/releases/tag/tests-glamsterdam-devnet%40v6.1.0", + "id": 900010, + "tag_name": "tests-glamsterdam-devnet@v6.1.0", + "name": "tests-glamsterdam-devnet@v6.1.0", + "created_at": "2026-06-25T10:32:04Z", + "published_at": "2026-06-25T10:32:04Z", "assets": [ { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/assets/180664153", - "id": 180664153, - "node_id": "RA_kwDOIQGLK84KxLdZ", - "name": "fixtures_eip7692.tar.gz", - "label": "", - "uploader": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, + "browser_download_url": "https://github.com/ethereum/execution-specs/releases/download/tests-glamsterdam-devnet%40v6.1.0/fixtures_glamsterdam-devnet.tar.gz", + "id": 9000100, + "name": "fixtures_glamsterdam-devnet.tar.gz", "content_type": "application/gzip", - "state": "uploaded", - "size": 2075714, - "download_count": 140, - "created_at": "2024-07-19T21:23:37Z", - "updated_at": "2024-07-19T21:23:38Z", - "browser_download_url": "https://github.com/ethereum/execution-spec-tests/releases/download/eip7692%40v1.0.7/fixtures_eip7692.tar.gz" + "size": 1000000 } - ], - "tarball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/tarball/eip7692@v1.0.7", - "zipball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/zipball/eip7692@v1.0.7", - "body": "## What's Changed\r\n* new(tests): EOF - EIP-6206: Runtime stack overflow at JUMPF by @gumb0 in https://github.com/ethereum/execution-spec-tests/pull/690\r\n\r\n**Full Changelog**: https://github.com/ethereum/execution-spec-tests/compare/eip7692@v1.0.6...eip7692@v1.0.7", - "mentions_count": 1 + ] }, { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/166305622", - "assets_url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/166305622/assets", - "upload_url": "https://uploads.github.com/repos/ethereum/execution-spec-tests/releases/166305622/assets{?name,label}", - "html_url": "https://github.com/ethereum/execution-spec-tests/releases/tag/eip7692-prague%40v1.0.7", - "id": 166305622, - "author": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "node_id": "RE_kwDOIQGLK84J6Z9W", - "tag_name": "eip7692-prague@v1.0.7", - "target_commitish": "main", - "name": "eip7692-prague@v1.0.7", - "draft": false, - "prerelease": true, - "created_at": "2024-07-19T21:16:26Z", - "published_at": "2024-07-19T21:34:05Z", + "html_url": "https://github.com/ethereum/execution-specs/releases/tag/tests-glamsterdam-devnet%40v6.0.1", + "id": 900011, + "tag_name": "tests-glamsterdam-devnet@v6.0.1", + "name": "tests-glamsterdam-devnet@v6.0.1", + "created_at": "2026-06-26T09:00:00Z", + "published_at": "2026-06-26T09:00:00Z", "assets": [ { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/assets/180664380", - "id": 180664380, - "node_id": "RA_kwDOIQGLK84KxLg8", - "name": "fixtures_eip7692-prague.tar.gz", - "label": "", - "uploader": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, + "browser_download_url": "https://github.com/ethereum/execution-specs/releases/download/tests-glamsterdam-devnet%40v6.0.1/fixtures_glamsterdam-devnet.tar.gz", + "id": 9000110, + "name": "fixtures_glamsterdam-devnet.tar.gz", "content_type": "application/gzip", - "state": "uploaded", - "size": 7797035, - "download_count": 6, - "created_at": "2024-07-19T21:24:33Z", - "updated_at": "2024-07-19T21:24:33Z", - "browser_download_url": "https://github.com/ethereum/execution-spec-tests/releases/download/eip7692-prague%40v1.0.7/fixtures_eip7692-prague.tar.gz" + "size": 1000000 } - ], - "tarball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/tarball/eip7692-prague@v1.0.7", - "zipball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/zipball/eip7692-prague@v1.0.7", - "body": "## What's Changed\r\n* new(tests): EOF - EIP-6206: Runtime stack overflow at JUMPF by @gumb0 in https://github.com/ethereum/execution-spec-tests/pull/690\r\n\r\n**Full Changelog**: https://github.com/ethereum/execution-spec-tests/compare/eip7692@v1.0.6...eip7692-prague@v1.0.7", - "mentions_count": 1 + ] }, { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/165899819", - "assets_url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/165899819/assets", - "upload_url": "https://uploads.github.com/repos/ethereum/execution-spec-tests/releases/165899819/assets{?name,label}", - "html_url": "https://github.com/ethereum/execution-spec-tests/releases/tag/eip7692%40v1.0.6", - "id": 165899819, - "author": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, - "node_id": "RE_kwDOIQGLK84J424r", - "tag_name": "eip7692@v1.0.6", - "target_commitish": "main", - "name": "eip7692@v1.0.6", - "draft": false, - "prerelease": true, - "created_at": "2024-07-17T17:31:27Z", - "published_at": "2024-07-17T18:17:33Z", + "html_url": "https://github.com/ethereum/execution-specs/releases/tag/v2.20.0", + "id": 900012, + "tag_name": "v2.20.0", + "name": "v2.20.0", + "created_at": "2026-07-21T10:00:00Z", + "published_at": "2026-07-21T10:00:00Z", "assets": [ { - "url": "https://api.github.com/repos/ethereum/execution-spec-tests/releases/assets/180177698", - "id": 180177698, - "node_id": "RA_kwDOIQGLK84KvUsi", - "name": "fixtures_eip7692.tar.gz", - "label": "", - "uploader": { - "login": "github-actions[bot]", - "id": 41898282, - "node_id": "MDM6Qm90NDE4OTgyODI=", - "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/github-actions%5Bbot%5D", - "html_url": "https://github.com/apps/github-actions", - "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", - "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", - "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", - "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", - "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", - "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", - "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", - "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", - "type": "Bot", - "user_view_type": "public", - "site_admin": false - }, + "browser_download_url": "https://github.com/ethereum/execution-specs/releases/download/v2.20.0/ethereum_execution_specs-2.20.0.tar.gz", + "id": 9000120, + "name": "ethereum_execution_specs-2.20.0.tar.gz", "content_type": "application/gzip", - "state": "uploaded", - "size": 2057949, - "download_count": 14, - "created_at": "2024-07-17T17:39:35Z", - "updated_at": "2024-07-17T17:39:35Z", - "browser_download_url": "https://github.com/ethereum/execution-spec-tests/releases/download/eip7692%40v1.0.6/fixtures_eip7692.tar.gz" + "size": 1000000 } - ], - "tarball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/tarball/eip7692@v1.0.6", - "zipball_url": "https://api.github.com/repos/ethereum/execution-spec-tests/zipball/eip7692@v1.0.6", - "body": "## What's Changed\r\n* fix(tests): EOF - EIP-4200: Organize code_validation_jump.py tests by @marioevz in https://github.com/ethereum/execution-spec-tests/pull/666\r\n* fix(tests): EOF - EIP-3540: EXTCODECOPY a hard-coded size for EOF target by @gurukamath in https://github.com/ethereum/execution-spec-tests/pull/667\r\n* new(tests): EOF - EIP-7480: Add tests for DATACOPY memory expansion by @pdobacz in https://github.com/ethereum/execution-spec-tests/pull/664\r\n* feat(fw): support invalid containers in EOFStateTest by @chfast in https://github.com/ethereum/execution-spec-tests/pull/665\r\n* fix(tests): EOF - EIP-3540: Organize code_validation.py tests by @marioevz in https://github.com/ethereum/execution-spec-tests/pull/668\r\n* new(tests): EOF - EIP-7620: Add more tests for validating EOF subcontainer kinds by @gumb0 in https://github.com/ethereum/execution-spec-tests/pull/676\r\n* new(tests): EOF - EIP-7069: RETURNDATACOPY mem expansion and copy OOG by @pdobacz in https://github.com/ethereum/execution-spec-tests/pull/671\r\n* fix(cli): `RJUMPV` in `evm_bytes_to_python` by @marioevz in https://github.com/ethereum/execution-spec-tests/pull/683\r\n* refactor(fw): Refactor `ethereum_test_tools` into separate libraries by @marioevz in https://github.com/ethereum/execution-spec-tests/pull/645\r\n* new(tests) EXT*CALL input data validation by @shemnon in https://github.com/ethereum/execution-spec-tests/pull/685\r\n* new(tests): EOF - EIP-4750: Runtime stack overflow at CALLF by @gumb0 in https://github.com/ethereum/execution-spec-tests/pull/678\r\n\r\n## New Contributors\r\n* @raymondnguyen8 made their first contribution in https://github.com/ethereum/execution-spec-tests/pull/632\r\n\r\n**Full Changelog**: https://github.com/ethereum/execution-spec-tests/compare/eip7692@v1.0.5...eip7692@v1.0.6", - "mentions_count": 7 + ] } ] diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/tests/test_fixtures_source_input_types.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/tests/test_fixtures_source_input_types.py index 241fda66d32..5a5418006d3 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/tests/test_fixtures_source_input_types.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/tests/test_fixtures_source_input_types.py @@ -36,16 +36,16 @@ def test_fixtures_source_from_release_spec_makes_api_calls(self) -> None: """ Test that release specs still make API calls and get release page. """ - test_spec = "stable@latest" + test_spec = "tests@latest" with patch( "execution_testing.cli.pytest_commands.plugins.consume.consume.get_release_url" ) as mock_get_url: - mock_get_url.return_value = "https://github.com/ethereum/execution-spec-tests/releases/download/v3.0.0/fixtures_stable.tar.gz" + mock_get_url.return_value = "https://github.com/ethereum/execution-specs/releases/download/tests%40v20.0.0/fixtures.tar.gz" with patch( "execution_testing.cli.pytest_commands.plugins.consume.consume.get_release_page_url" ) as mock_get_page: - mock_get_page.return_value = "https://github.com/ethereum/execution-spec-tests/releases/tag/v3.0.0" + mock_get_page.return_value = "https://github.com/ethereum/execution-specs/releases/tag/tests%40v20.0.0" with patch( "execution_testing.cli.pytest_commands.plugins.consume.consume.FixtureDownloader" ) as mock_downloader: @@ -61,11 +61,11 @@ def test_fixtures_source_from_release_spec_makes_api_calls(self) -> None: # Verify API calls were made and release page is set mock_get_url.assert_called_once_with(test_spec) mock_get_page.assert_called_once_with( - "https://github.com/ethereum/execution-spec-tests/releases/download/v3.0.0/fixtures_stable.tar.gz" + "https://github.com/ethereum/execution-specs/releases/download/tests%40v20.0.0/fixtures.tar.gz" ) assert ( source.release_page - == "https://github.com/ethereum/execution-spec-tests/releases/tag/v3.0.0" + == "https://github.com/ethereum/execution-specs/releases/tag/tests%40v20.0.0" ) def test_fixtures_source_from_regular_url_no_release_page(self) -> None: @@ -135,8 +135,8 @@ def test_output_formatting_with_release_page_for_specs(self) -> None: config.fixtures_source.was_cached = False config.fixtures_source.is_local = False config.fixtures_source.path = Path("/tmp/test") - config.fixtures_source.url = "https://github.com/ethereum/execution-spec-tests/releases/download/v3.0.0/fixtures_stable.tar.gz" - config.fixtures_source.release_page = "https://github.com/ethereum/execution-spec-tests/releases/tag/v3.0.0" + config.fixtures_source.url = "https://github.com/ethereum/execution-specs/releases/download/tests%40v20.0.0/fixtures.tar.gz" + config.fixtures_source.release_page = "https://github.com/ethereum/execution-specs/releases/tag/tests%40v20.0.0" # Simulate the output generation logic from pytest_configure reason = "" @@ -151,7 +151,7 @@ def test_output_formatting_with_release_page_for_specs(self) -> None: reason += f"\nRelease page: {config.fixtures_source.release_page}" assert ( - "Release page: https://github.com/ethereum/execution-spec-tests/releases/tag/v3.0.0" + "Release page: https://github.com/ethereum/execution-specs/releases/tag/tests%40v20.0.0" in reason ) @@ -176,7 +176,7 @@ def test_from_input_handles_release_url(self) -> None: def test_from_input_handles_release_spec(self) -> None: """Test that from_input properly handles release specs.""" - test_spec = "stable@latest" + test_spec = "tests@latest" with patch.object( FixturesSource, "from_release_spec" diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/tests/test_releases.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/tests/test_releases.py index 0b36da6228b..cb347efc946 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/tests/test_releases.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/tests/test_releases.py @@ -8,6 +8,7 @@ from ..releases import ( SUPPORTED_REPOS, + NoSuchReleaseError, ReleaseInformation, get_release_url_from_release_information, is_release_url, @@ -29,34 +30,101 @@ def release_information() -> List[ReleaseInformation]: @pytest.mark.parametrize( "release_name,expected_release_download_url", [ + # The `tests` feature tags as `tests@vX.Y.Z` and ships a plain + # `fixtures.tar.gz` asset. ( - "pectra-devnet-5", - "pectra-devnet-5%40v1.0.0/fixtures_pectra-devnet-5.tar.gz", + "tests@v20.0.0", + "tests%40v20.0.0/fixtures.tar.gz", ), ( - "pectra-devnet-4@v1.0.0", - "pectra-devnet-4%40v1.0.0/fixtures_pectra-devnet-4.tar.gz", + "tests@latest", + "tests%40v20.0.0/fixtures.tar.gz", ), + # A bare `latest` or `vX.Y.Z` resolves the mainnet `tests` release. ( - "stable", - "v3.0.0/fixtures_stable.tar.gz", + "latest", + "tests%40v20.0.0/fixtures.tar.gz", ), ( - "develop", - "v3.0.0/fixtures_develop.tar.gz", + "v20.0.0", + "tests%40v20.0.0/fixtures.tar.gz", ), + # Other features tag as `tests-@vX.Y.Z`; both the friendly + # feature name and the full tag are accepted. ( - "eip7692-prague", - "eip7692%40v1.1.1/fixtures_eip7692-prague.tar.gz", + "bal@v7.3.1", + "tests-bal%40v7.3.1/fixtures_bal.tar.gz", + ), + ( + "tests-bal@v7.3.2", + "tests-bal%40v7.3.2/fixtures_bal.tar.gz", + ), + ( + "bal@latest", + "tests-bal%40v7.3.2/fixtures_bal.tar.gz", + ), + ( + "bal-devnet@v8.0.0", + "tests-bal-devnet%40v8.0.0/fixtures_bal-devnet.tar.gz", + ), + ( + "benchmark@latest", + "tests-benchmark%40v0.0.9/fixtures_benchmark.tar.gz", + ), + ( + "tests-benchmark@latest", + "tests-benchmark%40v0.0.9/fixtures_benchmark.tar.gz", + ), + ( + "tests-glamsterdam-devnet@v6.1.0", + "tests-glamsterdam-devnet%40v6.1.0/" + "fixtures_glamsterdam-devnet.tar.gz", + ), + # `latest` resolves the highest version, not the most recently + # published: v6.0.1 is published after v6.1.0 in the manifest but + # must not win over the newer v6.1 line. + ( + "glamsterdam-devnet@latest", + "tests-glamsterdam-devnet%40v6.1.0/" + "fixtures_glamsterdam-devnet.tar.gz", + ), + ], +) +def test_eels_release_parsing( + release_name: str, + expected_release_download_url: str, + release_information: List[ReleaseInformation], +) -> None: + """Test parsing of the `tests[-]@vX.Y.Z` tag scheme.""" + assert ( + "https://github.com/ethereum/execution-specs/releases/download/" + + expected_release_download_url + ) == get_release_url_from_release_information( + release_name, release_information + ) + + +# TODO: Remove with the legacy `stable`/`develop` support and the `v4.5.0` +# manifest entry after 2026-08 (see #3085). +@pytest.mark.parametrize( + "release_name,expected_release_download_url", + [ + ( + "stable@latest", + "v4.5.0/fixtures_stable.tar.gz", + ), + ( + "develop@v4.5.0", + "v4.5.0/fixtures_develop.tar.gz", ), ], ) -def test_release_parsing( +def test_legacy_release_parsing( release_name: str, expected_release_download_url: str, release_information: List[ReleaseInformation], ) -> None: - """Test release parsing.""" + """Test legacy `stable`/`develop` releases still resolve.""" assert ( "https://github.com/ethereum/execution-spec-tests/releases/download/" + expected_release_download_url @@ -65,6 +133,37 @@ def test_release_parsing( ) +@pytest.mark.parametrize( + "release_name", + [ + # A bare `vX.Y.Z` is shorthand for `tests@vX.Y.Z` and must never + # fall back to the spec-package release tagged plain `v2.20.0` in + # the manifest, even though it is the most recently published + # release and its version exists. + "v2.20.0", + "tests@v2.20.0", + # The legacy `stable`/`develop` fallback matches bare `vX.Y.Z` + # tags, but the asset check must still exclude the decoy. + "stable@v2.20.0", + ], +) +def test_non_fixture_releases_do_not_resolve( + release_name: str, + release_information: List[ReleaseInformation], +) -> None: + """ + Test that spec-package releases never resolve. + + The manifest contains a spec-package decoy tagged `v2.20.0` whose only + asset is the Python package sdist. It must be excluded twice over: its + tag lacks the `tests` namespace, and it ships no fixture tarball. + """ + with pytest.raises(NoSuchReleaseError): + get_release_url_from_release_information( + release_name, release_information + ) + + @pytest.mark.parametrize( "url,expected", [ diff --git a/packages/testing/src/execution_testing/config/app.py b/packages/testing/src/execution_testing/config/app.py index 2f7a4867904..cd2b11ae47c 100644 --- a/packages/testing/src/execution_testing/config/app.py +++ b/packages/testing/src/execution_testing/config/app.py @@ -19,10 +19,15 @@ class AppConfig(BaseModel): @property def version(self) -> str: - """Get the current version from releases.""" - spec = "stable@latest" - release_url = releases.get_release_url(spec) - return release_url.split("/v")[-1].split("/")[0] + """Get the version of the latest mainnet `tests` release.""" + spec = f"{releases.TESTS_FEATURE_NAME}@latest" + try: + release = releases.find_release( + spec, releases.get_release_information() + ) + except releases.NoSuchReleaseError: + return "unknown" + return release.tag_name.split("@v")[-1] DEFAULT_LOGS_DIR: Path = ( Path(__file__).resolve().parent.parent.parent / "logs" From 64ed6762332162bbedb66657cab156cfa2b22c3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Thu, 2 Jul 2026 20:10:32 +0200 Subject: [PATCH 080/233] feat(tests): EIP-8037 calldata floor binds with an over-cap reservoir (#3080) Co-authored-by: LouisTsai Co-authored-by: spencer-tb --- .../test_state_gas_calldata_floor.py | 67 +++++++++++++++++-- 1 file changed, 60 insertions(+), 7 deletions(-) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_calldata_floor.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_calldata_floor.py index 52a5b034cac..b60fc41ce5c 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_calldata_floor.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_calldata_floor.py @@ -2,9 +2,8 @@ Test EIP-7623 calldata floor interaction with EIP-8037 state gas. The calldata floor applies to the regular gas dimension only. It -does not affect state gas. Block gas accounting uses -max(tx_regular_gas, calldata_floor) for regular gas and tracks -state gas separately. +does not affect state gas. Block gas accounting uses tx_regular_gas +(without the floor) for regular gas and tracks state gas separately. Tests for [EIP-8037: State Creation Gas Cost Increase] (https://eips.ethereum.org/EIPS/eip-8037). @@ -17,11 +16,13 @@ Block, BlockchainTestFiller, Fork, + Header, Op, StateTestFiller, Storage, Transaction, TransactionException, + TransactionReceipt, ) from execution_testing.checklists import EIPChecklist @@ -70,10 +71,10 @@ def test_calldata_floor_independent_of_state_gas( """ Test calldata floor applies only to regular gas dimension. - The calldata floor inflates regular gas used for block accounting - but does not affect the state gas dimension. A transaction with - high calldata and no state operations should succeed even when - the floor exceeds actual execution gas. + The calldata floor applies only to the sender's bill and does not + affect the state gas dimension. A transaction with high calldata + and no state operations should succeed even when the floor exceeds + actual execution gas. """ contract = pre.deploy_contract(code=Op.STOP) @@ -251,3 +252,55 @@ def test_calldata_floor_applied_to_sender_refund( blocks=[Block(txs=[tx])], post={sender: Account(balance=initial - calldata_floor * gas_price)}, ) + + +@pytest.mark.valid_from("EIP8037") +def test_calldata_floor_binds_with_reservoir( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Bind the calldata floor while an over-cap reservoir funds state gas. + + Large calldata makes the EIP-7976 floor the sender's bill, while an + over-cap `gas_limit` puts the SSTORE-set state charge in the + reservoir. The floor feeds only the receipt; the block accounts + regular and state separately, so the header gas_used is the state + dimension (not the floor). + """ + storage = Storage() + code = Op.SSTORE(storage.store_next(1), 1, new_value=1) + state_cost = code.state_cost(fork) + regular_cost = code.regular_cost(fork) + + # Sized so the floor binds while block-regular stays under storage_set. + calldata = b"\x00" * 5000 + floor = fork.transaction_data_floor_cost_calculator()(data=calldata) + intrinsic = fork.transaction_intrinsic_cost_calculator()( + calldata=calldata, + return_cost_deducted_prior_execution=True, + ) + tx_regular = intrinsic + regular_cost + assert floor > tx_regular + state_cost, ( + "calldata floor must exceed the sender's pre-floor bill" + ) + assert tx_regular < state_cost, ( + "block-regular must stay under the state dimension" + ) + + contract = pre.deploy_contract(code=code) + + tx = Transaction( + to=contract, + data=calldata, + state_gas_reservoir=state_cost, + sender=pre.fund_eoa(), + expected_receipt=TransactionReceipt(cumulative_gas_used=floor), + ) + state_test( + pre=pre, + post={contract: Account(storage=storage)}, + tx=tx, + blockchain_test_header_verify=Header(gas_used=state_cost), + ) From ebbe644a538bb3a4fa23432b690151e00b1c2449 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Thu, 2 Jul 2026 20:56:47 +0200 Subject: [PATCH 081/233] feat(tests): access-list slot warmth survives a failed CREATE2 (#3086) Co-authored-by: LouisTsai --- .../test_warm_status_revert.py | 59 ++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/tests/berlin/eip2929_gas_cost_increases/test_warm_status_revert.py b/tests/berlin/eip2929_gas_cost_increases/test_warm_status_revert.py index 97a36987279..522aa30011f 100644 --- a/tests/berlin/eip2929_gas_cost_increases/test_warm_status_revert.py +++ b/tests/berlin/eip2929_gas_cost_increases/test_warm_status_revert.py @@ -1,18 +1,25 @@ """ -Tests that warm/cold access status is reverted when a sub-call reverts. +Tests for warm/cold access status across reverting sub-frames. + +A reverting sub-call rolls back the warm status it introduced; warm status +from an outer frame, such as the transaction access list, survives a failed +child, including a reverted CREATE. """ import pytest from execution_testing import ( + AccessList, Account, Alloc, CodeGasMeasure, Conditional, Environment, Fork, + Hash, Op, StateTestFiller, Transaction, + compute_create2_address, ) REFERENCE_SPEC_GIT_PATH = "EIPS/eip-2929.md" @@ -136,3 +143,53 @@ def test_account_warm_status_reverted_by_subcall( gas_limit=1_000_000, ), ) + + +@pytest.mark.valid_from("Berlin") +def test_access_list_slot_warmth_survives_failed_create2( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + An access-list-warmed slot stays warm after a CREATE2 that reverts. + + The tx access list warms `(created, slot 0)` in the root frame. The + same value-branching initcode is deployed twice at one CREATE2 address: + the first attempt (no value) reverts, the second (with value) records + the warmed slot's SLOAD cost. The recorded cost is the warm price, + proving the failed create did not drop the access-list warmth. + """ + sload_push_cost = (Op.PUSH1(0) * len(Op.SLOAD.kwargs)).gas_cost(fork) + warm_sload_cost = Op.SLOAD(key_warm=True).gas_cost(fork) + + initcode = Conditional( + condition=Op.ISZERO(Op.CALLVALUE), + if_true=Op.REVERT(offset=0, size=0), + if_false=CodeGasMeasure( + code=Op.SLOAD(Op.PUSH1(0)), + overhead_cost=sload_push_cost, + extra_stack_items=1, + sstore_key=1, + ) + + Op.RETURN(0, 0), + ) + holder = pre.deploy_contract(code=initcode) + + creator = pre.deploy_contract( + code=Op.EXTCODECOPY(holder, 0, 0, len(initcode)) + + Op.POP(Op.CREATE2(value=0, size=len(initcode))) + + Op.POP(Op.CREATE2(value=1, size=len(initcode))), + balance=1, + ) + created = compute_create2_address(creator, 0, initcode) + + state_test( + pre=pre, + post={created: Account(balance=1, storage={1: warm_sload_cost})}, + tx=Transaction( + sender=pre.fund_eoa(), + to=creator, + access_list=[AccessList(address=created, storage_keys=[Hash(0)])], + ), + ) From b0d5cae83ac6b673ce90f5c922e8fa50678627a6 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Thu, 2 Jul 2026 13:29:03 -0600 Subject: [PATCH 082/233] feat(spec,tests): Implement EIP-8282 (#3070) * feat(spec,tests): Implement EIP-8282 (#2990) * feat(specs): Implement EIP-8282 * feat(test-forks): Implement EIP-8282 framework changes * feat(tests): Implement EIP-8282 tests * feat(tests): Add EIP-8282 to EIP-7685 tests * fix(test-forks): Update EIP-8282 deposit contract to 537b9c1 * fix(tests): Update EIP-8282 builder deposit contract target/max to 32/256 * fix(tests): Failing tests after update * refactor(test-forks): Introduce `minimum_block_gas_limit` * refactor(test-forks): Update EIP-8282 `empty_block_bal_item_count` * refactor(test-forks): Use pkgutil to load contract binaries * refactor(test-forks): Use pkgutil to load EIP-8282 contract binaries * refactor(test-forks): Remove pre_allocation_blockchain type: ignore comments * refactor(test-forks): Remove EIP-8282 pre_allocation_blockchain type: ignore comments * Update tests/amsterdam/eip8282_builder_execution_requests/__init__.py Co-authored-by: Sam Wilson <57262657+SamWilsn@users.noreply.github.com> * Update tests/amsterdam/eip8282_builder_execution_requests/spec.py Co-authored-by: Sam Wilson <57262657+SamWilsn@users.noreply.github.com> * chore(tests): Remove `dataclass` from `Spec` class EIP-8282 * chore(tests): Make test behavior more explicit Co-authored-by: Sam Wilson <57262657+SamWilsn@users.noreply.github.com> * chore(spec,tests): set EIP-8282 builder addresses for glamsterdam-devnet-6 --------- Co-authored-by: Sam Wilson <57262657+SamWilsn@users.noreply.github.com> Co-authored-by: spencer-tb * fix(test-forks): Match EIP contract names * fix(test-plugins): Add eth_config unit test * feat(tests): Mirror EIP-7002/7251 tests * feat(test-types): Implement `SystemContractInteractionMeasuredOutOfGasContract` * fix(tests): Use `SystemContractInteractionMeasuredOutOfGasContract` * fix(test-forks): Lint * fix(tests): Improve modified contract tests * fix(tests): EIP-8282 builder request test fixes (#3090) * fix(tests): exceed per-block max in builder deposit carry-over test * chore(tests): clarify multi-type request test gas limit comment --------- Co-authored-by: Sam Wilson <57262657+SamWilsn@users.noreply.github.com> Co-authored-by: spencer-tb Co-authored-by: spencer Co-authored-by: LouisTsai --- .../testing/src/execution_testing/__init__.py | 6 + .../tests/test_execute_eth_config.py | 134 +++++++++ .../contracts/builder_deposit_request.bin | Bin 0 -> 568 bytes .../contracts/builder_exit_request.bin | Bin 0 -> 396 bytes .../forks/forks/eips/amsterdam/eip_8282.py | 72 +++++ .../execution_testing/test_types/__init__.py | 6 + .../test_types/request_types.py | 47 ++++ .../system_contract_request_types.py | 138 ++++++++++ src/ethereum/forks/amsterdam/fork.py | 32 +++ src/ethereum/forks/amsterdam/requests.py | 26 +- .../__init__.py | 5 + .../builder_deposit_deploy_tx.json | 15 + .../builder_exit_deploy_tx.json | 15 + .../conftest.py | 8 + .../helpers.py | 115 ++++++++ .../spec.py | 58 ++++ .../test_builder_deposits.py | 259 ++++++++++++++++++ .../test_builder_exits.py | 164 +++++++++++ .../test_builder_requests_during_fork.py | 124 +++++++++ .../test_builder_requests_out_of_gas.py | 108 ++++++++ .../test_contract_deployment.py | 102 +++++++ .../test_eip_mainnet.py | 82 ++++++ .../test_modified_builder_contract.py | 226 +++++++++++++++ .../test_multi_type_requests.py | 11 +- 24 files changed, 1748 insertions(+), 5 deletions(-) create mode 100644 packages/testing/src/execution_testing/forks/forks/eips/amsterdam/contracts/builder_deposit_request.bin create mode 100644 packages/testing/src/execution_testing/forks/forks/eips/amsterdam/contracts/builder_exit_request.bin create mode 100644 packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8282.py create mode 100644 tests/amsterdam/eip8282_builder_execution_requests/__init__.py create mode 100644 tests/amsterdam/eip8282_builder_execution_requests/builder_deposit_deploy_tx.json create mode 100644 tests/amsterdam/eip8282_builder_execution_requests/builder_exit_deploy_tx.json create mode 100644 tests/amsterdam/eip8282_builder_execution_requests/conftest.py create mode 100644 tests/amsterdam/eip8282_builder_execution_requests/helpers.py create mode 100644 tests/amsterdam/eip8282_builder_execution_requests/spec.py create mode 100644 tests/amsterdam/eip8282_builder_execution_requests/test_builder_deposits.py create mode 100644 tests/amsterdam/eip8282_builder_execution_requests/test_builder_exits.py create mode 100644 tests/amsterdam/eip8282_builder_execution_requests/test_builder_requests_during_fork.py create mode 100644 tests/amsterdam/eip8282_builder_execution_requests/test_builder_requests_out_of_gas.py create mode 100644 tests/amsterdam/eip8282_builder_execution_requests/test_contract_deployment.py create mode 100644 tests/amsterdam/eip8282_builder_execution_requests/test_eip_mainnet.py create mode 100644 tests/amsterdam/eip8282_builder_execution_requests/test_modified_builder_contract.py diff --git a/packages/testing/src/execution_testing/__init__.py b/packages/testing/src/execution_testing/__init__.py index 6f3289afd26..6b079cd3d5d 100644 --- a/packages/testing/src/execution_testing/__init__.py +++ b/packages/testing/src/execution_testing/__init__.py @@ -63,6 +63,8 @@ Blob, BlockAccessList, BlockAccessListExpectation, + BuilderDepositRequest, + BuilderExitRequest, ChainConfig, ConsolidationRequest, DepositRequest, @@ -73,6 +75,7 @@ Requests, SystemContractInteractionBase, SystemContractInteractionContract, + SystemContractInteractionMeasuredOutOfGasContract, SystemContractInteractionTransaction, SystemContractRequest, TestParameterGroup, @@ -154,6 +157,8 @@ "BlockchainTest", "BlockchainTestFiller", "BlockException", + "BuilderDepositRequest", + "BuilderExitRequest", "Bytecode", "Bytes", "BytesConcatenation", @@ -202,6 +207,7 @@ "Switch", "SystemContractInteractionBase", "SystemContractInteractionContract", + "SystemContractInteractionMeasuredOutOfGasContract", "SystemContractInteractionTransaction", "SystemContractRequest", "TestAddress", diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/eth_config/tests/test_execute_eth_config.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/eth_config/tests/test_execute_eth_config.py index f9bedc47106..f83ea5ebda1 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/eth_config/tests/test_execute_eth_config.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/eth_config/tests/test_execute_eth_config.py @@ -291,6 +291,96 @@ } """) EXPECTED_BPO5_FORK_ID = ForkHash("0xd3a4880b") +EXPECTED_OSAKA = json.loads(""" +{ + "activationTime": 1753477608, + "blobSchedule": { + "baseFeeUpdateFraction": 5007716, + "max": 12, + "target": 9 + }, + "chainId": "0x88bb0", + "forkId": "0x5e2e4e84", + "precompiles": { + "BLAKE2F": "0x0000000000000000000000000000000000000009", + "BLS12_G1ADD": "0x000000000000000000000000000000000000000b", + "BLS12_G1MSM": "0x000000000000000000000000000000000000000c", + "BLS12_G2ADD": "0x000000000000000000000000000000000000000d", + "BLS12_G2MSM": "0x000000000000000000000000000000000000000e", + "BLS12_MAP_FP2_TO_G2": "0x0000000000000000000000000000000000000011", + "BLS12_MAP_FP_TO_G1": "0x0000000000000000000000000000000000000010", + "BLS12_PAIRING_CHECK": "0x000000000000000000000000000000000000000f", + "BN254_ADD": "0x0000000000000000000000000000000000000006", + "BN254_MUL": "0x0000000000000000000000000000000000000007", + "BN254_PAIRING": "0x0000000000000000000000000000000000000008", + "ECREC": "0x0000000000000000000000000000000000000001", + "ID": "0x0000000000000000000000000000000000000004", + "KZG_POINT_EVALUATION": "0x000000000000000000000000000000000000000a", + "MODEXP": "0x0000000000000000000000000000000000000005", + "P256VERIFY": "0x0000000000000000000000000000000000000100", + "RIPEMD160": "0x0000000000000000000000000000000000000003", + "SHA256": "0x0000000000000000000000000000000000000002" + }, + "systemContracts": { + "BEACON_ROOTS_ADDRESS": "0x000f3df6d732807ef1319fb7b8bb8522d0beac02", + "CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS": + "0x0000bbddc7ce488642fb579f8b00f3a590007251", + "DEPOSIT_CONTRACT_ADDRESS": "0x00000000219ab540356cbb839cbe05303d7705fa", + "HISTORY_STORAGE_ADDRESS": "0x0000f90827f1c53a10cb7a02335b175320002935", + "WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS": + "0x00000961ef480eb55e80d19ad83579a64c007002" + } +} +""") +EXPECTED_OSAKA_FORK_ID = ForkHash("0x5e2e4e84") +# Amsterdam (EIP-8282) adds the builder deposit and exit request predeploys +# to the `systemContracts` reported by `eth_config`. +EXPECTED_AMSTERDAM = json.loads(""" +{ + "activationTime": 1753575912, + "blobSchedule": { + "baseFeeUpdateFraction": 5007716, + "max": 12, + "target": 9 + }, + "chainId": "0x88bb0", + "forkId": "0x9d7b6bfb", + "precompiles": { + "BLAKE2F": "0x0000000000000000000000000000000000000009", + "BLS12_G1ADD": "0x000000000000000000000000000000000000000b", + "BLS12_G1MSM": "0x000000000000000000000000000000000000000c", + "BLS12_G2ADD": "0x000000000000000000000000000000000000000d", + "BLS12_G2MSM": "0x000000000000000000000000000000000000000e", + "BLS12_MAP_FP2_TO_G2": "0x0000000000000000000000000000000000000011", + "BLS12_MAP_FP_TO_G1": "0x0000000000000000000000000000000000000010", + "BLS12_PAIRING_CHECK": "0x000000000000000000000000000000000000000f", + "BN254_ADD": "0x0000000000000000000000000000000000000006", + "BN254_MUL": "0x0000000000000000000000000000000000000007", + "BN254_PAIRING": "0x0000000000000000000000000000000000000008", + "ECREC": "0x0000000000000000000000000000000000000001", + "ID": "0x0000000000000000000000000000000000000004", + "KZG_POINT_EVALUATION": "0x000000000000000000000000000000000000000a", + "MODEXP": "0x0000000000000000000000000000000000000005", + "P256VERIFY": "0x0000000000000000000000000000000000000100", + "RIPEMD160": "0x0000000000000000000000000000000000000003", + "SHA256": "0x0000000000000000000000000000000000000002" + }, + "systemContracts": { + "BEACON_ROOTS_ADDRESS": "0x000f3df6d732807ef1319fb7b8bb8522d0beac02", + "BUILDER_DEPOSIT_CONTRACT_ADDRESS": + "0x0000884d2aa32eaa155f59a2f24efa73d9008282", + "BUILDER_EXIT_CONTRACT_ADDRESS": + "0x000014574a74c805590aff9499fc7a690f008282", + "CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS": + "0x0000bbddc7ce488642fb579f8b00f3a590007251", + "DEPOSIT_CONTRACT_ADDRESS": "0x00000000219ab540356cbb839cbe05303d7705fa", + "HISTORY_STORAGE_ADDRESS": "0x0000f90827f1c53a10cb7a02335b175320002935", + "WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS": + "0x00000961ef480eb55e80d19ad83579a64c007002" + } +} +""") +EXPECTED_AMSTERDAM_FORK_ID = ForkHash("0x9d7b6bfb") CURRENT_FILE = Path(realpath(__file__)) CURRENT_FOLDER = CURRENT_FILE.parent @@ -426,6 +516,32 @@ target: 15 max: 20 baseFeeUpdateFraction: 5007716 + +HoodiWithAmsterdam: + chainId: 0x88BB0 + genesisHash: 0xbbe312868b376a3001692a646dd2d7d1e4406380dfd86b98aa8a34d1557c971b + forkActivationTimes: + Cancun: 0 + Prague: 1742999832 + Osaka: 1753477608 + Amsterdam: 1753575912 + blobSchedule: + Cancun: + target: 3 + max: 6 + baseFeeUpdateFraction: 3338477 + Prague: + target: 6 + max: 9 + baseFeeUpdateFraction: 5007716 + Osaka: + target: 9 + max: 12 + baseFeeUpdateFraction: 5007716 + Amsterdam: + target: 9 + max: 12 + baseFeeUpdateFraction: 5007716 """ # noqa: E501 @@ -528,6 +644,24 @@ def eth_config(network: NetworkConfig, current_time: int) -> EthConfigResponse: ), id="Hoodi_prague_with_bpos_5", ), + pytest.param( + "HoodiWithAmsterdam", + 1753477608, + EthConfigResponse( + current=EXPECTED_OSAKA, + next=EXPECTED_AMSTERDAM, + last=EXPECTED_AMSTERDAM, + ), + id="Hoodi_osaka_next_amsterdam", + ), + pytest.param( + "HoodiWithAmsterdam", + 1753575912, + EthConfigResponse( + current=EXPECTED_AMSTERDAM, + ), + id="Hoodi_amsterdam", + ), ], indirect=["network"], ) diff --git a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/contracts/builder_deposit_request.bin b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/contracts/builder_deposit_request.bin new file mode 100644 index 0000000000000000000000000000000000000000..6bf64afd5bc6ad74fc4222d2f6b48f1623e8fc36 GIT binary patch literal 568 zcma)(zfQw25XO6UC@e^=)FOr=BBqW=q@@x|p8#2HQV$;PA_m0F17N2tY;M_@`WV^S z*x7jpX8xSh6ok5v!S~bU?z{8nv)ixj@3WIbQ&SsLd$;}fZ>E!rL=zN(z@x#U-K87t zz|Y0~T=-}~01Ho-Gvgn9v{XtBsqD~$9)`Xo95|sE-;GULmDFf(y_s-euP&Zm8tss; zNccpqs~}}zf{bKpsVOJ|9Jnmx;yRA&NOiPzTn&6iCE__BtI!~`hG7{JsV$_bL_>lE*z|QqYn=t10qlN>r0)fz(g9B!}afS%6*gmap2A$Z~jNuN5*PU(NjM4ZFG6 i*9r}>t#MHRJAR^-!d&!chAs8dAj`4crQoMR{P+e#-sr^u literal 0 HcmV?d00001 diff --git a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/contracts/builder_exit_request.bin b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/contracts/builder_exit_request.bin new file mode 100644 index 0000000000000000000000000000000000000000..b41502db5d4d5a5a5f4d7d2770cb72adc4afeaac GIT binary patch literal 396 zcma)&Jx;_h5QP~h2S}u>C}`5st(5HU1=1o}krNj#=DDLsqC(o(OR=T1T!8y<2*eqv z`I$h4w!wuxe)jh~&&}P}`S`5xVo|c0_UDIxmCDo6f}#Q?b{xwUZf&4+P>%n+w5j&{3n?J{wXn%`Yp{5lySKm#%}Aydey34=(y`B_kJ($D}`LaiCq z4jn%)L^fPFVW7+uvk66sSF2Gtb|FZQmo^5uwD<*dDj2Z{Ju2A|8L4VYP-IfSn7xf| zrs*WywH0~r)Jg!&QnI|{{`D~{Pu^;FAn7VbDm~p>t=ZGV=m;uhj>S%*c1rRye|!Uu Ccdy3) literal 0 HcmV?d00001 diff --git a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8282.py b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8282.py new file mode 100644 index 00000000000..090a41ec119 --- /dev/null +++ b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8282.py @@ -0,0 +1,72 @@ +""" +EIP-8282: Builder Execution Requests. + +Predeploy builder deposit and exit request contracts for EIP-7732 builders on +the EIP-7685 request bus. + +https://eips.ethereum.org/EIPS/eip-8282 +""" + +from typing import List, Mapping + +from execution_testing.base_types import Address + +from ....base_fork import BaseFork +from ....bytecode import load_contract_bytecode + +BUILDER_DEPOSIT_CONTRACT_ADDRESS = 0x0000884D2AA32EAA155F59A2F24EFA73D9008282 +BUILDER_DEPOSIT_CONTRACT_BYTECODE = load_contract_bytecode( + __name__, "builder_deposit_request.bin" +) + +BUILDER_EXIT_CONTRACT_ADDRESS = 0x000014574A74C805590AFF9499FC7A690F008282 +BUILDER_EXIT_CONTRACT_BYTECODE = load_contract_bytecode( + __name__, "builder_exit_request.bin" +) + + +class EIP8282(BaseFork): + """EIP-8282 class.""" + + @classmethod + def max_request_type(cls) -> int: + """ + Two request types are introduced: builder deposit requests (0x03) + and builder exit requests (0x04). + """ + return super(EIP8282, cls).max_request_type() + 2 + + @classmethod + def empty_block_bal_item_count(cls) -> int: + """Add block-level access list elements for an empty block.""" + # Builder contracts: 2 addresses + 8 reads = 10 + return super(EIP8282, cls).empty_block_bal_item_count() + 10 + + @classmethod + def system_contracts(cls) -> List[Address]: + """Add the builder deposit and exit request predeploy contracts.""" + return [ + Address( + BUILDER_DEPOSIT_CONTRACT_ADDRESS, + label="BUILDER_DEPOSIT_CONTRACT_ADDRESS", + ), + Address( + BUILDER_EXIT_CONTRACT_ADDRESS, + label="BUILDER_EXIT_CONTRACT_ADDRESS", + ), + ] + super(EIP8282, cls).system_contracts() + + @classmethod + def pre_allocation_blockchain(cls) -> Mapping: + """Pre-allocate the builder deposit and exit request contracts.""" + return { + BUILDER_DEPOSIT_CONTRACT_ADDRESS: { + "nonce": 1, + "code": BUILDER_DEPOSIT_CONTRACT_BYTECODE, + }, + BUILDER_EXIT_CONTRACT_ADDRESS: { + "nonce": 1, + "code": BUILDER_EXIT_CONTRACT_BYTECODE, + }, + **super(EIP8282, cls).pre_allocation_blockchain(), + } diff --git a/packages/testing/src/execution_testing/test_types/__init__.py b/packages/testing/src/execution_testing/test_types/__init__.py index ad0df23da36..ffb82b59196 100644 --- a/packages/testing/src/execution_testing/test_types/__init__.py +++ b/packages/testing/src/execution_testing/test_types/__init__.py @@ -35,6 +35,8 @@ from .phase_manager import TestPhase, TestPhaseManager from .receipt_types import TransactionLog, TransactionReceipt from .request_types import ( + BuilderDepositRequest, + BuilderExitRequest, ConsolidationRequest, DepositRequest, Requests, @@ -44,6 +46,7 @@ FeeSystemContractRequest, SystemContractInteractionBase, SystemContractInteractionContract, + SystemContractInteractionMeasuredOutOfGasContract, SystemContractInteractionTransaction, SystemContractRequest, relay_contract_code, @@ -74,6 +77,8 @@ "Blob", "BlockAccessList", "BlockAccessListExpectation", + "BuilderDepositRequest", + "BuilderExitRequest", "ChainConfig", "ChainConfigDefaults", "ConsolidationRequest", @@ -87,6 +92,7 @@ "Requests", "SystemContractInteractionBase", "SystemContractInteractionContract", + "SystemContractInteractionMeasuredOutOfGasContract", "SystemContractInteractionTransaction", "SystemContractRequest", "TestParameterGroup", diff --git a/packages/testing/src/execution_testing/test_types/request_types.py b/packages/testing/src/execution_testing/test_types/request_types.py index ccd852e4ea1..c88e4dc6cbf 100644 --- a/packages/testing/src/execution_testing/test_types/request_types.py +++ b/packages/testing/src/execution_testing/test_types/request_types.py @@ -113,6 +113,53 @@ def __bytes__(self) -> bytes: ) +class BuilderDepositRequest(RequestBase, CamelModel): + """Builder Deposit Request type (EIP-8282).""" + + pubkey: BLSPublicKey + """The public key of the beacon chain builder.""" + withdrawal_credentials: Hash + """The withdrawal credentials of the beacon chain builder.""" + amount: HexNumber + """The amount in gwei of the builder deposit.""" + signature: BLSSignature + """ + The signature of the deposit using the builder's private key that matches + the `pubkey`. + """ + + type: ClassVar[int] = 3 + """Placeholder request-type byte pending the EIP-8282 final allocation.""" + + def __bytes__(self) -> bytes: + """Return builder deposit's attributes as bytes.""" + return ( + bytes(self.pubkey) + + bytes(self.withdrawal_credentials) + + self.amount.to_bytes(8, "little") + + bytes(self.signature) + ) + + +class BuilderExitRequest(RequestBase, CamelModel): + """Builder Exit Request type (EIP-8282).""" + + source_address: Address = Address(0) + """ + The address of the execution layer account that made the builder exit + request. + """ + pubkey: BLSPublicKey + """The public key of the builder to exit.""" + + type: ClassVar[int] = 4 + """Placeholder request-type byte pending the EIP-8282 final allocation.""" + + def __bytes__(self) -> bytes: + """Return builder exit's attributes as bytes.""" + return bytes(self.source_address) + bytes(self.pubkey) + + def requests_list_to_bytes( requests_list: List[RequestBase] | Bytes | SupportsBytes, ) -> Bytes: diff --git a/packages/testing/src/execution_testing/test_types/system_contract_request_types.py b/packages/testing/src/execution_testing/test_types/system_contract_request_types.py index a21a48d581c..3b39bf162a3 100644 --- a/packages/testing/src/execution_testing/test_types/system_contract_request_types.py +++ b/packages/testing/src/execution_testing/test_types/system_contract_request_types.py @@ -389,3 +389,141 @@ def update_pre(self, pre: Alloc) -> Self: contract_address=contract_address, entry_address=entry_address, ) + + +# Scratch memory offsets for the out-of-gas measurement, placed above the +# largest supported request calldata so they never overlap the copied calldata. +_MEASURE_TOTAL_SLOT = 0x400 +_MEASURE_OVERHEAD_SLOT = 0x420 + + +@dataclass(kw_only=True, frozen=True) +class SystemContractInteractionMeasuredOutOfGasContract( + SystemContractInteractionContract +): + """ + Relay-contract interaction that self-measures each request's gas cost and + forces the requests marked invalid (`valid=False`) out of gas by forwarding + one gas less than required, independent of the fork's gas schedule. + + Reuses `SystemContractInteractionContract` for the driving transaction and + pre-state allocation. + """ + + @property + def contract_code(self) -> Bytecode: + """ + Build a relay contract that measures, at runtime, the gas each system + contract request needs, then forces the requests marked invalid out of + gas by forwarding one gas less than required. + + Like `relay_contract_code`, the contract reads the concatenated request + calldata from its own calldata. It then issues, in order: + + 1. A warm-up call for the first valid request, warming the predeploy + account and its storage slots so the measurement reflects the warm + cost. + 2. A measured call for the second valid request, capturing `GAS` around + the call to record its total cost (CALL base cost + value transfer + + the gas value stipend + the callee's own consumption). + 3. Full-gas calls for any remaining valid requests. + 4. An overhead probe: a call identical to (2) that forwards only + minimal gas so the callee runs out, isolating everything except the + callee's own consumption. Subtracting it from (2) yields the gas + that must be forwarded for the callee to succeed. + 5. A call for each invalid request forwarding `(total - overhead) - 1` + gas, one short of the requirement, so it runs out of gas and is not + enqueued. + + Because steps (2) and (4) use byte-identical op sequences (including a + same-width gas `PUSH`), every base-opcode cost cancels in the + subtraction, making the forced out-of-gas independent of the fork's + gas schedule. + + All requests must share the same calldata length and a non-zero call + value so the measured overhead applies uniformly to each call. + """ + valid_indices = [i for i, r in enumerate(self.requests) if r.valid] + invalid_indices = [ + i for i, r in enumerate(self.requests) if not r.valid + ] + assert len(valid_indices) >= 2, ( + "measured_out_of_gas_relay_code needs at least two valid requests" + ) + assert invalid_indices, ( + "measured_out_of_gas_relay_code needs at least one invalid request" + ) + assert len({len(r.calldata) for r in self.requests}) == 1, ( + "all requests must share the same calldata length" + ) + assert all(r.value > 0 for r in self.requests), ( + "all requests must have a non-zero call value" + ) + + offsets: List[int] = [] + current = 0 + for r in self.requests: + offsets.append(current) + current += len(r.calldata) + + def issue( + *, + index: int, + gas_argument: Bytecode | Op, + measure_into: int | None = None, + ) -> Bytecode: + r = self.requests[index] + copy = Op.CALLDATACOPY(0, offsets[index], len(r.calldata)) + call = Op.CALL( + gas_argument, + r.interaction_contract_address, + r.value, + 0, + len(r.calldata), + 0, + 0, + ) + if measure_into is None: + return copy + Op.POP(call) + return ( + copy + + Op.GAS + + call + + Op.POP + + Op.GAS + + Op.SWAP1 + + Op.SUB + + Op.PUSH2(measure_into) + + Op.MSTORE + ) + + warmup_index = valid_indices[0] + probe_index = valid_indices[1] + + code = issue(index=warmup_index, gas_argument=Op.GAS) + code += issue( + index=probe_index, + # Forward all gas using the same PUSH opcode in both calls: + # Op.GAS and Op.PUSH0 could diverge in gas cost in the future. + gas_argument=Op.PUSH4[0xFFFFFFFF], + measure_into=_MEASURE_TOTAL_SLOT, + ) + for i in valid_indices[2:]: + code += issue(index=i, gas_argument=Op.GAS) + # The overhead probe reuses the second valid request so its call value + # and calldata size (hence CALL base cost) match the total measurement + # exactly. + code += issue( + index=probe_index, + gas_argument=Op.PUSH4[0], + measure_into=_MEASURE_OVERHEAD_SLOT, + ) + forwarded_gas = Op.SUB( + Op.SUB( + Op.MLOAD(_MEASURE_TOTAL_SLOT), Op.MLOAD(_MEASURE_OVERHEAD_SLOT) + ), + 1, + ) + for i in invalid_indices: + code += issue(index=i, gas_argument=forwarded_gas) + return code + self.extra_code diff --git a/src/ethereum/forks/amsterdam/fork.py b/src/ethereum/forks/amsterdam/fork.py index 6fe9cf29de0..245161e2e1c 100644 --- a/src/ethereum/forks/amsterdam/fork.py +++ b/src/ethereum/forks/amsterdam/fork.py @@ -61,6 +61,8 @@ ) from .fork_types import Authorization, BlockAccessIndex, VersionedHash from .requests import ( + BUILDER_DEPOSIT_REQUEST_TYPE, + BUILDER_EXIT_REQUEST_TYPE, CONSOLIDATION_REQUEST_TYPE, DEPOSIT_REQUEST_TYPE, WITHDRAWAL_REQUEST_TYPE, @@ -133,6 +135,12 @@ CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS = hex_to_address( "0x0000BBdDc7CE488642fb579F8B00f3a590007251" ) +BUILDER_DEPOSIT_CONTRACT_ADDRESS = hex_to_address( + "0x0000884d2AA32eAa155F59A2f24eFa73D9008282" +) +BUILDER_EXIT_CONTRACT_ADDRESS = hex_to_address( + "0x000014574A74c805590AFF9499fc7A690f008282" +) HISTORY_STORAGE_ADDRESS = hex_to_address( "0x0000F90827F1C53a10cb7A02335B175320002935" ) @@ -956,6 +964,30 @@ def process_general_purpose_requests( + system_consolidation_tx_output.return_data ) + system_builder_deposit_tx_output = process_checked_system_transaction( + block_env=block_env, + target_address=BUILDER_DEPOSIT_CONTRACT_ADDRESS, + data=b"", + ) + + if len(system_builder_deposit_tx_output.return_data) > 0: + requests_from_execution.append( + BUILDER_DEPOSIT_REQUEST_TYPE + + system_builder_deposit_tx_output.return_data + ) + + system_builder_exit_tx_output = process_checked_system_transaction( + block_env=block_env, + target_address=BUILDER_EXIT_CONTRACT_ADDRESS, + data=b"", + ) + + if len(system_builder_exit_tx_output.return_data) > 0: + requests_from_execution.append( + BUILDER_EXIT_REQUEST_TYPE + + system_builder_exit_tx_output.return_data + ) + def process_transaction( block_env: vm.BlockEnvironment, diff --git a/src/ethereum/forks/amsterdam/requests.py b/src/ethereum/forks/amsterdam/requests.py index 675f8a888f9..fdfab016599 100644 --- a/src/ethereum/forks/amsterdam/requests.py +++ b/src/ethereum/forks/amsterdam/requests.py @@ -6,11 +6,12 @@ typed requests. Each request is a type byte (see [`DEPOSIT_REQUEST_TYPE`][dt], -[`WITHDRAWAL_REQUEST_TYPE`][wt], and [`CONSOLIDATION_REQUEST_TYPE`][ct]) +[`WITHDRAWAL_REQUEST_TYPE`][wt], [`CONSOLIDATION_REQUEST_TYPE`][ct], +[`BUILDER_DEPOSIT_REQUEST_TYPE`][bd], and [`BUILDER_EXIT_REQUEST_TYPE`][be]) followed by an opaque payload. Deposit requests are discovered by scanning -transaction receipts for logs emitted by the deposit contract; withdrawal -and consolidation requests are produced by the corresponding system -contracts during block processing. +transaction receipts for logs emitted by the deposit contract; withdrawal, +consolidation, and builder deposit/exit requests ([EIP-8282]) are produced by +the corresponding system contracts during block processing. See [`parse_deposit_requests`][pd] for how deposit logs become request data, [`compute_requests_hash`][crh] for how the list is hashed for inclusion in the @@ -19,10 +20,13 @@ [EIP-4895]: https://eips.ethereum.org/EIPS/eip-4895 [EIP-7685]: https://eips.ethereum.org/EIPS/eip-7685 +[EIP-8282]: https://eips.ethereum.org/EIPS/eip-8282 [rh]: ref:ethereum.forks.amsterdam.blocks.Header.requests_hash [dt]: ref:ethereum.forks.amsterdam.requests.DEPOSIT_REQUEST_TYPE [wt]: ref:ethereum.forks.amsterdam.requests.WITHDRAWAL_REQUEST_TYPE [ct]: ref:ethereum.forks.amsterdam.requests.CONSOLIDATION_REQUEST_TYPE +[bd]: ref:ethereum.forks.amsterdam.requests.BUILDER_DEPOSIT_REQUEST_TYPE +[be]: ref:ethereum.forks.amsterdam.requests.BUILDER_EXIT_REQUEST_TYPE [pd]: ref:ethereum.forks.amsterdam.requests.parse_deposit_requests [crh]: ref:ethereum.forks.amsterdam.requests.compute_requests_hash [pgpr]: ref:ethereum.forks.amsterdam.fork.process_general_purpose_requests @@ -86,6 +90,20 @@ [EIP-7251]: https://eips.ethereum.org/EIPS/eip-7251 """ +BUILDER_DEPOSIT_REQUEST_TYPE = b"\x03" +""" +Request type byte identifying a builder deposit request, per [EIP-8282]. + +[EIP-8282]: https://eips.ethereum.org/EIPS/eip-8282 +""" + +BUILDER_EXIT_REQUEST_TYPE = b"\x04" +""" +Request type byte identifying a builder exit request, per [EIP-8282]. + +[EIP-8282]: https://eips.ethereum.org/EIPS/eip-8282 +""" + DEPOSIT_EVENT_LENGTH = Uint(576) """ diff --git a/tests/amsterdam/eip8282_builder_execution_requests/__init__.py b/tests/amsterdam/eip8282_builder_execution_requests/__init__.py new file mode 100644 index 00000000000..c6c55e5cb78 --- /dev/null +++ b/tests/amsterdam/eip8282_builder_execution_requests/__init__.py @@ -0,0 +1,5 @@ +""" +Test cases for [EIP-8282: Builder Execution Requests][EIP-8282]. + +[EIP-8282]: https://eips.ethereum.org/EIPS/eip-8282 +""" diff --git a/tests/amsterdam/eip8282_builder_execution_requests/builder_deposit_deploy_tx.json b/tests/amsterdam/eip8282_builder_execution_requests/builder_deposit_deploy_tx.json new file mode 100644 index 00000000000..0381a5367f0 --- /dev/null +++ b/tests/amsterdam/eip8282_builder_execution_requests/builder_deposit_deploy_tx.json @@ -0,0 +1,15 @@ +{ + "type": "0x0", + "nonce": "0x0", + "to": null, + "gasLimit": "0x3d090", + "gasPrice": "0xe8d4a51000", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x0", + "input": "0x00", + "v": "0x1b", + "r": "0x539", + "s": "0x5feeb084551e4e03a3581e269bc2ea2f8d0008", + "protected": false +} diff --git a/tests/amsterdam/eip8282_builder_execution_requests/builder_exit_deploy_tx.json b/tests/amsterdam/eip8282_builder_execution_requests/builder_exit_deploy_tx.json new file mode 100644 index 00000000000..0381a5367f0 --- /dev/null +++ b/tests/amsterdam/eip8282_builder_execution_requests/builder_exit_deploy_tx.json @@ -0,0 +1,15 @@ +{ + "type": "0x0", + "nonce": "0x0", + "to": null, + "gasLimit": "0x3d090", + "gasPrice": "0xe8d4a51000", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x0", + "input": "0x00", + "v": "0x1b", + "r": "0x539", + "s": "0x5feeb084551e4e03a3581e269bc2ea2f8d0008", + "protected": false +} diff --git a/tests/amsterdam/eip8282_builder_execution_requests/conftest.py b/tests/amsterdam/eip8282_builder_execution_requests/conftest.py new file mode 100644 index 00000000000..f0f2b059f78 --- /dev/null +++ b/tests/amsterdam/eip8282_builder_execution_requests/conftest.py @@ -0,0 +1,8 @@ +"""Fixtures for the EIP-8282 builder execution request tests.""" + +from ...common.system_contract_request_fixtures import ( + blocks, # noqa: F401 + included_requests, # noqa: F401 + prepared_system_contract_interactions_per_block, # noqa: F401 + timestamp, # noqa: F401 +) diff --git a/tests/amsterdam/eip8282_builder_execution_requests/helpers.py b/tests/amsterdam/eip8282_builder_execution_requests/helpers.py new file mode 100644 index 00000000000..55012984e4d --- /dev/null +++ b/tests/amsterdam/eip8282_builder_execution_requests/helpers.py @@ -0,0 +1,115 @@ +"""Helpers for the EIP-8282 builder execution request tests.""" + +from typing import ClassVar, Self + +from execution_testing import Address, FeeSystemContractRequest +from execution_testing import ( + BuilderDepositRequest as BuilderDepositRequestBase, +) +from execution_testing import ( + BuilderExitRequest as BuilderExitRequestBase, +) + +from .spec import Spec + + +class BuilderDepositRequest( + BuilderDepositRequestBase, FeeSystemContractRequest +): + """ + Builder deposit request used in a test. + + Serves both a builder's first deposit and stake top-ups. The request pays + the shared EIP-1559-style fee on top of the staked `amount`, so its call + value is `fee + amount * 1 gwei`. + """ + + interaction_contract_address: ClassVar[Address] = Address( + Spec.BUILDER_DEPOSIT_CONTRACT_ADDRESS + ) + min_fee: ClassVar[int] = Spec.MIN_REQUEST_FEE + update_fraction: ClassVar[int] = Spec.REQUEST_FEE_UPDATE_FRACTION + target_per_block: ClassVar[int] = Spec.TARGET_DEPOSIT_REQUESTS_PER_BLOCK + max_per_block: ClassVar[int] = Spec.MAX_DEPOSIT_REQUESTS_PER_BLOCK + + extra_wei: int = 0 + """ + Extra wei added to (or, if negative, subtracted from) the call value, used + to test the predeploy's `value >= fee + amount * 1 gwei` check. + """ + + @property + def value(self) -> int: + """ + Return the value of the call, equal to the request fee plus the staked + amount in wei (adjusted by `extra_wei`). + """ + return self.fee + self.amount * 10**9 + self.extra_wei + + @property + def calldata(self) -> bytes: + """ + Return the 184-byte input calldata: `pubkey ++ withdrawal_credentials + ++ amount (big-endian) ++ signature`. + """ + return self.calldata_modifier( + bytes(self.pubkey) + + bytes(self.withdrawal_credentials) + + self.amount.to_bytes(8, "big") + + bytes(self.signature) + ) + + def with_source_address( + self, source_address: Address + ) -> "BuilderDepositRequest": + """Return a copy; deposit records carry no source address.""" + del source_address + return self.copy() + + @classmethod + def from_index(cls, index: int, fee: int | None = None) -> Self: + """Build a builder deposit request from a sequential index.""" + if fee is None: + fee = cls.get_fee(0) + return cls( + pubkey=index * 3, + withdrawal_credentials=(index * 3) + 1, + amount=Spec.BUILDER_MIN_DEPOSIT // 10**9, + signature=(index * 3) + 2, + fee=fee, + ) + + +class BuilderExitRequest(BuilderExitRequestBase, FeeSystemContractRequest): + """ + Builder exit request used in a test. + + Authorized by the caller's address (recorded as `source_address`); it + stakes no value and only pays the shared request fee. + """ + + interaction_contract_address: ClassVar[Address] = Address( + Spec.BUILDER_EXIT_CONTRACT_ADDRESS + ) + min_fee: ClassVar[int] = Spec.MIN_REQUEST_FEE + update_fraction: ClassVar[int] = Spec.REQUEST_FEE_UPDATE_FRACTION + target_per_block: ClassVar[int] = Spec.TARGET_EXIT_REQUESTS_PER_BLOCK + max_per_block: ClassVar[int] = Spec.MAX_EXIT_REQUESTS_PER_BLOCK + + @property + def calldata(self) -> bytes: + """Return the 48-byte input calldata: the builder `pubkey`.""" + return self.calldata_modifier(bytes(self.pubkey)) + + def with_source_address( + self, source_address: Address + ) -> "BuilderExitRequest": + """Return a copy with the source address set.""" + return self.copy(source_address=source_address) + + @classmethod + def from_index(cls, index: int, fee: int | None = None) -> Self: + """Build a builder exit request from a sequential index.""" + if fee is None: + fee = cls.get_fee(0) + return cls(pubkey=index, fee=fee) diff --git a/tests/amsterdam/eip8282_builder_execution_requests/spec.py b/tests/amsterdam/eip8282_builder_execution_requests/spec.py new file mode 100644 index 00000000000..5e9f6e60da1 --- /dev/null +++ b/tests/amsterdam/eip8282_builder_execution_requests/spec.py @@ -0,0 +1,58 @@ +""" +Reference spec and constants for [EIP-8282: Builder Execution Requests][8282]. + +[8282]: https://eips.ethereum.org/EIPS/eip-8282 +""" + +from dataclasses import dataclass + + +@dataclass(frozen=True) +class ReferenceSpec: + """Reference specification.""" + + git_path: str + version: str + + +# EIP-8282 is a Draft; its addresses, request-type bytes, and predeploy +# bytecode are placeholders pending the EIP's final, audit-frozen values. +ref_spec_8282 = ReferenceSpec( + git_path="EIPS/eip-8282.md", + version="0000000000000000000000000000000000000000", +) + + +class Spec: + """ + Constants and parameters from EIP-8282. Addresses are the + glamsterdam-devnet-6 values; request-type bytes remain placeholders + pending the EIP's final allocation. + """ + + BUILDER_DEPOSIT_CONTRACT_ADDRESS = ( + 0x0000884D2AA32EAA155F59A2F24EFA73D9008282 + ) + BUILDER_EXIT_CONTRACT_ADDRESS = 0x000014574A74C805590AFF9499FC7A690F008282 + + BUILDER_DEPOSIT_REQUEST_TYPE = 0x03 + BUILDER_EXIT_REQUEST_TYPE = 0x04 + + SYSTEM_ADDRESS = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE + SYSTEM_CALL_GAS_LIMIT = 30_000_000 + + # Shared request-bus parameters (identical to EIP-7002 / EIP-7251). + MAX_DEPOSIT_REQUESTS_PER_BLOCK = 256 + TARGET_DEPOSIT_REQUESTS_PER_BLOCK = 32 + MAX_EXIT_REQUESTS_PER_BLOCK = 16 + TARGET_EXIT_REQUESTS_PER_BLOCK = 2 + MIN_REQUEST_FEE = 1 + REQUEST_FEE_UPDATE_FRACTION = 17 + EXCESS_INHIBITOR = 2**256 - 1 + + # Minimum credited stake for a builder deposit, in wei (1 ETH). + BUILDER_MIN_DEPOSIT = 1_000_000_000_000_000_000 + + # Calldata input sizes accepted by each predeploy. + DEPOSIT_REQUEST_INPUT_BYTES = 184 + EXIT_REQUEST_INPUT_BYTES = 48 diff --git a/tests/amsterdam/eip8282_builder_execution_requests/test_builder_deposits.py b/tests/amsterdam/eip8282_builder_execution_requests/test_builder_deposits.py new file mode 100644 index 00000000000..2014757318a --- /dev/null +++ b/tests/amsterdam/eip8282_builder_execution_requests/test_builder_deposits.py @@ -0,0 +1,259 @@ +""" +Builder deposit request tests for +[EIP-8282: Builder Execution Requests](https://eips.ethereum.org/EIPS/eip-8282). +""" + +from typing import List + +import pytest +from execution_testing import ( + Alloc, + Block, + BlockchainTestFiller, + SystemContractInteractionContract, + SystemContractInteractionTransaction, +) + +from .helpers import BuilderDepositRequest +from .spec import Spec, ref_spec_8282 + +REFERENCE_SPEC_GIT_PATH = ref_spec_8282.git_path +REFERENCE_SPEC_VERSION = ref_spec_8282.version + +pytestmark = pytest.mark.valid_from("Amsterdam") + +MIN_DEPOSIT_GWEI = Spec.BUILDER_MIN_DEPOSIT // 10**9 + + +@pytest.mark.parametrize( + "system_contract_interactions_per_block", + [ + pytest.param( + [ + [ + SystemContractInteractionTransaction( + requests=[ + BuilderDepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=MIN_DEPOSIT_GWEI, + signature=0x03, + ) + ], + ), + ], + ], + id="single_block_single_builder_deposit_from_eoa", + ), + pytest.param( + [ + [ + SystemContractInteractionContract( + requests=[ + BuilderDepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=MIN_DEPOSIT_GWEI, + signature=0x03, + ) + ], + ), + ], + ], + id="single_block_single_builder_deposit_from_contract", + ), + pytest.param( + [ + [ + SystemContractInteractionTransaction( + requests=[ + BuilderDepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + # A top-up of more than the minimum stake. + amount=32 * MIN_DEPOSIT_GWEI, + signature=0x03, + ) + ], + ), + ], + ], + id="single_block_single_builder_deposit_above_minimum", + ), + pytest.param( + [ + [ + SystemContractInteractionTransaction( + requests=[ + BuilderDepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=MIN_DEPOSIT_GWEI, + signature=0x03, + ), + BuilderDepositRequest( + pubkey=0x04, + withdrawal_credentials=0x05, + amount=MIN_DEPOSIT_GWEI, + signature=0x06, + ), + ], + ), + ], + ], + id="single_block_multiple_builder_deposits_from_same_eoa", + ), + pytest.param( + [ + [ + SystemContractInteractionTransaction( + requests=[ + BuilderDepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=MIN_DEPOSIT_GWEI, + signature=0x03, + ) + ], + ), + SystemContractInteractionTransaction( + requests=[ + BuilderDepositRequest( + pubkey=0x04, + withdrawal_credentials=0x05, + amount=MIN_DEPOSIT_GWEI, + signature=0x06, + ) + ], + ), + ], + ], + id="single_block_multiple_builder_deposits_from_different_eoa", + ), + pytest.param( + [ + [ + SystemContractInteractionContract( + requests=[ + BuilderDepositRequest( + pubkey=i + 1, + withdrawal_credentials=0x02, + amount=MIN_DEPOSIT_GWEI, + signature=0x03, + ) + for i in range(Spec.MAX_DEPOSIT_REQUESTS_PER_BLOCK) + ], + ), + ], + ], + id="single_block_max_builder_deposits_from_contract", + ), + pytest.param( + [ + [ + SystemContractInteractionContract( + requests=[ + BuilderDepositRequest( + pubkey=i + 1, + withdrawal_credentials=0x02, + amount=MIN_DEPOSIT_GWEI, + signature=0x03, + ) + for i in range( + Spec.MAX_DEPOSIT_REQUESTS_PER_BLOCK + 1 + ) + ], + ), + ], + ], + id="single_block_carry_over_builder_deposits_from_contract", + ), + pytest.param( + [ + [ + SystemContractInteractionTransaction( + requests=[ + BuilderDepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + # One gwei below the minimum stake. + amount=MIN_DEPOSIT_GWEI - 1, + signature=0x03, + valid=False, + ) + ], + ), + ], + ], + id="single_block_single_builder_deposit_below_minimum", + ), + pytest.param( + [ + [ + SystemContractInteractionTransaction( + requests=[ + BuilderDepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=MIN_DEPOSIT_GWEI, + signature=0x03, + # One wei short of `fee + amount * 1 gwei`. + extra_wei=-1, + valid=False, + ) + ], + ), + ], + ], + id="single_block_single_builder_deposit_insufficient_value", + ), + pytest.param( + [ + [ + SystemContractInteractionTransaction( + requests=[ + BuilderDepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=MIN_DEPOSIT_GWEI, + signature=0x03, + calldata_modifier=lambda x: x[:-1], + valid=False, + ) + ], + ), + ], + ], + id="single_block_single_builder_deposit_input_too_short", + ), + pytest.param( + [ + [ + SystemContractInteractionTransaction( + requests=[ + BuilderDepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=MIN_DEPOSIT_GWEI, + signature=0x03, + calldata_modifier=lambda x: x + b"\x00", + valid=False, + ) + ], + ), + ], + ], + id="single_block_single_builder_deposit_input_too_long", + ), + ], +) +def test_builder_deposit_requests( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + blocks: List[Block], +) -> None: + """ + Test submitting valid builder deposit requests to the builder deposit + predeploy and verifying they are dequeued into the block's requests. + """ + blockchain_test(pre=pre, post={}, blocks=blocks) diff --git a/tests/amsterdam/eip8282_builder_execution_requests/test_builder_exits.py b/tests/amsterdam/eip8282_builder_execution_requests/test_builder_exits.py new file mode 100644 index 00000000000..3e5ca7e739a --- /dev/null +++ b/tests/amsterdam/eip8282_builder_execution_requests/test_builder_exits.py @@ -0,0 +1,164 @@ +""" +Builder exit request tests for +[EIP-8282: Builder Execution Requests](https://eips.ethereum.org/EIPS/eip-8282). +""" + +from typing import List + +import pytest +from execution_testing import ( + Alloc, + Block, + BlockchainTestFiller, + SystemContractInteractionContract, + SystemContractInteractionTransaction, +) + +from .helpers import BuilderExitRequest +from .spec import Spec, ref_spec_8282 + +REFERENCE_SPEC_GIT_PATH = ref_spec_8282.git_path +REFERENCE_SPEC_VERSION = ref_spec_8282.version + +pytestmark = pytest.mark.valid_from("Amsterdam") + + +@pytest.mark.parametrize( + "system_contract_interactions_per_block", + [ + pytest.param( + [ + [ + SystemContractInteractionTransaction( + requests=[BuilderExitRequest(pubkey=0x01)], + ), + ], + ], + id="single_block_single_builder_exit_from_eoa", + ), + pytest.param( + [ + [ + SystemContractInteractionContract( + requests=[BuilderExitRequest(pubkey=0x01)], + ), + ], + ], + id="single_block_single_builder_exit_from_contract", + ), + pytest.param( + [ + [ + SystemContractInteractionTransaction( + requests=[ + BuilderExitRequest(pubkey=0x01), + BuilderExitRequest(pubkey=0x02), + ], + ), + ], + ], + id="single_block_multiple_builder_exits_from_same_eoa", + ), + pytest.param( + [ + [ + SystemContractInteractionTransaction( + requests=[BuilderExitRequest(pubkey=0x01)], + ), + SystemContractInteractionTransaction( + requests=[BuilderExitRequest(pubkey=0x02)], + ), + ], + ], + id="single_block_multiple_builder_exits_from_different_eoa", + ), + pytest.param( + [ + [ + SystemContractInteractionContract( + requests=[ + BuilderExitRequest(pubkey=i + 1) + for i in range(Spec.MAX_EXIT_REQUESTS_PER_BLOCK) + ], + ), + ], + ], + id="single_block_max_builder_exits_from_contract", + ), + pytest.param( + [ + [ + SystemContractInteractionContract( + requests=[ + BuilderExitRequest(pubkey=i + 1) + for i in range( + Spec.MAX_EXIT_REQUESTS_PER_BLOCK * 2 + 1 + ) + ], + ), + ], + ], + id="single_block_carry_over_builder_exits_from_contract", + ), + pytest.param( + [ + [ + SystemContractInteractionTransaction( + requests=[ + BuilderExitRequest( + pubkey=0x01, + # No fee paid covers the call value. + fee=0, + valid=False, + ) + ], + ), + ], + ], + id="single_block_single_builder_exit_insufficient_fee", + ), + pytest.param( + [ + [ + SystemContractInteractionTransaction( + requests=[ + BuilderExitRequest( + pubkey=0x01, + calldata_modifier=lambda x: x[:-1], + valid=False, + ) + ], + ), + ], + ], + id="single_block_single_builder_exit_input_too_short", + ), + pytest.param( + [ + [ + SystemContractInteractionTransaction( + requests=[ + BuilderExitRequest( + pubkey=0x01, + calldata_modifier=lambda x: x + b"\x00", + valid=False, + ) + ], + ), + ], + ], + id="single_block_single_builder_exit_input_too_long", + ), + ], +) +def test_builder_exit_requests( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + blocks: List[Block], +) -> None: + """ + Test submitting valid builder exit requests to the builder exit predeploy + and verifying they are dequeued into the block's requests, with + `source_address` set to the caller. + """ + blockchain_test(pre=pre, post={}, blocks=blocks) diff --git a/tests/amsterdam/eip8282_builder_execution_requests/test_builder_requests_during_fork.py b/tests/amsterdam/eip8282_builder_execution_requests/test_builder_requests_during_fork.py new file mode 100644 index 00000000000..5b6d26aa14d --- /dev/null +++ b/tests/amsterdam/eip8282_builder_execution_requests/test_builder_requests_during_fork.py @@ -0,0 +1,124 @@ +""" +Tests [EIP-8282: Builder Execution Requests](https://eips.ethereum.org/EIPS/eip-8282). +""" # noqa: E501 + +from os.path import realpath +from pathlib import Path +from typing import List + +import pytest +from execution_testing import ( + Account, + Alloc, + Block, + BlockchainTestFiller, + SystemContractInteractionTransaction, + Transaction, +) + +from .helpers import BuilderExitRequest +from .spec import Spec, ref_spec_8282 + +REFERENCE_SPEC_GIT_PATH = ref_spec_8282.git_path +REFERENCE_SPEC_VERSION = ref_spec_8282.version + +pytestmark = [ + pytest.mark.skip( + reason="EIP-8282 draft: builder predeploy deploy transactions are not " + "yet defined (placeholder devnet-6 genesis addresses)." + ), + pytest.mark.valid_at_transition_to("Amsterdam"), +] + +BLOCKS_BEFORE_FORK = 2 + + +@pytest.mark.parametrize( + "system_contract_interactions_per_block", + [ + pytest.param( + [ + [], # No builder exit requests, but we deploy the contract + [ + SystemContractInteractionTransaction( + requests=[ + BuilderExitRequest( + pubkey=0x01, + fee=BuilderExitRequest.get_fee(10), + # Pre-fork builder exit request + valid=False, + ) + ], + ), + ], + [ + SystemContractInteractionTransaction( + requests=[ + BuilderExitRequest( + pubkey=0x02, + fee=BuilderExitRequest.get_fee(10), + # First post-fork builder exit request, will + # not be included because the inhibitor is + # cleared at the end of the block + valid=False, + ) + ], + ), + ], + [ + SystemContractInteractionTransaction( + requests=[ + BuilderExitRequest( + pubkey=0x03, + # First builder exit request that is valid + valid=True, + ) + ], + ), + ], + ], + id="one_valid_request_second_block_after_fork", + ), + ], +) +@pytest.mark.parametrize("timestamp", [15_000 - BLOCKS_BEFORE_FORK], ids=[""]) +@pytest.mark.pre_alloc_mutable +def test_builder_requests_during_fork( + blockchain_test: BlockchainTestFiller, + blocks: List[Block], + pre: Alloc, +) -> None: + """ + Test making a builder exit request to the beacon chain at the time of the + fork. + """ + # We need to delete the deployed contract that comes by default in the pre + # state. + pre[Spec.BUILDER_EXIT_CONTRACT_ADDRESS] = Account( + balance=0, + code=bytes(), + nonce=0, + storage={}, + ) + + with open( + Path(realpath(__file__)).parent / "builder_exit_deploy_tx.json", + mode="r", + ) as f: + deploy_tx = Transaction.model_validate_json( + f.read() + ).with_signature_and_sender() + + deployer_address = deploy_tx.sender + assert deployer_address is not None + + tx_gas_price = deploy_tx.gas_price + assert tx_gas_price is not None + deployer_required_balance = deploy_tx.gas_limit * tx_gas_price + + pre.fund_address(deployer_address, deployer_required_balance) + + # Append the deployment transaction to the first block + blocks[0].txs.append(deploy_tx) + + blockchain_test(pre=pre, post={}, blocks=blocks) diff --git a/tests/amsterdam/eip8282_builder_execution_requests/test_builder_requests_out_of_gas.py b/tests/amsterdam/eip8282_builder_execution_requests/test_builder_requests_out_of_gas.py new file mode 100644 index 00000000000..a5e51d3102c --- /dev/null +++ b/tests/amsterdam/eip8282_builder_execution_requests/test_builder_requests_out_of_gas.py @@ -0,0 +1,108 @@ +""" +Out-of-gas builder request tests. + +Tests that builder deposit and exit requests whose triggering call runs out of +gas are not included in the block, for +[EIP-8282: Builder Execution Requests](https://eips.ethereum.org/EIPS/eip-8282). + +""" + +from typing import List + +import pytest +from execution_testing import ( + Alloc, + Block, + BlockchainTestFiller, + SystemContractInteractionMeasuredOutOfGasContract, +) + +from .helpers import BuilderDepositRequest, BuilderExitRequest +from .spec import Spec, ref_spec_8282 + +REFERENCE_SPEC_GIT_PATH = ref_spec_8282.git_path +REFERENCE_SPEC_VERSION = ref_spec_8282.version + +pytestmark = pytest.mark.valid_from("Amsterdam") + +MIN_DEPOSIT_GWEI = Spec.BUILDER_MIN_DEPOSIT // 10**9 + + +@pytest.mark.parametrize( + "system_contract_interactions_per_block", + [ + pytest.param( + [ + [ + SystemContractInteractionMeasuredOutOfGasContract( + requests=[ + BuilderDepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=MIN_DEPOSIT_GWEI, + signature=0x03, + fee=BuilderDepositRequest.get_fee(0), + ), + BuilderDepositRequest( + pubkey=0x04, + withdrawal_credentials=0x05, + amount=MIN_DEPOSIT_GWEI, + signature=0x06, + fee=BuilderDepositRequest.get_fee(0), + ), + BuilderDepositRequest( + pubkey=0x07, + withdrawal_credentials=0x08, + amount=MIN_DEPOSIT_GWEI, + signature=0x09, + fee=BuilderDepositRequest.get_fee(0), + # Starved of gas by the relay contract. + valid=False, + ), + ], + ), + ], + ], + id="single_block_builder_deposit_out_of_gas", + ), + pytest.param( + [ + [ + SystemContractInteractionMeasuredOutOfGasContract( + requests=[ + BuilderExitRequest( + pubkey=0x01, + fee=BuilderExitRequest.get_fee(0), + ), + BuilderExitRequest( + pubkey=0x02, + fee=BuilderExitRequest.get_fee(0), + ), + BuilderExitRequest( + pubkey=0x03, + fee=BuilderExitRequest.get_fee(0), + # Starved of gas by the relay contract. + valid=False, + ), + ], + ), + ], + ], + id="single_block_builder_exit_out_of_gas", + ), + ], +) +def test_builder_request_out_of_gas( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + blocks: List[Block], +) -> None: + """ + Test that a builder request whose triggering call runs out of gas is not + included, while the other requests in the block are. + + The relay contract self-measures the required gas and forwards one gas less + than needed to the invalid request, so the out-of-gas holds across forks + without any hard-coded gas value. + """ + blockchain_test(pre=pre, post={}, blocks=blocks) diff --git a/tests/amsterdam/eip8282_builder_execution_requests/test_contract_deployment.py b/tests/amsterdam/eip8282_builder_execution_requests/test_contract_deployment.py new file mode 100644 index 00000000000..a840d6dc6a8 --- /dev/null +++ b/tests/amsterdam/eip8282_builder_execution_requests/test_contract_deployment.py @@ -0,0 +1,102 @@ +""" +Tests [EIP-8282: Builder Execution Requests](https://eips.ethereum.org/EIPS/eip-8282). +""" # noqa: E501 + +from os.path import realpath +from pathlib import Path +from typing import Any, Generator + +import pytest +from execution_testing import ( + Address, + Alloc, + Block, + Requests, + Transaction, + TransitionFork, + generate_system_contract_deploy_test, +) +from execution_testing.forks import Amsterdam + +from .helpers import BuilderDepositRequest, BuilderExitRequest +from .spec import Spec, ref_spec_8282 + +REFERENCE_SPEC_GIT_PATH = ref_spec_8282.git_path +REFERENCE_SPEC_VERSION = ref_spec_8282.version + +pytestmark = pytest.mark.skip( + reason="EIP-8282 draft: builder predeploy deploy transactions are not yet " + "defined (placeholder devnet-6 genesis addresses)." +) + +MIN_DEPOSIT_GWEI = Spec.BUILDER_MIN_DEPOSIT // 10**9 + + +@pytest.mark.eels_base_coverage +@generate_system_contract_deploy_test( + fork=Amsterdam, + tx_json_path=Path(realpath(__file__)).parent + / "builder_deposit_deploy_tx.json", + expected_deploy_address=Address(Spec.BUILDER_DEPOSIT_CONTRACT_ADDRESS), + fail_on_empty_code=True, +) +def test_builder_deposit_contract_deployment( + *, + fork: TransitionFork, + pre: Alloc, + **kwargs: Any, +) -> Generator[Block, None, None]: + """Verify calling the builder deposit contract after deployment.""" + sender = pre.fund_eoa() + deposit_request = BuilderDepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=MIN_DEPOSIT_GWEI, + signature=0x03, + ) + + test_transaction = Transaction( + data=deposit_request.calldata, + to=Spec.BUILDER_DEPOSIT_CONTRACT_ADDRESS, + sender=sender, + value=deposit_request.value, + ) + + yield Block( + txs=[test_transaction], + requests_hash=Requests(deposit_request), + ) + + +@pytest.mark.eels_base_coverage +@generate_system_contract_deploy_test( + fork=Amsterdam, + tx_json_path=Path(realpath(__file__)).parent + / "builder_exit_deploy_tx.json", + expected_deploy_address=Address(Spec.BUILDER_EXIT_CONTRACT_ADDRESS), + fail_on_empty_code=True, +) +def test_builder_exit_contract_deployment( + *, + fork: TransitionFork, + pre: Alloc, + **kwargs: Any, +) -> Generator[Block, None, None]: + """Verify calling the builder exit contract after deployment.""" + sender = pre.fund_eoa() + exit_request = BuilderExitRequest( + pubkey=0x01, + source_address=sender, + ) + + test_transaction = Transaction( + data=exit_request.calldata, + to=Spec.BUILDER_EXIT_CONTRACT_ADDRESS, + sender=sender, + value=exit_request.value, + ) + + yield Block( + txs=[test_transaction], + requests_hash=Requests(exit_request), + ) diff --git a/tests/amsterdam/eip8282_builder_execution_requests/test_eip_mainnet.py b/tests/amsterdam/eip8282_builder_execution_requests/test_eip_mainnet.py new file mode 100644 index 00000000000..3ecaa71dcb4 --- /dev/null +++ b/tests/amsterdam/eip8282_builder_execution_requests/test_eip_mainnet.py @@ -0,0 +1,82 @@ +""" +abstract: Crafted tests for mainnet of [EIP-8282: Builder Execution Requests](https://eips.ethereum.org/EIPS/eip-8282). +""" # noqa: E501 + +from typing import List + +import pytest +from execution_testing import ( + Alloc, + Block, + BlockchainTestFiller, + SystemContractInteractionTransaction, +) + +from .helpers import BuilderDepositRequest, BuilderExitRequest +from .spec import Spec, ref_spec_8282 + +REFERENCE_SPEC_GIT_PATH = ref_spec_8282.git_path +REFERENCE_SPEC_VERSION = ref_spec_8282.version + +pytestmark = [pytest.mark.valid_at("Amsterdam"), pytest.mark.mainnet] + +MIN_DEPOSIT_GWEI = Spec.BUILDER_MIN_DEPOSIT // 10**9 + + +@pytest.mark.parametrize( + "system_contract_interactions_per_block", + [ + pytest.param( + [ + [ + SystemContractInteractionTransaction( + requests=[ + BuilderDepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=MIN_DEPOSIT_GWEI, + signature=0x03, + ) + ], + ), + ], + ], + id="single_builder_deposit_request", + ), + pytest.param( + [ + [ + SystemContractInteractionTransaction( + requests=[BuilderExitRequest(pubkey=0x01)], + ), + ], + ], + id="single_builder_exit_request", + ), + pytest.param( + [ + [ + SystemContractInteractionTransaction( + requests=[ + BuilderDepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=MIN_DEPOSIT_GWEI, + signature=0x03, + ), + BuilderExitRequest(pubkey=0x04), + ], + ), + ], + ], + id="single_builder_deposit_and_exit_request", + ), + ], +) +def test_eip_8282( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + blocks: List[Block], +) -> None: + """Test making builder deposit and exit requests.""" + blockchain_test(pre=pre, post={}, blocks=blocks) diff --git a/tests/amsterdam/eip8282_builder_execution_requests/test_modified_builder_contract.py b/tests/amsterdam/eip8282_builder_execution_requests/test_modified_builder_contract.py new file mode 100644 index 00000000000..ead0b94e08b --- /dev/null +++ b/tests/amsterdam/eip8282_builder_execution_requests/test_modified_builder_contract.py @@ -0,0 +1,226 @@ +""" +Tests [EIP-8282: Builder Execution Requests](https://eips.ethereum.org/EIPS/eip-8282). +""" + +from typing import List, Sequence + +import pytest +from execution_testing import ( + Account, + Address, + Alloc, + Block, + BlockchainTestFiller, + Bytecode, + Header, + Op, + Requests, + SystemContractRequest, + generate_system_contract_error_test, +) +from execution_testing import Macros as Om + +from .helpers import BuilderDepositRequest, BuilderExitRequest +from .spec import Spec, ref_spec_8282 + +REFERENCE_SPEC_GIT_PATH: str = ref_spec_8282.git_path +REFERENCE_SPEC_VERSION: str = ref_spec_8282.version + +pytestmark: List[pytest.MarkDecorator] = [ + pytest.mark.valid_from("Amsterdam"), + pytest.mark.pre_alloc_mutable(), +] + +MIN_DEPOSIT_GWEI = Spec.BUILDER_MIN_DEPOSIT // 10**9 + + +def builder_deposit_list_with_custom_fee( # noqa: D103 + n: int, +) -> List[BuilderDepositRequest]: + return [ + BuilderDepositRequest( + pubkey=i + 1, + withdrawal_credentials=0x02, + amount=MIN_DEPOSIT_GWEI, + signature=0x03, + fee=BuilderDepositRequest.get_fee(0), + ) + for i in range(n) + ] + + +def builder_exit_list_with_custom_fee(n: int) -> List[BuilderExitRequest]: # noqa: D103 + return [ + BuilderExitRequest( + pubkey=i + 1, + fee=BuilderExitRequest.get_fee(0), + ) + for i in range(n) + ] + + +def run_modified_requests_test( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + *, + predeploy_address: int, + requests_list: Sequence[SystemContractRequest], +) -> None: + """ + Replace a request predeploy with code that returns the given request + records verbatim, then verify the transition tool dequeues exactly those + records into the block, even when there are more than the per-block cap. + """ + modified_code: Bytecode = Bytecode() + memory_offset: int = 0 + + for request in requests_list: + record = bytes(request) + # Store records contiguously from offset 0 so the returned data is + # exactly the concatenated records (no gap, no trailing padding). + modified_code += Om.MSTORE(record, memory_offset) + memory_offset += len(record) + + modified_code += Op.RETURN(0, memory_offset) + + pre[predeploy_address] = Account(code=modified_code, nonce=1) + + blockchain_test( + pre=pre, + blocks=[ + Block( + header_verify=Header(requests_hash=Requests(*requests_list)) + ), + ], + post={}, + ) + + +@pytest.mark.parametrize( + "requests_list", + [ + pytest.param([], id="empty_request_list"), + pytest.param( + builder_deposit_list_with_custom_fee(1), + id="1_builder_deposit_request", + ), + pytest.param( + builder_deposit_list_with_custom_fee( + Spec.MAX_DEPOSIT_REQUESTS_PER_BLOCK - 1 + ), + id="max_minus_1_builder_deposit_requests", + ), + pytest.param( + builder_deposit_list_with_custom_fee( + Spec.MAX_DEPOSIT_REQUESTS_PER_BLOCK + ), + id="max_builder_deposit_requests", + ), + pytest.param( + builder_deposit_list_with_custom_fee( + Spec.MAX_DEPOSIT_REQUESTS_PER_BLOCK + 1 + ), + id="max_plus_1_builder_deposit_requests", + ), + pytest.param( + builder_deposit_list_with_custom_fee( + Spec.MAX_DEPOSIT_REQUESTS_PER_BLOCK + 2 + ), + id="max_plus_2_builder_deposit_requests", + ), + ], +) +def test_extra_builder_deposits( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + requests_list: List[BuilderDepositRequest], +) -> None: + """ + Test how clients were to behave when more than the per-block maximum of + builder deposit requests would be returned by the predeploy. + """ + run_modified_requests_test( + blockchain_test, + pre, + predeploy_address=Spec.BUILDER_DEPOSIT_CONTRACT_ADDRESS, + requests_list=requests_list, + ) + + +@pytest.mark.parametrize( + "requests_list", + [ + pytest.param([], id="empty_request_list"), + pytest.param( + builder_exit_list_with_custom_fee(1), + id="1_builder_exit_request", + ), + pytest.param( + builder_exit_list_with_custom_fee( + Spec.MAX_EXIT_REQUESTS_PER_BLOCK - 1 + ), + id="max_minus_1_builder_exit_requests", + ), + pytest.param( + builder_exit_list_with_custom_fee( + Spec.MAX_EXIT_REQUESTS_PER_BLOCK + ), + id="max_builder_exit_requests", + ), + pytest.param( + builder_exit_list_with_custom_fee( + Spec.MAX_EXIT_REQUESTS_PER_BLOCK + 1 + ), + id="max_plus_1_builder_exit_requests", + ), + pytest.param( + builder_exit_list_with_custom_fee( + Spec.MAX_EXIT_REQUESTS_PER_BLOCK + 2 + ), + id="max_plus_2_builder_exit_requests", + ), + ], +) +def test_extra_builder_exits( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + requests_list: List[BuilderExitRequest], +) -> None: + """ + Test how clients were to behave when more than the per-block maximum of + builder exit requests would be returned by the predeploy. + """ + run_modified_requests_test( + blockchain_test, + pre, + predeploy_address=Spec.BUILDER_EXIT_CONTRACT_ADDRESS, + requests_list=requests_list, + ) + + +@pytest.mark.parametrize( + "system_contract", + [ + pytest.param( + Address(Spec.BUILDER_DEPOSIT_CONTRACT_ADDRESS), + id="builder_deposit_contract", + ), + pytest.param( + Address(Spec.BUILDER_EXIT_CONTRACT_ADDRESS), + id="builder_exit_contract", + ), + ], +) +@generate_system_contract_error_test( # type: ignore[arg-type] + max_gas_limit=Spec.SYSTEM_CALL_GAS_LIMIT, +) +@pytest.mark.eels_base_coverage +def test_system_contract_errors() -> None: + """ + Test system contract raising different errors when called by the system + account at the end of the block execution. + + To see the list of generated tests, please refer to the + `generate_system_contract_error_test` decorator definition. + """ + pass diff --git a/tests/prague/eip7685_general_purpose_el_requests/test_multi_type_requests.py b/tests/prague/eip7685_general_purpose_el_requests/test_multi_type_requests.py index 3761b818d80..73e83643f48 100644 --- a/tests/prague/eip7685_general_purpose_el_requests/test_multi_type_requests.py +++ b/tests/prague/eip7685_general_purpose_el_requests/test_multi_type_requests.py @@ -26,6 +26,10 @@ TestAddress, ) +from ...amsterdam.eip8282_builder_execution_requests.helpers import ( + BuilderDepositRequest, + BuilderExitRequest, +) from ..eip6110_deposits.helpers import DepositRequest from ..eip7002_el_triggerable_withdrawals.helpers import WithdrawalRequest from ..eip7251_consolidations.helpers import ConsolidationRequest @@ -44,6 +48,8 @@ DepositRequest, WithdrawalRequest, ConsolidationRequest, + BuilderDepositRequest, + BuilderExitRequest, ] REQUEST_TYPE_BY_ADDRESS = { rt.interaction_contract_address: rt for rt in REQUEST_TYPES @@ -151,7 +157,10 @@ def test_valid_multi_type_requests( EOAs and from relay contracts, including per-type maximums. """ blockchain_test( - genesis_environment=Environment(), + genesis_environment=Environment( + # Per-type maximums exceed the default block gas limit. + gas_limit=500_000_000 + ), pre=pre, post={}, blocks=blocks, From abcc82a47fd99c7a53ffd71898f3d5df33b3ea40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Thu, 2 Jul 2026 21:56:48 +0200 Subject: [PATCH 083/233] feat(tests): EIP-7928 records an absent system contract in the BAL (#3087) --- .../test_block_access_lists_eip2935.py | 31 +++++++++++++++++++ .../test_block_access_lists_eip4788.py | 31 +++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip2935.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip2935.py index 174fd5947e2..64c7d7225ee 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip2935.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip2935.py @@ -441,3 +441,34 @@ def test_bal_2935_invalid_calldata_size( blocks=[block_1, block_2], post=post_state, ) + + +@pytest.mark.pre_alloc_mutable() +def test_bal_2935_absent_contract( + pre: Alloc, + blockchain_test: BlockchainTestFiller, +) -> None: + """ + Ensure an undeployed history contract is still recorded in the BAL. + + Overriding the genesis contract with an empty account drops it from the + pre-state. The block-start system call reads the now-absent account + (recording it) and finds no code to run, so the address is in the BAL + with an empty AccountChanges. Unreachable on mainnet, + consensus-relevant on custom or test chains. + """ + pre[HISTORY_STORAGE_ADDRESS] = Account(code=b"", nonce=0, balance=0) + blockchain_test( + pre=pre, + blocks=[ + Block( + txs=[], + expected_block_access_list=BlockAccessListExpectation( + account_expectations={ + HISTORY_STORAGE_ADDRESS: BalAccountExpectation.empty(), + } + ), + ) + ], + post={HISTORY_STORAGE_ADDRESS: Account.NONEXISTENT}, + ) diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip4788.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip4788.py index 13d08f06e6d..6da222aea4b 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip4788.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip4788.py @@ -552,3 +552,34 @@ def test_bal_4788_selfdestruct_to_beacon_root( BEACON_ROOTS_ADDRESS: Account(balance=contract_balance), }, ) + + +@pytest.mark.pre_alloc_mutable() +def test_bal_4788_absent_contract( + pre: Alloc, + blockchain_test: BlockchainTestFiller, +) -> None: + """ + Ensure an undeployed beacon root contract is still recorded in the BAL. + + Overriding the genesis contract with an empty account drops it from the + pre-state. The block-start system call reads the now-absent account + (recording it) and finds no code to run, so the address is in the BAL + with an empty AccountChanges. Unreachable on mainnet, + consensus-relevant on custom or test chains. + """ + pre[BEACON_ROOTS_ADDRESS] = Account(code=b"", nonce=0, balance=0) + blockchain_test( + pre=pre, + blocks=[ + Block( + txs=[], + expected_block_access_list=BlockAccessListExpectation( + account_expectations={ + BEACON_ROOTS_ADDRESS: BalAccountExpectation.empty(), + } + ), + ) + ], + post={BEACON_ROOTS_ADDRESS: Account.NONEXISTENT}, + ) From c074f38fa5a2dcc1d7e079c28a08b2eeaac11447 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Thu, 2 Jul 2026 21:57:03 +0200 Subject: [PATCH 084/233] feat(tests): EIP-8037 auth refund funds state gas only, not regular (#3083) --- .../test_state_gas_set_code.py | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py index 4b4252fb03c..bf18b2ba4a0 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py @@ -1464,3 +1464,71 @@ def test_auth_sender_billing_after_failure( tx=tx, blockchain_test_header_verify=Header(gas_used=expected_gas_used), ) + + +@pytest.mark.parametrize( + "gas_delta", + [ + pytest.param(0, id="exact_fit"), + pytest.param(-1, id="one_short"), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_auth_refund_reservoir_cannot_fund_regular_gas( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + gas_delta: int, +) -> None: + """ + Verify the auth NEW_ACCOUNT refund funds state gas only, not regular. + + A set_code tx on a pre-existing authority refunds NEW_ACCOUNT to the + reservoir. The target's SSTORE-set pays its state charge from that + refund but its regular charge from gas_left: at exactly the SSTORE + regular cost the write lands, one gas short it runs out of gas. + """ + total_intrinsic = fork.transaction_intrinsic_cost_calculator()( + authorization_list_or_count=1, + ) + set_op = Op.SSTORE.with_metadata( + key_warm=False, original_value=0, current_value=0, new_value=1 + ) + storage = Storage() + target_code = set_op(storage.store_next(1), 1) + sstore_regular = target_code.regular_cost(fork) + + # In-cap so the reservoir's only state gas is the refunded NEW_ACCOUNT. + gas_limit = total_intrinsic + sstore_regular + gas_delta + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + assert gas_limit <= gas_limit_cap + + target = pre.deploy_contract(code=target_code) + authority = pre.fund_eoa() + tx = Transaction( + to=target, + gas_limit=gas_limit, + authorization_list=[ + AuthorizationTuple(address=target, nonce=0, signer=authority), + ], + sender=pre.fund_eoa(), + ) + fits = gas_delta >= 0 + intrinsic_state = fork.transaction_intrinsic_state_gas( + authorization_count=1, + ) + auth_refund = fork.gas_costs().REFUND_AUTH_PER_EXISTING_ACCOUNT + state_used = ( + intrinsic_state + - auth_refund + + (target_code.state_cost(fork) if fits else 0) + ) + state_test( + pre=pre, + post={target: Account(storage=storage if fits else {})}, + tx=tx, + blockchain_test_header_verify=Header( + gas_used=max(gas_limit - intrinsic_state, state_used), + ), + ) From 2dfaa3e9b17d74ea5c16f752326ff11b3a510bdc Mon Sep 17 00:00:00 2001 From: danceratopz Date: Fri, 3 Jul 2026 00:25:05 +0200 Subject: [PATCH 085/233] feat(spec-specs,test-tests): add EIP-2780 and EIP-8038 (#3052) * feat(spec-specs, tests): add EIP-8038 state-access gas cost update (#2972) Co-authored-by: Sam Wilson <57262657+SamWilsn@users.noreply.github.com> Co-authored-by: Mario Vega <11726710+marioevz@users.noreply.github.com> * feat(spec-specs, tests): Implement EIP-2780 (#3017) * chore(tests): fix failing ported static slow tests for EIP-8038 (#3019) Co-authored-by: marioevz * test(amsterdam): add EIP-8038 state-access gas cost tests (#3033) Co-authored-by: danceratopz Co-authored-by: marioevz * fix(tests): account for EIP-8246 in EIP-8038 selfdestruct gas test (#3044) EIP-8246 removes the `SELFDESTRUCT` balance burn, which changes the same-transaction self-destruct-to-self outcome. Gate the affected expectations in `test_selfdestruct_gas.py` on `fork.is_eip_enabled(8246)` so the test holds on forks with and without EIP-8246: - Pre-EIP-8246: The originator balance is burnt (a `Burn` log) and the same-transaction-created account is deleted. - EIP-8246 onwards: The burn is removed, so the self-send is a no-op; the balance stays in the (emptied) originator and no log is emitted. `burn_log` is imported lazily in the pre-EIP-8246 branch because EIP-8246 deletes the helper from the EIP-7708 spec. The charged gas is unchanged, so `cumulative_gas_used` is asserted identically on both sides. * fix(spec-specs): EIP-2780 charge `NEW_ACCOUNT` for value transfer to zero balance precompile (#3048) * fix(amsterdam): charge NEW_ACCOUNT for value transfer to empty precompile EIP-2780 charges the NEW_ACCOUNT state cost when a transaction transfers value to a recipient that is empty per EIP-161. The top-frame charge previously carved out precompile recipients, but neither EIP-2780 nor EIP-161 authorizes that exemption: - EIP-2780 does not mention precompiles; its rule keys solely on "empty per EIP-161 and tx.value > 0". - EIP-161 defines empty structurally (no code, zero nonce, zero balance) with no precompile exception, so an unfunded precompile is empty and is created by the value transfer like any other account. Remove the `recipient_is_precompile` carve-out from the top-frame charge so an empty precompile receiving value pays NEW_ACCOUNT, drop the matching special-case from the testing framework's `transaction_top_frame_state_gas`, and rewrite `test_value_move_to_precompiles` to assert the charge fires for the not-funded precompile while a pre-funded (alive) precompile remains exempt by virtue of being non-empty. Co-authored-by: danceratopz * feat(tests): add more EIP-2780 tests (#3055) Co-authored-by: danceratopz Co-authored-by: Louis Tsai <72684086+LouisTsai-Csie@users.noreply.github.com> * feat(specs): update EIP-8037 impl for ethereum/EIPs#11715 (#3021) Co-authored-by: spencer-tb * fix(tests): align Amsterdam (gas) tests with EIP-2780/8037/8038 (#3088) Co-authored-by: marioevz * Apply suggestions from code review (packates/testing) Co-authored-by: Mario Vega * Apply suggestions from code review (specs) Co-authored-by: Mario Vega * Apply suggestions from code review (tests) Co-authored-by: Mario Vega * fix(specs): Lint * fix(test-tools): Lint --------- Co-authored-by: Sam Wilson <57262657+SamWilsn@users.noreply.github.com> Co-authored-by: Mario Vega <11726710+marioevz@users.noreply.github.com> Co-authored-by: Guruprasad Kamath Co-authored-by: spencer Co-authored-by: marioevz Co-authored-by: CPerezz <37264926+CPerezz@users.noreply.github.com> Co-authored-by: Guruprasad Kamath <48196632+gurukamath@users.noreply.github.com> Co-authored-by: Louis Tsai <72684086+LouisTsai-Csie@users.noreply.github.com> Co-authored-by: spencer-tb --- .../testing/src/execution_testing/__init__.py | 2 + .../src/execution_testing/forks/base_fork.py | 99 +++ .../forks/forks/eips/amsterdam/eip_2780.py | 156 ++++ .../forks/forks/eips/amsterdam/eip_7981.py | 5 + .../forks/forks/eips/amsterdam/eip_8037.py | 128 ++- .../forks/forks/eips/amsterdam/eip_8038.py | 188 +++++ .../forks/forks/eips/berlin/eip_2930.py | 4 + .../forks/forks/eips/cancun/eip_1153.py | 19 +- .../forks/forks/eips/homestead/eip_2.py | 4 + .../forks/forks/eips/prague/eip_7623.py | 5 + .../forks/forks/eips/prague/eip_7702.py | 5 + .../execution_testing/forks/forks/forks.py | 4 + .../src/execution_testing/forks/gas_costs.py | 6 + .../src/execution_testing/recipient_type.py | 14 + .../tools/utility/generators.py | 1 - src/ethereum/forks/amsterdam/fork.py | 34 +- src/ethereum/forks/amsterdam/state_tracker.py | 71 +- src/ethereum/forks/amsterdam/transactions.py | 57 +- src/ethereum/forks/amsterdam/vm/__init__.py | 2 + .../forks/amsterdam/vm/eoa_delegation.py | 74 +- src/ethereum/forks/amsterdam/vm/gas.py | 37 +- .../amsterdam/vm/instructions/environment.py | 7 +- .../amsterdam/vm/instructions/storage.py | 23 +- .../forks/amsterdam/vm/instructions/system.py | 8 +- .../forks/amsterdam/vm/interpreter.py | 45 +- .../__init__.py | 1 + .../helpers.py | 43 + .../eip2780_reduce_intrinsic_tx_gas/spec.py | 17 + .../test_calldata_floor.py | 149 ++++ .../test_fork_transition.py | 154 ++++ .../test_intrinsic_gas_boundary.py | 115 +++ .../test_top_frame_charges.py | 393 +++++++++ .../test_value_moving_transactions.py | 414 ++++++++++ .../test_value_moving_with_tx_delegation.py | 339 ++++++++ .../test_warmth_invariants.py | 543 +++++++++++++ .../test_eip_mainnet.py | 15 +- .../test_gas_accounting.py | 4 + .../test_block_access_lists.py | 97 ++- .../test_block_access_lists_eip4895.py | 15 +- .../test_block_access_lists_invalid.py | 23 +- .../test_additional_coverage.py | 5 + .../test_floor_boundary_exact_balance.py | 2 +- .../test_refunds.py | 21 +- .../test_floor_boundary_exact_balance.py | 6 +- .../spec.py | 11 +- .../test_block_2d_gas_accounting.py | 65 +- .../test_state_gas_call.py | 7 +- .../test_state_gas_create.py | 18 +- .../test_state_gas_multi_block.py | 4 + .../test_state_gas_selfdestruct.py | 32 +- .../test_state_gas_set_code.py | 349 +++++++- .../test_state_gas_sstore.py | 33 +- .../__init__.py | 3 + .../spec.py | 16 + .../test_access_list_gas.py | 329 ++++++++ .../test_call_gas.py | 768 ++++++++++++++++++ .../test_create_gas.py | 553 +++++++++++++ .../test_eip_mainnet.py | 265 ++++++ .../test_exact_balance_no_fallback.py | 222 +++++ .../test_ext_code_opcodes_gas.py | 471 +++++++++++ .../test_fork_transition.py | 523 ++++++++++++ .../test_selfdestruct_gas.py | 681 ++++++++++++++++ .../test_set_code_auth_gas.py | 609 ++++++++++++++ .../test_set_code_auth_refunds.py | 242 ++++++ .../test_sload_gas.py | 192 +++++ .../test_sstore_gas.py | 215 +++++ .../test_sstore_refunds.py | 349 ++++++++ .../test_transient_storage_regression.py | 87 ++ tests/berlin/eip2930_access_list/test_acl.py | 1 + .../test_tstorage_create_contexts.py | 91 ++- tests/cancun/eip4844_blobs/conftest.py | 11 +- tests/cancun/eip4844_blobs/test_blob_txs.py | 118 ++- .../test_collision_selfdestruct.py | 14 +- .../stBadOpcode/test_measure_gas.py | 33 +- .../stBadOpcode/test_operation_diff_gas.py | 39 +- .../test_create2_oo_gafter_init_code.py | 66 +- .../stCreate2/test_create2_smart_init_code.py | 26 +- .../test_create2check_fields_in_initcode.py | 3 +- .../test_create_address_warm_after_fail.py | 35 +- .../test_create_oo_gafter_init_code.py | 66 +- .../test_14_revert_after_nested_staticcall.py | 35 +- ...e_consume_more_gas_then_transaction_has.py | 22 +- .../test_call_goes_oog_on_second_level.py | 20 +- .../test_suicide_to_existing_contract.py | 13 +- .../test_suicide_to_not_existing_contract.py | 16 +- .../test_eip2929.py | 307 ++++--- .../test_eip2929_minus_ff.py | 17 +- .../test_gas_cost.py | 53 +- .../test_gas_cost_berlin.py | 31 +- .../test_gas_cost_memory.py | 15 +- .../test_raw_ext_code_copy_gas.py | 23 +- .../test_raw_ext_code_copy_memory_gas.py | 23 +- .../test_raw_ext_code_size_gas.py | 23 +- .../stEIP1559/test_low_gas_limit.py | 15 +- .../test_call_one_v_call_suicide.py | 21 +- .../test_call_one_v_call_suicide2.py | 22 +- .../test_call_zero_v_call_suicide.py | 13 +- .../test_extcodesize_to_epmty_paris.py | 28 +- .../test_extcodesize_to_non_existent.py | 26 +- .../stEIP2930/test_address_opcodes.py | 193 +++-- .../stEIP2930/test_coinbase_t01.py | 18 +- .../stEIP2930/test_coinbase_t2.py | 18 +- .../stEIP2930/test_manual_create.py | 35 +- .../stEIP2930/test_storage_costs.py | 126 ++- .../stEIP2930/test_transaction_costs.py | 11 + .../stEIP2930/test_varied_context.py | 279 ++++--- .../test_coinbase_warm_account_call_gas.py | 9 +- ...ransaction_has_with_mem_expanding_calls.py | 20 +- ...te_call_on_eip_with_mem_expanding_calls.py | 22 +- tests/ported_static/stMemoryTest/test_oog.py | 16 +- ...zero_value_call_to_non_non_zero_balance.py | 28 +- ..._value_callcode_to_non_non_zero_balance.py | 28 +- .../test_precomps_eip2929_cancun.py | 37 +- .../stRefundTest/test_refund50_1.py | 27 +- ...st_refund_call_a_not_enough_gas_in_call.py | 28 +- .../test_refund_change_non_zero_storage.py | 23 +- .../stRefundTest/test_refund_ff.py | 29 +- .../test_refund_get_ether_back.py | 47 +- .../stRefundTest/test_refund_max.py | 27 +- .../test_refund_multimple_suicide.py | 17 +- .../stRefundTest/test_refund_no_oog_1.py | 52 +- .../test_refund_single_suicide.py | 17 +- .../stRefundTest/test_refund_sstore.py | 52 +- .../stRevertTest/test_revert_opcode_calls.py | 15 +- .../stSpecialTest/test_eoa_empty_paris.py | 33 +- .../test_static_call_change_revert.py | 284 +------ .../stStaticCall/test_static_make_money.py | 72 +- .../test_static_raw_call_gas_ask.py | 211 +---- .../test_contract_store_clears_success.py | 26 +- .../stTransactionTest/test_high_gas_limit.py | 20 +- ...test_internal_call_store_clears_success.py | 32 +- ..._and_internal_call_store_clears_success.py | 32 +- .../test_transaction_sending_to_zero.py | 21 +- ...est_transaction_to_addressh160minus_one.py | 30 +- .../test_transaction_to_itself.py | 28 +- .../stZeroCallsTest/test_zero_value_call.py | 19 +- .../test_zero_value_call_to_empty_paris.py | 19 +- ...est_zero_value_call_to_non_zero_balance.py | 19 +- ...ero_value_call_to_one_storage_key_paris.py | 20 +- .../test_zero_value_callcode.py | 20 +- ...test_zero_value_callcode_to_empty_paris.py | 20 +- ...zero_value_callcode_to_non_zero_balance.py | 22 +- ...value_callcode_to_one_storage_key_paris.py | 21 +- .../test_zero_value_delegatecall.py | 21 +- ..._zero_value_delegatecall_to_empty_paris.py | 21 +- ..._value_delegatecall_to_non_zero_balance.py | 21 +- ...e_delegatecall_to_one_storage_key_paris.py | 21 +- tests/ported_static/vmTests/test_suicide.py | 26 +- tests/prague/eip7702_set_code_tx/test_gas.py | 17 +- .../eip7702_set_code_tx/test_set_code_txs.py | 47 +- .../eip4895_withdrawals/test_withdrawals.py | 10 +- 151 files changed, 11639 insertions(+), 1292 deletions(-) create mode 100644 packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_2780.py create mode 100644 packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8038.py create mode 100644 packages/testing/src/execution_testing/recipient_type.py create mode 100644 tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/__init__.py create mode 100644 tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/helpers.py create mode 100644 tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/spec.py create mode 100644 tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_calldata_floor.py create mode 100644 tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_fork_transition.py create mode 100644 tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_intrinsic_gas_boundary.py create mode 100644 tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_top_frame_charges.py create mode 100644 tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_value_moving_transactions.py create mode 100644 tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_value_moving_with_tx_delegation.py create mode 100644 tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_warmth_invariants.py create mode 100644 tests/amsterdam/eip8038_state_access_gas_cost_increase/__init__.py create mode 100644 tests/amsterdam/eip8038_state_access_gas_cost_increase/spec.py create mode 100644 tests/amsterdam/eip8038_state_access_gas_cost_increase/test_access_list_gas.py create mode 100644 tests/amsterdam/eip8038_state_access_gas_cost_increase/test_call_gas.py create mode 100644 tests/amsterdam/eip8038_state_access_gas_cost_increase/test_create_gas.py create mode 100644 tests/amsterdam/eip8038_state_access_gas_cost_increase/test_eip_mainnet.py create mode 100644 tests/amsterdam/eip8038_state_access_gas_cost_increase/test_exact_balance_no_fallback.py create mode 100644 tests/amsterdam/eip8038_state_access_gas_cost_increase/test_ext_code_opcodes_gas.py create mode 100644 tests/amsterdam/eip8038_state_access_gas_cost_increase/test_fork_transition.py create mode 100644 tests/amsterdam/eip8038_state_access_gas_cost_increase/test_selfdestruct_gas.py create mode 100644 tests/amsterdam/eip8038_state_access_gas_cost_increase/test_set_code_auth_gas.py create mode 100644 tests/amsterdam/eip8038_state_access_gas_cost_increase/test_set_code_auth_refunds.py create mode 100644 tests/amsterdam/eip8038_state_access_gas_cost_increase/test_sload_gas.py create mode 100644 tests/amsterdam/eip8038_state_access_gas_cost_increase/test_sstore_gas.py create mode 100644 tests/amsterdam/eip8038_state_access_gas_cost_increase/test_sstore_refunds.py create mode 100644 tests/amsterdam/eip8038_state_access_gas_cost_increase/test_transient_storage_regression.py diff --git a/packages/testing/src/execution_testing/__init__.py b/packages/testing/src/execution_testing/__init__.py index 6b079cd3d5d..87e87352186 100644 --- a/packages/testing/src/execution_testing/__init__.py +++ b/packages/testing/src/execution_testing/__init__.py @@ -31,6 +31,7 @@ ) from .fixtures import BaseFixture, FixtureCollector from .forks import Fork, GasCosts, RefundTypes, TransitionFork +from .recipient_type import RecipientType from .specs import ( BaseTest, BenchmarkTest, @@ -195,6 +196,7 @@ "OpcodeCallArg", "Opcodes", "ParameterSet", + "RecipientType", "ReferenceSpec", "ReferenceSpecTypes", "RefundTypes", diff --git a/packages/testing/src/execution_testing/forks/base_fork.py b/packages/testing/src/execution_testing/forks/base_fork.py index 6449ced32d4..7d2bafc517d 100644 --- a/packages/testing/src/execution_testing/forks/base_fork.py +++ b/packages/testing/src/execution_testing/forks/base_fork.py @@ -32,6 +32,7 @@ Opcodes, ) +from ..recipient_type import RecipientType from .gas_costs import GasCosts @@ -116,6 +117,8 @@ def __call__( access_list: List[AccessList] | None = None, authorization_list_or_count: Sized | int | None = None, return_cost_deducted_prior_execution: bool = False, + sends_value: bool = False, + recipient_type: RecipientType = RecipientType.CONTRACT, ) -> int: """ Return the intrinsic gas cost of a transaction given its properties. @@ -135,6 +138,14 @@ def __call__( that is deducted from the gas limit before the transaction starts execution. + sends_value: Whether the transaction transfers a non-zero value. + Forks that itemize the value-transfer charge in + intrinsic gas use this flag; ignored by older forks. + recipient_type: Category of the transaction recipient. Forks + that vary intrinsic gas by recipient kind + (e.g. no access cost for precompiles, no value + charge for self-transfers) use this; ignored + by older forks. Returns: Gas cost of a transaction @@ -142,6 +153,49 @@ def __call__( pass +class TopFrameGasCalculator(Protocol): + """ + A protocol to calculate the additional regular gas charged at the + top-level transaction frame, after intrinsic gas is deducted but + before EVM execution begins. + + Returns only the regular-gas portion of the post-intrinsic + state-aware preparation (e.g. the delegated-recipient access + charge). The state-gas portion is exposed separately by + ``BaseFork.transaction_top_frame_state_gas`` so tests can model the + two-dimensional reservoir explicitly or sum the two via + ``oog_budget_lift`` when targeting the spillover boundary. + + Returns 0 for forks that do not perform any such preparation. + """ + + def __call__( + self, + *, + contract_creation: bool = False, + sends_value: bool = False, + recipient_type: RecipientType = RecipientType.CONTRACT, + ) -> int: + """ + Return the regular gas consumed by top-frame preparation for a + transaction at this fork. + + Args: + contract_creation: Whether the transaction creates a contract. + Top-frame charges are zero for creates; + equivalent charges are paid via intrinsic + gas. + sends_value: Whether the transaction transfers a non-zero + value. + recipient_type: Category of the transaction recipient. + Drives the conditional charges. + + Returns: Regular gas added by top-frame preparation. + + """ + pass + + class BlobGasPriceCalculator(Protocol): """ A protocol to calculate the blob gas price given the excess blob gas at a @@ -705,6 +759,51 @@ def transaction_intrinsic_state_gas( del contract_creation, authorization_count return 0 + @classmethod + def transaction_top_frame_gas_calculator( + cls, + ) -> TopFrameGasCalculator: + """ + Return a callable that calculates the additional regular gas + charged at the top-level transaction frame, after intrinsic + gas is deducted but before EVM execution begins. + + Defaults to returning 0 for forks that do not perform such + post-intrinsic preparation. + """ + + def fn( + *, + contract_creation: bool = False, + sends_value: bool = False, + recipient_type: RecipientType = RecipientType.CONTRACT, + ) -> int: + del contract_creation, sends_value, recipient_type + return 0 + + return fn + + @classmethod + def transaction_top_frame_state_gas( + cls, + *, + contract_creation: bool = False, + sends_value: bool = False, + recipient_type: RecipientType = RecipientType.CONTRACT, + ) -> int: + """ + Return the state gas charged at the top-level transaction + frame, after intrinsic gas is deducted but before EVM execution + begins. Companion to ``transaction_top_frame_gas_calculator``; + tests targeting the spillover boundary feed this through + ``oog_budget_lift`` to get the equivalent regular-gas budget. + + Defaults to 0 for forks that do not perform such + post-intrinsic preparation. + """ + del contract_creation, sends_value, recipient_type + return 0 + @classmethod def system_call_gas_limit(cls) -> int: """ diff --git a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_2780.py b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_2780.py new file mode 100644 index 00000000000..8ef87da63b0 --- /dev/null +++ b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_2780.py @@ -0,0 +1,156 @@ +""" +EIP-2780: Resource-based intrinsic transaction gas. + +Decompose the intrinsic transaction gas into explicit recipient-access +and value-transfer primitives so that the cost paid before execution +reflects the actual work the transaction will perform. + +https://eips.ethereum.org/EIPS/eip-2780 +""" + +from dataclasses import replace +from typing import List, Sized + +from execution_testing.base_types import AccessList +from execution_testing.base_types.conversions import BytesConvertible + +from .....recipient_type import RecipientType +from ....base_fork import ( + BaseFork, + TopFrameGasCalculator, + TransactionIntrinsicCostCalculator, +) +from ....gas_costs import GasCosts + + +class EIP2780(BaseFork): + """EIP-2780 class.""" + + @classmethod + def gas_costs(cls) -> GasCosts: + """ + Lower ``TX_BASE`` to 12_000 to reflect the removal of the + bundled recipient access and account-write charges, and add + the transfer-log and value-transfer constants. + """ + parent = super(EIP2780, cls).gas_costs() + return replace( + parent, + TX_BASE=12_000, + TRANSFER_LOG_COST=1_756, + TX_VALUE_COST=4_244, + ) + + @classmethod + def transaction_intrinsic_cost_calculator( + cls, + ) -> TransactionIntrinsicCostCalculator: + """ + Decompose intrinsic gas into explicit recipient and + value-transfer primitives. + + Non-create, non-self targets pay ``COLD_ACCOUNT_ACCESS`` + unconditionally; access lists do not warm transaction-level + accounts. Value-bearing transactions pay + ``TRANSFER_LOG_COST`` plus ``TX_VALUE_COST``; self-transfers + suppress the value-transfer charge entirely. + """ + super_fn = super(EIP2780, cls).transaction_intrinsic_cost_calculator() + gas_costs = cls.gas_costs() + + def fn( + *, + calldata: BytesConvertible = b"", + contract_creation: bool = False, + access_list: List[AccessList] | None = None, + authorization_list_or_count: Sized | int | None = None, + return_cost_deducted_prior_execution: bool = False, + sends_value: bool = False, + recipient_type: RecipientType = RecipientType.CONTRACT, + ) -> int: + intrinsic_cost: int = super_fn( + calldata=calldata, + contract_creation=contract_creation, + access_list=access_list, + authorization_list_or_count=authorization_list_or_count, + return_cost_deducted_prior_execution=True, + ) + + is_self_transfer = recipient_type == RecipientType.SELF + + if contract_creation: + if sends_value: + intrinsic_cost += gas_costs.TRANSFER_LOG_COST + elif not is_self_transfer: + intrinsic_cost += gas_costs.COLD_ACCOUNT_ACCESS + if sends_value: + intrinsic_cost += ( + gas_costs.TRANSFER_LOG_COST + gas_costs.TX_VALUE_COST + ) + + if return_cost_deducted_prior_execution: + return intrinsic_cost + + transaction_data_floor_cost_calculator = ( + cls.transaction_data_floor_cost_calculator() + ) + transaction_floor_data_cost = ( + transaction_data_floor_cost_calculator( + data=calldata, access_list=access_list + ) + ) + return max(intrinsic_cost, transaction_floor_data_cost) + + return fn + + @classmethod + def transaction_top_frame_gas_calculator( + cls, + ) -> TopFrameGasCalculator: + """ + Return the additional regular gas charged at the top-level + transaction frame, after intrinsic gas is deducted but before + the EVM dispatches. + + Charges ``COLD_ACCOUNT_ACCESS`` when the recipient is an + existing delegated account. The empty-recipient + ``NEW_ACCOUNT`` charge is state gas, returned separately by + ``transaction_top_frame_state_gas``. + """ + gas_costs = cls.gas_costs() + + def fn( + *, + contract_creation: bool = False, + sends_value: bool = False, + recipient_type: RecipientType = RecipientType.CONTRACT, + ) -> int: + del sends_value + if contract_creation: + return 0 + + if recipient_type == RecipientType.DELEGATION_7702: + return gas_costs.COLD_ACCOUNT_ACCESS + return 0 + + return fn + + @classmethod + def transaction_top_frame_state_gas( + cls, + *, + contract_creation: bool = False, + sends_value: bool = False, + recipient_type: RecipientType = RecipientType.CONTRACT, + ) -> int: + """ + Return the state gas charged at the top-level transaction + frame. Charges ``NEW_ACCOUNT`` when value is transferred to an + empty recipient; zero otherwise. + """ + gas_costs = cls.gas_costs() + if contract_creation: + return 0 + if sends_value and recipient_type == RecipientType.EMPTY_ACCOUNT: + return gas_costs.NEW_ACCOUNT + return 0 diff --git a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_7981.py b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_7981.py index b2f927b507c..4aef4807938 100644 --- a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_7981.py +++ b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_7981.py @@ -11,6 +11,7 @@ from execution_testing.base_types import AccessList from execution_testing.base_types.conversions import BytesConvertible +from .....recipient_type import RecipientType from ....base_fork import ( BaseFork, TransactionDataFloorCostCalculator, @@ -85,7 +86,11 @@ def fn( access_list: List[AccessList] | None = None, authorization_list_or_count: Sized | int | None = None, return_cost_deducted_prior_execution: bool = False, + sends_value: bool = False, + recipient_type: RecipientType = RecipientType.CONTRACT, ) -> int: + del sends_value, recipient_type + intrinsic_cost: int = super_fn( calldata=calldata, contract_creation=contract_creation, diff --git a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8037.py b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8037.py index f1c2a4a4bda..c9f5fd170be 100644 --- a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8037.py +++ b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8037.py @@ -4,6 +4,13 @@ Harmonization, increase and separate metering of state creation gas costs to mitigate state growth and unblock scaling. +The companion EIP-8038 state-access repricing lives in its own `EIP8038` +mixin. Because the EIP mixins are ordered by number, `EIP8037` sits +immediately above `EIP8038` in the MRO, so `super().gas_costs()` here +returns the EIP-8038 schedule and this mixin folds its state-creation gas +into the shared `STORAGE_SET`, `TX_CREATE`, and `AUTH_PER_EMPTY_ACCOUNT` +totals on top of it. + https://eips.ethereum.org/EIPS/eip-8037 """ @@ -23,9 +30,6 @@ STATE_BYTES_PER_STORAGE_SET = 64 STATE_BYTES_PER_AUTH_BASE = 23 -PER_AUTH_BASE_COST = 7_500 -REGULAR_GAS_CREATE = 9_000 - SYSTEM_MAX_SSTORES_PER_CALL = 16 @@ -74,25 +78,23 @@ def create_state_gas(cls, *, code_size: int = 0) -> int: @classmethod def gas_costs(cls) -> GasCosts: """ - Return gas costs updated for two-dimensional gas metering, - with state gas folded into the relevant totals. + Return gas costs with the EIP-8037 state-creation gas folded + into the relevant totals, layered on top of the EIP-8038 + state-access repricing returned by `super().gas_costs()`. """ cpsb = cls.cost_per_state_byte() parent = super(EIP8037, cls).gas_costs() new_acct = STATE_BYTES_PER_NEW_ACCOUNT * cpsb + return replace( parent, - BLOCK_ACCESS_LIST_ITEM=2000, STORAGE_SET=( - parent.COLD_STORAGE_WRITE - - parent.COLD_STORAGE_ACCESS - + STATE_BYTES_PER_STORAGE_SET * cpsb + parent.STORAGE_SET + STATE_BYTES_PER_STORAGE_SET * cpsb ), NEW_ACCOUNT=new_acct, - OPCODE_CREATE_BASE=REGULAR_GAS_CREATE, - TX_CREATE=(REGULAR_GAS_CREATE + new_acct), + TX_CREATE=parent.TX_CREATE + new_acct, AUTH_PER_EMPTY_ACCOUNT=( - PER_AUTH_BASE_COST + parent.AUTH_PER_EMPTY_ACCOUNT + (STATE_BYTES_PER_NEW_ACCOUNT + STATE_BYTES_PER_AUTH_BASE) * cpsb ), @@ -144,6 +146,9 @@ def opcode_state_map( Opcodes.CREATE2: lambda op: cls._calculate_create_state_gas( op, gas_costs ), + Opcodes.SELFDESTRUCT: ( + lambda op: cls._calculate_selfdestruct_state_gas(op, gas_costs) + ), } @classmethod @@ -250,36 +255,6 @@ def transaction_intrinsic_state_gas( ) return state_gas - @classmethod - def _calculate_sstore_gas( - cls, opcode: OpcodeBase, gas_costs: GasCosts - ) -> int: - """ - Calculate the regular SSTORE gas cost. The state portion is - returned separately by `_calculate_sstore_state_gas`. A cold - slot adds `COLD_STORAGE_ACCESS`, a write to an unchanged - original adds `COLD_STORAGE_WRITE` minus `COLD_STORAGE_ACCESS`, - and every other case adds `WARM_SLOAD`. - """ - metadata = opcode.metadata - - original_value = metadata["original_value"] - current_value = metadata["current_value"] - if current_value is None: - current_value = original_value - new_value = metadata["new_value"] - - gas_cost = 0 if metadata["key_warm"] else gas_costs.COLD_STORAGE_ACCESS - - if original_value == current_value and current_value != new_value: - gas_cost += ( - gas_costs.COLD_STORAGE_WRITE - gas_costs.COLD_STORAGE_ACCESS - ) - else: - gas_cost += gas_costs.WARM_SLOAD - - return gas_cost - @classmethod def _calculate_sstore_state_gas( cls, opcode: OpcodeBase, gas_costs: GasCosts @@ -307,39 +282,6 @@ def _calculate_sstore_state_gas( return STATE_BYTES_PER_STORAGE_SET * cpsb return 0 - @classmethod - def _calculate_sstore_refund( - cls, opcode: OpcodeBase, gas_costs: GasCosts - ) -> int: - """ - Calculate the regular SSTORE gas refund. The state portion is - returned separately by `_calculate_sstore_state_refund`. - """ - metadata = opcode.metadata - - original_value = metadata["original_value"] - current_value = metadata["current_value"] - if current_value is None: - current_value = original_value - new_value = metadata["new_value"] - - refund = 0 - if current_value != new_value: - if original_value != 0 and current_value != 0 and new_value == 0: - refund += gas_costs.REFUND_STORAGE_CLEAR - - if original_value != 0 and current_value == 0: - refund -= gas_costs.REFUND_STORAGE_CLEAR - - if original_value == new_value: - refund += ( - gas_costs.COLD_STORAGE_WRITE - - gas_costs.COLD_STORAGE_ACCESS - - gas_costs.WARM_SLOAD - ) - - return refund - @classmethod def _calculate_sstore_state_refund( cls, opcode: OpcodeBase, gas_costs: GasCosts @@ -444,3 +386,39 @@ def _calculate_create_state_gas( """ del opcode return gas_costs.NEW_ACCOUNT + + @classmethod + def _calculate_selfdestruct_state_gas( + cls, opcode: OpcodeBase, gas_costs: GasCosts + ) -> int: + """ + Calculate the SELFDESTRUCT state gas cost: `NEW_ACCOUNT` when a + positive balance funds a new account. Before EIP-8037 this was + folded into the regular SELFDESTRUCT cost; under EIP-8037 it is + exposed here as state gas (mirroring `_calculate_create_state_gas`) + so the regular cost matches the spec EVM + (`OPCODE_SELFDESTRUCT_BASE` + account access + the EIP-8038 + `ACCOUNT_WRITE` surcharge). + """ + if opcode.metadata["account_new"]: + return gas_costs.NEW_ACCOUNT + return 0 + + @classmethod + def _calculate_selfdestruct_gas( + cls, opcode: OpcodeBase, gas_costs: GasCosts + ) -> int: + """ + Calculate the regular SELFDESTRUCT gas cost. The Frontier base + calculation folds `NEW_ACCOUNT` into the regular cost when a + positive balance funds a new account; EIP-8038 (the mixin between + the base and EIP-8037 in the MRO) adds only the `ACCOUNT_WRITE` + surcharge. EIP-8037 moves that funding cost to the state-gas + dimension (see `_calculate_selfdestruct_state_gas`), so this + subtracts the `NEW_ACCOUNT` term back out of the inherited regular + cost; the EIP-8038 `ACCOUNT_WRITE` surcharge stays in regular gas. + """ + gas_cost = super()._calculate_selfdestruct_gas(opcode, gas_costs) + if opcode.metadata["account_new"]: + gas_cost -= gas_costs.NEW_ACCOUNT + return gas_cost diff --git a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8038.py b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8038.py new file mode 100644 index 00000000000..26c05c5a0ea --- /dev/null +++ b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8038.py @@ -0,0 +1,188 @@ +""" +EIP-8038: State Access Gas Cost Increase. + +Harmonization and increase of state-access gas costs, repricing warm and +cold account and storage access, account writes, and the related access +list and authorization costs. + +This mixin ships alongside EIP-8037 in Amsterdam. It carries the +state-access repricing only; the EIP-8037 state-creation gas is folded in +on top by the (lower-numbered, therefore shallower) `EIP8037` mixin, which +reads these values via `super().gas_costs()` and adds its state-byte +portions to the shared `STORAGE_SET`, `TX_CREATE`, and +`AUTH_PER_EMPTY_ACCOUNT` totals. + +https://eips.ethereum.org/EIPS/eip-8038 +""" + +from dataclasses import replace +from typing import Callable, Dict + +from execution_testing.vm import ( + OpcodeBase, + Opcodes, +) + +from ....base_fork import BaseFork +from ....gas_costs import GasCosts + + +class EIP8038(BaseFork): + """EIP-8038 class.""" + + @classmethod + def gas_costs(cls) -> GasCosts: + """ + Return the EIP-8038 state-access gas repricing, layered on top + of the parent fork's schedule. EIP-8037 then folds its + state-creation gas into the relevant totals via + `super().gas_costs()`. + """ + parent = super(EIP8038, cls).gas_costs() + + warm_access = 100 + cold_account_access = 3_000 + cold_storage_access = 3_000 + storage_write = 10_000 + # The framework models the SSTORE write via the compound + # COLD_STORAGE_WRITE (access + write), so preserve the invariant + # COLD_STORAGE_WRITE - COLD_STORAGE_ACCESS == STORAGE_WRITE. + cold_storage_write = cold_storage_access + storage_write + # Surcharge for the first write to an account leaf, introduced as a + # standalone parameter by this repricing. + account_write = 8_000 + create_access = 11_000 + # ecRecover stays PRECOMPILE_ECRECOVER (3000) until EIP-7904 lands. + regular_per_auth_base_cost = ( + 1_616 + 3_000 + cold_account_access + 2 * warm_access + ) + + return replace( + parent, + WARM_ACCESS=warm_access, + WARM_SLOAD=warm_access, + COLD_ACCOUNT_ACCESS=cold_account_access, + COLD_STORAGE_ACCESS=cold_storage_access, + COLD_STORAGE_WRITE=cold_storage_write, + ACCOUNT_WRITE=account_write, + CALL_VALUE=account_write + 2_300, # ACCOUNT_WRITE + CALL_STIPEND + REFUND_STORAGE_CLEAR=12_480, + TX_ACCESS_LIST_ADDRESS=3_000, + TX_ACCESS_LIST_STORAGE_KEY=3_000, + BLOCK_ACCESS_LIST_ITEM=2000, + STORAGE_SET=storage_write, + OPCODE_CREATE_BASE=create_access, + TX_CREATE=create_access, + AUTH_PER_EMPTY_ACCOUNT=account_write + regular_per_auth_base_cost, + ) + + @classmethod + def opcode_gas_map( + cls, + ) -> Dict[OpcodeBase, int | Callable[[OpcodeBase], int]]: + """ + Return the opcode gas map with the EIP-8038 `EXT*` update: + `EXTCODESIZE` and `EXTCODECOPY` charge an extra `WARM_ACCESS` + for the second database read (the code). + """ + gas_costs = cls.gas_costs() + opcode_gas_map = dict(super(EIP8038, cls).opcode_gas_map()) + + def with_extra_warm_access( + inner: int | Callable[[OpcodeBase], int], + ) -> Callable[[OpcodeBase], int]: + def fn(opcode: OpcodeBase) -> int: + inner_gas = inner(opcode) if callable(inner) else inner + return inner_gas + gas_costs.WARM_ACCESS + + return fn + + for opcode in (Opcodes.EXTCODESIZE, Opcodes.EXTCODECOPY): + opcode_gas_map[opcode] = with_extra_warm_access( + opcode_gas_map[opcode] + ) + return opcode_gas_map + + @classmethod + def _calculate_selfdestruct_gas( + cls, opcode: OpcodeBase, gas_costs: GasCosts + ) -> int: + """ + Calculate the regular SELFDESTRUCT gas cost. EIP-8038 adds + `ACCOUNT_WRITE` when a positive balance is sent to an empty + account, on top of the inherited cost (where `NEW_ACCOUNT` + holds the EIP-8037 state-gas portion). + """ + gas_cost = super(EIP8038, cls)._calculate_selfdestruct_gas( + opcode, gas_costs + ) + if opcode.metadata["account_new"]: + gas_cost += gas_costs.ACCOUNT_WRITE + return gas_cost + + @classmethod + def _calculate_sstore_gas( + cls, opcode: OpcodeBase, gas_costs: GasCosts + ) -> int: + """ + Calculate the regular SSTORE gas cost. The state portion is + returned separately by `_calculate_sstore_state_gas`. Under + EIP-8038 the access cost (`COLD_STORAGE_ACCESS` when cold, else + `WARM_SLOAD`) is always charged, and a first-time change to the + slot additionally charges the write cost `STORAGE_WRITE` + (modeled as `COLD_STORAGE_WRITE` minus `COLD_STORAGE_ACCESS`). + """ + metadata = opcode.metadata + + original_value = metadata["original_value"] + current_value = metadata["current_value"] + if current_value is None: + current_value = original_value + new_value = metadata["new_value"] + + gas_cost = ( + gas_costs.WARM_SLOAD + if metadata["key_warm"] + else gas_costs.COLD_STORAGE_ACCESS + ) + + if original_value == current_value and current_value != new_value: + gas_cost += ( + gas_costs.COLD_STORAGE_WRITE - gas_costs.COLD_STORAGE_ACCESS + ) + + return gas_cost + + @classmethod + def _calculate_sstore_refund( + cls, opcode: OpcodeBase, gas_costs: GasCosts + ) -> int: + """ + Calculate the regular SSTORE gas refund. The state portion is + returned separately by `_calculate_sstore_state_refund`. + """ + metadata = opcode.metadata + + original_value = metadata["original_value"] + current_value = metadata["current_value"] + if current_value is None: + current_value = original_value + new_value = metadata["new_value"] + + refund = 0 + if current_value != new_value: + if original_value != 0 and current_value != 0 and new_value == 0: + refund += gas_costs.REFUND_STORAGE_CLEAR + + if original_value != 0 and current_value == 0: + refund -= gas_costs.REFUND_STORAGE_CLEAR + + if original_value == new_value: + # Refund the STORAGE_WRITE charged on the first-time + # change earlier in the transaction. + refund += ( + gas_costs.COLD_STORAGE_WRITE + - gas_costs.COLD_STORAGE_ACCESS + ) + + return refund diff --git a/packages/testing/src/execution_testing/forks/forks/eips/berlin/eip_2930.py b/packages/testing/src/execution_testing/forks/forks/eips/berlin/eip_2930.py index 7dede0ace15..675df0b7f10 100644 --- a/packages/testing/src/execution_testing/forks/forks/eips/berlin/eip_2930.py +++ b/packages/testing/src/execution_testing/forks/forks/eips/berlin/eip_2930.py @@ -12,6 +12,7 @@ from execution_testing.base_types import AccessList from execution_testing.base_types.conversions import BytesConvertible +from .....recipient_type import RecipientType from ....base_fork import BaseFork, TransactionIntrinsicCostCalculator @@ -45,8 +46,11 @@ def fn( access_list: List[AccessList] | None = None, authorization_list_or_count: Sized | int | None = None, return_cost_deducted_prior_execution: bool = False, + sends_value: bool = False, + recipient_type: RecipientType = RecipientType.CONTRACT, ) -> int: del return_cost_deducted_prior_execution + del sends_value, recipient_type intrinsic_cost: int = super_fn( calldata=calldata, diff --git a/packages/testing/src/execution_testing/forks/forks/eips/cancun/eip_1153.py b/packages/testing/src/execution_testing/forks/forks/eips/cancun/eip_1153.py index c98a22bc3f4..68133d5e8cd 100644 --- a/packages/testing/src/execution_testing/forks/forks/eips/cancun/eip_1153.py +++ b/packages/testing/src/execution_testing/forks/forks/eips/cancun/eip_1153.py @@ -7,16 +7,31 @@ https://eips.ethereum.org/EIPS/eip-1153 """ +from dataclasses import replace from typing import Callable, Dict, List from execution_testing.vm import OpcodeBase, Opcodes from ....base_fork import BaseFork +from ....gas_costs import GasCosts class EIP1153(BaseFork): """EIP-1153 class.""" + @classmethod + def gas_costs(cls) -> GasCosts: + """ + Set dedicated TLOAD and TSTORE gas costs. Transient storage is + in-memory only; its cost matches a warm storage access at + introduction but is independent of state-access pricing. + """ + return replace( + super(EIP1153, cls).gas_costs(), + OPCODE_TLOAD=100, + OPCODE_TSTORE=100, + ) + @classmethod def opcode_gas_map( cls, @@ -26,8 +41,8 @@ def opcode_gas_map( base_map = super(EIP1153, cls).opcode_gas_map() return { **base_map, - Opcodes.TLOAD: gas_costs.WARM_SLOAD, - Opcodes.TSTORE: gas_costs.WARM_SLOAD, + Opcodes.TLOAD: gas_costs.OPCODE_TLOAD, + Opcodes.TSTORE: gas_costs.OPCODE_TSTORE, } @classmethod diff --git a/packages/testing/src/execution_testing/forks/forks/eips/homestead/eip_2.py b/packages/testing/src/execution_testing/forks/forks/eips/homestead/eip_2.py index 811094f0a98..2f4f8385d3f 100644 --- a/packages/testing/src/execution_testing/forks/forks/eips/homestead/eip_2.py +++ b/packages/testing/src/execution_testing/forks/forks/eips/homestead/eip_2.py @@ -9,6 +9,7 @@ from execution_testing.base_types import AccessList from execution_testing.base_types.conversions import BytesConvertible +from .....recipient_type import RecipientType from ....base_fork import BaseFork, TransactionIntrinsicCostCalculator @@ -33,8 +34,11 @@ def fn( access_list: List[AccessList] | None = None, authorization_list_or_count: Sized | int | None = None, return_cost_deducted_prior_execution: bool = False, + sends_value: bool = False, + recipient_type: RecipientType = RecipientType.CONTRACT, ) -> int: del return_cost_deducted_prior_execution + del sends_value, recipient_type intrinsic_cost: int = super_fn( calldata=calldata, diff --git a/packages/testing/src/execution_testing/forks/forks/eips/prague/eip_7623.py b/packages/testing/src/execution_testing/forks/forks/eips/prague/eip_7623.py index d52c5601a16..6b82b788379 100644 --- a/packages/testing/src/execution_testing/forks/forks/eips/prague/eip_7623.py +++ b/packages/testing/src/execution_testing/forks/forks/eips/prague/eip_7623.py @@ -12,6 +12,7 @@ from execution_testing.base_types import AccessList, Bytes from execution_testing.base_types.conversions import BytesConvertible +from .....recipient_type import RecipientType from ....base_fork import ( BaseFork, CalldataGasCalculator, @@ -95,7 +96,11 @@ def fn( access_list: List[AccessList] | None = None, authorization_list_or_count: Sized | int | None = None, return_cost_deducted_prior_execution: bool = False, + sends_value: bool = False, + recipient_type: RecipientType = RecipientType.CONTRACT, ) -> int: + del sends_value, recipient_type + intrinsic_cost: int = super_fn( calldata=calldata, contract_creation=contract_creation, diff --git a/packages/testing/src/execution_testing/forks/forks/eips/prague/eip_7702.py b/packages/testing/src/execution_testing/forks/forks/eips/prague/eip_7702.py index 424c4ab2dab..7ce6a0876bf 100644 --- a/packages/testing/src/execution_testing/forks/forks/eips/prague/eip_7702.py +++ b/packages/testing/src/execution_testing/forks/forks/eips/prague/eip_7702.py @@ -13,6 +13,7 @@ from execution_testing.base_types.conversions import BytesConvertible from execution_testing.vm import OpcodeBase +from .....recipient_type import RecipientType from ....base_fork import ( BaseFork, RefundTypes, @@ -74,7 +75,11 @@ def fn( access_list: List[AccessList] | None = None, authorization_list_or_count: Sized | int | None = None, return_cost_deducted_prior_execution: bool = False, + sends_value: bool = False, + recipient_type: RecipientType = RecipientType.CONTRACT, ) -> int: + del sends_value, recipient_type + intrinsic_cost: int = super_fn( calldata=calldata, contract_creation=contract_creation, diff --git a/packages/testing/src/execution_testing/forks/forks/forks.py b/packages/testing/src/execution_testing/forks/forks/forks.py index 04fc4838045..168a4b19e2b 100644 --- a/packages/testing/src/execution_testing/forks/forks/forks.py +++ b/packages/testing/src/execution_testing/forks/forks/forks.py @@ -21,6 +21,7 @@ Opcodes, ) +from ...recipient_type import RecipientType from ..base_fork import ( BaseFeeChangeCalculator, BaseFeePerGasCalculator, @@ -872,8 +873,11 @@ def fn( access_list: List[AccessList] | None = None, authorization_list_or_count: Sized | int | None = None, return_cost_deducted_prior_execution: bool = False, + sends_value: bool = False, + recipient_type: RecipientType = RecipientType.CONTRACT, ) -> int: del return_cost_deducted_prior_execution + del sends_value, recipient_type assert access_list is None, ( f"Access list is not supported in {cls.name()}" diff --git a/packages/testing/src/execution_testing/forks/gas_costs.py b/packages/testing/src/execution_testing/forks/gas_costs.py index fcb8148cca0..a899d5a129a 100644 --- a/packages/testing/src/execution_testing/forks/gas_costs.py +++ b/packages/testing/src/execution_testing/forks/gas_costs.py @@ -36,6 +36,10 @@ class GasCosts: CALL_VALUE: int CALL_STIPEND: int NEW_ACCOUNT: int + ACCOUNT_WRITE: int = 0 + CREATE_ACCESS: int = 0 + TRANSFER_LOG_COST: int = 0 + TX_VALUE_COST: int = 0 # Contract Creation CODE_DEPOSIT_PER_BYTE: int @@ -146,3 +150,5 @@ class GasCosts: OPCODE_BLOBHASH: int = 0 OPCODE_MCOPY_BASE: int = 0 OPCODE_CLZ: int = 0 + OPCODE_TLOAD: int = 0 + OPCODE_TSTORE: int = 0 diff --git a/packages/testing/src/execution_testing/recipient_type.py b/packages/testing/src/execution_testing/recipient_type.py new file mode 100644 index 00000000000..f1bfe8770bb --- /dev/null +++ b/packages/testing/src/execution_testing/recipient_type.py @@ -0,0 +1,14 @@ +"""Recipient type enumeration for transaction gas calculations.""" + +from enum import Enum, auto + + +class RecipientType(Enum): + """The type of recipient for a transaction.""" + + SELF = auto() + EOA = auto() + CONTRACT = auto() + DELEGATION_7702 = auto() + PRECOMPILE = auto() + EMPTY_ACCOUNT = auto() diff --git a/packages/testing/src/execution_testing/tools/utility/generators.py b/packages/testing/src/execution_testing/tools/utility/generators.py index 028a114a6c4..a70b3831ce2 100644 --- a/packages/testing/src/execution_testing/tools/utility/generators.py +++ b/packages/testing/src/execution_testing/tools/utility/generators.py @@ -409,7 +409,6 @@ def wrapper( test_tx = Transaction( to=value_receiver, value=1, - gas_limit=100_000, sender=pre.fund_eoa(), ) post = Alloc() diff --git a/src/ethereum/forks/amsterdam/fork.py b/src/ethereum/forks/amsterdam/fork.py index 245161e2e1c..c2001554bd1 100644 --- a/src/ethereum/forks/amsterdam/fork.py +++ b/src/ethereum/forks/amsterdam/fork.py @@ -503,8 +503,9 @@ def check_transaction( block_env: vm.BlockEnvironment, block_output: vm.BlockOutput, tx: Transaction, + sender: Address, tx_state: TransactionState, -) -> Tuple[Address, Uint, Tuple[VersionedHash, ...], U64]: +) -> Tuple[Uint, Tuple[VersionedHash, ...], U64]: """ Check if the transaction is includable in the block. @@ -516,13 +517,13 @@ def check_transaction( The block output for the current block. tx : The transaction. + sender : + The recovered sender address of the transaction. tx_state : The transaction state tracker. Returns ------- - sender_address : - The sender of the transaction. effective_gas_price : The price to charge for gas when the transaction is executed. blob_versioned_hashes : @@ -584,15 +585,7 @@ def check_transaction( if tx_blob_gas_used > blob_gas_available: raise BlobGasLimitExceededError("blob gas limit exceeded") - tx_chain_id = chain_id(tx) - if tx_chain_id is not None and tx_chain_id != block_env.chain_id: - raise WrongChainIdError( - expected=block_env.chain_id, - actual=tx_chain_id, - ) - - sender_address = recover_sender(tx) - sender_account = get_account(tx_state, sender_address) + sender_account = get_account(tx_state, sender) if isinstance(tx, FeeMarketCapableTransaction): if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: @@ -665,7 +658,6 @@ def check_transaction( raise InvalidSenderError("not EOA") return ( - sender_address, effective_gas_price, blob_versioned_hashes, tx_blob_gas_used, @@ -800,6 +792,8 @@ def process_unchecked_system_transaction( tx_env = vm.TransactionEnvironment( origin=SYSTEM_ADDRESS, + recipient=target_address, + value=U256(0), gas_price=block_env.base_fee_per_gas, gas=SYSTEM_TRANSACTION_GAS, state_gas_reservoir=( @@ -1030,12 +1024,19 @@ def process_transaction( encode_transaction(tx), ) - intrinsic = validate_transaction(tx) + tx_chain_id = chain_id(tx) + if tx_chain_id is not None and tx_chain_id != block_env.chain_id: + raise WrongChainIdError( + expected=block_env.chain_id, + actual=tx_chain_id, + ) + + sender = recover_sender(tx) + intrinsic = validate_transaction(tx, sender) intrinsic_gas = Uint(intrinsic.regular) + Uint(intrinsic.state) ( - sender, effective_gas_price, blob_versioned_hashes, tx_blob_gas_used, @@ -1043,6 +1044,7 @@ def process_transaction( block_env=block_env, block_output=block_output, tx=tx, + sender=sender, tx_state=tx_state, ) @@ -1084,6 +1086,8 @@ def process_transaction( tx_env = vm.TransactionEnvironment( origin=sender, + recipient=tx.to, + value=tx.value, gas_price=effective_gas_price, gas=gas, state_gas_reservoir=state_gas_reservoir, diff --git a/src/ethereum/forks/amsterdam/state_tracker.py b/src/ethereum/forks/amsterdam/state_tracker.py index cda7bbf53ea..5f7d0eaf33c 100644 --- a/src/ethereum/forks/amsterdam/state_tracker.py +++ b/src/ethereum/forks/amsterdam/state_tracker.py @@ -92,6 +92,73 @@ class TransactionState: ) +def get_pre_state_account_optional( + tx_state: TransactionState, address: Address +) -> Optional[Account]: + """ + Get the `Account` object at an address that existed before the current + transaction, or `None` (rather than [`EMPTY_ACCOUNT`]) if there was no + account at the address at that point. + + Use [`get_pre_state_account()`][pre] if the difference between a + non-existent account and [`EMPTY_ACCOUNT`] isn't important. + + [`EMPTY_ACCOUNT`]: ref:ethereum.state.EMPTY_ACCOUNT + [pre]: ref:ethereum.forks.amsterdam.state_tracker.get_pre_state_account + + Parameters + ---------- + tx_state : + The transaction state. + address : + Address to look up. + + Returns + ------- + account : ``Optional[Account]`` + Account at address before the current transaction. + + """ + tx_state.account_reads.add(address) + if address in tx_state.parent.account_writes: + return tx_state.parent.account_writes[address] + return tx_state.parent.pre_state.get_account_optional(address) + + +def get_pre_state_account( + tx_state: TransactionState, address: Address +) -> Account: + """ + Get the `Account` object at an address that existed before the current + transaction, or [`EMPTY_ACCOUNT`]) if there was no account at the address + at that point. + + Use [`get_pre_state_account_optional()`][opt] if the difference between a + non-existent account and [`EMPTY_ACCOUNT`] is material. + + [`EMPTY_ACCOUNT`]: ref:ethereum.state.EMPTY_ACCOUNT + [opt]: ref:ethereum.forks.amsterdam.state_tracker.get_pre_state_account_optional + + Parameters + ---------- + tx_state : + The transaction state. + address : + Address to look up. + + Returns + ------- + account : ``Account`` + Account at address before the current transaction. + + """ # noqa: E501 + account = get_pre_state_account_optional(tx_state, address) + if account is None: + return EMPTY_ACCOUNT + else: + return account + + def get_account_optional( tx_state: TransactionState, address: Address ) -> Optional[Account]: @@ -115,9 +182,7 @@ def get_account_optional( tx_state.account_reads.add(address) if address in tx_state.account_writes: return tx_state.account_writes[address] - if address in tx_state.parent.account_writes: - return tx_state.parent.account_writes[address] - return tx_state.parent.pre_state.get_account_optional(address) + return get_pre_state_account_optional(tx_state, address) def get_account(tx_state: TransactionState, address: Address) -> Account: diff --git a/src/ethereum/forks/amsterdam/transactions.py b/src/ethereum/forks/amsterdam/transactions.py index b05711d28b4..136d0d91e72 100644 --- a/src/ethereum/forks/amsterdam/transactions.py +++ b/src/ethereum/forks/amsterdam/transactions.py @@ -577,7 +577,7 @@ def decode_transaction(tx: LegacyTransaction | Bytes) -> Transaction: return tx -def validate_transaction(tx: Transaction) -> IntrinsicGasCost: +def validate_transaction(tx: Transaction, sender: Address) -> IntrinsicGasCost: """ Verifies a transaction. @@ -609,7 +609,7 @@ def validate_transaction(tx: Transaction) -> IntrinsicGasCost: """ from .vm.interpreter import MAX_INIT_CODE_SIZE - intrinsic = calculate_intrinsic_cost(tx) + intrinsic = calculate_intrinsic_cost(tx, sender) intrinsic_gas = Uint(intrinsic.regular) + Uint(intrinsic.state) if intrinsic_gas > tx.gas: raise InsufficientTransactionGasError("Insufficient intrinsic gas") @@ -631,7 +631,9 @@ def validate_transaction(tx: Transaction) -> IntrinsicGasCost: return intrinsic -def calculate_intrinsic_cost(tx: Transaction) -> IntrinsicGasCost: +def calculate_intrinsic_cost( + tx: Transaction, sender: Address +) -> IntrinsicGasCost: """ Calculates the gas that is charged before execution is started. @@ -645,12 +647,18 @@ def calculate_intrinsic_cost(tx: Transaction) -> IntrinsicGasCost: for all operations to be implemented. The intrinsic cost includes: - 1. Base cost (`TX_BASE`) - 2. Cost for data (zero and non-zero bytes) - 3. Cost for contract creation (if applicable) - 4. Cost for access list entries (if applicable) - 5. Cost for authorizations (if applicable) - + 1. Sender cost (`TX_BASE`). + 2. Recipient cost (`COLD_ACCOUNT_ACCESS` for a non-self-transfer + call, or `CREATE_ACCESS` plus `NEW_ACCOUNT` state gas for a + contract creation). + 3. Value cost (`TRANSFER_LOG_COST`, plus `TX_VALUE_COST` for a + non-self-transfer call) when ``tx.value > 0``. + 4. Calldata cost (zero and non-zero bytes). + 5. Access list entries (if applicable). + 6. Authorizations (if applicable). + + Self-transfers (``sender == tx.to``) skip the recipient and value + charges. This function takes a transaction and gas_limit as parameters and returns the intrinsic regular gas cost, intrinsic state gas cost, and the @@ -666,13 +674,24 @@ def calculate_intrinsic_cost(tx: Transaction) -> IntrinsicGasCost: data_cost = tokens_in_calldata * GasCosts.TX_DATA_TOKEN_STANDARD - create_regular_gas = Uint(0) - create_state_gas = Uint(0) - if tx.to == Bytes0(b""): - create_state_gas = StateGasCosts.NEW_ACCOUNT - create_regular_gas = GasCosts.REGULAR_GAS_CREATE + init_code_cost( + is_create = tx.to == Bytes0(b"") + is_self_transfer = tx.to == sender + + recipient_regular_gas = Uint(0) + recipient_state_gas = Uint(0) + if is_create: + recipient_regular_gas = GasCosts.CREATE_ACCESS + init_code_cost( ulen(tx.data) ) + recipient_state_gas = StateGasCosts.NEW_ACCOUNT + if tx.value > U256(0): + recipient_regular_gas += GasCosts.TRANSFER_LOG_COST + elif not is_self_transfer: + recipient_regular_gas = GasCosts.COLD_ACCOUNT_ACCESS + if tx.value > U256(0): + recipient_regular_gas += ( + GasCosts.TRANSFER_LOG_COST + GasCosts.TX_VALUE_COST + ) access_list_cost = Uint(0) tokens_in_access_list = Uint(0) @@ -693,9 +712,9 @@ def calculate_intrinsic_cost(tx: Transaction) -> IntrinsicGasCost: auth_regular_gas = Uint(0) auth_state_gas = Uint(0) if isinstance(tx, SetCodeTransaction): - auth_regular_gas = GasCosts.PER_AUTH_BASE_COST * ulen( - tx.authorizations - ) + auth_regular_gas = ( + GasCosts.ACCOUNT_WRITE + GasCosts.REGULAR_PER_AUTH_BASE_COST + ) * ulen(tx.authorizations) auth_state_gas = ( StateGasCosts.NEW_ACCOUNT + StateGasCosts.AUTH_BASE ) * ulen(tx.authorizations) @@ -714,12 +733,12 @@ def calculate_intrinsic_cost(tx: Transaction) -> IntrinsicGasCost: intrinsic_regular_gas = ( GasCosts.TX_BASE + data_cost - + create_regular_gas + + recipient_regular_gas + access_list_cost + auth_regular_gas ) - intrinsic_state_gas = create_state_gas + auth_state_gas + intrinsic_state_gas = recipient_state_gas + auth_state_gas return IntrinsicGasCost( regular=RegularGas(intrinsic_regular_gas), diff --git a/src/ethereum/forks/amsterdam/vm/__init__.py b/src/ethereum/forks/amsterdam/vm/__init__.py index 1f54d2b3c64..0b9dae40e86 100644 --- a/src/ethereum/forks/amsterdam/vm/__init__.py +++ b/src/ethereum/forks/amsterdam/vm/__init__.py @@ -120,6 +120,8 @@ class TransactionEnvironment: """ origin: Address + recipient: Bytes0 | Address + value: U256 gas_price: Uint gas: Uint state_gas_reservoir: Uint diff --git a/src/ethereum/forks/amsterdam/vm/eoa_delegation.py b/src/ethereum/forks/amsterdam/vm/eoa_delegation.py index 8237225c713..2060d5465d7 100644 --- a/src/ethereum/forks/amsterdam/vm/eoa_delegation.py +++ b/src/ethereum/forks/amsterdam/vm/eoa_delegation.py @@ -5,18 +5,20 @@ from typing import Optional, Tuple from ethereum_rlp import rlp +from ethereum_types.bytes import Bytes from ethereum_types.numeric import U64, U256, Uint from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import keccak256 from ethereum.exceptions import InvalidBlock, InvalidSignatureError -from ethereum.state import EMPTY_CODE_HASH, Account, Address +from ethereum.state import Address -from ..fork_types import Authorization +from ..fork_types import Authorization, StateGas from ..state_tracker import ( account_exists, get_account, get_code, + get_pre_state_account, increment_nonce, set_code, ) @@ -157,11 +159,11 @@ def calculate_delegation_cost( def validate_authorization( message: Message, auth: Authorization -) -> None | Tuple[Address, Account]: +) -> None | Tuple[Address, Bytes]: """ Check if the given `Authorization` is valid against the current state. - Returns the `authority` address and its `Account`, or `None` if the + Returns the `authority` address and its code, or `None` if the validation was unsuccessful. """ tx_state = message.tx_env.state @@ -189,17 +191,20 @@ def validate_authorization( if authority_nonce != auth.nonce: return None - return (authority, authority_account) + return (authority, authority_code) -def set_delegation(message: Message) -> Uint: +def set_delegation(message: Message) -> Tuple[Uint, Uint]: """ Set the delegation code for the authorities in the message. Refills `StateGasCosts.NEW_ACCOUNT` when the authority's account leaf already exists, and `StateGasCosts.AUTH_BASE` when its code - slot already holds a delegation indicator. The total is returned - so block accounting can subtract it from `tx_state_gas`. + slot already holds a delegation indicator. When the authority leaf + already exists, the worst-case `GasCosts.ACCOUNT_WRITE` charged in + the intrinsic cost is also refunded to the regular-gas refund + counter. The totals are returned so block accounting can subtract + the state refill from `tx_state_gas` and apply the regular refund. Parameters ---------- @@ -210,41 +215,62 @@ def set_delegation(message: Message) -> Uint: ------- state_refund : `Uint` Total state gas refunded across all processed authorizations. + regular_refund : `Uint` + Total regular gas (`ACCOUNT_WRITE`) refunded for authorities + whose account leaf already existed. """ tx_state = message.tx_env.state state_refund = Uint(0) + regular_refund = Uint(0) for auth in message.tx_env.authorizations: match validate_authorization(message, auth): case None: + refund = StateGasCosts.AUTH_BASE + StateGasCosts.NEW_ACCOUNT + message.state_gas_reservoir += refund + state_refund += refund + regular_refund += GasCosts.ACCOUNT_WRITE continue - case (authority, authority_account): + case (authority, authority_code): pass + refund = StateGas(Uint(0)) + if account_exists(tx_state, authority): - refund = StateGasCosts.NEW_ACCOUNT - message.state_gas_reservoir += refund - state_refund += refund - - # No new delegation indicator bytes are written: either the - # authority already has one (overwrite in place / clear) or - # this auth clears against an authority with no prior code. - if ( - authority_account.code_hash != EMPTY_CODE_HASH - or auth.address == NULL_ADDRESS - ): - refund = StateGasCosts.AUTH_BASE - message.state_gas_reservoir += refund - state_refund += refund + refund += StateGasCosts.NEW_ACCOUNT + # The new-account ACCOUNT_WRITE charged at intrinsic time is + # not needed: refund it to the regular refund counter. + regular_refund += GasCosts.ACCOUNT_WRITE + + pre_state_authority_account = get_pre_state_account( + tx_state, authority + ) + pre_state_authority_code = get_code( + tx_state, pre_state_authority_account.code_hash + ) + + delegated_before_tx = is_valid_delegation(pre_state_authority_code) + delegated_now = is_valid_delegation(authority_code) if auth.address == NULL_ADDRESS: + refund += StateGasCosts.AUTH_BASE + + if delegated_now and not delegated_before_tx: + refund += StateGasCosts.AUTH_BASE + code_to_set = b"" else: code_to_set = EOA_DELEGATION_MARKER + auth.address + if delegated_now or delegated_before_tx: + refund += StateGasCosts.AUTH_BASE + set_code(tx_state, authority, code_to_set) increment_nonce(tx_state, authority) + message.state_gas_reservoir += refund + state_refund += refund + if message.code_address is None: raise InvalidBlock("Invalid type 4 transaction: no target") @@ -253,4 +279,4 @@ def set_delegation(message: Message) -> Uint: get_account(tx_state, message.code_address).code_hash, ) - return state_refund + return state_refund, regular_refund diff --git a/src/ethereum/forks/amsterdam/vm/gas.py b/src/ethereum/forks/amsterdam/vm/gas.py index 156d7db6ec8..91a5810b7d2 100644 --- a/src/ethereum/forks/amsterdam/vm/gas.py +++ b/src/ethereum/forks/amsterdam/vm/gas.py @@ -67,23 +67,21 @@ class GasCosts: # Access WARM_ACCESS: Final[Uint] = Uint(100) - COLD_ACCOUNT_ACCESS: Final[Uint] = Uint(2600) - COLD_STORAGE_ACCESS: Final[Uint] = Uint(2100) + COLD_ACCOUNT_ACCESS: Final[Uint] = Uint(3000) + COLD_STORAGE_ACCESS: Final[Uint] = Uint(3000) # Storage - COLD_STORAGE_WRITE: Final[Uint] = Uint(5000) + STORAGE_WRITE: Final[Uint] = Uint(10000) # Call - CALL_VALUE: Final[Uint] = Uint(9000) + CALL_VALUE: Final[Uint] = Uint(10300) # ACCOUNT_WRITE + CALL_STIPEND CALL_STIPEND: Final[Uint] = Uint(2300) + ACCOUNT_WRITE: Final[Uint] = Uint(8000) # Contract Creation CODE_DEPOSIT_PER_BYTE: Final[Uint] = Uint(200) CODE_INIT_PER_WORD: Final[Uint] = Uint(2) - REGULAR_GAS_CREATE: Final[Uint] = Uint(9000) - - # Authorization - PER_AUTH_BASE_COST: Final[Uint] = Uint(7500) + CREATE_ACCESS: Final[Uint] = ACCOUNT_WRITE + COLD_STORAGE_ACCESS # Utility ZERO: Final[Uint] = Uint(0) @@ -91,7 +89,9 @@ class GasCosts: FAST_STEP: Final[Uint] = Uint(5) # Refunds - REFUND_STORAGE_CLEAR: Final[int] = 4800 + REFUND_STORAGE_CLEAR: Final[int] = int( + (STORAGE_WRITE + COLD_STORAGE_ACCESS) * Uint(4800) // Uint(5000) + ) # Precompiles PRECOMPILE_ECRECOVER: Final[Uint] = Uint(3000) @@ -128,12 +128,23 @@ class GasCosts: BLOCK_ACCESS_LIST_ITEM: Final[Uint] = Uint(2000) # Transactions - TX_BASE: Final[Uint] = Uint(21000) + TX_BASE: Final[Uint] = Uint(12000) TX_CREATE: Final[Uint] = Uint(32000) + TX_VALUE_COST: Final[Uint] = Uint(4244) + TRANSFER_LOG_COST: Final[Uint] = Uint(1756) TX_DATA_TOKEN_STANDARD: Final[Uint] = Uint(4) TX_DATA_TOKEN_FLOOR: Final[Uint] = Uint(16) - TX_ACCESS_LIST_ADDRESS: Final[Uint] = Uint(2400) - TX_ACCESS_LIST_STORAGE_KEY: Final[Uint] = Uint(1900) + TX_ACCESS_LIST_ADDRESS: Final[Uint] = COLD_ACCOUNT_ACCESS + TX_ACCESS_LIST_STORAGE_KEY: Final[Uint] = COLD_STORAGE_ACCESS + + # Authorization + AUTH_TUPLE_BYTES: Final[Uint] = Uint(101) + REGULAR_PER_AUTH_BASE_COST: Final[Uint] = ( + AUTH_TUPLE_BYTES * TX_DATA_TOKEN_FLOOR + + PRECOMPILE_ECRECOVER + + COLD_ACCOUNT_ACCESS + + Uint(2) * WARM_ACCESS + ) # Block LIMIT_ADJUSTMENT_FACTOR: Final[Uint] = Uint(1024) @@ -199,6 +210,8 @@ class GasCosts: OPCODE_DUPN: Final[Uint] = VERY_LOW OPCODE_SWAPN: Final[Uint] = VERY_LOW OPCODE_EXCHANGE: Final[Uint] = VERY_LOW + OPCODE_TLOAD: Final[Uint] = Uint(100) + OPCODE_TSTORE: Final[Uint] = Uint(100) # Dynamic Opcode Components OPCODE_RETURNDATACOPY_BASE: Final[Uint] = VERY_LOW diff --git a/src/ethereum/forks/amsterdam/vm/instructions/environment.py b/src/ethereum/forks/amsterdam/vm/instructions/environment.py index 431cd3ba4c6..8a7e9ec1486 100644 --- a/src/ethereum/forks/amsterdam/vm/instructions/environment.py +++ b/src/ethereum/forks/amsterdam/vm/instructions/environment.py @@ -341,10 +341,12 @@ def extcodesize(evm: Evm) -> None: # GAS if address in evm.accessed_addresses: - charge_gas(evm, GasCosts.WARM_ACCESS) + access_gas_cost = GasCosts.WARM_ACCESS else: evm.accessed_addresses.add(address) - charge_gas(evm, GasCosts.COLD_ACCOUNT_ACCESS) + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS + access_gas_cost += GasCosts.WARM_ACCESS # Code reading cost (EIP-8038) + charge_gas(evm, access_gas_cost) # OPERATION tx_state = evm.message.tx_env.state @@ -386,6 +388,7 @@ def extcodecopy(evm: Evm) -> None: else: evm.accessed_addresses.add(address) access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS + access_gas_cost += GasCosts.WARM_ACCESS # Code reading cost (EIP-8038) total_gas_cost = access_gas_cost + copy_gas_cost + extend_memory.cost diff --git a/src/ethereum/forks/amsterdam/vm/instructions/storage.py b/src/ethereum/forks/amsterdam/vm/instructions/storage.py index 4e864b8ec71..91aec91163d 100644 --- a/src/ethereum/forks/amsterdam/vm/instructions/storage.py +++ b/src/ethereum/forks/amsterdam/vm/instructions/storage.py @@ -93,17 +93,17 @@ def sstore(evm: Evm) -> None: gas_cost = Uint(0) state_gas = StateGas(Uint(0)) + # Access cost: cold or warm, always charged. if (evm.message.current_target, key) not in evm.accessed_storage_keys: evm.accessed_storage_keys.add((evm.message.current_target, key)) gas_cost += GasCosts.COLD_STORAGE_ACCESS - - if original_value == current_value and current_value != new_value: - # charge regular cost for the operation, even when we - # already charge state gas for state creation - gas_cost += GasCosts.COLD_STORAGE_WRITE - GasCosts.COLD_STORAGE_ACCESS else: gas_cost += GasCosts.WARM_ACCESS + # Write cost: charged on the first change to the slot this transaction. + if original_value == current_value and current_value != new_value: + gas_cost += GasCosts.STORAGE_WRITE + # Refund Counter Calculation if current_value != new_value: if original_value != 0 and current_value != 0 and new_value == 0: @@ -115,12 +115,9 @@ def sstore(evm: Evm) -> None: evm.refund_counter -= GasCosts.REFUND_STORAGE_CLEAR if original_value == new_value: - # Storage slot being restored to its original value - evm.refund_counter += int( - GasCosts.COLD_STORAGE_WRITE - - GasCosts.COLD_STORAGE_ACCESS - - GasCosts.WARM_ACCESS - ) + # Slot restored to its original value: refund the STORAGE_WRITE + # charged on the first-time change earlier this transaction. + evm.refund_counter += int(GasCosts.STORAGE_WRITE) if original_value == current_value and current_value != new_value: if original_value == 0: @@ -157,7 +154,7 @@ def tload(evm: Evm) -> None: key = pop(evm.stack).to_be_bytes32() # GAS - charge_gas(evm, GasCosts.WARM_ACCESS) + charge_gas(evm, GasCosts.OPCODE_TLOAD) # OPERATION value = get_transient_storage( @@ -187,7 +184,7 @@ def tstore(evm: Evm) -> None: new_value = pop(evm.stack) # GAS - charge_gas(evm, GasCosts.WARM_ACCESS) + charge_gas(evm, GasCosts.OPCODE_TSTORE) set_transient_storage( evm.message.tx_env.state, evm.message.current_target, diff --git a/src/ethereum/forks/amsterdam/vm/instructions/system.py b/src/ethereum/forks/amsterdam/vm/instructions/system.py index 185bc277fd5..a3587e4f238 100644 --- a/src/ethereum/forks/amsterdam/vm/instructions/system.py +++ b/src/ethereum/forks/amsterdam/vm/instructions/system.py @@ -194,7 +194,7 @@ def create(evm: Evm) -> None: init_code_gas = init_code_cost(Uint(memory_size)) charge_gas( evm, - GasCosts.REGULAR_GAS_CREATE + extend_memory.cost + init_code_gas, + GasCosts.CREATE_ACCESS + extend_memory.cost + init_code_gas, ) # OPERATION @@ -248,7 +248,7 @@ def create2(evm: Evm) -> None: init_code_gas = init_code_cost(Uint(memory_size)) charge_gas( evm, - GasCosts.REGULAR_GAS_CREATE + GasCosts.CREATE_ACCESS + GasCosts.OPCODE_KECCAK256_PER_WORD * call_data_words + extend_memory.cost + init_code_gas, @@ -668,16 +668,18 @@ def selfdestruct(evm: Evm) -> None: evm.accessed_addresses.add(beneficiary) state_gas = StateGas(Uint(0)) + account_write_gas = Uint(0) if ( not is_account_alive(tx_state, beneficiary) and get_account(tx_state, evm.message.current_target).balance != 0 ): state_gas = StateGasCosts.NEW_ACCOUNT + account_write_gas = GasCosts.ACCOUNT_WRITE # Charge regular gas before state gas so that a regular-gas OOG # does not consume state gas that would inflate the parent's # reservoir on frame failure. - charge_gas(evm, gas_cost) + charge_gas(evm, gas_cost + account_write_gas) charge_state_gas(evm, state_gas) originator = evm.message.current_target diff --git a/src/ethereum/forks/amsterdam/vm/interpreter.py b/src/ethereum/forks/amsterdam/vm/interpreter.py index 5df1dccc8a0..921873a06bd 100644 --- a/src/ethereum/forks/amsterdam/vm/interpreter.py +++ b/src/ethereum/forks/amsterdam/vm/interpreter.py @@ -154,12 +154,13 @@ def process_message_call(message: Message) -> MessageCallOutput: ) else: if message.tx_env.authorizations != (): - state_refund += set_delegation(message) + auth_state_refund, auth_regular_refund = set_delegation(message) + state_refund += auth_state_refund + refund_counter += U256(auth_regular_refund) delegated_address = get_delegated_code_address(message.code) if delegated_address is not None: message.disable_precompiles = True - message.accessed_addresses.add(delegated_address) message.code = get_code( tx_state, get_account(tx_state, delegated_address).code_hash, @@ -309,20 +310,36 @@ def process_message(message: Message) -> Evm: snapshot = copy_tx_state(tx_state) - if message.should_transfer_value and message.value != 0: - move_ether( - tx_state, - message.caller, - message.current_target, - message.value, - ) - if message.caller != message.current_target: - emit_transfer_log( - evm, message.caller, message.current_target, message.value - ) - # Execute message code and handle errors try: + if message.depth == Uint(0) and message.target != Bytes0(b""): + recipient = message.current_target + if message.value > U256(0) and not is_account_alive( + tx_state, recipient + ): + charge_state_gas(evm, StateGasCosts.NEW_ACCOUNT) + recipient_code = get_code( + tx_state, get_account(tx_state, recipient).code_hash + ) + delegated_address = get_delegated_code_address(recipient_code) + if delegated_address is not None: + charge_gas(evm, GasCosts.COLD_ACCOUNT_ACCESS) + evm.accessed_addresses.add(delegated_address) + + if message.should_transfer_value and message.value != 0: + move_ether( + tx_state, + message.caller, + message.current_target, + message.value, + ) + if message.caller != message.current_target: + emit_transfer_log( + evm, + message.caller, + message.current_target, + message.value, + ) if evm.message.code_address in PRE_COMPILED_CONTRACTS: if not message.disable_precompiles: evm_trace(evm, PrecompileStart(evm.message.code_address)) diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/__init__.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/__init__.py new file mode 100644 index 00000000000..85942b2be4c --- /dev/null +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/__init__.py @@ -0,0 +1 @@ +"""Tests for [EIP-2780: Resource-based intrinsic transaction gas](https://eips.ethereum.org/EIPS/eip-2780).""" diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/helpers.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/helpers.py new file mode 100644 index 00000000000..4c90175c2a5 --- /dev/null +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/helpers.py @@ -0,0 +1,43 @@ +"""Shared helpers for EIP-2780 tests.""" + +from execution_testing import Address, Alloc, Op, RecipientType + +from ...prague.eip7702_set_code_tx.spec import Spec as Spec7702 + +EOA_INITIAL_BALANCE = 100 + +RECIPIENT_TYPES_NON_CREATE = [ + RecipientType.EOA, + RecipientType.CONTRACT, + RecipientType.EMPTY_ACCOUNT, + RecipientType.SELF, + RecipientType.DELEGATION_7702, +] + + +def setup_target( + pre: Alloc, recipient_type: RecipientType, sender: Address +) -> Address: + """ + Allocate a target account matching the given recipient type. + + ``EOA`` targets are pre-funded to ``EOA_INITIAL_BALANCE`` so that + post-state balance assertions distinguish a successful value + transfer from a no-op. + """ + match recipient_type: + case RecipientType.EOA: + return pre.fund_eoa(amount=EOA_INITIAL_BALANCE) + case RecipientType.CONTRACT: + return pre.deploy_contract(code=Op.STOP) + case RecipientType.EMPTY_ACCOUNT: + return pre.nonexistent_account() + case RecipientType.SELF: + return sender + case RecipientType.DELEGATION_7702: + delegated_to = pre.deploy_contract(code=Op.STOP) + return pre.deploy_contract( + code=Spec7702.delegation_designation(delegated_to) + ) + case _: + raise ValueError(f"Unsupported recipient type {recipient_type}") diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/spec.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/spec.py new file mode 100644 index 00000000000..e6fcb6bb528 --- /dev/null +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/spec.py @@ -0,0 +1,17 @@ +"""Reference spec for [EIP-2780: Resource-based intrinsic transaction gas.](https://eips.ethereum.org/EIPS/eip-2780).""" + +from dataclasses import dataclass + + +@dataclass(frozen=True) +class ReferenceSpec: + """Reference specification.""" + + git_path: str + version: str + + +ref_spec_2780 = ReferenceSpec( + git_path="EIPS/eip-2780.md", + version="992074053f12f24fed9e6d6bf6099d3a44707dca", +) diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_calldata_floor.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_calldata_floor.py new file mode 100644 index 00000000000..0907cf64599 --- /dev/null +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_calldata_floor.py @@ -0,0 +1,149 @@ +"""EIP-2780 interaction with the EIP-7623/7976 calldata floor.""" + +import pytest +from execution_testing import ( + Account, + Address, + Alloc, + Bytes, + Fork, + RecipientType, + StateTestFiller, + Transaction, + TransactionException, +) + +from ...prague.eip7623_increase_calldata_cost.helpers import ( + find_floor_cost_threshold, +) +from .helpers import EOA_INITIAL_BALANCE +from .spec import ref_spec_2780 + +REFERENCE_SPEC_GIT_PATH = ref_spec_2780.git_path +REFERENCE_SPEC_VERSION = ref_spec_2780.version + +pytestmark = pytest.mark.valid_from("Amsterdam") + + +def _floor_dominating_calldata(fork: Fork) -> Bytes: + """ + Return zero-byte calldata sized so its calldata floor strictly + exceeds the decomposed value-transfer intrinsic for a non-create + call to an existing EOA. + + Reuses the shared EIP-7623 ``find_floor_cost_threshold`` binary + search against this transaction shape, then steps one byte past the + threshold (the last size where the floor does not yet dominate) so + the floor strictly binds. + """ + intrinsic_calc = fork.transaction_intrinsic_cost_calculator() + floor_calc = fork.transaction_data_floor_cost_calculator() + + def intrinsic(byte_count: int) -> int: + return intrinsic_calc( + calldata=b"\x00" * byte_count, + sends_value=True, + recipient_type=RecipientType.EOA, + return_cost_deducted_prior_execution=True, + ) + + def floor(byte_count: int) -> int: + return floor_calc(data=b"\x00" * byte_count) + + threshold = find_floor_cost_threshold( + floor_data_gas_cost_calculator=floor, + intrinsic_gas_cost_calculator=intrinsic, + ) + byte_count = threshold + 1 + + assert floor(byte_count) > intrinsic(byte_count) + return Bytes(b"\x00" * byte_count) + + +@pytest.mark.parametrize( + "gas_modifier", + [ + pytest.param(0, id="at_floor"), + pytest.param( + -1, + id="below_floor", + marks=pytest.mark.exception_test, + ), + ], +) +@pytest.mark.parametrize( + "value", + [ + pytest.param(0, id="zero_value"), + pytest.param(1, id="non-zero_value"), + ], +) +def test_calldata_floor( + fork: Fork, + pre: Alloc, + state_test: StateTestFiller, + gas_modifier: int, + value: int, +) -> None: + """ + A data-heavy transaction to an existing EOA whose calldata floor + exceeds the decomposed value-transfer intrinsic. + + - ``at_floor``: with a gas limit exactly at the floor, ``gas_used`` + pins to the floor, so the value-transfer charges + (``TRANSFER_LOG_COST + TX_VALUE_COST``) folded into the intrinsic + ``value == 1`` and only the moved wei differs. + - ``below_floor``: a gas limit one short of the floor still covers + the (smaller) decomposed intrinsic, so the floor -- built on the + EIP-2780-lowered ``TX_BASE`` -- is the only thing that can reject + it, with ``INTRINSIC_GAS_BELOW_FLOOR_GAS_COST``. + """ + sender_initial_balance = 10**18 + sender = pre.fund_eoa(sender_initial_balance) + target = pre.fund_eoa(amount=EOA_INITIAL_BALANCE) + + calldata = _floor_dominating_calldata(fork) + calldata_floor = fork.transaction_data_floor_cost_calculator()( + data=calldata, + ) + gas_price = 1_000_000_000 + + post: dict[Address, Account] = {} + gas_limit = calldata_floor + gas_modifier + # Even at the reduced limit the decomposed intrinsic is still + # covered, so the calldata floor is the sole gate on the + # transaction. + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( + calldata=calldata, + sends_value=bool(value), + recipient_type=RecipientType.EOA, + return_cost_deducted_prior_execution=True, + ) + assert intrinsic_gas <= gas_limit, ( + "gas_limit must still cover the decomposed intrinsic so the " + "outcome is pinned to the calldata floor" + ) + + tx = Transaction( + sender=sender, + to=target, + value=value, + data=calldata, + gas_limit=gas_limit, + gas_price=gas_price, + error=( + TransactionException.INTRINSIC_GAS_BELOW_FLOOR_GAS_COST + if gas_modifier < 0 + else None + ), + ) + if gas_modifier == 0: + sender_final_balance = ( + sender_initial_balance - value - calldata_floor * gas_price + ) + post = { + sender: Account(nonce=1, balance=sender_final_balance), + target: Account(balance=EOA_INITIAL_BALANCE + value), + } + + state_test(pre=pre, tx=tx, post=post) diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_fork_transition.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_fork_transition.py new file mode 100644 index 00000000000..d94031fe34b --- /dev/null +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_fork_transition.py @@ -0,0 +1,154 @@ +""" +Fork-transition tests for EIP-2780. + +EIP-2780 reshapes the intrinsic transaction cost at the Amsterdam fork +boundary. These tests send identical transactions in a pre-fork block +and a post-fork block (straddling the transition timestamp) and assert +that the per-transaction gas paid changes by the EIP-2780 amount only +once the fork activates. + +For these shapes the post-fork intrinsic decomposes from the flat +pre-fork ``TX_BASE`` of 21_000 as follows: + +- A plain call to an existing account drops to ``TX_BASE`` (12_000) + plus the new ``COLD_ACCOUNT_ACCESS`` recipient charge; adding value + re-raises it to exactly 21_000 (the value-transfer cost is invariant + across the fork by design). +- A self-transfer is fully carved out post-fork: it pays only the + lowered ``TX_BASE`` with no recipient or value-transfer charge, + regardless of value, the largest reduction. +""" + +import pytest +from execution_testing import ( + Account, + Address, + Alloc, + Block, + BlockchainTestFiller, + RecipientType, + Transaction, + TransitionFork, +) + +from .helpers import EOA_INITIAL_BALANCE +from .spec import ref_spec_2780 + +REFERENCE_SPEC_GIT_PATH = ref_spec_2780.git_path +REFERENCE_SPEC_VERSION = ref_spec_2780.version + +pytestmark = pytest.mark.valid_at_transition_to("Amsterdam") + +# Transition forks switch at timestamp 15_000. +PRE_FORK_TIMESTAMP = 14_999 +POST_FORK_TIMESTAMP = 15_000 + + +@pytest.mark.parametrize( + "self_transfer", + [ + pytest.param(False, id="plain_call"), + pytest.param(True, id="self_transfer"), + ], +) +@pytest.mark.parametrize( + "value", + [ + pytest.param(0, id="zero_value"), + pytest.param(1, id="non-zero_value"), + ], +) +def test_intrinsic_reduction_across_amsterdam_transition( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: TransitionFork, + self_transfer: bool, + value: int, +) -> None: + """ + Pin the EIP-2780 intrinsic change across the Amsterdam boundary. + + The same transaction shape is sent in a pre-fork block (Osaka + rules, flat 21_000 intrinsic) and a post-fork block (Amsterdam + rules, decomposed intrinsic). Each block uses a distinct sender so + its post-tx balance pins the fork-appropriate intrinsic; the + recipient is an existing EOA (or the sender itself for + ``self_transfer``), so neither block runs EVM bytecode and + ``gas_used`` equals the intrinsic exactly. + + The per-fork intrinsic returned by the calculator is also checked + against a hand-derived decomposition built from each fork's gas + constants, so a calculator regression fails here with a clear + message rather than only as a downstream balance mismatch. + """ + gas_price = 1_000_000_000 + recipient_type = RecipientType.SELF if self_transfer else RecipientType.EOA + + pre_fork = fork.fork_at(timestamp=PRE_FORK_TIMESTAMP) + post_fork = fork.fork_at(timestamp=POST_FORK_TIMESTAMP) + + # Pre-fork: flat ``TX_BASE`` regardless of recipient kind or value. + expected_pre = pre_fork.gas_costs().TX_BASE + # Post-fork: EIP-2780 decomposition. Self-transfers are fully + # carved out; other recipients pay the recipient access charge plus + # the value-transfer charges when value is moved. + post_gas_costs = post_fork.gas_costs() + expected_post = post_gas_costs.TX_BASE + if not self_transfer: + expected_post += post_gas_costs.COLD_ACCOUNT_ACCESS + if value: + expected_post += ( + post_gas_costs.TRANSFER_LOG_COST + post_gas_costs.TX_VALUE_COST + ) + + timestamps = [PRE_FORK_TIMESTAMP, POST_FORK_TIMESTAMP] + expected_intrinsics = [expected_pre, expected_post] + blocks = [] + post: dict[Address, Account] = {} + + for timestamp, expected_intrinsic in zip( + timestamps, expected_intrinsics, strict=True + ): + sub_fork = fork.fork_at(timestamp=timestamp) + intrinsic_gas = sub_fork.transaction_intrinsic_cost_calculator()( + sends_value=bool(value), + recipient_type=recipient_type, + return_cost_deducted_prior_execution=True, + ) + assert intrinsic_gas == expected_intrinsic, ( + f"intrinsic at timestamp {timestamp} ({sub_fork}) is " + f"{intrinsic_gas}, expected {expected_intrinsic}" + ) + + sender_initial_balance = 10**18 + sender = pre.fund_eoa(sender_initial_balance) + if self_transfer: + target = sender + else: + target = pre.fund_eoa(amount=EOA_INITIAL_BALANCE) + + # No EVM bytecode runs (recipient is an EOA or the sender), so + # gas_used == intrinsic_gas; the gas limit is pinned to exactly + # the intrinsic, leaving no buffer. + tx = Transaction( + sender=sender, + to=target, + value=value, + gas_limit=intrinsic_gas, + gas_price=gas_price, + ) + blocks.append(Block(timestamp=timestamp, txs=[tx])) + + # A self-transfer returns the value to the sender (net zero); + # a plain call moves ``value`` to the distinct recipient. + sender_value_delta = 0 if self_transfer else value + sender_final_balance = ( + sender_initial_balance + - sender_value_delta + - intrinsic_gas * gas_price + ) + post[sender] = Account(nonce=1, balance=sender_final_balance) + if not self_transfer: + post[target] = Account(balance=EOA_INITIAL_BALANCE + value) + + blockchain_test(pre=pre, blocks=blocks, post=post) diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_intrinsic_gas_boundary.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_intrinsic_gas_boundary.py new file mode 100644 index 00000000000..e62a93c8999 --- /dev/null +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_intrinsic_gas_boundary.py @@ -0,0 +1,115 @@ +""" +Gas-limit boundary tests for EIP-2780. + +Pin transactions one gas below the intrinsic charge layer to verify the +transaction is rejected by the pre-execution intrinsic gas check at +that boundary. Top-frame boundary OOGs are covered by the dedicated +top-frame charge tests in ``test_top_frame_charges.py``. +""" + +import pytest +from execution_testing import ( + Alloc, + Fork, + Op, + RecipientType, + StateTestFiller, + Transaction, + TransactionException, +) + +from .helpers import RECIPIENT_TYPES_NON_CREATE, setup_target +from .spec import ref_spec_2780 + +REFERENCE_SPEC_GIT_PATH = ref_spec_2780.git_path +REFERENCE_SPEC_VERSION = ref_spec_2780.version + +pytestmark = pytest.mark.valid_from("Amsterdam") + + +@pytest.mark.exception_test +@pytest.mark.parametrize("recipient_type", RECIPIENT_TYPES_NON_CREATE) +@pytest.mark.parametrize( + "value", + [ + pytest.param(0, id="zero_value"), + pytest.param(1, id="non-zero_value"), + ], +) +def test_intrinsic_gas_floor_boundary( + fork: Fork, + pre: Alloc, + state_test: StateTestFiller, + recipient_type: RecipientType, + value: int, +) -> None: + """ + Reject when ``gas_limit = intrinsic_gas - 1``. + + The transaction never enters the EVM; it is rejected by the + pre-execution intrinsic gas check. + """ + sender = pre.fund_eoa(10**18) + target = setup_target(pre, recipient_type, sender) + + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( + sends_value=bool(value), + recipient_type=recipient_type, + return_cost_deducted_prior_execution=True, + ) + + tx = Transaction( + sender=sender, + to=target, + value=value, + gas_limit=intrinsic_gas - 1, + gas_price=1_000_000_000, + error=TransactionException.INTRINSIC_GAS_TOO_LOW, + ) + + state_test(pre=pre, tx=tx, post={}) + + +@pytest.mark.exception_test +@pytest.mark.parametrize( + "value", + [ + pytest.param(0, id="zero_value"), + pytest.param(1, id="non-zero_value"), + ], +) +def test_intrinsic_gas_floor_boundary_contract_creation( + fork: Fork, + pre: Alloc, + state_test: StateTestFiller, + value: int, +) -> None: + """ + Reject a contract-creation transaction when + ``gas_limit = intrinsic_gas - 1``. + + A creation tx's intrinsic includes the ``NEW_ACCOUNT`` state gas, so + the pre-execution check rejects against the combined + ``regular + state`` intrinsic. The init code never runs. + """ + sender = pre.fund_eoa(10**18) + init_code = Op.STOP + + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( + calldata=init_code, + contract_creation=True, + sends_value=bool(value), + return_cost_deducted_prior_execution=True, + ) + + tx = Transaction( + sender=sender, + to=None, + value=value, + data=init_code, + gas_limit=intrinsic_gas - 1, + gas_price=1_000_000_000, + error=TransactionException.INTRINSIC_GAS_TOO_LOW, + ) + + state_test(pre=pre, tx=tx, post={}) diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_top_frame_charges.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_top_frame_charges.py new file mode 100644 index 00000000000..2af1538f9c2 --- /dev/null +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_top_frame_charges.py @@ -0,0 +1,393 @@ +""" +Dedicated tests for the EIP-2780 top-frame charge layer. + +The top-frame layer applies *after* intrinsic gas is deducted but +*before* the EVM dispatches at the transaction's outermost frame. Two +charges may fire there, depending on the recipient: + +- ``NEW_ACCOUNT`` (state gas) when the recipient is empty and the + transaction transfers value. +- ``COLD_ACCOUNT_ACCESS`` (regular gas) when the recipient holds an + EIP-7702 delegation. + +Each test parametrizes over the interesting outcomes for that charge: +running out of gas at the boundary, succeeding through the charge and +into the EVM, and (for the regular charge) succeeding through the +charge but reverting from the delegated code. +""" + +import pytest +from execution_testing import ( + Account, + Address, + Alloc, + Block, + BlockchainTestFiller, + Fork, + Header, + Op, + RecipientType, + StateTestFiller, + Transaction, +) + +from ...prague.eip7702_set_code_tx.spec import Spec as Spec7702 +from .spec import ref_spec_2780 + +REFERENCE_SPEC_GIT_PATH = ref_spec_2780.git_path +REFERENCE_SPEC_VERSION = ref_spec_2780.version + +pytestmark = pytest.mark.valid_from("Amsterdam") + + +@pytest.mark.parametrize("outcome", ["oog", "success"]) +def test_top_frame_state_charge( + fork: Fork, + pre: Alloc, + state_test: StateTestFiller, + outcome: str, +) -> None: + """ + Recipient is empty and the transaction transfers a non-zero value, + so the top-frame fires the ``NEW_ACCOUNT`` state-gas charge. + + - ``oog``: gas limit is one short of covering the state charge. + The transaction passes the intrinsic check, enters + ``process_message``, and out-of-gases on + ``charge_state_gas(NEW_ACCOUNT)`` before any EVM bytecode runs. + The sender pays the full ``gas_limit`` and no value is + transferred. + - ``success``: gas limit covers the state charge. The value + transfer brings the recipient into existence and the recipient + ends the transaction holding the transferred balance. + """ + sender_initial_balance = 10**18 + sender = pre.fund_eoa(sender_initial_balance) + target = pre.fund_eoa(amount=0) + + value = 1 + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( + sends_value=True, + recipient_type=RecipientType.EMPTY_ACCOUNT, + return_cost_deducted_prior_execution=True, + ) + top_frame_state_gas = fork.transaction_top_frame_state_gas( + sends_value=True, + recipient_type=RecipientType.EMPTY_ACCOUNT, + ) + assert top_frame_state_gas > 0, ( + "top-frame state gas must be non-zero for this scenario" + ) + + gas_price = 1_000_000_000 + if outcome == "oog": + gas_limit = intrinsic_gas + top_frame_state_gas - 1 + sender_final_balance = sender_initial_balance - gas_limit * gas_price + expected_target: Account | None = None + else: + total_gas_cost = intrinsic_gas + top_frame_state_gas + gas_limit = total_gas_cost + 1000 + sender_final_balance = ( + sender_initial_balance - value - total_gas_cost * gas_price + ) + expected_target = Account(balance=value) + + tx = Transaction( + sender=sender, + to=target, + value=value, + gas_limit=gas_limit, + gas_price=gas_price, + ) + + post = { + sender: Account(nonce=1, balance=sender_final_balance), + target: expected_target, + } + + state_test(pre=pre, tx=tx, post=post) + + +def test_top_frame_state_charge_empty_precompile( + fork: Fork, + pre: Alloc, + state_test: StateTestFiller, +) -> None: + """ + An empty precompile recipient is still empty per EIP-161, so a + value-moving transaction to it must pay the top-frame + ``NEW_ACCOUNT`` state-gas charge. + + The gas limit is one short of covering that state charge. Without + the charge, the transaction would reach the identity precompile and + transfer value, which makes this a direct regression test for a + precompile carve-out. + """ + sender_initial_balance = 10**18 + sender = pre.fund_eoa(sender_initial_balance) + identity_precompile = Address(0x04) + + value = 1 + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( + sends_value=True, + recipient_type=RecipientType.PRECOMPILE, + return_cost_deducted_prior_execution=True, + ) + top_frame_state_gas = fork.transaction_top_frame_state_gas( + sends_value=True, + recipient_type=RecipientType.EMPTY_ACCOUNT, + ) + assert top_frame_state_gas > 0, ( + "top-frame state gas must be non-zero for empty recipients" + ) + + gas_price = 1_000_000_000 + gas_limit = intrinsic_gas + top_frame_state_gas - 1 + tx = Transaction( + sender=sender, + to=identity_precompile, + value=value, + gas_limit=gas_limit, + gas_price=gas_price, + ) + + post = { + sender: Account( + nonce=1, + balance=sender_initial_balance - gas_limit * gas_price, + ), + identity_precompile: None, + } + + state_test(pre=pre, tx=tx, post=post) + + +def test_top_frame_new_account_charged_as_state_gas( + fork: Fork, + pre: Alloc, + blockchain_test: BlockchainTestFiller, +) -> None: + """ + The top-frame ``NEW_ACCOUNT`` charge for a value transfer to an + empty recipient is *state* gas, not regular gas. This pins the + dimension via the block header ``gas_used``, which the spec + computes as ``max(block_regular_gas, block_state_gas)``. + + Correctly attributed, the ``NEW_ACCOUNT`` state gas dominates the + small regular intrinsic, so ``gas_used == NEW_ACCOUNT``. A + regression mis-classifying the charge as regular gas would instead + yield ``intrinsic_regular + NEW_ACCOUNT``. + + ``state_test``-based balance assertions (e.g. + ``test_top_frame_state_charge``) only observe the *sum* of the two + dimensions, so they cannot distinguish this; a block-level + ``gas_used`` assertion is required. + """ + sender = pre.fund_eoa(10**18) + target = pre.fund_eoa(amount=0) + value = 1 + + intrinsic_regular = fork.transaction_intrinsic_cost_calculator()( + sends_value=True, + recipient_type=RecipientType.EMPTY_ACCOUNT, + return_cost_deducted_prior_execution=True, + ) + new_account_state_gas = fork.transaction_top_frame_state_gas( + sends_value=True, + recipient_type=RecipientType.EMPTY_ACCOUNT, + ) + # The state charge must dominate the regular intrinsic for the + # header ``gas_used`` to distinguish a state vs regular + # mis-classification. + assert new_account_state_gas > intrinsic_regular, ( + "test only distinguishes the dimension when NEW_ACCOUNT " + f"({new_account_state_gas}) dominates the regular intrinsic " + f"({intrinsic_regular})" + ) + + # No EVM bytecode runs (empty recipient), so the only regular gas + # is the intrinsic and the only state gas is the top-frame + # ``NEW_ACCOUNT`` charge. + expected_gas_used = max(intrinsic_regular, new_account_state_gas) + + gas_price = 1_000_000_000 + tx = Transaction( + sender=sender, + to=target, + value=value, + gas_limit=intrinsic_regular + new_account_state_gas + 1000, + gas_price=gas_price, + ) + + blockchain_test( + pre=pre, + blocks=[ + Block( + txs=[tx], + header_verify=Header(gas_used=expected_gas_used), + ), + ], + post={ + sender: Account(nonce=1), + target: Account(balance=value), + }, + ) + + +@pytest.mark.pre_alloc_mutable +def test_top_frame_new_account_skipped_for_nonce_only_recipient( + fork: Fork, + pre: Alloc, + state_test: StateTestFiller, +) -> None: + """ + A recipient that is alive only by its nonce (``nonce=1``, zero + balance, no code) is not empty per EIP-161, so a value transfer to + it does *not* incur the top-frame ``NEW_ACCOUNT`` charge. This pins + that the gate keys on ``is_account_alive``, not ``balance == 0``. + + Such an account is reachable on-chain: any EOA that has sent a + transaction (nonce bumped) and been fully drained sits at + ``nonce>0, balance=0, no code``. + + The gas limit is pinned to exactly the intrinsic, leaving no room + for any extra charge: an implementation that wrongly charged + ``NEW_ACCOUNT`` (keying on the zero balance) would out-of-gas + rather than succeed. The recipient has no code, so no EVM runs and + the intrinsic is fully consumed with nothing to refund. + """ + sender_initial_balance = 10**18 + sender = pre.fund_eoa(sender_initial_balance) + # Alive via nonce only: not empty per EIP-161 because nonce != 0. + target = pre.fund_eoa(amount=0, nonce=1) + value = 1 + + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( + sends_value=True, + recipient_type=RecipientType.EOA, + return_cost_deducted_prior_execution=True, + ) + top_frame_state_gas = fork.transaction_top_frame_state_gas( + sends_value=True, + recipient_type=RecipientType.EOA, + ) + assert top_frame_state_gas == 0, ( + "a nonce-only-alive recipient must not incur the NEW_ACCOUNT charge" + ) + + gas_price = 1_000_000_000 + gas_limit = intrinsic_gas + tx = Transaction( + sender=sender, + to=target, + value=value, + gas_limit=gas_limit, + gas_price=gas_price, + ) + + sender_final_balance = ( + sender_initial_balance - value - intrinsic_gas * gas_price + ) + post = { + sender: Account(nonce=1, balance=sender_final_balance), + target: Account(nonce=1, balance=value), + } + + state_test(pre=pre, tx=tx, post=post) + + +@pytest.mark.parametrize("outcome", ["oog", "success", "evm_reverts"]) +@pytest.mark.parametrize( + "value", + [ + pytest.param(0, id="zero_value"), + pytest.param(1, id="non-zero_value"), + ], +) +def test_top_frame_regular_charge( + fork: Fork, + pre: Alloc, + state_test: StateTestFiller, + outcome: str, + value: int, +) -> None: + """ + Recipient is an existing EIP-7702 delegation, so the top-frame + fires the ``COLD_ACCOUNT_ACCESS`` regular-gas charge regardless of + whether the transaction transfers value. + + - ``oog``: gas limit is one short of covering the regular charge + (plus the value-transfer charge when ``value > 0``). The + transaction OOGs at ``charge_gas(COLD_ACCOUNT_ACCESS)`` before + the delegated code runs. The sender pays the full ``gas_limit`` + and the recipient keeps its pre-tx state. + - ``success``: gas limit covers the regular charge; the delegated + code is a ``STOP`` and the transaction lands the value transfer. + - ``evm_reverts``: the delegated code reverts immediately. The + top-frame charge is consumed before dispatch and the two + ``PUSH`` opcodes that feed the ``REVERT`` are paid before the + revert; the value transfer is rolled back, the unused EVM + budget is returned, and the intrinsic and top-frame gas remain + paid. + """ + sender_initial_balance = 10**18 + sender = pre.fund_eoa(sender_initial_balance) + + revert_code = Op.REVERT(0, 0) + if outcome == "evm_reverts": + delegated_to = pre.deploy_contract(code=revert_code) + else: + delegated_to = pre.deploy_contract(code=Op.STOP) + target_code = Spec7702.delegation_designation(delegated_to) + target = pre.deploy_contract(code=target_code) + + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( + sends_value=bool(value), + recipient_type=RecipientType.DELEGATION_7702, + return_cost_deducted_prior_execution=True, + ) + top_frame_gas = fork.transaction_top_frame_gas_calculator()( + sends_value=bool(value), + recipient_type=RecipientType.DELEGATION_7702, + ) + assert top_frame_gas > 0, ( + "top-frame regular gas must be non-zero for this scenario" + ) + + gas_price = 1_000_000_000 + if outcome == "oog": + gas_limit = intrinsic_gas + top_frame_gas - 1 + sender_final_balance = sender_initial_balance - gas_limit * gas_price + target_balance = 0 + elif outcome == "success": + total_gas_cost = intrinsic_gas + top_frame_gas + gas_limit = total_gas_cost + 1000 + sender_final_balance = ( + sender_initial_balance - value - total_gas_cost * gas_price + ) + target_balance = value + else: + # Two ``PUSH`` opcodes feed ``REVERT`` before it halts. + revert_exec_gas = revert_code.gas_cost(fork) + gas_used = intrinsic_gas + top_frame_gas + revert_exec_gas + gas_limit = gas_used + 1000 + # Value transfer is rolled back, so the sender keeps the + # would-be transferred value. The intrinsic, top-frame, and + # pre-revert EVM gas stay paid. + sender_final_balance = sender_initial_balance - gas_used * gas_price + target_balance = 0 + + tx = Transaction( + sender=sender, + to=target, + value=value, + gas_limit=gas_limit, + gas_price=gas_price, + ) + + post = { + sender: Account(nonce=1, balance=sender_final_balance), + target: Account(balance=target_balance, code=target_code), + } + + state_test(pre=pre, tx=tx, post=post) diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_value_moving_transactions.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_value_moving_transactions.py new file mode 100644 index 00000000000..0a7533cbe79 --- /dev/null +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_value_moving_transactions.py @@ -0,0 +1,414 @@ +""" +Tests for EIP-2780 Reduce Transaction Intrinsic Cost. + +Test gas costs with EIP-2780 for value-moving transactions to: +- EOAs, +- contracts, +- empty accounts, +- the sender itself, +- delegated EOAs, +- newly created contracts, and +- precompiles. +""" + +import pytest +from execution_testing import ( + Account, + Address, + Alloc, + Fork, + Initcode, + Op, + RecipientType, + StateTestFiller, + Transaction, + TransactionReceipt, + compute_create_address, +) + +from ..eip7708_eth_transfer_logs.spec import transfer_log +from .helpers import ( + EOA_INITIAL_BALANCE, + RECIPIENT_TYPES_NON_CREATE, + setup_target, +) +from .spec import ref_spec_2780 + +REFERENCE_SPEC_GIT_PATH = ref_spec_2780.git_path +REFERENCE_SPEC_VERSION = ref_spec_2780.version + +pytestmark = pytest.mark.valid_from("Amsterdam") + + +@pytest.mark.parametrize("recipient_type", RECIPIENT_TYPES_NON_CREATE) +@pytest.mark.parametrize( + "value", + [ + pytest.param(0, id="zero_value"), + pytest.param(1, id="non-zero_value"), + ], +) +def test_value_moving_transactions( + fork: Fork, + pre: Alloc, + state_test: StateTestFiller, + recipient_type: RecipientType, + value: int, +) -> None: + """ + Ensure value-moving transactions charge gas correctly across every + non-create recipient type. + + Self-transfers are carved out: the sender pays only the recipient + -access-free intrinsic and the value is moved to itself, so the + sender's post-tx balance reflects only gas. Pre-existing 7702 + delegations on the recipient surface as an extra top-frame + ``COLD_ACCOUNT_ACCESS``; empty recipients trigger the top-frame + ``NEW_ACCOUNT`` state charge when value is transferred. + + The EIP-7708 transfer log is asserted to fire exactly when + ``TRANSFER_LOG_COST`` is charged: for a non-self value transfer, + and never for a self-transfer (carve-out) or a zero-value tx. + """ + sender_initial_balance = 10**18 + sender = pre.fund_eoa(sender_initial_balance) + target = setup_target(pre, recipient_type, sender) + + target_initial_balance = ( + EOA_INITIAL_BALANCE if recipient_type == RecipientType.EOA else 0 + ) + + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( + sends_value=bool(value), + recipient_type=recipient_type, + return_cost_deducted_prior_execution=True, + ) + top_frame_gas = fork.transaction_top_frame_gas_calculator()( + sends_value=bool(value), + recipient_type=recipient_type, + ) + top_frame_state_gas = fork.transaction_top_frame_state_gas( + sends_value=bool(value), + recipient_type=recipient_type, + ) + # Under the default zero state-gas reservoir, top-frame state gas + # spills entirely into regular gas. + total_gas_cost = intrinsic_gas + top_frame_gas + top_frame_state_gas + + tx_gas_limit = total_gas_cost + 1000 # add a small buffer + gas_price = 1_000_000_000 + + is_self_transfer = recipient_type == RecipientType.SELF + + # A transfer log is emitted iff value moves to a distinct account, + # which is exactly when the intrinsic includes ``TRANSFER_LOG_COST``. + # ``logs=[]`` asserts no log fires for the carved-out cases. + if value > 0 and not is_self_transfer: + expected_logs = [transfer_log(sender, target, value)] + else: + expected_logs = [] + + tx = Transaction( + sender=sender, + to=target, + value=value, + gas_limit=tx_gas_limit, + gas_price=gas_price, + expected_receipt=TransactionReceipt(logs=expected_logs), + ) + + sender_value_delta = 0 if is_self_transfer else value + sender_final_balance = ( + sender_initial_balance + - sender_value_delta + - total_gas_cost * gas_price + ) + + post: dict[Address, Account | None] = { + sender: Account(nonce=1, balance=sender_final_balance), + } + if not is_self_transfer: + if recipient_type == RecipientType.EMPTY_ACCOUNT and value == 0: + post[target] = None + else: + post[target] = Account(balance=target_initial_balance + value) + + state_test(pre=pre, tx=tx, post=post) + + +@pytest.mark.parametrize( + "value", + [ + pytest.param(0, id="zero_value"), + pytest.param(1, id="non-zero_value"), + ], +) +@pytest.mark.parametrize( + "tx_reverts", + [ + pytest.param(False, id="success"), + pytest.param(True, id="init_reverts"), + ], +) +def test_value_contract_creation_tx( + fork: Fork, + pre: Alloc, + state_test: StateTestFiller, + tx_reverts: bool, + value: int, +) -> None: + """ + Test value moving contract creation transactions. + + When the init code succeeds, the contract is deployed with the + transferred value and the receipt's ``gas_used`` equals the + intrinsic plus the execution gas. + + When the init code reverts, the deploy is rolled back: no code is + set, the value transfer is reversed, and the intrinsic + ``NEW_ACCOUNT`` state-gas charge is refilled to the reservoir. + Under the default zero state-gas reservoir, the refill cancels + the spilled-to-regular portion of the intrinsic exactly, so the + sender pays only the regular portion of the intrinsic plus the + few EVM gas units spent before the revert. + """ + sender_initial_balance = 10**18 + sender = pre.fund_eoa(sender_initial_balance) + + code_to_deploy = Op.STOP + if tx_reverts: + # ``PUSH1 0 PUSH1 0 REVERT`` -- aborts immediately, so no + # code is deployed. + call_data = Op.REVERT(0, 0) + else: + call_data = Initcode(deploy_code=code_to_deploy) + execution_gas = call_data.gas_cost(fork) + + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( + calldata=call_data, + contract_creation=True, + sends_value=bool(value), + return_cost_deducted_prior_execution=True, + ) + + if tx_reverts: + # The ``NEW_ACCOUNT`` state portion of the intrinsic is + # refilled to the reservoir on revert, so it does not appear + # on the receipt. + new_account_refund = fork.transaction_intrinsic_state_gas( + contract_creation=True, + ) + gas_used = intrinsic_gas + execution_gas - new_account_refund + # Value transfer rolled back. + sender_value_delta = 0 + expected_target = None + else: + gas_used = intrinsic_gas + execution_gas + sender_value_delta = value + expected_target = Account(code=code_to_deploy, balance=value) + + expected_target_address = compute_create_address(address=sender, nonce=0) + + if value > 0 and not tx_reverts: + expected_logs = [transfer_log(sender, expected_target_address, value)] + else: + expected_logs = [] + + gas_price = 1_000_000_000 + gas_limit = intrinsic_gas + execution_gas + 1000 + + tx = Transaction( + sender=sender, + to=None, + value=value, + data=call_data, + gas_limit=gas_limit, + gas_price=gas_price, + expected_receipt=TransactionReceipt(logs=expected_logs), + ) + + sender_final_balance = ( + sender_initial_balance - sender_value_delta - gas_used * gas_price + ) + + post = { + sender: Account(nonce=1, balance=sender_final_balance), + expected_target_address: expected_target, + } + + state_test(pre=pre, tx=tx, post=post) + + +def _precompile_calldata(precompile: Address) -> bytes: + """Return minimal valid calldata for the given precompile address.""" + addr_int = int.from_bytes(precompile, "big") + + if addr_int == 0x0A: + # Valid point evaluation input from mainnet tx: + # https://etherscan.io/tx/0xcb3dc8f3b14f1cda0c16a619a112102a8ec70dce1b3f1b28272227cf8d5fbb0e + return ( + bytes.fromhex( + # versioned_hash (32) + "018156B94FE9735E573BAB36DAD05D60FEB720D424CCD20AAF719343C31E4246" + ) + + bytes.fromhex( + # z (32) + "019123BCB9D06356701F7BE08B4494625B87A7B02EDC566126FB81F6306E915F" + ) + + bytes.fromhex( + # y (32) + "6C2EB1E94C2532935B8465351BA1BD88EABE2B3FA1AADFF7D1CD816E8315BD38" + ) + + bytes.fromhex( + # kzg_commitment (48) + "A9546D41993E10DF2A7429B8490394EA9EE62807BAE6F326D1044A51581306F58D4B9DFD5931E044688855280FF3799E" + ) + + bytes.fromhex( + # kzg_proof (48) + "A2EA83D9391E0EE42E0C650ACC7A1F842A7D385189485DDB4FD54ADE3D9FD50D608167DCA6C776AAD4B8AD5C20691BFE" + ) + ) + + precompile_min_input = { + 0x01: 128, # ECRECOVER + 0x02: 0, # SHA256 (accepts empty) + 0x03: 0, # RIPEMD160 (accepts empty) + 0x04: 0, # IDENTITY (accepts empty) + 0x05: 96, # MODEXP + 0x06: 128, # BN256ADD + 0x07: 96, # BN256MUL + 0x08: 0, # BN256PAIRING (empty is valid) + 0x09: 213, # BLAKE2F + 0x0B: 256, # BLS12_G1_ADD + 0x0C: 160, # BLS12_G1_MSM + 0x0D: 512, # BLS12_G2_ADD + 0x0E: 288, # BLS12_G2_MSM + 0x0F: 384, # BLS12_PAIRING + 0x10: 64, # BLS12_MAP_FP_TO_G1 + 0x11: 128, # BLS12_MAP_FP2_TO_G2 + 0x100: 160, # P256VERIFY + } + + input_size = precompile_min_input.get(addr_int, 0) + return bytes([0x00] * input_size if input_size > 0 else []) + + +@pytest.mark.parametrize( + "value", + [ + pytest.param(0, id="zero_value"), + pytest.param(1, id="non-zero_value"), + ], +) +@pytest.mark.parametrize( + "pre_funded", + [ + pytest.param(True, id="pre_funded"), + pytest.param(False, id="not_funded"), + ], +) +@pytest.mark.with_all_precompiles +def test_value_move_to_precompiles( + fork: Fork, + pre: Alloc, + state_test: StateTestFiller, + precompile: Address, + pre_funded: bool, + value: int, +) -> None: + """ + Ensure value moving transactions to precompiles charge gas correctly. + + Precompile recipients pay the same ``COLD_ACCOUNT_ACCESS`` at + intrinsic time as any other non-self target -- access lists do + not warm transaction-level accounts. A value transfer to a + precompile additionally pays the transfer-log and value-transfer + charges. + + The top-frame ``NEW_ACCOUNT`` state charge keys solely on EIP-161 + emptiness; a precompile address is not special-cased. The + ``pre_funded`` parameter exercises both pre-tx states: + + - ``not_funded``: the precompile address is empty per EIP-161, so a + value transfer creates it and pays ``NEW_ACCOUNT`` -- exactly + like any other empty recipient. + - ``pre_funded``: the precompile already holds a balance and is + therefore alive, so no ``NEW_ACCOUNT`` charge applies. + """ + sender_initial_balance = 10**18 + sender = pre.fund_eoa(sender_initial_balance) + + pre_funded_amount = 0 + if pre_funded: + pre_funded_amount = 1 + pre.fund_address(precompile, amount=pre_funded_amount) + + tx_data = _precompile_calldata(precompile) + + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( + calldata=tx_data, + sends_value=bool(value), + recipient_type=RecipientType.PRECOMPILE, + return_cost_deducted_prior_execution=True, + ) + # A value transfer to an empty (not pre-funded) precompile fires the + # top-frame ``NEW_ACCOUNT`` state charge, modelled via + # ``EMPTY_ACCOUNT``; a pre-funded precompile is alive and exempt. + state_recipient_type = ( + RecipientType.PRECOMPILE if pre_funded else RecipientType.EMPTY_ACCOUNT + ) + top_frame_state_gas = fork.transaction_top_frame_state_gas( + sends_value=bool(value), + recipient_type=state_recipient_type, + ) + + if value > 0: + expected_logs = [transfer_log(sender, precompile, value)] + else: + expected_logs = [] + + gas_price = 1_000_000_000 + + tx = Transaction( + sender=sender, + to=precompile, + value=value, + data=tx_data, + gas_price=gas_price, + expected_receipt=TransactionReceipt(logs=expected_logs), + ) + + # Exact sender balance is generally not checked because precompile + # execution gas varies across the matrix. For identity with empty + # calldata, the execution gas is deterministic, so pin the exact + # balance to make the empty-precompile ``NEW_ACCOUNT`` charge a + # source-level assertion. + final_precompile_balance = pre_funded_amount + value + expected_precompile: Account | None + if final_precompile_balance > 0: + expected_precompile = Account(balance=final_precompile_balance) + else: + expected_precompile = None + expected_sender = Account(nonce=1) + if precompile == Address(0x04): + gas_costs = fork.gas_costs() + precompile_execution_gas = ( + gas_costs.PRECOMPILE_IDENTITY_BASE + + gas_costs.PRECOMPILE_IDENTITY_PER_WORD + * ((len(tx_data) + 31) // 32) + ) + total_gas_cost = ( + intrinsic_gas + top_frame_state_gas + precompile_execution_gas + ) + expected_sender = Account( + nonce=1, + balance=( + sender_initial_balance - value - total_gas_cost * gas_price + ), + ) + post = { + sender: expected_sender, + precompile: expected_precompile, + } + + state_test(pre=pre, tx=tx, post=post) diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_value_moving_with_tx_delegation.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_value_moving_with_tx_delegation.py new file mode 100644 index 00000000000..8722d39e1df --- /dev/null +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_value_moving_with_tx_delegation.py @@ -0,0 +1,339 @@ +""" +Tests for EIP-2780 x EIP-7702 interaction. + +When a type-4 transaction's authorization list installs a delegation on +``tx.to``, ``set_delegation`` runs before the top-frame check fires. +That ordering changes which top-frame charges apply: + +- ``COLD_ACCOUNT_ACCESS`` for the delegated recipient still fires; the + spec charges the access uniformly whenever the recipient holds a + delegation prefix at top-frame time, regardless of who installed it. +- ``NEW_ACCOUNT`` for a value transfer to an otherwise-empty recipient + is suppressed implicitly: ``set_delegation`` writes the delegation + code and increments the nonce, so ``is_account_alive`` returns + ``True`` by the time the top-frame check evaluates it. + +A complementary set of scenarios installs the delegation on the +*sender* (self-sponsored authorization). The authorization's nonce +must equal the sender's nonce *after* the transaction's nonce +increment. +""" + +import pytest +from execution_testing import ( + Account, + Alloc, + AuthorizationTuple, + Fork, + Op, + RecipientType, + StateTestFiller, + Transaction, +) + +from ...prague.eip7702_set_code_tx.spec import Spec as Spec7702 +from .spec import ref_spec_2780 + +REFERENCE_SPEC_GIT_PATH = ref_spec_2780.git_path +REFERENCE_SPEC_VERSION = ref_spec_2780.version + +pytestmark = pytest.mark.valid_from("Amsterdam") + + +@pytest.mark.parametrize( + "value", + [ + pytest.param(0, id="zero_value"), + pytest.param(1, id="non-zero_value"), + ], +) +def test_tx_installs_delegation_on_funded_recipient( + fork: Fork, + pre: Alloc, + state_test: StateTestFiller, + value: int, +) -> None: + """ + Scenario 1: ``tx.to`` is a funded EOA with no prior delegation. + The type-4 transaction's authorization installs delegation on + ``tx.to``. The top-frame ``COLD_ACCOUNT_ACCESS`` charge for the + now-delegated recipient still fires. + + The pre-existing authority account also produces a + ``REFUND_AUTH_PER_EXISTING_ACCOUNT`` state refund. + """ + gsc = fork.gas_costs() + sender_initial_balance = 10**18 + sender = pre.fund_eoa(sender_initial_balance) + + target_initial_balance = 100 + target = pre.fund_eoa(amount=target_initial_balance) + delegated_to = pre.deploy_contract(code=Op.STOP) + + auth = AuthorizationTuple( + address=delegated_to, + nonce=0, + signer=target, + ) + + # Intrinsic sees the recipient in its pre-tx form (funded EOA); + # the delegation is materialized later by ``set_delegation`` and + # only surfaces at the top-frame check. + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( + sends_value=bool(value), + recipient_type=RecipientType.EOA, + authorization_list_or_count=[auth], + return_cost_deducted_prior_execution=True, + ) + top_frame_gas = fork.transaction_top_frame_gas_calculator()( + sends_value=bool(value), + recipient_type=RecipientType.DELEGATION_7702, + ) + # The full intrinsic is deducted upfront. For each existing + # authority, ``set_delegation`` refunds ``NEW_ACCOUNT`` into the + # state gas reservoir (uncapped) and ``ACCOUNT_WRITE`` into the + # regular refund counter (capped at ``gas_used // 5`` by EIP-3529). + total_gas_cost = intrinsic_gas + top_frame_gas + state_refund = gsc.REFUND_AUTH_PER_EXISTING_ACCOUNT + gas_used_pre_regular_refund = total_gas_cost - state_refund + regular_refund = min(gsc.ACCOUNT_WRITE, gas_used_pre_regular_refund // 5) + gas_used = gas_used_pre_regular_refund - regular_refund + + tx_gas_limit = total_gas_cost + 1000 + gas_price = 1_000_000_000 + + tx = Transaction( + sender=sender, + to=target, + value=value, + authorization_list=[auth], + gas_limit=tx_gas_limit, + max_fee_per_gas=gas_price, + max_priority_fee_per_gas=gas_price, + ) + + sender_final_balance = ( + sender_initial_balance - value - (gas_used * gas_price) + ) + + post = { + sender: Account(nonce=1, balance=sender_final_balance), + target: Account( + nonce=1, + balance=target_initial_balance + value, + code=Spec7702.delegation_designation(delegated_to), + ), + } + + state_test(pre=pre, tx=tx, post=post) + + +@pytest.mark.parametrize( + "value", + [ + pytest.param(0, id="zero_value"), + pytest.param(1, id="non-zero_value"), + ], +) +def test_tx_installs_delegation_on_empty_recipient( + fork: Fork, + pre: Alloc, + state_test: StateTestFiller, + value: int, +) -> None: + """ + Scenario 2: ``tx.to`` is a non-existent (empty) account. The type-4 + transaction's authorization installs delegation on ``tx.to``. + + ``set_delegation`` runs before the top-frame check and makes the + recipient alive, so the ``NEW_ACCOUNT`` state-gas charge that a + value transfer to an empty recipient would otherwise incur is + implicitly suppressed. The ``COLD_ACCOUNT_ACCESS`` charge for the + now-delegated recipient still fires. + """ + sender_initial_balance = 10**18 + sender = pre.fund_eoa(sender_initial_balance) + + target = pre.fund_eoa(amount=0) + delegated_to = pre.deploy_contract(code=Op.STOP) + + auth = AuthorizationTuple( + address=delegated_to, + nonce=0, + signer=target, + ) + + # Intrinsic sees the recipient in its pre-tx form (empty); the + # delegation is materialized later by ``set_delegation`` and only + # surfaces at the top-frame check. + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( + sends_value=bool(value), + recipient_type=RecipientType.EMPTY_ACCOUNT, + authorization_list_or_count=[auth], + return_cost_deducted_prior_execution=True, + ) + top_frame_gas = fork.transaction_top_frame_gas_calculator()( + sends_value=bool(value), + recipient_type=RecipientType.DELEGATION_7702, + ) + # Authority does not pre-exist, so no auth refund applies. + total_gas_cost = intrinsic_gas + top_frame_gas + + tx_gas_limit = total_gas_cost + 1000 + gas_price = 1_000_000_000 + + tx = Transaction( + sender=sender, + to=target, + value=value, + authorization_list=[auth], + gas_limit=tx_gas_limit, + max_fee_per_gas=gas_price, + max_priority_fee_per_gas=gas_price, + ) + + sender_final_balance = ( + sender_initial_balance - value - (total_gas_cost * gas_price) + ) + + post = { + sender: Account(nonce=1, balance=sender_final_balance), + target: Account( + nonce=1, + balance=value, + code=Spec7702.delegation_designation(delegated_to), + ), + } + + state_test(pre=pre, tx=tx, post=post) + + +@pytest.mark.parametrize( + "value", + [ + pytest.param(0, id="zero_value"), + pytest.param(1, id="non-zero_value"), + ], +) +@pytest.mark.parametrize( + "call_target", + [ + pytest.param("self", id="calls_self"), + pytest.param("other_eoa", id="calls_other"), + ], +) +def test_tx_installs_delegation_on_sender( + fork: Fork, + pre: Alloc, + state_test: StateTestFiller, + call_target: str, + value: int, +) -> None: + """ + Self-sponsored type-4 transaction: the sender signs an + authorization installing delegation on itself, and the + authorization's nonce equals the sender's nonce *after* the + transaction-side increment (``1``). After ``set_delegation`` the + sender holds delegation code and its nonce reaches ``2``. + + Parametrized over the call target: + + - ``calls_self``: ``tx.to == sender``. The intrinsic self-transfer + carve-out suppresses the recipient access and value-transfer + charges; the top-frame fires ``COLD_ACCOUNT_ACCESS`` because + ``set_delegation`` has installed delegation code on the sender + by then. The transaction then dispatches into the sender's + delegated code. + - ``calls_other``: ``tx.to`` is a separate funded EOA. The + intrinsic charges include ``COLD_ACCOUNT_ACCESS`` for the + recipient (and the value-transfer charges when ``value > 0``). + The top-frame fires nothing because the recipient is a plain + EOA. The sender's delegation is installed and persists past the + transaction without ever being invoked. + """ + gsc = fork.gas_costs() + sender_initial_balance = 10**18 + sender = pre.fund_eoa(sender_initial_balance) + + delegated_to = pre.deploy_contract(code=Op.STOP) + + auth = AuthorizationTuple( + address=delegated_to, + nonce=1, + signer=sender, + ) + + target_initial_balance = 0 + if call_target == "self": + target = sender + # Intrinsic carve-out fires (SELF); top-frame fires + # ``COLD_ACCOUNT_ACCESS`` because the sender is delegated by + # the time the check runs. + intrinsic_recipient_type = RecipientType.SELF + top_frame_recipient_type = RecipientType.DELEGATION_7702 + else: + target_initial_balance = 100 + target = pre.fund_eoa(amount=target_initial_balance) + # Recipient is a plain EOA, so no carve-out and no top-frame + # charge. + intrinsic_recipient_type = RecipientType.EOA + top_frame_recipient_type = RecipientType.EOA + + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( + sends_value=bool(value), + recipient_type=intrinsic_recipient_type, + authorization_list_or_count=[auth], + return_cost_deducted_prior_execution=True, + ) + top_frame_gas = fork.transaction_top_frame_gas_calculator()( + sends_value=bool(value), + recipient_type=top_frame_recipient_type, + ) + + # Sender is the existing authority, so ``set_delegation`` refunds + # ``NEW_ACCOUNT`` to the state-gas reservoir and ``ACCOUNT_WRITE`` + # to the regular refund counter (the latter capped at + # ``gas_used // 5`` by EIP-3529). + total_gas_cost = intrinsic_gas + top_frame_gas + state_refund = gsc.REFUND_AUTH_PER_EXISTING_ACCOUNT + gas_used_pre_regular_refund = total_gas_cost - state_refund + regular_refund = min(gsc.ACCOUNT_WRITE, gas_used_pre_regular_refund // 5) + gas_used = gas_used_pre_regular_refund - regular_refund + + tx_gas_limit = total_gas_cost + 1000 + gas_price = 1_000_000_000 + + tx = Transaction( + sender=sender, + to=target, + value=value, + authorization_list=[auth], + gas_limit=tx_gas_limit, + max_fee_per_gas=gas_price, + max_priority_fee_per_gas=gas_price, + ) + + if call_target == "self": + # Value moves sender -> sender, net zero on balance. + sender_final_balance = sender_initial_balance - gas_used * gas_price + post = { + sender: Account( + nonce=2, + balance=sender_final_balance, + code=Spec7702.delegation_designation(delegated_to), + ), + } + else: + sender_final_balance = ( + sender_initial_balance - value - gas_used * gas_price + ) + post = { + sender: Account( + nonce=2, + balance=sender_final_balance, + code=Spec7702.delegation_designation(delegated_to), + ), + target: Account(balance=target_initial_balance + value), + } + + state_test(pre=pre, tx=tx, post=post) diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_warmth_invariants.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_warmth_invariants.py new file mode 100644 index 00000000000..5c819beabd3 --- /dev/null +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_warmth_invariants.py @@ -0,0 +1,543 @@ +""" +EIP-2780 invariants for transaction-level account charges. + +The recipient and any EIP-7702 delegation target referenced at the +top-level transaction frame always pay the cold access rate, even when +the address is otherwise warm, identical to the sender, or refers to +itself: + +- The access list does not warm transaction-level accounts. Listing + ``tx.to`` (or a delegation target) pays the access-list cost but + does not waive the cold charge. +- The block coinbase is pre-warmed by the protocol before transaction + execution, but tx-level cold charges still fire when ``tx.to`` or a + delegation target happens to be the coinbase. +- Precompile addresses still pay the cold charge. +- Self-referential delegations (delegation target equal to the + sender, the recipient itself, or a precompile) all pay the cold + charge; the dispatched EVM frame then runs whatever code lives at + the target, including the degenerate cases of empty code (EOA, + precompile address) or a delegation prefix that itself decodes as + the ``INVALID`` opcode. +""" + +import pytest +from execution_testing import ( + AccessList, + Account, + Address, + Alloc, + Environment, + Fork, + Op, + RecipientType, + StateTestFiller, + Transaction, +) + +from ...prague.eip7702_set_code_tx.spec import Spec as Spec7702 +from .spec import ref_spec_2780 + +REFERENCE_SPEC_GIT_PATH = ref_spec_2780.git_path +REFERENCE_SPEC_VERSION = ref_spec_2780.version + +pytestmark = pytest.mark.valid_from("Amsterdam") + + +@pytest.mark.parametrize( + "value", + [ + pytest.param(0, id="zero_value"), + pytest.param(1, id="non-zero_value"), + ], +) +def test_intrinsic_charges_recipient_in_access_list( + fork: Fork, + pre: Alloc, + state_test: StateTestFiller, + value: int, +) -> None: + """ + Recipient is listed in the access list. The intrinsic charge still + includes ``COLD_ACCOUNT_ACCESS`` for the recipient on top of the + access-list cost itself. + """ + sender_initial_balance = 10**18 + sender = pre.fund_eoa(sender_initial_balance) + + target_initial_balance = 100 + target = pre.fund_eoa(amount=target_initial_balance) + access_list = [AccessList(address=target, storage_keys=[])] + + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( + access_list=access_list, + sends_value=bool(value), + recipient_type=RecipientType.EOA, + return_cost_deducted_prior_execution=True, + ) + + gas_price = 1_000_000_000 + gas_limit = intrinsic_gas + 1000 + + tx = Transaction( + ty=1, + sender=sender, + to=target, + value=value, + access_list=access_list, + gas_limit=gas_limit, + gas_price=gas_price, + ) + + sender_final_balance = ( + sender_initial_balance - value - intrinsic_gas * gas_price + ) + + post = { + sender: Account(nonce=1, balance=sender_final_balance), + target: Account(balance=target_initial_balance + value), + } + + state_test(pre=pre, tx=tx, post=post) + + +@pytest.mark.parametrize( + "value", + [ + pytest.param(0, id="zero_value"), + pytest.param(1, id="non-zero_value"), + ], +) +def test_intrinsic_charges_recipient_is_coinbase( + env: Environment, + fork: Fork, + pre: Alloc, + state_test: StateTestFiller, + value: int, +) -> None: + """ + Recipient is the block coinbase, which is implicitly warm before + transaction execution. The intrinsic charge still includes + ``COLD_ACCOUNT_ACCESS`` for the recipient. + """ + sender_initial_balance = 10**18 + sender = pre.fund_eoa(sender_initial_balance) + target = Address(env.fee_recipient) + # Pre-fund coinbase so it is alive at top-frame check time; this + # isolates the test to the intrinsic charge invariant and avoids + # the orthogonal ``NEW_ACCOUNT`` top-frame state charge that would + # otherwise fire for value transfer to an empty recipient. + pre.fund_address(target, amount=1) + + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( + sends_value=bool(value), + recipient_type=RecipientType.EOA, + return_cost_deducted_prior_execution=True, + ) + + gas_price = 1_000_000_000 + gas_limit = intrinsic_gas + 1000 + + tx = Transaction( + sender=sender, + to=target, + value=value, + gas_limit=gas_limit, + gas_price=gas_price, + ) + + # Coinbase also receives miner fees, so its post-tx balance is not + # asserted exactly; verifying the sender balance is sufficient to + # pin the intrinsic charge. + sender_final_balance = ( + sender_initial_balance - value - intrinsic_gas * gas_price + ) + + post = { + sender: Account(nonce=1, balance=sender_final_balance), + } + + state_test(pre=pre, tx=tx, post=post) + + +@pytest.mark.parametrize( + "value", + [ + pytest.param(0, id="zero_value"), + pytest.param(1, id="non-zero_value"), + ], +) +def test_top_frame_charges_delegation_in_access_list( + fork: Fork, + pre: Alloc, + state_test: StateTestFiller, + value: int, +) -> None: + """ + Recipient holds a pre-existing EIP-7702 delegation; the delegation + target is listed in the access list. The top-frame still charges + ``COLD_ACCOUNT_ACCESS`` for the delegation target on top of the + access-list cost itself. + """ + sender_initial_balance = 10**18 + sender = pre.fund_eoa(sender_initial_balance) + + delegated_to = pre.deploy_contract(code=Op.STOP) + target_code = Spec7702.delegation_designation(delegated_to) + target = pre.deploy_contract(code=target_code) + access_list = [AccessList(address=delegated_to, storage_keys=[])] + + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( + access_list=access_list, + sends_value=bool(value), + recipient_type=RecipientType.DELEGATION_7702, + return_cost_deducted_prior_execution=True, + ) + top_frame_gas = fork.transaction_top_frame_gas_calculator()( + sends_value=bool(value), + recipient_type=RecipientType.DELEGATION_7702, + ) + + total_gas_cost = intrinsic_gas + top_frame_gas + gas_price = 1_000_000_000 + gas_limit = total_gas_cost + 1000 + + tx = Transaction( + ty=1, + sender=sender, + to=target, + value=value, + access_list=access_list, + gas_limit=gas_limit, + gas_price=gas_price, + ) + + sender_final_balance = ( + sender_initial_balance - value - total_gas_cost * gas_price + ) + + post = { + sender: Account(nonce=1, balance=sender_final_balance), + target: Account(balance=value, code=target_code), + } + + state_test(pre=pre, tx=tx, post=post) + + +@pytest.mark.parametrize( + "value", + [ + pytest.param(0, id="zero_value"), + pytest.param(1, id="non-zero_value"), + ], +) +def test_top_frame_charges_delegation_is_coinbase( + env: Environment, + fork: Fork, + pre: Alloc, + state_test: StateTestFiller, + value: int, +) -> None: + """ + Recipient holds a pre-existing EIP-7702 delegation whose target is + the block coinbase. Coinbase is implicitly warm before execution; + the top-frame still charges ``COLD_ACCOUNT_ACCESS`` for the + delegation target. + """ + sender_initial_balance = 10**18 + sender = pre.fund_eoa(sender_initial_balance) + + delegated_to = Address(env.fee_recipient) + target_code = Spec7702.delegation_designation(delegated_to) + target = pre.deploy_contract(code=target_code) + + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( + sends_value=bool(value), + recipient_type=RecipientType.DELEGATION_7702, + return_cost_deducted_prior_execution=True, + ) + top_frame_gas = fork.transaction_top_frame_gas_calculator()( + sends_value=bool(value), + recipient_type=RecipientType.DELEGATION_7702, + ) + + total_gas_cost = intrinsic_gas + top_frame_gas + gas_price = 1_000_000_000 + gas_limit = total_gas_cost + 1000 + + tx = Transaction( + sender=sender, + to=target, + value=value, + gas_limit=gas_limit, + gas_price=gas_price, + ) + + # Coinbase also receives miner fees, so its post-tx balance is not + # asserted exactly. + sender_final_balance = ( + sender_initial_balance - value - total_gas_cost * gas_price + ) + + post = { + sender: Account(nonce=1, balance=sender_final_balance), + target: Account(balance=value, code=target_code), + } + + state_test(pre=pre, tx=tx, post=post) + + +@pytest.mark.parametrize( + "value", + [ + pytest.param(0, id="zero_value"), + pytest.param(1, id="non-zero_value"), + ], +) +def test_sender_is_coinbase( + fork: Fork, + pre: Alloc, + state_test: StateTestFiller, + value: int, +) -> None: + """ + Sender is the block coinbase. The intrinsic charge is unchanged + by sender identity, but the priority-fee payment loops back to + the sender, so the net gas cost reduces to ``gas_used * + base_fee_per_gas``. + + The coinbase override is wired via a custom ``Environment`` whose + ``fee_recipient`` matches the sender's address. + """ + sender_initial_balance = 10**18 + sender = pre.fund_eoa(sender_initial_balance) + + target_initial_balance = 100 + target = pre.fund_eoa(amount=target_initial_balance) + + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( + sends_value=bool(value), + recipient_type=RecipientType.EOA, + return_cost_deducted_prior_execution=True, + ) + + base_fee = 7 + gas_price = 1_000_000_000 + gas_limit = intrinsic_gas + 1000 + # Sender pays the full gas fee upfront and is credited the + # priority fee back as the coinbase: net cost is + # ``gas_used * base_fee``. + sender_final_balance = ( + sender_initial_balance - value - intrinsic_gas * base_fee + ) + + tx = Transaction( + sender=sender, + to=target, + value=value, + gas_limit=gas_limit, + gas_price=gas_price, + ) + + post = { + sender: Account(nonce=1, balance=sender_final_balance), + target: Account(balance=target_initial_balance + value), + } + + state_test( + pre=pre, + tx=tx, + post=post, + env=Environment(fee_recipient=sender, base_fee_per_gas=base_fee), + ) + + +@pytest.mark.parametrize( + "value", + [ + pytest.param(0, id="zero_value"), + pytest.param(1, id="non-zero_value"), + ], +) +def test_top_frame_charges_delegation_is_sender( + fork: Fork, + pre: Alloc, + state_test: StateTestFiller, + value: int, +) -> None: + """ + Recipient holds a pre-existing EIP-7702 delegation whose target is + the sender (``tx.origin``). The top-frame still charges + ``COLD_ACCOUNT_ACCESS`` for the delegation target; the dispatched + EVM frame finds the sender's empty EOA code and exits immediately. + """ + sender_initial_balance = 10**18 + sender = pre.fund_eoa(sender_initial_balance) + + delegated_to = sender + target_code = Spec7702.delegation_designation(delegated_to) + target = pre.deploy_contract(code=target_code) + + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( + sends_value=bool(value), + recipient_type=RecipientType.DELEGATION_7702, + return_cost_deducted_prior_execution=True, + ) + top_frame_gas = fork.transaction_top_frame_gas_calculator()( + sends_value=bool(value), + recipient_type=RecipientType.DELEGATION_7702, + ) + + total_gas_cost = intrinsic_gas + top_frame_gas + gas_price = 1_000_000_000 + gas_limit = total_gas_cost + 1000 + + tx = Transaction( + sender=sender, + to=target, + value=value, + gas_limit=gas_limit, + gas_price=gas_price, + ) + + sender_final_balance = ( + sender_initial_balance - value - total_gas_cost * gas_price + ) + + post = { + sender: Account(nonce=1, balance=sender_final_balance), + target: Account(balance=value, code=target_code), + } + + state_test(pre=pre, tx=tx, post=post) + + +@pytest.mark.parametrize( + "value", + [ + pytest.param(0, id="zero_value"), + pytest.param(1, id="non-zero_value"), + ], +) +def test_top_frame_charges_delegation_is_recipient( + fork: Fork, + pre: Alloc, + state_test: StateTestFiller, + value: int, +) -> None: + """ + Recipient holds a pre-existing EIP-7702 delegation pointing back + at itself. The top-frame charges ``COLD_ACCOUNT_ACCESS`` for the + delegation target (the recipient itself), and then the dispatched + EVM frame runs the recipient's code -- which *is* the delegation + prefix ``0xef 01 00 ``. The leading ``0xef`` decodes as the + ``INVALID`` opcode, consuming the remaining EVM budget. The + intrinsic and top-frame gas remain paid; the value transfer is + rolled back. + """ + sender_initial_balance = 10**18 + sender = pre.fund_eoa(sender_initial_balance) + + # Pre-allocate an EOA that delegates to itself. The 1-wei balance + # keeps the account alive at top-frame check time so the + # ``NEW_ACCOUNT`` charge does not fire. + target = pre.fund_eoa(amount=1, delegation="Self") + target_code = Spec7702.delegation_designation(target) + + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( + sends_value=bool(value), + recipient_type=RecipientType.DELEGATION_7702, + return_cost_deducted_prior_execution=True, + ) + top_frame_gas = fork.transaction_top_frame_gas_calculator()( + sends_value=bool(value), + recipient_type=RecipientType.DELEGATION_7702, + ) + + # The dispatched frame burns the entire EVM budget on the + # ``INVALID`` opcode and the value transfer is rolled back, so the + # sender pays the full ``gas_limit``. + gas_price = 1_000_000_000 + gas_limit = intrinsic_gas + top_frame_gas + 50_000 + + tx = Transaction( + sender=sender, + to=target, + value=value, + gas_limit=gas_limit, + gas_price=gas_price, + ) + + sender_final_balance = sender_initial_balance - gas_limit * gas_price + + post = { + sender: Account(nonce=1, balance=sender_final_balance), + # Value transfer rolled back by the ``INVALID``; the pre-tx + # 1-wei balance is preserved. + target: Account(balance=1, code=target_code), + } + + state_test(pre=pre, tx=tx, post=post) + + +@pytest.mark.parametrize( + "value", + [ + pytest.param(0, id="zero_value"), + pytest.param(1, id="non-zero_value"), + ], +) +def test_top_frame_charges_delegation_is_precompile( + fork: Fork, + pre: Alloc, + state_test: StateTestFiller, + value: int, +) -> None: + """ + Recipient holds a pre-existing EIP-7702 delegation pointing at a + precompile address (``IDENTITY``, ``0x04``). The top-frame charges + ``COLD_ACCOUNT_ACCESS``; the dispatched EVM frame sets + ``disable_precompiles = True`` for delegated calls, so the + precompile body does not run. The code lookup at the precompile + address returns the empty byte string and the frame exits + immediately. + """ + sender_initial_balance = 10**18 + sender = pre.fund_eoa(sender_initial_balance) + + delegated_to = Address(0x04) + target_code = Spec7702.delegation_designation(delegated_to) + target = pre.deploy_contract(code=target_code) + + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( + sends_value=bool(value), + recipient_type=RecipientType.DELEGATION_7702, + return_cost_deducted_prior_execution=True, + ) + top_frame_gas = fork.transaction_top_frame_gas_calculator()( + sends_value=bool(value), + recipient_type=RecipientType.DELEGATION_7702, + ) + + total_gas_cost = intrinsic_gas + top_frame_gas + gas_price = 1_000_000_000 + gas_limit = total_gas_cost + 1000 + + tx = Transaction( + sender=sender, + to=target, + value=value, + gas_limit=gas_limit, + gas_price=gas_price, + ) + + sender_final_balance = ( + sender_initial_balance - value - total_gas_cost * gas_price + ) + + post = { + sender: Account(nonce=1, balance=sender_final_balance), + target: Account(balance=value, code=target_code), + } + + state_test(pre=pre, tx=tx, post=post) diff --git a/tests/amsterdam/eip7708_eth_transfer_logs/test_eip_mainnet.py b/tests/amsterdam/eip7708_eth_transfer_logs/test_eip_mainnet.py index 73edc070e00..f14be0735dd 100644 --- a/tests/amsterdam/eip7708_eth_transfer_logs/test_eip_mainnet.py +++ b/tests/amsterdam/eip7708_eth_transfer_logs/test_eip_mainnet.py @@ -9,6 +9,7 @@ Alloc, Fork, Op, + RecipientType, StateTestFiller, Transaction, TransactionReceipt, @@ -25,17 +26,29 @@ def test_simple_transfer_mainnet( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test that a simple ETH transfer emits a transfer log on mainnet.""" sender = pre.fund_eoa() recipient = pre.nonexistent_account() + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( + sends_value=True, + recipient_type=RecipientType.EMPTY_ACCOUNT, + return_cost_deducted_prior_execution=True, + ) + top_frame_state_gas = fork.transaction_top_frame_state_gas( + sends_value=True, + recipient_type=RecipientType.EMPTY_ACCOUNT, + ) + gas_limit = intrinsic_gas + top_frame_state_gas + tx = Transaction( ty=0x02, sender=sender, to=recipient, value=1, - gas_limit=21_000, + gas_limit=gas_limit, expected_receipt=TransactionReceipt( logs=[transfer_log(sender, recipient, 1)] ), diff --git a/tests/amsterdam/eip7778_block_gas_accounting_without_refunds/test_gas_accounting.py b/tests/amsterdam/eip7778_block_gas_accounting_without_refunds/test_gas_accounting.py index cc9fcee86ea..b521689d295 100644 --- a/tests/amsterdam/eip7778_block_gas_accounting_without_refunds/test_gas_accounting.py +++ b/tests/amsterdam/eip7778_block_gas_accounting_without_refunds/test_gas_accounting.py @@ -105,6 +105,10 @@ def build_refund_tx( auth_state_refund = ( gsc.REFUND_AUTH_PER_EXISTING_ACCOUNT * refunds_count ) + # The worst-case `ACCOUNT_WRITE` charged at intrinsic + # time is refunded via the refund counter for existing + # authorities, even if the transaction reverts. + refund_counter += gsc.ACCOUNT_WRITE * refunds_count case _: raise ValueError( f"Unknown refund type: {refund_type} (Test needs update)" diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py index bbf3877e673..e34f8cb13e2 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py @@ -28,6 +28,7 @@ Header, Initcode, Op, + RecipientType, StateTestFiller, Transaction, TransactionException, @@ -97,7 +98,14 @@ def test_bal_balance_changes( calldata=b"", contract_creation=False, access_list=[], + sends_value=True, + recipient_type=RecipientType.EMPTY_ACCOUNT, ) + top_frame_state_gas = fork.transaction_top_frame_state_gas( + sends_value=True, + recipient_type=RecipientType.EMPTY_ACCOUNT, + ) + total_gas_cost = intrinsic_gas_cost + top_frame_state_gas # Hard-coded gas price allows to calculate the tx final price gas_price = 1_000_000_000 tx_value = 100 @@ -115,7 +123,7 @@ def test_bal_balance_changes( # Account for both the value sent and gas cost (gas_price * gas_used) alice_final_balance = ( - alice_initial_balance - tx_value - (intrinsic_gas_cost * gas_price) + alice_initial_balance - tx_value - (total_gas_cost * gas_price) ) block = Block( @@ -495,8 +503,15 @@ def test_bal_block_rewards( calldata=b"", contract_creation=False, access_list=[], + recipient_type=RecipientType.EMPTY_ACCOUNT, + sends_value=True, + ) + top_frame_state_gas = fork.transaction_top_frame_state_gas( + sends_value=True, + recipient_type=RecipientType.EMPTY_ACCOUNT, ) - tx_gas_limit = intrinsic_gas + 1000 # add a small buffer + expected_gas_used = intrinsic_gas + top_frame_state_gas + tx_gas_limit = expected_gas_used + 1000 # add a small buffer gas_price = 0xA tx_value = 100 extra_balance = 1000 @@ -516,7 +531,7 @@ def test_bal_block_rewards( # EIP-1559 fee calculation: # - Total gas cost - total_gas_cost = intrinsic_gas * gas_price + total_gas_cost = expected_gas_used * gas_price # - Tip portion genesis_env = Environment(base_fee_per_gas=0x7) @@ -525,7 +540,7 @@ def test_bal_block_rewards( parent_gas_used=0, parent_gas_limit=genesis_env.gas_limit, ) - tip_to_charlie = (gas_price - base_fee_per_gas) * intrinsic_gas + tip_to_charlie = (gas_price - base_fee_per_gas) * expected_gas_used alice_final_balance = alice_initial_balance - tx_value - total_gas_cost @@ -903,7 +918,9 @@ def test_bal_self_transfer( alice = pre.fund_eoa(amount=start_balance) intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator() - intrinsic_gas_cost = intrinsic_gas_calculator() + intrinsic_gas_cost = intrinsic_gas_calculator( + recipient_type=RecipientType.SELF + ) tx = Transaction( sender=alice, @@ -947,7 +964,9 @@ def test_bal_zero_value_transfer( bob = pre.fund_eoa(amount=100) intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator() - intrinsic_gas_cost = intrinsic_gas_calculator() + intrinsic_gas_cost = intrinsic_gas_calculator( + recipient_type=RecipientType.EOA + ) tx = Transaction( sender=alice, @@ -1538,8 +1557,14 @@ def test_bal_coinbase_zero_tip( calldata=b"", contract_creation=False, access_list=[], + recipient_type=RecipientType.EMPTY_ACCOUNT, + sends_value=True, ) - tx_gas_limit = intrinsic_gas + 1000 + top_frame_state_gas = fork.transaction_top_frame_state_gas( + sends_value=True, + recipient_type=RecipientType.EMPTY_ACCOUNT, + ) + tx_gas_limit = intrinsic_gas + top_frame_state_gas + 1000 # Calculate base fee genesis_env = Environment(base_fee_per_gas=0x7) @@ -1562,7 +1587,9 @@ def test_bal_coinbase_zero_tip( ) alice_final_balance = ( - alice_initial_balance - tx_value - (intrinsic_gas * base_fee_per_gas) + alice_initial_balance + - tx_value + - ((intrinsic_gas + top_frame_state_gas) * base_fee_per_gas) ) block = Block( @@ -2022,11 +2049,21 @@ def test_bal_multiple_balance_changes_same_account( charlie = pre.fund_eoa(amount=0) intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator() - tx_intrinsic_gas = intrinsic_gas_calculator(calldata=b"", access_list=[]) + tx_intrinsic_gas = intrinsic_gas_calculator( + calldata=b"", + access_list=[], + recipient_type=RecipientType.EMPTY_ACCOUNT, + sends_value=True, + ) + top_frame_state_gas = fork.transaction_top_frame_state_gas( + sends_value=True, + recipient_type=RecipientType.EMPTY_ACCOUNT, + ) # bob receives funds in tx0, then spends everything in tx1 gas_price = 10 - tx1_gas_cost = tx_intrinsic_gas * gas_price + expected_gas_used = tx_intrinsic_gas + top_frame_state_gas + tx1_gas_cost = expected_gas_used * gas_price spend_amount = 100 funding_amount = tx1_gas_cost + spend_amount @@ -2034,7 +2071,7 @@ def test_bal_multiple_balance_changes_same_account( sender=alice, to=bob, value=funding_amount, - gas_limit=tx_intrinsic_gas, + gas_limit=expected_gas_used, gas_price=gas_price, ) @@ -2042,7 +2079,7 @@ def test_bal_multiple_balance_changes_same_account( sender=bob, to=charlie, value=spend_amount, - gas_limit=tx_intrinsic_gas, + gas_limit=expected_gas_used, gas_price=gas_price, ) @@ -2949,6 +2986,8 @@ def test_bal_cross_tx_funding_chain( target = pre.deploy_contract(code=target_code) intrinsic_calc = fork.transaction_intrinsic_cost_calculator() + # Last hop (eunice -> target) is a plain CONTRACT call with no + # value, so the default intrinsic applies. intrinsic_gas = intrinsic_calc() eunice_exact_gas = intrinsic_gas + target_code.gas_cost(fork) eunice_gas_limit = ( @@ -2957,7 +2996,21 @@ def test_bal_cross_tx_funding_chain( else eunice_exact_gas ) eunice_upfront = eunice_gas_limit * gas_price - transfer_cost = intrinsic_gas * gas_price + # Forwarding hops (alice -> bob, ..., dan -> eunice) transfer value + # to recipients that begin empty, so each pays the value-transfer + # intrinsic surcharges plus the top-frame ``NEW_ACCOUNT`` state + # charge that fires under EIP-2780. With the default zero + # state-gas reservoir the latter spills entirely into regular gas. + forwarding_intrinsic = intrinsic_calc( + sends_value=True, + recipient_type=RecipientType.EMPTY_ACCOUNT, + ) + forwarding_top_frame_state = fork.transaction_top_frame_state_gas( + sends_value=True, + recipient_type=RecipientType.EMPTY_ACCOUNT, + ) + forwarding_gas = forwarding_intrinsic + forwarding_top_frame_state + transfer_cost = forwarding_gas * gas_price # Each sender (including alice) starts with or receives exactly what # the next forward + its own gas demands; everyone ends at zero in @@ -2990,28 +3043,28 @@ def test_bal_cross_tx_funding_chain( sender=alice, to=bob, value=alice_value, - gas_limit=intrinsic_gas, + gas_limit=forwarding_gas, gas_price=gas_price, ), Transaction( sender=bob, to=charlie, value=bob_value, - gas_limit=intrinsic_gas, + gas_limit=forwarding_gas, gas_price=gas_price, ), Transaction( sender=charlie, to=dan, value=charlie_value, - gas_limit=intrinsic_gas, + gas_limit=forwarding_gas, gas_price=gas_price, ), Transaction( sender=dan, to=eunice, value=dan_value, - gas_limit=intrinsic_gas, + gas_limit=forwarding_gas, gas_price=gas_price, ), Transaction( @@ -3628,7 +3681,11 @@ def test_bal_gas_limit_boundary( if with_tx: alice = pre.fund_eoa() - bob = pre.fund_eoa(amount=0) + # Fund bob with 1 wei so the recipient is alive at top-frame + # check time; this avoids the EIP-2780 ``NEW_ACCOUNT`` state + # charge that would otherwise inflate the tx's gas needs past + # the BAL-sized ``block_gas_limit``. + bob = pre.fund_eoa(amount=1) # alice (sender) + bob (recipient) + coinbase (EIP-3651 warm). extra_items += 3 txs.append( @@ -3644,10 +3701,10 @@ def test_bal_gas_limit_boundary( ) expected_accounts[bob] = BalAccountExpectation( balance_changes=[ - BalBalanceChange(block_access_index=1, post_balance=1) + BalBalanceChange(block_access_index=1, post_balance=2) ], ) - post[bob] = Account(balance=1) + post[bob] = Account(balance=2) if with_cl_withdrawal: charlie = pre.fund_eoa(amount=0) diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip4895.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip4895.py index a78d67763dc..ce091534195 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip4895.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip4895.py @@ -21,6 +21,7 @@ Header, Initcode, Op, + RecipientType, Transaction, Withdrawal, compute_create_address, @@ -730,7 +731,15 @@ def test_bal_withdrawal_to_coinbase( coinbase = pre.fund_eoa(amount=0) intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator() - intrinsic_gas = intrinsic_gas_calculator() + intrinsic_gas = intrinsic_gas_calculator( + recipient_type=RecipientType.EMPTY_ACCOUNT, + sends_value=True, + ) + top_frame_state_gas = fork.transaction_top_frame_state_gas( + sends_value=True, + recipient_type=RecipientType.EMPTY_ACCOUNT, + ) + total_intrinsic_gas = intrinsic_gas + top_frame_state_gas # Calculate tip to coinbase genesis_env = Environment(base_fee_per_gas=0x7) @@ -755,11 +764,11 @@ def test_bal_withdrawal_to_coinbase( sender=alice, to=bob, value=tx_value, - gas_limit=intrinsic_gas, + gas_limit=total_intrinsic_gas, **tx_kwargs, ) - tip_to_coinbase = priority_fee * intrinsic_gas + tip_to_coinbase = priority_fee * total_intrinsic_gas withdrawal_amount = 10 coinbase_final_balance = tip_to_coinbase + (withdrawal_amount * GWEI) diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_invalid.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_invalid.py index 33585b2a3cc..c788617a287 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_invalid.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_invalid.py @@ -28,6 +28,7 @@ Header, Initcode, Op, + RecipientType, Storage, Transaction, Withdrawal, @@ -1464,14 +1465,21 @@ def test_bal_invalid_missing_coinbase( calldata=b"", contract_creation=False, access_list=[], + recipient_type=RecipientType.EMPTY_ACCOUNT, + sends_value=True, ) + top_frame_state_gas = fork.transaction_top_frame_state_gas( + sends_value=True, + recipient_type=RecipientType.EMPTY_ACCOUNT, + ) + total_intrinsic_gas = intrinsic_gas + top_frame_state_gas gas_price = 0xA tx = Transaction( sender=alice, to=bob, value=100, - gas_limit=intrinsic_gas + 1000, + gas_limit=total_intrinsic_gas + 1000, gas_price=gas_price, ) @@ -1481,7 +1489,7 @@ def test_bal_invalid_missing_coinbase( parent_gas_used=0, parent_gas_limit=genesis_env.gas_limit, ) - tip = (gas_price - base_fee_per_gas) * intrinsic_gas + tip = (gas_price - base_fee_per_gas) * total_intrinsic_gas blockchain_test( pre=pre, @@ -1546,14 +1554,21 @@ def test_bal_invalid_coinbase_balance_value( calldata=b"", contract_creation=False, access_list=[], + recipient_type=RecipientType.EMPTY_ACCOUNT, + sends_value=True, + ) + top_frame_state_gas = fork.transaction_top_frame_state_gas( + sends_value=True, + recipient_type=RecipientType.EMPTY_ACCOUNT, ) + total_intrinsic_gas = intrinsic_gas + top_frame_state_gas gas_price = 0xA tx = Transaction( sender=alice, to=bob, value=100, - gas_limit=intrinsic_gas + 1000, + gas_limit=total_intrinsic_gas + 1000, gas_price=gas_price, ) @@ -1563,7 +1578,7 @@ def test_bal_invalid_coinbase_balance_value( parent_gas_used=0, parent_gas_limit=genesis_env.gas_limit, ) - tip = (gas_price - base_fee_per_gas) * intrinsic_gas + tip = (gas_price - base_fee_per_gas) * total_intrinsic_gas blockchain_test( pre=pre, diff --git a/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_additional_coverage.py b/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_additional_coverage.py index c62550daa9d..11ef0937dd3 100644 --- a/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_additional_coverage.py +++ b/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_additional_coverage.py @@ -144,6 +144,11 @@ def test_token_calculation_verification( expected_intrinsic_cost = gas_costs.TX_BASE + ( expected_standard_tokens * gas_costs.TX_DATA_TOKEN_STANDARD ) + if fork.is_eip_enabled(2780): + # EIP-2780 surfaces an explicit recipient-access charge for + # non-self, non-create transactions; the ``to`` fixture + # defaults to a deployed contract, so the charge applies. + expected_intrinsic_cost += gas_costs.COLD_ACCOUNT_ACCESS assert intrinsic_cost_before_execution == expected_intrinsic_cost, ( f"Intrinsic cost mismatch for {description}: " f"{intrinsic_cost_before_execution} != {expected_intrinsic_cost} " diff --git a/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_floor_boundary_exact_balance.py b/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_floor_boundary_exact_balance.py index b913e674f4d..4561cf54626 100644 --- a/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_floor_boundary_exact_balance.py +++ b/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_floor_boundary_exact_balance.py @@ -25,7 +25,7 @@ @pytest.mark.parametrize( "zero_bytes", [ - pytest.param(100, id="100_zero_bytes"), + pytest.param(200, id="200_zero_bytes"), pytest.param(1000, id="1000_zero_bytes"), ], ) diff --git a/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_refunds.py b/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_refunds.py index 7463c6b66ef..e21d8c5a77e 100644 --- a/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_refunds.py +++ b/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_refunds.py @@ -97,11 +97,15 @@ def max_refund(fork: Fork, refund_type: RefundTypes) -> int: if refund_type == RefundTypes.STORAGE_CLEAR else 0 ) - if ( - not fork.is_eip_enabled(8037) - and refund_type == RefundTypes.AUTHORIZATION_EXISTING_AUTHORITY - ): - max_refund += gas_costs.REFUND_AUTH_PER_EXISTING_ACCOUNT + if refund_type == RefundTypes.AUTHORIZATION_EXISTING_AUTHORITY: + if fork.is_eip_enabled(8037): + # The worst-case `ACCOUNT_WRITE` charged at intrinsic time + # is refunded via the refund counter when the authority's + # account leaf already exists; the state-gas portion is + # refilled separately and is not subject to the cap. + max_refund += gas_costs.ACCOUNT_WRITE + else: + max_refund += gas_costs.REFUND_AUTH_PER_EXISTING_ACCOUNT return max_refund @@ -160,10 +164,11 @@ def intrinsic_gas_data_floor_minimum_delta() -> int: would always be the below the execution gas cost even after the refund is applied. - This value has been set as of Amsterdam and should be adjusted if the gas - costs change. + This value has been set as of Amsterdam (with the provisional + state-access repricing) and should be adjusted if the gas costs + change. """ - return 250 + return 11_000 @pytest.fixture diff --git a/tests/amsterdam/eip7981_increase_access_list_cost/test_floor_boundary_exact_balance.py b/tests/amsterdam/eip7981_increase_access_list_cost/test_floor_boundary_exact_balance.py index 10c15ec10a3..69fcff2d79a 100644 --- a/tests/amsterdam/eip7981_increase_access_list_cost/test_floor_boundary_exact_balance.py +++ b/tests/amsterdam/eip7981_increase_access_list_cost/test_floor_boundary_exact_balance.py @@ -31,7 +31,11 @@ @pytest.mark.parametrize( "nonzero_bytes", [ - pytest.param(1000, id="1000_nonzero_bytes"), + # Must be large enough that the floor midpoint chosen below + # stays above the access-list intrinsic cost (asserted in the + # test body): each nonzero byte adds 64 gas to the floor but + # only 16 to the intrinsic cost. + pytest.param(1700, id="1700_nonzero_bytes"), pytest.param(2000, id="2000_nonzero_bytes"), ], ) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/spec.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/spec.py index c5fbeb36071..e807c34ebd4 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/spec.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/spec.py @@ -47,7 +47,10 @@ class Spec: STATE_BYTES_PER_STORAGE_SET = 64 STATE_BYTES_PER_AUTH_BASE = 23 - # Regular gas constants (EIP-8037 replaces old combined costs) - REGULAR_GAS_CREATE = 9000 - PER_AUTH_BASE_COST = 7500 - GAS_COLD_STORAGE_WRITE = 5000 + # Regular gas constants. EIP-8037 separated state from regular gas; + # EIP-8038 then repriced them. + REGULAR_GAS_CREATE = 11000 + # Total regular intrinsic per EIP-7702 authorization: + # ACCOUNT_WRITE (8000) + REGULAR_PER_AUTH_BASE_COST (7816). + PER_AUTH_BASE_COST = 15816 + GAS_COLD_STORAGE_WRITE = 13000 diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py index 962e26fbdec..42775006b8c 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py @@ -41,9 +41,9 @@ def sstore_tx_gas(fork: Fork, num_sstores: int = 1) -> tuple[int, int]: """Return (regular, state) gas for a tx with N cold SSTOREs.""" intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() - evm_total = num_sstores * Op.SSTORE(0, 1).gas_cost(fork) + evm_total = num_sstores * Op.SSTORE(0, 1).regular_cost(fork) state = num_sstores * Op.SSTORE(new_value=1).state_cost(fork) - return intrinsic_gas + evm_total - state, state + return intrinsic_gas + evm_total, state def sstore_txs( @@ -625,7 +625,9 @@ def test_tx_inclusion_at_regular_gas_block_limit_small( """ gas_limit_cap = fork.transaction_gas_limit_cap() assert gas_limit_cap is not None - intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( + sends_value=True, + ) filler_tx_count = (fork.minimum_block_gas_limit() // intrinsic_gas) + 1 block_gas_limit = intrinsic_gas * (filler_tx_count + 1) @@ -636,6 +638,7 @@ def test_tx_inclusion_at_regular_gas_block_limit_small( Transaction( to=dest_contract, gas_limit=intrinsic_gas, + value=1, sender=filler_sender, ) for _ in range(filler_tx_count) @@ -647,6 +650,7 @@ def test_tx_inclusion_at_regular_gas_block_limit_small( excess_tx = Transaction( to=dest_contract, gas_limit=excess_tx_gas_limit, + value=1, sender=pre.fund_eoa(), error=error, ) @@ -789,18 +793,26 @@ def test_receipt_cumulative_differs_from_header_gas_used( @pytest.mark.parametrize("dominant_dimension", ["state", "regular"]) +@pytest.mark.parametrize( + "single_tx", + [ + pytest.param(True, id="single_tx"), + pytest.param(False, id="multiple_txs"), + ], +) @pytest.mark.valid_from("EIP8037") def test_base_fee_per_gas_follows_dominant_dimension( blockchain_test: BlockchainTestFiller, pre: Alloc, fork: Fork, dominant_dimension: str, + single_tx: bool, ) -> None: """ Verify the child block's base fee follows the bottleneck dimension. Block 1 exceeds the gas target on one dimension only: state, via - SSTORE-set txs that spill, or regular, via STOP txs. Its header + SSTORE-set txs that spill, or regular, via STOP/MSTORE txs. Its header gas_used = max(regular, state) is then set by that dimension alone, which lifts empty block 2's base fee under the EIP-1559 update. """ @@ -811,30 +823,53 @@ def test_base_fee_per_gas_follows_dominant_dimension( txs: list[Transaction] = [] post: dict = {} + num_sstores = 0 if dominant_dimension == "state": - num_txs = 5 - tx_regular, tx_state = sstore_tx_gas(fork) + if single_tx: + num_txs = 1 + num_sstores = target // sstore_tx_gas(fork, num_sstores=1)[1] + 1 + tx_regular, tx_state = sstore_tx_gas(fork, num_sstores=num_sstores) + else: + num_sstores = 1 + tx_regular, tx_state = sstore_tx_gas(fork, num_sstores=num_sstores) + while tx_regular >= tx_state: + num_sstores += 1 + tx_regular, tx_state = sstore_tx_gas( + fork, num_sstores=num_sstores + ) + num_txs = target // tx_state + 1 block_regular = num_txs * tx_regular block_state = num_txs * tx_state tx_gas_limit = tx_regular + tx_state assert block_state > target > block_regular else: - num_txs = 15 - tx_gas_limit = fork.transaction_intrinsic_cost_calculator()() + if single_tx: + num_txs = 1 + # Just consume all gas + regular_contract = pre.deploy_contract( + code=Op.MSTORE(offset=2**256 - 1, value=1) + Op.STOP + ) + tx_gas_limit = target + 1 + else: + tx_gas_limit = fork.transaction_intrinsic_cost_calculator()() + # Enough STOP txs that regular gas alone clears the target. + regular_contract = pre.deploy_contract(code=Op.STOP) + num_txs = target // tx_gas_limit + 1 block_regular = num_txs * tx_gas_limit block_state = 0 - stop_contract = pre.deploy_contract(code=Op.STOP) assert block_regular > target > block_state for _ in range(num_txs): if dominant_dimension == "state": storage = Storage() - contract = pre.deploy_contract( - code=Op.SSTORE(storage.store_next(1), 1) + Op.STOP, - ) + code = Bytecode() + for _ in range(num_sstores): + code += Op.SSTORE(storage.store_next(1), 1) + code += Op.STOP + contract = pre.deploy_contract(code=code) post[contract] = Account(storage=storage) else: - contract = stop_contract + contract = regular_contract txs.append( Transaction( to=contract, @@ -846,6 +881,10 @@ def test_base_fee_per_gas_follows_dominant_dimension( ) block_1_gas_used = max(block_regular, block_state) + assert block_1_gas_used < gas_limit, ( + "test needs update: gas_limit reached by usage, simply raise the " + "anchored gas_limit value" + ) base_fee_calc = fork.base_fee_per_gas_calculator() block_1_base_fee = base_fee_calc( parent_base_fee_per_gas=genesis_base_fee, diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py index a30fdb28391..62f3a91eeca 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py @@ -92,8 +92,9 @@ def test_delegatecall_child_spill_not_double_charged( """ Test DELEGATECALL child state gas paid from `gas_left` is not recharged. - With gas below the Amsterdam tx gas cap, the top-level frame starts with - no state gas reservoir and the child pays for SSTOREs by spilling from + With the gas limit pinned to the Amsterdam tx gas cap and no requested + reservoir (`state_gas_reservoir=0`), the top-level frame starts with no + state gas reservoir and the child pays for SSTOREs by spilling from `gas_left`. The parent frame must not charge the same state growth again at frame end. """ @@ -115,7 +116,7 @@ def test_delegatecall_child_spill_not_double_charged( tx = Transaction( to=caller, - gas_limit=700_000, + state_gas_reservoir=0, sender=pre.fund_eoa(), ) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py index 8cea2d84600..b9b5bde06f5 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py @@ -657,8 +657,8 @@ def test_code_deposit_oog_preserves_parent_reservoir( init_code = Op.RETURN(0, deploy_size) # Limited regular gas forwarded to the factory. After CREATE - # takes 63/64, the factory retains ~15 K for its SSTOREs. - child_gas = 1_000_000 + # takes 63/64, the factory retains ~23 K for its SSTOREs. + child_gas = 1_500_000 factory_storage = Storage() factory = pre.deploy_contract( @@ -790,8 +790,9 @@ def test_parent_state_gas_after_child_failure( # Factory bytecode shape costs, derived from fork.gas_costs(): # pre-CREATE: PUSH32 + PUSH1 + MSTORE (with 1-word expansion) # + 3 PUSHes for CREATE inputs - # post-CREATE: PUSH key + SSTORE (no-op) + 2 PUSHes + SSTORE - # (zero-to-nonzero regular) + # post-CREATE: PUSH key + SSTORE (cold no-op: access cost only) + # + 2 PUSHes + SSTORE (cold zero-to-nonzero: + # access + write, the compound COLD_STORAGE_WRITE) factory_pre_create_regular = ( gas_costs.VERY_LOW * 2 + gas_costs.OPCODE_MSTORE_BASE @@ -801,7 +802,6 @@ def test_parent_state_gas_after_child_failure( factory_post_create_regular = ( gas_costs.VERY_LOW + gas_costs.COLD_STORAGE_ACCESS - + gas_costs.WARM_ACCESS + gas_costs.VERY_LOW * 2 + gas_costs.COLD_STORAGE_WRITE ) @@ -2465,18 +2465,20 @@ def test_selfdestruct_in_create_tx_initcode( create_state_gas = fork.create_state_gas(code_size=0) beneficiary = 0xDEAD - initcode = Op.SELFDESTRUCT(beneficiary) + # `account_new` folds the beneficiary's `ACCOUNT_WRITE` regular + # cost and account-creation state gas into `gas_cost`. + initcode = Op.SELFDESTRUCT(beneficiary, account_new=True) sender = pre.fund_eoa() intrinsic_calc = fork.transaction_intrinsic_cost_calculator() intrinsic_total = intrinsic_calc( - calldata=bytes(initcode), contract_creation=True + calldata=bytes(initcode), contract_creation=True, sends_value=True ) expected_state = create_state_gas + gas_costs.NEW_ACCOUNT initcode_gas = initcode.gas_cost(fork) - gas_limit = intrinsic_total + initcode_gas + gas_costs.NEW_ACCOUNT + 1000 + gas_limit = intrinsic_total + initcode_gas + 1000 tx = Transaction( sender=sender, diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_multi_block.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_multi_block.py index 37ad180f529..04a109b5cd8 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_multi_block.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_multi_block.py @@ -58,6 +58,10 @@ def test_exact_coinbase_fee_simple_sstore( # Gas breakdown for tx 1 (SSTORE zero-to-nonzero, no calldata): # PUSH1(1) + PUSH1(0) + SSTORE(cold, zero-to-nonzero) + STOP intrinsic_regular = gas_costs.TX_BASE + if fork.is_eip_enabled(2780): + # EIP-2780 surfaces an explicit recipient-access charge for + # non-self, non-create transactions on top of ``TX_BASE``. + intrinsic_regular += gas_costs.COLD_ACCOUNT_ACCESS evm_regular = ( 2 * gas_costs.VERY_LOW # PUSH1 + PUSH1 + gas_costs.COLD_STORAGE_WRITE # SSTORE cold zero-to-nonzero diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_selfdestruct.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_selfdestruct.py index 82c465cd67a..ff0fec86530 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_selfdestruct.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_selfdestruct.py @@ -227,8 +227,9 @@ def test_selfdestruct_state_gas_refilled_on_ancestor_revert( The inner frame spills the NEW_ACCOUNT charge and self-destructs successfully, then the caller reverts: the beneficiary creation - rolls back and the spilled charge is refilled, so only regular gas - is billed. + rolls back and the spilled state charge is refilled. The EIP-8038 + regular account-write charge for the attempted empty-account value + transfer remains billed. """ beneficiary = 0xDEAD inner_code = Op.SELFDESTRUCT(beneficiary) @@ -240,6 +241,7 @@ def test_selfdestruct_state_gas_refilled_on_ancestor_revert( fork.transaction_intrinsic_cost_calculator()() + caller_code.gas_cost(fork) + inner_code.gas_cost(fork) + + fork.gas_costs().ACCOUNT_WRITE ) tx = Transaction(to=caller, sender=pre.fund_eoa()) @@ -714,34 +716,30 @@ def test_selfdestruct_via_delegatecall_chain_no_refund( @pytest.mark.valid_from("EIP8037") -def test_selfdestruct_new_beneficiary_no_regular_account_creation_cost( +def test_selfdestruct_new_beneficiary_account_write_cost( state_test: StateTestFiller, pre: Alloc, fork: Fork, ) -> None: """ - Verify SELFDESTRUCT to a new beneficiary does not charge a - regular account-creation cost on top of state gas. + Verify SELFDESTRUCT to a new beneficiary charges `ACCOUNT_WRITE` + regular gas plus the account-creation state gas, and not the + legacy combined regular account-creation cost. """ - gas_costs = fork.gas_costs() - new_account_state_gas = gas_costs.NEW_ACCOUNT - beneficiary = pre.fund_eoa(amount=0) - victim_code = Op.SELFDESTRUCT(beneficiary) + victim_code = Op.SELFDESTRUCT(beneficiary, account_new=True) victim = pre.deploy_contract(code=victim_code, balance=1) - # Tight budget: slack is less than the old pre-Amsterdam regular - # account-creation cost, so any extra regular draw would OOG. + # Tight budget: slack is less than the legacy 25,000 regular + # account-creation cost minus `ACCOUNT_WRITE`, so any regular draw + # beyond `ACCOUNT_WRITE` would OOG. The opcode metadata folds the + # `ACCOUNT_WRITE` regular cost and the account-creation state gas + # into `gas_cost`. intrinsic = fork.transaction_intrinsic_cost_calculator()() tx = Transaction( to=victim, - gas_limit=( - intrinsic - + victim_code.gas_cost(fork) - + new_account_state_gas - + 20_000 - ), + gas_limit=(intrinsic + victim_code.gas_cost(fork) + 4_000), sender=pre.fund_eoa(), ) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py index bf18b2ba4a0..434de6023df 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py @@ -386,6 +386,10 @@ def test_auth_refund_block_gas_accounting( `RESET_DELEGATION_ADDRESS`; same full refill, since the refill keys off the *pre-state* code slot, not what we're writing. + When the authority's account leaf already exists, the worst-case + `ACCOUNT_WRITE` charged at intrinsic time is additionally refunded + via the regular refund counter, subject to the refund cap. + Verified via header `gas_used`, receipt `cumulative_gas_used`, and the authority post-state (catches a silently-skipped auth). """ @@ -397,6 +401,7 @@ def test_auth_refund_block_gas_accounting( ) intrinsic_regular = total_intrinsic - intrinsic_state_gas new_account_refund = fork.gas_costs().REFUND_AUTH_PER_EXISTING_ACCOUNT + account_write = fork.gas_costs().ACCOUNT_WRITE # Per-auth intrinsic state gas covers NEW_ACCOUNT + AUTH_BASE; the # AUTH_BASE portion is what's left after stripping NEW_ACCOUNT. auth_base_refund = intrinsic_state_gas - new_account_refund @@ -411,17 +416,20 @@ def test_auth_refund_block_gas_accounting( signer = pre.fund_eoa(amount=0) pre_nonce = 0 auth_refund = auth_base_refund if authorize_to_null else 0 + refund_counter = 0 elif signer_pre_state == "existing_leaf": signer = pre.fund_eoa() pre_nonce = 0 auth_refund = new_account_refund + ( auth_base_refund if authorize_to_null else 0 ) + refund_counter = account_write elif signer_pre_state == "existing_delegation": # `fund_eoa(delegation=...)` sets the authority's nonce to 1. signer = pre.fund_eoa(delegation=contract_old) pre_nonce = 1 auth_refund = new_account_refund + auth_base_refund + refund_counter = account_write else: raise ValueError(f"unknown signer_pre_state: {signer_pre_state!r}") @@ -450,7 +458,14 @@ def test_auth_refund_block_gas_accounting( intrinsic_regular, intrinsic_state_gas - auth_refund, ) - receipt_cumulative_gas_used = total_intrinsic - auth_refund + # The state refill is not subject to the refund cap; the regular + # `ACCOUNT_WRITE` refund is. + gas_used_before_refund = total_intrinsic - auth_refund + regular_refund = min( + gas_used_before_refund // fork.max_refund_quotient(), + refund_counter, + ) + receipt_cumulative_gas_used = gas_used_before_refund - regular_refund tx = Transaction( to=contract_new, @@ -1409,9 +1424,12 @@ def test_auth_sender_billing_after_failure( on top-level failure. For existing accounts, set_delegation refunds new-account state - gas to the reservoir. On REVERT, the restored reservoir reduces - the sender's bill via the billing formula. The sender pays less - than in the new-account case by exactly the refund amount. + gas to the reservoir and the worst-case `ACCOUNT_WRITE` to the + regular refund counter; both survive the top-level REVERT since + delegations are applied before execution. On REVERT, the restored + reservoir and the capped regular refund reduce the sender's bill + via the billing formula. The sender pays less than in the + new-account case. """ auth_intrinsic_state = fork.transaction_intrinsic_state_gas( authorization_count=1, @@ -1431,7 +1449,13 @@ def test_auth_sender_billing_after_failure( revert_gas = (Op.REVERT(0, 0)).gas_cost(fork) auth_refund = new_account_refund if authority_exists else 0 - expected_cumulative = intrinsic_total + revert_gas - auth_refund + refund_counter = fork.gas_costs().ACCOUNT_WRITE if authority_exists else 0 + gas_used_before_refund = intrinsic_total + revert_gas - auth_refund + regular_refund = min( + gas_used_before_refund // fork.max_refund_quotient(), + refund_counter, + ) + expected_cumulative = gas_used_before_refund - regular_refund expected_gas_used = max( intrinsic_regular + revert_gas, auth_intrinsic_state - auth_refund, @@ -1532,3 +1556,318 @@ def test_auth_refund_reservoir_cannot_fund_regular_gas( gas_used=max(gas_limit - intrinsic_state, state_used), ), ) + + +@pytest.mark.parametrize( + "invalidity", + [ + pytest.param("nonce_mismatch", id="nonce_mismatch"), + pytest.param("nonce_at_u64_max", id="nonce_at_u64_max"), + pytest.param("chain_id_mismatch", id="chain_id_mismatch"), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_invalid_auth_rule1_refill_by_reason( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + invalidity: str, +) -> None: + """ + Verify an invalid authorization refills its full intrinsic state gas. + + A rejected authorization is skipped during processing. Its whole + state portion of NEW_ACCOUNT plus AUTH_BASE refills the reservoir + and one ACCOUNT_WRITE refunds to the refund counter. The regular + per authorization base cost stays charged and the authority is + never created. Swept over the reasons an authorization is rejected. + """ + per_auth_state = fork.transaction_intrinsic_state_gas( + authorization_count=1, + ) + total_intrinsic = fork.transaction_intrinsic_cost_calculator()( + authorization_list_or_count=1, + ) + intrinsic_regular = total_intrinsic - per_auth_state + account_write = fork.gas_costs().ACCOUNT_WRITE + + target = pre.deploy_contract(code=Op.STOP) + signer = pre.fund_eoa(amount=0) + + if invalidity == "nonce_mismatch": + auth = AuthorizationTuple(address=target, nonce=99, signer=signer) + elif invalidity == "nonce_at_u64_max": + auth = AuthorizationTuple( + address=target, + nonce=2**64 - 1, + signer=signer, + ) + elif invalidity == "chain_id_mismatch": + auth = AuthorizationTuple( + address=target, + nonce=0, + chain_id=9999, + signer=signer, + ) + else: + raise ValueError(f"unknown invalidity: {invalidity!r}") + + # The skipped auth refills its whole state portion to the reservoir + # so the net state charge is zero, and one ACCOUNT_WRITE returns to + # the capped refund counter. + auth_refund = per_auth_state + refund_counter = account_write + + header_gas_used = max(intrinsic_regular, per_auth_state - auth_refund) + gas_used_before_refund = total_intrinsic - auth_refund + regular_refund = min( + gas_used_before_refund // fork.max_refund_quotient(), + refund_counter, + ) + receipt_cumulative_gas_used = gas_used_before_refund - regular_refund + + tx = Transaction( + to=target, + state_gas_reservoir=per_auth_state, + authorization_list=[auth], + sender=pre.fund_eoa(), + expected_receipt=TransactionReceipt( + cumulative_gas_used=receipt_cumulative_gas_used, + ), + ) + + state_test( + pre=pre, + post={signer: Account.NONEXISTENT}, + tx=tx, + blockchain_test_header_verify=Header(gas_used=header_gas_used), + ) + + +@pytest.mark.valid_from("EIP8037") +def test_same_tx_create_then_clear_double_auth_base_refill( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify the create then clear double AUTH_BASE refill in one tx. + + A fresh authority is delegated by the first authorization then + cleared by the second within one transaction. The clear refills + AUTH_BASE twice. Once because the clear writes no indicator bytes. + Once because the delegation it removes was created earlier in this + same transaction. Net AUTH_BASE charged is zero and only the + NEW_ACCOUNT leaf cost remains. + """ + per_auth_state = fork.transaction_intrinsic_state_gas( + authorization_count=1, + ) + intrinsic_state = fork.transaction_intrinsic_state_gas( + authorization_count=2, + ) + total_intrinsic = fork.transaction_intrinsic_cost_calculator()( + authorization_list_or_count=2, + ) + intrinsic_regular = total_intrinsic - intrinsic_state + new_account_refund = fork.gas_costs().REFUND_AUTH_PER_EXISTING_ACCOUNT + account_write = fork.gas_costs().ACCOUNT_WRITE + auth_base_refund = per_auth_state - new_account_refund + + contract_a = pre.deploy_contract(code=Op.STOP) + target = pre.deploy_contract(code=Op.STOP) + + signer = pre.fund_eoa(amount=0) + authorization_list = [ + AuthorizationTuple(address=contract_a, nonce=0, signer=signer), + AuthorizationTuple( + address=Spec7702.RESET_DELEGATION_ADDRESS, + nonce=1, + signer=signer, + ), + ] + + # The first auth creates the leaf and writes the indicator with no + # refill. The second auth refills NEW_ACCOUNT, AUTH_BASE twice, and + # one ACCOUNT_WRITE. + auth_refund = new_account_refund + 2 * auth_base_refund + refund_counter = account_write + + header_gas_used = max(intrinsic_regular, intrinsic_state - auth_refund) + gas_used_before_refund = total_intrinsic - auth_refund + regular_refund = min( + gas_used_before_refund // fork.max_refund_quotient(), + refund_counter, + ) + receipt_cumulative_gas_used = gas_used_before_refund - regular_refund + + tx = Transaction( + to=target, + state_gas_reservoir=intrinsic_state, + authorization_list=authorization_list, + sender=pre.fund_eoa(), + expected_receipt=TransactionReceipt( + cumulative_gas_used=receipt_cumulative_gas_used, + ), + ) + + state_test( + pre=pre, + post={signer: Account(nonce=2, code=b"")}, + tx=tx, + blockchain_test_header_verify=Header(gas_used=header_gas_used), + ) + + +@pytest.mark.valid_from("EIP8037") +def test_same_tx_clear_then_reset_pre_delegated( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify clear then reset of a pre delegated authority in one tx. + + An authority delegated before the transaction is cleared by the + first authorization then set to a new target by the second. The + reset refills AUTH_BASE through the pre delegated term even though + the current code was empty at that point. Net AUTH_BASE charged is + zero because the authority started and ended delegated. + """ + per_auth_state = fork.transaction_intrinsic_state_gas( + authorization_count=1, + ) + intrinsic_state = fork.transaction_intrinsic_state_gas( + authorization_count=2, + ) + total_intrinsic = fork.transaction_intrinsic_cost_calculator()( + authorization_list_or_count=2, + ) + intrinsic_regular = total_intrinsic - intrinsic_state + new_account_refund = fork.gas_costs().REFUND_AUTH_PER_EXISTING_ACCOUNT + account_write = fork.gas_costs().ACCOUNT_WRITE + auth_base_refund = per_auth_state - new_account_refund + + contract_a = pre.deploy_contract(code=Op.STOP) + contract_b = pre.deploy_contract(code=Op.STOP) + target = pre.deploy_contract(code=Op.STOP) + + signer = pre.fund_eoa(delegation=contract_a) + authorization_list = [ + AuthorizationTuple( + address=Spec7702.RESET_DELEGATION_ADDRESS, + nonce=1, + signer=signer, + ), + AuthorizationTuple(address=contract_b, nonce=2, signer=signer), + ] + + # Both auths refill NEW_ACCOUNT and one AUTH_BASE each. The leaf + # already exists so each also refunds one ACCOUNT_WRITE. + auth_refund = 2 * (new_account_refund + auth_base_refund) + refund_counter = 2 * account_write + + header_gas_used = max(intrinsic_regular, intrinsic_state - auth_refund) + gas_used_before_refund = total_intrinsic - auth_refund + regular_refund = min( + gas_used_before_refund // fork.max_refund_quotient(), + refund_counter, + ) + receipt_cumulative_gas_used = gas_used_before_refund - regular_refund + + tx = Transaction( + to=target, + state_gas_reservoir=intrinsic_state, + authorization_list=authorization_list, + sender=pre.fund_eoa(), + expected_receipt=TransactionReceipt( + cumulative_gas_used=receipt_cumulative_gas_used, + ), + ) + + state_test( + pre=pre, + post={ + signer: Account( + nonce=3, + code=Spec7702.delegation_designation(contract_b), + ), + }, + tx=tx, + blockchain_test_header_verify=Header(gas_used=header_gas_used), + ) + + +@pytest.mark.valid_from("EIP8037") +def test_same_authority_increasing_nonce_net_once( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify the per authority once invariant across valid auths. + + The same fresh authority is delegated by three authorizations with + increasing nonces in one transaction. The account leaf and its + delegation indicator are written once. NEW_ACCOUNT and AUTH_BASE are + each charged once across the batch while ACCOUNT_WRITE is refunded + for every auth after the leaf is created. + """ + num_auths = 3 + per_auth_state = fork.transaction_intrinsic_state_gas( + authorization_count=1, + ) + intrinsic_state = fork.transaction_intrinsic_state_gas( + authorization_count=num_auths, + ) + total_intrinsic = fork.transaction_intrinsic_cost_calculator()( + authorization_list_or_count=num_auths, + ) + intrinsic_regular = total_intrinsic - intrinsic_state + new_account_refund = fork.gas_costs().REFUND_AUTH_PER_EXISTING_ACCOUNT + account_write = fork.gas_costs().ACCOUNT_WRITE + auth_base_refund = per_auth_state - new_account_refund + + targets = [pre.deploy_contract(code=Op.STOP) for _ in range(num_auths)] + call_target = pre.deploy_contract(code=Op.STOP) + + signer = pre.fund_eoa(amount=0) + authorization_list = [ + AuthorizationTuple(address=targets[i], nonce=i, signer=signer) + for i in range(num_auths) + ] + + # The first auth creates the leaf with no refill. Each later auth + # refills NEW_ACCOUNT, one AUTH_BASE, and one ACCOUNT_WRITE. + auth_refund = (num_auths - 1) * (new_account_refund + auth_base_refund) + refund_counter = (num_auths - 1) * account_write + + header_gas_used = max(intrinsic_regular, intrinsic_state - auth_refund) + gas_used_before_refund = total_intrinsic - auth_refund + regular_refund = min( + gas_used_before_refund // fork.max_refund_quotient(), + refund_counter, + ) + receipt_cumulative_gas_used = gas_used_before_refund - regular_refund + + tx = Transaction( + to=call_target, + state_gas_reservoir=intrinsic_state, + authorization_list=authorization_list, + sender=pre.fund_eoa(), + expected_receipt=TransactionReceipt( + cumulative_gas_used=receipt_cumulative_gas_used, + ), + ) + + state_test( + pre=pre, + post={ + signer: Account( + nonce=num_auths, + code=Spec7702.delegation_designation(targets[-1]), + ), + }, + tx=tx, + blockchain_test_header_verify=Header(gas_used=header_gas_used), + ) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_sstore.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_sstore.py index 50e404d149d..11ff400a16a 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_sstore.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_sstore.py @@ -968,29 +968,36 @@ def test_sstore_restoration_ancestor_revert( probe = pre.deploy_contract(code=probe_code) caller_storage = Storage() - caller_code = Op.POP(call_opcode(gas=Op.GAS, address=middle)) + Op.SSTORE( + # The probe OOGs and returns 0, so the caller's outer SSTORE is a + # cold no-op (0 to 0) on a fresh slot, charging only + # COLD_STORAGE_ACCESS rather than the cold set `regular_cost` + # assumes by default. + caller_code = Op.POP( + call_opcode(gas=Op.GAS, address=middle) + ) + Op.SSTORE.with_metadata( + key_warm=False, + original_value=0, + current_value=0, + new_value=0, + )( caller_storage.store_next(0, "probe_must_fail"), Op.CALL(gas=probe_gas, address=probe), ) caller = pre.deploy_contract(code=caller_code) - # Block state gas commits only the caller's outer SSTORE-set. The - # probe OOGs and inner's set+clear cancel before middle reverts. - # The probe's CALL burns its forwarded budget on the OOG, less the - # cold-call surcharge already in the caller's static regular cost. - # Header gas_used is max(regular, state). - probe_burned = ( - probe_gas - gas_costs.COLD_ACCOUNT_ACCESS - 2 * gas_costs.WARM_ACCESS - ) - expected_regular = ( + # No SSTORE-set persists (inner's set+clear cancel, middle reverts, + # the probe OOGs and reverts, and the caller's outer SSTORE is a + # no-op), so block state gas is zero and header gas_used (the max of + # regular and state) is just the regular total. The probe burns its + # full forwarded budget on the OOG; its CALL's cold-access surcharge + # is already counted in the caller's regular cost. + expected_gas_used = ( intrinsic_cost + caller_code.regular_cost(fork) + middle_code.regular_cost(fork) + inner_code.regular_cost(fork) - + probe_burned + + probe_gas ) - expected_state = Op.SSTORE(new_value=1).state_cost(fork) - expected_gas_used = max(expected_regular, expected_state) # gas_limit at the cap means the caller's reservoir starts at 0. tx = Transaction( diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/__init__.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/__init__.py new file mode 100644 index 00000000000..a84179b40a4 --- /dev/null +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/__init__.py @@ -0,0 +1,3 @@ +""" +Tests for [EIP-8038: State Access Gas Cost Increase](https://eips.ethereum.org/EIPS/eip-8038). +""" diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/spec.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/spec.py new file mode 100644 index 00000000000..66a7606fd8a --- /dev/null +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/spec.py @@ -0,0 +1,16 @@ +"""Defines the EIP-8038 reference specification.""" + +from dataclasses import dataclass + + +@dataclass(frozen=True) +class ReferenceSpec: + """Defines the reference spec version and git path.""" + + git_path: str + version: str + + +ref_spec_8038 = ReferenceSpec( + "EIPS/eip-8038.md", "a8862ae6653a12a2989b64a50eca5334cfe8b3cb" +) diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_access_list_gas.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_access_list_gas.py new file mode 100644 index 00000000000..1337c77b230 --- /dev/null +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_access_list_gas.py @@ -0,0 +1,329 @@ +""" +Tests for [EIP-8038: State Access Gas Cost Increase](https://eips.ethereum.org/EIPS/eip-8038). + +Covers the EIP-8038 access-list repricing: + +* The intrinsic surcharge per access-list entry is + ``TX_ACCESS_LIST_ADDRESS`` (3000) per address and + ``TX_ACCESS_LIST_STORAGE_KEY`` (3000) per storage key, isolated from + the EIP-7981 calldata-floor tokens that the Amsterdam intrinsic + calculator also charges on access-list bytes. +* A storage slot named in the access list is *warm* on its first runtime + access (``SLOAD``/``SSTORE`` pays ``WARM_SLOAD`` rather than the cold + cost). +* Warmth is scoped to ``(address, slot)``: listing slot ``s`` of account + ``A`` does not warm slot ``s`` of account ``B``. +""" + +from typing import List + +import pytest +from execution_testing import ( + AccessList, + Account, + Address, + Alloc, + Bytecode, + CodeGasMeasure, + Environment, + Fork, + Op, + StateTestFiller, + Transaction, +) +from execution_testing.checklists import EIPChecklist + +from .spec import ref_spec_8038 + +REFERENCE_SPEC_GIT_PATH = ref_spec_8038.git_path +REFERENCE_SPEC_VERSION = ref_spec_8038.version + +pytestmark = pytest.mark.valid_from("Amsterdam") + + +def _access_list_floor_token_gas( + access_list: List[AccessList], fork: Fork +) -> int: + """ + Return the EIP-7981 calldata-floor-token gas the Amsterdam intrinsic + calculator charges for an access list. + + Every byte of each address (20) and storage key (32) is four floor + tokens, each priced at ``TX_DATA_TOKEN_FLOOR``. Subtracting this from + the measured intrinsic delta isolates the pure EIP-8038 per-entry + surcharge. + """ + total_bytes = 0 + for access in access_list: + total_bytes += len(access.address) + total_bytes += 32 * len(access.storage_keys) + return total_bytes * 4 * fork.gas_costs().TX_DATA_TOKEN_FLOOR + + +def _make_access_list( + n_addr: int, n_keys_each: int, *, duplicate: bool = False +) -> List[AccessList]: + """Build an access list of ``n_addr`` entries, each with keys.""" + entries: List[AccessList] = [] + for i in range(n_addr): + address = Address(0x1000) if duplicate else Address(0x1000 + i) + keys = [bytes([j]) * 32 for j in range(n_keys_each)] + entries.append(AccessList(address=address, storage_keys=keys)) + return entries + + +# (n_addr, n_keys_each, duplicate, id) +ACCESS_LIST_SHAPES = [ + pytest.param(0, 0, False, id="empty"), + pytest.param(1, 0, False, id="single_addr"), + pytest.param(1, 3, False, id="one_addr_three_keys"), + pytest.param(2, 0, False, id="two_addr"), + pytest.param(2, 1, True, id="duplicate_addr"), +] + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +@pytest.mark.parametrize("n_addr,n_keys_each,duplicate", ACCESS_LIST_SHAPES) +def test_access_list_intrinsic_surcharge( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + n_addr: int, + n_keys_each: int, + duplicate: bool, +) -> None: + """ + Assert the per-entry intrinsic access-list surcharge. + + The intrinsic-cost delta from adding the access list, minus the + EIP-7981 floor-token contribution, must equal + ``n_addr * TX_ACCESS_LIST_ADDRESS + n_keys * TX_ACCESS_LIST_STORAGE_KEY``. + A simple value-less transaction then exercises the access list end to + end. + """ + gas_costs = fork.gas_costs() + intrinsic = fork.transaction_intrinsic_cost_calculator() + + access_list = _make_access_list(n_addr, n_keys_each, duplicate=duplicate) + n_keys = n_addr * n_keys_each + + base = intrinsic(return_cost_deducted_prior_execution=True) + with_al = intrinsic( + access_list=access_list, + return_cost_deducted_prior_execution=True, + ) + surcharge = ( + with_al - base - _access_list_floor_token_gas(access_list, fork) + ) + expected = ( + n_addr * gas_costs.TX_ACCESS_LIST_ADDRESS + + n_keys * gas_costs.TX_ACCESS_LIST_STORAGE_KEY + ) + assert surcharge == expected + + contract = pre.deploy_contract(code=Op.STOP) + tx = Transaction( + to=contract, + sender=pre.fund_eoa(), + access_list=access_list if access_list else None, + ) + + state_test(pre=pre, post={}, tx=tx) + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +def test_access_list_duplicate_address_key_intrinsic_and_warmth( + state_test: StateTestFiller, + env: Environment, + pre: Alloc, + fork: Fork, +) -> None: + """ + A duplicated ``(address, storage_key)`` access-list entry is billed + twice intrinsically but warms the slot only once. + + The same ``(contract, slot)`` pair is listed twice. The intrinsic + surcharge (floor tokens isolated as in + ``test_access_list_intrinsic_surcharge``) bills both listings: + ``2 * TX_ACCESS_LIST_ADDRESS + 2 * TX_ACCESS_LIST_STORAGE_KEY``. At + runtime the slot is nonetheless warm on its first ``SLOAD`` + (``WARM_SLOAD``), since warmth is set-membership, not a counter. + """ + gas_costs = fork.gas_costs() + intrinsic = fork.transaction_intrinsic_cost_calculator() + slot = 0x42 + + # First runtime SLOAD of the listed slot stores the warm access cost. + measured_read = Op.SLOAD(slot) + overhead = measured_read.gas_cost(fork) - Op.SLOAD( + key_warm=False + ).gas_cost(fork) + contract = pre.deploy_contract( + code=CodeGasMeasure( + code=measured_read, + overhead_cost=overhead, + extra_stack_items=1, + sstore_key=1, + ), + storage={slot: 1}, + ) + + # Build the access list after deploying so the address is real, then + # list the identical (contract, slot) pair twice. + access_list = [ + AccessList(address=contract, storage_keys=[slot]), + AccessList(address=contract, storage_keys=[slot]), + ] + + base = intrinsic(return_cost_deducted_prior_execution=True) + with_al = intrinsic( + access_list=access_list, + return_cost_deducted_prior_execution=True, + ) + surcharge = ( + with_al - base - _access_list_floor_token_gas(access_list, fork) + ) + expected_surcharge = ( + 2 * gas_costs.TX_ACCESS_LIST_ADDRESS + + 2 * gas_costs.TX_ACCESS_LIST_STORAGE_KEY + ) + assert surcharge == expected_surcharge + + expected_gas = Op.SLOAD(key_warm=True).gas_cost(fork) + tx = Transaction( + to=contract, + sender=pre.fund_eoa(), + access_list=access_list, + ) + + # Slot 1 holds the measured warm cost; the read slot keeps its value. + post = {contract: Account(storage={1: expected_gas, slot: 1})} + state_test(env=env, pre=pre, post=post, tx=tx) + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +@pytest.mark.parametrize("op", ["SLOAD", "SSTORE"], ids=["sload", "sstore"]) +def test_access_list_warms_storage_slot( + state_test: StateTestFiller, + env: Environment, + pre: Alloc, + fork: Fork, + op: str, +) -> None: + """ + A storage slot named in the access list is warm on first access. + + The first runtime ``SLOAD``/``SSTORE`` of an access-list slot pays + the warm cost: ``WARM_SLOAD`` for ``SLOAD``; for ``SSTORE`` an + overwrite of a non-zero original to a new non-zero value pays + ``WARM_SLOAD + STORAGE_WRITE``. + """ + gas_costs = fork.gas_costs() + very_low = gas_costs.VERY_LOW + slot = 0x42 + + if op == "SLOAD": + measured_code: Bytecode = Op.SLOAD(slot) + # Overhead is just the single PUSH (key); the stored value is the + # bare warm SLOAD access cost. + overhead_cost = 1 * very_low + extra_stack_items = 1 + expected_gas = Op.SLOAD(key_warm=True).gas_cost(fork) + else: + measured_code = Op.SSTORE(slot, 2) + # Overhead is the two PUSHes (key, value); the stored value is + # the bare warm SSTORE regular cost (overwrite of a non-zero + # original, no state gas). + overhead_cost = 2 * very_low + extra_stack_items = 0 + expected_gas = ( + Op.SSTORE.with_metadata( + key_warm=True, + original_value=1, + current_value=1, + new_value=2, + )(slot, 2).regular_cost(fork) + - 2 * very_low + ) + + code = CodeGasMeasure( + code=measured_code, + overhead_cost=overhead_cost, + extra_stack_items=extra_stack_items, + sstore_key=1, + ) + contract = pre.deploy_contract(code=code, storage={slot: 1}) + + tx = Transaction( + to=contract, + sender=pre.fund_eoa(), + access_list=[AccessList(address=contract, storage_keys=[slot])], + ) + + # Slot 1 holds the measured warm cost. The data slot ends at its + # original (SLOAD) or the written value (SSTORE). + final_slot_value = 1 if op == "SLOAD" else 2 + post = { + contract: Account(storage={1: expected_gas, slot: final_slot_value}) + } + state_test(env=env, pre=pre, post=post, tx=tx) + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +def test_access_list_slot_warmth_is_address_scoped( + state_test: StateTestFiller, + env: Environment, + pre: Alloc, + fork: Fork, +) -> None: + """ + Access-list slot warmth is scoped to ``(address, slot)``. + + Slot ``s`` of account ``A`` is listed in the access list. Reading + slot ``s`` of ``A`` is warm (``WARM_SLOAD``); reading the same slot + number of a different account ``B`` is cold (``COLD_STORAGE_ACCESS``). + """ + slot = 0x42 + warm_gas = Op.SLOAD(key_warm=True).gas_cost(fork) + cold_gas = Op.SLOAD(key_warm=False).gas_cost(fork) + + # Both accounts read their own slot ``s`` with the same wrapper, so the + # overhead that strips the operand PUSH is identical for each. + measured_read = Op.SLOAD(slot) + overhead = measured_read.gas_cost(fork) - cold_gas + + # B reads its own slot ``s`` (cold), storing the result in B's slot 1. + account_b = pre.deploy_contract( + code=CodeGasMeasure( + code=measured_read, + overhead_cost=overhead, + extra_stack_items=1, + sstore_key=1, + ), + storage={slot: 1}, + ) + + # A reads its own slot ``s`` (warm via the access list), then calls B. + account_a = pre.deploy_contract( + code=CodeGasMeasure( + code=measured_read, + overhead_cost=overhead, + extra_stack_items=1, + sstore_key=1, + ) + + Op.POP(Op.CALL(gas=200_000, address=account_b)), + storage={slot: 1}, + ) + + tx = Transaction( + to=account_a, + sender=pre.fund_eoa(), + # Only A's slot is listed; B's identical slot stays cold. + access_list=[AccessList(address=account_a, storage_keys=[slot])], + ) + + post = { + account_a: Account(storage={1: warm_gas, slot: 1}), + account_b: Account(storage={1: cold_gas, slot: 1}), + } + state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_call_gas.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_call_gas.py new file mode 100644 index 00000000000..37e543f4376 --- /dev/null +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_call_gas.py @@ -0,0 +1,768 @@ +""" +Tests for the EIP-8038 [State Access Gas Cost Increase](https://eips.ethereum.org/EIPS/eip-8038) +``CALL``-family regular-gas dimension. + +Under EIP-8038 the call opcodes are repriced in their *regular* gas +dimension: + +- account access costs ``COLD_ACCOUNT_ACCESS`` (3,000) cold or + ``WARM_ACCESS`` (100) warm; +- a positive value transfer adds ``CALL_VALUE`` (``ACCOUNT_WRITE`` + + ``CALL_STIPEND`` = 10,300), charged only by ``CALL``/``CALLCODE``; +- a value transfer to a *new* account additionally creates the account, + whose ``GAS_NEW_ACCOUNT`` charge is the EIP-8037 *state* dimension and + is asserted via the block header ``max(regular, state)`` accounting, + never as regular gas; +- an EIP-7702 delegated target is double-accessed (target leaf plus + delegation leaf), each access cold or warm independently. + +These tests assert the EIP-8038 *regular* dimension; the EIP-8037 +*state* dimension for value-to-new-account is covered in +``eip8037_state_creation_gas_cost_increase/test_state_gas_call.py`` and +is only re-derived here at the seam to feed header gas accounting. +""" + +import pytest +from execution_testing import ( + AccessList, + Account, + Address, + Alloc, + Bytecode, + CodeGasMeasure, + Environment, + Fork, + Header, + Op, + StateTestFiller, + Transaction, + TransactionReceipt, +) +from execution_testing.checklists import EIPChecklist + +from .spec import ref_spec_8038 + +REFERENCE_SPEC_GIT_PATH = ref_spec_8038.git_path +REFERENCE_SPEC_VERSION = ref_spec_8038.version + +pytestmark = pytest.mark.valid_from("Amsterdam") + + +def _measure_call( + pre: Alloc, + fork: Fork, + measured_code: Bytecode, + own_cold_cost: Bytecode, + balance: int = 0, +) -> Address: + """ + Deploy a ``CodeGasMeasure`` contract around ``measured_code``. + + The overhead subtracts the call opcode's OWN cold cost (computed from + ``own_cold_cost``) so only the wrapping ``PUSH`` arguments remain in + the overhead; the measured value isolates the opcode's gas. The call + leaves exactly one stack item (its success flag). + """ + overhead_cost = measured_code.gas_cost(fork) - own_cold_cost.gas_cost(fork) + code_gas_measure = CodeGasMeasure( + code=measured_code, + overhead_cost=overhead_cost, + extra_stack_items=1, + ) + return pre.deploy_contract(code=code_gas_measure, balance=balance) + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +@pytest.mark.with_all_call_opcodes() +@pytest.mark.parametrize("warm", [False, True], ids=["cold", "warm"]) +def test_call_access_gas( + state_test: StateTestFiller, + env: Environment, + pre: Alloc, + fork: Fork, + call_opcode: Op, + warm: bool, +) -> None: + """ + Measure the access cost of every call opcode with no value transfer. + + EIP-8038 charges ``COLD_ACCOUNT_ACCESS`` (3,000) cold and + ``WARM_ACCESS`` (100) warm for all four call opcodes. + """ + gas_costs = fork.gas_costs() + + target = pre.deploy_contract(Op.STOP) + + measured_code = call_opcode(gas=0, address=target) + cost_metadata = call_opcode(address_warm=warm) + measure_address = _measure_call( + pre, fork, measured_code, call_opcode(address_warm=False) + ) + + expected_gas = ( + gas_costs.WARM_ACCESS if warm else gas_costs.COLD_ACCOUNT_ACCESS + ) + # Cross-check the framework opcode model agrees with the formula. + assert expected_gas == cost_metadata.gas_cost(fork) + + access_list = ( + [AccessList(address=target, storage_keys=[])] if warm else None + ) + tx = Transaction( + to=measure_address, + sender=pre.fund_eoa(), + access_list=access_list, + ) + + post = {measure_address: Account(storage={0: expected_gas})} + state_test(env=env, pre=pre, post=post, tx=tx) + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +@pytest.mark.with_all_call_opcodes() +@pytest.mark.parametrize("warm", [False, True], ids=["cold", "warm"]) +def test_call_value_alive_target_gas( + state_test: StateTestFiller, + env: Environment, + pre: Alloc, + fork: Fork, + call_opcode: Op, + warm: bool, +) -> None: + """ + Measure call cost with value transfer to an already-alive target. + + ``CALL``/``CALLCODE`` add ``CALL_VALUE`` (10,300) on top of the + access cost, where ``CALL_VALUE = ACCOUNT_WRITE + CALL_STIPEND``. + ``DELEGATECALL``/``STATICCALL`` never transfer value, so they pay + only the access cost regardless of any value argument. No new + account is created (the target is alive), so no state gas is charged. + + The ``CALL_STIPEND`` (2,300) is forwarded to the callee; with a + ``STOP`` callee it is unused and returned, so the gas *consumed* by + the caller is ``access + ACCOUNT_WRITE`` while the *charged* schedule + is ``access + CALL_VALUE``. Both are asserted. + """ + gas_costs = fork.gas_costs() + transfers_value = call_opcode in (Op.CALL, Op.CALLCODE) + # Verify the EIP-8038 decomposition of the value-transfer charge. + assert gas_costs.CALL_VALUE == gas_costs.ACCOUNT_WRITE + ( + gas_costs.CALL_STIPEND + ) + + # The measured-vs-charged duality below hinges on the callee being a + # pure `STOP`: it executes no opcodes, so the forwarded `CALL_STIPEND` + # is wholly unused and returned. Pin that the callee is exactly the + # single zero byte with no gas cost, and that the returned stipend is + # precisely `CALL_VALUE - ACCOUNT_WRITE`. + callee = Op.STOP + assert bytes(callee) == b"\x00" + assert callee.gas_cost(fork) == 0 + assert gas_costs.CALL_VALUE - gas_costs.ACCOUNT_WRITE == ( + gas_costs.CALL_STIPEND + ) + + # Alive target with balance so no account creation occurs. + target = pre.deploy_contract(callee, balance=1) + + # Build the runnable call carrying the runtime metadata so that + # `measured_code.gas_cost(fork)` accounts for the value transfer. + if transfers_value: + measured_code = call_opcode.with_metadata( + address_warm=False, value_transfer=True + )( + gas=0, + address=target, + value=1, + args_offset=0, + args_size=0, + ret_offset=0, + ret_size=0, + ) + cost_metadata = call_opcode(address_warm=warm, value_transfer=True) + own_cold = call_opcode(address_warm=False, value_transfer=True) + else: + measured_code = call_opcode(gas=0, address=target) + cost_metadata = call_opcode(address_warm=warm) + own_cold = call_opcode(address_warm=False) + + # The measure contract needs balance to actually send the value. + measure_address = _measure_call( + pre, fork, measured_code, own_cold, balance=1 + ) + + access_cost = ( + gas_costs.WARM_ACCESS if warm else gas_costs.COLD_ACCOUNT_ACCESS + ) + # Charged schedule: access + CALL_VALUE (verified via the opcode + # model). CALL gas is wholly regular under EIP-8038 (no state map). + charged_gas = access_cost + ( + gas_costs.CALL_VALUE if transfers_value else 0 + ) + assert charged_gas == cost_metadata.gas_cost(fork) + assert cost_metadata.state_cost(fork) == 0 + + # Consumed gas: the STOP callee returns the forwarded CALL_STIPEND, + # so the caller's measured consumption is access + ACCOUNT_WRITE for + # value transfers, and just access otherwise. + measured_gas = access_cost + ( + gas_costs.ACCOUNT_WRITE if transfers_value else 0 + ) + + access_list = ( + [AccessList(address=target, storage_keys=[])] if warm else None + ) + tx = Transaction( + to=measure_address, + sender=pre.fund_eoa(), + access_list=access_list, + ) + + post = {measure_address: Account(storage={0: measured_gas})} + state_test(env=env, pre=pre, post=post, tx=tx) + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +def test_callcode_value_to_nonexistent_no_new_account( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify CALLCODE value to a non-existent target charges CALL_VALUE + but not GAS_NEW_ACCOUNT. + + ``CALLCODE`` runs the callee's code in the caller's own context, so + the value never leaves the caller and no beneficiary account is + created. The block ``gas_used`` therefore equals the regular tx + cost with ``CALL_VALUE`` but with no 183,600 state-gas component. + """ + gas_costs = fork.gas_costs() + intrinsic = fork.transaction_intrinsic_cost_calculator()() + + target = 0xDEAD # non-existent + + # CALLCODE with value to a cold, non-existent target. The metadata + # carries `value_transfer` so `caller_code.gas_cost(fork)` reflects + # the CALL_VALUE charge; it must NOT carry `account_new` since the + # value stays with the caller and no beneficiary leaf is created. + callcode = Op.CALLCODE.with_metadata( + address_warm=False, value_transfer=True + )( + gas=0, + address=target, + value=1, + args_offset=0, + args_size=0, + ret_offset=0, + ret_size=0, + ) + caller_code = Op.POP(callcode) + Op.STOP + caller = pre.deploy_contract(code=caller_code, balance=1) + + # CALLCODE-to-nonexistent regular charge: access + CALL_VALUE, no + # NEW_ACCOUNT (asserted via the metadata-only opcode model). + callcode_meta = Op.CALLCODE(address_warm=False, value_transfer=True) + assert callcode_meta.gas_cost(fork) == gas_costs.COLD_ACCOUNT_ACCESS + ( + gas_costs.CALL_VALUE + ) + # CALLCODE carries no state-gas (NEW_ACCOUNT) component. + assert callcode_meta.state_cost(fork) == 0 + + # Whole tx is regular gas; no NEW_ACCOUNT state component appears. + # The CALLCODE forwards CALL_STIPEND to the callee, which (running in + # the caller's own context with empty code) leaves it unused and + # returns it, so consumed gas is the charge minus the stipend. + expected_gas_used = ( + intrinsic + caller_code.gas_cost(fork) - gas_costs.CALL_STIPEND + ) + # Guard the no-state assertion: NEW_ACCOUNT would dominate if charged. + assert expected_gas_used < gas_costs.NEW_ACCOUNT + + tx = Transaction( + to=caller, + sender=pre.fund_eoa(), + expected_receipt=TransactionReceipt( + cumulative_gas_used=expected_gas_used + ), + ) + + state_test(pre=pre, post={caller: Account(balance=1)}, tx=tx) + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +def test_call_value_to_new_account_seam( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify the CALL value-to-new-account regular/state seam. + + The EIP-8038 *regular* dimension is ``COLD_ACCOUNT_ACCESS`` + + ``CALL_VALUE`` = 13,300; the account creation charge + ``GAS_NEW_ACCOUNT`` (183,600) lands in the EIP-8037 *state* + dimension. The block header reflects ``max(regular, state)``, which + is dominated by the state charge. + """ + gas_costs = fork.gas_costs() + new_account_state_gas = gas_costs.NEW_ACCOUNT + intrinsic = fork.transaction_intrinsic_cost_calculator()() + + # Fresh, value-receiving target (state-empty, will be created). + target = pre.fund_eoa(amount=0) + + # Metadata-bearing CALL so `caller_code.gas_cost(fork)` folds the + # value transfer and account-creation charges; we then split off the + # NEW_ACCOUNT state component for the 2D header accounting. + call = Op.CALL.with_metadata( + address_warm=False, value_transfer=True, account_new=True + )( + gas=0, + address=target, + value=1, + args_offset=0, + args_size=0, + ret_offset=0, + ret_size=0, + ) + caller_code = Op.POP(call) + Op.STOP + caller = pre.deploy_contract(code=caller_code, balance=1) + + # Regular dimension: access + value (NOT new account, which is the + # state dimension). Asserted via the metadata-only opcode model. + call_meta = Op.CALL( + address_warm=False, value_transfer=True, account_new=True + ) + call_regular = call_meta.gas_cost(fork) - new_account_state_gas + assert call_regular == gas_costs.COLD_ACCOUNT_ACCESS + gas_costs.CALL_VALUE + assert call_regular == 13_300 + + # block_gas_used = max(block_regular, block_state). The CALL opcode + # has no state-gas map, so its NEW_ACCOUNT charge spills as regular + # gas in the bytecode total; strip it back out to isolate the + # regular axis and re-add NEW_ACCOUNT explicitly on the state axis. + tx_regular = intrinsic + caller_code.gas_cost(fork) - new_account_state_gas + tx_state = new_account_state_gas + expected_gas_used = max(tx_regular, tx_state) + # State must dominate here, proving the 183,600 hit the state axis. + assert expected_gas_used == new_account_state_gas + + tx = Transaction( + to=caller, + sender=pre.fund_eoa(), + state_gas_reservoir=new_account_state_gas, + ) + + state_test( + pre=pre, + post={target: Account(balance=1)}, + tx=tx, + blockchain_test_header_verify=Header(gas_used=expected_gas_used), + ) + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +@pytest.mark.with_all_call_opcodes() +@pytest.mark.parametrize( + "target_warm", [False, True], ids=["target_cold", "target_warm"] +) +@pytest.mark.parametrize( + "delegate_warm", [False, True], ids=["delegate_cold", "delegate_warm"] +) +def test_call_to_delegated_target_double_access( + state_test: StateTestFiller, + env: Environment, + pre: Alloc, + fork: Fork, + call_opcode: Op, + target_warm: bool, + delegate_warm: bool, +) -> None: + """ + Measure a call to a 7702-delegated target: 2x2 double access. + + The spec applies the delegation surcharge to every call opcode + (``CALL``/``CALLCODE``/``DELEGATECALL``/``STATICCALL``), so each + reads two account leaves: the target's leaf and the delegation's + leaf. Each is charged independently as ``WARM_ACCESS`` (100) or + ``COLD_ACCOUNT_ACCESS`` (3,000) by warmth. ``DELEGATECALL`` and + ``STATICCALL`` carry no value but still pay the delegation + surcharge. + """ + gas_costs = fork.gas_costs() + + # Final code-bearing account that the delegation points at. + delegate = pre.deploy_contract(Op.STOP) + # EOA delegated (EIP-7702) to `delegate`. + target = pre.fund_eoa(amount=0, delegation=delegate) + + measured_code = call_opcode(gas=0, address=target) + cost_metadata = call_opcode( + address_warm=target_warm, + delegated_address=True, + delegated_address_warm=delegate_warm, + ) + measure_address = _measure_call( + pre, fork, measured_code, call_opcode(address_warm=False) + ) + + target_cost = ( + gas_costs.WARM_ACCESS if target_warm else gas_costs.COLD_ACCOUNT_ACCESS + ) + delegate_cost = ( + gas_costs.WARM_ACCESS + if delegate_warm + else gas_costs.COLD_ACCOUNT_ACCESS + ) + expected_gas = target_cost + delegate_cost + assert expected_gas == cost_metadata.gas_cost(fork) + + # Warm the target and/or the delegate leaf via the access list. + access_entries = [] + if target_warm: + access_entries.append(AccessList(address=target, storage_keys=[])) + if delegate_warm: + access_entries.append(AccessList(address=delegate, storage_keys=[])) + access_list = access_entries or None + + tx = Transaction( + to=measure_address, + sender=pre.fund_eoa(), + access_list=access_list, + ) + + post = {measure_address: Account(storage={0: expected_gas})} + state_test(env=env, pre=pre, post=post, tx=tx) + + +@EIPChecklist.GasCostChanges.Test.OutOfGas() +@pytest.mark.with_all_call_opcodes() +@pytest.mark.parametrize( + "sufficient_gas", [True, False], ids=["sufficient", "insufficient"] +) +def test_call_exact_gas_oog( + state_test: StateTestFiller, + env: Environment, + pre: Alloc, + fork: Fork, + call_opcode: Op, + sufficient_gas: bool, +) -> None: + """ + Drive a cold call at exactly its gas (success) and one gas short (OOG). + + The caller forwards exactly enough gas for the inner call opcode (its + cold access cost plus the wrapping pushes). One gas short forces the + inner call to halt out-of-gas before executing, so the outer SSTORE + records 0; with the exact amount it records 1. + """ + target = pre.deploy_contract(Op.STOP) + + # Inner contract just performs the cold call to `target`. + inner_code = call_opcode(gas=0, address=target) + Op.STOP + inner = pre.deploy_contract(inner_code) + + # Exact regular gas for the inner frame: bytecode cost (which folds + # the cold call cost via the default metadata) under EIP-8038. + inner_gas_exact = inner_code.gas_cost(fork) + if not sufficient_gas: + inner_gas_exact -= 1 + + caller_code = Op.SSTORE(0, Op.CALL(gas=inner_gas_exact, address=inner)) + caller = pre.deploy_contract(caller_code) + + tx = Transaction(to=caller, sender=pre.fund_eoa()) + + post = {caller: Account(storage={0: 1 if sufficient_gas else 0})} + state_test(env=env, pre=pre, post=post, tx=tx) + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +@pytest.mark.with_all_call_opcodes() +def test_call_self_is_warm( + state_test: StateTestFiller, + env: Environment, + pre: Alloc, + fork: Fork, + call_opcode: Op, +) -> None: + """ + Verify a self-call is warm: the executing account is pre-warmed. + + The current target is in the accessed-addresses set on message + entry, so a call to ``ADDRESS`` pays only ``WARM_ACCESS`` (100). + """ + gas_costs = fork.gas_costs() + + # `Op.ADDRESS` is the call's address argument, embedded inside the + # runnable call; the self address is in the accessed set on entry, so + # the call is warm. The overhead subtracts the call's own cold cost, + # leaving the ADDRESS push and the other arg pushes as overhead. + measured_code = call_opcode(gas=0, address=Op.ADDRESS) + measure_address = _measure_call( + pre, fork, measured_code, call_opcode(address_warm=False) + ) + + expected_gas = call_opcode(address_warm=True).gas_cost(fork) + assert expected_gas == gas_costs.WARM_ACCESS + + tx = Transaction(to=measure_address, sender=pre.fund_eoa()) + + post = {measure_address: Account(storage={0: expected_gas})} + state_test(env=env, pre=pre, post=post, tx=tx) + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +@pytest.mark.parametrize( + "sufficient_gas", [True, False], ids=["sufficient", "insufficient"] +) +def test_call_forwarded_gas_63_64( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + sufficient_gas: bool, +) -> None: + """ + Verify the 63/64 forwarding budget is computed after the repriced + cold access charge. + + A wrapper performs a cold, zero-value ``CALL`` requesting maximum + gas. The spec charges the repriced ``COLD_ACCOUNT_ACCESS`` (3,000) + up front and only then forwards ``floor(63/64 * gas_left)`` to the + child. The wrapper is handed an exact budget so that, net of the + access charge, ``gas_left`` equals ``child_regular * 64 // 63``; + forwarding then yields exactly the child's regular need + (``child_regular``) and its cold ``SSTORE`` takes effect. With one + gas less the floor drops below ``child_regular`` and the child OOGs, + so the slot stays zero. This pins that the floor is taken over + ``gas_left`` already net of the post-8038 cold access cost (not + before it, and not double-charging it). + """ + gas_costs = fork.gas_costs() + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + + # Child: a single cold zero-to-nonzero SSTORE as proof of execution. + # Its regular need is the two operand pushes plus the cold storage + # write (the state portion is funded separately via the reservoir, + # which is passed to the child in full with no 63/64 rule). + child = pre.deploy_contract(Op.SSTORE(0, 1)) + child_regular = 2 * gas_costs.VERY_LOW + gas_costs.COLD_STORAGE_WRITE + + # Smallest budget whose 63/64 floor still reaches `child_regular`. + forward_budget = child_regular * 64 // 63 + if not sufficient_gas: + forward_budget -= 1 + + # Wrapper: cold zero-value CALL requesting max gas (so the forwarded + # amount is bound by `gas_left`, not by the request). ret_size=0 + # avoids any memory-expansion term. + wrapper = pre.deploy_contract( + Op.CALL( + gas=0xFFFFFFFF, + address=child, + value=0, + args_offset=0, + args_size=0, + ret_offset=0, + ret_size=0, + ) + ) + + # At the wrapper's CALL the access charge (`extra_gas`) is deducted + # first, leaving exactly `forward_budget` as `gas_left` for the 63/64 + # floor. The seven CALL operand pushes precede it. + wrapper_pushes = 7 * gas_costs.VERY_LOW + extra_gas = gas_costs.COLD_ACCOUNT_ACCESS # cold call, value 0 + wrapper_gas = wrapper_pushes + extra_gas + forward_budget + + # Outer caller hands the wrapper exactly `wrapper_gas`. + caller = pre.deploy_contract( + Op.POP(Op.CALL(gas=wrapper_gas, address=wrapper)) + ) + + tx = Transaction( + to=caller, + sender=pre.fund_eoa(), + state_gas_reservoir=sstore_state_gas, + ) + + # Child SSTORE lands only when the forwarded floor reaches its need. + post = {child: Account(storage={0: 1 if sufficient_gas else 0})} + state_test(pre=pre, post=post, tx=tx) + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +def test_account_warmth_reverts_on_subcall_revert( + state_test: StateTestFiller, + env: Environment, + pre: Alloc, + fork: Fork, +) -> None: + """ + Account warmth acquired inside a reverted sub-call does not persist. + + An inner contract reads an address's ``BALANCE`` via + ``DELEGATECALL`` (so the warmed address belongs to the shared + accessed-addresses set) then ``REVERT``s. Back in the outer frame, + that same address's first ``BALANCE`` is cold again and is charged + ``COLD_ACCOUNT_ACCESS`` (3,000), proving the warm-address set is + rolled back on revert (mirrors the ``SLOAD`` warmth-revert case for + the account dimension). + """ + gas_costs = fork.gas_costs() + cold_gas = Op.BALANCE(address_warm=False).gas_cost(fork) + assert cold_gas == gas_costs.COLD_ACCOUNT_ACCESS + + # Address whose warmth we probe; left out of the access list so its + # first runtime touch is cold. + probed = pre.fund_eoa(amount=1) + + # Inner: warm `probed` by reading its balance, then revert. + inner = pre.deploy_contract( + code=Op.POP(Op.BALANCE(probed)) + Op.REVERT(0, 0), + ) + + # Outer: DELEGATECALL inner (which warms `probed` in the shared + # accessed set, then reverts, discarding that warmth), then measure + # its own first BALANCE of `probed`, which must be cold again. + measured_code = Op.BALANCE(probed) + overhead_cost = measured_code.gas_cost(fork) - Op.BALANCE( + address_warm=False + ).gas_cost(fork) + outer_code: Bytecode = Op.POP( + Op.DELEGATECALL(gas=100_000, address=inner) + ) + CodeGasMeasure( + code=measured_code, + overhead_cost=overhead_cost, + extra_stack_items=1, + ) + outer = pre.deploy_contract(code=outer_code) + + tx = Transaction(to=outer, sender=pre.fund_eoa()) + + # Slot 0 holds the measured (cold) BALANCE read. + post = {outer: Account(storage={0: cold_gas})} + state_test(env=env, pre=pre, post=post, tx=tx) + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +def test_call_to_double_delegated_target_single_hop( + state_test: StateTestFiller, + env: Environment, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify delegation resolution is single-hop: A -> B -> C charges two. + + ``target`` (A) is an EOA delegated to ``mid`` (B), which is itself + an EOA delegated to ``final`` (C), a code-bearing account. A cold + ``CALL`` to ``target`` reads exactly two account leaves -- the + target's and its delegation's -- and is charged + ``2 * COLD_ACCOUNT_ACCESS`` (6,000). The chain is not followed a + second hop, so ``final``'s leaf is not charged. Both the framework + opcode model and a runtime ``CodeGasMeasure`` confirm the value. + """ + gas_costs = fork.gas_costs() + + # A -> B -> C delegation chain. `mid` is an EOA whose code is the + # 7702 delegation designator pointing at `final`; `target` delegates + # to `mid` in turn. + final = pre.deploy_contract(Op.STOP) + mid = pre.fund_eoa(amount=0, delegation=final) + target = pre.fund_eoa(amount=0, delegation=mid) + + # Framework model: cold target leaf + cold delegation leaf, no third + # access for the second hop. + cost_metadata = Op.CALL( + address_warm=False, + delegated_address=True, + delegated_address_warm=False, + ) + expected_gas = 2 * gas_costs.COLD_ACCOUNT_ACCESS + assert expected_gas == cost_metadata.gas_cost(fork) + assert cost_metadata.state_cost(fork) == 0 + + measured_code = Op.CALL(gas=0, address=target) + measure_address = _measure_call( + pre, fork, measured_code, Op.CALL(address_warm=False) + ) + + tx = Transaction(to=measure_address, sender=pre.fund_eoa()) + + post = {measure_address: Account(storage={0: expected_gas})} + state_test(env=env, pre=pre, post=post, tx=tx) + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +@pytest.mark.with_all_call_opcodes() +def test_call_precompile_is_warm( + state_test: StateTestFiller, + env: Environment, + pre: Alloc, + fork: Fork, + call_opcode: Op, +) -> None: + """ + Verify a call to a precompile is warm from the start. + + Precompiles are part of the accessed-addresses set from the start of + every transaction, so a call to one pays only ``WARM_ACCESS`` (100). + The identity precompile (address 4) is used as the target. + """ + gas_costs = fork.gas_costs() + + identity_precompile = Address(4) + + measured_code = call_opcode(gas=0, address=identity_precompile) + measure_address = _measure_call( + pre, fork, measured_code, call_opcode(address_warm=False) + ) + + expected_gas = call_opcode(address_warm=True).gas_cost(fork) + assert expected_gas == gas_costs.WARM_ACCESS + + tx = Transaction(to=measure_address, sender=pre.fund_eoa()) + + post = {measure_address: Account(storage={0: expected_gas})} + state_test(env=env, pre=pre, post=post, tx=tx) + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +@pytest.mark.parametrize( + "value", [0, 1], ids=["no_value_no_stipend", "value_grants_stipend"] +) +def test_call_value_stipend_is_usable( + state_test: StateTestFiller, + env: Environment, + pre: Alloc, + value: int, +) -> None: + """ + The ``CALL`` value-transfer stipend (``CALL_STIPEND`` = 2,300) is + forwarded to the callee and usable for execution. + + The caller forwards ``gas=0``, so the callee receives only the stipend + (2,300) when a positive value is sent, and nothing otherwise. The + callee runs a small amount of work (well under 2,300 gas) then stops: + with the stipend the call succeeds (returns 1); without value (no + stipend, zero forwarded gas) the work runs out of gas and the call + fails (returns 0). This proves the stipend is not merely returned but + is spendable by the callee. + """ + # ~250 gas of cheap work: comfortably within the 2,300 stipend, far + # above the zero gas forwarded when no value (so no stipend) is sent. + work = (Op.PUSH1(0) + Op.POP) * 50 + Op.STOP + callee = pre.deploy_contract(code=work) + + caller = pre.deploy_contract( + code=Op.SSTORE(0, Op.CALL(0, callee, value, 0, 0, 0, 0)), + balance=1, + ) + + tx = Transaction(to=caller, sender=pre.fund_eoa()) + + # 1 when the stipend funded the callee's work, 0 when it ran out. + post = {caller: Account(storage={0: 1 if value else 0})} + state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_create_gas.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_create_gas.py new file mode 100644 index 00000000000..862b6fc77a7 --- /dev/null +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_create_gas.py @@ -0,0 +1,553 @@ +""" +Tests for the EIP-8038 [State Access Gas Cost Increase](https://eips.ethereum.org/EIPS/eip-8038) +``CREATE``/``CREATE2`` regular-gas dimension. + +Under EIP-8038 the contract-creation opcodes are repriced in their +*regular* gas dimension to ``CREATE_ACCESS`` (``ACCOUNT_WRITE`` + +``COLD_STORAGE_ACCESS`` = 11,000), on top of which the EIP-3860 init +code word cost (2 per word) and, for ``CREATE2`` only, an additional +keccak word cost (6 per word) are charged. The new-account creation +and per-byte code deposit charges are the EIP-8037 *state* dimension, +covered in +``eip8037_state_creation_gas_cost_increase/test_state_gas_create.py``. + +These tests isolate and assert the EIP-8038 *regular* dimension. At the +contract-creating-transaction boundary the state component is re-derived +only to feed the ``max(regular, state)`` block-header accounting. +""" + +from typing import List + +import pytest +from execution_testing import ( + AccessList, + Account, + Address, + Alloc, + Bytecode, + CodeGasMeasure, + Fork, + Hash, + Header, + Initcode, + Op, + StateTestFiller, + Storage, + Transaction, + TransactionException, + compute_create_address, +) +from execution_testing.checklists import EIPChecklist + +from .spec import ref_spec_8038 + +REFERENCE_SPEC_GIT_PATH = ref_spec_8038.git_path +REFERENCE_SPEC_VERSION = ref_spec_8038.version + +pytestmark = pytest.mark.valid_from("Amsterdam") + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +@pytest.mark.with_all_create_opcodes() +@pytest.mark.parametrize( + "init_code_size", + [ + pytest.param(0, id="empty"), + pytest.param(32, id="one_word"), + pytest.param(33, id="two_words"), + pytest.param(96, id="three_words"), + ], +) +def test_create_regular_gas( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + create_opcode: Op, + init_code_size: int, +) -> None: + """ + Measure the regular gas of CREATE/CREATE2 and assert the schedule. + + The EIP-8038 *regular* dimension is ``CREATE_ACCESS`` (11,000) plus + the EIP-3860 init code word cost (2 per word) plus, for ``CREATE2`` + only, an additional keccak word cost (6 per word). The EIP-8037 + account-creation state gas is excluded by subtracting + ``create_state_gas(0)``. + """ + gas_costs = fork.gas_costs() + # The EIP-8038 CREATE regular base equals ACCOUNT_WRITE + + # COLD_STORAGE_ACCESS = 11,000. + assert gas_costs.OPCODE_CREATE_BASE == 11_000 + assert ( + gas_costs.OPCODE_CREATE_BASE + == gas_costs.ACCOUNT_WRITE + gas_costs.COLD_STORAGE_ACCESS + ) + + # Isolate the regular dimension: opcode total minus its account + # creation state gas (the only state component carried by the CREATE + # opcode itself; code deposit is charged on RETURN inside initcode). + create_meta = create_opcode(init_code_size=init_code_size) + regular_gas = create_meta.gas_cost(fork) - fork.create_state_gas( + code_size=0 + ) + # Equivalent isolation via the regular_cost helper. + assert regular_gas == create_meta.regular_cost(fork) + + init_code_words = (init_code_size + 31) // 32 + expected_regular = ( + gas_costs.OPCODE_CREATE_BASE + + gas_costs.CODE_INIT_PER_WORD * init_code_words + ) + if create_opcode == Op.CREATE2: + expected_regular += ( + gas_costs.OPCODE_KECCAK256_PER_WORD * init_code_words + ) + assert regular_gas == expected_regular + + # Runtime confirmation via CodeGasMeasure: a factory whose CREATE + # deploys empty code, so no code-deposit state gas is charged and the + # only state component is the account-creation gas funded from the + # reservoir. The initcode is brought into memory BEFORE the measured + # window, so the memory-expansion charge is excluded; the measured + # value is the CREATE opcode's regular cost exactly. The overhead + # subtracts the create-call argument pushes (the create leaves one + # stack item, its result). + # + # The initcode is all-zero bytes (`STOP`), so the child frame halts + # immediately consuming zero gas and deposits empty code. This keeps + # the measured value the CREATE opcode's own regular cost, with no + # child-execution gas folded in. `init_code_size` still drives the + # opcode's per-init-word charge. + padded_init = b"\x00" * init_code_size + + create_call = ( + Op.CREATE2(value=0, offset=0, size=init_code_size, salt=0) + if create_opcode == Op.CREATE2 + else Op.CREATE(value=0, offset=0, size=init_code_size) + ) + arg_pushes = (4 if create_opcode == Op.CREATE2 else 3) * gas_costs.VERY_LOW + + memory_setup = ( + Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE, new_memory_size=init_code_size) + if init_code_size + else Bytecode() + ) + storage = Storage() + measure = CodeGasMeasure( + code=create_call, + overhead_cost=arg_pushes, + extra_stack_items=1, + sstore_key=storage.store_next(regular_gas, "create_regular_gas"), + ) + factory = pre.deploy_contract(code=memory_setup + measure) + + tx = Transaction( + to=factory, + data=padded_init, + # Reservoir funds the account-creation state gas; leaving + # gas_limit unset keeps `Op.GAS` honest about gas_left. + state_gas_reservoir=fork.create_state_gas(code_size=0), + sender=pre.fund_eoa(), + ) + + post = {factory: Account(storage=storage)} + state_test(pre=pre, post=post, tx=tx) + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +@pytest.mark.parametrize( + "init_code_size", + [ + pytest.param(32, id="one_word"), + pytest.param(64, id="two_words"), + pytest.param(128, id="four_words"), + ], +) +def test_create2_keccak_word_delta( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + init_code_size: int, +) -> None: + """ + Verify CREATE2 costs exactly the keccak word surcharge over CREATE. + + ``CREATE2`` hashes the init code to derive the salted address, adding + ``OPCODE_KECCAK256_PER_WORD`` (6) per init-code word on top of the + regular cost shared with ``CREATE``. Both opcodes carry the identical + EIP-8038 ``CREATE_ACCESS`` base and EIP-3860 word cost. + + The regular-gas delta is asserted via the opcode model + (``create2_regular - create_regular`` equals the keccak word + surcharge). At runtime a factory then measures a single ``CREATE2`` + with ``CodeGasMeasure`` and stores its absolute regular cost: the + surcharge is established by the model assertion, and the runtime leg + confirms the absolute ``CREATE2`` regular cost. + """ + gas_costs = fork.gas_costs() + init_code_words = (init_code_size + 31) // 32 + keccak_surcharge = gas_costs.OPCODE_KECCAK256_PER_WORD * init_code_words + + create_regular = Op.CREATE(init_code_size=init_code_size).regular_cost( + fork + ) + create2_regular = Op.CREATE2(init_code_size=init_code_size).regular_cost( + fork + ) + assert create2_regular - create_regular == keccak_surcharge + + # Runtime confirmation. Init code is all-zero bytes (`STOP`), so the + # child frame halts immediately (zero gas) depositing empty code; the + # CREATE2 charges no code-deposit state gas and no child execution gas + # is folded into the measurement. The single CREATE2 regular cost is + # measured via CodeGasMeasure with a reservoir sized for its account + # creation state gas, keeping the GAS-measured `gas_left` free of + # state-gas spill. The opcode-model assertion above is the + # load-bearing keccak-delta check; this confirms the absolute value. + padded = b"\x00" * init_code_size + + push4 = 4 * gas_costs.VERY_LOW + storage = Storage() + measure_create2 = CodeGasMeasure( + code=Op.CREATE2(value=0, offset=0, size=init_code_size, salt=0), + overhead_cost=push4, + extra_stack_items=1, + sstore_key=storage.store_next(create2_regular, "create2_regular"), + ) + factory_code = ( + Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE, new_memory_size=init_code_size) + + measure_create2 + ) + factory = pre.deploy_contract(code=factory_code) + + tx = Transaction( + to=factory, + data=padded, + state_gas_reservoir=fork.create_state_gas(code_size=0), + sender=pre.fund_eoa(), + ) + + post = {factory: Account(storage=storage)} + state_test(pre=pre, post=post, tx=tx) + + +class TestCreateTxGasBoundary: + """ + Test the contract-creating-transaction gas boundary under EIP-8038. + + Four scenarios pin the boundary, mirroring EIP-3860's + ``TestContractCreationGasUsage`` but with the EIP-8037/8038 2D gas + split: + + 1. ``too_little_intrinsic_gas``: one below the total intrinsic; the + transaction is rejected (``INTRINSIC_GAS_TOO_LOW``). + 2. ``exact_intrinsic_gas``: exactly the intrinsic; the tx is valid + but the initcode runs out of execution gas. + 3. ``too_little_execution_gas``: one below the full execution gas; + creation fails but the tx is valid. + 4. ``exact_execution_gas``: exactly the full execution gas; creation + succeeds. + """ + + @pytest.fixture + def initcode(self) -> Initcode: + """Return a small initcode that deposits a multi-byte contract.""" + # Deploy 32 bytes (STOP + 31 padding) so code-deposit state gas + # is non-zero and the code-deposit branch is exercised. + return Initcode( + deploy_code=Op.STOP + Op.INVALID * 31, initcode_length=64 + ) + + @pytest.fixture + def tx_access_list(self) -> List[AccessList]: + """ + Return an access list to raise the intrinsic gas cost above the + EIP-7623 floor data cost, mirroring EIP-3860's fixture. + """ + return [ + AccessList(address=Address(i), storage_keys=[]) + for i in range(1, 642) + ] + + @pytest.fixture + def exact_intrinsic_gas( + self, + fork: Fork, + initcode: Initcode, + tx_access_list: List[AccessList], + ) -> int: + """Return the total (regular + state) intrinsic tx gas cost.""" + calc = fork.transaction_intrinsic_cost_calculator() + return calc( + calldata=initcode, + contract_creation=True, + access_list=tx_access_list, + ) + + @pytest.fixture + def exact_execution_gas( + self, fork: Fork, exact_intrinsic_gas: int, initcode: Initcode + ) -> int: + """ + Return the total execution gas: intrinsic plus the initcode + execution gas plus the code-deposit gas. + + ``deployment_gas`` is fork-aware: under EIP-8037 it splits the + deposit into the keccak word cost (regular) and the per-byte cost + (state), while on a fork without state-byte metering it is the + flat regular per-byte deposit cost. The single call is therefore + correct in either regime. + """ + execution = exact_intrinsic_gas + initcode.execution_gas(fork) + execution += initcode.deployment_gas(fork) + return execution + + @pytest.mark.parametrize( + "gas_test_case", + [ + pytest.param( + "too_little_intrinsic_gas", marks=pytest.mark.exception_test + ), + pytest.param("exact_intrinsic_gas"), + pytest.param("too_little_execution_gas"), + pytest.param("exact_execution_gas"), + ], + ) + @EIPChecklist.GasCostChanges.Test.OutOfGas() + def test_create_tx_gas_boundary( + self, + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + initcode: Initcode, + tx_access_list: List[AccessList], + exact_intrinsic_gas: int, + exact_execution_gas: int, + gas_test_case: str, + ) -> None: + """Drive a creation tx at each of the four gas boundary points.""" + sender = pre.fund_eoa() + create_address = compute_create_address(address=sender, nonce=0) + + if gas_test_case == "too_little_intrinsic_gas": + gas_limit = exact_intrinsic_gas - 1 + elif gas_test_case == "exact_intrinsic_gas": + gas_limit = exact_intrinsic_gas + elif gas_test_case == "too_little_execution_gas": + gas_limit = exact_execution_gas - 1 + else: + gas_limit = exact_execution_gas + + tx_error = ( + TransactionException.INTRINSIC_GAS_TOO_LOW + if gas_test_case == "too_little_intrinsic_gas" + else None + ) + + succeeds = gas_test_case == "exact_execution_gas" + post = { + create_address: ( + Account(code=initcode.deploy_code) + if succeeds + else Account.NONEXISTENT + ) + } + + tx = Transaction( + to=None, + data=initcode, + access_list=tx_access_list, + gas_limit=gas_limit, + error=tx_error, + sender=sender, + ) + + # 2D block accounting: gas_used = max(regular, state). The state + # axis carries the intrinsic NEW_ACCOUNT and (when the deposit + # succeeds) the per-byte code-deposit gas. + if tx_error is not None: + header_verify = None + else: + intrinsic_state = ( + fork.transaction_intrinsic_state_gas(contract_creation=True) + if hasattr(fork, "transaction_intrinsic_state_gas") + else 0 + ) + regular_used = gas_limit - intrinsic_state + state_used = intrinsic_state + if succeeds: + code_deposit_state = fork.code_deposit_state_gas( + code_size=len(initcode.deploy_code) + ) + state_used += code_deposit_state + regular_used -= code_deposit_state + header_verify = Header(gas_used=max(regular_used, state_used)) + + state_test( + pre=pre, + post=post, + tx=tx, + blockchain_test_header_verify=header_verify, + ) + + +@pytest.mark.with_all_create_opcodes() +@pytest.mark.parametrize( + "abort_mode", + [ + pytest.param("insufficient_balance", id="insufficient_balance"), + pytest.param("nonce_overflow", id="nonce_overflow"), + pytest.param(None, id="no_error"), + ], +) +def test_aborted_create_does_not_warm_address( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + create_opcode: Op, + abort_mode: str | None, +) -> None: + """ + Verify a silently-aborted CREATE does not warm the target address. + + When CREATE aborts before spawning the child frame (insufficient + balance for the endowment, or nonce overflow), the would-be address + is never added to the accessed-addresses set. A subsequent + ``BALANCE`` of that address is therefore charged the full + ``COLD_ACCOUNT_ACCESS`` (3,000), not ``WARM_ACCESS`` (100). + """ + init_code = Op.STOP + init_code_bytes = bytes(init_code) + init_code_len = len(init_code) + + create_value = 1 + create_call = create_opcode( + value=create_value, offset=0, size=init_code_len + ) + + # After the aborted CREATE, measure the BALANCE access of the + # would-be address (passed via calldata). + # The address should only be warm when the CREATE/CREATE2 opcode + # successfully reached initcode execution stage. + address_warm = abort_mode is None + balance_code = Op.BALANCE(Op.CALLDATALOAD(0), address_warm=address_warm) + measure = CodeGasMeasure(code=balance_code, extra_stack_items=1) + + setup = Op.MSTORE( + 0, + int.from_bytes(init_code_bytes, "big") << (256 - 8 * init_code_len), + ) + factory_code = setup + Op.POP(create_call) + measure + + factory_nonce = 2**64 - 1 if abort_mode == "nonce_overflow" else 1 + factory_balance = create_value + if abort_mode == "insufficient_balance": + factory_balance -= 1 + factory = pre.deploy_contract( + code=factory_code, nonce=factory_nonce, balance=factory_balance + ) + + target_address = compute_create_address( + address=factory, + salt=0, + initcode=init_code_bytes, + nonce=factory_nonce, + opcode=create_opcode, + ) + + tx = Transaction( + to=factory, + data=Hash(target_address, left_padding=True), + sender=pre.fund_eoa(), + ) + + # The BALANCE must be cold: in case of error, the aborted CREATE never + # warmed the would-be address. + post = { + factory: Account(storage={0: balance_code.gas_cost(fork)}), + target_address: Account(nonce=1) + if abort_mode is None + else Account.NONEXISTENT, + } + state_test(pre=pre, post=post, tx=tx) + + +@pytest.mark.pre_alloc_mutable +def test_create2_to_occupied_address( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify ``CREATE2`` to an occupied address creates nothing and refunds. + + When ``CREATE2`` targets an address that is not deployable (here an + already-deployed contract, whose ``code_hash`` is non-empty), the + creation aborts after the account-access charge: the opcode pushes + ``0``, bumps the factory's nonce, charges the message gas to the + regular dimension, and refunds the ``NEW_ACCOUNT`` *state* gas so no + net account-creation charge lands. No child frame runs, so the + occupied contract's code and storage are left untouched. + """ + # Initcode the factory passes to CREATE2; were the target free it + # would deposit a single STOP. The salt is fixed so the collision + # address is deterministic from the factory address. + init_code = Op.STOP + init_code_bytes = bytes(init_code) + init_code_len = len(init_code_bytes) + salt = 0 + + # Factory CREATE2s the calldata initcode and stores the pushed result; + # a collision pushes 0. The initcode is copied into memory before the + # CREATE2 so the address derivation hashes exactly ``init_code_bytes``. + storage = Storage() + factory_code = Op.CALLDATACOPY( + 0, 0, Op.CALLDATASIZE, new_memory_size=init_code_len + ) + Op.SSTORE( + storage.store_next(0, "create2_collision_result"), + Op.CREATE2(value=0, offset=0, size=init_code_len, salt=salt), + ) + factory = pre.deploy_contract(code=factory_code) + + # The address CREATE2 would compute from this factory, salt, and + # initcode. ``compute_create_address`` with ``opcode=Op.CREATE2`` is + # the unified EEST helper for the CREATE2 derivation. + collision_address = compute_create_address( + address=factory, + salt=salt, + initcode=init_code_bytes, + opcode=Op.CREATE2, + ) + + # Pre-occupy the collision address with a contract carrying distinct + # code and storage so a successful (and therefore incorrect) creation + # would be detectable. A non-empty ``code_hash`` makes the account + # non-deployable (``account_deployable`` is False). + # + # `address=` hard-codes the occupant at the derived collision address; + # it requires `pre_alloc_mutable`. This is the only way to pre-seat the + # exact CREATE2 target, mirroring the EIP-7610 collision suite. + occupant_code = Op.SSTORE(0, 0x42) + Op.STOP + occupant_storage = Storage({0x1: 0xCAFE}) # type: ignore[dict-item] + pre.deploy_contract( + code=occupant_code, + storage=occupant_storage, + nonce=1, + address=collision_address, + ) + + tx = Transaction( + to=factory, + data=init_code_bytes, + sender=pre.fund_eoa(), + ) + + # Factory stored a 0 result; the occupant is untouched (its initcode + # never ran, so slot 0 stays unset and slot 1 keeps its seeded value). + post = { + factory: Account(storage=storage), + collision_address: Account( + code=occupant_code, storage=occupant_storage + ), + } + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_eip_mainnet.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_eip_mainnet.py new file mode 100644 index 00000000000..210879bf6f3 --- /dev/null +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_eip_mainnet.py @@ -0,0 +1,265 @@ +""" +Mainnet-marked happy-path smoke tests for +[EIP-8038: State Access Gas Cost Increase](https://eips.ethereum.org/EIPS/eip-8038). + +One minimal success per repriced dimension (no boundaries, no exact +magnitudes): a state slot is written, a value-bearing cold ``CALL`` +lands, an ``EXTCODESIZE`` runs, a ``CREATE`` deploys a contract, a +``SELFDESTRUCT`` funds a fresh account, a single ``7702`` authorization +installs a delegation, and a re-authorization of an already-delegated +authority applies the existing-authority refund. Gas limits are +deliberately generous so these prove the operation runs under the +EIP-8038 schedule without re-deriving any per-opcode cost (other files +own those matrices). +""" + +import pytest +from execution_testing import ( + Account, + Alloc, + AuthorizationTuple, + Fork, + Op, + StateTestFiller, + Storage, + Transaction, +) +from execution_testing.checklists import EIPChecklist + +from ...prague.eip7702_set_code_tx.spec import Spec as Spec7702 +from .spec import ref_spec_8038 + +REFERENCE_SPEC_GIT_PATH = ref_spec_8038.git_path +REFERENCE_SPEC_VERSION = ref_spec_8038.version + +pytestmark = [pytest.mark.valid_at("Amsterdam"), pytest.mark.mainnet] + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +def test_sstore_zero_to_nonzero( + state_test: StateTestFiller, + pre: Alloc, +) -> None: + """ + A zero-to-nonzero ``SSTORE`` pays the EIP-8038 storage write and + succeeds, leaving the slot set. + """ + storage = Storage() + contract = pre.deploy_contract(code=Op.SSTORE(storage.store_next(1), 1)) + + tx = Transaction( + to=contract, + gas_limit=1_000_000, + sender=pre.fund_eoa(), + ) + + post = {contract: Account(storage=storage)} + state_test(pre=pre, post=post, tx=tx) + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +def test_cold_call_with_value( + state_test: StateTestFiller, + pre: Alloc, +) -> None: + """ + A value-bearing cold ``CALL`` pays ``COLD_ACCOUNT_ACCESS`` plus + ``CALL_VALUE`` and succeeds; the caller records the ``CALL`` success + flag and the callee receives the forwarded value. + """ + callee = pre.deploy_contract(code=Op.STOP, balance=0) + + caller_storage = Storage() + caller = pre.deploy_contract( + code=( + Op.SSTORE( + caller_storage.store_next(1), + Op.CALL(gas=100_000, address=callee, value=1), + ) + ), + ) + + tx = Transaction( + to=caller, + gas_limit=1_000_000, + value=1, + sender=pre.fund_eoa(), + ) + + post = { + caller: Account(storage=caller_storage), + callee: Account(balance=1), + } + state_test(pre=pre, post=post, tx=tx) + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +def test_extcodesize( + state_test: StateTestFiller, + pre: Alloc, +) -> None: + """ + ``EXTCODESIZE`` pays the EIP-8038 account access plus the code-read + surcharge and succeeds, returning the target's non-zero code size. + """ + target = pre.deploy_contract(code=Op.STOP * 3) + + storage = Storage() + contract = pre.deploy_contract( + code=Op.SSTORE(storage.store_next(3), Op.EXTCODESIZE(target)), + ) + + tx = Transaction( + to=contract, + gas_limit=1_000_000, + sender=pre.fund_eoa(), + ) + + post = {contract: Account(storage=storage)} + state_test(pre=pre, post=post, tx=tx) + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +def test_create_deploys_contract( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + A factory ``CREATE``s a one-byte (``STOP``) contract under the + EIP-8038 schedule and succeeds; the factory records the ``CREATE`` + success flag in a slot. The transaction supplies the CREATE state + gas via the reservoir. + """ + init_code = Op.STOP + init_word = int.from_bytes(bytes(init_code), "big") << ( + 256 - 8 * len(init_code) + ) + + storage = Storage() + factory = pre.deploy_contract( + code=( + Op.MSTORE(0, init_word) + + Op.SSTORE( + storage.store_next(True), + Op.GT(Op.CREATE(0, 0, len(init_code)), 0), + ) + ), + ) + + tx = Transaction( + to=factory, + gas_limit=1_000_000, + state_gas_reservoir=fork.create_state_gas(code_size=0), + sender=pre.fund_eoa(), + ) + + post = {factory: Account(storage=storage)} + state_test(pre=pre, post=post, tx=tx) + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +def test_selfdestruct_funds_new_account( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + A balance-bearing contract ``SELFDESTRUCT``s to a fresh beneficiary + under the EIP-8038 schedule, forwarding its balance. The new-account + state gas is supplied via the reservoir; the beneficiary ends up + holding the transferred balance. + """ + beneficiary = pre.fund_eoa(amount=0) + + suicidal = pre.deploy_contract( + code=Op.SELFDESTRUCT(beneficiary), + balance=1, + ) + + tx = Transaction( + to=suicidal, + gas_limit=1_000_000, + state_gas_reservoir=fork.gas_costs().NEW_ACCOUNT, + sender=pre.fund_eoa(), + ) + + post = {beneficiary: Account(balance=1)} + state_test(pre=pre, post=post, tx=tx) + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +def test_auth_installs_delegation( + state_test: StateTestFiller, + pre: Alloc, +) -> None: + """ + A single valid ``7702`` authorization pays the EIP-8038 auth + intrinsic and installs a delegation designation on the authority. + """ + auth_signer = pre.fund_eoa() + set_code_to = pre.deploy_contract(code=Op.STOP) + + authorization_list = [ + AuthorizationTuple( + address=set_code_to, + nonce=0, + signer=auth_signer, + ), + ] + + tx = Transaction( + to=auth_signer, + gas_limit=1_000_000, + authorization_list=authorization_list, + sender=pre.fund_eoa(), + ) + + post = { + auth_signer: Account( + nonce=1, + code=Spec7702.delegation_designation(set_code_to), + ), + } + state_test(pre=pre, post=post, tx=tx) + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +def test_existing_authority_refund( + state_test: StateTestFiller, + pre: Alloc, +) -> None: + """ + Re-authorizing an already-delegated authority applies the + existing-authority refund and re-points the delegation; the tx + succeeds with the new designation installed. + """ + old_target = pre.deploy_contract(code=Op.STOP) + new_target = pre.deploy_contract(code=Op.STOP) + + # Authority already carries a delegation, so the new authorization + # triggers REFUND_AUTH_PER_EXISTING_ACCOUNT. + auth_signer = pre.fund_eoa(delegation=old_target) + + authorization_list = [ + AuthorizationTuple( + address=new_target, + nonce=1, + signer=auth_signer, + ), + ] + + tx = Transaction( + to=auth_signer, + gas_limit=1_000_000, + authorization_list=authorization_list, + sender=pre.fund_eoa(), + ) + + post = { + auth_signer: Account( + nonce=2, + code=Spec7702.delegation_designation(new_target), + ), + } + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_exact_balance_no_fallback.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_exact_balance_no_fallback.py new file mode 100644 index 00000000000..40376c8af55 --- /dev/null +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_exact_balance_no_fallback.py @@ -0,0 +1,222 @@ +""" +No-silent-fallback exact-balance tests for +[EIP-8038: State Access Gas Cost Increase](https://eips.ethereum.org/EIPS/eip-8038). + +Each test funds the sender with *exactly* ``gas_limit * gas_price`` and +sets ``gas_limit`` one gas below the spec-correct Amsterdam intrinsic for +a single repriced dimension. A spec-correct client therefore rejects the +transaction with ``INTRINSIC_GAS_TOO_LOW``; a client that silently fell +back to the pre-Amsterdam value for that one constant would have computed +a strictly smaller intrinsic (``new - per_unit_delta``) and could have +executed the transaction. Because the sender holds no surplus wei, there +is no room for such a fallback to hide. + +The pre-Amsterdam (old) per-component value is read from the parent +fork's schedule (``fork.parent()``); the spec-correct intrinsic is read +from the active fork's intrinsic calculator. Nothing is hardcoded; the +gap is asserted to be positive so the construction is only emitted when +the dimension genuinely got more expensive. +""" + +import pytest +from execution_testing import ( + AccessList, + Alloc, + AuthorizationTuple, + Fork, + StateTestFiller, + Transaction, + TransactionException, +) +from execution_testing.checklists import EIPChecklist + +from .spec import ref_spec_8038 + +REFERENCE_SPEC_GIT_PATH = ref_spec_8038.git_path +REFERENCE_SPEC_VERSION = ref_spec_8038.version + +pytestmark = pytest.mark.valid_from("Amsterdam") + +GAS_PRICE = 10 + + +@EIPChecklist.GasCostChanges.Test.OutOfGas() +@pytest.mark.exception_test +@pytest.mark.parametrize( + "num_addresses,num_keys", + [ + pytest.param(1, 0, id="one_address"), + pytest.param(2, 0, id="two_addresses"), + pytest.param(1, 1, id="one_address_one_key"), + ], +) +def test_access_list_no_fallback( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + num_addresses: int, + num_keys: int, +) -> None: + """ + Reject an access-list transaction whose ``gas_limit`` is one gas + below the Amsterdam intrinsic. + + EIP-8038 raises ``TX_ACCESS_LIST_ADDRESS`` (2400 -> 3000) and + ``TX_ACCESS_LIST_STORAGE_KEY`` (1900 -> 3000). A client reusing the + old per-address/per-key constants would compute an intrinsic smaller + by ``num_addresses * addr_delta + num_keys * key_delta``; with the + sender funded to the wei, that fallback must not slip through. + """ + new_costs = fork.gas_costs() + old_costs = fork.parent_or_fail().gas_costs() + addr_delta = ( + new_costs.TX_ACCESS_LIST_ADDRESS - old_costs.TX_ACCESS_LIST_ADDRESS + ) + key_delta = ( + new_costs.TX_ACCESS_LIST_STORAGE_KEY + - old_costs.TX_ACCESS_LIST_STORAGE_KEY + ) + fallback_delta = num_addresses * addr_delta + num_keys * key_delta + assert fallback_delta > 0 + + # All storage keys live on the first listed address; the remaining + # addresses carry no keys. + storage_keys = list(range(num_keys)) + access_list = [ + AccessList( + address=pre.fund_eoa(amount=0), + storage_keys=storage_keys if index == 0 else [], + ) + for index in range(num_addresses) + ] + + intrinsic = fork.transaction_intrinsic_cost_calculator()( + access_list=access_list, + return_cost_deducted_prior_execution=True, + ) + # One gas below the spec-correct intrinsic: a fallback client using + # the old constants needs only `intrinsic - fallback_delta`. + gas_limit = intrinsic - 1 + assert intrinsic - fallback_delta <= gas_limit < intrinsic + + sender = pre.fund_eoa(amount=gas_limit * GAS_PRICE) + tx = Transaction( + sender=sender, + to=pre.fund_eoa(amount=0), + access_list=access_list, + gas_limit=gas_limit, + gas_price=GAS_PRICE, + error=TransactionException.INTRINSIC_GAS_TOO_LOW, + ) + + state_test(pre=pre, post={}, tx=tx) + + +@EIPChecklist.GasCostChanges.Test.OutOfGas() +@pytest.mark.exception_test +@pytest.mark.parametrize( + "num_auths", + [ + pytest.param(1, id="one_auth"), + pytest.param(2, id="two_auths"), + ], +) +def test_authorization_no_fallback( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + num_auths: int, +) -> None: + """ + Reject a ``7702`` set-code transaction whose ``gas_limit`` is one + gas below the Amsterdam intrinsic. + + EIP-8038 raises the per-authorization intrinsic + (``AUTH_PER_EMPTY_ACCOUNT``). A client reusing the old per-auth + constant would compute an intrinsic smaller by + ``num_auths * auth_delta``; the exact-balance sender leaves no slack + for that fallback. + """ + new_costs = fork.gas_costs() + old_costs = fork.parent_or_fail().gas_costs() + auth_delta = ( + new_costs.AUTH_PER_EMPTY_ACCOUNT - old_costs.AUTH_PER_EMPTY_ACCOUNT + ) + fallback_delta = num_auths * auth_delta + assert fallback_delta > 0 + + target = pre.deploy_contract(code=b"") + authorization_list = [ + AuthorizationTuple( + address=target, + nonce=0, + signer=pre.fund_eoa(), + ) + for _ in range(num_auths) + ] + + intrinsic = fork.transaction_intrinsic_cost_calculator()( + authorization_list_or_count=authorization_list, + return_cost_deducted_prior_execution=True, + ) + gas_limit = intrinsic - 1 + assert intrinsic - fallback_delta <= gas_limit < intrinsic + + # Set-code (type-4) txs require EIP-1559 fee fields. With + # max_fee == max_priority and value 0, the upfront debit the + # protocol reserves is exactly gas_limit * GAS_PRICE. + sender = pre.fund_eoa(amount=gas_limit * GAS_PRICE) + tx = Transaction( + sender=sender, + to=pre.fund_eoa(amount=0), + authorization_list=authorization_list, + gas_limit=gas_limit, + max_fee_per_gas=GAS_PRICE, + max_priority_fee_per_gas=GAS_PRICE, + error=TransactionException.INTRINSIC_GAS_TOO_LOW, + ) + + state_test(pre=pre, post={}, tx=tx) + + +@EIPChecklist.GasCostChanges.Test.OutOfGas() +@pytest.mark.exception_test +def test_cold_account_access_no_fallback( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Reject a plain call transaction whose ``gas_limit`` is one gas below + the Amsterdam intrinsic. + + Under EIP-2780 every non-create, non-self transaction pays one + ``COLD_ACCOUNT_ACCESS`` in its intrinsic for touching the recipient; + EIP-8038 raises that constant (2600 -> 3000). A client reusing the + old ``COLD_ACCOUNT_ACCESS`` would compute an intrinsic smaller by the + per-access delta, and with the sender funded to the wei that fallback + must not execute. + """ + new_costs = fork.gas_costs() + old_costs = fork.parent_or_fail().gas_costs() + fallback_delta = ( + new_costs.COLD_ACCOUNT_ACCESS - old_costs.COLD_ACCOUNT_ACCESS + ) + assert fallback_delta > 0 + + intrinsic = fork.transaction_intrinsic_cost_calculator()( + return_cost_deducted_prior_execution=True, + ) + gas_limit = intrinsic - 1 + assert intrinsic - fallback_delta <= gas_limit < intrinsic + + sender = pre.fund_eoa(amount=gas_limit * GAS_PRICE) + tx = Transaction( + sender=sender, + to=pre.deploy_contract(code=b""), + gas_limit=gas_limit, + gas_price=GAS_PRICE, + error=TransactionException.INTRINSIC_GAS_TOO_LOW, + ) + + state_test(pre=pre, post={}, tx=tx) diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_ext_code_opcodes_gas.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_ext_code_opcodes_gas.py new file mode 100644 index 00000000000..90f446398ce --- /dev/null +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_ext_code_opcodes_gas.py @@ -0,0 +1,471 @@ +""" +Tests for [EIP-8038: State Access Gas Cost Increase](https://eips.ethereum.org/EIPS/eip-8038). + +Covers the EIP-8038 ``EXT*`` "double-read" surcharge: ``EXTCODESIZE`` and +``EXTCODECOPY`` perform two database reads (the account leaf and then the +code) and are therefore charged an extra ``WARM_ACCESS`` on top of the +account-access cost, whereas ``BALANCE`` and ``EXTCODEHASH`` read only the +account leaf and are charged the account-access cost alone. +""" + +from typing import Callable + +import pytest +from execution_testing import ( + AccessList, + Account, + Address, + Alloc, + Bytecode, + CodeGasMeasure, + Environment, + Fork, + Op, + StateTestFiller, + Storage, + Transaction, +) +from execution_testing.checklists import EIPChecklist + +from .spec import ref_spec_8038 + +REFERENCE_SPEC_GIT_PATH = ref_spec_8038.git_path +REFERENCE_SPEC_VERSION = ref_spec_8038.version + +pytestmark = pytest.mark.valid_from("Amsterdam") + + +# Each parameter carries: +# - executable: builds the runnable opcode targeting ``target`` +# - cost_metadata: builds the metadata-only opcode for gas computation +# - extra_stack_items: stack items left by the opcode (for CodeGasMeasure) +# - code_read_surcharge: whether EIP-8038 adds the extra WARM_ACCESS read +EXT_OPCODES = [ + pytest.param( + lambda target: Op.EXTCODESIZE(target), + lambda warm: Op.EXTCODESIZE(address_warm=warm), + 1, + True, + id="EXTCODESIZE", + ), + pytest.param( + lambda target: Op.EXTCODECOPY(target, 0, 0, 0), + lambda warm: Op.EXTCODECOPY(address_warm=warm), + 0, + True, + id="EXTCODECOPY", + ), + pytest.param( + lambda target: Op.EXTCODEHASH(target), + lambda warm: Op.EXTCODEHASH(address_warm=warm), + 1, + False, + id="EXTCODEHASH", + ), + pytest.param( + lambda target: Op.BALANCE(target), + lambda warm: Op.BALANCE(address_warm=warm), + 1, + False, + id="BALANCE", + ), +] + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +@pytest.mark.parametrize("warm", [False, True], ids=["cold", "warm"]) +@pytest.mark.parametrize( + "executable,cost_metadata,extra_stack_items,code_read_surcharge", + EXT_OPCODES, +) +def test_ext_code_opcode_gas( + state_test: StateTestFiller, + env: Environment, + pre: Alloc, + fork: Fork, + warm: bool, + executable: Callable[[object], Bytecode], + cost_metadata: Callable[[bool], Bytecode], + extra_stack_items: int, + code_read_surcharge: bool, +) -> None: + """ + Measure the exact gas of an external-code/account-access opcode and + assert it matches the EIP-8038 schedule. + + ``EXTCODESIZE``/``EXTCODECOPY`` must cost exactly one ``WARM_ACCESS`` + more than ``BALANCE``/``EXTCODEHASH`` at equal warmth (the second, + code-reading database access). + """ + gas_costs = fork.gas_costs() + + target = pre.deploy_contract(Op.STOP) + + measured_code = executable(target) + # Subtract the opcode's OWN cold cost (not BALANCE's) so the + # CodeGasMeasure overhead excludes only the PUSH wrapper; under + # EIP-8038 EXTCODESIZE/EXTCODECOPY have a higher cold cost than + # BALANCE because of the code-read surcharge. + overhead_cost = measured_code.gas_cost(fork) - cost_metadata( + False + ).gas_cost(fork) + + code_gas_measure = CodeGasMeasure( + code=measured_code, + overhead_cost=overhead_cost, + extra_stack_items=extra_stack_items, + ) + measure_address = pre.deploy_contract(code=code_gas_measure) + + access_cost = ( + gas_costs.WARM_ACCESS if warm else gas_costs.COLD_ACCOUNT_ACCESS + ) + surcharge = gas_costs.WARM_ACCESS if code_read_surcharge else 0 + expected_gas = access_cost + surcharge + # Cross-check the framework opcode model agrees with the formula. + assert expected_gas == cost_metadata(warm).gas_cost(fork) + + # Warm the target via the access list when required; the cold case + # leaves it absent so its first runtime access is cold. + tx = Transaction( + to=measure_address, + sender=pre.fund_eoa(), + access_list=[AccessList(address=target, storage_keys=[])] + if warm + else None, + ) + + post = {measure_address: Account(storage={0: expected_gas})} + + state_test(env=env, pre=pre, post=post, tx=tx) + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +@pytest.mark.parametrize("warm", [False, True], ids=["cold", "warm"]) +@pytest.mark.parametrize( + "copy_size", [32, 96], ids=["one_word", "three_words"] +) +def test_extcodecopy_nonzero_composes_additively( + state_test: StateTestFiller, + env: Environment, + pre: Alloc, + fork: Fork, + warm: bool, + copy_size: int, +) -> None: + """ + Verify the EIP-8038 ``EXTCODECOPY`` surcharge composes additively. + + With a non-zero copy size, ``EXTCODECOPY`` charges the account-access + cost, the EIP-8038 code-read ``WARM_ACCESS`` surcharge, the EIP-150 + per-word copy cost (``OPCODE_COPY_PER_WORD`` per word, driven by the + copied data size), and the memory-expansion cost. The surcharge is a + flat add-on that does not interact with the copy or memory terms, so + the measured gas must equal the sum of all four components. + """ + gas_costs = fork.gas_costs() + + # Target carries enough code to satisfy the copy; STOP padding keeps + # it a deployable contract with a non-empty code hash. + target = pre.deploy_contract(Op.STOP * copy_size) + + # Runnable opcode copying ``copy_size`` bytes of the target's code into + # memory at offset 0. The metadata mirrors the runtime effect (warmth, + # copied byte count, and the 0 -> copy_size memory growth) so the + # opcode model agrees with execution and the overhead reduces to the + # operand pushes alone. + measured_code = Op.EXTCODECOPY.with_metadata( + address_warm=warm, + data_size=copy_size, + new_memory_size=copy_size, + old_memory_size=0, + )(target, 0, 0, copy_size) + + # Oracle: the same metadata-only opcode. Subtracting its cost from the + # measured code's cost yields the CodeGasMeasure overhead (the operand + # PUSHes only), so the stored value equals exactly this opcode cost. + oracle = Op.EXTCODECOPY.with_metadata( + address_warm=warm, + data_size=copy_size, + new_memory_size=copy_size, + old_memory_size=0, + ) + expected_gas = oracle.gas_cost(fork) + + # Additive decomposition the surcharge must satisfy. + words = (copy_size + 31) // 32 + access_cost = ( + gas_costs.WARM_ACCESS if warm else gas_costs.COLD_ACCOUNT_ACCESS + ) + memory_expansion = fork.memory_expansion_gas_calculator()( + new_bytes=copy_size, previous_bytes=0 + ) + assert expected_gas == ( + access_cost + + gas_costs.WARM_ACCESS # EIP-8038 code-read surcharge + + gas_costs.OPCODE_COPY_PER_WORD * words + + memory_expansion + ) + + code_gas_measure = CodeGasMeasure( + code=measured_code, + overhead_cost=measured_code.gas_cost(fork) - oracle.gas_cost(fork), + extra_stack_items=0, + ) + measure_address = pre.deploy_contract(code=code_gas_measure) + + tx = Transaction( + to=measure_address, + sender=pre.fund_eoa(), + access_list=[AccessList(address=target, storage_keys=[])] + if warm + else None, + ) + + post = {measure_address: Account(storage={0: expected_gas})} + state_test(env=env, pre=pre, post=post, tx=tx) + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +@pytest.mark.parametrize("warm", [False, True], ids=["cold", "warm"]) +def test_extcodehash_empty_account( + state_test: StateTestFiller, + env: Environment, + pre: Alloc, + fork: Fork, + warm: bool, +) -> None: + """ + Verify ``EXTCODEHASH`` of an empty account is priced without surcharge. + + ``EXTCODEHASH`` reads only the account leaf, so EIP-8038 adds no + code-read surcharge: the cost is exactly ``COLD_ACCOUNT_ACCESS`` (cold) + or ``WARM_ACCESS`` (warm) regardless of the target being empty. The + returned hash of an empty/non-existent account is ``0``. + """ + gas_costs = fork.gas_costs() + + # A non-existent (empty) target: never deployed, no balance, no code. + empty_addr = Address(0xDEAD) + + expected_gas = ( + gas_costs.WARM_ACCESS if warm else gas_costs.COLD_ACCOUNT_ACCESS + ) + # No code-read surcharge for EXTCODEHASH; the opcode model must agree. + assert expected_gas == Op.EXTCODEHASH(address_warm=warm).gas_cost(fork) + + # Measure the access cost, then store the returned hash so the + # empty-account 0 result is asserted alongside the pricing. The + # measured opcode carries the runtime warmth so the overhead reduces + # to the address PUSH alone. + # + # The empty-account hash is 0, which is also the default of an + # unwritten storage slot: a stranded hash store would leave slot 1 at + # 0 and pass vacuously (the original defect). Slot 1 is poisoned with + # a non-zero sentinel before the measured region, so the real store + # must overwrite it back to 0. If that store is ever stranded, slot 1 + # keeps the sentinel and the assertion fails instead of silently + # passing. + # The poison precedes the measured region and the hash store follows + # it, so neither touches 0xDEAD before the measured access nor + # perturbs the cold-case gas measurement. + storage = Storage() + measured_code = Op.EXTCODEHASH.with_metadata(address_warm=warm)(empty_addr) + gas_slot = storage.store_next(expected_gas, "extcodehash_empty_gas") + hash_slot = storage.store_next(0, "extcodehash_empty_hash") + hash_slot_sentinel = 0xBADC0FFEE + code = ( + Op.SSTORE(hash_slot, hash_slot_sentinel) + + CodeGasMeasure( + code=measured_code, + overhead_cost=measured_code.gas_cost(fork) + - Op.EXTCODEHASH(address_warm=warm).gas_cost(fork), + extra_stack_items=1, + sstore_key=gas_slot, + ) + + Op.SSTORE(hash_slot, Op.EXTCODEHASH(empty_addr)) + ) + measure_address = pre.deploy_contract(code=code) + + tx = Transaction( + to=measure_address, + sender=pre.fund_eoa(), + access_list=[AccessList(address=empty_addr, storage_keys=[])] + if warm + else None, + ) + + post = {measure_address: Account(storage=storage)} + state_test(env=env, pre=pre, post=post, tx=tx) + + +# The two surcharge opcodes only -- both always pay the second, +# code-reading access, so there is no no-surcharge variant here. +DOUBLE_READ_OPCODES = [Op.EXTCODESIZE, Op.EXTCODECOPY] + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +@pytest.mark.parametrize("warm", [False, True], ids=["cold", "warm"]) +@pytest.mark.parametrize("opcode", DOUBLE_READ_OPCODES) +def test_ext_code_double_read_empty_account( + state_test: StateTestFiller, + env: Environment, + pre: Alloc, + fork: Fork, + warm: bool, + opcode: Op, +) -> None: + """ + Charge the EIP-8038 double-read surcharge on an empty target. + + ``EXTCODESIZE``/``EXTCODECOPY`` add the second, code-reading database + access unconditionally: the surcharge is charged before the account is + read, so an empty/non-existent target still costs + ``COLD_ACCOUNT_ACCESS + WARM_ACCESS`` (cold) or ``2 * WARM_ACCESS`` + (warm), i.e. 3100 / 200, exactly as for a code-bearing target. This + contrasts with ``EXTCODEHASH``/``BALANCE``, which read only the account + leaf and carry no surcharge (see ``test_extcodehash_empty_account``). A + client that skipped the second read for code-less accounts would be + caught here. + """ + # Never deployed: no code, no balance, non-existent account. + empty_addr = pre.nonexistent_account() + + measured_code = opcode(address=empty_addr) + # Subtract the opcode's OWN cold cost so the CodeGasMeasure overhead is + # only the operand PUSH wrapper; the surcharge is part of the cold cost. + overhead_cost = measured_code.gas_cost(fork) - opcode( + address_warm=False + ).gas_cost(fork) + + code_gas_measure = CodeGasMeasure( + code=measured_code, + overhead_cost=overhead_cost, + extra_stack_items=opcode.pushed_stack_items, + ) + measure_address = pre.deploy_contract(code=code_gas_measure) + expected_gas = opcode(address_warm=warm).gas_cost(fork) + + tx = Transaction( + to=measure_address, + sender=pre.fund_eoa(), + access_list=[AccessList(address=empty_addr, storage_keys=[])] + if warm + else None, + ) + + post = {measure_address: Account(storage={0: expected_gas})} + + state_test(env=env, pre=pre, post=post, tx=tx) + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +@pytest.mark.parametrize("warm", [False, True], ids=["cold", "warm"]) +def test_extcodesize_empty_account_returns_zero( + state_test: StateTestFiller, + env: Environment, + pre: Alloc, + fork: Fork, + warm: bool, +) -> None: + """ + Pay the surcharge on an empty target while ``EXTCODESIZE`` returns 0. + + The size returned for an empty/non-existent account is ``0``, which + confirms the target genuinely has no code: the measured + ``COLD_ACCOUNT_ACCESS + WARM_ACCESS`` (cold) / ``2 * WARM_ACCESS`` + (warm) cost is therefore unambiguously the surcharge applied to an + empty account, not an artifact of the target accidentally holding code. + """ + # Never deployed: no code, no balance, non-existent account. + empty_addr = pre.nonexistent_account() + + expected_gas = Op.EXTCODESIZE(address_warm=warm).gas_cost(fork) + + # Measure the access cost and, separately, store the returned size so + # the empty-account 0 result is asserted alongside the pricing. The + # measured opcode carries the runtime warmth so the overhead reduces to + # the address PUSH alone. + storage = Storage() + measured_code = Op.EXTCODESIZE.with_metadata(address_warm=warm)(empty_addr) + gas_slot = storage.store_next(expected_gas, "extcodesize_empty_gas") + size_slot = storage.store_next(0, "extcodesize_empty_size") + code = CodeGasMeasure( + code=measured_code, + overhead_cost=measured_code.gas_cost(fork) + - Op.EXTCODESIZE(address_warm=warm).gas_cost(fork), + extra_stack_items=1, + sstore_key=gas_slot, + ) + Op.SSTORE(size_slot, Op.EXTCODESIZE(empty_addr)) + measure_address = pre.deploy_contract(code=code) + + tx = Transaction( + to=measure_address, + sender=pre.fund_eoa(), + access_list=[AccessList(address=empty_addr, storage_keys=[])] + if warm + else None, + ) + + post = {measure_address: Account(storage=storage)} + state_test(env=env, pre=pre, post=post, tx=tx) + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +@pytest.mark.parametrize("warm", [False, True], ids=["cold", "warm"]) +@pytest.mark.parametrize( + "copy_size", [32, 96], ids=["one_word", "three_words"] +) +def test_extcodecopy_empty_account_composes_additively( + state_test: StateTestFiller, + env: Environment, + pre: Alloc, + fork: Fork, + warm: bool, + copy_size: int, +) -> None: + """ + Compose the surcharge additively when copying from an empty account. + + ``EXTCODECOPY`` of a non-existent source copies zero bytes into memory, + yet still charges the account-access cost, the EIP-8038 code-read + ``WARM_ACCESS`` surcharge, the EIP-150 per-word copy cost + (``OPCODE_COPY_PER_WORD`` per word, driven by the requested size, not + the source length), and the memory-expansion cost. The measured gas + must equal the sum of all four components, confirming the surcharge + composes additively even when there is no code to read. + """ + # Empty source: never deployed, no code. The copy yields zeros, but the + # cost is driven by the requested size, identical to a code-bearing + # source of the same length. + empty_addr = pre.nonexistent_account() + + oracle = Op.EXTCODECOPY.with_metadata( + address_warm=warm, + data_size=copy_size, + new_memory_size=copy_size, + old_memory_size=0, + ) + measured_code = oracle( + address=empty_addr, dest_offset=0, offset=0, size=copy_size + ) + + expected_gas = oracle.gas_cost(fork) + + code_gas_measure = CodeGasMeasure( + code=measured_code, + overhead_cost=measured_code.gas_cost(fork) - oracle.gas_cost(fork), + extra_stack_items=0, + ) + measure_address = pre.deploy_contract(code=code_gas_measure) + + tx = Transaction( + to=measure_address, + sender=pre.fund_eoa(), + access_list=[AccessList(address=empty_addr, storage_keys=[])] + if warm + else None, + ) + + post = {measure_address: Account(storage={0: expected_gas})} + state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_fork_transition.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_fork_transition.py new file mode 100644 index 00000000000..5ff91b0bbf6 --- /dev/null +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_fork_transition.py @@ -0,0 +1,523 @@ +""" +Fork-transition tests for +[EIP-8038: State Access Gas Cost Increase](https://eips.ethereum.org/EIPS/eip-8038). + +"Same operation, different gas" across the Amsterdam boundary. A block +at ``timestamp=14_999`` runs under the pre-fork (parent) schedule; a +block at ``timestamp=15_000`` runs under the EIP-8038 schedule. Every +before/after magnitude is derived from +``fork.fork_at(timestamp=...).gas_costs()`` — nothing is hardcoded. + +Two proof styles are used: + +* Account-access dimensions that are pure regular gas (``BALANCE`` cold + access and the ``EXT*`` code-read surcharge) are measured exactly with + ``CodeGasMeasure`` in each regime and asserted against the derived + cost. +* Constant repricings that the runtime opcode model cannot isolate + without state-gas confounders (``CALL_VALUE``, ``CREATE`` base, + ``SELFDESTRUCT`` account-write) are asserted at the constant level + from the derived schedules while the operation is still exercised in + both blocks to prove it runs in each regime. +* The authorization intrinsic rise is proven behaviourally: a tx whose + ``gas_limit`` equals the old auth intrinsic is valid before the fork + and rejected with ``INTRINSIC_GAS_TOO_LOW`` after. +""" + +from typing import List + +import pytest +from execution_testing import ( + Account, + Address, + Alloc, + AuthorizationTuple, + Block, + BlockchainTestFiller, + Bytecode, + CodeGasMeasure, + Fork, + Op, + Storage, + Transaction, + TransactionException, +) +from execution_testing.checklists import EIPChecklist + +from .spec import ref_spec_8038 + +REFERENCE_SPEC_GIT_PATH = ref_spec_8038.git_path +REFERENCE_SPEC_VERSION = ref_spec_8038.version + +pytestmark = pytest.mark.valid_at_transition_to("Amsterdam") + +# Block timestamps straddling the Amsterdam activation. +BEFORE_TS = 14_999 +AFTER_TS = 15_000 + + +def _measure_contract( + pre: Alloc, measured: Bytecode, opcode_cost: int, fork: Fork +) -> Address: + """ + Deploy a contract that stores the exact gas consumed by the measured + opcode in slot 0. + + ``measured`` is the runnable expression (opcode plus its PUSH + operands); ``opcode_cost`` is the bare opcode's own gas at ``fork``. + The wrapper overhead (the PUSH operands) is the difference between + the two, so ``CodeGasMeasure`` strips it and slot 0 holds only the + opcode's own cost. The opcode leaves one stack item (its result). + """ + overhead = measured.gas_cost(fork) - opcode_cost + code = CodeGasMeasure( + code=measured, + overhead_cost=overhead, + extra_stack_items=1, + ) + return pre.deploy_contract(code=code) + + +def transition_blocks( + before_to: Address, + after_to: Address, + pre: Alloc, + *, + value: int = 0, +) -> List[Block]: + """ + Return the two blocks that straddle the Amsterdam activation. + + The first block runs at ``BEFORE_TS`` (pre-fork schedule) and the + second at ``AFTER_TS`` (EIP-8038 schedule). Each carries a single + transaction from a fresh sender to its respective ``to`` target, + forwarding ``value`` so a value-bearing operation is exercised in both + regimes. + """ + return [ + Block( + timestamp=BEFORE_TS, + txs=[ + Transaction(to=before_to, value=value, sender=pre.fund_eoa()), + ], + ), + Block( + timestamp=AFTER_TS, + txs=[ + Transaction(to=after_to, value=value, sender=pre.fund_eoa()), + ], + ), + ] + + +@EIPChecklist.GasCostChanges.Test.ForkTransition.Before() +@EIPChecklist.GasCostChanges.Test.ForkTransition.After() +def test_cold_account_access_at_transition( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + ``BALANCE`` of a cold account costs ``COLD_ACCOUNT_ACCESS``, which + rises across the Amsterdam boundary (2600 -> 3000 on mainnet). The + same opcode is measured before and after; each block asserts its + regime's derived cost. + """ + before = fork.fork_at(timestamp=BEFORE_TS) + after = fork.fork_at(timestamp=AFTER_TS) + + cost_before = before.gas_costs().COLD_ACCOUNT_ACCESS + cost_after = after.gas_costs().COLD_ACCOUNT_ACCESS + assert cost_after > cost_before + + target = pre.deploy_contract(code=Op.STOP) + + # A distinct cold target per block keeps each measurement cold. + target_after = pre.deploy_contract(code=Op.STOP) + + # BALANCE has no code-read surcharge, so its bare cost equals + # COLD_ACCOUNT_ACCESS in each regime. + measure_before = _measure_contract( + pre, Op.BALANCE(target), cost_before, before + ) + measure_after = _measure_contract( + pre, Op.BALANCE(target_after), cost_after, after + ) + + blocks = transition_blocks(measure_before, measure_after, pre) + + post = { + measure_before: Account(storage={0: cost_before}), + measure_after: Account(storage={0: cost_after}), + } + blockchain_test(pre=pre, blocks=blocks, post=post) + + +@EIPChecklist.GasCostChanges.Test.ForkTransition.Before() +@EIPChecklist.GasCostChanges.Test.ForkTransition.After() +def test_ext_code_surcharge_at_transition( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + The EIP-8038 ``EXT*`` code-read surcharge appears at the fork. The + surcharge equals ``EXTCODESIZE`` minus ``BALANCE`` at equal warmth: + it is zero before the fork and one ``WARM_ACCESS`` (100) after. That + comparison is computed from the opcode model. On-chain, each block + measures only a cold ``EXTCODESIZE`` (2600 before, 3100 after): its + rise reflects the surcharge on top of the cold-access repricing, and + ``BALANCE`` is never executed. + """ + before = fork.fork_at(timestamp=BEFORE_TS) + after = fork.fork_at(timestamp=AFTER_TS) + + surcharge_before = Op.EXTCODESIZE(address_warm=True).gas_cost( + before + ) - Op.BALANCE(address_warm=True).gas_cost(before) + surcharge_after = Op.EXTCODESIZE(address_warm=True).gas_cost( + after + ) - Op.BALANCE(address_warm=True).gas_cost(after) + assert surcharge_before == 0 + assert surcharge_after == after.gas_costs().WARM_ACCESS + assert surcharge_after > surcharge_before + + extcodesize_cost_before = Op.EXTCODESIZE(address_warm=False).gas_cost( + before + ) + extcodesize_cost_after = Op.EXTCODESIZE(address_warm=False).gas_cost(after) + assert extcodesize_cost_after > extcodesize_cost_before + + target = pre.deploy_contract(code=Op.STOP) + target_after = pre.deploy_contract(code=Op.STOP) + + measure_before = _measure_contract( + pre, Op.EXTCODESIZE(target), extcodesize_cost_before, before + ) + measure_after = _measure_contract( + pre, Op.EXTCODESIZE(target_after), extcodesize_cost_after, after + ) + + blocks = transition_blocks(measure_before, measure_after, pre) + + post = { + measure_before: Account(storage={0: extcodesize_cost_before}), + measure_after: Account(storage={0: extcodesize_cost_after}), + } + blockchain_test(pre=pre, blocks=blocks, post=post) + + +@EIPChecklist.GasCostChanges.Test.ForkTransition.Before() +@EIPChecklist.GasCostChanges.Test.ForkTransition.After() +def test_call_value_cost_at_transition( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + ``CALL_VALUE`` rises across the boundary (9000 -> 10300 on mainnet, + becoming ``ACCOUNT_WRITE + CALL_STIPEND``). The constant transition + is asserted from the derived schedules while a value-bearing ``CALL`` + is exercised in both blocks to prove it still succeeds in each + regime. + """ + before = fork.fork_at(timestamp=BEFORE_TS) + after = fork.fork_at(timestamp=AFTER_TS) + + call_value_before = before.gas_costs().CALL_VALUE + call_value_after = after.gas_costs().CALL_VALUE + assert call_value_after > call_value_before + + callee_before = pre.deploy_contract(code=Op.STOP, balance=0) + callee_after = pre.deploy_contract(code=Op.STOP, balance=0) + + storage_before = Storage() + caller_before = pre.deploy_contract( + code=Op.SSTORE( + storage_before.store_next(1), + Op.CALL(gas=100_000, address=callee_before, value=1), + ), + ) + storage_after = Storage() + caller_after = pre.deploy_contract( + code=Op.SSTORE( + storage_after.store_next(1), + Op.CALL(gas=100_000, address=callee_after, value=1), + ), + ) + + blocks = transition_blocks(caller_before, caller_after, pre, value=1) + + post = { + caller_before: Account(storage=storage_before), + callee_before: Account(balance=1), + caller_after: Account(storage=storage_after), + callee_after: Account(balance=1), + } + blockchain_test(pre=pre, blocks=blocks, post=post) + + +@EIPChecklist.GasCostChanges.Test.ForkTransition.Before() +@EIPChecklist.GasCostChanges.Test.ForkTransition.After() +def test_create_base_cost_at_transition( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + The ``CREATE`` regular base cost changes across the boundary + (``OPCODE_CREATE_BASE``: 32000 -> 11000 on mainnet, redefined as + ``ACCOUNT_WRITE + COLD_STORAGE_ACCESS``). The constant transition is + asserted from the derived schedules and a ``CREATE`` is exercised in + both blocks to prove it still deploys. + """ + before = fork.fork_at(timestamp=BEFORE_TS) + after = fork.fork_at(timestamp=AFTER_TS) + + create_base_before = before.gas_costs().OPCODE_CREATE_BASE + create_base_after = after.gas_costs().OPCODE_CREATE_BASE + assert create_base_after != create_base_before + # Post-fork base is the harmonized ACCOUNT_WRITE + COLD_STORAGE_ACCESS. + assert create_base_after == ( + after.gas_costs().ACCOUNT_WRITE + after.gas_costs().COLD_STORAGE_ACCESS + ) + + init_code = Op.STOP + init_word = int.from_bytes(bytes(init_code), "big") << ( + 256 - 8 * len(init_code) + ) + + storage_before = Storage() + factory_before = pre.deploy_contract( + code=( + Op.MSTORE(0, init_word) + + Op.SSTORE( + storage_before.store_next(True), + Op.GT(Op.CREATE(0, 0, len(init_code)), 0), + ) + ), + ) + storage_after = Storage() + factory_after = pre.deploy_contract( + code=( + Op.MSTORE(0, init_word) + + Op.SSTORE( + storage_after.store_next(True), + Op.GT(Op.CREATE(0, 0, len(init_code)), 0), + ) + ), + ) + + blocks = transition_blocks(factory_before, factory_after, pre) + + post = { + factory_before: Account(storage=storage_before), + factory_after: Account(storage=storage_after), + } + blockchain_test(pre=pre, blocks=blocks, post=post) + + +@EIPChecklist.GasCostChanges.Test.ForkTransition.Before() +@EIPChecklist.GasCostChanges.Test.ForkTransition.After() +def test_selfdestruct_account_write_at_transition( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + ``SELFDESTRUCT`` gains an ``ACCOUNT_WRITE`` charge when it sends a + positive balance to an empty account, which is a new EIP-8038 + parameter (0 -> 8000 on mainnet). The constant transition is + asserted from the derived schedules and a value-bearing + ``SELFDESTRUCT`` to a fresh beneficiary is exercised in both blocks + to prove it still runs. + """ + before = fork.fork_at(timestamp=BEFORE_TS) + after = fork.fork_at(timestamp=AFTER_TS) + + account_write_before = before.gas_costs().ACCOUNT_WRITE + account_write_after = after.gas_costs().ACCOUNT_WRITE + assert account_write_after > account_write_before + + # Fresh empty beneficiaries so the positive-balance-to-empty branch + # that adds ACCOUNT_WRITE is taken in each regime. + beneficiary_before = pre.fund_eoa(amount=0) + beneficiary_after = pre.fund_eoa(amount=0) + + suicidal_before = pre.deploy_contract( + code=Op.SELFDESTRUCT(beneficiary_before), + balance=1, + ) + suicidal_after = pre.deploy_contract( + code=Op.SELFDESTRUCT(beneficiary_after), + balance=1, + ) + + blocks = transition_blocks(suicidal_before, suicidal_after, pre) + + post = { + beneficiary_before: Account(balance=1), + beneficiary_after: Account(balance=1), + } + blockchain_test(pre=pre, blocks=blocks, post=post) + + +@EIPChecklist.GasCostChanges.Test.ForkTransition.Before() +@EIPChecklist.GasCostChanges.Test.ForkTransition.After() +def test_sstore_write_cost_at_transition( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + The ``SSTORE`` first-change cost is repriced across the Amsterdam + boundary, and EIP-8038 changes the *model*, not a single number. + + Before the fork (parent schedule) a zero-to-nonzero ``SSTORE`` is a + flat regular charge (``COLD_STORAGE_ACCESS + STORAGE_SET``) with no + state-gas dimension. After the fork the charge splits: the regular + portion drops to ``COLD_STORAGE_ACCESS + STORAGE_WRITE`` while the + bulk moves into the new state-gas dimension, and the clear refund + rises. Every magnitude is derived from the two schedules; nothing is + hardcoded. + + The transition is asserted at the derived-constant level (the + runtime opcode cost cannot isolate the regular portion without the + state-gas confounder) and a zero-to-nonzero ``SSTORE`` is exercised + in both blocks to prove it still sets the slot in each regime. + """ + before = fork.fork_at(timestamp=BEFORE_TS) + after = fork.fork_at(timestamp=AFTER_TS) + + # First-change (zero -> nonzero, cold) SSTORE in each regime. + sstore = Op.SSTORE(new_value=1) + + regular_before = sstore.regular_cost(before) + regular_after = sstore.regular_cost(after) + state_before = sstore.state_cost(before) + state_after = sstore.state_cost(after) + total_before = sstore.gas_cost(before) + total_after = sstore.gas_cost(after) + + # The repricing changes the regular charge, introduces the state + # dimension, and therefore moves the total. + assert regular_after != regular_before + assert state_before == 0 + assert state_after > 0 + assert total_after != total_before + + # After the fork the regular portion is the EIP-8038 split: + # COLD_STORAGE_ACCESS plus the standalone STORAGE_WRITE (modeled as + # COLD_STORAGE_WRITE minus COLD_STORAGE_ACCESS). + after_costs = after.gas_costs() + storage_write_after = ( + after_costs.COLD_STORAGE_WRITE - after_costs.COLD_STORAGE_ACCESS + ) + assert regular_after == ( + after_costs.COLD_STORAGE_ACCESS + storage_write_after + ) + + # The storage-clear refund also rises across the boundary. + refund_before = before.gas_costs().REFUND_STORAGE_CLEAR + refund_after = after_costs.REFUND_STORAGE_CLEAR + assert refund_after > refund_before + + # Exercise the zero-to-nonzero SSTORE in both regimes; the slot ends + # set in each block. + storage_before = Storage() + contract_before = pre.deploy_contract( + code=Op.SSTORE(storage_before.store_next(1), 1), + ) + storage_after = Storage() + contract_after = pre.deploy_contract( + code=Op.SSTORE(storage_after.store_next(1), 1), + ) + + blocks = transition_blocks(contract_before, contract_after, pre) + + post = { + contract_before: Account(storage=storage_before), + contract_after: Account(storage=storage_after), + } + blockchain_test(pre=pre, blocks=blocks, post=post) + + +@EIPChecklist.GasCostChanges.Test.ForkTransition.Before() +@EIPChecklist.GasCostChanges.Test.ForkTransition.After() +@pytest.mark.exception_test +def test_auth_intrinsic_at_transition( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + The ``7702`` authorization intrinsic rises across the boundary. A tx + whose ``gas_limit`` equals the pre-fork single-authorization + intrinsic is valid before the fork but is rejected with + ``INTRINSIC_GAS_TOO_LOW`` after, because the EIP-8038 auth intrinsic + is strictly larger. + """ + before = fork.fork_at(timestamp=BEFORE_TS) + after = fork.fork_at(timestamp=AFTER_TS) + + intrinsic_before = before.transaction_intrinsic_cost_calculator()( + authorization_list_or_count=1, + return_cost_deducted_prior_execution=True, + ) + intrinsic_after = after.transaction_intrinsic_cost_calculator()( + authorization_list_or_count=1, + return_cost_deducted_prior_execution=True, + ) + # The pre-fork intrinsic is below the post-fork one, so the same + # gas_limit straddles validity at the boundary. + assert intrinsic_before < intrinsic_after + gas_limit = intrinsic_before + + target_before = pre.deploy_contract(code=Op.STOP) + target_after = pre.deploy_contract(code=Op.STOP) + + auth_before = pre.fund_eoa() + auth_after = pre.fund_eoa() + + blocks = [ + # Before the fork: gas_limit covers the old auth intrinsic. + Block( + timestamp=BEFORE_TS, + txs=[ + Transaction( + to=auth_before, + gas_limit=gas_limit, + authorization_list=[ + AuthorizationTuple( + address=target_before, + nonce=0, + signer=auth_before, + ), + ], + sender=pre.fund_eoa(), + ), + ], + ), + # After the fork: identical gas_limit is now below intrinsic. + Block( + timestamp=AFTER_TS, + txs=[ + Transaction( + to=auth_after, + gas_limit=gas_limit, + authorization_list=[ + AuthorizationTuple( + address=target_after, + nonce=0, + signer=auth_after, + ), + ], + sender=pre.fund_eoa(), + error=TransactionException.INTRINSIC_GAS_TOO_LOW, + ), + ], + exception=TransactionException.INTRINSIC_GAS_TOO_LOW, + ), + ] + + blockchain_test(pre=pre, blocks=blocks, post={}) diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_selfdestruct_gas.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_selfdestruct_gas.py new file mode 100644 index 00000000000..9349b191071 --- /dev/null +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_selfdestruct_gas.py @@ -0,0 +1,681 @@ +""" +Tests for the EIP-8038 [State Access Gas Cost Increase](https://eips.ethereum.org/EIPS/eip-8038) +``SELFDESTRUCT`` regular-gas dimension. + +Under EIP-8038 ``SELFDESTRUCT`` is charged, in its *regular* gas +dimension: + +- ``OPCODE_SELFDESTRUCT_BASE`` (5,000); +- a ``COLD_ACCOUNT_ACCESS`` (3,000) surcharge when the beneficiary is + cold (a warm beneficiary adds nothing — SELFDESTRUCT has no + ``WARM_ACCESS`` surcharge); +- a net-new ``ACCOUNT_WRITE`` (8,000) when a positive balance is sent to + an empty (or non-existent) beneficiary, replacing the legacy combined + 25,000 regular account-creation cost. + +So ``regular = 5,000 + (3,000 if cold) + (8,000 if creating)``: 13,000 +warm / 16,000 cold when a new beneficiary is created, 5,000 warm / 8,000 +cold otherwise. + +The beneficiary account-creation charge ``GAS_NEW_ACCOUNT`` (183,600) is +the EIP-8037 *state* dimension (`charge_state_gas` in the spec), covered +in ``eip8037_state_creation_gas_cost_increase/test_state_gas_selfdestruct.py``. + +``SELFDESTRUCT`` halts the frame, so it is driven via a wrapping ``CALL`` +and verified through block ``gas_used`` accounting and balances. Per +EIP-6780, a contract not created in the same transaction is not deleted, +but its balance is still transferred and the beneficiary creation charge +still applies. + +The framework opcode-gas model splits the two dimensions for +``SELFDESTRUCT`` exactly as the spec does: ``ACCOUNT_WRITE`` is charged +as regular gas and ``GAS_NEW_ACCOUNT`` as state gas, so +``Op.SELFDESTRUCT(account_new=True).regular_cost(fork)`` is the regular +charge (16,000 cold / 13,000 warm) and ``.state_cost(fork)`` is +``GAS_NEW_ACCOUNT``. These tests assert the regular dimension and verify +account-creation via balances; the state dimension is owned by +``eip8037_state_creation_gas_cost_increase/test_state_gas_selfdestruct.py``. +""" + +import pytest +from execution_testing import ( + AccessList, + Account, + Address, + Alloc, + Bytecode, + Environment, + Fork, + Header, + Op, + StateTestFiller, + Storage, + Transaction, + TransactionLog, + TransactionReceipt, + compute_create_address, +) +from execution_testing.checklists import EIPChecklist + +from ..eip7708_eth_transfer_logs.spec import transfer_log +from .spec import ref_spec_8038 + +REFERENCE_SPEC_GIT_PATH = ref_spec_8038.git_path +REFERENCE_SPEC_VERSION = ref_spec_8038.version + +pytestmark = pytest.mark.valid_from("Amsterdam") + + +def _selfdestruct_regular(fork: Fork, *, warm: bool, account_new: bool) -> int: + """ + Return the EIP-8038 *regular* gas charged by SELFDESTRUCT. + + ``OPCODE_SELFDESTRUCT_BASE + access + (ACCOUNT_WRITE if account_new)``; + the ``GAS_NEW_ACCOUNT`` account-creation cost is the EIP-8037 state + dimension and is excluded from ``regular_cost``. + """ + gas_costs = fork.gas_costs() + regular = Op.SELFDESTRUCT( + address_warm=warm, account_new=account_new + ).regular_cost(fork) + # SELFDESTRUCT charges a cold-access surcharge only; a warm + # beneficiary adds nothing beyond the base (no WARM_ACCESS). + access = 0 if warm else gas_costs.COLD_ACCOUNT_ACCESS + expected = ( + gas_costs.OPCODE_SELFDESTRUCT_BASE + + access + + (gas_costs.ACCOUNT_WRITE if account_new else 0) + ) + assert regular == expected + return regular + + +def _destructor_code( + beneficiary: Address | Bytecode, *, warm: bool, account_new: bool +) -> Bytecode: + """ + Build SELFDESTRUCT bytecode with metadata so ``regular_cost(fork)`` + folds the beneficiary PUSH and the correct access/account-write + charge (account-creation state gas excluded — it is charged + separately by the spec). + """ + return Op.SELFDESTRUCT.with_metadata( + address_warm=warm, account_new=account_new + )(beneficiary) + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +@pytest.mark.parametrize("warm", [False, True], ids=["cold", "warm"]) +def test_selfdestruct_new_beneficiary_regular_gas( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + warm: bool, +) -> None: + """ + SELFDESTRUCT to an empty beneficiary with balance charges + ACCOUNT_WRITE. + + The destructor has a non-zero balance and targets an empty, + non-existent beneficiary, so the net-new ``ACCOUNT_WRITE`` applies: + ``regular = 5,000 + access + 8,000`` (13,000 warm, 16,000 cold). The + creation gas ``GAS_NEW_ACCOUNT`` is charged on the state axis (the + EIP-8037 suite asserts it); here it is funded from the reservoir and + the value transfer to the new beneficiary confirms the path. + """ + gas_costs = fork.gas_costs() + new_account_state_gas = gas_costs.NEW_ACCOUNT + + regular = _selfdestruct_regular(fork, warm=warm, account_new=True) + assert regular == (13_000 if warm else 16_000) + + beneficiary = Address(0xDEAD) # empty, non-existent + + destructor_code = Op.SELFDESTRUCT(beneficiary) + destructor = pre.deploy_contract(code=destructor_code, balance=1) + + storage = Storage() + caller_code = Op.SSTORE( + storage.store_next(1, "call_succeeds"), + Op.CALL(gas=Op.GAS, address=destructor), + ) + caller = pre.deploy_contract(code=caller_code) + + tx = Transaction( + to=caller, + sender=pre.fund_eoa(), + access_list=[AccessList(address=beneficiary, storage_keys=[])] + if warm + else None, + state_gas_reservoir=new_account_state_gas, + ) + + state_test( + pre=pre, + post={ + caller: Account(storage=storage), + # New beneficiary created and credited the destructor balance. + beneficiary: Account(balance=1), + }, + tx=tx, + ) + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +@pytest.mark.parametrize("warm", [False, True], ids=["cold", "warm"]) +def test_selfdestruct_alive_beneficiary_no_account_write( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + warm: bool, +) -> None: + """ + SELFDESTRUCT to an already-alive beneficiary charges no ACCOUNT_WRITE. + + The beneficiary already exists, so no account is created: regular = + ``5,000 + (3,000 if cold)`` (5,000 warm, 8,000 cold) and no state gas is + charged. The block header reflects the pure regular consumption. + """ + regular = _selfdestruct_regular(fork, warm=warm, account_new=False) + assert regular == (5_000 if warm else 8_000) + + beneficiary = pre.fund_eoa(amount=1) # alive + + destructor_code = _destructor_code( + beneficiary, warm=warm, account_new=False + ) + destructor = pre.deploy_contract(code=destructor_code, balance=1) + + caller_code = Op.POP(Op.CALL(gas=Op.GAS, address=destructor)) + Op.STOP + caller = pre.deploy_contract(code=caller_code) + + access_list = ( + [AccessList(address=beneficiary, storage_keys=[])] if warm else None + ) + # Intrinsic must include the access-list cost that warms the + # beneficiary; pass the list so the calculator folds it in. + intrinsic = fork.transaction_intrinsic_cost_calculator()( + access_list=access_list + ) + + # Pure regular: intrinsic + caller frame + destructor frame (whose + # regular_cost folds the SELFDESTRUCT charge and beneficiary PUSH). + expected_gas_used = ( + intrinsic + + caller_code.gas_cost(fork) + + destructor_code.regular_cost(fork) + ) + + tx = Transaction( + to=caller, + sender=pre.fund_eoa(), + access_list=access_list, + state_gas_reservoir=0, + expected_receipt=TransactionReceipt( + cumulative_gas_used=expected_gas_used, + ), + ) + + state_test( + pre=pre, + # EIP-6780: the pre-deployed destructor is not same-tx-created, + # so it is not deleted; its balance still transfers. + post={ + destructor: Account(balance=0, code=destructor_code), + beneficiary: Account(balance=2), + }, + tx=tx, + ) + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +@pytest.mark.parametrize("warm", [False, True], ids=["cold", "warm"]) +def test_selfdestruct_codebearing_zero_balance_beneficiary_no_account_write( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + warm: bool, +) -> None: + """ + SELFDESTRUCT to a code-bearing zero-balance beneficiary: no + ACCOUNT_WRITE. + + The beneficiary is alive because it has code, not balance: it holds a + zero balance but a non-empty code (``Op.STOP``), so EIP-161 emptiness + does not apply and no account is created when a positive balance is + sent to it. Regular = ``5,000 + (3,000 if cold)`` (5,000 warm, 8,000 + cold) with no ACCOUNT_WRITE and no state gas — distinct from the + alive-via-balance case, which exercises the same path through a + different liveness source. + """ + regular = _selfdestruct_regular(fork, warm=warm, account_new=False) + assert regular == (5_000 if warm else 8_000) + + # Alive via code (non-empty code), with zero balance. + beneficiary = pre.deploy_contract(code=Op.STOP, balance=0) + + destructor_code = _destructor_code( + beneficiary, warm=warm, account_new=False + ) + destructor = pre.deploy_contract(code=destructor_code, balance=1) + + caller_code = Op.POP(Op.CALL(gas=Op.GAS, address=destructor)) + Op.STOP + caller = pre.deploy_contract(code=caller_code) + + access_list = ( + [AccessList(address=beneficiary, storage_keys=[])] if warm else None + ) + # Intrinsic must include the access-list cost that warms the + # beneficiary; pass the list so the calculator folds it in. + intrinsic = fork.transaction_intrinsic_cost_calculator()( + access_list=access_list + ) + + # Pure regular: intrinsic + caller frame + destructor frame (whose + # regular_cost folds the SELFDESTRUCT charge and beneficiary PUSH). + expected_gas_used = ( + intrinsic + + caller_code.gas_cost(fork) + + destructor_code.regular_cost(fork) + ) + + tx = Transaction( + to=caller, + sender=pre.fund_eoa(), + access_list=access_list, + state_gas_reservoir=0, + expected_receipt=TransactionReceipt( + cumulative_gas_used=expected_gas_used + ), + ) + + state_test( + pre=pre, + # EIP-6780: the pre-deployed destructor is not same-tx-created, + # so it is not deleted; its balance still transfers. + post={ + destructor: Account(balance=0, code=destructor_code), + # Code-bearing beneficiary credited the destructor balance. + beneficiary: Account(balance=1, code=Op.STOP), + }, + tx=tx, + ) + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +@pytest.mark.parametrize("warm", [False, True], ids=["cold", "warm"]) +def test_selfdestruct_zero_balance_no_account_write( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + warm: bool, +) -> None: + """ + SELFDESTRUCT with a zero-balance destructor charges no ACCOUNT_WRITE. + + No value is transferred, so even a non-existent beneficiary is not + created: regular = ``5,000 + access`` and no state gas is charged. + """ + regular = _selfdestruct_regular(fork, warm=warm, account_new=False) + assert regular == (5_000 if warm else 8_000) + + beneficiary = Address(0xDEAD) # non-existent, but no value sent + + destructor_code = _destructor_code( + beneficiary, warm=warm, account_new=False + ) + destructor = pre.deploy_contract(code=destructor_code, balance=0) + + caller_code = Op.POP(Op.CALL(gas=Op.GAS, address=destructor)) + Op.STOP + caller = pre.deploy_contract(code=caller_code) + + access_list = ( + [AccessList(address=beneficiary, storage_keys=[])] if warm else None + ) + intrinsic = fork.transaction_intrinsic_cost_calculator()( + access_list=access_list + ) + + expected_gas_used = ( + intrinsic + + caller_code.gas_cost(fork) + + destructor_code.regular_cost(fork) + ) + + tx = Transaction( + to=caller, + sender=pre.fund_eoa(), + access_list=access_list, + state_gas_reservoir=0, + expected_receipt=TransactionReceipt( + cumulative_gas_used=expected_gas_used + ), + ) + + state_test( + pre=pre, + post={ + destructor: Account(balance=0, code=destructor_code), + beneficiary: Account.NONEXISTENT, + }, + tx=tx, + ) + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +@pytest.mark.parametrize( + "beneficiary_kind", + [ + pytest.param("self", id="self_beneficiary"), + pytest.param("precompile", id="precompile_beneficiary"), + ], +) +def test_selfdestruct_self_or_precompile_beneficiary( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + beneficiary_kind: str, +) -> None: + """ + SELFDESTRUCT to self or a precompile is warm and charges no + ACCOUNT_WRITE. + + The executing account is in the accessed set on entry (self), and + precompiles are pre-warmed from the start, so neither pays a cold + surcharge: regular = ``5,000`` (warm base, no ``WARM_ACCESS``) with no + state gas. + + The destructor balance is chosen so no account creation occurs: self + is alive (sending to itself never creates), and the precompile case + sends zero value (precompiles hold no state entry, so a value + transfer would otherwise create one and charge ``GAS_NEW_ACCOUNT`` on + the state axis). + """ + gas_costs = fork.gas_costs() + + regular = _selfdestruct_regular(fork, warm=True, account_new=False) + # SELFDESTRUCT has no warm-access surcharge: warm == base only. + assert regular == gas_costs.OPCODE_SELFDESTRUCT_BASE + + if beneficiary_kind == "self": + # Self is warm on entry; the PUSH is `ADDRESS` (BASE=2). A + # non-zero balance is transferred to self (no creation). + destructor_code = Op.SELFDESTRUCT.with_metadata(address_warm=True)( + Op.ADDRESS + ) + balance = 1 + else: + # Identity precompile (address 4) is pre-warmed. Zero balance so + # no value transfer and thus no account creation. + destructor_code = Op.SELFDESTRUCT.with_metadata(address_warm=True)( + Address(4) + ) + balance = 0 + destructor = pre.deploy_contract(code=destructor_code, balance=balance) + + caller_code = Op.POP(Op.CALL(gas=Op.GAS, address=destructor)) + Op.STOP + caller = pre.deploy_contract(code=caller_code) + + intrinsic = fork.transaction_intrinsic_cost_calculator()() + + expected_gas_used = ( + intrinsic + + caller_code.gas_cost(fork) + + destructor_code.regular_cost(fork) + ) + + tx = Transaction( + to=caller, + sender=pre.fund_eoa(), + state_gas_reservoir=0, + expected_receipt=TransactionReceipt( + cumulative_gas_used=expected_gas_used + ), + ) + + # EIP-6780: the pre-deployed destructor is not deleted. The self case + # keeps its balance (transferred to itself); the precompile case sent + # nothing. + post = {destructor: Account(balance=balance, code=destructor_code)} + + state_test( + pre=pre, + post=post, + tx=tx, + ) + + +@EIPChecklist.GasCostChanges.Test.OutOfGas() +@pytest.mark.parametrize( + "sufficient_gas", [True, False], ids=["sufficient", "insufficient"] +) +def test_selfdestruct_oog_boundary( + state_test: StateTestFiller, + env: Environment, + pre: Alloc, + fork: Fork, + sufficient_gas: bool, +) -> None: + """ + Drive a cold SELFDESTRUCT that funds a new account at its exact total + gas and one short. + + The destructor sends value to an empty beneficiary, charging + ``5,000 + COLD_ACCOUNT_ACCESS + ACCOUNT_WRITE`` (16,000) in regular gas + and ``GAS_NEW_ACCOUNT`` in state gas. The child CALL frame has no state + reservoir of its own, so the state gas spills into the forwarded + regular gas and the frame needs its full ``gas_cost`` total. Forwarding + exactly that total lets the SELFDESTRUCT succeed (CALL returns 1); one + gas short OOGs (CALL returns 0) before the value transfer, so the + beneficiary is never created. + """ + gas_costs = fork.gas_costs() + + beneficiary = Address(0xDEAD) + regular = _selfdestruct_regular(fork, warm=False, account_new=True) + assert regular == ( + gas_costs.OPCODE_SELFDESTRUCT_BASE + + gas_costs.COLD_ACCOUNT_ACCESS + + gas_costs.ACCOUNT_WRITE + ) + + destructor_code = _destructor_code( + beneficiary, warm=False, account_new=True + ) + destructor = pre.deploy_contract(code=destructor_code, balance=1) + + # The child CALL frame gets no state reservoir, so the NEW_ACCOUNT + # state gas spills into the forwarded regular gas: forward the full + # total. One gas short forces an out-of-gas before the value transfer. + forwarded = destructor_code.gas_cost(fork) + if not sufficient_gas: + forwarded -= 1 + + storage = Storage() + caller_code = Op.SSTORE( + storage.store_next(1 if sufficient_gas else 0, "sd_result"), + Op.CALL(gas=forwarded, address=destructor), + ) + caller = pre.deploy_contract(code=caller_code) + + tx = Transaction( + to=caller, + sender=pre.fund_eoa(), + state_gas_reservoir=0, + ) + + if sufficient_gas: + post: dict = { + caller: Account(storage=storage), + beneficiary: Account(balance=1), + } + else: + post = { + caller: Account(storage=storage), + beneficiary: Account.NONEXISTENT, + destructor: Account(balance=1), + } + + state_test(env=env, pre=pre, post=post, tx=tx) + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +@pytest.mark.pre_alloc_mutable() +def test_same_tx_created_selfdestruct_self_burn( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + EIP-6780: a same-tx-created contract SELFDESTRUCTs to itself, charged + the warm base only. + + A creation transaction whose initcode SELFDESTRUCTs the new contract + to ITSELF: the originator is created in this transaction so it is + deleted, and because a same-tx-created contract holding balance is + alive, ``account_new`` is false for the self-beneficiary — + ``regular = 5,000`` (warm self, no ``ACCOUNT_WRITE``) and no + SELFDESTRUCT state gas. + + EIP-8246 removes the SELFDESTRUCT burn, so the self-send is a no-op: + the balance stays in the (otherwise emptied) originator and no log is + emitted. + + No net state gas is charged either way: the only state cost is the + intrinsic creation ``NEW_ACCOUNT``, but the pre-funded created target + is alive at message entry, so EIP-8037 refunds it (the create-tx + ``created_target_alive`` refund). The block ``gas_used`` is therefore + the pure regular consumption regardless of the burn behavior. + """ + new_account_state_gas = fork.gas_costs().NEW_ACCOUNT + intrinsic_calc = fork.transaction_intrinsic_cost_calculator() + + amount = 1 + sender = pre.fund_eoa(amount=10**18) + created = compute_create_address(address=sender, nonce=0) + # Pre-fund the created address so its balance is present without an + # in-tx value transfer (which would emit its own Transfer log). The + # pre-funded target is alive at message entry, so the create-tx + # intrinsic NEW_ACCOUNT is refunded (EIP-8037). + pre.fund_address(created, amount) + + # Self is the executing account, warm on entry: no cold surcharge. + init_code = Op.SELFDESTRUCT.with_metadata(address_warm=True)(Op.ADDRESS) + + # Self-beneficiary on a balance-bearing same-tx-created contract is + # alive: account_new is false, so only the warm base is charged. + regular = _selfdestruct_regular(fork, warm=True, account_new=False) + assert regular == fork.gas_costs().OPCODE_SELFDESTRUCT_BASE + + intrinsic_total = intrinsic_calc( + calldata=bytes(init_code), contract_creation=True + ) + # The creation NEW_ACCOUNT is refunded (target alive at entry) and the + # self-burn adds no state gas, so net state gas is zero. + intrinsic_regular = intrinsic_total - new_account_state_gas + expected_regular = intrinsic_regular + init_code.regular_cost(fork) + expected_gas_used = expected_regular + + # EIP-8246 removes the SELFDESTRUCT burn: the self-send is a no-op, + # the balance stays in the (otherwise emptied) originator, and no + # log is emitted. + expected_logs: list[TransactionLog] = [] + created_post = Account(balance=amount, nonce=0, code=b"", storage={}) + + tx = Transaction( + to=None, + data=init_code, + sender=sender, + expected_receipt=TransactionReceipt( + logs=expected_logs, + cumulative_gas_used=expected_gas_used, + ), + ) + + state_test( + pre=pre, + post={created: created_post}, + tx=tx, + ) + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +@pytest.mark.pre_alloc_mutable() +def test_same_tx_created_selfdestruct_to_fresh_beneficiary( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + EIP-6780: a same-tx-created contract sends value to a fresh + beneficiary, charged ``ACCOUNT_WRITE`` and creation state gas. + + A creation transaction whose initcode SELFDESTRUCTs the new contract + to a fresh ``Address(0xDEAD)``: the fresh, non-existent beneficiary + receives a positive balance, so ``account_new`` is true — + ``regular = 5,000 + COLD_ACCOUNT_ACCESS + ACCOUNT_WRITE`` (16,000 + cold) plus a beneficiary ``NEW_ACCOUNT`` on the state axis. The + beneficiary creation charge keys on the beneficiary, while the + originator (created in this transaction) is still deleted: a + ``Transfer`` log is emitted (not a ``Burn``). + + The net state gas is a single beneficiary ``NEW_ACCOUNT``: the + intrinsic creation ``NEW_ACCOUNT`` is refunded because the pre-funded + created target is alive at message entry (EIP-8037), while the fresh + beneficiary's ``NEW_ACCOUNT`` persists. + """ + new_account_state_gas = fork.gas_costs().NEW_ACCOUNT + intrinsic_calc = fork.transaction_intrinsic_cost_calculator() + + amount = 1 + beneficiary = Address(0xDEAD) # fresh, non-existent + sender = pre.fund_eoa(amount=10**18) + created = compute_create_address(address=sender, nonce=0) + # Pre-fund the created address so its balance is present without an + # in-tx value transfer (which would emit its own Transfer log). The + # pre-funded target is alive at message entry, so the create-tx + # intrinsic NEW_ACCOUNT is refunded (EIP-8037). + pre.fund_address(created, amount) + + # Cold beneficiary receiving value: account_new is true. + init_code = Op.SELFDESTRUCT.with_metadata( + address_warm=False, account_new=True + )(beneficiary) + + regular = _selfdestruct_regular(fork, warm=False, account_new=True) + assert regular == 16_000 + + intrinsic_total = intrinsic_calc( + calldata=bytes(init_code), contract_creation=True + ) + # The creation NEW_ACCOUNT is refunded (target alive at entry); only + # the fresh beneficiary's NEW_ACCOUNT remains as net state gas. + intrinsic_regular = intrinsic_total - new_account_state_gas + expected_state = new_account_state_gas + expected_regular = intrinsic_regular + init_code.regular_cost(fork) + expected_gas_used = max(expected_regular, expected_state) + + tx = Transaction( + to=None, + data=init_code, + sender=sender, + # Reservoir holds the beneficiary-creation state gas (above the + # creation's intrinsic NEW_ACCOUNT) so it does not spill into + # regular gas. + state_gas_reservoir=new_account_state_gas, + expected_receipt=TransactionReceipt( + logs=[transfer_log(created, beneficiary, amount)] + ), + ) + + state_test( + pre=pre, + # Same-tx-created originator is deleted; the fresh beneficiary is + # created and credited the originator balance. + post={ + created: Account.NONEXISTENT, + beneficiary: Account(balance=amount), + }, + tx=tx, + blockchain_test_header_verify=Header(gas_used=expected_gas_used), + ) diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_set_code_auth_gas.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_set_code_auth_gas.py new file mode 100644 index 00000000000..61cd928c2a5 --- /dev/null +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_set_code_auth_gas.py @@ -0,0 +1,609 @@ +""" +Tests for the EIP-7702 authorization *regular*-gas repricing under +[EIP-8038: State Access Gas Cost Increase](https://eips.ethereum.org/EIPS/eip-8038). + +EIP-8037 splits each EIP-7702 authorization into a *state* component +(refunded against the state-gas reservoir, covered by the sibling +``eip8037_state_creation_gas_cost_increase`` suite) and a *regular* +component. This module pins the **regular** per-authorization intrinsic +magnitude and the repriced cold/warm account-access costs that an +authorized delegation incurs when later accessed by a ``CALL``. + +The regular per-authorization magnitude is derived purely from fork +helpers as:: + + regular_per_auth = ( + fork.gas_costs().AUTH_PER_EMPTY_ACCOUNT + - fork.transaction_intrinsic_state_gas(authorization_count=1) + ) + +which on Amsterdam equals ``ACCOUNT_WRITE`` (``8000``) plus the EIP-7702 +regular auth base cost (``7816``), i.e. ``15816``. The state portion that +this subtracts off (``transaction_intrinsic_state_gas``) is exactly what +the EIP-8037 suite asserts on the state channel; this suite never +re-asserts it. +""" + +from typing import List + +import pytest +from execution_testing import ( + AccessList, + Account, + Address, + Alloc, + AuthorizationTuple, + Bytecode, + CodeGasMeasure, + Environment, + Fork, + Op, + StateTestFiller, + Storage, + Transaction, + TransactionException, + TransactionReceipt, +) +from execution_testing.checklists import EIPChecklist + +from ...prague.eip7702_set_code_tx.spec import Spec as Spec7702 +from .spec import ref_spec_8038 + +REFERENCE_SPEC_GIT_PATH = ref_spec_8038.git_path +REFERENCE_SPEC_VERSION = ref_spec_8038.version + +pytestmark = pytest.mark.valid_from("Amsterdam") + + +def _regular_per_auth(fork: Fork) -> int: + """ + Return the EIP-8038 *regular* intrinsic gas charged per EIP-7702 + authorization, i.e. the total per-auth intrinsic less the EIP-8037 + state portion. + """ + return fork.gas_costs().AUTH_PER_EMPTY_ACCOUNT - ( + fork.transaction_intrinsic_state_gas(authorization_count=1) + ) + + +def _regular_intrinsic( + fork: Fork, + *, + n: int, + access_list: List[AccessList] | None = None, + calldata: bytes = b"", +) -> int: + """ + Return the regular (non-state) intrinsic gas of a set-code + transaction: the full intrinsic less the authorization state gas. + """ + total = fork.transaction_intrinsic_cost_calculator()( + authorization_list_or_count=n, + access_list=access_list, + calldata=calldata, + return_cost_deducted_prior_execution=True, + ) + return total - fork.transaction_intrinsic_state_gas( + authorization_count=n, + ) + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +@pytest.mark.parametrize("n", [1, 2, 3]) +@pytest.mark.parametrize( + "authority_exists", + [ + pytest.param(False, id="new_authority"), + pytest.param(True, id="existing_authority"), + ], +) +@pytest.mark.parametrize( + "authority_in_access_list", + [ + pytest.param(False, id="empty_access_list"), + pytest.param(True, id="access_list_contains_authority"), + ], +) +def test_auth_regular_intrinsic_magnitude( + state_test: StateTestFiller, + env: Environment, + pre: Alloc, + fork: Fork, + n: int, + authority_exists: bool, + authority_in_access_list: bool, +) -> None: + """ + Assert the EIP-8038 *regular* per-authorization intrinsic magnitude. + + The regular intrinsic above the ``n=0`` base must equal + ``n * regular_per_auth`` plus the access-list delta (derived from + the calculator itself so the calldata-floor contribution of the + access-list bytes is accounted for). The state portion is excluded + via ``transaction_intrinsic_state_gas`` and is left to the EIP-8037 + suite. + """ + contract = pre.deploy_contract(code=Op.STOP) + + signers = [ + pre.fund_eoa() if authority_exists else pre.fund_eoa(amount=0) + for _ in range(n) + ] + authorization_list = [ + AuthorizationTuple(address=contract, nonce=0, signer=signer) + for signer in signers + ] + + access_list: List[AccessList] | None = None + if authority_in_access_list: + access_list = [ + AccessList(address=signer, storage_keys=[]) for signer in signers + ] + + base_regular = _regular_intrinsic(fork, n=0) + regular = _regular_intrinsic(fork, n=n, access_list=access_list) + + # Access-list delta is derived from the calculator (it folds in the + # calldata-floor cost of the access-list bytes), never hardcoded. + access_list_delta = _regular_intrinsic( + fork, n=0, access_list=access_list + ) - _regular_intrinsic(fork, n=0) + + expected_per_auth = _regular_per_auth(fork) + assert regular - base_regular == n * expected_per_auth + access_list_delta + + sender = pre.fund_eoa() + tx = Transaction( + to=contract, + authorization_list=authorization_list, + access_list=access_list, + sender=sender, + ) + + post = { + signer: Account(code=Spec7702.delegation_designation(contract)) + for signer in signers + } + state_test(env=env, pre=pre, post=post, tx=tx) + + +@EIPChecklist.GasCostChanges.Test.OutOfGas() +@pytest.mark.exception_test +@pytest.mark.parametrize("n", [1, 3]) +def test_auth_intrinsic_oog_boundary( + state_test: StateTestFiller, + env: Environment, + pre: Alloc, + fork: Fork, + n: int, +) -> None: + """ + Reject a set-code transaction one gas below the full intrinsic. + + ``gas_limit`` is set to ``full_intrinsic - 1`` (full intrinsic = + regular + auth state gas). Catches an implementation that omits the + repriced regular per-authorization cost from the intrinsic check. + """ + contract = pre.deploy_contract(code=Op.STOP) + authorization_list = [ + AuthorizationTuple(address=contract, nonce=0, signer=pre.fund_eoa()) + for _ in range(n) + ] + + full_intrinsic = fork.transaction_intrinsic_cost_calculator()( + authorization_list_or_count=authorization_list, + ) + + tx = Transaction( + to=contract, + gas_limit=full_intrinsic - 1, + authorization_list=authorization_list, + sender=pre.fund_eoa(), + error=TransactionException.INTRINSIC_GAS_TOO_LOW, + ) + + state_test(env=env, pre=pre, post={}, tx=tx) + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +@pytest.mark.parametrize( + "invalidity", + [ + pytest.param("invalid_nonce", id="invalid_nonce"), + pytest.param("invalid_chain_id", id="invalid_chain_id"), + pytest.param("repeated_nonce", id="repeated_nonce"), + pytest.param("authority_is_contract", id="authority_is_contract"), + ], +) +@pytest.mark.pre_alloc_mutable +def test_invalid_auth_charged_intrinsic( + state_test: StateTestFiller, + env: Environment, + pre: Alloc, + fork: Fork, + invalidity: str, +) -> None: + """ + A skipped (invalid) authorization is still charged the full + intrinsic, and the invalid authority's account is left unchanged. + + Each invalidity kind (``INVALID_NONCE``, ``INVALID_CHAIN_ID``, + ``REPEATED_NONCE``, ``AUTHORITY_IS_CONTRACT``) makes the + authorization invalid during processing, so it is silently skipped, + but its regular + state intrinsic gas is still paid. The transaction + succeeds. + """ + contract = pre.deploy_contract(code=Op.STOP) + + # Build a (possibly multi-element) authorization list where the + # authority that *should* end up untouched is the invalid one. + authorization_list: List[AuthorizationTuple] = [] + + if invalidity == "invalid_nonce": + authority = pre.fund_eoa() + authorization_list.append( + AuthorizationTuple( + address=contract, + nonce=99, # wrong nonce -> skipped + signer=authority, + ) + ) + expected_code: bytes | Bytecode = b"" + elif invalidity == "invalid_chain_id": + authority = pre.fund_eoa() + authorization_list.append( + AuthorizationTuple( + address=contract, + nonce=0, + chain_id=9999, # wrong chain id -> skipped + signer=authority, + ) + ) + expected_code = b"" + elif invalidity == "repeated_nonce": + # First auth is valid and consumes nonce 0; the second reuses + # nonce 0 and is therefore skipped. The (single) signer ends up + # delegated by the first auth, so assert that delegation. + authority = pre.fund_eoa() + authorization_list.append( + AuthorizationTuple(address=contract, nonce=0, signer=authority) + ) + authorization_list.append( + AuthorizationTuple(address=contract, nonce=0, signer=authority) + ) + expected_code = Spec7702.delegation_designation(contract) + elif invalidity == "authority_is_contract": + # An authority that is already a (non-delegation) contract is an + # invalid authority; the authorization is skipped and the + # contract code is left intact. + authority = pre.fund_eoa(code=Op.STOP) + authorization_list.append( + AuthorizationTuple(address=contract, nonce=0, signer=authority) + ) + expected_code = Op.STOP + else: + raise ValueError(f"unknown invalidity: {invalidity!r}") + + # The full intrinsic (regular + state) is charged regardless of + # validity. Provide a comfortable gas limit and let the receipt + # accounting be verified by the framework; the key assertion is the + # untouched-authority post state. + full_intrinsic = fork.transaction_intrinsic_cost_calculator()( + authorization_list_or_count=authorization_list, + ) + assert full_intrinsic > 0 + + sender = pre.fund_eoa() + tx = Transaction( + to=contract, + authorization_list=authorization_list, + sender=sender, + ) + + post = {authority: Account(code=expected_code)} + state_test(env=env, pre=pre, post=post, tx=tx) + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +@pytest.mark.parametrize( + "invalidity", + [ + pytest.param("invalid_nonce", id="invalid_nonce"), + pytest.param("invalid_chain_id", id="invalid_chain_id"), + pytest.param("repeated_nonce", id="repeated_nonce"), + pytest.param("authority_is_contract", id="authority_is_contract"), + ], +) +@pytest.mark.pre_alloc_mutable +def test_mixed_validity_multi_auth_receipt_gas( + state_test: StateTestFiller, + env: Environment, + pre: Alloc, + fork: Fork, + invalidity: str, +) -> None: + """ + Pin the exact receipt gas of a transaction carrying one valid and + one invalid authorization. + + Every authorization tuple, valid or invalid, is charged the full + regular + state per-authorization intrinsic. The valid + authorization whose authority leaf already exists refills + ``NEW_ACCOUNT`` on the state channel (uncapped, subtracted first) + and returns ``ACCOUNT_WRITE`` on the regular channel (one-fifth + capped). The invalid tuple is silently skipped during + ``set_delegation``, refilling the full per-auth state intrinsic and + returning its regular ``ACCOUNT_WRITE`` charge. + + The dual-channel accounting mirrors ``process_transaction`` and the + sibling ``test_set_code_auth_refunds`` module: the state refill is + subtracted from ``gas_before_regular_refund`` first and uncapped, + then the regular refund clamps to + ``min(k * ACCOUNT_WRITE, gas_before_regular_refund // 5)`` where + ``k`` is the number of authorizations that return the regular + account-write charge. With no EVM execution, + ``gas_before_regular_refund`` reduces to the full per-authorization + intrinsic less the state refill, and the exact result is asserted + via ``expected_receipt``. + + Each ``invalidity`` kind (``INVALID_NONCE``, ``INVALID_CHAIN_ID``, + ``REPEATED_NONCE``, ``AUTHORITY_IS_CONTRACT``) yields one valid and + one invalid tuple, so ``n = 2`` and ``k = 2`` uniformly and every + kind pins the same receipt gas. This is the numeric-receipt + companion to ``test_invalid_auth_charged_intrinsic`` (which asserts + only post state). + """ + gas_costs = fork.gas_costs() + account_write = gas_costs.ACCOUNT_WRITE + + delegate = pre.deploy_contract(code=Op.STOP) + + # The single refundable (valid, existing-leaf) authorization. + valid_signer = pre.fund_eoa() + valid_auth = AuthorizationTuple( + address=delegate, nonce=0, signer=valid_signer + ) + + # Build the authorization list: one valid tuple plus one invalid + # tuple of the requested kind. ``authority`` is the account that must + # end up untouched by the skipped (invalid) authorization. + authorization_list: List[AuthorizationTuple] + post: dict = { + valid_signer: Account( + code=Spec7702.delegation_designation(delegate), + ), + } + + if invalidity == "invalid_nonce": + authority = pre.fund_eoa() + authorization_list = [ + valid_auth, + AuthorizationTuple( + address=delegate, + nonce=99, # wrong nonce -> skipped + signer=authority, + ), + ] + post[authority] = Account(code=b"") + elif invalidity == "invalid_chain_id": + authority = pre.fund_eoa() + authorization_list = [ + valid_auth, + AuthorizationTuple( + address=delegate, + nonce=0, + chain_id=9999, # wrong chain id -> skipped + signer=authority, + ), + ] + post[authority] = Account(code=b"") + elif invalidity == "repeated_nonce": + # The valid tuple consumes the signer's nonce 0; a second tuple + # reusing nonce 0 on the same signer is skipped. The signer is + # the refundable authority, delegated by its first (valid) tuple. + authorization_list = [ + valid_auth, + AuthorizationTuple(address=delegate, nonce=0, signer=valid_signer), + ] + elif invalidity == "authority_is_contract": + # An authority that is already a (non-delegation) contract is an + # invalid authority; its authorization is skipped and the + # contract code is left intact. + authority = pre.fund_eoa(code=Op.STOP) + authorization_list = [ + valid_auth, + AuthorizationTuple(address=delegate, nonce=0, signer=authority), + ] + post[authority] = Account(code=Op.STOP) + else: + raise ValueError(f"unknown invalidity: {invalidity!r}") + + n = len(authorization_list) + regular_refundable = 2 + + total_intrinsic = fork.transaction_intrinsic_cost_calculator()( + authorization_list_or_count=n, + ) + intrinsic_state = fork.transaction_intrinsic_state_gas( + authorization_count=n, + ) + # The valid existing-leaf authorization refills NEW_ACCOUNT. The + # invalid skipped tuple refills the full per-auth state intrinsic. + # State refills are subtracted first and are not subject to the + # one-fifth cap. + state_refund = gas_costs.REFUND_AUTH_PER_EXISTING_ACCOUNT + ( + intrinsic_state // n + ) + + # No EVM execution (the target is a STOP), so the regular and state + # execution gas are both zero and ``gas_before_regular_refund`` + # reduces to the full per-auth intrinsic less the state refill. + gas_before_regular_refund = total_intrinsic - state_refund + regular_refund = min( + regular_refundable * account_write, + gas_before_regular_refund // fork.max_refund_quotient(), + ) + # The one-fifth cap is generous, so both ACCOUNT_WRITE refunds clear + # on the regular channel. + assert regular_refund == regular_refundable * account_write + cumulative_gas_used = gas_before_regular_refund - regular_refund + + tx = Transaction( + to=delegate, + state_gas_reservoir=intrinsic_state, + authorization_list=authorization_list, + sender=pre.fund_eoa(), + expected_receipt=TransactionReceipt( + cumulative_gas_used=cumulative_gas_used, + ), + ) + + state_test(env=env, pre=pre, post=post, tx=tx) + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +@pytest.mark.parametrize( + "self_sponsored", + [ + pytest.param(False, id="external_sponsor"), + pytest.param(True, id="self_sponsor"), + ], +) +@pytest.mark.parametrize( + "delegation_in_access_list", + [ + pytest.param(False, id="delegation_cold"), + pytest.param(True, id="delegation_warm"), + ], +) +def test_auth_account_warming( + state_test: StateTestFiller, + env: Environment, + pre: Alloc, + fork: Fork, + self_sponsored: bool, + delegation_in_access_list: bool, +) -> None: + """ + A later ``CALL`` to an authorized authority pays the repriced + cold/warm account-access costs, plus the delegation double-charge. + + The authority itself is warmed by the authorization (added to + ``accessed_addresses`` during validation), so the ``CALL`` access + to it is ``WARM_ACCESS``. Because the authority carries a delegation + designator, accessing it triggers a *second* access to the + delegation target: ``WARM_ACCESS`` if that target is in the access + list (or is the authority itself, for self-delegation), else + ``COLD_ACCOUNT_ACCESS``. When the sponsor is the authority, the + authority is already warm for the same reason. + + All costs are taken from ``fork.gas_costs()`` so the repricing is + asserted against the live schedule rather than hardcoded constants. + """ + gas_costs = fork.gas_costs() + cold = gas_costs.COLD_ACCOUNT_ACCESS + warm = gas_costs.WARM_ACCESS + + delegation_target = pre.deploy_contract(code=Op.STOP) + + if self_sponsored: + # Self-sponsored: the sender is the authority. fund_eoa with a + # delegation pre-installs the designator and sets nonce to 1. + sender = pre.fund_eoa(delegation=delegation_target) + authority: Address = sender + authorization_list = None + else: + sender = pre.fund_eoa() + authority = pre.fund_eoa() + authorization_list = [ + AuthorizationTuple( + address=delegation_target, + nonce=0, + signer=authority, + ) + ] + + access_list: List[AccessList] | None = None + if delegation_in_access_list: + access_list = [AccessList(address=delegation_target, storage_keys=[])] + + # Authority access: always warm (authorization or self-sponsor warms + # it). Delegation target double-charge: warm iff in the access list, + # else cold. + delegation_access = warm if delegation_in_access_list else cold + expected_cost = warm + delegation_access + + # Measure the cost of a single CALL to the authority. The CALL + # opcode leaves one stack item (success); the overhead is the PUSHes + # for its arguments. + overhead_cost = gas_costs.VERY_LOW * len(Op.CALL.kwargs) + storage = Storage() + callee_code = CodeGasMeasure( + code=Op.CALL(gas=0, address=authority), + overhead_cost=overhead_cost, + extra_stack_items=1, + sstore_key=storage.store_next(expected_cost), + ) + callee_address = pre.deploy_contract(callee_code) + + tx = Transaction( + to=callee_address, + authorization_list=authorization_list, + access_list=access_list, + sender=sender, + ) + + post = { + callee_address: Account(storage=storage), + authority: Account( + code=Spec7702.delegation_designation(delegation_target), + ), + } + state_test(env=env, pre=pre, post=post, tx=tx) + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +def test_many_auths_block_limit( + state_test: StateTestFiller, + env: Environment, + pre: Alloc, + fork: Fork, +) -> None: + """ + Pack many authorizations into a single transaction near the gas + limit cap and confirm it succeeds. + + The authorization count is sized from the per-authorization total + intrinsic (regular + state) and the transaction gas-limit cap, so it + automatically tracks the repriced cost. + """ + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + + per_auth_total = fork.gas_costs().AUTH_PER_EMPTY_ACCOUNT + base = fork.transaction_intrinsic_cost_calculator()( + authorization_list_or_count=0, + ) + # Leave headroom for the base intrinsic and a little slack. + num_auths = (gas_limit_cap - base) // per_auth_total + assert num_auths >= 2 + + contract = pre.deploy_contract(code=Op.STOP) + signers = [pre.fund_eoa() for _ in range(num_auths)] + authorization_list = [ + AuthorizationTuple(address=contract, nonce=0, signer=signer) + for signer in signers + ] + + sender = pre.fund_eoa() + tx = Transaction( + to=contract, + authorization_list=authorization_list, + sender=sender, + ) + + post = { + signer: Account(code=Spec7702.delegation_designation(contract)) + for signer in signers + } + state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_set_code_auth_refunds.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_set_code_auth_refunds.py new file mode 100644 index 00000000000..ba3dc5e5773 --- /dev/null +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_set_code_auth_refunds.py @@ -0,0 +1,242 @@ +""" +Tests for the EIP-7702 authorization *regular*-gas refund under +[EIP-8038: State Access Gas Cost Increase](https://eips.ethereum.org/EIPS/eip-8038). + +When an authority's account leaf already exists, ``set_delegation`` +refunds on two independent channels: + +* the **state** channel: ``StateGasCosts.NEW_ACCOUNT`` is refilled into + ``state_gas_reservoir`` / ``state_refund`` (and ``AUTH_BASE`` too when + the code slot already holds a delegation indicator). It is subtracted + from ``tx_state_gas`` *before* the regular refund is applied and is + **not** subject to the EIP-3529 one-fifth cap. This channel is the + subject of the EIP-8037 ``eip8037_state_creation_gas_cost_increase`` + suite. +* the **regular** channel: the worst-case ``GasCosts.ACCOUNT_WRITE`` + charged in the regular intrinsic is returned via the regular refund + counter, and **is** subject to the one-fifth cap. + +This module pins the *regular* ``ACCOUNT_WRITE`` refund. The dual-channel +accounting mirrors ``process_transaction``: + + gas_before_regular_refund = ( + intrinsic_regular + exec_regular + + intrinsic_state + exec_state + - state_refund # uncapped, subtracted first + ) + regular_refund = min( + n * ACCOUNT_WRITE, + gas_before_regular_refund // fork.max_refund_quotient(), + ) + cumulative_gas_used = gas_before_regular_refund - regular_refund + +Two regimes are exercised: + +* a non-clearing delegation on an existing leaf, padded with cold + SSTOREs so ``gas_before_regular_refund`` is large and the full + ``n * ACCOUNT_WRITE`` clears under the cap; and +* a *clearing* re-authorization of an existing-delegation authority, + where the state channel refunds the **full** per-auth state intrinsic + (``NEW_ACCOUNT + AUTH_BASE``). That collapses + ``gas_before_regular_refund`` to the regular intrinsic alone, so the + cap ``gas // 5`` becomes the binding term and the regular refund + clamps below ``ACCOUNT_WRITE``. +""" + +from typing import List + +import pytest +from execution_testing import ( + Account, + Alloc, + AuthorizationTuple, + Bytecode, + Environment, + Fork, + Op, + StateTestFiller, + Storage, + Transaction, + TransactionReceipt, +) +from execution_testing.checklists import EIPChecklist + +from ...prague.eip7702_set_code_tx.spec import Spec as Spec7702 +from .spec import ref_spec_8038 + +REFERENCE_SPEC_GIT_PATH = ref_spec_8038.git_path +REFERENCE_SPEC_VERSION = ref_spec_8038.version + +pytestmark = pytest.mark.valid_from("Amsterdam") + + +def _sstore_state_per_op(fork: Fork) -> int: + """Return the state gas of one cold ``0 -> 1`` SSTORE.""" + return Op.SSTORE(new_value=1).state_cost(fork) + + +@EIPChecklist.GasRefundsChanges.Test.RefundCalculation() +@pytest.mark.parametrize("n", [1, 2]) +def test_existing_authority_regular_refund_visible( + state_test: StateTestFiller, + env: Environment, + pre: Alloc, + fork: Fork, + n: int, +) -> None: + """ + Pin the full regular ``ACCOUNT_WRITE`` refund for set-code + authorizations whose authority leaves already exist. + + Each authority is an existing funded EOA delegating to a fresh + contract, so ``set_delegation`` refunds ``NEW_ACCOUNT`` on the state + channel (uncapped) and ``ACCOUNT_WRITE`` on the regular channel + (capped). The execution is padded with ten cold ``0 -> 1`` SSTOREs + so ``gas_before_regular_refund`` is large and the one-fifth cap + exceeds ``n * ACCOUNT_WRITE``; the entire regular refund is visible + in the receipt. + + The state refill is subtracted first and is not capped; it belongs + to the EIP-8037 suite and is only used here to size the receipt. + """ + gas_costs = fork.gas_costs() + account_write = gas_costs.ACCOUNT_WRITE + # Existing leaf overwritten with a fresh (non-clearing) delegation + # indicator: only NEW_ACCOUNT is refilled on the state channel. + state_refund = gas_costs.REFUND_AUTH_PER_EXISTING_ACCOUNT * n + + total_intrinsic = fork.transaction_intrinsic_cost_calculator()( + authorization_list_or_count=n, + ) + intrinsic_state = fork.transaction_intrinsic_state_gas( + authorization_count=n, + ) + + num_sstores = 10 + storage = Storage() + code = Bytecode() + for _ in range(num_sstores): + code += Op.SSTORE(storage.store_next(1), 1) + code += Op.STOP + contract = pre.deploy_contract(code=code) + + exec_state = _sstore_state_per_op(fork) * num_sstores + # The deployed bytecode's combined cost minus its state portion is + # the regular execution gas (includes the PUSHes for SSTORE args). + exec_regular = code.gas_cost(fork) - exec_state + + delegate = pre.deploy_contract(code=Op.STOP) + signers = [pre.fund_eoa() for _ in range(n)] + authorization_list = [ + AuthorizationTuple(address=delegate, nonce=0, signer=signer) + for signer in signers + ] + + gas_before_regular_refund = ( + total_intrinsic + exec_regular + exec_state - state_refund + ) + regular_refund = min( + n * account_write, + gas_before_regular_refund // fork.max_refund_quotient(), + ) + assert regular_refund == n * account_write + cumulative_gas_used = gas_before_regular_refund - regular_refund + + tx = Transaction( + to=contract, + state_gas_reservoir=intrinsic_state + exec_state, + authorization_list=authorization_list, + sender=pre.fund_eoa(), + expected_receipt=TransactionReceipt( + cumulative_gas_used=cumulative_gas_used, + ), + ) + + post: dict = {contract: Account(storage=storage)} + for signer in signers: + post[signer] = Account( + code=Spec7702.delegation_designation(delegate), + ) + state_test(env=env, pre=pre, post=post, tx=tx) + + +@EIPChecklist.GasRefundsChanges.Test.RefundCalculation() +@pytest.mark.parametrize("n", [1, 3]) +def test_clearing_delegation_regular_refund_capped( + state_test: StateTestFiller, + env: Environment, + pre: Alloc, + fork: Fork, + n: int, +) -> None: + """ + Clearing a delegation refunds the full per-auth state intrinsic on + the state channel, which drives the regular refund into the + one-fifth cap. + + Each authority already holds a delegation and re-authorizes to the + reset (zero) address, clearing its code. The leaf exists, so + ``ACCOUNT_WRITE`` is refunded on the regular channel; the code slot + held a delegation indicator and the new indicator is empty, so both + ``NEW_ACCOUNT`` and ``AUTH_BASE`` are refilled on the state channel. + Refunding the full per-auth state intrinsic collapses + ``gas_before_regular_refund`` to the regular intrinsic alone, so the + cap ``gas // 5`` is below ``n * ACCOUNT_WRITE`` and the regular + refund clamps to ``gas // 5`` (cap-saturated). No execution padding + is used, so the contrast with the full-refund test is purely the + refunded state magnitude. + """ + gas_costs = fork.gas_costs() + account_write = gas_costs.ACCOUNT_WRITE + + total_intrinsic = fork.transaction_intrinsic_cost_calculator()( + authorization_list_or_count=n, + ) + intrinsic_state = fork.transaction_intrinsic_state_gas( + authorization_count=n, + ) + # Clearing an existing delegation refills the full per-auth state + # intrinsic (NEW_ACCOUNT + AUTH_BASE) for every authorization. + state_refund = intrinsic_state + + contract = pre.deploy_contract(code=Op.STOP) + delegated_to = pre.deploy_contract(code=Op.STOP) + # Authorities that already delegate; fund_eoa(delegation=...) sets + # the authority nonce to 1, which is the expected auth nonce. + signers = [pre.fund_eoa(delegation=delegated_to) for _ in range(n)] + authorization_list: List[AuthorizationTuple] = [ + AuthorizationTuple( + address=Spec7702.RESET_DELEGATION_ADDRESS, + nonce=1, + signer=signer, + ) + for signer in signers + ] + + gas_before_regular_refund = total_intrinsic - state_refund + regular_refund = min( + n * account_write, + gas_before_regular_refund // fork.max_refund_quotient(), + ) + # The cap is the binding term: the refund clamps below ACCOUNT_WRITE. + assert regular_refund < n * account_write + assert regular_refund == gas_before_regular_refund // ( + fork.max_refund_quotient() + ) + cumulative_gas_used = gas_before_regular_refund - regular_refund + + tx = Transaction( + to=contract, + state_gas_reservoir=intrinsic_state, + authorization_list=authorization_list, + sender=pre.fund_eoa(), + expected_receipt=TransactionReceipt( + cumulative_gas_used=cumulative_gas_used, + ), + ) + + post: dict = {} + for signer in signers: + # Delegation cleared back to empty code, nonce incremented. + post[signer] = Account(nonce=2, code=b"") + state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_sload_gas.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_sload_gas.py new file mode 100644 index 00000000000..26502836c7b --- /dev/null +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_sload_gas.py @@ -0,0 +1,192 @@ +""" +Tests for [EIP-8038: State Access Gas Cost Increase](https://eips.ethereum.org/EIPS/eip-8038). + +Covers the EIP-8038 ``SLOAD`` repricing: a cold storage slot read costs +``COLD_STORAGE_ACCESS`` (3000) and a warm read costs ``WARM_SLOAD`` (100). +A slot is warmed either by listing it in the transaction access list or by +a prior in-frame access; warmth acquired inside a sub-call that REVERTs is +discarded, so a subsequent read in the outer frame is cold again. +""" + +import pytest +from execution_testing import ( + AccessList, + Account, + Alloc, + Bytecode, + CodeGasMeasure, + Environment, + Fork, + Op, + StateTestFiller, + Transaction, +) +from execution_testing.checklists import EIPChecklist + +from .spec import ref_spec_8038 + +REFERENCE_SPEC_GIT_PATH = ref_spec_8038.git_path +REFERENCE_SPEC_VERSION = ref_spec_8038.version + +pytestmark = pytest.mark.valid_from("Amsterdam") + + +def _measure_sload(slot: int, fork: Fork) -> CodeGasMeasure: + """ + Build a ``CodeGasMeasure`` around a single ``SLOAD`` whose stored + result is the bare opcode cost (the PUSH wrapper is subtracted out). + + The runtime warmth of ``slot`` determines whether the measured value + lands at ``COLD_STORAGE_ACCESS`` or ``WARM_SLOAD``. + """ + measured_code = Op.SLOAD(slot) + # Subtract the SLOAD opcode's own cold cost so only the PUSH wrapper + # remains as overhead; the runtime access cost is what gets stored. + overhead_cost = measured_code.gas_cost(fork) - Op.SLOAD( + key_warm=False + ).gas_cost(fork) + return CodeGasMeasure( + code=measured_code, + overhead_cost=overhead_cost, + extra_stack_items=1, + ) + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +@pytest.mark.parametrize("warm", [False, True], ids=["cold", "warm"]) +def test_sload_gas( + state_test: StateTestFiller, + env: Environment, + pre: Alloc, + fork: Fork, + warm: bool, +) -> None: + """ + Measure the gas of a ``SLOAD`` on a slot that is either cold or + pre-warmed via the transaction access list. + + A cold read must cost ``COLD_STORAGE_ACCESS`` (3000); a warm read + must cost ``WARM_SLOAD`` (100). + """ + slot = 0x42 + expected_gas = Op.SLOAD(key_warm=warm).gas_cost(fork) + + measure_address = pre.deploy_contract( + code=_measure_sload(slot, fork), + storage={slot: 1}, + ) + + # Warm the slot via the access list when required; the cold case + # leaves it unlisted so its first runtime read is cold. + access_list = ( + [AccessList(address=measure_address, storage_keys=[slot])] + if warm + else None + ) + tx = Transaction( + to=measure_address, + sender=pre.fund_eoa(), + access_list=access_list, + ) + + # Slot 0 holds the measured gas; the read slot keeps its value. + post = {measure_address: Account(storage={0: expected_gas, slot: 1})} + state_test(env=env, pre=pre, post=post, tx=tx) + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +def test_sload_warm_after_prior_touch( + state_test: StateTestFiller, + env: Environment, + pre: Alloc, + fork: Fork, +) -> None: + """ + A first ``SLOAD`` on a cold slot warms it; the second in-frame + ``SLOAD`` of the same slot is charged ``WARM_SLOAD`` (100). + + Slot 0 records the cold first read and slot 1 the warm second read. + """ + slot = 0x42 + cold_gas = Op.SLOAD(key_warm=False).gas_cost(fork) + warm_gas = Op.SLOAD(key_warm=True).gas_cost(fork) + + measured_code = Op.SLOAD(slot) + overhead_cost = measured_code.gas_cost(fork) - Op.SLOAD( + key_warm=False + ).gas_cost(fork) + + # First measure (slot 0): cold read. Second measure (slot 1): the + # same slot is now warm. + code = CodeGasMeasure( + code=measured_code, + overhead_cost=overhead_cost, + extra_stack_items=1, + sstore_key=0, + ) + CodeGasMeasure( + code=measured_code, + overhead_cost=overhead_cost, + extra_stack_items=1, + sstore_key=1, + ) + measure_address = pre.deploy_contract(code=code, storage={slot: 1}) + + tx = Transaction(to=measure_address, sender=pre.fund_eoa()) + + # Slots 0/1 hold the two measured reads; the read slot keeps its + # value. + post = { + measure_address: Account(storage={0: cold_gas, 1: warm_gas, slot: 1}) + } + state_test(env=env, pre=pre, post=post, tx=tx) + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +def test_sload_warmth_reverts_on_subcall_revert( + state_test: StateTestFiller, + env: Environment, + pre: Alloc, + fork: Fork, +) -> None: + """ + Warmth acquired inside a reverted sub-call does not persist. + + An inner contract ``SLOAD``s the slot via ``DELEGATECALL`` (so the + warmed ``(address, slot)`` pair belongs to the outer account) then + ``REVERT``s. Back in the outer frame, that same slot's first + ``SLOAD`` is cold again and is charged ``COLD_STORAGE_ACCESS`` + (3000), proving the warm-slot set is rolled back on revert. + """ + slot = 0x42 + cold_gas = Op.SLOAD(key_warm=False).gas_cost(fork) + + # Inner: read the slot (warming it in the delegating account's + # context) then revert. + inner = pre.deploy_contract( + code=Op.SLOAD(slot) + Op.REVERT(0, 0), + ) + + # Outer: DELEGATECALL inner (which reverts), then measure its own + # first SLOAD of the slot. DELEGATECALL keeps the outer account's + # storage context, so inner's read warms (outer, slot); the revert + # discards that warmth, making the measured read cold. + measured_code = Op.SLOAD(slot) + overhead_cost = measured_code.gas_cost(fork) - Op.SLOAD( + key_warm=False + ).gas_cost(fork) + + outer_code: Bytecode = Op.POP( + Op.DELEGATECALL(gas=100_000, address=inner) + ) + CodeGasMeasure( + code=measured_code, + overhead_cost=overhead_cost, + extra_stack_items=1, + ) + outer = pre.deploy_contract(code=outer_code, storage={slot: 1}) + + tx = Transaction(to=outer, sender=pre.fund_eoa()) + + # Slot 0 holds the measured (cold) read; the read slot keeps its + # value. + post = {outer: Account(storage={0: cold_gas, slot: 1})} + state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_sstore_gas.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_sstore_gas.py new file mode 100644 index 00000000000..e6ba6c912fe --- /dev/null +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_sstore_gas.py @@ -0,0 +1,215 @@ +""" +Tests for [EIP-8038: State Access Gas Cost Increase](https://eips.ethereum.org/EIPS/eip-8038). + +Covers the EIP-8038 ``SSTORE`` *regular* (non-state) gas schedule. The +state-creation charge for a zero-to-nonzero write is owned by EIP-8037 +and is asserted separately; here every expectation is taken from the +``regular_cost`` dimension only. + +The regular ``SSTORE`` cost is the slot-access cost (``COLD_STORAGE_ACCESS`` +when the key is cold, else ``WARM_SLOAD``) plus, on the first change of the +slot in the transaction (``original == current != new``), the write cost +``STORAGE_WRITE`` (modeled as ``COLD_STORAGE_WRITE - COLD_STORAGE_ACCESS``). +""" + +import pytest +from execution_testing import ( + AccessList, + Account, + Alloc, + Bytecode, + CodeGasMeasure, + Fork, + Op, + StateTestFiller, + Transaction, +) +from execution_testing.checklists import EIPChecklist + +from .spec import ref_spec_8038 + +REFERENCE_SPEC_GIT_PATH = ref_spec_8038.git_path +REFERENCE_SPEC_VERSION = ref_spec_8038.version + +pytestmark = pytest.mark.valid_from("Amsterdam") + + +# Each parameter: (key_warm, original, current, new). The id encodes the +# (original, current, new) triple, where ``0`` is the zero value and +# ``x``/``y``/``z`` are distinct non-zero values (1, 2, 3). The suffix marks +# the slot state at the measured write. A clean slot (current == original) +# is ``_cold`` or access-list ``_warm``; a dirty slot (current != original) +# is ``_dirty`` and has necessarily been warmed by the prior in-frame SSTORE. +SSTORE_ROWS = [ + pytest.param(False, 0, 0, 1, id="00x_cold"), + pytest.param(True, 0, 0, 1, id="00x_warm"), + pytest.param(True, 0, 1, 0, id="0x0_dirty"), + pytest.param(True, 1, 1, 0, id="xx0_warm"), + pytest.param(False, 1, 1, 2, id="xxy_cold"), + pytest.param(True, 1, 1, 2, id="xxy_warm"), + pytest.param(True, 1, 2, 3, id="xyz_dirty"), + pytest.param(True, 1, 2, 1, id="xyx_dirty"), + pytest.param(True, 1, 1, 1, id="xxx_warm"), + pytest.param(False, 1, 1, 1, id="xxx_cold"), +] + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +@pytest.mark.parametrize("key_warm,original,current,new", SSTORE_ROWS) +def test_sstore_regular_gas( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + key_warm: bool, + original: int, + current: int, + new: int, +) -> None: + """ + Measure the regular ``SSTORE`` gas for each EIP-8038 row and assert it. + + The final (measured) ``SSTORE`` is wrapped in ``CodeGasMeasure`` so the + executed regular cost is stored on-chain and asserted against + ``expected_regular`` (slot access plus write-on-first-change). The same + value is cross-checked against the framework opcode model's + ``regular_cost`` as a secondary guard. The state-gas dimension is owned + by EIP-8037 and funded from the reservoir, so it is excluded here. + """ + # Move the data off slot 0 so ``CodeGasMeasure`` can store the measured + # cost in slot 0. The bare (operand-free) opcode carries the metadata so + # the measure overhead resolves to just the two operand PUSHes, and + # ``regular_cost``/``gas_cost`` are exact. + data_slot = 0x42 + result_slot = 0 + measured_bare = Op.SSTORE.with_metadata( + key_warm=key_warm, + original_value=original, + current_value=current, + new_value=new, + ) + measured = measured_bare(data_slot, new) + + # Cross-check the oracle agrees with the hand-derived formula. + expected_regular = measured_bare.regular_cost(fork) + + # Reach ``current`` from ``original`` with an unmeasured prep SSTORE when + # they differ, then measure the write to ``new``. The slot is warmed for + # ``key_warm`` rows via the access list (and, where current != original, + # the prep SSTORE warms it too); cold rows have neither, so the measured + # write is cold. + code = Bytecode() + if current != original: + code += Op.SSTORE(data_slot, current) + code += CodeGasMeasure( + code=measured, + overhead_cost=measured.gas_cost(fork) - measured_bare.gas_cost(fork), + extra_stack_items=0, + sstore_key=result_slot, + ) + + contract = pre.deploy_contract( + code=code, + storage={data_slot: original} if original != 0 else {}, + ) + + # Warm the slot for ``key_warm`` rows that have no prep to warm it; + # harmless for prep rows (warmth is set membership). Built after + # ``deploy_contract`` so the address exists. + access_list = ( + [AccessList(address=contract, storage_keys=[data_slot])] + if key_warm + else None + ) + + # State gas (owned by EIP-8037) is funded from the reservoir so it never + # disturbs the regular gas this test isolates. ``gas_limit`` is left + # unset so the reservoir lands above the EIP-7825 cap and ``Op.GAS`` + # measures regular gas only; an explicit gas_limit below the cap would + # zero the reservoir and spill state gas into the measurement. + single_set_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + tx = Transaction( + to=contract, + sender=pre.fund_eoa(), + access_list=access_list, + state_gas_reservoir=2 * single_set_state_gas, + ) + + # result_slot holds the measured regular cost; data_slot holds ``new`` + # (absent when new == 0, because the slot is cleared). + expected_storage = {result_slot: expected_regular} + if new != 0: + expected_storage[data_slot] = new + post = {contract: Account(storage=expected_storage)} + state_test(pre=pre, post=post, tx=tx) + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +def test_sstore_cold_then_warm_same_slot( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + A first ``SSTORE`` on a cold slot warms it; the second in-frame + ``SSTORE`` of the same slot is charged only ``WARM_SLOAD`` (100). + + The slot starts non-zero (original 1) and is left unlisted, so the + first write is cold and is its first change (original == current != + new), costing ``COLD_STORAGE_ACCESS + STORAGE_WRITE`` (3000 + 10000). + That write warms the slot, so the second write -- which moves the slot + again without being a first change -- costs only ``WARM_SLOAD`` (100), + with no further ``STORAGE_WRITE``. Slot 0 records the cold first write + and slot 1 the warm second write; the data slot keeps its final value. + """ + data_slot = 0x42 + + # First write: cold, first change of a non-zero-original slot. The + # bare (operand-free) opcode carries the same metadata so that the + # CodeGasMeasure overhead resolves to just the two operand PUSHes. + first_bare = Op.SSTORE.with_metadata( + key_warm=False, + original_value=1, + current_value=1, + new_value=2, + ) + first = first_bare(data_slot, 2) + # Second write: same slot, now warm; not a first change, so the + # write cost is not re-charged and only the warm access applies. + second_bare = Op.SSTORE.with_metadata( + key_warm=True, + original_value=1, + current_value=2, + new_value=3, + ) + second = second_bare(data_slot, 3) + + expected_first = first.regular_cost(fork) - 2 * fork.gas_costs().VERY_LOW + expected_second = second.regular_cost(fork) - 2 * fork.gas_costs().VERY_LOW + + # Each measured write stores its own runtime cost; the overhead + # subtraction strips the two operand PUSHes so the stored value is the + # bare SSTORE cost. The second write finds the slot warm. + code = CodeGasMeasure( + code=first, + overhead_cost=first.gas_cost(fork) - first_bare.gas_cost(fork), + extra_stack_items=0, + sstore_key=0, + ) + CodeGasMeasure( + code=second, + overhead_cost=second.gas_cost(fork) - second_bare.gas_cost(fork), + extra_stack_items=0, + sstore_key=1, + ) + + contract = pre.deploy_contract(code=code, storage={data_slot: 1}) + + tx = Transaction(to=contract, sender=pre.fund_eoa()) + + # Slots 0/1 hold the two measured writes; the data slot ends at its + # final written value. + post = { + contract: Account( + storage={0: expected_first, 1: expected_second, data_slot: 3} + ) + } + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_sstore_refunds.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_sstore_refunds.py new file mode 100644 index 00000000000..7becbe2bb22 --- /dev/null +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_sstore_refunds.py @@ -0,0 +1,349 @@ +""" +Tests for [EIP-8038: State Access Gas Cost Increase](https://eips.ethereum.org/EIPS/eip-8038). + +The headline mechanism of the pinned spec version ``a8862ae`` is the +``SSTORE`` clear-refund *reversal*: ``refund_counter`` is decremented by +``REFUND_STORAGE_CLEAR`` when a slot's original value is non-zero, its +current value is zero and the new value is non-zero (a slot cleared +earlier in the same transaction is restored). The spec reverses the clear +refund "so that clearing and then restoring a slot within the same +transaction is never net-profitable", closing the ``x -> 0 -> x`` round +trip; this reversal is exercised by +``test_sstore_clear_then_reset_nets_zero``. + +This module covers the EIP-8038 *regular* ``SSTORE`` refund schedule via +the transaction receipt's ``cumulative_gas_used``: + +* Clearing a slot whose original value is non-zero grants + ``REFUND_STORAGE_CLEAR`` (12480) to ``refund_counter`` (no EIP-8037 + state refund, since no state was created). +* Clearing then re-setting the same non-zero-original slot nets a zero + refund: the clear grant is reversed (``refund -= REFUND_STORAGE_CLEAR``) + exactly when ``original != 0 and current == 0`` and a non-zero value is + written back. +* Restoring a non-zero-original slot to its original value refunds the + write cost ``STORAGE_WRITE`` (10000). +* The applied refund is capped at ``gas_used // 5`` (EIP-3529 quotient). + +All refunds use a non-zero original so the state-creation refund owned by +EIP-8037 is never involved; only the EIP-8038 regular dimension is +exercised. +""" + +import pytest +from execution_testing import ( + Account, + Alloc, + Bytecode, + Fork, + Op, + StateTestFiller, + Transaction, + TransactionReceipt, +) +from execution_testing.checklists import EIPChecklist + +from .spec import ref_spec_8038 + +REFERENCE_SPEC_GIT_PATH = ref_spec_8038.git_path +REFERENCE_SPEC_VERSION = ref_spec_8038.version + +pytestmark = pytest.mark.valid_from("Amsterdam") + + +def _cumulative_gas_used(code: Bytecode, fork: Fork) -> int: + """ + Return the receipt ``cumulative_gas_used`` for a single transaction + whose execution is exactly ``code``. + + Mirrors the spec: gross gas is intrinsic plus the regular and state + gas of the code; the applied refund is ``min(gross // 5, refund)`` + (EIP-3529 quotient cap); the receipt reports gross minus the applied + refund. + """ + intrinsic = fork.transaction_intrinsic_cost_calculator()( + return_cost_deducted_prior_execution=True + ) + gross = intrinsic + code.regular_cost(fork) + code.state_cost(fork) + applied_refund = min(gross // 5, code.refund(fork)) + return gross - applied_refund + + +@EIPChecklist.GasRefundsChanges.Test.RefundCalculation() +@EIPChecklist.GasRefundsChanges.Test.RefundCalculation.Under() +def test_sstore_clear_grants_refund( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Clearing a non-zero-original slot grants ``REFUND_STORAGE_CLEAR``. + + Enough unrelated gas is burned so the EIP-3529 quotient cap + (``gas_used // 5``) does not bind, letting the full 12480 refund be + observed in ``cumulative_gas_used``. The non-zero original means no + EIP-8037 state refund participates. + """ + gas_costs = fork.gas_costs() + refund_clear = gas_costs.REFUND_STORAGE_CLEAR + + clear = Op.SSTORE.with_metadata( + key_warm=False, + original_value=1, + current_value=1, + new_value=0, + )(0, 0) + # Burn cheap gas (JUMPDEST = 1 gas, no stack effect) so that + # gas_used // 5 exceeds the refund and the full grant applies. + burn = Op.JUMPDEST * 60_000 + code = clear + burn + + contract = pre.deploy_contract(code=code, storage={0: 1}) + + # Sanity: the slot's refund counter accrues exactly one clear grant. + assert code.refund(fork) == refund_clear + expected_cumulative = _cumulative_gas_used(code, fork) + # The cap must not bind here, so the full grant is visible. + intrinsic = fork.transaction_intrinsic_cost_calculator()( + return_cost_deducted_prior_execution=True + ) + gross = intrinsic + code.regular_cost(fork) + assert gross // 5 > refund_clear + assert expected_cumulative == gross - refund_clear + + tx = Transaction( + to=contract, + sender=pre.fund_eoa(), + expected_receipt=TransactionReceipt( + cumulative_gas_used=expected_cumulative + ), + ) + + post = {contract: Account(storage={0: 0})} + state_test(pre=pre, post=post, tx=tx) + + +@EIPChecklist.GasRefundsChanges.Test.RefundCalculation() +def test_sstore_clear_then_reset_nets_zero( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Clearing then re-setting a non-zero-original slot nets zero refund. + + The clear grants ``REFUND_STORAGE_CLEAR``; re-setting the slot to a + non-zero value reverses it. ``refund_counter`` ends at zero, so + ``cumulative_gas_used`` equals the gross gas with no refund applied. + """ + code = Op.SSTORE.with_metadata( + key_warm=False, + original_value=1, + current_value=1, + new_value=0, + )(0, 0) + Op.SSTORE.with_metadata( + key_warm=True, + original_value=1, + current_value=0, + new_value=2, + )(0, 2) + + contract = pre.deploy_contract(code=code, storage={0: 1}) + + # The grant and its reversal cancel exactly. + assert code.refund(fork) == 0 + expected_cumulative = _cumulative_gas_used(code, fork) + + tx = Transaction( + to=contract, + sender=pre.fund_eoa(), + expected_receipt=TransactionReceipt( + cumulative_gas_used=expected_cumulative + ), + ) + + post = {contract: Account(storage={0: 2})} + state_test(pre=pre, post=post, tx=tx) + + +@EIPChecklist.GasRefundsChanges.Test.RefundCalculation() +@EIPChecklist.GasRefundsChanges.Test.RefundCalculation.Under() +def test_sstore_restore_nonzero_refunds_write( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Restoring a non-zero-original slot refunds the write cost. + + The slot is changed (charging ``STORAGE_WRITE``) then restored to its + original non-zero value, refunding ``STORAGE_WRITE`` (10000). Gas is + burned so the quotient cap does not bind and the full refund is + observable. + """ + gas_costs = fork.gas_costs() + storage_write = ( + gas_costs.COLD_STORAGE_WRITE - gas_costs.COLD_STORAGE_ACCESS + ) + + code = Op.SSTORE.with_metadata( + key_warm=False, + original_value=1, + current_value=1, + new_value=2, + )(0, 2) + Op.SSTORE.with_metadata( + key_warm=True, + original_value=1, + current_value=2, + new_value=1, + )(0, 1) + burn = Op.JUMPDEST * 60_000 + code += burn + + contract = pre.deploy_contract(code=code, storage={0: 1}) + + assert code.refund(fork) == storage_write + expected_cumulative = _cumulative_gas_used(code, fork) + intrinsic = fork.transaction_intrinsic_cost_calculator()( + return_cost_deducted_prior_execution=True + ) + gross = intrinsic + code.regular_cost(fork) + assert gross // 5 > storage_write + assert expected_cumulative == gross - storage_write + + tx = Transaction( + to=contract, + sender=pre.fund_eoa(), + expected_receipt=TransactionReceipt( + cumulative_gas_used=expected_cumulative + ), + ) + + post = {contract: Account(storage={0: 1})} + state_test(pre=pre, post=post, tx=tx) + + +@EIPChecklist.GasRefundsChanges.Test.RefundCalculation() +@EIPChecklist.GasRefundsChanges.Test.RefundCalculation.Exact() +@pytest.mark.parametrize("num_clears", [1, 8, 32]) +def test_sstore_refund_quotient_cap( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + num_clears: int, +) -> None: + """ + The applied refund saturates at the EIP-3529 quotient cap. + + ``num_clears`` distinct non-zero-original slots are each cleared, + accruing ``num_clears * REFUND_STORAGE_CLEAR`` into ``refund_counter``. + A single clear's gross gas is small enough that ``gas_used // 5`` is + always below the accrued refund, so the applied refund is the cap and + ``cumulative_gas_used`` reflects ``min(gas_used // 5, accrued)``. + """ + gas_costs = fork.gas_costs() + accrued = num_clears * gas_costs.REFUND_STORAGE_CLEAR + + code = Bytecode() + for slot in range(num_clears): + code += Op.SSTORE.with_metadata( + key_warm=False, + original_value=1, + current_value=1, + new_value=0, + )(slot, 0) + + contract = pre.deploy_contract( + code=code, + storage=dict.fromkeys(range(num_clears), 1), + ) + + assert code.refund(fork) == accrued + intrinsic = fork.transaction_intrinsic_cost_calculator()( + return_cost_deducted_prior_execution=True + ) + gross = intrinsic + code.regular_cost(fork) + # The cap binds for every parametrization (single-clear gross is far + # below 5x a clear refund). + cap = gross // 5 + assert cap < accrued + applied_refund = min(cap, accrued) + expected_cumulative = gross - applied_refund + + tx = Transaction( + to=contract, + sender=pre.fund_eoa(), + expected_receipt=TransactionReceipt( + cumulative_gas_used=expected_cumulative + ), + ) + + post = {contract: Account(storage=dict.fromkeys(range(num_clears), 0))} + state_test(pre=pre, post=post, tx=tx) + + +@EIPChecklist.GasRefundsChanges.Test.RefundCalculation() +@EIPChecklist.GasRefundsChanges.Test.RefundCalculation.Exact() +def test_sstore_refund_cap_exact_equality( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + The applied refund equals the EIP-3529 cap at exact equality. + + A single non-zero-original clear accrues ``REFUND_STORAGE_CLEAR``. + Cheap ``JUMPDEST`` gas (1 each) is burned so the gross gas lands at + exactly ``max_refund_quotient * accrued``; the quotient cap + ``gross // max_refund_quotient`` then equals the accrued refund + *exactly*, the boundary between the cap binding and not binding. The + full refund applies and ``cumulative_gas_used`` is ``gross - accrued``. + """ + gas_costs = fork.gas_costs() + quotient = fork.max_refund_quotient() + accrued = gas_costs.REFUND_STORAGE_CLEAR + + clear = Op.SSTORE.with_metadata( + key_warm=False, + original_value=1, + current_value=1, + new_value=0, + )(0, 0) + + intrinsic = fork.transaction_intrinsic_cost_calculator()( + return_cost_deducted_prior_execution=True + ) + # Target the exact boundary: gross == quotient * accrued, so that + # gross // quotient == accrued with no slack. Solve for the JUMPDEST + # count from the remaining gas after intrinsic and the clear's + # regular cost; each JUMPDEST costs exactly 1 gas. + jumpdest_gas = Op.JUMPDEST.gas_cost(fork) + target_gross = quotient * accrued + base_gross = intrinsic + clear.regular_cost(fork) + burn_gas = target_gross - base_gross + num_jumpdest, remainder = divmod(burn_gas, jumpdest_gas) + # An exact integer JUMPDEST count must reach the boundary; otherwise + # the equality below would not hold and the test would (correctly) + # fail rather than silently approximate. + assert remainder == 0 + + code = clear + Op.JUMPDEST * num_jumpdest + contract = pre.deploy_contract(code=code, storage={0: 1}) + + assert code.refund(fork) == accrued + gross = intrinsic + code.regular_cost(fork) + code.state_cost(fork) + # Exact equality: the cap is neither under nor over the accrued refund. + assert gross == target_gross + assert gross // quotient == accrued + expected_cumulative = gross - accrued + + tx = Transaction( + to=contract, + sender=pre.fund_eoa(), + expected_receipt=TransactionReceipt( + cumulative_gas_used=expected_cumulative + ), + ) + + post = {contract: Account(storage={0: 0})} + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_transient_storage_regression.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_transient_storage_regression.py new file mode 100644 index 00000000000..fc79ee51299 --- /dev/null +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_transient_storage_regression.py @@ -0,0 +1,87 @@ +""" +Tests for [EIP-8038: State Access Gas Cost Increase](https://eips.ethereum.org/EIPS/eip-8038). + +Regression guard: EIP-8038 reprices persistent storage and account +access but must NOT touch transient storage. ``TLOAD`` and ``TSTORE`` +remain at their EIP-1153 cost of ``OPCODE_TLOAD`` / ``OPCODE_TSTORE`` +(100 each), unchanged by the persistent-storage repricing and distinct +from the (repriced) persistent ``COLD_STORAGE_WRITE``. +""" + +import pytest +from execution_testing import ( + Account, + Alloc, + CodeGasMeasure, + Environment, + Fork, + Op, + StateTestFiller, + Transaction, +) +from execution_testing.checklists import EIPChecklist + +from .spec import ref_spec_8038 + +REFERENCE_SPEC_GIT_PATH = ref_spec_8038.git_path +REFERENCE_SPEC_VERSION = ref_spec_8038.version + +pytestmark = pytest.mark.valid_from("Amsterdam") + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +def test_transient_storage_gas_unchanged( + state_test: StateTestFiller, + env: Environment, + pre: Alloc, + fork: Fork, +) -> None: + """ + Measure ``TLOAD`` and ``TSTORE`` gas and confirm EIP-8038 left them + at the transient-storage price of 100 each. + + The bare-opcode costs (excluding their PUSH wrappers) must equal + ``OPCODE_TLOAD`` / ``OPCODE_TSTORE``. The guard + ``OPCODE_TSTORE != COLD_STORAGE_WRITE`` ensures the persistent + write repricing did not bleed into transient storage. + """ + gas_costs = fork.gas_costs() + very_low = gas_costs.VERY_LOW + + # Bare opcode costs: subtract the PUSH wrapper from each. + tload_bare = Op.TLOAD(0).gas_cost(fork) - 1 * very_low + tstore_bare = Op.TSTORE(0, 1).gas_cost(fork) - 2 * very_low + + assert tload_bare == gas_costs.OPCODE_TLOAD == 100 + assert tstore_bare == gas_costs.OPCODE_TSTORE == 100 + # Guard against over-eager repricing: transient write must not have + # been folded into the (repriced) persistent cold write cost. + assert gas_costs.OPCODE_TSTORE != gas_costs.COLD_STORAGE_WRITE + + # Measure TSTORE then TLOAD of the same transient slot in one frame. + tstore_code = CodeGasMeasure( + code=Op.TSTORE(0, 1), + overhead_cost=2 * very_low, + extra_stack_items=0, + sstore_key=0, + ) + tload_code = CodeGasMeasure( + code=Op.TLOAD(0), + overhead_cost=1 * very_low, + extra_stack_items=1, + sstore_key=1, + ) + contract = pre.deploy_contract(code=tstore_code + tload_code) + + tx = Transaction(to=contract, sender=pre.fund_eoa()) + + # Slot 0: measured TSTORE cost. Slot 1: measured TLOAD cost. + post = { + contract: Account( + storage={ + 0: gas_costs.OPCODE_TSTORE, + 1: gas_costs.OPCODE_TLOAD, + } + ) + } + state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/berlin/eip2930_access_list/test_acl.py b/tests/berlin/eip2930_access_list/test_acl.py index 226f842bc6c..9e7299994e6 100644 --- a/tests/berlin/eip2930_access_list/test_acl.py +++ b/tests/berlin/eip2930_access_list/test_acl.py @@ -235,6 +235,7 @@ def test_transaction_intrinsic_gas_cost( calldata=tx_data, contract_creation=contract_creation, access_list=access_lists, + sends_value=True, ) if not enough_gas: tx_gas_limit -= 1 diff --git a/tests/cancun/eip1153_tstore/test_tstorage_create_contexts.py b/tests/cancun/eip1153_tstore/test_tstorage_create_contexts.py index bd69ce23086..ed426e0be87 100644 --- a/tests/cancun/eip1153_tstore/test_tstorage_create_contexts.py +++ b/tests/cancun/eip1153_tstore/test_tstorage_create_contexts.py @@ -6,7 +6,6 @@ import pytest from execution_testing import ( - AccessList, Account, Address, Alloc, @@ -280,24 +279,33 @@ def test_tstore_rollback_on_failed_create( https://github.com/ethereum/execution-specs/issues/917 Initcode does TLOAD(1) to compute a return size, then does - TSTORE(1, 0x6000), then returns data of the computed size. - When TLOAD(1) is 0, the return size is 0x600a (exceeds max code - size 0x6000), so creation fails. + TSTORE(1, max_code_size), then returns data of the computed size. + When TLOAD(1) is 0, the return size exceeds the max code size, so + creation fails. The caller invokes CREATE/CREATE2 twice with the same initcode. If TSTORE from the first (failed) creation is properly rolled - back, the second creation also sees TLOAD(1)==0 and fails the - same way. If not rolled back, TLOAD(1)==0x6000 and the second - creation succeeds. + back, the second CREATE2 also sees TLOAD(1)==0 and fails the same + way, so nothing is deployed. If it were not rolled back, the + second CREATE2 would see TLOAD(1)==max_code_size, return a small + valid contract and succeed; the post-state therefore asserts the + target address is non-existent. + + The create results are not recorded with SSTORE: a failed create + burns its full 63/64 gas forward, and under the EIP-8037 + transaction gas-limit cap two of them in sequence leave too little + regular gas to write the result. Asserting account non-existence + checks the same rollback property without that write. """ # Initcode: - # return_size = 0x600a - TLOAD(1) - # TSTORE(1, 0x6000) + # return_size = (max_code_size + 0x0A) - TLOAD(1) + # TSTORE(1, max_code_size) # RETURN(offset=0, size=return_size) # - # TLOAD(1)==0: return_size = 0x600a > max code size -> fail - # TLOAD(1)==0x6000: return_size = 0x0a <= max code size -> succeed + # TLOAD(1)==0: return_size > max code size -> fail + # TLOAD(1)==max_code_size: return_size = 0x0A <= max -> succeed max_code_size = fork.max_code_size() + salt = 0 initcode = ( Op.TLOAD(1) @@ -310,36 +318,47 @@ def test_tstore_rollback_on_failed_create( initcode_bytes = bytes(initcode) initcode_len = len(initcode_bytes) + create_call = ( + create_opcode(0, 0, initcode_len, salt) + if create_opcode == Op.CREATE2 + else create_opcode(0, 0, initcode_len) + ) caller_code = ( Om.MSTORE(initcode_bytes, 0) - + Op.SSTORE( - 0, - create_opcode(0, 0, initcode_len, 0) - if create_opcode == Op.CREATE2 - else create_opcode(0, 0, initcode_len), - ) - + Op.SSTORE( - 1, - create_opcode(0, 0, initcode_len, 0) - if create_opcode == Op.CREATE2 - else create_opcode(0, 0, initcode_len), - ) + + create_call + + Op.POP + + create_call + + Op.POP ) - caller_address = pre.deploy_contract(caller_code, storage={0: 1, 1: 1}) + caller_address = pre.deploy_contract(caller_code) + + # CREATE2 targets one deterministic address (salt + initcode) for + # both attempts; CREATE targets nonce-derived addresses (the + # deployed caller starts at nonce 1). + if create_opcode == Op.CREATE2: + created_addresses = [ + compute_create_address( + address=caller_address, + salt=salt, + initcode=initcode, + opcode=Op.CREATE2, + ) + ] + else: + created_addresses = [ + compute_create_address( + address=caller_address, nonce=nonce, opcode=Op.CREATE + ) + for nonce in (1, 2) + ] sender = pre.fund_eoa() - tx = Transaction( - sender=sender, - to=caller_address, - access_list=[ - AccessList(address=caller_address, storage_keys=[0, 1]), - ], - ) + tx = Transaction(sender=sender, to=caller_address) - post = { - # Both creations fail because TSTORE is rolled back; - # initial storage {0: 1, 1: 1} is overwritten to zeros - caller_address: Account(storage={0: 0, 1: 0}), - } + # Both creations fail because TSTORE is rolled back, so nothing is + # deployed; the caller nonce still advances once per attempt. + post = {caller_address: Account(nonce=3)} + for created_address in created_addresses: + post[created_address] = Account.NONEXISTENT # type: ignore state_test(pre=pre, post=post, tx=tx) diff --git a/tests/cancun/eip4844_blobs/conftest.py b/tests/cancun/eip4844_blobs/conftest.py index 1504daa458f..6f93d162f88 100644 --- a/tests/cancun/eip4844_blobs/conftest.py +++ b/tests/cancun/eip4844_blobs/conftest.py @@ -9,6 +9,7 @@ Environment, Fork, Hash, + RecipientType, Transaction, TransitionFork, add_kzg_version, @@ -333,12 +334,20 @@ def non_zero_blob_gas_used_genesis_block( ] def create_blob_transaction(blob_range: Iterable[int]) -> Transaction: + intrinsic_gas = block_fork.transaction_intrinsic_cost_calculator()( + recipient_type=RecipientType.EMPTY_ACCOUNT, + sends_value=True, + ) + top_frame_gas = block_fork.transaction_top_frame_state_gas( + recipient_type=RecipientType.EMPTY_ACCOUNT, + sends_value=True, + ) return Transaction( ty=Spec.BLOB_TX_TYPE, sender=sender, to=empty_account_destination, value=1, - gas_limit=21_000, + gas_limit=intrinsic_gas + top_frame_gas, max_fee_per_gas=tx_max_fee_per_gas, max_priority_fee_per_gas=0, max_fee_per_blob_gas=blob_gas_price_calculator( diff --git a/tests/cancun/eip4844_blobs/test_blob_txs.py b/tests/cancun/eip4844_blobs/test_blob_txs.py index 50be25f9524..9b05cf607ef 100644 --- a/tests/cancun/eip4844_blobs/test_blob_txs.py +++ b/tests/cancun/eip4844_blobs/test_blob_txs.py @@ -33,6 +33,7 @@ Hash, Header, Op, + RecipientType, Removable, StateTestFiller, Storage, @@ -75,19 +76,93 @@ def destination_account( return pre.fund_eoa(destination_account_balance) +def _destination_recipient_type( + destination_account_code: Bytecode | None, + destination_account_balance: int, +) -> RecipientType: + if destination_account_code is not None: + return RecipientType.CONTRACT + if destination_account_balance == 0: + return RecipientType.EMPTY_ACCOUNT + return RecipientType.EOA + + @pytest.fixture def tx_gas( fork: Fork | TransitionFork, tx_calldata: bytes, tx_access_list: List[AccessList], + tx_value: int, + destination_account_code: Bytecode | None, + destination_account_balance: int, ) -> int: """Gas allocated to transactions sent during test.""" + post_transition_fork = fork.transitions_to() tx_intrinsic_cost_calculator = ( - fork.transitions_to().transaction_intrinsic_cost_calculator() + post_transition_fork.transaction_intrinsic_cost_calculator() + ) + recipient_type = _destination_recipient_type( + destination_account_code, destination_account_balance + ) + sends_value = tx_value > 0 + intrinsic = tx_intrinsic_cost_calculator( + calldata=tx_calldata, + access_list=tx_access_list, + recipient_type=recipient_type, + sends_value=sends_value, ) - return tx_intrinsic_cost_calculator( - calldata=tx_calldata, access_list=tx_access_list + top_frame_state = post_transition_fork.transaction_top_frame_state_gas( + recipient_type=recipient_type, + sends_value=sends_value, ) + return intrinsic + top_frame_state + + +@pytest.fixture +def tx_gas_per_tx( + fork: Fork | TransitionFork, + tx_gas: int, + tx_calldata: bytes, + tx_access_list: List[AccessList], + tx_value: int, + destination_account_code: Bytecode | None, + destination_account_balance: int, + blob_hashes_per_tx: List[List[bytes]], +) -> List[int]: + """ + Gas allocated to each transaction in the block. + + After the first value-sending tx to an initially-empty destination, + the recipient is no longer empty, so the EIP-2780 top-frame + ``NEW_ACCOUNT`` state-gas charge does not fire on subsequent txs. + """ + n_txs = len(blob_hashes_per_tx) + if n_txs <= 1: + return [tx_gas] * n_txs + + destination_starts_empty = ( + destination_account_code is None and destination_account_balance == 0 + ) + if destination_starts_empty and tx_value > 0: + post_transition_fork = fork.transitions_to() + intrinsic_calc = ( + post_transition_fork.transaction_intrinsic_cost_calculator() + ) + intrinsic = intrinsic_calc( + calldata=tx_calldata, + access_list=tx_access_list, + recipient_type=RecipientType.EOA, + sends_value=True, + ) + top_frame_state = post_transition_fork.transaction_top_frame_state_gas( + recipient_type=RecipientType.EOA, + sends_value=True, + ) + tx_gas_nonempty = intrinsic + top_frame_state + else: + tx_gas_nonempty = tx_gas + + return [tx_gas] + [tx_gas_nonempty] * (n_txs - 1) @pytest.fixture @@ -124,7 +199,7 @@ def blob_hashes_per_tx(blobs_per_tx: List[int]) -> List[List[Hash]]: @pytest.fixture def total_account_minimum_balance( # noqa: D103 blob_gas_per_blob: int, - tx_gas: int, + tx_gas_per_tx: List[int], tx_value: int, tx_max_fee_per_gas: int, tx_max_fee_per_blob_gas: int, @@ -135,15 +210,17 @@ def total_account_minimum_balance( # noqa: D103 transactions in the block of the test. """ minimum_cost = 0 - for tx_blob_count in [len(x) for x in blob_hashes_per_tx]: + for tx_i, tx_blob_count in enumerate(len(x) for x in blob_hashes_per_tx): blob_cost = tx_max_fee_per_blob_gas * blob_gas_per_blob * tx_blob_count - minimum_cost += (tx_gas * tx_max_fee_per_gas) + tx_value + blob_cost + minimum_cost += ( + (tx_gas_per_tx[tx_i] * tx_max_fee_per_gas) + tx_value + blob_cost + ) return minimum_cost @pytest.fixture def total_account_transactions_fee( # noqa: D103 - tx_gas: int, + tx_gas_per_tx: List[int], tx_value: int, blob_gas_price: int, block_base_fee_per_gas: int, @@ -156,7 +233,7 @@ def total_account_transactions_fee( # noqa: D103 Calculate actual fee for the blob transactions in the block of the test. """ total_cost = 0 - for tx_blob_count in [len(x) for x in blob_hashes_per_tx]: + for tx_i, tx_blob_count in enumerate(len(x) for x in blob_hashes_per_tx): blob_cost = blob_gas_price * blob_gas_per_blob * tx_blob_count block_producer_fee = ( tx_max_fee_per_gas - block_base_fee_per_gas @@ -164,7 +241,7 @@ def total_account_transactions_fee( # noqa: D103 else 0 ) total_cost += ( - (tx_gas * (block_base_fee_per_gas + block_producer_fee)) + tx_gas_per_tx[tx_i] * (block_base_fee_per_gas + block_producer_fee) + tx_value + blob_cost ) @@ -208,7 +285,7 @@ def sender(pre: Alloc, sender_initial_balance: int) -> Address: # noqa: D103 def txs( # noqa: D103 sender: EOA, destination_account: Optional[Address], - tx_gas: int, + tx_gas_per_tx: List[int], tx_value: int, tx_calldata: bytes, tx_max_fee_per_gas: int, @@ -225,7 +302,7 @@ def txs( # noqa: D103 sender=sender, to=destination_account, value=tx_value, - gas_limit=tx_gas, + gas_limit=tx_gas_per_tx[tx_i], data=tx_calldata, max_fee_per_gas=tx_max_fee_per_gas, max_priority_fee_per_gas=tx_max_priority_fee_per_gas, @@ -754,6 +831,7 @@ def test_sufficient_balance_blob_tx( @pytest.mark.valid_from("Cancun") def test_sufficient_balance_blob_tx_pre_fund_tx( blockchain_test: BlockchainTestFiller, + fork: Fork, total_account_minimum_balance: int, sender: EOA, env: Environment, @@ -773,15 +851,29 @@ def test_sufficient_balance_blob_tx_pre_fund_tx( - Transactions with max fee per blob gas lower or higher than the priority fee """ + recipient_type = ( + RecipientType.EOA if sender in pre else RecipientType.EMPTY_ACCOUNT + ) + sends_value = total_account_minimum_balance > 0 + intrinsic_calc = fork.transaction_intrinsic_cost_calculator() + intrinsic_gas = intrinsic_calc( + recipient_type=recipient_type, + sends_value=sends_value, + ) + top_frame_state_gas = fork.transaction_top_frame_state_gas( + recipient_type=recipient_type, + sends_value=sends_value, + ) + pre_funding_gas_limit = intrinsic_gas + top_frame_state_gas pre_funding_sender = pre.fund_eoa( - amount=(21_000 * 100) + total_account_minimum_balance + amount=(pre_funding_gas_limit * 100) + total_account_minimum_balance ) txs = [ Transaction( sender=pre_funding_sender, to=sender, value=total_account_minimum_balance, - gas_limit=21_000, + gas_limit=pre_funding_gas_limit, ) ] + txs blockchain_test( diff --git a/tests/paris/eip7610_create_collision/test_collision_selfdestruct.py b/tests/paris/eip7610_create_collision/test_collision_selfdestruct.py index a0f58bbbf18..4bb20365f3d 100644 --- a/tests/paris/eip7610_create_collision/test_collision_selfdestruct.py +++ b/tests/paris/eip7610_create_collision/test_collision_selfdestruct.py @@ -74,14 +74,12 @@ def test_selfdestruct_after_create2_collision( + Op.SSTORE( storage.store_next(1, "create2_call_success"), Op.CALL( - # Forwarded budget covers deployer's CREATE2 (charged - # then refunded on collision under EIP-8037) plus its - # SSTORE; both 0 pre-EIP-8037 and scale with cpsb. - gas=( - 500_000 - + fork.gas_costs().NEW_ACCOUNT - + Op.SSTORE(new_value=1).state_cost(fork) - ), + # The colliding CREATE2 consumes 63/64 of the deployer's + # gas (the account-creation state gas is charged then + # refunded on collision under EIP-8037); size the budget + # so the surviving 1/64 still covers the deployer's cold + # SSTORE of the CREATE2 result. + gas=500_000 + 64 * fork.gas_costs().COLD_STORAGE_WRITE, address=deployer, args_size=Op.CALLDATASIZE, ), diff --git a/tests/ported_static/stBadOpcode/test_measure_gas.py b/tests/ported_static/stBadOpcode/test_measure_gas.py index b262b96b127..f100b2b1ea8 100644 --- a/tests/ported_static/stBadOpcode/test_measure_gas.py +++ b/tests/ported_static/stBadOpcode/test_measure_gas.py @@ -3,6 +3,15 @@ Ported from: state_tests/stBadOpcode/measureGasFiller.yml + +@manually-enhanced: Do not overwrite. A binary search measures the gas +an opcode needs to succeed. Only the EXTCODE case shifts: it runs a +warm `EXTCODESIZE` plus a warm `EXTCODECOPY` (the target is warmed by +earlier search iterations), and EIP-8038 adds a flat +100 to each warm +extcode access. The stored threshold therefore grows by the sum of the +two opcodes' warm `(Amsterdam - Cancun)` cost deltas, derived from the +fork's own gas model so it is exactly 0 before EIP-8038; do not +hardcode the Amsterdam number. """ import pytest @@ -363,6 +372,26 @@ def test_measure_gas( address=Address(0x0000000000000000000000000000000000C0DEF2), # noqa: E501 ) + # The EXTCODE search measures a warm `EXTCODESIZE` plus a warm + # `EXTCODECOPY` (the target is warmed by earlier search iterations). + # EIP-8038 adds a flat surcharge to each warm extcode access, so the + # threshold grows by the two opcodes' combined warm cost delta versus + # Cancun. Derived from the fork gas model so it is 0 before EIP-8038. + # The EXTCODECOPY metadata mirrors the measured access: a 0x20-byte + # copy into already-expanded memory, so only the account-access + # component varies across forks. + warm_extcode_delta = ( + Op.EXTCODESIZE.with_metadata(address_warm=True).gas_cost(fork) - 100 + ) + ( + Op.EXTCODECOPY.with_metadata( + address_warm=True, + data_size=0x20, + new_memory_size=0x120, + old_memory_size=0x120, + ).gas_cost(fork) + - 103 + ) + expect_entries_: list[dict] = [ { "indexes": {"data": [0], "gas": -1, "value": -1}, @@ -397,7 +426,9 @@ def test_measure_gas( { "indexes": {"data": [10], "gas": -1, "value": -1}, "network": [">=Cancun"], - "result": {contract_12: Account(storage={0: 221})}, + "result": { + contract_12: Account(storage={0: 221 + warm_extcode_delta}) + }, }, { "indexes": {"data": [9], "gas": -1, "value": -1}, diff --git a/tests/ported_static/stBadOpcode/test_operation_diff_gas.py b/tests/ported_static/stBadOpcode/test_operation_diff_gas.py index 82a3e5c4cbd..be3bf8c0559 100644 --- a/tests/ported_static/stBadOpcode/test_operation_diff_gas.py +++ b/tests/ported_static/stBadOpcode/test_operation_diff_gas.py @@ -3,6 +3,16 @@ Ported from: state_tests/stBadOpcode/operationDiffGasFiller.yml + +@manually-enhanced: Do not overwrite. A search measures the gas an +opcode needs to succeed. Two access classes shift under EIP-8038: the +CALL-family probes (`CALL`/`CALLCODE`/`DELEGATECALL`/`STATICCALL`) make +one cold account access to the callee, repricing by +`COLD_ACCOUNT_ACCESS - 2600`; the EXTCODE probe runs a cold +`EXTCODESIZE` plus a warm `EXTCODECOPY`, each carrying the extra +extcode surcharge. Every delta is derived from the fork's own gas +model, so it is exactly 0 before EIP-8038 and tracks future parameter +changes; do not hardcode the Amsterdam numbers. """ import pytest @@ -358,6 +368,27 @@ def test_operation_diff_gas( address=Address(0x0000000000000000000000000000000000C0DEF2), # noqa: E501 ) + # The CALL-family probes make one cold account access to the callee; + # EIP-8038 reprices it by `COLD_ACCOUNT_ACCESS - 2600`. The EXTCODE + # probe runs a cold `EXTCODESIZE` plus a warm `EXTCODECOPY`, each + # carrying the extcode surcharge. Both deltas come from the fork gas + # model, so they are 0 before EIP-8038. The EXTCODECOPY metadata + # mirrors the measured access (a 0x20-byte copy into already-expanded + # memory) so only the account-access component varies across forks. + gas_costs = fork.gas_costs() + cold_account_delta = gas_costs.COLD_ACCOUNT_ACCESS - 2600 + extcode_probe_delta = ( + Op.EXTCODESIZE.with_metadata(address_warm=False).gas_cost(fork) - 2600 + ) + ( + Op.EXTCODECOPY.with_metadata( + address_warm=True, + data_size=0x20, + new_memory_size=0x120, + old_memory_size=0x120, + ).gas_cost(fork) + - 103 + ) + expect_entries_: list[dict] = [ { "indexes": {"data": [0], "gas": -1, "value": -1}, @@ -372,7 +403,9 @@ def test_operation_diff_gas( { "indexes": {"data": [2, 3, 4, 5], "gas": -1, "value": -1}, "network": [">=Cancun"], - "result": {contract_12: Account(storage={0: 2700})}, + "result": { + contract_12: Account(storage={0: 2700 + cold_account_delta}) + }, }, { "indexes": {"data": [8, 6, 7], "gas": -1, "value": -1}, @@ -382,7 +415,9 @@ def test_operation_diff_gas( { "indexes": {"data": [10], "gas": -1, "value": -1}, "network": [">=Cancun"], - "result": {contract_12: Account(storage={0: 2800})}, + "result": { + contract_12: Account(storage={0: 2800 + extcode_probe_delta}) + }, }, { "indexes": {"data": [9], "gas": -1, "value": -1}, diff --git a/tests/ported_static/stCreate2/test_create2_oo_gafter_init_code.py b/tests/ported_static/stCreate2/test_create2_oo_gafter_init_code.py index fd230fb046f..08c8f8e8445 100644 --- a/tests/ported_static/stCreate2/test_create2_oo_gafter_init_code.py +++ b/tests/ported_static/stCreate2/test_create2_oo_gafter_init_code.py @@ -3,9 +3,15 @@ Ported from: state_tests/stCreate2/Create2OOGafterInitCodeFiller.json -@manually-enhanced: Do not overwrite. tx_gas[1] is tuned to barely -succeed CREATE2 on Cancun; on Amsterdam EIP-8037 the NEW_ACCOUNT -state-gas spills, so lift the budget by Fork.oog_budget_lift. +@manually-enhanced: Do not overwrite. The init code RETURNs a 5-byte +deployed contract; g0 must run out before the deposit (account stays +NONEXISTENT) and g1 must just clear it (account created). On Cancun +the deploy gap is the 1000-gas regular code deposit and the test's +two budgets straddle it. EIP-8037/8038 move account creation into a +spilling state-gas charge AND drop OPCODE_CREATE_BASE, so the budget +that reaches the same RETURN point changes by a fork-derived amount. +The lift restores the straddle: it is exactly 0 pre-EIP-8037 and +tracks the parameters. See `_oog_lift` below for the derivation. """ import pytest @@ -113,19 +119,47 @@ def test_create2_oo_gafter_init_code( tx_data = [ Bytes(""), ] - # Lift both entries on Amsterdam so the test still exercises its - # named scenario. With only tx_gas[1] lifted, g=0 OoG'd at CREATE2 - # dispatch (NEW_ACCOUNT state-gas spill) before init code ever ran — - # the assertion still passes (`NONEXISTENT` either way) but the - # failure mode is "dispatch-time OoG" instead of "OoG after init - # code". A simple `fork.oog_budget_lift(creates_before_oog=1)` (183600) - # is *too* generous and pushes g=0 past the deploy threshold; the - # Cancun 1000-gas gap between g=0 and g=1 collapses on Amsterdam - # because once dispatch is cleared, the 5-byte init code is cheap - # enough to always complete. The value below is the middle of the - # empirically-safe range (166499, 167000) where g=0 still OoGs at - # dispatch *and* g=1 just clears the deploy threshold (~221.5k). - _oog_lift = 166_750 if fork.is_eip_enabled(8037) else 0 + # The init code RETURNs a 5-byte deployed contract, so the CREATE2 + # frame is charged a code deposit after the init RETURN. On Cancun + # that deposit is 1000 (200 * 5 regular) and the two budgets below + # straddle it: g0 reaches RETURN just under 1000 gas (deploy fails, + # account NONEXISTENT) and g1 just over (deploy succeeds). The + # 1000-gas gap between the budgets is exactly this Cancun deploy + # threshold. + # + # EIP-8037/8038 change the CREATE2 dispatch in two ways that the + # budget must absorb before the init code RETURNs: the new + # `create_state_gas()` spills into regular gas (empty reservoir), + # and `OPCODE_CREATE_BASE` drops from its Cancun value of 32000. + # Their sum is the net extra the dispatch consumes from the budget. + # The deposit step then changes too: its regular `CODE_DEPOSIT_PER_BYTE + # * 5` portion is now covered by the state-gas reservoir credited at + # dispatch, while `code_deposit_state_gas(code_size=5)` spills and + # must come from the forwarded gas instead. The lift restores the + # Cancun straddle by funding the net dispatch consumption plus the + # deposit's state spill, minus the regular deposit the budgets + # already carried in their 1000-gas gap. Every term is 0 + # pre-EIP-8037, so the original Cancun behavior is preserved. + gas_costs = fork.gas_costs() + _cancun_create_base = 32000 + _deploy_size = 5 + _oog_lift = 0 + if fork.is_eip_enabled(8037): + _oog_lift = ( + fork.oog_budget_lift( + creates_before_oog=1, deploy_code_size=_deploy_size + ) + + (gas_costs.OPCODE_CREATE_BASE - _cancun_create_base) + - gas_costs.CODE_DEPOSIT_PER_BYTE * _deploy_size + ) + # EIP-2780 reshapes the tx intrinsic for non-self non-value txs: + # ``TX_BASE`` drops to 12_000 and an explicit + # ``COLD_ACCOUNT_ACCESS`` (3_000) recipient charge is added. The + # original test was built against Cancun's flat ``TX_BASE`` of + # 21_000, so shift the budget by the intrinsic delta to keep the + # straddle landing at the same RETURN point. + intrinsic = fork.transaction_intrinsic_cost_calculator()() + _oog_lift += intrinsic - 21_000 tx_gas = [54000 + _oog_lift, 55000 + _oog_lift] tx = Transaction( diff --git a/tests/ported_static/stCreate2/test_create2_smart_init_code.py b/tests/ported_static/stCreate2/test_create2_smart_init_code.py index 09b73134629..f63fd21214e 100644 --- a/tests/ported_static/stCreate2/test_create2_smart_init_code.py +++ b/tests/ported_static/stCreate2/test_create2_smart_init_code.py @@ -4,9 +4,16 @@ Ported from: state_tests/stCreate2/create2SmartInitCodeFiller.json -@manually-enhanced: Do not overwrite. tx_gas was raised from 400 000 to -1 000 000 so the CREATE2 path can afford its EIP-8037 NEW_ACCOUNT state -gas on Amsterdam (post-state expectations are unchanged on all forks). +@manually-enhanced: Do not overwrite. The d0 call chain performs two +value-bearing CREATE2s plus a SELFDESTRUCT to a non-alive beneficiary +and two fresh SSTORE-sets before it finishes; with an empty state-gas +reservoir every one of those state-gas charges spills into regular gas +on EIP-8037, overrunning the original 400 000 budget. Lift the budget +by exactly that spilled state gas via `fork.oog_budget_lift` (three +`create_state_gas()` charges -- two CREATE2 dispatches and the +SELFDESTRUCT account creation -- plus two fresh SSTORE-set state +costs), which is 0 pre-EIP-8037. Post-state expectations are unchanged +on all forks. """ import pytest @@ -173,11 +180,14 @@ def test_create2_smart_init_code( Hash(contract_0, left_padding=True), Hash(contract_1, left_padding=True), ] - # EIP-8037 NEW_ACCOUNT + per-byte state-gas spill into the regular - # budget on Amsterdam; pre-EIP-8037 forks keep the original 400 000. - outer_tx_gas = 400_000 - if fork.is_eip_enabled(8037): - outer_tx_gas = 1_000_000 + # The d0 chain spills three `create_state_gas()` charges (two + # CREATE2 dispatches and the SELFDESTRUCT to a non-alive + # beneficiary) and two fresh SSTORE-set state costs into regular + # gas when the reservoir is empty. Lift the original budget by + # exactly that spilled state gas; 0 pre-EIP-8037. + outer_tx_gas = 400_000 + fork.oog_budget_lift( + creates_before_oog=3, sstores_before_oog=2 + ) tx_gas = [outer_tx_gas] tx = Transaction( diff --git a/tests/ported_static/stCreate2/test_create2check_fields_in_initcode.py b/tests/ported_static/stCreate2/test_create2check_fields_in_initcode.py index da4c2cabd35..8ee471f590e 100644 --- a/tests/ported_static/stCreate2/test_create2check_fields_in_initcode.py +++ b/tests/ported_static/stCreate2/test_create2check_fields_in_initcode.py @@ -3,6 +3,8 @@ Ported from: state_tests/stCreate2/create2checkFieldsInInitcodeFiller.json +@manually-enhanced: Do not overwrite. The env `gas_limit` is omitted so +the framework default supplies ample gas for EIP-8037 state accounting. """ import pytest @@ -115,7 +117,6 @@ def test_create2check_fields_in_initcode( timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=1000000, ) pre[sender] = Account(balance=0x56BC75E2D63100000) diff --git a/tests/ported_static/stCreateTest/test_create_address_warm_after_fail.py b/tests/ported_static/stCreateTest/test_create_address_warm_after_fail.py index 80c2ba100bc..358473d4fb0 100644 --- a/tests/ported_static/stCreateTest/test_create_address_warm_after_fail.py +++ b/tests/ported_static/stCreateTest/test_create_address_warm_after_fail.py @@ -9,6 +9,14 @@ Ported from: state_tests/stCreateTest/CreateAddressWarmAfterFailFiller.yml + +@manually-enhanced: Do not overwrite. The post-state records the +measured cost of accessing the create address after a failed CREATE, +which is a cold account access. EIP-8038 reprices a cold account +access from 2 600 to 3 000, so each such measurement gains 400 at +Amsterdam. Derive that delta from the fork's gas model so it is +exactly 0 pre-EIP-8037 and tracks parameter changes; do not hardcode +the Amsterdam value. """ import pytest @@ -381,6 +389,11 @@ def test_create_address_warm_after_fail( address=Address(0x00000000000000000000000000000000000C0DEC), # noqa: E501 ) + # The create address access after a failed CREATE is cold here; + # EIP-8038 reprices a cold account access from 2 600 to 3 000. + # Derive the delta from the fork so it is 0 pre-EIP-8037. + cold_account_delta = fork.gas_costs().COLD_ACCOUNT_ACCESS - 2600 + expect_entries_: list[dict] = [ { "indexes": {"data": [0, 2, 11, 4], "gas": -1, "value": [0]}, @@ -396,7 +409,7 @@ def test_create_address_warm_after_fail( 5: 1, 12: 328, 13: 316, - 14: 2828, + 14: 2828 + cold_account_delta, 15: 316, }, nonce=1, @@ -447,7 +460,7 @@ def test_create_address_warm_after_fail( 5: 1, 12: 328, 13: 316, - 14: 2828, + 14: 2828 + cold_account_delta, 15: 316, }, nonce=1, @@ -498,7 +511,7 @@ def test_create_address_warm_after_fail( 5: 1, 12: 328, 13: 316, - 14: 2828, + 14: 2828 + cold_account_delta, 15: 316, }, nonce=1, @@ -549,7 +562,7 @@ def test_create_address_warm_after_fail( 5: 1, 12: 328, 13: 316, - 14: 2828, + 14: 2828 + cold_account_delta, 15: 316, }, nonce=1, @@ -600,7 +613,7 @@ def test_create_address_warm_after_fail( 5: 1, 12: 328, 13: 316, - 14: 2828, + 14: 2828 + cold_account_delta, 15: 316, }, nonce=1, @@ -649,9 +662,9 @@ def test_create_address_warm_after_fail( 3: 1, 4: 1, 5: 1, - 12: 2828, + 12: 2828 + cold_account_delta, 13: 316, - 14: 2828, + 14: 2828 + cold_account_delta, 15: 316, }, nonce=0, @@ -703,9 +716,9 @@ def test_create_address_warm_after_fail( 3: 1, 4: 1, 5: 1, - 12: 2828, + 12: 2828 + cold_account_delta, 13: 316, - 14: 2828, + 14: 2828 + cold_account_delta, 15: 316, }, nonce=0, @@ -753,7 +766,7 @@ def test_create_address_warm_after_fail( 5: 1, 12: 328, 13: 316, - 14: 2828, + 14: 2828 + cold_account_delta, 15: 316, }, nonce=1, @@ -804,7 +817,7 @@ def test_create_address_warm_after_fail( 5: 1, 12: 328, 13: 316, - 14: 2828, + 14: 2828 + cold_account_delta, 15: 316, }, nonce=1, diff --git a/tests/ported_static/stCreateTest/test_create_oo_gafter_init_code.py b/tests/ported_static/stCreateTest/test_create_oo_gafter_init_code.py index 76b1fd09653..8fcff4f2bbd 100644 --- a/tests/ported_static/stCreateTest/test_create_oo_gafter_init_code.py +++ b/tests/ported_static/stCreateTest/test_create_oo_gafter_init_code.py @@ -3,9 +3,15 @@ Ported from: state_tests/stCreateTest/CreateOOGafterInitCodeFiller.json -@manually-enhanced: Do not overwrite. tx_gas[1] is tuned to barely -succeed CREATE on Cancun; on Amsterdam EIP-8037 the NEW_ACCOUNT -state-gas spills, so lift the budget by Fork.oog_budget_lift. +@manually-enhanced: Do not overwrite. The init code RETURNs a 5-byte +deployed contract; g0 must run out before the deposit (account stays +NONEXISTENT) and g1 must just clear it (account created). On Cancun +the deploy gap is the 1000-gas regular code deposit and the test's +two budgets straddle it. EIP-8037/8038 move account creation into a +spilling state-gas charge AND drop OPCODE_CREATE_BASE, so the budget +that reaches the same RETURN point changes by a fork-derived amount. +The lift restores the straddle: it is exactly 0 pre-EIP-8037 and +tracks the parameters. See `_oog_lift` below for the derivation. """ import pytest @@ -109,19 +115,47 @@ def test_create_oo_gafter_init_code( tx_data = [ Bytes(""), ] - # Lift both entries on Amsterdam so the test still exercises its - # named scenario. With only tx_gas[1] lifted, g=0 OoG'd at CREATE - # dispatch (NEW_ACCOUNT state-gas spill) before init code ever ran — - # the assertion still passes (`NONEXISTENT` either way) but the - # failure mode is "dispatch-time OoG" instead of "OoG after init - # code". A simple `fork.oog_budget_lift(creates_before_oog=1)` (183600) - # is *too* generous and pushes g=0 past the deploy threshold; the - # Cancun 1000-gas gap between g=0 and g=1 collapses on Amsterdam - # because once dispatch is cleared, the 5-byte init code is cheap - # enough to always complete. The value below is the middle of the - # empirically-safe range (166499, 167000) where g=0 still OoGs at - # dispatch *and* g=1 just clears the deploy threshold (~221.5k). - _oog_lift = 166_750 if fork.is_eip_enabled(8037) else 0 + # The init code RETURNs a 5-byte deployed contract, so the CREATE + # frame is charged a code deposit after the init RETURN. On Cancun + # that deposit is 1000 (CODE_DEPOSIT_PER_BYTE * 5 regular) and the + # two budgets below straddle it: g0 reaches RETURN just under the + # threshold (deploy fails, account NONEXISTENT) and g1 just over + # (deploy succeeds). The 1000-gas gap between the budgets is exactly + # this Cancun deploy threshold. + # + # EIP-8037/8038 change the CREATE dispatch in two ways that the + # budget must absorb before the init code RETURNs: the new + # `create_state_gas()` spills into regular gas (empty reservoir), + # and `OPCODE_CREATE_BASE` drops from its Cancun value of 32000. + # Their sum is the net extra the dispatch consumes from the budget. + # The deposit step then changes too: its regular `CODE_DEPOSIT_PER_BYTE + # * 5` portion is now covered by the state-gas reservoir credited at + # dispatch, while `code_deposit_state_gas(code_size=5)` spills and + # must come from the forwarded gas instead. The lift restores the + # Cancun straddle by funding the net dispatch consumption plus the + # deposit's state spill, minus the regular deposit the budgets + # already carried in their 1000-gas gap. Every term is 0 + # pre-EIP-8037, so the original Cancun behavior is preserved. + gas_costs = fork.gas_costs() + _cancun_create_base = 32000 + _deploy_size = 5 + _oog_lift = 0 + if fork.is_eip_enabled(8037): + _oog_lift = ( + fork.oog_budget_lift( + creates_before_oog=1, deploy_code_size=_deploy_size + ) + + (gas_costs.OPCODE_CREATE_BASE - _cancun_create_base) + - gas_costs.CODE_DEPOSIT_PER_BYTE * _deploy_size + ) + # EIP-2780 reshapes the tx intrinsic for non-self non-value txs: + # ``TX_BASE`` drops to 12_000 and an explicit + # ``COLD_ACCOUNT_ACCESS`` (3_000) recipient charge is added. The + # original test was built against Cancun's flat ``TX_BASE`` of + # 21_000, so shift the budget by the intrinsic delta to keep the + # straddle landing at the same RETURN point. + intrinsic = fork.transaction_intrinsic_cost_calculator()() + _oog_lift += intrinsic - 21_000 tx_gas = [54000 + _oog_lift, 55000 + _oog_lift] tx = Transaction( diff --git a/tests/ported_static/stEIP1153_transientStorage/test_14_revert_after_nested_staticcall.py b/tests/ported_static/stEIP1153_transientStorage/test_14_revert_after_nested_staticcall.py index 06ddaf015a0..739a986e972 100644 --- a/tests/ported_static/stEIP1153_transientStorage/test_14_revert_after_nested_staticcall.py +++ b/tests/ported_static/stEIP1153_transientStorage/test_14_revert_after_nested_staticcall.py @@ -3,6 +3,16 @@ Ported from: state_tests/Cancun/stEIP1153_transientStorage/14_revertAfterNestedStaticcallFiller.yml + +@manually-enhanced: Do not overwrite. The caller writes four fresh +storage slots and asserts the resulting values (slot 1's pre-marker +must be overwritten). EIP-8037/8038 spill each fresh SSTORE's +state-gas charge back into regular gas (the reservoir is empty), +pushing total consumption past the original 400 000 transaction +budget; the final SSTORE then OOGs and reverts the whole call, +leaving slot 1 at its marker. Bump the gas limit by the summed +fork-derived SSTORE increases so the success path stays funded; the +bump is exactly 0 before EIP-8037. """ import pytest @@ -15,6 +25,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +42,30 @@ def test_14_revert_after_nested_staticcall( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Transient storage can't be manipulated from nested staticcall.""" + + # EIP-8037/8038 spill each fresh SSTORE's state-gas charge back into + # regular gas. The caller writes three fresh slots (0, 2, 3: each a + # cold zero -> nonzero set) and clears slot 1's cold marker; sum the + # per-slot increases so the original budget stays sufficient. Each + # term is exactly 0 before EIP-8037. + def _sstore_delta(cancun_cost: int, **metadata: int) -> int: + op = Op.SSTORE.with_metadata(**metadata) + return op.gas_cost(fork) - cancun_cost + + cold_set_delta = _sstore_delta( + 22100, key_warm=False, original_value=0, current_value=0, new_value=10 + ) + cold_clear_delta = _sstore_delta( + 5000, + key_warm=False, + original_value=65535, + current_value=65535, + new_value=0, + ) + gas_limit_bump = 3 * cold_set_delta + cold_clear_delta coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0x3635C9ADC5DEA00000) @@ -136,7 +169,7 @@ def test_14_revert_after_nested_staticcall( sender=sender, to=target, data=Bytes("f5f40590"), - gas_limit=400000, + gas_limit=400000 + gas_limit_bump, max_fee_per_gas=2000, max_priority_fee_per_gas=0, access_list=[], diff --git a/tests/ported_static/stEIP150Specific/test_call_and_callcode_consume_more_gas_then_transaction_has.py b/tests/ported_static/stEIP150Specific/test_call_and_callcode_consume_more_gas_then_transaction_has.py index aa6ecf33493..1e588e4489e 100644 --- a/tests/ported_static/stEIP150Specific/test_call_and_callcode_consume_more_gas_then_transaction_has.py +++ b/tests/ported_static/stEIP150Specific/test_call_and_callcode_consume_more_gas_then_transaction_has.py @@ -3,6 +3,16 @@ Ported from: state_tests/stEIP150Specific/CallAndCallcodeConsumeMoreGasThenTransactionHasFiller.json + +@manually-enhanced: Do not overwrite. The post-state asserts +`storage[8] = 0x8D5B6` captured by `Op.GAS`, which depends on the exact +post-intrinsic execution budget. The original hardcoded `gas_limit` of +600_000 was built against Cancun's `TX_BASE` of 21_000; EIP-2780 lowers +the intrinsic for non-self non-value txs, so `gas_limit` is derived as +`600_000 + (intrinsic - 21_000)` from `transaction_intrinsic_cost_calculator` +to shift by the fork intrinsic delta and keep the Op.GAS assertion correct. +The `- 21_000` is the pre-EIP-2780 baseline intrinsic, so the adjustment +is exactly 0 pre-repricing. Do not hardcode the literal gas_limit. """ import pytest @@ -12,6 +22,7 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) @@ -31,6 +42,7 @@ def test_call_and_callcode_consume_more_gas_then_transaction_has( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_call_and_callcode_consume_more_gas_then_transaction_has.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) @@ -83,11 +95,19 @@ def test_call_and_callcode_consume_more_gas_then_transaction_has( nonce=0, ) + # The original test was built against Cancun's ``TX_BASE`` of + # 21_000. EIP-2780 lowers the intrinsic for non-self non-value + # txs, so shift ``gas_limit`` by the intrinsic delta to preserve + # the post-intrinsic execution budget the Op.GAS storage + # assertions depend on. + intrinsic = fork.transaction_intrinsic_cost_calculator()() + gas_limit = 600_000 + (intrinsic - 21_000) + tx = Transaction( sender=sender, to=target, data=Bytes(""), - gas_limit=600000, + gas_limit=gas_limit, ) post = {target: Account(storage={0: 18, 8: 0x8D5B6, 9: 1, 10: 1})} diff --git a/tests/ported_static/stEIP150Specific/test_call_goes_oog_on_second_level.py b/tests/ported_static/stEIP150Specific/test_call_goes_oog_on_second_level.py index ce3ec8f7f19..954c5e29ae7 100644 --- a/tests/ported_static/stEIP150Specific/test_call_goes_oog_on_second_level.py +++ b/tests/ported_static/stEIP150Specific/test_call_goes_oog_on_second_level.py @@ -3,6 +3,14 @@ Ported from: state_tests/stEIP150Specific/CallGoesOOGOnSecondLevelFiller.json + +@manually-enhanced: Do not overwrite. The `gas_limit` is derived from +the fork intrinsic calculator instead of the original hardcoded value. +The test fixes the post-intrinsic budget that the nested Op.GAS storage +assertions (8: 0x927BE, 8: 0x213FB6) depend on, so it shifts the base +2_200_000 budget by the intrinsic delta versus the pre-EIP-2780 Cancun +baseline of 21_000 (`intrinsic - 21_000`). This stays correct across +the EIP-2780 intrinsic decomposition. Do not hardcode the gas_limit. """ import pytest @@ -12,6 +20,7 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) @@ -29,6 +38,7 @@ def test_call_goes_oog_on_second_level( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_call_goes_oog_on_second_level.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) @@ -93,11 +103,19 @@ def test_call_goes_oog_on_second_level( nonce=0, ) + # The original test was built against Cancun's ``TX_BASE`` of + # 21_000. EIP-2780 lowers the intrinsic for non-self non-value + # txs, so shift ``gas_limit`` by the intrinsic delta to preserve + # the post-intrinsic execution budget the Op.GAS storage + # assertions depend on. + intrinsic = fork.transaction_intrinsic_cost_calculator()() + gas_limit = 2_200_000 + (intrinsic - 21_000) + tx = Transaction( sender=sender, to=target, data=Bytes(""), - gas_limit=2200000, + gas_limit=gas_limit, ) post = { diff --git a/tests/ported_static/stEIP150Specific/test_suicide_to_existing_contract.py b/tests/ported_static/stEIP150Specific/test_suicide_to_existing_contract.py index f8cea302098..15bacfb9c84 100644 --- a/tests/ported_static/stEIP150Specific/test_suicide_to_existing_contract.py +++ b/tests/ported_static/stEIP150Specific/test_suicide_to_existing_contract.py @@ -3,6 +3,13 @@ Ported from: state_tests/stEIP150Specific/SuicideToExistingContractFiller.json + +@manually-enhanced: Do not overwrite. The measured slot captures the +regular gas of a value-0 CALL to a cold contract that then +SELFDESTRUCTs back to its (warm, alive) caller. EIP-8038 reprices the +cold account access of that CALL; the beneficiary is warm so the +SELFDESTRUCT is unchanged. The delta is therefore the fork's +`COLD_ACCOUNT_ACCESS - 2600`, exactly 0 before EIP-8038. """ import pytest @@ -15,6 +22,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +37,11 @@ def test_suicide_to_existing_contract( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_suicide_to_existing_contract.""" + # EIP-8038 cold account access reprice; 0 before EIP-8038. + cold_account_delta = fork.gas_costs().COLD_ACCOUNT_ACCESS - 2600 coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xE8D4A51000) @@ -90,7 +101,7 @@ def test_suicide_to_existing_contract( balance=0, nonce=0, ), - target: Account(storage={1: 7637}), + target: Account(storage={1: 7637 + cold_account_delta}), } state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP150Specific/test_suicide_to_not_existing_contract.py b/tests/ported_static/stEIP150Specific/test_suicide_to_not_existing_contract.py index 5a3ff77c96f..6dfee984b84 100644 --- a/tests/ported_static/stEIP150Specific/test_suicide_to_not_existing_contract.py +++ b/tests/ported_static/stEIP150Specific/test_suicide_to_not_existing_contract.py @@ -3,6 +3,14 @@ Ported from: state_tests/stEIP150Specific/SuicideToNotExistingContractFiller.json + +@manually-enhanced: Do not overwrite. The measured slot captures the +regular gas of a value-0 CALL to a cold contract that then +SELFDESTRUCTs (with a zero balance) to a cold, non-alive beneficiary. +EIP-8038 reprices both the CALL's cold account access and the +SELFDESTRUCT beneficiary's cold access; no value is sent so there is +no new-account write. The delta is therefore twice the fork's +`COLD_ACCOUNT_ACCESS - 2600`, exactly 0 before EIP-8038. """ import pytest @@ -15,6 +23,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +38,13 @@ def test_suicide_to_not_existing_contract( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_suicide_to_not_existing_contract.""" + # EIP-8038 cold account access reprice; 0 before EIP-8038. Charged + # twice: once for the CALL target, once for the cold SELFDESTRUCT + # beneficiary. + cold_account_delta = fork.gas_costs().COLD_ACCOUNT_ACCESS - 2600 coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xE8D4A51000) @@ -88,7 +102,7 @@ def test_suicide_to_not_existing_contract( balance=0, nonce=0, ), - target: Account(storage={1: 10237}), + target: Account(storage={1: 10237 + 2 * cold_account_delta}), } state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP150singleCodeGasPrices/test_eip2929.py b/tests/ported_static/stEIP150singleCodeGasPrices/test_eip2929.py index c62a6e1859a..8cc16c0dc46 100644 --- a/tests/ported_static/stEIP150singleCodeGasPrices/test_eip2929.py +++ b/tests/ported_static/stEIP150singleCodeGasPrices/test_eip2929.py @@ -3,6 +3,25 @@ Ported from: state_tests/stEIP150singleCodeGasPrices/eip2929Filler.yml + +@manually-enhanced: Do not overwrite. Each parametrization runs three +operations (`oper1, oper2, oper3` from the calldata) on the same +measurement contract and stores each one's `Op.GAS` cost in slots 0, +1, 2. EIP-8038 reprices state access, so the cost of every measured +operation shifts by the (Amsterdam - Cancun) repricing of whatever +cold/warm account or storage access it performs. The access pattern, +and hence the delta, depends on what the two preceding operations +already warmed, so the deltas are computed by a small simulator +(`_slot_deltas`) that walks the operation triple while tracking the +warm state of the contract-0 account and storage slot 0x100. Each +component is built only from the fork's own gas model +(`COLD_ACCOUNT_ACCESS`, `COLD_STORAGE_ACCESS`, the EIP-8038 extra +`WARM_ACCESS` for code reads, and `Op.SSTORE` metadata costs), so +every delta is exactly 0 pre-EIP-8037 and tracks future parameter +changes. The `far*` operations call contract-1 (which does +`BALANCE(contract-0)`) or contract-2 (which does `SLOAD(0x100)`), so +they contribute the inner access's delta. Do not hardcode the +Amsterdam numbers. """ import pytest @@ -836,113 +855,201 @@ def test_eip2929( nonce=0, ) + # EIP-8038 access-repricing component deltas (each 0 pre-EIP-8037). + gas_costs = fork.gas_costs() + eip_active = fork.is_eip_enabled(8037) + cold_account_delta = gas_costs.COLD_ACCOUNT_ACCESS - 2600 + cold_storage_delta = gas_costs.COLD_STORAGE_ACCESS - 2100 + # EIP-8038 charges an extra warm access for an EXTCODE* code read, + # on every access (cold adds it on top of the cold account cost, + # warm pays it as a second warm access). + extra_code_read = gas_costs.WARM_ACCESS if eip_active else 0 + cold_code_read_delta = cold_account_delta + extra_code_read + warm_code_read_delta = extra_code_read + + def _sstore_delta(cancun_cost: int, **metadata: int) -> int: + return Op.SSTORE.with_metadata(**metadata).gas_cost(fork) - cancun_cost + + # SSTORE 24743 -> 5 (existing nonzero slot changed to a new nonzero + # value): cold first write vs warm subsequent write. + cold_sstore_write_delta = _sstore_delta( + 5000, key_warm=False, original_value=1, current_value=1, new_value=2 + ) + warm_sstore_write_delta = _sstore_delta( + 2900, key_warm=True, original_value=1, current_value=1, new_value=2 + ) + + # Operation opcodes (from the calldata oper words). + op_nop, op_sload, op_sstore = 0x0, 0x1, 0x2 + op_balance, op_extsize, op_extcopy, op_exthash = 0xB, 0xC, 0xD, 0xE + op_call0, op_callcode0, op_deleg0, op_static0 = 0x15, 0x16, 0x17, 0x18 + op_call1, op_callcode2, op_deleg2 = 0x1F, 0x20, 0x21 + account_c0_ops = { + op_balance, + op_exthash, + op_call0, + op_callcode0, + op_deleg0, + op_static0, + } + code_read_ops = {op_extsize, op_extcopy} + inner_sload_ops = {op_sload, op_callcode2, op_deleg2} + # oper triples per data index, matching tx_data below. + oper_triples = { + 0: (op_nop, op_nop, op_nop), + 1: (op_sload, op_sload, op_sload), + 2: (op_sstore, op_sstore, op_sstore), + 3: (op_balance, op_balance, op_balance), + 4: (op_extsize, op_extsize, op_extsize), + 5: (op_extcopy, op_extcopy, op_extcopy), + 6: (op_exthash, op_exthash, op_exthash), + 7: (op_call0, op_call0, op_call0), + 8: (op_callcode0, op_callcode0, op_callcode0), + 9: (op_deleg0, op_deleg0, op_deleg0), + 10: (op_static0, op_static0, op_static0), + 11: (op_call1, op_call1, op_call1), + 12: (op_callcode2, op_callcode2, op_callcode2), + 13: (op_deleg2, op_deleg2, op_deleg2), + 14: (op_sload, op_sstore, op_sload), + 15: (op_sload, op_callcode2, op_deleg2), + 16: (op_sload, op_sstore, op_deleg2), + 17: (op_callcode2, op_sload, op_deleg2), + 18: (op_deleg2, op_sload, op_sstore), + 19: (op_balance, op_extsize, op_exthash), + 20: (op_balance, op_exthash, op_extsize), + 21: (op_extsize, op_balance, op_exthash), + 22: (op_extsize, op_exthash, op_balance), + 23: (op_exthash, op_extsize, op_balance), + 24: (op_exthash, op_balance, op_extsize), + 25: (op_call0, op_callcode0, op_call0), + 26: (op_callcode0, op_callcode0, op_call0), + 27: (op_deleg0, op_static0, op_deleg0), + 28: (op_deleg0, op_static0, op_static0), + 29: (op_balance, op_call0, op_callcode0), + 30: (op_extsize, op_call0, op_callcode0), + 31: (op_exthash, op_call0, op_callcode0), + 32: (op_balance, op_callcode0, op_call0), + 33: (op_extsize, op_callcode0, op_call0), + 34: (op_exthash, op_callcode0, op_call0), + 35: (op_balance, op_extsize, op_call1), + 36: (op_balance, op_call1, op_exthash), + 37: (op_call1, op_exthash, op_balance), + } + + def _slot_deltas(index: int) -> tuple[int, int, int]: + """ + Return the (slot0, slot1, slot2) EIP-8038 deltas for an index. + + Walk the operation triple, tracking the warm state of the + contract-0 account and storage slot 0x100 (pre-value 24743), + and accumulate the (Amsterdam - Cancun) repricing each measured + operation incurs. The `far*` calls reach a pre-warmed contract + whose body performs the inner access, so they contribute that + inner access's delta. + """ + c0_warm = False + slot_warm = False + slot_value = 24743 + out = [] + for op in oper_triples[index]: + delta = 0 + if op in inner_sload_ops: + # Direct SLOAD(0x100) or a far call whose body SLOADs it. + if not slot_warm: + delta = cold_storage_delta + slot_warm = True + elif op == op_sstore: + if slot_value != 5: + delta = ( + cold_sstore_write_delta + if not slot_warm + else warm_sstore_write_delta + ) + slot_value = 5 + slot_warm = True + elif op in code_read_ops: + delta = ( + cold_code_read_delta + if not c0_warm + else warm_code_read_delta + ) + c0_warm = True + elif op in account_c0_ops or op == op_call1: + # Account access to contract-0 (CALL1's body BALANCEs it). + if not c0_warm: + delta = cold_account_delta + c0_warm = True + out.append(delta) + return out[0], out[1], out[2] + + def _expect(index: int, base: tuple[int, int, int]) -> dict: + """Storage dict with each slot bumped by its EIP-8038 delta.""" + deltas = _slot_deltas(index) + return {i: base[i] + deltas[i] for i in range(3)} + + # Cancun-era base value of each measured slot, per data index. The + # per-index EIP-8038 delta is added by `_expect`, so each entry is a + # single data index (grouped entries with identical Cancun bases can + # still need different deltas once the access pattern differs). + base_values: dict[int, tuple[int, int, int]] = { + 1: (2090, 90, 90), + 2: (4991, 91, 91), + 3: (2590, 90, 90), + 4: (2590, 90, 90), + 5: (2597, 97, 97), + 6: (2590, 90, 90), + 7: (2608, 108, 108), + 8: (2608, 108, 108), + 9: (2605, 105, 105), + 10: (2605, 105, 105), + 11: (2711, 211, 211), + 12: (2211, 211, 211), + 13: (2208, 208, 208), + 14: (2090, 2891, 90), + 15: (2090, 211, 208), + 16: (2090, 2891, 208), + 17: (2211, 90, 208), + 18: (2208, 90, 2891), + 19: (2590, 90, 90), + 20: (2590, 90, 90), + 21: (2590, 90, 90), + 22: (2590, 90, 90), + 23: (2590, 90, 90), + 24: (2590, 90, 90), + 25: (2608, 108, 108), + 26: (2608, 108, 108), + 27: (2605, 105, 105), + 28: (2605, 105, 105), + 29: (2590, 108, 108), + 30: (2590, 108, 108), + 31: (2590, 108, 108), + 32: (2590, 108, 108), + 33: (2590, 108, 108), + 34: (2590, 108, 108), + 35: (2590, 90, 211), + 36: (2590, 211, 90), + 37: (2711, 90, 90), + } + expect_entries_: list[dict] = [ { "indexes": {"data": [0], "gas": -1, "value": -1}, "network": [">=Cancun"], "result": {contract_3: Account(storage={0: 0})}, }, - { - "indexes": {"data": [1], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {contract_3: Account(storage={0: 2090, 1: 90, 2: 90})}, - }, - { - "indexes": {"data": [2], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {contract_3: Account(storage={0: 4991, 1: 91, 2: 91})}, - }, - { - "indexes": {"data": [14], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {contract_3: Account(storage={0: 2090, 1: 2891, 2: 90})}, - }, - { - "indexes": { - "data": [3, 4, 6, 19, 20, 21, 22, 23, 24], - "gas": -1, - "value": -1, - }, - "network": [">=Cancun"], - "result": {contract_3: Account(storage={0: 2590, 1: 90, 2: 90})}, - }, - { - "indexes": {"data": [5], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {contract_3: Account(storage={0: 2597, 1: 97, 2: 97})}, - }, - { - "indexes": {"data": [8, 25, 26, 7], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {contract_3: Account(storage={0: 2608, 1: 108, 2: 108})}, - }, - { - "indexes": {"data": [9, 10, 27, 28], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {contract_3: Account(storage={0: 2605, 1: 105, 2: 105})}, - }, - { - "indexes": { - "data": [32, 33, 34, 29, 30, 31], - "gas": -1, - "value": -1, - }, - "network": [">=Cancun"], - "result": {contract_3: Account(storage={0: 2590, 1: 108, 2: 108})}, - }, - { - "indexes": {"data": [11], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {contract_3: Account(storage={0: 2711, 1: 211, 2: 211})}, - }, - { - "indexes": {"data": [35], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {contract_3: Account(storage={0: 2590, 1: 90, 2: 211})}, - }, - { - "indexes": {"data": [36], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {contract_3: Account(storage={0: 2590, 1: 211, 2: 90})}, - }, - { - "indexes": {"data": [37], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {contract_3: Account(storage={0: 2711, 1: 90, 2: 90})}, - }, - { - "indexes": {"data": [12], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {contract_3: Account(storage={0: 2211, 1: 211, 2: 211})}, - }, - { - "indexes": {"data": [13], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {contract_3: Account(storage={0: 2208, 1: 208, 2: 208})}, - }, - { - "indexes": {"data": [15], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {contract_3: Account(storage={0: 2090, 1: 211, 2: 208})}, - }, - { - "indexes": {"data": [16], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": { - contract_3: Account(storage={0: 2090, 1: 2891, 2: 208}) - }, - }, - { - "indexes": {"data": [17], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {contract_3: Account(storage={0: 2211, 1: 90, 2: 208})}, - }, - { - "indexes": {"data": [18], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {contract_3: Account(storage={0: 2208, 1: 90, 2: 2891})}, - }, ] + for index in sorted(base_values): + expect_entries_.append( + { + "indexes": {"data": [index], "gas": -1, "value": -1}, + "network": [">=Cancun"], + "result": { + contract_3: Account( + storage=_expect(index, base_values[index]) + ) + }, + } + ) post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork) diff --git a/tests/ported_static/stEIP150singleCodeGasPrices/test_eip2929_minus_ff.py b/tests/ported_static/stEIP150singleCodeGasPrices/test_eip2929_minus_ff.py index 0e9ecf78d46..fbba23d97e0 100644 --- a/tests/ported_static/stEIP150singleCodeGasPrices/test_eip2929_minus_ff.py +++ b/tests/ported_static/stEIP150singleCodeGasPrices/test_eip2929_minus_ff.py @@ -3,6 +3,15 @@ Ported from: state_tests/stEIP150singleCodeGasPrices/eip2929-ffFiller.yml + +@manually-enhanced: Do not overwrite. The first expect-entry (the +`simple`/NOP case) measures a `CALL` into a contract that +`SELFDESTRUCT`s with an as-yet-untouched (cold) beneficiary. EIP-8038 +reprices the cold account access (`COLD_ACCOUNT_ACCESS`, 2600 -> 3000), +so that cost shifts by `COLD_ACCOUNT_ACCESS - 2600`, derived from the +fork's own constant so it is exactly 0 pre-EIP-8038. The other entry +pre-warms the beneficiary, so its cost is unchanged. Do not hardcode +the Amsterdam number. """ import pytest @@ -99,6 +108,8 @@ def test_eip2929_minus_ff( v: int, ) -> None: """Ori Pomerantz qbzzt1@gmail.""" + # EIP-8038 cold account repricing (2600 -> 3000); 0 on earlier forks. + cold_account_delta = fork.gas_costs().COLD_ACCOUNT_ACCESS - 2600 coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) contract_0 = Address(0x000000000000000000000000000000000000DE57) contract_1 = Address(0x000000000000000000000000000000000000CA11) @@ -315,7 +326,11 @@ def test_eip2929_minus_ff( { "indexes": {"data": [0], "gas": -1, "value": -1}, "network": [">=Cancun"], - "result": {contract_2: Account(storage={0: 7726, 1: 105})}, + "result": { + contract_2: Account( + storage={0: 7726 + cold_account_delta, 1: 105} + ) + }, }, { "indexes": { diff --git a/tests/ported_static/stEIP150singleCodeGasPrices/test_gas_cost.py b/tests/ported_static/stEIP150singleCodeGasPrices/test_gas_cost.py index 98b4497269a..d539c14f735 100644 --- a/tests/ported_static/stEIP150singleCodeGasPrices/test_gas_cost.py +++ b/tests/ported_static/stEIP150singleCodeGasPrices/test_gas_cost.py @@ -3,6 +3,17 @@ Ported from: state_tests/stEIP150singleCodeGasPrices/gasCostFiller.yml + +@manually-enhanced: Do not overwrite. This crafts a one-opcode +contract, CALLs it, and stores the opcode's measured gas via `Op.GAS`. +EIP-8038 reprices state access, so four opcodes shift: `BALANCE` and +`SELFDESTRUCT` (cold account, `COLD_ACCOUNT_ACCESS` 2600 -> 3000, +400), +`EXTCODESIZE` (cold account plus the extra `WARM_ACCESS` charged for +the opcode's second read of the code, +500), and `SSTORE` to a cold +fresh slot (`COLD_STORAGE_ACCESS` 2100 -> 3000, +900). `BALANCE` and +`EXTCODESIZE` share a Cancun baseline but need different deltas, so +their expect-entries are split. Every delta is derived from the fork's +own constants and is exactly 0 pre-EIP-8038; do not hardcode it. """ import pytest @@ -714,6 +725,14 @@ def test_gas_cost( v: int, ) -> None: """Ori Pomerantz qbzzt1@gmail.""" + gas_costs = fork.gas_costs() + # EIP-8038 access repricing; each term is 0 on earlier forks. + cold_account_delta = gas_costs.COLD_ACCOUNT_ACCESS - 2600 + cold_storage_delta = gas_costs.COLD_STORAGE_ACCESS - 2100 + # EXTCODESIZE also gains an extra warm access for its code read. + code_read_delta = cold_account_delta + ( + gas_costs.WARM_ACCESS if fork.is_eip_enabled(8037) else 0 + ) coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = EOA( key=0x40AC0FC28C27E961EE46EC43355A094DE205856EDBD4654CF2577C2608D4EC1E @@ -1070,10 +1089,15 @@ def test_gas_cost( }, }, { + # SSTORE to a cold fresh slot: cold storage repricing. "indexes": {"data": [39], "gas": -1, "value": -1}, "network": [">=Cancun"], "result": { - addr: Account(storage=_storage_with_any({0: 700}, [1])) + addr: Account( + storage=_storage_with_any( + {0: 700 + cold_storage_delta}, [1] + ) + ) }, }, { @@ -1084,17 +1108,38 @@ def test_gas_cost( }, }, { + # SELFDESTRUCT to a cold (zero) beneficiary: cold account. "indexes": {"data": [45], "gas": -1, "value": -1}, "network": [">=Cancun"], "result": { - addr: Account(storage=_storage_with_any({0: 2000}, [1])) + addr: Account( + storage=_storage_with_any( + {0: 2000 + cold_account_delta}, [1] + ) + ) + }, + }, + { + # BALANCE on a cold (zero) address: cold account. + "indexes": {"data": [23], "gas": -1, "value": -1}, + "network": [">=Cancun"], + "result": { + addr: Account( + storage=_storage_with_any( + {0: 1300 + cold_account_delta}, [1] + ) + ) }, }, { - "indexes": {"data": [31, 23], "gas": -1, "value": -1}, + # EXTCODESIZE on a cold (zero) address: cold account plus the + # extra warm access for the opcode's code read. + "indexes": {"data": [31], "gas": -1, "value": -1}, "network": [">=Cancun"], "result": { - addr: Account(storage=_storage_with_any({0: 1300}, [1])) + addr: Account( + storage=_storage_with_any({0: 1300 + code_read_delta}, [1]) + ) }, }, ] diff --git a/tests/ported_static/stEIP150singleCodeGasPrices/test_gas_cost_berlin.py b/tests/ported_static/stEIP150singleCodeGasPrices/test_gas_cost_berlin.py index c122aab7d30..5a92e62ca28 100644 --- a/tests/ported_static/stEIP150singleCodeGasPrices/test_gas_cost_berlin.py +++ b/tests/ported_static/stEIP150singleCodeGasPrices/test_gas_cost_berlin.py @@ -3,6 +3,18 @@ Ported from: state_tests/stEIP150singleCodeGasPrices/gasCostBerlinFiller.yml + +@manually-enhanced: Do not overwrite. This crafts a one-opcode +contract, CALLs it, and stores the opcode's measured gas minus the +data's hardcoded Cancun-era expected cost (so the net is normally 0). +EIP-8038 reprices state access, so four opcodes now exceed their old +expected cost by a fork-derived delta: `BALANCE` and `SELFDESTRUCT` +(cold account, `COLD_ACCOUNT_ACCESS` 2600 -> 3000, +400), `EXTCODESIZE` +(cold account plus the extra `WARM_ACCESS` for the opcode's code read, ++500), and `SLOAD` (cold storage, `COLD_STORAGE_ACCESS` 2100 -> 3000, ++900). The stored net for those four data indices becomes that delta; +every other index stays 0. Each delta is derived from the fork's own +constants and is exactly 0 pre-EIP-8038; do not hardcode it. """ import pytest @@ -710,6 +722,23 @@ def test_gas_cost_berlin( v: int, ) -> None: """Ori Pomerantz qbzzt1@gmail.""" + gas_costs = fork.gas_costs() + # EIP-8038 access repricing; each term is 0 on earlier forks. + cold_account_delta = gas_costs.COLD_ACCOUNT_ACCESS - 2600 + cold_storage_delta = gas_costs.COLD_STORAGE_ACCESS - 2100 + # EXTCODESIZE also gains an extra warm access for its code read. + code_read_delta = cold_account_delta + ( + gas_costs.WARM_ACCESS if fork.is_eip_enabled(8037) else 0 + ) + # Each measured opcode subtracts its Cancun-era expected cost, so the + # net is the (Amsterdam - Cancun) repricing of the one state access + # it performs (cold address 0 / cold fresh slot), keyed by data index. + measured_delta = { + 23: cold_account_delta, # BALANCE + 31: code_read_delta, # EXTCODESIZE + 39: cold_storage_delta, # SLOAD + 45: cold_account_delta, # SELFDESTRUCT + }.get(d, 0) coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xBA1A9CE0BA1A9CE) @@ -969,6 +998,6 @@ def test_gas_cost_berlin( value=tx_value[v], ) - post = {addr: Account(storage=_storage_with_any({0: 0}, [1]))} + post = {addr: Account(storage=_storage_with_any({0: measured_delta}, [1]))} state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP150singleCodeGasPrices/test_gas_cost_memory.py b/tests/ported_static/stEIP150singleCodeGasPrices/test_gas_cost_memory.py index aad4a36436a..ba5c6ac4b67 100644 --- a/tests/ported_static/stEIP150singleCodeGasPrices/test_gas_cost_memory.py +++ b/tests/ported_static/stEIP150singleCodeGasPrices/test_gas_cost_memory.py @@ -3,6 +3,15 @@ Ported from: state_tests/stEIP150singleCodeGasPrices/gasCostMemoryFiller.yml + +@manually-enhanced: Do not overwrite. The second expect-entry (data +36-48) stores the regular gas of a measured window that includes one +extra cold `CALL` to a previously untouched contract relative to its +baseline. EIP-8038 reprices `COLD_ACCOUNT_ACCESS` (2600 -> 3000), so +that net cost shifts by `COLD_ACCOUNT_ACCESS - 2600`, derived from the +fork's own constant so it is exactly 0 pre-EIP-8038. The first entry +measures a difference of two equal-cost operations and is unchanged. +Do not hardcode the Amsterdam number. """ import pytest @@ -495,6 +504,8 @@ def test_gas_cost_memory( v: int, ) -> None: """Ori Pomerantz qbzzt1@gmail.""" + # EIP-8038 cold account repricing (2600 -> 3000); 0 on earlier forks. + cold_account_delta = fork.gas_costs().COLD_ACCOUNT_ACCESS - 2600 coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) contract_0 = Address(0x000000000000000000000000000000000000BA5E) contract_1 = Address(0x000000000000000000000000000000000010BA5E) @@ -846,7 +857,9 @@ def test_gas_cost_memory( "value": -1, }, "network": [">=Cancun"], - "result": {contract_3: Account(storage={0: 1900})}, + "result": { + contract_3: Account(storage={0: 1900 + cold_account_delta}) + }, }, ] diff --git a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_ext_code_copy_gas.py b/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_ext_code_copy_gas.py index c75b732ee35..4a23ef5d079 100644 --- a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_ext_code_copy_gas.py +++ b/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_ext_code_copy_gas.py @@ -3,6 +3,17 @@ Ported from: state_tests/stEIP150singleCodeGasPrices/RawExtCodeCopyGasFiller.json + +@manually-enhanced: Do not overwrite. This measures the regular gas +that a single cold `EXTCODECOPY` consumes via `Op.GAS`. EIP-8038 +reprices the cold account access (`COLD_ACCOUNT_ACCESS`, 2600 -> 3000) +and charges an extra `WARM_ACCESS` for the opcode's second read (the +code). The stored cost therefore shifts by +`(COLD_ACCOUNT_ACCESS - 2600) + WARM_ACCESS`. The cold term comes from +the fork's own constant; the extra warm term is gated on the +`is_eip_enabled(8037)` flag (the registered flag that activates the +repricing at Amsterdam), so the delta is exactly 0 on earlier forks. +Do not hardcode it. """ import pytest @@ -15,6 +26,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +41,17 @@ def test_raw_ext_code_copy_gas( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_raw_ext_code_copy_gas.""" + gas_costs = fork.gas_costs() + # EIP-8038: cold account repricing plus the extra warm access charged + # for the opcode's second read (the code). Both terms are 0 before + # EIP-8038, so the stored cost is unchanged on earlier forks. + cold_account_delta = gas_costs.COLD_ACCOUNT_ACCESS - 2600 + code_read_delta = cold_account_delta + ( + gas_costs.WARM_ACCESS if fork.is_eip_enabled(8037) else 0 + ) coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xE8D4A51000) @@ -68,6 +89,6 @@ def test_raw_ext_code_copy_gas( gas_limit=600000, ) - post = {target: Account(storage={1: 2629})} + post = {target: Account(storage={1: 2629 + code_read_delta})} state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_ext_code_copy_memory_gas.py b/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_ext_code_copy_memory_gas.py index a03431f4f6c..d16eef9ae8f 100644 --- a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_ext_code_copy_memory_gas.py +++ b/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_ext_code_copy_memory_gas.py @@ -3,6 +3,17 @@ Ported from: state_tests/stEIP150singleCodeGasPrices/RawExtCodeCopyMemoryGasFiller.json + +@manually-enhanced: Do not overwrite. This measures the regular gas +that a single cold `EXTCODECOPY` (with memory expansion) consumes via +`Op.GAS`. EIP-8038 reprices the cold account access +(`COLD_ACCOUNT_ACCESS`, 2600 -> 3000) and charges an extra +`WARM_ACCESS` for the opcode's second read (the code). The stored cost +therefore shifts by `(COLD_ACCOUNT_ACCESS - 2600) + WARM_ACCESS`. The +cold term comes from the fork's own constant; the extra warm term is +gated on the `is_eip_enabled(8037)` flag (the registered flag that +activates the repricing at Amsterdam), so the delta is exactly 0 on +earlier forks. Do not hardcode it. """ import pytest @@ -15,6 +26,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +43,17 @@ def test_raw_ext_code_copy_memory_gas( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_raw_ext_code_copy_memory_gas.""" + gas_costs = fork.gas_costs() + # EIP-8038: cold account repricing plus the extra warm access charged + # for the opcode's second read (the code). Both terms are 0 before + # EIP-8038, so the stored cost is unchanged on earlier forks. + cold_account_delta = gas_costs.COLD_ACCOUNT_ACCESS - 2600 + code_read_delta = cold_account_delta + ( + gas_costs.WARM_ACCESS if fork.is_eip_enabled(8037) else 0 + ) coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xE8D4A51000) @@ -72,6 +93,6 @@ def test_raw_ext_code_copy_memory_gas( gas_limit=600000, ) - post = {target: Account(storage={1: 4948})} + post = {target: Account(storage={1: 4948 + code_read_delta})} state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_ext_code_size_gas.py b/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_ext_code_size_gas.py index 4da7798cbdc..cc1641db59f 100644 --- a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_ext_code_size_gas.py +++ b/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_ext_code_size_gas.py @@ -3,6 +3,17 @@ Ported from: state_tests/stEIP150singleCodeGasPrices/RawExtCodeSizeGasFiller.json + +@manually-enhanced: Do not overwrite. This measures the regular gas +that a single cold `EXTCODESIZE` consumes via `Op.GAS`. EIP-8038 +reprices the cold account access (`COLD_ACCOUNT_ACCESS`, 2600 -> 3000) +and charges an extra `WARM_ACCESS` for the opcode's second read (the +code). The stored cost therefore shifts by +`(COLD_ACCOUNT_ACCESS - 2600) + WARM_ACCESS`. The cold term comes from +the fork's own constant; the extra warm term is gated on the +`is_eip_enabled(8037)` flag (the registered flag that activates the +repricing at Amsterdam), so the delta is exactly 0 on earlier forks. +Do not hardcode it. """ import pytest @@ -15,6 +26,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +41,17 @@ def test_raw_ext_code_size_gas( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_raw_ext_code_size_gas.""" + gas_costs = fork.gas_costs() + # EIP-8038: cold account repricing plus the extra warm access charged + # for the opcode's second read (the code). Both terms are 0 before + # EIP-8038, so the stored cost is unchanged on earlier forks. + cold_account_delta = gas_costs.COLD_ACCOUNT_ACCESS - 2600 + code_read_delta = cold_account_delta + ( + gas_costs.WARM_ACCESS if fork.is_eip_enabled(8037) else 0 + ) coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xE8D4A51000) @@ -68,6 +89,6 @@ def test_raw_ext_code_size_gas( gas_limit=600000, ) - post = {target: Account(storage={1: 2616})} + post = {target: Account(storage={1: 2616 + code_read_delta})} state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP1559/test_low_gas_limit.py b/tests/ported_static/stEIP1559/test_low_gas_limit.py index d2baa131eae..9e42e627208 100644 --- a/tests/ported_static/stEIP1559/test_low_gas_limit.py +++ b/tests/ported_static/stEIP1559/test_low_gas_limit.py @@ -3,6 +3,13 @@ Ported from: state_tests/stEIP1559/lowGasLimitFiller.yml + +@manually-enhanced: Do not overwrite. The `-g3` case must sit just below +the fork intrinsic to trigger `INTRINSIC_GAS_TOO_LOW`. EIP-2780 decomposes +and lowers the intrinsic, so the original hardcoded `20000` is no longer +below it; instead derive `intrinsic - 1` from the fork's +`transaction_intrinsic_cost_calculator()` for the single zero-byte +calldata so the boundary stays correct across the repricing. """ import pytest @@ -129,7 +136,13 @@ def test_low_gas_limit( tx_data = [ Bytes("00"), ] - tx_gas = [90000, 50000, 25000, 20000] + # -g3 must sit below the fork's intrinsic to trigger + # ``INTRINSIC_GAS_TOO_LOW``. EIP-2780 lowers the intrinsic so the + # original ``20000`` is no longer below it; derive the boundary. + intrinsic = fork.transaction_intrinsic_cost_calculator()( + calldata=Bytes("00"), + ) + tx_gas = [90000, 50000, 25000, intrinsic - 1] tx_access_lists: dict[int, list] = { 0: [], } diff --git a/tests/ported_static/stEIP158Specific/test_call_one_v_call_suicide.py b/tests/ported_static/stEIP158Specific/test_call_one_v_call_suicide.py index d99dcdf7185..b9be146f25c 100644 --- a/tests/ported_static/stEIP158Specific/test_call_one_v_call_suicide.py +++ b/tests/ported_static/stEIP158Specific/test_call_one_v_call_suicide.py @@ -3,6 +3,15 @@ Ported from: state_tests/stEIP158Specific/CALL_OneVCallSuicideFiller.json + +@manually-enhanced: Do not overwrite. The measured slot captures the +regular gas of a CALL with value to a not-yet-accessed contract that +SELFDESTRUCTs to the (alive) caller. EIP-8038 reprices the cold account +access (2600 -> 3000) and the CALL value transfer (9000 -> 10300); the +beneficiary stays alive so there is no new-account write. The delta is +`(COLD_ACCOUNT_ACCESS - 2600) + (CALL_VALUE - 9000)`, exactly 0 before +EIP-8037 and tracks parameter changes; do not hardcode the Amsterdam +value. """ import pytest @@ -15,6 +24,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +39,14 @@ def test_call_one_v_call_suicide( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_call_one_v_call_suicide.""" + # EIP-8038 deltas, each 0 before EIP-8037. The CALL pays a cold + # account access plus a value transfer; the beneficiary stays alive. + gas_costs = fork.gas_costs() + cold_account_delta = gas_costs.COLD_ACCOUNT_ACCESS - 2600 + call_value_delta = gas_costs.CALL_VALUE - 9000 coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xE8D4A51000) @@ -84,7 +100,10 @@ def test_call_one_v_call_suicide( post = { addr: Account(storage={}, balance=0), - target: Account(storage={100: 14337}, balance=100), + target: Account( + storage={100: 14337 + cold_account_delta + call_value_delta}, + balance=100, + ), } state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP158Specific/test_call_one_v_call_suicide2.py b/tests/ported_static/stEIP158Specific/test_call_one_v_call_suicide2.py index 2c222ca68f4..ad7c1dcc8ed 100644 --- a/tests/ported_static/stEIP158Specific/test_call_one_v_call_suicide2.py +++ b/tests/ported_static/stEIP158Specific/test_call_one_v_call_suicide2.py @@ -3,6 +3,15 @@ Ported from: state_tests/stEIP158Specific/CALL_OneVCallSuicide2Filler.json + +@manually-enhanced: Do not overwrite. The measured slot captures the +regular gas of a value-1 CALL to a cold contract that then +SELFDESTRUCTs (with a zero balance) to a cold, alive beneficiary. +EIP-8038 reprices the CALL's cold account access and value transfer, +plus the SELFDESTRUCT beneficiary's cold access; the beneficiary is +alive so there is no new-account write. The delta is therefore +`2 * (COLD_ACCOUNT_ACCESS - 2600) + (CALL_VALUE - 9000)`, exactly 0 +before EIP-8038. """ import pytest @@ -16,6 +25,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +40,15 @@ def test_call_one_v_call_suicide2( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_call_one_v_call_suicide2.""" + # EIP-8038 deltas, each 0 before EIP-8038. The CALL pays the cold + # account reprice and the value-transfer reprice; the cold + # SELFDESTRUCT beneficiary pays a second cold account reprice. + gas_costs = fork.gas_costs() + cold_account_delta = gas_costs.COLD_ACCOUNT_ACCESS - 2600 + call_value_delta = gas_costs.CALL_VALUE - 9000 coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) addr_2 = Address(0xEB201D2887816E041F6E807E804F64F3A7A226FE) sender = EOA( @@ -90,7 +107,10 @@ def test_call_one_v_call_suicide2( post = { addr: Account(storage={}, balance=0), - target: Account(storage={100: 16937}, balance=99), + target: Account( + storage={100: 16937 + 2 * cold_account_delta + call_value_delta}, + balance=99, + ), addr_2: Account(balance=1), } diff --git a/tests/ported_static/stEIP158Specific/test_call_zero_v_call_suicide.py b/tests/ported_static/stEIP158Specific/test_call_zero_v_call_suicide.py index 901902b9a5b..6dbef1bc4d7 100644 --- a/tests/ported_static/stEIP158Specific/test_call_zero_v_call_suicide.py +++ b/tests/ported_static/stEIP158Specific/test_call_zero_v_call_suicide.py @@ -3,6 +3,13 @@ Ported from: state_tests/stEIP158Specific/CALL_ZeroVCallSuicideFiller.json + +@manually-enhanced: Do not overwrite. The measured slot captures the +regular gas of a value-0 CALL to a cold contract that then +SELFDESTRUCTs back to its (warm, alive) caller. EIP-8038 reprices the +cold account access of that CALL; the beneficiary is warm so the +SELFDESTRUCT is unchanged. The delta is therefore the fork's +`COLD_ACCOUNT_ACCESS - 2600`, exactly 0 before EIP-8038. """ import pytest @@ -15,6 +22,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +37,11 @@ def test_call_zero_v_call_suicide( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_call_zero_v_call_suicide.""" + # EIP-8038 cold account access reprice; 0 before EIP-8038. + cold_account_delta = fork.gas_costs().COLD_ACCOUNT_ACCESS - 2600 coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xE8D4A51000) @@ -83,7 +94,7 @@ def test_call_zero_v_call_suicide( post = { addr: Account(balance=0), - target: Account(storage={100: 7637}), + target: Account(storage={100: 7637 + cold_account_delta}), } state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP158Specific/test_extcodesize_to_epmty_paris.py b/tests/ported_static/stEIP158Specific/test_extcodesize_to_epmty_paris.py index 35e01d26d30..8437cc2a5fb 100644 --- a/tests/ported_static/stEIP158Specific/test_extcodesize_to_epmty_paris.py +++ b/tests/ported_static/stEIP158Specific/test_extcodesize_to_epmty_paris.py @@ -3,6 +3,15 @@ Ported from: state_tests/stEIP158Specific/EXTCODESIZE_toEpmtyParisFiller.json + +@manually-enhanced: Do not overwrite. The measured slot captures the +regular gas of an EXTCODESIZE on a cold (empty, code-less) EOA plus +the SSTORE that clears a populated slot to its (zero) result. +EIP-8038 reprices the cold account access and adds a second +WARM_ACCESS for the code read (EXTCODESIZE delta), and spills the +cold SSTORE-clear's state-gas into regular gas (the reservoir is +empty). Both deltas are derived from the fork's own opcode gas model, +so each is exactly 0 before EIP-8038. """ import pytest @@ -15,6 +24,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,8 +39,22 @@ def test_extcodesize_to_epmty_paris( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_extcodesize_to_epmty_paris.""" + # EIP-8038 deltas, each 0 before EIP-8038. EXTCODESIZE gains the + # cold account reprice plus a second WARM_ACCESS for the code read; + # the cold SSTORE-clear (nonzero -> 0) spills its state-gas back + # into regular gas. + extcodesize_delta = ( + Op.EXTCODESIZE.with_metadata(address_warm=False).gas_cost(fork) - 2600 + ) + cold_clear_sstore_delta = ( + Op.SSTORE.with_metadata( + key_warm=False, original_value=1, current_value=1, new_value=0 + ).gas_cost(fork) + - 5000 + ) coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xE8D4A51000) @@ -64,7 +88,9 @@ def test_extcodesize_to_epmty_paris( post = { addr: Account(storage={}, code=b"", balance=10, nonce=0), - target: Account(storage={100: 7617}), + target: Account( + storage={100: 7617 + extcodesize_delta + cold_clear_sstore_delta} + ), } state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP158Specific/test_extcodesize_to_non_existent.py b/tests/ported_static/stEIP158Specific/test_extcodesize_to_non_existent.py index 66b98387b28..5f597f0b42e 100644 --- a/tests/ported_static/stEIP158Specific/test_extcodesize_to_non_existent.py +++ b/tests/ported_static/stEIP158Specific/test_extcodesize_to_non_existent.py @@ -3,6 +3,14 @@ Ported from: state_tests/stEIP158Specific/EXTCODESIZE_toNonExistentFiller.json + +@manually-enhanced: Do not overwrite. The measured slot captures the +regular gas of an EXTCODESIZE on a cold, non-existent address plus the +SSTORE that stores its (zero) result. EIP-8038 reprices the cold +account access and adds a second WARM_ACCESS for the code read +(EXTCODESIZE delta), and reprices the cold value-unchanged SSTORE. +Both deltas are derived from the fork's own opcode gas model, so each +is exactly 0 before EIP-8038. """ import pytest @@ -16,6 +24,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -30,8 +39,21 @@ def test_extcodesize_to_non_existent( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_extcodesize_to_non_existent.""" + # EIP-8038 deltas, each 0 before EIP-8038. EXTCODESIZE gains the + # cold account reprice plus a second WARM_ACCESS for the code read; + # the cold value-unchanged SSTORE gains its own reprice. + extcodesize_delta = ( + Op.EXTCODESIZE.with_metadata(address_warm=False).gas_cost(fork) - 2600 + ) + cold_noop_sstore_delta = ( + Op.SSTORE.with_metadata( + key_warm=False, original_value=0, current_value=0, new_value=0 + ).gas_cost(fork) + - 2200 + ) coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) contract_0 = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B) sender = EOA( @@ -75,7 +97,9 @@ def test_extcodesize_to_non_existent( Address( 0xC94F5374FCE5EDBC8E2A8697C15331677E6EBF0B ): Account.NONEXISTENT, - contract_0: Account(storage={100: 4817}), + contract_0: Account( + storage={100: 4817 + extcodesize_delta + cold_noop_sstore_delta} + ), } state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP2930/test_address_opcodes.py b/tests/ported_static/stEIP2930/test_address_opcodes.py index 2a55b6043b5..38279bf7408 100644 --- a/tests/ported_static/stEIP2930/test_address_opcodes.py +++ b/tests/ported_static/stEIP2930/test_address_opcodes.py @@ -3,6 +3,18 @@ Ported from: state_tests/stEIP2930/addressOpcodesFiller.yml + +@manually-enhanced: Do not overwrite. The contract measures, via +`Op.GAS`, the regular gas of each account-touching opcode (`BALANCE`, +`EXTCODESIZE`, `EXTCODEHASH`, `EXTCODECOPY`) on both a first (cold or +pre-warmed) and a second (warm) access. EIP-8038 reprices these: cold +`BALANCE`/`EXTCODEHASH` by +`COLD_ACCOUNT_ACCESS - 2600`, while +`EXTCODESIZE`/`EXTCODECOPY` carry an extra flat surcharge on both their +warm and cold forms. The single Cancun-era literals are therefore split +per opcode and per access, each adjusted by that opcode's own warm or +cold `(Amsterdam - Cancun)` cost delta taken from the fork gas model, so +every value is exactly 0 before EIP-8038 and tracks future parameter +changes; do not hardcode the Amsterdam numbers. """ import pytest @@ -21,7 +33,7 @@ from execution_testing.specs.static_state.expect_section import ( resolve_expect_post, ) -from execution_testing.vm import Op +from execution_testing.vm import Op, Opcode REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" @@ -545,65 +557,154 @@ def test_address_opcodes( address=Address(0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC), # noqa: E501 ) + # Per-opcode warm and cold cost deltas versus Cancun, derived from + # the fork gas model so each is exactly 0 before EIP-8038. The + # EXTCODECOPY metadata mirrors the measured access (a 0x20-byte copy + # into already-expanded memory) so only the account-access component + # varies across forks. `BALANCE`/`EXTCODEHASH` warm forms are + # unchanged; `EXTCODESIZE`/`EXTCODECOPY` gain a flat warm surcharge. + extcodecopy_meta = dict( + data_size=0x20, new_memory_size=0x120, old_memory_size=0x120 + ) + + def _account_delta(op: Opcode, warm: bool, base: int, **meta: int) -> int: + cost = op.with_metadata(address_warm=warm, **meta).gas_cost(fork) + return cost - base + + balance_warm_d = _account_delta(Op.BALANCE, True, 100) + balance_cold_d = _account_delta(Op.BALANCE, False, 2600) + extcodesize_warm_d = _account_delta(Op.EXTCODESIZE, True, 100) + extcodesize_cold_d = _account_delta(Op.EXTCODESIZE, False, 2600) + extcodehash_warm_d = _account_delta(Op.EXTCODEHASH, True, 100) + extcodehash_cold_d = _account_delta(Op.EXTCODEHASH, False, 2600) + extcodecopy_warm_d = _account_delta( + Op.EXTCODECOPY, True, 103, **extcodecopy_meta + ) + extcodecopy_cold_d = _account_delta( + Op.EXTCODECOPY, False, 2603, **extcodecopy_meta + ) + + # Slot 0 holds the first access (pre-warmed in the valid cases, cold + # in the invalid cases); slot 1 holds the always-warm second access. expect_entries_: list[dict] = [ + # valid (pre-warmed first access): both slots measure a warm + # access; only EXTCODESIZE/EXTCODECOPY shift. { "indexes": { - "data": [ - 0, - 1, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25, - 28, - 29, - 30, - 31, - 32, - 33, - 34, - 35, - 36, - 37, - 40, - 41, - 42, - 43, - 44, - 45, - 46, - 47, - ], + "data": [0, 1, 4, 5, 6, 7, 8, 9, 10, 11], + "gas": -1, + "value": -1, + }, + "network": [">=Cancun"], + "result": { + contract_0: Account( + storage={ + 0: 97 + balance_warm_d, + 1: 97 + balance_warm_d, + } + ) + }, + }, + { + "indexes": { + "data": [12, 13, 16, 17, 18, 19, 20, 21, 22, 23], + "gas": -1, + "value": -1, + }, + "network": [">=Cancun"], + "result": { + contract_0: Account( + storage={ + 0: 97 + extcodesize_warm_d, + 1: 97 + extcodesize_warm_d, + } + ) + }, + }, + { + "indexes": { + "data": [24, 25, 28, 29, 30, 31, 32, 33, 34, 35], "gas": -1, "value": -1, }, "network": [">=Cancun"], - "result": {contract_0: Account(storage={0: 97, 1: 97})}, + "result": { + contract_0: Account( + storage={ + 0: 97 + extcodehash_warm_d, + 1: 97 + extcodehash_warm_d, + } + ) + }, }, { "indexes": { - "data": [2, 3, 14, 15, 26, 27, 38, 39], + "data": [36, 37, 40, 41, 42, 43, 44, 45, 46, 47], "gas": -1, "value": -1, }, "network": [">=Cancun"], - "result": {contract_0: Account(storage={0: 2597, 1: 97, 2: 0})}, + "result": { + contract_0: Account( + storage={ + 0: 97 + extcodecopy_warm_d, + 1: 97 + extcodecopy_warm_d, + } + ) + }, + }, + # invalid (cold first access): slot 0 cold, slot 1 warm. + { + "indexes": {"data": [2, 3], "gas": -1, "value": -1}, + "network": [">=Cancun"], + "result": { + contract_0: Account( + storage={ + 0: 2597 + balance_cold_d, + 1: 97 + balance_warm_d, + 2: 0, + } + ) + }, + }, + { + "indexes": {"data": [14, 15], "gas": -1, "value": -1}, + "network": [">=Cancun"], + "result": { + contract_0: Account( + storage={ + 0: 2597 + extcodesize_cold_d, + 1: 97 + extcodesize_warm_d, + 2: 0, + } + ) + }, + }, + { + "indexes": {"data": [26, 27], "gas": -1, "value": -1}, + "network": [">=Cancun"], + "result": { + contract_0: Account( + storage={ + 0: 2597 + extcodehash_cold_d, + 1: 97 + extcodehash_warm_d, + 2: 0, + } + ) + }, + }, + { + "indexes": {"data": [38, 39], "gas": -1, "value": -1}, + "network": [">=Cancun"], + "result": { + contract_0: Account( + storage={ + 0: 2597 + extcodecopy_cold_d, + 1: 97 + extcodecopy_warm_d, + 2: 0, + } + ) + }, }, ] diff --git a/tests/ported_static/stEIP2930/test_coinbase_t01.py b/tests/ported_static/stEIP2930/test_coinbase_t01.py index b6c08afc361..754f52c4628 100644 --- a/tests/ported_static/stEIP2930/test_coinbase_t01.py +++ b/tests/ported_static/stEIP2930/test_coinbase_t01.py @@ -3,6 +3,14 @@ Ported from: state_tests/stEIP2930/coinbaseT01Filler.yml + +@manually-enhanced: Do not overwrite. The target contract measures, via +`Op.GAS`, the regular gas of a `CALL` that transfers value to the warm, +already-existing coinbase. EIP-8038 reprices the value-transfer +component (`CALL_VALUE` 9 000 -> 10 300), so the measurement grows by +`gas_costs.CALL_VALUE - 9000`. That delta is derived from the fork's +own gas model, so it is exactly 0 before EIP-8038 and tracks future +parameter changes; do not hardcode the Amsterdam number. """ import pytest @@ -111,16 +119,22 @@ def test_coinbase_t01( nonce=1, ) + # EIP-8038 reprices the value-transfer component of `CALL`; with the + # coinbase warm and already in state, the measured gas grows by the + # `CALL_VALUE` reprice alone. Derived from the fork gas model so it + # is 0 before EIP-8038. + call_value_delta = fork.gas_costs().CALL_VALUE - 9000 + expect_entries_: list[dict] = [ { "indexes": {"data": [1], "gas": -1, "value": -1}, "network": [">=Cancun"], - "result": {target: Account(storage={0: 6800})}, + "result": {target: Account(storage={0: 6800 + call_value_delta})}, }, { "indexes": {"data": [0, 2], "gas": -1, "value": -1}, "network": [">=Cancun"], - "result": {target: Account(storage={0: 6800})}, + "result": {target: Account(storage={0: 6800 + call_value_delta})}, }, ] diff --git a/tests/ported_static/stEIP2930/test_coinbase_t2.py b/tests/ported_static/stEIP2930/test_coinbase_t2.py index ea96482ef8b..5e8924e0b17 100644 --- a/tests/ported_static/stEIP2930/test_coinbase_t2.py +++ b/tests/ported_static/stEIP2930/test_coinbase_t2.py @@ -3,6 +3,14 @@ Ported from: state_tests/stEIP2930/coinbaseT2Filler.yml + +@manually-enhanced: Do not overwrite. The target contract measures, via +`Op.GAS`, the regular gas of a `CALL` that transfers value to the warm, +already-existing coinbase. EIP-8038 reprices the value-transfer +component (`CALL_VALUE` 9 000 -> 10 300), so the measurement grows by +`gas_costs.CALL_VALUE - 9000`. That delta is derived from the fork's +own gas model, so it is exactly 0 before EIP-8038 and tracks future +parameter changes; do not hardcode the Amsterdam number. """ import pytest @@ -105,16 +113,22 @@ def test_coinbase_t2( nonce=1, ) + # EIP-8038 reprices the value-transfer component of `CALL`; with the + # coinbase warm and already in state, the measured gas grows by the + # `CALL_VALUE` reprice alone. Derived from the fork gas model so it + # is 0 before EIP-8038. + call_value_delta = fork.gas_costs().CALL_VALUE - 9000 + expect_entries_: list[dict] = [ { "indexes": {"data": [0], "gas": -1, "value": -1}, "network": [">=Cancun"], - "result": {target: Account(storage={0: 6800})}, + "result": {target: Account(storage={0: 6800 + call_value_delta})}, }, { "indexes": {"data": [1], "gas": -1, "value": -1}, "network": [">=Cancun"], - "result": {target: Account(storage={0: 6800})}, + "result": {target: Account(storage={0: 6800 + call_value_delta})}, }, ] diff --git a/tests/ported_static/stEIP2930/test_manual_create.py b/tests/ported_static/stEIP2930/test_manual_create.py index 5ca118c9646..6bf26e1228a 100644 --- a/tests/ported_static/stEIP2930/test_manual_create.py +++ b/tests/ported_static/stEIP2930/test_manual_create.py @@ -6,12 +6,13 @@ @manually-enhanced: Do not overwrite. The three parametrizations of this test measure regular gas around a fresh SSTORE-set inside a -CREATE-deployed contract. EIP-8037 splits the Cancun-era SSTORE-set -base into a smaller regular portion plus 37 568 state-gas; with an -empty reservoir the full state-gas spills into regular gas and -`Op.GAS` reads +20 468 = 37 568 - 17 100 compared to Cancun. Bake -that delta into both `[">=Cancun"]` expect entries fork-conditionally -via `Op.SSTORE(new_value=1).state_cost(fork) - 17100`. +CREATE-deployed contract. EIP-8037 moves the bulk of the SSTORE-set +cost into a per-storage state-gas charge; with an empty reservoir it +spills back into regular gas, which `Op.GAS` observes. Derive the +warm and cold fresh-set deltas from the fork's own gas model so each +is exactly 0 pre-EIP-8037 and tracks parameter changes; bake the +warm delta into the declared-key entry and the cold delta into the +undeclared-key entries. """ import pytest @@ -90,12 +91,18 @@ def test_manual_create( pre[sender] = Account(balance=0x1000000000000000000, nonce=1) - # EIP-8037 SSTORE-set spillover: +20 468 regular gas per fresh set - # when the reservoir is empty. - sstore_set_delta = ( - (Op.SSTORE(new_value=1).state_cost(fork) - 17100) - if fork.is_eip_enabled(8037) - else 0 + # EIP-8037 SSTORE-set spill into regular gas (empty reservoir). + # Derive the warm and cold fresh-set deltas from the fork's own + # gas model so each is exactly 0 pre-EIP-8037. + def _sstore_delta(cancun_cost: int, **metadata: int) -> int: + op = Op.SSTORE.with_metadata(**metadata) + return op.gas_cost(fork) - cancun_cost + + warm_set_delta = _sstore_delta( + 20000, key_warm=True, current_value=0, new_value=2 + ) + cold_set_delta = _sstore_delta( + 22100, key_warm=False, current_value=0, new_value=2 ) expect_entries_: list[dict] = [ @@ -104,7 +111,7 @@ def test_manual_create( "network": [">=Cancun"], "result": { compute_create_address(address=sender, nonce=1): Account( - storage={0: 20008 + sstore_set_delta, 1: 106} + storage={0: 20008 + warm_set_delta, 1: 106} ), }, }, @@ -113,7 +120,7 @@ def test_manual_create( "network": [">=Cancun"], "result": { compute_create_address(address=sender, nonce=1): Account( - storage={0: 22108 + sstore_set_delta, 1: 106} + storage={0: 22108 + cold_set_delta, 1: 106} ), }, }, diff --git a/tests/ported_static/stEIP2930/test_storage_costs.py b/tests/ported_static/stEIP2930/test_storage_costs.py index 03bb03dd189..5e588d94abe 100644 --- a/tests/ported_static/stEIP2930/test_storage_costs.py +++ b/tests/ported_static/stEIP2930/test_storage_costs.py @@ -4,19 +4,19 @@ Ported from: state_tests/stEIP2930/storageCostsFiller.yml -@manually-enhanced: Do not overwrite. The SSTORE gas measurements in -this test were authored against the Cancun-era SSTORE-set base cost -of 20 000 (per EIP-2200). EIP-8037 splits that cost into a smaller -regular portion (~2 900) plus a per-storage state-gas charge of -`STATE_BYTES_PER_STORAGE_SET (32) * COST_PER_STATE_BYTE (1174) = -37 568`. When the state-gas reservoir is empty — as it is here, since -the tests don't pre-allocate state-gas budget — the full state-gas -spills back into regular gas, so `Op.GAS` observes -`+37 568 - 17 100 = +20 468` regular gas per fresh SSTORE-set -compared to Cancun. Bake that fork-conditional delta into the -expected post-state values for the 10 parametrizations whose measured -SSTORE writes triggered the spill; the remaining entries (SLOAD-only, -no-op SSTOREs) are unaffected. +@manually-enhanced: Do not overwrite. This test measures the regular +gas consumed by storage accesses via `Op.GAS`. EIP-8037 moves the +bulk of storage-write cost into a per-storage state-gas charge; with +an empty state-gas reservoir (these tests pre-allocate none) the full +state gas spills back into regular gas, so each measurement shifts by +its `(Amsterdam - Cancun)` cost delta. Six access classes shift: warm +and cold fresh SSTORE-sets (state-gas spill dominates), warm and cold +SSTORE writes to existing slots (clear/reset: the storage-write +component), cold value-unchanged SSTOREs, and cold SLOADs (the +`COLD_STORAGE_ACCESS` repricing). Warm reads and no-op SSTOREs are +unchanged. Each delta below is derived from the fork's own opcode gas +model, so it is exactly 0 pre-EIP-8037 and tracks future parameter +changes; do not hardcode the Amsterdam numbers. """ import pytest @@ -661,113 +661,150 @@ def test_storage_costs( address=Address(0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC), # noqa: E501 ) - # EIP-8037 splits the SSTORE-set base cost (Cancun: 20 000 regular) - # into a smaller regular portion plus per-storage state-gas. When - # the state-gas reservoir is empty for these tests, the full state - # gas spills into regular gas, so Op.GAS sees +20 468 per fresh - # SSTORE-set compared to Cancun (=37 568 state-gas - 17 100 base - # regular drop). Apply that delta to the 10 measurements that - # trigger a fresh-set spill; the SLOAD-only and no-op SSTORE - # entries below are unchanged. - sstore_set_delta = ( - (Op.SSTORE(new_value=1).state_cost(fork) - 17100) - if fork.is_eip_enabled(8037) - else 0 + # EIP-8037 moves the bulk of storage-write cost into a per-storage + # state-gas charge. These tests pre-allocate no state-gas reservoir, + # so the full state gas spills back into regular gas and `Op.GAS` + # observes each measured SSTORE/SLOAD at its combined regular + state + # cost. Every measured access therefore shifts by its + # (Amsterdam - Cancun) delta; derive each delta from the fork's own + # opcode gas model so it is exactly 0 pre-EIP-8037 and tracks future + # parameter changes. The subtracted Cancun-era pure costs are frozen + # historical values. + def _sstore_delta(cancun_cost: int, **metadata: int) -> int: + op = Op.SSTORE.with_metadata(**metadata) + return op.gas_cost(fork) - cancun_cost + + d_warm_set = _sstore_delta( + 20000, key_warm=True, original_value=0, current_value=0, new_value=2 + ) + d_cold_set = _sstore_delta( + 22100, key_warm=False, original_value=0, current_value=0, new_value=2 + ) + d_warm_write = _sstore_delta( + 2900, key_warm=True, original_value=1, current_value=1, new_value=2 + ) + d_cold_write = _sstore_delta( + 5000, key_warm=False, original_value=1, current_value=1, new_value=2 + ) + d_cold_noop = _sstore_delta( + 2200, key_warm=False, original_value=1, current_value=1, new_value=1 ) + d_cold_read = fork.gas_costs().COLD_STORAGE_ACCESS - 2100 expect_entries_: list[dict] = [ + # declaredKeyWrite: warm fresh SSTORE-set. { "indexes": {"data": [0, 35], "gas": -1, "value": -1}, "network": [">=Cancun"], "result": { - contract_0: Account( - storage={0: 2, 1: 20003 + sstore_set_delta} - ) + contract_0: Account(storage={0: 2, 1: 20003 + d_warm_set}) }, }, + # undeclaredKeyWrite: cold fresh SSTORE-set. { "indexes": {"data": [6, 12, 18], "gas": -1, "value": -1}, "network": [">=Cancun"], "result": { - contract_0: Account( - storage={0: 2, 1: 22103 + sstore_set_delta} - ) + contract_0: Account(storage={0: 2, 1: 22103 + d_cold_set}) }, }, + # declaredKeyUpdate: warm SSTORE-reset (nonzero -> nonzero). { "indexes": {"data": [3], "gas": -1, "value": -1}, "network": [">=Cancun"], - "result": {contract_3: Account(storage={0: 48879, 1: 2903})}, + "result": { + contract_3: Account(storage={0: 48879, 1: 2903 + d_warm_write}) + }, }, + # undeclaredKeyUpdate: cold SSTORE-reset (nonzero -> nonzero). { "indexes": {"data": [9, 15, 21], "gas": -1, "value": -1}, "network": [">=Cancun"], - "result": {contract_3: Account(storage={0: 48879, 1: 5003})}, + "result": { + contract_3: Account(storage={0: 48879, 1: 5003 + d_cold_write}) + }, }, + # declaredKeyNOP: warm value-unchanged SSTORE (no write). { "indexes": {"data": [4], "gas": -1, "value": -1}, "network": [">=Cancun"], "result": {contract_4: Account(storage={0: 24743, 1: 103})}, }, + # undeclaredKeyNOP: cold value-unchanged SSTORE. { "indexes": {"data": [10, 16, 22], "gas": -1, "value": -1}, "network": [">=Cancun"], - "result": {contract_4: Account(storage={0: 24743, 1: 2203})}, + "result": { + contract_4: Account(storage={0: 24743, 1: 2203 + d_cold_noop}) + }, }, + # declaredKeyNOP0: warm value-unchanged SSTORE (no write). { "indexes": {"data": [5], "gas": -1, "value": -1}, "network": [">=Cancun"], "result": {contract_5: Account(storage={1: 103})}, }, + # undeclaredKeyNOP0: cold value-unchanged SSTORE. { "indexes": {"data": [11, 17, 23], "gas": -1, "value": -1}, "network": [">=Cancun"], - "result": {contract_5: Account(storage={1: 2203})}, + "result": {contract_5: Account(storage={1: 2203 + d_cold_noop})}, }, + # declaredKeyDel: warm SSTORE-clear (nonzero -> 0). { "indexes": {"data": [2], "gas": -1, "value": -1}, "network": [">=Cancun"], - "result": {contract_2: Account(storage={0: 0, 1: 2903})}, + "result": { + contract_2: Account(storage={0: 0, 1: 2903 + d_warm_write}) + }, }, + # undeclaredKeyDel: cold SSTORE-clear (nonzero -> 0). { "indexes": {"data": [8, 14, 20], "gas": -1, "value": -1}, "network": [">=Cancun"], - "result": {contract_2: Account(storage={0: 0, 1: 5003})}, + "result": { + contract_2: Account(storage={0: 0, 1: 5003 + d_cold_write}) + }, }, + # declaredKeyRead: warm SLOAD. { "indexes": {"data": [1], "gas": -1, "value": -1}, "network": [">=Cancun"], "result": {contract_1: Account(storage={1: 100})}, }, + # undeclaredKeyRead: cold SLOAD. { "indexes": {"data": [7, 13, 19], "gas": -1, "value": -1}, "network": [">=Cancun"], - "result": {contract_1: Account(storage={1: 2100})}, + "result": {contract_1: Account(storage={1: 2100 + d_cold_read})}, }, + # postSSTORE write: key already warm/dirty, no fresh-set spill. { "indexes": {"data": [24, 25], "gas": -1, "value": -1}, "network": [">=Cancun"], "result": {contract_6: Account(storage={0: 2, 1: 103})}, }, + # postSSTORE read: key already warm. { "indexes": {"data": [26, 27], "gas": -1, "value": -1}, "network": [">=Cancun"], "result": {contract_7: Account(storage={0: 24743, 1: 100})}, }, + # postSLOAD write: SLOAD warms the key, then warm fresh SSTORE-set. { "indexes": {"data": [28, 29], "gas": -1, "value": -1}, "network": [">=Cancun"], "result": { - contract_8: Account( - storage={0: 2, 1: 20000 + sstore_set_delta} - ) + contract_8: Account(storage={0: 2, 1: 20000 + d_warm_set}) }, }, + # postSLOAD read: key already warm. { "indexes": {"data": [30, 31], "gas": -1, "value": -1}, "network": [">=Cancun"], "result": {contract_9: Account(storage={1: 97})}, }, + # declaredTo: warm SLOAD (slot 1) + warm fresh SSTORE-set (slot 2). { "indexes": {"data": [32], "gas": -1, "value": -1}, "network": [">=Cancun"], @@ -776,12 +813,13 @@ def test_storage_costs( storage={ 0: 2, 1: 100, - 2: 20000 + sstore_set_delta, + 2: 20000 + d_warm_set, 24743: 57005, } ) }, }, + # undeclaredTo: cold SLOAD (slot 1) + cold fresh SSTORE-set (slot 2). { "indexes": {"data": [33, 34], "gas": -1, "value": -1}, "network": [">=Cancun"], @@ -789,8 +827,8 @@ def test_storage_costs( contract_10: Account( storage={ 0: 2, - 1: 2100, - 2: 22100 + sstore_set_delta, + 1: 2100 + d_cold_read, + 2: 22100 + d_cold_set, 24743: 57005, } ), diff --git a/tests/ported_static/stEIP2930/test_transaction_costs.py b/tests/ported_static/stEIP2930/test_transaction_costs.py index 3695bf9a542..fb03a48592e 100644 --- a/tests/ported_static/stEIP2930/test_transaction_costs.py +++ b/tests/ported_static/stEIP2930/test_transaction_costs.py @@ -3,6 +3,16 @@ Ported from: state_tests/stEIP2930/transactionCostsFiller.yml + +@manually-enhanced: Do not overwrite. The post-state asserts the sender +balance after a STOP-only call. For Amsterdam+ it is derived from +`fork.transaction_intrinsic_cost_calculator()` (over calldata, +access_list, and sends_value) as `pre_balance - tx.value - +intrinsic_gas * gas_price`, instead of a hardcoded literal, so the +access-list-heavy cases stay correct across the EIP-2780 intrinsic +decomposition and EIP-7981/EIP-8038 access-list repricing. Pre-Amsterdam +forks (Cancun/Prague) keep their original hardcoded balances. No +21_000 baseline or SSTORE-clear constants are subtracted here. """ import pytest @@ -471,6 +481,7 @@ def test_transaction_costs( calldata=tx.data, contract_creation=tx.to is None, access_list=tx.access_list, + sends_value=bool(tx.value), ) post[sender] = Account( balance=( diff --git a/tests/ported_static/stEIP2930/test_varied_context.py b/tests/ported_static/stEIP2930/test_varied_context.py index 3c78c50a4c9..705de655138 100644 --- a/tests/ported_static/stEIP2930/test_varied_context.py +++ b/tests/ported_static/stEIP2930/test_varied_context.py @@ -4,21 +4,21 @@ Ported from: state_tests/stEIP2930/variedContextFiller.yml -@manually-enhanced: Do not overwrite. 28 parametrizations of this -test measure gas consumption around SSTORE/CALL/SELFDESTRUCT in -various access-list contexts. EIP-8037 splits the Cancun-era base -costs (SSTORE-set 20 000, CALL-new-account 25 000, SELFDESTRUCT-new- -beneficiary 25 000) into smaller regular portions plus per-storage -or per-new-account state-gas charges. When the reservoir is empty — -the case here, since no state-gas budget is pre-allocated — the -full state-gas spills back into regular gas and Op.GAS reads three -distinct deltas: - +20 468 per fresh SSTORE-set - +106 488 per NEW_ACCOUNT (CALL with value or SELFDESTRUCT) - +126 956 = both, for SELFDESTRUCT-with-write paths -Each affected post-state literal is bumped by the appropriate -delta fork-conditionally; pre-EIP-8037 forks use the original -values. +@manually-enhanced: Do not overwrite. This test measures gas +consumption around SSTORE/CALL/SELFDESTRUCT in various access-list +contexts via `Op.GAS`. EIP-8037/8038 reprice several components; +with an empty reservoir (the case here) the state-gas portion +spills back into regular gas, so each measurement shifts by its +`(Amsterdam - Cancun)` delta. Every delta below is derived from the +fork's own opcode gas model, so it is exactly 0 pre-EIP-8037 and +tracks parameter changes: warm/cold fresh SSTORE-sets, the +NEW_ACCOUNT spill for CALL-with-value and SELFDESTRUCT-to-non-alive +(the latter also gaining `ACCOUNT_WRITE` and the cold reprice), and +the cold account/storage access reprices. The `*ValidGas` +parametrizations instead forward a fixed in-bytecode gas budget that +EIP-8038 made insufficient; those budgets are bumped by the inner +SSTORE-write increase so the success path stays funded while the +under-funded cold path still runs out of gas. """ import pytest @@ -1152,9 +1152,24 @@ def test_varied_context( # { ; WRITE_INVALID_OOG WRITE_VALID_NO_OOG # (call 0x0B65 0xF114 0 0 0 0 0x20) # } + # EIP-8038 raises the inner SSTORE-write cost. Bump the "valid" gas + # these callers forward by exactly that increase so their success + # path stays funded at Amsterdam (preserving the original Cancun + # margin) while the under-funded cold "invalid" path still OOGs. + # The contract_13 inner SSTORE is a warm reset; contract_15's is a + # cold reset. Both bumps are 0 pre-EIP-8037. + _warm_reset = Op.SSTORE.with_metadata( + key_warm=True, original_value=1, current_value=1, new_value=2 + ) + _cold_reset = Op.SSTORE.with_metadata( + key_warm=False, original_value=1, current_value=1, new_value=2 + ) + valid_write_gas = 0xB65 + (_warm_reset.gas_cost(fork) - 2900) + valid_read_gas = 0x1800 + (_cold_reset.gas_cost(fork) - 5000) + contract_13 = pre.deploy_contract( # noqa: F841 code=Op.CALL( - gas=0xB65, + gas=valid_write_gas, address=0xF114, value=0x0, args_offset=0x0, @@ -1173,7 +1188,7 @@ def test_varied_context( # } contract_15 = pre.deploy_contract( # noqa: F841 code=Op.CALL( - gas=0x1800, + gas=valid_read_gas, address=0xF115, value=0x0, args_offset=0x0, @@ -1337,24 +1352,41 @@ def test_varied_context( address=Address(0x0000000000000000000000000000000000001016), # noqa: E501 ) - # EIP-8037 splits SSTORE-set, NEW_ACCOUNT call value transfer, and - # SELFDESTRUCT new-beneficiary base costs into state-gas portions. - # With an empty reservoir (the case here), the full state-gas - # spills into regular gas, which Op.GAS observes. - # sstore-set spill: +37 568 - 17 100 = +20 468 per fresh set - # new-account spill: +131 488 - 25 000 = +106 488 per CALL - # with value to a non-alive account, and - # per SELFDESTRUCT to non-alive beneficiary - # suicide-write spill: +126 956 = both deltas combined - sstore_set_delta = ( - (Op.SSTORE(new_value=1).state_cost(fork) - 17100) - if fork.is_eip_enabled(8037) - else 0 + # EIP-8037/8038 reprice several access components. With an empty + # reservoir (the case here) the state-gas portion spills back into + # regular gas, which `Op.GAS` observes. Derive each delta from the + # fork's own gas model so it is exactly 0 pre-EIP-8037 and tracks + # parameter changes. + gas_costs = fork.gas_costs() + + def _sstore_delta(cancun_cost: int, **metadata: int) -> int: + op = Op.SSTORE.with_metadata(**metadata) + return op.gas_cost(fork) - cancun_cost + + # Fresh SSTORE-set (state-gas spill dominates), warm vs cold key. + warm_set_delta = _sstore_delta( + 20000, key_warm=True, current_value=0, new_value=2 + ) + cold_set_delta = _sstore_delta( + 22100, key_warm=False, current_value=0, new_value=2 ) + # CALL value transfer to a non-alive account: the 25 000 NEW_ACCOUNT + # base becomes a spilling state-gas charge. new_account_delta = ( (fork.create_state_gas() - 25000) if fork.is_eip_enabled(8037) else 0 ) - suicide_write_delta = sstore_set_delta + new_account_delta + # Cold account access reprice (0 pre-Amsterdam). + cold_account_delta = gas_costs.COLD_ACCOUNT_ACCESS - 2600 + # Cold storage access (SLOAD) reprice (0 pre-Amsterdam). + cold_storage_delta = gas_costs.COLD_STORAGE_ACCESS - 2100 + # SELFDESTRUCT to a non-alive cold beneficiary: new-account spill, + # the new ACCOUNT_WRITE charge (0 pre-Amsterdam), and the cold + # reprice. + suicide_new_delta = ( + new_account_delta + gas_costs.ACCOUNT_WRITE + cold_account_delta + ) + # callWriteSuicide measures a warm SSTORE-set then that SELFDESTRUCT. + suicide_write_delta = warm_set_delta + suicide_new_delta expect_entries_: list[dict] = [ { @@ -1362,7 +1394,7 @@ def test_varied_context( "network": [">=Cancun"], "result": { contract_0: Account( - storage={0: 2, 1: (20003 + sstore_set_delta), 2: 107} + storage={0: 2, 1: (20003 + warm_set_delta), 2: 107} ) }, }, @@ -1371,7 +1403,11 @@ def test_varied_context( "network": [">=Cancun"], "result": { contract_0: Account( - storage={0: 2, 1: (22103 + sstore_set_delta), 2: 2107} + storage={ + 0: 2, + 1: (22103 + cold_set_delta), + 2: 2107 + cold_storage_delta, + } ) }, }, @@ -1380,7 +1416,7 @@ def test_varied_context( "network": [">=Cancun"], "result": { contract_2: Account( - storage={0: 2, 1: (20003 + sstore_set_delta), 2: 107} + storage={0: 2, 1: (20003 + warm_set_delta), 2: 107} ) }, }, @@ -1389,7 +1425,11 @@ def test_varied_context( "network": [">=Cancun"], "result": { contract_2: Account( - storage={0: 2, 1: (22103 + sstore_set_delta), 2: 2107} + storage={ + 0: 2, + 1: (22103 + cold_set_delta), + 2: 2107 + cold_storage_delta, + } ) }, }, @@ -1400,8 +1440,8 @@ def test_varied_context( contract_3: Account( storage={ 0: 2, - 1: (22103 + sstore_set_delta), - 2: 2107, + 1: (22103 + cold_set_delta), + 2: 2107 + cold_storage_delta, 24743: 57005, } ) @@ -1414,7 +1454,7 @@ def test_varied_context( contract_3: Account( storage={ 0: 2, - 1: (20003 + sstore_set_delta), + 1: (20003 + warm_set_delta), 2: 107, 24743: 57005, } @@ -1424,7 +1464,9 @@ def test_varied_context( { "indexes": {"data": [6], "gas": -1, "value": -1}, "network": [">=Cancun"], - "result": {contract_4: Account(storage={0: 2107})}, + "result": { + contract_4: Account(storage={0: 2107 + cold_storage_delta}) + }, }, { "indexes": {"data": [7], "gas": -1, "value": -1}, @@ -1436,7 +1478,7 @@ def test_varied_context( "network": [">=Cancun"], "result": { contract_26: Account( - storage={0: (20003 + sstore_set_delta), 1: 100} + storage={0: (20003 + warm_set_delta), 1: 100} ) }, }, @@ -1445,7 +1487,10 @@ def test_varied_context( "network": [">=Cancun"], "result": { contract_26: Account( - storage={0: (22103 + sstore_set_delta), 1: 2100} + storage={ + 0: (22103 + cold_set_delta), + 1: 2100 + cold_storage_delta, + } ) }, }, @@ -1460,21 +1505,37 @@ def test_varied_context( "indexes": {"data": [11], "gas": -1, "value": -1}, "network": [">=Cancun"], "result": { - contract_7: Account(storage={0: (24601 + suicide_write_delta)}) + contract_7: Account( + storage={ + 0: ( + 24601 + + cold_set_delta + + suicide_new_delta + + cold_account_delta + ) + } + ) }, }, { "indexes": {"data": [12], "gas": -1, "value": -1}, "network": [">=Cancun"], "result": { - contract_9: Account(storage={0: 100 + new_account_delta}) + contract_9: Account(storage={0: 100 + suicide_new_delta}) }, }, { "indexes": {"data": [13], "gas": -1, "value": -1}, "network": [">=Cancun"], "result": { - contract_9: Account(storage={0: 4600 + new_account_delta}) + contract_9: Account( + storage={ + 0: 4600 + + suicide_new_delta + + cold_account_delta + + cold_storage_delta + } + ) }, }, { @@ -1524,7 +1585,7 @@ def test_varied_context( 268: 103, 269: 103, 270: 103, - 271: (20003 + sstore_set_delta), + 271: (20003 + warm_set_delta), 512: 100, 513: 100, 514: 100, @@ -1541,22 +1602,22 @@ def test_varied_context( 525: 100, 526: 100, 527: 100, - 768: (20003 + sstore_set_delta), - 769: (20003 + sstore_set_delta), - 770: (20003 + sstore_set_delta), - 771: (20003 + sstore_set_delta), - 772: (20003 + sstore_set_delta), - 773: (20003 + sstore_set_delta), - 774: (20003 + sstore_set_delta), - 775: (20003 + sstore_set_delta), - 776: (20003 + sstore_set_delta), - 777: (20003 + sstore_set_delta), - 778: (20003 + sstore_set_delta), - 779: (20003 + sstore_set_delta), - 780: (20003 + sstore_set_delta), - 781: (20003 + sstore_set_delta), - 782: (20003 + sstore_set_delta), - 783: (20003 + sstore_set_delta), + 768: (20003 + warm_set_delta), + 769: (20003 + warm_set_delta), + 770: (20003 + warm_set_delta), + 771: (20003 + warm_set_delta), + 772: (20003 + warm_set_delta), + 773: (20003 + warm_set_delta), + 774: (20003 + warm_set_delta), + 775: (20003 + warm_set_delta), + 776: (20003 + warm_set_delta), + 777: (20003 + warm_set_delta), + 778: (20003 + warm_set_delta), + 779: (20003 + warm_set_delta), + 780: (20003 + warm_set_delta), + 781: (20003 + warm_set_delta), + 782: (20003 + warm_set_delta), + 783: (20003 + warm_set_delta), 1024: 100, 1025: 100, 1026: 100, @@ -1617,7 +1678,7 @@ def test_varied_context( 268: 103, 269: 103, 270: 103, - 271: (22103 + sstore_set_delta), + 271: (22103 + cold_set_delta), 512: 100, 513: 100, 514: 100, @@ -1633,39 +1694,39 @@ def test_varied_context( 524: 100, 525: 100, 526: 100, - 527: 2100, - 768: (22103 + sstore_set_delta), - 769: (22103 + sstore_set_delta), - 770: (22103 + sstore_set_delta), - 771: (22103 + sstore_set_delta), - 772: (22103 + sstore_set_delta), - 773: (22103 + sstore_set_delta), - 774: (22103 + sstore_set_delta), - 775: (22103 + sstore_set_delta), - 776: (22103 + sstore_set_delta), - 777: (22103 + sstore_set_delta), - 778: (22103 + sstore_set_delta), - 779: (22103 + sstore_set_delta), - 780: (22103 + sstore_set_delta), - 781: (22103 + sstore_set_delta), - 782: (22103 + sstore_set_delta), - 783: (22103 + sstore_set_delta), - 1024: 2100, - 1025: 2100, - 1026: 2100, - 1027: 2100, - 1028: 2100, - 1029: 2100, - 1030: 2100, - 1031: 2100, - 1032: 2100, - 1033: 2100, - 1034: 2100, - 1035: 2100, - 1036: 2100, - 1037: 2100, - 1038: 2100, - 1039: 2100, + 527: 2100 + cold_storage_delta, + 768: (22103 + cold_set_delta), + 769: (22103 + cold_set_delta), + 770: (22103 + cold_set_delta), + 771: (22103 + cold_set_delta), + 772: (22103 + cold_set_delta), + 773: (22103 + cold_set_delta), + 774: (22103 + cold_set_delta), + 775: (22103 + cold_set_delta), + 776: (22103 + cold_set_delta), + 777: (22103 + cold_set_delta), + 778: (22103 + cold_set_delta), + 779: (22103 + cold_set_delta), + 780: (22103 + cold_set_delta), + 781: (22103 + cold_set_delta), + 782: (22103 + cold_set_delta), + 783: (22103 + cold_set_delta), + 1024: 2100 + cold_storage_delta, + 1025: 2100 + cold_storage_delta, + 1026: 2100 + cold_storage_delta, + 1027: 2100 + cold_storage_delta, + 1028: 2100 + cold_storage_delta, + 1029: 2100 + cold_storage_delta, + 1030: 2100 + cold_storage_delta, + 1031: 2100 + cold_storage_delta, + 1032: 2100 + cold_storage_delta, + 1033: 2100 + cold_storage_delta, + 1034: 2100 + cold_storage_delta, + 1035: 2100 + cold_storage_delta, + 1036: 2100 + cold_storage_delta, + 1037: 2100 + cold_storage_delta, + 1038: 2100 + cold_storage_delta, + 1039: 2100 + cold_storage_delta, 24743: 57005, 48879: 2, 61440: 48879, @@ -1693,7 +1754,7 @@ def test_varied_context( "network": [">=Cancun"], "result": { compute_create_address(address=contract_18, nonce=0): Account( - storage={0: 65535, 1: (20017 + sstore_set_delta)} + storage={0: 65535, 1: (20017 + warm_set_delta)} ), }, }, @@ -1702,7 +1763,7 @@ def test_varied_context( "network": [">=Cancun"], "result": { compute_create_address(address=contract_18, nonce=0): Account( - storage={0: 65535, 1: (22117 + sstore_set_delta)} + storage={0: 65535, 1: (22117 + cold_set_delta)} ), }, }, @@ -1711,7 +1772,7 @@ def test_varied_context( "network": [">=Cancun"], "result": { Address(0xD82F21135ED7D7D833A9F2A0F1CF6C3DA214B8E3): Account( - storage={0: 65535, 1: (20017 + sstore_set_delta)} + storage={0: 65535, 1: (20017 + warm_set_delta)} ), }, }, @@ -1720,7 +1781,7 @@ def test_varied_context( "network": [">=Cancun"], "result": { Address(0xD82F21135ED7D7D833A9F2A0F1CF6C3DA214B8E3): Account( - storage={0: 65535, 1: (22117 + sstore_set_delta)} + storage={0: 65535, 1: (22117 + cold_set_delta)} ), }, }, @@ -1729,7 +1790,7 @@ def test_varied_context( "network": [">=Cancun"], "result": { compute_create_address(address=contract_20, nonce=0): Account( - storage={0: 65535, 1: (20017 + sstore_set_delta)} + storage={0: 65535, 1: (20017 + warm_set_delta)} ), }, }, @@ -1738,7 +1799,7 @@ def test_varied_context( "network": [">=Cancun"], "result": { compute_create_address(address=contract_20, nonce=0): Account( - storage={0: 65535, 1: (22117 + sstore_set_delta)} + storage={0: 65535, 1: (22117 + cold_set_delta)} ), }, }, @@ -1747,7 +1808,7 @@ def test_varied_context( "network": [">=Cancun"], "result": { Address(0x530508498D2AA75D8E591612809FEC3D37A45615): Account( - storage={0: 65535, 1: (20017 + sstore_set_delta)} + storage={0: 65535, 1: (20017 + warm_set_delta)} ), }, }, @@ -1756,7 +1817,7 @@ def test_varied_context( "network": [">=Cancun"], "result": { Address(0x530508498D2AA75D8E591612809FEC3D37A45615): Account( - storage={0: 65535, 1: (22117 + sstore_set_delta)} + storage={0: 65535, 1: (22117 + cold_set_delta)} ), }, }, @@ -1765,7 +1826,7 @@ def test_varied_context( "network": [">=Cancun"], "result": { compute_create_address(address=contract_22, nonce=0): Account( - storage={0: 65535, 1: (20017 + sstore_set_delta), 2: 117} + storage={0: 65535, 1: (20017 + warm_set_delta), 2: 117} ), }, }, @@ -1774,7 +1835,7 @@ def test_varied_context( "network": [">=Cancun"], "result": { compute_create_address(address=contract_22, nonce=0): Account( - storage={0: 65535, 1: (22117 + sstore_set_delta), 2: 117} + storage={0: 65535, 1: (22117 + cold_set_delta), 2: 117} ), }, }, @@ -1783,7 +1844,7 @@ def test_varied_context( "network": [">=Cancun"], "result": { Address(0x83FBDAE70258AC0FA837B701CC63CEDF48D4B6BF): Account( - storage={0: 65535, 1: (20017 + sstore_set_delta), 2: 117} + storage={0: 65535, 1: (20017 + warm_set_delta), 2: 117} ), }, }, @@ -1792,7 +1853,7 @@ def test_varied_context( "network": [">=Cancun"], "result": { Address(0x83FBDAE70258AC0FA837B701CC63CEDF48D4B6BF): Account( - storage={0: 65535, 1: (22117 + sstore_set_delta), 2: 117} + storage={0: 65535, 1: (22117 + cold_set_delta), 2: 117} ), }, }, @@ -1801,7 +1862,7 @@ def test_varied_context( "network": [">=Cancun"], "result": { contract_25: Account( - storage={0: 24743, 1: (20017 + sstore_set_delta), 2: 117} + storage={0: 24743, 1: (20017 + warm_set_delta), 2: 117} ) }, }, @@ -1810,7 +1871,7 @@ def test_varied_context( "network": [">=Cancun"], "result": { contract_25: Account( - storage={0: 24743, 1: (22117 + sstore_set_delta), 2: 117} + storage={0: 24743, 1: (22117 + cold_set_delta), 2: 117} ) }, }, diff --git a/tests/ported_static/stEIP3651_warmcoinbase/test_coinbase_warm_account_call_gas.py b/tests/ported_static/stEIP3651_warmcoinbase/test_coinbase_warm_account_call_gas.py index 7e8250c3ad0..1316363c63c 100644 --- a/tests/ported_static/stEIP3651_warmcoinbase/test_coinbase_warm_account_call_gas.py +++ b/tests/ported_static/stEIP3651_warmcoinbase/test_coinbase_warm_account_call_gas.py @@ -3,6 +3,8 @@ Ported from: state_tests/Shanghai/stEIP3651_warmcoinbase/coinbaseWarmAccountCallGasFiller.yml +@manually-enhanced: Do not overwrite. When EIP-8038 is enabled, +EXTCODESIZE and EXTCODECOPY charge an extra warm code-read. """ import pytest @@ -278,6 +280,11 @@ def test_coinbase_warm_account_call_gas( nonce=1, ) - post = {target: Account(storage={0: 100})} + warm_access = fork.gas_costs().WARM_ACCESS + # EIP-8038 charges EXTCODESIZE (d0) and EXTCODECOPY (d1) a second + # WARM_ACCESS for the account code read; other opcodes are unchanged. + ext_code_read = d in (0, 1) and fork.is_eip_enabled(8038) + expected_gas = warm_access + (warm_access if ext_code_read else 0) + post = {target: Account(storage={0: expected_gas})} state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stMemExpandingEIP150Calls/test_call_and_callcode_consume_more_gas_then_transaction_has_with_mem_expanding_calls.py b/tests/ported_static/stMemExpandingEIP150Calls/test_call_and_callcode_consume_more_gas_then_transaction_has_with_mem_expanding_calls.py index ff44e5b435b..d9ae39a4bb4 100644 --- a/tests/ported_static/stMemExpandingEIP150Calls/test_call_and_callcode_consume_more_gas_then_transaction_has_with_mem_expanding_calls.py +++ b/tests/ported_static/stMemExpandingEIP150Calls/test_call_and_callcode_consume_more_gas_then_transaction_has_with_mem_expanding_calls.py @@ -3,6 +3,14 @@ Ported from: state_tests/stMemExpandingEIP150Calls/CallAndCallcodeConsumeMoreGasThenTransactionHasWithMemExpandingCallsFiller.json + +@manually-enhanced: Do not overwrite. The post-state asserts the +remaining-gas snapshot stored by `Op.GAS` (slot 8 == 0x8D5B6), which +fixes the post-intrinsic execution budget. So `gas_limit` is derived +from the fork as `600_000 + (intrinsic - 21_000)`: it shifts the budget +by the intrinsic delta from the pre-EIP-2780 Cancun `TX_BASE` baseline +of 21_000, keeping the budget constant across the EIP-2780 intrinsic +decomposition and EIP-8038 access repricing. Do not hardcode 600_000. """ import pytest @@ -12,6 +20,7 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) @@ -31,6 +40,7 @@ def test_call_and_callcode_consume_more_gas_then_transaction_has_with_mem_expanding_calls( # noqa: E501 state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_call_and_callcode_consume_more_gas_then_transaction_has_with_m...""" # noqa: E501 coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) @@ -82,11 +92,19 @@ def test_call_and_callcode_consume_more_gas_then_transaction_has_with_mem_expand nonce=0, ) + # The original test was built against Cancun's ``TX_BASE`` of + # 21_000. EIP-2780 lowers the intrinsic for non-self non-value + # txs, so shift ``gas_limit`` by the intrinsic delta to preserve + # the post-intrinsic execution budget the Op.GAS storage + # assertion depends on. + intrinsic = fork.transaction_intrinsic_cost_calculator()() + gas_limit = 600_000 + (intrinsic - 21_000) + tx = Transaction( sender=sender, to=target, data=Bytes(""), - gas_limit=600000, + gas_limit=gas_limit, ) post = { diff --git a/tests/ported_static/stMemExpandingEIP150Calls/test_delegate_call_on_eip_with_mem_expanding_calls.py b/tests/ported_static/stMemExpandingEIP150Calls/test_delegate_call_on_eip_with_mem_expanding_calls.py index dec68916fab..7e3953c09c0 100644 --- a/tests/ported_static/stMemExpandingEIP150Calls/test_delegate_call_on_eip_with_mem_expanding_calls.py +++ b/tests/ported_static/stMemExpandingEIP150Calls/test_delegate_call_on_eip_with_mem_expanding_calls.py @@ -3,6 +3,16 @@ Ported from: state_tests/stMemExpandingEIP150Calls/DelegateCallOnEIPWithMemExpandingCallsFiller.json + +@manually-enhanced: Do not overwrite. The post-state asserts the GAS +opcode value stored at target slot 8 (0x8D5B6), which depends on the +execution budget left after the intrinsic charge. The original test +hardcoded `gas_limit` against Cancun's `TX_BASE` of 21_000; EIP-2780 +lowers the intrinsic for non-self non-value txs, so `gas_limit` is +derived from the fork as `600_000 + (intrinsic - 21_000)`, subtracting +the pre-EIP-2780 baseline 21_000 so the budget is invariant across the +intrinsic decomposition and EIP-8038 access repricing. Do not hardcode +the literal gas_limit. """ import pytest @@ -12,6 +22,7 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) @@ -31,6 +42,7 @@ def test_delegate_call_on_eip_with_mem_expanding_calls( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_delegate_call_on_eip_with_mem_expanding_calls.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) @@ -69,11 +81,19 @@ def test_delegate_call_on_eip_with_mem_expanding_calls( nonce=0, ) + # The original test was built against Cancun's ``TX_BASE`` of + # 21_000. EIP-2780 lowers the intrinsic for non-self non-value + # txs, so shift ``gas_limit`` by the intrinsic delta to preserve + # the post-intrinsic execution budget the Op.GAS storage + # assertion depends on. + intrinsic = fork.transaction_intrinsic_cost_calculator()() + gas_limit = 600_000 + (intrinsic - 21_000) + tx = Transaction( sender=sender, to=target, data=Bytes(""), - gas_limit=600000, + gas_limit=gas_limit, ) post = { diff --git a/tests/ported_static/stMemoryTest/test_oog.py b/tests/ported_static/stMemoryTest/test_oog.py index 5c2f1cc4f01..cd47deb6687 100644 --- a/tests/ported_static/stMemoryTest/test_oog.py +++ b/tests/ported_static/stMemoryTest/test_oog.py @@ -3,6 +3,15 @@ Ported from: state_tests/stMemoryTest/oogFiller.yml + +@manually-enhanced: Do not overwrite. Each parametrization forwards a +fixed in-bytecode gas budget to an inner operation and asserts whether +it succeeds. The `0x3E` (RETURNDATACOPY) success case routes through a +nested value-0 CALL to a cold contract; EIP-8038's cold account access +reprice consumes the budget's slack and OOGs the copy. Bump only that +budget by the fork-derived `COLD_ACCOUNT_ACCESS - 2600` so the success +path stays funded; the value is exactly 0 before EIP-8038 and all +other budgets are untouched. """ import pytest @@ -297,6 +306,11 @@ def test_oog( v: int, ) -> None: """Ori Pomerantz qbzzt1@gmail.""" + # EIP-8038 cold account access reprice; 0 before EIP-8038. The + # `0x3E` RETURNDATACOPY success case forwards just enough gas for a + # nested CALL to a cold contract plus the copy; the reprice eats the + # slack, so add it back to that one budget. + cold_account_delta = fork.gas_costs().COLD_ACCOUNT_ACCESS - 2600 coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) contract_0 = Address(0x0000000000000000000000000000000000010020) contract_1 = Address(0x0000000000000000000000000000000000010037) @@ -753,7 +767,7 @@ def test_oog( Bytes("1a8451e6") + Hash(0x3C) + Hash(0xFFFF), Bytes("1a8451e6") + Hash(0x3C) + Hash(0x2BC), Bytes("1a8451e6") + Hash(0x3E) + Hash(0xFFFF), - Bytes("1a8451e6") + Hash(0x3E) + Hash(0xC02), + Bytes("1a8451e6") + Hash(0x3E) + Hash(0xC02 + cold_account_delta), Bytes("1a8451e6") + Hash(0x3E) + Hash(0x7D0), Bytes("1a8451e6") + Hash(0x3E) + Hash(0xC01), Bytes("1a8451e6") + Hash(0x51) + Hash(0xFFFF), diff --git a/tests/ported_static/stNonZeroCallsTest/test_non_zero_value_call_to_non_non_zero_balance.py b/tests/ported_static/stNonZeroCallsTest/test_non_zero_value_call_to_non_non_zero_balance.py index 462c93ae2ae..5df9cd42733 100644 --- a/tests/ported_static/stNonZeroCallsTest/test_non_zero_value_call_to_non_non_zero_balance.py +++ b/tests/ported_static/stNonZeroCallsTest/test_non_zero_value_call_to_non_non_zero_balance.py @@ -3,6 +3,15 @@ Ported from: state_tests/stNonZeroCallsTest/NonZeroValue_CALL_ToNonNonZeroBalanceFiller.json + +@manually-enhanced: Do not overwrite. The measured slot captures the +regular gas of a value-1 CALL to a cold, alive EOA plus the SSTORE +storing the (success) result. EIP-8038 reprices the CALL's cold +account access and value transfer, and reprices the cold +value-unchanged SSTORE. The delta is therefore +`(COLD_ACCOUNT_ACCESS - 2600) + (CALL_VALUE - 9000)` plus the cold +SSTORE reprice, each derived from the fork and exactly 0 before +EIP-8038. """ import pytest @@ -15,6 +24,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +41,24 @@ def test_non_zero_value_call_to_non_non_zero_balance( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_non_zero_value_call_to_non_non_zero_balance.""" + # EIP-8038 deltas, each 0 before EIP-8038. The CALL pays the cold + # account reprice and the value-transfer reprice; the cold + # value-unchanged SSTORE gains its own reprice. + gas_costs = fork.gas_costs() + cold_account_delta = gas_costs.COLD_ACCOUNT_ACCESS - 2600 + call_value_delta = gas_costs.CALL_VALUE - 9000 + cold_noop_sstore_delta = ( + Op.SSTORE.with_metadata( + key_warm=False, original_value=0, current_value=0, new_value=0 + ).gas_cost(fork) + - 2200 + ) + call_measure_delta = ( + cold_account_delta + call_value_delta + cold_noop_sstore_delta + ) coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xE8D4A51000) @@ -76,7 +102,7 @@ def test_non_zero_value_call_to_non_non_zero_balance( post = { addr: Account(balance=100), - target: Account(storage={100: 11535}), + target: Account(storage={100: 11535 + call_measure_delta}), } state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stNonZeroCallsTest/test_non_zero_value_callcode_to_non_non_zero_balance.py b/tests/ported_static/stNonZeroCallsTest/test_non_zero_value_callcode_to_non_non_zero_balance.py index 20ecf378e6b..a55087af42b 100644 --- a/tests/ported_static/stNonZeroCallsTest/test_non_zero_value_callcode_to_non_non_zero_balance.py +++ b/tests/ported_static/stNonZeroCallsTest/test_non_zero_value_callcode_to_non_non_zero_balance.py @@ -3,6 +3,15 @@ Ported from: state_tests/stNonZeroCallsTest/NonZeroValue_CALLCODE_ToNonNonZeroBalanceFiller.json + +@manually-enhanced: Do not overwrite. The measured slot captures the +regular gas of a value-1 CALLCODE to a cold, alive EOA plus the SSTORE +storing the (success) result. EIP-8038 reprices the CALLCODE's cold +account access and value transfer, and reprices the cold +value-unchanged SSTORE. The delta is therefore +`(COLD_ACCOUNT_ACCESS - 2600) + (CALL_VALUE - 9000)` plus the cold +SSTORE reprice, each derived from the fork and exactly 0 before +EIP-8038. """ import pytest @@ -15,6 +24,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,8 +41,24 @@ def test_non_zero_value_callcode_to_non_non_zero_balance( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_non_zero_value_callcode_to_non_non_zero_balance.""" + # EIP-8038 deltas, each 0 before EIP-8038. The CALLCODE pays the + # cold account reprice and the value-transfer reprice; the cold + # value-unchanged SSTORE gains its own reprice. + gas_costs = fork.gas_costs() + cold_account_delta = gas_costs.COLD_ACCOUNT_ACCESS - 2600 + call_value_delta = gas_costs.CALL_VALUE - 9000 + cold_noop_sstore_delta = ( + Op.SSTORE.with_metadata( + key_warm=False, original_value=0, current_value=0, new_value=0 + ).gas_cost(fork) + - 2200 + ) + call_measure_delta = ( + cold_account_delta + call_value_delta + cold_noop_sstore_delta + ) coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) sender = pre.fund_eoa(amount=0xE8D4A51000) @@ -76,7 +102,7 @@ def test_non_zero_value_callcode_to_non_non_zero_balance( post = { addr: Account(balance=100), - target: Account(storage={100: 11535}), + target: Account(storage={100: 11535 + call_measure_delta}), } state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stPreCompiledContracts/test_precomps_eip2929_cancun.py b/tests/ported_static/stPreCompiledContracts/test_precomps_eip2929_cancun.py index 27dbb54b3af..cbd90e4a2a0 100644 --- a/tests/ported_static/stPreCompiledContracts/test_precomps_eip2929_cancun.py +++ b/tests/ported_static/stPreCompiledContracts/test_precomps_eip2929_cancun.py @@ -8,12 +8,14 @@ test measure the regular gas consumed by a CALL with value to an inactive precompile address. EIP-8037 replaces the Cancun-era CALL_NEW_ACCOUNT cost of 25 000 with a per-new-account state-gas -charge of `STATE_BYTES_PER_NEW_ACCOUNT (112) * COST_PER_STATE_BYTE -(1174) = 131 488`. With an empty reservoir (the case here), the -full state-gas spills back into regular gas, so `Op.GAS` reads -+106 488 (= 131 488 - 25 000) compared to Cancun. Bake that delta -into the two affected `[">=Cancun"]` expect-entries fork-condition- -ally; the third entry is gated to `["Cancun"]` only and unchanged. +charge that, with an empty reservoir (the case here), spills back +into regular gas; EIP-8038 also reprices the cold account access +from 2 600 to 3 000. `Op.GAS` therefore reads +`fork.create_state_gas() - 25 000 + COLD_ACCOUNT_ACCESS - 2 600` +extra regular gas compared to Cancun. Derive that delta from the +fork so it is 0 pre-EIP-8037 and tracks parameter changes; bake it +into the two affected `[">=Cancun"]` expect-entries. The third +entry is gated to `["Cancun"]` only and unchanged. """ import pytest @@ -3591,12 +3593,18 @@ def test_precomps_eip2929_cancun( nonce=1, ) - # EIP-8037 replaces the 25 000 CALL_NEW_ACCOUNT base cost with a - # 131 488 state-gas charge. With an empty reservoir the full - # state-gas spills into regular gas, so Op.GAS reads +106 488. + # These measurements isolate repriced components applied per + # expect-entry below: EIP-8037 replaces the 25 000 CALL_NEW_ACCOUNT + # base cost with a state-gas charge that, with an empty reservoir, + # spills back into regular gas; EIP-8038 separately reprices a cold + # account access from 2 600 to 3 000. Derive both from the fork so + # they are 0 pre-EIP-8037 and track parameter changes. `new` + # entries shift by the account delta, `no` entries by the cold + # delta, and `all` entries by both. new_account_delta = ( (fork.create_state_gas() - 25000) if fork.is_eip_enabled(8037) else 0 ) + cold_account_delta = fork.gas_costs().COLD_ACCOUNT_ACCESS - 2600 expect_entries_: list[dict] = [ { @@ -4060,7 +4068,9 @@ def test_precomps_eip2929_cancun( "value": -1, }, "network": [">=Cancun"], - "result": {target: Account(storage={0: 0, 1: 2500})}, + "result": { + target: Account(storage={0: 0, 1: 2500 + cold_account_delta}) + }, }, { "indexes": { @@ -4248,7 +4258,12 @@ def test_precomps_eip2929_cancun( }, "network": [">=Cancun"], "result": { - target: Account(storage={0: 0, 1: 27500 + new_account_delta}) + target: Account( + storage={ + 0: 0, + 1: 27500 + new_account_delta + cold_account_delta, + } + ) }, }, { diff --git a/tests/ported_static/stRefundTest/test_refund50_1.py b/tests/ported_static/stRefundTest/test_refund50_1.py index 1135b777d09..1dbb8c5ccb7 100644 --- a/tests/ported_static/stRefundTest/test_refund50_1.py +++ b/tests/ported_static/stRefundTest/test_refund50_1.py @@ -3,6 +3,16 @@ Ported from: state_tests/stRefundTest/refund50_1Filler.json + +@manually-enhanced: Do not overwrite. The post-state asserts the sender +balance, which equals its start minus `gas_used * gas_price`. The +contract clears five cold storage slots; EIP-8038 raises each cold +SSTORE-clear charge from 5000 to 13000. The EIP-3529 refund cap +(`gas_used // 5`) binds at both forks (the clear refunds far exceed a +fifth of gas used), so the extra charge raises `gas_used` by exactly +four fifths of itself. Derive the per-clear charge delta from the fork +gas model (0 pre-EIP-8037) and subtract `gas_price * 5 * delta * 4 // 5` +from the Cancun balance; do not hardcode the Amsterdam value. """ import pytest @@ -15,6 +25,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +40,7 @@ def test_refund50_1( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_refund50_1.""" coinbase = Address(0xEB201D2887816E041F6E807E804F64F3A7A226FE) @@ -65,10 +77,23 @@ def test_refund50_1( gas_limit=100000, ) + # EIP-8038 raises each cold SSTORE-clear charge and EIP-2780 + # shifts the tx intrinsic. With the EIP-3529 refund cap binding, + # gas_used rises by 4/5 of the gross-gas delta. + cold_clear_delta = ( + Op.SSTORE.with_metadata( + key_warm=False, original_value=1, current_value=1, new_value=0 + ).gas_cost(fork) + - 5000 + ) + intrinsic_delta = fork.transaction_intrinsic_cost_calculator()() - 21_000 + gross_delta = 5 * cold_clear_delta + intrinsic_delta + extra_gas_used = gross_delta * 4 // 5 + post = { target: Account(storage={}), coinbase: Account(balance=0), - sender: Account(balance=0x92F810, nonce=1), + sender: Account(balance=0x92F810 - 10 * extra_gas_used, nonce=1), } state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stRefundTest/test_refund_call_a_not_enough_gas_in_call.py b/tests/ported_static/stRefundTest/test_refund_call_a_not_enough_gas_in_call.py index def38bc9f7b..eaf1f5a8c97 100644 --- a/tests/ported_static/stRefundTest/test_refund_call_a_not_enough_gas_in_call.py +++ b/tests/ported_static/stRefundTest/test_refund_call_a_not_enough_gas_in_call.py @@ -3,6 +3,16 @@ Ported from: state_tests/stRefundTest/refund_CallA_notEnoughGasInCallFiller.json + +@manually-enhanced: Do not overwrite. The post-state asserts the sender +balance, which equals its start minus `gas_used * gas_price`. The inner +CALL is starved of gas so its SSTORE clear always reverts (no refund +survives); the only surviving repricing is in the outer frame, where +EIP-8038 raises the cold account-access charged by the CALL (2600 -> +3000) and the cold no-op SSTORE of slot 0 (2200 -> 3000). Derive both +deltas from the fork gas model (0 pre-EIP-8037) and subtract +`gas_price * (call_access_delta + outer_sstore_delta)` from the Cancun +balance; do not hardcode the Amsterdam value. """ import pytest @@ -15,6 +25,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +40,7 @@ def test_refund_call_a_not_enough_gas_in_call( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_refund_call_a_not_enough_gas_in_call.""" coinbase = Address(0xEB201D2887816E041F6E807E804F64F3A7A226FE) @@ -81,10 +93,24 @@ def test_refund_call_a_not_enough_gas_in_call( value=10, ) + # The inner SSTORE clear always reverts (gas-starved), so its refund + # never survives. Only the outer frame reprices under EIP-8038: the + # cold account access charged by the CALL and the cold no-op SSTORE + # of slot 0 (original == current == new == 0). + gas_costs = fork.gas_costs() + call_access_delta = gas_costs.COLD_ACCOUNT_ACCESS - 2600 + outer_sstore_delta = ( + Op.SSTORE.with_metadata( + key_warm=False, original_value=0, current_value=0, new_value=0 + ).gas_cost(fork) + - 2200 + ) + gas_used_delta = call_access_delta + outer_sstore_delta + post = { target: Account(storage={1: 1}, balance=0xDE0B6B3A764000A), coinbase: Account(balance=0), - sender: Account(balance=0xA8DF4, nonce=1), + sender: Account(balance=0xA8DF4 - 10 * gas_used_delta, nonce=1), addr: Account(storage={1: 1}), } diff --git a/tests/ported_static/stRefundTest/test_refund_change_non_zero_storage.py b/tests/ported_static/stRefundTest/test_refund_change_non_zero_storage.py index b82dcefae41..800ea8835ea 100644 --- a/tests/ported_static/stRefundTest/test_refund_change_non_zero_storage.py +++ b/tests/ported_static/stRefundTest/test_refund_change_non_zero_storage.py @@ -3,6 +3,16 @@ Ported from: state_tests/stRefundTest/refund_changeNonZeroStorageFiller.json + +@manually-enhanced: Do not overwrite. The post-state asserts the sender +balance, which equals its start minus `gas_used * gas_price`. The +contract resets one warm-after-cold storage slot from a non-zero value +to another non-zero value (1 -> 23); EIP-8038 raises this cold +SSTORE-reset charge from 5000 to 13000. There is no storage-clear +refund, so `gas_used` rises by exactly the charge delta. Derive that +delta from the fork gas model (0 pre-EIP-8037) and subtract +`gas_price * delta` from the Cancun balance; do not hardcode the +Amsterdam value. """ import pytest @@ -15,6 +25,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +40,7 @@ def test_refund_change_non_zero_storage( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_refund_change_non_zero_storage.""" coinbase = Address(0xEB201D2887816E041F6E807E804F64F3A7A226FE) @@ -61,10 +73,19 @@ def test_refund_change_non_zero_storage( value=10, ) + # EIP-8038 raises the cold SSTORE-reset charge (non-zero to non-zero); + # with no storage-clear refund, gas_used rises by the full delta. + cold_reset_delta = ( + Op.SSTORE.with_metadata( + key_warm=False, original_value=1, current_value=1, new_value=23 + ).gas_cost(fork) + - 5000 + ) + post = { target: Account(storage={1: 23}, balance=0xDE0B6B3A764000A), coinbase: Account(balance=0), - sender: Account(balance=0x3C2F689A, nonce=1), + sender: Account(balance=0x3C2F689A - 10 * cold_reset_delta, nonce=1), } state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stRefundTest/test_refund_ff.py b/tests/ported_static/stRefundTest/test_refund_ff.py index 06c1e9435f2..e0535dc9ab8 100644 --- a/tests/ported_static/stRefundTest/test_refund_ff.py +++ b/tests/ported_static/stRefundTest/test_refund_ff.py @@ -3,6 +3,16 @@ Ported from: state_tests/stRefundTest/refundFFFiller.yml + +@manually-enhanced: Do not overwrite. The post-state asserts the sender +balance, which equals its start minus `gas_used * gas_price`. The +contract self-destructs and sends its (zero) balance to a cold, already +existing beneficiary; EIP-8038 raises the cold account-access surcharge +from 2600 to 3000. No positive balance is moved, so no `ACCOUNT_WRITE` +applies and there is no refund, so `gas_used` rises by exactly the +SELFDESTRUCT charge delta. Derive that delta from the fork gas model +(0 pre-EIP-8037) and subtract `gas_price * delta` from the Cancun +balance; do not hardcode the Amsterdam value. """ import pytest @@ -15,6 +25,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +40,7 @@ def test_refund_ff( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Ori Pomerantz qbzzt1@gmail.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) @@ -64,6 +76,21 @@ def test_refund_ff( access_list=[], ) - post = {sender: Account(balance=0xE8D4A51000)} + # EIP-8038 raises the cold account-access surcharge applied by + # SELFDESTRUCT; with no balance transfer and no refund, gas_used + # rises by exactly this charge delta. + selfdestruct_delta = ( + Op.SELFDESTRUCT.with_metadata( + address_warm=False, account_new=False + ).gas_cost(fork) + - 7600 + ) + # EIP-2780 lowers the intrinsic for non-self non-value txs; the + # delta is negative on Amsterdam, so it reduces ``gas_used`` and + # raises the sender balance correspondingly. + intrinsic_delta = fork.transaction_intrinsic_cost_calculator()() - 21_000 + gas_used_delta = selfdestruct_delta + intrinsic_delta + + post = {sender: Account(balance=0xE8D4A51000 - 1000 * gas_used_delta)} state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stRefundTest/test_refund_get_ether_back.py b/tests/ported_static/stRefundTest/test_refund_get_ether_back.py index 81633765711..2411e062abd 100644 --- a/tests/ported_static/stRefundTest/test_refund_get_ether_back.py +++ b/tests/ported_static/stRefundTest/test_refund_get_ether_back.py @@ -3,6 +3,18 @@ Ported from: state_tests/stRefundTest/refund_getEtherBackFiller.json + +@manually-enhanced: Do not overwrite. The post-state asserts the sender +balance, which equals its start minus `gas_used * gas_price`. The +contract clears one cold storage slot (1 -> 0); EIP-8038 raises the cold +SSTORE-clear charge from 5000 to 13000 and the storage-clear refund from +4800 to 12480. The EIP-3529 refund cap (`gas_used // 5`) does not bind at +Cancun but does at Amsterdam, so the shift is modeled from the fork gas +model: reconstruct the cap-bounded `gas_used` from the fork-invariant +non-SSTORE gross gas plus the fork SSTORE charge minus the capped refund, +and subtract the same expression evaluated with the pre-repricing Cancun +charges (so the adjustment is exactly 0 pre-EIP-8037). Do not hardcode +the Amsterdam value. """ import pytest @@ -15,6 +27,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +42,7 @@ def test_refund_get_ether_back( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_refund_get_ether_back.""" coinbase = Address(0xEB201D2887816E041F6E807E804F64F3A7A226FE) @@ -61,10 +75,41 @@ def test_refund_get_ether_back( value=10, ) + # Gas used = gross gas minus the capped storage-clear refund. The + # non-SSTORE gross gas comes from the fork's intrinsic calculator + # (covers TX_BASE and any EIP-2780 recipient/value surcharges) + # plus the two PUSH1s that feed the single SSTORE (STOP is free). + gas_costs = fork.gas_costs() + # ``return_cost_deducted_prior_execution=True`` returns the + # upfront-deducted intrinsic only (Prague's calc would otherwise + # return ``max(intrinsic, EIP-7623 floor)``). + intrinsic = fork.transaction_intrinsic_cost_calculator()( + sends_value=bool(tx.value), + return_cost_deducted_prior_execution=True, + ) + base_gross = intrinsic + 2 * gas_costs.VERY_LOW + cancun_base_gross = 21_000 + 2 * gas_costs.VERY_LOW + + def clear_gas_used( + sstore_charge: int, clear_refund: int, gross_base: int + ) -> int: + gross = gross_base + sstore_charge + return gross - min(clear_refund, gross // 5) + + sstore_charge = Op.SSTORE.with_metadata( + key_warm=False, original_value=1, current_value=1, new_value=0 + ).gas_cost(fork) + # Cancun charges 5000 for the clear and refunds 4800; subtracting the + # same model evaluated at those constants and the Cancun base makes + # this exactly 0 before the EIP-8037/8038 repricing. + gas_used_delta = clear_gas_used( + sstore_charge, gas_costs.REFUND_STORAGE_CLEAR, base_gross + ) - clear_gas_used(5000, 4800, cancun_base_gross) + post = { target: Account(storage={}, balance=0xDE0B6B3A764000A), coinbase: Account(balance=0), - sender: Account(balance=0x3CF4376A, nonce=1), + sender: Account(balance=0x3CF4376A - 10 * gas_used_delta, nonce=1), } state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stRefundTest/test_refund_max.py b/tests/ported_static/stRefundTest/test_refund_max.py index 4924bc77fa2..ac549958112 100644 --- a/tests/ported_static/stRefundTest/test_refund_max.py +++ b/tests/ported_static/stRefundTest/test_refund_max.py @@ -3,6 +3,16 @@ Ported from: state_tests/stRefundTest/refundMaxFiller.yml + +@manually-enhanced: Do not overwrite. The post-state asserts the sender +balance, which equals its start minus `gas_used * gas_price`. The +contract clears eight cold storage slots; EIP-8038 raises each cold +SSTORE-clear charge from 5000 to 13000. The EIP-3529 refund cap +(`gas_used // 5`) binds at both forks (the clear refunds far exceed a +fifth of gas used), so the extra charge raises `gas_used` by exactly +four fifths of itself. Derive the per-clear charge delta from the fork +gas model (0 pre-EIP-8037) and subtract `gas_price * 8 * delta * 4 // 5` +from the Cancun balance; do not hardcode the Amsterdam value. """ import pytest @@ -15,6 +25,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +40,7 @@ def test_refund_max( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Ori Pomerantz qbzzt1@gmail.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) @@ -95,6 +107,19 @@ def test_refund_max( access_list=[], ) - post = {sender: Account(balance=0xE8D55F7E90)} + # EIP-8038 raises each cold SSTORE-clear charge and EIP-2780 + # shifts the tx intrinsic. With the EIP-3529 refund cap binding, + # gas_used rises by 4/5 of the gross-gas delta. + cold_clear_delta = ( + Op.SSTORE.with_metadata( + key_warm=False, original_value=1, current_value=1, new_value=0 + ).gas_cost(fork) + - 5000 + ) + intrinsic_delta = fork.transaction_intrinsic_cost_calculator()() - 21_000 + gross_delta = 8 * cold_clear_delta + intrinsic_delta + extra_gas_used = gross_delta * 4 // 5 + + post = {sender: Account(balance=0xE8D55F7E90 - 1000 * extra_gas_used)} state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stRefundTest/test_refund_multimple_suicide.py b/tests/ported_static/stRefundTest/test_refund_multimple_suicide.py index 015e02e5d2a..97571fbd22c 100644 --- a/tests/ported_static/stRefundTest/test_refund_multimple_suicide.py +++ b/tests/ported_static/stRefundTest/test_refund_multimple_suicide.py @@ -3,6 +3,15 @@ Ported from: state_tests/stRefundTest/refund_multimpleSuicideFiller.json + +@manually-enhanced: Do not overwrite. The post-state asserts the sender +balance, which the original fixture hardcoded as 0x61EC43A. EIP-2780 +decomposes the intrinsic cost and lowers it for non-self, non-value +txs, so the balance is derived from the fork model instead: take +`fork.transaction_intrinsic_cost_calculator()()` minus the pre-EIP-2780 +baseline 21_000, then add `gas_price (10) * |delta|` back to the sender +(the delta is negative on Amsterdam). This keeps the adjustment exactly +0 pre-EIP-2780. Do not hardcode the Amsterdam value. """ import pytest @@ -15,6 +24,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +39,7 @@ def test_refund_multimple_suicide( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_refund_multimple_suicide.""" coinbase = Address(0xEB201D2887816E041F6E807E804F64F3A7A226FE) @@ -151,10 +162,14 @@ def test_refund_multimple_suicide( gas_limit=300000, ) + # EIP-2780 lowers the intrinsic for non-self non-value txs; the + # delta is negative on Amsterdam and raises the sender balance by + # ``gas_price * |delta|``. + intrinsic_delta = fork.transaction_intrinsic_cost_calculator()() - 21_000 post = { target: Account(balance=0xDE0B6B3A7640000), coinbase: Account(balance=0), - sender: Account(balance=0x61EC43A, nonce=1), + sender: Account(balance=0x61EC43A - 10 * intrinsic_delta, nonce=1), } state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stRefundTest/test_refund_no_oog_1.py b/tests/ported_static/stRefundTest/test_refund_no_oog_1.py index 79eb43dc804..b74b450caf3 100644 --- a/tests/ported_static/stRefundTest/test_refund_no_oog_1.py +++ b/tests/ported_static/stRefundTest/test_refund_no_oog_1.py @@ -3,6 +3,17 @@ Ported from: state_tests/stRefundTest/refund_NoOOG_1Filler.json + +@manually-enhanced: Do not overwrite. The transaction supplies exactly +enough gas to clear one cold storage slot (1 -> 0) and no more (the "no +out-of-gas" boundary). EIP-8038 raises the cold SSTORE-clear charge from +5000 to 13000, so the gas limit must rise by that charge delta to keep +the slot clearing instead of running out of gas. The asserted sender +balance equals its start minus `gas_used * gas_price`, and `gas_used` +is the gross gas minus the storage-clear refund (capped by EIP-3529 only +at Amsterdam). Both the gas limit bump and the balance shift are derived +from the fork gas model and are exactly 0 pre-EIP-8037; do not hardcode +the Amsterdam values. """ import pytest @@ -15,6 +26,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +41,7 @@ def test_refund_no_oog_1( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_refund_no_oog_1.""" coinbase = Address(0xEB201D2887816E041F6E807E804F64F3A7A226FE) @@ -53,17 +66,52 @@ def test_refund_no_oog_1( nonce=0, ) + # EIP-8038 raises the cold SSTORE-clear charge and EIP-2780 shifts + # the tx intrinsic; bump the gas limit by both deltas so the clear + # still lands exactly at the limit (the "no out-of-gas" boundary) + # instead of running out of gas. + sstore_charge = Op.SSTORE.with_metadata( + key_warm=False, original_value=1, current_value=1, new_value=0 + ).gas_cost(fork) + cold_clear_delta = sstore_charge - 5000 + # ``return_cost_deducted_prior_execution=True`` returns the + # upfront-deducted intrinsic only (Prague's calc would otherwise + # return ``max(intrinsic, EIP-7623 floor)``). + intrinsic = fork.transaction_intrinsic_cost_calculator()( + return_cost_deducted_prior_execution=True, + ) + intrinsic_delta = intrinsic - 21_000 + tx = Transaction( sender=sender, to=target, data=Bytes(""), - gas_limit=26006, + gas_limit=26006 + cold_clear_delta + intrinsic_delta, ) + # Gas used = gross gas minus the capped storage-clear refund. The + # non-SSTORE gross gas comes from the fork's intrinsic calculator + # (covers TX_BASE and any EIP-2780 recipient surcharge) plus the + # two PUSH1s that feed the single SSTORE (STOP is free). + gas_costs = fork.gas_costs() + base_gross = intrinsic + 2 * gas_costs.VERY_LOW + cancun_base_gross = 21_000 + 2 * gas_costs.VERY_LOW + + def clear_gas_used(charge: int, clear_refund: int, gross_base: int) -> int: + gross = gross_base + charge + return gross - min(clear_refund, gross // 5) + + # Cancun charges 5000 for the clear and refunds 4800; subtracting the + # same model evaluated at those constants and the Cancun base makes + # this exactly 0 before the EIP-8037/8038 repricing. + gas_used_delta = clear_gas_used( + sstore_charge, gas_costs.REFUND_STORAGE_CLEAR, base_gross + ) - clear_gas_used(5000, 4800, cancun_base_gross) + post = { target: Account(storage={}), coinbase: Account(balance=0), - sender: Account(balance=0x9D0314, nonce=1), + sender: Account(balance=0x9D0314 - 10 * gas_used_delta, nonce=1), } state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stRefundTest/test_refund_single_suicide.py b/tests/ported_static/stRefundTest/test_refund_single_suicide.py index 4cee829af0d..a1da9e3e582 100644 --- a/tests/ported_static/stRefundTest/test_refund_single_suicide.py +++ b/tests/ported_static/stRefundTest/test_refund_single_suicide.py @@ -3,6 +3,15 @@ Ported from: state_tests/stRefundTest/refund_singleSuicideFiller.json + +@manually-enhanced: Do not overwrite. The post-state asserts the sender +balance, which the original fixture hardcoded. EIP-2780 decomposes the +intrinsic and lowers it for this non-self, non-value tx, so the balance +is derived from the fork: ``intrinsic_delta`` subtracts the pre-EIP-2780 +baseline intrinsic 21_000 from the fork's intrinsic calculator (the +literal 21_000 is the old TX_BASE), making the delta 0 pre-EIP-2780 and +negative on Amsterdam. The sender balance is then adjusted by +``gas_price * intrinsic_delta`` (base fee 10). Do not hardcode it. """ import pytest @@ -15,6 +24,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +39,7 @@ def test_refund_single_suicide( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_refund_single_suicide.""" coinbase = Address(0xEB201D2887816E041F6E807E804F64F3A7A226FE) @@ -126,10 +137,14 @@ def test_refund_single_suicide( gas_limit=300000, ) + # EIP-2780 lowers the intrinsic for non-self non-value txs; the + # delta is negative on Amsterdam and raises the sender balance by + # ``gas_price * |delta|``. + intrinsic_delta = fork.transaction_intrinsic_cost_calculator()() - 21_000 post = { target: Account(balance=0xDE0B6B3A7640000), coinbase: Account(balance=0), - sender: Account(balance=0x1C5AF34, nonce=1), + sender: Account(balance=0x1C5AF34 - 10 * intrinsic_delta, nonce=1), } state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stRefundTest/test_refund_sstore.py b/tests/ported_static/stRefundTest/test_refund_sstore.py index 9a8cd6f7ddc..732f909022e 100644 --- a/tests/ported_static/stRefundTest/test_refund_sstore.py +++ b/tests/ported_static/stRefundTest/test_refund_sstore.py @@ -3,6 +3,18 @@ Ported from: state_tests/stRefundTest/refundSSTOREFiller.yml + +@manually-enhanced: Do not overwrite. The post-state asserts the sender +balance, which equals its start minus `gas_used * gas_price`. The +contract clears one cold storage slot (non-zero -> 0); EIP-8038 raises +the cold SSTORE-clear charge from 5000 to 13000 and the storage-clear +refund from 4800 to 12480. The EIP-3529 refund cap (`gas_used // 5`) does +not bind at Cancun but does at Amsterdam, so the shift is modeled from +the fork gas model: reconstruct the cap-bounded `gas_used` from the +fork-invariant non-SSTORE gross gas plus the fork SSTORE charge minus the +capped refund, and subtract the same expression evaluated with the +pre-repricing Cancun charges (so the adjustment is exactly 0 +pre-EIP-8037). Do not hardcode the Amsterdam value. """ import pytest @@ -15,6 +27,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +42,7 @@ def test_refund_sstore( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Ori Pomerantz qbzzt1@gmail.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) @@ -65,6 +79,42 @@ def test_refund_sstore( access_list=[], ) - post = {sender: Account(balance=0xE8D4EE4E00)} + # Gas used = gross gas minus the capped storage-clear refund. The + # non-SSTORE gross gas comes from the fork's intrinsic calculator + # (covers TX_BASE, calldata, and any EIP-2780 recipient surcharge) + # plus the PUSH1 and DUP1 that feed the SSTORE (STOP is free). + gas_costs = fork.gas_costs() + # ``return_cost_deducted_prior_execution=True`` returns the + # upfront-deducted intrinsic only. Without it, Prague's + # ``intrinsic_calc`` returns ``max(intrinsic, EIP-7623 floor)`` — + # the floor only binds for data-heavy txs with little execution, + # which is not the case here. + intrinsic = fork.transaction_intrinsic_cost_calculator()( + calldata=tx.data, + return_cost_deducted_prior_execution=True, + ) + base_gross = intrinsic + 2 * gas_costs.VERY_LOW + # Cancun's intrinsic for this tx shape was 21_004 (TX_BASE + + # single zero-byte). Capture it as the baseline so the Cancun + # branch of ``gas_used_delta`` evaluates at the original base. + cancun_base_gross = 21_004 + 2 * gas_costs.VERY_LOW + + def clear_gas_used( + sstore_charge: int, clear_refund: int, gross_base: int + ) -> int: + gross = gross_base + sstore_charge + return gross - min(clear_refund, gross // 5) + + sstore_charge = Op.SSTORE.with_metadata( + key_warm=False, original_value=24743, current_value=24743, new_value=0 + ).gas_cost(fork) + # Cancun charges 5000 for the clear and refunds 4800; subtracting the + # same model evaluated at those constants and the Cancun base makes + # this exactly 0 before the EIP-8037/8038 repricing. + gas_used_delta = clear_gas_used( + sstore_charge, gas_costs.REFUND_STORAGE_CLEAR, base_gross + ) - clear_gas_used(5000, 4800, cancun_base_gross) + + post = {sender: Account(balance=0xE8D4EE4E00 - 1000 * gas_used_delta)} state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stRevertTest/test_revert_opcode_calls.py b/tests/ported_static/stRevertTest/test_revert_opcode_calls.py index 1089825aeca..e8dd0e77288 100644 --- a/tests/ported_static/stRevertTest/test_revert_opcode_calls.py +++ b/tests/ported_static/stRevertTest/test_revert_opcode_calls.py @@ -5,7 +5,13 @@ state_tests/stRevertTest/RevertOpcodeCallsFiller.json @manually-enhanced: Do not overwrite. Gas bumped fork-conditionally to cover EIP-8037 state-gas spill into regular gas; pre-EIP-8037 -behavior unchanged. +behavior unchanged. The d3 call chain ends in a fresh SSTORE-set in +the outermost (transaction) frame; with an empty state-gas reservoir +that set's state gas spills into regular gas, so the success path +(g=0) runs out at the final `SSTORE` unless the outer budget absorbs +the spill. Lift `tx_gas[0]` by one fresh-set SSTORE state cost via +`fork.oog_budget_lift`, which is exactly 0 pre-EIP-8037 and tracks +the parameter. g=1 (the OoG case) keeps the original budget. """ @@ -334,7 +340,12 @@ def test_revert_opcode_calls( Hash(addr_3, left_padding=True), Hash(addr_4, left_padding=True), ] - tx_gas = [460000, 83622] + # The g=0 success path bottoms out on a fresh SSTORE-set in the + # transaction frame whose EIP-8037 state gas spills (empty + # reservoir). Lift the outer budget by that spilled state cost so + # the chain still completes on Amsterdam; 0 pre-EIP-8037. + g0_lift = fork.oog_budget_lift(sstores_before_oog=1) + tx_gas = [460000 + g0_lift, 83622] tx = Transaction( sender=sender, diff --git a/tests/ported_static/stSpecialTest/test_eoa_empty_paris.py b/tests/ported_static/stSpecialTest/test_eoa_empty_paris.py index 5c93c366823..b6e28c96397 100644 --- a/tests/ported_static/stSpecialTest/test_eoa_empty_paris.py +++ b/tests/ported_static/stSpecialTest/test_eoa_empty_paris.py @@ -3,6 +3,14 @@ Ported from: state_tests/stSpecialTest/eoaEmptyParisFiller.yml + +@manually-enhanced: Do not overwrite. Two measured slots shift under +EIP-8038. Slot 0xF1 times a CALL that forwards `value` to the (warm) +origin EOA: when `value` is nonzero it gains the value-transfer +reprice `CALL_VALUE - 9000`; the value-0 cases are unchanged. Slot +0xFF times a value-0 CALL to a cold contract and gains the cold +account reprice `COLD_ACCOUNT_ACCESS - 2600`. Both deltas come from +the fork's own gas model, so each is exactly 0 before EIP-8038. """ import pytest @@ -97,6 +105,13 @@ def test_eoa_empty_paris( v: int, ) -> None: """Test_eoa_empty_paris.""" + # EIP-8038 deltas, each 0 before EIP-8038. Slot 0xF1's value-bearing + # CALL to the warm origin gains the value-transfer reprice; slot + # 0xFF's value-0 CALL to a cold contract gains the cold account + # reprice. + gas_costs = fork.gas_costs() + call_value_delta = gas_costs.CALL_VALUE - 9000 + cold_account_delta = gas_costs.COLD_ACCOUNT_ACCESS - 2600 coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) contract_0 = Address(0x000000000000000000000000000000000000BAD1) contract_1 = Address(0x000000000000000000000000000000000000BAD2) @@ -244,7 +259,7 @@ def test_eoa_empty_paris( 59: 0, 63: 0xC5D2460186F7233C927E7DB2DCC703C0E500B653CA82273B7BFAD8045D85A470, # noqa: E501 241: 118, - 255: 7626, + 255: 7626 + cold_account_delta, 319: 0, 47825: 0xC5D2460186F7233C927E7DB2DCC703C0E500B653CA82273B7BFAD8045D85A470, # noqa: E501 47826: 0xC5D2460186F7233C927E7DB2DCC703C0E500B653CA82273B7BFAD8045D85A470, # noqa: E501 @@ -265,8 +280,8 @@ def test_eoa_empty_paris( 49: 0, 59: 0, 63: 0xC5D2460186F7233C927E7DB2DCC703C0E500B653CA82273B7BFAD8045D85A470, # noqa: E501 - 241: 6818, - 255: 7626, + 241: 6818 + call_value_delta, + 255: 7626 + cold_account_delta, 319: 0, 47825: 0xC5D2460186F7233C927E7DB2DCC703C0E500B653CA82273B7BFAD8045D85A470, # noqa: E501 47826: 0xC5D2460186F7233C927E7DB2DCC703C0E500B653CA82273B7BFAD8045D85A470, # noqa: E501 @@ -296,7 +311,7 @@ def test_eoa_empty_paris( 59: 0, 63: 0xC5D2460186F7233C927E7DB2DCC703C0E500B653CA82273B7BFAD8045D85A470, # noqa: E501 241: 118, - 255: 7626, + 255: 7626 + cold_account_delta, 319: 0, 47825: 0xC5D2460186F7233C927E7DB2DCC703C0E500B653CA82273B7BFAD8045D85A470, # noqa: E501 47826: 0xC5D2460186F7233C927E7DB2DCC703C0E500B653CA82273B7BFAD8045D85A470, # noqa: E501 @@ -317,8 +332,8 @@ def test_eoa_empty_paris( 49: 100, 59: 0, 63: 0xC5D2460186F7233C927E7DB2DCC703C0E500B653CA82273B7BFAD8045D85A470, # noqa: E501 - 241: 6818, - 255: 7626, + 241: 6818 + call_value_delta, + 255: 7626 + cold_account_delta, 319: 0, 47825: 0xC5D2460186F7233C927E7DB2DCC703C0E500B653CA82273B7BFAD8045D85A470, # noqa: E501 47826: 0xC5D2460186F7233C927E7DB2DCC703C0E500B653CA82273B7BFAD8045D85A470, # noqa: E501 @@ -340,7 +355,7 @@ def test_eoa_empty_paris( 59: 0, 63: 0xC5D2460186F7233C927E7DB2DCC703C0E500B653CA82273B7BFAD8045D85A470, # noqa: E501 241: 118, - 255: 7626, + 255: 7626 + cold_account_delta, 319: 0, 47825: 0xC5D2460186F7233C927E7DB2DCC703C0E500B653CA82273B7BFAD8045D85A470, # noqa: E501 47826: 0xC5D2460186F7233C927E7DB2DCC703C0E500B653CA82273B7BFAD8045D85A470, # noqa: E501 @@ -361,8 +376,8 @@ def test_eoa_empty_paris( 49: 0, 59: 0, 63: 0xC5D2460186F7233C927E7DB2DCC703C0E500B653CA82273B7BFAD8045D85A470, # noqa: E501 - 241: 6818, - 255: 7626, + 241: 6818 + call_value_delta, + 255: 7626 + cold_account_delta, 319: 0, 47825: 0xC5D2460186F7233C927E7DB2DCC703C0E500B653CA82273B7BFAD8045D85A470, # noqa: E501 47826: 0xC5D2460186F7233C927E7DB2DCC703C0E500B653CA82273B7BFAD8045D85A470, # noqa: E501 diff --git a/tests/ported_static/stStaticCall/test_static_call_change_revert.py b/tests/ported_static/stStaticCall/test_static_call_change_revert.py index 2d6b7295118..c13ba330562 100644 --- a/tests/ported_static/stStaticCall/test_static_call_change_revert.py +++ b/tests/ported_static/stStaticCall/test_static_call_change_revert.py @@ -3,22 +3,18 @@ Ported from: state_tests/stStaticCall/static_callChangeRevertFiller.json + +@manually-enhanced: Do not overwrite. """ import pytest from execution_testing import ( Account, - Address, Alloc, - Environment, - Hash, StateTestFiller, + Storage, Transaction, ) -from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( - resolve_expect_post, -) from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,267 +25,55 @@ ["state_tests/stStaticCall/static_callChangeRevertFiller.json"], ) @pytest.mark.valid_from("Cancun") -@pytest.mark.slow @pytest.mark.parametrize( - "d, g, v", + "sstore_in_static,oog", [ - pytest.param( - 0, - 0, - 0, - id="d0", - ), - pytest.param( - 1, - 0, - 0, - id="d1", - ), - pytest.param( - 2, - 0, - 0, - id="d2", - ), + pytest.param(False, False), + pytest.param(False, True), + pytest.param(True, False), ], ) -@pytest.mark.pre_alloc_mutable def test_static_call_change_revert( state_test: StateTestFiller, pre: Alloc, - fork: Fork, - d: int, - g: int, - v: int, + sstore_in_static: bool, + oog: bool, ) -> None: """Test_static_call_change_revert.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) + sender = pre.fund_eoa() - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) + subcall_code = Op.MSTORE(offset=0x1, value=0x1) + if sstore_in_static: + subcall_code += Op.SSTORE(key=0x1, value=Op.SLOAD(key=0x1)) + subcall_code += Op.STOP + subcall_contract = pre.deploy_contract(subcall_code) - # Source: lll - # { (CALL 350000 (CALLDATALOAD 0) 0 0 0 0 0) } - target = pre.deploy_contract( # noqa: F841 - code=Op.CALL( - gas=0x55730, - address=Op.CALLDATALOAD(offset=0x0), - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ) - + Op.STOP, - balance=0xDE0B6B3A7640000, - nonce=0, - address=Address(0x492BB18ADCE7DA2BED3592742FB4E3DF9086FB4C), # noqa: E501 - ) - # Source: lll - # { (MSTORE 1 1) } - addr_2 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x1, value=0x1) + Op.STOP, - nonce=0, - address=Address(0xC031FC0AA7B61A5D7D962AFEE8838DEC6948ABB7), # noqa: E501 - ) - # Source: lll - # { (MSTORE 1 1) (SSTORE 1 (SLOAD 1)) } - addr_5 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x1, value=0x1) - + Op.SSTORE(key=0x1, value=Op.SLOAD(key=0x1)) - + Op.STOP, - nonce=0, - address=Address(0x47C4ED3D93429CB8304737E2327B522E8928C9F3), # noqa: E501 - ) - # Source: lll - # { [[ 0 ]] (CALL 100000 1 0 0 0 0) [[ 1 ]] (STATICCALL 100000 0 0 0 0) [[ 2 ]] (CALL 100000 1 0 0 0 0) } # noqa: E501 - addr = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE( - key=0x0, - value=Op.CALL( - gas=0x186A0, - address=0xC031FC0AA7B61A5D7D962AFEE8838DEC6948ABB7, - value=0x1, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.SSTORE( - key=0x1, - value=Op.STATICCALL( - gas=0x186A0, - address=0xC031FC0AA7B61A5D7D962AFEE8838DEC6948ABB7, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.SSTORE( - key=0x2, - value=Op.CALL( - gas=0x186A0, - address=0xC031FC0AA7B61A5D7D962AFEE8838DEC6948ABB7, - value=0x1, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.STOP, - balance=0xDE0B6B3A7640000, - nonce=0, - address=Address(0xE6F1FDAA1C99007971C641E10AF3A8FAC0B641C8), # noqa: E501 - ) - # Source: lll - # { [[ 0 ]] (CALL 100000 1 0 0 0 0) [[ 1 ]] (STATICCALL 100000 0 0 0 0) [[ 2 ]] (CALL 100000 1 0 0 0 0) (def 'i 0x80) (for {} (< @i 50000) [i](+ @i 1) (EXTCODESIZE 1)) } # noqa: E501 - addr_3 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE( - key=0x0, - value=Op.CALL( - gas=0x186A0, - address=0xC031FC0AA7B61A5D7D962AFEE8838DEC6948ABB7, - value=0x1, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), + caller_storage = Storage() + caller_code = ( + Op.SSTORE( + key=caller_storage.store_next(not oog), + value=Op.CALL(address=subcall_contract, value=0x1), ) + Op.SSTORE( - key=0x1, - value=Op.STATICCALL( - gas=0x186A0, - address=0xC031FC0AA7B61A5D7D962AFEE8838DEC6948ABB7, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), + key=caller_storage.store_next(not oog and not sstore_in_static), + value=Op.STATICCALL(address=subcall_contract), ) + Op.SSTORE( - key=0x2, - value=Op.CALL( - gas=0x186A0, - address=0xC031FC0AA7B61A5D7D962AFEE8838DEC6948ABB7, - value=0x1, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.JUMPDEST - + Op.JUMPI( - pc=0x8F, condition=Op.ISZERO(Op.LT(Op.MLOAD(offset=0x80), 0xC350)) + key=caller_storage.store_next(not oog), + value=Op.CALL(address=subcall_contract, value=0x1), ) - + Op.POP(Op.EXTCODESIZE(address=0x1)) - + Op.MSTORE(offset=0x80, value=Op.ADD(Op.MLOAD(offset=0x80), 0x1)) - + Op.JUMP(pc=0x73) - + Op.JUMPDEST - + Op.STOP, - balance=0xDE0B6B3A7640000, - nonce=0, - address=Address(0xEA22EC955AC71D8E4380541212BD20818D704567), # noqa: E501 - ) - # Source: lll - # { [[ 0 ]] (CALL 100000 1 0 0 0 0) [[ 1 ]] (STATICCALL 100000 0 0 0 0) [[ 2 ]] (CALL 100000 1 0 0 0 0) } # noqa: E501 - addr_4 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE( - key=0x0, - value=Op.CALL( - gas=0x186A0, - address=0x47C4ED3D93429CB8304737E2327B522E8928C9F3, - value=0x1, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.SSTORE( - key=0x1, - value=Op.STATICCALL( - gas=0x186A0, - address=0x47C4ED3D93429CB8304737E2327B522E8928C9F3, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.SSTORE( - key=0x2, - value=Op.CALL( - gas=0x186A0, - address=0x47C4ED3D93429CB8304737E2327B522E8928C9F3, - value=0x1, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.STOP, - balance=0xDE0B6B3A7640000, - nonce=0, - address=Address(0x2C004389EDAAE817E664B6D660F46735756B56D3), # noqa: E501 ) + if oog: + caller_code += Op.MLOAD(2**256 - 1) + caller_code += Op.STOP - expect_entries_: list[dict] = [ - { - "indexes": {"data": 0, "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": { - addr: Account(storage={0: 1, 1: 1, 2: 1}), - addr_2: Account(balance=2), - }, - }, - { - "indexes": {"data": 1, "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": { - addr_3: Account(storage={0: 0, 1: 0, 2: 0}), - addr_2: Account(balance=0), - }, - }, - { - "indexes": {"data": 2, "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": { - addr_4: Account(storage={0: 1, 1: 0, 2: 1}), - addr_5: Account(balance=2), - }, - }, - ] + caller_contract = pre.deploy_contract(caller_code, balance=2) - post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork) + post = { + caller_contract: Account(storage=caller_storage), + subcall_contract: Account(balance=2 if not oog else 0), + } - tx_data = [ - Hash(addr, left_padding=True), - Hash(addr_3, left_padding=True), - Hash(addr_4, left_padding=True), - ] - tx_gas = [1000000] - tx_value = [100000] - - tx = Transaction( - sender=sender, - to=target, - data=tx_data[d], - gas_limit=tx_gas[g], - value=tx_value[v], - error=_exc, - ) + tx = Transaction(sender=sender, to=caller_contract) - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stStaticCall/test_static_make_money.py b/tests/ported_static/stStaticCall/test_static_make_money.py index add8a413112..4a921cd29f6 100644 --- a/tests/ported_static/stStaticCall/test_static_make_money.py +++ b/tests/ported_static/stStaticCall/test_static_make_money.py @@ -3,18 +3,12 @@ Ported from: state_tests/stStaticCall/static_makeMoneyFiller.json + +@manually-enhanced: Do not overwrite. """ import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, -) +from execution_testing import Account, Alloc, StateTestFiller, Transaction from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -25,65 +19,47 @@ ["state_tests/stStaticCall/static_makeMoneyFiller.json"], ) @pytest.mark.valid_from("Cancun") -@pytest.mark.slow -@pytest.mark.pre_alloc_mutable def test_static_make_money( state_test: StateTestFiller, pre: Alloc, ) -> None: """Test_static_make_money.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0x5F5E100) + sender = pre.fund_eoa() - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=1000000, - ) + contracts_starting_balance = 1 - # Source: raw - # 0x600160015532600255 - addr = pre.deploy_contract( # noqa: F841 + subcall_contract = pre.deploy_contract( # noqa: F841 code=Op.SSTORE(key=0x1, value=0x1) + Op.SSTORE(key=0x2, value=Op.ORIGIN), - balance=0xDE0B6B3A7640000, - nonce=0, + balance=contracts_starting_balance, ) - # Source: lll - # { (MSTORE 0 0x601080600c6000396000f20060003554156009570060203560003555) (STATICCALL 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffec 0 0 0 0) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 + entry_contract = pre.deploy_contract( # noqa: F841 code=Op.MSTORE( offset=0x0, value=0x601080600C6000396000F20060003554156009570060203560003555, ) + Op.STATICCALL( - gas=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEC, # noqa: E501 - address=addr, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, + gas=2**256 - 20, + address=subcall_contract, ) + Op.STOP, - balance=0xDE0B6B3A7640000, - nonce=0, + balance=contracts_starting_balance, ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=228500, - value=10, - ) + tx_value = 1 + tx = Transaction(sender=sender, to=entry_contract, value=tx_value) post = { - target: Account(balance=0xDE0B6B3A764000A), - sender: Account(balance=0x5D38038), - addr: Account(balance=0xDE0B6B3A7640000), + entry_contract: Account( + balance=contracts_starting_balance + tx_value, + ), + subcall_contract: Account( + balance=contracts_starting_balance, + storage={ + 1: 0, + 2: 0, + }, + ), } - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stStaticCall/test_static_raw_call_gas_ask.py b/tests/ported_static/stStaticCall/test_static_raw_call_gas_ask.py index 0ea735ae867..fa31ff1b4e6 100644 --- a/tests/ported_static/stStaticCall/test_static_raw_call_gas_ask.py +++ b/tests/ported_static/stStaticCall/test_static_raw_call_gas_ask.py @@ -8,17 +8,12 @@ import pytest from execution_testing import ( Account, - Address, Alloc, - Environment, - Hash, + CodeGasMeasure, StateTestFiller, Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( - resolve_expect_post, -) from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,198 +26,48 @@ @pytest.mark.valid_from("Cancun") @pytest.mark.slow @pytest.mark.parametrize( - "d, g, v", + "mem_expansion", [ - pytest.param( - 0, - 0, - 0, - id="d0", - ), - pytest.param( - 1, - 0, - 0, - id="d1", - ), - pytest.param( - 2, - 0, - 0, - id="d2", - ), - pytest.param( - 3, - 0, - 0, - id="d3", - ), + pytest.param(False, id="without_mem_expansion"), + pytest.param(True, id="with_mem_expansion"), ], ) @pytest.mark.pre_alloc_mutable def test_static_raw_call_gas_ask( state_test: StateTestFiller, pre: Alloc, + mem_expansion: bool, fork: Fork, - d: int, - g: int, - v: int, ) -> None: """Test_static_raw_call_gas_ask.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0x094F5374FCE5EDBC8E2A8697C15331677E6EBF0B) - contract_1 = Address(0x1000000000000000000000000000000000000000) - contract_2 = Address(0x1000000000000000000000000000000000000001) - contract_3 = Address(0x2000000000000000000000000000000000000001) - contract_4 = Address(0x3000000000000000000000000000000000000001) - contract_5 = Address(0x4000000000000000000000000000000000000001) - sender = pre.fund_eoa(amount=0xE8D4A51000) + sender = pre.fund_eoa() - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) + subcall_code = Op.MSTORE(0, Op.GAS, new_memory_size=32) + Op.STOP + subcall_contract = pre.deploy_contract(code=subcall_code) - # Source: lll - # { (MSTORE 0 (GAS)) } - contract_0 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.GAS) + Op.STOP, - nonce=0, - address=Address(0x094F5374FCE5EDBC8E2A8697C15331677E6EBF0B), # noqa: E501 - ) - # Source: lll - # { (CALL (GAS) (CALLDATALOAD 0) 0 0 0 0 0) } - contract_1 = pre.deploy_contract( # noqa: F841 - code=Op.CALL( - gas=Op.GAS, - address=Op.CALLDATALOAD(offset=0x0), - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ) - + Op.STOP, - balance=0xE8D4A51000, - nonce=0, - address=Address(0x1000000000000000000000000000000000000000), # noqa: E501 - ) - # Source: lll - # { (STATICCALL 130000 0x094f5374fce5edbc8e2a8697c15331677e6ebf0b 0 0 0 0) [[1]] (GAS) } # noqa: E501 - contract_3 = pre.deploy_contract( # noqa: F841 - code=Op.POP( - Op.STATICCALL( - gas=0x1FBD0, - address=0x94F5374FCE5EDBC8E2A8697C15331677E6EBF0B, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ) + mem_expansion_size = 0x1F40 + static_call_code = ( + Op.STATICCALL( + address=subcall_contract, + args_size=mem_expansion_size, + ret_size=mem_expansion_size, + new_memory_size=mem_expansion_size, ) - + Op.SSTORE(key=0x1, value=Op.GAS) - + Op.STOP, - nonce=0, - address=Address(0x2000000000000000000000000000000000000001), # noqa: E501 - ) - # Source: lll - # { (STATICCALL 130000 0x094f5374fce5edbc8e2a8697c15331677e6ebf0b 0 8000 0 8000) [[1]] (GAS) } # noqa: E501 - contract_5 = pre.deploy_contract( # noqa: F841 - code=Op.POP( - Op.STATICCALL( - gas=0x1FBD0, - address=0x94F5374FCE5EDBC8E2A8697C15331677E6EBF0B, - args_offset=0x0, - args_size=0x1F40, - ret_offset=0x0, - ret_size=0x1F40, - ) - ) - + Op.SSTORE(key=0x1, value=Op.GAS) - + Op.STOP, - nonce=0, - address=Address(0x4000000000000000000000000000000000000001), # noqa: E501 - ) - # Source: lll - # { (STATICCALL 3000000 0x094f5374fce5edbc8e2a8697c15331677e6ebf0b 0 8000 0 8000) [[1]] (GAS) } # noqa: E501 - contract_4 = pre.deploy_contract( # noqa: F841 - code=Op.POP( - Op.STATICCALL( - gas=0x2DC6C0, - address=0x94F5374FCE5EDBC8E2A8697C15331677E6EBF0B, - args_offset=0x0, - args_size=0x1F40, - ret_offset=0x0, - ret_size=0x1F40, - ) + if mem_expansion + else Op.STATICCALL( + address=subcall_contract, ) - + Op.SSTORE(key=0x1, value=Op.GAS) - + Op.STOP, - nonce=0, - address=Address(0x3000000000000000000000000000000000000001), # noqa: E501 ) - # Source: lll - # { (STATICCALL 3000000 0x094f5374fce5edbc8e2a8697c15331677e6ebf0b 0 0 0 0) [[1]] (GAS) } # noqa: E501 - contract_2 = pre.deploy_contract( # noqa: F841 - code=Op.POP( - Op.STATICCALL( - gas=0x2DC6C0, - address=0x94F5374FCE5EDBC8E2A8697C15331677E6EBF0B, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ) - ) - + Op.SSTORE(key=0x1, value=Op.GAS) - + Op.STOP, - nonce=0, - address=Address(0x1000000000000000000000000000000000000001), # noqa: E501 + static_call_contract = pre.deploy_contract( + code=CodeGasMeasure( + code=static_call_code, + extra_stack_items=1, + sstore_key=1, + ), ) - expect_entries_: list[dict] = [ - { - "indexes": {"data": 0, "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {contract_2: Account(storage={1: 0xE9F83})}, - }, - { - "indexes": {"data": 1, "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {contract_3: Account(storage={1: 0xE9F83})}, - }, - { - "indexes": {"data": 2, "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {contract_4: Account(storage={1: 0xE9C1B})}, - }, - { - "indexes": {"data": 3, "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {contract_5: Account(storage={1: 0xE9C1B})}, - }, - ] - - post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork) - - tx_data = [ - Hash(contract_2, left_padding=True), - Hash(contract_3, left_padding=True), - Hash(contract_4, left_padding=True), - Hash(contract_5, left_padding=True), - ] - tx_gas = [1000000] - - tx = Transaction( - sender=sender, - to=contract_1, - data=tx_data[d], - gas_limit=tx_gas[g], - error=_exc, - ) + gas_cost = static_call_code.gas_cost(fork) + subcall_code.gas_cost(fork) + post = {static_call_contract: Account(storage={1: gas_cost})} + tx = Transaction(sender=sender, to=static_call_contract) - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stTransactionTest/test_contract_store_clears_success.py b/tests/ported_static/stTransactionTest/test_contract_store_clears_success.py index 90f1ca0d067..a61f620fb02 100644 --- a/tests/ported_static/stTransactionTest/test_contract_store_clears_success.py +++ b/tests/ported_static/stTransactionTest/test_contract_store_clears_success.py @@ -3,6 +3,18 @@ Ported from: state_tests/stTransactionTest/ContractStoreClearsSuccessFiller.json + +@manually-enhanced: Do not overwrite. The contract clears 10 cold +storage slots (each 12 -> 0) and the transaction sends value alongside, +so the asserted post is the cleared storage plus the received value. +EIP-8038 raises the cold SSTORE-clear charge from 5000 to 13000, so the +10 clears no longer fit in the original gas limit and the contract runs +out of gas before clearing the storage or keeping the transfer. Bump the +gas limit by the per-clear charge delta times the 10 clears so every +clear still lands at Amsterdam. The delta is derived from the fork gas +model and is exactly 0 pre-EIP-8037; do not hardcode the Amsterdam +value. The post asserts only the target account (cleared storage and the +received value), which holds at every fork once the gas fits. """ import pytest @@ -15,6 +27,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -29,6 +42,7 @@ def test_contract_store_clears_success( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_contract_store_clears_success.""" coinbase = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B) @@ -72,11 +86,21 @@ def test_contract_store_clears_success( nonce=0, ) + # EIP-8038 raises the cold SSTORE-clear charge; bump the gas limit by + # the per-clear charge delta times the 10 clears so all of them still + # land instead of running out of gas before clearing the storage. + cold_clear_delta = ( + Op.SSTORE.with_metadata( + key_warm=False, original_value=1, current_value=1, new_value=0 + ).gas_cost(fork) + - 5000 + ) + tx = Transaction( sender=sender, to=target, data=Bytes(""), - gas_limit=130000, + gas_limit=130000 + 10 * cold_clear_delta, value=10, ) diff --git a/tests/ported_static/stTransactionTest/test_high_gas_limit.py b/tests/ported_static/stTransactionTest/test_high_gas_limit.py index db5ce28e428..60069018353 100644 --- a/tests/ported_static/stTransactionTest/test_high_gas_limit.py +++ b/tests/ported_static/stTransactionTest/test_high_gas_limit.py @@ -3,6 +3,13 @@ Ported from: state_tests/stTransactionTest/HighGasLimitFiller.json + +@manually-enhanced: Do not overwrite. The tx sends value to an empty +recipient, so EIP-2780 charges ``NEW_ACCOUNT`` state gas at the top +frame; with the default zero state-gas reservoir that charge spills into +regular gas. Instead of the original hardcoded ``gas_limit``, lift the +100000 base by ``fork.transaction_top_frame_state_gas`` so the budget +covers the spillover and stays exactly 0 on pre-EIP-2780 forks. """ import pytest @@ -13,6 +20,8 @@ Alloc, Bytes, Environment, + Fork, + RecipientType, StateTestFiller, Transaction, ) @@ -29,6 +38,7 @@ def test_high_gas_limit( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_high_gas_limit.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) @@ -49,11 +59,19 @@ def test_high_gas_limit( balance=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF # noqa: E501 ) + # EIP-2780 charges ``NEW_ACCOUNT`` state gas at the top frame when + # value is sent to an empty recipient; with the default zero + # state-gas reservoir that charge spills into regular gas, so lift + # ``gas_limit`` by exactly that amount (0 on pre-EIP-2780 forks). + top_frame_state_gas = fork.transaction_top_frame_state_gas( + recipient_type=RecipientType.EMPTY_ACCOUNT, + sends_value=True, + ) tx = Transaction( sender=sender, to=Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B), data=Bytes("3240349548983454"), - gas_limit=100000, + gas_limit=100000 + top_frame_state_gas, value=900, ) diff --git a/tests/ported_static/stTransactionTest/test_internal_call_store_clears_success.py b/tests/ported_static/stTransactionTest/test_internal_call_store_clears_success.py index bdd47e999a2..e699a350a8d 100644 --- a/tests/ported_static/stTransactionTest/test_internal_call_store_clears_success.py +++ b/tests/ported_static/stTransactionTest/test_internal_call_store_clears_success.py @@ -3,6 +3,20 @@ Ported from: state_tests/stTransactionTest/InternalCallStoreClearsSuccessFiller.json + +@manually-enhanced: Do not overwrite. The `target` contract forwards a +fixed `CALL` gas budget (0x186A0) to `addr`, which clears 10 cold +storage slots (12 -> 0). EIP-8038 raises the cold SSTORE-clear charge +from 5000 to 13000, so the 10 clears jump from 50000 to 130000 gas and +no longer fit in the forwarded budget or the transaction gas limit: +`addr` runs out of gas, its slots stay set, and the inner value +transfer rolls back, defeating the "store clears success" intent. Both +the inner `CALL` gas argument and the transaction gas limit are raised +by `10 * cold_clear_delta` so all 10 clears still succeed. The per-clear +delta is derived from the fork gas model and is exactly 0 pre-EIP-8037; +do not hardcode the Amsterdam values. The asserted balances are +fork-invariant once the clears land, and the post does not assert the +sender balance, so no balance adjustment is needed. """ import pytest @@ -15,6 +29,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,6 +46,7 @@ def test_internal_call_store_clears_success( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_internal_call_store_clears_success.""" coinbase = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B) @@ -45,6 +61,18 @@ def test_internal_call_store_clears_success( gas_limit=1000000, ) + # EIP-8038 raises the cold SSTORE-clear charge; bump the forwarded + # CALL gas and the transaction gas limit by the per-clear delta times + # the 10 clears so every clear still lands instead of running out of + # gas. The delta is 0 before the EIP-8037/8038 repricing. + cold_clear_delta = ( + Op.SSTORE.with_metadata( + key_warm=False, original_value=1, current_value=1, new_value=0 + ).gas_cost(fork) + - 5000 + ) + clears_gas_bump = 10 * cold_clear_delta + # Source: lll # {(SSTORE 0 0)(SSTORE 1 0)(SSTORE 2 0)(SSTORE 3 0)(SSTORE 4 0)(SSTORE 5 0)(SSTORE 6 0)(SSTORE 7 0)(SSTORE 8 0)(SSTORE 9 0)} # noqa: E501 addr = pre.deploy_contract( # noqa: F841 @@ -77,7 +105,7 @@ def test_internal_call_store_clears_success( # { (CALL 100000 1 0 0 0 0) } # noqa: E501 target = pre.deploy_contract( # noqa: F841 code=Op.CALL( - gas=0x186A0, + gas=0x186A0 + clears_gas_bump, address=addr, value=0x1, args_offset=0x0, @@ -94,7 +122,7 @@ def test_internal_call_store_clears_success( sender=sender, to=target, data=Bytes(""), - gas_limit=160000, + gas_limit=160000 + clears_gas_bump, value=10, ) diff --git a/tests/ported_static/stTransactionTest/test_store_clears_and_internal_call_store_clears_success.py b/tests/ported_static/stTransactionTest/test_store_clears_and_internal_call_store_clears_success.py index 3af9a80d962..163c044ce66 100644 --- a/tests/ported_static/stTransactionTest/test_store_clears_and_internal_call_store_clears_success.py +++ b/tests/ported_static/stTransactionTest/test_store_clears_and_internal_call_store_clears_success.py @@ -3,6 +3,19 @@ Ported from: state_tests/stTransactionTest/StoreClearsAndInternalCallStoreClearsSuccessFiller.json + +@manually-enhanced: Do not overwrite. The outer contract `target` clears 4 +cold storage slots then `CALL`s the inner contract `addr`, which clears 10 +cold storage slots; the value transfer and clears must all succeed. +EIP-8037/8038 raise the cold SSTORE-clear charge from 5000 to 13000 at +Amsterdam, so both gas budgets must rise by that charge delta or the inner +frame runs out of gas (clearing only 4 of its 10 slots) and the value +transfer rolls back. The inner `CALL` only forwards a fixed gas amount, so +its budget is bumped by the 10 inner clears; the transaction gas limit is +bumped by all 14 clears (10 inner plus 4 outer) so the outer frame can both +pay its own clears and forward the larger amount. Both bumps are derived +from the fork gas model and are exactly 0 pre-EIP-8037; do not hardcode the +Amsterdam values. """ import pytest @@ -15,6 +28,7 @@ StateTestFiller, Transaction, ) +from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -31,11 +45,19 @@ def test_store_clears_and_internal_call_store_clears_success( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_store_clears_and_internal_call_store_clears_success.""" coinbase = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B) sender = pre.fund_eoa(amount=0x1DCD6500) + # EIP-8037/8038 raise the cold SSTORE-clear charge; derive the per-clear + # delta (0 pre-EIP-8037) so both gas budgets keep every clear landing. + sstore_charge = Op.SSTORE.with_metadata( + key_warm=False, original_value=1, current_value=1, new_value=0 + ).gas_cost(fork) + cold_clear_delta = sstore_charge - 5000 + env = Environment( fee_recipient=coinbase, number=1, @@ -81,7 +103,10 @@ def test_store_clears_and_internal_call_store_clears_success( + Op.SSTORE(key=0x2, value=0x0) + Op.SSTORE(key=0x3, value=0x0) + Op.CALL( - gas=0xC350, + # The inner frame clears 10 cold slots; forward its extra + # charge so all 10 clears land at Amsterdam (delta is 0 + # pre-EIP-8037). + gas=0xC350 + 10 * cold_clear_delta, address=addr, value=0x1, args_offset=0x0, @@ -99,7 +124,10 @@ def test_store_clears_and_internal_call_store_clears_success( sender=sender, to=target, data=Bytes(""), - gas_limit=200000, + # The whole transaction clears 14 cold slots (4 in the outer frame, + # 10 in the inner frame); bump the limit by all of them so the outer + # frame can pay its own clears and forward the larger inner budget. + gas_limit=200000 + 14 * cold_clear_delta, value=10, ) diff --git a/tests/ported_static/stTransactionTest/test_transaction_sending_to_zero.py b/tests/ported_static/stTransactionTest/test_transaction_sending_to_zero.py index 77749f1463e..1fbe1129c26 100644 --- a/tests/ported_static/stTransactionTest/test_transaction_sending_to_zero.py +++ b/tests/ported_static/stTransactionTest/test_transaction_sending_to_zero.py @@ -3,6 +3,14 @@ Ported from: state_tests/stTransactionTest/TransactionSendingToZeroFiller.json + +@manually-enhanced: Do not overwrite. The tx sends value 1 to the empty +zero address, so EIP-2780 charges NEW_ACCOUNT state gas at the top frame; +with the default zero reservoir that charge spills into regular gas. The +`gas_limit` is lifted by `fork.transaction_top_frame_state_gas` for an +EMPTY_ACCOUNT recipient with `sends_value=True` (0 on pre-EIP-2780 +forks), so the literal 25000 budget stays valid across the repricing. Do +not collapse the lift back to a hardcoded gas_limit. """ import pytest @@ -13,6 +21,8 @@ Alloc, Bytes, Environment, + Fork, + RecipientType, StateTestFiller, Transaction, ) @@ -29,6 +39,7 @@ def test_transaction_sending_to_zero( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_transaction_sending_to_zero.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) @@ -47,11 +58,19 @@ def test_transaction_sending_to_zero( pre[sender] = Account(balance=0x5F5E100) + # EIP-2780 charges ``NEW_ACCOUNT`` state gas at the top frame when + # value is sent to an empty recipient; with the default zero + # state-gas reservoir that charge spills into regular gas, so lift + # ``gas_limit`` by exactly that amount (0 on pre-EIP-2780 forks). + top_frame_state_gas = fork.transaction_top_frame_state_gas( + recipient_type=RecipientType.EMPTY_ACCOUNT, + sends_value=True, + ) tx = Transaction( sender=sender, to=Address(0x0000000000000000000000000000000000000000), data=Bytes(""), - gas_limit=25000, + gas_limit=25000 + top_frame_state_gas, value=1, ) diff --git a/tests/ported_static/stTransactionTest/test_transaction_to_addressh160minus_one.py b/tests/ported_static/stTransactionTest/test_transaction_to_addressh160minus_one.py index 0a27a5403dd..c8601268898 100644 --- a/tests/ported_static/stTransactionTest/test_transaction_to_addressh160minus_one.py +++ b/tests/ported_static/stTransactionTest/test_transaction_to_addressh160minus_one.py @@ -3,6 +3,14 @@ Ported from: state_tests/stTransactionTest/TransactionToAddressh160minusOneFiller.json + +@manually-enhanced: Do not overwrite. Sending value to the empty 0xff..ff +recipient triggers EIP-2780's NEW_ACCOUNT top-frame state-gas charge. +Both the tx and block ``gas_limit`` are lifted by +``fork.transaction_top_frame_state_gas(EMPTY_ACCOUNT, sends_value=True)`` +so the charge (which spills into regular gas via the zero reservoir) +fits the budget; this derived value is 0 on pre-EIP-2780 forks, keeping +the original hardcoded 22000/100000 limits intact there. """ import pytest @@ -13,6 +21,8 @@ Alloc, Bytes, Environment, + Fork, + RecipientType, StateTestFiller, Transaction, ) @@ -31,6 +41,7 @@ def test_transaction_to_addressh160minus_one( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_transaction_to_addressh160minus_one.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) @@ -38,22 +49,31 @@ def test_transaction_to_addressh160minus_one( key=0xF79127A3004ABDE26A4CBD80C428CB10F829FA11B54D36E7B326F4F4A5927ACF ) + pre[sender] = Account(balance=0x3B9ACA00) + + # EIP-2780 charges ``NEW_ACCOUNT`` state gas at the top frame when + # value is sent to an empty recipient; with the default zero + # state-gas reservoir that charge spills into regular gas, so lift + # ``gas_limit`` by exactly that amount (0 on pre-EIP-2780 forks). + # The block ``gas_limit`` must also accommodate the lifted tx. + top_frame_state_gas = fork.transaction_top_frame_state_gas( + recipient_type=RecipientType.EMPTY_ACCOUNT, + sends_value=True, + ) + env = Environment( fee_recipient=coinbase, number=1, timestamp=1000, prev_randao=0x20000, base_fee_per_gas=10, - gas_limit=100000, + gas_limit=100000 + top_frame_state_gas, ) - - pre[sender] = Account(balance=0x3B9ACA00) - tx = Transaction( sender=sender, to=Address(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF), data=Bytes(""), - gas_limit=22000, + gas_limit=22000 + top_frame_state_gas, value=100, ) diff --git a/tests/ported_static/stTransactionTest/test_transaction_to_itself.py b/tests/ported_static/stTransactionTest/test_transaction_to_itself.py index 320a0073f09..28e0e14505a 100644 --- a/tests/ported_static/stTransactionTest/test_transaction_to_itself.py +++ b/tests/ported_static/stTransactionTest/test_transaction_to_itself.py @@ -3,6 +3,16 @@ Ported from: state_tests/stTransactionTest/TransactionToItselfFiller.json + +@manually-enhanced: Do not overwrite. The post-state asserts the sender +balance after a self-transfer (to == sender). Instead of the original +hardcoded value, the balance shift is derived from the fork intrinsic +calculator: ``intrinsic(recipient_type=SELF, sends_value=True) - 21_000`` +is the delta versus the pre-EIP-2780 baseline intrinsic 21_000. EIP-2780 +carves out the recipient and value-transfer surcharges for self-sends, +dropping the intrinsic to ``TX_BASE`` (12_000 on Amsterdam), so the delta +is 0 at Cancun and negative afterward. The balance moves by +``gas_price * intrinsic_delta``. Do not hardcode the Amsterdam value. """ import pytest @@ -12,6 +22,8 @@ Alloc, Bytes, Environment, + Fork, + RecipientType, StateTestFiller, Transaction, ) @@ -27,6 +39,7 @@ def test_transaction_to_itself( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_transaction_to_itself.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) @@ -49,6 +62,19 @@ def test_transaction_to_itself( value=1, ) - post = {sender: Account(balance=0x3B9795B0, nonce=1)} + # EIP-2780 carves out self-transfers from the recipient and + # value-transfer surcharges, leaving only ``TX_BASE`` (12_000 on + # Amsterdam vs 21_000 on Cancun). Shift the sender balance by + # ``gas_price * delta``. + intrinsic_delta = ( + fork.transaction_intrinsic_cost_calculator()( + recipient_type=RecipientType.SELF, + sends_value=True, + ) + - 21_000 + ) + post = { + sender: Account(balance=0x3B9795B0 - 10 * intrinsic_delta, nonce=1) + } state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stZeroCallsTest/test_zero_value_call.py b/tests/ported_static/stZeroCallsTest/test_zero_value_call.py index 7c0f01feb36..93a4faac1a7 100644 --- a/tests/ported_static/stZeroCallsTest/test_zero_value_call.py +++ b/tests/ported_static/stZeroCallsTest/test_zero_value_call.py @@ -3,6 +3,14 @@ Ported from: state_tests/stZeroCallsTest/ZeroValue_CALLFiller.json + +@manually-enhanced: Do not overwrite. The post-state asserts storage slot +0 holds the `Op.GAS` value (0x8D5B6), which depends on the gas remaining +at a fixed execution point. EIP-2780 lowers the intrinsic for this non-self +non-value tx, so the gas budget is derived from the fork as +`600_000 + (intrinsic - 21_000)`: the fork intrinsic minus the +pre-EIP-2780 baseline 21_000 keeps the post-intrinsic budget fixed at +Cancun's value across forks. Do not hardcode the gas_limit. """ import pytest @@ -13,6 +21,7 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) @@ -30,6 +39,7 @@ def test_zero_value_call( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_zero_value_call.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) @@ -70,11 +80,18 @@ def test_zero_value_call( address=Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B), # noqa: E501 ) + # Preserve Cancun's post-intrinsic execution budget across + # forks; EIP-2780 lowers the intrinsic for non-self non-value + # txs, and the Op.GAS storage assertion depends on the + # remaining gas at a fixed execution point. + intrinsic = fork.transaction_intrinsic_cost_calculator()() + gas_limit = 600_000 + (intrinsic - 21_000) + tx = Transaction( sender=sender, to=contract_0, data=Bytes(""), - gas_limit=600000, + gas_limit=gas_limit, ) post = { diff --git a/tests/ported_static/stZeroCallsTest/test_zero_value_call_to_empty_paris.py b/tests/ported_static/stZeroCallsTest/test_zero_value_call_to_empty_paris.py index 9153b52e963..dea5687fb4b 100644 --- a/tests/ported_static/stZeroCallsTest/test_zero_value_call_to_empty_paris.py +++ b/tests/ported_static/stZeroCallsTest/test_zero_value_call_to_empty_paris.py @@ -3,6 +3,14 @@ Ported from: state_tests/stZeroCallsTest/ZeroValue_CALL_ToEmpty_ParisFiller.json + +@manually-enhanced: Do not overwrite. The post-state asserts the +`Op.GAS` value stored at slot 0 (0x8D5B6), which depends on the gas +remaining at a fixed execution point. To keep that budget constant +across forks, `gas_limit` is derived as 600_000 plus the fork's intrinsic +cost minus the pre-EIP-2780 baseline intrinsic of 21_000 +(`intrinsic - 21_000`), since EIP-2780 lowers the intrinsic for non-self +non-value txs. Do not hardcode the gas_limit. """ import pytest @@ -12,6 +20,7 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) @@ -29,6 +38,7 @@ def test_zero_value_call_to_empty_paris( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_zero_value_call_to_empty_paris.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) @@ -65,11 +75,18 @@ def test_zero_value_call_to_empty_paris( nonce=0, ) + # Preserve Cancun's post-intrinsic execution budget across + # forks; EIP-2780 lowers the intrinsic for non-self non-value + # txs, and the Op.GAS storage assertion depends on the + # remaining gas at a fixed execution point. + intrinsic = fork.transaction_intrinsic_cost_calculator()() + gas_limit = 600_000 + (intrinsic - 21_000) + tx = Transaction( sender=sender, to=target, data=Bytes(""), - gas_limit=600000, + gas_limit=gas_limit, ) post = { diff --git a/tests/ported_static/stZeroCallsTest/test_zero_value_call_to_non_zero_balance.py b/tests/ported_static/stZeroCallsTest/test_zero_value_call_to_non_zero_balance.py index 50c861952c0..4e65814717a 100644 --- a/tests/ported_static/stZeroCallsTest/test_zero_value_call_to_non_zero_balance.py +++ b/tests/ported_static/stZeroCallsTest/test_zero_value_call_to_non_zero_balance.py @@ -3,6 +3,14 @@ Ported from: state_tests/stZeroCallsTest/ZeroValue_CALL_ToNonZeroBalanceFiller.json + +@manually-enhanced: Do not overwrite. Slot 0 stores `Op.GAS` and is +asserted at a fixed `0x8D5B6`, so the post-intrinsic execution budget +must stay constant across forks. The `gas_limit` is derived from the +fork intrinsic via `fork.transaction_intrinsic_cost_calculator()()` +minus the pre-EIP-2780 baseline `21_000`, leaving exactly 600_000 for +execution (the adjustment is 0 pre-EIP-2780). Do not hardcode the +gas_limit. """ import pytest @@ -12,6 +20,7 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) @@ -29,6 +38,7 @@ def test_zero_value_call_to_non_zero_balance( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_zero_value_call_to_non_zero_balance.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) @@ -65,11 +75,18 @@ def test_zero_value_call_to_non_zero_balance( nonce=0, ) + # Preserve Cancun's post-intrinsic execution budget across + # forks; EIP-2780 lowers the intrinsic for non-self non-value + # txs, and the Op.GAS storage assertion depends on the + # remaining gas at a fixed execution point. + intrinsic = fork.transaction_intrinsic_cost_calculator()() + gas_limit = 600_000 + (intrinsic - 21_000) + tx = Transaction( sender=sender, to=target, data=Bytes(""), - gas_limit=600000, + gas_limit=gas_limit, ) post = { diff --git a/tests/ported_static/stZeroCallsTest/test_zero_value_call_to_one_storage_key_paris.py b/tests/ported_static/stZeroCallsTest/test_zero_value_call_to_one_storage_key_paris.py index a073612b73b..68d21a57d11 100644 --- a/tests/ported_static/stZeroCallsTest/test_zero_value_call_to_one_storage_key_paris.py +++ b/tests/ported_static/stZeroCallsTest/test_zero_value_call_to_one_storage_key_paris.py @@ -3,6 +3,15 @@ Ported from: state_tests/stZeroCallsTest/ZeroValue_CALL_ToOneStorageKey_ParisFiller.json + +@manually-enhanced: Do not overwrite. The contract stores `Op.GAS` into +slot 0, asserting `0x8D5B6` remaining at a fixed execution point, so the +tx gas budget must track the intrinsic across forks. `gas_limit` is +derived as `600_000 + (intrinsic - 21_000)`, where `intrinsic` comes from +`fork.transaction_intrinsic_cost_calculator()`; subtracting the +pre-EIP-2780 baseline intrinsic 21_000 keeps the post-intrinsic execution +budget fixed when EIP-2780 lowers the intrinsic for non-value, non-self +txs. Do not hardcode the literal gas limit. """ import pytest @@ -13,6 +22,7 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) @@ -32,6 +42,7 @@ def test_zero_value_call_to_one_storage_key_paris( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_zero_value_call_to_one_storage_key_paris.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) @@ -73,11 +84,18 @@ def test_zero_value_call_to_one_storage_key_paris( address=Address(0xF202BAE278AC09857F5A56991C7A4679632F5841), # noqa: E501 ) + # Preserve Cancun's post-intrinsic execution budget across + # forks; EIP-2780 lowers the intrinsic for non-self non-value + # txs, and the Op.GAS storage assertion depends on the + # remaining gas at a fixed execution point. + intrinsic = fork.transaction_intrinsic_cost_calculator()() + gas_limit = 600_000 + (intrinsic - 21_000) + tx = Transaction( sender=sender, to=target, data=Bytes(""), - gas_limit=600000, + gas_limit=gas_limit, ) post = { diff --git a/tests/ported_static/stZeroCallsTest/test_zero_value_callcode.py b/tests/ported_static/stZeroCallsTest/test_zero_value_callcode.py index d4721fe68c5..a018699bea2 100644 --- a/tests/ported_static/stZeroCallsTest/test_zero_value_callcode.py +++ b/tests/ported_static/stZeroCallsTest/test_zero_value_callcode.py @@ -3,6 +3,15 @@ Ported from: state_tests/stZeroCallsTest/ZeroValue_CALLCODEFiller.json + +@manually-enhanced: Do not overwrite. The contract stores `Op.GAS` at +slot 0 (asserted as 0x8D5B6), so the post-state depends on the gas left +at a fixed execution point. The tx gas budget is derived from the fork +gas model instead of a hardcoded literal: `gas_limit = 600_000 + +(intrinsic - 21_000)` adds back whatever EIP-2780 shaved off the +intrinsic for this non-self non-value tx (subtracting the pre-EIP-2780 +baseline 21_000) so the 600_000 post-intrinsic execution budget, and +thus the stored GAS value, stays invariant across forks. """ import pytest @@ -13,6 +22,7 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) @@ -30,6 +40,7 @@ def test_zero_value_callcode( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_zero_value_callcode.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) @@ -70,11 +81,18 @@ def test_zero_value_callcode( address=Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B), # noqa: E501 ) + # Preserve Cancun's post-intrinsic execution budget across + # forks; EIP-2780 lowers the intrinsic for non-self non-value + # txs, and the Op.GAS storage assertion depends on the + # remaining gas at a fixed execution point. + intrinsic = fork.transaction_intrinsic_cost_calculator()() + gas_limit = 600_000 + (intrinsic - 21_000) + tx = Transaction( sender=sender, to=contract_0, data=Bytes(""), - gas_limit=600000, + gas_limit=gas_limit, ) post = { diff --git a/tests/ported_static/stZeroCallsTest/test_zero_value_callcode_to_empty_paris.py b/tests/ported_static/stZeroCallsTest/test_zero_value_callcode_to_empty_paris.py index 8117720261d..935396c723e 100644 --- a/tests/ported_static/stZeroCallsTest/test_zero_value_callcode_to_empty_paris.py +++ b/tests/ported_static/stZeroCallsTest/test_zero_value_callcode_to_empty_paris.py @@ -3,6 +3,15 @@ Ported from: state_tests/stZeroCallsTest/ZeroValue_CALLCODE_ToEmpty_ParisFiller.json + +@manually-enhanced: Do not overwrite. The target stores `Op.GAS` into +slot 0 and the post-state asserts it as a fixed value (0x8D5B6), so the +remaining gas at that point must be fork-invariant. The `gas_limit` is +derived as `600_000 + (intrinsic - 21_000)` from the fork intrinsic +calculator instead of a hardcoded number: subtracting the pre-EIP-2780 +baseline intrinsic of 21_000 keeps a constant 600_000 post-intrinsic +execution budget when EIP-2780 lowers the intrinsic for non-self +non-value txs. Do not hardcode the gas_limit. """ import pytest @@ -12,6 +21,7 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) @@ -31,6 +41,7 @@ def test_zero_value_callcode_to_empty_paris( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_zero_value_callcode_to_empty_paris.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) @@ -67,11 +78,18 @@ def test_zero_value_callcode_to_empty_paris( nonce=0, ) + # Preserve Cancun's post-intrinsic execution budget across + # forks; EIP-2780 lowers the intrinsic for non-self non-value + # txs, and the Op.GAS storage assertion depends on the + # remaining gas at a fixed execution point. + intrinsic = fork.transaction_intrinsic_cost_calculator()() + gas_limit = 600_000 + (intrinsic - 21_000) + tx = Transaction( sender=sender, to=target, data=Bytes(""), - gas_limit=600000, + gas_limit=gas_limit, ) post = { diff --git a/tests/ported_static/stZeroCallsTest/test_zero_value_callcode_to_non_zero_balance.py b/tests/ported_static/stZeroCallsTest/test_zero_value_callcode_to_non_zero_balance.py index 9b5ab5f0cae..f34ddf54aab 100644 --- a/tests/ported_static/stZeroCallsTest/test_zero_value_callcode_to_non_zero_balance.py +++ b/tests/ported_static/stZeroCallsTest/test_zero_value_callcode_to_non_zero_balance.py @@ -3,6 +3,17 @@ Ported from: state_tests/stZeroCallsTest/ZeroValue_CALLCODE_ToNonZeroBalanceFiller.json + +@manually-enhanced: Do not overwrite. The post-state asserts the +`Op.GAS` value stored at slot 0 (0x8D5B6), which depends on the gas +remaining at a fixed execution point. To hold that point constant, the +`gas_limit` is derived from the fork gas model rather than hardcoded: +`gas_limit = 600_000 + (intrinsic - 21_000)`, where `intrinsic` comes +from `fork.transaction_intrinsic_cost_calculator()`. Subtracting the +pre-EIP-2780 baseline intrinsic 21_000 keeps the post-intrinsic +execution budget at 600_000 across the EIP-2780 intrinsic +decomposition (which lowers the intrinsic for non-self, non-value txs). +Do not hardcode the gas_limit. """ import pytest @@ -12,6 +23,7 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) @@ -31,6 +43,7 @@ def test_zero_value_callcode_to_non_zero_balance( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_zero_value_callcode_to_non_zero_balance.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) @@ -67,11 +80,18 @@ def test_zero_value_callcode_to_non_zero_balance( nonce=0, ) + # Preserve Cancun's post-intrinsic execution budget across + # forks; EIP-2780 lowers the intrinsic for non-self non-value + # txs, and the Op.GAS storage assertion depends on the + # remaining gas at a fixed execution point. + intrinsic = fork.transaction_intrinsic_cost_calculator()() + gas_limit = 600_000 + (intrinsic - 21_000) + tx = Transaction( sender=sender, to=target, data=Bytes(""), - gas_limit=600000, + gas_limit=gas_limit, ) post = { diff --git a/tests/ported_static/stZeroCallsTest/test_zero_value_callcode_to_one_storage_key_paris.py b/tests/ported_static/stZeroCallsTest/test_zero_value_callcode_to_one_storage_key_paris.py index 6872e5b24d9..b11a46374e3 100644 --- a/tests/ported_static/stZeroCallsTest/test_zero_value_callcode_to_one_storage_key_paris.py +++ b/tests/ported_static/stZeroCallsTest/test_zero_value_callcode_to_one_storage_key_paris.py @@ -3,6 +3,16 @@ Ported from: state_tests/stZeroCallsTest/ZeroValue_CALLCODE_ToOneStorageKey_ParisFiller.json + +@manually-enhanced: Do not overwrite. The contract's first SSTORE records +`Op.GAS`, so the slot-0 post value (`0x8D5B6`) pins the remaining gas at a +fixed execution point. To keep that budget constant as the intrinsic +shifts, `gas_limit` is derived from the fork intrinsic calculator rather +than hardcoded: `600_000 + (intrinsic - 21_000)`, where `21_000` is the +pre-EIP-2780 baseline intrinsic. EIP-2780 lowers the intrinsic for this +non-self, zero-value tx, so the `- 21_000` term keeps the post-intrinsic +execution budget (and thus the `Op.GAS` assertion) correct across forks. +Do not replace the calculator-derived value with a literal. """ import pytest @@ -13,6 +23,7 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) @@ -32,6 +43,7 @@ def test_zero_value_callcode_to_one_storage_key_paris( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_zero_value_callcode_to_one_storage_key_paris.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) @@ -73,11 +85,18 @@ def test_zero_value_callcode_to_one_storage_key_paris( address=Address(0xA93AE635B4FA4D618045C019AC32ED9ADC8F54EA), # noqa: E501 ) + # Preserve Cancun's post-intrinsic execution budget across + # forks; EIP-2780 lowers the intrinsic for non-self non-value + # txs, and the Op.GAS storage assertion depends on the + # remaining gas at a fixed execution point. + intrinsic = fork.transaction_intrinsic_cost_calculator()() + gas_limit = 600_000 + (intrinsic - 21_000) + tx = Transaction( sender=sender, to=target, data=Bytes(""), - gas_limit=600000, + gas_limit=gas_limit, ) post = { diff --git a/tests/ported_static/stZeroCallsTest/test_zero_value_delegatecall.py b/tests/ported_static/stZeroCallsTest/test_zero_value_delegatecall.py index 3e630665533..d3fad1a766a 100644 --- a/tests/ported_static/stZeroCallsTest/test_zero_value_delegatecall.py +++ b/tests/ported_static/stZeroCallsTest/test_zero_value_delegatecall.py @@ -3,6 +3,16 @@ Ported from: state_tests/stZeroCallsTest/ZeroValue_DELEGATECALLFiller.json + +@manually-enhanced: Do not overwrite. The `gas_limit` is derived from +the fork intrinsic calculator instead of a hardcoded literal, so the +post-intrinsic execution budget stays fixed at 600_000 across forks: +`gas_limit = 600_000 + (intrinsic - 21_000)`, where `21_000` is the +pre-EIP-2780 baseline intrinsic. EIP-2780 lowers the intrinsic for +non-self, non-value txs, and the `SSTORE(0, GAS)` post assertion +(`0x8D5B6`) pins `Op.GAS` at a fixed execution point, so the remaining +gas after the intrinsic deduction must not shift. Do not hardcode the +gas limit. """ import pytest @@ -13,6 +23,7 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) @@ -30,6 +41,7 @@ def test_zero_value_delegatecall( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_zero_value_delegatecall.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) @@ -69,11 +81,18 @@ def test_zero_value_delegatecall( address=Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B), # noqa: E501 ) + # Preserve Cancun's post-intrinsic execution budget across + # forks; EIP-2780 lowers the intrinsic for non-self non-value + # txs, and the Op.GAS storage assertion depends on the + # remaining gas at a fixed execution point. + intrinsic = fork.transaction_intrinsic_cost_calculator()() + gas_limit = 600_000 + (intrinsic - 21_000) + tx = Transaction( sender=sender, to=contract_0, data=Bytes(""), - gas_limit=600000, + gas_limit=gas_limit, ) post = { diff --git a/tests/ported_static/stZeroCallsTest/test_zero_value_delegatecall_to_empty_paris.py b/tests/ported_static/stZeroCallsTest/test_zero_value_delegatecall_to_empty_paris.py index a92303da1cc..a858d9edcc5 100644 --- a/tests/ported_static/stZeroCallsTest/test_zero_value_delegatecall_to_empty_paris.py +++ b/tests/ported_static/stZeroCallsTest/test_zero_value_delegatecall_to_empty_paris.py @@ -3,6 +3,16 @@ Ported from: state_tests/stZeroCallsTest/ZeroValue_DELEGATECALL_ToEmpty_ParisFiller.json + +@manually-enhanced: Do not overwrite. The contract records `Op.GAS` into +storage slot 0, and the post-state asserts that value (`0x8D5B6`), so the +gas remaining at that fixed execution point must stay constant across +forks. The `gas_limit` is therefore derived from the fork as +`600_000 + (fork.transaction_intrinsic_cost_calculator()() - 21_000)`: +it re-adds the 600k post-intrinsic execution budget onto the fork +intrinsic and subtracts the pre-EIP-2780 baseline intrinsic `21_000`, +which EIP-2780 lowers for non-self non-value txs. Do not hardcode the +gas limit. """ import pytest @@ -12,6 +22,7 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) @@ -31,6 +42,7 @@ def test_zero_value_delegatecall_to_empty_paris( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_zero_value_delegatecall_to_empty_paris.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) @@ -66,11 +78,18 @@ def test_zero_value_delegatecall_to_empty_paris( nonce=0, ) + # Preserve Cancun's post-intrinsic execution budget across + # forks; EIP-2780 lowers the intrinsic for non-self non-value + # txs, and the Op.GAS storage assertion depends on the + # remaining gas at a fixed execution point. + intrinsic = fork.transaction_intrinsic_cost_calculator()() + gas_limit = 600_000 + (intrinsic - 21_000) + tx = Transaction( sender=sender, to=target, data=Bytes(""), - gas_limit=600000, + gas_limit=gas_limit, ) post = { diff --git a/tests/ported_static/stZeroCallsTest/test_zero_value_delegatecall_to_non_zero_balance.py b/tests/ported_static/stZeroCallsTest/test_zero_value_delegatecall_to_non_zero_balance.py index a02b7615016..2a06868f8a7 100644 --- a/tests/ported_static/stZeroCallsTest/test_zero_value_delegatecall_to_non_zero_balance.py +++ b/tests/ported_static/stZeroCallsTest/test_zero_value_delegatecall_to_non_zero_balance.py @@ -3,6 +3,16 @@ Ported from: state_tests/stZeroCallsTest/ZeroValue_DELEGATECALL_ToNonZeroBalanceFiller.json + +@manually-enhanced: Do not overwrite. The contract stores `Op.GAS` into +slot 0 (asserted as 0x8D5B6), so the gas remaining at that fixed +execution point must be fork-invariant. The `gas_limit` is derived from +the fork: `600_000 + (intrinsic - 21_000)`, where `intrinsic` comes from +`fork.transaction_intrinsic_cost_calculator()()` and 21_000 is the +pre-EIP-2780 baseline intrinsic. EIP-2780's intrinsic decomposition +lowers the intrinsic for non-self non-value txs, so subtracting the old +21_000 literal keeps the post-intrinsic execution budget (600_000) +constant. Do not hardcode the gas_limit. """ import pytest @@ -12,6 +22,7 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) @@ -31,6 +42,7 @@ def test_zero_value_delegatecall_to_non_zero_balance( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_zero_value_delegatecall_to_non_zero_balance.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) @@ -66,11 +78,18 @@ def test_zero_value_delegatecall_to_non_zero_balance( nonce=0, ) + # Preserve Cancun's post-intrinsic execution budget across + # forks; EIP-2780 lowers the intrinsic for non-self non-value + # txs, and the Op.GAS storage assertion depends on the + # remaining gas at a fixed execution point. + intrinsic = fork.transaction_intrinsic_cost_calculator()() + gas_limit = 600_000 + (intrinsic - 21_000) + tx = Transaction( sender=sender, to=target, data=Bytes(""), - gas_limit=600000, + gas_limit=gas_limit, ) post = { diff --git a/tests/ported_static/stZeroCallsTest/test_zero_value_delegatecall_to_one_storage_key_paris.py b/tests/ported_static/stZeroCallsTest/test_zero_value_delegatecall_to_one_storage_key_paris.py index d4e3d2fad43..10553129c76 100644 --- a/tests/ported_static/stZeroCallsTest/test_zero_value_delegatecall_to_one_storage_key_paris.py +++ b/tests/ported_static/stZeroCallsTest/test_zero_value_delegatecall_to_one_storage_key_paris.py @@ -3,6 +3,16 @@ Ported from: state_tests/stZeroCallsTest/ZeroValue_DELEGATECALL_ToOneStorageKey_ParisFiller.json + +@manually-enhanced: Do not overwrite. Slot 0 asserts the `Op.GAS` +value (0x8D5B6) captured by the first SSTORE, so the gas remaining at +that fixed execution point must be constant across forks. The +`gas_limit` is derived from the fork intrinsic calculator +(`fork.transaction_intrinsic_cost_calculator()()`) as +`600_000 + (intrinsic - 21_000)`: subtracting the pre-EIP-2780 +baseline intrinsic 21_000 keeps the post-intrinsic execution budget +fixed at 600_000 even as EIP-2780 lowers the intrinsic for +non-value, non-self calls. Do not hardcode the gas_limit. """ import pytest @@ -13,6 +23,7 @@ Alloc, Bytes, Environment, + Fork, StateTestFiller, Transaction, ) @@ -32,6 +43,7 @@ def test_zero_value_delegatecall_to_one_storage_key_paris( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """Test_zero_value_delegatecall_to_one_storage_key_paris.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) @@ -72,11 +84,18 @@ def test_zero_value_delegatecall_to_one_storage_key_paris( address=Address(0xC8881A7E48D37B4A4CDD6338CE7076D6A116283D), # noqa: E501 ) + # Preserve Cancun's post-intrinsic execution budget across + # forks; EIP-2780 lowers the intrinsic for non-self non-value + # txs, and the Op.GAS storage assertion depends on the + # remaining gas at a fixed execution point. + intrinsic = fork.transaction_intrinsic_cost_calculator()() + gas_limit = 600_000 + (intrinsic - 21_000) + tx = Transaction( sender=sender, to=target, data=Bytes(""), - gas_limit=600000, + gas_limit=gas_limit, ) post = { diff --git a/tests/ported_static/vmTests/test_suicide.py b/tests/ported_static/vmTests/test_suicide.py index 02c1db148af..c562b182d0d 100644 --- a/tests/ported_static/vmTests/test_suicide.py +++ b/tests/ported_static/vmTests/test_suicide.py @@ -3,6 +3,18 @@ Ported from: state_tests/VMTests/vmTests/suicideFiller.yml + +@manually-enhanced: Do not overwrite. For the `caller` case the post-state +asserts the sender balance, which equals its start minus +`gas_used * gas_price`. The transaction calls a contract that CALLs a +cold, existing account (slot 0x1000) before it self-destructs; EIP-8038 +raises the cold account-access surcharge on that CALL from 2600 to 3000. +The SELFDESTRUCT itself is to a warm, non-empty beneficiary (the caller), +so its charge is unchanged, and there is no refund. Derive the +account-access delta from the fork gas model (0 pre-EIP-8037) and +subtract `gas_price * delta` from the Cancun balance; do not hardcode the +Amsterdam value. The `random` and `myself` cases assert only +non-gas-dependent balances and need no adjustment. """ import pytest @@ -133,12 +145,24 @@ def test_suicide( address=Address(0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC), # noqa: E501 ) + # The CALL into the self-destructing contract touches a cold, already + # existing account; EIP-8038 raises that cold account-access surcharge + # from 2600 to 3000. The SELFDESTRUCT beneficiary is warm and + # non-empty, so its charge is unchanged. EIP-2780 separately reshapes + # the tx intrinsic for this non-self non-value call. The sender pays + # the combined delta at the base fee (no priority fee). + cold_account_access_delta = fork.gas_costs().COLD_ACCOUNT_ACCESS - 2600 + intrinsic_delta = fork.transaction_intrinsic_cost_calculator()() - 21_000 + caller_balance = ( + 0x5AF31075D9DE - 10 * cold_account_access_delta - 10 * intrinsic_delta + ) + expect_entries_: list[dict] = [ { "indexes": {"data": [0], "gas": -1, "value": -1}, "network": [">=Cancun"], "result": { - sender: Account(balance=0x5AF31075D9DE), + sender: Account(balance=caller_balance), contract_3: Account(balance=0xFF100000000000), }, }, diff --git a/tests/prague/eip7702_set_code_tx/test_gas.py b/tests/prague/eip7702_set_code_tx/test_gas.py index 431b4166add..4acb1f4f083 100644 --- a/tests/prague/eip7702_set_code_tx/test_gas.py +++ b/tests/prague/eip7702_set_code_tx/test_gas.py @@ -973,6 +973,7 @@ def test_gas_cost( def test_account_warming( state_test: StateTestFiller, pre: Alloc, + fork: Fork, authorization_list_with_properties: List[AuthorizationWithProperties], authorization_list: List[AuthorizationTuple], access_list: List[AccessList], @@ -988,8 +989,9 @@ def test_account_warming( # check. overhead_cost = 3 * len(Op.CALL.kwargs) - cold_account_cost = 2600 - warm_account_cost = 100 + gas_costs = fork.gas_costs() + cold_account_cost = gas_costs.COLD_ACCOUNT_ACCESS + warm_account_cost = gas_costs.WARM_ACCESS access_list_addresses = { access_list.address for access_list in access_list @@ -1190,6 +1192,7 @@ def test_intrinsic_gas_cost( def test_self_set_code_cost( state_test: StateTestFiller, pre: Alloc, + fork: Fork, pre_authorized: bool, ) -> None: """Test set to code account access cost when it delegates to itself.""" @@ -1200,6 +1203,10 @@ def test_self_set_code_cost( slot_call_cost = 1 + gas_costs = fork.gas_costs() + cold_account_cost = gas_costs.COLD_ACCOUNT_ACCESS + warm_account_cost = gas_costs.WARM_ACCESS + overhead_cost = 3 * len(Op.CALL.kwargs) callee_code = CodeGasMeasure( @@ -1211,7 +1218,11 @@ def test_self_set_code_cost( callee_address = pre.deploy_contract(callee_code) callee_storage = Storage() - callee_storage[slot_call_cost] = 200 if not pre_authorized else 2700 + callee_storage[slot_call_cost] = ( + 2 * warm_account_cost + if not pre_authorized + else cold_account_cost + warm_account_cost + ) tx = Transaction( to=callee_address, diff --git a/tests/prague/eip7702_set_code_tx/test_set_code_txs.py b/tests/prague/eip7702_set_code_tx/test_set_code_txs.py index a083c9035e0..e7167078a04 100644 --- a/tests/prague/eip7702_set_code_tx/test_set_code_txs.py +++ b/tests/prague/eip7702_set_code_tx/test_set_code_txs.py @@ -36,6 +36,7 @@ Hash, Initcode, Op, + RecipientType, Requests, StateTestFiller, Storage, @@ -1470,6 +1471,7 @@ def test_ext_code_on_self_set_code( def test_set_code_address_and_authority_warm_state( state_test: StateTestFiller, pre: Alloc, + fork: Fork, set_code_address_first: bool, ) -> None: """ @@ -1512,13 +1514,18 @@ def test_set_code_address_and_authority_warm_state( callee_code += Op.SSTORE(slot_call_success, 1) + Op.STOP callee_address = pre.deploy_contract(callee_code) + gas_costs = fork.gas_costs() + cold_account_cost = gas_costs.COLD_ACCOUNT_ACCESS + warm_account_cost = gas_costs.WARM_ACCESS callee_storage = Storage() callee_storage[slot_call_success] = 1 callee_storage[slot_set_code_to_warm_state] = ( - 2_600 if set_code_address_first else 100 + cold_account_cost if set_code_address_first else warm_account_cost ) callee_storage[slot_authority_warm_state] = ( - 200 if set_code_address_first else 2_700 + 2 * warm_account_cost + if set_code_address_first + else warm_account_cost + cold_account_cost ) tx = Transaction( @@ -3995,13 +4002,16 @@ def test_many_delegations( max_gas = tx_gas_limit_cap else: max_gas = env.gas_limit - gas_for_delegations = max_gas - 21_000 - 20_000 - (3 * 2) + + success_slot = 1 + entry_code = Op.SSTORE(success_slot, 1) + Op.STOP + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() + entry_code_gas = entry_code.gas_cost(fork) + gas_for_delegations = max_gas - intrinsic_gas - entry_code_gas gas_costs = fork.gas_costs() delegation_count = gas_for_delegations // gas_costs.AUTH_PER_EMPTY_ACCOUNT - success_slot = 1 - entry_code = Op.SSTORE(success_slot, 1) + Op.STOP entry_address = pre.deploy_contract(entry_code) signers = [pre.fund_eoa(signer_balance) for _ in range(delegation_count)] @@ -4092,6 +4102,7 @@ def test_invalid_transaction_after_authorization( def test_authorization_reusing_nonce( blockchain_test: BlockchainTestFiller, pre: Alloc, + fork: Fork, ) -> None: """ Test an authorization reusing the same nonce as a prior transaction @@ -4100,11 +4111,34 @@ def test_authorization_reusing_nonce( auth_signer = pre.fund_eoa() sender = pre.fund_eoa() recipient = pre.fund_eoa(amount=0) + + intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator() + # Tx1: value transfer to an empty recipient -- pays the intrinsic + # value-transfer surcharges plus the top-frame ``NEW_ACCOUNT`` + # state-gas charge. + tx1_intrinsic = intrinsic_gas_calculator( + recipient_type=RecipientType.EMPTY_ACCOUNT, + sends_value=True, + ) + tx1_top_frame_state = fork.transaction_top_frame_state_gas( + recipient_type=RecipientType.EMPTY_ACCOUNT, + sends_value=True, + ) + tx1_gas = tx1_intrinsic + tx1_top_frame_state + + # Tx2: recipient is now alive (received 1 wei in tx1), so the + # recipient is an EOA and no top-frame charge fires. The auth + # list adds one ``AUTH_PER_EMPTY_ACCOUNT`` to intrinsic. + tx2_gas = intrinsic_gas_calculator( + recipient_type=RecipientType.EOA, + authorization_list_or_count=1, + ) + txs = [ Transaction( sender=auth_signer, nonce=0, - gas_limit=21_000, + gas_limit=tx1_gas, to=recipient, value=1, ), @@ -4112,6 +4146,7 @@ def test_authorization_reusing_nonce( sender=sender, to=recipient, value=0, + gas_limit=tx2_gas, authorization_list=[ AuthorizationTuple( address=Address(1), diff --git a/tests/shanghai/eip4895_withdrawals/test_withdrawals.py b/tests/shanghai/eip4895_withdrawals/test_withdrawals.py index 8c4c62adf60..9d9a3074888 100644 --- a/tests/shanghai/eip4895_withdrawals/test_withdrawals.py +++ b/tests/shanghai/eip4895_withdrawals/test_withdrawals.py @@ -16,6 +16,7 @@ Fork, Hash, Op, + RecipientType, Transaction, TransactionException, Withdrawal, @@ -69,11 +70,16 @@ def recipient(self, pre: Alloc) -> EOA: return pre.fund_eoa(0) @pytest.fixture - def tx(self, sender: EOA, recipient: EOA) -> Transaction: # noqa: D102 + def tx( # noqa: D102 + self, sender: EOA, recipient: EOA, fork: Fork + ) -> Transaction: # Transaction sent from the `sender`, which has 1 wei balance at start + gas_limit = fork.transaction_intrinsic_cost_calculator()( + recipient_type=RecipientType.EOA, + ) return Transaction( gas_price=ONE_GWEI, - gas_limit=21_000, + gas_limit=gas_limit, to=recipient, sender=sender, ) From dee9dcfd5285e2c564e2aba041511c25cbd7265b Mon Sep 17 00:00:00 2001 From: spencer Date: Thu, 2 Jul 2026 23:53:15 +0100 Subject: [PATCH 086/233] feat(tests): EIP-7928 reject newPayload with malformed or missing block access list (#3082) --- .../src/execution_testing/specs/blockchain.py | 35 ++++++---- .../specs/tests/test_types.py | 66 ++++++++++++++++--- .../test_block_access_lists_invalid.py | 48 ++++++++++++++ .../test_fork_transition.py | 37 +++++++++++ 4 files changed, 163 insertions(+), 23 deletions(-) diff --git a/packages/testing/src/execution_testing/specs/blockchain.py b/packages/testing/src/execution_testing/specs/blockchain.py index 764217845bc..a9948f289ee 100644 --- a/packages/testing/src/execution_testing/specs/blockchain.py +++ b/packages/testing/src/execution_testing/specs/blockchain.py @@ -346,6 +346,8 @@ class Block(Header): """Post state for verification after block execution in BlockchainTest""" block_access_list: Bytes | None = Field(None) """EIP-7928: Block-level access lists (serialized).""" + engine_new_payload_block_access_list: Bytes | None = None + """EIP-7928: override only the engine newPayload blockAccessList field.""" expected_gas_used: int | None = None """Expected gas used for the block.""" @@ -460,6 +462,7 @@ class BuiltBlock(CamelModel): rlp_modifier: Header | None = None fork: Fork block_access_list: BlockAccessList | None + engine_new_payload_block_access_list: Bytes | None = None def get_fixture_block( self, *, include_receipts: bool = True @@ -510,10 +513,8 @@ def get_block_rlp(self) -> Bytes: """Get the RLP of the block.""" return self.get_fixture_block().rlp - @staticmethod - def derive_engine_payload_modifier( - rlp_modifier: Header | None, - block_access_list: BlockAccessList | None, + def engine_payload_modifier( + self, ) -> "FixtureExecutionPayloadModifier | None": """ Propagate ``rlp_modifier``'s header changes to the engine payload. @@ -523,9 +524,13 @@ def derive_engine_payload_modifier( the ``block_access_list`` body. So a header modifier that touches the BAL hash needs to drive a matching change on the payload body. """ - if rlp_modifier is None: + if self.engine_new_payload_block_access_list is not None: + return FixtureExecutionPayloadModifier( + block_access_list=self.engine_new_payload_block_access_list, + ) + if self.rlp_modifier is None: return None - bal_hash_override = rlp_modifier.block_access_list_hash + bal_hash_override = self.rlp_modifier.block_access_list_hash if bal_hash_override is None: return None if bal_hash_override is Header.REMOVE_FIELD: @@ -538,7 +543,7 @@ def derive_engine_payload_modifier( # payload by forcing a body to be present. Its exact value is # irrelevant for negative tests — a non-``None`` value is enough to # make a payload-version mismatch detectable. - if block_access_list is None: + if self.block_access_list is None: return FixtureExecutionPayloadModifier( block_access_list=Bytes(b""), ) @@ -555,9 +560,7 @@ def get_fixture_engine_new_payload(self) -> FixtureEngineNewPayload: block_access_list=self.block_access_list.rlp if self.block_access_list else None, - execution_payload_modifier=self.derive_engine_payload_modifier( - self.rlp_modifier, self.block_access_list - ), + execution_payload_modifier=self.engine_payload_modifier(), validation_error=self.expected_exception, error_code=self.engine_api_error_code, ) @@ -1016,6 +1019,9 @@ def generate_block_data( rlp_modifier=block.rlp_modifier, fork=fork, block_access_list=bal, + engine_new_payload_block_access_list=( + block.engine_new_payload_block_access_list + ), ) built_block: BuiltBlock if transition_tool_output.engine_payload is not None: @@ -1035,6 +1041,7 @@ def generate_block_data( and block.rlp_modifier is None and block.requests is None and not block.skip_exception_verification + and block.engine_new_payload_block_access_list is None and not ( block.expected_block_access_list is not None and block.expected_block_access_list._modifier is not None @@ -1045,9 +1052,11 @@ def generate_block_data( # exceptions. - No RLP modifier was specified, because the # modifier is what normally produces the block exception. - No # requests were specified, because modified requests are also - # what normally produces the block exception. - No BAL modifier - # was specified, because modified BAL also produces block - # exceptions. + # what normally produces the block exception. - No engine + # payload BAL override was specified, because it corrupts only + # the engine payload after the transition tool has run. - No + # BAL modifier was specified, because modified BAL also + # produces block exceptions. built_block.verify_block_exception( transition_tool_exceptions_reliable=t8n.exception_mapper.reliable, ) diff --git a/packages/testing/src/execution_testing/specs/tests/test_types.py b/packages/testing/src/execution_testing/specs/tests/test_types.py index f5c43cb5fe9..838f98f68b5 100644 --- a/packages/testing/src/execution_testing/specs/tests/test_types.py +++ b/packages/testing/src/execution_testing/specs/tests/test_types.py @@ -9,10 +9,14 @@ Hash, HeaderNonce, ) +from execution_testing.client_clis import Result +from execution_testing.client_clis.cli_types import LazyAllocStr from execution_testing.fixtures.blockchain import ( FixtureExecutionPayloadModifier, FixtureHeader, ) +from execution_testing.forks import Amsterdam +from execution_testing.test_types import Environment from execution_testing.test_types.block_access_list import BlockAccessList from ..blockchain import BuiltBlock, Header @@ -39,6 +43,15 @@ excess_blob_gas=1, # hash=Hash(1), ) +result_empty = Result( + state_root=0, + transactions_trie=0, + receipts_root=0, + logs_hash=0, + logs_bloom=0, + receipts=[], + gas_used=0, +) @pytest.mark.parametrize( @@ -145,6 +158,30 @@ def test_fixture_header_join( assert modifier.apply(fixture_header) == fixture_header_expected +def built_block( + *, + rlp_modifier: Header | None = None, + block_access_list: BlockAccessList | None = None, + engine_new_payload_block_access_list: Bytes | None = None, +) -> BuiltBlock: + """Generate a dummy built block with all default values.""" + return BuiltBlock( + header=fixture_header_ones, + env=Environment(), + alloc=LazyAllocStr(raw="", _state_root=Hash(0)), + state_root=Hash(0), + txs=[], + ommers=[], + withdrawals=None, + requests=None, + result=result_empty, + fork=Amsterdam, + rlp_modifier=rlp_modifier, + block_access_list=block_access_list, + engine_new_payload_block_access_list=engine_new_payload_block_access_list, + ) + + class TestDeriveEnginePayloadModifier: """ Verify the auto-propagation from ``rlp_modifier``'s header-only changes @@ -156,29 +193,30 @@ class TestDeriveEnginePayloadModifier: def test_no_rlp_modifier_returns_none(self) -> None: """No modifier → no engine payload override.""" assert ( - BuiltBlock.derive_engine_payload_modifier( + built_block( rlp_modifier=None, block_access_list=None, - ) + engine_new_payload_block_access_list=None, + ).engine_payload_modifier() is None ) def test_rlp_modifier_unrelated_field_returns_none(self) -> None: """A modifier that doesn't touch BAL hash leaves the payload alone.""" assert ( - BuiltBlock.derive_engine_payload_modifier( + built_block( rlp_modifier=Header(state_root=Hash(100)), block_access_list=None, - ) + ).engine_payload_modifier() is None ) def test_remove_bal_hash_removes_body_from_payload(self) -> None: """Removing the header's BAL hash also removes the payload body.""" - modifier = BuiltBlock.derive_engine_payload_modifier( + modifier = built_block( rlp_modifier=Header(block_access_list_hash=Header.REMOVE_FIELD), block_access_list=BlockAccessList(), - ) + ).engine_payload_modifier() assert isinstance(modifier, FixtureExecutionPayloadModifier) assert modifier.block_access_list is ( FixtureExecutionPayloadModifier.REMOVE_FIELD @@ -190,10 +228,10 @@ def test_inject_bal_hash_on_pre_fork_adds_body(self) -> None: triggers a body to be added to the engine payload, so a payload- version mismatch is detectable. """ - modifier = BuiltBlock.derive_engine_payload_modifier( + modifier = built_block( rlp_modifier=Header(block_access_list_hash=Hash(0)), block_access_list=None, - ) + ).engine_payload_modifier() assert isinstance(modifier, FixtureExecutionPayloadModifier) assert modifier.block_access_list == Bytes(b"") @@ -204,9 +242,17 @@ def test_inject_bal_hash_on_post_fork_leaves_body_alone(self) -> None: what triggers the client rejection in that scenario. """ assert ( - BuiltBlock.derive_engine_payload_modifier( + built_block( rlp_modifier=Header(block_access_list_hash=Hash(0)), block_access_list=BlockAccessList(), - ) + ).engine_payload_modifier() is None ) + + def test_empty_bytes_override_sends_raw_body(self) -> None: + """Raw `Bytes` (e.g. the invalid `0x`) are sent verbatim.""" + modifier = built_block( + engine_new_payload_block_access_list=Bytes(b"") + ).engine_payload_modifier() + assert isinstance(modifier, FixtureExecutionPayloadModifier) + assert modifier.block_access_list == Bytes(b"") diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_invalid.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_invalid.py index c788617a287..4a06f085c70 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_invalid.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_invalid.py @@ -21,7 +21,9 @@ BlockAccessListExpectation, BlockchainTestFiller, BlockException, + Bytes, EIPChecklist, + EngineAPIError, Environment, Fork, Hash, @@ -1682,3 +1684,49 @@ def test_bal_invalid_extraneous_coinbase( ) ], ) + + +@pytest.mark.valid_from("Amsterdam") +@pytest.mark.blockchain_test_engine_only +@pytest.mark.exception_test +@pytest.mark.parametrize( + "invalid_bal_payload", + [ + pytest.param(b"", id="empty_byte_string"), + pytest.param(b"\x80", id="rlp_non_list"), + pytest.param(b"\xc1", id="rlp_truncated_list"), + ], +) +def test_bal_invalid_engine_payload_encoding( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + invalid_bal_payload: bytes, +) -> None: + """ + Reject a `newPayload` whose `blockAccessList` does not decode as an RLP + list: the empty byte string `0x` (an empty BAL is `0xc0`), the RLP + empty byte string `0x80` (valid RLP but not a list), or a truncated + list header `0xc1`. + """ + sender = pre.fund_eoa() + receiver = pre.nonexistent_account() + + tx = Transaction(sender=sender, to=receiver) + + blockchain_test( + pre=pre, + post={ + sender: Account(nonce=0), + receiver: None, + }, + blocks=[ + Block( + txs=[tx], + engine_new_payload_block_access_list=Bytes( + invalid_bal_payload + ), + exception=BlockException.INVALID_BLOCK_ACCESS_LIST, + engine_api_error_code=EngineAPIError.InvalidParams, + ) + ], + ) diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_fork_transition.py b/tests/amsterdam/eip7928_block_level_access_lists/test_fork_transition.py index 008011f5108..e138d75d2da 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_fork_transition.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_fork_transition.py @@ -11,6 +11,7 @@ BlockAccessListExpectation, BlockchainTestFiller, BlockException, + Bytes, EIPChecklist, EngineAPIError, Environment, @@ -118,6 +119,42 @@ def test_invalid_pre_fork_block_with_bal_hash_field( ) +@pytest.mark.valid_at_transition_to("Amsterdam") +@pytest.mark.blockchain_test_engine_only +@pytest.mark.exception_test +def test_bal_invalid_engine_payload_field_before_fork( + blockchain_test: BlockchainTestFiller, + pre: Alloc, +) -> None: + """ + Reject a pre-Amsterdam `newPayload` that carries a `blockAccessList`. + + The block and its header are otherwise valid, so the spurious payload + field is the only defect: clients that silently drop unknown + `newPayloadV4` fields would answer VALID and must fail this test. + """ + sender = pre.fund_eoa() + receiver = pre.nonexistent_account() + + tx = Transaction(sender=sender, to=receiver, value=100) + + blockchain_test( + pre=pre, + post={}, + blocks=[ + Block( + timestamp=FORK_TIMESTAMP - 1, + txs=[tx], + # A valid empty-BAL encoding: field presence alone, not + # decodability, must trigger the rejection. + engine_new_payload_block_access_list=Bytes(b"\xc0"), + exception=BlockException.INCORRECT_BLOCK_FORMAT, + engine_api_error_code=EngineAPIError.InvalidParams, + ), + ], + ) + + @EIPChecklist.BlockHeaderField.Test.ForkTransition.After() @pytest.mark.valid_at_transition_to("Amsterdam") @pytest.mark.exception_test From 7603ff69a8dca74dfbbcdd7c90c63f023d4e8f77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Fri, 3 Jul 2026 11:54:57 +0200 Subject: [PATCH 087/233] feat(tests): EIP-8246 creation-tx initcode selfdestruct coverage (#3084) --- .../test_selfdestruct_no_burn.py | 44 ++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/tests/amsterdam/eip8246_selfdestruct_no_burn/test_selfdestruct_no_burn.py b/tests/amsterdam/eip8246_selfdestruct_no_burn/test_selfdestruct_no_burn.py index 3ba7cc5fa6e..1cf6462de75 100644 --- a/tests/amsterdam/eip8246_selfdestruct_no_burn/test_selfdestruct_no_burn.py +++ b/tests/amsterdam/eip8246_selfdestruct_no_burn/test_selfdestruct_no_burn.py @@ -1,4 +1,9 @@ -"""Tests for [EIP-8246: Remove SELFDESTRUCT balance burn](https://eips.ethereum.org/EIPS/eip-8246).""" +""" +Tests for [EIP-8246: Remove SELFDESTRUCT balance burn](https://eips.ethereum.org/EIPS/eip-8246). + +Further fork-aware EIP-8246 coverage lives in the EIP-6780 selfdestruct +tests (``tests/cancun/eip6780_selfdestruct``). +""" import pytest from execution_testing import ( @@ -10,6 +15,7 @@ Bytecode, Hash, Op, + StateTestFiller, Storage, Transaction, compute_create_address, @@ -225,3 +231,39 @@ def test_selfdestructing_initcode_preserves_balance( }, blocks=[Block(txs=[selfdestruct_tx, probe_tx])], ) + + +@pytest.mark.parametrize( + "value", + [pytest.param(1, id="kept"), pytest.param(0, id="removed")], +) +def test_create_transaction_initcode_selfdestruct( + state_test: StateTestFiller, + pre: Alloc, + value: int, +) -> None: + """ + Depth-0 creation-tx initcode SELFDESTRUCT keeps balance per EIP-8246. + + A creation transaction (``tx.to is None``) whose initcode + self-destructs to itself exercises the depth-0 create path. A nonzero + endowment is kept as a balance-only account; a zero endowment is + removed. + """ + sender = pre.fund_eoa() + created = compute_create_address(address=sender, nonce=sender.nonce) + + tx = Transaction( + sender=sender, + to=None, + value=value, + data=Op.SELFDESTRUCT(Op.ADDRESS), + ) + post = { + created: ( + Account(balance=value, nonce=0, code=b"", storage={}) + if value + else Account.NONEXISTENT + ) + } + state_test(pre=pre, post=post, tx=tx) From 3da5390f9f1d330acbd845025a89564e455ba743 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Fri, 3 Jul 2026 15:41:31 +0200 Subject: [PATCH 088/233] docs(spec-specs): fix stale Amsterdam fork docstring and comments (#3097) --- src/ethereum/forks/amsterdam/__init__.py | 20 +++++++++++++++++++ src/ethereum/forks/amsterdam/fork.py | 2 ++ .../forks/amsterdam/vm/instructions/system.py | 2 +- 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/ethereum/forks/amsterdam/__init__.py b/src/ethereum/forks/amsterdam/__init__.py index 36057c1a383..d2a3b030138 100644 --- a/src/ethereum/forks/amsterdam/__init__.py +++ b/src/ethereum/forks/amsterdam/__init__.py @@ -4,18 +4,38 @@ ### Changes +- [EIP-2780: Resource-based intrinsic transaction gas][EIP-2780] +- [EIP-7708: ETH transfers emit a log][EIP-7708] +- [EIP-7778: Block Gas Accounting without Refunds][EIP-7778] +- [EIP-7843: SLOTNUM][EIP-7843] - [EIP-7928: Block-Level Access Lists][EIP-7928] - [EIP-7954: Increase Maximum Contract Size][EIP-7954] +- [EIP-7976: Increase calldata floor cost][EIP-7976] +- [EIP-7981: Increase Access List Cost][EIP-7981] - [EIP-7997: Deterministic Factory Predeploy][EIP-7997] +- [EIP-8024: Stack Access Instructions][EIP-8024] +- [EIP-8037: State Creation Gas Cost Increase][EIP-8037] +- [EIP-8038: State Access Gas Cost Increase][EIP-8038] - [EIP-8246: Remove SELFDESTRUCT balance burn][EIP-8246] +- [EIP-8282: Builder Execution Requests][EIP-8282] ### Releases [EIP-7773]: https://eips.ethereum.org/EIPS/eip-7773 +[EIP-2780]: https://eips.ethereum.org/EIPS/eip-2780 +[EIP-7708]: https://eips.ethereum.org/EIPS/eip-7708 +[EIP-7778]: https://eips.ethereum.org/EIPS/eip-7778 +[EIP-7843]: https://eips.ethereum.org/EIPS/eip-7843 [EIP-7928]: https://eips.ethereum.org/EIPS/eip-7928 [EIP-7954]: https://eips.ethereum.org/EIPS/eip-7954 +[EIP-7976]: https://eips.ethereum.org/EIPS/eip-7976 +[EIP-7981]: https://eips.ethereum.org/EIPS/eip-7981 [EIP-7997]: https://eips.ethereum.org/EIPS/eip-7997 +[EIP-8024]: https://eips.ethereum.org/EIPS/eip-8024 +[EIP-8037]: https://eips.ethereum.org/EIPS/eip-8037 +[EIP-8038]: https://eips.ethereum.org/EIPS/eip-8038 [EIP-8246]: https://eips.ethereum.org/EIPS/eip-8246 +[EIP-8282]: https://eips.ethereum.org/EIPS/eip-8282 """ from ethereum.fork_criteria import ForkCriteria, Unscheduled diff --git a/src/ethereum/forks/amsterdam/fork.py b/src/ethereum/forks/amsterdam/fork.py index c2001554bd1..1f2771c4c49 100644 --- a/src/ethereum/forks/amsterdam/fork.py +++ b/src/ethereum/forks/amsterdam/fork.py @@ -1147,6 +1147,8 @@ def process_transaction( + tx_output.state_gas_used - int(tx_output.state_refund) ) + # Defensive guard for Uint conversion: State refunds never exceed + # the state charges so the value is non-negative. tx_regular_gas = tx_gas_used_before_refund - Uint(max(0, tx_state_gas)) block_output.block_gas_used += tx_regular_gas block_output.block_state_gas_used += Uint(max(0, tx_state_gas)) diff --git a/src/ethereum/forks/amsterdam/vm/instructions/system.py b/src/ethereum/forks/amsterdam/vm/instructions/system.py index a3587e4f238..9d4e4fa815d 100644 --- a/src/ethereum/forks/amsterdam/vm/instructions/system.py +++ b/src/ethereum/forks/amsterdam/vm/instructions/system.py @@ -688,7 +688,7 @@ def selfdestruct(evm: Evm) -> None: # Transfer balance move_ether(tx_state, originator, beneficiary, originator_balance) - # Emit transfer or burn log + # Emit transfer log if beneficiary != originator: emit_transfer_log(evm, originator, beneficiary, originator_balance) From 8dd924e9a41c3bcd9df9e85af7da350a47320702 Mon Sep 17 00:00:00 2001 From: Sam Wilson <57262657+SamWilsn@users.noreply.github.com> Date: Fri, 3 Jul 2026 10:32:47 -0400 Subject: [PATCH 089/233] chore(specs): reorder tx validity checks (#3036) * chore(specs): reorder tx validity checks * chore(tests): New eels base coverage test * fix comment --------- Co-authored-by: marioevz --- src/ethereum/forks/amsterdam/transactions.py | 4 +-- src/ethereum/forks/bpo1/transactions.py | 4 +-- src/ethereum/forks/bpo2/transactions.py | 4 +-- src/ethereum/forks/bpo3/transactions.py | 4 +-- src/ethereum/forks/bpo4/transactions.py | 4 +-- src/ethereum/forks/bpo5/transactions.py | 4 +-- src/ethereum/forks/cancun/transactions.py | 4 +-- src/ethereum/forks/osaka/transactions.py | 4 +-- src/ethereum/forks/prague/transactions.py | 4 +-- src/ethereum/forks/shanghai/transactions.py | 4 +-- tests/frontier/validation/test_transaction.py | 27 ++++++++++++++----- 11 files changed, 41 insertions(+), 26 deletions(-) diff --git a/src/ethereum/forks/amsterdam/transactions.py b/src/ethereum/forks/amsterdam/transactions.py index 136d0d91e72..9bb3262934a 100644 --- a/src/ethereum/forks/amsterdam/transactions.py +++ b/src/ethereum/forks/amsterdam/transactions.py @@ -615,6 +615,8 @@ def validate_transaction(tx: Transaction, sender: Address) -> IntrinsicGasCost: raise InsufficientTransactionGasError("Insufficient intrinsic gas") if intrinsic.calldata_floor > tx.gas: raise InsufficientTransactionGasError("Insufficient calldata floor") + if tx.to == Bytes0(b"") and len(tx.data) > MAX_INIT_CODE_SIZE: + raise InitCodeTooLargeError("Code size too large") if intrinsic.regular > TX_MAX_GAS_LIMIT: raise InsufficientTransactionGasError( "Intrinsic regular gas exceeds TX_MAX_GAS_LIMIT" @@ -625,8 +627,6 @@ def validate_transaction(tx: Transaction, sender: Address) -> IntrinsicGasCost: ) if U256(tx.nonce) >= U256(U64.MAX_VALUE): raise NonceOverflowError("Nonce too high") - if tx.to == Bytes0(b"") and len(tx.data) > MAX_INIT_CODE_SIZE: - raise InitCodeTooLargeError("Code size too large") return intrinsic diff --git a/src/ethereum/forks/bpo1/transactions.py b/src/ethereum/forks/bpo1/transactions.py index ad60842ee81..569f1c1ffc9 100644 --- a/src/ethereum/forks/bpo1/transactions.py +++ b/src/ethereum/forks/bpo1/transactions.py @@ -572,12 +572,12 @@ def validate_transaction(tx: Transaction) -> IntrinsicGasCost: intrinsic = calculate_intrinsic_cost(tx) if max(intrinsic.regular, intrinsic.calldata_floor) > tx.gas: raise InsufficientTransactionGasError("Insufficient gas") - if U256(tx.nonce) >= U256(U64.MAX_VALUE): - raise NonceOverflowError("Nonce too high") if tx.to == Bytes0(b"") and len(tx.data) > MAX_INIT_CODE_SIZE: raise InitCodeTooLargeError("Code size too large") if tx.gas > TX_MAX_GAS_LIMIT: raise TransactionGasLimitExceededError("Gas limit too high") + if U256(tx.nonce) >= U256(U64.MAX_VALUE): + raise NonceOverflowError("Nonce too high") return intrinsic diff --git a/src/ethereum/forks/bpo2/transactions.py b/src/ethereum/forks/bpo2/transactions.py index 569d6867270..2232f4e2976 100644 --- a/src/ethereum/forks/bpo2/transactions.py +++ b/src/ethereum/forks/bpo2/transactions.py @@ -572,12 +572,12 @@ def validate_transaction(tx: Transaction) -> IntrinsicGasCost: intrinsic = calculate_intrinsic_cost(tx) if max(intrinsic.regular, intrinsic.calldata_floor) > tx.gas: raise InsufficientTransactionGasError("Insufficient gas") - if U256(tx.nonce) >= U256(U64.MAX_VALUE): - raise NonceOverflowError("Nonce too high") if tx.to == Bytes0(b"") and len(tx.data) > MAX_INIT_CODE_SIZE: raise InitCodeTooLargeError("Code size too large") if tx.gas > TX_MAX_GAS_LIMIT: raise TransactionGasLimitExceededError("Gas limit too high") + if U256(tx.nonce) >= U256(U64.MAX_VALUE): + raise NonceOverflowError("Nonce too high") return intrinsic diff --git a/src/ethereum/forks/bpo3/transactions.py b/src/ethereum/forks/bpo3/transactions.py index 258364835f8..a06202c81ad 100644 --- a/src/ethereum/forks/bpo3/transactions.py +++ b/src/ethereum/forks/bpo3/transactions.py @@ -572,12 +572,12 @@ def validate_transaction(tx: Transaction) -> IntrinsicGasCost: intrinsic = calculate_intrinsic_cost(tx) if max(intrinsic.regular, intrinsic.calldata_floor) > tx.gas: raise InsufficientTransactionGasError("Insufficient gas") - if U256(tx.nonce) >= U256(U64.MAX_VALUE): - raise NonceOverflowError("Nonce too high") if tx.to == Bytes0(b"") and len(tx.data) > MAX_INIT_CODE_SIZE: raise InitCodeTooLargeError("Code size too large") if tx.gas > TX_MAX_GAS_LIMIT: raise TransactionGasLimitExceededError("Gas limit too high") + if U256(tx.nonce) >= U256(U64.MAX_VALUE): + raise NonceOverflowError("Nonce too high") return intrinsic diff --git a/src/ethereum/forks/bpo4/transactions.py b/src/ethereum/forks/bpo4/transactions.py index 5e86fc63f13..8a9080ba356 100644 --- a/src/ethereum/forks/bpo4/transactions.py +++ b/src/ethereum/forks/bpo4/transactions.py @@ -572,12 +572,12 @@ def validate_transaction(tx: Transaction) -> IntrinsicGasCost: intrinsic = calculate_intrinsic_cost(tx) if max(intrinsic.regular, intrinsic.calldata_floor) > tx.gas: raise InsufficientTransactionGasError("Insufficient gas") - if U256(tx.nonce) >= U256(U64.MAX_VALUE): - raise NonceOverflowError("Nonce too high") if tx.to == Bytes0(b"") and len(tx.data) > MAX_INIT_CODE_SIZE: raise InitCodeTooLargeError("Code size too large") if tx.gas > TX_MAX_GAS_LIMIT: raise TransactionGasLimitExceededError("Gas limit too high") + if U256(tx.nonce) >= U256(U64.MAX_VALUE): + raise NonceOverflowError("Nonce too high") return intrinsic diff --git a/src/ethereum/forks/bpo5/transactions.py b/src/ethereum/forks/bpo5/transactions.py index 2aded367166..136ef6a1475 100644 --- a/src/ethereum/forks/bpo5/transactions.py +++ b/src/ethereum/forks/bpo5/transactions.py @@ -572,12 +572,12 @@ def validate_transaction(tx: Transaction) -> IntrinsicGasCost: intrinsic = calculate_intrinsic_cost(tx) if max(intrinsic.regular, intrinsic.calldata_floor) > tx.gas: raise InsufficientTransactionGasError("Insufficient gas") - if U256(tx.nonce) >= U256(U64.MAX_VALUE): - raise NonceOverflowError("Nonce too high") if tx.to == Bytes0(b"") and len(tx.data) > MAX_INIT_CODE_SIZE: raise InitCodeTooLargeError("Code size too large") if tx.gas > TX_MAX_GAS_LIMIT: raise TransactionGasLimitExceededError("Gas limit too high") + if U256(tx.nonce) >= U256(U64.MAX_VALUE): + raise NonceOverflowError("Nonce too high") return intrinsic diff --git a/src/ethereum/forks/cancun/transactions.py b/src/ethereum/forks/cancun/transactions.py index d65da512655..f9d1cac4026 100644 --- a/src/ethereum/forks/cancun/transactions.py +++ b/src/ethereum/forks/cancun/transactions.py @@ -443,10 +443,10 @@ def validate_transaction(tx: Transaction) -> Uint: intrinsic_gas = calculate_intrinsic_cost(tx) if intrinsic_gas > tx.gas: raise InsufficientTransactionGasError("Insufficient gas") - if U256(tx.nonce) >= U256(U64.MAX_VALUE): - raise NonceOverflowError("Nonce too high") if tx.to == Bytes0(b"") and len(tx.data) > MAX_INIT_CODE_SIZE: raise InitCodeTooLargeError("Code size too large") + if U256(tx.nonce) >= U256(U64.MAX_VALUE): + raise NonceOverflowError("Nonce too high") return intrinsic_gas diff --git a/src/ethereum/forks/osaka/transactions.py b/src/ethereum/forks/osaka/transactions.py index 58b4f03c253..ae087f98730 100644 --- a/src/ethereum/forks/osaka/transactions.py +++ b/src/ethereum/forks/osaka/transactions.py @@ -576,12 +576,12 @@ def validate_transaction(tx: Transaction) -> IntrinsicGasCost: intrinsic = calculate_intrinsic_cost(tx) if max(intrinsic.regular, intrinsic.calldata_floor) > tx.gas: raise InsufficientTransactionGasError("Insufficient gas") - if U256(tx.nonce) >= U256(U64.MAX_VALUE): - raise NonceOverflowError("Nonce too high") if tx.to == Bytes0(b"") and len(tx.data) > MAX_INIT_CODE_SIZE: raise InitCodeTooLargeError("Code size too large") if tx.gas > TX_MAX_GAS_LIMIT: raise TransactionGasLimitExceededError("Gas limit too high") + if U256(tx.nonce) >= U256(U64.MAX_VALUE): + raise NonceOverflowError("Nonce too high") return intrinsic diff --git a/src/ethereum/forks/prague/transactions.py b/src/ethereum/forks/prague/transactions.py index 2c5dd7d1b1f..f28a2a636aa 100644 --- a/src/ethereum/forks/prague/transactions.py +++ b/src/ethereum/forks/prague/transactions.py @@ -569,10 +569,10 @@ def validate_transaction(tx: Transaction) -> IntrinsicGasCost: intrinsic = calculate_intrinsic_cost(tx) if max(intrinsic.regular, intrinsic.calldata_floor) > tx.gas: raise InsufficientTransactionGasError("Insufficient gas") - if U256(tx.nonce) >= U256(U64.MAX_VALUE): - raise NonceOverflowError("Nonce too high") if tx.to == Bytes0(b"") and len(tx.data) > MAX_INIT_CODE_SIZE: raise InitCodeTooLargeError("Code size too large") + if U256(tx.nonce) >= U256(U64.MAX_VALUE): + raise NonceOverflowError("Nonce too high") return intrinsic diff --git a/src/ethereum/forks/shanghai/transactions.py b/src/ethereum/forks/shanghai/transactions.py index b76058eaf39..59239f4a136 100644 --- a/src/ethereum/forks/shanghai/transactions.py +++ b/src/ethereum/forks/shanghai/transactions.py @@ -331,10 +331,10 @@ def validate_transaction(tx: Transaction) -> Uint: intrinsic_gas = calculate_intrinsic_cost(tx) if intrinsic_gas > tx.gas: raise InsufficientTransactionGasError("Insufficient gas") - if U256(tx.nonce) >= U256(U64.MAX_VALUE): - raise NonceOverflowError("Nonce too high") if tx.to == Bytes0(b"") and len(tx.data) > MAX_INIT_CODE_SIZE: raise InitCodeTooLargeError("Code size too large") + if U256(tx.nonce) >= U256(U64.MAX_VALUE): + raise NonceOverflowError("Nonce too high") return intrinsic_gas diff --git a/tests/frontier/validation/test_transaction.py b/tests/frontier/validation/test_transaction.py index 39613ae4ec6..fed47389481 100644 --- a/tests/frontier/validation/test_transaction.py +++ b/tests/frontier/validation/test_transaction.py @@ -76,9 +76,8 @@ def test_tx_gas_limit( @pytest.mark.pre_alloc_mutable @pytest.mark.eels_base_coverage def test_tx_nonce( - blockchain_test: BlockchainTestFiller, + state_test: StateTestFiller, pre: Alloc, - env: Environment, nonce_diff: int, expected_exception: TransactionException | None, ) -> None: @@ -96,12 +95,28 @@ def test_tx_nonce( error=expected_exception, ) - block = Block( - txs=[tx], - exception=expected_exception, + state_test(pre=pre, post={}, tx=tx) + + +@pytest.mark.exception_test +@pytest.mark.eels_base_coverage +def test_tx_max_nonce(state_test: StateTestFiller, pre: Alloc) -> None: + """ + Test that a transaction that exceeds the maximum allowed value for the + nonce (U64.MAX_VALUE) is rejected. + """ + sender = pre.fund_eoa() + to = pre.nonexistent_account() + + tx = Transaction( + to=to, + nonce=2**64, + sender=sender, + protected=False, + error=TransactionException.NONCE_IS_MAX, ) - blockchain_test(pre=pre, post={}, blocks=[block], genesis_environment=env) + state_test(pre=pre, post={sender: Account(nonce=0)}, tx=tx) @pytest.mark.parametrize( From f188d01b1c5f78fc8b3ff84a3b4e80d626034242 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Fri, 3 Jul 2026 18:17:47 +0200 Subject: [PATCH 090/233] fix(testing): clear watch screen via Console, not deprecated os.system (#3102) mypy's `deprecated` error code (enabled in pyproject.toml) flags the `os.system` call in the `--watch` file watcher. Use the Rich `Console.clear()` the watcher already holds instead of shelling out to `clear`/`cls`; it is cross-platform and removes the last `os` use, so drop `import os`. Why CI does not catch this: typeshed marks `os.system` `@deprecated` only for `sys.version_info >= (3, 14)`, and `[tool.mypy]` pins no `python_version`, so mypy targets whatever interpreter runs it. The `static` CI job has no setup-python step (unlike the fill jobs, which pin 3.14) and runs mypy under the runner default, CPython 3.13, where the marker is inactive. Local dev on 3.14 sees the error; CI stays green. Claude-Session: https://claude.ai/code/session_01Nw3qUNd4aNzVypuzNhbQyg --- .../src/execution_testing/cli/pytest_commands/watcher.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/watcher.py b/packages/testing/src/execution_testing/cli/pytest_commands/watcher.py index 8a2504baa0e..7f987124b67 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/watcher.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/watcher.py @@ -1,6 +1,5 @@ """File watcher implementation for --watch flag functionality.""" -import os import subprocess import time from pathlib import Path @@ -84,7 +83,7 @@ def run_fill() -> None: if current_mtimes != file_mtimes: if not self.verbose: - os.system("clear" if os.name != "nt" else "cls") + self.console.clear() self.console.print( "[yellow]File changes detected, " "re-running...[/yellow]\n" From 4f5c7d19adc916a268b7eadc196756068a325515 Mon Sep 17 00:00:00 2001 From: danceratopz Date: Sat, 4 Jul 2026 11:20:59 +0200 Subject: [PATCH 091/233] fix(test-consume,test-rpc): close RPC sessions on teardown to fix fd leak (#3094) --- .../plugins/consume/simulators/base.py | 7 ++-- .../simulators/build_block/conftest.py | 7 ++-- .../plugins/consume/simulators/engine_api.py | 15 +++++-- .../consume/simulators/sync/conftest.py | 39 ++++++++++++------- .../testing/src/execution_testing/rpc/rpc.py | 29 +++++++++++++- .../rpc/tests/test_session_lifecycle.py | 23 +++++++++++ 6 files changed, 94 insertions(+), 26 deletions(-) create mode 100644 packages/testing/src/execution_testing/rpc/tests/test_session_lifecycle.py diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/base.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/base.py index 969a7a06e67..694ce2bef87 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/base.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/base.py @@ -1,7 +1,7 @@ """Common pytest fixtures for the Hive simulators.""" from pathlib import Path -from typing import Dict, Literal +from typing import Dict, Generator, Literal import pytest from hive.client import Client @@ -20,9 +20,10 @@ @pytest.fixture(scope="function") -def eth_rpc(client: Client) -> EthRPC: +def eth_rpc(client: Client) -> Generator[EthRPC, None, None]: """Initialize ethereum RPC client for the execution client under test.""" - return EthRPC(f"http://{client.ip}:8545") + with EthRPC(f"http://{client.ip}:8545") as rpc: + yield rpc @pytest.fixture(scope="function") diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/build_block/conftest.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/build_block/conftest.py index e23b5d03563..782623841ab 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/build_block/conftest.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/build_block/conftest.py @@ -6,7 +6,7 @@ """ import io -from typing import Mapping +from typing import Generator, Mapping import pytest from hive.client import Client @@ -61,6 +61,7 @@ def genesis_header(fixture: BlockchainEngineFixture) -> FixtureHeader: @pytest.fixture(scope="function") -def testing_rpc(client: Client) -> TestingRPC: +def testing_rpc(client: Client) -> Generator[TestingRPC, None, None]: """Initialize Testing RPC client for the execution client under test.""" - return TestingRPC(f"http://{client.ip}:8545") + with TestingRPC(f"http://{client.ip}:8545") as rpc: + yield rpc diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/engine_api.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/engine_api.py index 93768cfdf2e..e60e916a452 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/engine_api.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/engine_api.py @@ -1,5 +1,7 @@ """Pytest fixtures for Engine API RPC clients.""" +from typing import Generator + import pytest from hive.client import Client @@ -10,7 +12,7 @@ @pytest.fixture(scope="function") def engine_rpc( client: Client, client_exception_mapper: ExceptionMapper | None -) -> EngineRPC: +) -> Generator[EngineRPC, None, None]: """ Initialize Engine RPC client for the execution client under test. @@ -20,19 +22,24 @@ def engine_rpc( validation to map client-specific error messages to standard exception types. + The session is closed on teardown. + Args: client: The Hive client instance to connect to. client_exception_mapper: Optional exception mapper. - Returns: + Yields: Configured EngineRPC instance for making Engine API calls. """ if client_exception_mapper: - return EngineRPC( + rpc = EngineRPC( f"http://{client.ip}:8551", response_validation_context={ "exception_mapper": client_exception_mapper, }, ) - return EngineRPC(f"http://{client.ip}:8551") + else: + rpc = EngineRPC(f"http://{client.ip}:8551") + with rpc: + yield rpc diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/sync/conftest.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/sync/conftest.py index 94ddfb14dba..fcfd790d0bd 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/sync/conftest.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/sync/conftest.py @@ -108,21 +108,24 @@ def pytest_collection_modifyitems( @pytest.fixture(scope="function") -def eth_rpc(client: Client) -> EthRPC: +def eth_rpc(client: Client) -> Generator[EthRPC, None, None]: """Initialize eth RPC client for the execution client under test.""" - return EthRPC(f"http://{client.ip}:8545") + with EthRPC(f"http://{client.ip}:8545") as rpc: + yield rpc @pytest.fixture(scope="function") -def net_rpc(client: Client) -> NetRPC: +def net_rpc(client: Client) -> Generator[NetRPC, None, None]: """Initialize net RPC client for the execution client under test.""" - return NetRPC(f"http://{client.ip}:8545") + with NetRPC(f"http://{client.ip}:8545") as rpc: + yield rpc @pytest.fixture(scope="function") -def admin_rpc(client: Client) -> AdminRPC: +def admin_rpc(client: Client) -> Generator[AdminRPC, None, None]: """Initialize admin RPC client for the execution client under test.""" - return AdminRPC(f"http://{client.ip}:8545") + with AdminRPC(f"http://{client.ip}:8545") as rpc: + yield rpc @pytest.fixture(scope="function") @@ -271,34 +274,40 @@ def sync_client_exception_mapper( @pytest.fixture(scope="function") def sync_engine_rpc( sync_client: Client, sync_client_exception_mapper: ExceptionMapper | None -) -> EngineRPC: +) -> Generator[EngineRPC, None, None]: """Initialize engine RPC client for the sync client.""" if sync_client_exception_mapper: - return EngineRPC( + rpc = EngineRPC( f"http://{sync_client.ip}:8551", response_validation_context={ "exception_mapper": sync_client_exception_mapper, }, ) - return EngineRPC(f"http://{sync_client.ip}:8551") + else: + rpc = EngineRPC(f"http://{sync_client.ip}:8551") + with rpc: + yield rpc @pytest.fixture(scope="function") -def sync_eth_rpc(sync_client: Client) -> EthRPC: +def sync_eth_rpc(sync_client: Client) -> Generator[EthRPC, None, None]: """Initialize eth RPC client for the sync client.""" - return EthRPC(f"http://{sync_client.ip}:8545") + with EthRPC(f"http://{sync_client.ip}:8545") as rpc: + yield rpc @pytest.fixture(scope="function") -def sync_net_rpc(sync_client: Client) -> NetRPC: +def sync_net_rpc(sync_client: Client) -> Generator[NetRPC, None, None]: """Initialize net RPC client for the sync client.""" - return NetRPC(f"http://{sync_client.ip}:8545") + with NetRPC(f"http://{sync_client.ip}:8545") as rpc: + yield rpc @pytest.fixture(scope="function") -def sync_admin_rpc(sync_client: Client) -> AdminRPC: +def sync_admin_rpc(sync_client: Client) -> Generator[AdminRPC, None, None]: """Initialize admin RPC client for the sync client.""" - return AdminRPC(f"http://{sync_client.ip}:8545") + with AdminRPC(f"http://{sync_client.ip}:8545") as rpc: + yield rpc @pytest.fixture(scope="module") diff --git a/packages/testing/src/execution_testing/rpc/rpc.py b/packages/testing/src/execution_testing/rpc/rpc.py index 3f9c64c332f..155e93aafde 100644 --- a/packages/testing/src/execution_testing/rpc/rpc.py +++ b/packages/testing/src/execution_testing/rpc/rpc.py @@ -8,7 +8,16 @@ from contextlib import AbstractContextManager, nullcontext from itertools import count from pprint import pprint -from typing import Any, Callable, ClassVar, Dict, List, Literal, Sequence +from typing import ( + Any, + Callable, + ClassVar, + Dict, + List, + Literal, + Self, + Sequence, +) import requests from jwt import encode @@ -210,6 +219,24 @@ def __init__( self.response_validation_context = response_validation_context self.session = requests.Session() + def close(self) -> None: + """ + Close the underlying HTTP session, releasing its pooled sockets. + + RPC instances are typically created per test; closing the session + on teardown prevents file descriptors from accumulating across a + client that serves many tests. + """ + self.session.close() + + def __enter__(self) -> Self: + """Enter the runtime context, returning this RPC instance.""" + return self + + def __exit__(self, *exc_info: object) -> None: + """Close the HTTP session on context-manager exit.""" + self.close() + def __init_subclass__(cls, namespace: str | None = None) -> None: """ Set namespace of the RPC class to the lowercase of the class name. diff --git a/packages/testing/src/execution_testing/rpc/tests/test_session_lifecycle.py b/packages/testing/src/execution_testing/rpc/tests/test_session_lifecycle.py new file mode 100644 index 00000000000..3ac5069b741 --- /dev/null +++ b/packages/testing/src/execution_testing/rpc/tests/test_session_lifecycle.py @@ -0,0 +1,23 @@ +"""Test the HTTP session lifecycle of `BaseRPC` clients.""" + +from unittest.mock import patch + +from execution_testing.rpc import EthRPC + + +def test_close_closes_session() -> None: + """`close()` closes the underlying HTTP session.""" + rpc = EthRPC("http://localhost:8545") + with patch.object(rpc.session, "close") as session_close: + rpc.close() + session_close.assert_called_once_with() + + +def test_context_manager_closes_session() -> None: + """The context manager yields the instance and closes on exit.""" + rpc = EthRPC("http://localhost:8545") + with patch.object(rpc.session, "close") as session_close: + with rpc as entered: + assert entered is rpc + session_close.assert_not_called() + session_close.assert_called_once_with() From ecd94a47d1ffa7d6fe8eba2cc342ff6944ad344f Mon Sep 17 00:00:00 2001 From: Edgar Date: Mon, 6 Jul 2026 08:56:25 +0200 Subject: [PATCH 092/233] chore(test-client-clis): map ethrex invalid signature v/r/s rejections to INVALID_SIGNATURE_VRS (#3104) --- .../testing/src/execution_testing/client_clis/clis/ethrex.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/testing/src/execution_testing/client_clis/clis/ethrex.py b/packages/testing/src/execution_testing/client_clis/clis/ethrex.py index bb8ec00ec7e..707ebdacbe3 100644 --- a/packages/testing/src/execution_testing/client_clis/clis/ethrex.py +++ b/packages/testing/src/execution_testing/client_clis/clis/ethrex.py @@ -61,6 +61,11 @@ class EthrexExceptionMapper(ExceptionMapper): ), } mapping_regex = { + TransactionException.INVALID_SIGNATURE_VRS: ( + r"Couldn't recover addresses with error: invalid signature|" + r"Error decoding field 'signature_y_parity' of type bool: " + r"MalformedBoolean" + ), TransactionException.PRIORITY_GREATER_THAN_MAX_FEE_PER_GAS: ( r"(?i)priority fee.* is greater than max fee.*" ), From e0e4abc744fda937ccb4da26b9d5c4bdd1e74bc5 Mon Sep 17 00:00:00 2001 From: milen <94537774+taratorio@users.noreply.github.com> Date: Mon, 6 Jul 2026 17:17:07 +1000 Subject: [PATCH 093/233] fix(consume): map erigon INVALID_SIGNATURE_VRS exception (#3105) Erigon rejects bad-signature transactions with "invalid transaction v, r, s values" (types.ErrInvalidSig), but ErigonExceptionMapper had no entry for TransactionException.INVALID_SIGNATURE_VRS. As a result consume-engine / consume-rlp report "Undefined exception message" for the frontier/validation/test_transaction::test_bad_v_r_s cases (24 per fork, surfaced by the tests@v20.0.0 fixtures). Add the substring mapping. --- .../testing/src/execution_testing/client_clis/clis/erigon.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/testing/src/execution_testing/client_clis/clis/erigon.py b/packages/testing/src/execution_testing/client_clis/clis/erigon.py index 3ef96449490..3599d6e69d4 100644 --- a/packages/testing/src/execution_testing/client_clis/clis/erigon.py +++ b/packages/testing/src/execution_testing/client_clis/clis/erigon.py @@ -54,6 +54,9 @@ class ErigonExceptionMapper(ExceptionMapper): TransactionException.NONCE_MISMATCH_TOO_HIGH: "nonce too high", TransactionException.GAS_ALLOWANCE_EXCEEDED: "gas limit reached", TransactionException.INVALID_CHAINID: "invalid chain id for signer", + TransactionException.INVALID_SIGNATURE_VRS: ( + "invalid transaction v, r, s values" + ), TransactionException.TYPE_3_TX_PRE_FORK: ( "blob txn is not supported by signer" ), From f878b229bd85f899cd38a44644ae2ec734461471 Mon Sep 17 00:00:00 2001 From: danceratopz Date: Mon, 6 Jul 2026 13:04:35 +0200 Subject: [PATCH 094/233] perf(test-consume): align enginex engine-API flow and skip redundant genesis check (#3093) --- .../plugins/consume/simulators/base.py | 12 ++ .../simulator_logic/test_via_engine.py | 122 +++++++++--------- 2 files changed, 71 insertions(+), 63 deletions(-) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/base.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/base.py index 694ce2bef87..5d44d99f758 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/base.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/base.py @@ -26,6 +26,18 @@ def eth_rpc(client: Client) -> Generator[EthRPC, None, None]: yield rpc +@pytest.fixture(scope="session") +def genesis_verified_clients() -> set[str]: + """ + Return the set of client ids whose genesis block has been verified. + + Genesis is immutable per client, so the `getBlockByNumber(0)` check only + needs to run once per client. In enginex mode a client is reused across a + pre-alloc group, letting later tests skip the redundant check. + """ + return set() + + @pytest.fixture(scope="function") def check_live_port(test_suite_name: str) -> Literal[8545, 8551]: """Port used by hive to check for liveness of the client.""" diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/simulator_logic/test_via_engine.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/simulator_logic/test_via_engine.py index 9370c77b0b6..f064c4f48e2 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/simulator_logic/test_via_engine.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/simulator_logic/test_via_engine.py @@ -15,6 +15,8 @@ from typing import Union +from hive.client import Client + from execution_testing.exceptions import UndefinedException from execution_testing.fixtures import ( BlockchainEngineFixture, @@ -46,6 +48,8 @@ def test_blockchain_via_engine( timing_data: TimingData, eth_rpc: EthRPC, engine_rpc: EngineRPC, + client: Client, + genesis_verified_clients: set[str], fixture: Union[BlockchainEngineFixture, BlockchainEngineXFixture], strict_exception_matching: bool, genesis_header: FixtureHeader, @@ -53,69 +57,63 @@ def test_blockchain_via_engine( """ Execute blockchain test fixtures against a client using the Engine API. - This function supports two modes: - - 1. **Engine Mode** (`BlockchainEngineFixture`): - - Uses per-test clients (started fresh for each test). - - Always performs initial FCU to genesis. - - Always performs FCU after valid payloads. - - genesis_header comes from fixture.genesis (via fixture). - - needs_genesis_init is always True (via fixture). - - 2. **EngineX Mode** (`BlockchainEngineXFixture`): - - Reuses clients across tests with same pre-alloc group. - - Skips initial FCU for reused clients. - - Skips FCU after valid payloads to keep client at genesis. - - genesis_header comes from separate pre_alloc_group fixture. - - needs_genesis_init is False for reused clients. - - Steps: - 1. Check the client genesis block hash matches genesis_header.block_hash - 2. Execute test fixture blocks using engine_newPayloadVX - 3. For valid payloads, perform forkchoice update to finalize chain - (unless client is being reused, in which case skip FCU) + This function supports both engine mode (`BlockchainEngineFixture`) + with per-test clients and enginex mode (`BlockchainEngineXFixture`) + with client reuse across tests sharing a pre-alloc group. + + Both modes follow the same test sequence for equivalence: + + 1. Send initial FCU to genesis to establish the chain head. + 2. Verify the client genesis block hash matches genesis_header. Genesis + is immutable per client, so in shared-client (enginex) mode this is + done once per client and skipped for later tests in the group. + 3. Execute test fixture blocks using engine_newPayloadVX. + 4. For valid payloads, send FCU to advance the chain head. """ - if isinstance(fixture, BlockchainEngineFixture): - with timing_data.time("Initial forkchoice update"): - logger.info( - "Sending initial forkchoice update to genesis block..." + with timing_data.time("Initial forkchoice update"): + logger.info("Sending initial forkchoice update to genesis block...") + try: + response = engine_rpc.forkchoice_updated_with_retry( + forkchoice_state=ForkchoiceState( + head_block_hash=genesis_header.block_hash, + ), + forkchoice_version=fixture.payloads[ + 0 + ].forkchoice_updated_version, + max_attempts=30, + wait_fixed=1.0, ) - try: - response = engine_rpc.forkchoice_updated_with_retry( - forkchoice_state=ForkchoiceState( - head_block_hash=fixture.genesis.block_hash, - ), - forkchoice_version=fixture.payloads[ - 0 - ].forkchoice_updated_version, - max_attempts=30, - wait_fixed=1.0, - ) - if response.payload_status.status != PayloadStatusEnum.VALID: - raise LoggedError( - f"Unexpected status on forkchoice updated to genesis: " - f"{response.payload_status.status}" - ) - except ForkchoiceUpdateTimeoutError as e: + if response.payload_status.status != PayloadStatusEnum.VALID: raise LoggedError( - f"Timed out waiting for forkchoice update to genesis: {e}" - ) from None - - with timing_data.time("Get genesis block"): - logger.info("Calling getBlockByNumber to get genesis block...") - genesis_block = eth_rpc.get_block_by_number(0) - assert genesis_block is not None, "genesis_block is None" - if genesis_block["hash"] != str(genesis_header.block_hash): - expected = genesis_header.block_hash - got = genesis_block["hash"] - logger.fail( - f"Genesis block hash mismatch. " - f"Expected: {expected}, Got: {got}" - ) - raise GenesisBlockMismatchExceptionError( - expected_header=genesis_header, - got_genesis_block=genesis_block, - ) + f"Unexpected status on forkchoice updated to genesis: " + f"{response.payload_status.status}" + ) + except ForkchoiceUpdateTimeoutError as e: + raise LoggedError( + f"Timed out waiting for forkchoice update to genesis: {e}" + ) from None + + if client.id not in genesis_verified_clients: + with timing_data.time("Get genesis block"): + logger.info("Calling getBlockByNumber to get genesis block...") + genesis_block = eth_rpc.get_block_by_number(0) + assert genesis_block is not None, "genesis_block is None" + if genesis_block["hash"] != str(genesis_header.block_hash): + expected = genesis_header.block_hash + got = genesis_block["hash"] + logger.fail( + f"Genesis block hash mismatch. " + f"Expected: {expected}, Got: {got}" + ) + raise GenesisBlockMismatchExceptionError( + expected_header=genesis_header, + got_genesis_block=genesis_block, + ) + # Genesis is immutable per client, so verify it once per client. In + # shared-client (enginex) mode the same client serves every test in a + # pre-alloc group, so later tests skip the redundant getBlockByNumber + # round-trip; per-test clients get a fresh id each test and re-verify. + genesis_verified_clients.add(client.id) with timing_data.time("Payloads execution") as total_payload_timing: logger.info( @@ -214,9 +212,7 @@ def test_blockchain_via_engine( f"expected: {payload.error_code}" ) from e - if payload.valid() and isinstance( - fixture, BlockchainEngineFixture - ): + if payload.valid(): with payload_timing.time( f"engine_forkchoiceUpdatedV{payload.forkchoice_updated_version}" ): From b8d7c7a5a5b4dc58ff5f594d64b39b7b66198615 Mon Sep 17 00:00:00 2001 From: danceratopz Date: Mon, 6 Jul 2026 14:11:16 +0200 Subject: [PATCH 095/233] fix(test-types,test-forks): import `ethereum` lazily so xdist fill coverage measures it (#3059) Co-authored-by: spencer --- .../base_types/base_types.py | 7 ++- .../pytest_commands/plugins/forks/forks.py | 46 ++++++++++++++----- .../client_clis/clis/execution_specs.py | 39 ++++++++++++---- .../src/execution_testing/test_types/trie.py | 15 +++++- 4 files changed, 85 insertions(+), 22 deletions(-) diff --git a/packages/testing/src/execution_testing/base_types/base_types.py b/packages/testing/src/execution_testing/base_types/base_types.py index c81b61ef8f3..5727aa46caa 100644 --- a/packages/testing/src/execution_testing/base_types/base_types.py +++ b/packages/testing/src/execution_testing/base_types/base_types.py @@ -13,7 +13,6 @@ TypeVar, ) -from ethereum.crypto.hash import keccak256 as _keccak256 from pydantic import GetCoreSchemaHandler, StringConstraints from pydantic_core.core_schema import ( PlainValidatorFunctionSchema, @@ -201,6 +200,12 @@ def hex(self, *args: Any, **kwargs: Any) -> str: def keccak256(self) -> "Hash": """Return the keccak256 hash of the opcode byte representation.""" + # Imported lazily so that merely importing the test framework does not + # import the `ethereum` package: on xdist workers that import would + # happen before pytest-cov starts the worker's coverage session, + # making coverage report `ethereum` as "module-not-measured". + from ethereum.crypto.hash import keccak256 as _keccak256 + return Hash(_keccak256(self)) def sha256(self) -> "Hash": diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/forks/forks.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/forks/forks.py index ca56fce5c7f..d927c6b1637 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/forks/forks.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/forks/forks.py @@ -12,6 +12,7 @@ Callable, ClassVar, Dict, + FrozenSet, Iterable, Iterator, List, @@ -23,7 +24,7 @@ import pytest from _pytest.mark.structures import ParameterSet -from pytest import Mark, Metafunc +from pytest import Mark, Metafunc, StashKey from execution_testing.client_clis import TransitionTool from execution_testing.forks import ( @@ -45,6 +46,10 @@ logger = get_logger(__name__) +# Session-scoped cache for the lazily-computed unsupported-fork set +# (see `get_unsupported_forks`). +unsupported_forks_key: StashKey[FrozenSet[Fork | TransitionFork]] = StashKey() + def pytest_addoption(parser: pytest.Parser) -> None: """Add command-line options to pytest.""" @@ -622,18 +627,38 @@ def get_fork_option( returncode=pytest.ExitCode.USAGE_ERROR, ) - config.unsupported_forks: Set[Fork | TransitionFork] = set() # type: ignore + +def get_unsupported_forks( + config: pytest.Config, +) -> FrozenSet[Fork | TransitionFork]: + """ + Return the selected forks not supported by the configured t8n tool. + + The result is computed once and cached in ``config.stash``. Computation is + deferred out of ``pytest_configure`` (where it previously lived) so that + the ``ethereum`` package, imported when the t8n tool is queried, is only + imported after pytest-cov has started the xdist worker's coverage session. + Importing it earlier left it "previously imported, but not measured". + """ + cached = config.stash.get(unsupported_forks_key, None) + if cached is not None: + return cached + + selected_fork_set: Set[Fork | TransitionFork] = config.selected_fork_set # type: ignore[attr-defined] t8n: TransitionTool | None = getattr(config, "t8n", None) - if t8n: - config.unsupported_forks = frozenset( # type: ignore + if t8n is None: + unsupported_forks: FrozenSet[Fork | TransitionFork] = frozenset() + else: + unsupported_forks = frozenset( fork for fork in selected_fork_set if not t8n.is_fork_supported(fork.transitions_from()) or not t8n.is_fork_supported(fork.transitions_to()) ) - logger.debug( - f"List of unsupported forks: {list(config.unsupported_forks)}" # type: ignore - ) + logger.debug(f"List of unsupported forks: {list(unsupported_forks)}") + + config.stash[unsupported_forks_key] = unsupported_forks + return unsupported_forks @pytest.hookimpl(trylast=True) @@ -653,7 +678,7 @@ def pytest_report_header(config: pytest.Config, start_path: Any) -> List[str]: + reset ), ] - unsupported_forks: Set[Fork | TransitionFork] = config.unsupported_forks # type: ignore[attr-defined] + unsupported_forks = get_unsupported_forks(config) if unsupported_forks: t8n_name = config.t8n.__class__.__name__ # type: ignore[attr-defined] excluded = ", ".join(f.name() for f in sorted(unsupported_forks)) @@ -1293,10 +1318,7 @@ def pytest_generate_tests(metafunc: pytest.Metafunc) -> None: if "fork" not in metafunc.fixturenames: return - unsupported_forks: Set[Fork | TransitionFork] = ( - metafunc.config.unsupported_forks # type: ignore - ) - intersection_set -= unsupported_forks + intersection_set -= get_unsupported_forks(metafunc.config) if not intersection_set: if metafunc.config.getoption("verbose") >= 2: diff --git a/packages/testing/src/execution_testing/client_clis/clis/execution_specs.py b/packages/testing/src/execution_testing/client_clis/clis/execution_specs.py index be3b9939a1a..7df1e8ab2bc 100644 --- a/packages/testing/src/execution_testing/client_clis/clis/execution_specs.py +++ b/packages/testing/src/execution_testing/client_clis/clis/execution_specs.py @@ -6,12 +6,8 @@ import tempfile from io import StringIO from pathlib import Path -from typing import Any, ClassVar, Dict, Optional +from typing import TYPE_CHECKING, Any, ClassVar, Dict, Optional -import ethereum -from ethereum_spec_tools.evm_tools import create_parser -from ethereum_spec_tools.evm_tools.t8n import T8N, ForkCache -from ethereum_spec_tools.evm_tools.utils import get_supported_forks from typing_extensions import override from execution_testing.client_clis.cli_types import TransitionToolOutput @@ -31,6 +27,9 @@ ) from execution_testing.forks import Fork +if TYPE_CHECKING: + from ethereum_spec_tools.evm_tools.t8n import ForkCache + class ExecutionSpecsTransitionTool(TransitionTool): """Implementation of the EELS T8N for execution-spec-tests.""" @@ -49,18 +48,39 @@ def __init__( self.exception_mapper = ExecutionSpecsExceptionMapper() self.trace = trace self._info_metadata: Optional[Dict[str, Any]] = {} - self.fork_cache = ForkCache() + # Defer importing the `ethereum` package (see `fork_cache` and + # `version`) until the tool is actually used. The tool is constructed + # during `pytest_configure`, which on xdist workers runs *before* + # pytest-cov starts the worker's coverage session; importing `ethereum` + # here would make coverage report it as "module-not-measured". + self._fork_cache: Optional["ForkCache"] = None + + @property + def fork_cache(self) -> "ForkCache": + """Lazily import and instantiate the EELS fork cache on first use.""" + if self._fork_cache is None: + from ethereum_spec_tools.evm_tools.t8n import ForkCache + + self._fork_cache = ForkCache() + return self._fork_cache @override def shutdown(self) -> None: - self.fork_cache.__exit__() + if self._fork_cache is not None: + self._fork_cache.__exit__() def version(self) -> str: """Version of the t8n tool.""" - return ethereum.__version__ + # Use package metadata rather than `ethereum.__version__` to avoid + # importing `ethereum` here (see `__init__` for why it must stay lazy). + from importlib.metadata import version + + return version("ethereum-execution") def is_fork_supported(self, fork: Fork) -> bool: """Return True if the fork is supported by the tool.""" + from ethereum_spec_tools.evm_tools.utils import get_supported_forks + return fork.transition_tool_name() in get_supported_forks() def _evaluate( @@ -74,6 +94,9 @@ def _evaluate( """ Evaluate using the EELS T8N entry point. """ + from ethereum_spec_tools.evm_tools import create_parser + from ethereum_spec_tools.evm_tools.t8n import T8N + del slow_request, profiler request_data = transition_tool_data.get_request_data() request_data_json = request_data.model_dump( diff --git a/packages/testing/src/execution_testing/test_types/trie.py b/packages/testing/src/execution_testing/test_types/trie.py index 16fd4d5709d..aec7206697e 100644 --- a/packages/testing/src/execution_testing/test_types/trie.py +++ b/packages/testing/src/execution_testing/test_types/trie.py @@ -18,7 +18,6 @@ cast, ) -from ethereum.crypto.hash import keccak256 from ethereum_rlp import Extended, rlp from ethereum_types.bytes import Bytes, Bytes20, Bytes32 from ethereum_types.frozen import slotted_freezable @@ -26,6 +25,20 @@ from typing_extensions import assert_type +def keccak256(buffer: bytes | bytearray) -> Bytes32: + """ + Compute the keccak256 hash of ``buffer``. + + The spec implementation is imported lazily so that importing this module + does not import the ``ethereum`` package: on xdist workers that import + would otherwise happen before pytest-cov starts the worker's coverage + session, making coverage report ``ethereum`` as "module-not-measured". + """ + from ethereum.crypto.hash import keccak256 as _keccak256 + + return _keccak256(buffer) + + @slotted_freezable @dataclass class FrontierAccount: From b548afb0f8b92c9745206ab958dcde7e4d82ab28 Mon Sep 17 00:00:00 2001 From: danceratopz Date: Mon, 6 Jul 2026 14:28:23 +0200 Subject: [PATCH 096/233] fix(test-fill): fix `derived_test` marking for single-format tests (#3108) --- .../pytest_commands/plugins/filler/filler.py | 33 +++- .../filler/tests/test_derived_test_marker.py | 179 ++++++++++++++++++ .../src/execution_testing/specs/base.py | 3 +- .../src/execution_testing/specs/benchmark.py | 5 +- .../src/execution_testing/specs/blockchain.py | 5 +- .../src/execution_testing/specs/state.py | 5 +- 6 files changed, 208 insertions(+), 22 deletions(-) create mode 100644 packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_derived_test_marker.py diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py index ffcced6e476..012fad66e2f 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py @@ -26,6 +26,7 @@ import pytest import xdist from _pytest.compat import NotSetType +from _pytest.mark.structures import ParameterSet from _pytest.terminal import TerminalReporter from filelock import FileLock from pytest_metadata.plugin import metadata_key @@ -1779,20 +1780,36 @@ def pytest_generate_tests(metafunc: pytest.Metafunc) -> None: FillerFile.collect() in ./static_filler.py for more details. """ session: FillingSession = metafunc.config.filling_session # type: ignore[attr-defined] + markers = list(metafunc.definition.iter_markers()) for test_type in BaseTest.spec_types.values(): if test_type.pytest_parameter_name() in metafunc.fixturenames: - parameters = [] - for i, format_with_or_without_label in enumerate( - test_type.supported_fixture_formats - ): + parameters: List[ParameterSet] = [] + for ( + format_with_or_without_label + ) in test_type.supported_fixture_formats: if not session.should_generate_format( format_with_or_without_label ): continue + fixture_format = ( + format_with_or_without_label.format + if isinstance( + format_with_or_without_label, LabeledFixtureFormat + ) + else format_with_or_without_label + ) + if test_type.discard_fixture_format_by_marks( + fixture_format, markers + ): + continue parameter = labeled_format_parameter_set( format_with_or_without_label ) - if i > 0: + # The first surviving format is the test's primary; the + # rest are derived from it (e.g. a BlockchainTest derived + # from a StateTest) and can be deselected with + # `-m "not derived_test"`. + if parameters: parameter.marks.append(pytest.mark.derived_test) # type: ignore parameters.append(parameter) metafunc.parametrize( @@ -1857,9 +1874,9 @@ def pytest_collection_modifyitems( if fixture_format.discard_fixture_format_by_marks(fork, markers): items_for_removal.append(i) continue - if spec_type.discard_fixture_format_by_marks( - fixture_format, fork, markers - ): + # Only static tests can be discarded here: dynamic tests never + # generate discarded formats (see pytest_generate_tests above). + if spec_type.discard_fixture_format_by_marks(fixture_format, markers): items_for_removal.append(i) continue for marker in markers: diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_derived_test_marker.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_derived_test_marker.py new file mode 100644 index 00000000000..a7891c0db86 --- /dev/null +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_derived_test_marker.py @@ -0,0 +1,179 @@ +""" +Test that the `derived_test` marker tracks the first fixture format that is +actually generated for a test, not merely the first entry in +``supported_fixture_formats``. + +Two ways the positional-first format can drop out, leaving a fixture that +used to be tagged ``derived_test`` purely because of its list position: + +1. A single-format marker such as ``blockchain_test_engine_only`` discards + the test's default (primary) format. +2. A session-level format filter (e.g. a ``--generate-pre-alloc-groups`` + session, which only generates EngineX fixtures) excludes the primary + format from parametrization entirely. + +In both cases the surviving format is the test's effective primary, so it +must stay unmarked and remain selectable via ``-m "not derived_test"``. +""" + +import textwrap + +import pytest + +# A post-Paris fork is required: pre-Paris hive/engine fixtures are removed +# during collection (see `pytest_collection_modifyitems` in filler.py), which +# would empty a `blockchain_test_engine_only` test regardless of this marker. +FORK = "Prague" + +ENGINE_ONLY_MODULE = textwrap.dedent( + f"""\ + import pytest + + @pytest.mark.valid_at("{FORK}") + @pytest.mark.blockchain_test_engine_only + def test_case(blockchain_test) -> None: + pass + """ +) + +NORMAL_BLOCKCHAIN_MODULE = textwrap.dedent( + f"""\ + import pytest + + @pytest.mark.valid_at("{FORK}") + def test_case(blockchain_test) -> None: + pass + """ +) + +STATE_ONLY_MODULE = textwrap.dedent( + f"""\ + import pytest + + @pytest.mark.valid_at("{FORK}") + @pytest.mark.state_test_only + def test_case(state_test) -> None: + pass + """ +) + +NORMAL_STATE_MODULE = textwrap.dedent( + f"""\ + import pytest + + @pytest.mark.valid_at("{FORK}") + def test_case(state_test) -> None: + pass + """ +) + +TEST_MODULE_DIR = "tests/prague/dummy_test_module" + + +def write_test_module(pytester: pytest.Pytester, module_source: str) -> None: + """ + Write a test module and the fill ini file to the pytester directory. + """ + module_dir = pytester.path / TEST_MODULE_DIR + module_dir.mkdir(parents=True) + (module_dir / "test_dummy.py").write_text(module_source) + pytester.copy_example( + name="src/execution_testing/cli/pytest_commands/pytest_ini_files/pytest-fill.ini" + ) + + +@pytest.mark.parametrize( + "module_source,present,absent", + [ + pytest.param( + ENGINE_ONLY_MODULE, + "-blockchain_test_engine]", + "-blockchain_test]", + id="engine_only_survivor_is_primary", + ), + pytest.param( + NORMAL_BLOCKCHAIN_MODULE, + "-blockchain_test]", + "-blockchain_test_engine]", + id="normal_test_still_marks_derived", + ), + pytest.param( + STATE_ONLY_MODULE, + "-state_test]", + "-blockchain_test_from_state_test]", + id="state_only_survivor_is_primary", + ), + ], +) +def test_not_derived_test_selects_primary_survivor( + pytester: pytest.Pytester, + module_source: str, + present: str, + absent: str, +) -> None: + """ + Collect with ``-m "not derived_test"`` and assert the test's primary + (first surviving) fixture format is selected while its derived formats are + not. + """ + write_test_module(pytester, module_source) + + result = pytester.runpytest( + "-c", + "pytest-fill.ini", + "--fork", + FORK, + "-m", + "not derived_test", + TEST_MODULE_DIR, + "--collect-only", + "-q", + ) + + assert result.ret == 0, f"Collection failed:\n{result.outlines}" + assert any(present in line for line in result.outlines), ( + f"Expected {present!r} to be collected:\n{result.outlines}" + ) + assert not any(absent in line for line in result.outlines), ( + f"Expected {absent!r} to be absent under `not derived_test`:\n" + f"{result.outlines}" + ) + + +def test_not_derived_test_selects_session_filter_survivor( + pytester: pytest.Pytester, +) -> None: + """ + Collect a plain state test in a ``--generate-pre-alloc-groups`` session + with ``-m "not derived_test"`` and assert that the only format generated + in this session (EngineX) is selected as the test's primary. + + The session-level format filter (``should_generate_format``) excludes all + other formats before parametrization, so the EngineX fixture must not + inherit a ``derived_test`` mark from its position in + ``supported_fixture_formats``. + """ + write_test_module(pytester, NORMAL_STATE_MODULE) + + result = pytester.runpytest( + "-c", + "pytest-fill.ini", + "--fork", + FORK, + "--generate-pre-alloc-groups", + "-m", + "not derived_test", + TEST_MODULE_DIR, + "--collect-only", + "-q", + ) + + engine_x = "-blockchain_test_engine_x_from_state_test]" + assert result.ret == 0, f"Collection failed:\n{result.outlines}" + assert any(engine_x in line for line in result.outlines), ( + f"Expected {engine_x!r} to be collected:\n{result.outlines}" + ) + assert not any("-state_test]" in line for line in result.outlines), ( + "Expected the state test format to be excluded by the session " + f"format filter:\n{result.outlines}" + ) diff --git a/packages/testing/src/execution_testing/specs/base.py b/packages/testing/src/execution_testing/specs/base.py index 49cf517ff7b..5a330a5e7e9 100644 --- a/packages/testing/src/execution_testing/specs/base.py +++ b/packages/testing/src/execution_testing/specs/base.py @@ -142,14 +142,13 @@ def model_post_init(self, __context: Any, /) -> None: def discard_fixture_format_by_marks( cls, fixture_format: FixtureFormat, - fork: Fork | TransitionFork, markers: List[pytest.Mark], ) -> bool: """ Discard a fixture format from filling if the appropriate marker is used. """ - del fork, fixture_format, markers + del fixture_format, markers return False @classmethod diff --git a/packages/testing/src/execution_testing/specs/benchmark.py b/packages/testing/src/execution_testing/specs/benchmark.py index 9e75c303313..ae283b98fa2 100644 --- a/packages/testing/src/execution_testing/specs/benchmark.py +++ b/packages/testing/src/execution_testing/specs/benchmark.py @@ -38,7 +38,7 @@ FixtureFormat, LabeledFixtureFormat, ) -from execution_testing.forks import Fork, TransitionFork +from execution_testing.forks import Fork from execution_testing.test_types import Alloc, Environment, Transaction from execution_testing.vm import Bytecode, Op @@ -421,15 +421,12 @@ def pytest_parameter_name(cls) -> str: def discard_fixture_format_by_marks( cls, fixture_format: FixtureFormat, - fork: Fork | TransitionFork, markers: List[pytest.Mark], ) -> bool: """ Discard a fixture format from filling if the appropriate marker is used. """ - del fork - if "blockchain_test_only" in [m.name for m in markers]: return fixture_format != BlockchainFixture if "blockchain_test_engine_only" in [m.name for m in markers]: diff --git a/packages/testing/src/execution_testing/specs/blockchain.py b/packages/testing/src/execution_testing/specs/blockchain.py index a9948f289ee..6f927f24729 100644 --- a/packages/testing/src/execution_testing/specs/blockchain.py +++ b/packages/testing/src/execution_testing/specs/blockchain.py @@ -83,7 +83,7 @@ FixtureTransactionReceipt, ) from execution_testing.fixtures.post_verifications import PostVerifications -from execution_testing.forks import Fork, TransitionFork +from execution_testing.forks import Fork from execution_testing.test_types import ( Alloc, Environment, @@ -740,15 +740,12 @@ class BlockchainTest(BaseTest): def discard_fixture_format_by_marks( cls, fixture_format: FixtureFormat, - fork: Fork | TransitionFork, markers: List[pytest.Mark], ) -> bool: """ Discard a fixture format from filling if the appropriate marker is used. """ - del fork - marker_names = [m.name for m in markers] if ( fixture_format != BlockchainFixture diff --git a/packages/testing/src/execution_testing/specs/state.py b/packages/testing/src/execution_testing/specs/state.py index 4fe88209fcf..6e13e07480a 100644 --- a/packages/testing/src/execution_testing/specs/state.py +++ b/packages/testing/src/execution_testing/specs/state.py @@ -46,7 +46,7 @@ FixtureTransaction, FixtureTransactionReceipt, ) -from execution_testing.forks import Fork, TransitionFork +from execution_testing.forks import Fork from execution_testing.logging import ( get_logger, ) @@ -233,15 +233,12 @@ def verify_modified_gas_limit( def discard_fixture_format_by_marks( cls, fixture_format: FixtureFormat, - fork: Fork | TransitionFork, markers: List[pytest.Mark], ) -> bool: """ Discard a fixture format from filling if the appropriate marker is used. """ - del fork - if "state_test_only" in [m.name for m in markers]: return fixture_format != StateFixture return False From 988ccb52b60497b16ac1b57a9c8230da63767e96 Mon Sep 17 00:00:00 2001 From: danceratopz Date: Mon, 6 Jul 2026 14:49:30 +0200 Subject: [PATCH 097/233] chore(testing): silence pytest collection warnings (#3109) Add `__test__ = False` to `TestingRPC`, `TestInfo`, and `TestPhase` so that pytest does not attempt to collect these `Test`-prefixed framework classes when running the framework's own test suite, which previously emitted a `PytestCollectionWarning` for each of them. --- packages/testing/src/execution_testing/fixtures/collector.py | 2 ++ packages/testing/src/execution_testing/rpc/rpc.py | 2 ++ .../testing/src/execution_testing/test_types/phase_manager.py | 2 ++ 3 files changed, 6 insertions(+) diff --git a/packages/testing/src/execution_testing/fixtures/collector.py b/packages/testing/src/execution_testing/fixtures/collector.py index 02a69858dce..5865e99e7b3 100644 --- a/packages/testing/src/execution_testing/fixtures/collector.py +++ b/packages/testing/src/execution_testing/fixtures/collector.py @@ -110,6 +110,8 @@ def merge_partial_fixture_files(output_dir: Path) -> None: class TestInfo: """Contains test information from the current node.""" + __test__ = False # stop pytest from collecting this class as a test + name: str # pytest: Item.name, e.g. test_paris_one[fork_Paris-state_test] id: str # pytest: Item.nodeid, e.g. # tests/paris/test_module_paris.py::test_paris_one[...] diff --git a/packages/testing/src/execution_testing/rpc/rpc.py b/packages/testing/src/execution_testing/rpc/rpc.py index 155e93aafde..a0ae5084c41 100644 --- a/packages/testing/src/execution_testing/rpc/rpc.py +++ b/packages/testing/src/execution_testing/rpc/rpc.py @@ -1533,6 +1533,8 @@ class TestingRPC(BaseRPC): testing-only methods like ``testing_buildBlockV1``. """ + __test__ = False # stop pytest from collecting this class as a test + def build_block( self, parent_block_hash: Hash, diff --git a/packages/testing/src/execution_testing/test_types/phase_manager.py b/packages/testing/src/execution_testing/test_types/phase_manager.py index 64d5eb2f333..cdd4fb182d5 100644 --- a/packages/testing/src/execution_testing/test_types/phase_manager.py +++ b/packages/testing/src/execution_testing/test_types/phase_manager.py @@ -8,6 +8,8 @@ class TestPhase(str, Enum): """Test phase for state and blockchain tests.""" + __test__ = False # stop pytest from collecting this class as a test + SETUP = "setup" # TODO: Change string to "execution", remain as "testing" for backwards # compatibility From 40172e5e112418bfafde8e16f35393766a58615a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=94=A1=E4=BD=B3=E8=AA=A0=20Louis=20Tsai?= <72684086+LouisTsai-Csie@users.noreply.github.com> Date: Mon, 6 Jul 2026 22:33:36 +0800 Subject: [PATCH 098/233] refactor(test-benchmark): deploy contract gas usage (#3110) --- .../cli/pytest_commands/plugins/execute/pre_alloc.py | 7 ++++++- .../plugins/fill_stateful/fill_stateful.py | 10 +++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py index f0d1f418351..5b6f12dded1 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py @@ -261,7 +261,12 @@ def _compute_deploy_gas_limit( # Regular portion, bound by the gas cap. regular_gas = intrinsic_regular_gas - regular_gas += deploy_code_size * gas_costs.CODE_DEPOSIT_PER_BYTE + if fork.state_gas_reservoir_enabled(): + regular_gas += gas_costs.OPCODE_KECCAK256_PER_WORD * ( + (deploy_code_size + 31) // 32 + ) + else: + regular_gas += deploy_code_size * gas_costs.CODE_DEPOSIT_PER_BYTE regular_gas += memory_expansion_gas_calculator( new_bytes=len(bytes(initcode)) ) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/fill_stateful/fill_stateful.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/fill_stateful/fill_stateful.py index 0d50c671e6a..a869915d43d 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/fill_stateful/fill_stateful.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/fill_stateful/fill_stateful.py @@ -41,6 +41,7 @@ TransitionFork, ) from execution_testing.logging import get_logger +from execution_testing.recipient_type import RecipientType from execution_testing.rpc import DebugRPC, EngineRPC, EthRPC from execution_testing.specs.blockchain import ( payload_metadata_to_fixture, @@ -413,7 +414,14 @@ def sender_fund_refund_gas_limit( ) -> int: """Intrinsic gas for the funding tx, derived from the fork.""" fork = session_fork.fork_at(block_number=0, timestamp=0) - return fork.transaction_intrinsic_cost_calculator()() + intrinsic = fork.transaction_intrinsic_cost_calculator()( + sends_value=True, + recipient_type=RecipientType.EMPTY_ACCOUNT, + ) + return intrinsic + fork.transaction_top_frame_state_gas( + sends_value=True, + recipient_type=RecipientType.EMPTY_ACCOUNT, + ) @pytest.fixture() From b15a08b906d5e982eb4b7103bf81de0b84752087 Mon Sep 17 00:00:00 2001 From: spencer Date: Mon, 6 Jul 2026 15:49:06 +0100 Subject: [PATCH 099/233] perf(spec-tools,ci): speed up json-loader and fill jobs (#3096) --- .github/workflows/test.yaml | 20 +++++++++- Justfile | 14 +++++++ src/ethereum_spec_tools/new_fork/builder.py | 37 +++++++++++++++---- .../test_tools.py => evm_tools/test_lint.py} | 0 .../test_new_fork.py} | 0 5 files changed, 62 insertions(+), 9 deletions(-) rename tests/{json_loader/test_tools.py => evm_tools/test_lint.py} (100%) rename tests/{json_loader/test_tools_new_fork.py => evm_tools/test_new_fork.py} (100%) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 29d4d393aee..7fa70f2676e 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -105,7 +105,9 @@ jobs: python-version: "3.14" - uses: ./.github/actions/setup-env - name: Run fill (${{ matrix.label }}) - run: just fill --from ${{ matrix.from_fork }} --until ${{ matrix.until_fork }} + run: > + just fill --from ${{ matrix.from_fork }} --until ${{ matrix.until_fork }} + -m "not slow and not derived_test" env: PYTEST_XDIST_AUTO_NUM_WORKERS: auto - name: Upload coverage reports to Codecov @@ -154,6 +156,22 @@ jobs: flags: unittests token: ${{ secrets.CODECOV_TOKEN }} + spec-tools: + runs-on: [self-hosted-ghr, size-xl-x64] + needs: static + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + submodules: recursive + - uses: ./.github/actions/setup-uv + with: + python-version: "3.14" + - uses: ./.github/actions/setup-env + - name: Run spec-tools tests + run: just spec-tools + env: + PYTEST_XDIST_AUTO_NUM_WORKERS: auto + test-tests: runs-on: [self-hosted-ghr, size-xl-x64] needs: static diff --git a/Justfile b/Justfile index c3b7de95a5c..3c85aa826a2 100644 --- a/Justfile +++ b/Justfile @@ -13,6 +13,9 @@ xdist_workers := env("PYTEST_XDIST_AUTO_NUM_WORKERS", "6") evm_bin := env("EVM_BIN", "evm") latest_fork := "Amsterdam" +# Use the faster sys.monitoring coverage core (default on 3.14, opt-in below). +export COVERAGE_CORE := "sysmon" + # --- Static Analysis --- # Auto-fix formatting and lint issues @@ -180,6 +183,17 @@ json-loader *args: "$@" \ tests/json_loader +# Run the spec-tools tests (lint and new-fork tooling) +[group('integration tests')] +spec-tools *args: + @mkdir -p "{{ output_dir }}/spec-tools/tmp" + uv run pytest \ + -n {{ xdist_workers }} \ + --basetemp="{{ output_dir }}/spec-tools/tmp" \ + --ignore=tests/evm_tools/test_count_opcodes.py \ + "$@" \ + tests/evm_tools + # --- Unit Tests --- # Run the testing package unit tests (with Python) diff --git a/src/ethereum_spec_tools/new_fork/builder.py b/src/ethereum_spec_tools/new_fork/builder.py index 14a4899db1f..8883ac4b556 100644 --- a/src/ethereum_spec_tools/new_fork/builder.py +++ b/src/ethereum_spec_tools/new_fork/builder.py @@ -10,7 +10,7 @@ from contextlib import ExitStack, chdir from dataclasses import dataclass, field from pathlib import Path -from shutil import copytree, rmtree +from shutil import copytree, ignore_patterns, rmtree from tempfile import TemporaryDirectory from typing import Final, NamedTuple @@ -28,6 +28,26 @@ from ..forks import Hardfork +def _source_file_for(fork_root: Path, qualified_name: str) -> Path: + """ + Resolve the source file that defines `qualified_name` within `fork_root`. + + Walk the dotted name from its longest module prefix to its shortest, + returning the first prefix that resolves to a module file (or package + `__init__.py`). Fall back to the fork's `__init__.py` for names bound + directly in the package. Used to scope constant codemods to a single file + instead of re-parsing the whole fork. + """ + parts = qualified_name.split(".") + for length in range(len(parts) - 1, 0, -1): + module = fork_root.joinpath(*parts[:length]) + if module.with_suffix(".py").is_file(): + return module.with_suffix(".py") + if (module / "__init__.py").is_file(): + return module / "__init__.py" + return fork_root / "__init__.py" + + @dataclass class CodemodArgs(ABC): """ @@ -56,7 +76,6 @@ def _to_args( commands = [ [ "codemod", - "-j1", "rename.RenameCommand", "--no-format", "--old_name", @@ -94,7 +113,6 @@ def _to_args( commands.append( [ "codemod", - "-j1", "rename.RenameCommand", "--no-format", "--old_name", @@ -142,6 +160,9 @@ def _to_args( f"ethereum.{fork_builder.new_fork}.{qualified_name}" ) + fork_root = working_directory / "ethereum" / fork_builder.new_fork + target = _source_file_for(fork_root, qualified_name) + command = [ "codemod", "-j1", @@ -151,7 +172,7 @@ def _to_args( fully_qualified_name, "--value", value, - str(working_directory), + str(target), ] for module, identifier in imports: @@ -238,14 +259,12 @@ def _to_args( commands = [ [ "codemod", - "-j1", "--no-format", "string_replace.StringReplaceCommand", ] + common, [ "codemod", - "-j1", "--no-format", "comment.CommentReplaceCommand", ] @@ -425,17 +444,19 @@ def _commit(self, fork_directory: Path) -> None: fork_directory.rename(self.new_fork_path) def _copy(self, fork_directory: Path) -> None: - # TODO: Filter out __pycache__ and similar files that shouldn't be - # copied. template_path = self.template_fork.path if template_path is None: raise Exception( f"fork `{self.template_fork.short_name}` has no path" ) + # Skip `__pycache__`: copying compiled bytecode into a new fork is + # pointless, and its transient `.pyc.` files race with concurrent + # bytecode writes, breaking `copytree` under parallel test runs. copytree( template_path, fork_directory, + ignore=ignore_patterns("__pycache__", "*.pyc"), dirs_exist_ok=True, ) diff --git a/tests/json_loader/test_tools.py b/tests/evm_tools/test_lint.py similarity index 100% rename from tests/json_loader/test_tools.py rename to tests/evm_tools/test_lint.py diff --git a/tests/json_loader/test_tools_new_fork.py b/tests/evm_tools/test_new_fork.py similarity index 100% rename from tests/json_loader/test_tools_new_fork.py rename to tests/evm_tools/test_new_fork.py From d43487d1c3c0f29bd71bad40d1f4c6cff104454e Mon Sep 17 00:00:00 2001 From: Andrew Ashikhmin <34320705+yperbasis@users.noreply.github.com> Date: Mon, 6 Jul 2026 17:33:47 +0200 Subject: [PATCH 100/233] fix(test-rpc): bound JSON-RPC requests with a default timeout (#3107) Co-authored-by: danceratopz --- .../execute/rpc/chain_builder_eth_rpc.py | 9 +- .../src/execution_testing/rpc/__init__.py | 4 + .../testing/src/execution_testing/rpc/rpc.py | 80 ++++++- .../rpc/tests/test_request_timeout.py | 215 ++++++++++++++++++ 4 files changed, 295 insertions(+), 13 deletions(-) create mode 100644 packages/testing/src/execution_testing/rpc/tests/test_request_timeout.py diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/rpc/chain_builder_eth_rpc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/rpc/chain_builder_eth_rpc.py index 66fc5c75e80..37b7af9aa93 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/rpc/chain_builder_eth_rpc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/rpc/chain_builder_eth_rpc.py @@ -19,7 +19,12 @@ ) from execution_testing.client_clis.cli_types import EnginePayloadMetadata from execution_testing.forks import Fork, TransitionFork -from execution_testing.rpc import EngineRPC, TestingRPC +from execution_testing.rpc import ( + DEFAULT_REQUEST_TIMEOUT, + EngineRPC, + TestingRPC, + TimeoutType, +) from execution_testing.rpc import EthRPC as BaseEthRPC from execution_testing.rpc.rpc_types import ( ForkchoiceState, @@ -55,6 +60,7 @@ def __init__( initial_forkchoice_update_retries: int = 5, transaction_wait_timeout: int = 60, max_transactions_per_batch: int | None = None, + request_timeout: TimeoutType = DEFAULT_REQUEST_TIMEOUT, testing_rpc: TestingRPC | None = None, ): """Initialize the Ethereum RPC client for the hive simulator.""" @@ -62,6 +68,7 @@ def __init__( rpc_endpoint, transaction_wait_timeout=transaction_wait_timeout, max_transactions_per_batch=max_transactions_per_batch, + request_timeout=request_timeout, ) self.fork = fork self.engine_rpc = engine_rpc diff --git a/packages/testing/src/execution_testing/rpc/__init__.py b/packages/testing/src/execution_testing/rpc/__init__.py index 4fe14aa2aa0..d62b65bcd7a 100644 --- a/packages/testing/src/execution_testing/rpc/__init__.py +++ b/packages/testing/src/execution_testing/rpc/__init__.py @@ -3,6 +3,7 @@ """ from .rpc import ( + DEFAULT_REQUEST_TIMEOUT, AdminRPC, BlockNotAvailableError, BlockNumberType, @@ -15,6 +16,7 @@ PeerConnectionTimeoutError, SendTransactionExceptionError, TestingRPC, + TimeoutType, Web3RPC, ) from .rpc_types import ( @@ -36,6 +38,7 @@ "BlockNotAvailableError", "BlockNumberType", "DebugRPC", + "DEFAULT_REQUEST_TIMEOUT", "EngineRPC", "EthConfigResponse", "EthRPC", @@ -50,6 +53,7 @@ "PeerConnectionTimeoutError", "SendTransactionExceptionError", "TestingRPC", + "TimeoutType", "TransactionProtocol", "Web3RPC", ] diff --git a/packages/testing/src/execution_testing/rpc/rpc.py b/packages/testing/src/execution_testing/rpc/rpc.py index a0ae5084c41..d5dc0d44e70 100644 --- a/packages/testing/src/execution_testing/rpc/rpc.py +++ b/packages/testing/src/execution_testing/rpc/rpc.py @@ -64,6 +64,13 @@ logger = get_logger(__name__) BlockNumberType = int | Literal["latest", "earliest", "pending"] +TimeoutType = float | tuple[float, float] | None + +# Default (connect, read) timeout for JSON-RPC requests. Without one, a +# request whose packets are silently dropped (e.g. by docker network +# churn in hive) blocks until the kernel abandons TCP retransmission, +# which takes ~15 minutes on Linux. +DEFAULT_REQUEST_TIMEOUT: TimeoutType = (10.0, 300.0) class SendTransactionExceptionError(Exception): @@ -206,17 +213,25 @@ class BaseRPC: namespace: ClassVar[str] response_validation_context: Any | None + request_timeout: TimeoutType def __init__( self, url: str, *, response_validation_context: Any | None = None, + request_timeout: TimeoutType = DEFAULT_REQUEST_TIMEOUT, ): - """Initialize BaseRPC class with the given url.""" + """ + Initialize BaseRPC class with the given url. + + `request_timeout` bounds every request made through this client; + `None` disables the bound. + """ self.url = url self.request_id_counter = count(1) self.response_validation_context = response_validation_context + self.request_timeout = request_timeout self.session = requests.Session() def close(self) -> None: @@ -250,7 +265,11 @@ def __init_subclass__(cls, namespace: str | None = None) -> None: @retry( retry=retry_if_exception_type( - (requests.ConnectionError, ConnectionRefusedError) + ( + requests.ConnectionError, + requests.Timeout, + ConnectionRefusedError, + ) ), stop=stop_after_attempt(5), wait=wait_exponential(multiplier=0.5, min=0.5, max=4.0), @@ -262,19 +281,24 @@ def _make_request( url: str, json_payload: dict[str, Any] | list[dict[str, Any]], headers: dict[str, str], - timeout: int | None, + timeout: TimeoutType, ) -> requests.Response: """ - Make HTTP POST request with retry logic for connection errors only. + Make HTTP POST request with retry logic for transport errors only. - This method only retries network-level connection failures - (ConnectionError, ConnectionRefusedError). HTTP status errors (4xx/5xx) - are handled by the caller using response.raise_for_status() WITHOUT - retries because: + This method only retries network-level failures: connection errors + (ConnectionError, ConnectionRefusedError) and timeouts. Re-sending + cannot corrupt state: the methods used here either read state or + re-broadcast the same signed payload. A re-sent transaction may + however be answered with a duplicate-transaction error rather than + its hash. HTTP status errors (4xx/5xx) are handled by the caller + using response.raise_for_status() WITHOUT retries because: - 4xx errors are client errors (permanent failures, no point retrying) - 5xx errors are server errors that typically indicate application-level issues rather than transient network problems """ + if timeout is None: + timeout = self.request_timeout logger.debug(f"Making HTTP request to {url}, timeout={timeout}") return self.session.post( url, json=json_payload, headers=headers, timeout=timeout @@ -311,11 +335,13 @@ def post_request( *, request: RPCCall, extra_headers: Dict[str, str] | None = None, - timeout: int | None = None, + timeout: TimeoutType = None, ) -> JSONRPCResponse: """ Send JSON-RPC POST request to the client RPC server at port defined in the url. + + A `timeout` of `None` applies the client's `request_timeout`. """ if extra_headers is None: extra_headers = {} @@ -328,7 +354,7 @@ def post_request( logger.debug( f"Sending RPC request to {self.url}, " - f"method={json_rpc_request.method}, timeout={timeout}..." + f"method={json_rpc_request.method}..." ) response = self._make_request( @@ -343,11 +369,13 @@ def post_batch_request( *, calls: Sequence[RPCCall], extra_headers: Dict[str, str] | None = None, - timeout: int | None = None, + timeout: TimeoutType = None, ) -> List[JSONRPCResponse]: """ Send a JSON-RPC batch POST request to the client RPC server at port defined in the url. + + A `timeout` of `None` applies the client's `request_timeout`. """ if extra_headers is None: extra_headers = {} @@ -363,7 +391,7 @@ def post_batch_request( logger.debug( f"Sending batch RPC request to {self.url}, " - f"{len(json_rpc_requests)} calls, timeout={timeout}..." + f"{len(json_rpc_requests)} calls..." ) response = self._make_request(self.url, payload, headers, timeout) @@ -889,6 +917,29 @@ def send_raw_transaction( str(e), tx_rlp=transaction_rlp ) from e + def _transaction_is_known( + self, transaction: TransactionProtocol, error: Exception + ) -> bool: + """ + Check whether the client knows `transaction` despite a send error. + + A retried `eth_sendRawTransaction` whose first delivery succeeded + is answered with a duplicate-transaction error; if the client can + return the transaction by hash, the send in fact succeeded. Lookup + failures count as unknown so that the original send error + propagates. + """ + try: + known = self.get_transaction_by_hash(transaction.hash) is not None + except Exception: + return False + if known: + logger.warning( + f"Client answered eth_sendRawTransaction with '{error}' " + "but knows the transaction; treating the send as success." + ) + return known + def send_transaction(self, transaction: TransactionProtocol) -> Hash: """ Convenience method to send a single transaction to the client via @@ -908,6 +959,8 @@ def send_transaction(self, transaction: TransactionProtocol) -> Hash: assert result_hash is not None return transaction.hash except Exception as e: + if self._transaction_is_known(transaction, e): + return transaction.hash raise SendTransactionExceptionError(str(e), tx=transaction) from e def send_transactions( @@ -938,6 +991,9 @@ def send_transactions( assert result_hash is not None results.append(tx.hash) except Exception as e: + if self._transaction_is_known(tx, e): + results.append(tx.hash) + continue raise SendTransactionExceptionError(str(e), tx=tx) from e return results diff --git a/packages/testing/src/execution_testing/rpc/tests/test_request_timeout.py b/packages/testing/src/execution_testing/rpc/tests/test_request_timeout.py new file mode 100644 index 00000000000..3cfcd506424 --- /dev/null +++ b/packages/testing/src/execution_testing/rpc/tests/test_request_timeout.py @@ -0,0 +1,215 @@ +"""Test the HTTP request timeout and retry behavior of `BaseRPC` clients.""" + +from pathlib import Path +from typing import Any +from unittest.mock import MagicMock, patch + +import pytest +import requests + +from execution_testing.base_types import Hash +from execution_testing.cli.pytest_commands.plugins.execute.rpc.chain_builder_eth_rpc import ( # noqa: E501 + ChainBuilderEthRPC, +) +from execution_testing.forks import Cancun +from execution_testing.rpc import ( + DEFAULT_REQUEST_TIMEOUT, + EthRPC, + RPCCall, + SendTransactionExceptionError, +) +from execution_testing.rpc.rpc_types import PayloadStatusEnum + + +def response_mock( + json_value: dict[str, Any] | list[dict[str, Any]] | None = None, +) -> MagicMock: + """Return a mock of a successful JSON-RPC HTTP response.""" + if json_value is None: + json_value = {"jsonrpc": "2.0", "id": 1, "result": "0x0"} + response = MagicMock() + response.json.return_value = json_value + return response + + +def duplicate_error_response() -> MagicMock: + """Return a mock duplicate-transaction JSON-RPC error response.""" + return response_mock( + { + "jsonrpc": "2.0", + "id": 1, + "error": {"code": -32000, "message": "already known"}, + } + ) + + +def transaction_mock(index: int = 0) -> Any: + """Return a mock signed transaction for send tests.""" + transaction: Any = MagicMock() + transaction.hash = Hash(index + 1) + transaction.rlp.return_value = bytes([index + 1]) + transaction.metadata_string.return_value = f"tx-{index}" + return transaction + + +def test_default_timeout_is_applied() -> None: + """Requests carry the default timeout when none is given.""" + rpc = EthRPC("http://localhost:8545") + with patch.object( + rpc.session, "post", return_value=response_mock() + ) as post: + rpc.post_request(request=RPCCall(method="blockNumber")) + assert post.call_args.kwargs["timeout"] == DEFAULT_REQUEST_TIMEOUT + + +def test_per_request_timeout_overrides_default() -> None: + """An explicit per-request timeout takes precedence.""" + rpc = EthRPC("http://localhost:8545") + with patch.object( + rpc.session, "post", return_value=response_mock() + ) as post: + rpc.post_request(request=RPCCall(method="blockNumber"), timeout=7) + assert post.call_args.kwargs["timeout"] == 7 + + +def test_constructor_timeout_overrides_default() -> None: + """A timeout given at construction replaces the default.""" + rpc = EthRPC("http://localhost:8545", request_timeout=5.0) + with patch.object( + rpc.session, "post", return_value=response_mock() + ) as post: + rpc.post_request(request=RPCCall(method="blockNumber")) + assert post.call_args.kwargs["timeout"] == 5.0 + + +def test_constructor_timeout_none_disables_timeout() -> None: + """`request_timeout=None` restores unbounded requests.""" + rpc = EthRPC("http://localhost:8545", request_timeout=None) + with patch.object( + rpc.session, "post", return_value=response_mock() + ) as post: + rpc.post_request(request=RPCCall(method="blockNumber")) + assert post.call_args.kwargs["timeout"] is None + + +def test_read_timeout_is_retried() -> None: + """A read timeout is retried on a fresh request.""" + rpc = EthRPC("http://localhost:8545") + with ( + patch("time.sleep"), + patch.object( + rpc.session, + "post", + side_effect=[ + requests.ReadTimeout("read timed out"), + response_mock(), + ], + ) as post, + ): + rpc.post_request(request=RPCCall(method="blockNumber")) + assert post.call_count == 2 + + +def test_batch_request_default_timeout_is_applied() -> None: + """Batch requests carry the default timeout as well.""" + rpc = EthRPC("http://localhost:8545") + batch_response = response_mock( + [{"jsonrpc": "2.0", "id": 1, "result": "0x0"}] + ) + with patch.object( + rpc.session, "post", return_value=batch_response + ) as post: + rpc.post_batch_request(calls=[RPCCall(method="blockNumber")]) + assert post.call_args.kwargs["timeout"] == DEFAULT_REQUEST_TIMEOUT + + +def test_chain_builder_accepts_request_timeout(tmp_path: Path) -> None: + """`ChainBuilderEthRPC` forwards `request_timeout` to the base client.""" + engine_rpc: Any = MagicMock() + engine_rpc.forkchoice_updated.return_value.payload_status.status = ( + PayloadStatusEnum.VALID + ) + head_block = { + "number": "0x0", + "timestamp": "0x0", + "hash": f"0x{'00' * 32}", + } + with patch.object( + ChainBuilderEthRPC, "get_block_by_number", return_value=head_block + ): + rpc = ChainBuilderEthRPC( + rpc_endpoint="http://localhost:8545", + fork=Cancun, + engine_rpc=engine_rpc, + session_temp_folder=tmp_path, + get_payload_wait_time=1, + request_timeout=5.0, + ) + assert rpc.request_timeout == 5.0 + + +def test_resent_transaction_duplicate_error_is_success() -> None: + """A re-sent transaction answered "already known" counts as sent.""" + rpc = EthRPC("http://localhost:8545") + transaction = transaction_mock() + with ( + patch("time.sleep"), + patch.object( + rpc.session, + "post", + side_effect=[ + requests.ReadTimeout("read timed out"), + duplicate_error_response(), + ], + ) as post, + patch.object( + EthRPC, "get_transaction_by_hash", return_value=MagicMock() + ) as get_transaction, + ): + result = rpc.send_transaction(transaction) + assert result == transaction.hash + assert post.call_count == 2 + get_transaction.assert_called_once_with(transaction.hash) + + +def test_send_transaction_error_raises_when_transaction_unknown() -> None: + """A send error for a transaction the client does not know raises.""" + rpc = EthRPC("http://localhost:8545") + transaction = transaction_mock() + with ( + patch.object( + rpc.session, "post", return_value=duplicate_error_response() + ), + patch.object(EthRPC, "get_transaction_by_hash", return_value=None), + pytest.raises(SendTransactionExceptionError), + ): + rpc.send_transaction(transaction) + + +def test_send_transactions_recover_duplicate_batch_items() -> None: + """Duplicate errors in a re-sent batch count as sent per item.""" + rpc = EthRPC("http://localhost:8545") + transactions = [transaction_mock(0), transaction_mock(1)] + batch_response = response_mock( + [ + { + "jsonrpc": "2.0", + "id": "tx-0", + "error": {"code": -32000, "message": "already known"}, + }, + { + "jsonrpc": "2.0", + "id": "tx-1", + "result": f"{transactions[1].hash}", + }, + ] + ) + with ( + patch.object(rpc.session, "post", return_value=batch_response), + patch.object( + EthRPC, "get_transaction_by_hash", return_value=MagicMock() + ) as get_transaction, + ): + results = rpc.send_transactions(transactions) + assert results == [tx.hash for tx in transactions] + get_transaction.assert_called_once_with(transactions[0].hash) From d8ab47eeaa8a14c150562b421944a2c824b04324 Mon Sep 17 00:00:00 2001 From: spencer Date: Mon, 6 Jul 2026 16:36:54 +0100 Subject: [PATCH 101/233] chore: update pr template (#3089) Co-authored-by: danceratopz Co-authored-by: Jochem Brouwer --- .github/PULL_REQUEST_TEMPLATE.md | 25 +++++++------------------ CLAUDE.md | 2 +- tests/ported_static/README.md | 13 +++++++++++++ 3 files changed, 21 insertions(+), 19 deletions(-) create mode 100644 tests/ported_static/README.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 89068893dbd..6d95da13894 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,28 +1,17 @@ -## 🗒️ Description +### Description -## 🔗 Related Issues or PRs +### Related Issues or PRs N/A. -## ✅ Checklist - +### Checklist + -- [ ] All: Ran fast static checks to avoid unnecessary CI fails, see also [Code Standards](https://steel.ethereum.foundation/docs/execution-specs/getting_started/code_standards/) and [Verifying Changes](https://steel.ethereum.foundation/docs/execution-specs/getting_started/verifying_changes/): - ```console - just static - ``` -- [ ] All: PR title have the form `():`, where `` and `` come from an approrpriate `C-`, respectively `A-`, label. The title should match the a target squash commit message. -- [ ] All: Considered updating the online docs in the [./docs/](/ethereum/execution-specs/blob/HEAD/docs/) directory. -- [ ] All: Set appropriate labels for the changes (only maintainers can apply labels). -- [ ] Tests: For PRs implementing a missed test case, update the [post-mortem document](/ethereum/execution-specs/blob/HEAD/docs/writing_tests/post_mortems.md) to add an entry the list. -- [ ] Ported Tests: Add the following docstring to manually enhanced tests from `./tests/ported_static/`: - ```text - @manually-enhanced: Do not overwrite. Post-state expectations corrected - manually (see PR #2784). - ```` +- [ ] Ran fast static checks to avoid CI fails, see [Code Standards](https://steel.ethereum.foundation/docs/execution-specs/getting_started/code_standards/) & [Verifying Changes](https://steel.ethereum.foundation/docs/execution-specs/getting_started/verifying_changes/): `just static` +- [ ] PR title has the form `(): `, where `<type>` and `<area>` come from an appropriate [`C-<type>`](https://github.com/ethereum/execution-specs/labels?q=C-), respectively [`A-<area>`](https://github.com/ethereum/execution-specs/labels?q=A-), label. The title should match the target squash commit message. -#### Cute Animal Picture +### Cute Animal Picture ![Put a link to a cute animal picture inside the parenthesis-->]() diff --git a/CLAUDE.md b/CLAUDE.md index c2aeb8fb7df..806edfc565b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -29,7 +29,7 @@ When done with changes, ask the user if they'd like to run `/lint` before commit - **There is no `main` branch.** Default branch = most active fork (currently `forks/amsterdam`). Run `git remote show origin | grep HEAD` to check. - `mainnet` = stable specs for forks live on mainnet - PRs target the default branch -- PRs strictly follow the template in `.github/PULL_REQUEST_TEMPLATE.md`. In the Checklist section, include unchecked items that don't apply — only remove them if they are truly irrelevant to the PR type. +- PRs strictly follow the template in `.github/PULL_REQUEST_TEMPLATE.md`. ## PR Reviews diff --git a/tests/ported_static/README.md b/tests/ported_static/README.md new file mode 100644 index 00000000000..de6ceb31967 --- /dev/null +++ b/tests/ported_static/README.md @@ -0,0 +1,13 @@ +# Ported Static Tests + +Tests in this directory were auto-converted from the static fillers in +[ethereum/tests](https://github.com/ethereum/tests) and may be regenerated +by the conversion tooling. + +If you correct a test by hand (e.g. fix its post-state expectations), add +the following docstring so the file is not overwritten on regeneration: + +```text +@manually-enhanced: Do not overwrite. Post-state expectations corrected +manually (see PR #2784). +``` From 80cc337bf364f5ab5ce77103ea07535d8f32eabe Mon Sep 17 00:00:00 2001 From: danceratopz <danceratopz@gmail.com> Date: Mon, 6 Jul 2026 23:08:20 +0200 Subject: [PATCH 102/233] fix(deps): constrain scikit-build-core for coincurve source builds (#3119) --- pyproject.toml | 3 +++ uv.lock | 1 + 2 files changed, 4 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 99ed678edc7..aa9cb996504 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -530,6 +530,9 @@ plugins = ["pydantic.mypy"] [tool.uv] required-version = ">=0.7.0" extra-build-dependencies = { ethash = ["setuptools", "cmake>=4.2.1,<5"] } +# Pin scikit-build-core < 0.10 for coincurve's sdist build on Python 3.13+. +# See https://github.com/ethereum/execution-specs/pull/3119 +build-constraint-dependencies = ["scikit-build-core<0.10"] [tool.uv.workspace] members = ["packages/*"] diff --git a/uv.lock b/uv.lock index 9694dd767d5..5020d13995c 100644 --- a/uv.lock +++ b/uv.lock @@ -12,6 +12,7 @@ members = [ "ethereum-execution", "ethereum-execution-testing", ] +build-constraints = [{ name = "scikit-build-core", specifier = "<0.10" }] [[package]] name = "actionlint-py" From f8ec1d6a0e8bf0ce312d4d2d13843a64fc22f72a Mon Sep 17 00:00:00 2001 From: danceratopz <danceratopz@gmail.com> Date: Mon, 6 Jul 2026 23:51:33 +0200 Subject: [PATCH 103/233] chore(tests): OOG instantly in EIP-8037 failure tests (#3117) --- .../test_state_gas_reservoir.py | 9 +++++---- .../test_state_gas_set_code.py | 7 ++++++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_reservoir.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_reservoir.py index b949c150b00..7ec5dd62f87 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_reservoir.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_reservoir.py @@ -744,9 +744,10 @@ def test_top_level_failure_refunds_execution_state_gas( elif failure_mode == "halt": code = Op.SSTORE(0, 1) + Op.INVALID else: - # OOG: perform the SSTORE then spin with JUMPDEST loop until - # gas runs out. - code = Op.SSTORE(0, 1) + Op.JUMPDEST + Op.JUMP(0x5) + # OOG: perform the SSTORE, then consume all remaining gas at + # once (a spin loop would execute millions of ops in the EVM + # and slow down filling). + code = Op.SSTORE(0, 1) + Om.OOG contract = pre.deploy_contract(code=code) tx_gas = gas_limit_cap + sstore_state_gas @@ -806,7 +807,7 @@ def test_top_level_failure_zeros_block_state_gas( elif failure_mode == "halt": code = Op.SSTORE(0, 1) + Op.INVALID else: - code = Op.SSTORE(0, 1) + Op.JUMPDEST + Op.JUMP(0x5) + code = Op.SSTORE(0, 1) + Om.OOG contract = pre.deploy_contract(code=code) tx_gas = gas_limit_cap + sstore_state_gas diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py index 434de6023df..56b19ec4b8f 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py @@ -27,6 +27,9 @@ TransactionException, TransactionReceipt, ) +from execution_testing import ( + Macros as Om, +) from tests.prague.eip7702_set_code_tx.spec import Spec as Spec7702 @@ -1363,7 +1366,9 @@ def test_auth_state_gas_in_header_after_failure( elif failure_mode == "halt": target = pre.deploy_contract(code=Op.INVALID) else: - target = pre.deploy_contract(code=Op.JUMPDEST + Op.JUMP(0x0)) + # Consume all remaining gas at once (a spin loop would execute + # millions of ops in the EVM and slow down filling). + target = pre.deploy_contract(code=Om.OOG) if authority_exists: signer = pre.fund_eoa() From 376414e07c4642574b38c4bb97eab2f2f719436b Mon Sep 17 00:00:00 2001 From: spencer <spencer.taylor-brown@ethereum.org> Date: Mon, 6 Jul 2026 23:52:15 +0100 Subject: [PATCH 104/233] perf(spec-tools,ci): parallel PR docs-spec builds, publish-only social cards (#3101) Co-authored-by: danceratopz <danceratopz@gmail.com> --- .github/workflows/docs-build.yaml | 7 ++ Justfile | 6 ++ mkdocs.yml | 5 +- pyproject.toml | 4 + src/ethereum_spec_tools/docc.py | 124 +++++++++++++++++++++++++ src/ethereum_spec_tools/docc_shards.py | 87 +++++++++++++++++ tests/evm_tools/test_docc_shards.py | 50 ++++++++++ vulture_whitelist.py | 2 + 8 files changed, 284 insertions(+), 1 deletion(-) create mode 100644 src/ethereum_spec_tools/docc_shards.py create mode 100644 tests/evm_tools/test_docc_shards.py diff --git a/.github/workflows/docs-build.yaml b/.github/workflows/docs-build.yaml index b49c2c518a9..4d167b159f1 100644 --- a/.github/workflows/docs-build.yaml +++ b/.github/workflows/docs-build.yaml @@ -227,6 +227,8 @@ jobs: - name: Build MkDocs documentation env: SITE_URL: ${{ needs.check-should-publish.outputs.site_url }} + # Social cards are publish-only; PR builds skip them for speed. + DOCS_SOCIAL_CARDS: ${{ github.event_name != 'pull_request' }} run: | echo "Building MkDocs with SITE_URL=$SITE_URL" just docs @@ -255,7 +257,12 @@ jobs: - uses: ./.github/actions/setup-uv + - name: Build spec documentation (parallel shards) + if: github.event_name == 'pull_request' + run: just docs-spec-parallel + - name: Build spec documentation + if: github.event_name != 'pull_request' run: just docs-spec env: DOCC_SKIP_DIFFS: ${{ case(github.event_name == 'push' && github.ref_name == github.event.repository.default_branch, '', '1') }} diff --git a/Justfile b/Justfile index 3c85aa826a2..a068c1f1d05 100644 --- a/Justfile +++ b/Justfile @@ -330,6 +330,12 @@ docs-spec $DOCC_SKIP_DIFFS=env_var_or_default("DOCC_SKIP_DIFFS", ""): [group('docs')] docs-spec-fast: (docs-spec "1") +# Build spec docs in parallel shards for fast PR validation +[group('docs')] +docs-spec-parallel shards="4": + uv run python -m ethereum_spec_tools.docc_shards \ + -n {{ shards }} -o "{{ output_dir }}/docs-spec-parallel" + # Build HTML site documentation with mkdocs [group('docs')] docs *args: diff --git a/mkdocs.yml b/mkdocs.yml index f5f9fa55257..5824e80e677 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -22,7 +22,10 @@ plugins: members_order: source group_by_category: false - search - - social + # Social cards render a PNG per page (~2m for the full site) and need + # native cairo; only publish builds enable them (see docs-build.yaml). + - social: + enabled: !ENV [DOCS_SOCIAL_CARDS, false] - gen-files: scripts: - docs/scripts/copy_repo_docs_to_mkdocs.py diff --git a/pyproject.toml b/pyproject.toml index aa9cb996504..d7bc28a0acc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -277,6 +277,7 @@ whitelist = "ethereum_spec_tools.whitelist:main" "ethereum_spec_tools.docc.build" = "ethereum_spec_tools.docc:EthereumBuilder" "ethereum_spec_tools.docc.fix-indexes" = "ethereum_spec_tools.docc:FixIndexTransform" "ethereum_spec_tools.docc.minimize-diffs" = "ethereum_spec_tools.docc:MinimizeDiffsTransform" +"ethereum_spec_tools.docc.prune-references" = "ethereum_spec_tools.docc:PruneReferencesTransform" [project.entry-points."docc.plugins.html"] "ethereum_spec_tools.docc:DiffNode" = "ethereum_spec_tools.docc:render_diff" @@ -341,6 +342,9 @@ transform = [ "ethereum_spec_tools.docc.fix-indexes", "ethereum_spec_tools.docc.minimize-diffs", "docc.references.index", + # prune-references must run after the index is populated and before + # any transform that resolves references (search, html). + "ethereum_spec_tools.docc.prune-references", "docc.search.transform", "docc.html.transform", ] diff --git a/src/ethereum_spec_tools/docc.py b/src/ethereum_spec_tools/docc.py index c93a943eeb1..550d97685ab 100644 --- a/src/ethereum_spec_tools/docc.py +++ b/src/ethereum_spec_tools/docc.py @@ -55,6 +55,8 @@ from docc.plugins.python import PythonBuilder, PythonDiscover from docc.plugins.python.cst import PythonSource from docc.plugins.references import Definition, Reference +from docc.plugins.references import Index as ReferenceIndex +from docc.plugins.references import ReferenceError as DoccReferenceError from docc.settings import PluginSettings from docc.source import Source from docc.transform import Transform @@ -144,6 +146,17 @@ def _find_forks(config: PluginSettings) -> List[Hardfork]: return Hardfork.discover([str(forks)]) +def _only_forks() -> Set[str]: + """ + Parse the `DOCC_ONLY_FORKS` fork subset from the environment. + + Return the lower-cased fork short names to render, or an empty set + when the whole fork range should be rendered. + """ + value = os.environ.get("DOCC_ONLY_FORKS", "") + return {f.strip().lower() for f in value.split(",") if f.strip()} + + def _diff_path(before: Hardfork, after: Hardfork) -> PurePath: return PurePath("diffs") / before.short_name / after.short_name @@ -210,6 +223,43 @@ class EthereumPythonDiscover(PythonDiscover): def __init__(self, config: PluginSettings) -> None: super().__init__(config) self._fork_order = _ForkOrder(config) + self._apply_fork_filter(config) + + def _apply_fork_filter(self, config: PluginSettings) -> None: + """ + Exclude fork packages not listed in `DOCC_ONLY_FORKS`. + + The variable holds comma-separated fork short names (for example + `amsterdam,osaka`). When unset or empty, render every fork. When + no listed name matches a known fork, disable the filter so a bad + value cannot produce an empty (but successful) build. + """ + keep = _only_forks() + if not keep: + return + forks = _find_forks(config) + known = {f.short_name.lower() for f in forks} + if not keep & known: + logging.warning( + "DOCC_ONLY_FORKS matches no known fork; rendering all" + ) + return + dropped = [ + config.unresolve_path(PurePath(f.path)) + for f in forks + if f.path is not None and f.short_name.lower() not in keep + ] + logging.info( + "DOCC_ONLY_FORKS: rendering %d of %d fork package(s)", + len(forks) - len(dropped), + len(forks), + ) + # `excluded_paths` is declared `Final` upstream; replace it here, + # before discovery runs, to narrow the rendered sources. + self.excluded_paths = [ # type: ignore[misc] + *self.excluded_paths, + *dropped, + ] @override def _python_source( @@ -231,6 +281,75 @@ def _python_source( ) +class PruneReferencesTransform(Transform): + """ + Drop references into fork packages excluded from the build. + + When `DOCC_ONLY_FORKS` narrows discovery, links into excluded fork + packages have no definition. Replace each such reference with its + plain content so rendering succeeds without a link. References to + anything else are left alone, so genuinely broken identifiers still + fail the build. + """ + + def __init__(self, config: PluginSettings) -> None: + pass + + def transform(self, context: Context) -> None: + """ + Apply the transformation to the given document. + """ + keep = _only_forks() + if not keep: + return + context[Document].root.visit(_PruneReferencesVisitor(context, keep)) + + +class _PruneReferencesVisitor(Visitor): + _context: Context + _keep: Set[str] + _stack: List[Node] + + def __init__(self, context: Context, keep: Set[str]) -> None: + self._context = context + self._keep = keep + self._stack = [] + + def _prunable(self, node: Node) -> bool: + """ + Check whether the node links into an excluded fork package. + """ + if not isinstance(node, Reference): + return False + parts = node.identifier.split(".") + if parts[:2] != ["ethereum", "forks"] or len(parts) < 3: + return False + if parts[2].lower() in self._keep: + return False + try: + self._context[ReferenceIndex].lookup(node.identifier) + except DoccReferenceError: + return True + return False + + @override + def enter(self, node: Node) -> Visit: + if self._stack: + replacement = node + while self._prunable(replacement): + assert isinstance(replacement, Reference) + replacement = replacement.child + if replacement is not node: + self._stack[-1].replace_child(node, replacement) + node = replacement + self._stack.append(node) + return Visit.TraverseChildren + + @override + def exit(self, node: Node) -> None: + self._stack.pop() + + class EthereumDiscover(Discover): """ Creates sources that represent the diff between two other sources, one per @@ -252,6 +371,11 @@ def discover(self, known: FrozenSet[T]) -> Iterator[Source]: logging.info("Skipping diff discovery (DOCC_SKIP_DIFFS)") return + if _only_forks(): + # Fork-subset builds have no complete fork pairs to diff. + logging.info("Skipping diff discovery (DOCC_ONLY_FORKS)") + return + forks = {f.path: f for f in self.forks if f.path is not None} by_fork: Dict[Hardfork, Dict[PurePath, Source]] = defaultdict(dict) diff --git a/src/ethereum_spec_tools/docc_shards.py b/src/ethereum_spec_tools/docc_shards.py new file mode 100644 index 00000000000..fbea24eb550 --- /dev/null +++ b/src/ethereum_spec_tools/docc_shards.py @@ -0,0 +1,87 @@ +""" +Build the docc spec docs as parallel shards of consecutive forks. + +Fast PR-time validation only: the fork range is split into ``n`` +contiguous shards, each overlapping its predecessor by one fork so a +fork's reference to the previous fork resolves within its shard. One +``docc`` process renders each shard concurrently; the per-shard outputs +are not merged. + +Forward references (a fork referencing a later fork) fall outside their +shard and are pruned, so they are validated only by the serial +default-branch ``docs-spec`` build that gates the docs deploy. +""" + +import argparse +import os +import subprocess +import sys +from pathlib import Path +from typing import List + +from .forks import Hardfork + + +def compute_shards(forks: List[str], n: int) -> List[List[str]]: + """ + Split ``forks`` into at most ``n`` contiguous shards. + + Every shard after the first is prefixed with its predecessor fork, so + a fork's reference to the previous fork resolves within its shard. + """ + per = (len(forks) + n - 1) // n + shards: List[List[str]] = [] + for i in range(n): + start = i * per + if start >= len(forks): + break + lo = start - 1 if start > 0 else start + shards.append(forks[lo : min(start + per, len(forks))]) + return shards + + +def main() -> int: + """Discover forks, shard them, and render each shard with ``docc``.""" + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "-n", + "--shards", + type=int, + default=4, + help="number of parallel shards (default: 4)", + ) + parser.add_argument( + "-o", + "--output-dir", + type=Path, + required=True, + help="parent directory for each shard's output", + ) + args = parser.parse_args() + if args.shards < 1: + parser.error("--shards must be a positive integer") + + forks = [fork.short_name for fork in Hardfork.discover()] + if not forks: + print("error: no forks discovered", file=sys.stderr) + return 1 + + processes: List[subprocess.Popen[bytes]] = [] + for i, shard in enumerate(compute_shards(forks, args.shards)): + print(f"shard {i}: {','.join(shard)}", flush=True) + env = { + **os.environ, + "DOCC_SKIP_DIFFS": "1", + "DOCC_ONLY_FORKS": ",".join(shard), + } + output = args.output_dir / f"shard-{i}" + processes.append( + subprocess.Popen(["docc", "--output", str(output)], env=env) + ) + + exit_codes = [process.wait() for process in processes] + return 1 if any(exit_codes) else 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tests/evm_tools/test_docc_shards.py b/tests/evm_tools/test_docc_shards.py new file mode 100644 index 00000000000..456a7d7d72e --- /dev/null +++ b/tests/evm_tools/test_docc_shards.py @@ -0,0 +1,50 @@ +""" +Unit tests for the parallel spec-doc shard planner. +""" + +from ethereum_spec_tools.docc_shards import compute_shards +from ethereum_spec_tools.forks import Hardfork + + +def test_shards_cover_every_fork() -> None: + """Every fork lands in at least one shard.""" + forks = [f"f{i}" for i in range(24)] + shards = compute_shards(forks, 4) + covered = {fork for shard in shards for fork in shard} + assert covered == set(forks) + + +def test_shards_are_contiguous_with_one_fork_overlap() -> None: + """Shards tile the range in order, overlapping one fork per seam.""" + forks = [f"f{i}" for i in range(24)] + assert compute_shards(forks, 4) == [ + forks[0:6], + forks[5:12], + forks[11:18], + forks[17:24], + ] + + +def test_each_fork_shares_a_shard_with_its_predecessor() -> None: + """The overlap keeps every fork beside its immediate predecessor.""" + forks = [f"f{i}" for i in range(24)] + shards = compute_shards(forks, 4) + for i in range(1, len(forks)): + assert any( + forks[i] in shard and forks[i - 1] in shard for shard in shards + ) + + +def test_more_shards_than_forks_still_covers_all() -> None: + """Requesting more shards than forks leaves no fork uncovered.""" + shards = compute_shards(["a", "b", "c"], 4) + covered = {fork for shard in shards for fork in shard} + assert covered == {"a", "b", "c"} + + +def test_real_fork_set_is_fully_covered() -> None: + """The discovered fork set shards without dropping any fork.""" + forks = [fork.short_name for fork in Hardfork.discover()] + shards = compute_shards(forks, 4) + covered = {fork for shard in shards for fork in shard} + assert covered == set(forks) diff --git a/vulture_whitelist.py b/vulture_whitelist.py index ffd8e3992bd..fd1f6712690 100644 --- a/vulture_whitelist.py +++ b/vulture_whitelist.py @@ -77,6 +77,8 @@ docc.FixIndexTransform.transform docc.MinimizeDiffsTransform docc.MinimizeDiffsTransform.transform +docc.PruneReferencesTransform +docc.PruneReferencesTransform.transform docc._FixIndexVisitor.enter docc._DoccAdapter.shallow_equals docc._DoccAdapter.shallow_hash From 76b7f701175c3315a0dcd5ba819f2286e2e52942 Mon Sep 17 00:00:00 2001 From: Guruprasad Kamath <48196632+gurukamath@users.noreply.github.com> Date: Wed, 8 Jul 2026 15:45:03 +0200 Subject: [PATCH 105/233] feat(spec-specs, tests): EIP-8038: move SSTORE access-cost check before read (#3064) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(spec-specs, tests): frontload SSTORE access-cost check before BAL read Post EIP-8038 the cold storage access cost (3000) exceeds the EIP-2200 call stipend (2300), so clearing the stipend sentry no longer guarantees the access cost is affordable. The implicit storage read in SSTORE records the slot into the EIP-7928 Block Access List, and that record survives frame rollback. With gas_left in (stipend, access_cost), the old ordering recorded a phantom read for an SSTORE that then ran out of gas on the access cost itself. Compute the access cost first and check it (alongside the stipend sentry) before the read, warming the slot only once the access is affordable -- mirroring the CALL opcode and matching SLOAD, which already charges access before reading. Pivot test_bal_sstore_and_oog OOG boundaries on the access cost instead of the stipend, and refresh the test_sstore_stipend_check_excludes_reservoir docstring. * test(amsterdam): pin the stipend+1 SSTORE BAL boundary, refresh test_cases.md * more descriptive name Co-authored-by: Jochem Brouwer <jochembrouwer96@gmail.com> --------- Co-authored-by: Toni Wahrstätter <info@toniwahrstaetter.com> Co-authored-by: Jochem Brouwer <jochembrouwer96@gmail.com> --- .../amsterdam/vm/instructions/storage.py | 29 +++++++---- .../test_block_access_lists_opcodes.py | 48 ++++++++++++++----- .../test_cases.md | 2 +- .../test_state_gas_sstore.py | 15 ++++-- 4 files changed, 65 insertions(+), 29 deletions(-) diff --git a/src/ethereum/forks/amsterdam/vm/instructions/storage.py b/src/ethereum/forks/amsterdam/vm/instructions/storage.py index 91aec91163d..5d7ded542e2 100644 --- a/src/ethereum/forks/amsterdam/vm/instructions/storage.py +++ b/src/ethereum/forks/amsterdam/vm/instructions/storage.py @@ -81,8 +81,25 @@ def sstore(evm: Evm) -> None: key = pop(evm.stack).to_be_bytes32() new_value = pop(evm.stack) - # check we have at least the stipend gas - check_gas(evm, GasCosts.CALL_STIPEND + Uint(1)) + gas_cost = Uint(0) + + # Access cost: cold or warm, always charged. + is_cold_access = ( + evm.message.current_target, + key, + ) not in evm.accessed_storage_keys + if is_cold_access: + gas_cost += GasCosts.COLD_STORAGE_ACCESS + else: + gas_cost += GasCosts.WARM_ACCESS + + # Gas must cover the access cost before the state access below + # records the slot read in the Block Access List. Post-repricing the + # access cost can exceed the stipend, so the EIP-2200 stipend sentry + # (`gas_left > CALL_STIPEND`) is no longer sufficient on its own. + check_gas(evm, max(gas_cost, GasCosts.CALL_STIPEND + Uint(1))) + if is_cold_access: + evm.accessed_storage_keys.add((evm.message.current_target, key)) tx_state = evm.message.tx_env.state original_value = get_storage_original( @@ -90,16 +107,8 @@ def sstore(evm: Evm) -> None: ) current_value = get_storage(tx_state, evm.message.current_target, key) - gas_cost = Uint(0) state_gas = StateGas(Uint(0)) - # Access cost: cold or warm, always charged. - if (evm.message.current_target, key) not in evm.accessed_storage_keys: - evm.accessed_storage_keys.add((evm.message.current_target, key)) - gas_cost += GasCosts.COLD_STORAGE_ACCESS - else: - gas_cost += GasCosts.WARM_ACCESS - # Write cost: charged on the first change to the slot this transaction. if original_value == current_value and current_value != new_value: gas_cost += GasCosts.STORAGE_WRITE diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py index 00f639bd6ff..ec3e199084f 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py @@ -58,6 +58,8 @@ class OutOfGasAt(Enum): EIP_2200_STIPEND = "oog_at_eip2200_stipend" EIP_2200_STIPEND_PLUS_1 = "oog_at_eip2200_stipend_plus_1" + ABOVE_STIPEND_BELOW_ACCESS = "oog_above_stipend_below_access" + ACCESS_COVERED_OOG_ON_WRITE = "access_covered_oog_on_write" EXACT_GAS_MINUS_1 = "oog_at_exact_gas_minus_1" @@ -96,6 +98,8 @@ class OutOfGasBoundary(Enum): [ OutOfGasAt.EIP_2200_STIPEND, OutOfGasAt.EIP_2200_STIPEND_PLUS_1, + OutOfGasAt.ABOVE_STIPEND_BELOW_ACCESS, + OutOfGasAt.ACCESS_COVERED_OOG_ON_WRITE, OutOfGasAt.EXACT_GAS_MINUS_1, None, # no oog, successful sstore ], @@ -110,10 +114,18 @@ def test_bal_sstore_and_oog( """ Test BAL recording with SSTORE at various OOG boundaries and success. - 1. OOG at EIP-2200 stipend check & implicit SLOAD -> no BAL changes - 2. OOG post EIP-2200 stipend check & implicit SLOAD -> storage read in BAL - 3. OOG at exact gas minus 1 -> storage read in BAL - 4. exact gas (success) -> storage write in BAL + The slot read is recorded in the BAL only once the cold access cost + is covered. Post-repricing that cost (COLD_STORAGE_ACCESS) exceeds the + EIP-2200 stipend, so clearing the stipend sentry alone no longer + records the read. The stipend + 1 case pins the old sentry boundary + against regressions to sentry-gated recording. + + 1. OOG at the stipend, below the access cost -> no BAL changes + 2. OOG above the stipend but below access cost (probed at + stipend + 1 and access cost - 1) -> no BAL changes + 3. OOG at the access cost, write unaffordable -> storage read in BAL + 4. OOG at exact gas minus 1 -> storage read in BAL + 5. exact gas (success) -> storage write in BAL """ alice = pre.fund_eoa() @@ -129,22 +141,32 @@ def test_bal_sstore_and_oog( # Full cost: PUSHes + SSTORE (COLD_STORAGE_ACCESS + STORAGE_SET) full_cost = storage_contract_code.gas_cost(fork) - # Push cost for stipend boundary calculations + # Push cost for the gas-boundary calculations below. push_code = Op.PUSH1(0x42) + Op.PUSH1(0x01) push_cost = push_code.gas_cost(fork) - # CALL_STIPEND is a threshold check, not a gas cost - # Keep from gas_costs + # CALL_STIPEND is a threshold check, not a gas cost. The cold access + # cost gates the read into the BAL and now exceeds the stipend. stipend = fork.gas_costs().CALL_STIPEND + cold_access = fork.gas_costs().COLD_STORAGE_ACCESS if out_of_gas_at == OutOfGasAt.EIP_2200_STIPEND: - # 2300 after PUSHes (fails stipend check: 2300 <= 2300) + # gas_left == stipend: fails the check, below the access cost. tx_gas_limit = intrinsic_gas_cost + push_cost + stipend elif out_of_gas_at == OutOfGasAt.EIP_2200_STIPEND_PLUS_1: - # 2301 after PUSHes (passes stipend, does SLOAD, fails charge_gas) + # gas_left == stipend + 1: clears the stipend sentry by one but + # cannot afford the access, so OOG before the read. tx_gas_limit = intrinsic_gas_cost + push_cost + stipend + 1 + elif out_of_gas_at == OutOfGasAt.ABOVE_STIPEND_BELOW_ACCESS: + # gas_left == access cost - 1: clears the stipend sentry but + # cannot afford the access, so OOG before the read. + tx_gas_limit = intrinsic_gas_cost + push_cost + cold_access - 1 + elif out_of_gas_at == OutOfGasAt.ACCESS_COVERED_OOG_ON_WRITE: + # gas_left == access cost: access affordable (read recorded), + # then OOG on the write cost. + tx_gas_limit = intrinsic_gas_cost + push_cost + cold_access elif out_of_gas_at == OutOfGasAt.EXACT_GAS_MINUS_1: - # fail at charge_gas() at exact gas - 1 (boundary condition) + # fail at the final charge at exact gas - 1 (boundary condition). tx_gas_limit = intrinsic_gas_cost + full_cost - 1 else: # exact gas for successful SSTORE @@ -156,10 +178,10 @@ def test_bal_sstore_and_oog( gas_limit=tx_gas_limit, ) - # Storage read recorded only if we pass the stipend check and reach - # implicit SLOAD (STIPEND_PLUS_1 and EXACT_GAS_MINUS_1) + # The read is recorded only once the access cost is covered: the + # frame reaches the implicit SLOAD before any later OOG. expect_storage_read = out_of_gas_at in ( - OutOfGasAt.EIP_2200_STIPEND_PLUS_1, + OutOfGasAt.ACCESS_COVERED_OOG_ON_WRITE, OutOfGasAt.EXACT_GAS_MINUS_1, ) expect_storage_write = out_of_gas_at is None diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_cases.md b/tests/amsterdam/eip7928_block_level_access_lists/test_cases.md index 67b7e6a4e8e..ad2f739d7ec 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_cases.md +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_cases.md @@ -63,7 +63,7 @@ | `test_bal_7702_null_address_delegation_no_code_change` | Ensure BAL does not record spurious code changes for net-zero code operations | Alice sends transaction with authorization delegating to NULL_ADDRESS (0x0), which sets code to `b""` on an account that already has `b""` code. Transaction sends 10 wei to Bob. | BAL **MUST** include Alice with `nonce_changes` (tx nonce + auth nonce increment) but **MUST NOT** include `code_changes` (setting `b"" -> b""` is net-zero and filtered out). Bob: `balance_changes` (receives 10 wei). This ensures net-zero code change is not recorded. | ✅ Completed | | `test_bal_7702_double_auth_reset` | Ensure BAL tracks multiple 7702 nonce increments but filters net-zero code change | Single transaction contains two EIP-7702 authorizations for `Alice`: (1) first auth sets delegation `0xef0100\|\|Oracle`, (2) second auth clears delegation back to empty. Transaction sends 10 wei to `Bob`. Two variants: (a) Self-funded: `Alice` is tx sender (one tx nonce bump + two auth bumps → nonce 0→3). (b) Sponsored: `Relayer` is tx sender (`Alice` only in auths → nonce 0→2 for `Alice`, plus one nonce bump for `Relayer`). | Variant (a): BAL **MUST** include `Alice` with `nonce_changes` 0→3. Variant (b): BAL **MUST** include `Alice` with `nonce_changes` 0→2 and `Relayer` with its own `nonce_changes`. For both variants, BAL **MUST NOT** include `code_changes` for `Alice` (net code is empty), **MUST** include `Bob` with `balance_changes` (receives 10 wei), and `Oracle` **MUST NOT** appear in BAL. | ✅ Completed | | `test_bal_7702_double_auth_swap` | Ensure BAL captures final code when double auth swaps delegation targets | `Relayer` sends transaction with two authorizations for Alice: (1) First auth sets delegation to `CONTRACT_A` at nonce=0, (2) Second auth changes delegation to `CONTRACT_B` at nonce=1. Transaction sends 10 wei to Bob. Per EIP-7702, only the last authorization takes effect. | BAL **MUST** include Alice with `nonce_changes` (both auths increment nonce to 2) and `code_changes` (final code is delegation designation for `CONTRACT_B`, not `CONTRACT_A`). Bob: `balance_changes` (receives 10 wei). Relayer: `nonce_changes`. Neither `CONTRACT_A` nor `CONTRACT_B` appear in BAL during delegation setup (never accessed). This ensures BAL shows final state, not intermediate changes. | ✅ Completed | -| `test_bal_sstore_and_oog` | Ensure BAL handles OOG during SSTORE execution at various gas boundaries (EIP-2200 stipend and implicit SLOAD) | Alice calls contract that attempts `SSTORE` to cold slot `0x01`. Parameterized: (1) OOG at EIP-2200 stipend check (2300 gas after PUSH opcodes) - fails before implicit SLOAD, (2) OOG at stipend + 1 (2301 gas) - passes stipend check but fails after implicit SLOAD, (3) OOG at exact gas - 1, (4) Successful SSTORE with exact gas. | For case (1): BAL **MUST NOT** include slot `0x01` in `storage_reads` or `storage_changes` (fails before implicit SLOAD). For cases (2) and (3): BAL **MUST** include slot `0x01` in `storage_reads` (implicit SLOAD occurred) but **MUST NOT** include in `storage_changes` (write didn't complete). For case (4): BAL **MUST** include slot `0x01` in `storage_changes` only (successful write; read is filtered by builder). | ✅ Completed | +| `test_bal_sstore_and_oog` | Ensure BAL handles OOG during SSTORE execution at various gas boundaries (EIP-2200 stipend, cold access cost, and implicit SLOAD) | Alice calls contract that attempts `SSTORE` to cold slot `0x01`. Parameterized: (1) OOG at EIP-2200 stipend check (2300 gas after PUSH opcodes) - fails the stipend sentry, (2) OOG at stipend + 1 (2301 gas) - clears the sentry but fails the access-cost check before the implicit SLOAD, (3) OOG at cold access cost - 1 - same as (2) at the upper boundary, (4) OOG at exactly the cold access cost - access affordable, implicit SLOAD occurs, OOG on the write cost, (5) OOG at exact gas - 1, (6) Successful SSTORE with exact gas. | For cases (1)-(3): BAL **MUST NOT** include slot `0x01` in `storage_reads` or `storage_changes` (the implicit SLOAD must not happen unless the frame covers the slot's access cost; the stipend sentry alone is insufficient since `COLD_STORAGE_ACCESS` > `CALL_STIPEND`). For cases (4) and (5): BAL **MUST** include slot `0x01` in `storage_reads` (implicit SLOAD occurred) but **MUST NOT** include in `storage_changes` (write didn't complete). For case (6): BAL **MUST** include slot `0x01` in `storage_changes` only (successful write; read is filtered by builder). | ✅ Completed | | `test_bal_sstore_static_context` | SSTORE in static context must not leak storage reads into BAL | Contract A STATICCALLs Contract B which attempts `SSTORE`. Parametrized: `original_value` (0, nonzero) to catch clients that perform the implicit SLOAD before the static check. | Contract B IS in BAL (accessed via STATICCALL) but **MUST NOT** have `storage_reads`. | ✅ Completed | | `test_bal_sload_and_oog` | Ensure BAL handles OOG during SLOAD execution correctly | Alice calls contract that attempts `SLOAD` from cold slot `0x01`. Parameterized: (1) OOG at SLOAD opcode (insufficient gas), (2) Successful SLOAD execution. | For OOG case: BAL **MUST NOT** contain slot `0x01` in `storage_reads` since storage wasn't accessed. For success case: BAL **MUST** contain slot `0x01` in `storage_reads`. | ✅ Completed | | `test_bal_balance_and_oog` | Ensure BAL handles OOG during BALANCE opcode execution correctly | Alice calls contract that attempts `BALANCE` opcode on cold target account. Parameterized: (1) OOG at BALANCE opcode (insufficient gas), (2) Successful BALANCE execution. | For OOG case: BAL **MUST NOT** include target account (wasn't accessed). For success case: BAL **MUST** include target account in `account_changes`. | ✅ Completed | diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_sstore.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_sstore.py index 11ff400a16a..653a986f8cc 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_sstore.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_sstore.py @@ -421,15 +421,20 @@ def test_sstore_stipend_check_excludes_reservoir( gas_above_stipend: int, ) -> None: """ - Verify SSTORE stipend check uses gas_left only, not the reservoir. + Verify the SSTORE gas check uses gas_left only, not the reservoir. A child frame has gas_left at or just below the stipend threshold (GAS_CALL_STIPEND + 1) while the reservoir holds ample state gas. - The stipend check must fail when gas_left < stipend, regardless - of the reservoir balance. + The check must fail when gas_left is too low, regardless of the + reservoir balance. - With below_stipend: SSTORE fails (gas_left < 2301, reservoir ignored). - With at_stipend: SSTORE passes the stipend check and proceeds. + Post-8038 the cold access cost (COLD_STORAGE_ACCESS = 3000) exceeds + the stipend (2300), so for this cold slot the access cost is the + binding gate and the stipend sentry is subsumed. The reservoir is + excluded either way, which is what this test pins down. + + With below_stipend: SSTORE fails (gas_left too low, reservoir ignored). + With at_stipend: SSTORE has full regular gas and proceeds. """ gas_costs = fork.gas_costs() stipend = gas_costs.CALL_STIPEND + 1 From 842f0a6bb5a9c3c06f6bfc64c1638a65c12a1103 Mon Sep 17 00:00:00 2001 From: spencer <spencer.taylor-brown@ethereum.org> Date: Wed, 8 Jul 2026 17:48:49 +0100 Subject: [PATCH 106/233] feat(spec-specs, tests): finalize EIP-8282 builder deposit contract (#3091) * feat(spec,tests): adopt devnet-7 EIP-8282 builder deposit contract * chore(spec-specs, tests): update EIP-8282 builder deposit bytecode and add disable test * Part 1 * Part 2 * Part 3 * Remove valid_requests * Remove fee from `from_index` * Fix per-call excess processing * Update EIP-8282 exits contract * Update EIP-8282 contracts addresses * Specs: Update EIP-8282 contracts addresses * Packages: Update EIP-8282 contracts addresses * Tests: Update EIP-8282 contract deployment tests * feat(test-tools): Allow system contracts to be deployed via factory * feat(specs): EIP-8282: Factory deployment method addresses update * feat(tests): EIP-8282: Factory deployment method addresses update * fix(tests): EIP-8282: pay request fees, verify requests hash, add exits disable test * Apply suggestion from @marioevz --------- Co-authored-by: marioevz <marioevz@gmail.com> --- .../tests/test_execute_eth_config.py | 4 +- .../contracts/builder_deposit_request.bin | Bin 568 -> 628 bytes .../contracts/builder_exit_request.bin | Bin 396 -> 458 bytes .../forks/forks/eips/amsterdam/eip_8282.py | 4 +- .../system_contract_request_types.py | 45 ++-- .../tools/utility/generators.py | 97 ++++++--- src/ethereum/forks/amsterdam/fork.py | 4 +- .../builder_deposit_deploy_tx.json | 15 -- .../builder_deposit_factory_deploy.json | 5 + .../builder_exit_deploy_tx.json | 15 -- .../builder_exit_factory_deploy.json | 5 + .../conftest.py | 2 +- .../helpers.py | 15 +- .../spec.py | 30 +-- .../test_builder_deposit_disable.py | 111 ++++++++++ .../test_builder_exit_disable.py | 113 ++++++++++ .../test_contract_deployment.py | 20 +- .../system_contract_request_fixtures.py | 108 ++++++++-- tests/prague/eip6110_deposits/conftest.py | 18 +- tests/prague/eip6110_deposits/helpers.py | 5 +- .../conftest.py | 3 +- .../helpers.py | 6 +- .../test_withdrawal_requests.py | 178 +++++++--------- .../prague/eip7251_consolidations/conftest.py | 3 +- .../prague/eip7251_consolidations/helpers.py | 10 +- .../test_consolidations.py | 193 ++++++++---------- .../conftest.py | 31 +-- .../eip7702_set_code_tx/test_set_code_txs.py | 5 + 28 files changed, 650 insertions(+), 395 deletions(-) delete mode 100644 tests/amsterdam/eip8282_builder_execution_requests/builder_deposit_deploy_tx.json create mode 100644 tests/amsterdam/eip8282_builder_execution_requests/builder_deposit_factory_deploy.json delete mode 100644 tests/amsterdam/eip8282_builder_execution_requests/builder_exit_deploy_tx.json create mode 100644 tests/amsterdam/eip8282_builder_execution_requests/builder_exit_factory_deploy.json create mode 100644 tests/amsterdam/eip8282_builder_execution_requests/test_builder_deposit_disable.py create mode 100644 tests/amsterdam/eip8282_builder_execution_requests/test_builder_exit_disable.py diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/eth_config/tests/test_execute_eth_config.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/eth_config/tests/test_execute_eth_config.py index f83ea5ebda1..aea19ae6138 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/eth_config/tests/test_execute_eth_config.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/eth_config/tests/test_execute_eth_config.py @@ -368,9 +368,9 @@ "systemContracts": { "BEACON_ROOTS_ADDRESS": "0x000f3df6d732807ef1319fb7b8bb8522d0beac02", "BUILDER_DEPOSIT_CONTRACT_ADDRESS": - "0x0000884d2aa32eaa155f59a2f24efa73d9008282", + "0x0000bff46984e3725691fa540a8c7589300d8282", "BUILDER_EXIT_CONTRACT_ADDRESS": - "0x000014574a74c805590aff9499fc7a690f008282", + "0x000064d678505ad48f8ccb093bc65613800e8282", "CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS": "0x0000bbddc7ce488642fb579f8b00f3a590007251", "DEPOSIT_CONTRACT_ADDRESS": "0x00000000219ab540356cbb839cbe05303d7705fa", diff --git a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/contracts/builder_deposit_request.bin b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/contracts/builder_deposit_request.bin index 6bf64afd5bc6ad74fc4222d2f6b48f1623e8fc36..b5e13fba5caffa5660c3b634a3becb02ca5884c3 100644 GIT binary patch delta 261 zcmW-cu}T9$5Qd$-E3Pw(5fKDWP#YV?BM3IZCs;!6R+wUDQLOU@W-mU#Y_8M7w~%`V zU%=YZ+O&dmX@>v*Vdnp)&o{Xonj_zR!`V|X3(qj;B3hn#$M+3#AsHZBVQ3A55K?RR z`PoO1m3dhi$rA%4;l~E0lYH%*n{YqocRv~KRl}eem-LIvYNRg7!S2F8+`fM97^$*t z^nrUkwE?cQb&7;Bjstvk=kRuM-C%;{TK$g~Hg$lFHaSvBoYkl;cb?|T9aDLFeCfGp fTd=|0cyx|}Q7{<Z*s4^XjF5B_YVM6P5&iuGmu*uV delta 264 zcmXX>F;2ul48%?Z6%vsyNEC?ZprA&}3BeyuE0kml7xsz*@d}b04V{zg;3Mhj=qc#< z1GEU70~gkeH6D+@(lOP8o$VkQm(74D_bv!@D4<N-Wx3pi7IcLVE7Y_XfL>);ldd%# zy!Q(kXYv>p5nXUo<7+ASlNz@-F}XOu{rRu`=H}|@bt{|Z8-(hTKNXZ_EKo^O_V3F) zf~nvzcmu@LwzCK?>q~e(KB}pQW-$I1RIrNhVOcyIWJxO8{4hVuBkl&zBxYQ69oWrz W@SY={)&x>$LTn3ma6}8s{QV0s+Eu^+ diff --git a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/contracts/builder_exit_request.bin b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/contracts/builder_exit_request.bin index b41502db5d4d5a5a5f4d7d2770cb72adc4afeaac..a2d194ef87c85a83536a8182078361572133ca63 100644 GIT binary patch delta 247 zcmW-bu}VWh5Jj1N!BVo2fSn4K22zByHdbP>O5WRIin)&z`HPr+304wjV`Jrq_yHFF zg`K^Kl_aw^!@Y1hbDpY?N>7eHqs&$h`TTUTAGGuyK{L4IaTfz(!*E&=!z-wBAQhI* z53!7ibnP~4r@aiKte7{f&)VCC5T+Op@EWIuHz`N%aVFn9>08O$+v6XD_gOe4`#H*A zqs!lhGp%-3ucE@$E!`L|h($qFv$0;x)a+7`y(qGe5w7dVx3rBMg0%G3FcstfRCa#P NVgRaf?My)A?*Ix2Qvv`0 delta 216 zcmXYrJqp4=6oh#j3o9|8Xd@`tSO^v&C$Lml#bk?DytkbvuoBFVg@wgiy@IFFLx^Xv z*0=$iVW#+I<{q2_={FySL)v5qHyW&}s_Ixc8HPeJ2q6_=m$xPdDb=x5(r^smgqJDG zRT>wqb<DF4pG;hHQ7yFQA-yqb``~Vi*3)QiBVnHV?w<Qj$FCF}WEUSNMpz{lBHyRU rTjC(W|DCwL)3soYCS=&KDhdcK?2OiAVV@$fk^sq5knmjjRie)ixNk|Q diff --git a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8282.py b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8282.py index 090a41ec119..b8860fd0ba3 100644 --- a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8282.py +++ b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8282.py @@ -14,12 +14,12 @@ from ....base_fork import BaseFork from ....bytecode import load_contract_bytecode -BUILDER_DEPOSIT_CONTRACT_ADDRESS = 0x0000884D2AA32EAA155F59A2F24EFA73D9008282 +BUILDER_DEPOSIT_CONTRACT_ADDRESS = 0x0000BFF46984E3725691FA540A8C7589300D8282 BUILDER_DEPOSIT_CONTRACT_BYTECODE = load_contract_bytecode( __name__, "builder_deposit_request.bin" ) -BUILDER_EXIT_CONTRACT_ADDRESS = 0x000014574A74C805590AFF9499FC7A690F008282 +BUILDER_EXIT_CONTRACT_ADDRESS = 0x000064D678505AD48F8CCB093BC65613800E8282 BUILDER_EXIT_CONTRACT_BYTECODE = load_contract_bytecode( __name__, "builder_exit_request.bin" ) diff --git a/packages/testing/src/execution_testing/test_types/system_contract_request_types.py b/packages/testing/src/execution_testing/test_types/system_contract_request_types.py index 3b39bf162a3..4223269a288 100644 --- a/packages/testing/src/execution_testing/test_types/system_contract_request_types.py +++ b/packages/testing/src/execution_testing/test_types/system_contract_request_types.py @@ -12,7 +12,7 @@ from abc import abstractmethod from dataclasses import dataclass, field, replace -from typing import Any, Callable, ClassVar, List, Self, Sequence +from typing import Callable, ClassVar, List, Literal, Self, Sequence from execution_testing.base_types import Address, CamelModel from execution_testing.forks.forks.helpers import fake_exponential @@ -57,10 +57,19 @@ def with_source_address(self, source_address: Address) -> Self: """Return a copy of the request with its source address set.""" ... + def set_source_address(self, source_address: Address) -> None: + """ + Record `source_address` on the request in place, for request types + that carry one (e.g. withdrawals, consolidations). A no-op for request + types whose serialized form omits the source (e.g. deposits). + """ + if "source_address" in type(self).model_fields: + self.source_address = source_address + @classmethod @abstractmethod - def from_index(cls, index: int, fee: int | None = None) -> Self: - """Build a request from a sequential index, paying `fee`.""" + def from_index(cls, index: int) -> Self: + """Build a request from a sequential index.""" ... @@ -85,12 +94,8 @@ class FeeSystemContractRequest(SystemContractRequest): """Target requests per block; excess above this raises the fee.""" max_per_block: ClassVar[int] """Maximum number of requests dequeued into a single block.""" - - def model_post_init(self, __context: Any) -> None: - """Default an unset fee to the base fee (the fee at zero excess).""" - super().model_post_init(__context) - if "fee" not in self.model_fields_set: - self.fee = type(self).get_fee(0) + excess_fee_processing: ClassVar[Literal["block", "call"]] = "block" + """When the excess fee is recalculated.""" @property def value(self) -> int: @@ -149,7 +154,7 @@ def get_n_fee_increment_blocks( [ SystemContractInteractionContract( requests=[ - cls.from_index(i, fee) + cls.from_index(i) for i in range( request_index, request_index + requests_required, @@ -241,26 +246,6 @@ def update_pre(self, pre: Alloc) -> Self: """ raise NotImplementedError - def valid_requests( - self, current_minimum_fee: int | None = None - ) -> List[SystemContractRequest]: - """ - Return the list of requests that should be included in the block. - - `current_minimum_fee` filters out requests whose value is below it - (e.g. the per-block fee). When `None`, no fee filter is applied and - every request marked `valid` is returned, trusting the caller to - ensure each request's value is sufficient. - """ - source = self.request_source_address - assert source is not None, "Source address not initialized" - return [ - r.with_source_address(source) - for r in self.requests - if r.valid - and (current_minimum_fee is None or r.value >= current_minimum_fee) - ] - @dataclass(kw_only=True, frozen=True) class SystemContractInteractionTransaction(SystemContractInteractionBase): diff --git a/packages/testing/src/execution_testing/tools/utility/generators.py b/packages/testing/src/execution_testing/tools/utility/generators.py index a70b3831ce2..ee7b526d81f 100644 --- a/packages/testing/src/execution_testing/tools/utility/generators.py +++ b/packages/testing/src/execution_testing/tools/utility/generators.py @@ -8,7 +8,13 @@ import pytest -from execution_testing.base_types import Account, Address, Hash +from execution_testing.base_types import ( + Account, + Address, + Bytes, + CamelModel, + Hash, +) from execution_testing.exceptions import BlockException from execution_testing.forks import Berlin, Fork, TransitionFork from execution_testing.forks.base_fork import BaseFork @@ -87,10 +93,22 @@ def __call__( pass +class FactoryDeployment(CamelModel): + """ + Information required to deploy a system contract using the factory + deployment method. + """ + + factory: Address + salt: Hash + initcode: Bytes + + def generate_system_contract_deploy_test( *, fork: Fork, - tx_json_path: Path, + tx_json_path: Path | None = None, + factory_json_path: Path | None = None, expected_deploy_address: Address, fail_on_empty_code: bool, expected_system_contract_storage: Dict | None = None, @@ -128,10 +146,11 @@ def generate_system_contract_deploy_test( Arguments: fork (Fork): The fork to test. - tx_json_path (Path): Path to the JSON file with the transaction to - deploy the system contract. Providing a JSON - file is useful to copy-paste the transaction - from the EIP. + tx_json_path (Path | None): Path to the JSON file with the transaction to + deploy the system contract. Providing a JSON file is useful to + copy-paste the transaction from the EIP. + factory_json_path (Path | None): Path to the JSON file with the factory + deployment details. expected_deploy_address (Address): The expected address of the deployed contract. fail_on_empty_code (bool): If True, the test is expected to fail @@ -145,22 +164,26 @@ def generate_system_contract_deploy_test( "fork parameter of generate_system_contract_deploy_test must be " "a subclass of Fork" ) - with open(tx_json_path, mode="r") as f: - tx_json = json.loads(f.read()) - if "gasLimit" not in tx_json and "gas" in tx_json: - tx_json["gasLimit"] = tx_json["gas"] - del tx_json["gas"] - if "protected" not in tx_json: - tx_json["protected"] = False - deploy_tx = Transaction.model_validate(tx_json).with_signature_and_sender() - gas_price = deploy_tx.gas_price - assert gas_price is not None - deployer_required_balance = deploy_tx.gas_limit * gas_price - deployer_address = deploy_tx.sender - if "hash" in tx_json: - assert deploy_tx.hash == Hash(tx_json["hash"]) - if "sender" in tx_json: - assert deploy_tx.sender == Address(tx_json["sender"]) + + tx_json: Dict | None = None + factory_json: FactoryDeployment | None = None + + if tx_json_path is not None: + with open(tx_json_path, mode="r") as f: + tx_json = json.loads(f.read()) + if "gasLimit" not in tx_json and "gas" in tx_json: + tx_json["gasLimit"] = tx_json["gas"] + del tx_json["gas"] + if "protected" not in tx_json: + tx_json["protected"] = False + elif factory_json_path is not None: + with open(factory_json_path, mode="r") as f: + factory_json = FactoryDeployment.model_validate_json(f.read()) + else: + raise Exception( + "Either `tx_json_path` or `factory_json_path` have to " + "be provided to generate a system contract deploy test" + ) def decorator(func: SystemContractDeployTestFunction) -> Callable: @pytest.mark.parametrize( @@ -198,8 +221,33 @@ def wrapper( "Block number based transition forks are not supported by " "generate_system_contract_deploy_test" ) - assert deployer_address is not None - assert deploy_tx.created_contract == expected_deploy_address + if tx_json is not None: + deploy_tx = Transaction.model_validate( + tx_json + ).with_signature_and_sender() + gas_price = deploy_tx.gas_price + assert gas_price is not None + deployer_required_balance = deploy_tx.gas_limit * gas_price + deployer_address = deploy_tx.sender + assert deployer_address is not None + pre.fund_address(deployer_address, deployer_required_balance) + if "hash" in tx_json: + assert deploy_tx.hash == Hash(tx_json["hash"]) + if "sender" in tx_json: + assert deploy_tx.sender == Address(tx_json["sender"]) + assert deploy_tx.created_contract == expected_deploy_address + elif factory_json is not None: + deployer_address = pre.fund_eoa() + deploy_tx = Transaction( + to=factory_json.factory, + data=factory_json.salt + factory_json.initcode, + sender=deployer_address, + ) + else: + raise Exception( + "Either `tx_json_path` or `factory_json_path` have to " + "be provided to generate a system contract deploy test" + ) blocks: List[Block] = [] if test_type == DeploymentTestType.DEPLOY_BEFORE_FORK: @@ -258,7 +306,6 @@ def wrapper( nonce=0, balance=balance, ) - pre.fund_address(deployer_address, deployer_required_balance) expected_deploy_address_int = int.from_bytes( expected_deploy_address, "big" diff --git a/src/ethereum/forks/amsterdam/fork.py b/src/ethereum/forks/amsterdam/fork.py index 1f2771c4c49..2d960c408a1 100644 --- a/src/ethereum/forks/amsterdam/fork.py +++ b/src/ethereum/forks/amsterdam/fork.py @@ -136,10 +136,10 @@ "0x0000BBdDc7CE488642fb579F8B00f3a590007251" ) BUILDER_DEPOSIT_CONTRACT_ADDRESS = hex_to_address( - "0x0000884d2AA32eAa155F59A2f24eFa73D9008282" + "0x0000BFF46984E3725691FA540A8C7589300D8282" ) BUILDER_EXIT_CONTRACT_ADDRESS = hex_to_address( - "0x000014574A74c805590AFF9499fc7A690f008282" + "0x000064D678505AD48F8CCB093BC65613800E8282" ) HISTORY_STORAGE_ADDRESS = hex_to_address( "0x0000F90827F1C53a10cb7A02335B175320002935" diff --git a/tests/amsterdam/eip8282_builder_execution_requests/builder_deposit_deploy_tx.json b/tests/amsterdam/eip8282_builder_execution_requests/builder_deposit_deploy_tx.json deleted file mode 100644 index 0381a5367f0..00000000000 --- a/tests/amsterdam/eip8282_builder_execution_requests/builder_deposit_deploy_tx.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "type": "0x0", - "nonce": "0x0", - "to": null, - "gasLimit": "0x3d090", - "gasPrice": "0xe8d4a51000", - "maxPriorityFeePerGas": null, - "maxFeePerGas": null, - "value": "0x0", - "input": "0x00", - "v": "0x1b", - "r": "0x539", - "s": "0x5feeb084551e4e03a3581e269bc2ea2f8d0008", - "protected": false -} diff --git a/tests/amsterdam/eip8282_builder_execution_requests/builder_deposit_factory_deploy.json b/tests/amsterdam/eip8282_builder_execution_requests/builder_deposit_factory_deploy.json new file mode 100644 index 00000000000..7a613e200d5 --- /dev/null +++ b/tests/amsterdam/eip8282_builder_execution_requests/builder_deposit_factory_deploy.json @@ -0,0 +1,5 @@ +{ + "factory": "0x4e59b44847b379578588920cA78FbF26c0B4956C", + "salt": "0x1f4f2c41c28e816e259b621c58b94b37309c8dec42c8f6e400001a46c9d96bf7", + "initcode": "0x61027480600a5f395ff33373fffffffffffffffffffffffffffffffffffffffe1461011c575f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff146102705760015460088111605257506058565b60089003015b601190600182026001905f5b5f821115607f57810190830284830290049160010191906064565b90939004925050503660b814609f57366102705734610270575f5260205ff35b8034106102705760383567ffffffffffffffff1680633b9aca001161027057633b9aca00029034031061027057600154600101600155600354806006026004015f358155600101602035815560010160403581556001016060358155600101608035815560010160a035905560b85f5f3760b85fa0600101600355005b60035460025480820380604011610131575060405b5f5b8181146101d7578281016006026004018160b8028154815260200181600101548152602001816002015480825260401c67ffffffffffffffff16816010018160381c81600701538160301c81600601538160281c81600501538160201c81600401538160181c81600301538160101c81600201538160081c816001015353602001816003015481526020018160040154815260200190600501549052600101610133565b91018092146101e957906002556101f4565b90505f6002555f6003555b36610242575f54600154817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1461023057600882820111610238575b50505f610264565b0160089003610264565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5b5f555f60015560b8025ff35b5f5ffd" +} diff --git a/tests/amsterdam/eip8282_builder_execution_requests/builder_exit_deploy_tx.json b/tests/amsterdam/eip8282_builder_execution_requests/builder_exit_deploy_tx.json deleted file mode 100644 index 0381a5367f0..00000000000 --- a/tests/amsterdam/eip8282_builder_execution_requests/builder_exit_deploy_tx.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "type": "0x0", - "nonce": "0x0", - "to": null, - "gasLimit": "0x3d090", - "gasPrice": "0xe8d4a51000", - "maxPriorityFeePerGas": null, - "maxFeePerGas": null, - "value": "0x0", - "input": "0x00", - "v": "0x1b", - "r": "0x539", - "s": "0x5feeb084551e4e03a3581e269bc2ea2f8d0008", - "protected": false -} diff --git a/tests/amsterdam/eip8282_builder_execution_requests/builder_exit_factory_deploy.json b/tests/amsterdam/eip8282_builder_execution_requests/builder_exit_factory_deploy.json new file mode 100644 index 00000000000..ea388532bb0 --- /dev/null +++ b/tests/amsterdam/eip8282_builder_execution_requests/builder_exit_factory_deploy.json @@ -0,0 +1,5 @@ +{ + "factory": "0x4e59b44847b379578588920cA78FbF26c0B4956C", + "salt": "0x89abb1878437213f971f849327423ed1d9c5cdb03970cd8f0000318b3ff10119", + "initcode": "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5f556101ca80602d5f395ff33373fffffffffffffffffffffffffffffffffffffffe1460e1575f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff146101c65760015460028111605157506057565b60029003015b601190600182026001905f5b5f821115607e57810190830284830290049160010191906063565b909390049250505036603014609e57366101c657346101c6575f5260205ff35b34106101c657600154600101600155600354806003026004013381556001015f35815560010160203590553360601b5f5260305f60143760445fa0600101600355005b6003546002548082038060101160f5575060105b5f5b81811461012d5782810160030260040181604402815460601b8152601401816001015481526020019060020154905260010160f7565b910180921461013f579060025561014a565b90505f6002555f6003555b36610198575f54600154817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff146101865760028282011161018e575b50505f6101ba565b01600290036101ba565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5b5f555f6001556044025ff35b5f5ffd" +} diff --git a/tests/amsterdam/eip8282_builder_execution_requests/conftest.py b/tests/amsterdam/eip8282_builder_execution_requests/conftest.py index f0f2b059f78..e17cee467fc 100644 --- a/tests/amsterdam/eip8282_builder_execution_requests/conftest.py +++ b/tests/amsterdam/eip8282_builder_execution_requests/conftest.py @@ -3,6 +3,6 @@ from ...common.system_contract_request_fixtures import ( blocks, # noqa: F401 included_requests, # noqa: F401 - prepared_system_contract_interactions_per_block, # noqa: F401 + system_contract_interactions_per_block_copy, # noqa: F401 timestamp, # noqa: F401 ) diff --git a/tests/amsterdam/eip8282_builder_execution_requests/helpers.py b/tests/amsterdam/eip8282_builder_execution_requests/helpers.py index 55012984e4d..598daaabc56 100644 --- a/tests/amsterdam/eip8282_builder_execution_requests/helpers.py +++ b/tests/amsterdam/eip8282_builder_execution_requests/helpers.py @@ -1,6 +1,6 @@ """Helpers for the EIP-8282 builder execution request tests.""" -from typing import ClassVar, Self +from typing import ClassVar, Literal, Self from execution_testing import Address, FeeSystemContractRequest from execution_testing import ( @@ -31,6 +31,7 @@ class BuilderDepositRequest( update_fraction: ClassVar[int] = Spec.REQUEST_FEE_UPDATE_FRACTION target_per_block: ClassVar[int] = Spec.TARGET_DEPOSIT_REQUESTS_PER_BLOCK max_per_block: ClassVar[int] = Spec.MAX_DEPOSIT_REQUESTS_PER_BLOCK + excess_fee_processing: ClassVar[Literal["block", "call"]] = "call" extra_wei: int = 0 """ @@ -67,16 +68,13 @@ def with_source_address( return self.copy() @classmethod - def from_index(cls, index: int, fee: int | None = None) -> Self: + def from_index(cls, index: int) -> Self: """Build a builder deposit request from a sequential index.""" - if fee is None: - fee = cls.get_fee(0) return cls( pubkey=index * 3, withdrawal_credentials=(index * 3) + 1, amount=Spec.BUILDER_MIN_DEPOSIT // 10**9, signature=(index * 3) + 2, - fee=fee, ) @@ -95,6 +93,7 @@ class BuilderExitRequest(BuilderExitRequestBase, FeeSystemContractRequest): update_fraction: ClassVar[int] = Spec.REQUEST_FEE_UPDATE_FRACTION target_per_block: ClassVar[int] = Spec.TARGET_EXIT_REQUESTS_PER_BLOCK max_per_block: ClassVar[int] = Spec.MAX_EXIT_REQUESTS_PER_BLOCK + excess_fee_processing: ClassVar[Literal["block", "call"]] = "call" @property def calldata(self) -> bytes: @@ -108,8 +107,6 @@ def with_source_address( return self.copy(source_address=source_address) @classmethod - def from_index(cls, index: int, fee: int | None = None) -> Self: + def from_index(cls, index: int) -> Self: """Build a builder exit request from a sequential index.""" - if fee is None: - fee = cls.get_fee(0) - return cls(pubkey=index, fee=fee) + return cls(pubkey=index) diff --git a/tests/amsterdam/eip8282_builder_execution_requests/spec.py b/tests/amsterdam/eip8282_builder_execution_requests/spec.py index 5e9f6e60da1..3f4e9520966 100644 --- a/tests/amsterdam/eip8282_builder_execution_requests/spec.py +++ b/tests/amsterdam/eip8282_builder_execution_requests/spec.py @@ -15,25 +15,19 @@ class ReferenceSpec: version: str -# EIP-8282 is a Draft; its addresses, request-type bytes, and predeploy -# bytecode are placeholders pending the EIP's final, audit-frozen values. ref_spec_8282 = ReferenceSpec( git_path="EIPS/eip-8282.md", - version="0000000000000000000000000000000000000000", + version="35ab20cb31a416c50600da00125d262e1756850c", ) class Spec: - """ - Constants and parameters from EIP-8282. Addresses are the - glamsterdam-devnet-6 values; request-type bytes remain placeholders - pending the EIP's final allocation. - """ + """Constants and parameters from EIP-8282.""" BUILDER_DEPOSIT_CONTRACT_ADDRESS = ( - 0x0000884D2AA32EAA155F59A2F24EFA73D9008282 + 0x0000BFF46984E3725691FA540A8C7589300D8282 ) - BUILDER_EXIT_CONTRACT_ADDRESS = 0x000014574A74C805590AFF9499FC7A690F008282 + BUILDER_EXIT_CONTRACT_ADDRESS = 0x000064D678505AD48F8CCB093BC65613800E8282 BUILDER_DEPOSIT_REQUEST_TYPE = 0x03 BUILDER_EXIT_REQUEST_TYPE = 0x04 @@ -41,15 +35,25 @@ class Spec: SYSTEM_ADDRESS = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE SYSTEM_CALL_GAS_LIMIT = 30_000_000 - # Shared request-bus parameters (identical to EIP-7002 / EIP-7251). - MAX_DEPOSIT_REQUESTS_PER_BLOCK = 256 - TARGET_DEPOSIT_REQUESTS_PER_BLOCK = 32 + # Request-bus parameters. + MAX_DEPOSIT_REQUESTS_PER_BLOCK = 64 + TARGET_DEPOSIT_REQUESTS_PER_BLOCK = 8 MAX_EXIT_REQUESTS_PER_BLOCK = 16 TARGET_EXIT_REQUESTS_PER_BLOCK = 2 MIN_REQUEST_FEE = 1 REQUEST_FEE_UPDATE_FRACTION = 17 EXCESS_INHIBITOR = 2**256 - 1 + # Storage layout shared by both predeploys (the EIP-7002 request-bus + # pattern): queued records are stored as 32-byte words from the queue + # offset onward. Seeding the excess slot with `EXCESS_INHIBITOR` disables + # the queue; the next system call resets it. + EXCESS_STORAGE_SLOT = 0 + COUNT_STORAGE_SLOT = 1 + QUEUE_HEAD_STORAGE_SLOT = 2 + QUEUE_TAIL_STORAGE_SLOT = 3 + QUEUE_STORAGE_OFFSET = 4 + # Minimum credited stake for a builder deposit, in wei (1 ETH). BUILDER_MIN_DEPOSIT = 1_000_000_000_000_000_000 diff --git a/tests/amsterdam/eip8282_builder_execution_requests/test_builder_deposit_disable.py b/tests/amsterdam/eip8282_builder_execution_requests/test_builder_deposit_disable.py new file mode 100644 index 00000000000..2612ecf0bc0 --- /dev/null +++ b/tests/amsterdam/eip8282_builder_execution_requests/test_builder_deposit_disable.py @@ -0,0 +1,111 @@ +""" +Disable-switch tests for +[EIP-8282: Builder Execution Requests](https://eips.ethereum.org/EIPS/eip-8282). + +The builder deposit predeploy carries a reversible kill switch: while +`EXCESS_INHIBITOR` sits in the excess slot, deposits revert, and the next +end-of-block system call clears the slot and re-enables the queue (the system +call sets the inhibitor only when it carries input, which the protocol never +sends, so absent a fork that appends calldata the disabled state always lasts +exactly until the end of the block). The disabled state is therefore seeded +directly here rather than triggered. +""" + +import pytest +from execution_testing import ( + Account, + Alloc, + Block, + BlockchainTestFiller, + Fork, + Header, + Requests, + SystemContractInteractionTransaction, +) + +from .helpers import BuilderDepositRequest +from .spec import Spec, ref_spec_8282 + +REFERENCE_SPEC_GIT_PATH = ref_spec_8282.git_path +REFERENCE_SPEC_VERSION = ref_spec_8282.version + +pytestmark = [ + pytest.mark.valid_from("Amsterdam"), + pytest.mark.pre_alloc_mutable(), +] + + +@pytest.fixture +def inhibited_pre(pre: Alloc, fork: Fork) -> Alloc: + """Seed the builder deposit predeploy with the disable inhibitor set.""" + predeploy = fork.pre_allocation_blockchain()[ + Spec.BUILDER_DEPOSIT_CONTRACT_ADDRESS + ] + pre[Spec.BUILDER_DEPOSIT_CONTRACT_ADDRESS] = Account( + nonce=predeploy["nonce"], + code=predeploy["code"], + storage={Spec.EXCESS_STORAGE_SLOT: Spec.EXCESS_INHIBITOR}, + ) + return pre + + +def deposit_request() -> BuilderDepositRequest: + """Build a minimum-stake deposit request paying the zero-excess fee.""" + return BuilderDepositRequest( + pubkey=0x01, + withdrawal_credentials=0x02, + amount=Spec.BUILDER_MIN_DEPOSIT // 10**9, + signature=0x03, + fee=BuilderDepositRequest.get_fee(0), + ) + + +def test_builder_deposit_inhibited( + blockchain_test: BlockchainTestFiller, + inhibited_pre: Alloc, +) -> None: + """ + A deposit to an inhibited predeploy reverts and produces no request, the + end-of-block system call clears the inhibitor back to zero, and an + identical deposit in the next block is queued and dequeued normally. + """ + rejected = SystemContractInteractionTransaction( + requests=[deposit_request()] + ).update_pre(inhibited_pre) + accepted = SystemContractInteractionTransaction( + requests=[deposit_request()] + ).update_pre(inhibited_pre) + + # The dequeue advances past the record but does not zero its slots, so + # the accepted request's calldata words remain in the queue's storage. + calldata = deposit_request().calldata + residual_record_slots = { + Spec.QUEUE_STORAGE_OFFSET + i: calldata[i * 32 : (i + 1) * 32].ljust( + 32, b"\x00" + ) + for i in range((len(calldata) + 31) // 32) + } + + blockchain_test( + pre=inhibited_pre, + blocks=[ + Block( + txs=rejected.transactions(), + header_verify=Header(requests_hash=Requests()), + ), + Block( + txs=accepted.transactions(), + header_verify=Header( + requests_hash=Requests(*accepted.requests) + ), + ), + ], + post={ + Spec.BUILDER_DEPOSIT_CONTRACT_ADDRESS: Account( + storage={ + Spec.EXCESS_STORAGE_SLOT: 0, + **residual_record_slots, + }, + ), + }, + ) diff --git a/tests/amsterdam/eip8282_builder_execution_requests/test_builder_exit_disable.py b/tests/amsterdam/eip8282_builder_execution_requests/test_builder_exit_disable.py new file mode 100644 index 00000000000..778bff6a1d2 --- /dev/null +++ b/tests/amsterdam/eip8282_builder_execution_requests/test_builder_exit_disable.py @@ -0,0 +1,113 @@ +""" +Disable-switch tests for +[EIP-8282: Builder Execution Requests](https://eips.ethereum.org/EIPS/eip-8282). + +The builder exit predeploy carries the same reversible kill switch as the +deposit predeploy: while `EXCESS_INHIBITOR` sits in the excess slot, exits +revert, and the next end-of-block system call clears the slot and re-enables +the queue. The disabled state is seeded directly here rather than triggered, +as the protocol's system call never carries the input that sets it. +""" + +import pytest +from execution_testing import ( + Account, + Alloc, + Block, + BlockchainTestFiller, + Fork, + Header, + Requests, + SystemContractInteractionTransaction, +) + +from .helpers import BuilderExitRequest +from .spec import Spec, ref_spec_8282 + +REFERENCE_SPEC_GIT_PATH = ref_spec_8282.git_path +REFERENCE_SPEC_VERSION = ref_spec_8282.version + +pytestmark = [ + pytest.mark.valid_from("Amsterdam"), + pytest.mark.pre_alloc_mutable(), +] + + +@pytest.fixture +def inhibited_pre(pre: Alloc, fork: Fork) -> Alloc: + """Seed the builder exit predeploy with the disable inhibitor set.""" + predeploy = fork.pre_allocation_blockchain()[ + Spec.BUILDER_EXIT_CONTRACT_ADDRESS + ] + pre[Spec.BUILDER_EXIT_CONTRACT_ADDRESS] = Account( + nonce=predeploy["nonce"], + code=predeploy["code"], + storage={Spec.EXCESS_STORAGE_SLOT: Spec.EXCESS_INHIBITOR}, + ) + return pre + + +def exit_request() -> BuilderExitRequest: + """Build an exit request paying the zero-excess fee.""" + return BuilderExitRequest( + pubkey=0x01, + fee=BuilderExitRequest.get_fee(0), + ) + + +def test_builder_exit_inhibited( + blockchain_test: BlockchainTestFiller, + inhibited_pre: Alloc, +) -> None: + """ + An exit to an inhibited predeploy reverts and produces no request, the + end-of-block system call clears the inhibitor back to zero, and an + identical exit in the next block is queued and dequeued normally. + """ + rejected = SystemContractInteractionTransaction( + requests=[exit_request()] + ).update_pre(inhibited_pre) + accepted = SystemContractInteractionTransaction( + requests=[exit_request()] + ).update_pre(inhibited_pre) + + source_address = accepted.request_source_address + assert source_address is not None + + # The dequeue advances past the record but does not zero its slots. The + # exit record is stored as caller ++ pubkey[0:32] ++ pubkey[32:48]. + calldata = exit_request().calldata + residual_record_slots = { + Spec.QUEUE_STORAGE_OFFSET: source_address, + Spec.QUEUE_STORAGE_OFFSET + 1: calldata[0:32], + Spec.QUEUE_STORAGE_OFFSET + 2: calldata[32:48].ljust(32, b"\x00"), + } + + blockchain_test( + pre=inhibited_pre, + blocks=[ + Block( + txs=rejected.transactions(), + header_verify=Header(requests_hash=Requests()), + ), + Block( + txs=accepted.transactions(), + header_verify=Header( + requests_hash=Requests( + *( + request.with_source_address(source_address) + for request in accepted.requests + ) + ) + ), + ), + ], + post={ + Spec.BUILDER_EXIT_CONTRACT_ADDRESS: Account( + storage={ + Spec.EXCESS_STORAGE_SLOT: 0, + **residual_record_slots, + }, + ), + }, + ) diff --git a/tests/amsterdam/eip8282_builder_execution_requests/test_contract_deployment.py b/tests/amsterdam/eip8282_builder_execution_requests/test_contract_deployment.py index a840d6dc6a8..7e033735e2e 100644 --- a/tests/amsterdam/eip8282_builder_execution_requests/test_contract_deployment.py +++ b/tests/amsterdam/eip8282_builder_execution_requests/test_contract_deployment.py @@ -11,6 +11,7 @@ Address, Alloc, Block, + Header, Requests, Transaction, TransitionFork, @@ -24,19 +25,14 @@ REFERENCE_SPEC_GIT_PATH = ref_spec_8282.git_path REFERENCE_SPEC_VERSION = ref_spec_8282.version -pytestmark = pytest.mark.skip( - reason="EIP-8282 draft: builder predeploy deploy transactions are not yet " - "defined (placeholder devnet-6 genesis addresses)." -) - MIN_DEPOSIT_GWEI = Spec.BUILDER_MIN_DEPOSIT // 10**9 @pytest.mark.eels_base_coverage @generate_system_contract_deploy_test( fork=Amsterdam, - tx_json_path=Path(realpath(__file__)).parent - / "builder_deposit_deploy_tx.json", + factory_json_path=Path(realpath(__file__)).parent + / "builder_deposit_factory_deploy.json", expected_deploy_address=Address(Spec.BUILDER_DEPOSIT_CONTRACT_ADDRESS), fail_on_empty_code=True, ) @@ -53,6 +49,7 @@ def test_builder_deposit_contract_deployment( withdrawal_credentials=0x02, amount=MIN_DEPOSIT_GWEI, signature=0x03, + fee=BuilderDepositRequest.get_fee(0), ) test_transaction = Transaction( @@ -64,15 +61,15 @@ def test_builder_deposit_contract_deployment( yield Block( txs=[test_transaction], - requests_hash=Requests(deposit_request), + header_verify=Header(requests_hash=Requests(deposit_request)), ) @pytest.mark.eels_base_coverage @generate_system_contract_deploy_test( fork=Amsterdam, - tx_json_path=Path(realpath(__file__)).parent - / "builder_exit_deploy_tx.json", + factory_json_path=Path(realpath(__file__)).parent + / "builder_exit_factory_deploy.json", expected_deploy_address=Address(Spec.BUILDER_EXIT_CONTRACT_ADDRESS), fail_on_empty_code=True, ) @@ -87,6 +84,7 @@ def test_builder_exit_contract_deployment( exit_request = BuilderExitRequest( pubkey=0x01, source_address=sender, + fee=BuilderExitRequest.get_fee(0), ) test_transaction = Transaction( @@ -98,5 +96,5 @@ def test_builder_exit_contract_deployment( yield Block( txs=[test_transaction], - requests_hash=Requests(exit_request), + header_verify=Header(requests_hash=Requests(exit_request)), ) diff --git a/tests/common/system_contract_request_fixtures.py b/tests/common/system_contract_request_fixtures.py index d60537bf577..d00f7f9b748 100644 --- a/tests/common/system_contract_request_fixtures.py +++ b/tests/common/system_contract_request_fixtures.py @@ -12,6 +12,7 @@ """ from collections import defaultdict +from dataclasses import replace from itertools import zip_longest from typing import Dict, List, Type @@ -19,6 +20,7 @@ from execution_testing import ( Alloc, Block, + BlockException, FeeSystemContractRequest, Fork, Header, @@ -32,27 +34,39 @@ @pytest.fixture -def prepared_system_contract_interactions_per_block( - pre: Alloc, +def system_contract_interactions_per_block_copy( system_contract_interactions_per_block: List[ List[SystemContractInteractionBase] ], ) -> List[List[SystemContractInteractionBase]]: """ - Allocate accounts/contracts for each interaction in `pre` and return copies - with the allocated state populated. The parametrize value - `system_contract_interactions_per_block` is not mutated, so it stays - pristine across fixture format runs. + Return a copy of `system_contract_interactions_per_block` whose requests + can be safely mutated by fixtures in the test. + + Each interaction's requests are copied because `included_requests` writes + the per-block fee onto individual requests in place; sharing them would + leak those mutations back into the parametrize value. A full `deepcopy` is + avoided because relay-contract interactions embed opcode objects that are + not copiable. """ return [ - [r.update_pre(pre) for r in block_interactions] + [ + replace( + interaction, + requests=[ + request.model_copy() for request in interaction.requests + ], + ) + for interaction in block_interactions + ] for block_interactions in system_contract_interactions_per_block ] @pytest.fixture def included_requests( - prepared_system_contract_interactions_per_block: List[ + pre: Alloc, + system_contract_interactions_per_block_copy: List[ List[SystemContractInteractionBase] ], ) -> List[List[SystemContractRequest]]: @@ -67,23 +81,51 @@ def included_requests( seen_types: List[RequestType] = [] per_block_included: List[List[SystemContractRequest]] = [] - for block_interactions in prepared_system_contract_interactions_per_block: + for block_interactions in system_contract_interactions_per_block_copy: # Group this block's valid requests by type. Fee requests are kept only # if they meet their type's current (per-block) fee; fee-less requests # (e.g. deposits) are always included. current: Dict[RequestType, List[SystemContractRequest]] = defaultdict( list ) - for interaction in block_interactions: - for request in interaction.valid_requests(): + for i in range(len(block_interactions)): + # Update the fee in all interaction's requests + for request in block_interactions[i].requests: request_type = type(request) if request_type not in seen_types: seen_types.append(request_type) if isinstance(request, FeeSystemContractRequest): - minimum_fee = type(request).get_fee(excess[request_type]) - if request.value < minimum_fee: - continue - current[request_type].append(request) + current_excess = excess[request_type] + if request.excess_fee_processing == "call": + # The contract adds the requests already queued this + # block beyond the target onto the stored excess. + current_excess += max( + len(current[request_type]) + - request.target_per_block, + 0, + ) + minimum_fee = request.get_fee(current_excess) + # Write the correct fee if unset regardless of validity + if "fee" not in request.model_fields_set: + request.fee = minimum_fee + if request.fee < minimum_fee and request.valid: + raise Exception( + "Invalid request marked as valid: " + f"{request.model_dump_json()}" + ) + if request.valid: + current[request_type].append(request) + + # With the correct fee, now update the pre. + block_interactions[i] = block_interactions[i].update_pre(pre=pre) + + # Finally, set the source address in the valid requests + source_address = block_interactions[i].request_source_address + assert source_address is not None + for request in block_interactions[i].requests: + if not request.valid: + continue + request.set_source_address(source_address) block_included: List[SystemContractRequest] = [] for request_type in seen_types: @@ -126,7 +168,7 @@ def timestamp() -> int: @pytest.fixture def blocks( fork: Fork | TransitionFork, - prepared_system_contract_interactions_per_block: List[ + system_contract_interactions_per_block_copy: List[ List[SystemContractInteractionBase] ], included_requests: List[List[SystemContractRequest]], @@ -136,7 +178,7 @@ def blocks( blocks: List[Block] = [] for block_interactions, block_included_requests in zip_longest( # type: ignore - prepared_system_contract_interactions_per_block, + system_contract_interactions_per_block_copy, included_requests, fillvalue=[], ): @@ -170,3 +212,35 @@ def blocks( timestamp=timestamp, ) ] + + +@pytest.fixture +def override_blocks( + blocks: List[Block], + block_body_override_requests: List[SystemContractRequest] | None, + exception: BlockException | None, +) -> List[Block]: + """ + Return a single block for negative tests where the requests in the block + body do not match the requests that actually happened in the block's + transactions. + + The transactions and expected requests hash are taken from the shared + `blocks` fixture; only the block body's requests list is overridden with + `block_body_override_requests` and the block is expected to fail with + `exception`. + """ + assert len(blocks) == 2 + requests_block = blocks[0] + return [ + Block( + txs=requests_block.txs, + header_verify=requests_block.header_verify, + requests=( + Requests(*block_body_override_requests).requests_list + if block_body_override_requests is not None + else None + ), + exception=exception, + ) + ] diff --git a/tests/prague/eip6110_deposits/conftest.py b/tests/prague/eip6110_deposits/conftest.py index 6162db86bb6..4a99e1a6710 100644 --- a/tests/prague/eip6110_deposits/conftest.py +++ b/tests/prague/eip6110_deposits/conftest.py @@ -72,13 +72,23 @@ def included_requests( ) -> List[SystemContractRequest]: """ Return the list of deposit requests that should be included in each block. + + A deposit is included only if it is marked valid and sends at least the + minimum deposit value (1 ETH); deposits below the minimum revert in the + deposit contract and never emit a log. """ - valid_requests: List[SystemContractRequest] = [] + min_deposit_value = 10**18 # 1 ETH, the deposit contract's minimum + included: List[SystemContractRequest] = [] for d in prepared_requests: - valid_requests += d.valid_requests(10**18) - - return valid_requests + source = d.request_source_address + assert source is not None, "Source address not initialized" + included += [ + r.with_source_address(source) + for r in d.requests + if r.valid and r.value >= min_deposit_value + ] + return included @pytest.fixture diff --git a/tests/prague/eip6110_deposits/helpers.py b/tests/prague/eip6110_deposits/helpers.py index 570744c1a2a..476e8b21ba2 100644 --- a/tests/prague/eip6110_deposits/helpers.py +++ b/tests/prague/eip6110_deposits/helpers.py @@ -199,9 +199,8 @@ def with_source_address(self, source_address: Address) -> "DepositRequest": return self.copy() @classmethod - def from_index(cls, index: int, fee: int | None = None) -> Self: - """Build a request from a sequential index, paying `fee`.""" - assert fee is None, f"Deposit requests do not require any fee: {fee}" + def from_index(cls, index: int) -> Self: + """Build a request from a sequential index.""" return cls( pubkey=(index * 3), withdrawal_credentials=(index * 3) + 1, diff --git a/tests/prague/eip7002_el_triggerable_withdrawals/conftest.py b/tests/prague/eip7002_el_triggerable_withdrawals/conftest.py index aad25c1e5ea..5e609913ae1 100644 --- a/tests/prague/eip7002_el_triggerable_withdrawals/conftest.py +++ b/tests/prague/eip7002_el_triggerable_withdrawals/conftest.py @@ -3,6 +3,7 @@ from ...common.system_contract_request_fixtures import ( blocks, # noqa: F401 included_requests, # noqa: F401 - prepared_system_contract_interactions_per_block, # noqa: F401 + override_blocks, # noqa: F401 + system_contract_interactions_per_block_copy, # noqa: F401 timestamp, # noqa: F401 ) diff --git a/tests/prague/eip7002_el_triggerable_withdrawals/helpers.py b/tests/prague/eip7002_el_triggerable_withdrawals/helpers.py index 31268da9522..2ea8c24818c 100644 --- a/tests/prague/eip7002_el_triggerable_withdrawals/helpers.py +++ b/tests/prague/eip7002_el_triggerable_withdrawals/helpers.py @@ -43,8 +43,6 @@ def with_source_address( return self.copy(source_address=source_address) @classmethod - def from_index(cls, index: int, fee: int | None = None) -> Self: + def from_index(cls, index: int) -> Self: """Build a withdrawal request from a sequential index.""" - if fee is None: - fee = cls.get_fee(0) - return cls(validator_pubkey=index, amount=0, fee=fee) + return cls(validator_pubkey=index, amount=0) diff --git a/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests.py b/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests.py index eb4a09690ab..1d54bfe532b 100644 --- a/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests.py +++ b/tests/prague/eip7002_el_triggerable_withdrawals/test_withdrawal_requests.py @@ -11,13 +11,8 @@ Block, BlockchainTestFiller, BlockException, - Environment, - Fork, - Header, Macros, Op, - Requests, - SystemContractInteractionBase, SystemContractInteractionContract, SystemContractInteractionTransaction, TestAddress, @@ -171,6 +166,7 @@ validator_pubkey=0x01, amount=0, fee=0, + valid=False, ), WithdrawalRequest( validator_pubkey=0x02, @@ -195,6 +191,7 @@ validator_pubkey=0x02, amount=Spec.MAX_AMOUNT - 1, fee=0, + valid=False, ), ] ), @@ -296,6 +293,7 @@ validator_pubkey=1, amount=Spec.MAX_AMOUNT, fee=0, + valid=False, ) ] + [ @@ -343,6 +341,7 @@ else 0 ), fee=0, + valid=False, ) ], ), @@ -524,19 +523,14 @@ def test_withdrawal_requests( pre: Alloc, ) -> None: """Test making a withdrawal request to the beacon chain.""" - blockchain_test( - genesis_environment=Environment(), - pre=pre, - post={}, - blocks=blocks, - ) + blockchain_test(pre=pre, post={}, blocks=blocks) @pytest.mark.parametrize( - "requests,block_body_override_requests,exception", + "system_contract_interactions_per_block,block_body_override_requests,exception", [ pytest.param( - [], + [[]], [ WithdrawalRequest( validator_pubkey=0x01, @@ -549,14 +543,16 @@ def test_withdrawal_requests( ), pytest.param( [ - SystemContractInteractionTransaction( - requests=[ - WithdrawalRequest( - validator_pubkey=0x01, - amount=0, - ), - ] - ), + [ + SystemContractInteractionTransaction( + requests=[ + WithdrawalRequest( + validator_pubkey=0x01, + amount=0, + ), + ] + ), + ] ], [], BlockException.INVALID_REQUESTS, @@ -564,14 +560,16 @@ def test_withdrawal_requests( ), pytest.param( [ - SystemContractInteractionTransaction( - requests=[ - WithdrawalRequest( - validator_pubkey=0x01, - amount=0, - ), - ] - ), + [ + SystemContractInteractionTransaction( + requests=[ + WithdrawalRequest( + validator_pubkey=0x01, + amount=0, + ), + ] + ), + ] ], [ WithdrawalRequest( @@ -585,14 +583,16 @@ def test_withdrawal_requests( ), pytest.param( [ - SystemContractInteractionTransaction( - requests=[ - WithdrawalRequest( - validator_pubkey=0x01, - amount=0, - ) - ], - ), + [ + SystemContractInteractionTransaction( + requests=[ + WithdrawalRequest( + validator_pubkey=0x01, + amount=0, + ) + ], + ), + ] ], [ WithdrawalRequest( @@ -606,14 +606,16 @@ def test_withdrawal_requests( ), pytest.param( [ - SystemContractInteractionTransaction( - requests=[ - WithdrawalRequest( - validator_pubkey=0x01, - amount=0, - ) - ], - ), + [ + SystemContractInteractionTransaction( + requests=[ + WithdrawalRequest( + validator_pubkey=0x01, + amount=0, + ) + ], + ), + ] ], [ WithdrawalRequest( @@ -627,18 +629,20 @@ def test_withdrawal_requests( ), pytest.param( [ - SystemContractInteractionTransaction( - requests=[ - WithdrawalRequest( - validator_pubkey=0x01, - amount=0, - ), - WithdrawalRequest( - validator_pubkey=0x02, - amount=0, - ), - ], - ), + [ + SystemContractInteractionTransaction( + requests=[ + WithdrawalRequest( + validator_pubkey=0x01, + amount=0, + ), + WithdrawalRequest( + validator_pubkey=0x02, + amount=0, + ), + ], + ), + ] ], [ WithdrawalRequest( @@ -657,14 +661,16 @@ def test_withdrawal_requests( ), pytest.param( [ - SystemContractInteractionTransaction( - requests=[ - WithdrawalRequest( - validator_pubkey=0x01, - amount=0, - ) - ], - ), + [ + SystemContractInteractionTransaction( + requests=[ + WithdrawalRequest( + validator_pubkey=0x01, + amount=0, + ) + ], + ), + ] ], [ WithdrawalRequest( @@ -685,48 +691,12 @@ def test_withdrawal_requests( ) @pytest.mark.exception_test def test_withdrawal_requests_negative( - pre: Alloc, - fork: Fork, blockchain_test: BlockchainTestFiller, - requests: List[SystemContractInteractionBase], - block_body_override_requests: List[WithdrawalRequest], - exception: BlockException, + override_blocks: List[Block], + pre: Alloc, ) -> None: """ Test blocks where the requests list and the actual withdrawal requests that happened in the block's transactions do not match. """ - prepared = [d.update_pre(pre) for d in requests] - - # No previous block so fee is the base - fee = 1 - current_block_requests = [] - for w in prepared: - current_block_requests += w.valid_requests(fee) - included_requests = current_block_requests[ - : Spec.MAX_WITHDRAWAL_REQUESTS_PER_BLOCK - ] - - blockchain_test( - genesis_environment=Environment(), - pre=pre, - post={}, - blocks=[ - Block( - txs=sum((r.transactions() for r in prepared), []), - header_verify=Header( - requests_hash=Requests( - *included_requests, - ), - ), - requests=( - Requests( - *block_body_override_requests, - ).requests_list - if block_body_override_requests is not None - else None - ), - exception=exception, - ) - ], - ) + blockchain_test(pre=pre, post={}, blocks=override_blocks) diff --git a/tests/prague/eip7251_consolidations/conftest.py b/tests/prague/eip7251_consolidations/conftest.py index 0c390b3a418..ac3a1f70bf7 100644 --- a/tests/prague/eip7251_consolidations/conftest.py +++ b/tests/prague/eip7251_consolidations/conftest.py @@ -3,6 +3,7 @@ from ...common.system_contract_request_fixtures import ( blocks, # noqa: F401 included_requests, # noqa: F401 - prepared_system_contract_interactions_per_block, # noqa: F401 + override_blocks, # noqa: F401 + system_contract_interactions_per_block_copy, # noqa: F401 timestamp, # noqa: F401 ) diff --git a/tests/prague/eip7251_consolidations/helpers.py b/tests/prague/eip7251_consolidations/helpers.py index cdfcfe828cd..1a4e4ac0460 100644 --- a/tests/prague/eip7251_consolidations/helpers.py +++ b/tests/prague/eip7251_consolidations/helpers.py @@ -43,12 +43,6 @@ def with_source_address( return self.copy(source_address=source_address) @classmethod - def from_index(cls, index: int, fee: int | None = None) -> Self: + def from_index(cls, index: int) -> Self: """Build a consolidation request from a sequential index.""" - if fee is None: - fee = cls.get_fee(0) - return cls( - source_pubkey=index * 2, - target_pubkey=index * 2 + 1, - fee=fee, - ) + return cls(source_pubkey=index * 2, target_pubkey=index * 2 + 1) diff --git a/tests/prague/eip7251_consolidations/test_consolidations.py b/tests/prague/eip7251_consolidations/test_consolidations.py index 6964b9b246d..4643685b058 100644 --- a/tests/prague/eip7251_consolidations/test_consolidations.py +++ b/tests/prague/eip7251_consolidations/test_consolidations.py @@ -11,13 +11,8 @@ Block, BlockchainTestFiller, BlockException, - Environment, - Fork, - Header, Macros, Op, - Requests, - SystemContractInteractionBase, SystemContractInteractionContract, SystemContractInteractionTransaction, TestAddress, @@ -201,6 +196,7 @@ source_pubkey=0x01, target_pubkey=0x02, fee=0, + valid=False, ), ConsolidationRequest( source_pubkey=0x03, @@ -225,6 +221,7 @@ source_pubkey=0x03, target_pubkey=0x04, fee=0, + valid=False, ), ] ), @@ -330,6 +327,7 @@ source_pubkey=0x00, target_pubkey=0x01, fee=0, + valid=False, ) ] + [ @@ -365,6 +363,7 @@ source_pubkey=-1, target_pubkey=-2, fee=0, + valid=False, ) ], ), @@ -542,19 +541,15 @@ def test_consolidation_requests( pre: Alloc, ) -> None: """Test making a consolidation request to the beacon chain.""" - blockchain_test( - genesis_environment=Environment(), - pre=pre, - post={}, - blocks=blocks, - ) + blockchain_test(pre=pre, post={}, blocks=blocks) @pytest.mark.parametrize( - "requests,block_body_override_requests,exception", + "system_contract_interactions_per_block,block_body_override_requests," + "exception", [ pytest.param( - [], + [[]], [ ConsolidationRequest( source_pubkey=0x01, @@ -567,14 +562,16 @@ def test_consolidation_requests( ), pytest.param( [ - SystemContractInteractionTransaction( - requests=[ - ConsolidationRequest( - source_pubkey=0x01, - target_pubkey=0x02, - ), - ] - ), + [ + SystemContractInteractionTransaction( + requests=[ + ConsolidationRequest( + source_pubkey=0x01, + target_pubkey=0x02, + ), + ] + ), + ] ], [], BlockException.INVALID_REQUESTS, @@ -582,14 +579,16 @@ def test_consolidation_requests( ), pytest.param( [ - SystemContractInteractionTransaction( - requests=[ - ConsolidationRequest( - source_pubkey=0x01, - target_pubkey=0x02, - ), - ] - ), + [ + SystemContractInteractionTransaction( + requests=[ + ConsolidationRequest( + source_pubkey=0x01, + target_pubkey=0x02, + ), + ] + ), + ] ], [ ConsolidationRequest( @@ -603,14 +602,16 @@ def test_consolidation_requests( ), pytest.param( [ - SystemContractInteractionTransaction( - requests=[ - ConsolidationRequest( - source_pubkey=0x01, - target_pubkey=0x02, - ), - ] - ), + [ + SystemContractInteractionTransaction( + requests=[ + ConsolidationRequest( + source_pubkey=0x01, + target_pubkey=0x02, + ), + ] + ), + ] ], [ ConsolidationRequest( @@ -624,14 +625,16 @@ def test_consolidation_requests( ), pytest.param( [ - SystemContractInteractionTransaction( - requests=[ - ConsolidationRequest( - source_pubkey=0x01, - target_pubkey=0x02, - ), - ] - ), + [ + SystemContractInteractionTransaction( + requests=[ + ConsolidationRequest( + source_pubkey=0x01, + target_pubkey=0x02, + ), + ] + ), + ] ], [ ConsolidationRequest( @@ -645,14 +648,16 @@ def test_consolidation_requests( ), pytest.param( [ - SystemContractInteractionTransaction( - requests=[ - ConsolidationRequest( - source_pubkey=0x01, - target_pubkey=0x02, - ) - ], - ), + [ + SystemContractInteractionTransaction( + requests=[ + ConsolidationRequest( + source_pubkey=0x01, + target_pubkey=0x02, + ) + ], + ), + ] ], [ ConsolidationRequest( @@ -666,18 +671,20 @@ def test_consolidation_requests( ), pytest.param( [ - SystemContractInteractionTransaction( - requests=[ - ConsolidationRequest( - source_pubkey=0x01, - target_pubkey=0x02, - ), - ConsolidationRequest( - source_pubkey=0x03, - target_pubkey=0x04, - ), - ], - ), + [ + SystemContractInteractionTransaction( + requests=[ + ConsolidationRequest( + source_pubkey=0x01, + target_pubkey=0x02, + ), + ConsolidationRequest( + source_pubkey=0x03, + target_pubkey=0x04, + ), + ], + ), + ] ], [ ConsolidationRequest( @@ -696,14 +703,16 @@ def test_consolidation_requests( ), pytest.param( [ - SystemContractInteractionTransaction( - requests=[ - ConsolidationRequest( - source_pubkey=0x01, - target_pubkey=0x02, - ) - ], - ), + [ + SystemContractInteractionTransaction( + requests=[ + ConsolidationRequest( + source_pubkey=0x01, + target_pubkey=0x02, + ) + ], + ), + ] ], [ ConsolidationRequest( @@ -724,44 +733,12 @@ def test_consolidation_requests( ) @pytest.mark.exception_test def test_consolidation_requests_negative( - pre: Alloc, - fork: Fork, blockchain_test: BlockchainTestFiller, - requests: List[SystemContractInteractionBase], - block_body_override_requests: List[ConsolidationRequest], - exception: BlockException, + override_blocks: List[Block], + pre: Alloc, ) -> None: """ Test blocks where the requests list and the actual consolidation requests that happened in the block's transactions do not match. """ - prepared = [d.update_pre(pre) for d in requests] - - # No previous block so fee is the base - fee = 1 - current_block_requests = [] - for w in prepared: - current_block_requests += w.valid_requests(fee) - included_requests = current_block_requests[ - : Spec.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK - ] - - blockchain_test( - genesis_environment=Environment(), - pre=pre, - post={}, - blocks=[ - Block( - txs=sum((r.transactions() for r in prepared), []), - header_verify=Header( - requests_hash=Requests(*included_requests), - ), - requests=( - Requests(*block_body_override_requests).requests_list - if block_body_override_requests is not None - else None - ), - exception=exception, - ) - ], - ) + blockchain_test(pre=pre, post={}, blocks=override_blocks) diff --git a/tests/prague/eip7685_general_purpose_el_requests/conftest.py b/tests/prague/eip7685_general_purpose_el_requests/conftest.py index 61152e8104b..623de3ebc25 100644 --- a/tests/prague/eip7685_general_purpose_el_requests/conftest.py +++ b/tests/prague/eip7685_general_purpose_el_requests/conftest.py @@ -4,21 +4,18 @@ import pytest from execution_testing import ( - Alloc, Block, BlockException, Bytes, EngineAPIError, Header, - Requests, SystemContractInteractionBase, - SystemContractRequest, ) from ...common.system_contract_request_fixtures import ( blocks, # noqa: F401 included_requests, # noqa: F401 - prepared_system_contract_interactions_per_block, # noqa: F401 + system_contract_interactions_per_block_copy, # noqa: F401 timestamp, # noqa: F401 ) from ..eip6110_deposits.helpers import DepositRequest @@ -92,8 +89,7 @@ def is_monotonically_increasing(requests: List[bytes]) -> bool: @pytest.fixture def override_blocks( - pre: Alloc, - requests: List[SystemContractInteractionBase], + blocks: List[Block], # noqa: F811 block_body_override_requests: List[Bytes | SupportsBytes] | None, correct_requests_hash_in_header: bool, exception: BlockException | None, @@ -102,25 +98,20 @@ def override_blocks( """ Single block whose request body / header can be overridden, used by the negative tests to inject invalid requests and expect a block exception. - """ - valid_requests_list: List[SystemContractRequest] = [] - # Every request here is constructed with a sufficient value, so no fee - # filter is needed: each interaction returns all of its `valid` requests. - prepared = [r.update_pre(pre) for r in requests] - for r in prepared: - valid_requests_list += r.valid_requests() - - valid_requests = Requests(*valid_requests_list) + The block's transactions and expected requests hash are taken from the + shared `blocks` fixture; only the block body requests / header are + overridden here. + """ + assert len(blocks) == 2 + requests_block = blocks[0] rlp_modifier: Header | None = None if correct_requests_hash_in_header: - rlp_modifier = Header( - requests_hash=valid_requests, - ) + rlp_modifier = requests_block.header_verify return [ Block( - txs=sum((r.transactions() for r in prepared), []), - header_verify=Header(requests_hash=valid_requests), + txs=requests_block.txs, + header_verify=requests_block.header_verify, requests=block_body_override_requests, exception=exception, rlp_modifier=rlp_modifier, diff --git a/tests/prague/eip7702_set_code_tx/test_set_code_txs.py b/tests/prague/eip7702_set_code_tx/test_set_code_txs.py index e7167078a04..bd2bc30a90a 100644 --- a/tests/prague/eip7702_set_code_tx/test_set_code_txs.py +++ b/tests/prague/eip7702_set_code_tx/test_set_code_txs.py @@ -32,6 +32,7 @@ Conditional, EIPChecklist, Environment, + FeeSystemContractRequest, Fork, Hash, Initcode, @@ -3211,6 +3212,10 @@ def test_set_code_to_system_contract( if Address(system_contract) in REQUEST_TYPE_BY_ADDRESS: rt = REQUEST_TYPE_BY_ADDRESS[Address(system_contract)] request = rt.from_index(0) + if isinstance(request, FeeSystemContractRequest): + # `from_index` leaves the fee unset; pay the zero-excess fee the + # delegated contract charges. + request.fee = request.get_fee(0) caller_payload = request.calldata call_value = request.value else: From 695e5ef1540d63c8d8c24aa1687911811c66a90b Mon Sep 17 00:00:00 2001 From: spencer <spencer.taylor-brown@ethereum.org> Date: Wed, 8 Jul 2026 19:36:30 +0100 Subject: [PATCH 107/233] fix(spec-specs, tests): anchor calldata floor on decomposed EIP-2780 base (#3120) --- .../src/execution_testing/forks/base_fork.py | 12 ++++- .../forks/forks/eips/amsterdam/eip_2780.py | 50 ++++++++++++++++++- .../forks/forks/eips/amsterdam/eip_7981.py | 10 +++- .../forks/forks/eips/prague/eip_7623.py | 5 +- .../execution_testing/forks/forks/forks.py | 4 ++ src/ethereum/forks/amsterdam/transactions.py | 25 ++++++---- .../test_calldata_floor.py | 2 + .../test_value_moving_transactions.py | 11 ++++ .../conftest.py | 14 +++++- .../test_additional_coverage.py | 7 +++ .../test_access_list_cost.py | 3 ++ .../test_state_gas_calldata_floor.py | 6 ++- .../test_state_gas_create.py | 7 +++ .../stRandom2/test_random_statetest644.py | 4 +- 14 files changed, 142 insertions(+), 18 deletions(-) diff --git a/packages/testing/src/execution_testing/forks/base_fork.py b/packages/testing/src/execution_testing/forks/base_fork.py index 7d2bafc517d..08ab44229b8 100644 --- a/packages/testing/src/execution_testing/forks/base_fork.py +++ b/packages/testing/src/execution_testing/forks/base_fork.py @@ -67,8 +67,18 @@ def __call__( *, data: BytesConvertible, access_list: List[AccessList] | None = None, + contract_creation: bool = False, + sends_value: bool = False, + recipient_type: RecipientType = RecipientType.CONTRACT, ) -> int: - """Return transaction gas cost of calldata given its contents.""" + """ + Return transaction gas cost of calldata given its contents. + + The defaults model a zero-value call to another account. Forks + that anchor the floor on the transaction's intrinsic base + (EIP-2780) add gas for these arguments, so create, value-bearing, + and self-transfer transactions must pass them explicitly. + """ pass diff --git a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_2780.py b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_2780.py index 8ef87da63b0..713d761d77c 100644 --- a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_2780.py +++ b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_2780.py @@ -18,6 +18,7 @@ from ....base_fork import ( BaseFork, TopFrameGasCalculator, + TransactionDataFloorCostCalculator, TransactionIntrinsicCostCalculator, ) from ....gas_costs import GasCosts @@ -41,6 +42,49 @@ def gas_costs(cls) -> GasCosts: TX_VALUE_COST=4_244, ) + @classmethod + def transaction_data_floor_cost_calculator( + cls, + ) -> TransactionDataFloorCostCalculator: + """ + Anchor the calldata floor on the decomposed regular-gas intrinsic + base (EIP-2780). + + The inherited floor base is ``TX_BASE`` alone; add the recipient + access and value-transfer primitives so the floor never undercuts + the transaction's own intrinsic base. Calldata and access-list + floor tokens still accrue via the inherited calculator; init code + and authorization costs do not enter the floor. + """ + super_fn = super(EIP2780, cls).transaction_data_floor_cost_calculator() + gas_costs = cls.gas_costs() + + def fn( + *, + data: BytesConvertible, + access_list: List[AccessList] | None = None, + contract_creation: bool = False, + sends_value: bool = False, + recipient_type: RecipientType = RecipientType.CONTRACT, + ) -> int: + floor = super_fn(data=data, access_list=access_list) + is_self_transfer = recipient_type == RecipientType.SELF + if contract_creation: + # CREATE_ACCESS regular gas; TX_CREATE folds in the + # NEW_ACCOUNT state gas, which the floor excludes. + floor += gas_costs.TX_CREATE - gas_costs.NEW_ACCOUNT + if sends_value: + floor += gas_costs.TRANSFER_LOG_COST + elif not is_self_transfer: + floor += gas_costs.COLD_ACCOUNT_ACCESS + if sends_value: + floor += ( + gas_costs.TRANSFER_LOG_COST + gas_costs.TX_VALUE_COST + ) + return floor + + return fn + @classmethod def transaction_intrinsic_cost_calculator( cls, @@ -96,7 +140,11 @@ def fn( ) transaction_floor_data_cost = ( transaction_data_floor_cost_calculator( - data=calldata, access_list=access_list + data=calldata, + access_list=access_list, + contract_creation=contract_creation, + sends_value=sends_value, + recipient_type=recipient_type, ) ) return max(intrinsic_cost, transaction_floor_data_cost) diff --git a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_7981.py b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_7981.py index 4aef4807938..a09e9e3d33d 100644 --- a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_7981.py +++ b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_7981.py @@ -56,9 +56,17 @@ def fn( *, data: BytesConvertible, access_list: List[AccessList] | None = None, + contract_creation: bool = False, + sends_value: bool = False, + recipient_type: RecipientType = RecipientType.CONTRACT, ) -> int: return ( - super_fn(data=data) + super_fn( + data=data, + contract_creation=contract_creation, + sends_value=sends_value, + recipient_type=recipient_type, + ) + cls._access_list_floor_tokens(access_list) * gas_costs.TX_DATA_TOKEN_FLOOR ) diff --git a/packages/testing/src/execution_testing/forks/forks/eips/prague/eip_7623.py b/packages/testing/src/execution_testing/forks/forks/eips/prague/eip_7623.py index 6b82b788379..501b8ff2bfb 100644 --- a/packages/testing/src/execution_testing/forks/forks/eips/prague/eip_7623.py +++ b/packages/testing/src/execution_testing/forks/forks/eips/prague/eip_7623.py @@ -67,8 +67,11 @@ def fn( *, data: BytesConvertible, access_list: List[AccessList] | None = None, + contract_creation: bool = False, + sends_value: bool = False, + recipient_type: RecipientType = RecipientType.CONTRACT, ) -> int: - del access_list + del access_list, contract_creation, sends_value, recipient_type return ( calldata_gas_calculator(data=data, floor=True) + gas_costs.TX_BASE diff --git a/packages/testing/src/execution_testing/forks/forks/forks.py b/packages/testing/src/execution_testing/forks/forks/forks.py index 168a4b19e2b..8d1bb82e5d0 100644 --- a/packages/testing/src/execution_testing/forks/forks/forks.py +++ b/packages/testing/src/execution_testing/forks/forks/forks.py @@ -849,8 +849,12 @@ def fn( *, data: BytesConvertible, access_list: List[AccessList] | None = None, + contract_creation: bool = False, + sends_value: bool = False, + recipient_type: RecipientType = RecipientType.CONTRACT, ) -> int: del data, access_list + del contract_creation, sends_value, recipient_type return 0 return fn diff --git a/src/ethereum/forks/amsterdam/transactions.py b/src/ethereum/forks/amsterdam/transactions.py index 9bb3262934a..d876433ab95 100644 --- a/src/ethereum/forks/amsterdam/transactions.py +++ b/src/ethereum/forks/amsterdam/transactions.py @@ -660,9 +660,12 @@ def calculate_intrinsic_cost( Self-transfers (``sender == tx.to``) skip the recipient and value charges. - This function takes a transaction and gas_limit as parameters and - returns the intrinsic regular gas cost, intrinsic state gas cost, and the - minimum gas cost used by the transaction based on the calldata size. + This function takes a transaction and its sender as parameters and + returns the intrinsic regular gas cost, the intrinsic state gas cost, + and the minimum (floor) gas cost based on the calldata size. The floor + is anchored on the regular-gas portion of items 1 to 3 above rather + than `TX_BASE` alone, so it never undercuts the transaction's own + intrinsic base. """ from .vm.gas import ( GasCosts, @@ -679,10 +682,10 @@ def calculate_intrinsic_cost( recipient_regular_gas = Uint(0) recipient_state_gas = Uint(0) + init_code_gas = Uint(0) if is_create: - recipient_regular_gas = GasCosts.CREATE_ACCESS + init_code_cost( - ulen(tx.data) - ) + recipient_regular_gas = GasCosts.CREATE_ACCESS + init_code_gas = init_code_cost(ulen(tx.data)) recipient_state_gas = StateGasCosts.NEW_ACCOUNT if tx.value > U256(0): recipient_regular_gas += GasCosts.TRANSFER_LOG_COST @@ -725,15 +728,19 @@ def calculate_intrinsic_cost( # Total floor tokens. total_floor_tokens = floor_tokens_in_calldata + tokens_in_access_list + # Decomposed regular-gas intrinsic base (EIP-2780), which also anchors + # the calldata floor. + base_regular_gas = GasCosts.TX_BASE + recipient_regular_gas + # Floor gas cost (EIP-7623: minimum gas for data-heavy transactions). data_floor_gas_cost = ( - total_floor_tokens * GasCosts.TX_DATA_TOKEN_FLOOR + GasCosts.TX_BASE + total_floor_tokens * GasCosts.TX_DATA_TOKEN_FLOOR + base_regular_gas ) intrinsic_regular_gas = ( - GasCosts.TX_BASE + base_regular_gas + + init_code_gas + data_cost - + recipient_regular_gas + access_list_cost + auth_regular_gas ) diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_calldata_floor.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_calldata_floor.py index 0907cf64599..e6d87eea505 100644 --- a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_calldata_floor.py +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_calldata_floor.py @@ -105,6 +105,8 @@ def test_calldata_floor( calldata = _floor_dominating_calldata(fork) calldata_floor = fork.transaction_data_floor_cost_calculator()( data=calldata, + sends_value=bool(value), + recipient_type=RecipientType.EOA, ) gas_price = 1_000_000_000 diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_value_moving_transactions.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_value_moving_transactions.py index 0a7533cbe79..af4d63113ca 100644 --- a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_value_moving_transactions.py +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_value_moving_transactions.py @@ -199,6 +199,17 @@ def test_value_contract_creation_tx( contract_creation=True, ) gas_used = intrinsic_gas + execution_gas - new_account_refund + # A tiny init code can leave the decomposed calldata floor above + # the regular gas actually consumed; gas_used then pins to the + # floor, which EIP-2780 anchors on the create intrinsic base. + gas_used = max( + gas_used, + fork.transaction_data_floor_cost_calculator()( + data=call_data, + contract_creation=True, + sends_value=bool(value), + ), + ) # Value transfer rolled back. sender_value_delta = 0 expected_target = None diff --git a/tests/amsterdam/eip7976_increase_calldata_floor_cost/conftest.py b/tests/amsterdam/eip7976_increase_calldata_floor_cost/conftest.py index 78de4e79df2..f92a0e003a0 100644 --- a/tests/amsterdam/eip7976_increase_calldata_floor_cost/conftest.py +++ b/tests/amsterdam/eip7976_increase_calldata_floor_cost/conftest.py @@ -191,7 +191,11 @@ def transaction_intrinsic_cost_calculator(byte_count: int) -> int: ) def transaction_data_floor_cost_calculator(byte_count: int) -> int: - return fork_data_floor_cost_calculator(data=bytes_to_data(byte_count)) + return fork_data_floor_cost_calculator( + data=bytes_to_data(byte_count), + contract_creation=contract_creating_tx, + access_list=access_list, + ) # Start with zero data and check the difference in the gas calculator # between the intrinsic gas cost and the floor gas cost. @@ -296,12 +300,18 @@ def tx_intrinsic_gas_cost_including_floor_data_cost( def tx_floor_data_cost( fork: Fork, tx_data: Bytes, + contract_creating_tx: bool, + access_list: List[AccessList] | None, ) -> int: """Floor data cost for the given transaction data.""" fork_data_floor_cost_calculator = ( fork.transaction_data_floor_cost_calculator() ) - return fork_data_floor_cost_calculator(data=tx_data) + return fork_data_floor_cost_calculator( + data=tx_data, + contract_creation=contract_creating_tx, + access_list=access_list, + ) @pytest.fixture diff --git a/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_additional_coverage.py b/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_additional_coverage.py index 11ef0937dd3..8dbc92267e6 100644 --- a/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_additional_coverage.py +++ b/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_additional_coverage.py @@ -134,6 +134,10 @@ def test_token_calculation_verification( expected_floor_cost = gas_costs.TX_BASE + ( expected_floor_tokens * floor_token_cost ) + # EIP-2780 anchors the floor on the decomposed intrinsic base, + # which includes the recipient-access charge for a non-self, + # non-create transaction (the ``to`` fixture is a contract). + expected_floor_cost += gas_costs.COLD_ACCOUNT_ACCESS assert floor_cost == expected_floor_cost, ( f"Floor cost mismatch for {description}: " f"{floor_cost} != {expected_floor_cost} " @@ -447,6 +451,9 @@ def test_nested_call_no_additional_floor_cost( expected_floor_cost = gas_costs.TX_BASE + ( tokens_tx * gas_costs.TX_DATA_TOKEN_FLOOR ) + # EIP-2780 anchors the floor on the decomposed intrinsic base; + # the tx targets a contract, adding the recipient-access charge. + expected_floor_cost += gas_costs.COLD_ACCOUNT_ACCESS assert floor_cost == expected_floor_cost tx = Transaction( diff --git a/tests/amsterdam/eip7981_increase_access_list_cost/test_access_list_cost.py b/tests/amsterdam/eip7981_increase_access_list_cost/test_access_list_cost.py index e9479b276c9..1adbf536371 100644 --- a/tests/amsterdam/eip7981_increase_access_list_cost/test_access_list_cost.py +++ b/tests/amsterdam/eip7981_increase_access_list_cost/test_access_list_cost.py @@ -121,6 +121,9 @@ def test_access_list_token_calculation( expected_floor_cost = ( expected_floor_tokens * gas_costs.TX_DATA_TOKEN_FLOOR + gas_costs.TX_BASE + # EIP-2780 anchors the floor on the decomposed intrinsic base; the + # tx targets a non-self account, adding the recipient-access charge. + + gas_costs.COLD_ACCOUNT_ACCESS ) actual_floor_cost = fork.transaction_data_floor_cost_calculator()( data=b"", access_list=access_list diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_calldata_floor.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_calldata_floor.py index b60fc41ce5c..640a96c13f0 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_calldata_floor.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_calldata_floor.py @@ -161,8 +161,10 @@ def test_calldata_floor_exceeding_tx_gas_limit_cap( floor_cost = fork.transaction_data_floor_cost_calculator() floor_token = gas_costs.TX_DATA_TOKEN_FLOOR - tx_base = gas_costs.TX_BASE - max_tokens = (cap - tx_base) // floor_token + # EIP-2780 anchors the floor on the decomposed intrinsic base; the tx + # targets a contract, so the base includes the recipient-access charge. + floor_base = gas_costs.TX_BASE + gas_costs.COLD_ACCOUNT_ACCESS + max_tokens = (cap - floor_base) // floor_token if fork.is_eip_enabled(7976): # EIP-7976: all bytes contribute 4 floor tokens regardless of diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py index b9b5bde06f5..c54ba39fab7 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py @@ -2156,6 +2156,13 @@ def test_failed_create_tx_refunds_intrinsic_new_account( expected_gas_used = intrinsic_regular + regular_consumed expected_cumulative = intrinsic_total + regular_consumed - create_state_gas + # A tiny init code can leave the decomposed calldata floor above the + # regular gas consumed, pinning gas_used to the floor. + floor = fork.transaction_data_floor_cost_calculator()( + data=bytes(init_code), contract_creation=True + ) + expected_gas_used = max(expected_gas_used, floor) + expected_cumulative = max(expected_cumulative, floor) tx = Transaction( to=None, diff --git a/tests/ported_static/stRandom2/test_random_statetest644.py b/tests/ported_static/stRandom2/test_random_statetest644.py index 79e94782a18..65377842a9e 100644 --- a/tests/ported_static/stRandom2/test_random_statetest644.py +++ b/tests/ported_static/stRandom2/test_random_statetest644.py @@ -146,7 +146,9 @@ def test_random_statetest644( tx_data = Bytes( "7300000000000000000000000000000000000000013b7ea30da9ff11bd5f11e4529c93ce4b37d5a256d61e1f1a0ecccb5fbb21fec97f6b3d456b8caaaa84ef30a44fd8779fae5a48354b937835d82d57999d194d4edfbaf0a8dd026d727e3315a53e907b0e1873b4dcb7f806014bc23164e8cc0560256f0c6a8c09c0df2f0f8208ff622bb459d46ffab16ce9d64bcf9cec668338ebbc7f9e64656ae99c617d0dd709c1f78f96bea46e2df76db8418e2b657fc77ff2f979952911a73b767a6ce270c7392d2ff340648610fe0219aaf24df2b26e97e2761497bc6b97dea1269de3aca3b69ec7098a7257114a4a2e22c401ec6319bc2deb70980ebef372a327809b3c2473ab86578d2fccd458e6b99a277c4a1d3e96351fbebe62fe63d300444afd3a9077c20905d2a92b5b2945de6bf9b28d1d42795ca74b029dce6934312994a31fed72e45da26c73c636b40b1f6d529f35488625624a9dfd0b62309f286277b5ab6259b2fd62144722631c4722737300000000000000000000000000000000000000056317345497f13368b2a96595a00933d8dd6dc111a13b90768f330898544a443407620316d3625614816282f1e9622e741d730346ad0b28ea31b7c3d398881dc11ebc97869461631d791a38fa" # noqa: E501 ) - floor_cost = fork.transaction_data_floor_cost_calculator()(data=tx_data) + floor_cost = fork.transaction_data_floor_cost_calculator()( + data=tx_data, sends_value=True + ) tx = Transaction( sender=sender, to=Address(0x0000000000000000000000000000000000000001), From 5ca8383c6ef587594ef3abdb97e1d4f6a6eb9768 Mon Sep 17 00:00:00 2001 From: spencer <spencer.taylor-brown@ethereum.org> Date: Thu, 9 Jul 2026 10:03:41 +0100 Subject: [PATCH 108/233] fix(test-tests): skip Constantinople for evmone in t8n support test (#3130) --- .../client_clis/tests/test_transition_tools_support.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/testing/src/execution_testing/client_clis/tests/test_transition_tools_support.py b/packages/testing/src/execution_testing/client_clis/tests/test_transition_tools_support.py index 1d4a9e6fb14..82a74e82574 100644 --- a/packages/testing/src/execution_testing/client_clis/tests/test_transition_tools_support.py +++ b/packages/testing/src/execution_testing/client_clis/tests/test_transition_tools_support.py @@ -13,6 +13,7 @@ TestPrivateKey, ) from execution_testing.client_clis import ( + EvmOneTransitionTool, ExecutionSpecsTransitionTool, TransitionTool, ) @@ -74,9 +75,9 @@ def test_t8n_support(fork: Fork, installed_t8n: TransitionTool) -> None: """Stress test that sends all possible t8n interactions.""" if fork in [MuirGlacier, ArrowGlacier, GrayGlacier]: return - if isinstance(installed_t8n, ExecutionSpecsTransitionTool) and fork in [ - Constantinople - ]: + if isinstance( + installed_t8n, (ExecutionSpecsTransitionTool, EvmOneTransitionTool) + ) and fork in [Constantinople]: return env = Environment() sender = TestAddress From fb62755b68fe4e637549fda9f75dce8b9f593605 Mon Sep 17 00:00:00 2001 From: danceratopz <danceratopz@gmail.com> Date: Thu, 9 Jul 2026 11:03:57 +0200 Subject: [PATCH 109/233] chore: enable enginex generation for devnet releases (#3129) --- .github/configs/feature.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/configs/feature.yaml b/.github/configs/feature.yaml index 39297ec6e84..fcb18a91525 100644 --- a/.github/configs/feature.yaml +++ b/.github/configs/feature.yaml @@ -22,4 +22,4 @@ benchmark_fast: # Shared entry for all `<feat>-devnet` releases; matched by `-devnet` suffix. devnet: evm-type: eels - fill-params: --until=Amsterdam + fill-params: --until=Amsterdam --generate-all-formats From 78daabca4739dce907a67689032273484d695b0a Mon Sep 17 00:00:00 2001 From: danceratopz <danceratopz@gmail.com> Date: Thu, 9 Jul 2026 13:27:27 +0200 Subject: [PATCH 110/233] chore(tests, test-client-clis): map invalid transaction signature exceptions (#3131) --- .../client_clis/clis/besu.py | 5 +++++ .../client_clis/clis/geth.py | 3 +++ .../client_clis/clis/nethermind.py | 3 +++ .../client_clis/clis/reth.py | 3 +++ tests/frontier/validation/test_transaction.py | 19 +++++++++++++++++-- 5 files changed, 31 insertions(+), 2 deletions(-) diff --git a/packages/testing/src/execution_testing/client_clis/clis/besu.py b/packages/testing/src/execution_testing/client_clis/clis/besu.py index 6325b830ef9..c4c17101d5d 100644 --- a/packages/testing/src/execution_testing/client_clis/clis/besu.py +++ b/packages/testing/src/execution_testing/client_clis/clis/besu.py @@ -452,6 +452,11 @@ class BesuExceptionMapper(ExceptionMapper): r"transaction invalid Transaction gas limit " r"must be at most \d+" ), + TransactionException.INVALID_SIGNATURE_VRS: ( + r"Failed to decode transactions from block parameter|" + r"transaction invalid Signature s value should be less " + r"than \d+, but got \d+" + ), TransactionException.TYPE_3_TX_MAX_BLOB_GAS_ALLOWANCE_EXCEEDED: ( r"Blob transaction 0x[0-9a-f]+ exceeds " r"block blob gas limit: \d+ > \d+" diff --git a/packages/testing/src/execution_testing/client_clis/clis/geth.py b/packages/testing/src/execution_testing/client_clis/clis/geth.py index 1a9c46c720d..e133c839455 100644 --- a/packages/testing/src/execution_testing/client_clis/clis/geth.py +++ b/packages/testing/src/execution_testing/client_clis/clis/geth.py @@ -55,6 +55,9 @@ class GethExceptionMapper(ExceptionMapper): "max priority fee per gas higher than max fee per gas" ), TransactionException.INVALID_CHAINID: "invalid chain id for signer", + TransactionException.INVALID_SIGNATURE_VRS: ( + "invalid transaction v, r, s values" + ), TransactionException.TYPE_1_TX_PRE_FORK: ( "transaction type not supported" ), diff --git a/packages/testing/src/execution_testing/client_clis/clis/nethermind.py b/packages/testing/src/execution_testing/client_clis/clis/nethermind.py index bb3687ef970..5581777a3cf 100644 --- a/packages/testing/src/execution_testing/client_clis/clis/nethermind.py +++ b/packages/testing/src/execution_testing/client_clis/clis/nethermind.py @@ -328,6 +328,9 @@ class NethermindExceptionMapper(ExceptionMapper): TransactionException.INSUFFICIENT_MAX_FEE_PER_BLOB_GAS: ( "InsufficientMaxFeePerBlobGas: Not enough to cover blob gas fee" ), + TransactionException.INVALID_SIGNATURE_VRS: ( + "InvalidTxSignature: Signature is invalid." + ), TransactionException.TYPE_1_TX_PRE_FORK: ( "InvalidTxType: Transaction type in Custom is not supported" ), diff --git a/packages/testing/src/execution_testing/client_clis/clis/reth.py b/packages/testing/src/execution_testing/client_clis/clis/reth.py index d3a77fdedce..9cf169b15da 100644 --- a/packages/testing/src/execution_testing/client_clis/clis/reth.py +++ b/packages/testing/src/execution_testing/client_clis/clis/reth.py @@ -28,6 +28,9 @@ class RethExceptionMapper(ExceptionMapper): TransactionException.TYPE_3_TX_CONTRACT_CREATION: "unexpected length", TransactionException.TYPE_3_TX_WITH_FULL_BLOBS: "unexpected list", TransactionException.INVALID_CHAINID: "invalid chain ID", + TransactionException.INVALID_SIGNATURE_VRS: ( + "invalid bool value, must be 0 or 1" + ), TransactionException.TYPE_3_TX_INVALID_BLOB_VERSIONED_HASH: ( "blob version not supported" ), diff --git a/tests/frontier/validation/test_transaction.py b/tests/frontier/validation/test_transaction.py index fed47389481..d52003dc3c0 100644 --- a/tests/frontier/validation/test_transaction.py +++ b/tests/frontier/validation/test_transaction.py @@ -11,7 +11,10 @@ add_kzg_version, ) from execution_testing.base_types.base_types import ZeroPaddedHexNumber -from execution_testing.exceptions.exceptions import TransactionException +from execution_testing.exceptions.exceptions import ( + TransactionException, + TransactionExceptionInstanceOrList, +) from execution_testing.forks.base_fork import BaseFork from execution_testing.specs.blockchain import ( Block, @@ -252,11 +255,23 @@ def test_bad_v_r_s( """ to = pre.fund_eoa(0xDEADBEEE) + error: TransactionExceptionInstanceOrList = ( + TransactionException.INVALID_SIGNATURE_VRS + ) + if tx_type == 0 and v not in (27, 28): + # A legacy transaction encodes its chain id within v, so a client that + # derives the chain id from an out-of-range v rejects the transaction + # with a chain id mismatch instead of an invalid signature. + error = [ + TransactionException.INVALID_SIGNATURE_VRS, + TransactionException.INVALID_CHAINID, + ] + blob_versioned_hashes = add_kzg_version([0], 1) if tx_type == 3 else None tx = Transaction( sender=pre.fund_eoa(), to=to, - error=TransactionException.INVALID_SIGNATURE_VRS, + error=error, ty=tx_type, blob_versioned_hashes=blob_versioned_hashes, value=1, From d25e02415cec52f2ea166f761c5ef635e6586579 Mon Sep 17 00:00:00 2001 From: Rafael Matias <rafael@skyle.net> Date: Thu, 9 Jul 2026 22:27:05 +0200 Subject: [PATCH 111/233] feat(fill-stateful): make the per-test chain rewind optional (add debug_resetHead) (#3127) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Port of #3050 to forks/amsterdam. The per-test chain rewind (_reset_chain_between_tests) hard-required geth's debug_setHead. Make it optional and client-agnostic via a three-tier rewind_head, probed once and cached: 1. debug_setHead (by number) — geth; truncates the chain, keeping the block tree small; 2. else debug_resetHead (by hash) — e.g. Nethermind; 3. else nothing — rely on the explicit start_block parent; the client reorgs onto it without any debug call. Also read per-test state at start_block, not "latest": a resetHead-style rewind (or no rewind) restores the build state but leaves "latest" at the previous test's tip, so reading "latest" there returns a stale nonce and every funding tx is rejected. Plus two base fill fixes: cap the deploy gas safety-buffer at the tx gas cap, and skip redundant seed withdrawal funding when the seed is already funded. --- docs/filling_tests/fill_stateful.md | 6 +- .../plugins/execute/pre_alloc.py | 19 ++++- .../plugins/fill_stateful/fill_stateful.py | 73 ++++++++++++++----- .../testing/src/execution_testing/rpc/rpc.py | 67 ++++++++++++++++- 4 files changed, 143 insertions(+), 22 deletions(-) diff --git a/docs/filling_tests/fill_stateful.md b/docs/filling_tests/fill_stateful.md index ada7c290d13..4bc31c64af1 100644 --- a/docs/filling_tests/fill_stateful.md +++ b/docs/filling_tests/fill_stateful.md @@ -13,7 +13,7 @@ The target client must expose: - `testing` (`testing_buildBlockV1`) — block construction with explicit transaction ordering. - `engine` — `engine_newPayloadVX`, `engine_forkchoiceUpdatedVX`. -- `eth`, `debug` — chain queries and `debug_setHead` for between-test rewind. +- `eth`, `debug` — chain queries and `debug_setHead` (or `debug_resetHead` on clients like Nethermind that lack `debug_setHead`) for between-test rewind. - `web3` (optional) — `web3_clientVersion` is recorded into the fixture's `_info.filling-transition-tool` for traceability. The production-ready filler is `ethpandaops/geth:master`. @@ -213,7 +213,7 @@ Both backends satisfy `FillerBackend` (`client_clis/filler_backend.py`). `Client 2. `_split_blocks_by_phase` splits any mixed-phase blocks (e.g. EIP-7702 SETUP + benchmark TEST). 3. For each block, `ClientBackend.evaluate` builds + finalises it; payload partitioned by `Block.phase` into `setupEngineNewPayloads` vs `engineNewPayloads`. 4. Write `<test>.json` (a `BlockchainEngineStatefulFixture`). -3. **Per-test reset** (`_reset_chain_between_tests`): `debug_setHead(start_block.number)`, re-fetch `latest`, abort if hash drifted. +3. **Per-test reset** (`_reset_chain_between_tests`): `debug_setHead(start_block.number)` — or `debug_resetHead(start_block.hash)` on clients without `debug_setHead`, e.g. Nethermind — re-fetch `latest`, abort if hash drifted. ### Fixture types @@ -239,7 +239,7 @@ pristine snapshot ───copy──▶ datadir ───▶ geth ───▶ └── for each test fixture: ├── replay setupEngineNewPayloads ├── replay engineNewPayloads (timed) - ├── debug_setHead → start_block + ├── debug_setHead/resetHead → start_block └── re-fetch latest, verify hash ``` diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py index 5b6f12dded1..5ec484fd8fd 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py @@ -271,7 +271,24 @@ def _compute_deploy_gas_limit( new_bytes=len(bytes(initcode)) ) regular_gas += storage_slots * sstore_regular_gas - regular_gas *= 2 + + # Double as a safety buffer since gas estimation is approximate. The buffer + # must not, by itself, push a contract that genuinely deploys within the + # EIP-7825 regular-gas cap over it: when the unbuffered estimate still fits + # the cap, clamp the limit to the cap instead. The deploy then runs with a + # cap-sized regular limit and consumes only its (smaller) actual gas. + # Only a contract whose unbuffered estimate exceeds the cap is truly + # undeployable (the caller raises on that). + buffered_regular_gas = regular_gas * 2 + tx_gas_limit_cap = fork.transaction_gas_limit_cap() + if ( + tx_gas_limit_cap is not None + and buffered_regular_gas > tx_gas_limit_cap + and regular_gas <= tx_gas_limit_cap + ): + regular_gas = tx_gas_limit_cap + else: + regular_gas = buffered_regular_gas # State portion, from the block reservoir. state_gas = intrinsic_state_gas diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/fill_stateful/fill_stateful.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/fill_stateful/fill_stateful.py index a869915d43d..587f856f1cc 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/fill_stateful/fill_stateful.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/fill_stateful/fill_stateful.py @@ -395,9 +395,26 @@ def session_worker_key(seed_key: EOA) -> EOA: @pytest.fixture(scope="function") -def worker_key(eth_rpc: EthRPC, session_worker_key: EOA) -> EOA: +def worker_key( + eth_rpc: EthRPC, + session_worker_key: EOA, + client_backend: ClientBackend, +) -> EOA: """Sync seed key nonce before each test.""" - account = eth_rpc.get_account(session_worker_key, skip_code=True) + # Read the nonce at the reset head (start_block), not "latest": a client + # whose rewind restores the build state but leaves the `latest` pointer at + # the previous test's tip (e.g. nethermind's debug_resetHead) would + # otherwise report a stale, too-high nonce and get every funding tx + # rejected ("Invalid nonce - expected 0"). + start = client_backend.start_block + if start is None: + account = eth_rpc.get_account(session_worker_key, skip_code=True) + else: + account = eth_rpc.get_account( + session_worker_key, + block_number=int(start["number"], 16), + skip_code=True, + ) session_worker_key.nonce = Number(account.nonce) return session_worker_key @@ -454,7 +471,7 @@ def max_gas_limit_per_test( @pytest.fixture(scope="session") def debug_rpc(eth_rpc: EthRPC) -> DebugRPC: - """DebugRPC on the same endpoint as eth_rpc (for debug_setHead).""" + """DebugRPC on eth_rpc's endpoint (debug_setHead/resetHead rewind).""" return DebugRPC(eth_rpc.url) @@ -600,14 +617,27 @@ def _session_pre_run( f"hash={snapshot_block['hash'][:20]}..." ) - # 2. Fund seed key via CL withdrawal; helper returns the built payload. + # 2. Fund seed key via CL withdrawal, unless it is already funded (e.g. + # pre-funded in the snapshot state). Skipping the withdrawal keeps the + # pre-run empty so start_block == the snapshot block, whose persistent + # state debug_setHead can always rewind to between tests. Otherwise + # start_block sits one diff-layer above the snapshot and a test that + # builds many blocks (e.g. test_blockhash) can prune its state, making + # the per-test rewind collapse to the snapshot block and abort the run. captured: List[EnginePayloadMetadata] = [] - fund_payload = eth_rpc.fund_via_withdrawals( - [(Address(session_worker_key), SEED_FUNDING_WEI)] - ) - if fund_payload is not None: - captured.append(fund_payload) - logger.info(f"Funded {Address(session_worker_key)} via withdrawal") + seed_address = Address(session_worker_key) + if eth_rpc.get_balance(seed_address) >= SEED_FUNDING_WEI: + logger.info( + f"Seed {seed_address} already funded " + f"(>= {SEED_FUNDING_WEI} wei); skipping withdrawal" + ) + else: + fund_payload = eth_rpc.fund_via_withdrawals( + [(seed_address, SEED_FUNDING_WEI)] + ) + if fund_payload is not None: + captured.append(fund_payload) + logger.info(f"Funded {seed_address} via withdrawal") # 3. Deploy deterministic factory if not already present. lock_file = session_temp_folder / "fill_stateful_setup.lock" @@ -706,9 +736,12 @@ def _reset_chain_between_tests( ) -> Generator[None, None, None]: """ Rewind to start_block after each test so the chain is identical for - every fill. ``debug_setHead`` only takes a number, so after the - rewind we re-fetch ``latest`` and fail loudly if the hash drifted - (e.g. live reorg of a same-numbered block). + every fill. Uses ``debug_setHead`` (by number) when available, else + ``debug_resetHead`` (by hash) for clients like Nethermind, else nothing + — each test's first block is built on its explicit start_block parent, + so the client reorgs onto it even without a debug rewind. Afterwards we + verify the block at the start_block number matches and fail loudly if it + drifted (e.g. a live reorg). """ yield if client_backend.start_block is None: @@ -720,14 +753,20 @@ def _reset_chain_between_tests( if current_head is not None and current_head["hash"] == expected_hash: return try: - debug_rpc.set_head(start_hex) + debug_rpc.rewind_head(block_number=start_hex, block_hash=expected_hash) except Exception as e: - pytest.exit(f"debug_setHead failed — subsequent fixtures invalid: {e}") - head = eth_rpc.get_block_by_number("latest") + pytest.exit(f"head rewind failed — subsequent fixtures invalid: {e}") + # Verify the rewind landed by querying the block at the expected + # start_block number, not "latest": nethermind's debug_resetHead restores + # the build head but leaves the `latest` pointer at the previous test's + # tip, so "latest" is unreliable there. The block at start_block's number + # is the start block on both geth (debug_setHead moves latest) and + # nethermind (resetHead leaves it stale). + head = eth_rpc.get_block_by_number(start_hex) if head is None or head["hash"] != expected_hash: observed = head["hash"] if head is not None else "<none>" pytest.exit( - f"debug_setHead landed on hash {observed} but expected " + f"head rewind landed on hash {observed} but expected " f"{expected_hash} (start_block at number {start_hex}). The " "live chain may have reorged out from under fill-stateful; " "rerun against a quiescent client or use --snapshot-block " diff --git a/packages/testing/src/execution_testing/rpc/rpc.py b/packages/testing/src/execution_testing/rpc/rpc.py index d5dc0d44e70..316f8a09ec2 100644 --- a/packages/testing/src/execution_testing/rpc/rpc.py +++ b/packages/testing/src/execution_testing/rpc/rpc.py @@ -52,6 +52,7 @@ ForkchoiceUpdateResponse, GetBlobsResponse, GetPayloadResponse, + JSONRPCError, JSONRPCRequest, JSONRPCResponse, PayloadAttributes, @@ -1282,6 +1283,17 @@ class DebugRPC(EthRPC): used within EEST based hive simulators. """ + # JSON-RPC "method not found" error code. + _METHOD_NOT_FOUND = -32601 + + # JSON-RPC "internal error" code. Nethermind registers debug_setHead + # but throws NotImplementedException, surfaced as this code. + _METHOD_NOT_IMPLEMENTED = -32603 + + # Which head-rewind method the client supports; resolved on first use + # so the fallback probe runs only once per session. + _rewind_method: str | None = None + def trace_call(self, tr: dict[str, str], block_number: str) -> Any | None: """`debug_traceCall`: Returns pre state required for transaction.""" params = [tr, block_number, {"tracer": "prestateTracer"}] @@ -1290,11 +1302,64 @@ def trace_call(self, tr: dict[str, str], block_number: str) -> Any | None: ).result_or_raise() def set_head(self, block_number: str) -> None: - """`debug_setHead`: Reset chain head to the given block.""" + """`debug_setHead`: Reset chain head to the given block number.""" self.post_request( request=RPCCall(method="setHead", params=[block_number]) ).result_or_raise() + def reset_head(self, block_hash: str) -> None: + """`debug_resetHead`: Reset chain head to the given block hash.""" + self.post_request( + request=RPCCall(method="resetHead", params=[block_hash]) + ).result_or_raise() + + def rewind_head(self, *, block_number: str, block_hash: str) -> None: + """ + Rewind the chain head to a given block. + + Prefer geth's ``debug_setHead`` (takes a block number); if the + client does not expose it (e.g. Nethermind, which returns a + "method not found"/"not implemented" error), fall back to + ``debug_resetHead`` (takes a block hash). If the client implements + neither, give up gracefully (``"none"``): each per-test block is + built on its explicit start_block parent, so the client reorgs onto + it without a debug rewind. The chosen method is cached after the + first call so the probe runs only once. + + Note ``debug_setHead`` truncates the chain (geth), keeping the block + tree small, whereas ``debug_resetHead`` only repoints the head on + Nethermind — there per-test forks still accumulate as under + ``"none"``. + """ + if self._rewind_method is None: + try: + self.set_head(block_number) + self._rewind_method = "setHead" + return + except JSONRPCError as e: + if e.code not in ( + self._METHOD_NOT_FOUND, + self._METHOD_NOT_IMPLEMENTED, + ): + raise + try: + self.reset_head(block_hash) + self._rewind_method = "resetHead" + return + except JSONRPCError as e: + if e.code not in ( + self._METHOD_NOT_FOUND, + self._METHOD_NOT_IMPLEMENTED, + ): + raise + self._rewind_method = "none" + return + if self._rewind_method == "setHead": + self.set_head(block_number) + elif self._rewind_method == "resetHead": + self.reset_head(block_hash) + # "none": no debug rewind available; the explicit-parent build reorgs. + class EngineRPC(BaseJwtRPC): """ From 20d6aa3bca084e090e502c4e52f4259d60bae2be Mon Sep 17 00:00:00 2001 From: Mario Vega <marioevz@gmail.com> Date: Fri, 10 Jul 2026 03:14:51 -0600 Subject: [PATCH 112/233] fix(tests): EIP-8037: failing test - create tx refunds new account (#3140) --- .../test_state_gas_create.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py index c54ba39fab7..067d66c58e6 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py @@ -2123,7 +2123,9 @@ def test_create_account_charge_reduces_child_gas( @pytest.mark.parametrize( "init_code", [ - pytest.param(Op.REVERT(0, 0), id="revert"), + pytest.param( + Op.REVERT(0, 10_000, new_memory_size=10_000), id="revert" + ), pytest.param(Op.INVALID, id="halt"), ], ) @@ -2139,6 +2141,9 @@ def test_failed_create_tx_refunds_intrinsic_new_account( refunded on creation-tx revert/halt. Block state-gas excludes it so header gas_used reflects only the regular component, and the sender's receipt reflects the same refund via cumulative_gas_used. + + Gas consumed must be above the floor for the test to work, hence + the increased memory consumption in some of the initcodes. """ intrinsic_calc = fork.transaction_intrinsic_cost_calculator() create_state_gas = fork.create_state_gas(code_size=0) @@ -2147,7 +2152,7 @@ def test_failed_create_tx_refunds_intrinsic_new_account( calldata=bytes(init_code), contract_creation=True ) intrinsic_regular = intrinsic_total - create_state_gas - gas_limit = intrinsic_total + 1000 + gas_limit = intrinsic_total + init_code.regular_cost(fork) + 1000 if init_code == Op.INVALID: regular_consumed = gas_limit - intrinsic_total @@ -2158,11 +2163,10 @@ def test_failed_create_tx_refunds_intrinsic_new_account( expected_cumulative = intrinsic_total + regular_consumed - create_state_gas # A tiny init code can leave the decomposed calldata floor above the # regular gas consumed, pinning gas_used to the floor. - floor = fork.transaction_data_floor_cost_calculator()( - data=bytes(init_code), contract_creation=True - ) - expected_gas_used = max(expected_gas_used, floor) - expected_cumulative = max(expected_cumulative, floor) + data_floor_calc = fork.transaction_data_floor_cost_calculator() + floor = data_floor_calc(data=init_code, contract_creation=True) + assert expected_gas_used > floor + assert expected_cumulative > floor tx = Transaction( to=None, From c74f1a67b63c7b34c2204bf2d5fd20b8fcabc981 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toni=20Wahrst=C3=A4tter?= <51536394+nerolation@users.noreply.github.com> Date: Fri, 10 Jul 2026 12:49:51 +0200 Subject: [PATCH 113/233] fix(spec-specs, tests): apply calldata floor to block-level regular gas (#3144) Co-authored-by: spencer-tb <spencer.tb@ethereum.org> --- src/ethereum/forks/amsterdam/fork.py | 7 +- .../test_gas_accounting.py | 49 ++++---- .../test_state_gas_calldata_floor.py | 111 +++++++++++++++++- .../test_state_gas_create.py | 9 +- 4 files changed, 147 insertions(+), 29 deletions(-) diff --git a/src/ethereum/forks/amsterdam/fork.py b/src/ethereum/forks/amsterdam/fork.py index 2d960c408a1..e1454de093f 100644 --- a/src/ethereum/forks/amsterdam/fork.py +++ b/src/ethereum/forks/amsterdam/fork.py @@ -1147,9 +1147,14 @@ def process_transaction( + tx_output.state_gas_used - int(tx_output.state_refund) ) + # The calldata floor binds the regular-gas dimension: subtract state gas + # first so the floor is not discounted by a transaction's state spending. # Defensive guard for Uint conversion: State refunds never exceed # the state charges so the value is non-negative. - tx_regular_gas = tx_gas_used_before_refund - Uint(max(0, tx_state_gas)) + tx_regular_gas = max( + tx_gas_used_before_refund - Uint(max(0, tx_state_gas)), + intrinsic.calldata_floor, + ) block_output.block_gas_used += tx_regular_gas block_output.block_state_gas_used += Uint(max(0, tx_state_gas)) block_output.blob_gas_used += tx_blob_gas_used diff --git a/tests/amsterdam/eip7778_block_gas_accounting_without_refunds/test_gas_accounting.py b/tests/amsterdam/eip7778_block_gas_accounting_without_refunds/test_gas_accounting.py index b521689d295..b870f0e1ae4 100644 --- a/tests/amsterdam/eip7778_block_gas_accounting_without_refunds/test_gas_accounting.py +++ b/tests/amsterdam/eip7778_block_gas_accounting_without_refunds/test_gas_accounting.py @@ -251,8 +251,9 @@ def test_simple_gas_accounting( refund_tx_reverts=refund_tx_reverts, ) - # EIP-8037: block gas_used = max(block_regular_gas, block_state_gas) - block_regular = gas_used_pre_refund + # EIP-8037: block gas_used = max(block_regular_gas, block_state_gas), + # with the calldata floor binding the regular dimension. + block_regular = max(gas_used_pre_refund, call_data_floor_cost) refund_tx_block_gas_used = max(block_regular, tx_state_gas) blockchain_test( @@ -332,6 +333,7 @@ def test_multi_transaction_gas_accounting( ) intrinsic_cost_calc = fork.transaction_intrinsic_cost_calculator() + data_floor_calc = fork.transaction_data_floor_cost_calculator() refunds_count = 10 stop_bytecode = Op.STOP @@ -362,10 +364,13 @@ def test_multi_transaction_gas_accounting( extra_tx_intrinsic_gas_cost = intrinsic_cost_calc( calldata=extra_tx_calldata ) - # Block regular gas uses actual charge, not the tx-level floor. - extra_tx_block_gas = intrinsic_cost_calc( - calldata=extra_tx_calldata, - return_cost_deducted_prior_execution=True, + # Block regular gas applies the calldata floor to the actual charge. + extra_tx_block_gas = max( + intrinsic_cost_calc( + calldata=extra_tx_calldata, + return_cost_deducted_prior_execution=True, + ), + data_floor_calc(data=extra_tx_calldata), ) extra_tx = Transaction( @@ -388,9 +393,9 @@ def test_multi_transaction_gas_accounting( block_regular = gas_used_pre_refund + extra_tx_block_gas block_state = tx_state_gas total_block_gas_used = max(block_regular, block_state) - # The block gas_limit must accommodate extra_tx's full gas_limit (which - # may be floor-inclusive) even though block gas_used uses the lower actual - # charge. For exceed_block_gas_limit=True we set the limit below + # The block gas_limit must accommodate extra_tx's full gas_limit + # (floor-inclusive, like its block-regular charge). For + # exceed_block_gas_limit=True we set the limit below # total_block_gas_used to test that the extra_tx fails. if exceed_block_gas_limit: environment_gas_limit = total_block_gas_used - 1 @@ -568,8 +573,9 @@ def test_varying_calldata_costs( f"Could not find the call_data with {num_iterations} iterations." ) - # EIP-8037: block gas_used = max(block_regular_gas, block_state_gas) - block_regular = gas_used_pre_refund + # EIP-8037: block gas_used = max(block_regular_gas, block_state_gas), + # with the calldata floor binding the regular dimension. + block_regular = max(gas_used_pre_refund, call_data_floor_cost) refund_tx_block_gas_used = max(block_regular, tx_state_gas) blockchain_test( @@ -620,8 +626,9 @@ def test_multiple_refund_types_in_one_tx( refund_tx_reverts=refund_tx_reverts, ) - # EIP-8037: block gas_used = max(block_regular_gas, block_state_gas) - block_regular = gas_used_pre_refund + # EIP-8037: block gas_used = max(block_regular_gas, block_state_gas), + # with the calldata floor binding the regular dimension. + block_regular = max(gas_used_pre_refund, call_data_floor_cost) refund_tx_block_gas_used = max(block_regular, tx_state_gas) blockchain_test( @@ -649,13 +656,13 @@ def test_mixed_gas_regimes( tx1: SSTORE-set fresh slot (no refund, pre_refund > floor). tx2: SSTORE-clear x10 (normal refund, refund not clipped to floor). - tx3: 1000 zero-byte calldata to STOP (floor binds upward for fee only). + tx3: 1000 zero-byte calldata to STOP (floor binds fee and block gas). - After EIP-8037's calldata-floor alignment, the floor only affects tx-level - fee calculation (tx_gas_used = max(post_refund, floor)); block regular gas - uses pre-refund gas minus state gas, with no floor applied. Per-tx sender - balance is also asserted to lock in that the floor-binding tx pays - `floor * gas_price`, not `pre_refund * gas_price`. + The floor binds the tx-level fee (tx_gas_used = max(post_refund, + floor)) and the block's regular dimension (max(pre_refund gas minus + state gas, floor)) alike. Per-tx sender balance is also asserted to + lock in that the floor-binding tx pays `floor * gas_price`, not + `pre_refund * gas_price`. """ intrinsic_cost_calc = fork.transaction_intrinsic_cost_calculator() data_floor_calc = fork.transaction_data_floor_cost_calculator() @@ -731,8 +738,8 @@ def test_mixed_gas_regimes( tx3_floor = data_floor_calc(data=tx3_data) assert tx3_floor > tx3_pre_refund, "tx3: floor must bind upward" tx3_fee_gas = max(tx3_pre_refund, tx3_floor) - # Block regular gas uses pre-refund only; floor is tx-level only. - tx3_block_contribution = tx3_pre_refund + # The floor binds the block's regular dimension as well as the fee. + tx3_block_contribution = max(tx3_pre_refund, tx3_floor) tx3 = Transaction( to=tx3_target, gas_limit=tx3_fee_gas, diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_calldata_floor.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_calldata_floor.py index 640a96c13f0..d0c8f7e4ed9 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_calldata_floor.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_calldata_floor.py @@ -2,8 +2,10 @@ Test EIP-7623 calldata floor interaction with EIP-8037 state gas. The calldata floor applies to the regular gas dimension only. It -does not affect state gas. Block gas accounting uses tx_regular_gas -(without the floor) for regular gas and tracks state gas separately. +does not affect state gas. Block gas accounting applies the floor to +the regular dimension (``max(pre_refund_gas - state_gas, floor)``), +so a transaction contributes at least the floor to the block's +regular gas while state gas is tracked separately. Tests for [EIP-8037: State Creation Gas Cost Increase] (https://eips.ethereum.org/EIPS/eip-8037). @@ -267,9 +269,9 @@ def test_calldata_floor_binds_with_reservoir( Large calldata makes the EIP-7976 floor the sender's bill, while an over-cap `gas_limit` puts the SSTORE-set state charge in the - reservoir. The floor feeds only the receipt; the block accounts - regular and state separately, so the header gas_used is the state - dimension (not the floor). + reservoir. The floor binds the receipt and the block's regular + dimension alike, so the header gas_used is the floor (not the + state dimension). """ storage = Storage() code = Op.SSTORE(storage.store_next(1), 1, new_value=1) @@ -304,5 +306,102 @@ def test_calldata_floor_binds_with_reservoir( pre=pre, post={contract: Account(storage=storage)}, tx=tx, - blockchain_test_header_verify=Header(gas_used=state_cost), + blockchain_test_header_verify=Header(gas_used=floor), + ) + + +@pytest.mark.valid_from("EIP8037") +def test_calldata_floor_counts_toward_block_gas( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify the calldata floor is charged to the block's regular gas. + + With a STOP callee and large zero-byte calldata the floor exceeds + the actual regular gas charge, so the transaction contributes the + floor (not the pre-floor charge) to the header gas_used. + """ + calldata = b"\x00" * 1024 + floor = fork.transaction_data_floor_cost_calculator()(data=calldata) + charge = fork.transaction_intrinsic_cost_calculator()( + calldata=calldata, + return_cost_deducted_prior_execution=True, + ) + assert charge < floor, "calldata floor must bind" + + contract = pre.deploy_contract(code=Op.STOP) + + tx = Transaction( + to=contract, + data=calldata, + sender=pre.fund_eoa(), + expected_receipt=TransactionReceipt(cumulative_gas_used=floor), + ) + state_test( + pre=pre, + post={}, + tx=tx, + blockchain_test_header_verify=Header(gas_used=floor), + ) + + +@pytest.mark.valid_from("EIP8037") +def test_calldata_floor_not_discounted_by_state_gas( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify state gas spending does not discount the block-level floor. + + Calldata is sized so the floor sits between the transaction's + regular-gas portion and its total gas used + (``tx_regular < floor < tx_regular + state``). The sender's bill is + the pre-floor total, yet the block's regular dimension must still + charge the full floor: the floor is compared against the regular + portion alone, so state gas cannot absorb it. An implementation + that instead floors the transaction total before deducting state + gas (or skips the floor entirely) would report the state dimension + in the header; the correct header gas_used is the floor. + """ + storage = Storage() + code = Op.SSTORE(storage.store_next(1), 1, new_value=1) + state_cost = code.state_cost(fork) + regular_cost = code.regular_cost(fork) + floor_cost = fork.transaction_data_floor_cost_calculator() + + # Smallest zero-byte calldata whose floor exceeds the state + # dimension; the floor then also dominates the header. + size = 0 + while floor_cost(data=b"\x00" * size) <= state_cost: + size += 32 + calldata = b"\x00" * size + floor = floor_cost(data=calldata) + + intrinsic = fork.transaction_intrinsic_cost_calculator()( + calldata=calldata, + return_cost_deducted_prior_execution=True, + ) + tx_regular = intrinsic + regular_cost + tx_total = tx_regular + state_cost + assert tx_regular < floor < tx_total, ( + "floor must bind the regular portion but not the total" + ) + + contract = pre.deploy_contract(code=code) + + tx = Transaction( + to=contract, + data=calldata, + state_gas_reservoir=state_cost, + sender=pre.fund_eoa(), + expected_receipt=TransactionReceipt(cumulative_gas_used=tx_total), + ) + state_test( + pre=pre, + post={contract: Account(storage=storage)}, + tx=tx, + blockchain_test_header_verify=Header(gas_used=floor), ) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py index 067d66c58e6..9f964e0b7e1 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py @@ -1350,7 +1350,14 @@ def test_create_tx_header_gas_used( intrinsic_total = intrinsic_cost( calldata=bytes(initcode), contract_creation=True ) - expected_gas_used = intrinsic_total - gas_costs.NEW_ACCOUNT + # Block regular gas applies the calldata floor, which tops up + # the small regular remainder left after the NEW_ACCOUNT refund. + expected_gas_used = max( + intrinsic_total - gas_costs.NEW_ACCOUNT, + fork.transaction_data_floor_cost_calculator()( + data=bytes(initcode), contract_creation=True + ), + ) else: # For a minimal CREATE tx deploying Op.STOP (1 byte), # state gas (new account) dominates regular gas. From 0f8b81b192166e07408088860112703584a9fa61 Mon Sep 17 00:00:00 2001 From: danceratopz <danceratopz@gmail.com> Date: Sat, 11 Jul 2026 11:31:40 +0200 Subject: [PATCH 114/233] feat(test-consume): set HIVE_EXPECT_DEEP_REORGS in the multi-test client environment (#3145) --- .../plugins/consume/simulators/multi_test_client.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/multi_test_client.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/multi_test_client.py index 003a685536a..898c9273959 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/multi_test_client.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/multi_test_client.py @@ -252,6 +252,11 @@ def environment( "HIVE_CHECK_LIVE_PORT": str(check_live_port), **{k: f"{v:d}" for k, v in ruleset[fork].items()}, "HIVE_FORK": pre_alloc_group.fork.name(), + # Tell client wrapper scripts this workload performs deep reorgs: + # clients are reused across a group's tests with a rewind to genesis + # in between, so wrappers can raise client-specific limits that would + # otherwise reject them (e.g. geth's engine API max reorg depth). + "HIVE_EXPECT_DEEP_REORGS": "1", } environment_cache[pre_hash] = env From a9abd46ec1e49cb97dd7bad39164c8f19ed8bf6a Mon Sep 17 00:00:00 2001 From: danceratopz <danceratopz@gmail.com> Date: Sat, 11 Jul 2026 12:42:17 +0200 Subject: [PATCH 115/233] docs(test-consume): fix and augment enginex docs (#3143) --- docs/running_tests/running.md | 51 ++++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 15 deletions(-) diff --git a/docs/running_tests/running.md b/docs/running_tests/running.md index d5278726ca4..62970bf4fc3 100644 --- a/docs/running_tests/running.md +++ b/docs/running_tests/running.md @@ -14,7 +14,7 @@ Both `consume` and `execute` provide sub-commands which correspond to different | [`consume direct`](#direct) | Client consume tests via a `statetest` interface | EVM | None | Module test | | [`consume direct`](#direct) | Client consume tests via a `blocktest` interface | EVM, block processing | None | Module test,</br>Integration test | | [`consume engine`](#engine) | Client imports blocks via Engine API `EngineNewPayload` in Hive | EVM, block processing, Engine API | Staging, Hive | System test | -| [`consume enginex`](#enginex) | Client imports blocks via Engine API in Hive, optimized by client reuse | EVM, block processing, Engine API | Staging, Hive | System test | +| [`consume enginex`](#enginex) | Client imports blocks via Engine API in Hive, optimized by client reuse | EVM, block processing, Engine API, chain reorgs (implicit\*\*) | Staging, Hive | System test | | [`consume sync`](#sync) | Client syncs from another client using Engine API in Hive | EVM, block processing, Engine API, P2P sync | Staging, Hive | System test | | [`consume rlp`](#rlp) | Client imports RLP-encoded blocks upon start-up in Hive | EVM, block processing, RLP import (sync\*) | Staging, Hive | System test | | [`build-block`](#block-building) | Client builds blocks via `testing_buildBlockV1` in Hive, validated against fixture | EVM, block production, Engine API (testing namespace) | Staging, Hive | System test | @@ -23,6 +23,8 @@ Both `consume` and `execute` provide sub-commands which correspond to different \*sync: Depending on code paths used in the client implementation, see the [RLP vs Engine Simulator section below](#engine-vs-rlp-simulator). +\*\*chain reorgs: A side-effect of client reuse, not something the test cases describe, see the [Implicit Chain Reorg Coverage section below](#implicit-chain-reorg-coverage). + The following sections describe the different methods in more detail. !!! note "`./hive --sim=eels/consume-engine` vs `consume engine`" @@ -59,10 +61,12 @@ The `consume engine` command: 1. **Initializes the execution client** with genesis state. 2. **Connects via Engine API** (port 8551), primitively mocking a consensus client. -3. **Sends a forkchoice update** to establish the chain head. -4. **Submits payloads** using `engine_newPayload` calls. -5. **Validates responses** against expected results. -6. **Tests error conditions** and exception handling. +3. **Sends a forkchoice update** to the genesis block to establish the chain head. +4. **Verifies the client's genesis block hash** via `eth_getBlockByNumber(0)`. +5. **Submits payloads** using `engine_newPayload` calls. +6. **Validates responses** against expected results. +7. **Sends a forkchoice update** after each valid payload to advance the chain head. +8. **Tests error conditions** and exception handling. ## EngineX @@ -78,29 +82,46 @@ The `consume enginex` command, for each pre-allocation group: 1. **Initializes the execution client** with the group's shared genesis state. 2. **Connects via Engine API** (port 8551). -3. **Executes all tests in the group** against the same client: +3. **Executes all tests in the group** against the same client. Each test: - - Submits payloads from each test using `engine_newPayload` calls. + - Sends a forkchoice update to the genesis block, resetting the chain head. + - Verifies the client's genesis block hash via `eth_getBlockByNumber(0)`; this is only done for the first test executed against the client, as genesis is immutable. + - Submits payloads from the test using `engine_newPayload` calls. - Validates responses against expected results. + - Sends a forkchoice update after each valid payload to advance the chain head. - Tests error conditions and exception handling. 4. **Stops the client** when all tests in the group complete. +### Implicit Chain Reorg Coverage + +Client reuse gives `consume enginex` coverage that `consume engine` does not have. The forkchoice update at the start of each test resets the client's head from the previous test's chain tip back to genesis. The payload that follows is therefore a sibling of a block the client already imported and considered canonical (the same parent and block number, but a different block hash), and the forkchoice update sent after it makes the new branch canonical. + +Every test after the first in a pre-allocation group consequently exercises the client's chain reorganization path: rolling the head state back to an ancestor, importing a competing block at an already-occupied height, and re-canonicalizing a new branch. + +!!! note "This coverage is implicit" + + No `blockchain_test_engine_x` fixture describes a reorg; the reorgs are an artifact of how the simulator reuses clients. A test whose payloads are all invalid also leaves the head at genesis, so no rollback precedes the next test in the group. + + It does mean, however, that a test which fails under `consume enginex` but passes under `consume engine` is more likely to indicate a bug in the client's reorg, head state rollback or block caching logic than in its EVM or block validation logic. + ### Engine vs EngineX -| | `consume engine` | `consume enginex` | -| -------------------- | ---------------------------------------------------------------------- | ------------------------------------------------------------------------ | -| **Fixture format** | [`blockchain_test_engine`](./test_formats/blockchain_test_engine.md) | [`blockchain_test_engine_x`](./test_formats/blockchain_test_engine_x.md) | -| **Client lifecycle** | New client per test | Client reused across tests with same pre-alloc | -| **Fork choice update** | FCU called for genesis and final payload | FCU for genesis and final payload skipped | -| **Execution speed** | Slower (client startup overhead) | Faster (amortized startup cost) | -| **Test isolation** | Full isolation | Shared genesis state within group | +| | `consume engine` | `consume enginex` | +| ----------------------- | -------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | +| **Fixture format** | [`blockchain_test_engine`](./test_formats/blockchain_test_engine.md) | [`blockchain_test_engine_x`](./test_formats/blockchain_test_engine_x.md) | +| **Client lifecycle** | New client per test | Client reused across tests with same pre-alloc | +| **Engine API flow** | FCU to genesis, then an `engine_newPayload` and FCU per valid payload | Identical, to keep both methods equivalent | +| **Genesis block check** | `eth_getBlockByNumber(0)` per test | `eth_getBlockByNumber(0)` once per client; genesis is immutable | +| **Execution speed** | Slower (client startup overhead) | Faster (amortized startup cost) | +| **Test isolation** | Full isolation | Shared client and genesis state within group; the chain head is reset to genesis for each test | +| **Chain reorgs** | Not exercised; each client executes one test's payloads only | [Implicitly exercised](#implicit-chain-reorg-coverage) by every test after the first in a group | EngineX achieves faster execution by: 1. **Grouping tests** by their pre-allocation state (genesis configuration). 2. **Reusing clients** across all tests in a group, avoiding repeated client startup. -3. **Skipping redundant initialization** since the client is already at the expected genesis state. +3. **Skipping the redundant genesis block check** for reused clients: the client's genesis block hash is verified once per client, instead of once per test. !!! note "When to use EngineX vs Engine" From 77e95ef10593cbeabcdefef41c130abee8760f91 Mon Sep 17 00:00:00 2001 From: Guruprasad Kamath <48196632+gurukamath@users.noreply.github.com> Date: Mon, 13 Jul 2026 13:36:50 +0200 Subject: [PATCH 116/233] feat(spec-specs,test-tests): update the EIP-2780 implementation (#3126) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(spec-specs): EIP-2780 charge state-dependent tx costs at the top frame Move every state-dependent charge out of the transaction intrinsic -- which over-charged and then refunded -- and into the top frame, charged lazily against the transaction's pre-state. The intrinsic keeps only state-independent costs and remains the sole validity input: contract creation no longer adds NEW_ACCOUNT, and each authorization adds only REGULAR_PER_AUTH_BASE_COST. At depth 0, process_message now: - Applies the EIP-7702 authorizations via set_delegation, charging per authorization: NEW_ACCOUNT when the authority's leaf does not exist; ACCOUNT_WRITE on the transaction's first write to the authority (the written set is seeded with the sender, so a self-sponsored authority pays nothing and repeated authorizations pay once); and AUTH_BASE once per authority for a net-new delegation indicator, never credited back. - Folds the authorization state gas into the frame baseline (Evm.auth_state_gas_used, rebased state_gas_reservoir) so a dispatch revert or halt cannot refill it -- the delegations, nonce bumps, and authority leaves persist per EIP-7702's dispatch-failure rule. A preparation-phase out-of-gas instead rolls the delegations back, undoes the fold, and halts consuming all gas without dispatching. - Charges the remaining dispatch costs in prepare_dispatch, which must not mutate transaction state: the recipient's or creation target's NEW_ACCOUNT keyed on pre-transaction aliveness (still refillable, since that state rolls back with the frame), and delegation resolution at WARM_ACCESS or COLD_ACCOUNT_ACCESS by target warmth. set_delegation is thereby the last transaction-state mutation before dispatch, so the execution snapshot brackets exactly the state the folded gas paid for. Remove the superseded over-charge/refund plumbing: the intrinsic NEW_ACCOUNT refund paths in fork.py and the vestigial MessageCallOutput state_refund / created_target_alive fields. validate_authorization returns just the authority address. * feat(amsterdam): model EIP-2780 top-frame gas in the test framework Teach execution_testing the EIP-2780 split between intrinsic and top-frame charges: - Intrinsic calculators charge only the state-independent costs: REGULAR_PER_AUTH_BASE_COST per authorization and no NEW_ACCOUNT for contract creation. AUTH_BASE (EIP-8037) and REGULAR_PER_AUTH_BASE_COST (EIP-8038) become named gas costs. - The top-frame calculators (transaction_top_frame_gas_calculator, transaction_top_frame_state_gas) price the per-authorization NEW_ACCOUNT / ACCOUNT_WRITE / AUTH_BASE from the authorizations list threaded into them, the recipient's or creation target's NEW_ACCOUNT, and delegation-target resolution as warm or cold via delegation_warm. - AuthorizationTuple carries creates_account / writes_delegation / first_write annotations (excluded from serialization) so each tuple declares its pre-state effect; ACCOUNT_WRITE keys on first_write -- the transaction's first write to the authority -- rather than leaf creation. The AuthorizationGasInfo structural Protocol lets forks read the annotations without importing test_types. - EIP2780.refund_types() drops AUTHORIZATION_EXISTING_AUTHORITY: Amsterdam charges authorizations by pre-state instead of over-charging and refunding, while Prague's refund coverage keeps the shared enum member. * test(amsterdam): cover the EIP-2780 top-frame charges Pin the EIP-2780 top-frame model end to end in the EIP-2780 suite: - test_authorization_charges.py (new): per-authorization billing keyed on the authority's pre-transaction state -- NEW_ACCOUNT for new leaves; ACCOUNT_WRITE on the transaction's first write (existing authorities pay it, the sender and repeated authorizations do not, tx.to as authority does); AUTH_BASE only for a net-new delegation, at most once per authority, never credited back (pre-tx-delegated re-set and clear-then-set, multiple sets, set/clear cycles). - test_authorization_oog.py (new): charge-by-charge out-of-gas points across the preparation phase -- a prep out-of-gas rolls back every applied delegation and halts without dispatching, while applied authorization state gas survives a dispatch revert or halt (balance-, header-, and reservoir-level pins), with guards that the recipient's NEW_ACCOUNT and in-frame SSTORE state gas still refill when their state rolls back. - test_calldata_floor.py / test_intrinsic_gas_boundary.py: a data-heavy transfer whose EIP-7623/7976 floor (built on the lowered TX_BASE) dominates the decomposed intrinsic, one-below-floor rejection, and the contract-creation boundary on the regular-only intrinsic. The floor-dominating calldata size comes from the shared find_floor_cost_threshold search rather than a hardcoded value. - Migrations: the value-moving tests adopt the top-frame NEW_ACCOUNT for the creation target (charged on success, refilled when the init code reverts), the delegation variants annotate self-sponsored tuples with first_write, the warmth invariants adopt warm/cold delegation-target access, and helpers.py's authorization actions declare the creates_account / writes_delegation / first_write annotations the framework calculators consume. * test(amsterdam): migrate remaining suites to the EIP-2780 top-frame model EIP-2780 moved the contract-creation NEW_ACCOUNT and every state-dependent authorization cost out of the intrinsic and into the top frame, with no over-charge refunds. Migrate the suites that pinned the old model: - eip8037 test_state_gas_set_code.py: authorization tests bill by the declared pre-state annotations; receipts are plain sums with the first-write ACCOUNT_WRITE included, headers are max(block_regular, block_state), and authorization state gas persists on the block's state dimension through every dispatch-failure mode (no refill on revert, halt, or out-of-gas). - eip8037 create cluster (test_state_gas_create.py, test_state_gas_reservoir.py, test_state_gas_pricing.py): gas limits cover the top-frame NEW_ACCOUNT; reject-below-intrinsic keys off the regular-only intrinsic; NEW_ACCOUNT refills on init-code failure and is never charged on collision; AUTH_BASE is measured via transaction_top_frame_state_gas; an existing-authority authorization credits nothing to the reservoir. - eip8038 (test_create_gas.py, test_selfdestruct_gas.py, test_set_code_auth_gas.py, test_set_code_auth_refunds.py, test_fork_transition.py): the same top-frame NEW_ACCOUNT treatment for creates, the reduced refund-free per-authorization charges, and the authorization intrinsic drop across the BPO2->Amsterdam transition. - eip7778 / eip7976: drop the AUTHORIZATION_EXISTING_AUTHORITY refund path (Amsterdam no longer has that refund type) and add the top-frame regular charge to expected receipts. - eip7954 test_max_code_size.py: add the top-frame state gas back into the exact-fit deposit-gas limit so it still lands precisely on the code-deposit boundary. * test(amsterdam): cover BAL inclusion at the top-frame delegation charge The delegation target of a delegated tx.to must enter the block access list only when the EIP-2780 top-frame access charge succeeds. Pin the charge-before-access order of the top-frame dispatch: on out-of-gas the target stays out of the BAL, on success it appears with an empty change set. * feat(amsterdam): EIP-2780 exempt value-bearing recipient authority from ACCOUNT_WRITE When a transaction moves value to its recipient and that recipient is also an authority, seed the recipient into `set_delegation`'s written-set: the value transfer already pays to write the recipient, so applying the authorization is not the transaction's first write to it and no `ACCOUNT_WRITE` is charged. Adopt the framework top-frame calculators in the EIP-2780 authorization charge and value-moving tests (driving the charges through the `first_write` / `writes_delegation` / `creates_account` annotations instead of hand-summed gas constants), and refresh the now value-dependent `ACCOUNT_WRITE` docstrings. * refactor(specs,tests): Remove intrinsic state gas concept * refactor(specs): Remove intrinsic state gas concept * refactor(test-forks): Remove intrinsic state gas * fix(tests): Fix intrinsic state gas usages * fix(tests): correct inert authorization flags in delegation-pointer tests The two migrated delegation-pointer tests re-target an authority that already has a delegation and whose account nonce (1, from the delegation setup) no longer matches the authorization's nonce=0, so the authorization is invalid and charges no top-frame state gas. Setting writes_delegation/first_write to True added a phantom AUTH_BASE to state_gas_reservoir that was silently refunded, contradicting the AuthorizationTuple.first_write contract (False for invalid authorizations) and the spec's AUTH_BASE gate (not delegated_before_tx). Set both flags to False so the reserved state gas matches what the top frame actually charges, and reuse the single authorization object in test_delegation_pointer_new_account_state_gas instead of building a second inline tuple. * fix(test-forks): drop deleted transaction_intrinsic_state_gas call The execute plugin's _compute_deploy_gas_limit still called the removed transaction_intrinsic_state_gas, which failed mypy (and CI static). On every concrete fork that value was zero (BaseFork default, or forced to zero once EIP-2780 moves the created account's NEW_ACCOUNT to the top frame), and the intrinsic calculator already returns the regular-only cost, so the back-out was a no-op. Remove it, keeping the deploy gas limit unchanged. --------- Co-authored-by: Guruprasad Kamath <guru241987@gmail.com> * test(amsterdam): reconcile floor-pinned expectations with header gas accounting After the top-frame decomposition, the calldata floor exceeds the create intrinsic for tiny initcode. The floor pins only the amount billed (receipt cumulative gas); the block header counts pre-refund, pre-floor regular consumption. Build expectations from the pre-floor intrinsic, apply the floor once to the receipt only, and verify the header and receipt separately where they diverge. * fix(spec-specs, tests): exclude unloaded recipient from the BAL * fix(spec-specs, tests): exclude unloaded recipient from the BAL (cherry picked from commit 89d15f3d447aec9ae5189b143d7a106cfd73c0c5) * refactor(spec-specs): represent unresolved top-frame code as None * test(amsterdam): pin reservoir settlement at each top-frame failure point A transaction whose gas limit exceeds the EIP-7825 cap forms a state-gas reservoir, and how much of it returns depends on where the transaction fails. Two tests drive one many-authorization transaction shape to every failure point: - a preparation out-of-gas (inside set_delegation or at the dispatch charge) rolls everything back and returns the reservoir whole: the sender pays exactly TX_MAX_GAS_LIMIT, however much extra was sent; - an execution failure keeps the applied delegations and their state gas consumed, so the reservoir is preserved only down to the post-authorization baseline -- an exceptional halt burns the full gas limit when the auth state gas exceeds the reservoir, while a revert pays exact usage. The value-bearing variant targets the bn254 pairing precompile (the one empty recipient that executes) so both state-charge classes ride one transaction: the halt scenario sizes the reservoir above the auth state gas to make the recipient NEW_ACCOUNT refill observable in the settlement, pinning gas_used == cap + auth state gas exactly. Also corrects the module docstring, which claimed a preparation-phase OOG consumes the full gas limit -- only true with a zero reservoir. * post review updates * fix(tests): Ported static failing tests --------- Co-authored-by: Toni Wahrstätter <info@toniwahrstaetter.com> Co-authored-by: Mario Vega <marioevz@gmail.com> Co-authored-by: spencer <spencer.tb@ethereum.org> --- .../plugins/execute/pre_alloc.py | 14 +- .../src/execution_testing/forks/base_fork.py | 44 +- .../forks/forks/eips/amsterdam/eip_2780.py | 92 +- .../forks/forks/eips/amsterdam/eip_8037.py | 25 +- .../forks/forks/eips/amsterdam/eip_8038.py | 1 + .../src/execution_testing/forks/gas_costs.py | 6 + .../test_types/transaction_types.py | 7 + src/ethereum/forks/amsterdam/fork.py | 29 +- src/ethereum/forks/amsterdam/transactions.py | 77 +- src/ethereum/forks/amsterdam/utils/message.py | 6 +- src/ethereum/forks/amsterdam/vm/__init__.py | 52 +- .../forks/amsterdam/vm/eoa_delegation.py | 120 +- .../forks/amsterdam/vm/interpreter.py | 154 +- .../helpers.py | 201 +- .../eip2780_reduce_intrinsic_tx_gas/spec.py | 2 +- .../test_authorization_charges.py | 543 +++++ .../test_authorization_oog.py | 1492 ++++++++++++ .../test_calldata_floor.py | 96 +- .../test_intrinsic_gas_boundary.py | 66 +- .../test_value_moving_transactions.py | 32 +- .../test_value_moving_with_tx_delegation.py | 202 +- .../test_warmth_invariants.py | 101 +- .../test_gas_accounting.py | 76 +- .../test_block_access_lists_eip7702.py | 153 ++ .../test_cases.md | 2 + .../test_max_code_size.py | 7 + .../test_additional_coverage.py | 92 +- .../test_refunds.py | 54 +- .../test_state_gas_create.py | 242 +- .../test_state_gas_delegation_pointer.py | 53 +- .../test_state_gas_pricing.py | 53 +- .../test_state_gas_reservoir.py | 87 +- .../test_state_gas_set_code.py | 2003 ++++++++++------- .../test_create_gas.py | 44 +- .../test_fork_transition.py | 31 +- .../test_selfdestruct_gas.py | 29 +- .../test_set_code_auth_gas.py | 183 +- .../test_set_code_auth_refunds.py | 251 +-- .../test_tx_gas_limit.py | 5 - ...transaction_collision_to_empty_but_code.py | 18 +- ...ransaction_collision_to_empty_but_nonce.py | 18 +- ...t_init_colliding_with_non_empty_account.py | 18 +- 42 files changed, 4838 insertions(+), 1943 deletions(-) create mode 100644 tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_authorization_charges.py create mode 100644 tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_authorization_oog.py diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py index 5ec484fd8fd..f8712d21f41 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py @@ -250,13 +250,10 @@ def _compute_deploy_gas_limit( sstore_state_gas = sstore.state_cost(fork) sstore_regular_gas = sstore.gas_cost(fork) - sstore_state_gas - # Back out the state gas folded into TX_CREATE. - intrinsic_state_gas = fork.transaction_intrinsic_state_gas( - contract_creation=True - ) - intrinsic_regular_gas = ( - intrinsic_gas_calculator(calldata=initcode, contract_creation=True) - - intrinsic_state_gas + # The intrinsic cost is now regular-only: the created account's + # NEW_ACCOUNT state gas is charged at the top frame, not folded in. + intrinsic_regular_gas = intrinsic_gas_calculator( + calldata=initcode, contract_creation=True ) # Regular portion, bound by the gas cap. @@ -291,8 +288,7 @@ def _compute_deploy_gas_limit( regular_gas = buffered_regular_gas # State portion, from the block reservoir. - state_gas = intrinsic_state_gas - state_gas += fork.code_deposit_state_gas(code_size=deploy_code_size) + state_gas = fork.code_deposit_state_gas(code_size=deploy_code_size) state_gas += storage_slots * sstore_state_gas deploy_gas_limit = regular_gas + state_gas diff --git a/packages/testing/src/execution_testing/forks/base_fork.py b/packages/testing/src/execution_testing/forks/base_fork.py index 08ab44229b8..911867deec8 100644 --- a/packages/testing/src/execution_testing/forks/base_fork.py +++ b/packages/testing/src/execution_testing/forks/base_fork.py @@ -12,6 +12,7 @@ Mapping, Optional, Protocol, + Sequence, Set, Sized, Type, @@ -152,10 +153,8 @@ def __call__( Forks that itemize the value-transfer charge in intrinsic gas use this flag; ignored by older forks. recipient_type: Category of the transaction recipient. Forks - that vary intrinsic gas by recipient kind - (e.g. no access cost for precompiles, no value - charge for self-transfers) use this; ignored - by older forks. + that vary intrinsic gas by recipient kind use this; + ignored by older forks. Returns: Gas cost of a transaction @@ -163,6 +162,19 @@ def __call__( pass +class AuthorizationGasInfo(Protocol): + """ + Structural view of an EIP-7702 authorization's effect on the + pre-state, used to compute its top-frame gas. The test + ``AuthorizationTuple`` satisfies it via its ``creates_account``, + ``writes_delegation``, and ``first_write`` fields. + """ + + creates_account: bool + writes_delegation: bool + first_write: bool + + class TopFrameGasCalculator(Protocol): """ A protocol to calculate the additional regular gas charged at the @@ -185,6 +197,8 @@ def __call__( contract_creation: bool = False, sends_value: bool = False, recipient_type: RecipientType = RecipientType.CONTRACT, + delegation_warm: bool = False, + authorizations: Sequence[AuthorizationGasInfo] = (), ) -> int: """ Return the regular gas consumed by top-frame preparation for a @@ -199,6 +213,11 @@ def __call__( value. recipient_type: Category of the transaction recipient. Drives the conditional charges. + delegation_warm: Whether a delegated recipient's delegation + target is already warm, charging warm rather + than cold access. + authorizations: The transaction's EIP-7702 authorizations; + each contributes its top-frame regular gas. Returns: Regular gas added by top-frame preparation. @@ -758,17 +777,6 @@ def transaction_intrinsic_cost_calculator( """ pass - @classmethod - def transaction_intrinsic_state_gas( - cls, - *, - contract_creation: bool = False, - authorization_count: int = 0, - ) -> int: - """Return intrinsic state gas (zero pre-Amsterdam).""" - del contract_creation, authorization_count - return 0 - @classmethod def transaction_top_frame_gas_calculator( cls, @@ -787,8 +795,11 @@ def fn( contract_creation: bool = False, sends_value: bool = False, recipient_type: RecipientType = RecipientType.CONTRACT, + delegation_warm: bool = False, + authorizations: Sequence[AuthorizationGasInfo] = (), ) -> int: del contract_creation, sends_value, recipient_type + del delegation_warm, authorizations return 0 return fn @@ -800,6 +811,7 @@ def transaction_top_frame_state_gas( contract_creation: bool = False, sends_value: bool = False, recipient_type: RecipientType = RecipientType.CONTRACT, + authorizations: Sequence[AuthorizationGasInfo] = (), ) -> int: """ Return the state gas charged at the top-level transaction @@ -811,7 +823,7 @@ def transaction_top_frame_state_gas( Defaults to 0 for forks that do not perform such post-intrinsic preparation. """ - del contract_creation, sends_value, recipient_type + del contract_creation, sends_value, recipient_type, authorizations return 0 @classmethod diff --git a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_2780.py b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_2780.py index 713d761d77c..28bbcb60b84 100644 --- a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_2780.py +++ b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_2780.py @@ -9,14 +9,16 @@ """ from dataclasses import replace -from typing import List, Sized +from typing import List, Sequence, Sized from execution_testing.base_types import AccessList from execution_testing.base_types.conversions import BytesConvertible from .....recipient_type import RecipientType from ....base_fork import ( + AuthorizationGasInfo, BaseFork, + RefundTypes, TopFrameGasCalculator, TransactionDataFloorCostCalculator, TransactionIntrinsicCostCalculator, @@ -112,17 +114,39 @@ def fn( sends_value: bool = False, recipient_type: RecipientType = RecipientType.CONTRACT, ) -> int: + # Only the state-independent base cost per authorization is + # charged in the intrinsic; the state-dependent + # account-creation and delegation-write costs are charged at + # the top frame. Exclude the base-fork authorization cost + # and re-add the base cost here. + authorization_count = 0 + if authorization_list_or_count is not None: + authorization_count = ( + len(authorization_list_or_count) + if isinstance(authorization_list_or_count, Sized) + else authorization_list_or_count + ) + intrinsic_cost: int = super_fn( calldata=calldata, contract_creation=contract_creation, access_list=access_list, - authorization_list_or_count=authorization_list_or_count, + authorization_list_or_count=None, return_cost_deducted_prior_execution=True, ) + intrinsic_cost += ( + authorization_count * gas_costs.REGULAR_PER_AUTH_BASE_COST + ) is_self_transfer = recipient_type == RecipientType.SELF if contract_creation: + # EIP-2780: the created account's NEW_ACCOUNT state gas + # is charged at the top frame, not deducted in the + # intrinsic. The base-fork intrinsic bundles it in, so + # remove it here, mirroring value transfer to an empty + # account whose NEW_ACCOUNT is likewise top-frame. + intrinsic_cost -= gas_costs.NEW_ACCOUNT if sends_value: intrinsic_cost += gas_costs.TRANSFER_LOG_COST elif not is_self_transfer: @@ -160,10 +184,14 @@ def transaction_top_frame_gas_calculator( transaction frame, after intrinsic gas is deducted but before the EVM dispatches. - Charges ``COLD_ACCOUNT_ACCESS`` when the recipient is an - existing delegated account. The empty-recipient - ``NEW_ACCOUNT`` charge is state gas, returned separately by - ``transaction_top_frame_state_gas``. + Charges the delegation-target access when the recipient is an + existing delegated account: ``WARM_ACCESS`` when the target is + already warm, otherwise ``COLD_ACCOUNT_ACCESS``. Each + authorization whose application is the transaction's first + write to its authority's leaf (``first_write``) adds + ``ACCOUNT_WRITE``. The state-gas portions (empty-recipient and + per-authorization ``NEW_ACCOUNT``, plus ``AUTH_BASE``) are + returned separately by ``transaction_top_frame_state_gas``. """ gas_costs = cls.gas_costs() @@ -172,14 +200,24 @@ def fn( contract_creation: bool = False, sends_value: bool = False, recipient_type: RecipientType = RecipientType.CONTRACT, + delegation_warm: bool = False, + authorizations: Sequence[AuthorizationGasInfo] = (), ) -> int: del sends_value if contract_creation: return 0 + regular = 0 if recipient_type == RecipientType.DELEGATION_7702: - return gas_costs.COLD_ACCOUNT_ACCESS - return 0 + regular += ( + gas_costs.WARM_ACCESS + if delegation_warm + else gas_costs.COLD_ACCOUNT_ACCESS + ) + for auth in authorizations: + if auth.first_write: + regular += gas_costs.ACCOUNT_WRITE + return regular return fn @@ -190,15 +228,43 @@ def transaction_top_frame_state_gas( contract_creation: bool = False, sends_value: bool = False, recipient_type: RecipientType = RecipientType.CONTRACT, + authorizations: Sequence[AuthorizationGasInfo] = (), ) -> int: """ Return the state gas charged at the top-level transaction - frame. Charges ``NEW_ACCOUNT`` when value is transferred to an - empty recipient; zero otherwise. + frame. A contract creation charges the created account's + ``NEW_ACCOUNT`` here (state-dependent, no longer intrinsic), + assuming a fresh target. Otherwise, charges ``NEW_ACCOUNT`` when + value is transferred to an empty recipient, and each + authorization adds ``NEW_ACCOUNT`` when its authority's account + leaf must be created and ``AUTH_BASE`` when it writes a net-new + delegation indicator. """ gas_costs = cls.gas_costs() if contract_creation: - return 0 - if sends_value and recipient_type == RecipientType.EMPTY_ACCOUNT: return gas_costs.NEW_ACCOUNT - return 0 + state = 0 + if sends_value and recipient_type == RecipientType.EMPTY_ACCOUNT: + state += gas_costs.NEW_ACCOUNT + for auth in authorizations: + if auth.creates_account: + state += gas_costs.NEW_ACCOUNT + if auth.writes_delegation: + state += gas_costs.AUTH_BASE + return state + + @classmethod + def refund_types(cls) -> List[RefundTypes]: + """ + Drop the existing-authority authorization refund. + + EIP-2780 charges each authorization's state-dependent cost at the + top frame, keyed on the authority's pre-transaction state, with no + refund. The Prague-era ``AUTHORIZATION_EXISTING_AUTHORITY`` refund + therefore no longer applies. + """ + return [ + refund + for refund in super(EIP2780, cls).refund_types() + if refund != RefundTypes.AUTHORIZATION_EXISTING_AUTHORITY + ] diff --git a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8037.py b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8037.py index c9f5fd170be..daab14316c8 100644 --- a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8037.py +++ b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8037.py @@ -92,6 +92,7 @@ def gas_costs(cls) -> GasCosts: parent.STORAGE_SET + STATE_BYTES_PER_STORAGE_SET * cpsb ), NEW_ACCOUNT=new_acct, + AUTH_BASE=STATE_BYTES_PER_AUTH_BASE * cpsb, TX_CREATE=parent.TX_CREATE + new_acct, AUTH_PER_EMPTY_ACCOUNT=( parent.AUTH_PER_EMPTY_ACCOUNT @@ -231,30 +232,6 @@ def fn(opcode: OpcodeBase) -> int: return fn - @classmethod - def transaction_intrinsic_state_gas( - cls, - *, - contract_creation: bool = False, - authorization_count: int = 0, - ) -> int: - """ - Return the intrinsic state gas for a transaction. Creation - adds `STATE_BYTES_PER_NEW_ACCOUNT * cpsb`, and each - authorization adds - `(STATE_BYTES_PER_NEW_ACCOUNT + STATE_BYTES_PER_AUTH_BASE) * cpsb`. - """ - cpsb = cls.cost_per_state_byte() - state_gas = 0 - if contract_creation: - state_gas += STATE_BYTES_PER_NEW_ACCOUNT * cpsb - state_gas += ( - (STATE_BYTES_PER_NEW_ACCOUNT + STATE_BYTES_PER_AUTH_BASE) - * cpsb - * authorization_count - ) - return state_gas - @classmethod def _calculate_sstore_state_gas( cls, opcode: OpcodeBase, gas_costs: GasCosts diff --git a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8038.py b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8038.py index 26c05c5a0ea..0e120edbc91 100644 --- a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8038.py +++ b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8038.py @@ -74,6 +74,7 @@ def gas_costs(cls) -> GasCosts: OPCODE_CREATE_BASE=create_access, TX_CREATE=create_access, AUTH_PER_EMPTY_ACCOUNT=account_write + regular_per_auth_base_cost, + REGULAR_PER_AUTH_BASE_COST=regular_per_auth_base_cost, ) @classmethod diff --git a/packages/testing/src/execution_testing/forks/gas_costs.py b/packages/testing/src/execution_testing/forks/gas_costs.py index a899d5a129a..0113895108f 100644 --- a/packages/testing/src/execution_testing/forks/gas_costs.py +++ b/packages/testing/src/execution_testing/forks/gas_costs.py @@ -47,6 +47,12 @@ class GasCosts: # Authorization AUTH_PER_EMPTY_ACCOUNT: int + # State gas for writing a net-new EIP-7702 delegation indicator; + # 0 before the state-creation repricing introduces it. + AUTH_BASE: int = 0 + # State-independent regular gas charged per EIP-7702 authorization + # tuple; 0 before the state-access repricing introduces it. + REGULAR_PER_AUTH_BASE_COST: int = 0 # Utility MEMORY_PER_WORD: int diff --git a/packages/testing/src/execution_testing/test_types/transaction_types.py b/packages/testing/src/execution_testing/test_types/transaction_types.py index 4b808e08b16..b2b04d3ed6a 100644 --- a/packages/testing/src/execution_testing/test_types/transaction_types.py +++ b/packages/testing/src/execution_testing/test_types/transaction_types.py @@ -120,6 +120,13 @@ class AuthorizationTuple(AuthorizationTupleGeneric[HexNumber]): signer: EOA | None = None secret_key: Hash | None = None + creates_account: bool = Field(False, exclude=True) + writes_delegation: bool = Field(True, exclude=True) + # Whether applying this authorization is the transaction's first + # write to the authority's account leaf. False for a self-sponsored + # authority (the sender is written at inclusion), for repeated + # authorizations on one authority, and for invalid authorizations. + first_write: bool = Field(True, exclude=True) def model_post_init(self, __context: Any) -> None: """ diff --git a/src/ethereum/forks/amsterdam/fork.py b/src/ethereum/forks/amsterdam/fork.py index e1454de093f..fc66f86744d 100644 --- a/src/ethereum/forks/amsterdam/fork.py +++ b/src/ethereum/forks/amsterdam/fork.py @@ -15,7 +15,7 @@ from typing import Final, List, Optional, Tuple, final from ethereum_rlp import rlp -from ethereum_types.bytes import Bytes, Bytes0 +from ethereum_types.bytes import Bytes from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U64, U256, Uint, ulen @@ -806,8 +806,6 @@ def process_unchecked_system_transaction( authorizations=(), index_in_block=None, tx_hash=None, - intrinsic_regular_gas=Uint(0), - intrinsic_state_gas=Uint(0), ) system_tx_message = Message( @@ -1034,7 +1032,7 @@ def process_transaction( sender = recover_sender(tx) intrinsic = validate_transaction(tx, sender) - intrinsic_gas = Uint(intrinsic.regular) + Uint(intrinsic.state) + intrinsic_gas = Uint(intrinsic.regular) ( effective_gas_price, @@ -1098,25 +1096,12 @@ def process_transaction( authorizations=authorizations, index_in_block=index, tx_hash=get_transaction_hash(encode_transaction(tx)), - intrinsic_regular_gas=intrinsic.regular, - intrinsic_state_gas=intrinsic.state, ) - message = prepare_message( - block_env, - tx_env, - tx, - ) + message = prepare_message(block_env, tx_env, tx) tx_output = process_message_call(message) - if isinstance(tx.to, Bytes0) and ( - tx_output.error is not None or tx_output.created_target_alive - ): - new_account_refund = StateGasCosts.NEW_ACCOUNT - tx_output.state_gas_left += new_account_refund - tx_output.state_refund += new_account_refund - tx_gas_used_before_refund = ( tx.gas - tx_output.gas_left - tx_output.state_gas_left ) @@ -1142,15 +1127,9 @@ def process_transaction( # transfer miner fees create_ether(tx_state, block_env.coinbase, U256(transaction_fee)) - tx_state_gas = ( - int(tx_env.intrinsic_state_gas) - + tx_output.state_gas_used - - int(tx_output.state_refund) - ) + tx_state_gas = tx_output.state_gas_used # The calldata floor binds the regular-gas dimension: subtract state gas # first so the floor is not discounted by a transaction's state spending. - # Defensive guard for Uint conversion: State refunds never exceed - # the state charges so the value is non-negative. tx_regular_gas = max( tx_gas_used_before_refund - Uint(max(0, tx_state_gas)), intrinsic.calldata_floor, diff --git a/src/ethereum/forks/amsterdam/transactions.py b/src/ethereum/forks/amsterdam/transactions.py index d876433ab95..ceae8f63168 100644 --- a/src/ethereum/forks/amsterdam/transactions.py +++ b/src/ethereum/forks/amsterdam/transactions.py @@ -25,12 +25,7 @@ InitCodeTooLargeError, TransactionTypeError, ) -from .fork_types import ( - Authorization, - RegularGas, - StateGas, - VersionedHash, -) +from .fork_types import Authorization, RegularGas, VersionedHash @final @@ -41,14 +36,6 @@ class IntrinsicGasCost: regular: RegularGas """Regular execution gas (calldata, base cost, access list, etc.).""" - state: StateGas - """ - State growth gas (account creation, storage set, authorization) per - [EIP-8037]. - - [EIP-8037]: https://eips.ethereum.org/EIPS/eip-8037 - """ - calldata_floor: RegularGas """ Minimum gas cost based on calldata size per [EIP-7623]. @@ -610,7 +597,7 @@ def validate_transaction(tx: Transaction, sender: Address) -> IntrinsicGasCost: from .vm.interpreter import MAX_INIT_CODE_SIZE intrinsic = calculate_intrinsic_cost(tx, sender) - intrinsic_gas = Uint(intrinsic.regular) + Uint(intrinsic.state) + intrinsic_gas = Uint(intrinsic.regular) if intrinsic_gas > tx.gas: raise InsufficientTransactionGasError("Insufficient intrinsic gas") if intrinsic.calldata_floor > tx.gas: @@ -649,29 +636,28 @@ def calculate_intrinsic_cost( The intrinsic cost includes: 1. Sender cost (`TX_BASE`). 2. Recipient cost (`COLD_ACCOUNT_ACCESS` for a non-self-transfer - call, or `CREATE_ACCESS` plus `NEW_ACCOUNT` state gas for a - contract creation). + call, or `CREATE_ACCESS` for a contract creation). The created + account's `NEW_ACCOUNT` state gas is state-dependent and is + charged at the top frame, not here. 3. Value cost (`TRANSFER_LOG_COST`, plus `TX_VALUE_COST` for a non-self-transfer call) when ``tx.value > 0``. 4. Calldata cost (zero and non-zero bytes). 5. Access list entries (if applicable). - 6. Authorizations (if applicable). + 6. Authorizations (if applicable): only the state-independent base + cost (`REGULAR_PER_AUTH_BASE_COST`) per tuple. The + state-dependent account-creation and delegation-write costs are + charged at the top frame by `set_delegation`. Self-transfers (``sender == tx.to``) skip the recipient and value charges. This function takes a transaction and its sender as parameters and - returns the intrinsic regular gas cost, the intrinsic state gas cost, - and the minimum (floor) gas cost based on the calldata size. The floor - is anchored on the regular-gas portion of items 1 to 3 above rather - than `TX_BASE` alone, so it never undercuts the transaction's own - intrinsic base. - """ - from .vm.gas import ( - GasCosts, - StateGasCosts, - init_code_cost, - ) + returns the intrinsic regular gas cost and the minimum (floor) gas + cost based on the calldata size. The floor is anchored on the + regular-gas portion of items 1 to 3 above rather than `TX_BASE` + alone, so it never undercuts the transaction's own intrinsic base. + """ + from .vm.gas import GasCosts, init_code_cost tokens_in_calldata = count_tokens_in_data(tx.data) @@ -681,12 +667,10 @@ def calculate_intrinsic_cost( is_self_transfer = tx.to == sender recipient_regular_gas = Uint(0) - recipient_state_gas = Uint(0) init_code_gas = Uint(0) if is_create: recipient_regular_gas = GasCosts.CREATE_ACCESS init_code_gas = init_code_cost(ulen(tx.data)) - recipient_state_gas = StateGasCosts.NEW_ACCOUNT if tx.value > U256(0): recipient_regular_gas += GasCosts.TRANSFER_LOG_COST elif not is_self_transfer: @@ -712,15 +696,11 @@ def calculate_intrinsic_cost( # Data token floor cost for access list bytes. access_list_cost += tokens_in_access_list * GasCosts.TX_DATA_TOKEN_FLOOR - auth_regular_gas = Uint(0) - auth_state_gas = Uint(0) + auth_cost = Uint(0) if isinstance(tx, SetCodeTransaction): - auth_regular_gas = ( - GasCosts.ACCOUNT_WRITE + GasCosts.REGULAR_PER_AUTH_BASE_COST - ) * ulen(tx.authorizations) - auth_state_gas = ( - StateGasCosts.NEW_ACCOUNT + StateGasCosts.AUTH_BASE - ) * ulen(tx.authorizations) + auth_cost = GasCosts.REGULAR_PER_AUTH_BASE_COST * ulen( + tx.authorizations + ) # EIP-7976 floor tokens: all calldata bytes count uniformly. floor_tokens_in_calldata = ulen(tx.data) * GasCosts.TX_DATA_TOKEN_STANDARD @@ -737,19 +717,14 @@ def calculate_intrinsic_cost( total_floor_tokens * GasCosts.TX_DATA_TOKEN_FLOOR + base_regular_gas ) - intrinsic_regular_gas = ( - base_regular_gas - + init_code_gas - + data_cost - + access_list_cost - + auth_regular_gas - ) - - intrinsic_state_gas = recipient_state_gas + auth_state_gas - return IntrinsicGasCost( - regular=RegularGas(intrinsic_regular_gas), - state=StateGas(intrinsic_state_gas), + regular=RegularGas( + base_regular_gas + + init_code_gas + + data_cost + + access_list_cost + + auth_cost + ), calldata_floor=RegularGas(data_floor_gas_cost), ) diff --git a/src/ethereum/forks/amsterdam/utils/message.py b/src/ethereum/forks/amsterdam/utils/message.py index 0c442e007d5..a0387240d0b 100644 --- a/src/ethereum/forks/amsterdam/utils/message.py +++ b/src/ethereum/forks/amsterdam/utils/message.py @@ -17,7 +17,7 @@ from ethereum.state import Address -from ..state_tracker import get_account, get_code +from ..state_tracker import get_account from ..transactions import Transaction from ..vm import BlockEnvironment, Message, TransactionEnvironment from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS @@ -63,9 +63,7 @@ def prepare_message( elif isinstance(tx.to, Address): current_target = tx.to msg_data = tx.data - code = get_code( - tx_env.state, get_account(tx_env.state, tx.to).code_hash - ) + code = None code_address = tx.to else: raise AssertionError("Target must be address or empty bytes") diff --git a/src/ethereum/forks/amsterdam/vm/__init__.py b/src/ethereum/forks/amsterdam/vm/__init__.py index 0b9dae40e86..608eae89056 100644 --- a/src/ethereum/forks/amsterdam/vm/__init__.py +++ b/src/ethereum/forks/amsterdam/vm/__init__.py @@ -132,8 +132,6 @@ class TransactionEnvironment: authorizations: Tuple[Authorization, ...] index_in_block: Optional[Uint] tx_hash: Optional[Hash32] - intrinsic_regular_gas: Uint - intrinsic_state_gas: Uint @final @@ -153,7 +151,7 @@ class Message: value: U256 data: Bytes code_address: Optional[Address] - code: Bytes + code: Optional[Bytes] depth: Uint should_transfer_value: bool is_static: bool @@ -187,6 +185,13 @@ class Evm: accessed_storage_keys: Set[Tuple[Address, Bytes32]] regular_gas_used: Uint = Uint(0) state_gas_spilled: Uint = Uint(0) + committed_state_gas: int = 0 + """ + State gas locked in by [`commit_frame_state_gas`] because the state + it paid for outlives a later failure in the frame. + + [`commit_frame_state_gas`]: ref:ethereum.forks.amsterdam.vm.commit_frame_state_gas + """ # noqa: E501 def credit_state_gas_refund(evm: Evm, amount: StateGas) -> None: @@ -256,11 +261,14 @@ def refill_frame_state_gas(evm: Evm) -> None: def frame_state_gas_used(evm: Evm) -> int: """ - Return the net state gas consumed by a finished frame. + Return the net state gas consumed by a finished frame, including + any state gas committed as non-refillable earlier in the frame. - Equal to the reservoir drawn down ([`state_gas_reservoir`][sgr] at entry - minus the reservoir now) plus [`state_gas_spilled`][sgs]. May be negative - when refunds exceed charges. + Equal to the reservoir drawn down ([`state_gas_reservoir`][sgr] at + the last commit -- or frame entry, absent one -- minus the + reservoir now) plus [`state_gas_spilled`][sgs] plus + [`committed_state_gas`][csg]. May be negative when refunds exceed + charges. Parameters ---------- @@ -269,15 +277,45 @@ def frame_state_gas_used(evm: Evm) -> int: [sgr]: ref:ethereum.forks.amsterdam.vm.Message.state_gas_reservoir [sgs]: ref:ethereum.forks.amsterdam.vm.Evm.state_gas_spilled + [csg]: ref:ethereum.forks.amsterdam.vm.Evm.committed_state_gas """ return ( int(evm.message.state_gas_reservoir) - int(evm.state_gas_left) + int(evm.state_gas_spilled) + + evm.committed_state_gas ) +def commit_frame_state_gas(evm: Evm) -> None: + """ + Mark the state gas consumed so far as non-refillable and reset the + refill baseline. + + The state this gas paid for (the delegations applied by + [`set_delegation`][sd]) outlives a later failure of the dispatched + code, so a subsequent [`refill_frame_state_gas`][refill] must not + credit it back. The consumption so far is folded into + [`committed_state_gas`][csg] and the reservoir baseline moves down + to the current level, so only charges made after this commit are + refillable. + + Parameters + ---------- + evm : + The frame whose state gas consumption is committed. + + [sd]: ref:ethereum.forks.amsterdam.vm.eoa_delegation.set_delegation + [refill]: ref:ethereum.forks.amsterdam.vm.refill_frame_state_gas + [csg]: ref:ethereum.forks.amsterdam.vm.Evm.committed_state_gas + + """ + evm.committed_state_gas = frame_state_gas_used(evm) + evm.message.state_gas_reservoir = evm.state_gas_left + evm.state_gas_spilled = Uint(0) + + def incorporate_child_on_error( evm: Evm, child_evm: Evm, diff --git a/src/ethereum/forks/amsterdam/vm/eoa_delegation.py b/src/ethereum/forks/amsterdam/vm/eoa_delegation.py index 2060d5465d7..7454ee0f115 100644 --- a/src/ethereum/forks/amsterdam/vm/eoa_delegation.py +++ b/src/ethereum/forks/amsterdam/vm/eoa_delegation.py @@ -2,18 +2,17 @@ Set EOA account code. """ -from typing import Optional, Tuple +from typing import Optional, Set, Tuple from ethereum_rlp import rlp -from ethereum_types.bytes import Bytes from ethereum_types.numeric import U64, U256, Uint from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import keccak256 -from ethereum.exceptions import InvalidBlock, InvalidSignatureError +from ethereum.exceptions import InvalidSignatureError from ethereum.state import Address -from ..fork_types import Authorization, StateGas +from ..fork_types import Authorization from ..state_tracker import ( account_exists, get_account, @@ -26,6 +25,8 @@ from ..vm.gas import ( GasCosts, StateGasCosts, + charge_gas, + charge_state_gas, ) from . import Evm, Message @@ -159,12 +160,12 @@ def calculate_delegation_cost( def validate_authorization( message: Message, auth: Authorization -) -> None | Tuple[Address, Bytes]: +) -> Optional[Address]: """ Check if the given `Authorization` is valid against the current state. - Returns the `authority` address and its code, or `None` if the - validation was unsuccessful. + Returns the `authority` address, or `None` if the validation was + unsuccessful. """ tx_state = message.tx_env.state @@ -191,56 +192,67 @@ def validate_authorization( if authority_nonce != auth.nonce: return None - return (authority, authority_code) + return authority -def set_delegation(message: Message) -> Tuple[Uint, Uint]: +def set_delegation(evm: Evm) -> None: """ - Set the delegation code for the authorities in the message. - - Refills `StateGasCosts.NEW_ACCOUNT` when the authority's account - leaf already exists, and `StateGasCosts.AUTH_BASE` when its code - slot already holds a delegation indicator. When the authority leaf - already exists, the worst-case `GasCosts.ACCOUNT_WRITE` charged in - the intrinsic cost is also refunded to the regular-gas refund - counter. The totals are returned so block accounting can subtract - the state refill from `tx_state_gas` and apply the regular refund. + Apply the EIP-7702 authorizations and charge their state-dependent + costs at the top frame. + + Each valid authorization is charged, on top of the + state-independent ``GasCosts.REGULAR_PER_AUTH_BASE_COST`` already + paid in the intrinsic cost: + + - ``StateGasCosts.NEW_ACCOUNT`` (state) when the authority's + account leaf does not yet exist. + - ``GasCosts.ACCOUNT_WRITE`` (regular) when applying the + authorization is the transaction's first write to the authority's + leaf. The sender's leaf was already written at inclusion (priced + into ``TX_BASE``), so a self-sponsored authority pays no + ``ACCOUNT_WRITE``, and repeated authorizations on one authority + pay it once. + - ``StateGasCosts.AUTH_BASE`` (state) when a net-new delegation + indicator is written: the authority held no delegation before the + transaction, none was set for it earlier in the transaction, and + this authorization sets one. It is charged at most once per + authority and is never credited back -- a delegation set and then + cleared in the same transaction keeps its charge. + + These costs depend on the authority's current state and so cannot + be charged in the intrinsic cost. Insufficient gas raises an + ``OutOfGasError``; the caller rolls back the authorizations applied + so far and halts the top frame. Parameters ---------- - message : - Transaction specific items. - - Returns - ------- - state_refund : `Uint` - Total state gas refunded across all processed authorizations. - regular_refund : `Uint` - Total regular gas (`ACCOUNT_WRITE`) refunded for authorities - whose account leaf already existed. + evm : + The top-level transaction frame. """ + message = evm.message tx_state = message.tx_env.state - state_refund = Uint(0) - regular_refund = Uint(0) + # Accounts this transaction has already written: the sender's leaf + # was written at inclusion (nonce bump and fee deduction). The + # recipient is written when value is transferred. + written_accounts: Set[Address] = {message.tx_env.origin} + if evm.message.tx_env.value > U256(0): + written_accounts.add(evm.message.current_target) + # Authorities a delegation was set for earlier in this transaction. + delegation_set_for: Set[Address] = set() for auth in message.tx_env.authorizations: match validate_authorization(message, auth): case None: - refund = StateGasCosts.AUTH_BASE + StateGasCosts.NEW_ACCOUNT - message.state_gas_reservoir += refund - state_refund += refund - regular_refund += GasCosts.ACCOUNT_WRITE continue - case (authority, authority_code): + case authority: pass - refund = StateGas(Uint(0)) + if not account_exists(tx_state, authority): + charge_state_gas(evm, StateGasCosts.NEW_ACCOUNT) - if account_exists(tx_state, authority): - refund += StateGasCosts.NEW_ACCOUNT - # The new-account ACCOUNT_WRITE charged at intrinsic time is - # not needed: refund it to the regular refund counter. - regular_refund += GasCosts.ACCOUNT_WRITE + if authority not in written_accounts: + charge_gas(evm, GasCosts.ACCOUNT_WRITE) + written_accounts.add(authority) pre_state_authority_account = get_pre_state_account( tx_state, authority @@ -248,35 +260,15 @@ def set_delegation(message: Message) -> Tuple[Uint, Uint]: pre_state_authority_code = get_code( tx_state, pre_state_authority_account.code_hash ) - delegated_before_tx = is_valid_delegation(pre_state_authority_code) - delegated_now = is_valid_delegation(authority_code) if auth.address == NULL_ADDRESS: - refund += StateGasCosts.AUTH_BASE - - if delegated_now and not delegated_before_tx: - refund += StateGasCosts.AUTH_BASE - code_to_set = b"" else: + if not delegated_before_tx and authority not in delegation_set_for: + charge_state_gas(evm, StateGasCosts.AUTH_BASE) + delegation_set_for.add(authority) code_to_set = EOA_DELEGATION_MARKER + auth.address - if delegated_now or delegated_before_tx: - refund += StateGasCosts.AUTH_BASE - set_code(tx_state, authority, code_to_set) increment_nonce(tx_state, authority) - - message.state_gas_reservoir += refund - state_refund += refund - - if message.code_address is None: - raise InvalidBlock("Invalid type 4 transaction: no target") - - message.code = get_code( - tx_state, - get_account(tx_state, message.code_address).code_hash, - ) - - return state_refund, regular_refund diff --git a/src/ethereum/forks/amsterdam/vm/interpreter.py b/src/ethereum/forks/amsterdam/vm/interpreter.py index 921873a06bd..d24f930726c 100644 --- a/src/ethereum/forks/amsterdam/vm/interpreter.py +++ b/src/ethereum/forks/amsterdam/vm/interpreter.py @@ -18,7 +18,7 @@ from ethereum_types.numeric import U256, Uint, ulen from ethereum.exceptions import EthereumException -from ethereum.state import Address +from ethereum.state import EMPTY_ACCOUNT, Address from ethereum.trace import ( EvmStop, OpEnd, @@ -38,6 +38,7 @@ destroy_storage, get_account, get_code, + get_pre_state_account, increment_nonce, is_account_alive, mark_account_created, @@ -56,6 +57,7 @@ from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS from . import ( Evm, + commit_frame_state_gas, emit_transfer_log, frame_state_gas_used, refill_frame_state_gas, @@ -93,12 +95,6 @@ class MessageCallOutput: 6. `return_data`: The output of the execution. 7. `regular_gas_used`: Regular gas used during execution. 8. `state_gas_used`: State gas used during execution. - 9. `state_refund`: State gas refunded by `set_delegation` for - authorities that already existed in state. Subtracted from - `tx_state_gas` in block accounting so `block.gas_used` - matches the receipt `cumulative_gas_used`. - 10. `created_target_alive`: Whether a top-level creation - transaction targeted an already-existent account. """ gas_left: Uint @@ -110,8 +106,6 @@ class MessageCallOutput: state_gas_left: Uint regular_gas_used: Uint state_gas_used: int - state_refund: Uint - created_target_alive: bool def process_message_call(message: Message) -> MessageCallOutput: @@ -131,12 +125,8 @@ def process_message_call(message: Message) -> MessageCallOutput: """ tx_state = message.tx_env.state - refund_counter = U256(0) - state_refund = Uint(0) - target_alive = False if message.target == Bytes0(b""): if account_deployable(tx_state, message.current_target): - target_alive = is_account_alive(tx_state, message.current_target) evm = process_create_message(message) else: return MessageCallOutput( @@ -149,33 +139,22 @@ def process_message_call(message: Message) -> MessageCallOutput: state_gas_left=message.state_gas_reservoir, regular_gas_used=message.gas, state_gas_used=0, - state_refund=Uint(0), - created_target_alive=False, ) else: - if message.tx_env.authorizations != (): - auth_state_refund, auth_regular_refund = set_delegation(message) - state_refund += auth_state_refund - refund_counter += U256(auth_regular_refund) - - delegated_address = get_delegated_code_address(message.code) - if delegated_address is not None: - message.disable_precompiles = True - message.code = get_code( - tx_state, - get_account(tx_state, delegated_address).code_hash, - ) - message.code_address = delegated_address - + # Authorizations and delegation resolution are handled at the + # top frame inside ``process_message`` (depth 0), so their + # state-dependent gas charges go through the EVM gas pools and + # an out-of-gas there halts the frame cleanly. evm = process_message(message) if evm.error: logs: Tuple[Log, ...] = () accounts_to_delete = set() + refund_counter = U256(0) else: logs = evm.logs accounts_to_delete = evm.accounts_to_delete - refund_counter += U256(evm.refund_counter) + refund_counter = U256(evm.refund_counter) tx_end = TransactionEnd( int(message.gas) - int(evm.gas_left), evm.output, evm.error @@ -192,8 +171,6 @@ def process_message_call(message: Message) -> MessageCallOutput: state_gas_left=evm.state_gas_left, regular_gas_used=evm.regular_gas_used, state_gas_used=frame_state_gas_used(evm), - state_refund=state_refund, - created_target_alive=target_alive, ) @@ -267,6 +244,70 @@ def process_create_message(message: Message) -> Evm: return evm +def prepare_dispatch(evm: Evm) -> None: + """ + Charge the state-dependent dispatch costs and resolve the code the + top frame will run. + + Runs at the top frame (depth 0), after any EIP-7702 authorizations + have been applied by ``set_delegation`` and before the call is + dispatched: + + - charges the ``NEW_ACCOUNT`` state gas for a contract creation + whose target leaf does not yet exist, or for a value transfer to + a recipient that is not yet alive; and + - resolves a delegation on the recipient, charging the warm or + cold account access and pointing the frame at the delegated + code. + + This function must not mutate the transaction state. Every charge + here pays for state that only materializes inside the dispatched + frame and rolls back with it, so these charges stay refillable -- + unlike the ``set_delegation`` charges, whose state outlives a + dispatch failure and whose gas the caller folds into the frame + baseline. The no-mutation rule is also what keeps the caller's + execution snapshot equal to the state at that fold. + + Insufficient gas raises an ``ExceptionalHalt``; the caller rolls + back the whole preparation -- including the applied authorizations + -- and halts the frame without dispatching. + """ + message = evm.message + tx_state = message.tx_env.state + + if message.target == Bytes0(b""): + if ( + get_pre_state_account(tx_state, message.current_target) + == EMPTY_ACCOUNT + ): + charge_state_gas(evm, StateGasCosts.NEW_ACCOUNT) + else: + recipient = message.current_target + if message.value > U256(0) and not is_account_alive( + tx_state, recipient + ): + charge_state_gas(evm, StateGasCosts.NEW_ACCOUNT) + recipient_code = get_code( + tx_state, get_account(tx_state, recipient).code_hash + ) + delegated_address = get_delegated_code_address(recipient_code) + if delegated_address is not None: + if delegated_address in evm.accessed_addresses: + charge_gas(evm, GasCosts.WARM_ACCESS) + else: + charge_gas(evm, GasCosts.COLD_ACCOUNT_ACCESS) + evm.accessed_addresses.add(delegated_address) + + message.disable_precompiles = True + message.code_address = delegated_address + message.code = get_code( + tx_state, + get_account(tx_state, delegated_address).code_hash, + ) + else: + message.code = recipient_code + + def process_message(message: Message) -> Evm: """ Move ether and execute the relevant code. @@ -286,16 +327,14 @@ def process_message(message: Message) -> Evm: if message.depth > STACK_DEPTH_LIMIT: raise StackDepthLimitError("Stack depth limit reached") - code = message.code - valid_jump_destinations = get_valid_jump_destinations(code) evm = Evm( pc=Uint(0), stack=[], memory=bytearray(), - code=code, + code=Bytes(b""), gas_left=message.gas, state_gas_left=message.state_gas_reservoir, - valid_jump_destinations=valid_jump_destinations, + valid_jump_destinations=set(), logs=(), refund_counter=0, running=True, @@ -308,24 +347,39 @@ def process_message(message: Message) -> Evm: accessed_storage_keys=message.accessed_storage_keys, ) + if message.depth == Uint(0): + prep_snapshot = copy_tx_state(tx_state) + prep_reservoir = message.state_gas_reservoir + try: + if message.tx_env.authorizations != (): + set_delegation(evm) + # The applied delegations outlive a failure of the + # dispatched code, so their state gas must not refill + # with it. + commit_frame_state_gas(evm) + prepare_dispatch(evm) + except ExceptionalHalt as error: + evm_trace(evm, OpException(error)) + restore_tx_state(tx_state, prep_snapshot) + # The rollback reverts any applied delegations, so the + # commit above is undone with it and every state charge is + # refilled. + message.state_gas_reservoir = prep_reservoir + evm.committed_state_gas = 0 + refill_frame_state_gas(evm) + evm.regular_gas_used += evm.gas_left + evm.gas_left = Uint(0) + evm.error = error + return evm + + assert message.code is not None + evm.code = message.code + evm.valid_jump_destinations = get_valid_jump_destinations(message.code) + snapshot = copy_tx_state(tx_state) # Execute message code and handle errors try: - if message.depth == Uint(0) and message.target != Bytes0(b""): - recipient = message.current_target - if message.value > U256(0) and not is_account_alive( - tx_state, recipient - ): - charge_state_gas(evm, StateGasCosts.NEW_ACCOUNT) - recipient_code = get_code( - tx_state, get_account(tx_state, recipient).code_hash - ) - delegated_address = get_delegated_code_address(recipient_code) - if delegated_address is not None: - charge_gas(evm, GasCosts.COLD_ACCOUNT_ACCESS) - evm.accessed_addresses.add(delegated_address) - if message.should_transfer_value and message.value != 0: move_ether( tx_state, diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/helpers.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/helpers.py index 4c90175c2a5..6a61ddbb6b4 100644 --- a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/helpers.py +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/helpers.py @@ -1,10 +1,23 @@ """Shared helpers for EIP-2780 tests.""" -from execution_testing import Address, Alloc, Op, RecipientType +from dataclasses import dataclass +from enum import Enum, auto + +from execution_testing import ( + EOA, + Account, + Address, + Alloc, + AuthorizationTuple, + Fork, + Op, + RecipientType, +) from ...prague.eip7702_set_code_tx.spec import Spec as Spec7702 EOA_INITIAL_BALANCE = 100 +NULL_ADDRESS = Address(0) RECIPIENT_TYPES_NON_CREATE = [ RecipientType.EOA, @@ -15,6 +28,192 @@ ] +class AuthorizationAction(Enum): + """The action an EIP-7702 authorization performs on its authority.""" + + CREATES_ACCOUNT = auto() + SETS_NEW_DELEGATION = auto() + SETS_DIFFERENT_DELEGATION = auto() + SETS_SAME_DELEGATION = auto() + CLEARS_DELEGATION = auto() + INVALID = auto() + + +@dataclass +class AuthorizationScenario: + """ + An EIP-7702 authorization together with the authority it acts on and + that authority's account before and after the authorization applies. + + ``applied_account`` is the authority's post-state once + ``set_delegation`` runs the authorization; ``original_account`` is + its pre-transaction state (``None`` when the leaf does not yet + exist), used to assert the authority is untouched when the + authorization is rolled back or skipped. + """ + + authority: EOA + authorization: AuthorizationTuple + applied_account: Account + original_account: Account | None + + +def build_authorization( + pre: Alloc, + action: AuthorizationAction, + *, + balance: int = EOA_INITIAL_BALANCE, +) -> AuthorizationScenario: + """ + Fund an authority in the pre-state and build an authorization acting + on it for the given ``action``, annotated with the + ``creates_account`` / ``writes_delegation`` / ``first_write`` flags + the top-frame calculators consume. + + The authority is always a third party (never ``tx.to`` or the + sender), so every valid action is the transaction's first write to + it and ``first_write`` keeps its ``True`` default; only ``INVALID`` + (never applied) clears it. Delegation targets are freshly deployed + and referenced by the returned accounts, so callers need only + assert against ``applied_account`` / ``original_account``. + """ + designation = Spec7702.delegation_designation + + match action: + case AuthorizationAction.CREATES_ACCOUNT: + target = pre.deploy_contract(code=Op.STOP) + authority = pre.fund_eoa(amount=0) + authorization = AuthorizationTuple( + address=target, + nonce=0, + signer=authority, + creates_account=True, + ) + applied = Account(nonce=1, balance=0, code=designation(target)) + original = None + case AuthorizationAction.SETS_NEW_DELEGATION: + target = pre.deploy_contract(code=Op.STOP) + assert balance > 0, ( + "An existing account must have non-zero balance" + ) + authority = pre.fund_eoa(amount=balance) + authorization = AuthorizationTuple( + address=target, + nonce=0, + signer=authority, + creates_account=False, + ) + applied = Account( + nonce=1, balance=balance, code=designation(target) + ) + original = Account(nonce=0, balance=balance, code=b"") + case AuthorizationAction.SETS_DIFFERENT_DELEGATION: + old_target = pre.deploy_contract(code=Op.STOP) + new_target = pre.deploy_contract(code=Op.STOP) + authority = pre.fund_eoa(amount=balance, delegation=old_target) + authorization = AuthorizationTuple( + address=new_target, + nonce=1, + signer=authority, + creates_account=False, + writes_delegation=False, + ) + applied = Account( + nonce=2, balance=balance, code=designation(new_target) + ) + original = Account( + nonce=1, balance=balance, code=designation(old_target) + ) + case AuthorizationAction.SETS_SAME_DELEGATION: + target = pre.deploy_contract(code=Op.STOP) + authority = pre.fund_eoa(amount=balance, delegation=target) + authorization = AuthorizationTuple( + address=target, + nonce=1, + signer=authority, + creates_account=False, + writes_delegation=False, + ) + applied = Account( + nonce=2, balance=balance, code=designation(target) + ) + original = Account( + nonce=1, balance=balance, code=designation(target) + ) + case AuthorizationAction.CLEARS_DELEGATION: + target = pre.deploy_contract(code=Op.STOP) + authority = pre.fund_eoa(amount=balance, delegation=target) + authorization = AuthorizationTuple( + address=NULL_ADDRESS, + nonce=1, + signer=authority, + creates_account=False, + writes_delegation=False, + ) + applied = Account(nonce=2, balance=balance, code=b"") + original = Account( + nonce=1, balance=balance, code=designation(target) + ) + case AuthorizationAction.INVALID: + target = pre.deploy_contract(code=Op.STOP) + assert balance > 0, ( + "An existing account must have non-zero balance" + ) + authority = pre.fund_eoa(amount=balance) + # The nonce does not match the authority's account nonce, so + # validate_authorization skips the authorization; only the + # intrinsic base cost is paid and the authority is untouched + # -- never applied, so never written. + authorization = AuthorizationTuple( + address=target, + nonce=99, + signer=authority, + creates_account=False, + writes_delegation=False, + first_write=False, + ) + applied = Account(nonce=0, balance=balance, code=b"") + original = Account(nonce=0, balance=balance, code=b"") + case _: + raise ValueError(f"unknown authorization action {action}") + + return AuthorizationScenario( + authority=authority, + authorization=authorization, + applied_account=applied, + original_account=original, + ) + + +def authorization_transaction_cost( + fork: Fork, authorization_list: list[AuthorizationTuple] +) -> int: + """ + Return the exact gas a value-free type-4 transaction to a plain + contract recipient consumes for the given authorizations. + + The recipient is a ``CONTRACT`` that runs no code, so no recipient + top-frame charge applies and the cost reduces to the intrinsic plus + the authorizations' own top-frame regular and state charges. Each + authorization's charge is driven by its ``creates_account`` / + ``writes_delegation`` / ``first_write`` annotations. + """ + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( + recipient_type=RecipientType.CONTRACT, + authorization_list_or_count=authorization_list, + return_cost_deducted_prior_execution=True, + ) + top_frame_regular = fork.transaction_top_frame_gas_calculator()( + recipient_type=RecipientType.CONTRACT, + authorizations=authorization_list, + ) + top_frame_state = fork.transaction_top_frame_state_gas( + recipient_type=RecipientType.CONTRACT, + authorizations=authorization_list, + ) + return intrinsic_gas + top_frame_regular + top_frame_state + + def setup_target( pre: Alloc, recipient_type: RecipientType, sender: Address ) -> Address: diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/spec.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/spec.py index e6fcb6bb528..5ac7b7e50bf 100644 --- a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/spec.py +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/spec.py @@ -13,5 +13,5 @@ class ReferenceSpec: ref_spec_2780 = ReferenceSpec( git_path="EIPS/eip-2780.md", - version="992074053f12f24fed9e6d6bf6099d3a44707dca", + version="e6d8f589d355e891c37ff479d3ce668352e5b1be", ) diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_authorization_charges.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_authorization_charges.py new file mode 100644 index 00000000000..152d1c45d7f --- /dev/null +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_authorization_charges.py @@ -0,0 +1,543 @@ +"""Charge accounting for EIP-7702 authorizations under EIP-2780.""" + +import pytest +from execution_testing import ( + Account, + Alloc, + AuthorizationTuple, + Fork, + Op, + RecipientType, + StateTestFiller, + Transaction, + TransactionReceipt, +) + +from ...prague.eip7702_set_code_tx.spec import Spec as Spec7702 +from .helpers import ( + NULL_ADDRESS, + AuthorizationAction, + authorization_transaction_cost, + build_authorization, +) +from .spec import ref_spec_2780 + +REFERENCE_SPEC_GIT_PATH = ref_spec_2780.git_path +REFERENCE_SPEC_VERSION = ref_spec_2780.version + +pytestmark = pytest.mark.valid_from("Amsterdam") + + +@pytest.mark.parametrize( + "action", list(AuthorizationAction), ids=lambda a: a.name.lower() +) +def test_single_authorization_charges( + fork: Fork, + pre: Alloc, + state_test: StateTestFiller, + action: AuthorizationAction, +) -> None: + """ + A single authorization on a third-party authority, spanning the + action space that drives its top-frame charge. + + Every valid action below is the transaction's first write to its + (third-party) authority, so each pays ``ACCOUNT_WRITE`` on top of + the charges listed: + + - ``CREATES_ACCOUNT``: the authority does not exist, so the + authorization also pays ``NEW_ACCOUNT`` (creation) and + ``AUTH_BASE`` (net-new delegation indicator). + - ``SETS_NEW_DELEGATION``: an existing empty-code EOA gains a + delegation, paying ``AUTH_BASE``. + - ``SETS_DIFFERENT_DELEGATION`` / ``SETS_SAME_DELEGATION``: an + already-delegated EOA is re-pointed (or re-pointed to the same + target); it was delegated before the transaction, so no + ``AUTH_BASE`` accrues. The nonce still advances. + - ``CLEARS_DELEGATION``: an already-delegated EOA is cleared (the + authorization target is the null address); no ``AUTH_BASE`` + accrues and the delegation code is removed. + - ``INVALID``: the authorization nonce does not match the + authority's account nonce, so ``validate_authorization`` skips it. + The intrinsic base cost is still paid; no top-frame charge (not + even ``ACCOUNT_WRITE``) accrues and the authority is untouched. + """ + sender = pre.fund_eoa() + recipient = pre.deploy_contract(code=Op.STOP) + + scenario = build_authorization(pre, action) + authorization_list = [scenario.authorization] + total_gas_cost = authorization_transaction_cost(fork, authorization_list) + + tx = Transaction( + sender=sender, + to=recipient, + value=0, + authorization_list=authorization_list, + gas_limit=total_gas_cost, + expected_receipt=TransactionReceipt( + cumulative_gas_used=total_gas_cost, + ), + ) + + post = { + scenario.authority: scenario.applied_account, + } + + state_test(pre=pre, tx=tx, post=post) + + +@pytest.mark.parametrize( + "scenario", + [ + "set_then_modify", + "create_then_modify", + "clear_then_set", + "different_accounts", + ], +) +def test_multi_authorization_intra_tx_state( + fork: Fork, + pre: Alloc, + state_test: StateTestFiller, + scenario: str, +) -> None: + """ + Two authorizations in one transaction, charged against the state + each leaves for the next. + + ``set_delegation`` applies authorizations in list order and reads + live state, so a second authorization on the same authority sees the + code the first installed: + + - ``set_then_modify``: the first sets a fresh delegation on an empty + EOA (paying ``AUTH_BASE``); the second re-points it. The authority + now has code, so the second pays no ``AUTH_BASE``. + - ``create_then_modify``: the first creates the authority and + delegates it (``NEW_ACCOUNT`` + ``ACCOUNT_WRITE`` + ``AUTH_BASE``); + the second re-points it and pays no further state charge. + - ``clear_then_set``: the first clears an existing delegation and + the second re-delegates. The authority was already delegated + before the transaction, so the indicator slot was already paid + for: neither authorization writes a net-new indicator and no + ``AUTH_BASE`` is charged. + - ``different_accounts``: the two authorizations touch distinct + authorities and are charged independently. + + Consecutive authorizations on one authority use consecutive nonces + (the first bumps the nonce), and the post-state confirms both were + applied rather than the second being silently skipped. + """ + sender = pre.fund_eoa() + recipient = pre.deploy_contract(code=Op.STOP) + + if scenario == "different_accounts": + first = build_authorization(pre, AuthorizationAction.CREATES_ACCOUNT) + second = build_authorization( + pre, AuthorizationAction.SETS_NEW_DELEGATION + ) + authorization_list = [first.authorization, second.authorization] + expected_authorities = { + first.authority: first.applied_account, + second.authority: second.applied_account, + } + else: + first_action = { + "set_then_modify": AuthorizationAction.SETS_NEW_DELEGATION, + "create_then_modify": AuthorizationAction.CREATES_ACCOUNT, + "clear_then_set": AuthorizationAction.CLEARS_DELEGATION, + }[scenario] + leg = build_authorization(pre, first_action) + new_target = pre.deploy_contract(code=Op.STOP) + + # The second authorization runs on the same authority right + # after the first, using the next nonce. The first already + # wrote the authority's leaf (no second ``ACCOUNT_WRITE``) and + # either set a delegation in this transaction or found one from + # before it, so the re-point writes no net-new indicator and + # pays no ``AUTH_BASE``. + applied_nonce = int(leg.applied_account.nonce) + second_auth = AuthorizationTuple( + address=new_target, + nonce=applied_nonce, + signer=leg.authority, + creates_account=False, + writes_delegation=False, + first_write=False, + ) + authorization_list = [leg.authorization, second_auth] + expected_authorities = { + leg.authority: Account( + nonce=applied_nonce + 1, + balance=int(leg.applied_account.balance), + code=Spec7702.delegation_designation(new_target), + ), + } + + total_gas_cost = authorization_transaction_cost(fork, authorization_list) + + tx = Transaction( + sender=sender, + to=recipient, + value=0, + authorization_list=authorization_list, + gas_limit=total_gas_cost, + expected_receipt=TransactionReceipt( + cumulative_gas_used=total_gas_cost, + ), + ) + + post = { + **expected_authorities, + } + + state_test(pre=pre, tx=tx, post=post) + + +def _intrinsic_gas( + fork: Fork, + authorization_count: int, + *, + recipient_type: RecipientType = RecipientType.CONTRACT, + sends_value: bool = False, +) -> int: + """Return the regular intrinsic gas deducted before execution.""" + return fork.transaction_intrinsic_cost_calculator()( + recipient_type=recipient_type, + sends_value=sends_value, + authorization_list_or_count=authorization_count, + return_cost_deducted_prior_execution=True, + ) + + +@pytest.mark.parametrize( + "authority_prestate", ["non_existent", "existing_eoa"] +) +def test_account_write_first_write_of_authority( + fork: Fork, + pre: Alloc, + state_test: StateTestFiller, + authority_prestate: str, +) -> None: + """ + ``ACCOUNT_WRITE`` is charged on the first write to the authority + within the transaction, independent of whether its account leaf + already exists. + + Applying an authorization writes the authority's leaf (code and + nonce), so the first authorization on any authority not yet written + in the transaction pays ``ACCOUNT_WRITE``: + + - ``non_existent``: the authority also pays ``NEW_ACCOUNT`` for the + fresh leaf (and ``AUTH_BASE`` for the net-new indicator). + - ``existing_eoa``: the leaf exists, but the delegation write is + still this transaction's first write to it, so ``ACCOUNT_WRITE`` + is charged all the same (plus ``AUTH_BASE``). + """ + sender = pre.fund_eoa() + recipient = pre.deploy_contract(code=Op.STOP) + + if authority_prestate == "non_existent": + scenario = build_authorization( + pre, AuthorizationAction.CREATES_ACCOUNT + ) + else: + scenario = build_authorization( + pre, AuthorizationAction.SETS_NEW_DELEGATION + ) + + authorization_list = [scenario.authorization] + total_gas_cost = authorization_transaction_cost(fork, authorization_list) + + tx = Transaction( + sender=sender, + to=recipient, + value=0, + authorization_list=authorization_list, + gas_limit=total_gas_cost, + expected_receipt=TransactionReceipt( + cumulative_gas_used=total_gas_cost, + ), + ) + + post = { + scenario.authority: scenario.applied_account, + } + + state_test(pre=pre, tx=tx, post=post) + + +def test_account_write_authority_is_sender( + fork: Fork, + pre: Alloc, + state_test: StateTestFiller, +) -> None: + """ + An authority that is the transaction sender pays no + ``ACCOUNT_WRITE``: the sender's account was already written at + inclusion (nonce bump and fee deduction, priced into ``TX_BASE``), + so the delegation write is not the first write to it within the + transaction. + + The self-sponsored authorization still pays ``AUTH_BASE`` for its + net-new delegation indicator. This case guards the first-write rule + against over-charging accounts the transaction has already paid to + write. + """ + sender = pre.fund_eoa() + recipient = pre.deploy_contract(code=Op.STOP) + delegation_target = pre.deploy_contract(code=Op.STOP) + + # The sender's nonce is bumped at inclusion, before authorizations + # are processed, so the self-sponsored authorization signs nonce 1. + authorization = AuthorizationTuple( + address=delegation_target, + nonce=1, + signer=sender, + first_write=False, + ) + + total_gas_cost = authorization_transaction_cost(fork, [authorization]) + + tx = Transaction( + sender=sender, + to=recipient, + value=0, + authorization_list=[authorization], + gas_limit=total_gas_cost, + expected_receipt=TransactionReceipt( + cumulative_gas_used=total_gas_cost, + ), + ) + + post = { + sender: Account( + nonce=2, + code=Spec7702.delegation_designation(delegation_target), + ), + } + + state_test(pre=pre, tx=tx, post=post) + + +@pytest.mark.parametrize( + "value", + [ + pytest.param(0, id="zero_value"), + pytest.param(1, id="non-zero_value"), + ], +) +def test_account_write_authority_is_recipient( + fork: Fork, + pre: Alloc, + state_test: StateTestFiller, + value: int, +) -> None: + """ + An authority that is also ``tx.to`` pays ``ACCOUNT_WRITE`` only when + the transaction moves no value to it. + + The charge depends on whether the transaction transfers value: + + - ``zero_value``: no value is transferred, so at + authorization-processing time ``tx.to`` has not been written yet + (only the sender is written at inclusion). The delegation write + is the transaction's first write to it, so ``ACCOUNT_WRITE`` is + charged. + - ``non-zero_value``: the transaction already pays to write + ``tx.to`` when it transfers value to it, so the delegation write + is not the first write and no ``ACCOUNT_WRITE`` accrues. + + This resolves the EIP text's "(i.e. the authority differs from + ``tx.to``)" parenthetical: the rule is first-write tracking, and + ``tx.to`` counts as pre-written only when the transaction moves + value to it. + + Either way the authorization writes a net-new delegation indicator + (``AUTH_BASE``), and after it applies the recipient is delegated, so + the top frame additionally resolves the delegation target at the + cold rate before dispatching its code (a ``STOP``). + """ + authority_initial_balance = 100 + sender = pre.fund_eoa() + delegation_target = pre.deploy_contract(code=Op.STOP) + recipient = pre.fund_eoa(amount=authority_initial_balance) + + authorization = AuthorizationTuple( + address=delegation_target, + nonce=0, + signer=recipient, + first_write=not bool(value), + ) + + # The recipient is delegated by the time the top frame resolves it, + # so model it as a 7702 delegation: the framework then charges the + # cold delegation-target access on top of the intrinsic recipient + # access, matching the spec's resolution of the freshly-set + # delegation. + recipient_type = RecipientType.DELEGATION_7702 + authorizations = [authorization] + top_frame_regular = fork.transaction_top_frame_gas_calculator()( + recipient_type=recipient_type, + authorizations=authorizations, + ) + top_frame_state = fork.transaction_top_frame_state_gas( + recipient_type=recipient_type, + authorizations=authorizations, + ) + total_gas_cost = ( + _intrinsic_gas( + fork, + 1, + recipient_type=recipient_type, + sends_value=bool(value), + ) + + top_frame_regular + + top_frame_state + ) + + tx = Transaction( + sender=sender, + to=recipient, + value=value, + authorization_list=[authorization], + gas_limit=total_gas_cost, + expected_receipt=TransactionReceipt( + cumulative_gas_used=total_gas_cost, + ), + ) + + post = { + recipient: Account( + nonce=1, + balance=authority_initial_balance + value, + code=Spec7702.delegation_designation(delegation_target), + ), + } + + state_test(pre=pre, tx=tx, post=post) + + +@pytest.mark.parametrize( + "scenario", + [ + "pre_tx_delegated_re_set", + "pre_tx_delegated_clear_then_set", + "multiple_sets", + "set_clear_cycles", + ], +) +def test_auth_base_net_new_only( + fork: Fork, + pre: Alloc, + state_test: StateTestFiller, + scenario: str, +) -> None: + """ + ``AUTH_BASE`` is charged only when a net-new delegation is set: the + authority held no delegation before the transaction, none was set + for it earlier in the transaction, and the current authorization + sets one. It is charged at most once per authority per transaction + and is never credited back. + + - ``pre_tx_delegated_re_set``: a pre-delegated authority is + re-pointed; the indicator bytes already exist, so no + ``AUTH_BASE``. The re-point is still the transaction's first + write to the authority, so ``ACCOUNT_WRITE`` applies. + - ``pre_tx_delegated_clear_then_set``: a pre-delegated authority is + cleared and then re-delegated in the same transaction. The + authority held a delegation before the transaction, so neither + authorization pays ``AUTH_BASE`` (and ``ACCOUNT_WRITE`` applies + once, at the clear -- the first write). + - ``multiple_sets``: an empty-code EOA is delegated and then + re-pointed. Only the first set is net-new: one ``AUTH_BASE``, and + one ``ACCOUNT_WRITE`` for the first write. + - ``set_clear_cycles``: an empty-code EOA is set, cleared, set, and + cleared again. The first set charges ``AUTH_BASE``; the clears + credit nothing back and the second set is not net-new (a + delegation was already set in this transaction). Exactly one + ``AUTH_BASE`` and one ``ACCOUNT_WRITE`` are paid even though the + authority ends the transaction with no delegation. + """ + authority_initial_balance = 100 + sender = pre.fund_eoa() + recipient = pre.deploy_contract(code=Op.STOP) + + target_a = pre.deploy_contract(code=Op.STOP) + target_b = pre.deploy_contract(code=Op.STOP) + + expected_code: bytes + if scenario == "pre_tx_delegated_re_set": + old_target = pre.deploy_contract(code=Op.STOP) + authority = pre.fund_eoa( + amount=authority_initial_balance, delegation=old_target + ) + auth_specs = [target_a] + first_nonce = 1 + expected_code = Spec7702.delegation_designation(target_a) + # Delegated before the transaction, so the re-point is not + # net-new: no AUTH_BASE. + net_new = [False] + elif scenario == "pre_tx_delegated_clear_then_set": + old_target = pre.deploy_contract(code=Op.STOP) + authority = pre.fund_eoa( + amount=authority_initial_balance, delegation=old_target + ) + auth_specs = [NULL_ADDRESS, target_a] + first_nonce = 1 + expected_code = Spec7702.delegation_designation(target_a) + # Delegated before the transaction, so neither the clear nor + # the re-set is net-new. + net_new = [False, False] + elif scenario == "multiple_sets": + authority = pre.fund_eoa(amount=authority_initial_balance) + auth_specs = [target_a, target_b] + first_nonce = 0 + expected_code = Spec7702.delegation_designation(target_b) + # Only the first set is net-new; the re-point is not. + net_new = [True, False] + else: # set_clear_cycles + authority = pre.fund_eoa(amount=authority_initial_balance) + auth_specs = [target_a, NULL_ADDRESS, target_b, NULL_ADDRESS] + first_nonce = 0 + expected_code = b"" + # Only the first set is net-new; the clears credit nothing and + # the second set is not net-new (already set in this tx). + net_new = [True, False, False, False] + + authorization_list = [ + AuthorizationTuple( + address=address, + nonce=first_nonce + offset, + signer=authority, + creates_account=False, + writes_delegation=net_new[offset], + # Only the first authorization writes the authority's leaf; + # later ones on the same authority are not first writes. + first_write=(offset == 0), + ) + for offset, address in enumerate(auth_specs) + ] + + total_gas_cost = authorization_transaction_cost(fork, authorization_list) + + tx = Transaction( + sender=sender, + to=recipient, + value=0, + authorization_list=authorization_list, + gas_limit=total_gas_cost, + expected_receipt=TransactionReceipt( + cumulative_gas_used=total_gas_cost, + ), + ) + + post = { + authority: Account( + nonce=first_nonce + len(auth_specs), + balance=authority_initial_balance, + code=expected_code, + ), + } + + state_test(pre=pre, tx=tx, post=post) diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_authorization_oog.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_authorization_oog.py new file mode 100644 index 00000000000..c9aab1ea12f --- /dev/null +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_authorization_oog.py @@ -0,0 +1,1492 @@ +""" +Out-of-gas rollback semantics for EIP-7702 authorizations under +EIP-2780. + +At the top frame, EIP-2780 charges each authorization's +state-dependent cost in ``set_delegation`` and then charges the +recipient's or contract-creation ``NEW_ACCOUNT`` and any +delegation-resolution access -- all before dispatching the call. Two +snapshots bound these two phases: + +- **Prep-phase OOG** -- a charge anywhere in the top-frame preparation + runs out: inside ``set_delegation``, on the recipient's + ``NEW_ACCOUNT``, or on the delegation-resolution access. The whole + preparation shares one snapshot, so every authorization applied so + far is rolled back and the frame halts without dispatching. The + transaction is still included and consumes its full regular budget; + a state-gas reservoir, whose charges are refilled with the rollback, + is returned to the sender in full. The sender nonce (bumped at + inclusion, before the snapshot) is not rolled back. +- **Execution-phase failure** -- the dispatched call itself reverts or + runs out of gas. A second snapshot is taken after preparation, so the + applied delegations persist, matching the EIP-7702 "dispatch reverts, + delegation remains" rule. + +Because the applied delegations persist across an execution-phase +failure, the state gas that paid for them (the authority's +``NEW_ACCOUNT`` leaf and ``AUTH_BASE`` indicator bytes) must stay +consumed: only state gas whose state effects are rolled back with the +frame (e.g. an ``SSTORE`` inside the dispatched call) is refilled. + +""" + +import pytest +from execution_testing import ( + EOA, + Account, + Address, + Alloc, + AuthorizationTuple, + BalAccountExpectation, + BalBalanceChange, + BalCodeChange, + BalNonceChange, + Block, + BlockAccessListExpectation, + BlockchainTestFiller, + Bytecode, + Environment, + Fork, + Header, + Op, + RecipientType, + StateTestFiller, + Transaction, + TransactionException, + TransactionReceipt, +) + +from ...prague.eip7702_set_code_tx.spec import Spec as Spec7702 +from .helpers import ( + EOA_INITIAL_BALANCE, + AuthorizationAction, + AuthorizationScenario, + build_authorization, +) +from .spec import ref_spec_2780 + +REFERENCE_SPEC_GIT_PATH = ref_spec_2780.git_path +REFERENCE_SPEC_VERSION = ref_spec_2780.version + +pytestmark = pytest.mark.valid_from("Amsterdam") + + +def _auth_top_frame_charges(fork: Fork, authorizations: list) -> int: + """ + Return the top-frame regular + state gas attributable to the + authorizations alone. + + Computed against a ``CONTRACT`` recipient, which contributes no + top-frame charge, so the result is exactly the sum of each + authorization's own ``ACCOUNT_WRITE`` / ``NEW_ACCOUNT`` / + ``AUTH_BASE``. Under the zero state reservoir these all draw from + ``gas_left``. + """ + regular = fork.transaction_top_frame_gas_calculator()( + recipient_type=RecipientType.CONTRACT, + authorizations=authorizations, + ) + state = fork.transaction_top_frame_state_gas( + recipient_type=RecipientType.CONTRACT, + authorizations=authorizations, + ) + return regular + state + + +def _intrinsic_regular( + fork: Fork, + authorization_list: list, + *, + recipient_type: RecipientType, + sends_value: bool = False, +) -> int: + """Return the regular intrinsic gas deducted before execution.""" + return fork.transaction_intrinsic_cost_calculator()( + recipient_type=recipient_type, + sends_value=sends_value, + authorization_list_or_count=authorization_list, + return_cost_deducted_prior_execution=True, + ) + + +def _applied_delegation_bal( + scenario: AuthorizationScenario, +) -> BalAccountExpectation: + """ + BAL entry for an authority whose applied delegation reaches the + post-state. + + The nonce bump and delegation-code write are applied before the + execution snapshot, so they also survive a dispatched frame's + revert or halt and must be recorded in the block access list. + """ + return BalAccountExpectation( + nonce_changes=[ + BalNonceChange( + block_access_index=1, + post_nonce=scenario.applied_account.nonce, + ) + ], + code_changes=[ + BalCodeChange( + block_access_index=1, + new_code=scenario.applied_account.code, + ) + ], + ) + + +@pytest.mark.parametrize( + "outcome", ["new_account", "account_write", "auth_base", "succeeds"] +) +def test_set_delegation_oog_charge_point( + fork: Fork, + pre: Alloc, + state_test: StateTestFiller, + outcome: str, +) -> None: + """ + OOG at each distinct charge point inside ``set_delegation`` rolls + the whole authorization phase back atomically. + + The first authorization creates and delegates its authority in full + (exercising ``NEW_ACCOUNT`` + ``ACCOUNT_WRITE`` + ``AUTH_BASE``). The + ``gas_limit`` then starves the second authorization at exactly the + parametrized charge: + + - ``new_account``: the second (a creation) runs out at its opening + ``NEW_ACCOUNT`` state charge. + - ``account_write``: the second covers ``NEW_ACCOUNT`` but runs out + at the following ``ACCOUNT_WRITE`` regular charge. + - ``auth_base``: the second (a delegation on an existing empty EOA) + covers its first-write ``ACCOUNT_WRITE`` but runs out at the + following ``AUTH_BASE`` state charge. + - ``succeeds``: as ``auth_base``, but with the one starved gas + restored the closing ``AUTH_BASE`` is covered exactly and both + authorizations apply, pinning the off-by-one boundary of the + last charge from above. + + In every out-of-gas case the transaction halts in + ``set_delegation`` and both authorizations are rolled back -- the + first, already applied, as well as the second -- so both + authorities return to their pre-tx state. The receipt shows the + full ``gas_limit`` consumed (exactly covered, in the ``succeeds`` + case) and the sender nonce is not rolled back. + + Both authorities are read during authorization validation before + the halt, so per EIP-7928 they still appear in the block access + list; only in the ``succeeds`` case do they record changes. The + recipient is only loaded by the top-frame dispatch, so it must be + absent whenever the halt precedes it. + """ + gas_costs = fork.gas_costs() + sender = pre.fund_eoa() + recipient = pre.deploy_contract(code=Op.STOP) + + first = build_authorization(pre, AuthorizationAction.CREATES_ACCOUNT) + if outcome in ("auth_base", "succeeds"): + second = build_authorization( + pre, AuthorizationAction.SETS_NEW_DELEGATION + ) + else: + second = build_authorization(pre, AuthorizationAction.CREATES_ACCOUNT) + + authorization_list = [first.authorization, second.authorization] + + intrinsic_regular = _intrinsic_regular( + fork, authorization_list, recipient_type=RecipientType.CONTRACT + ) + first_auth_charges = _auth_top_frame_charges(fork, [first.authorization]) + + # gas_left entering set_delegation is gas_limit - intrinsic_regular + # (the state reservoir is zero). The first authorization is applied + # in full; the second is starved by one gas at the target charge, + # after covering any charges that precede it within that same + # authorization -- or, for ``succeeds``, covered exactly. + if outcome == "new_account": + preceding = 0 + shortfall_charge = gas_costs.NEW_ACCOUNT + elif outcome == "account_write": + preceding = gas_costs.NEW_ACCOUNT + shortfall_charge = gas_costs.ACCOUNT_WRITE + else: # auth_base / succeeds + preceding = gas_costs.ACCOUNT_WRITE + shortfall_charge = gas_costs.AUTH_BASE + + gas_limit = ( + intrinsic_regular + first_auth_charges + preceding + shortfall_charge + ) + if outcome != "succeeds": + gas_limit -= 1 + + tx = Transaction( + sender=sender, + to=recipient, + value=0, + authorization_list=authorization_list, + gas_limit=gas_limit, + expected_receipt=TransactionReceipt( + cumulative_gas_used=gas_limit, + ), + ) + + post: dict[EOA, Account | None] + if outcome == "succeeds": + post = { + first.authority: first.applied_account, + second.authority: second.applied_account, + } + expected_block_access_list = BlockAccessListExpectation( + account_expectations={ + recipient: BalAccountExpectation.empty(), + first.authority: _applied_delegation_bal(first), + second.authority: _applied_delegation_bal(second), + } + ) + else: + post = { + first.authority: first.original_account, + second.authority: second.original_account, + } + # An implementation recording accesses only for dispatched + # frames would drop the authority entries; one recording the + # recipient at inclusion would add it. Either forks on the BAL + # hash. + expected_block_access_list = BlockAccessListExpectation( + account_expectations={ + recipient: None, + first.authority: BalAccountExpectation.empty(), + second.authority: BalAccountExpectation.empty(), + } + ) + + state_test( + pre=pre, + tx=tx, + post=post, + expected_block_access_list=expected_block_access_list, + ) + + +@pytest.mark.parametrize( + "first_action", + [ + AuthorizationAction.SETS_NEW_DELEGATION, + AuthorizationAction.SETS_DIFFERENT_DELEGATION, + AuthorizationAction.SETS_SAME_DELEGATION, + AuthorizationAction.CLEARS_DELEGATION, + ], + ids=lambda a: a.name.lower(), +) +def test_set_delegation_oog_rolls_back_first_auth( + fork: Fork, + pre: Alloc, + state_test: StateTestFiller, + first_action: AuthorizationAction, +) -> None: + """ + Auth-phase rollback undoes every flavor of the surviving first + authorization's mutation. + + A second authorization (a creation) is starved at its opening + ``NEW_ACCOUNT`` charge, so the whole authorization phase rolls back. + The first authorization -- which applied in full before the OOG -- + is parametrized across the action space, and the post-state confirms + its mutation is fully reverted: + + - ``SETS_NEW_DELEGATION``: the fresh delegation and nonce bump are + undone (authority back to an empty EOA). + - ``SETS_DIFFERENT_DELEGATION`` / ``SETS_SAME_DELEGATION``: the + re-point and nonce bump are undone (authority back to its original + delegation). + - ``CLEARS_DELEGATION``: the clear and nonce bump are undone + (authority's original delegation restored). + + The creation-first case is covered by + ``test_set_delegation_oog_charge_point[new_account]``. + + Both authorities are read during validation before the halt and + stay in the block access list with no recorded changes; the + recipient, never loaded before the halt, must be absent. + """ + gas_costs = fork.gas_costs() + sender = pre.fund_eoa() + recipient = pre.deploy_contract(code=Op.STOP) + + first = build_authorization(pre, first_action) + second = build_authorization(pre, AuthorizationAction.CREATES_ACCOUNT) + authorization_list = [first.authorization, second.authorization] + + intrinsic_regular = _intrinsic_regular( + fork, authorization_list, recipient_type=RecipientType.CONTRACT + ) + first_auth_charges = _auth_top_frame_charges(fork, [first.authorization]) + + # The first authorization applies in full; the second (a creation) + # runs out at its opening NEW_ACCOUNT state charge, rolling back the + # whole authorization phase. + gas_limit = ( + intrinsic_regular + first_auth_charges + gas_costs.NEW_ACCOUNT - 1 + ) + + tx = Transaction( + sender=sender, + to=recipient, + value=0, + authorization_list=authorization_list, + gas_limit=gas_limit, + expected_receipt=TransactionReceipt( + cumulative_gas_used=gas_limit, + ), + ) + + post = { + first.authority: first.original_account, + second.authority: second.original_account, + } + + expected_block_access_list = BlockAccessListExpectation( + account_expectations={ + recipient: None, + first.authority: BalAccountExpectation.empty(), + second.authority: BalAccountExpectation.empty(), + } + ) + + state_test( + pre=pre, + tx=tx, + post=post, + expected_block_access_list=expected_block_access_list, + ) + + +@pytest.mark.parametrize( + "succeeds", + [ + pytest.param(False, id="fails"), + pytest.param(True, id="succeeds"), + ], +) +@pytest.mark.parametrize( + "recipient_charge", ["new_account", "delegation_access"] +) +def test_recipient_charge_oog_rolls_back_delegations( + fork: Fork, + pre: Alloc, + state_test: StateTestFiller, + recipient_charge: str, + succeeds: bool, +) -> None: + """ + A recipient top-frame charge running out of gas rolls back the + already-applied delegations, because it shares the preparation + snapshot with ``set_delegation``. + + Two valid authorizations on third-party authorities are paid in + full, then the recipient's own top-frame charge is starved by one + gas: + + - ``new_account``: value moves to an EIP-161-empty recipient, whose + ``NEW_ACCOUNT`` state charge runs out. + - ``delegation_access``: the recipient is a pre-existing delegation + whose top-frame ``COLD_ACCOUNT_ACCESS`` charge runs out. + + The recipient charge is part of the top-frame preparation, so its + out-of-gas unwinds the whole preparation: both authorities return to + their pre-transaction state. The transaction is still included, the + receipt shows the full ``gas_limit`` consumed, and the recipient + itself is unchanged. + + The recipient and both authorities were accessed before the halt, + so per EIP-7928 all three must still appear in the block access + list, with no recorded changes. + + The ``succeeds`` control restores the one starved gas: the + recipient charge is covered exactly, the dispatch completes (the + recipient runs no code of its own), and the delegations -- and any + value moved -- stick, pinning the off-by-one boundary from above. + """ + gas_costs = fork.gas_costs() + sender = pre.fund_eoa() + + auth_a = build_authorization(pre, AuthorizationAction.CREATES_ACCOUNT) + auth_b = build_authorization(pre, AuthorizationAction.SETS_NEW_DELEGATION) + authorization_list = [auth_a.authorization, auth_b.authorization] + auth_charges = _auth_top_frame_charges(fork, authorization_list) + + recipient_bal = BalAccountExpectation.empty() + if recipient_charge == "new_account": + recipient = pre.fund_eoa(amount=0) + value = 1 + recipient_type = RecipientType.EMPTY_ACCOUNT + recipient_charge_gas = gas_costs.NEW_ACCOUNT + if succeeds: + expected_recipient: Account | None = Account(balance=value) + recipient_bal = BalAccountExpectation( + balance_changes=[ + BalBalanceChange(block_access_index=1, post_balance=value) + ] + ) + else: + expected_recipient = None + else: # delegation_access + delegated_to = pre.deploy_contract(code=Op.STOP) + recipient = pre.fund_eoa( + amount=EOA_INITIAL_BALANCE, delegation=delegated_to + ) + value = 0 + recipient_type = RecipientType.DELEGATION_7702 + recipient_charge_gas = gas_costs.COLD_ACCOUNT_ACCESS + expected_recipient = Account( + nonce=1, + balance=EOA_INITIAL_BALANCE, + code=Spec7702.delegation_designation(delegated_to), + ) + + intrinsic_regular = _intrinsic_regular( + fork, + authorization_list, + recipient_type=recipient_type, + sends_value=bool(value), + ) + + # Both authorizations apply, then the recipient's top-frame charge + # is starved by one gas -- or, with ``succeeds``, covered exactly. + # The charge shares the preparation snapshot, so its out-of-gas + # rolls the applied delegations back. + gas_limit = intrinsic_regular + auth_charges + recipient_charge_gas + if not succeeds: + gas_limit -= 1 + + tx = Transaction( + sender=sender, + to=recipient, + value=value, + authorization_list=authorization_list, + gas_limit=gas_limit, + expected_receipt=TransactionReceipt( + cumulative_gas_used=gas_limit, + ), + ) + + if succeeds: + post = { + auth_a.authority: auth_a.applied_account, + auth_b.authority: auth_b.applied_account, + recipient: expected_recipient, + } + expected_block_access_list = BlockAccessListExpectation( + account_expectations={ + recipient: recipient_bal, + auth_a.authority: _applied_delegation_bal(auth_a), + auth_b.authority: _applied_delegation_bal(auth_b), + } + ) + else: + post = { + auth_a.authority: auth_a.original_account, + auth_b.authority: auth_b.original_account, + recipient: expected_recipient, + } + expected_block_access_list = BlockAccessListExpectation( + account_expectations={ + recipient: BalAccountExpectation.empty(), + auth_a.authority: BalAccountExpectation.empty(), + auth_b.authority: BalAccountExpectation.empty(), + } + ) + + state_test( + pre=pre, + tx=tx, + post=post, + expected_block_access_list=expected_block_access_list, + ) + + +@pytest.mark.parametrize( + "failure_point", + [ + "set_delegation_oog", + "dispatch_charge_oog", + "execution_halt", + "execution_revert", + ], +) +def test_reservoir_settlement_by_failure_point( + fork: Fork, + pre: Alloc, + state_test: StateTestFiller, + failure_point: str, +) -> None: + """ + One transaction shape with a non-zero state-gas reservoir, failed + at each point along the top frame, settles four different ways. + + A non-zero reservoir requires ``gas_limit`` above the EIP-7825 cap, + which also hands the frame the *full* regular budget -- so starving + the preparation is only reachable when its demand exceeds the cap + plus the reservoir. Account-creating authorizations are the one + charge dense enough to get there: each demands ~234,606 gas + (intrinsic base, ``ACCOUNT_WRITE``, and ``NEW_ACCOUNT`` + + ``AUTH_BASE`` state bytes), so ~73 of them overtop the cap. The + count is derived from the fork's calculators. The recipient is a + delegated EOA in every scenario, so the top-frame dispatch always + owes a cold delegation-resolution access and only the + ``failure_point`` moves: + + - ``set_delegation_oog``: the last authorization's closing + ``AUTH_BASE`` charge is starved by one gas. The preparation + snapshot rolls every delegation back, the refilled state charges + restore the reservoir, and settlement returns it whole: + ``gas_used == cap`` exactly, however much extra gas was sent. + - ``dispatch_charge_oog``: every authorization applies, then the + recipient's delegation-resolution access is starved by one gas. + The charge shares the preparation snapshot, so the settlement is + identical: ``gas_used == cap``. + - ``execution_halt``: preparation completes and the delegated code + hits ``INVALID``. The persisting delegations keep their state gas + consumed -- far more than the reservoir holds -- so the fold + leaves the reservoir empty and the halt burns the rest: + ``gas_used == gas_limit``, the full amount. + - ``execution_revert``: as above, but ``REVERT`` returns the unused + regular budget: ``gas_used`` is exactly the intrinsic cost plus + every preparation charge plus the reverting code's own gas. + + Together the four pin that the reservoir's fate follows the state + it paid for: returned in full while nothing survives, consumed to + the extent the delegations persist. + """ + cap = fork.transaction_gas_limit_cap() + assert cap is not None, "EIP-7825 cap expected on this fork" + gas_costs = fork.gas_costs() + + sender = pre.fund_eoa() + + delegation_target = pre.deploy_contract(code=Op.STOP) + recipient_code: Bytecode + if failure_point == "execution_halt": + recipient_code = Op.INVALID + elif failure_point == "execution_revert": + recipient_code = Op.REVERT(0, 0) + else: + recipient_code = Op.STOP + code_target = pre.deploy_contract(code=recipient_code) + recipient = pre.fund_eoa( + amount=EOA_INITIAL_BALANCE, delegation=code_target + ) + + def creation_authorization(authority: EOA) -> AuthorizationTuple: + """Authorization creating a fresh authority's account leaf.""" + return AuthorizationTuple( + address=delegation_target, + nonce=0, + signer=authority, + creates_account=True, + ) + + probe_authority = pre.fund_eoa(amount=0) + probe = creation_authorization(probe_authority) + base_intrinsic = _intrinsic_regular( + fork, [], recipient_type=RecipientType.DELEGATION_7702 + ) + per_auth_intrinsic = ( + _intrinsic_regular( + fork, [probe], recipient_type=RecipientType.DELEGATION_7702 + ) + - base_intrinsic + ) + per_auth_charges = _auth_top_frame_charges(fork, [probe]) + per_auth_total = per_auth_intrinsic + per_auth_charges + + # The smallest authorization count whose starved-by-one gas limit + # exceeds the cap, plus one more so the reservoir is larger than a + # full authorization's preparation charge -- a refund too big to be + # confused with any single refilled charge. + min_count = (cap + 1 - base_intrinsic) // per_auth_total + 1 + auth_count = min_count + 1 + + authorities = [probe_authority] + [ + pre.fund_eoa(amount=0) for _ in range(auth_count - 1) + ] + authorization_list = [probe] + [ + creation_authorization(authority) for authority in authorities[1:] + ] + + intrinsic_regular = base_intrinsic + auth_count * per_auth_intrinsic + auth_charges = auth_count * per_auth_charges + dispatch_charge = gas_costs.COLD_ACCOUNT_ACCESS + auth_state_total = fork.transaction_top_frame_state_gas( + recipient_type=RecipientType.CONTRACT, + authorizations=authorization_list, + ) + + if failure_point == "set_delegation_oog": + # The final authorization's closing AUTH_BASE is starved by one. + gas_limit = intrinsic_regular + auth_charges - 1 + expected_gas_used = cap + delegations_persist = False + elif failure_point == "dispatch_charge_oog": + # All authorizations apply; the recipient's cold + # delegation-resolution access is starved by one. + gas_limit = intrinsic_regular + auth_charges + dispatch_charge - 1 + expected_gas_used = cap + delegations_persist = False + elif failure_point == "execution_halt": + gas_limit = intrinsic_regular + auth_charges + dispatch_charge + 10_000 + expected_gas_used = gas_limit + delegations_persist = True + else: # execution_revert + exec_gas = recipient_code.gas_cost(fork) + gas_limit = ( + intrinsic_regular + + auth_charges + + dispatch_charge + + exec_gas + + 10_000 + ) + expected_gas_used = ( + intrinsic_regular + auth_charges + dispatch_charge + exec_gas + ) + delegations_persist = True + + reservoir = gas_limit - cap + if delegations_persist: + # The persisting delegations' state gas exceeds the reservoir, + # so the reservoir is consumed in full. + assert reservoir < auth_state_total, ( + "the persisted auth state gas must swallow the reservoir" + ) + else: + assert reservoir > per_auth_charges, ( + "the reservoir must exceed one authorization's charges" + ) + + tx = Transaction( + sender=sender, + to=recipient, + value=0, + authorization_list=authorization_list, + gas_limit=gas_limit, + expected_receipt=TransactionReceipt( + cumulative_gas_used=expected_gas_used, + ), + ) + + applied_authority = Account( + nonce=1, + balance=0, + code=Spec7702.delegation_designation(delegation_target), + ) + post = { + recipient: Account( + nonce=1, + balance=EOA_INITIAL_BALANCE, + code=Spec7702.delegation_designation(code_target), + ), + **( + dict.fromkeys(authorities, applied_authority) + if delegations_persist + # Every authority's account creation is rolled back. + else dict.fromkeys(authorities) + ), + } + + # All authorities are read during authorization validation before + # any failure, so they always appear in the block access list -- + # with their persisted nonce and code writes past an execution + # failure, with no recorded changes past a preparation rollback. + # The recipient is only loaded once preparation reaches the + # dispatch charge. + if delegations_persist: + authority_bal = BalAccountExpectation( + nonce_changes=[BalNonceChange(block_access_index=1, post_nonce=1)], + code_changes=[ + BalCodeChange( + block_access_index=1, + new_code=Spec7702.delegation_designation( + delegation_target + ), + ) + ], + ) + else: + authority_bal = BalAccountExpectation.empty() + recipient_bal = ( + None + if failure_point == "set_delegation_oog" + else BalAccountExpectation.empty() + ) + expected_block_access_list = BlockAccessListExpectation( + account_expectations={ + recipient: recipient_bal, + **dict.fromkeys(authorities, authority_bal), + } + ) + + state_test( + pre=pre, + tx=tx, + post=post, + expected_block_access_list=expected_block_access_list, + ) + + +@pytest.mark.parametrize( + "failure_point", + ["set_delegation_oog", "dispatch_charge_oog", "execution_halt"], +) +def test_reservoir_settlement_with_value_to_empty_recipient( + fork: Fork, + pre: Alloc, + state_test: StateTestFiller, + failure_point: str, +) -> None: + """ + The many-authorization reservoir transaction, now moving value to an + empty recipient, carries both classes of state charge at once -- + and a failure settles each class by whether its state survives. + + The value transfer adds the recipient's ``NEW_ACCOUNT`` dispatch + charge alongside the authorizations' ``NEW_ACCOUNT`` + ``AUTH_BASE`` + charges. The recipient is a precompile (here the bn254 pairing) -- + an empty account that still executes, so an execution-phase failure + is reachable (a 1-byte input makes the pairing exceptionally halt + after the value has moved). An empty recipient runs no code of its + own, so there is no revert scenario here; the delegated-recipient + variant above covers it. + + - ``set_delegation_oog``: the last authorization's closing + ``AUTH_BASE`` is starved by one gas. Everything rolls back and + the whole reservoir returns: ``gas_used == cap``. + - ``dispatch_charge_oog``: every authorization applies, then the + recipient's ``NEW_ACCOUNT`` state charge is starved by one gas. + It shares the preparation snapshot: ``gas_used == cap``. + - ``execution_halt``: the reservoir is sized *above* the + authorizations' total state gas, so it survives the preparation + and the settlement can distinguish the two charge classes. The + precompile halts after the transfer: the recipient's leaf rolls + back, so its ``NEW_ACCOUNT`` refills and returns with the + reservoir remainder, while the persisting delegations keep their + state gas consumed -- ``gas_used == cap + auth_state_total`` + exactly. (With a small reservoir the refill would be burned with + ``gas_left`` and be unobservable, which is why the sizes differ + per scenario.) + + In every scenario the transfer never sticks: the recipient's leaf + is absent from the post state and the sender keeps the value, + paying only the gas. + """ + cap = fork.transaction_gas_limit_cap() + assert cap is not None, "EIP-7825 cap expected on this fork" + gas_costs = fork.gas_costs() + + value = 1 + sender = pre.fund_eoa() + recipient = Address(0x08) + + delegation_target = pre.deploy_contract(code=Op.STOP) + + def creation_authorization(authority: EOA) -> AuthorizationTuple: + """Authorization creating a fresh authority's account leaf.""" + return AuthorizationTuple( + address=delegation_target, + nonce=0, + signer=authority, + creates_account=True, + ) + + probe_authority = pre.fund_eoa(amount=0) + probe = creation_authorization(probe_authority) + base_intrinsic = _intrinsic_regular( + fork, + [], + recipient_type=RecipientType.EMPTY_ACCOUNT, + sends_value=True, + ) + per_auth_intrinsic = ( + _intrinsic_regular( + fork, + [probe], + recipient_type=RecipientType.EMPTY_ACCOUNT, + sends_value=True, + ) + - base_intrinsic + ) + per_auth_charges = _auth_top_frame_charges(fork, [probe]) + per_auth_total = per_auth_intrinsic + per_auth_charges + + # The smallest authorization count whose starved-by-one gas limit + # exceeds the cap, plus one more so the starved scenarios' reservoir + # exceeds a full authorization's preparation charge. + min_count = (cap + 1 - base_intrinsic) // per_auth_total + 1 + auth_count = min_count + 1 + + authorities = [probe_authority] + [ + pre.fund_eoa(amount=0) for _ in range(auth_count - 1) + ] + authorization_list = [probe] + [ + creation_authorization(authority) for authority in authorities[1:] + ] + + intrinsic_regular = base_intrinsic + auth_count * per_auth_intrinsic + auth_charges = auth_count * per_auth_charges + auth_state_total = fork.transaction_top_frame_state_gas( + recipient_type=RecipientType.CONTRACT, + authorizations=authorization_list, + ) + + data = b"" + if failure_point == "set_delegation_oog": + # The final authorization's closing AUTH_BASE is starved by one. + gas_limit = intrinsic_regular + auth_charges - 1 + expected_gas_used = cap + delegations_persist = False + elif failure_point == "dispatch_charge_oog": + # All authorizations apply; the recipient's NEW_ACCOUNT state + # charge is starved by one. + gas_limit = ( + intrinsic_regular + auth_charges + gas_costs.NEW_ACCOUNT - 1 + ) + expected_gas_used = cap + delegations_persist = False + else: # execution_halt + # One byte: not a multiple of 192, so the pairing precompile + # exceptionally halts after the value has moved. The reservoir + # covers the authorizations' state gas and the recipient's + # NEW_ACCOUNT with headroom, so no preparation charge spills + # into gas_left and the refill is observable in the settlement. + data = b"\x00" + gas_limit = cap + auth_state_total + gas_costs.NEW_ACCOUNT + 100_000 + expected_gas_used = cap + auth_state_total + delegations_persist = True + + reservoir = gas_limit - cap + if delegations_persist: + assert reservoir > auth_state_total, ( + "the reservoir must survive the persisted auth state gas" + ) + else: + assert reservoir > per_auth_charges, ( + "the reservoir must exceed one authorization's charges" + ) + + tx = Transaction( + sender=sender, + to=recipient, + value=value, + data=data, + authorization_list=authorization_list, + gas_limit=gas_limit, + expected_receipt=TransactionReceipt( + cumulative_gas_used=expected_gas_used, + ), + ) + + applied_authority = Account( + nonce=1, + balance=0, + code=Spec7702.delegation_designation(delegation_target), + ) + post: dict[Address, Account | None] = { + # The transfer never sticks; the recipient's leaf stays absent. + recipient: None, + **( + dict.fromkeys(authorities, applied_authority) + if delegations_persist + else dict.fromkeys(authorities) + ), + } + + # The recipient is first loaded for its NEW_ACCOUNT alive-check, so + # it is absent from the block access list only when the halt lands + # inside set_delegation; afterwards it appears with no net change + # (the transfer, if any, rolled back). + if delegations_persist: + authority_bal = BalAccountExpectation( + nonce_changes=[BalNonceChange(block_access_index=1, post_nonce=1)], + code_changes=[ + BalCodeChange( + block_access_index=1, + new_code=Spec7702.delegation_designation( + delegation_target + ), + ) + ], + ) + else: + authority_bal = BalAccountExpectation.empty() + recipient_bal = ( + None + if failure_point == "set_delegation_oog" + else BalAccountExpectation.empty() + ) + expected_block_access_list = BlockAccessListExpectation( + account_expectations={ + recipient: recipient_bal, + **dict.fromkeys(authorities, authority_bal), + } + ) + + state_test( + pre=pre, + tx=tx, + post=post, + expected_block_access_list=expected_block_access_list, + ) + + +@pytest.mark.parametrize( + "value", + [pytest.param(0, id="no_value"), pytest.param(1, id="with_value")], +) +def test_delegation_persists_on_execution_oog( + fork: Fork, + pre: Alloc, + state_test: StateTestFiller, + value: int, +) -> None: + """ + Once the top-frame preparation completes, an out-of-gas in the + dispatched call does NOT roll the applied delegations back. + + Two valid authorizations on third-party authorities are paid in + full and the recipient (a plain contract, so it adds no top-frame + charge) is dispatched with only enough execution budget for a single + opcode. The recipient runs that opcode and then runs out of gas on + the next, so the frame halts during execution and consumes the full + ``gas_limit``. + + The execution snapshot is taken after preparation, so the applied + delegations survive the halt -- matching the EIP-7702 "dispatch + reverts, delegation remains" rule -- while the recipient is + unchanged. + + With ``value`` set, the transfer to the recipient happens inside + the execution snapshot, so the halt that keeps the delegations in + place reverses the transfer: the recipient stays at balance zero + and the sender pays only the gas. + + The block access list mirrors this split: both authorities carry + their persisted nonce bump and delegation-code write, while the + recipient -- accessed for dispatch but whose (reverted) value + transfer leaves no net change -- appears with no recorded changes. + """ + sender = pre.fund_eoa() + + auth_a = build_authorization(pre, AuthorizationAction.CREATES_ACCOUNT) + auth_b = build_authorization(pre, AuthorizationAction.SETS_NEW_DELEGATION) + authorization_list = [auth_a.authorization, auth_b.authorization] + auth_charges = _auth_top_frame_charges(fork, authorization_list) + + intrinsic_regular = _intrinsic_regular( + fork, + authorization_list, + recipient_type=RecipientType.CONTRACT, + sends_value=bool(value), + ) + + # Two VERYLOW pushes: the budget covers one, so the frame enters + # execution and then runs out on the second, consuming all gas. + recipient_code = Op.PUSH1(0) + Op.PUSH1(0) + one_opcode = Op.PUSH1(0).gas_cost(fork) + gas_limit = intrinsic_regular + auth_charges + one_opcode + + recipient = pre.deploy_contract(code=recipient_code) + + tx = Transaction( + sender=sender, + to=recipient, + value=value, + authorization_list=authorization_list, + gas_limit=gas_limit, + expected_receipt=TransactionReceipt( + cumulative_gas_used=gas_limit, + ), + ) + + post = { + auth_a.authority: auth_a.applied_account, + auth_b.authority: auth_b.applied_account, + recipient: Account(code=recipient_code, balance=0), + } + + expected_block_access_list = BlockAccessListExpectation( + account_expectations={ + auth_a.authority: _applied_delegation_bal(auth_a), + auth_b.authority: _applied_delegation_bal(auth_b), + recipient: BalAccountExpectation.empty(), + } + ) + + state_test( + pre=pre, + tx=tx, + post=post, + expected_block_access_list=expected_block_access_list, + ) + + +@pytest.mark.parametrize( + "auth_action", + [ + AuthorizationAction.CREATES_ACCOUNT, + AuthorizationAction.SETS_NEW_DELEGATION, + ], + ids=lambda a: a.name.lower(), +) +def test_auth_state_charges_survive_dispatch_revert( + fork: Fork, + pre: Alloc, + state_test: StateTestFiller, + auth_action: AuthorizationAction, +) -> None: + """ + The state gas charged for an applied authorization stays consumed + when the dispatched call reverts, because the delegation persists. + + A single authorization is applied in full, then the recipient (a + plain contract) reverts immediately. Per EIP-7702 the applied + delegation survives the revert, so the state it created -- the + authority's account leaf (``NEW_ACCOUNT``) and delegation indicator + (``AUTH_BASE``) -- remains, and the state gas that paid for it must + remain consumed. Only the dispatched frame's unused budget is + returned. + + A regression that refills the authorization's state gas with the + frame's rollback would refund the sender 218,790 + (``NEW_ACCOUNT + AUTH_BASE``) or 35,190 (``AUTH_BASE``) gas for + state that persists; the receipt's exact gas used pins this. + + The same persistence must show in the block access list: the + authority carries its nonce bump and delegation-code write even + though the dispatched frame reverted, while the recipient appears + with no recorded changes. + """ + sender = pre.fund_eoa() + + revert_code = Op.REVERT(0, 0) + recipient = pre.deploy_contract(code=revert_code) + + auth = build_authorization(pre, auth_action) + authorization_list = [auth.authorization] + + intrinsic_regular = _intrinsic_regular( + fork, authorization_list, recipient_type=RecipientType.CONTRACT + ) + auth_charges = _auth_top_frame_charges(fork, authorization_list) + revert_exec_gas = revert_code.gas_cost(fork) + + # The authorization's regular and state charges and the two PUSH + # opcodes feeding the REVERT stay paid; only the unused execution + # budget returns. + gas_used = intrinsic_regular + auth_charges + revert_exec_gas + + tx = Transaction( + sender=sender, + to=recipient, + value=0, + authorization_list=authorization_list, + expected_receipt=TransactionReceipt( + cumulative_gas_used=gas_used, + ), + ) + + post = { + auth.authority: auth.applied_account, + recipient: Account(code=revert_code, balance=0), + } + + expected_block_access_list = BlockAccessListExpectation( + account_expectations={ + auth.authority: _applied_delegation_bal(auth), + recipient: BalAccountExpectation.empty(), + } + ) + + state_test( + pre=pre, + tx=tx, + post=post, + expected_block_access_list=expected_block_access_list, + ) + + +def test_auth_state_charges_survive_dispatch_halt_with_reservoir( + fork: Fork, + pre: Alloc, + state_test: StateTestFiller, +) -> None: + """ + The state gas charged for an applied authorization stays consumed + when the dispatched call exceptionally halts, observed through the + state-gas reservoir. + + With an ordinary gas limit the reservoir is zero and a halt + consumes all of ``gas_left`` anyway, masking any wrongly-refilled + state gas. Here the gas limit exceeds the EIP-7825 cap (allowed -- + the cap binds only the regular dimension), so the excess forms a + state-gas reservoir that covers the authorization's ``NEW_ACCOUNT`` + + ``AUTH_BASE``. The dispatched call hits ``INVALID``, consuming + all regular gas; the *unused* reservoir returns to the sender, but + the portion consumed for the persisting delegation must not. + + A regression that refills the authorization's state gas with the + frame's rollback would return the full reservoir, refunding the + sender 218,790 gas for state that persists. + """ + cap = fork.transaction_gas_limit_cap() + assert cap is not None, "EIP-7825 cap expected on this fork" + + sender = pre.fund_eoa() + + halt_code = Op.INVALID + recipient = pre.deploy_contract(code=halt_code) + + auth = build_authorization(pre, AuthorizationAction.CREATES_ACCOUNT) + authorization_list = [auth.authorization] + + auth_state_gas = fork.transaction_top_frame_state_gas( + recipient_type=RecipientType.CONTRACT, + authorizations=authorization_list, + ) + assert auth_state_gas > 0, ( + "the authorization must carry a state-gas charge" + ) + + # The reservoir covers the authorization's state charges with + # headroom, so they draw from the reservoir rather than spilling + # into gas_left. + reservoir = auth_state_gas + 50_000 + + # The halt consumes the full regular budget (the cap); of the + # reservoir, only the authorization's state gas is consumed -- its + # delegation persists -- and the unused remainder returns. + gas_used = cap + auth_state_gas + + tx = Transaction( + sender=sender, + to=recipient, + value=0, + authorization_list=authorization_list, + state_gas_reservoir=reservoir, + expected_receipt=TransactionReceipt( + cumulative_gas_used=gas_used, + ), + ) + + post = { + auth.authority: auth.applied_account, + recipient: Account(code=halt_code, balance=0), + } + + state_test(pre=pre, tx=tx, post=post) + + +def test_auth_state_gas_in_header_on_dispatch_revert( + fork: Fork, + pre: Alloc, + blockchain_test: BlockchainTestFiller, +) -> None: + """ + The state gas of an applied authorization is counted in the block's + state dimension when the dispatched call reverts. + + The header ``gas_used`` is ``max(block_regular_gas, + block_state_gas)``. The authorization creates and delegates a fresh + authority (218,790 state gas), which dominates the small regular + side (intrinsic + ``ACCOUNT_WRITE`` + the pre-revert execution), so + a correct accounting yields ``gas_used == 218,790`` even though the + dispatched call reverts -- the delegation, and the state it grew, + persist. + + A regression that refills the authorization's state gas on the + frame's rollback collapses ``tx_state_gas`` to zero and the header + to the small regular sum, which balance-only state tests cannot + distinguish from a correctly-split total. + """ + sender = pre.fund_eoa() + + revert_code = Op.REVERT(0, 0) + recipient = pre.deploy_contract(code=revert_code) + + auth = build_authorization(pre, AuthorizationAction.CREATES_ACCOUNT) + authorization_list = [auth.authorization] + + intrinsic_regular = _intrinsic_regular( + fork, authorization_list, recipient_type=RecipientType.CONTRACT + ) + auth_regular = fork.transaction_top_frame_gas_calculator()( + recipient_type=RecipientType.CONTRACT, + authorizations=authorization_list, + ) + auth_state = fork.transaction_top_frame_state_gas( + recipient_type=RecipientType.CONTRACT, + authorizations=authorization_list, + ) + revert_exec_gas = revert_code.gas_cost(fork) + + regular_total = intrinsic_regular + auth_regular + revert_exec_gas + assert auth_state > regular_total, ( + "the state dimension must dominate for the header to pin it" + ) + expected_gas_used = max(regular_total, auth_state) + + tx = Transaction( + sender=sender, + to=recipient, + value=0, + authorization_list=authorization_list, + ) + + blockchain_test( + pre=pre, + blocks=[ + Block( + txs=[tx], + header_verify=Header(gas_used=expected_gas_used), + ), + ], + post={ + sender: Account(nonce=1), + auth.authority: auth.applied_account, + recipient: Account(code=revert_code, balance=0), + }, + ) + + +@pytest.mark.parametrize( + "delta", + [ + pytest.param(0, id="exact_fit"), + pytest.param(1, id="exceeded", marks=pytest.mark.exception_test), + ], +) +def test_reverted_dispatch_state_gas_counts_toward_block_limit( + fork: Fork, + pre: Alloc, + blockchain_test: BlockchainTestFiller, + delta: int, +) -> None: + """ + The state gas persisted by a reverted transaction's authorization + counts against the block's state dimension when including later + transactions. + + The first transaction applies an account-creating authorization and + its dispatched call reverts: the delegation, and the state gas that + paid for it, persist. The last transaction is then sized to the + remaining state capacity exactly (``exact_fit``: the inclusion + check is strictly greater-than, so the block is valid) or one gas + beyond it (``exceeded``: the per-transaction state check fires and + the block is correctly rejected). + + The regular dimension is asserted to have room either way, pinning + the rejection to the state dimension. An implementation that drops + a reverted transaction's persisting state gas from the block's + state total would accept the ``exceeded`` block and fork. + """ + cap = fork.transaction_gas_limit_cap() + assert cap is not None, "EIP-7825 cap expected on this fork" + + block_gas_limit = 100_000_000 + + revert_code = Op.REVERT(0, 0) + recipient = pre.deploy_contract(code=revert_code) + + auth = build_authorization(pre, AuthorizationAction.CREATES_ACCOUNT) + authorization_list = [auth.authorization] + + intrinsic_regular = _intrinsic_regular( + fork, authorization_list, recipient_type=RecipientType.CONTRACT + ) + auth_regular = fork.transaction_top_frame_gas_calculator()( + recipient_type=RecipientType.CONTRACT, + authorizations=authorization_list, + ) + auth_state = fork.transaction_top_frame_state_gas( + recipient_type=RecipientType.CONTRACT, + authorizations=authorization_list, + ) + revert_exec_gas = revert_code.gas_cost(fork) + + first_tx_regular = intrinsic_regular + auth_regular + revert_exec_gas + first_tx = Transaction( + sender=pre.fund_eoa(), + to=recipient, + value=0, + authorization_list=authorization_list, + gas_limit=first_tx_regular + auth_state, + ) + + # The last transaction's worst-case state contribution is its full + # ``tx.gas`` (the strict EIP-8037 inclusion rule), charged against + # a state dimension that already carries the reverted first + # transaction's persisting authorization state gas. + state_available = block_gas_limit - auth_state + last_tx_gas = state_available + delta + + # Pin the rejection (when delta > 0) to the state check: the + # regular check must not fire. + regular_available = block_gas_limit - first_tx_regular + assert min(cap, last_tx_gas) < regular_available, ( + "the last tx would fail the regular check instead of the state check" + ) + + last_tx_error = ( + TransactionException.GAS_ALLOWANCE_EXCEEDED if delta > 0 else None + ) + last_tx = Transaction( + sender=pre.fund_eoa(), + to=pre.deploy_contract(code=Op.STOP), + value=0, + gas_limit=last_tx_gas, + error=last_tx_error, + ) + + # On rejection nothing in the block applies; on the exact fit the + # reverted first transaction still leaves its delegation behind. + post = {} if delta > 0 else {auth.authority: auth.applied_account} + + blockchain_test( + genesis_environment=Environment(gas_limit=block_gas_limit), + pre=pre, + blocks=[ + Block( + txs=[first_tx, last_tx], + gas_limit=block_gas_limit, + exception=last_tx_error, + ) + ], + post=post, + ) + + +def test_recipient_new_account_refilled_on_dispatch_halt_with_reservoir( + fork: Fork, + pre: Alloc, + state_test: StateTestFiller, +) -> None: + """ + The recipient ``NEW_ACCOUNT`` charge is refilled when the dispatch + fails, because the recipient's account creation rolls back with it + -- unlike an authorization's state gas, whose delegation persists. + + Value moves to an *empty precompile* (a recipient that is empty + yet still executes): the top frame charges ``NEW_ACCOUNT``, + dispatch moves the value -- materializing the leaf -- and the + precompile then halts (the bn254 pairing rejects a 1-byte input), + rolling the leaf back. The state did not grow, so the charge + refills. + + The gas limit exceeds the EIP-7825 cap so the charge draws from a + state-gas reservoir; the halt consumes the full regular budget (the + cap) but the *entire* reservoir returns, pinning the refill in the + receipt's gas used. This is the counterpart of + ``test_auth_state_charges_survive_dispatch_halt_with_reservoir``, + which pins that an authorization's state gas does NOT return. + """ + cap = fork.transaction_gas_limit_cap() + assert cap is not None, "EIP-7825 cap expected on this fork" + + sender = pre.fund_eoa() + pairing_precompile = Address(0x08) + + value = 1 + new_account_state_gas = fork.transaction_top_frame_state_gas( + sends_value=True, + recipient_type=RecipientType.EMPTY_ACCOUNT, + ) + assert new_account_state_gas > 0, ( + "an empty recipient receiving value must charge NEW_ACCOUNT" + ) + + reservoir = new_account_state_gas + 50_000 + + # The halt consumes the full regular budget; the NEW_ACCOUNT drawn + # from the reservoir is refilled (the account creation rolled + # back), so the whole reservoir returns to the sender. + gas_used = cap + + tx = Transaction( + sender=sender, + to=pairing_precompile, + value=value, + # One byte: not a multiple of 192, so the pairing precompile + # exceptionally halts after the value has moved. + data=b"\x00", + state_gas_reservoir=reservoir, + expected_receipt=TransactionReceipt( + cumulative_gas_used=gas_used, + ), + ) + + post = { + pairing_precompile: None, + } + + state_test(pre=pre, tx=tx, post=post) + + +def test_dispatched_frame_state_gas_still_refills_on_revert( + fork: Fork, + pre: Alloc, + state_test: StateTestFiller, +) -> None: + """ + State gas charged *inside* the dispatched call is still refilled + when that call reverts, in the same transaction whose authorization + state gas must stay consumed. + + The authorization sets a delegation on an existing EOA + (``AUTH_BASE``, persists across the revert). The recipient then + ``SSTORE``s a fresh slot -- charging ``STORAGE_SET`` state gas -- + and reverts, rolling the slot back, so that state gas is refilled. + + This brackets the rollback boundary from both sides: the current + over-refill (returning the ``AUTH_BASE`` too) underpays by 35,190, + while an over-correction that stops refilling frame state gas + altogether would overcharge by the 97,920 ``STORAGE_SET``. + """ + sender = pre.fund_eoa() + + sstore_revert_code = Op.SSTORE( + 0, 1, original_value=0, new_value=1 + ) + Op.REVERT(0, 0) + recipient = pre.deploy_contract(code=sstore_revert_code) + + auth = build_authorization(pre, AuthorizationAction.SETS_NEW_DELEGATION) + authorization_list = [auth.authorization] + + intrinsic_regular = _intrinsic_regular( + fork, authorization_list, recipient_type=RecipientType.CONTRACT + ) + auth_charges = _auth_top_frame_charges(fork, authorization_list) + exec_regular = sstore_revert_code.regular_cost(fork) + exec_state = sstore_revert_code.state_cost(fork) + assert exec_state > 0, ( + "the dispatched SSTORE must carry a state-gas charge" + ) + + # The SSTORE's state gas is charged and then refilled by the + # revert (the slot rolls back), so the sender pays only the + # authorization charges and the regular execution gas. + gas_used = intrinsic_regular + auth_charges + exec_regular + + tx = Transaction( + sender=sender, + to=recipient, + value=0, + authorization_list=authorization_list, + expected_receipt=TransactionReceipt( + cumulative_gas_used=gas_used, + ), + ) + + post = { + auth.authority: auth.applied_account, + recipient: Account(code=sstore_revert_code, balance=0, storage={0: 0}), + } + + state_test(pre=pre, tx=tx, post=post) diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_calldata_floor.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_calldata_floor.py index e6d87eea505..e5685524768 100644 --- a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_calldata_floor.py +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_calldata_floor.py @@ -1,4 +1,20 @@ -"""EIP-2780 interaction with the EIP-7623/7976 calldata floor.""" +""" +EIP-2780 interaction with the EIP-7623/7976 calldata floor. + +A transaction's gas accounting uses ``max(intrinsic, calldata_floor)``. +EIP-2780 decomposes the intrinsic (``TX_BASE`` + recipient access + +value-transfer charges) and lowers ``TX_BASE`` to 12_000; that lowered +base also feeds the calldata floor. These tests pin the data-heavy +regime where the floor dominates: + +- The floor binds, so ``gas_used`` equals the floor and the + recipient/value charges folded into the intrinsic are masked: the + gas paid is identical for a zero-value and a value-bearing + transaction of the same calldata size. +- One gas below the floor, the transaction is rejected with + ``INTRINSIC_GAS_BELOW_FLOOR_GAS_COST`` even though it covers the + (smaller) decomposed intrinsic. +""" import pytest from execution_testing import ( @@ -61,12 +77,12 @@ def floor(byte_count: int) -> int: @pytest.mark.parametrize( - "gas_modifier", + "outcome", [ - pytest.param(0, id="at_floor"), + pytest.param("floor_binds", id="floor_binds"), pytest.param( - -1, - id="below_floor", + "below_floor", + id="below_floor_rejected", marks=pytest.mark.exception_test, ), ], @@ -82,16 +98,17 @@ def test_calldata_floor( fork: Fork, pre: Alloc, state_test: StateTestFiller, - gas_modifier: int, + outcome: str, value: int, ) -> None: """ A data-heavy transaction to an existing EOA whose calldata floor exceeds the decomposed value-transfer intrinsic. - - ``at_floor``: with a gas limit exactly at the floor, ``gas_used`` + - ``floor_binds``: with a gas limit above the floor, ``gas_used`` pins to the floor, so the value-transfer charges (``TRANSFER_LOG_COST + TX_VALUE_COST``) folded into the intrinsic + are masked -- the gas paid is identical at ``value == 0`` and ``value == 1`` and only the moved wei differs. - ``below_floor``: a gas limit one short of the floor still covers the (smaller) decomposed intrinsic, so the floor -- built on the @@ -111,35 +128,42 @@ def test_calldata_floor( gas_price = 1_000_000_000 post: dict[Address, Account] = {} - gas_limit = calldata_floor + gas_modifier - # Even at the reduced limit the decomposed intrinsic is still - # covered, so the calldata floor is the sole gate on the - # transaction. - intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( - calldata=calldata, - sends_value=bool(value), - recipient_type=RecipientType.EOA, - return_cost_deducted_prior_execution=True, - ) - assert intrinsic_gas <= gas_limit, ( - "gas_limit must still cover the decomposed intrinsic so the " - "outcome is pinned to the calldata floor" - ) - - tx = Transaction( - sender=sender, - to=target, - value=value, - data=calldata, - gas_limit=gas_limit, - gas_price=gas_price, - error=( - TransactionException.INTRINSIC_GAS_BELOW_FLOOR_GAS_COST - if gas_modifier < 0 - else None - ), - ) - if gas_modifier == 0: + if outcome == "below_floor": + # ``gas_limit`` one short of the floor still covers the + # decomposed intrinsic, so the floor is the only thing that can + # reject it; the post state is empty (transaction rejected). + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( + calldata=calldata, + sends_value=bool(value), + recipient_type=RecipientType.EOA, + return_cost_deducted_prior_execution=True, + ) + gas_limit = calldata_floor - 1 + assert intrinsic_gas <= gas_limit, ( + "gas_limit must still cover the decomposed intrinsic so the " + "rejection is pinned to the calldata floor" + ) + tx = Transaction( + sender=sender, + to=target, + value=value, + data=calldata, + gas_limit=gas_limit, + gas_price=gas_price, + error=TransactionException.INTRINSIC_GAS_BELOW_FLOOR_GAS_COST, + ) + else: + # ``floor_binds``: no explicit gas limit (auto-fills above the + # floor). The gas component is the floor regardless of value + # (charges masked); only the transferred wei changes the + # balance. + tx = Transaction( + sender=sender, + to=target, + value=value, + data=calldata, + gas_price=gas_price, + ) sender_final_balance = ( sender_initial_balance - value - calldata_floor * gas_price ) diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_intrinsic_gas_boundary.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_intrinsic_gas_boundary.py index e62a93c8999..2292edae54e 100644 --- a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_intrinsic_gas_boundary.py +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_intrinsic_gas_boundary.py @@ -10,6 +10,7 @@ import pytest from execution_testing import ( Alloc, + AuthorizationTuple, Fork, Op, RecipientType, @@ -18,7 +19,11 @@ TransactionException, ) -from .helpers import RECIPIENT_TYPES_NON_CREATE, setup_target +from .helpers import ( + EOA_INITIAL_BALANCE, + RECIPIENT_TYPES_NON_CREATE, + setup_target, +) from .spec import ref_spec_2780 REFERENCE_SPEC_GIT_PATH = ref_spec_2780.git_path @@ -113,3 +118,62 @@ def test_intrinsic_gas_floor_boundary_contract_creation( ) state_test(pre=pre, tx=tx, post={}) + + +@pytest.mark.exception_test +@pytest.mark.parametrize( + "authorization_count", + [ + pytest.param(1, id="one_authorization"), + pytest.param(2, id="two_authorizations"), + ], +) +def test_intrinsic_gas_floor_boundary_with_authorizations( + fork: Fork, + pre: Alloc, + state_test: StateTestFiller, + authorization_count: int, +) -> None: + """ + Reject a type-4 transaction when ``gas_limit = intrinsic_gas - 1``, + where the intrinsic includes ``REGULAR_PER_AUTH_BASE_COST`` per + authorization. + + EIP-2780 keeps only the state-independent per-authorization base + cost in the intrinsic (the state-dependent remainder moved to the + top frame). The calldata floor does not count authorization tuples, + so the intrinsic -- which scales with the authorization count -- is + the binding minimum. The transaction is rejected before + ``set_delegation`` runs, so no authority is mutated. + """ + sender = pre.fund_eoa(10**18) + target = pre.fund_eoa(amount=EOA_INITIAL_BALANCE) + delegate_to = pre.deploy_contract(code=Op.STOP) + + authorization_list = [ + AuthorizationTuple( + address=delegate_to, + nonce=0, + signer=pre.fund_eoa(), + ) + for _ in range(authorization_count) + ] + + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( + recipient_type=RecipientType.EOA, + authorization_list_or_count=authorization_list, + return_cost_deducted_prior_execution=True, + ) + + tx = Transaction( + sender=sender, + to=target, + value=0, + authorization_list=authorization_list, + gas_limit=intrinsic_gas - 1, + max_fee_per_gas=1_000_000_000, + max_priority_fee_per_gas=1_000_000_000, + error=TransactionException.INTRINSIC_GAS_TOO_LOW, + ) + + state_test(pre=pre, tx=tx, post=pre) diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_value_moving_transactions.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_value_moving_transactions.py index af4d63113ca..a329470b6ba 100644 --- a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_value_moving_transactions.py +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_value_moving_transactions.py @@ -165,12 +165,11 @@ def test_value_contract_creation_tx( intrinsic plus the execution gas. When the init code reverts, the deploy is rolled back: no code is - set, the value transfer is reversed, and the intrinsic - ``NEW_ACCOUNT`` state-gas charge is refilled to the reservoir. - Under the default zero state-gas reservoir, the refill cancels - the spilled-to-regular portion of the intrinsic exactly, so the - sender pays only the regular portion of the intrinsic plus the - few EVM gas units spent before the revert. + set, the value transfer is reversed, and the top-frame + ``NEW_ACCOUNT`` state-gas charge for the created account is + refilled. The sender therefore pays only the regular intrinsic + plus the few EVM gas units spent before the revert -- the + ``NEW_ACCOUNT`` charge does not appear on the receipt. """ sender_initial_balance = 10**18 sender = pre.fund_eoa(sender_initial_balance) @@ -191,14 +190,17 @@ def test_value_contract_creation_tx( return_cost_deducted_prior_execution=True, ) + # EIP-2780: the created account's ``NEW_ACCOUNT`` state gas is + # charged at the top frame (not the intrinsic). It must be covered + # by the gas limit; it is consumed on a successful deploy and + # refilled if the init code reverts. + new_account_state_gas = fork.transaction_top_frame_state_gas( + contract_creation=True, + ) if tx_reverts: - # The ``NEW_ACCOUNT`` state portion of the intrinsic is - # refilled to the reservoir on revert, so it does not appear - # on the receipt. - new_account_refund = fork.transaction_intrinsic_state_gas( - contract_creation=True, - ) - gas_used = intrinsic_gas + execution_gas - new_account_refund + # The deploy is rolled back, so the top-frame ``NEW_ACCOUNT`` + # charge is refilled and does not appear on the receipt. + gas_used = intrinsic_gas + execution_gas # A tiny init code can leave the decomposed calldata floor above # the regular gas actually consumed; gas_used then pins to the # floor, which EIP-2780 anchors on the create intrinsic base. @@ -214,7 +216,7 @@ def test_value_contract_creation_tx( sender_value_delta = 0 expected_target = None else: - gas_used = intrinsic_gas + execution_gas + gas_used = intrinsic_gas + new_account_state_gas + execution_gas sender_value_delta = value expected_target = Account(code=code_to_deploy, balance=value) @@ -226,7 +228,7 @@ def test_value_contract_creation_tx( expected_logs = [] gas_price = 1_000_000_000 - gas_limit = intrinsic_gas + execution_gas + 1000 + gas_limit = intrinsic_gas + new_account_state_gas + execution_gas + 1000 tx = Transaction( sender=sender, diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_value_moving_with_tx_delegation.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_value_moving_with_tx_delegation.py index 8722d39e1df..14e9d4a1fee 100644 --- a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_value_moving_with_tx_delegation.py +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_value_moving_with_tx_delegation.py @@ -1,22 +1,34 @@ """ Tests for EIP-2780 x EIP-7702 interaction. -When a type-4 transaction's authorization list installs a delegation on -``tx.to``, ``set_delegation`` runs before the top-frame check fires. -That ordering changes which top-frame charges apply: - -- ``COLD_ACCOUNT_ACCESS`` for the delegated recipient still fires; the - spec charges the access uniformly whenever the recipient holds a - delegation prefix at top-frame time, regardless of who installed it. -- ``NEW_ACCOUNT`` for a value transfer to an otherwise-empty recipient - is suppressed implicitly: ``set_delegation`` writes the delegation - code and increments the nonce, so ``is_account_alive`` returns - ``True`` by the time the top-frame check evaluates it. +A type-4 transaction's authorizations are processed at the top frame +(in ``set_delegation``), where their state-dependent costs are charged. +Each authorization pays, on top of the state-independent +``REGULAR_PER_AUTH_BASE_COST`` charged in the intrinsic: + +- ``NEW_ACCOUNT`` (state) + ``ACCOUNT_WRITE`` (regular) when the + authority's account leaf does not yet exist, and +- ``AUTH_BASE`` (state) when a net-new delegation indicator is written. + +The intrinsic no longer over-charges and refunds; the costs are charged +exactly, keyed on each authority's pre-transaction state. Each +authorization carries that state as its ``creates_account`` / +``writes_delegation`` annotations, and the top-frame calculators read +them off the same list that is handed to ``authorization_list``. + +When the authorization installs a delegation on ``tx.to``, +``set_delegation`` runs before the recipient top-frame check, so: + +- the recipient's delegation-target access charge fires for the + now-delegated recipient (warm or cold per the target's warmth); and +- the ``NEW_ACCOUNT`` charge a value transfer to an empty recipient + would otherwise incur is suppressed -- ``set_delegation`` has made the + recipient alive, and the per-authorization ``NEW_ACCOUNT`` accounts + for the leaf instead. A complementary set of scenarios installs the delegation on the -*sender* (self-sponsored authorization). The authorization's nonce -must equal the sender's nonce *after* the transaction's nonce -increment. +*sender* (self-sponsored authorization), whose nonce must equal the +sender's nonce after the transaction-side increment. """ import pytest @@ -56,13 +68,19 @@ def test_tx_installs_delegation_on_funded_recipient( """ Scenario 1: ``tx.to`` is a funded EOA with no prior delegation. The type-4 transaction's authorization installs delegation on - ``tx.to``. The top-frame ``COLD_ACCOUNT_ACCESS`` charge for the - now-delegated recipient still fires. - - The pre-existing authority account also produces a - ``REFUND_AUTH_PER_EXISTING_ACCOUNT`` state refund. + ``tx.to``. + + The authority (``tx.to``) already exists, so it pays no + ``NEW_ACCOUNT``; it has no prior code, so writing the delegation + indicator pays ``AUTH_BASE``. Whether it pays ``ACCOUNT_WRITE`` + depends on the value transfer: with ``zero_value`` the delegation + write is the transaction's first write to ``tx.to`` and + ``ACCOUNT_WRITE`` is charged; with ``non-zero_value`` the + transaction already pays to write ``tx.to`` when it transfers value + to it, so no ``ACCOUNT_WRITE`` accrues. The top-frame + ``COLD_ACCOUNT_ACCESS`` charge for the now-delegated recipient (its + fresh delegation target is cold) still fires. """ - gsc = fork.gas_costs() sender_initial_balance = 10**18 sender = pre.fund_eoa(sender_initial_balance) @@ -74,31 +92,35 @@ def test_tx_installs_delegation_on_funded_recipient( address=delegated_to, nonce=0, signer=target, + # Funded authority already exists, so no NEW_ACCOUNT. + creates_account=False, + first_write=not bool(value), ) + authorization_list = [auth] - # Intrinsic sees the recipient in its pre-tx form (funded EOA); - # the delegation is materialized later by ``set_delegation`` and - # only surfaces at the top-frame check. + # Intrinsic sees the recipient in its pre-tx form (funded EOA); the + # delegation only surfaces at the top-frame check. intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( sends_value=bool(value), recipient_type=RecipientType.EOA, - authorization_list_or_count=[auth], + authorization_list_or_count=authorization_list, return_cost_deducted_prior_execution=True, ) - top_frame_gas = fork.transaction_top_frame_gas_calculator()( + top_frame_regular = fork.transaction_top_frame_gas_calculator()( + sends_value=bool(value), + recipient_type=RecipientType.DELEGATION_7702, + delegation_warm=False, + authorizations=authorization_list, + ) + top_frame_state = fork.transaction_top_frame_state_gas( sends_value=bool(value), recipient_type=RecipientType.DELEGATION_7702, + authorizations=authorization_list, ) - # The full intrinsic is deducted upfront. For each existing - # authority, ``set_delegation`` refunds ``NEW_ACCOUNT`` into the - # state gas reservoir (uncapped) and ``ACCOUNT_WRITE`` into the - # regular refund counter (capped at ``gas_used // 5`` by EIP-3529). - total_gas_cost = intrinsic_gas + top_frame_gas - state_refund = gsc.REFUND_AUTH_PER_EXISTING_ACCOUNT - gas_used_pre_regular_refund = total_gas_cost - state_refund - regular_refund = min(gsc.ACCOUNT_WRITE, gas_used_pre_regular_refund // 5) - gas_used = gas_used_pre_regular_refund - regular_refund + # Costs are charged exactly (no refund); under the default zero + # state-gas reservoir the state gas spills into regular gas. + total_gas_cost = intrinsic_gas + top_frame_regular + top_frame_state tx_gas_limit = total_gas_cost + 1000 gas_price = 1_000_000_000 @@ -106,14 +128,14 @@ def test_tx_installs_delegation_on_funded_recipient( sender=sender, to=target, value=value, - authorization_list=[auth], + authorization_list=authorization_list, gas_limit=tx_gas_limit, max_fee_per_gas=gas_price, max_priority_fee_per_gas=gas_price, ) sender_final_balance = ( - sender_initial_balance - value - (gas_used * gas_price) + sender_initial_balance - value - (total_gas_cost * gas_price) ) post = { @@ -145,10 +167,18 @@ def test_tx_installs_delegation_on_empty_recipient( Scenario 2: ``tx.to`` is a non-existent (empty) account. The type-4 transaction's authorization installs delegation on ``tx.to``. - ``set_delegation`` runs before the top-frame check and makes the - recipient alive, so the ``NEW_ACCOUNT`` state-gas charge that a - value transfer to an empty recipient would otherwise incur is - implicitly suppressed. The ``COLD_ACCOUNT_ACCESS`` charge for the + The authority's account leaf does not exist, so the authorization + pays ``NEW_ACCOUNT`` (account creation) and ``AUTH_BASE`` (net-new + delegation indicator). Whether it also pays ``ACCOUNT_WRITE`` + depends on the value transfer: with ``zero_value`` the delegation + write is the transaction's first write to ``tx.to`` and + ``ACCOUNT_WRITE`` is charged; with ``non-zero_value`` the + transaction already pays to write ``tx.to`` when it transfers value + to it, so no ``ACCOUNT_WRITE`` accrues. ``set_delegation`` runs + before the recipient top-frame check and makes the recipient alive, + so the recipient ``NEW_ACCOUNT`` charge a value transfer would + otherwise incur is suppressed (the per-authorization ``NEW_ACCOUNT`` + accounts for the leaf). The ``COLD_ACCOUNT_ACCESS`` charge for the now-delegated recipient still fires. """ sender_initial_balance = 10**18 @@ -161,24 +191,31 @@ def test_tx_installs_delegation_on_empty_recipient( address=delegated_to, nonce=0, signer=target, + # Empty authority leaf must be created. + creates_account=True, + first_write=not bool(value), ) + authorization_list = [auth] - # Intrinsic sees the recipient in its pre-tx form (empty); the - # delegation is materialized later by ``set_delegation`` and only - # surfaces at the top-frame check. intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( sends_value=bool(value), recipient_type=RecipientType.EMPTY_ACCOUNT, - authorization_list_or_count=[auth], + authorization_list_or_count=authorization_list, return_cost_deducted_prior_execution=True, ) - top_frame_gas = fork.transaction_top_frame_gas_calculator()( + top_frame_regular = fork.transaction_top_frame_gas_calculator()( + sends_value=bool(value), + recipient_type=RecipientType.DELEGATION_7702, + delegation_warm=False, + authorizations=authorization_list, + ) + top_frame_state = fork.transaction_top_frame_state_gas( sends_value=bool(value), recipient_type=RecipientType.DELEGATION_7702, + authorizations=authorization_list, ) - # Authority does not pre-exist, so no auth refund applies. - total_gas_cost = intrinsic_gas + top_frame_gas + total_gas_cost = intrinsic_gas + top_frame_regular + top_frame_state tx_gas_limit = total_gas_cost + 1000 gas_price = 1_000_000_000 @@ -186,7 +223,7 @@ def test_tx_installs_delegation_on_empty_recipient( sender=sender, to=target, value=value, - authorization_list=[auth], + authorization_list=authorization_list, gas_limit=tx_gas_limit, max_fee_per_gas=gas_price, max_priority_fee_per_gas=gas_price, @@ -236,22 +273,28 @@ def test_tx_installs_delegation_on_sender( transaction-side increment (``1``). After ``set_delegation`` the sender holds delegation code and its nonce reaches ``2``. + The sender authority already exists, so no ``NEW_ACCOUNT`` accrues, + and its leaf was already written at inclusion (priced into + ``TX_BASE``), so the delegation write is not the transaction's + first write to it and no ``ACCOUNT_WRITE`` accrues either. The + authorization pays only ``AUTH_BASE`` (the net-new delegation + indicator). + Parametrized over the call target: - ``calls_self``: ``tx.to == sender``. The intrinsic self-transfer carve-out suppresses the recipient access and value-transfer - charges; the top-frame fires ``COLD_ACCOUNT_ACCESS`` because - ``set_delegation`` has installed delegation code on the sender - by then. The transaction then dispatches into the sender's - delegated code. - - ``calls_other``: ``tx.to`` is a separate funded EOA. The - intrinsic charges include ``COLD_ACCOUNT_ACCESS`` for the - recipient (and the value-transfer charges when ``value > 0``). - The top-frame fires nothing because the recipient is a plain - EOA. The sender's delegation is installed and persists past the - transaction without ever being invoked. + charges; the top-frame fires the delegation access charge because + ``set_delegation`` has installed delegation code on the sender by + then. The transaction then dispatches into the sender's delegated + code. + - ``calls_other``: ``tx.to`` is a separate funded EOA. The intrinsic + charges include ``COLD_ACCOUNT_ACCESS`` for the recipient (and the + value-transfer charges when ``value > 0``). The top-frame fires no + recipient charge because the recipient is a plain EOA. The + sender's delegation is installed and persists past the transaction + without ever being invoked. """ - gsc = fork.gas_costs() sender_initial_balance = 10**18 sender = pre.fund_eoa(sender_initial_balance) @@ -261,13 +304,18 @@ def test_tx_installs_delegation_on_sender( address=delegated_to, nonce=1, signer=sender, + # Sender authority already exists, so no NEW_ACCOUNT; its leaf + # was already written at inclusion, so no ACCOUNT_WRITE. + creates_account=False, + first_write=False, ) + authorization_list = [auth] target_initial_balance = 0 if call_target == "self": target = sender - # Intrinsic carve-out fires (SELF); top-frame fires - # ``COLD_ACCOUNT_ACCESS`` because the sender is delegated by + # Intrinsic carve-out fires (SELF); top-frame fires the + # delegation access charge because the sender is delegated by # the time the check runs. intrinsic_recipient_type = RecipientType.SELF top_frame_recipient_type = RecipientType.DELEGATION_7702 @@ -275,31 +323,29 @@ def test_tx_installs_delegation_on_sender( target_initial_balance = 100 target = pre.fund_eoa(amount=target_initial_balance) # Recipient is a plain EOA, so no carve-out and no top-frame - # charge. + # recipient charge. intrinsic_recipient_type = RecipientType.EOA top_frame_recipient_type = RecipientType.EOA intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( sends_value=bool(value), recipient_type=intrinsic_recipient_type, - authorization_list_or_count=[auth], + authorization_list_or_count=authorization_list, return_cost_deducted_prior_execution=True, ) - top_frame_gas = fork.transaction_top_frame_gas_calculator()( + top_frame_regular = fork.transaction_top_frame_gas_calculator()( sends_value=bool(value), recipient_type=top_frame_recipient_type, + delegation_warm=False, + authorizations=authorization_list, + ) + top_frame_state = fork.transaction_top_frame_state_gas( + sends_value=bool(value), + recipient_type=top_frame_recipient_type, + authorizations=authorization_list, ) - # Sender is the existing authority, so ``set_delegation`` refunds - # ``NEW_ACCOUNT`` to the state-gas reservoir and ``ACCOUNT_WRITE`` - # to the regular refund counter (the latter capped at - # ``gas_used // 5`` by EIP-3529). - total_gas_cost = intrinsic_gas + top_frame_gas - state_refund = gsc.REFUND_AUTH_PER_EXISTING_ACCOUNT - gas_used_pre_regular_refund = total_gas_cost - state_refund - regular_refund = min(gsc.ACCOUNT_WRITE, gas_used_pre_regular_refund // 5) - gas_used = gas_used_pre_regular_refund - regular_refund - + total_gas_cost = intrinsic_gas + top_frame_regular + top_frame_state tx_gas_limit = total_gas_cost + 1000 gas_price = 1_000_000_000 @@ -307,7 +353,7 @@ def test_tx_installs_delegation_on_sender( sender=sender, to=target, value=value, - authorization_list=[auth], + authorization_list=authorization_list, gas_limit=tx_gas_limit, max_fee_per_gas=gas_price, max_priority_fee_per_gas=gas_price, @@ -315,7 +361,9 @@ def test_tx_installs_delegation_on_sender( if call_target == "self": # Value moves sender -> sender, net zero on balance. - sender_final_balance = sender_initial_balance - gas_used * gas_price + sender_final_balance = ( + sender_initial_balance - total_gas_cost * gas_price + ) post = { sender: Account( nonce=2, @@ -325,7 +373,7 @@ def test_tx_installs_delegation_on_sender( } else: sender_final_balance = ( - sender_initial_balance - value - gas_used * gas_price + sender_initial_balance - value - total_gas_cost * gas_price ) post = { sender: Account( diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_warmth_invariants.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_warmth_invariants.py index 5c819beabd3..c094b5d3bef 100644 --- a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_warmth_invariants.py +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_warmth_invariants.py @@ -1,24 +1,22 @@ """ EIP-2780 invariants for transaction-level account charges. -The recipient and any EIP-7702 delegation target referenced at the -top-level transaction frame always pay the cold access rate, even when -the address is otherwise warm, identical to the sender, or refers to -itself: - -- The access list does not warm transaction-level accounts. Listing - ``tx.to`` (or a delegation target) pays the access-list cost but - does not waive the cold charge. -- The block coinbase is pre-warmed by the protocol before transaction - execution, but tx-level cold charges still fire when ``tx.to`` or a - delegation target happens to be the coinbase. -- Precompile addresses still pay the cold charge. -- Self-referential delegations (delegation target equal to the - sender, the recipient itself, or a precompile) all pay the cold - charge; the dispatched EVM frame then runs whatever code lives at - the target, including the degenerate cases of empty code (EOA, - precompile address) or a delegation prefix that itself decodes as - the ``INVALID`` opcode. +Two distinct charges are exercised here, and they treat warmth +differently: + +- The recipient's intrinsic ``COLD_ACCOUNT_ACCESS`` is charged in the + intrinsic phase, without reading state, so it is *always cold*: + listing ``tx.to`` in the access list pays the access-list cost but + does not waive it, and the protocol-warmed coinbase is still charged + cold when it is the recipient. +- A delegated recipient's delegation-target access is a *top-frame* + charge that reads state, so it follows normal warm/cold accounting: + ``WARM_ACCESS`` when the target is already warm -- the sender, the + coinbase, a precompile, the recipient itself, or an access-list + entry -- and ``COLD_ACCOUNT_ACCESS`` otherwise. The dispatched EVM + frame then runs whatever code lives at the target, including the + degenerate cases of empty code (EOA, precompile address) or a + delegation prefix that itself decodes as the ``INVALID`` opcode. """ import pytest @@ -160,6 +158,7 @@ def test_intrinsic_charges_recipient_is_coinbase( state_test(pre=pre, tx=tx, post=post) +@pytest.mark.parametrize("outcome", ["oog", "success"]) @pytest.mark.parametrize( "value", [ @@ -172,12 +171,26 @@ def test_top_frame_charges_delegation_in_access_list( pre: Alloc, state_test: StateTestFiller, value: int, + outcome: str, ) -> None: """ Recipient holds a pre-existing EIP-7702 delegation; the delegation - target is listed in the access list. The top-frame still charges - ``COLD_ACCOUNT_ACCESS`` for the delegation target on top of the - access-list cost itself. + target is listed in the access list, which warms it, so the + top-frame charges ``WARM_ACCESS`` (100) for the delegation target -- + not ``COLD_ACCOUNT_ACCESS`` (3000) -- on top of the access-list cost + itself. + + Parametrized over the outcome to also pin the exact warm charge at + the gas boundary: + + - ``success``: ``gas_limit = intrinsic + WARM_ACCESS`` (exact) + passes, proving the charge is the 100-gas warm access -- a cold + charge would need far more headroom and out-of-gas here. The + delegated ``STOP`` runs and any value transfer lands. + - ``oog``: ``gas_limit = intrinsic + WARM_ACCESS - 1`` runs out at + ``charge_gas(WARM_ACCESS)`` before the delegated code runs; the + sender pays the full ``gas_limit``, no value moves, and the + recipient keeps its delegation unchanged. """ sender_initial_balance = 10**18 sender = pre.fund_eoa(sender_initial_balance) @@ -196,11 +209,26 @@ def test_top_frame_charges_delegation_in_access_list( top_frame_gas = fork.transaction_top_frame_gas_calculator()( sends_value=bool(value), recipient_type=RecipientType.DELEGATION_7702, + delegation_warm=True, ) total_gas_cost = intrinsic_gas + top_frame_gas gas_price = 1_000_000_000 - gas_limit = total_gas_cost + 1000 + + if outcome == "oog": + # Runs out one gas short of the warm charge, before dispatch: + # no value moves and the sender pays the full gas_limit. + gas_limit = total_gas_cost - 1 + sender_final_balance = sender_initial_balance - gas_limit * gas_price + target_balance = 0 + else: + # Exact gas: the delegated STOP costs nothing, so the warm + # charge is the last gas spent and the value transfer lands. + gas_limit = total_gas_cost + sender_final_balance = ( + sender_initial_balance - value - total_gas_cost * gas_price + ) + target_balance = value tx = Transaction( ty=1, @@ -212,13 +240,9 @@ def test_top_frame_charges_delegation_in_access_list( gas_price=gas_price, ) - sender_final_balance = ( - sender_initial_balance - value - total_gas_cost * gas_price - ) - post = { sender: Account(nonce=1, balance=sender_final_balance), - target: Account(balance=value, code=target_code), + target: Account(balance=target_balance, code=target_code), } state_test(pre=pre, tx=tx, post=post) @@ -240,9 +264,8 @@ def test_top_frame_charges_delegation_is_coinbase( ) -> None: """ Recipient holds a pre-existing EIP-7702 delegation whose target is - the block coinbase. Coinbase is implicitly warm before execution; - the top-frame still charges ``COLD_ACCOUNT_ACCESS`` for the - delegation target. + the block coinbase. Coinbase is implicitly warm before execution, + so the top-frame charges ``WARM_ACCESS`` for the delegation target. """ sender_initial_balance = 10**18 sender = pre.fund_eoa(sender_initial_balance) @@ -259,6 +282,7 @@ def test_top_frame_charges_delegation_is_coinbase( top_frame_gas = fork.transaction_top_frame_gas_calculator()( sends_value=bool(value), recipient_type=RecipientType.DELEGATION_7702, + delegation_warm=True, ) total_gas_cost = intrinsic_gas + top_frame_gas @@ -367,9 +391,9 @@ def test_top_frame_charges_delegation_is_sender( ) -> None: """ Recipient holds a pre-existing EIP-7702 delegation whose target is - the sender (``tx.origin``). The top-frame still charges - ``COLD_ACCOUNT_ACCESS`` for the delegation target; the dispatched - EVM frame finds the sender's empty EOA code and exits immediately. + the sender (``tx.origin``), which is warm, so the top-frame charges + ``WARM_ACCESS`` for the delegation target; the dispatched EVM frame + finds the sender's empty EOA code and exits immediately. """ sender_initial_balance = 10**18 sender = pre.fund_eoa(sender_initial_balance) @@ -386,6 +410,7 @@ def test_top_frame_charges_delegation_is_sender( top_frame_gas = fork.transaction_top_frame_gas_calculator()( sends_value=bool(value), recipient_type=RecipientType.DELEGATION_7702, + delegation_warm=True, ) total_gas_cost = intrinsic_gas + top_frame_gas @@ -427,8 +452,8 @@ def test_top_frame_charges_delegation_is_recipient( ) -> None: """ Recipient holds a pre-existing EIP-7702 delegation pointing back - at itself. The top-frame charges ``COLD_ACCOUNT_ACCESS`` for the - delegation target (the recipient itself), and then the dispatched + at itself. The delegation target is the recipient, which is warm, + so the top-frame charges ``WARM_ACCESS``, and then the dispatched EVM frame runs the recipient's code -- which *is* the delegation prefix ``0xef 01 00 <addr>``. The leading ``0xef`` decodes as the ``INVALID`` opcode, consuming the remaining EVM budget. The @@ -452,6 +477,7 @@ def test_top_frame_charges_delegation_is_recipient( top_frame_gas = fork.transaction_top_frame_gas_calculator()( sends_value=bool(value), recipient_type=RecipientType.DELEGATION_7702, + delegation_warm=True, ) # The dispatched frame burns the entire EVM budget on the @@ -495,8 +521,8 @@ def test_top_frame_charges_delegation_is_precompile( ) -> None: """ Recipient holds a pre-existing EIP-7702 delegation pointing at a - precompile address (``IDENTITY``, ``0x04``). The top-frame charges - ``COLD_ACCOUNT_ACCESS``; the dispatched EVM frame sets + precompile address (``IDENTITY``, ``0x04``), which is warm, so the + top-frame charges ``WARM_ACCESS``; the dispatched EVM frame sets ``disable_precompiles = True`` for delegated calls, so the precompile body does not run. The code lookup at the precompile address returns the empty byte string and the frame exits @@ -517,6 +543,7 @@ def test_top_frame_charges_delegation_is_precompile( top_frame_gas = fork.transaction_top_frame_gas_calculator()( sends_value=bool(value), recipient_type=RecipientType.DELEGATION_7702, + delegation_warm=True, ) total_gas_cost = intrinsic_gas + top_frame_gas diff --git a/tests/amsterdam/eip7778_block_gas_accounting_without_refunds/test_gas_accounting.py b/tests/amsterdam/eip7778_block_gas_accounting_without_refunds/test_gas_accounting.py index b870f0e1ae4..987ecf51784 100644 --- a/tests/amsterdam/eip7778_block_gas_accounting_without_refunds/test_gas_accounting.py +++ b/tests/amsterdam/eip7778_block_gas_accounting_without_refunds/test_gas_accounting.py @@ -10,7 +10,6 @@ from execution_testing import ( Account, Alloc, - AuthorizationTuple, Block, BlockchainTestFiller, BlockException, @@ -45,7 +44,6 @@ def build_refund_tx( # All essential calc functions intrinsic_cost_calc = fork.transaction_intrinsic_cost_calculator() max_refund_quotient = fork.max_refund_quotient() - gsc = fork.gas_costs() data_floor_calc = fork.transaction_data_floor_cost_calculator() # Initial account pre loading @@ -61,11 +59,6 @@ def build_refund_tx( empty_storage_on_success = False refund_tx_extra_gas = 1 if refund_tx_has_extra_gas_limit else 0 - # EIP-8037: existing authority "refund" adjusts intrinsic_state_gas, - # not the standard refund counter. - auth_state_gas = 0 - auth_state_refund = 0 - # Sort by name so iteration order is deterministic across Python # invocations (set iteration over enum members depends on Python's # per-process hash randomization). @@ -82,33 +75,6 @@ def build_refund_tx( ) empty_storage_on_success = True - case RefundTypes.AUTHORIZATION_EXISTING_AUTHORITY: - code += Op.PUSH0 - delegated_contract = pre.deploy_contract(code=Bytecode()) - authority_signers = [ - pre.fund_eoa(amount=1) for _ in range(refunds_count) - ] - authorization_list = [ - AuthorizationTuple( - address=delegated_contract, - nonce=0, - signer=signer, - ) - for signer in authority_signers - ] - post[delegated_contract] = Account(code=Bytecode()) - for signer in authority_signers: - post[signer] = Account(balance=1) - auth_state_gas = fork.transaction_intrinsic_state_gas( - authorization_count=refunds_count, - ) - auth_state_refund = ( - gsc.REFUND_AUTH_PER_EXISTING_ACCOUNT * refunds_count - ) - # The worst-case `ACCOUNT_WRITE` charged at intrinsic - # time is refunded via the refund counter for existing - # authorities, even if the transaction reverts. - refund_counter += gsc.ACCOUNT_WRITE * refunds_count case _: raise ValueError( f"Unknown refund type: {refund_type} (Test needs update)" @@ -130,15 +96,16 @@ def build_refund_tx( ) + code.gas_cost(fork) # EIP-8037: block gas_used only counts regular gas - gas_used_pre_refund = combined_gas_used - auth_state_gas + gas_used_pre_refund = combined_gas_used # Calculate refund (still applied to user's balance) if not refund_tx_reverts: refund_counter += code.refund(fork) - # EIP-8037: remaining state gas = intrinsic state gas - state gas - # returned to reservoir for existing authorities - remaining_state_gas = auth_state_gas - auth_state_refund + # EIP-2780 moved the EIP-7702 authorization charge to the top frame, + # so no transaction-level state gas remains here; the STORAGE_CLEAR + # path carries none. + remaining_state_gas = 0 # In the spec, the refund cap uses tx_gas_used_before_refund which is # tx.gas - gas_left - state_gas_left (combined regular + remaining @@ -201,10 +168,9 @@ def build_refund_tx( if not exceed_block_gas_limit: post[refund_tx_sender] = Account(balance=expected_balance) - # block_state_gas_used reflects intrinsic_state minus the - # existing-authority auth refund (state_refund), since - # `process_transaction` deducts it from `tx_state_gas` before - # accumulating into `block_state_gas_used`. + # No transaction-level state gas is tracked here anymore; the third + # element is always zero and kept for the return-tuple shape callers + # unpack. return ( receipt_gas_used, gas_used_pre_refund, @@ -320,18 +286,6 @@ def test_multi_transaction_gas_accounting( This tests that clients correctly use pre-refund gas for block accounting. """ - # TODO[EIP-8037]: this test's exceed_block_gas_limit branch builds - # `environment_gas_limit = total - 1` from a single combined - # `total_block_gas_used`, but post-fix the auth refund splits the - # regular vs state dimensions further. Reworking the per-dimension - # budget math is out of scope for the auth-refund spec fix; until - # then, skip the AUTHORIZATION_EXISTING_AUTHORITY case here. - if refund_type == RefundTypes.AUTHORIZATION_EXISTING_AUTHORITY: - pytest.skip( - "AUTHORIZATION_EXISTING_AUTHORITY not yet adapted to the " - "two-dimensional block budget post EIP-8037 auth-refund fix" - ) - intrinsic_cost_calc = fork.transaction_intrinsic_cost_calculator() data_floor_calc = fork.transaction_data_floor_cost_calculator() @@ -489,23 +443,9 @@ def test_varying_calldata_costs( 2. tx_gas_after_refund < calldata_floor < tx_gas_before_refund 3. calldata_floor > tx_gas_before_refund """ - if refund_type == RefundTypes.AUTHORIZATION_EXISTING_AUTHORITY: - if calldata_test_type == ( - CallDataTestType.DATA_FLOOR_BETWEEN_TX_GAS_BEFORE_AND_AFTER - ): - pytest.skip( - "EIP-7702 auth refund routes through state_gas_reservoir " - "and state_refund (deducted from tx_state_gas); it does " - "not feed refund_counter, so receipt gas_used_pre_refund " - "== gas_used_post_refund and no calldata floor can land " - "strictly between them" - ) - match refund_type: case RefundTypes.STORAGE_CLEAR: bytes_to_add_per_iteration = b"00" * 2 - case RefundTypes.AUTHORIZATION_EXISTING_AUTHORITY: - bytes_to_add_per_iteration = b"00" * 10 case _: raise ValueError( f"Unknown refund type: {refund_type} (Test needs update)" diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip7702.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip7702.py index dd0f036a714..fd8a11c1fff 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip7702.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip7702.py @@ -21,6 +21,8 @@ Fork, Initcode, Op, + RecipientType, + StateTestFiller, Transaction, Withdrawal, compute_create_address, @@ -30,6 +32,10 @@ ) from ...prague.eip7702_set_code_tx.spec import Spec as Spec7702 +from ..eip2780_reduce_intrinsic_tx_gas.helpers import ( + AuthorizationAction, + build_authorization, +) from .spec import ref_spec_7928 REFERENCE_SPEC_GIT_PATH = ref_spec_7928.git_path @@ -485,6 +491,153 @@ def test_bal_7702_delegated_storage_access( ) +@pytest.mark.parametrize( + "outcome", + [ + pytest.param("oog", id="oog_at_delegation_charge"), + pytest.param("success", id="success"), + ], +) +def test_bal_7702_top_frame_delegation_oog( + fork: Fork, + pre: Alloc, + blockchain_test: BlockchainTestFiller, + outcome: str, +) -> None: + """ + Ensure the delegation target of a delegated ``tx.to`` enters the + BAL only when gas covers the top-frame delegation charge. + """ + sender = pre.fund_eoa() + + delegated_to = pre.deploy_contract(code=Op.STOP) + target = pre.fund_eoa(amount=0, delegation=delegated_to) + + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( + recipient_type=RecipientType.DELEGATION_7702, + return_cost_deducted_prior_execution=True, + ) + top_frame_gas = fork.transaction_top_frame_gas_calculator()( + recipient_type=RecipientType.DELEGATION_7702, + ) + + gas_limit = intrinsic_gas + top_frame_gas + if outcome == "oog": + gas_limit -= 1 + # The delegation charge fails, so the target is never accessed. + delegated_to_expectation = None + else: + delegated_to_expectation = BalAccountExpectation.empty() + + tx = Transaction( + sender=sender, + to=target, + gas_limit=gas_limit, + ) + + block = Block( + txs=[tx], + expected_block_access_list=BlockAccessListExpectation( + account_expectations={ + target: BalAccountExpectation.empty(), + delegated_to: delegated_to_expectation, + } + ), + ) + + blockchain_test( + pre=pre, + blocks=[block], + post={sender: Account(nonce=1)}, + ) + + +@pytest.mark.parametrize( + "outcome", + [ + pytest.param("oog", id="oog_at_authorization_charge"), + pytest.param("success", id="success"), + ], +) +def test_bal_7702_recipient_excluded_on_authorization_oog( + fork: Fork, + pre: Alloc, + state_test: StateTestFiller, + outcome: str, +) -> None: + """ + Ensure ``tx.to`` enters the BAL only when authorization processing + completes. + + The single authorization is starved at its opening ``NEW_ACCOUNT`` + charge, halting the transaction before the top-frame dispatch loads + the recipient: the recipient must be absent from the BAL, while the + authority -- read during authorization validation -- stays in it + with no recorded changes. + """ + sender = pre.fund_eoa() + recipient = pre.deploy_contract(code=Op.STOP) + + auth = build_authorization(pre, AuthorizationAction.CREATES_ACCOUNT) + authorization_list = [auth.authorization] + + intrinsic_regular = fork.transaction_intrinsic_cost_calculator()( + recipient_type=RecipientType.CONTRACT, + authorization_list_or_count=authorization_list, + return_cost_deducted_prior_execution=True, + ) + + recipient_expectation: BalAccountExpectation | None + expected_authority: Account | None + if outcome == "oog": + # The authorization runs out at its opening NEW_ACCOUNT state + # charge, drawn from gas_left under the zero state reservoir. + gas_limit = intrinsic_regular + fork.gas_costs().NEW_ACCOUNT - 1 + recipient_expectation = None + authority_expectation = BalAccountExpectation.empty() + expected_authority = auth.original_account + else: + top_frame_regular = fork.transaction_top_frame_gas_calculator()( + recipient_type=RecipientType.CONTRACT, + authorizations=authorization_list, + ) + top_frame_state = fork.transaction_top_frame_state_gas( + recipient_type=RecipientType.CONTRACT, + authorizations=authorization_list, + ) + gas_limit = intrinsic_regular + top_frame_regular + top_frame_state + recipient_expectation = BalAccountExpectation.empty() + authority_expectation = BalAccountExpectation( + nonce_changes=[BalNonceChange(block_access_index=1, post_nonce=1)], + code_changes=[ + BalCodeChange( + block_access_index=1, + new_code=auth.applied_account.code, + ) + ], + ) + expected_authority = auth.applied_account + + tx = Transaction( + sender=sender, + to=recipient, + authorization_list=authorization_list, + gas_limit=gas_limit, + ) + + state_test( + pre=pre, + tx=tx, + post={sender: Account(nonce=1), auth.authority: expected_authority}, + expected_block_access_list=BlockAccessListExpectation( + account_expectations={ + recipient: recipient_expectation, + auth.authority: authority_expectation, + } + ), + ) + + def test_bal_7702_invalid_nonce_authorization( pre: Alloc, blockchain_test: BlockchainTestFiller, diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_cases.md b/tests/amsterdam/eip7928_block_level_access_lists/test_cases.md index ad2f739d7ec..6bcc98b121a 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_cases.md +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_cases.md @@ -50,6 +50,8 @@ | `test_bal_7702_delegation_update` | Ensure BAL captures update of existing EOA delegation. Three variants: (1) Self-funded: Alice sends both 7702 txs herself. (2) Sponsored: a single `Relayer` sends both txs on Alice's behalf. (3) `sponsored_cross_sender`: a distinct relayer per tx — exercises the cross-tx auth-nonce-chain dependency since sender-nonce serialization no longer trivializes the test. A client that parallel-verifies auth signatures must consult Alice's BAL `nonce_changes` to validate the second auth against her post-tx-1 nonce. | Alice first delegates to `Oracle1`, then in second tx updates delegation to `Oracle2`. Each transaction sends 10 wei to Bob. | BAL **MUST** include Alice: first tx has `code_changes` (delegation designation `0xef0100\|\|address(Oracle1)`),`nonce_changes`. Second tx has`code_changes` (delegation designation `0xef0100\|\|address(Oracle2)`),`nonce_changes`. Bob:`balance_changes` (receives 10 wei on each tx). For sponsored variant, BAL **MUST** also include `Relayer`:`nonce_changes` for both transactions; for `sponsored_cross_sender`, each relayer has one `nonce_changes` at its tx index. `Oracle1` and `Oracle2` **MUST NOT** be present in BAL - accounts are never accessed. | ✅ Completed | | `test_bal_7702_delegation_clear` | Ensure BAL captures clearing of EOA delegation | Alice first delegates to `Oracle`, then in second tx clears delegation by authorizing to `0x0` address. Each transaction sends 10 wei to Bob. Two variants: (1) Self-funded: Alice sends both 7702 txs herself. (2) Sponsored: `Relayer` sends both 7702 txs on Alice's behalf. | BAL **MUST** include Alice: first tx has `code_changes` (delegation designation `0xef0100\|\|address(Oracle)`), `nonce_changes`. Second tx has `code_changes` (empty code - delegation cleared), `nonce_changes`. Bob: `balance_changes` (receives 10 wei on each tx). For sponsored variant, BAL **MUST** also include `Relayer`: `nonce_changes` for both transactions. `Oracle` and `0x0` address **MUST NOT** be present in BAL - accounts are never accessed. | ✅ Completed | | `test_bal_7702_delegated_storage_access` | Ensure BAL captures storage operations when calling a delegated EIP-7702 account | Alice has delegated her account to `Oracle`. `Oracle` contract contains code that reads from storage slot `0x01` and writes to storage slot `0x02`. Bob sends 10 wei to Alice (the delegated account), which executes `Oracle`'s code. | BAL **MUST** include Alice: `balance_changes` (receives 10 wei), `storage_changes` for slot `0x02` (write operation performed in Alice's storage), `storage_reads` for slot `0x01` (read operation from Alice's storage). Bob: `nonce_changes` (sender), `balance_changes` (loses 10 wei plus gas costs). `Oracle` (account access). | ✅ Completed | +| `test_bal_7702_top_frame_delegation_oog` | Ensure the delegation target of a delegated `tx.to` enters the BAL only when gas covers the top-frame delegation charge ([EIP-2780](https://eips.ethereum.org/EIPS/eip-2780) runtime charge) | `target` holds a pre-existing delegation to `delegated_to`. Sender sends a transaction to `target`. Parametrized: (1) `oog_at_delegation_charge`: gas limit is one short of intrinsic + top-frame delegation charge, (2) `success`: gas covers the charge and the delegated `STOP` runs. | For case (1): BAL **MUST** include `target` (recipient touch) but **MUST NOT** include `delegated_to` (the charge fails before the target is accessed). For case (2): BAL **MUST** include both `target` and `delegated_to` with empty changes. Sender always has `nonce_changes`. | ✅ Completed | +| `test_bal_7702_recipient_excluded_on_authorization_oog` | Ensure `tx.to` enters the BAL only when authorization processing completes ([EIP-2780](https://eips.ethereum.org/EIPS/eip-2780) runtime charges precede the recipient load) | Sender sends a transaction with one authorization (fresh authority) to a `STOP` contract. Parametrized: (1) `oog_at_authorization_charge`: gas limit is one short of the authorization's opening `NEW_ACCOUNT` charge, (2) `success`: gas covers the top-frame charges and the delegation applies. | For case (1): BAL **MUST NOT** include the recipient (the halt precedes its load) but **MUST** include the authority with empty changes (read during validation). For case (2): BAL **MUST** include the recipient with empty changes and the authority with `nonce_changes` and `code_changes`. | ✅ Completed | | `test_bal_7702_invalid_nonce_authorization` | Ensure BAL handles failed authorization due to wrong nonce | `Relayer` sends sponsored transaction to Bob (10 wei transfer succeeds) but Alice's authorization to delegate to `Oracle` uses incorrect nonce, causing silent authorization failure | BAL **MUST** include Alice with empty changes (account access), Bob with `balance_changes` (receives 10 wei), Relayer with `nonce_changes`. **MUST NOT** include `Oracle` (authorization failed, no delegation) | ✅ Completed | | `test_bal_7702_invalid_chain_id_authorization` | Ensure BAL handles failed authorization due to wrong chain id | `Relayer` sends sponsored transaction to Bob (10 wei transfer succeeds) but Alice's authorization to delegate to `Oracle` uses incorrect chain id, causing authorization failure before account access | BAL **MUST** include Bob with `balance_changes` (receives 10 wei), Relayer with `nonce_changes`. **MUST NOT** include Alice (authorization fails before loading account) or `Oracle` (authorization failed, no delegation) | ✅ Completed | | `test_call_into_self_delegating_set_code` | Self-delegation degenerate one-hop case (companion to `test_call_into_chain_delegating_set_code`). File: `tests/prague/eip7702_set_code_tx/test_set_code_txs.py`. Parametrized over `@pytest.mark.with_all_call_opcodes`. | `auth_signer` auths itself as its own delegation target. `entry_address` issues `call_opcode(auth_signer)`. EVM resolves once: `auth_signer`'s code is the designator pointing back to `auth_signer`; the second hop is not followed, so the `0xef0100...` bytecode runs as legacy code → INVALID → returns 0. | `auth_signer` **MUST** appear with `nonce_changes` and `code_changes` (delegation designator to itself). `entry_address` **MUST** have `storage_reads=[0]` (no-op SSTORE demoted). No additional delegation-target entry is created because the target coincides with the authority. | ✅ Completed | 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 5fbee4e337d..808d911d3cd 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 @@ -148,6 +148,12 @@ def test_max_code_size_deposit_gas( contract_creation=True, return_cost_deducted_prior_execution=True, ) + # Under EIP-2780 the created account's NEW_ACCOUNT state gas is + # charged at the top frame, no longer bundled in the intrinsic, so + # add it back into the exact-fit gas limit. + top_frame_state_gas = fork.transaction_top_frame_state_gas( + contract_creation=True, + ) tx = Transaction( sender=alice, @@ -155,6 +161,7 @@ def test_max_code_size_deposit_gas( data=initcode, gas_limit=( intrinsic_gas + + top_frame_state_gas + initcode.execution_gas(fork) + initcode.deployment_gas(fork) - gas_shortfall diff --git a/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_additional_coverage.py b/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_additional_coverage.py index 8dbc92267e6..0b745830914 100644 --- a/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_additional_coverage.py +++ b/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_additional_coverage.py @@ -697,71 +697,93 @@ def test_authorization_list_intrinsic_gas( num_authorizations: int, ) -> None: """ - Verify authorization list gas costs are included in intrinsic gas. - - Each authorization in the list adds a fixed gas cost to the - intrinsic gas. This should be accounted for before comparing - with floor cost. + Verify the authorization-list intrinsic cost under EIP-2780. + + Each authorization adds exactly ``REGULAR_PER_AUTH_BASE_COST`` to + the (regular) intrinsic; the state-dependent authorization costs + moved to the top frame. Measured on the *raw* intrinsic (before + the EIP-7623 calldata floor is applied) the per-authorization + delta is exactly ``num_authorizations * + REGULAR_PER_AUTH_BASE_COST`` -- even when the floor would + otherwise mask it (e.g. a single authorization whose base cost + stays below the floor). Each existing authority then pays the + first-write ``ACCOUNT_WRITE`` (regular) and ``AUTH_BASE`` + (state) at the top frame, so with a STOP recipient the receipt + is ``max(intrinsic_regular + num_authorizations * + (ACCOUNT_WRITE + AUTH_BASE), floor_cost)``. """ - # Create authorization list + gas_costs = fork.gas_costs() + + # Existing authorities each gaining a fresh delegation. An + # existing leaf pays the first-write ACCOUNT_WRITE and the + # top-frame AUTH_BASE (no NEW_ACCOUNT), keeping the billing + # clean. authorization_list = [ AuthorizationTuple( - signer=pre.fund_eoa(0), + signer=pre.fund_eoa(), address=Address(i + 1), + nonce=0, + creates_account=False, + writes_delegation=True, ) for i in range(num_authorizations) ] - # Use calldata that triggers floor cost + # Use calldata that triggers the floor cost. calldata = Bytes(b"\x01" * 500) - # Calculate costs intrinsic_cost_calculator = ( fork.transaction_intrinsic_cost_calculator() ) - intrinsic_cost_with_auth = intrinsic_cost_calculator( + # Raw intrinsic (no floor max) isolates the per-authorization base + # cost even when the calldata floor dominates the floored value. + intrinsic_with_auth = intrinsic_cost_calculator( calldata=calldata, - contract_creation=False, - access_list=None, authorization_list_or_count=authorization_list, + return_cost_deducted_prior_execution=True, ) - - intrinsic_cost_without_auth = intrinsic_cost_calculator( + intrinsic_without_auth = intrinsic_cost_calculator( calldata=calldata, - contract_creation=False, - access_list=None, authorization_list_or_count=None, + return_cost_deducted_prior_execution=True, ) - floor_cost_calculator = fork.transaction_data_floor_cost_calculator() - floor_cost = floor_cost_calculator(data=calldata) + actual_auth_cost = intrinsic_with_auth - intrinsic_without_auth + assert actual_auth_cost == ( + num_authorizations * gas_costs.REGULAR_PER_AUTH_BASE_COST + ), ( + "auth intrinsic must be n * REGULAR_PER_AUTH_BASE_COST, got: " + f"{actual_auth_cost}" + ) - # Each authorization adds calldata cost for the authorization tuple - # plus G_AUTHORIZATION gas cost. The difference we see should be - # primarily from the authorization gas but may include calldata costs - # for encoding the authorization list. - actual_auth_cost = ( - intrinsic_cost_with_auth - intrinsic_cost_without_auth + floor_cost = fork.transaction_data_floor_cost_calculator()( + data=calldata ) - # Just verify that there is a positive cost increase - assert actual_auth_cost > 0, ( - f"Authorization should add gas cost, got: {actual_auth_cost}" + # Existing authorities pay the first-write ACCOUNT_WRITE + # (regular) and AUTH_BASE (state) each at the top frame; the + # STOP recipient does no execution, so the receipt is + # max(regular + state, floor). + top_frame_regular = fork.transaction_top_frame_gas_calculator()( + authorizations=authorization_list, + ) + top_frame_state = fork.transaction_top_frame_state_gas( + authorizations=authorization_list, + ) + expected_gas = max( + intrinsic_with_auth + top_frame_regular + top_frame_state, + floor_cost, ) - - # The transaction should pay max(intrinsic_with_auth, floor_cost) - expected_gas = max(intrinsic_cost_with_auth, floor_cost) tx = Transaction( ty=4, # Type 4 supports authorization lists sender=sender, to=to, data=calldata, - gas_limit=expected_gas, + gas_limit=expected_gas + 10_000, authorization_list=authorization_list, - ) - - tx.expected_receipt = TransactionReceipt( - cumulative_gas_used=expected_gas + expected_receipt=TransactionReceipt( + cumulative_gas_used=expected_gas + ), ) state_test( diff --git a/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_refunds.py b/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_refunds.py index e21d8c5a77e..0e6cea51385 100644 --- a/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_refunds.py +++ b/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_refunds.py @@ -52,16 +52,9 @@ def data_test_type() -> DataTestType: @pytest.fixture -def authorization_list( - pre: Alloc, refund_type: RefundTypes -) -> List[AuthorizationTuple] | None: - """ - Modify fixture from conftest to automatically read the refund_type - information. - """ - if refund_type != RefundTypes.AUTHORIZATION_EXISTING_AUTHORITY: - return None - return [AuthorizationTuple(signer=pre.fund_eoa(1), address=Address(1))] +def authorization_list() -> List[AuthorizationTuple] | None: + """Return no authorizations; the STORAGE_CLEAR refund needs none.""" + return None @pytest.fixture @@ -70,43 +63,20 @@ def ty(refund_type: RefundTypes) -> int: Modify fixture from conftest to automatically read the refund_type information. """ - if refund_type == RefundTypes.AUTHORIZATION_EXISTING_AUTHORITY: - return 4 if refund_type == RefundTypes.STORAGE_CLEAR: return 2 raise ValueError(f"Unknown refund type: {refund_type}") -@pytest.fixture -def state_gas_refund(fork: Fork, refund_type: RefundTypes) -> int: - """Return the EIP-8037 auth state-gas refund (not subject to 1/5 cap).""" - if ( - fork.is_eip_enabled(8037) - and refund_type == RefundTypes.AUTHORIZATION_EXISTING_AUTHORITY - ): - return fork.gas_costs().REFUND_AUTH_PER_EXISTING_ACCOUNT - return 0 - - @pytest.fixture def max_refund(fork: Fork, refund_type: RefundTypes) -> int: """Return the max refund gas of the transaction.""" gas_costs = fork.gas_costs() - max_refund = ( + return ( gas_costs.REFUND_STORAGE_CLEAR if refund_type == RefundTypes.STORAGE_CLEAR else 0 ) - if refund_type == RefundTypes.AUTHORIZATION_EXISTING_AUTHORITY: - if fork.is_eip_enabled(8037): - # The worst-case `ACCOUNT_WRITE` charged at intrinsic time - # is refunded via the refund counter when the authority's - # account leaf already exists; the state-gas portion is - # refilled separately and is not subject to the cap. - max_refund += gas_costs.ACCOUNT_WRITE - else: - max_refund += gas_costs.REFUND_AUTH_PER_EXISTING_ACCOUNT - return max_refund @pytest.fixture @@ -177,7 +147,6 @@ def execution_gas_used( tx_intrinsic_gas_cost_before_execution: int, tx_floor_data_cost: int, max_refund: int, - state_gas_refund: int, prefix_code_gas: int, refund_test_type: RefundTestType, ) -> int: @@ -195,9 +164,8 @@ def execution_gas_used( def execution_gas_cost(execution_gas: int) -> int: total_gas_used = tx_intrinsic_gas_cost_before_execution + execution_gas - effective_gas = total_gas_used - state_gas_refund - return effective_gas - min( - max_refund, effective_gas // fork.max_refund_quotient() + return total_gas_used - min( + max_refund, total_gas_used // fork.max_refund_quotient() ) execution_gas = prefix_code_gas @@ -241,14 +209,12 @@ def refund( tx_intrinsic_gas_cost_before_execution: int, execution_gas_used: int, max_refund: int, - state_gas_refund: int, ) -> int: """Return the refund gas of the transaction.""" total_gas_used = ( tx_intrinsic_gas_cost_before_execution + execution_gas_used ) - effective_gas = total_gas_used - state_gas_refund - return min(max_refund, effective_gas // fork.max_refund_quotient()) + return min(max_refund, total_gas_used // fork.max_refund_quotient()) @pytest.fixture @@ -343,7 +309,6 @@ def test_gas_refunds_from_data_floor( tx_intrinsic_gas_cost_before_execution: int, execution_gas_used: int, refund: int, - state_gas_refund: int, refund_test_type: RefundTestType, ) -> None: """ @@ -351,10 +316,7 @@ def test_gas_refunds_from_data_floor( floor. """ gas_used = ( - tx_intrinsic_gas_cost_before_execution - + execution_gas_used - - state_gas_refund - - refund + tx_intrinsic_gas_cost_before_execution + execution_gas_used - refund ) if ( refund_test_type diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py index 9f964e0b7e1..51c93b5ec08 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py @@ -256,11 +256,14 @@ def test_code_deposit_state_gas_exact_fit_boundary( A CREATE tx deploys ``code_size`` bytes with ``gas_limit`` set so the deposit lands exactly at the available gas (deploys) or one gas short - (halts: state restored, NEW_ACCOUNT refilled, no code). The two - regimes pin the halt billing: over-cap ``reservoir`` rolls the - reservoir back so the sender pays the cap; in-cap ``spill`` burns - ``gas_left`` and bills ``gas_limit - NEW_ACCOUNT``. The scaling - tests assert success only. + (halts: state restored, the top-frame ``NEW_ACCOUNT`` refilled, no + code). Under EIP-2780 the created account's ``NEW_ACCOUNT`` state gas + is charged at the top frame (not bundled in the intrinsic), so + ``exact_fit_gas`` includes it explicitly. The two regimes pin the halt + billing: over-cap ``reservoir`` rolls the reservoir back so the sender + pays the cap; in-cap ``spill`` refills the spilled state gas into + ``gas_left`` and burns it all, billing the full ``gas_limit``. The + scaling tests assert success only. """ gas_costs = fork.gas_costs() cap = fork.transaction_gas_limit_cap() @@ -275,13 +278,19 @@ def test_code_deposit_state_gas_exact_fit_boundary( keccak_gas = gas_costs.OPCODE_KECCAK256_PER_WORD * words deposit_state_gas = fork.code_deposit_state_gas(code_size=code_size) - intrinsic_total = fork.transaction_intrinsic_cost_calculator()( + intrinsic_regular = fork.transaction_intrinsic_cost_calculator()( calldata=bytes(init_code), contract_creation=True, return_cost_deducted_prior_execution=True, ) + # The fresh target's NEW_ACCOUNT is a top-frame state charge under + # EIP-2780, no longer folded into the intrinsic. exact_fit_gas = ( - intrinsic_total + init_exec_regular + keccak_gas + deposit_state_gas + intrinsic_regular + + gas_costs.NEW_ACCOUNT + + init_exec_regular + + keccak_gas + + deposit_state_gas ) if funding == "reservoir": assert exact_fit_gas > cap @@ -297,11 +306,10 @@ def test_code_deposit_state_gas_exact_fit_boundary( receipt_gas_used = exact_fit_gas post = {created: Account(code=b"\x00" * code_size)} else: - receipt_gas_used = ( - cap - if funding == "reservoir" - else gas_limit - gas_costs.NEW_ACCOUNT - ) + # reservoir: the deposit OOG refills the reservoir, so the sender + # pays the regular cap. spill: the refilled NEW_ACCOUNT lands in + # gas_left and is burned, so the sender pays the full gas_limit. + receipt_gas_used = cap if funding == "reservoir" else gas_limit post = {created: Account.NONEXISTENT} tx = Transaction( @@ -588,10 +596,10 @@ def test_create_tx_intrinsic_gas_boundary( @pytest.mark.exception_test @pytest.mark.parametrize( - "extra_gas", + "initcode", [ - pytest.param(0, id="at_regular_intrinsic"), - pytest.param(1, id="one_above_regular_intrinsic"), + pytest.param(Bytecode(), id="empty_initcode"), + pytest.param(Op.RETURN(0, 0), id="return_initcode"), ], ) @pytest.mark.valid_from("EIP8037") @@ -599,31 +607,33 @@ def test_create_tx_below_total_intrinsic( state_test: StateTestFiller, pre: Alloc, fork: Fork, - extra_gas: int, + initcode: Bytecode, ) -> None: """ - Reject CREATE tx when gas_limit covers regular but not state intrinsic. + Reject a creation tx one gas below the (now regular-only) intrinsic. + + Under EIP-2780 the created account's ``NEW_ACCOUNT`` cost moved out + of the transaction intrinsic and into the top frame, so the creation + intrinsic is entirely regular: + ``fork.transaction_intrinsic_cost_calculator()(contract_creation=True, + calldata=initcode)``. Pinning ``gas_limit`` at ``intrinsic - 1`` must + be rejected as intrinsic-gas-too-low, mirroring the set_code case in + ``test_set_code_tx_below_total_intrinsic``. - EIP-8037 splits the CREATE intrinsic into regular and state - components (`STATE_BYTES_PER_NEW_ACCOUNT * COST_PER_STATE_BYTE`). - `test_create_tx_intrinsic_gas_boundary` pins the upper boundary - (`total - 1`); this pins the lower end — `intrinsic_regular` and - one gas above — to catch implementations that omit the state - component from the pre-validate check. + This now overlaps ``test_create_tx_intrinsic_gas_boundary`` + (``gas_delta=-1``), but additionally sweeps the initcode so the + per-word init-code cost folded into the regular intrinsic is + exercised. """ - total_intrinsic = fork.transaction_intrinsic_cost_calculator()( - contract_creation=True, - ) - intrinsic_state = fork.transaction_intrinsic_state_gas( + intrinsic = fork.transaction_intrinsic_cost_calculator()( contract_creation=True, + calldata=bytes(initcode), ) - intrinsic_regular = total_intrinsic - intrinsic_state - gas_limit = intrinsic_regular + extra_gas - assert gas_limit < total_intrinsic tx = Transaction( to=None, - gas_limit=gas_limit, + data=bytes(initcode), + gas_limit=intrinsic - 1, sender=pre.fund_eoa(), error=TransactionException.INTRINSIC_GAS_TOO_LOW, ) @@ -1317,12 +1327,14 @@ def test_create_tx_header_gas_used( header. Catches bugs where clients report gas_limit instead of actual consumed gas. - For a fresh target the NEW_ACCOUNT state gas is charged and + For a fresh target the top-frame NEW_ACCOUNT state gas is charged and dominates the regular gas, so gas_used == NEW_ACCOUNT. For a - pre-existing balance-only leaf the NEW_ACCOUNT charge is refunded, - so net state gas is zero and the regular intrinsic gas dominates. - The expected value subtracts NEW_ACCOUNT and so fails if the - refund regresses. + pre-existing balance-only leaf the target is not EMPTY pre-tx, so the + top-frame NEW_ACCOUNT is never charged: net state gas is zero and only + the regular dimension remains. The block-level calldata floor tops up + that regular remainder, so the expected value is the greater of the + regular intrinsic and the floor, and fails if a stray NEW_ACCOUNT is + charged. """ gas_costs = fork.gas_costs() initcode = Op.STOP @@ -1332,7 +1344,8 @@ def test_create_tx_header_gas_used( sender = pre.fund_eoa(nonce=0) contract_address = compute_create_address(address=sender, nonce=0) # Balance-only leaf: alive and deployable, so the creation - # succeeds and the intrinsic NEW_ACCOUNT charge is refunded. + # succeeds and (being non-EMPTY pre-tx) the top-frame NEW_ACCOUNT + # is never charged. pre.fund_address(contract_address, amount=1) else: sender = pre.fund_eoa() @@ -1347,17 +1360,20 @@ def test_create_tx_header_gas_used( # block_gas_used = max(block_regular, block_state) if target == "existing": intrinsic_cost = fork.transaction_intrinsic_cost_calculator() - intrinsic_total = intrinsic_cost( - calldata=bytes(initcode), contract_creation=True + # Regular-only creation intrinsic; STOP initcode deploys empty + # code (zero deposit) and the pre-existing target adds no state + # gas. The block-level calldata floor tops up this small regular + # remainder and, being the larger of the two, is what the header + # reflects (the floor applies to block-level regular gas). + regular_intrinsic = intrinsic_cost( + calldata=bytes(initcode), + contract_creation=True, + return_cost_deducted_prior_execution=True, ) - # Block regular gas applies the calldata floor, which tops up - # the small regular remainder left after the NEW_ACCOUNT refund. - expected_gas_used = max( - intrinsic_total - gas_costs.NEW_ACCOUNT, - fork.transaction_data_floor_cost_calculator()( - data=bytes(initcode), contract_creation=True - ), + floor = fork.transaction_data_floor_cost_calculator()( + data=bytes(initcode), contract_creation=True ) + expected_gas_used = max(regular_intrinsic, floor) else: # For a minimal CREATE tx deploying Op.STOP (1 byte), # state gas (new account) dominates regular gas. @@ -2137,89 +2153,120 @@ def test_create_account_charge_reduces_child_gas( ], ) @pytest.mark.valid_from("EIP8037") -def test_failed_create_tx_refunds_intrinsic_new_account( +def test_failed_create_tx_refills_top_frame_new_account( state_test: StateTestFiller, pre: Alloc, fork: Fork, init_code: Bytecode, ) -> None: """ - Verify the NEW_ACCOUNT × CPSB portion of intrinsic_state_gas is - refunded on creation-tx revert/halt. Block state-gas excludes it - so header gas_used reflects only the regular component, and the - sender's receipt reflects the same refund via cumulative_gas_used. + Verify the top-frame NEW_ACCOUNT of a creation tx is refilled when the + initcode fails. - Gas consumed must be above the floor for the test to work, hence - the increased memory consumption in some of the initcodes. + Under EIP-2780 the created account's ``NEW_ACCOUNT`` state gas is + charged in the top-frame preparation (not the intrinsic), so + ``gas_limit`` must cover it for the initcode to run at all. When the + initcode then fails the whole creation rolls back and no account + persists: + + * REVERT preserves ``gas_left`` and ``refill_frame_state_gas`` returns + the spilled ``NEW_ACCOUNT`` to it, so the state block nets to zero + and only the regular consumption counts as work. The tiny init code + leaves the decomposed calldata floor above that consumption, so the + amount billed (receipt) is pinned to the floor while the header + excludes the floor top-up. + * HALT (INVALID) refills the spilled ``NEW_ACCOUNT`` to ``gas_left`` + and then burns all of it, so the sender pays the full ``gas_limit``. """ + gas_costs = fork.gas_costs() intrinsic_calc = fork.transaction_intrinsic_cost_calculator() - create_state_gas = fork.create_state_gas(code_size=0) - intrinsic_total = intrinsic_calc( - calldata=bytes(init_code), contract_creation=True + intrinsic_regular = intrinsic_calc( + calldata=bytes(init_code), + contract_creation=True, + return_cost_deducted_prior_execution=True, + ) + # gas_limit must cover the top-frame NEW_ACCOUNT and the initcode's own + # regular execution so the initcode runs to completion. + gas_limit = ( + intrinsic_regular + + gas_costs.NEW_ACCOUNT + + init_code.regular_cost(fork) + + 1000 ) - intrinsic_regular = intrinsic_total - create_state_gas - gas_limit = intrinsic_total + init_code.regular_cost(fork) + 1000 if init_code == Op.INVALID: - regular_consumed = gas_limit - intrinsic_total + # Exceptional halt burns all gas_left (the refilled NEW_ACCOUNT + # included). + expected_gas_used = gas_limit + expected_header_gas = gas_limit else: - regular_consumed = init_code.regular_cost(fork) + # REVERT refills the spilled NEW_ACCOUNT, netting the state block + # to zero, so only the regular consumption counts as work. + regular_consumed = intrinsic_regular + init_code.regular_cost(fork) + # The tiny init code leaves the decomposed calldata floor above + # the regular gas consumed: the receipt bills at the floor, while + # the header's regular-gas accounting excludes the floor top-up. + floor = fork.transaction_data_floor_cost_calculator()( + data=bytes(init_code), contract_creation=True + ) + expected_gas_used = max(regular_consumed, floor) + expected_header_gas = regular_consumed - expected_gas_used = intrinsic_regular + regular_consumed - expected_cumulative = intrinsic_total + regular_consumed - create_state_gas - # A tiny init code can leave the decomposed calldata floor above the - # regular gas consumed, pinning gas_used to the floor. - data_floor_calc = fork.transaction_data_floor_cost_calculator() - floor = data_floor_calc(data=init_code, contract_creation=True) - assert expected_gas_used > floor - assert expected_cumulative > floor + sender = pre.fund_eoa() + created = compute_create_address(address=sender, nonce=0) tx = Transaction( to=None, data=init_code, gas_limit=gas_limit, - sender=pre.fund_eoa(), + sender=sender, expected_receipt=TransactionReceipt( - cumulative_gas_used=expected_cumulative, + cumulative_gas_used=expected_gas_used, ), ) state_test( pre=pre, - post={}, + post={created: Account.NONEXISTENT}, tx=tx, - blockchain_test_header_verify=Header(gas_used=expected_gas_used), + blockchain_test_header_verify=Header(gas_used=expected_header_gas), ) @pytest.mark.pre_alloc_mutable() @pytest.mark.valid_from("EIP8037") -def test_create_tx_collision_refunds_intrinsic_new_account( +def test_create_tx_collision_no_new_account_charge( blockchain_test: BlockchainTestFiller, pre: Alloc, fork: Fork, ) -> None: """ - Verify the NEW_ACCOUNT × CPSB portion of intrinsic_state_gas is - refunded on creation-tx address collision, so block state-gas - excludes it and header gas_used reflects only the regular - consumption (full forwarded gas, no initcode runs). + Verify a creation-tx address collision charges no NEW_ACCOUNT. + + Under EIP-2780 the created account's ``NEW_ACCOUNT`` is a top-frame + charge, but on an address collision the target already exists + pre-tx, the create path returns ``AddressCollision`` before the top + frame is prepared, and no ``NEW_ACCOUNT`` is ever charged. The full + forwarded gas is burned as regular (no initcode runs) and block + state-gas is zero, so header ``gas_used`` equals the whole + ``gas_limit``. """ intrinsic_calc = fork.transaction_intrinsic_cost_calculator() - create_state_gas = fork.create_state_gas(code_size=0) init_code = Op.STOP - intrinsic_total = intrinsic_calc( + intrinsic_regular = intrinsic_calc( calldata=bytes(init_code), contract_creation=True ) - gas_limit = intrinsic_total + 1000 + gas_limit = intrinsic_regular + 1000 sender = pre.fund_eoa() collision_target = compute_create_address(address=sender, nonce=0) pre[collision_target] = Account(nonce=1) - expected_gas_used = gas_limit - create_state_gas + # Collision burns the full forwarded gas as regular; state block is + # zero (no NEW_ACCOUNT charged). + expected_gas_used = gas_limit tx = Transaction( to=None, @@ -2236,7 +2283,7 @@ def test_create_tx_collision_refunds_intrinsic_new_account( header_verify=Header(gas_used=expected_gas_used), ), ], - post={}, + post={collision_target: Account(nonce=1)}, ) @@ -2478,6 +2525,12 @@ def test_selfdestruct_in_create_tx_initcode( """ Verify state gas accounting when a creation tx's initcode immediately SELFDESTRUCTs to a new beneficiary. + + Under EIP-2780 the created contract's ``NEW_ACCOUNT`` is charged at + the top frame from ``gas_left`` (not the intrinsic), so ``gas_limit`` + must cover it on top of the initcode. The block state gas is the + created contract's ``NEW_ACCOUNT`` plus the fresh beneficiary's + ``NEW_ACCOUNT`` charged by the SELFDESTRUCT. """ gas_costs = fork.gas_costs() create_state_gas = fork.create_state_gas(code_size=0) @@ -2489,14 +2542,16 @@ def test_selfdestruct_in_create_tx_initcode( sender = pre.fund_eoa() intrinsic_calc = fork.transaction_intrinsic_cost_calculator() - intrinsic_total = intrinsic_calc( + intrinsic_regular = intrinsic_calc( calldata=bytes(initcode), contract_creation=True, sends_value=True ) + # State: the created contract's top-frame NEW_ACCOUNT plus the fresh + # beneficiary's NEW_ACCOUNT from the SELFDESTRUCT. expected_state = create_state_gas + gas_costs.NEW_ACCOUNT initcode_gas = initcode.gas_cost(fork) - gas_limit = intrinsic_total + initcode_gas + 1000 + gas_limit = intrinsic_regular + gas_costs.NEW_ACCOUNT + initcode_gas + 1000 tx = Transaction( sender=sender, @@ -2536,8 +2591,14 @@ def test_inner_create_succeeds_code_deposit_state_gas( outer_outcome: str, ) -> None: """ - Verify state gas accumulation and top-level failure refund in a + Verify state gas accumulation and top-level failure handling in a creation tx whose initcode runs a successful inner CREATE. + + Under EIP-2780 the outer (tx-level) created account's ``NEW_ACCOUNT`` + is charged at the top frame from ``gas_left`` (not the intrinsic), so + ``gas_limit`` must cover it on top of the inner CREATE's own state + gas. On success the block state gas is the outer ``NEW_ACCOUNT`` plus + the inner account creation and code deposit. """ gas_costs = fork.gas_costs() outer_state_gas = fork.create_state_gas(code_size=0) @@ -2579,7 +2640,16 @@ def test_inner_create_succeeds_code_deposit_state_gas( initcode_gas = initcode.regular_cost(fork) else: initcode_gas = initcode.gas_cost(fork) - gas_limit = intrinsic_total + initcode_gas + inner_code_deposit + 1000 + # The outer created account's NEW_ACCOUNT is a top-frame state charge + # under EIP-2780; gas_limit must cover it alongside the initcode and + # the inner code deposit. + gas_limit = ( + intrinsic_total + + gas_costs.NEW_ACCOUNT + + initcode_gas + + inner_code_deposit + + 1000 + ) create_address = compute_create_address(address=sender, nonce=0) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_delegation_pointer.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_delegation_pointer.py index 4907e573878..4f31f16b85d 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_delegation_pointer.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_delegation_pointer.py @@ -41,9 +41,6 @@ def test_sstore_via_delegation_pointer( contract code in the EOA's context. The SSTORE state gas should be charged from the reservoir just as it would for a direct call. """ - auth_state_gas = fork.transaction_intrinsic_state_gas( - authorization_count=1, - ) sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) storage = Storage() @@ -54,17 +51,25 @@ def test_sstore_via_delegation_pointer( # EOA with pre-existing delegation to the contract delegator = pre.fund_eoa(delegation=contract) + # The authorization re-targets an already-delegated authority whose + # nonce (1, from the delegation setup) no longer matches nonce=0, so + # it is invalid and charges no top-frame state gas. + authorization = AuthorizationTuple( + address=contract, + nonce=0, + signer=delegator, + creates_account=False, + writes_delegation=False, + first_write=False, + ) + auth_state_gas = fork.transaction_top_frame_state_gas( + authorizations=[authorization] + ) sender = pre.fund_eoa() tx = Transaction( to=delegator, state_gas_reservoir=auth_state_gas + sstore_state_gas, - authorization_list=[ - AuthorizationTuple( - address=contract, - nonce=0, - signer=delegator, - ), - ], + authorization_list=[authorization], sender=sender, ) @@ -117,12 +122,9 @@ def test_delegation_pointer_new_account_state_gas( is charged identically to a direct call. """ gas_costs = fork.gas_costs() - auth_state_gas = fork.transaction_intrinsic_state_gas( - authorization_count=1, - ) new_account_state_gas = gas_costs.NEW_ACCOUNT - target = 0xDEAD + target = pre.nonexistent_account() parent_storage = Storage() contract = pre.deploy_contract( @@ -138,17 +140,26 @@ def test_delegation_pointer_new_account_state_gas( # EOA delegates to the contract delegator = pre.fund_eoa(delegation=contract, amount=1) + # The authorization re-targets an already-delegated authority whose + # nonce (1, from the delegation setup) no longer matches nonce=0, so + # it is invalid and charges no top-frame state gas. + authorization = AuthorizationTuple( + address=contract, + nonce=0, + signer=delegator, + creates_account=False, + writes_delegation=False, + first_write=False, + ) + auth_state_gas = fork.transaction_top_frame_state_gas( + authorizations=[authorization] + ) + sender = pre.fund_eoa() tx = Transaction( to=delegator, state_gas_reservoir=auth_state_gas + new_account_state_gas, - authorization_list=[ - AuthorizationTuple( - address=contract, - nonce=0, - signer=delegator, - ), - ], + authorization_list=[authorization], sender=sender, ) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_pricing.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_pricing.py index 46091384d00..d52ac0d60b1 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_pricing.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_pricing.py @@ -330,11 +330,10 @@ def test_intrinsic_regular_gas_exceeds_cap( return_cost_deducted_prior_execution=True, ) floor = floor_cost(data=b"", access_list=access_list) - state = fork.transaction_intrinsic_state_gas() - tx_gas = regular + state + 1_000_000 + tx_gas = regular + 1_000_000 assert max(regular, floor) > cap, "cap check must fire" - assert regular + state <= tx_gas, "sufficiency check must not fire" + assert regular <= tx_gas, "sufficiency check must not fire" assert floor <= tx_gas tx = Transaction( @@ -380,12 +379,11 @@ def test_intrinsic_regular_gas_exceeds_cap_with_floor_below_cap( return_cost_deducted_prior_execution=True, ) floor = floor_cost(data=b"", access_list=access_list) - state = fork.transaction_intrinsic_state_gas() - tx_gas = regular + state + 1_000_000 + tx_gas = regular + 1_000_000 assert regular > cap, "regular operand must exceed the cap" assert floor < cap, "calldata floor must stay below the cap" - assert regular + state <= tx_gas, "sufficiency check must not fire" + assert regular <= tx_gas, "sufficiency check must not fire" tx = Transaction( ty=1, @@ -671,22 +669,41 @@ def test_auth_state_gas_scales_with_cpsb( fork: Fork, ) -> None: """ - Test SetCode authorization state gas scales with block gas limit. + Test SetCode authorization top-frame state gas scales with cpsb. - A type-4 tx with one authorization charges - (STATE_BYTES_PER_NEW_ACCOUNT + STATE_BYTES_PER_AUTH_BASE) * cpsb - of intrinsic state gas for the new account delegation. + Under EIP-2780 an authorization's state-dependent cost is charged at + the top frame, not the intrinsic. An existing authority gaining a + fresh delegation pays ``AUTH_BASE`` (= STATE_BYTES_PER_AUTH_BASE * + cost_per_state_byte) of state gas there. The tx gas is sized so the + charge draws from the reservoir when block_gas_limit is large and + spills into gas_left when it is small; the delegated call must succeed + in every regime. """ gas_limit_cap = fork.transaction_gas_limit_cap() assert gas_limit_cap is not None env = Environment(gas_limit=block_gas_limit) - auth_state_gas = fork.transaction_intrinsic_state_gas( - authorization_count=1, - ) - delegate = pre.deploy_contract(code=Op.SSTORE(0, 1)) + # A cheap STOP delegate: the delegated call only needs to resolve and + # succeed to prove the delegation applied; the state gas under test is + # the top-frame AUTH_BASE, not the delegate's own work. + delegate = pre.deploy_contract(code=Op.STOP) signer = pre.fund_eoa() + authorization_list = [ + AuthorizationTuple( + address=delegate, + nonce=0, + signer=signer, + creates_account=False, + writes_delegation=True, + ), + ] + # Top-frame state gas for the existing authority's fresh delegation + # (AUTH_BASE = STATE_BYTES_PER_AUTH_BASE * cpsb). + auth_state_gas = fork.transaction_top_frame_state_gas( + authorizations=authorization_list, + ) + storage = Storage() target = pre.deploy_contract( code=Op.SSTORE( @@ -701,13 +718,7 @@ def test_auth_state_gas_scales_with_cpsb( to=target, gas_limit=tx_gas, sender=pre.fund_eoa(), - authorization_list=[ - AuthorizationTuple( - address=delegate, - nonce=0, - signer=signer, - ), - ], + authorization_list=authorization_list, ) post = {target: Account(storage=storage)} diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_reservoir.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_reservoir.py index 7ec5dd62f87..445cb1d885c 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_reservoir.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_reservoir.py @@ -344,46 +344,49 @@ def test_creation_tx_regular_check_uses_full_tx_gas( Verify the regular check uses the full `tx.gas` (no subtraction). The EIP regular check is `min(TX_MAX, tx.gas) > regular_available`. - For a creation tx, `intrinsic.state = GAS_NEW_ACCOUNT`. This test - sizes a creation tx whose raw `tx.gas` exceeds `regular_available` - while `tx.gas - intrinsic.state` would fit; it must be rejected. A - formula subtracting `intrinsic.state` would have wrongly accepted. + Under EIP-2780 a creation tx has `intrinsic.state == 0` (the created + account's `NEW_ACCOUNT` moved to the top frame), so its intrinsic is + regular-only. This test sizes a creation tx whose full `tx.gas` + exceeds the remaining regular budget by one — it must be rejected. A + formula that instead used the execution gas + (`tx.gas - intrinsic_regular`) would have wrongly accepted. """ gas_limit_cap = fork.transaction_gas_limit_cap() assert gas_limit_cap is not None - # `intrinsic_regular` for a creation tx is cpsb-free - # (GAS_TX_BASE + REGULAR_GAS_CREATE + init_code_cost), so - # reading it at the current cpsb and using it to size the block - # gives a stable `block_gas_limit` independent of cpsb. + # The creation intrinsic is regular-only and cpsb-free + # (GAS_TX_BASE + REGULAR_GAS_CREATE + init_code_cost), giving a stable + # `block_gas_limit` independent of cpsb. intrinsic_regular = fork.transaction_intrinsic_cost_calculator()( contract_creation=True - ) - fork.transaction_intrinsic_state_gas(contract_creation=True) + ) - # Tight boundary: after the filler consumes gas_limit_cap, the - # remaining regular is exactly intrinsic_regular + 1. The strict - # formula `min(TX_MAX, tx.gas)` rejects (tx.gas = intrinsic_total - # > intrinsic_regular + 1); a formula subtracting `intrinsic.state` - # would accept (tx.gas - intrinsic.state == intrinsic_regular). + # Tight boundary: after the filler consumes gas_limit_cap, exactly + # `intrinsic_regular + 1` regular gas remains in the block. block_gas_limit = gas_limit_cap + intrinsic_regular + 1 - intrinsic_state = fork.transaction_intrinsic_state_gas( - contract_creation=True, - ) - create_tx_gas = fork.transaction_intrinsic_cost_calculator()( - contract_creation=True, - ) + # Ask for one more than the remaining regular budget: min(TX_MAX, + # tx.gas) == tx.gas exceeds `remaining_regular` by one, so the strict + # check rejects. The tx still carries more than its own intrinsic, so + # it is a valid creation tx on its own — only the block-level regular + # check fails. + remaining_regular = block_gas_limit - gas_limit_cap + create_tx_gas = remaining_regular + 1 # Filler consumes the full regular cap (OOG on INVALID). filler = pre.deploy_contract(code=Op.INVALID) - remaining_regular = block_gas_limit - gas_limit_cap - - assert create_tx_gas > remaining_regular, ( + assert create_tx_gas <= gas_limit_cap, ( + "min(TX_MAX, tx.gas) must be tx.gas for this boundary" + ) + assert create_tx_gas > intrinsic_regular, ( + "tx must carry more than its own intrinsic" + ) + assert min(gas_limit_cap, create_tx_gas) > remaining_regular, ( "strict formula must reject: full tx.gas exceeds remaining regular" ) - assert create_tx_gas - intrinsic_state <= remaining_regular, ( - "a subtracting formula would have accepted" + assert create_tx_gas - intrinsic_regular <= remaining_regular, ( + "a formula using execution gas would have accepted" ) filler_tx = Transaction( @@ -856,7 +859,7 @@ def test_creation_tx_failure_preserves_intrinsic_state_gas( gas_limit_cap = fork.transaction_gas_limit_cap() assert gas_limit_cap is not None - create_intrinsic_state = fork.transaction_intrinsic_state_gas( + create_intrinsic_state = fork.transaction_top_frame_state_gas( contract_creation=True, ) sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) @@ -1342,34 +1345,32 @@ def test_nested_state_gas_refund_consumed_at_depth( consume_at: str, ) -> None: """ - Verify how state gas refund credits route under LIFO refills. + Verify no state gas credit routes to the reservoir under LIFO refills. Refund sources SSTORE `0->1->0`, CREATE collision, and CREATE initcode revert all refund LIFO, so the credit returns to - `gas_left`, not the reservoir. A SetCode auth on an `existing_leaf` - authority still credits the reservoir directly at message entry. + `gas_left`, not the reservoir. Under EIP-2780 a SetCode auth on an + `existing_leaf` authority no longer over-charges and refunds: it + charges only ``AUTH_BASE`` at the top frame, crediting nothing back. A probe CALL sized one short of covering an SSTORE forwards a fixed - gas to a sub-call, so it can only observe the reservoir, never the - `gas_left` refund. It therefore succeeds only for the auth scenario - and fails (stores 0) for the SSTORE/CREATE scenarios whose refund - lands in `gas_left`. + gas to a sub-call, so it can only observe the reservoir, never a + `gas_left` refund. With no scenario crediting the reservoir the probe + always OOGs and CALL returns 0. The auth scenario additionally pins + the applied delegation via post-state, guarding against a regression + that re-introduces a reservoir credit for existing-authority auths. """ is_auth_scenario = refund_scenario == "auth_existing_leaf" probe_address = pre.deploy_contract(code=Op.SSTORE(0, 1)) probe_gas = Op.SSTORE(0, 1).gas_cost(fork) - 1 consumer_storage = Storage() - # The probe forwards a fixed gas and can only see the reservoir, - # so it succeeds (CALL returns 1) only when the refund credited the - # reservoir, the auth scenario. Otherwise the LIFO refund lands in - # gas_left, the sub-call OOGs, and CALL returns 0. - if is_auth_scenario: - probe_label = "auth_reservoir_probe_must_succeed" - probe_result = 1 - else: - probe_label = "gas_left_refund_probe_must_fail" - probe_result = 0 + # The probe forwards a fixed gas and can only see the reservoir. No + # scenario credits the reservoir under EIP-2780 (SSTORE/CREATE refunds + # land in gas_left LIFO; the existing-leaf auth incurs no refund), so + # the sub-call OOGs and CALL returns 0 in every case. + probe_label = "no_reservoir_credit_probe_must_fail" + probe_result = 0 consume_op = Op.SSTORE( consumer_storage.store_next(probe_result, probe_label), Op.CALL(gas=probe_gas, address=probe_address), diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py index 56b19ec4b8f..f0df65f9569 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py @@ -1,18 +1,40 @@ """ -Test EIP-7702 SetCode authorization state gas under EIP-8037. - -Each authorization charges intrinsic state gas for the new account -plus auth base bytes, and intrinsic regular gas. When the authority -account already exists, the new-account state gas is refunded to the -state gas reservoir. +Test EIP-7702 SetCode authorization state gas under the EIP-2780 +top-frame charge model. + +Under EIP-2780 (Amsterdam) an authorization's intrinsic cost is only the +state-independent ``REGULAR_PER_AUTH_BASE_COST``; there is no intrinsic +auth state gas and there are no auth refunds. The state-dependent costs +are charged lazily at the top frame in ``set_delegation``, keyed on each +authority's pre-transaction state: + +* ``NEW_ACCOUNT`` (state) + ``ACCOUNT_WRITE`` (regular) when the + authority's account leaf does not exist pre-tx (it gets created); and +* ``AUTH_BASE`` (state) when a net-new delegation indicator is written -- + the authority holds no delegation both before the transaction and at + the point the authorization applies, and the authorization is not a + clear. + +For a value-free type-4 transaction whose recipient runs code ``code``: + +* the receipt ``cumulative_gas_used`` is the plain sum + ``intrinsic_regular + top_frame_regular + top_frame_state + + execution_regular + execution_state`` (no refund term); and +* the header ``gas_used`` is ``max(block_regular, block_state)`` where + ``block_regular = intrinsic_regular + top_frame_regular + + execution_regular`` and ``block_state = top_frame_state + + execution_state``. Tests for [EIP-8037: State Creation Gas Cost Increase] -(https://eips.ethereum.org/EIPS/eip-8037). +(https://eips.ethereum.org/EIPS/eip-8037); the ``valid_from("EIP8037")`` +markers resolve to Amsterdam, where EIP-2780 governs the charge model. """ import pytest from execution_testing import ( + AccessList, Account, + Address, Alloc, AuthorizationTuple, Block, @@ -21,6 +43,7 @@ Fork, Header, Op, + RecipientType, StateTestFiller, Storage, Transaction, @@ -39,6 +62,55 @@ REFERENCE_SPEC_VERSION = ref_spec_8037.version +def _auth_gas( + fork: Fork, + authorization_list: list[AuthorizationTuple], + *, + recipient_type: RecipientType = RecipientType.CONTRACT, + sends_value: bool = False, + delegation_warm: bool = False, +) -> tuple[int, int, int]: + """Return (intrinsic_regular, top_frame_regular, top_frame_state).""" + intrinsic_regular = fork.transaction_intrinsic_cost_calculator()( + authorization_list_or_count=authorization_list, + recipient_type=recipient_type, + sends_value=sends_value, + return_cost_deducted_prior_execution=True, + ) + top_frame_regular = fork.transaction_top_frame_gas_calculator()( + recipient_type=recipient_type, + sends_value=sends_value, + delegation_warm=delegation_warm, + authorizations=authorization_list, + ) + top_frame_state = fork.transaction_top_frame_state_gas( + recipient_type=recipient_type, + sends_value=sends_value, + authorizations=authorization_list, + ) + return intrinsic_regular, top_frame_regular, top_frame_state + + +def _receipt_and_header( + intrinsic_regular: int, + top_frame_regular: int, + top_frame_state: int, + *, + execution_regular: int = 0, + execution_state: int = 0, +) -> tuple[int, int]: + """ + Return the (receipt cumulative_gas_used, header gas_used) for a + successful (non-reverting) transaction under the no-refund top-frame + model. + """ + block_regular = intrinsic_regular + top_frame_regular + execution_regular + block_state = top_frame_state + execution_state + cumulative_gas_used = block_regular + block_state + header_gas_used = max(block_regular, block_state) + return cumulative_gas_used, header_gas_used + + @pytest.mark.parametrize( "num_auths", [ @@ -54,39 +126,55 @@ def test_authorization_state_gas_scaling( fork: Fork, ) -> None: """ - Test authorization intrinsic state gas scales with count. - - Each authorization adds - (STATE_BYTES_PER_NEW_ACCOUNT + STATE_BYTES_PER_AUTH_BASE) * - cost_per_state_byte of intrinsic state gas. The transaction - should succeed with enough total gas. + Test the top-frame authorization state gas scales with count. + + Each authority is an existing funded EOA gaining a fresh delegation, + so ``set_delegation`` charges only the top-frame ``AUTH_BASE`` per + authorization (no ``NEW_ACCOUNT`` / ``ACCOUNT_WRITE`` and no refund). + The receipt gas is the regular intrinsic plus ``num_auths * + AUTH_BASE`` and the header ``gas_used`` is the max of the regular and + state blocks. """ - auth_state_gas = fork.transaction_intrinsic_state_gas( - authorization_count=1, - ) - contract = pre.deploy_contract(code=Op.STOP) - authorization_list = [] - for _ in range(num_auths): - signer = pre.fund_eoa() - authorization_list.append( - AuthorizationTuple( - address=contract, - nonce=1, - signer=signer, - ), + signers = [pre.fund_eoa() for _ in range(num_auths)] + authorization_list = [ + AuthorizationTuple( + address=contract, + nonce=0, + signer=signer, + creates_account=False, + writes_delegation=True, ) + for signer in signers + ] + + intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( + fork, authorization_list + ) + cumulative_gas_used, header_gas_used = _receipt_and_header( + intrinsic_regular, top_frame_regular, top_frame_state + ) - sender = pre.fund_eoa() tx = Transaction( to=contract, - state_gas_reservoir=auth_state_gas * num_auths, authorization_list=authorization_list, - sender=sender, + sender=pre.fund_eoa(), + expected_receipt=TransactionReceipt( + cumulative_gas_used=cumulative_gas_used, + ), ) - state_test(pre=pre, post={}, tx=tx) + post = { + signer: Account(code=Spec7702.delegation_designation(contract)) + for signer in signers + } + state_test( + pre=pre, + post=post, + tx=tx, + blockchain_test_header_verify=Header(gas_used=header_gas_used), + ) @pytest.mark.exception_test @@ -98,57 +186,43 @@ def test_authorization_state_gas_scaling( pytest.param(3, id="three_auths"), ], ) -@pytest.mark.parametrize( - "extra_gas", - [ - pytest.param(0, id="at_regular_intrinsic"), - pytest.param(1, id="one_above_regular_intrinsic"), - pytest.param(-1, id="one_below_total_intrinsic"), - ], -) @pytest.mark.valid_from("EIP8037") def test_set_code_tx_below_total_intrinsic( state_test: StateTestFiller, pre: Alloc, fork: Fork, num_auths: int, - extra_gas: int, ) -> None: """ - Reject set_code tx when gas_limit covers regular but not state intrinsic. - - EIP-8037 charges each authorization a state component - `(STATE_BYTES_PER_NEW_ACCOUNT + STATE_BYTES_PER_AUTH_BASE) * - COST_PER_STATE_BYTE`; total intrinsic = `regular + N * state` for - N authorizations. Sweep N = 1, 2, 3 and pin gas_limit at the - lower end of the rejected interval to catch implementations that - omit the state component from the pre-validate check. + Reject a set_code tx one gas below the (now regular-only) intrinsic. + + Under EIP-2780 the authorization intrinsic is entirely regular (the + state-dependent costs moved to the top frame), so the intrinsic gas + the transaction must cover is exactly + ``fork.transaction_intrinsic_cost_calculator()(auth_list)``. Sweeping + ``num_auths`` and pinning ``gas_limit`` at ``intrinsic - 1`` catches + an implementation that omits the repriced per-authorization base cost + from the pre-validate check. """ - intrinsic_state = fork.transaction_intrinsic_state_gas( - authorization_count=num_auths, - ) - total_intrinsic = fork.transaction_intrinsic_cost_calculator()( - authorization_list_or_count=num_auths, - ) - intrinsic_regular = total_intrinsic - intrinsic_state - gas_limit = ( - intrinsic_regular if extra_gas >= 0 else total_intrinsic - ) + extra_gas - assert gas_limit < total_intrinsic - contract = pre.deploy_contract(code=Op.STOP) authorization_list = [ AuthorizationTuple( address=contract, - nonce=1, + nonce=0, signer=pre.fund_eoa(), + creates_account=False, + writes_delegation=True, ) for _ in range(num_auths) ] + intrinsic = fork.transaction_intrinsic_cost_calculator()( + authorization_list_or_count=authorization_list, + ) + tx = Transaction( to=contract, - gas_limit=gas_limit, + gas_limit=intrinsic - 1, authorization_list=authorization_list, sender=pre.fund_eoa(), error=TransactionException.INTRINSIC_GAS_TOO_LOW, @@ -158,43 +232,58 @@ def test_set_code_tx_below_total_intrinsic( @pytest.mark.valid_from("EIP8037") -def test_existing_account_refund( +def test_existing_account_no_refund( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: """ - Test authorization targeting existing account refunds state gas. - - When the authority account already exists, new-account state gas - is refunded to the state gas reservoir and subtracted from - intrinsic_state_gas. Only 23 * cost_per_state_byte is effectively - charged. + An existing-authority delegation is charged the reduced top-frame + cost directly, with no refund. + + The authority is an existing funded EOA gaining a fresh delegation. + Its leaf exists, so ``set_delegation`` charges neither ``NEW_ACCOUNT`` + nor ``ACCOUNT_WRITE`` (and, unlike the superseded EIP-8037 behaviour, + refunds neither); it charges only the top-frame ``AUTH_BASE``. The + receipt gas is therefore exactly the regular intrinsic plus + ``AUTH_BASE``. """ contract = pre.deploy_contract(code=Op.STOP) - # Signer is an existing funded EOA (account_exists = True) signer = pre.fund_eoa() - authorization_list = [ AuthorizationTuple( address=contract, nonce=0, signer=signer, + creates_account=False, + writes_delegation=True, ), ] - # Only need enough state gas for STATE_BYTES_PER_AUTH_BASE, not - # the full (STATE_BYTES_PER_NEW_ACCOUNT + STATE_BYTES_PER_AUTH_BASE), - # because existing account refunds STATE_BYTES_PER_NEW_ACCOUNT - sender = pre.fund_eoa() + intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( + fork, authorization_list + ) + cumulative_gas_used, header_gas_used = _receipt_and_header( + intrinsic_regular, top_frame_regular, top_frame_state + ) + tx = Transaction( to=contract, - state_gas_reservoir=0, authorization_list=authorization_list, - sender=sender, + sender=pre.fund_eoa(), + expected_receipt=TransactionReceipt( + cumulative_gas_used=cumulative_gas_used, + ), ) - state_test(pre=pre, post={}, tx=tx) + post = {signer: Account(code=Spec7702.delegation_designation(contract))} + state_test( + pre=pre, + post=post, + tx=tx, + blockchain_test_header_verify=Header(gas_used=header_gas_used), + ) @pytest.mark.valid_from("EIP8037") @@ -204,55 +293,67 @@ def test_mixed_new_and_existing_auths( fork: Fork, ) -> None: """ - Test mixed new and existing account authorizations. + Test mixed new and existing account authorizations at the top frame. - One authorization targets an existing account (gets refund), - another targets a new account (no refund). The total state gas - should reflect the mixed charges. + One authority is an existing EOA (charged only ``AUTH_BASE``); the + other does not exist pre-tx (charged ``NEW_ACCOUNT`` + ``ACCOUNT_WRITE`` + for the leaf plus ``AUTH_BASE`` for the net-new indicator). The total + top-frame charge is the sum of the two, with no refund. """ - full_auth_state_gas = fork.transaction_intrinsic_state_gas( - authorization_count=1, - ) - contract = pre.deploy_contract(code=Op.STOP) - # Existing account (gets new-account state gas refund) existing_signer = pre.fund_eoa() - - # New account — fund_eoa creates it in pre-state, so we need - # an address that doesn't exist. Use fund_eoa with amount=0 - # Actually fund_eoa always creates the account. For a "new" - # authorization, we need the nonce to be wrong so it's treated - # as a new account entry, or we accept that both are existing. - # In practice, all signers from fund_eoa are existing accounts. - # The key difference is whether account_exists returns True. - # Since fund_eoa creates the account, both are existing. - # This test verifies both auths succeed with appropriate gas. - second_signer = pre.fund_eoa() + new_signer = pre.fund_eoa(amount=0) authorization_list = [ AuthorizationTuple( address=contract, nonce=0, signer=existing_signer, + creates_account=False, + writes_delegation=True, ), AuthorizationTuple( address=contract, nonce=0, - signer=second_signer, + signer=new_signer, + creates_account=True, + writes_delegation=True, ), ] - # Both are existing accounts, so both get the new-account state gas refund - sender = pre.fund_eoa() + intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( + fork, authorization_list + ) + cumulative_gas_used, header_gas_used = _receipt_and_header( + intrinsic_regular, top_frame_regular, top_frame_state + ) + tx = Transaction( to=contract, - state_gas_reservoir=full_auth_state_gas * 2, authorization_list=authorization_list, - sender=sender, + sender=pre.fund_eoa(), + expected_receipt=TransactionReceipt( + cumulative_gas_used=cumulative_gas_used, + ), ) - state_test(pre=pre, post={}, tx=tx) + post = { + existing_signer: Account( + code=Spec7702.delegation_designation(contract), + ), + new_signer: Account( + nonce=1, + balance=0, + code=Spec7702.delegation_designation(contract), + ), + } + state_test( + pre=pre, + post=post, + tx=tx, + blockchain_test_header_verify=Header(gas_used=header_gas_used), + ) @pytest.mark.valid_from("EIP8037") @@ -262,21 +363,19 @@ def test_authorization_with_sstore( fork: Fork, ) -> None: """ - Test SetCode authorization combined with SSTORE. + Test SetCode authorization combined with a recipient SSTORE. - A SetCode transaction authorizes delegation and then the called - contract performs an SSTORE. Both the authorization state gas and - the SSTORE state gas are charged. + The authority (an existing EOA) gains a fresh delegation, charged the + top-frame ``AUTH_BASE``; the called recipient then performs an SSTORE + whose regular and state costs are charged during execution. The header + ``gas_used`` is the max of the regular block and the (``AUTH_BASE`` + + SSTORE) state block. """ - auth_state_gas = fork.transaction_intrinsic_state_gas( - authorization_count=1, - ) - sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) - storage = Storage() - contract = pre.deploy_contract( - code=Op.SSTORE(storage.store_next(1), 1), - ) + code = Op.SSTORE(storage.store_next(1), 1) + contract = pre.deploy_contract(code=code) + execution_regular = code.regular_cost(fork) + execution_state = code.state_cost(fork) signer = pre.fund_eoa() authorization_list = [ @@ -284,67 +383,103 @@ def test_authorization_with_sstore( address=contract, nonce=0, signer=signer, + creates_account=False, + writes_delegation=True, ), ] - sender = pre.fund_eoa() + intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( + fork, authorization_list + ) + _, header_gas_used = _receipt_and_header( + intrinsic_regular, + top_frame_regular, + top_frame_state, + execution_regular=execution_regular, + execution_state=execution_state, + ) + tx = Transaction( to=contract, - state_gas_reservoir=auth_state_gas + sstore_state_gas, authorization_list=authorization_list, - sender=sender, + sender=pre.fund_eoa(), ) - post = {contract: Account(storage=storage)} - state_test(pre=pre, post=post, tx=tx) + post = { + contract: Account(storage=storage), + signer: Account(code=Spec7702.delegation_designation(contract)), + } + state_test( + pre=pre, + post=post, + tx=tx, + blockchain_test_header_verify=Header(gas_used=header_gas_used), + ) @pytest.mark.valid_from("EIP8037") -def test_existing_account_refund_enables_sstore( +def test_existing_account_no_refund_with_sstore( state_test: StateTestFiller, pre: Alloc, fork: Fork, ) -> None: """ - Test auth refund to reservoir enables subsequent state ops. - - When an authorization targets an existing account, the - new-account state gas refund goes to state_gas_reservoir. - This refunded gas should then be available for SSTORE state - gas in the execution phase. + An existing-authority auth and a recipient SSTORE are both charged + in full, with no refund reducing either. + + The existing authority pays only the top-frame ``AUTH_BASE`` (no + ``NEW_ACCOUNT`` / ``ACCOUNT_WRITE`` and no refund), and the recipient's + SSTORE pays its own regular + state costs. The receipt gas is the + exact sum of the intrinsic, the ``AUTH_BASE`` and the SSTORE cost; + there is no reservoir refund to draw on. """ - auth_state_gas = fork.transaction_intrinsic_state_gas( - authorization_count=1, - ) - sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) - storage = Storage() - contract = pre.deploy_contract( - code=Op.SSTORE(storage.store_next(1), 1), - ) + code = Op.SSTORE(storage.store_next(1), 1) + contract = pre.deploy_contract(code=code) + execution_regular = code.regular_cost(fork) + execution_state = code.state_cost(fork) - # Existing signer — gets new-account state gas refunded to reservoir signer = pre.fund_eoa() authorization_list = [ AuthorizationTuple( address=contract, nonce=0, signer=signer, + creates_account=False, + writes_delegation=True, ), ] - # Provide enough for auth intrinsic state gas, but rely on the - # existing-account refund to cover the SSTORE state gas - sender = pre.fund_eoa() + intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( + fork, authorization_list + ) + cumulative_gas_used, header_gas_used = _receipt_and_header( + intrinsic_regular, + top_frame_regular, + top_frame_state, + execution_regular=execution_regular, + execution_state=execution_state, + ) + tx = Transaction( to=contract, - state_gas_reservoir=auth_state_gas + sstore_state_gas, authorization_list=authorization_list, - sender=sender, + sender=pre.fund_eoa(), + expected_receipt=TransactionReceipt( + cumulative_gas_used=cumulative_gas_used, + ), ) - post = {contract: Account(storage=storage)} - state_test(pre=pre, post=post, tx=tx) + post = { + contract: Account(storage=storage), + signer: Account(code=Spec7702.delegation_designation(contract)), + } + state_test( + pre=pre, + post=post, + tx=tx, + blockchain_test_header_verify=Header(gas_used=header_gas_used), + ) @pytest.mark.parametrize( @@ -367,7 +502,7 @@ def test_existing_account_refund_enables_sstore( ], ) @pytest.mark.valid_from("EIP8037") -def test_auth_refund_block_gas_accounting( +def test_auth_block_gas_accounting( state_test: StateTestFiller, pre: Alloc, fork: Fork, @@ -375,64 +510,41 @@ def test_auth_refund_block_gas_accounting( authorize_to_null: bool, ) -> None: """ - Verify block + receipt gas accounting against per-authorization - state-gas refunds from `set_delegation`. - - Four signer pre-states span every refund branch: - - * `nonexistent` — no account leaf; no refund; - * `existing_leaf` — leaf, empty code; `NEW_ACCOUNT × CPSB` refilled; - * `existing_delegation` overwrite — leaf + delegation; full refill - (`NEW_ACCOUNT + AUTH_BASE`) as the 23 delegation bytes overwrite - in place; - * `existing_delegation` clear — `auth.address` = - `RESET_DELEGATION_ADDRESS`; same full refill, since the refill - keys off the *pre-state* code slot, not what we're writing. - - When the authority's account leaf already exists, the worst-case - `ACCOUNT_WRITE` charged at intrinsic time is additionally refunded - via the regular refund counter, subject to the refund cap. - - Verified via header `gas_used`, receipt `cumulative_gas_used`, and - the authority post-state (catches a silently-skipped auth). + Verify block + receipt gas accounting against the per-authorization + top-frame charge in ``set_delegation``. + + Six signer pre-states span every top-frame charge branch: + + * ``nonexistent`` + delegate -- leaf created and a net-new indicator + written: ``NEW_ACCOUNT`` + ``ACCOUNT_WRITE`` + ``AUTH_BASE``; + * ``nonexistent`` + clear -- leaf created, no indicator: + ``NEW_ACCOUNT`` + ``ACCOUNT_WRITE`` only; + * ``existing_leaf`` + delegate -- net-new indicator only: + ``AUTH_BASE``; + * ``existing_leaf`` + clear -- nothing beyond the intrinsic base; + * ``existing_delegation`` overwrite / clear -- already delegated + pre-tx, so no net-new indicator: nothing beyond the intrinsic base. + + No branch is refunded (the EIP-8037 over-charge-then-refund is gone). + Verified via header ``gas_used``, receipt ``cumulative_gas_used`` and + the authority post-state (which catches a silently-skipped auth). """ - intrinsic_state_gas = fork.transaction_intrinsic_state_gas( - authorization_count=1, - ) - total_intrinsic = fork.transaction_intrinsic_cost_calculator()( - authorization_list_or_count=1, - ) - intrinsic_regular = total_intrinsic - intrinsic_state_gas - new_account_refund = fork.gas_costs().REFUND_AUTH_PER_EXISTING_ACCOUNT - account_write = fork.gas_costs().ACCOUNT_WRITE - # Per-auth intrinsic state gas covers NEW_ACCOUNT + AUTH_BASE; the - # AUTH_BASE portion is what's left after stripping NEW_ACCOUNT. - auth_base_refund = intrinsic_state_gas - new_account_refund - contract_old = pre.deploy_contract(code=Op.STOP) contract_new = pre.deploy_contract(code=Op.STOP) - # AUTH_BASE is refunded when no new delegation-indicator bytes are - # written: either the authority already has an indicator (overwrite - # in place / clear) or `auth.address` is zero (no indicator written). if signer_pre_state == "nonexistent": signer = pre.fund_eoa(amount=0) pre_nonce = 0 - auth_refund = auth_base_refund if authorize_to_null else 0 - refund_counter = 0 + creates_account = True elif signer_pre_state == "existing_leaf": signer = pre.fund_eoa() pre_nonce = 0 - auth_refund = new_account_refund + ( - auth_base_refund if authorize_to_null else 0 - ) - refund_counter = account_write + creates_account = False elif signer_pre_state == "existing_delegation": # `fund_eoa(delegation=...)` sets the authority's nonce to 1. signer = pre.fund_eoa(delegation=contract_old) pre_nonce = 1 - auth_refund = new_account_refund + auth_base_refund - refund_counter = account_write + creates_account = False else: raise ValueError(f"unknown signer_pre_state: {signer_pre_state!r}") @@ -441,42 +553,45 @@ def test_auth_refund_block_gas_accounting( if authorize_to_null else contract_new ) + # A net-new delegation indicator is written only when the auth is not + # a clear and the authority was not already delegated before the tx. + writes_delegation = (not authorize_to_null) and ( + signer_pre_state != "existing_delegation" + ) + authorization_list = [ AuthorizationTuple( address=auth_target, nonce=pre_nonce, signer=signer, + creates_account=creates_account, + writes_delegation=writes_delegation, ), ] - post_signer = Account( - nonce=pre_nonce + 1, - code=( - b"" - if authorize_to_null - else Spec7702.delegation_designation(auth_target) - ), + intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( + fork, authorization_list ) - header_gas_used = max( - intrinsic_regular, - intrinsic_state_gas - auth_refund, + cumulative_gas_used, header_gas_used = _receipt_and_header( + intrinsic_regular, top_frame_regular, top_frame_state ) - # The state refill is not subject to the refund cap; the regular - # `ACCOUNT_WRITE` refund is. - gas_used_before_refund = total_intrinsic - auth_refund - regular_refund = min( - gas_used_before_refund // fork.max_refund_quotient(), - refund_counter, + + post_code = ( + b"" + if authorize_to_null + else Spec7702.delegation_designation(contract_new) ) - receipt_cumulative_gas_used = gas_used_before_refund - regular_refund + if signer_pre_state == "nonexistent": + post_signer = Account(nonce=pre_nonce + 1, balance=0, code=post_code) + else: + post_signer = Account(nonce=pre_nonce + 1, code=post_code) tx = Transaction( to=contract_new, - state_gas_reservoir=intrinsic_state_gas, authorization_list=authorization_list, sender=pre.fund_eoa(), expected_receipt=TransactionReceipt( - cumulative_gas_used=receipt_cumulative_gas_used, + cumulative_gas_used=cumulative_gas_used, ), ) @@ -489,60 +604,75 @@ def test_auth_refund_block_gas_accounting( @pytest.mark.valid_from("EIP8037") -def test_invalid_nonce_auth_still_charges_intrinsic_state_gas( +def test_invalid_nonce_auth_still_charges_intrinsic( state_test: StateTestFiller, pre: Alloc, fork: Fork, ) -> None: """ - Test invalid-nonce authorization still charges intrinsic state gas. + Test an invalid-nonce authorization still pays the intrinsic base. - An authorization with a wrong nonce is skipped during processing, - but its intrinsic state gas (135 * cpsb) is still charged upfront - as part of the transaction's intrinsic gas. + An authorization with a wrong nonce is skipped during + ``set_delegation``, so it writes no delegation indicator and incurs + no top-frame charge. Its state-independent + ``REGULAR_PER_AUTH_BASE_COST`` is still charged in the intrinsic, and + the authority is left untouched. """ - auth_state_gas = fork.transaction_intrinsic_state_gas( - authorization_count=1, - ) - contract = pre.deploy_contract(code=Op.STOP) signer = pre.fund_eoa() authorization_list = [ AuthorizationTuple( address=contract, - nonce=99, # Wrong nonce — auth will be skipped + nonce=99, # Wrong nonce -- auth will be skipped signer=signer, + creates_account=False, + writes_delegation=False, + first_write=False, ), ] - sender = pre.fund_eoa() + intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( + fork, authorization_list + ) + assert top_frame_regular == 0 + assert top_frame_state == 0 + cumulative_gas_used, header_gas_used = _receipt_and_header( + intrinsic_regular, top_frame_regular, top_frame_state + ) + tx = Transaction( to=contract, - state_gas_reservoir=auth_state_gas, authorization_list=authorization_list, - sender=sender, + sender=pre.fund_eoa(), + expected_receipt=TransactionReceipt( + cumulative_gas_used=cumulative_gas_used, + ), ) - state_test(pre=pre, post={}, tx=tx) + post = {signer: Account(nonce=0, code=b"")} + state_test( + pre=pre, + post=post, + tx=tx, + blockchain_test_header_verify=Header(gas_used=header_gas_used), + ) @pytest.mark.valid_from("EIP8037") -def test_invalid_chain_id_auth_still_charges_intrinsic_state_gas( +def test_invalid_chain_id_auth_still_charges_intrinsic( state_test: StateTestFiller, pre: Alloc, fork: Fork, ) -> None: """ - Test invalid-chain-id authorization still charges intrinsic state gas. + Test an invalid-chain-id authorization still pays the intrinsic base. An authorization with a mismatched chain ID is skipped during - processing, but intrinsic state gas is still charged upfront. + ``set_delegation`` and incurs no top-frame charge, but its + ``REGULAR_PER_AUTH_BASE_COST`` is still charged in the intrinsic and + the authority is left untouched. """ - auth_state_gas = fork.transaction_intrinsic_state_gas( - authorization_count=1, - ) - contract = pre.deploy_contract(code=Op.STOP) signer = pre.fund_eoa() @@ -550,20 +680,39 @@ def test_invalid_chain_id_auth_still_charges_intrinsic_state_gas( AuthorizationTuple( address=contract, nonce=0, - chain_id=9999, # Wrong chain ID — auth will be skipped + chain_id=9999, # Wrong chain ID -- auth will be skipped signer=signer, + creates_account=False, + writes_delegation=False, + first_write=False, ), ] - sender = pre.fund_eoa() + intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( + fork, authorization_list + ) + assert top_frame_regular == 0 + assert top_frame_state == 0 + cumulative_gas_used, header_gas_used = _receipt_and_header( + intrinsic_regular, top_frame_regular, top_frame_state + ) + tx = Transaction( to=contract, - state_gas_reservoir=auth_state_gas, authorization_list=authorization_list, - sender=sender, + sender=pre.fund_eoa(), + expected_receipt=TransactionReceipt( + cumulative_gas_used=cumulative_gas_used, + ), ) - state_test(pre=pre, post={}, tx=tx) + post = {signer: Account(nonce=0, code=b"")} + state_test( + pre=pre, + post=post, + tx=tx, + blockchain_test_header_verify=Header(gas_used=header_gas_used), + ) @pytest.mark.valid_from("EIP8037") @@ -573,41 +722,64 @@ def test_self_sponsored_authorization( fork: Fork, ) -> None: """ - Test self-sponsored authorization where sender is also the signer. - - The sender authorizes delegation to a contract and is also the - authority. The intrinsic state gas for the authorization is still - charged. Since the sender account already exists, the - new-account state gas refund applies. + Test a self-sponsored authorization where the sender is the authority. + + The transaction consumes the sender's nonce (0 -> 1) before + ``set_delegation`` runs, so the authorization must carry ``nonce=1`` + to match. ``set_delegation`` then applies the delegation and bumps the + nonce again (1 -> 2). The sender's leaf already exists (no + ``NEW_ACCOUNT``) and was already written at inclusion -- priced into + ``TX_BASE`` -- so the delegation write is not the transaction's + first write to it (no ``ACCOUNT_WRITE``); only the top-frame + ``AUTH_BASE`` is charged, with no refund. """ - auth_state_gas = fork.transaction_intrinsic_state_gas( - authorization_count=1, - ) - - storage = Storage() - contract = pre.deploy_contract( - code=Op.SSTORE(storage.store_next(1), 1), - ) + delegate = pre.deploy_contract(code=Op.STOP) + recipient = pre.deploy_contract(code=Op.STOP) - # Sender is also the signer (self-sponsored) + # Sender is also the authority (self-sponsored). The tx bumps the + # sender nonce to 1 before set_delegation, so the auth uses nonce=1. sender = pre.fund_eoa() authorization_list = [ AuthorizationTuple( - address=contract, - nonce=0, + address=delegate, + nonce=1, signer=sender, + creates_account=False, + writes_delegation=True, + # The sender's leaf is written at inclusion, so this is not + # the transaction's first write to it. + first_write=False, ), ] + intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( + fork, authorization_list + ) + cumulative_gas_used, header_gas_used = _receipt_and_header( + intrinsic_regular, top_frame_regular, top_frame_state + ) + tx = Transaction( - to=contract, - state_gas_reservoir=auth_state_gas, + to=recipient, authorization_list=authorization_list, sender=sender, + expected_receipt=TransactionReceipt( + cumulative_gas_used=cumulative_gas_used, + ), ) - post = {contract: Account(storage=storage)} - state_test(pre=pre, post=post, tx=tx) + post = { + sender: Account( + nonce=2, + code=Spec7702.delegation_designation(delegate), + ), + } + state_test( + pre=pre, + post=post, + tx=tx, + blockchain_test_header_verify=Header(gas_used=header_gas_used), + ) @pytest.mark.valid_from("EIP8037") @@ -617,45 +789,66 @@ def test_duplicate_signer_authorizations( fork: Fork, ) -> None: """ - Test multiple authorizations from the same signer. - - When the same signer appears multiple times in the authorization - list, each authorization charges intrinsic state gas independently. - Only the last valid authorization takes effect, but all contribute - to intrinsic state gas. + Test two authorizations from the same signer with increasing nonces. + + The first authorization (nonce 0) sets a fresh delegation on the + existing authority, paying the first-write ``ACCOUNT_WRITE`` and the + top-frame ``AUTH_BASE``. The second (nonce 1) overwrites it to a + different target; the authority is already written and already had a + delegation set in this transaction, so it pays nothing beyond the + intrinsic base. The authority ends delegated to the second target + with nonce 2. """ - auth_state_gas = fork.transaction_intrinsic_state_gas( - authorization_count=1, - ) - contract_a = pre.deploy_contract(code=Op.STOP) contract_b = pre.deploy_contract(code=Op.STOP) - # Same signer, two authorizations signer = pre.fund_eoa() authorization_list = [ AuthorizationTuple( address=contract_a, nonce=0, signer=signer, + creates_account=False, + writes_delegation=True, ), AuthorizationTuple( address=contract_b, - nonce=0, + nonce=1, signer=signer, + creates_account=False, + writes_delegation=False, + first_write=False, ), ] - # Both auths charge intrinsic state gas (2x) - sender = pre.fund_eoa() + intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( + fork, authorization_list + ) + cumulative_gas_used, header_gas_used = _receipt_and_header( + intrinsic_regular, top_frame_regular, top_frame_state + ) + tx = Transaction( to=contract_a, - state_gas_reservoir=auth_state_gas * 2, authorization_list=authorization_list, - sender=sender, + sender=pre.fund_eoa(), + expected_receipt=TransactionReceipt( + cumulative_gas_used=cumulative_gas_used, + ), ) - state_test(pre=pre, post={}, tx=tx) + post = { + signer: Account( + nonce=2, + code=Spec7702.delegation_designation(contract_b), + ), + } + state_test( + pre=pre, + post=post, + tx=tx, + blockchain_test_header_verify=Header(gas_used=header_gas_used), + ) @pytest.mark.valid_from("EIP8037") @@ -665,22 +858,19 @@ def test_auth_with_calldata_and_access_list( fork: Fork, ) -> None: """ - Test authorization combined with calldata and access list. + Test authorization combined with calldata and an access list. - Intrinsic gas includes calldata cost, access list cost, and - authorization state gas. All components contribute to the total - intrinsic gas requirement. + The regular intrinsic folds in the calldata and access-list costs; on + top of it the existing authority pays the top-frame ``AUTH_BASE`` and + the recipient's SSTORE pays its execution regular + state costs. The + receipt gas is the exact sum, with no refund term. Access lists do not + warm the authority under EIP-2780, so the auth charge is unaffected. """ - auth_state_gas = fork.transaction_intrinsic_state_gas( - authorization_count=1, - ) - sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) - storage = Storage() - # Contract that reads calldata and stores it - contract = pre.deploy_contract( - code=(Op.SSTORE(storage.store_next(0x42), Op.CALLDATALOAD(0))), - ) + code = Op.SSTORE(storage.store_next(0x42), Op.CALLDATALOAD(0)) + contract = pre.deploy_contract(code=code) + execution_regular = code.regular_cost(fork) + execution_state = code.state_cost(fork) signer = pre.fund_eoa() authorization_list = [ @@ -688,20 +878,50 @@ def test_auth_with_calldata_and_access_list( address=contract, nonce=0, signer=signer, + creates_account=False, + writes_delegation=True, ), ] - sender = pre.fund_eoa() + data = b"\x00" * 31 + b"\x42" + access_list = [AccessList(address=contract, storage_keys=[])] + + intrinsic_regular = fork.transaction_intrinsic_cost_calculator()( + authorization_list_or_count=authorization_list, + calldata=data, + access_list=access_list, + return_cost_deducted_prior_execution=True, + ) + _, top_frame_regular, top_frame_state = _auth_gas(fork, authorization_list) + cumulative_gas_used, header_gas_used = _receipt_and_header( + intrinsic_regular, + top_frame_regular, + top_frame_state, + execution_regular=execution_regular, + execution_state=execution_state, + ) + tx = Transaction( to=contract, - state_gas_reservoir=auth_state_gas + sstore_state_gas, - data=b"\x00" * 31 + b"\x42", # Calldata adds to intrinsic gas + data=data, + access_list=access_list, authorization_list=authorization_list, - sender=sender, + sender=pre.fund_eoa(), + expected_receipt=TransactionReceipt( + cumulative_gas_used=cumulative_gas_used, + ), ) - post = {contract: Account(storage=storage)} - state_test(pre=pre, post=post, tx=tx) + post = { + contract: Account(storage=storage), + signer: Account(code=Spec7702.delegation_designation(contract)), + } + state_test( + pre=pre, + post=post, + tx=tx, + blockchain_test_header_verify=Header(gas_used=header_gas_used), + ) @pytest.mark.parametrize( @@ -721,95 +941,127 @@ def test_mixed_valid_and_invalid_auths( fork: Fork, ) -> None: """ - Test mixed valid and invalid authorizations state gas charging. - - Both valid and invalid authorizations charge intrinsic state gas. - Invalid auths (wrong nonce) are skipped during processing but their - state gas is still consumed. The total intrinsic state gas equals - (num_valid + num_invalid) * 135 * cpsb. + Test mixed valid and invalid authorizations under the top-frame model. + + Every tuple (valid or invalid) pays the intrinsic + ``REGULAR_PER_AUTH_BASE_COST``. Only the valid authorizations reach + ``set_delegation`` and each writes a net-new delegation on an existing + authority, paying the first-write ``ACCOUNT_WRITE`` and the top-frame + ``AUTH_BASE``; the invalid (wrong nonce) tuples are skipped and pay + no top-frame charge. The receipt gas is ``intrinsic_regular + + num_valid * (ACCOUNT_WRITE + AUTH_BASE)``. """ - auth_state_gas = fork.transaction_intrinsic_state_gas( - authorization_count=1, - ) - contract = pre.deploy_contract(code=Op.STOP) - authorization_list = [] + valid_signers = [pre.fund_eoa() for _ in range(num_valid)] + invalid_signers = [pre.fund_eoa() for _ in range(num_invalid)] - # Valid authorizations - for _ in range(num_valid): - signer = pre.fund_eoa() - authorization_list.append( - AuthorizationTuple( - address=contract, - nonce=0, - signer=signer, - ), + authorization_list = [ + AuthorizationTuple( + address=contract, + nonce=0, + signer=signer, + creates_account=False, + writes_delegation=True, ) - - # Invalid authorizations (wrong nonce) - for _ in range(num_invalid): - signer = pre.fund_eoa() - authorization_list.append( - AuthorizationTuple( - address=contract, - nonce=99, # Wrong nonce - signer=signer, - ), + for signer in valid_signers + ] + [ + AuthorizationTuple( + address=contract, + nonce=99, # Wrong nonce -- skipped + signer=signer, + creates_account=False, + writes_delegation=False, + first_write=False, ) + for signer in invalid_signers + ] + + intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( + fork, authorization_list + ) + cumulative_gas_used, header_gas_used = _receipt_and_header( + intrinsic_regular, top_frame_regular, top_frame_state + ) - total_auths = num_valid + num_invalid - sender = pre.fund_eoa() tx = Transaction( to=contract, - state_gas_reservoir=auth_state_gas * total_auths, authorization_list=authorization_list, - sender=sender, + sender=pre.fund_eoa(), + expected_receipt=TransactionReceipt( + cumulative_gas_used=cumulative_gas_used, + ), ) - state_test(pre=pre, post={}, tx=tx) + post = { + signer: Account(code=Spec7702.delegation_designation(contract)) + for signer in valid_signers + } + for signer in invalid_signers: + post[signer] = Account(nonce=0, code=b"") + + state_test( + pre=pre, + post=post, + tx=tx, + blockchain_test_header_verify=Header(gas_used=header_gas_used), + ) @pytest.mark.valid_from("EIP8037") -def test_many_authorizations_state_gas( +def test_many_authorizations( state_test: StateTestFiller, pre: Alloc, fork: Fork, ) -> None: """ - Test many authorizations with state gas from reservoir. + Test ten authorizations, each charged the top-frame ``AUTH_BASE``. - Ten authorizations each charge 135 * cpsb intrinsic state gas. - The total state gas is drawn from the reservoir. Verifies that - large authorization lists scale correctly. + Ten existing authorities each gain a fresh delegation, so the total + top-frame state charge is ``10 * AUTH_BASE`` with no refund. Verifies + the top-frame charge scales correctly for large authorization lists. """ - auth_state_gas = fork.transaction_intrinsic_state_gas( - authorization_count=1, - ) num_auths = 10 - contract = pre.deploy_contract(code=Op.STOP) - authorization_list = [] - for _ in range(num_auths): - signer = pre.fund_eoa() - authorization_list.append( - AuthorizationTuple( - address=contract, - nonce=0, - signer=signer, - ), + signers = [pre.fund_eoa() for _ in range(num_auths)] + authorization_list = [ + AuthorizationTuple( + address=contract, + nonce=0, + signer=signer, + creates_account=False, + writes_delegation=True, ) + for signer in signers + ] + + intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( + fork, authorization_list + ) + cumulative_gas_used, header_gas_used = _receipt_and_header( + intrinsic_regular, top_frame_regular, top_frame_state + ) - sender = pre.fund_eoa() tx = Transaction( to=contract, - state_gas_reservoir=auth_state_gas * num_auths, authorization_list=authorization_list, - sender=sender, + sender=pre.fund_eoa(), + expected_receipt=TransactionReceipt( + cumulative_gas_used=cumulative_gas_used, + ), ) - state_test(pre=pre, post={}, tx=tx) + post = { + signer: Account(code=Spec7702.delegation_designation(contract)) + for signer in signers + } + state_test( + pre=pre, + post=post, + tx=tx, + blockchain_test_header_verify=Header(gas_used=header_gas_used), + ) @pytest.mark.valid_from("EIP8037") @@ -819,24 +1071,22 @@ def test_auth_with_multiple_sstores( fork: Fork, ) -> None: """ - Test authorization combined with multiple SSTOREs. + Test an authorization combined with multiple recipient SSTOREs. - Authorization intrinsic state gas plus multiple SSTORE state gas - charges all draw from the same reservoir. Verifies combined state - gas accounting across intrinsic and execution phases. + The existing authority pays the top-frame ``AUTH_BASE`` and the + recipient performs five distinct zero-to-nonzero SSTOREs, each paying + its own regular + state cost during execution. Verifies combined + accounting across the top-frame and execution state charges, all drawn + from ``gas_left`` with no refund. """ - auth_state_gas = fork.transaction_intrinsic_state_gas( - authorization_count=1, - ) - sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) num_sstores = 5 - storage = Storage() code = Bytecode() for _ in range(num_sstores): code += Op.SSTORE(storage.store_next(1), 1) - contract = pre.deploy_contract(code=code) + execution_regular = code.regular_cost(fork) + execution_state = code.state_cost(fork) signer = pre.fund_eoa() authorization_list = [ @@ -844,20 +1094,38 @@ def test_auth_with_multiple_sstores( address=contract, nonce=0, signer=signer, + creates_account=False, + writes_delegation=True, ), ] - total_state_gas = auth_state_gas + sstore_state_gas * num_sstores - sender = pre.fund_eoa() + intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( + fork, authorization_list + ) + _, header_gas_used = _receipt_and_header( + intrinsic_regular, + top_frame_regular, + top_frame_state, + execution_regular=execution_regular, + execution_state=execution_state, + ) + tx = Transaction( to=contract, - state_gas_reservoir=total_state_gas, authorization_list=authorization_list, - sender=sender, + sender=pre.fund_eoa(), ) - post = {contract: Account(storage=storage)} - state_test(pre=pre, post=post, tx=tx) + post = { + contract: Account(storage=storage), + signer: Account(code=Spec7702.delegation_designation(contract)), + } + state_test( + pre=pre, + post=post, + tx=tx, + blockchain_test_header_verify=Header(gas_used=header_gas_used), + ) @pytest.mark.parametrize( @@ -879,41 +1147,52 @@ def test_authorization_exact_state_gas_boundary( gas_delta: int, ) -> None: """ - Test exact intrinsic gas boundary including auth state gas. - - The intrinsic cost includes regular gas (G_TRANSACTION + G_AUTHORIZATION - per auth) and state gas - ((STATE_BYTES_PER_NEW_ACCOUNT + STATE_BYTES_PER_AUTH_BASE) * cpsb - per auth). With gas_delta=0 the tx has exactly enough and succeeds. - With gas_delta=-1 the tx is 1 gas short and is rejected as - intrinsic-gas-too-low. + Test the intrinsic-gas boundary and the top-frame OOG behaviour. + + Under EIP-2780 the intrinsic is regular-only, so the boundary keys off + ``fork.transaction_intrinsic_cost_calculator()(auth_list)``. With + ``gas_delta=-1`` the transaction is one gas below the intrinsic and is + rejected as intrinsic-gas-too-low. With ``gas_delta=0`` the gas limit + equals the intrinsic exactly, so the transaction is included but has + zero gas left for the top frame: the authority's ``NEW_ACCOUNT`` state + charge in ``set_delegation`` runs out of gas, the whole preparation + rolls back, and the authority is never created. """ - contract = pre.deploy_contract(code=Op.STOP) + target = pre.deploy_contract(code=Op.STOP) + recipient = pre.deploy_contract(code=Op.STOP) - signer = pre.fund_eoa() + # A fresh (nonexistent) authority so the first top-frame charge is + # NEW_ACCOUNT, which OOGs when no gas is left after the intrinsic. + signer = pre.fund_eoa(amount=0) authorization_list = [ AuthorizationTuple( - address=contract, + address=target, nonce=0, signer=signer, + creates_account=True, + writes_delegation=True, ), ] - intrinsic_cost_calculator = fork.transaction_intrinsic_cost_calculator() - intrinsic_cost = intrinsic_cost_calculator( + intrinsic_cost = fork.transaction_intrinsic_cost_calculator()( authorization_list_or_count=authorization_list, ) - is_oog = gas_delta < 0 - sender = pre.fund_eoa() + is_rejected = gas_delta < 0 tx = Transaction( - to=contract, + to=recipient, gas_limit=intrinsic_cost + gas_delta, authorization_list=authorization_list, - sender=sender, - error=TransactionException.INTRINSIC_GAS_TOO_LOW if is_oog else None, + sender=pre.fund_eoa(), + error=( + TransactionException.INTRINSIC_GAS_TOO_LOW if is_rejected else None + ), ) + # gas_delta == 0: tx included, top-frame OOG rolls back the auth, so + # the authority leaf is never created. + # gas_delta == -1: tx rejected before execution; authority untouched. + post = {signer: Account.NONEXISTENT} blockchain_test( pre=pre, blocks=[ @@ -921,12 +1200,12 @@ def test_authorization_exact_state_gas_boundary( txs=[tx], exception=( TransactionException.INTRINSIC_GAS_TOO_LOW - if is_oog + if is_rejected else None ), ) ], - post={}, + post=post, ) @@ -934,167 +1213,193 @@ def test_authorization_exact_state_gas_boundary( def test_authorization_to_precompile_address( state_test: StateTestFiller, pre: Alloc, - fork: Fork, ) -> None: """ - Test authorization targeting a precompile address charges state gas. + Test an authorization targeting a precompile address applies. - Authorizing delegation to a precompile address (e.g., ecrecover at - 0x01) charges the same intrinsic state gas as any other target. - The authorization is processed and the signer's code is set to - the precompile address delegation designator. + Authorizing delegation to a precompile address (ecrecover at 0x01) is + processed like any other target: the authority's code is set to the + precompile's delegation designator. Only the post-state is asserted + here; the recipient path becomes a delegation and its exact charge is + not the focus of this test. """ - auth_state_gas = fork.transaction_intrinsic_state_gas( - authorization_count=1, - ) - - # ecrecover precompile at 0x01 - precompile_addr = 0x01 + precompile_address = Address(0x01) + recipient = pre.deploy_contract(code=Op.STOP) signer = pre.fund_eoa() authorization_list = [ AuthorizationTuple( - address=precompile_addr, + address=precompile_address, nonce=0, signer=signer, + creates_account=False, + writes_delegation=True, ), ] - sender = pre.fund_eoa() tx = Transaction( - to=signer, - state_gas_reservoir=auth_state_gas, + to=recipient, authorization_list=authorization_list, - sender=sender, + sender=pre.fund_eoa(), ) - state_test(pre=pre, post={}, tx=tx) + post = { + signer: Account( + code=Spec7702.delegation_designation(precompile_address), + ), + } + state_test(pre=pre, post=post, tx=tx) @pytest.mark.valid_from("EIP8037") -def test_multi_tx_block_auth_refund_and_sstore( +def test_multi_tx_block_auth_and_sstore( blockchain_test: BlockchainTestFiller, pre: Alloc, fork: Fork, ) -> None: """ - Test multi-transaction block with auth refund and SSTORE state gas. + Test a multi-transaction block combining a top-frame auth and an + SSTORE. - Two transactions in one block: - 1. A SetCode tx authorizing an existing account (gets new-account state gas - refund to reservoir). The refund reduces intrinsic_state_gas. - 2. A regular tx performing an SSTORE (charges - STATE_BYTES_PER_STORAGE_SET * cpsb state gas). + Two transactions share one block: - Verifies block-level state gas accounting correctly handles both - the auth refund from tx1 and the SSTORE charge from tx2. - """ - auth_state_gas = fork.transaction_intrinsic_state_gas( - authorization_count=1, - ) - sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + 1. a SetCode tx delegating an existing authority (top-frame + ``AUTH_BASE``, no refund); and + 2. a regular tx performing a zero-to-nonzero SSTORE (execution regular + + state). + The per-transaction receipt ``cumulative_gas_used`` accumulates across + the block, so tx1's receipt is its own cost and tx2's is the running + total. Verifies block-level accounting handles the two side by side. + """ contract = pre.deploy_contract(code=Op.STOP) - # TX 1: auth targeting existing account (gets refund) + # TX 1: delegate an existing authority. signer = pre.fund_eoa() authorization_list = [ AuthorizationTuple( address=contract, nonce=0, signer=signer, + creates_account=False, + writes_delegation=True, ), ] - sender_1 = pre.fund_eoa() + intrinsic_regular_1, top_frame_regular_1, top_frame_state_1 = _auth_gas( + fork, authorization_list + ) + tx1_gas, _ = _receipt_and_header( + intrinsic_regular_1, top_frame_regular_1, top_frame_state_1 + ) tx_1 = Transaction( to=contract, - state_gas_reservoir=auth_state_gas, authorization_list=authorization_list, - sender=sender_1, + sender=pre.fund_eoa(), + expected_receipt=TransactionReceipt(cumulative_gas_used=tx1_gas), ) - # TX 2: SSTORE zero-to-nonzero (charges state gas) + # TX 2: a plain zero-to-nonzero SSTORE. storage = Storage() - sstore_contract = pre.deploy_contract( - code=Op.SSTORE(storage.store_next(1), 1), + sstore_code = Op.SSTORE(storage.store_next(1), 1) + sstore_contract = pre.deploy_contract(code=sstore_code) + intrinsic_regular_2 = fork.transaction_intrinsic_cost_calculator()( + recipient_type=RecipientType.CONTRACT, + return_cost_deducted_prior_execution=True, + ) + tx2_gas = ( + intrinsic_regular_2 + + sstore_code.regular_cost(fork) + + sstore_code.state_cost(fork) ) - sender_2 = pre.fund_eoa() tx_2 = Transaction( to=sstore_contract, - state_gas_reservoir=sstore_state_gas, - sender=sender_2, + sender=pre.fund_eoa(), + expected_receipt=TransactionReceipt( + cumulative_gas_used=tx1_gas + tx2_gas, + ), ) + post = { + signer: Account(code=Spec7702.delegation_designation(contract)), + sstore_contract: Account(storage=storage), + } blockchain_test( pre=pre, blocks=[Block(txs=[tx_1, tx_2])], - post={sstore_contract: Account(storage=storage)}, + post=post, ) @pytest.mark.valid_from("EIP8037") -def test_auth_refund_bypasses_one_fifth_cap( +def test_fresh_authority_and_sstores_full_state( state_test: StateTestFiller, pre: Alloc, fork: Fork, ) -> None: """ - Test auth refund to reservoir bypasses the 1/5 refund cap. - - The existing-account auth refund (new-account state gas) goes directly to - state_gas_reservoir, NOT to refund_counter. This means it is not - subject to the 1/5 refund cap. The test provides just enough gas - for the auth intrinsic state gas and multiple SSTOREs whose state - gas can only be funded from the reservoir if the full auth refund - is available (i.e. not capped at 1/5). - - If the auth refund went through refund_counter with the 1/5 cap, - the SSTOREs would OOG. By succeeding, this test proves the refund - bypasses the cap. + Test a fresh authority plus multiple SSTOREs pay the full state cost. + + A fresh (nonexistent) authority is delegated to the recipient, paying + ``NEW_ACCOUNT`` + ``ACCOUNT_WRITE`` + ``AUTH_BASE`` at the top frame, + and the recipient performs three zero-to-nonzero SSTOREs. Every state + charge (top-frame and execution) is drawn from ``gas_left`` in full: + there is no reservoir refund and no 1/5 cap in play. The receipt gas + is the exact sum of all components. """ - auth_state_gas = fork.transaction_intrinsic_state_gas( - authorization_count=1, - ) - sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) - # Auth refund for existing account = new-account state gas - # (documents the expected value for reasoning about gas budgets). - - # Use 3 SSTOREs: 3 * 64 * cpsb = 192 * cpsb state gas needed. - # Auth refund gives new-account state gas to reservoir for all 3. - # If it were 1/5 capped: refund would be at most - # (143 * cpsb) / 5 ≈ 28 * cpsb, which can only fund 0 SSTOREs. num_sstores = 3 - storage = Storage() code = Bytecode() for _ in range(num_sstores): code += Op.SSTORE(storage.store_next(1), 1) - contract = pre.deploy_contract(code=code) + execution_regular = code.regular_cost(fork) + execution_state = code.state_cost(fork) - # Existing signer — gets auth_refund to reservoir - signer = pre.fund_eoa() + signer = pre.fund_eoa(amount=0) authorization_list = [ AuthorizationTuple( address=contract, nonce=0, signer=signer, + creates_account=True, + writes_delegation=True, ), ] - # Provide auth intrinsic state gas + SSTORE state gas. - # After the auth refund (new-account state gas) returns to the reservoir, - # the reservoir holds auth_refund which covers 3 SSTOREs (96*cpsb). - sender = pre.fund_eoa() + intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( + fork, authorization_list + ) + cumulative_gas_used, header_gas_used = _receipt_and_header( + intrinsic_regular, + top_frame_regular, + top_frame_state, + execution_regular=execution_regular, + execution_state=execution_state, + ) + tx = Transaction( to=contract, - state_gas_reservoir=auth_state_gas + sstore_state_gas * num_sstores, authorization_list=authorization_list, - sender=sender, + sender=pre.fund_eoa(), + expected_receipt=TransactionReceipt( + cumulative_gas_used=cumulative_gas_used, + ), ) - post = {contract: Account(storage=storage)} - state_test(pre=pre, post=post, tx=tx) + post = { + contract: Account(storage=storage), + signer: Account( + nonce=1, + balance=0, + code=Spec7702.delegation_designation(contract), + ), + } + state_test( + pre=pre, + post=post, + tx=tx, + blockchain_test_header_verify=Header(gas_used=header_gas_used), + ) @pytest.mark.parametrize( @@ -1105,57 +1410,56 @@ def test_auth_refund_bypasses_one_fifth_cap( ], ) @pytest.mark.valid_from("EIP8037") -def test_existing_account_auth_header_gas_used_reflects_refund( +def test_existing_account_auth_header_gas_used( state_test: StateTestFiller, pre: Alloc, fork: Fork, num_auths: int, ) -> None: """ - Verify the block header gas_used reflects the existing-authority - auth refund (deducted from `tx_state_gas`) when every authority - is an existing account. - - `set_delegation` credits `state_gas_reservoir` and accumulates - `state_refund`, which `process_transaction` subtracts from - `tx_state_gas` before adding it to `block_state_gas_used`. With - STOP execution there is no extra regular or state gas used, so - header gas_used equals - `max(intrinsic_regular, intrinsic_state - N * auth_refund)`. - """ - intrinsic_state_gas = fork.transaction_intrinsic_state_gas( - authorization_count=num_auths, - ) - total_intrinsic = fork.transaction_intrinsic_cost_calculator()( - authorization_list_or_count=num_auths, - ) - intrinsic_regular = total_intrinsic - intrinsic_state_gas - auth_refund = fork.gas_costs().REFUND_AUTH_PER_EXISTING_ACCOUNT * num_auths + Verify the header ``gas_used`` for existing-authority delegations. + Every authority is an existing account gaining a fresh delegation, so + each pays only the top-frame ``AUTH_BASE`` (no ``NEW_ACCOUNT`` / + ``ACCOUNT_WRITE`` and no refund). With STOP execution the header + ``gas_used`` is ``max(intrinsic_regular, num_auths * AUTH_BASE)``. + """ contract = pre.deploy_contract(code=Op.STOP) + signers = [pre.fund_eoa() for _ in range(num_auths)] authorization_list = [ - AuthorizationTuple(address=contract, nonce=0, signer=pre.fund_eoa()) - for _ in range(num_auths) + AuthorizationTuple( + address=contract, + nonce=0, + signer=signer, + creates_account=False, + writes_delegation=True, + ) + for signer in signers ] + intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( + fork, authorization_list + ) + _, header_gas_used = _receipt_and_header( + intrinsic_regular, top_frame_regular, top_frame_state + ) + tx = Transaction( to=contract, - state_gas_reservoir=intrinsic_state_gas, authorization_list=authorization_list, sender=pre.fund_eoa(), ) - expected_gas_used = max( - intrinsic_regular, - intrinsic_state_gas - auth_refund, - ) - + post = { + signer: Account(code=Spec7702.delegation_designation(contract)) + for signer in signers + } state_test( pre=pre, - post={}, + post=post, tx=tx, - blockchain_test_header_verify=Header(gas_used=expected_gas_used), + blockchain_test_header_verify=Header(gas_used=header_gas_used), ) @@ -1167,7 +1471,7 @@ def test_existing_account_auth_header_gas_used_reflects_refund( ], ) @pytest.mark.valid_from("EIP8037") -def test_mixed_auths_header_gas_used_reflects_existing_refunds( +def test_mixed_auths_header_gas_used( state_test: StateTestFiller, pre: Alloc, fork: Fork, @@ -1175,137 +1479,138 @@ def test_mixed_auths_header_gas_used_reflects_existing_refunds( num_new: int, ) -> None: """ - Verify the block header gas_used deducts only the existing-authority - auth refunds across a mix of existing and new account - authorizations. - - Each existing authority contributes - `REFUND_AUTH_PER_EXISTING_ACCOUNT` to `state_refund`; new - authorities contribute none. Header gas_used is - `max(intrinsic_regular, intrinsic_state - num_existing * refund)`. + Verify the header ``gas_used`` across a mix of existing and new + authorities. + + Existing authorities pay only ``AUTH_BASE``; new (nonexistent) + authorities additionally pay ``NEW_ACCOUNT`` (state) + ``ACCOUNT_WRITE`` + (regular) for the created leaf. The header ``gas_used`` is + ``max(block_regular, block_state)`` over the summed top-frame charges, + with no refund term. """ - num_auths = num_existing + num_new - intrinsic_state_gas = fork.transaction_intrinsic_state_gas( - authorization_count=num_auths, - ) - total_intrinsic = fork.transaction_intrinsic_cost_calculator()( - authorization_list_or_count=num_auths, - ) - intrinsic_regular = total_intrinsic - intrinsic_state_gas - auth_refund = ( - fork.gas_costs().REFUND_AUTH_PER_EXISTING_ACCOUNT * num_existing - ) - contract = pre.deploy_contract(code=Op.STOP) - authorization_list = [] - for _ in range(num_existing): - authorization_list.append( - AuthorizationTuple( - address=contract, - nonce=0, - signer=pre.fund_eoa(), - ) + existing_signers = [pre.fund_eoa() for _ in range(num_existing)] + new_signers = [pre.fund_eoa(amount=0) for _ in range(num_new)] + + authorization_list = [ + AuthorizationTuple( + address=contract, + nonce=0, + signer=signer, + creates_account=False, + writes_delegation=True, ) - for _ in range(num_new): - authorization_list.append( - AuthorizationTuple( - address=contract, - nonce=0, - signer=pre.fund_eoa(amount=0), - ) + for signer in existing_signers + ] + [ + AuthorizationTuple( + address=contract, + nonce=0, + signer=signer, + creates_account=True, + writes_delegation=True, ) + for signer in new_signers + ] + + intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( + fork, authorization_list + ) + _, header_gas_used = _receipt_and_header( + intrinsic_regular, top_frame_regular, top_frame_state + ) tx = Transaction( to=contract, - state_gas_reservoir=intrinsic_state_gas, authorization_list=authorization_list, sender=pre.fund_eoa(), ) - expected_gas_used = max( - intrinsic_regular, - intrinsic_state_gas - auth_refund, - ) + post = { + signer: Account(code=Spec7702.delegation_designation(contract)) + for signer in existing_signers + } + for signer in new_signers: + post[signer] = Account( + nonce=1, + balance=0, + code=Spec7702.delegation_designation(contract), + ) state_test( pre=pre, - post={}, + post=post, tx=tx, - blockchain_test_header_verify=Header(gas_used=expected_gas_used), + blockchain_test_header_verify=Header(gas_used=header_gas_used), ) @pytest.mark.valid_from("EIP8037") -def test_existing_auth_refund_survives_top_level_revert( +def test_auth_state_gas_persists_on_top_level_revert( state_test: StateTestFiller, pre: Alloc, fork: Fork, ) -> None: """ - Verify the existing-authority auth refund still flows through - `state_refund` when execution REVERTs at the top level. - - `set_delegation` runs before EVM execution and accumulates the - refund into `MessageCallOutput.state_refund`. A subsequent - top-level REVERT discards the SSTORE state changes (and resets - `state_gas_used` to 0), but it does not unwind the auth refund — - `process_transaction` still subtracts the refund from - `tx_state_gas`. The header gas_used therefore reflects: - - `max(intrinsic_regular + execution_regular, - intrinsic_state - auth_refund)` - - with `execution_state` netting to 0 because of the revert. + Verify the auth state gas stays consumed on a top-level REVERT, + because the delegation persists, while the reverted execution's own + state gas is refilled. + + ``set_delegation`` runs in the top-frame preparation, before the + execution snapshot, so the delegation survives a top-level REVERT + and the state gas that paid for it (the ``AUTH_BASE`` here) is + folded out of the frame's refillable pools. The recipient writes an + SSTORE then REVERTs: the slot rolls back with the frame, so the + SSTORE's ``STORAGE_SET`` state gas *is* refilled. The receipt is + therefore the intrinsic and top-frame charges (regular and state) + plus the regular execution gas, with only the authorization's state + portion in the block's state component. """ - intrinsic_state_gas = fork.transaction_intrinsic_state_gas( - authorization_count=1, - ) - total_intrinsic = fork.transaction_intrinsic_cost_calculator()( - authorization_list_or_count=1, - ) - intrinsic_regular = total_intrinsic - intrinsic_state_gas - auth_refund = fork.gas_costs().REFUND_AUTH_PER_EXISTING_ACCOUNT - - sstore_op = Op.SSTORE( - key=0, - value=1, - key_warm=False, - original_value=0, - new_value=1, - ) - code = sstore_op + Op.REVERT(0, 0) + code = Op.SSTORE(0, 1) + Op.REVERT(0, 0) contract = pre.deploy_contract(code=code) - - # bytecode.gas_cost(fork) returns the combined (regular + state) - # cost; subtract the SSTORE state portion to isolate the regular - # gas burned before REVERT. - execution_regular = code.gas_cost(fork) - Op.SSTORE( - new_value=1 - ).state_cost(fork) + execution_regular = code.regular_cost(fork) signer = pre.fund_eoa() authorization_list = [ - AuthorizationTuple(address=contract, nonce=0, signer=signer), + AuthorizationTuple( + address=contract, + nonce=0, + signer=signer, + creates_account=False, + writes_delegation=True, + ), ] + intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( + fork, authorization_list + ) + # The SSTORE's state gas is refilled by the REVERT (the slot rolls + # back); the authorization's state gas persists with its delegation. + cumulative_gas_used, header_gas_used = _receipt_and_header( + intrinsic_regular, + top_frame_regular, + top_frame_state, + execution_regular=execution_regular, + ) + tx = Transaction( to=contract, - state_gas_reservoir=intrinsic_state_gas, authorization_list=authorization_list, sender=pre.fund_eoa(), + expected_receipt=TransactionReceipt( + cumulative_gas_used=cumulative_gas_used, + ), ) - expected_gas_used = max( - intrinsic_regular + execution_regular, - intrinsic_state_gas - auth_refund, - ) - + post = { + contract: Account(storage={}), + signer: Account(code=Spec7702.delegation_designation(contract)), + } state_test( pre=pre, - post={contract: Account(storage={})}, + post=post, tx=tx, - blockchain_test_header_verify=Header(gas_used=expected_gas_used), + blockchain_test_header_verify=Header(gas_used=header_gas_used), ) @@ -1333,30 +1638,25 @@ def test_auth_state_gas_in_header_after_failure( authority_exists: bool, ) -> None: """ - Verify block header reflects intrinsic state gas from a 7702 - authorization when the top-level tx fails. - - Execution state gas is zeroed on failure but intrinsic state gas - is preserved. For existing-account auths the spec subtracts the - auth refund from `tx_state_gas`, reducing the state component. - The delegation indicator persists (set before the execution - snapshot). Parametrized across all failure modes (revert/halt/oog) - and authority states (new vs existing). + Verify the header ``gas_used`` when the top-level call fails after the + authorization is applied. + + The delegation is applied in the top-frame preparation (before the + execution snapshot), so it persists through every failure mode -- + and so does the state gas that paid for it (``NEW_ACCOUNT`` + + ``AUTH_BASE`` for a fresh authority, ``AUTH_BASE`` for an existing + one), which is folded out of the frame's refillable pools. The + header is ``max(block_regular, block_state)``: + + * REVERT -- the unused execution budget returns, so the regular + component is ``intrinsic_regular + top_frame_regular + + execution_regular`` and the state component is the persisting + authorization state gas. + * HALT / OOG -- the frame consumes its whole gas limit; the + authorization state gas within it is accounted on the state + component, and the remainder on the regular component. """ - gas_limit_cap = fork.transaction_gas_limit_cap() - assert gas_limit_cap is not None - - auth_intrinsic_state = fork.transaction_intrinsic_state_gas( - authorization_count=1, - ) - intrinsic_cost = fork.transaction_intrinsic_cost_calculator() - intrinsic_total = intrinsic_cost(authorization_list_or_count=1) - intrinsic_regular = intrinsic_total - auth_intrinsic_state - auth_refund = ( - fork.gas_costs().REFUND_AUTH_PER_EXISTING_ACCOUNT - if authority_exists - else 0 - ) + gas_limit = 500_000 delegate = pre.deploy_contract(code=Op.STOP) @@ -1372,39 +1672,53 @@ def test_auth_state_gas_in_header_after_failure( if authority_exists: signer = pre.fund_eoa() + creates_account = False else: - signer = pre.fund_eoa(0) + signer = pre.fund_eoa(amount=0) + creates_account = True - tx_gas = gas_limit_cap + auth_intrinsic_state + authorization_list = [ + AuthorizationTuple( + address=delegate, + nonce=0, + signer=signer, + creates_account=creates_account, + writes_delegation=True, + ), + ] - tx = Transaction( - ty=4, - to=target, - state_gas_reservoir=auth_intrinsic_state, - sender=pre.fund_eoa(), - authorization_list=[ - AuthorizationTuple( - address=delegate, - nonce=0, - signer=signer, - ), - ], + intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( + fork, authorization_list ) if failure_mode == "revert": - block_regular = intrinsic_regular + revert_code.gas_cost(fork) + # The authorization's state gas persists with its delegation. + _, expected_gas_used = _receipt_and_header( + intrinsic_regular, + top_frame_regular, + top_frame_state, + execution_regular=revert_code.regular_cost(fork), + ) else: - block_regular = tx_gas - auth_intrinsic_state + # HALT / OOG consume the whole gas limit, of which the + # persisting authorization state gas is accounted on the state + # component and the remainder on the regular component. + expected_gas_used = max(gas_limit - top_frame_state, top_frame_state) - expected_gas_used = max(block_regular, auth_intrinsic_state - auth_refund) + tx = Transaction( + ty=4, + to=target, + gas_limit=gas_limit, + sender=pre.fund_eoa(), + authorization_list=authorization_list, + ) + post = { + signer: Account(code=Spec7702.delegation_designation(delegate)), + } state_test( pre=pre, - post={ - signer: Account( - code=Spec7702.delegation_designation(delegate), - ), - }, + post=post, tx=tx, blockchain_test_header_verify=Header(gas_used=expected_gas_used), ) @@ -1425,73 +1739,69 @@ def test_auth_sender_billing_after_failure( authority_exists: bool, ) -> None: """ - Verify sender billing distinguishes new vs existing account auth - on top-level failure. - - For existing accounts, set_delegation refunds new-account state - gas to the reservoir and the worst-case `ACCOUNT_WRITE` to the - regular refund counter; both survive the top-level REVERT since - delegations are applied before execution. On REVERT, the restored - reservoir and the capped regular refund reduce the sender's bill - via the billing formula. The sender pays less than in the - new-account case. + Verify sender billing distinguishes new vs existing authority on a + top-level REVERT. + + The delegation persists through the REVERT, so the state gas that + paid for it stays billed alongside the regular gas: the sender pays + ``intrinsic_regular + top_frame_regular + revert_regular`` plus the + authorization's state charges. Both authorities pay the first-write + ``ACCOUNT_WRITE`` and the ``AUTH_BASE``; a new authority + additionally pays ``NEW_ACCOUNT`` for the created leaf, so its + sender pays exactly ``NEW_ACCOUNT`` more than the + existing-authority case. """ - auth_intrinsic_state = fork.transaction_intrinsic_state_gas( - authorization_count=1, - ) - intrinsic_cost = fork.transaction_intrinsic_cost_calculator() - intrinsic_total = intrinsic_cost(authorization_list_or_count=1) - intrinsic_regular = intrinsic_total - auth_intrinsic_state - new_account_refund = fork.gas_costs().REFUND_AUTH_PER_EXISTING_ACCOUNT - delegate = pre.deploy_contract(code=Op.STOP) - target = pre.deploy_contract(code=Op.REVERT(0, 0)) + revert_code = Op.REVERT(0, 0) + target = pre.deploy_contract(code=revert_code) if authority_exists: signer = pre.fund_eoa() + creates_account = False else: - signer = pre.fund_eoa(0) + signer = pre.fund_eoa(amount=0) + creates_account = True + + authorization_list = [ + AuthorizationTuple( + address=delegate, + nonce=0, + signer=signer, + creates_account=creates_account, + writes_delegation=True, + ), + ] - revert_gas = (Op.REVERT(0, 0)).gas_cost(fork) - auth_refund = new_account_refund if authority_exists else 0 - refund_counter = fork.gas_costs().ACCOUNT_WRITE if authority_exists else 0 - gas_used_before_refund = intrinsic_total + revert_gas - auth_refund - regular_refund = min( - gas_used_before_refund // fork.max_refund_quotient(), - refund_counter, + intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( + fork, authorization_list ) - expected_cumulative = gas_used_before_refund - regular_refund - expected_gas_used = max( - intrinsic_regular + revert_gas, - auth_intrinsic_state - auth_refund, + # The authorization's state gas persists with its delegation across + # the REVERT and stays billed to the sender. + expected_cumulative, header_gas_used = _receipt_and_header( + intrinsic_regular, + top_frame_regular, + top_frame_state, + execution_regular=revert_code.regular_cost(fork), ) tx = Transaction( ty=4, to=target, - state_gas_reservoir=auth_intrinsic_state, sender=pre.fund_eoa(), - authorization_list=[ - AuthorizationTuple( - address=delegate, - nonce=0, - signer=signer, - ), - ], + authorization_list=authorization_list, expected_receipt=TransactionReceipt( cumulative_gas_used=expected_cumulative, ), ) + post = { + signer: Account(code=Spec7702.delegation_designation(delegate)), + } state_test( pre=pre, - post={ - signer: Account( - code=Spec7702.delegation_designation(delegate), - ), - }, + post=post, tx=tx, - blockchain_test_header_verify=Header(gas_used=expected_gas_used), + blockchain_test_header_verify=Header(gas_used=header_gas_used), ) @@ -1503,63 +1813,88 @@ def test_auth_sender_billing_after_failure( ], ) @pytest.mark.valid_from("EIP8037") -def test_auth_refund_reservoir_cannot_fund_regular_gas( +def test_auth_and_execution_state_oog_boundary( state_test: StateTestFiller, pre: Alloc, fork: Fork, gas_delta: int, ) -> None: """ - Verify the auth NEW_ACCOUNT refund funds state gas only, not regular. - - A set_code tx on a pre-existing authority refunds NEW_ACCOUNT to the - reservoir. The target's SSTORE-set pays its state charge from that - refund but its regular charge from gas_left: at exactly the SSTORE - regular cost the write lands, one gas short it runs out of gas. + Verify the top-frame + execution state gas OOG boundary. + + A set_code tx delegates an existing authority (top-frame + ``AUTH_BASE``) to a recipient that performs a zero-to-nonzero SSTORE. + All state charges draw from ``gas_left`` (there is no reservoir to + fund them). At exactly the total cost the SSTORE lands and the storage + is written; one gas short, the execution runs out of gas at the top + frame, the storage change rolls back, and the transaction consumes its + whole gas limit. The delegation, applied in the earlier preparation + snapshot, persists in both cases. """ - total_intrinsic = fork.transaction_intrinsic_cost_calculator()( - authorization_list_or_count=1, - ) - set_op = Op.SSTORE.with_metadata( - key_warm=False, original_value=0, current_value=0, new_value=1 - ) storage = Storage() - target_code = set_op(storage.store_next(1), 1) - sstore_regular = target_code.regular_cost(fork) + target_code = Op.SSTORE(storage.store_next(1), 1) + target = pre.deploy_contract(code=target_code) + execution_regular = target_code.regular_cost(fork) + execution_state = target_code.state_cost(fork) + + authority = pre.fund_eoa() + authorization_list = [ + AuthorizationTuple( + address=target, + nonce=0, + signer=authority, + creates_account=False, + writes_delegation=True, + ), + ] - # In-cap so the reservoir's only state gas is the refunded NEW_ACCOUNT. - gas_limit = total_intrinsic + sstore_regular + gas_delta + intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( + fork, authorization_list + ) + full_cost = ( + intrinsic_regular + + top_frame_regular + + top_frame_state + + execution_regular + + execution_state + ) + gas_limit = full_cost + gas_delta gas_limit_cap = fork.transaction_gas_limit_cap() assert gas_limit_cap is not None assert gas_limit <= gas_limit_cap - target = pre.deploy_contract(code=target_code) - authority = pre.fund_eoa() + fits = gas_delta >= 0 + if fits: + _, header_gas_used = _receipt_and_header( + intrinsic_regular, + top_frame_regular, + top_frame_state, + execution_regular=execution_regular, + execution_state=execution_state, + ) + else: + # One gas short: execution OOGs at the top frame, consuming the + # whole gas limit; the SSTORE rolls back (its state gas is + # refilled) while the delegation persists, so its AUTH_BASE is + # accounted on the block's state component. + header_gas_used = max(gas_limit - top_frame_state, top_frame_state) + tx = Transaction( to=target, gas_limit=gas_limit, - authorization_list=[ - AuthorizationTuple(address=target, nonce=0, signer=authority), - ], + authorization_list=authorization_list, sender=pre.fund_eoa(), ) - fits = gas_delta >= 0 - intrinsic_state = fork.transaction_intrinsic_state_gas( - authorization_count=1, - ) - auth_refund = fork.gas_costs().REFUND_AUTH_PER_EXISTING_ACCOUNT - state_used = ( - intrinsic_state - - auth_refund - + (target_code.state_cost(fork) if fits else 0) - ) + + post = { + target: Account(storage=storage if fits else {}), + authority: Account(code=Spec7702.delegation_designation(target)), + } state_test( pre=pre, - post={target: Account(storage=storage if fits else {})}, + post=post, tx=tx, - blockchain_test_header_verify=Header( - gas_used=max(gas_limit - intrinsic_state, state_used), - ), + blockchain_test_header_verify=Header(gas_used=header_gas_used), ) @@ -1572,40 +1907,43 @@ def test_auth_refund_reservoir_cannot_fund_regular_gas( ], ) @pytest.mark.valid_from("EIP8037") -def test_invalid_auth_rule1_refill_by_reason( +def test_invalid_auth_no_top_frame_charge( state_test: StateTestFiller, pre: Alloc, fork: Fork, invalidity: str, ) -> None: """ - Verify an invalid authorization refills its full intrinsic state gas. - - A rejected authorization is skipped during processing. Its whole - state portion of NEW_ACCOUNT plus AUTH_BASE refills the reservoir - and one ACCOUNT_WRITE refunds to the refund counter. The regular - per authorization base cost stays charged and the authority is - never created. Swept over the reasons an authorization is rejected. + Verify a rejected authorization incurs no top-frame charge. + + A rejected authorization is skipped during ``set_delegation``, so it + writes no delegation indicator and creates no account: it incurs + neither ``NEW_ACCOUNT`` / ``ACCOUNT_WRITE`` nor ``AUTH_BASE`` at the + top frame (and, unlike the superseded EIP-8037 model, nothing is + refilled because nothing was charged). Only the intrinsic + ``REGULAR_PER_AUTH_BASE_COST`` is paid and the authority is never + created. Swept over the reasons an authorization is rejected. """ - per_auth_state = fork.transaction_intrinsic_state_gas( - authorization_count=1, - ) - total_intrinsic = fork.transaction_intrinsic_cost_calculator()( - authorization_list_or_count=1, - ) - intrinsic_regular = total_intrinsic - per_auth_state - account_write = fork.gas_costs().ACCOUNT_WRITE - target = pre.deploy_contract(code=Op.STOP) signer = pre.fund_eoa(amount=0) if invalidity == "nonce_mismatch": - auth = AuthorizationTuple(address=target, nonce=99, signer=signer) + auth = AuthorizationTuple( + address=target, + nonce=99, + signer=signer, + creates_account=False, + writes_delegation=False, + first_write=False, + ) elif invalidity == "nonce_at_u64_max": auth = AuthorizationTuple( address=target, nonce=2**64 - 1, signer=signer, + creates_account=False, + writes_delegation=False, + first_write=False, ) elif invalidity == "chain_id_mismatch": auth = AuthorizationTuple( @@ -1613,31 +1951,28 @@ def test_invalid_auth_rule1_refill_by_reason( nonce=0, chain_id=9999, signer=signer, + creates_account=False, + writes_delegation=False, + first_write=False, ) else: raise ValueError(f"unknown invalidity: {invalidity!r}") - # The skipped auth refills its whole state portion to the reservoir - # so the net state charge is zero, and one ACCOUNT_WRITE returns to - # the capped refund counter. - auth_refund = per_auth_state - refund_counter = account_write - - header_gas_used = max(intrinsic_regular, per_auth_state - auth_refund) - gas_used_before_refund = total_intrinsic - auth_refund - regular_refund = min( - gas_used_before_refund // fork.max_refund_quotient(), - refund_counter, + intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( + fork, [auth] + ) + assert top_frame_regular == 0 + assert top_frame_state == 0 + cumulative_gas_used, header_gas_used = _receipt_and_header( + intrinsic_regular, top_frame_regular, top_frame_state ) - receipt_cumulative_gas_used = gas_used_before_refund - regular_refund tx = Transaction( to=target, - state_gas_reservoir=per_auth_state, authorization_list=[auth], sender=pre.fund_eoa(), expected_receipt=TransactionReceipt( - cumulative_gas_used=receipt_cumulative_gas_used, + cumulative_gas_used=cumulative_gas_used, ), ) @@ -1650,75 +1985,63 @@ def test_invalid_auth_rule1_refill_by_reason( @pytest.mark.valid_from("EIP8037") -def test_same_tx_create_then_clear_double_auth_base_refill( +def test_same_tx_create_then_clear( state_test: StateTestFiller, pre: Alloc, fork: Fork, ) -> None: """ - Verify the create then clear double AUTH_BASE refill in one tx. - - A fresh authority is delegated by the first authorization then - cleared by the second within one transaction. The clear refills - AUTH_BASE twice. Once because the clear writes no indicator bytes. - Once because the delegation it removes was created earlier in this - same transaction. Net AUTH_BASE charged is zero and only the - NEW_ACCOUNT leaf cost remains. + Verify a create-then-clear on one authority in a single transaction. + + A fresh authority is delegated by the first authorization then cleared + by the second. The first charges ``NEW_ACCOUNT`` + ``ACCOUNT_WRITE`` + (leaf creation and first write) and ``AUTH_BASE`` (net-new + indicator); the second clears the delegation the first set. The + ``AUTH_BASE`` is charged at most once per authority and never + credited back, so it stays paid even though the authority ends the + transaction with empty code and nonce 2. """ - per_auth_state = fork.transaction_intrinsic_state_gas( - authorization_count=1, - ) - intrinsic_state = fork.transaction_intrinsic_state_gas( - authorization_count=2, - ) - total_intrinsic = fork.transaction_intrinsic_cost_calculator()( - authorization_list_or_count=2, - ) - intrinsic_regular = total_intrinsic - intrinsic_state - new_account_refund = fork.gas_costs().REFUND_AUTH_PER_EXISTING_ACCOUNT - account_write = fork.gas_costs().ACCOUNT_WRITE - auth_base_refund = per_auth_state - new_account_refund - contract_a = pre.deploy_contract(code=Op.STOP) target = pre.deploy_contract(code=Op.STOP) signer = pre.fund_eoa(amount=0) authorization_list = [ - AuthorizationTuple(address=contract_a, nonce=0, signer=signer), + AuthorizationTuple( + address=contract_a, + nonce=0, + signer=signer, + creates_account=True, + writes_delegation=True, + ), AuthorizationTuple( address=Spec7702.RESET_DELEGATION_ADDRESS, nonce=1, signer=signer, + creates_account=False, + writes_delegation=False, + first_write=False, ), ] - # The first auth creates the leaf and writes the indicator with no - # refill. The second auth refills NEW_ACCOUNT, AUTH_BASE twice, and - # one ACCOUNT_WRITE. - auth_refund = new_account_refund + 2 * auth_base_refund - refund_counter = account_write - - header_gas_used = max(intrinsic_regular, intrinsic_state - auth_refund) - gas_used_before_refund = total_intrinsic - auth_refund - regular_refund = min( - gas_used_before_refund // fork.max_refund_quotient(), - refund_counter, + intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( + fork, authorization_list + ) + cumulative_gas_used, header_gas_used = _receipt_and_header( + intrinsic_regular, top_frame_regular, top_frame_state ) - receipt_cumulative_gas_used = gas_used_before_refund - regular_refund tx = Transaction( to=target, - state_gas_reservoir=intrinsic_state, authorization_list=authorization_list, sender=pre.fund_eoa(), expected_receipt=TransactionReceipt( - cumulative_gas_used=receipt_cumulative_gas_used, + cumulative_gas_used=cumulative_gas_used, ), ) state_test( pre=pre, - post={signer: Account(nonce=2, code=b"")}, + post={signer: Account(nonce=2, balance=0, code=b"")}, tx=tx, blockchain_test_header_verify=Header(gas_used=header_gas_used), ) @@ -1731,28 +2054,18 @@ def test_same_tx_clear_then_reset_pre_delegated( fork: Fork, ) -> None: """ - Verify clear then reset of a pre delegated authority in one tx. - - An authority delegated before the transaction is cleared by the - first authorization then set to a new target by the second. The - reset refills AUTH_BASE through the pre delegated term even though - the current code was empty at that point. Net AUTH_BASE charged is - zero because the authority started and ended delegated. + Verify a clear-then-reset of a pre-delegated authority in one tx. + + An authority delegated before the transaction is cleared by the first + authorization then re-delegated to a new target by the second. Because + the authority was already delegated before the transaction, neither + authorization writes a net-new delegation indicator: no ``AUTH_BASE`` + is charged and, as the leaf already exists, no ``NEW_ACCOUNT`` + either. The clear is the transaction's first write to the leaf, so + one ``ACCOUNT_WRITE`` is paid on top of the intrinsic + per-authorization bases. The authority ends delegated to the new + target with nonce 3. """ - per_auth_state = fork.transaction_intrinsic_state_gas( - authorization_count=1, - ) - intrinsic_state = fork.transaction_intrinsic_state_gas( - authorization_count=2, - ) - total_intrinsic = fork.transaction_intrinsic_cost_calculator()( - authorization_list_or_count=2, - ) - intrinsic_regular = total_intrinsic - intrinsic_state - new_account_refund = fork.gas_costs().REFUND_AUTH_PER_EXISTING_ACCOUNT - account_write = fork.gas_costs().ACCOUNT_WRITE - auth_base_refund = per_auth_state - new_account_refund - contract_a = pre.deploy_contract(code=Op.STOP) contract_b = pre.deploy_contract(code=Op.STOP) target = pre.deploy_contract(code=Op.STOP) @@ -1763,30 +2076,34 @@ def test_same_tx_clear_then_reset_pre_delegated( address=Spec7702.RESET_DELEGATION_ADDRESS, nonce=1, signer=signer, + creates_account=False, + writes_delegation=False, + ), + AuthorizationTuple( + address=contract_b, + nonce=2, + signer=signer, + creates_account=False, + writes_delegation=False, + first_write=False, ), - AuthorizationTuple(address=contract_b, nonce=2, signer=signer), ] - # Both auths refill NEW_ACCOUNT and one AUTH_BASE each. The leaf - # already exists so each also refunds one ACCOUNT_WRITE. - auth_refund = 2 * (new_account_refund + auth_base_refund) - refund_counter = 2 * account_write - - header_gas_used = max(intrinsic_regular, intrinsic_state - auth_refund) - gas_used_before_refund = total_intrinsic - auth_refund - regular_refund = min( - gas_used_before_refund // fork.max_refund_quotient(), - refund_counter, + intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( + fork, authorization_list + ) + assert top_frame_regular == fork.gas_costs().ACCOUNT_WRITE + assert top_frame_state == 0 + cumulative_gas_used, header_gas_used = _receipt_and_header( + intrinsic_regular, top_frame_regular, top_frame_state ) - receipt_cumulative_gas_used = gas_used_before_refund - regular_refund tx = Transaction( to=target, - state_gas_reservoir=intrinsic_state, authorization_list=authorization_list, sender=pre.fund_eoa(), expected_receipt=TransactionReceipt( - cumulative_gas_used=receipt_cumulative_gas_used, + cumulative_gas_used=cumulative_gas_used, ), ) @@ -1810,58 +2127,47 @@ def test_same_authority_increasing_nonce_net_once( fork: Fork, ) -> None: """ - Verify the per authority once invariant across valid auths. + Verify the per-authority once invariant across valid auths. The same fresh authority is delegated by three authorizations with - increasing nonces in one transaction. The account leaf and its - delegation indicator are written once. NEW_ACCOUNT and AUTH_BASE are - each charged once across the batch while ACCOUNT_WRITE is refunded - for every auth after the leaf is created. + increasing nonces in one transaction. The account leaf is created and + first written once (``NEW_ACCOUNT`` + ``ACCOUNT_WRITE`` on the first + authorization) and a net-new delegation indicator is written once + (``AUTH_BASE`` on the first). The later authorizations re-point an + already-written, already-delegated authority, so they add nothing + beyond the intrinsic base. The authority ends delegated to the last + target with nonce 3. """ num_auths = 3 - per_auth_state = fork.transaction_intrinsic_state_gas( - authorization_count=1, - ) - intrinsic_state = fork.transaction_intrinsic_state_gas( - authorization_count=num_auths, - ) - total_intrinsic = fork.transaction_intrinsic_cost_calculator()( - authorization_list_or_count=num_auths, - ) - intrinsic_regular = total_intrinsic - intrinsic_state - new_account_refund = fork.gas_costs().REFUND_AUTH_PER_EXISTING_ACCOUNT - account_write = fork.gas_costs().ACCOUNT_WRITE - auth_base_refund = per_auth_state - new_account_refund - targets = [pre.deploy_contract(code=Op.STOP) for _ in range(num_auths)] call_target = pre.deploy_contract(code=Op.STOP) signer = pre.fund_eoa(amount=0) authorization_list = [ - AuthorizationTuple(address=targets[i], nonce=i, signer=signer) + AuthorizationTuple( + address=targets[i], + nonce=i, + signer=signer, + creates_account=(i == 0), + writes_delegation=(i == 0), + first_write=(i == 0), + ) for i in range(num_auths) ] - # The first auth creates the leaf with no refill. Each later auth - # refills NEW_ACCOUNT, one AUTH_BASE, and one ACCOUNT_WRITE. - auth_refund = (num_auths - 1) * (new_account_refund + auth_base_refund) - refund_counter = (num_auths - 1) * account_write - - header_gas_used = max(intrinsic_regular, intrinsic_state - auth_refund) - gas_used_before_refund = total_intrinsic - auth_refund - regular_refund = min( - gas_used_before_refund // fork.max_refund_quotient(), - refund_counter, + intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( + fork, authorization_list + ) + cumulative_gas_used, header_gas_used = _receipt_and_header( + intrinsic_regular, top_frame_regular, top_frame_state ) - receipt_cumulative_gas_used = gas_used_before_refund - regular_refund tx = Transaction( to=call_target, - state_gas_reservoir=intrinsic_state, authorization_list=authorization_list, sender=pre.fund_eoa(), expected_receipt=TransactionReceipt( - cumulative_gas_used=receipt_cumulative_gas_used, + cumulative_gas_used=cumulative_gas_used, ), ) @@ -1870,6 +2176,7 @@ def test_same_authority_increasing_nonce_net_once( post={ signer: Account( nonce=num_auths, + balance=0, code=Spec7702.delegation_designation(targets[-1]), ), }, diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_create_gas.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_create_gas.py index 862b6fc77a7..d76057ecf1e 100644 --- a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_create_gas.py +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_create_gas.py @@ -289,8 +289,13 @@ def exact_execution_gas( self, fork: Fork, exact_intrinsic_gas: int, initcode: Initcode ) -> int: """ - Return the total execution gas: intrinsic plus the initcode - execution gas plus the code-deposit gas. + Return the total execution gas: intrinsic plus the top-frame + ``NEW_ACCOUNT`` plus the initcode execution gas plus the + code-deposit gas. + + Under EIP-2780 the created account's ``NEW_ACCOUNT`` state gas + moved out of the intrinsic and into the top frame, so it is added + explicitly here (the intrinsic is regular-only). ``deployment_gas`` is fork-aware: under EIP-8037 it splits the deposit into the keccak word cost (regular) and the per-byte cost @@ -298,7 +303,8 @@ def exact_execution_gas( flat regular per-byte deposit cost. The single call is therefore correct in either regime. """ - execution = exact_intrinsic_gas + initcode.execution_gas(fork) + execution = exact_intrinsic_gas + fork.gas_costs().NEW_ACCOUNT + execution += initcode.execution_gas(fork) execution += initcode.deployment_gas(fork) return execution @@ -362,26 +368,26 @@ def test_create_tx_gas_boundary( sender=sender, ) - # 2D block accounting: gas_used = max(regular, state). The state - # axis carries the intrinsic NEW_ACCOUNT and (when the deposit - # succeeds) the per-byte code-deposit gas. + # 2D block accounting: gas_used = max(regular, state). Under + # EIP-2780 the state axis carries the fresh target's top-frame + # NEW_ACCOUNT and (when the deposit succeeds) the per-byte + # code-deposit gas. if tx_error is not None: header_verify = None - else: - intrinsic_state = ( - fork.transaction_intrinsic_state_gas(contract_creation=True) - if hasattr(fork, "transaction_intrinsic_state_gas") - else 0 + elif succeeds: + # Fresh target: top-frame NEW_ACCOUNT plus the per-byte code + # deposit are the state-gas axis; the rest is regular. + state_used = fork.gas_costs().NEW_ACCOUNT + state_used += fork.code_deposit_state_gas( + code_size=len(initcode.deploy_code) ) - regular_used = gas_limit - intrinsic_state - state_used = intrinsic_state - if succeeds: - code_deposit_state = fork.code_deposit_state_gas( - code_size=len(initcode.deploy_code) - ) - state_used += code_deposit_state - regular_used -= code_deposit_state + regular_used = gas_limit - state_used header_verify = Header(gas_used=max(regular_used, state_used)) + else: + # exact_intrinsic / too_little_execution: the top-frame + # NEW_ACCOUNT (and any deposit) cannot be covered, the whole + # preparation rolls back, and all gas is burned as regular. + header_verify = Header(gas_used=gas_limit) state_test( pre=pre, diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_fork_transition.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_fork_transition.py index 5ff91b0bbf6..a1de15f4108 100644 --- a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_fork_transition.py +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_fork_transition.py @@ -451,11 +451,14 @@ def test_auth_intrinsic_at_transition( fork: Fork, ) -> None: """ - The ``7702`` authorization intrinsic rises across the boundary. A tx - whose ``gas_limit`` equals the pre-fork single-authorization - intrinsic is valid before the fork but is rejected with - ``INTRINSIC_GAS_TOO_LOW`` after, because the EIP-8038 auth intrinsic - is strictly larger. + The ``7702`` authorization intrinsic *falls* across the boundary. + EIP-2780 moves the state-dependent authorization costs (account + creation and the delegation-write base) out of the intrinsic and into + the top frame, leaving only the regular ``REGULAR_PER_AUTH_BASE_COST`` + in the intrinsic. The post-fork single-authorization intrinsic is + therefore strictly smaller than the pre-fork one, so a tx whose + ``gas_limit`` equals the (lower) post-fork intrinsic is rejected with + ``INTRINSIC_GAS_TOO_LOW`` before the fork but valid after. """ before = fork.fork_at(timestamp=BEFORE_TS) after = fork.fork_at(timestamp=AFTER_TS) @@ -468,10 +471,10 @@ def test_auth_intrinsic_at_transition( authorization_list_or_count=1, return_cost_deducted_prior_execution=True, ) - # The pre-fork intrinsic is below the post-fork one, so the same + # The post-fork intrinsic is below the pre-fork one, so the same # gas_limit straddles validity at the boundary. - assert intrinsic_before < intrinsic_after - gas_limit = intrinsic_before + assert intrinsic_after < intrinsic_before + gas_limit = intrinsic_after target_before = pre.deploy_contract(code=Op.STOP) target_after = pre.deploy_contract(code=Op.STOP) @@ -480,7 +483,8 @@ def test_auth_intrinsic_at_transition( auth_after = pre.fund_eoa() blocks = [ - # Before the fork: gas_limit covers the old auth intrinsic. + # Before the fork: gas_limit is below the (higher) old auth + # intrinsic, so the tx is rejected. Block( timestamp=BEFORE_TS, txs=[ @@ -495,10 +499,15 @@ def test_auth_intrinsic_at_transition( ), ], sender=pre.fund_eoa(), + error=TransactionException.INTRINSIC_GAS_TOO_LOW, ), ], + exception=TransactionException.INTRINSIC_GAS_TOO_LOW, ), - # After the fork: identical gas_limit is now below intrinsic. + # After the fork: the auth intrinsic dropped to exactly this + # gas_limit, so the tx is now valid (included). It has no gas left + # for the top-frame delegation, so execution runs out of gas and + # the delegation rolls back, but the block itself is valid. Block( timestamp=AFTER_TS, txs=[ @@ -513,10 +522,8 @@ def test_auth_intrinsic_at_transition( ), ], sender=pre.fund_eoa(), - error=TransactionException.INTRINSIC_GAS_TOO_LOW, ), ], - exception=TransactionException.INTRINSIC_GAS_TOO_LOW, ), ] diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_selfdestruct_gas.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_selfdestruct_gas.py index 9349b191071..2fb565c7a36 100644 --- a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_selfdestruct_gas.py +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_selfdestruct_gas.py @@ -541,13 +541,13 @@ def test_same_tx_created_selfdestruct_self_burn( the balance stays in the (otherwise emptied) originator and no log is emitted. - No net state gas is charged either way: the only state cost is the - intrinsic creation ``NEW_ACCOUNT``, but the pre-funded created target - is alive at message entry, so EIP-8037 refunds it (the create-tx - ``created_target_alive`` refund). The block ``gas_used`` is therefore - the pure regular consumption regardless of the burn behavior. + No net state gas is charged either way: under EIP-2780 the create-tx + ``NEW_ACCOUNT`` is a top-frame charge levied only when the target is + ``EMPTY`` pre-tx, but the pre-funded created target already has a + balance, so it is never charged. The self-burn adds no state gas, so + the block ``gas_used`` is the pure regular consumption regardless of + the burn behavior. """ - new_account_state_gas = fork.gas_costs().NEW_ACCOUNT intrinsic_calc = fork.transaction_intrinsic_cost_calculator() amount = 1 @@ -555,8 +555,8 @@ def test_same_tx_created_selfdestruct_self_burn( created = compute_create_address(address=sender, nonce=0) # Pre-fund the created address so its balance is present without an # in-tx value transfer (which would emit its own Transfer log). The - # pre-funded target is alive at message entry, so the create-tx - # intrinsic NEW_ACCOUNT is refunded (EIP-8037). + # pre-funded target is not EMPTY pre-tx, so the top-frame NEW_ACCOUNT + # is never charged. pre.fund_address(created, amount) # Self is the executing account, warm on entry: no cold surcharge. @@ -567,12 +567,15 @@ def test_same_tx_created_selfdestruct_self_burn( regular = _selfdestruct_regular(fork, warm=True, account_new=False) assert regular == fork.gas_costs().OPCODE_SELFDESTRUCT_BASE - intrinsic_total = intrinsic_calc( - calldata=bytes(init_code), contract_creation=True + # Creation intrinsic is regular-only under EIP-2780; the pre-existing + # target adds no top-frame NEW_ACCOUNT and the self-burn adds no state + # gas, so net state gas is zero. The regular consumption exceeds the + # decomposed calldata floor, so the floor never pins the billing. + intrinsic_regular = intrinsic_calc( + calldata=bytes(init_code), + contract_creation=True, + return_cost_deducted_prior_execution=True, ) - # The creation NEW_ACCOUNT is refunded (target alive at entry) and the - # self-burn adds no state gas, so net state gas is zero. - intrinsic_regular = intrinsic_total - new_account_state_gas expected_regular = intrinsic_regular + init_code.regular_cost(fork) expected_gas_used = expected_regular diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_set_code_auth_gas.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_set_code_auth_gas.py index 61cd928c2a5..a3a50b782a5 100644 --- a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_set_code_auth_gas.py +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_set_code_auth_gas.py @@ -2,26 +2,23 @@ Tests for the EIP-7702 authorization *regular*-gas repricing under [EIP-8038: State Access Gas Cost Increase](https://eips.ethereum.org/EIPS/eip-8038). -EIP-8037 splits each EIP-7702 authorization into a *state* component -(refunded against the state-gas reservoir, covered by the sibling -``eip8037_state_creation_gas_cost_increase`` suite) and a *regular* -component. This module pins the **regular** per-authorization intrinsic -magnitude and the repriced cold/warm account-access costs that an -authorized delegation incurs when later accessed by a ``CALL``. - -The regular per-authorization magnitude is derived purely from fork -helpers as:: - - regular_per_auth = ( - fork.gas_costs().AUTH_PER_EMPTY_ACCOUNT - - fork.transaction_intrinsic_state_gas(authorization_count=1) - ) - -which on Amsterdam equals ``ACCOUNT_WRITE`` (``8000``) plus the EIP-7702 -regular auth base cost (``7816``), i.e. ``15816``. The state portion that -this subtracts off (``transaction_intrinsic_state_gas``) is exactly what -the EIP-8037 suite asserts on the state channel; this suite never -re-asserts it. +Under EIP-2780 each EIP-7702 authorization is charged in two parts: a +state-independent *regular* base cost paid in the intrinsic, and +state-dependent costs (``NEW_ACCOUNT`` / ``ACCOUNT_WRITE`` for a new +authority leaf, ``AUTH_BASE`` for a net-new delegation indicator) paid +lazily at the top frame in ``set_delegation``. This module pins the +**regular** per-authorization intrinsic magnitude and the repriced +cold/warm account-access costs that an authorized delegation incurs +when later accessed by a ``CALL``. + +The regular per-authorization intrinsic magnitude is +``fork.gas_costs().REGULAR_PER_AUTH_BASE_COST`` (``7816`` on Amsterdam: +``101 * 16`` calldata tokens plus the ``3000`` ecrecover, ``3000`` cold +and ``2 * 100`` warm accesses of the EIP-7702 base). The top-frame +state charges are asserted by the sibling +``eip8037_state_creation_gas_cost_increase`` and +``eip2780_reduce_intrinsic_tx_gas`` suites; this suite does not +re-assert them. """ from typing import List @@ -57,13 +54,15 @@ def _regular_per_auth(fork: Fork) -> int: """ - Return the EIP-8038 *regular* intrinsic gas charged per EIP-7702 - authorization, i.e. the total per-auth intrinsic less the EIP-8037 - state portion. + Return the *regular* intrinsic gas charged per EIP-7702 + authorization. + + Under EIP-2780 the intrinsic charges only the state-independent + ``REGULAR_PER_AUTH_BASE_COST`` per authorization; the account-write + (``ACCOUNT_WRITE``) and delegation-write (``AUTH_BASE``) costs are + charged lazily at the top frame, not in the intrinsic. """ - return fork.gas_costs().AUTH_PER_EMPTY_ACCOUNT - ( - fork.transaction_intrinsic_state_gas(authorization_count=1) - ) + return fork.gas_costs().REGULAR_PER_AUTH_BASE_COST def _regular_intrinsic( @@ -74,18 +73,15 @@ def _regular_intrinsic( calldata: bytes = b"", ) -> int: """ - Return the regular (non-state) intrinsic gas of a set-code + Return the intrinsic gas of a set-code transaction: the full intrinsic less the authorization state gas. """ - total = fork.transaction_intrinsic_cost_calculator()( + return fork.transaction_intrinsic_cost_calculator()( authorization_list_or_count=n, access_list=access_list, calldata=calldata, return_cost_deducted_prior_execution=True, ) - return total - fork.transaction_intrinsic_state_gas( - authorization_count=n, - ) @EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() @@ -119,9 +115,7 @@ def test_auth_regular_intrinsic_magnitude( The regular intrinsic above the ``n=0`` base must equal ``n * regular_per_auth`` plus the access-list delta (derived from the calculator itself so the calldata-floor contribution of the - access-list bytes is accounted for). The state portion is excluded - via ``transaction_intrinsic_state_gas`` and is left to the EIP-8037 - suite. + access-list bytes is accounted for). """ contract = pre.deploy_contract(code=Op.STOP) @@ -324,49 +318,40 @@ def test_mixed_validity_multi_auth_receipt_gas( ) -> None: """ Pin the exact receipt gas of a transaction carrying one valid and - one invalid authorization. - - Every authorization tuple, valid or invalid, is charged the full - regular + state per-authorization intrinsic. The valid - authorization whose authority leaf already exists refills - ``NEW_ACCOUNT`` on the state channel (uncapped, subtracted first) - and returns ``ACCOUNT_WRITE`` on the regular channel (one-fifth - capped). The invalid tuple is silently skipped during - ``set_delegation``, refilling the full per-auth state intrinsic and - returning its regular ``ACCOUNT_WRITE`` charge. - - The dual-channel accounting mirrors ``process_transaction`` and the - sibling ``test_set_code_auth_refunds`` module: the state refill is - subtracted from ``gas_before_regular_refund`` first and uncapped, - then the regular refund clamps to - ``min(k * ACCOUNT_WRITE, gas_before_regular_refund // 5)`` where - ``k`` is the number of authorizations that return the regular - account-write charge. With no EVM execution, - ``gas_before_regular_refund`` reduces to the full per-authorization - intrinsic less the state refill, and the exact result is asserted - via ``expected_receipt``. + one invalid authorization under the EIP-2780 top-frame charge model. + + Both tuples pay the state-independent ``REGULAR_PER_AUTH_BASE_COST`` + in the intrinsic. The single valid authorization's authority leaf + already exists (a funded EOA) and gains a net-new delegation + indicator, so at the top frame it pays the first-write + ``ACCOUNT_WRITE`` and ``AUTH_BASE`` -- no ``NEW_ACCOUNT`` and no + refund. The invalid tuple is silently skipped during + ``set_delegation`` and pays nothing beyond the intrinsic base. Each ``invalidity`` kind (``INVALID_NONCE``, ``INVALID_CHAIN_ID``, - ``REPEATED_NONCE``, ``AUTHORITY_IS_CONTRACT``) yields one valid and - one invalid tuple, so ``n = 2`` and ``k = 2`` uniformly and every - kind pins the same receipt gas. This is the numeric-receipt - companion to ``test_invalid_auth_charged_intrinsic`` (which asserts - only post state). + ``REPEATED_NONCE``, ``AUTHORITY_IS_CONTRACT``) yields the same + one-valid-one-invalid shape, so every kind pins the same receipt + gas. This is the numeric-receipt companion to + ``test_invalid_auth_charged_intrinsic`` (which asserts only post + state). """ - gas_costs = fork.gas_costs() - account_write = gas_costs.ACCOUNT_WRITE - delegate = pre.deploy_contract(code=Op.STOP) - # The single refundable (valid, existing-leaf) authorization. + # The single valid authorization: an existing (funded) authority + # gaining a net-new delegation. It writes a delegation indicator + # (AUTH_BASE at the top frame) but creates no account. valid_signer = pre.fund_eoa() valid_auth = AuthorizationTuple( - address=delegate, nonce=0, signer=valid_signer + address=delegate, + nonce=0, + signer=valid_signer, + creates_account=False, + writes_delegation=True, ) - # Build the authorization list: one valid tuple plus one invalid - # tuple of the requested kind. ``authority`` is the account that must - # end up untouched by the skipped (invalid) authorization. + # One valid tuple plus one invalid tuple of the requested kind. The + # invalid tuple is skipped in ``set_delegation``, so it neither + # creates an account nor writes a delegation indicator. authorization_list: List[AuthorizationTuple] post: dict = { valid_signer: Account( @@ -382,6 +367,9 @@ def test_mixed_validity_multi_auth_receipt_gas( address=delegate, nonce=99, # wrong nonce -> skipped signer=authority, + creates_account=False, + writes_delegation=False, + first_write=False, ), ] post[authority] = Account(code=b"") @@ -394,16 +382,25 @@ def test_mixed_validity_multi_auth_receipt_gas( nonce=0, chain_id=9999, # wrong chain id -> skipped signer=authority, + creates_account=False, + writes_delegation=False, + first_write=False, ), ] post[authority] = Account(code=b"") elif invalidity == "repeated_nonce": # The valid tuple consumes the signer's nonce 0; a second tuple - # reusing nonce 0 on the same signer is skipped. The signer is - # the refundable authority, delegated by its first (valid) tuple. + # reusing nonce 0 on the same signer is skipped. authorization_list = [ valid_auth, - AuthorizationTuple(address=delegate, nonce=0, signer=valid_signer), + AuthorizationTuple( + address=delegate, + nonce=0, + signer=valid_signer, + creates_account=False, + writes_delegation=False, + first_write=False, + ), ] elif invalidity == "authority_is_contract": # An authority that is already a (non-delegation) contract is an @@ -412,45 +409,41 @@ def test_mixed_validity_multi_auth_receipt_gas( authority = pre.fund_eoa(code=Op.STOP) authorization_list = [ valid_auth, - AuthorizationTuple(address=delegate, nonce=0, signer=authority), + AuthorizationTuple( + address=delegate, + nonce=0, + signer=authority, + creates_account=False, + writes_delegation=False, + first_write=False, + ), ] post[authority] = Account(code=Op.STOP) else: raise ValueError(f"unknown invalidity: {invalidity!r}") n = len(authorization_list) - regular_refundable = 2 - total_intrinsic = fork.transaction_intrinsic_cost_calculator()( + # Charge model (no refunds): the intrinsic charges the per-auth base + # for every tuple; the one valid authorization adds ``AUTH_BASE`` at + # the top frame for its net-new delegation indicator. The skipped + # tuple and the plain ``STOP`` recipient add nothing. + intrinsic_regular = fork.transaction_intrinsic_cost_calculator()( authorization_list_or_count=n, + return_cost_deducted_prior_execution=True, ) - intrinsic_state = fork.transaction_intrinsic_state_gas( - authorization_count=n, + top_frame_regular = fork.transaction_top_frame_gas_calculator()( + authorizations=authorization_list, ) - # The valid existing-leaf authorization refills NEW_ACCOUNT. The - # invalid skipped tuple refills the full per-auth state intrinsic. - # State refills are subtracted first and are not subject to the - # one-fifth cap. - state_refund = gas_costs.REFUND_AUTH_PER_EXISTING_ACCOUNT + ( - intrinsic_state // n + top_frame_state = fork.transaction_top_frame_state_gas( + authorizations=authorization_list, ) - - # No EVM execution (the target is a STOP), so the regular and state - # execution gas are both zero and ``gas_before_regular_refund`` - # reduces to the full per-auth intrinsic less the state refill. - gas_before_regular_refund = total_intrinsic - state_refund - regular_refund = min( - regular_refundable * account_write, - gas_before_regular_refund // fork.max_refund_quotient(), + cumulative_gas_used = ( + intrinsic_regular + top_frame_regular + top_frame_state ) - # The one-fifth cap is generous, so both ACCOUNT_WRITE refunds clear - # on the regular channel. - assert regular_refund == regular_refundable * account_write - cumulative_gas_used = gas_before_regular_refund - regular_refund tx = Transaction( to=delegate, - state_gas_reservoir=intrinsic_state, authorization_list=authorization_list, sender=pre.fund_eoa(), expected_receipt=TransactionReceipt( diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_set_code_auth_refunds.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_set_code_auth_refunds.py index ba3dc5e5773..d202332d6da 100644 --- a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_set_code_auth_refunds.py +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_set_code_auth_refunds.py @@ -1,46 +1,32 @@ """ -Tests for the EIP-7702 authorization *regular*-gas refund under -[EIP-8038: State Access Gas Cost Increase](https://eips.ethereum.org/EIPS/eip-8038). - -When an authority's account leaf already exists, ``set_delegation`` -refunds on two independent channels: - -* the **state** channel: ``StateGasCosts.NEW_ACCOUNT`` is refilled into - ``state_gas_reservoir`` / ``state_refund`` (and ``AUTH_BASE`` too when - the code slot already holds a delegation indicator). It is subtracted - from ``tx_state_gas`` *before* the regular refund is applied and is - **not** subject to the EIP-3529 one-fifth cap. This channel is the - subject of the EIP-8037 ``eip8037_state_creation_gas_cost_increase`` - suite. -* the **regular** channel: the worst-case ``GasCosts.ACCOUNT_WRITE`` - charged in the regular intrinsic is returned via the regular refund - counter, and **is** subject to the one-fifth cap. - -This module pins the *regular* ``ACCOUNT_WRITE`` refund. The dual-channel -accounting mirrors ``process_transaction``: - - gas_before_regular_refund = ( - intrinsic_regular + exec_regular - + intrinsic_state + exec_state - - state_refund # uncapped, subtracted first - ) - regular_refund = min( - n * ACCOUNT_WRITE, - gas_before_regular_refund // fork.max_refund_quotient(), - ) - cumulative_gas_used = gas_before_regular_refund - regular_refund - -Two regimes are exercised: - -* a non-clearing delegation on an existing leaf, padded with cold - SSTOREs so ``gas_before_regular_refund`` is large and the full - ``n * ACCOUNT_WRITE`` clears under the cap; and -* a *clearing* re-authorization of an existing-delegation authority, - where the state channel refunds the **full** per-auth state intrinsic - (``NEW_ACCOUNT + AUTH_BASE``). That collapses - ``gas_before_regular_refund`` to the regular intrinsic alone, so the - cap ``gas // 5`` becomes the binding term and the regular refund - clamps below ``ACCOUNT_WRITE``. +Tests for the EIP-7702 authorization charge on an *existing* authority +leaf under [EIP-8038: State Access Gas Cost Increase](https://eips.ethereum.org/EIPS/eip-8038). + +EIP-8038 originally over-charged every authorization as if it created a +new account and *refunded* the difference (``ACCOUNT_WRITE`` on the +regular channel, ``NEW_ACCOUNT`` -- and ``AUTH_BASE`` on a clear -- on +the state channel) when the authority leaf already existed. + +Under EIP-2780 that over-charge-then-refund is gone: the +state-dependent portion of each authorization is charged lazily at the +top frame in ``set_delegation``. An existing leaf therefore never pays +-- and is never refunded -- the ``NEW_ACCOUNT`` creation cost. It does +pay ``ACCOUNT_WRITE`` once, for the transaction's first write to its +leaf, since applying the authorization writes its code and nonce +regardless of whether the leaf pre-existed. This module pins that +reduced, refund-free charge via the exact receipt gas: + +* a non-clearing delegation on an existing empty-code leaf pays the + intrinsic ``REGULAR_PER_AUTH_BASE_COST`` plus the top-frame + ``ACCOUNT_WRITE`` (first leaf write) and ``AUTH_BASE`` (the net-new + delegation indicator); and +* a *clearing* re-authorization of an existing-delegation authority + writes no net-new indicator, so it pays only the intrinsic base plus + the first-write ``ACCOUNT_WRITE``, with no top-frame state charge at + all. + +In both regimes the receipt gas equals the exact charge with no refund +term. """ from typing import List @@ -50,12 +36,10 @@ Account, Alloc, AuthorizationTuple, - Bytecode, Environment, Fork, Op, StateTestFiller, - Storage, Transaction, TransactionReceipt, ) @@ -70,14 +54,9 @@ pytestmark = pytest.mark.valid_from("Amsterdam") -def _sstore_state_per_op(fork: Fork) -> int: - """Return the state gas of one cold ``0 -> 1`` SSTORE.""" - return Op.SSTORE(new_value=1).state_cost(fork) - - @EIPChecklist.GasRefundsChanges.Test.RefundCalculation() @pytest.mark.parametrize("n", [1, 2]) -def test_existing_authority_regular_refund_visible( +def test_existing_authority_no_new_account_charge( state_test: StateTestFiller, env: Environment, pre: Alloc, @@ -85,66 +64,52 @@ def test_existing_authority_regular_refund_visible( n: int, ) -> None: """ - Pin the full regular ``ACCOUNT_WRITE`` refund for set-code - authorizations whose authority leaves already exist. - - Each authority is an existing funded EOA delegating to a fresh - contract, so ``set_delegation`` refunds ``NEW_ACCOUNT`` on the state - channel (uncapped) and ``ACCOUNT_WRITE`` on the regular channel - (capped). The execution is padded with ten cold ``0 -> 1`` SSTOREs - so ``gas_before_regular_refund`` is large and the one-fifth cap - exceeds ``n * ACCOUNT_WRITE``; the entire regular refund is visible - in the receipt. - - The state refill is subtracted first and is not capped; it belongs - to the EIP-8037 suite and is only used here to size the receipt. + An authorization whose authority leaf already exists is charged the + reduced top-frame cost directly, with no refund. + + Each authority is an existing funded EOA gaining a fresh delegation. + Its leaf exists, so ``set_delegation`` charges no ``NEW_ACCOUNT`` + (and, unlike the superseded EIP-8038 behaviour, refunds none); it + charges the first-write ``ACCOUNT_WRITE`` and the top-frame + ``AUTH_BASE`` for the net-new delegation indicator. The receipt gas + is therefore exactly the regular intrinsic plus + ``n * (ACCOUNT_WRITE + AUTH_BASE)``, with no refund term. """ - gas_costs = fork.gas_costs() - account_write = gas_costs.ACCOUNT_WRITE - # Existing leaf overwritten with a fresh (non-clearing) delegation - # indicator: only NEW_ACCOUNT is refilled on the state channel. - state_refund = gas_costs.REFUND_AUTH_PER_EXISTING_ACCOUNT * n - - total_intrinsic = fork.transaction_intrinsic_cost_calculator()( - authorization_list_or_count=n, - ) - intrinsic_state = fork.transaction_intrinsic_state_gas( - authorization_count=n, - ) - - num_sstores = 10 - storage = Storage() - code = Bytecode() - for _ in range(num_sstores): - code += Op.SSTORE(storage.store_next(1), 1) - code += Op.STOP - contract = pre.deploy_contract(code=code) - - exec_state = _sstore_state_per_op(fork) * num_sstores - # The deployed bytecode's combined cost minus its state portion is - # the regular execution gas (includes the PUSHes for SSTORE args). - exec_regular = code.gas_cost(fork) - exec_state - + recipient = pre.deploy_contract(code=Op.STOP) delegate = pre.deploy_contract(code=Op.STOP) signers = [pre.fund_eoa() for _ in range(n)] authorization_list = [ - AuthorizationTuple(address=delegate, nonce=0, signer=signer) + AuthorizationTuple( + address=delegate, + nonce=0, + signer=signer, + # Existing leaf gaining a net-new delegation indicator; the + # transaction's first write to the leaf. + creates_account=False, + writes_delegation=True, + ) for signer in signers ] - gas_before_regular_refund = ( - total_intrinsic + exec_regular + exec_state - state_refund + # Existing leaf + net-new delegation: the first-write ACCOUNT_WRITE + # and AUTH_BASE at the top frame. NEW_ACCOUNT is neither charged + # nor refunded, so the receipt gas is the exact charge. + intrinsic_regular = fork.transaction_intrinsic_cost_calculator()( + authorization_list_or_count=n, + return_cost_deducted_prior_execution=True, + ) + top_frame_regular = fork.transaction_top_frame_gas_calculator()( + authorizations=authorization_list, + ) + top_frame_state = fork.transaction_top_frame_state_gas( + authorizations=authorization_list, ) - regular_refund = min( - n * account_write, - gas_before_regular_refund // fork.max_refund_quotient(), + cumulative_gas_used = ( + intrinsic_regular + top_frame_regular + top_frame_state ) - assert regular_refund == n * account_write - cumulative_gas_used = gas_before_regular_refund - regular_refund tx = Transaction( - to=contract, - state_gas_reservoir=intrinsic_state + exec_state, + to=recipient, authorization_list=authorization_list, sender=pre.fund_eoa(), expected_receipt=TransactionReceipt( @@ -152,17 +117,16 @@ def test_existing_authority_regular_refund_visible( ), ) - post: dict = {contract: Account(storage=storage)} - for signer in signers: - post[signer] = Account( - code=Spec7702.delegation_designation(delegate), - ) + post = { + signer: Account(code=Spec7702.delegation_designation(delegate)) + for signer in signers + } state_test(env=env, pre=pre, post=post, tx=tx) @EIPChecklist.GasRefundsChanges.Test.RefundCalculation() @pytest.mark.parametrize("n", [1, 3]) -def test_clearing_delegation_regular_refund_capped( +def test_clearing_delegation_no_state_charge( state_test: StateTestFiller, env: Environment, pre: Alloc, @@ -170,36 +134,20 @@ def test_clearing_delegation_regular_refund_capped( n: int, ) -> None: """ - Clearing a delegation refunds the full per-auth state intrinsic on - the state channel, which drives the regular refund into the - one-fifth cap. - - Each authority already holds a delegation and re-authorizes to the - reset (zero) address, clearing its code. The leaf exists, so - ``ACCOUNT_WRITE`` is refunded on the regular channel; the code slot - held a delegation indicator and the new indicator is empty, so both - ``NEW_ACCOUNT`` and ``AUTH_BASE`` are refilled on the state channel. - Refunding the full per-auth state intrinsic collapses - ``gas_before_regular_refund`` to the regular intrinsic alone, so the - cap ``gas // 5`` is below ``n * ACCOUNT_WRITE`` and the regular - refund clamps to ``gas // 5`` (cap-saturated). No execution padding - is used, so the contrast with the full-refund test is purely the - refunded state magnitude. + Clearing an existing delegation is charged the intrinsic + per-authorization base plus the first-write ``ACCOUNT_WRITE``, with + no top-frame state charge and no refund. + + Each authority already exists and already holds a delegation + indicator (delegated before the transaction), and the authorization + resets to the null address, so ``set_delegation`` writes no net-new + indicator: ``NEW_ACCOUNT`` and ``AUTH_BASE`` fall away. The clear + still writes the authority's leaf (code emptied, nonce bumped), so + the transaction's first-write ``ACCOUNT_WRITE`` applies. Nothing is + refunded (the over-charge is gone), so the receipt gas is exactly + the regular intrinsic plus ``n * ACCOUNT_WRITE``. """ - gas_costs = fork.gas_costs() - account_write = gas_costs.ACCOUNT_WRITE - - total_intrinsic = fork.transaction_intrinsic_cost_calculator()( - authorization_list_or_count=n, - ) - intrinsic_state = fork.transaction_intrinsic_state_gas( - authorization_count=n, - ) - # Clearing an existing delegation refills the full per-auth state - # intrinsic (NEW_ACCOUNT + AUTH_BASE) for every authorization. - state_refund = intrinsic_state - - contract = pre.deploy_contract(code=Op.STOP) + recipient = pre.deploy_contract(code=Op.STOP) delegated_to = pre.deploy_contract(code=Op.STOP) # Authorities that already delegate; fund_eoa(delegation=...) sets # the authority nonce to 1, which is the expected auth nonce. @@ -209,25 +157,33 @@ def test_clearing_delegation_regular_refund_capped( address=Spec7702.RESET_DELEGATION_ADDRESS, nonce=1, signer=signer, + # Existing leaf, delegated before the tx: clearing writes no + # net-new indicator, so no top-frame state charge. The clear + # is still the transaction's first write to the leaf. + creates_account=False, + writes_delegation=False, ) for signer in signers ] - gas_before_regular_refund = total_intrinsic - state_refund - regular_refund = min( - n * account_write, - gas_before_regular_refund // fork.max_refund_quotient(), + # Clearing an existing delegation writes no net-new indicator, so + # no top-frame state charge applies and no refund fires; only the + # first-write ACCOUNT_WRITE is charged per authority. + intrinsic_regular = fork.transaction_intrinsic_cost_calculator()( + authorization_list_or_count=n, + return_cost_deducted_prior_execution=True, + ) + top_frame_regular = fork.transaction_top_frame_gas_calculator()( + authorizations=authorization_list, ) - # The cap is the binding term: the refund clamps below ACCOUNT_WRITE. - assert regular_refund < n * account_write - assert regular_refund == gas_before_regular_refund // ( - fork.max_refund_quotient() + top_frame_state = fork.transaction_top_frame_state_gas( + authorizations=authorization_list, ) - cumulative_gas_used = gas_before_regular_refund - regular_refund + assert top_frame_state == 0 + cumulative_gas_used = intrinsic_regular + top_frame_regular tx = Transaction( - to=contract, - state_gas_reservoir=intrinsic_state, + to=recipient, authorization_list=authorization_list, sender=pre.fund_eoa(), expected_receipt=TransactionReceipt( @@ -235,8 +191,5 @@ def test_clearing_delegation_regular_refund_capped( ), ) - post: dict = {} - for signer in signers: - # Delegation cleared back to empty code, nonce incremented. - post[signer] = Account(nonce=2, code=b"") + post = {signer: Account(nonce=2, code=b"") for signer in signers} state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/osaka/eip7825_transaction_gas_limit_cap/test_tx_gas_limit.py b/tests/osaka/eip7825_transaction_gas_limit_cap/test_tx_gas_limit.py index f4feb0d6da7..4fb5bed7cef 100644 --- a/tests/osaka/eip7825_transaction_gas_limit_cap/test_tx_gas_limit.py +++ b/tests/osaka/eip7825_transaction_gas_limit_cap/test_tx_gas_limit.py @@ -677,11 +677,6 @@ def capped_intrinsic_cost(auth_count: int) -> int: access_list=make_access_list(auth_count), authorization_list_or_count=auth_count, ) - if fork.is_eip_enabled(8037): - # EIP-8037 caps only the regular dimension, not state gas. - cost -= fork.transaction_intrinsic_state_gas( - authorization_count=auth_count - ) return cost auth_list_length = max_count_with_intrinsic_cost_at_most( diff --git a/tests/ported_static/stCreateTest/test_transaction_collision_to_empty_but_code.py b/tests/ported_static/stCreateTest/test_transaction_collision_to_empty_but_code.py index 41fc7ade876..ab1aedcd0b5 100644 --- a/tests/ported_static/stCreateTest/test_transaction_collision_to_empty_but_code.py +++ b/tests/ported_static/stCreateTest/test_transaction_collision_to_empty_but_code.py @@ -137,28 +137,12 @@ def test_transaction_collision_to_empty_but_code( error=_exc, ) - # On collision, all execution gas is reclassified to regular and the - # tx-time state reservoir is restored. Under EIP-8037 2D gas this - # gives header.gas_used = max(intrinsic_regular + execution_gas, - # intrinsic_state); pre-EIP-8037 the state component is zero, so the - # same expression collapses to tx.gas. - intrinsic_total = fork.transaction_intrinsic_cost_calculator()( - calldata=bytes(tx_data[d]), - contract_creation=True, - ) - intrinsic_state = fork.create_state_gas() - intrinsic_regular = intrinsic_total - intrinsic_state - execution_gas = tx_gas[g] - intrinsic_total - expected_header_gas_used = max( - intrinsic_regular + execution_gas, intrinsic_state - ) - state_test( env=env, pre=pre, post=post, tx=tx, blockchain_test_header_verify=Header( - gas_used=expected_header_gas_used, + gas_used=tx_gas[g], ), ) diff --git a/tests/ported_static/stCreateTest/test_transaction_collision_to_empty_but_nonce.py b/tests/ported_static/stCreateTest/test_transaction_collision_to_empty_but_nonce.py index 997575c4479..14a3c066470 100644 --- a/tests/ported_static/stCreateTest/test_transaction_collision_to_empty_but_nonce.py +++ b/tests/ported_static/stCreateTest/test_transaction_collision_to_empty_but_nonce.py @@ -105,28 +105,12 @@ def test_transaction_collision_to_empty_but_nonce( contract_0: Account(storage={1: 0}, nonce=1), } - # On collision, all execution gas is reclassified to regular and the - # tx-time state reservoir is restored. Under EIP-8037 2D gas this - # gives header.gas_used = max(intrinsic_regular + execution_gas, - # intrinsic_state); pre-EIP-8037 the state component is zero, so the - # same expression collapses to tx.gas. - intrinsic_total = fork.transaction_intrinsic_cost_calculator()( - calldata=bytes(tx_data[d]), - contract_creation=True, - ) - intrinsic_state = fork.create_state_gas() - intrinsic_regular = intrinsic_total - intrinsic_state - execution_gas = tx_gas[g] - intrinsic_total - expected_header_gas_used = max( - intrinsic_regular + execution_gas, intrinsic_state - ) - state_test( env=env, pre=pre, post=post, tx=tx, blockchain_test_header_verify=Header( - gas_used=expected_header_gas_used, + gas_used=tx_gas[g], ), ) diff --git a/tests/ported_static/stEIP3607/test_init_colliding_with_non_empty_account.py b/tests/ported_static/stEIP3607/test_init_colliding_with_non_empty_account.py index 7bb0f93c560..74bc79ca971 100644 --- a/tests/ported_static/stEIP3607/test_init_colliding_with_non_empty_account.py +++ b/tests/ported_static/stEIP3607/test_init_colliding_with_non_empty_account.py @@ -164,28 +164,12 @@ def test_init_colliding_with_non_empty_account( sender: Account(nonce=1), } - # On collision, all execution gas is reclassified to regular and the - # tx-time state reservoir is restored. Under EIP-8037 2D gas this - # gives header.gas_used = max(intrinsic_regular + execution_gas, - # intrinsic_state); pre-EIP-8037 the state component is zero, so the - # same expression collapses to tx.gas. - intrinsic_total = fork.transaction_intrinsic_cost_calculator()( - calldata=bytes(tx_data[d]), - contract_creation=True, - ) - intrinsic_state = fork.create_state_gas() - intrinsic_regular = intrinsic_total - intrinsic_state - execution_gas = tx_gas[g] - intrinsic_total - expected_header_gas_used = max( - intrinsic_regular + execution_gas, intrinsic_state - ) - state_test( env=env, pre=pre, post=post, tx=tx, blockchain_test_header_verify=Header( - gas_used=expected_header_gas_used, + gas_used=tx_gas[g], ), ) From ccab088314c7a3197947bf26da3b26824bd7601e Mon Sep 17 00:00:00 2001 From: Aliaksei Osipau <me@flcl.me> Date: Mon, 13 Jul 2026 17:31:14 +0300 Subject: [PATCH 117/233] chore(test-client-clis): update Nethermind exception mappings (#3151) --- .../src/execution_testing/client_clis/clis/nethermind.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/testing/src/execution_testing/client_clis/clis/nethermind.py b/packages/testing/src/execution_testing/client_clis/clis/nethermind.py index 5581777a3cf..7bfc86bc2ae 100644 --- a/packages/testing/src/execution_testing/client_clis/clis/nethermind.py +++ b/packages/testing/src/execution_testing/client_clis/clis/nethermind.py @@ -414,6 +414,9 @@ class NethermindExceptionMapper(ExceptionMapper): TransactionException.INSUFFICIENT_MAX_FEE_PER_GAS: ( r"max fee per gas less than block base fee" ), + TransactionException.INSUFFICIENT_MAX_FEE_PER_BLOB_GAS: ( + r"max fee per blob gas less than block blob gas fee" + ), TransactionException.NONCE_MISMATCH_TOO_LOW: (r"nonce too low"), TransactionException.NONCE_MISMATCH_TOO_HIGH: (r"nonce too high"), TransactionException.INVALID_CHAINID: ( @@ -442,10 +445,12 @@ class NethermindExceptionMapper(ExceptionMapper): r"calculated hash 0x[0-9a-f]+" ), BlockException.SYSTEM_CONTRACT_EMPTY: ( - r"(Withdrawals|Consolidations)Empty: Contract is not deployed\." + r"(Withdrawals|Consolidations|BuilderDeposits|BuilderExits)" + r"Empty: Contract is not deployed\." ), BlockException.SYSTEM_CONTRACT_CALL_FAILED: ( - r"(Withdrawals|Consolidations)Failed: Contract execution failed\." + r"(Withdrawals|Consolidations|BuilderDeposits|BuilderExits)" + r"Failed: Contract execution failed\." ), # BAL Exceptions — specific exceptions have unique patterns, but # INVALID_BLOCK_ACCESS_LIST and INCORRECT_BLOCK_FORMAT intentionally From b07e0c485b09e5bf695dd24a708e7986d5bf7f4e Mon Sep 17 00:00:00 2001 From: danceratopz <danceratopz@gmail.com> Date: Mon, 13 Jul 2026 23:39:40 +0200 Subject: [PATCH 118/233] feat(test-fill): pack Engine X pre-alloc groups (#3122) --- .../pytest_commands/plugins/filler/filler.py | 102 +++- .../execution_testing/fixtures/__init__.py | 2 + .../fixtures/engine_x_checks.py | 162 ++++++ .../fixtures/pre_alloc_groups.py | 283 ++++++++++ .../fixtures/tests/test_engine_x_checks.py | 213 +++++++ .../fixtures/tests/test_pre_alloc_groups.py | 528 ++++++++++++++++++ 6 files changed, 1286 insertions(+), 4 deletions(-) create mode 100644 packages/testing/src/execution_testing/fixtures/engine_x_checks.py create mode 100644 packages/testing/src/execution_testing/fixtures/tests/test_engine_x_checks.py create mode 100644 packages/testing/src/execution_testing/fixtures/tests/test_pre_alloc_groups.py diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py index 012fad66e2f..148b5dd1ea3 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py @@ -60,9 +60,17 @@ merge_partial_fixture_files, strip_fixture_format_from_node, ) +from execution_testing.fixtures.engine_x_checks import ( + ENGINE_X_FIXTURES_DIR, + verify_engine_x_execution, +) from execution_testing.fixtures.pre_alloc_groups import ( + GroupIndexEntry, _get_worker_id, merge_partial_group_files, + pack_pre_alloc_groups, + packed_group_hash_for_test, + read_test_group_index, ) from execution_testing.forks import ( Fork, @@ -159,6 +167,14 @@ class FillingSession: filling_phase: FixtureFillingPhase pre_alloc_groups: PreAllocGroups | None = None pre_alloc_group_builders: PreAllocGroupBuilders | None = None + # Phase 2 reverse index: test id -> packed pre-alloc group. Packing + # (see pack_pre_alloc_groups) makes a group's hash depend on the whole set + # of tests it holds, so it can no longer be recomputed per-test; a test + # finds its group through the packed index file instead (see + # read_test_group_index). + _test_group_index: Dict[str, GroupIndexEntry] | None = field( + default=None, repr=False + ) @classmethod def from_config( @@ -288,6 +304,24 @@ def get_pre_alloc_group(self, hash_key: str) -> PreAllocGroup: return self.pre_alloc_groups[hash_key] + def group_hash_for_test(self, test_id: str, phase1_hash: str) -> str: + """ + Return the packed pre-alloc group hash that owns ``test_id``. + + Loaded once (per worker) from the index file written by + `pack_pre_alloc_groups` at the end of phase 1. ``phase1_hash`` is + the test's fine-grained group hash recomputed from its current + content, so a stale pre-alloc folder fails loudly (see + `packed_group_hash_for_test`). + """ + if self._test_group_index is None: + self._test_group_index = read_test_group_index( + self.fixture_output.pre_alloc_groups_folder_path + ) + return packed_group_hash_for_test( + self._test_group_index, test_id, phase1_hash + ) + def save_pre_alloc_groups(self) -> None: """Save pre-allocation groups to disk as partial files.""" if self.pre_alloc_group_builders is None: @@ -966,6 +1000,16 @@ def pytest_terminal_summary( yellow=True, ) + engine_x_warning = getattr(config, "engine_x_check_warning", None) + if engine_x_warning is not None: + terminalreporter.write_sep( + "=", + " WARNING: Engine X execution consistency check skipped ", + bold=True, + yellow=True, + ) + terminalreporter.write_line(engine_x_warning, yellow=True) + def _aggregate_cache_stats(node: Any) -> None: """Aggregate t8n cache stats from an xdist worker.""" @@ -1644,6 +1688,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: chain_id=ChainConfigDefaults.chain_id, environment=genesis_environment, pre=pre, + group_salt=group_salt, ) return # Skip fixture generation in phase 1 @@ -1653,10 +1698,20 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: FixtureFillingPhase.PRE_ALLOC_GENERATION in fixture_format.format_phases ): - pre_alloc_hash = pre.compute_pre_alloc_group_hash( - fork=fork, - genesis_environment=self.get_genesis_environment(), - group_salt=group_salt, + # Groups are packed after phase 1, so a test's group hash + # can no longer be recomputed from its own pre; look it up + # by test id instead, fingerprinted by the recomputed + # phase 1 hash so a stale group folder fails loudly. + test_id = _strip_xdist_group_suffix(request.node.nodeid) + pre_alloc_hash = session.group_hash_for_test( + test_id, + phase1_hash=pre.compute_pre_alloc_group_hash( + fork=fork, + genesis_environment=( + self.get_genesis_environment() + ), + group_salt=group_salt, + ), ) group = session.get_pre_alloc_group(pre_alloc_hash) self.pre = group.pre @@ -2104,6 +2159,13 @@ def _log_timing(msg: str) -> None: _log_timing( f"Phase 1 (master): merge done in {time.time() - t0:.1f}s" ) + # Pack the fine-grained groups into fewer, larger ones so Engine X + # boots one client for many tests instead of one per test. + t0 = time.time() + pack_pre_alloc_groups(pre_alloc_folder) + _log_timing( + f"Phase 1 (master): pack done in {time.time() - t0:.1f}s" + ) else: # Workers: clear in-memory state to reduce memory pressure while # waiting for other workers to finish @@ -2165,6 +2227,38 @@ def _log_timing(msg: str) -> None: file.unlink() _log_timing(f"Lock files removed in {time.time() - t0:.1f}s") + # Loudly fail the fill if pre-alloc group packing changed any Engine X + # test's execution (raises on drift, like a pre-alloc collision). + _log_timing("verify_engine_x_execution: starting...") + t0 = time.time() + engine_x_check = verify_engine_x_execution(fixture_output.directory) + engine_x_warning: str | None = None + if engine_x_check is not None: + if engine_x_check.compared > 0: + logger.info(engine_x_check.summary) + elif engine_x_check.skipped > 0: + engine_x_warning = ( + "Engine X execution consistency check skipped: none of " + f"the {engine_x_check.skipped} Engine X fixtures have a " + "blockchain_tests_engine sibling fixture to compare " + "against. Leaks from pre-alloc group packing are not " + "verified for this output." + ) + elif (fixture_output.directory / ENGINE_X_FIXTURES_DIR).is_dir(): + engine_x_warning = ( + "Engine X execution consistency check skipped: this fill " + "generated no blockchain_tests_engine fixtures to compare " + "against (e.g. filling with `-m blockchain_test_engine_x`). " + "Leaks from pre-alloc group packing are not verified for this " + "output." + ) + if engine_x_warning is not None: + logger.warning(engine_x_warning) + # Repeated in the terminal summary; a log line alone is easy to + # miss. + session.config.engine_x_check_warning = engine_x_warning # type: ignore[attr-defined] # noqa: E501 + _log_timing(f"verify_engine_x_execution: done in {time.time() - t0:.1f}s") + # Verify fixtures after merge if verification is enabled if session.config.getoption("verify_fixtures"): _log_timing("_verify_fixtures_post_merge: starting...") diff --git a/packages/testing/src/execution_testing/fixtures/__init__.py b/packages/testing/src/execution_testing/fixtures/__init__.py index f74946a8c72..5c20c291b03 100644 --- a/packages/testing/src/execution_testing/fixtures/__init__.py +++ b/packages/testing/src/execution_testing/fixtures/__init__.py @@ -28,6 +28,7 @@ PreAllocGroupBuilder, PreAllocGroupBuilders, PreAllocGroups, + pack_pre_alloc_groups, ) from .state import StateFixture from .transaction import TransactionFixture @@ -57,4 +58,5 @@ "TestInfo", "TransactionFixture", "merge_partial_fixture_files", + "pack_pre_alloc_groups", ] diff --git a/packages/testing/src/execution_testing/fixtures/engine_x_checks.py b/packages/testing/src/execution_testing/fixtures/engine_x_checks.py new file mode 100644 index 00000000000..f3f43d78cde --- /dev/null +++ b/packages/testing/src/execution_testing/fixtures/engine_x_checks.py @@ -0,0 +1,162 @@ +"""Fill-time execution-consistency check for Engine X fixtures.""" + +import json +from pathlib import Path +from typing import Any, Dict, List, NamedTuple, Optional, Tuple + +ENGINE_X_FIXTURES_DIR = "blockchain_tests_engine_x" +SIBLING_FIXTURES_DIR = "blockchain_tests_engine" + +# Every state-root-derived field of an execution payload. These are the only +# fields a packed (merged) genesis is allowed to change; everything else in a +# payload is a pure function of the test's execution. +_STATE_ROOT_DERIVED_FIELDS = ("stateRoot", "blockHash", "parentHash") + + +class EngineXExecutionDriftError(Exception): + """ + A packed pre-allocation group changed a test's execution. + + An Engine X fixture is filled against its group's merged genesis, while + the test's `blockchain_test_engine` sibling is filled against the test's + own pre-allocation. Their per-payload execution outputs (gas used, + receipts root, logs bloom, ...) must be identical; a difference means an + account introduced by pre-alloc group packing leaked into the test's + execution (see `pack_pre_alloc_groups`). + """ + + def __init__(self, mismatches: List[Tuple[str, str]], compared: int): + """Initialize with the mismatched test ids and the compared count.""" + self.mismatches = mismatches + self.compared = compared + details = "\n".join( + f" {test_id}: {what}" for test_id, what in mismatches[:10] + ) + if len(mismatches) > 10: + details += f"\n ... and {len(mismatches) - 10} more" + super().__init__( + f"{len(mismatches)} of {compared} Engine X fixtures execute " + "differently against their packed pre-allocation group's genesis " + "than against their own pre-allocation:\n" + f"{details}\n" + "An account introduced by pre-alloc group packing leaked into " + "these tests' execution. Isolate the affected tests with " + "@pytest.mark.pre_alloc_group and re-fill." + ) + + +class EngineXCheckResult(NamedTuple): + """Comparison counts from a completed Engine X execution check.""" + + compared: int + skipped: int + + @property + def summary(self) -> str: + """Return a one-line summary of the check for the fill log.""" + summary = ( + f"{self.compared} Engine X fixtures execute identically " + "against their packed group's genesis" + ) + if self.skipped: + summary += f" ({self.skipped} skipped: no sibling engine fixture)" + return summary + + +def _scrubbed_payloads(fixture: Dict[str, Any]) -> List[Any]: + """Return the fixture's payload entries minus state-root-derived fields.""" + payloads = [] + for entry in fixture.get("engineNewPayloads", []): + entry = json.loads(json.dumps(entry)) + params = entry.get("params") + if params and isinstance(params[0], dict): + for field in _STATE_ROOT_DERIVED_FIELDS: + params[0].pop(field, None) + payloads.append(entry) + return payloads + + +def _describe_mismatch(base: List[Any], packed: List[Any]) -> str: + """Return a short description of the first difference between payloads.""" + if len(base) != len(packed): + return f"payload count: {len(base)} != {len(packed)}" + for i, (base_entry, packed_entry) in enumerate( + zip(base, packed, strict=False) + ): + if base_entry == packed_entry: + continue + base_payload = base_entry.get("params", [{}])[0] + packed_payload = packed_entry.get("params", [{}])[0] + if isinstance(base_payload, dict) and isinstance(packed_payload, dict): + fields = sorted( + field + for field in set(base_payload) | set(packed_payload) + if base_payload.get(field) != packed_payload.get(field) + ) + if fields: + return f"payload {i} differs in: {', '.join(fields)}" + return f"payload {i} differs" + return "payloads differ" + + +def verify_engine_x_execution( + output_dir: Path, +) -> Optional[EngineXCheckResult]: + """ + Verify that pre-alloc group packing did not change any test's execution. + + For every Engine X fixture (filled against its packed group's merged + genesis), compare its `engineNewPayloads` against the test's + `blockchain_test_engine` sibling fixture (filled against the test's own + pre-allocation in the same session, with an independent `t8n` execution: + Engine X fixtures never share the transition tool output cache). All + payload fields except the state-root-derived ones must match exactly. + + Return the comparison counts, or ``None`` when one of the two fixture + format trees was not generated at all (e.g. when filling with + ``-m blockchain_test_engine_x``, which produces no siblings). + + Raise `EngineXExecutionDriftError` if any test executed differently. + """ + engine_x_dir = output_dir / ENGINE_X_FIXTURES_DIR + sibling_dir = output_dir / SIBLING_FIXTURES_DIR + if not engine_x_dir.is_dir() or not sibling_dir.is_dir(): + return None + + compared = 0 + skipped = 0 + mismatches: List[Tuple[str, str]] = [] + for engine_x_file in engine_x_dir.rglob("*.json"): + if "pre_alloc" in engine_x_file.parts: + continue + sibling_file = sibling_dir / engine_x_file.relative_to(engine_x_dir) + if not sibling_file.exists(): + # A --single-fixture-per-file fill embeds the fixture format + # name in every file name, so the sibling's basename differs. + sibling_file = sibling_file.with_name( + sibling_file.name.replace( + "blockchain_test_engine_x", "blockchain_test_engine" + ) + ) + sibling_fixtures = ( + json.loads(sibling_file.read_text()) + if sibling_file.exists() + else {} + ) + for test_id, fixture in json.loads(engine_x_file.read_text()).items(): + sibling_id = test_id.replace( + "blockchain_test_engine_x", "blockchain_test_engine" + ) + sibling = sibling_fixtures.get(sibling_id) + if sibling is None: + skipped += 1 + continue + compared += 1 + base = _scrubbed_payloads(sibling) + packed = _scrubbed_payloads(fixture) + if base != packed: + mismatches.append((test_id, _describe_mismatch(base, packed))) + + if mismatches: + raise EngineXExecutionDriftError(mismatches, compared) + return EngineXCheckResult(compared=compared, skipped=skipped) diff --git a/packages/testing/src/execution_testing/fixtures/pre_alloc_groups.py b/packages/testing/src/execution_testing/fixtures/pre_alloc_groups.py index a34a72781ac..1c854e45d6d 100644 --- a/packages/testing/src/execution_testing/fixtures/pre_alloc_groups.py +++ b/packages/testing/src/execution_testing/fixtures/pre_alloc_groups.py @@ -1,7 +1,9 @@ """Pre-allocation group models for test fixture generation.""" +import hashlib import json import os +from collections import defaultdict from dataclasses import dataclass from pathlib import Path from typing import ( @@ -12,8 +14,10 @@ KeysView, List, Literal, + NamedTuple, Optional, Self, + Set, Tuple, ) @@ -40,6 +44,13 @@ class PreAllocGroupBuilder(CamelModel): ) fork: Fork | TransitionFork = Field(..., alias="network") chain_id: int = DEFAULT_CHAIN_ID + group_salt: str | None = Field( + None, + description=( + "Explicit isolation salt from the `pre_alloc_group` marker; " + "groups only pack with groups carrying the same salt." + ), + ) pre: Alloc def get_pre_account_count(self) -> int: @@ -74,6 +85,7 @@ def build(self) -> "PreAllocGroup": environment=self.environment, fork=self.fork, chain_id=self.chain_id, + group_salt=self.group_salt, pre=self.pre.model_dump(), pre_account_count=self.get_pre_account_count(), test_count=self.get_test_count(), @@ -180,6 +192,272 @@ def merge_partial_group_files(folder: Path) -> None: ) +def _environment_group_key(environment: Environment) -> str: + """ + Return a stable string identifying a genesis environment. + + Two groups can only share a client if they share a genesis block, so the + environment is part of every packing bucket. The canonical JSON dump + matches the equality semantics of `Environment` (which compares the + alias-keyed, none-excluded dump). + """ + return json.dumps( + environment.model_dump(mode="json", by_alias=True, exclude_none=True), + sort_keys=True, + ) + + +def _packed_group_hash(test_ids: List[str]) -> str: + """Return a deterministic ``0x``-prefixed id for a packed group.""" + digest = hashlib.sha256("\n".join(test_ids).encode("utf-8")).digest() + return f"0x{int.from_bytes(digest[:8], byteorder='big'):016x}" + + +# The test id -> group hash index written next to the group files by +# `pack_pre_alloc_groups`. Deliberately not a `*.json` name: every consumer +# of the folder (including this module) discovers group files by that glob. +TEST_GROUP_INDEX_FILE = "test_group_index" + + +class GroupIndexEntry(NamedTuple): + """ + A test's entry in the test id -> pre-alloc group index. + + ``group_hash`` names the (packed) group that holds the test. + ``phase1_hash`` is the test's fine-grained phase 1 group hash, which + phase 2 recomputes from the test's own fork, genesis environment, and + pre-allocation to detect a stale group folder (see + `packed_group_hash_for_test`); it is ``None`` when the index was + reconstructed by scanning group files. + """ + + group_hash: str + phase1_hash: str | None + + +def read_test_group_index(folder: Path) -> Dict[str, GroupIndexEntry]: + """ + Map every test id to the pre-alloc group that contains it. + + Prefer the index file written by `pack_pre_alloc_groups`; fall back to + scanning every group file's ``testIds`` for folders produced without a + packing pass (e.g. by an older framework version). Scanned entries + carry no phase 1 fingerprint. + """ + index_file = folder / TEST_GROUP_INDEX_FILE + if index_file.exists(): + return { + test_id: GroupIndexEntry(entry["group"], entry["phase1"]) + for test_id, entry in json.loads(index_file.read_text()).items() + } + index: Dict[str, GroupIndexEntry] = {} + for file in folder.glob("*.json"): + data = json.loads(file.read_text()) + for test_id in data.get("testIds", []): + index[test_id] = GroupIndexEntry(file.stem, None) + return index + + +def packed_group_hash_for_test( + index: Dict[str, GroupIndexEntry], + test_id: str, + phase1_hash: str, +) -> str: + """ + Return the packed group hash owning ``test_id``, verifying freshness. + + ``phase1_hash`` is the test's fine-grained phase 1 group hash, + recomputed by phase 2 from the test's current fork, genesis + environment, and pre-allocation. A mismatch with the fingerprint + recorded by `pack_pre_alloc_groups` means the groups on disk were + built from a different version of the test, so phase 2 would fill it + against the wrong genesis. + """ + entry = index.get(test_id) + if entry is None: + raise ValueError( + f"Test {test_id!r} was not assigned to any pre-allocation " + "group. Ensure phase 1 (--generate-pre-alloc-groups) ran over " + "the same test selection as phase 2." + ) + if entry.phase1_hash is not None and entry.phase1_hash != phase1_hash: + raise ValueError( + f"The pre-allocation groups are stale for test {test_id!r}: " + "its pre-allocation or genesis environment changed after they " + "were generated. Re-run phase 1 (--generate-pre-alloc-groups) " + "to regenerate them." + ) + return entry.group_hash + + +# Blanket-reserved low address range. A ported state test can blindly call +# a low address without ever declaring it in its pre, so an account +# introduced there by another test in the group silently changes its +# execution. Precompiles at or above this range (EIP-7951 puts P256VERIFY +# at 0x100) are reserved via the bucket fork's precompile list instead. +_RESERVED_ADDRESS_CEILING = 0x100 + + +def _reserved_addresses(builders: List["PreAllocGroupBuilder"]) -> Set[str]: + """ + Return the addresses that are unsafe to introduce via a merge. + + A shared genesis leaks every account it holds to every test in the group. + A ported state test only declares the accounts it sets and assumes all + other addresses are empty, so introducing an account at an address it + quietly depends on (a precompile, a canonical scratch contract, ...) + changes its result. Three kinds of address are therefore reserved: the + blanket low range, the fork's precompile addresses (which extend beyond + that range from EIP-7951's P256VERIFY at ``0x100`` on), and any address + more than one group allocates (i.e. a shared/canonical address rather + than one private to a single test). + """ + # Packing buckets by fork, so every builder shares this one; a + # transition fork reserves the post-transition precompiles, matching + # the genesis built by `PreAllocGroupBuilders.add_test_pre`. + fork = builders[0].fork.transitions_to() + reserved = {str(address) for address in fork.precompiles()} + frequency: Dict[str, int] = defaultdict(int) + for builder in builders: + for address in builder.pre.root: + frequency[str(address)] += 1 + return reserved | { + address + for address, count in frequency.items() + if count > 1 or int(address, 16) < _RESERVED_ADDRESS_CEILING + } + + +def _reserved_signature( + builder: "PreAllocGroupBuilder", reserved: Set[str] +) -> Tuple[Tuple[str, str], ...]: + """ + Return a group's reserved-address footprint as a hashable signature. + + Groups may only merge when this matches exactly, so every test in a packed + group sees identical reserved accounts (and identically absent ones). + """ + return tuple( + sorted( + ( + str(address), + "null" + if account is None + else json.dumps( + account.model_dump(mode="json"), sort_keys=True + ), + ) + for address, account in builder.pre.root.items() + if str(address) in reserved + ) + ) + + +def pack_pre_alloc_groups(folder: Path) -> None: + """ + Merge fine-grained pre-allocation groups into fewer, larger ones. + + Phase 1 keys every test's group on the exact content of any hard-coded + accounts it sets (`modified_accounts_salt`), so a test that pins accounts + to fixed addresses lands in its own group even when it could safely share a + genesis with others. This is conservative: it splits far more than the + genuine address conflicts require. `groupstats` shows this dominates the + group count, with most groups a single test. + + This pass reclaims that while preserving each test's isolation. Groups + are bucketed by everything a shared genesis requires (fork, chain id, and + environment), by the explicit `pre_alloc_group` marker salt (so a test + that demands its own genesis keeps it), and then by their + reserved-address footprint (see `_reserved_addresses`), so two tests only + share a genesis when they agree on every precompile and shared address. + Within a bucket the reserved accounts are identical and the remaining + (test-private) addresses are unique to one group, so the union is always + conflict-free and the whole bucket collapses to a single group. + + The packing is deterministic: buckets are processed in sorted order and + each group's id is derived from its sorted test ids, so a re-fill of the + same tests reproduces the same groups. + + Called on the master process after `merge_partial_group_files`, replacing + the fine-grained files in `folder` with the packed ones. Also writes a + test id -> group index file (see `read_test_group_index`), so phase 2 + workers can find a test's group without scanning every group file; each + entry records the test's fine-grained phase 1 hash as a fingerprint so a + stale folder is detected (see `packed_group_hash_for_test`). + """ + files = sorted(folder.glob("*.json")) + if not files: + return + + builders = [] + phase1_hash_by_test: Dict[str, str] = {} + for file in files: + builder = PreAllocGroupBuilder.model_validate_json(file.read_text()) + for test_id in builder.test_ids: + phase1_hash_by_test[test_id] = file.stem + builders.append(builder) + + genesis_buckets: Dict[ + Tuple[str, int, str, str], List[PreAllocGroupBuilder] + ] = defaultdict(list) + for builder in builders: + genesis_buckets[ + ( + builder.fork.name(), + builder.chain_id, + builder.group_salt or "", + _environment_group_key(builder.environment), + ) + ].append(builder) + + # Drop the fine-grained files up front; the packed files written below are + # named by content hash and never clash with the (now stale) originals. + for file in files: + file.unlink() + + test_group_index: Dict[str, GroupIndexEntry] = {} + for genesis_key in sorted(genesis_buckets): + bucket = genesis_buckets[genesis_key] + reserved = _reserved_addresses(bucket) + + packed: Dict[Tuple[Tuple[str, str], ...], PreAllocGroupBuilder] = {} + for builder in bucket: + signature = _reserved_signature(builder, reserved) + if signature in packed: + merged = packed[signature] + merged.pre.root.update(builder.pre.root) + merged.test_ids.extend(builder.test_ids) + else: + packed[signature] = builder + + for merged in packed.values(): + merged.test_ids.sort() + packed_hash = _packed_group_hash(merged.test_ids) + (folder / f"{packed_hash}.json").write_text( + merged.model_dump_json( + by_alias=True, exclude_none=True, indent=2 + ) + ) + for test_id in merged.test_ids: + test_group_index[test_id] = GroupIndexEntry( + packed_hash, phase1_hash_by_test[test_id] + ) + + (folder / TEST_GROUP_INDEX_FILE).write_text( + json.dumps( + { + test_id: { + "group": entry.group_hash, + "phase1": entry.phase1_hash, + } + for test_id, entry in test_group_index.items() + }, + sort_keys=True, + indent=2, + ) + ) + + class PreAllocGroupBuilders(EthereumTestRootModel): """ Root model mapping pre-allocation group hashes to test groups. @@ -212,6 +490,7 @@ def add_test_pre( chain_id: int, environment: Environment, pre: Alloc, + group_salt: str | None = None, ) -> None: """Adds a single test to the appropriate group based on the hash.""" if pre_alloc_hash in self.root: @@ -223,6 +502,9 @@ def add_test_pre( assert group.chain_id == chain_id, ( f"Incompatible chain id: {group.chain_id}!={chain_id}" ) + assert group.group_salt == group_salt, ( + f"Incompatible group salt: {group.group_salt}!={group_salt}" + ) group.add_test_alloc(test_id, pre) else: # Create new group - use Environment instead of expensive genesis @@ -232,6 +514,7 @@ def add_test_pre( fork=fork, chain_id=chain_id, environment=environment, + group_salt=group_salt, pre=Alloc.merge( Alloc.model_validate( fork.transitions_to().pre_allocation_blockchain() diff --git a/packages/testing/src/execution_testing/fixtures/tests/test_engine_x_checks.py b/packages/testing/src/execution_testing/fixtures/tests/test_engine_x_checks.py new file mode 100644 index 00000000000..7e44afee03c --- /dev/null +++ b/packages/testing/src/execution_testing/fixtures/tests/test_engine_x_checks.py @@ -0,0 +1,213 @@ +"""Tests for the Engine X execution-consistency check.""" + +import json +from pathlib import Path +from typing import Any, Dict, List + +import pytest + +from execution_testing.fixtures.engine_x_checks import ( + ENGINE_X_FIXTURES_DIR, + SIBLING_FIXTURES_DIR, + EngineXExecutionDriftError, + verify_engine_x_execution, +) + +ENGINE_X_ID = ( + "tests/a.py::test_a[fork_Prague-blockchain_test_engine_x_from_state_test]" +) +SIBLING_ID = ( + "tests/a.py::test_a[fork_Prague-blockchain_test_engine_from_state_test]" +) + + +def _payload( + *, gas_used: str, state_root: str, block_hash: str +) -> Dict[str, Any]: + """Build a single newPayload entry.""" + return { + "newPayloadVersion": "4", + "forkchoiceUpdatedVersion": "3", + "params": [ + { + "parentHash": f"0x{'00' * 31}aa", + "stateRoot": state_root, + "blockHash": block_hash, + "gasUsed": gas_used, + "receiptsRoot": f"0x{'11' * 32}", + "logsBloom": f"0x{'00' * 256}", + "transactions": ["0xf86b..."], + }, + [], + f"0x{'00' * 32}", + ], + } + + +def _write_fixture( + folder: Path, + fixture_dir: str, + test_id: str, + payloads: List[Dict[str, Any]], +) -> None: + """Write a single-fixture file into a format tree.""" + file = folder / fixture_dir / "prague" / "module" / "test_a.json" + file.parent.mkdir(parents=True, exist_ok=True) + file.write_text(json.dumps({test_id: {"engineNewPayloads": payloads}})) + + +def test_identical_execution_passes(tmp_path: Path) -> None: + """State-root-derived differences alone do not trip the check.""" + _write_fixture( + tmp_path, + SIBLING_FIXTURES_DIR, + SIBLING_ID, + [_payload(gas_used="0x5208", state_root="0x01", block_hash="0x02")], + ) + _write_fixture( + tmp_path, + ENGINE_X_FIXTURES_DIR, + ENGINE_X_ID, + [_payload(gas_used="0x5208", state_root="0xaa", block_hash="0xbb")], + ) + + result = verify_engine_x_execution(tmp_path) + + assert result is not None + assert result.compared == 1 + assert "1 Engine X fixtures execute identically" in result.summary + + +def test_execution_drift_raises(tmp_path: Path) -> None: + """A gas difference (a leaked account changed execution) fails loudly.""" + _write_fixture( + tmp_path, + SIBLING_FIXTURES_DIR, + SIBLING_ID, + [_payload(gas_used="0x5208", state_root="0x01", block_hash="0x02")], + ) + _write_fixture( + tmp_path, + ENGINE_X_FIXTURES_DIR, + ENGINE_X_ID, + [_payload(gas_used="0xbeef", state_root="0xaa", block_hash="0xbb")], + ) + + with pytest.raises(EngineXExecutionDriftError) as exc_info: + verify_engine_x_execution(tmp_path) + + message = str(exc_info.value) + assert ENGINE_X_ID in message + assert "gasUsed" in message + + +def test_payload_count_drift_raises(tmp_path: Path) -> None: + """A different number of payloads fails loudly.""" + payload = _payload(gas_used="0x5208", state_root="0x01", block_hash="0x02") + _write_fixture(tmp_path, SIBLING_FIXTURES_DIR, SIBLING_ID, [payload]) + _write_fixture( + tmp_path, ENGINE_X_FIXTURES_DIR, ENGINE_X_ID, [payload, payload] + ) + + with pytest.raises(EngineXExecutionDriftError) as exc_info: + verify_engine_x_execution(tmp_path) + + assert "payload count" in str(exc_info.value) + + +def test_no_sibling_fixtures_skips_check(tmp_path: Path) -> None: + """An Engine X only fill (no sibling format tree) skips the check.""" + _write_fixture( + tmp_path, + ENGINE_X_FIXTURES_DIR, + ENGINE_X_ID, + [_payload(gas_used="0x5208", state_root="0x01", block_hash="0x02")], + ) + + assert verify_engine_x_execution(tmp_path) is None + + +def test_no_engine_x_fixtures_skips_check(tmp_path: Path) -> None: + """A fill without Engine X fixtures skips the check.""" + _write_fixture( + tmp_path, + SIBLING_FIXTURES_DIR, + SIBLING_ID, + [_payload(gas_used="0x5208", state_root="0x01", block_hash="0x02")], + ) + + assert verify_engine_x_execution(tmp_path) is None + + +def test_single_fixture_per_file_sibling_lookup(tmp_path: Path) -> None: + """ + A `--single-fixture-per-file` fill embeds the fixture format name in + every file name; the sibling is still found under its own basename. + """ + payload = _payload(gas_used="0x5208", state_root="0x01", block_hash="0x02") + sibling_file = ( + tmp_path + / SIBLING_FIXTURES_DIR + / "prague" + / "module" + / "a__fork_Prague_blockchain_test_engine_from_state_test.json" + ) + sibling_file.parent.mkdir(parents=True, exist_ok=True) + sibling_file.write_text( + json.dumps({SIBLING_ID: {"engineNewPayloads": [payload]}}) + ) + engine_x_file = ( + tmp_path + / ENGINE_X_FIXTURES_DIR + / "prague" + / "module" + / "a__fork_Prague_blockchain_test_engine_x_from_state_test.json" + ) + engine_x_file.parent.mkdir(parents=True, exist_ok=True) + engine_x_file.write_text( + json.dumps({ENGINE_X_ID: {"engineNewPayloads": [payload]}}) + ) + + result = verify_engine_x_execution(tmp_path) + + assert result is not None + assert result.compared == 1 + assert result.skipped == 0 + + +def test_missing_sibling_fixture_is_skipped(tmp_path: Path) -> None: + """A test filtered from the sibling format is skipped, not failed.""" + payload = _payload(gas_used="0x5208", state_root="0x01", block_hash="0x02") + _write_fixture(tmp_path, SIBLING_FIXTURES_DIR, SIBLING_ID, [payload]) + other_engine_x_id = ENGINE_X_ID.replace("test_a[", "test_b[") + _write_fixture(tmp_path, ENGINE_X_FIXTURES_DIR, ENGINE_X_ID, [payload]) + file = ( + tmp_path / ENGINE_X_FIXTURES_DIR / "prague" / "module" / "test_b.json" + ) + file.write_text( + json.dumps({other_engine_x_id: {"engineNewPayloads": [payload]}}) + ) + + result = verify_engine_x_execution(tmp_path) + + assert result is not None + assert result.compared == 1 + assert result.skipped == 1 + assert "1 skipped" in result.summary + + +def test_no_matching_siblings_reports_skip_count(tmp_path: Path) -> None: + """ + Sibling fixtures exist but none match: The check reports the skip + count instead of pretending no siblings were generated. + """ + payload = _payload(gas_used="0x5208", state_root="0x01", block_hash="0x02") + other_sibling_id = SIBLING_ID.replace("test_a[", "test_b[") + _write_fixture(tmp_path, SIBLING_FIXTURES_DIR, other_sibling_id, [payload]) + _write_fixture(tmp_path, ENGINE_X_FIXTURES_DIR, ENGINE_X_ID, [payload]) + + result = verify_engine_x_execution(tmp_path) + + assert result is not None + assert result.compared == 0 + assert result.skipped == 1 diff --git a/packages/testing/src/execution_testing/fixtures/tests/test_pre_alloc_groups.py b/packages/testing/src/execution_testing/fixtures/tests/test_pre_alloc_groups.py new file mode 100644 index 00000000000..f2bb4a4c797 --- /dev/null +++ b/packages/testing/src/execution_testing/fixtures/tests/test_pre_alloc_groups.py @@ -0,0 +1,528 @@ +"""Tests for conflict-aware packing of pre-allocation groups.""" + +import json +from pathlib import Path +from typing import Dict + +import pytest + +from execution_testing.base_types import Account, Address +from execution_testing.fixtures.pre_alloc_groups import ( + TEST_GROUP_INDEX_FILE, + GroupIndexEntry, + PreAllocGroupBuilder, + pack_pre_alloc_groups, + packed_group_hash_for_test, + read_test_group_index, +) +from execution_testing.forks import Fork, Osaka, Prague +from execution_testing.test_types import Alloc, Environment + + +def _write_group( + folder: Path, + stem: str, + test_id: str, + pre: Dict[int, Account], + *, + environment: Environment, + group_salt: str | None = None, + fork: Fork = Prague, +) -> None: + """Write a single fine-grained group file, as Phase 1 would.""" + builder = PreAllocGroupBuilder( + test_ids=[test_id], + environment=environment, + fork=fork, + group_salt=group_salt, + pre=Alloc( + {Address(address): account for address, account in pre.items()} + ), + ) + (folder / f"{stem}.json").write_text( + builder.model_dump_json(by_alias=True, exclude_none=True, indent=2) + ) + + +def _packed(folder: Path) -> Dict[str, dict]: + """Load the packed group files by stem.""" + return { + file.stem: json.loads(file.read_text()) + for file in folder.glob("*.json") + } + + +def test_pack_merges_non_conflicting_groups(tmp_path: Path) -> None: + """Two groups sharing a genesis but no address merge into one.""" + env = Environment() + _write_group( + tmp_path, + "0x01", + "tests/a.py::test_a", + {0x1000: Account(balance=1)}, + environment=env, + ) + _write_group( + tmp_path, + "0x02", + "tests/b.py::test_b", + {0x2000: Account(balance=2)}, + environment=env, + ) + + pack_pre_alloc_groups(tmp_path) + + packed = _packed(tmp_path) + assert len(packed) == 1 + (group,) = packed.values() + assert sorted(group["testIds"]) == [ + "tests/a.py::test_a", + "tests/b.py::test_b", + ] + assert set(group["pre"]) == { + "0x0000000000000000000000000000000000001000", + "0x0000000000000000000000000000000000002000", + } + + +def test_pack_keeps_conflicting_groups_apart(tmp_path: Path) -> None: + """The same address with different accounts cannot be merged.""" + env = Environment() + _write_group( + tmp_path, + "0x01", + "tests/a.py::test_a", + {0x1000: Account(balance=1)}, + environment=env, + ) + _write_group( + tmp_path, + "0x02", + "tests/b.py::test_b", + {0x1000: Account(balance=2)}, + environment=env, + ) + + pack_pre_alloc_groups(tmp_path) + + packed = _packed(tmp_path) + assert len(packed) == 2 + # Every test is still represented exactly once. + all_ids = sorted( + tid for group in packed.values() for tid in group["testIds"] + ) + assert all_ids == ["tests/a.py::test_a", "tests/b.py::test_b"] + + +def test_pack_merges_identical_account_at_shared_address( + tmp_path: Path, +) -> None: + """ + A shared address with the *same* account (e.g. a system contract) is + not a conflict. + """ + env = Environment() + shared = Account(balance=1, nonce=1) + _write_group( + tmp_path, + "0x01", + "tests/a.py::test_a", + {0x1000: shared, 0x2000: Account(balance=5)}, + environment=env, + ) + _write_group( + tmp_path, + "0x02", + "tests/b.py::test_b", + {0x1000: shared, 0x3000: Account(balance=6)}, + environment=env, + ) + + pack_pre_alloc_groups(tmp_path) + + assert len(_packed(tmp_path)) == 1 + + +def test_pack_separates_distinct_environments(tmp_path: Path) -> None: + """Groups with different genesis environments never merge.""" + _write_group( + tmp_path, + "0x01", + "tests/a.py::test_a", + {0x1000: Account(balance=1)}, + environment=Environment(), + ) + _write_group( + tmp_path, + "0x02", + "tests/b.py::test_b", + {0x2000: Account(balance=2)}, + environment=Environment(gas_limit=0x1000000), + ) + + pack_pre_alloc_groups(tmp_path) + + assert len(_packed(tmp_path)) == 2 + + +def test_pack_respects_group_salt(tmp_path: Path) -> None: + """ + A group salted via the `pre_alloc_group` marker never merges with an + unsalted group or a group carrying a different salt. + """ + env = Environment() + _write_group( + tmp_path, + "0x01", + "tests/a.py::test_a", + {0x1000: Account(balance=1)}, + environment=env, + ) + _write_group( + tmp_path, + "0x02", + "tests/b.py::test_b", + {0x2000: Account(balance=2)}, + environment=env, + group_salt="isolated", + ) + _write_group( + tmp_path, + "0x03", + "tests/c.py::test_c", + {0x3000: Account(balance=3)}, + environment=env, + group_salt="other", + ) + + pack_pre_alloc_groups(tmp_path) + + packed = _packed(tmp_path) + assert len(packed) == 3 + all_ids = sorted( + tid for group in packed.values() for tid in group["testIds"] + ) + assert all_ids == [ + "tests/a.py::test_a", + "tests/b.py::test_b", + "tests/c.py::test_c", + ] + + +def test_pack_merges_groups_with_matching_salt(tmp_path: Path) -> None: + """Groups sharing the same explicit salt still pack together.""" + env = Environment() + _write_group( + tmp_path, + "0x01", + "tests/a.py::test_a", + {0x1000: Account(balance=1)}, + environment=env, + group_salt="shared", + ) + _write_group( + tmp_path, + "0x02", + "tests/b.py::test_b", + {0x2000: Account(balance=2)}, + environment=env, + group_salt="shared", + ) + + pack_pre_alloc_groups(tmp_path) + + packed = _packed(tmp_path) + assert len(packed) == 1 + (group,) = packed.values() + assert group["groupSalt"] == "shared" + + +def test_pack_writes_test_group_index(tmp_path: Path) -> None: + """ + Packing writes a test id -> group hash index that matches the packed + files' ``testIds`` and is not picked up as a group file itself. + """ + env = Environment() + _write_group( + tmp_path, + "0x01", + "tests/a.py::test_a", + {0x1000: Account(balance=1)}, + environment=env, + ) + _write_group( + tmp_path, + "0x02", + "tests/b.py::test_b", + {0x2000: Account(balance=2)}, + environment=env, + ) + _write_group( + tmp_path, + "0x03", + "tests/c.py::test_c", + {0x1000: Account(balance=3)}, + environment=env, + ) + + pack_pre_alloc_groups(tmp_path) + + assert (tmp_path / TEST_GROUP_INDEX_FILE).exists() + packed = _packed(tmp_path) + assert TEST_GROUP_INDEX_FILE not in packed + index = read_test_group_index(tmp_path) + assert sorted(index) == [ + "tests/a.py::test_a", + "tests/b.py::test_b", + "tests/c.py::test_c", + ] + for test_id, entry in index.items(): + assert test_id in packed[entry.group_hash]["testIds"] + # Every entry records the test's fine-grained phase 1 hash. + assert index["tests/a.py::test_a"].phase1_hash == "0x01" + assert index["tests/b.py::test_b"].phase1_hash == "0x02" + assert index["tests/c.py::test_c"].phase1_hash == "0x03" + + +def test_read_test_group_index_falls_back_to_scanning( + tmp_path: Path, +) -> None: + """A folder without an index file (unpacked/legacy) is scanned.""" + env = Environment() + _write_group( + tmp_path, + "0x01", + "tests/a.py::test_a", + {0x1000: Account(balance=1)}, + environment=env, + ) + _write_group( + tmp_path, + "0x02", + "tests/b.py::test_b", + {0x2000: Account(balance=2)}, + environment=env, + ) + + assert not (tmp_path / TEST_GROUP_INDEX_FILE).exists() + assert read_test_group_index(tmp_path) == { + "tests/a.py::test_a": GroupIndexEntry("0x01", None), + "tests/b.py::test_b": GroupIndexEntry("0x02", None), + } + + +def test_packed_group_hash_lookup_validates_phase1_hash( + tmp_path: Path, +) -> None: + """ + A phase 2 lookup verifies the recomputed phase 1 hash against the + index fingerprint, so a stale pre-alloc folder fails loudly instead + of silently filling a changed test against its old genesis. + """ + env = Environment() + _write_group( + tmp_path, + "0x01", + "tests/a.py::test_a", + {0x1000: Account(balance=1)}, + environment=env, + ) + pack_pre_alloc_groups(tmp_path) + index = read_test_group_index(tmp_path) + + packed_hash = packed_group_hash_for_test( + index, "tests/a.py::test_a", phase1_hash="0x01" + ) + assert packed_hash == index["tests/a.py::test_a"].group_hash + + with pytest.raises(ValueError, match="stale"): + packed_group_hash_for_test( + index, "tests/a.py::test_a", phase1_hash="0xff" + ) + with pytest.raises(ValueError, match="not assigned"): + packed_group_hash_for_test( + index, "tests/b.py::test_b", phase1_hash="0x02" + ) + + +def test_packed_group_hash_lookup_without_fingerprint( + tmp_path: Path, +) -> None: + """A scanned (legacy) index has no fingerprints to validate against.""" + env = Environment() + _write_group( + tmp_path, + "0x01", + "tests/a.py::test_a", + {0x1000: Account(balance=1)}, + environment=env, + ) + + index = read_test_group_index(tmp_path) + assert ( + packed_group_hash_for_test( + index, "tests/a.py::test_a", phase1_hash="0xff" + ) + == "0x01" + ) + + +def test_pack_is_deterministic(tmp_path: Path) -> None: + """Packing the same inputs twice yields the same group ids.""" + + def build(folder: Path) -> None: + folder.mkdir() + env = Environment() + for i in range(6): + _write_group( + folder, + f"0x0{i}", + f"tests/t{i}.py::test_{i}", + {0x1000 + i: Account(balance=i)}, + environment=env, + ) + + first, second = tmp_path / "first", tmp_path / "second" + build(first) + build(second) + pack_pre_alloc_groups(first) + pack_pre_alloc_groups(second) + + assert set(_packed(first)) == set(_packed(second)) + + +def test_pack_isolates_funded_precompile(tmp_path: Path) -> None: + """ + A precompile funded by one test is never merged into a test that + assumes it empty (precompiles are in the reserved range). + """ + env = Environment() + _write_group( + tmp_path, + "0x01", + "tests/a.py::test_a", + {0x02: Account(balance=1), 0x9000: Account(balance=1)}, + environment=env, + ) + _write_group( + tmp_path, + "0x02", + "tests/b.py::test_b", + {0x9001: Account(balance=2)}, + environment=env, + ) + + pack_pre_alloc_groups(tmp_path) + + packed = _packed(tmp_path) + assert len(packed) == 2 + with_precompile = [ + g + for g in packed.values() + if "0x0000000000000000000000000000000000000002" in g["pre"] + ] + assert len(with_precompile) == 1 + assert with_precompile[0]["testIds"] == ["tests/a.py::test_a"] + + +def test_pack_isolates_fork_precompile_above_blanket_range( + tmp_path: Path, +) -> None: + """ + An account pinned at a fork precompile address above the blanket + reserved range (P256VERIFY at ``0x100``, EIP-7951) keeps its group + isolated on a fork that has the precompile; on an earlier fork the + same address is plain scratch space and the groups merge. + """ + env = Environment() + for fork, expected_group_count in ((Prague, 1), (Osaka, 2)): + folder = tmp_path / fork.name() + folder.mkdir() + _write_group( + folder, + "0x01", + "tests/a.py::test_a", + {0x100: Account(balance=1)}, + environment=env, + fork=fork, + ) + _write_group( + folder, + "0x02", + "tests/b.py::test_b", + {0x2000: Account(balance=2)}, + environment=env, + fork=fork, + ) + + pack_pre_alloc_groups(folder) + + assert len(_packed(folder)) == expected_group_count, fork.name() + + +def test_pack_merges_when_shared_address_agrees(tmp_path: Path) -> None: + """ + Groups that agree on a shared (canonical) address and differ only in + test-private addresses still merge into one. + """ + env = Environment() + shared = Account(balance=1, nonce=1) + for stem, test_id, private in [ + ("0x01", "tests/a.py::test_a", 0xA000), + ("0x02", "tests/b.py::test_b", 0xB000), + ("0x03", "tests/c.py::test_c", 0xC000), + ]: + _write_group( + tmp_path, + stem, + test_id, + {0x9000: shared, private: Account(balance=2)}, + environment=env, + ) + + pack_pre_alloc_groups(tmp_path) + + assert len(_packed(tmp_path)) == 1 + + +def test_pack_isolates_disagreeing_shared_address(tmp_path: Path) -> None: + """ + A shared address present in some groups but absent from others keeps + them apart, so it is never leaked into a test that omits it. + """ + env = Environment() + shared = Account(balance=1, nonce=1) + _write_group( + tmp_path, + "0x01", + "tests/a.py::test_a", + {0x9000: shared, 0xA000: Account(balance=2)}, + environment=env, + ) + _write_group( + tmp_path, + "0x02", + "tests/b.py::test_b", + {0x9000: shared, 0xB000: Account(balance=2)}, + environment=env, + ) + _write_group( + tmp_path, + "0x03", + "tests/c.py::test_c", + {0xC000: Account(balance=2)}, + environment=env, + ) + + pack_pre_alloc_groups(tmp_path) + + packed = _packed(tmp_path) + assert len(packed) == 2 + all_ids = sorted( + tid for group in packed.values() for tid in group["testIds"] + ) + assert all_ids == [ + "tests/a.py::test_a", + "tests/b.py::test_b", + "tests/c.py::test_c", + ] From 31f948fc04693e7d11d93eacf5236d1b32ae28ba Mon Sep 17 00:00:00 2001 From: danceratopz <danceratopz@gmail.com> Date: Tue, 14 Jul 2026 08:29:01 +0200 Subject: [PATCH 119/233] docs(tests): remove legacy `abstract:` prefix from test module docstrings (#3162) Co-authored-by: LouisTsai <q1030176@gmail.com> --- .../cli/eest/make/templates/blockchain_test.py.j2 | 3 +-- .../cli/eest/make/templates/state_test.py.j2 | 3 +-- .../eip7976_increase_calldata_floor_cost/test_eip_mainnet.py | 4 ++-- .../test_floor_boundary_exact_balance.py | 5 +++-- .../test_access_list_cost.py | 4 ++-- .../eip7981_increase_access_list_cost/test_eip_mainnet.py | 4 ++-- .../test_floor_boundary_exact_balance.py | 5 +++-- .../test_transaction_validity.py | 4 ++-- .../test_eip_mainnet.py | 4 ++-- .../eip8282_builder_execution_requests/test_eip_mainnet.py | 4 ++-- tests/homestead/identity_precompile/__init__.py | 2 +- tests/homestead/identity_precompile/test_identity.py | 2 +- .../eip2537_bls_12_381_precompiles/test_eip_mainnet.py | 4 ++-- .../test_eip_mainnet.py | 4 ++-- tests/prague/eip6110_deposits/test_eip_mainnet.py | 4 ++-- .../eip7002_el_triggerable_withdrawals/test_eip_mainnet.py | 4 ++-- tests/prague/eip7251_consolidations/test_eip_mainnet.py | 4 ++-- .../eip7623_increase_calldata_cost/test_eip_mainnet.py | 4 ++-- tests/prague/eip7702_set_code_tx/test_eip_mainnet.py | 4 ++-- 19 files changed, 36 insertions(+), 36 deletions(-) diff --git a/packages/testing/src/execution_testing/cli/eest/make/templates/blockchain_test.py.j2 b/packages/testing/src/execution_testing/cli/eest/make/templates/blockchain_test.py.j2 index b63f70cbc09..9b51206b061 100644 --- a/packages/testing/src/execution_testing/cli/eest/make/templates/blockchain_test.py.j2 +++ b/packages/testing/src/execution_testing/cli/eest/make/templates/blockchain_test.py.j2 @@ -1,6 +1,5 @@ """ -abstract: Tests [EIP-{{eip_number}} {{eip_name}}](https://eips.ethereum.org/EIPS/eip-{{eip_number}}) - Test cases for [EIP-{{eip_number}} {{eip_name}}](https://eips.ethereum.org/EIPS/eip-{{eip_number}})]. +Test cases for [EIP-{{eip_number}} {{eip_name}}](https://eips.ethereum.org/EIPS/eip-{{eip_number}}). """ import pytest diff --git a/packages/testing/src/execution_testing/cli/eest/make/templates/state_test.py.j2 b/packages/testing/src/execution_testing/cli/eest/make/templates/state_test.py.j2 index 01159512b22..6d053f7b221 100644 --- a/packages/testing/src/execution_testing/cli/eest/make/templates/state_test.py.j2 +++ b/packages/testing/src/execution_testing/cli/eest/make/templates/state_test.py.j2 @@ -1,6 +1,5 @@ """ -abstract: Tests [EIP-{{eip_number}} {{eip_name}}](https://eips.ethereum.org/EIPS/eip-{{eip_number}}) - Test cases for [EIP-{{eip_number}} {{eip_name}}](https://eips.ethereum.org/EIPS/eip-{{eip_number}})]. +Test cases for [EIP-{{eip_number}} {{eip_name}}](https://eips.ethereum.org/EIPS/eip-{{eip_number}}). """ import pytest diff --git a/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_eip_mainnet.py b/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_eip_mainnet.py index 7926edd7032..d258c357656 100644 --- a/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_eip_mainnet.py +++ b/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_eip_mainnet.py @@ -1,6 +1,6 @@ """ -abstract: Crafted tests for mainnet of [EIP-7976: Increase calldata floor cost](https://eips.ethereum.org/EIPS/eip-7976). -""" # noqa: E501 +Crafted tests for mainnet of [EIP-7976: Increase calldata floor cost](https://eips.ethereum.org/EIPS/eip-7976). +""" import pytest from execution_testing import ( diff --git a/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_floor_boundary_exact_balance.py b/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_floor_boundary_exact_balance.py index 4561cf54626..1dddc735d39 100644 --- a/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_floor_boundary_exact_balance.py +++ b/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_floor_boundary_exact_balance.py @@ -1,6 +1,7 @@ """ -abstract: Tests for floor-boundary rejection with exact-balance funding in [EIP-7976: Increase Calldata Floor Cost](https://eips.ethereum.org/EIPS/eip-7976). -""" # noqa: E501 +Tests for floor-boundary rejection with exact-balance funding in +[EIP-7976: Increase Calldata Floor Cost](https://eips.ethereum.org/EIPS/eip-7976). +""" import pytest from execution_testing import ( diff --git a/tests/amsterdam/eip7981_increase_access_list_cost/test_access_list_cost.py b/tests/amsterdam/eip7981_increase_access_list_cost/test_access_list_cost.py index 1adbf536371..411b96322ad 100644 --- a/tests/amsterdam/eip7981_increase_access_list_cost/test_access_list_cost.py +++ b/tests/amsterdam/eip7981_increase_access_list_cost/test_access_list_cost.py @@ -1,6 +1,6 @@ """ -abstract: Tests for access list cost calculations in [EIP-7981: Increase Access List Cost](https://eips.ethereum.org/EIPS/eip-7981). -""" # noqa: E501 +Tests for access list cost calculations in [EIP-7981: Increase Access List Cost](https://eips.ethereum.org/EIPS/eip-7981). +""" import pytest from execution_testing import ( diff --git a/tests/amsterdam/eip7981_increase_access_list_cost/test_eip_mainnet.py b/tests/amsterdam/eip7981_increase_access_list_cost/test_eip_mainnet.py index 771828d80b8..8a20c3c6bed 100644 --- a/tests/amsterdam/eip7981_increase_access_list_cost/test_eip_mainnet.py +++ b/tests/amsterdam/eip7981_increase_access_list_cost/test_eip_mainnet.py @@ -1,6 +1,6 @@ """ -abstract: Crafted tests for mainnet of [EIP-7981: Increase Access List Cost](https://eips.ethereum.org/EIPS/eip-7981). -""" # noqa: E501 +Crafted tests for mainnet of [EIP-7981: Increase Access List Cost](https://eips.ethereum.org/EIPS/eip-7981). +""" import pytest from execution_testing import ( diff --git a/tests/amsterdam/eip7981_increase_access_list_cost/test_floor_boundary_exact_balance.py b/tests/amsterdam/eip7981_increase_access_list_cost/test_floor_boundary_exact_balance.py index 69fcff2d79a..c961dfe18ab 100644 --- a/tests/amsterdam/eip7981_increase_access_list_cost/test_floor_boundary_exact_balance.py +++ b/tests/amsterdam/eip7981_increase_access_list_cost/test_floor_boundary_exact_balance.py @@ -1,6 +1,7 @@ """ -abstract: Tests for floor-boundary rejection with exact-balance funding in [EIP-7981: Increase Access List Cost](https://eips.ethereum.org/EIPS/eip-7981). -""" # noqa: E501 +Tests for floor-boundary rejection with exact-balance funding in +[EIP-7981: Increase Access List Cost](https://eips.ethereum.org/EIPS/eip-7981). +""" import pytest from execution_testing import ( diff --git a/tests/amsterdam/eip7981_increase_access_list_cost/test_transaction_validity.py b/tests/amsterdam/eip7981_increase_access_list_cost/test_transaction_validity.py index 1f995b1f1b7..97ddba5bc45 100644 --- a/tests/amsterdam/eip7981_increase_access_list_cost/test_transaction_validity.py +++ b/tests/amsterdam/eip7981_increase_access_list_cost/test_transaction_validity.py @@ -1,6 +1,6 @@ """ -abstract: Tests for transaction validity with [EIP-7981: Increase Access List Cost](https://eips.ethereum.org/EIPS/eip-7981). -""" # noqa: E501 +Tests for transaction validity with [EIP-7981: Increase Access List Cost](https://eips.ethereum.org/EIPS/eip-7981). +""" import pytest from execution_testing import ( diff --git a/tests/amsterdam/eip7997_deterministic_factory_predeploy/test_eip_mainnet.py b/tests/amsterdam/eip7997_deterministic_factory_predeploy/test_eip_mainnet.py index 33fdae13f77..52db4c99669 100644 --- a/tests/amsterdam/eip7997_deterministic_factory_predeploy/test_eip_mainnet.py +++ b/tests/amsterdam/eip7997_deterministic_factory_predeploy/test_eip_mainnet.py @@ -1,7 +1,7 @@ """ -abstract: Crafted tests for mainnet of +Crafted tests for mainnet of [EIP-7997: Deterministic Factory Predeploy](https://eips.ethereum.org/EIPS/eip-7997). -""" # noqa: E501 +""" import pytest from execution_testing import ( diff --git a/tests/amsterdam/eip8282_builder_execution_requests/test_eip_mainnet.py b/tests/amsterdam/eip8282_builder_execution_requests/test_eip_mainnet.py index 3ecaa71dcb4..3422f48a756 100644 --- a/tests/amsterdam/eip8282_builder_execution_requests/test_eip_mainnet.py +++ b/tests/amsterdam/eip8282_builder_execution_requests/test_eip_mainnet.py @@ -1,6 +1,6 @@ """ -abstract: Crafted tests for mainnet of [EIP-8282: Builder Execution Requests](https://eips.ethereum.org/EIPS/eip-8282). -""" # noqa: E501 +Crafted tests for mainnet of [EIP-8282: Builder Execution Requests](https://eips.ethereum.org/EIPS/eip-8282). +""" from typing import List diff --git a/tests/homestead/identity_precompile/__init__.py b/tests/homestead/identity_precompile/__init__.py index 786785884f8..14f991b6e79 100644 --- a/tests/homestead/identity_precompile/__init__.py +++ b/tests/homestead/identity_precompile/__init__.py @@ -1 +1 @@ -"""abstract: EIP-2: Homestead Precompile Identity Test Cases.""" +"""EIP-2: Homestead Precompile Identity Test Cases.""" diff --git a/tests/homestead/identity_precompile/test_identity.py b/tests/homestead/identity_precompile/test_identity.py index ad2062900e6..31f62744fae 100644 --- a/tests/homestead/identity_precompile/test_identity.py +++ b/tests/homestead/identity_precompile/test_identity.py @@ -1,4 +1,4 @@ -"""abstract: EIP-2: Homestead Identity Precompile Test Cases.""" +"""EIP-2: Homestead Identity Precompile Test Cases.""" import pytest from execution_testing import ( diff --git a/tests/prague/eip2537_bls_12_381_precompiles/test_eip_mainnet.py b/tests/prague/eip2537_bls_12_381_precompiles/test_eip_mainnet.py index 44ec557ca1f..1992891f809 100644 --- a/tests/prague/eip2537_bls_12_381_precompiles/test_eip_mainnet.py +++ b/tests/prague/eip2537_bls_12_381_precompiles/test_eip_mainnet.py @@ -1,7 +1,7 @@ """ -abstract: Crafted tests for mainnet of +Crafted tests for mainnet of [EIP-2537: Precompile for BLS12-381 curve operations](https://eips.ethereum.org/EIPS/eip-2537). -""" # noqa: E501 +""" import pytest from execution_testing import Alloc, StateTestFiller, Transaction diff --git a/tests/prague/eip2935_historical_block_hashes_from_state/test_eip_mainnet.py b/tests/prague/eip2935_historical_block_hashes_from_state/test_eip_mainnet.py index 5345b6dfb84..8630a3d7f07 100644 --- a/tests/prague/eip2935_historical_block_hashes_from_state/test_eip_mainnet.py +++ b/tests/prague/eip2935_historical_block_hashes_from_state/test_eip_mainnet.py @@ -1,7 +1,7 @@ """ -abstract: Crafted tests for mainnet of +Crafted tests for mainnet of [EIP-2935: Serve historical block hashes from state](https://eips.ethereum.org/EIPS/eip-2935). -""" # noqa: E501 +""" import pytest from execution_testing import ( diff --git a/tests/prague/eip6110_deposits/test_eip_mainnet.py b/tests/prague/eip6110_deposits/test_eip_mainnet.py index b33865860ca..0533ea1e2f0 100644 --- a/tests/prague/eip6110_deposits/test_eip_mainnet.py +++ b/tests/prague/eip6110_deposits/test_eip_mainnet.py @@ -1,6 +1,6 @@ """ -abstract: Crafted tests for mainnet of [EIP-6110: Supply validator deposits on chain](https://eips.ethereum.org/EIPS/eip-6110). -""" # noqa: E501 +Crafted tests for mainnet of [EIP-6110: Supply validator deposits on chain](https://eips.ethereum.org/EIPS/eip-6110). +""" from typing import List diff --git a/tests/prague/eip7002_el_triggerable_withdrawals/test_eip_mainnet.py b/tests/prague/eip7002_el_triggerable_withdrawals/test_eip_mainnet.py index 956db33bdac..86795b7d884 100644 --- a/tests/prague/eip7002_el_triggerable_withdrawals/test_eip_mainnet.py +++ b/tests/prague/eip7002_el_triggerable_withdrawals/test_eip_mainnet.py @@ -1,6 +1,6 @@ """ -abstract: Crafted tests for mainnet of [EIP-7002: Execution layer triggerable withdrawals](https://eips.ethereum.org/EIPS/eip-7002). -""" # noqa: E501 +Crafted tests for mainnet of [EIP-7002: Execution layer triggerable withdrawals](https://eips.ethereum.org/EIPS/eip-7002). +""" from typing import List diff --git a/tests/prague/eip7251_consolidations/test_eip_mainnet.py b/tests/prague/eip7251_consolidations/test_eip_mainnet.py index 30f78c022ee..0f228fb2f64 100644 --- a/tests/prague/eip7251_consolidations/test_eip_mainnet.py +++ b/tests/prague/eip7251_consolidations/test_eip_mainnet.py @@ -1,6 +1,6 @@ """ -abstract: Crafted tests for mainnet of [EIP-7251: Increase the MAX_EFFECTIVE_BALANCE](https://eips.ethereum.org/EIPS/eip-7251). -""" # noqa: E501 +Crafted tests for mainnet of [EIP-7251: Increase the MAX_EFFECTIVE_BALANCE](https://eips.ethereum.org/EIPS/eip-7251). +""" from typing import List diff --git a/tests/prague/eip7623_increase_calldata_cost/test_eip_mainnet.py b/tests/prague/eip7623_increase_calldata_cost/test_eip_mainnet.py index 697be6c8ccc..fa6e86b6a39 100644 --- a/tests/prague/eip7623_increase_calldata_cost/test_eip_mainnet.py +++ b/tests/prague/eip7623_increase_calldata_cost/test_eip_mainnet.py @@ -1,6 +1,6 @@ """ -abstract: Crafted tests for mainnet of [EIP-7623: Increase calldata cost](https://eips.ethereum.org/EIPS/eip-7623). -""" # noqa: E501 +Crafted tests for mainnet of [EIP-7623: Increase calldata cost](https://eips.ethereum.org/EIPS/eip-7623). +""" import pytest from execution_testing import ( diff --git a/tests/prague/eip7702_set_code_tx/test_eip_mainnet.py b/tests/prague/eip7702_set_code_tx/test_eip_mainnet.py index 8702fd15149..d5cb05c4687 100644 --- a/tests/prague/eip7702_set_code_tx/test_eip_mainnet.py +++ b/tests/prague/eip7702_set_code_tx/test_eip_mainnet.py @@ -1,6 +1,6 @@ """ -abstract: Crafted tests for mainnet of [EIP-7702: Set EOA account code for one transaction](https://eips.ethereum.org/EIPS/eip-7702). -""" # noqa: E501 +Crafted tests for mainnet of [EIP-7702: Set EOA account code for one transaction](https://eips.ethereum.org/EIPS/eip-7702). +""" import pytest from execution_testing import ( From 21a52f4a0508dbf8b3649dd7bdfa33988fef4406 Mon Sep 17 00:00:00 2001 From: spencer <spencer.taylor-brown@ethereum.org> Date: Tue, 14 Jul 2026 11:10:08 +0100 Subject: [PATCH 120/233] fix(test-fixtures): emit chainId in state test fixture transaction (#3125) --- docs/running_tests/test_formats/state_test.md | 4 ++++ packages/testing/src/execution_testing/fixtures/state.py | 4 ++++ .../tests/fixtures/chainid_cancun_state_test_tx_type_1.json | 3 ++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/running_tests/test_formats/state_test.md b/docs/running_tests/test_formats/state_test.md index 51d341d5c55..1aa1c64621c 100644 --- a/docs/running_tests/test_formats/state_test.md +++ b/docs/running_tests/test_formats/state_test.md @@ -108,6 +108,10 @@ Excess blob gas of the block where the transaction is executed. ### `FixtureTransaction` +#### - `chainId`: [`ZeroPaddedHexNumber`](./common_types.md#zeropaddedhexnumber) + +Chain id of the transaction; omitted for unprotected (non-EIP-155) type-0 transactions + #### - `nonce`: [`ZeroPaddedHexNumber`](./common_types.md#zeropaddedhexnumber) Nonce of the account that sends the transaction diff --git a/packages/testing/src/execution_testing/fixtures/state.py b/packages/testing/src/execution_testing/fixtures/state.py index 1dc9043bfc2..9e29a6e9a4d 100644 --- a/packages/testing/src/execution_testing/fixtures/state.py +++ b/packages/testing/src/execution_testing/fixtures/state.py @@ -46,6 +46,7 @@ class FixtureTransaction(TransactionFixtureConverter): # via model_dump(), which includes many fields not in this model. model_config = CamelModel.model_config | {"extra": "ignore"} + chain_id: ZeroPaddedHexNumber | None = None nonce: ZeroPaddedHexNumber gas_price: ZeroPaddedHexNumber | None = None max_priority_fee_per_gas: ZeroPaddedHexNumber | None = None @@ -69,6 +70,9 @@ def from_transaction(cls, tx: Transaction) -> "FixtureTransaction": exclude={"gas_limit", "value", "data", "access_list"}, exclude_none=True, ) + if tx.ty == 0 and not tx.protected: + # Unprotected legacy transactions encode no chain id. + model_as_dict.pop("chain_id", None) model_as_dict["gas_limit"] = [tx.gas_limit] model_as_dict["value"] = [tx.value] model_as_dict["data"] = [tx.data] diff --git a/packages/testing/src/execution_testing/specs/tests/fixtures/chainid_cancun_state_test_tx_type_1.json b/packages/testing/src/execution_testing/specs/tests/fixtures/chainid_cancun_state_test_tx_type_1.json index 387201566ca..06807940290 100644 --- a/packages/testing/src/execution_testing/specs/tests/fixtures/chainid_cancun_state_test_tx_type_1.json +++ b/packages/testing/src/execution_testing/specs/tests/fixtures/chainid_cancun_state_test_tx_type_1.json @@ -1,7 +1,7 @@ { "000/my_chain_id_test/Cancun/tx_type_1": { "_info": { - "hash": "0x49130f37343fa73f364ed83e2a2e7146c4effa64cdb6b371f5f2bf3f2004fade", + "hash": "0x0c9cdd9849022dfb46f20fc00a646394a58e0fc8847f6e3cf578dff67378e6bc", "fixture_format": "state_test" }, "env": { @@ -39,6 +39,7 @@ } }, "transaction": { + "chainId": "0x01", "accessLists": [ [ { From 86a66991ca525238d97d4448d70d75c56a622c02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= <pawel@hepcolgum.band> Date: Tue, 14 Jul 2026 12:40:04 +0200 Subject: [PATCH 121/233] feat(tests): add CREATE refund-vs-child-spill routing test (#3163) The NEW_ACCOUNT refund on a successful CREATE onto an alive target is credited LIFO against the incorporated child spill, landing in the parent's gas_left where the GAS opcode (which excludes the reservoir) observes it. Complements test_create_onto_alive_refunds_to_gas_left, which covers the parent-spill case that is insensitive to the credit vs incorporation order. The factory stores the gas measured across the CREATE, pinning the refund routing: reordering the credit before child incorporation (PR #3099) shifts the stored value by exactly NEW_ACCOUNT (183,600). Parametrized over CREATE and CREATE2. --- .../test_state_gas_create.py | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py index 51c93b5ec08..f277307697f 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py @@ -17,6 +17,7 @@ Block, BlockchainTestFiller, Bytecode, + CodeGasMeasure, Fork, Header, Initcode, @@ -3050,3 +3051,78 @@ def test_create_account_creation_charge( post={factory: Account(storage=storage)}, blockchain_test_header_verify=Header(gas_used=expected), ) + + +@pytest.mark.with_all_create_opcodes() +@pytest.mark.valid_from("EIP8037") +def test_create_refund_credited_against_child_spill( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + create_opcode: Op, +) -> None: + """ + Verify the NEW_ACCOUNT refund routing is visible through GAS. + + The reservoir covers exactly the CREATE NEW_ACCOUNT charge, leaving + none for the child frame, whose initcode SSTOREs then spill more + than NEW_ACCOUNT of state gas from gas_left. The target is alive + (pre-funded), so NEW_ACCOUNT is refunded and credited LIFO against + the incorporated child spill, landing in the parent's gas_left + where GAS (which excludes the reservoir) observes it. + """ + gas_costs = fork.gas_costs() + + initcode = Op.SSTORE(0, 1) + Op.SSTORE(1, 1) + Op.STOP + child_spill = initcode.state_cost(fork) + assert child_spill >= gas_costs.NEW_ACCOUNT + + mstore_value, initcode_size = init_code_at_high_bytes(initcode) + create_call = ( + create_opcode( + value=0, + offset=0, + size=initcode_size, + salt=0, + init_code_size=initcode_size, + ) + if create_opcode == Op.CREATE2 + else create_opcode( + value=0, + offset=0, + size=initcode_size, + init_code_size=initcode_size, + ) + ) + + factory = pre.deploy_contract( + code=Op.MSTORE(0, mstore_value) + + CodeGasMeasure(code=create_call, extra_stack_items=1), + ) + created = compute_create_address( + address=factory, + nonce=1, + salt=0, + initcode=initcode, + opcode=create_opcode, + ) + pre.fund_address(created, amount=1) + + expected_gas = ( + create_call.regular_cost(fork) + + initcode.regular_cost(fork) + + child_spill + - gas_costs.NEW_ACCOUNT # refund credited to gas_left + ) + + tx = Transaction( + to=factory, + state_gas_reservoir=gas_costs.NEW_ACCOUNT, + sender=pre.fund_eoa(), + ) + + post = { + factory: Account(storage={0: expected_gas}), + created: Account(nonce=1, balance=1, storage={0: 1, 1: 1}), + } + state_test(pre=pre, post=post, tx=tx) From a3638dc8db526ac5fc612c56acdc078b14daec95 Mon Sep 17 00:00:00 2001 From: spencer <spencer.taylor-brown@ethereum.org> Date: Tue, 14 Jul 2026 12:00:23 +0100 Subject: [PATCH 122/233] fix(tests): test tx max nonce at u64 boundary, add nonce overflow test (#3165) --- tests/frontier/validation/test_transaction.py | 39 ++++++++++++++++--- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/tests/frontier/validation/test_transaction.py b/tests/frontier/validation/test_transaction.py index d52003dc3c0..8284fc31614 100644 --- a/tests/frontier/validation/test_transaction.py +++ b/tests/frontier/validation/test_transaction.py @@ -8,6 +8,7 @@ StateTestFiller, Storage, Transaction, + TransactionTestFiller, add_kzg_version, ) from execution_testing.base_types.base_types import ZeroPaddedHexNumber @@ -101,25 +102,53 @@ def test_tx_nonce( state_test(pre=pre, post={}, tx=tx) +@pytest.mark.pre_alloc_mutable @pytest.mark.exception_test @pytest.mark.eels_base_coverage def test_tx_max_nonce(state_test: StateTestFiller, pre: Alloc) -> None: """ - Test that a transaction that exceeds the maximum allowed value for the - nonce (U64.MAX_VALUE) is rejected. + Test that a transaction with the maximum nonce value (`2**64 - 1`) is + rejected, as the maximum usable nonce is `2**64 - 2`. + + The sender account is funded at the same nonce so that clients which + check nonce equality first reach the max-nonce check instead of + rejecting the transaction with a nonce mismatch. """ - sender = pre.fund_eoa() + max_nonce = 2**64 - 1 + sender = pre.fund_eoa(nonce=max_nonce) to = pre.nonexistent_account() tx = Transaction( to=to, - nonce=2**64, + nonce=max_nonce, sender=sender, protected=False, error=TransactionException.NONCE_IS_MAX, ) - state_test(pre=pre, post={sender: Account(nonce=0)}, tx=tx) + state_test(pre=pre, post={sender: Account(nonce=max_nonce)}, tx=tx) + + +@pytest.mark.exception_test +def test_tx_nonce_overflow( + transaction_test: TransactionTestFiller, + pre: Alloc, + fork: BaseFork, +) -> None: + """ + Test that a transaction with a nonce that does not fit in 64 bits is + rejected at deserialization. + """ + tx = Transaction( + to=pre.nonexistent_account(), + nonce=2**64, + gas_limit=fork.transaction_intrinsic_cost_calculator()(), + sender=pre.fund_eoa(), + protected=False, + error=TransactionException.NONCE_OVERFLOW, + ) + + transaction_test(pre=pre, tx=tx) @pytest.mark.parametrize( From 745fe1131f81846a3c08e9a2a9ee16fde67fa3a7 Mon Sep 17 00:00:00 2001 From: kevaundray <kevtheappdev@gmail.com> Date: Tue, 14 Jul 2026 12:03:24 +0100 Subject: [PATCH 123/233] refactor(deps): replace `coincurve` with `spec256k1` (#2374) Co-authored-by: danceratopz <danceratopz@gmail.com> --- .../installation_troubleshooting.md | 32 ---- packages/testing/pyproject.toml | 2 +- .../test_types/account_types.py | 2 +- .../test_types/transaction_types.py | 21 ++- pyproject.toml | 5 +- src/ethereum/crypto/elliptic_curve.py | 9 +- src/ethereum_spec_tools/evm_tools/utils.py | 6 +- uv.lock | 147 ++++++++++++------ whitelist.txt | 1 - 9 files changed, 121 insertions(+), 104 deletions(-) diff --git a/docs/getting_started/installation_troubleshooting.md b/docs/getting_started/installation_troubleshooting.md index e405b385779..0a47f3662f7 100644 --- a/docs/getting_started/installation_troubleshooting.md +++ b/docs/getting_started/installation_troubleshooting.md @@ -2,38 +2,6 @@ This page provides guidance on how to troubleshoot common issues that may arise when installing [ethereum/execution-specs](https://github.com/ethereum/execution-specs). -## Problem: `Failed building wheel for coincurve` - -!!! danger "Problem: `Failed building wheel for coincurve`" - Installing EEST and its dependencies via `uv sync` fails with: - - ```bash - Stored in directory: /tmp/... - Building wheel for coincurve (pyproject.toml) ... error - error: subprocess-exited-with-error - - × Building wheel for coincurve (pyproject.toml) did not run successfully. - │ exit code: 1 - ╰─> [27 lines of output] - ... - 571 | #include <secp256k1_extrakeys.h> - | ^~~~~~~~~~~~~~~~~~~~~~~ - compilation terminated. - error: command '/usr/bin/gcc' failed with exit code 1 - [end of output] - - note: This error originates from a subprocess, and is likely not a problem with pip. - ERROR: Failed building wheel for coincurve - ``` - -!!! success "Solution: Install the `libsecp256k1` library" - On Ubuntu, you can install this library with: - - ```bash - sudo apt update - sudo apt-get install libsecp256k1-dev - ``` - ## Problem: `solc` Installation issues ### Problem: `CERTIFICATE_VERIFY_FAILED` diff --git a/packages/testing/pyproject.toml b/packages/testing/pyproject.toml index afd2e37ab02..11577956ef7 100644 --- a/packages/testing/pyproject.toml +++ b/packages/testing/pyproject.toml @@ -33,7 +33,7 @@ dependencies = [ "pytest-html>=4.1.0,<5", "pytest-metadata>=3,<4", "pytest-xdist>=3.3.1,<4", - "coincurve>=20.0.0,<21", + "spec256k1>=0.2.3,<0.3", "trie>=3.1.0,<4", "semver>=3.0.1,<4", "pydantic>=2.12.3,<3", diff --git a/packages/testing/src/execution_testing/test_types/account_types.py b/packages/testing/src/execution_testing/test_types/account_types.py index aba69bed4aa..340b61597ba 100644 --- a/packages/testing/src/execution_testing/test_types/account_types.py +++ b/packages/testing/src/execution_testing/test_types/account_types.py @@ -15,9 +15,9 @@ Tuple, ) -from coincurve.keys import PrivateKey from ethereum_types.bytes import Bytes20 from ethereum_types.numeric import U256, Bytes32, Uint +from spec256k1 import PrivateKey from execution_testing.base_types import ( Account, diff --git a/packages/testing/src/execution_testing/test_types/transaction_types.py b/packages/testing/src/execution_testing/test_types/transaction_types.py index b2b04d3ed6a..00fd02ee8bb 100644 --- a/packages/testing/src/execution_testing/test_types/transaction_types.py +++ b/packages/testing/src/execution_testing/test_types/transaction_types.py @@ -7,7 +7,6 @@ from typing import Any, ClassVar, Dict, Generic, List, Literal, Self, Sequence import ethereum_rlp as eth_rlp -from coincurve.keys import PrivateKey, PublicKey from pydantic import ( AliasChoices, BaseModel, @@ -17,6 +16,7 @@ model_serializer, model_validator, ) +from spec256k1 import PrivateKey, PublicKey from execution_testing.base_types import ( AccessList, @@ -154,8 +154,8 @@ def sign(self: "AuthorizationTuple") -> None: signing_key = eoa.key assert signing_key is not None, "secret_key or signer must be set" - signature_bytes = PrivateKey(secret=signing_key).sign_recoverable( - rlp_signing_bytes, hasher=keccak256 + signature_bytes = PrivateKey(signing_key).sign_recoverable( + rlp_signing_bytes.keccak256() ) self.v, self.r, self.s = ( HexNumber(signature_bytes[64]), @@ -179,7 +179,7 @@ def sign(self: "AuthorizationTuple") -> None: + bytes([self.v]) ) public_key = PublicKey.from_signature_and_message( - signature_bytes, rlp_signing_bytes.keccak256(), hasher=None + signature_bytes, rlp_signing_bytes.keccak256() ) self.signer = EOA( address=Address( @@ -557,8 +557,8 @@ def sign(self: "Transaction") -> None: signing_key = eoa.key assert signing_key is not None, "secret_key or signer must be set" - signature_bytes = PrivateKey(secret=signing_key).sign_recoverable( - rlp_signing_bytes, hasher=keccak256 + signature_bytes = PrivateKey(signing_key).sign_recoverable( + rlp_signing_bytes.keccak256() ) v, r, s = ( signature_bytes[64], @@ -590,7 +590,7 @@ def sign(self: "Transaction") -> None: + bytes([v]) ) public_key = PublicKey.from_signature_and_message( - signature_bytes, rlp_signing_bytes.keccak256(), hasher=None + signature_bytes, rlp_signing_bytes.keccak256() ) self.sender = EOA( address=Address( @@ -712,7 +712,6 @@ def with_signature_and_sender( public_key = PublicKey.from_signature_and_message( self.signature_bytes, self.rlp_signing_bytes().keccak256(), - hasher=None, ) updated_values["sender"] = Address( keccak256(public_key.format(compressed=False)[1:])[32 - 20 :] @@ -729,11 +728,11 @@ def with_signature_and_sender( signing_hash = self.rlp_signing_bytes().keccak256() # Sign the bytes - signature_bytes = PrivateKey(secret=self.secret_key).sign_recoverable( - signing_hash, hasher=None + signature_bytes = PrivateKey(self.secret_key).sign_recoverable( + signing_hash ) public_key = PublicKey.from_signature_and_message( - signature_bytes, signing_hash, hasher=None + signature_bytes, signing_hash ) sender = keccak256(public_key.format(compressed=False)[1:])[32 - 20 :] diff --git a/pyproject.toml b/pyproject.toml index d7bc28a0acc..bf89f3593cb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ classifiers = [ ] dependencies = [ "pycryptodome>=3.22,<4", - "coincurve>=20,<21", + "spec256k1>=0.2.3,<0.3", "typing_extensions>=4.4", "py-ecc>=8.0.0b2,<9", "ethereum-types>=0.4.1,<0.5", @@ -534,9 +534,6 @@ plugins = ["pydantic.mypy"] [tool.uv] required-version = ">=0.7.0" extra-build-dependencies = { ethash = ["setuptools", "cmake>=4.2.1,<5"] } -# Pin scikit-build-core < 0.10 for coincurve's sdist build on Python 3.13+. -# See https://github.com/ethereum/execution-specs/pull/3119 -build-constraint-dependencies = ["scikit-build-core<0.10"] [tool.uv.workspace] members = ["packages/*"] diff --git a/src/ethereum/crypto/elliptic_curve.py b/src/ethereum/crypto/elliptic_curve.py index 03a4f95aa10..c2533db47f4 100644 --- a/src/ethereum/crypto/elliptic_curve.py +++ b/src/ethereum/crypto/elliptic_curve.py @@ -1,6 +1,6 @@ """Elliptic Curves.""" -import coincurve +import spec256k1 from Crypto.Util.asn1 import DerSequence from cryptography.exceptions import InvalidSignature from cryptography.hazmat.backends import default_backend @@ -67,14 +67,13 @@ def secp256k1_recover(r: U256, s: U256, v: U256, msg_hash: Hash32) -> Bytes: # the signature is considered invalid # the below function will raise a ValueError. try: - public_key = coincurve.PublicKey.from_signature_and_message( - bytes(signature), msg_hash, hasher=None + public_key = spec256k1.PublicKey.from_signature_and_message( + bytes(signature), msg_hash ) except ValueError as e: raise InvalidSignatureError from e - public_key = public_key.format(compressed=False)[1:] - return public_key + return public_key.format(compressed=False)[1:] SECP256R1N = U256( diff --git a/src/ethereum_spec_tools/evm_tools/utils.py b/src/ethereum_spec_tools/evm_tools/utils.py index 8698fd07a7a..15aee92af71 100644 --- a/src/ethereum_spec_tools/evm_tools/utils.py +++ b/src/ethereum_spec_tools/evm_tools/utils.py @@ -18,7 +18,7 @@ Union, ) -import coincurve +import spec256k1 from ethereum_types.numeric import U64, U256, Uint from ethereum.crypto.hash import Hash32 @@ -172,8 +172,8 @@ def secp256k1_sign(msg_hash: Hash32, secret_key: int) -> Tuple[U256, ...]: """ Returns the signature of a message hash given the secret key. """ - private_key = coincurve.PrivateKey.from_int(secret_key) - signature = private_key.sign_recoverable(msg_hash, hasher=None) + private_key = spec256k1.PrivateKey(secret_key.to_bytes(32, "big")) + signature = private_key.sign_recoverable(msg_hash) return ( U256.from_be_bytes(signature[0:32]), diff --git a/uv.lock b/uv.lock index 5020d13995c..667058a8cc2 100644 --- a/uv.lock +++ b/uv.lock @@ -12,7 +12,6 @@ members = [ "ethereum-execution", "ethereum-execution-testing", ] -build-constraints = [{ name = "scikit-build-core", specifier = "<0.10" }] [[package]] name = "actionlint-py" @@ -29,15 +28,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, ] -[[package]] -name = "asn1crypto" -version = "1.5.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/de/cf/d547feed25b5244fcb9392e288ff9fdc3280b10260362fc45d37a798a6ee/asn1crypto-1.5.1.tar.gz", hash = "sha256:13ae38502be632115abf8a24cbe5f4da52e3b5231990aff31123c805306ccb9c", size = 121080, upload-time = "2022-03-15T14:46:52.889Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c9/7f/09065fd9e27da0eda08b4d6897f1c13535066174cc023af248fc2a8d5e5a/asn1crypto-1.5.1-py2.py3-none-any.whl", hash = "sha256:db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67", size = 105045, upload-time = "2022-03-15T14:46:51.055Z" }, -] - [[package]] name = "ast-serialize" version = "0.5.0" @@ -414,38 +404,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/20/01/b394922252051e97aab231d416c86da3d8a6d781eeadcdca1082867de64e/codespell-2.4.1-py3-none-any.whl", hash = "sha256:3dadafa67df7e4a3dbf51e0d7315061b80d265f9552ebd699b3dd6834b47e425", size = 344501, upload-time = "2025-01-28T18:52:37.057Z" }, ] -[[package]] -name = "coincurve" -version = "20.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "asn1crypto" }, - { name = "cffi" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d9/4c/9e5e51e6c12cec6444c86697992f9c6ccffa19f84d042ff939c8b89206ff/coincurve-20.0.0.tar.gz", hash = "sha256:872419e404300302e938849b6b92a196fabdad651060b559dc310e52f8392829", size = 122865, upload-time = "2024-06-02T18:15:50.787Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/24/a7/d60a41b3f0a546854c9b7ca65ab99a5fdf1c9e158ae264a580de8f23fd1c/coincurve-20.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:44087d1126d43925bf9a2391ce5601bf30ce0dba4466c239172dc43226696018", size = 1255635, upload-time = "2024-06-02T18:14:42.483Z" }, - { url = "https://files.pythonhosted.org/packages/b7/4a/727fab66c0fbecfd7beeb38467910bd3652a77df649565e30160a9d2bae2/coincurve-20.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ccf0ba38b0f307a9b3ce28933f6c71dc12ef3a0985712ca09f48591afd597c8", size = 1255536, upload-time = "2024-06-02T18:14:44.077Z" }, - { url = "https://files.pythonhosted.org/packages/0f/8b/25d4ae5bb60665023e6d71681fada88ee95b5010dae6fc0b44d8b23b8df1/coincurve-20.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:566bc5986debdf8572b6be824fd4de03d533c49f3de778e29f69017ae3fe82d8", size = 1191928, upload-time = "2024-06-02T18:14:45.739Z" }, - { url = "https://files.pythonhosted.org/packages/0d/86/8c32c512fa27bfe7cfe70329fd43ebac23c0c8cec202cf6e4f52854e7ce3/coincurve-20.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4d70283168e146f025005c15406086513d5d35e89a60cf4326025930d45013a", size = 1194365, upload-time = "2024-06-02T18:14:47.008Z" }, - { url = "https://files.pythonhosted.org/packages/fe/74/fefbe512f54df7d02a7ea4821b87cf199a91b3565cdf0c94448b3f6b1af1/coincurve-20.0.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:763c6122dd7d5e7a81c86414ce360dbe9a2d4afa1ca6c853ee03d63820b3d0c5", size = 1204658, upload-time = "2024-06-02T18:14:48.348Z" }, - { url = "https://files.pythonhosted.org/packages/09/68/05b29f881f628ce8e8468f5f7420f6c4d7c129f43964e81d15bf388ae67a/coincurve-20.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f00c361c356bcea386d47a191bb8ac60429f4b51c188966a201bfecaf306ff7f", size = 1215301, upload-time = "2024-06-02T18:14:49.84Z" }, - { url = "https://files.pythonhosted.org/packages/ee/5d/d91549cf5a163797b0724dc2dcd551b908b6beddb6598b37743df7f6f3ec/coincurve-20.0.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4af57bdadd2e64d117dd0b33cfefe76e90c7a6c496a7b034fc65fd01ec249b15", size = 1204505, upload-time = "2024-06-02T18:14:51.816Z" }, - { url = "https://files.pythonhosted.org/packages/37/0f/898022e08760fb57d281f3695576e859b0f8a8ac629670223d9066c3f60d/coincurve-20.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a26437b7cbde13fb6e09261610b788ca2a0ca2195c62030afd1e1e0d1a62e035", size = 1209305, upload-time = "2024-06-02T18:14:53.39Z" }, - { url = "https://files.pythonhosted.org/packages/57/b9/643567d3f680ddf8d1bf10a56112ae7755296500d8eaaef498be637a8533/coincurve-20.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:ed51f8bba35e6c7676ad65539c3dbc35acf014fc402101fa24f6b0a15a74ab9e", size = 1198932, upload-time = "2024-06-02T18:14:54.751Z" }, - { url = "https://files.pythonhosted.org/packages/b3/3a/898f5c12469b292042608dd0702bcb0420ec32bac6b1ca2a0dd790f922bd/coincurve-20.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:594b840fc25d74118407edbbbc754b815f1bba9759dbf4f67f1c2b78396df2d3", size = 1193318, upload-time = "2024-06-02T18:14:55.981Z" }, - { url = "https://files.pythonhosted.org/packages/8f/24/e1bf259dd57186fbdc7cec51909db320884162cfad5ec72cbaa63573ff9d/coincurve-20.0.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4df4416a6c0370d777aa725a25b14b04e45aa228da1251c258ff91444643f688", size = 1255671, upload-time = "2024-06-02T18:14:57.863Z" }, - { url = "https://files.pythonhosted.org/packages/0a/c5/1817f87d1cd5ff50d8537fe60fb96f66b76dd02da885d970952e6189a801/coincurve-20.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1ccc3e4db55abf3fc0e604a187fdb05f0702bc5952e503d9a75f4ae6eeb4cb3a", size = 1255565, upload-time = "2024-06-02T18:14:59.128Z" }, - { url = "https://files.pythonhosted.org/packages/90/9f/35e15f993717ed1dcc4c26d9771f073a1054af26808a0f421783bb4cd7e0/coincurve-20.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac8335b1658a2ef5b3eb66d52647742fe8c6f413ad5b9d5310d7ea6d8060d40f", size = 1191953, upload-time = "2024-06-02T18:15:01.047Z" }, - { url = "https://files.pythonhosted.org/packages/4a/3d/6a9bc32e69b738b5e05f5027bace1da6722352a4a447e495d3c03a601d99/coincurve-20.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7ac025e485a0229fd5394e0bf6b4a75f8a4f6cee0dcf6f0b01a2ef05c5210ff", size = 1194425, upload-time = "2024-06-02T18:15:02.919Z" }, - { url = "https://files.pythonhosted.org/packages/1a/a6/15424973dc47fc7c87e3c0f8859f6f1b1032582ee9f1b85fdd5d1e33d630/coincurve-20.0.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e46e3f1c21b3330857bcb1a3a5b942f645c8bce912a8a2b252216f34acfe4195", size = 1204678, upload-time = "2024-06-02T18:15:04.308Z" }, - { url = "https://files.pythonhosted.org/packages/6a/e7/71ddb4d66c11c4ad13e729362f8852e048ae452eba3dfcf57751842bb292/coincurve-20.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:df9ff9b17a1d27271bf476cf3fa92df4c151663b11a55d8cea838b8f88d83624", size = 1215395, upload-time = "2024-06-02T18:15:05.701Z" }, - { url = "https://files.pythonhosted.org/packages/b9/7d/03e0a19cfff1d86f5d019afc69cfbff02caada701ed5a4a50abc63d4261c/coincurve-20.0.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4155759f071375699282e03b3d95fb473ee05c022641c077533e0d906311e57a", size = 1204552, upload-time = "2024-06-02T18:15:07.107Z" }, - { url = "https://files.pythonhosted.org/packages/07/cd/e9bd4ca7d931653a35c74194da04191a9aecc54b8f48a554cd538dc810e4/coincurve-20.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0530b9dd02fc6f6c2916716974b79bdab874227f560c422801ade290e3fc5013", size = 1209392, upload-time = "2024-06-02T18:15:08.663Z" }, - { url = "https://files.pythonhosted.org/packages/99/54/260053f14f74b99b645084231e1c76994134ded49407a3bba23a8ffc0ff6/coincurve-20.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:eacf9c0ce8739c84549a89c083b1f3526c8780b84517ee75d6b43d276e55f8a0", size = 1198932, upload-time = "2024-06-02T18:15:10.786Z" }, - { url = "https://files.pythonhosted.org/packages/b4/b5/c465e09345dd38b9415f5d47ae7683b3f461db02fcc03e699b6b5687ab2b/coincurve-20.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:52a67bfddbd6224dfa42085c88ad176559801b57d6a8bd30d92ee040de88b7b3", size = 1193324, upload-time = "2024-06-02T18:15:12.511Z" }, -] - [[package]] name = "colorama" version = "0.4.6" @@ -872,7 +830,6 @@ wheels = [ name = "ethereum-execution" source = { editable = "." } dependencies = [ - { name = "coincurve" }, { name = "cryptography" }, { name = "ethereum-rlp" }, { name = "ethereum-types" }, @@ -880,6 +837,7 @@ dependencies = [ { name = "platformdirs" }, { name = "py-ecc" }, { name = "pycryptodome" }, + { name = "spec256k1" }, { name = "typing-extensions" }, ] @@ -982,7 +940,6 @@ test = [ [package.metadata] requires-dist = [ - { name = "coincurve", specifier = ">=20,<21" }, { name = "cryptography", specifier = ">=45.0.1,<46" }, { name = "ethash", marker = "extra == 'optimized'", specifier = ">=1.1.0,<2" }, { name = "ethereum-rlp", specifier = ">=0.1.6,<0.2" }, @@ -992,6 +949,7 @@ requires-dist = [ { name = "py-ecc", specifier = ">=8.0.0b2,<9" }, { name = "pycryptodome", specifier = ">=3.22,<4" }, { name = "rust-pyspec-glue", marker = "extra == 'optimized'", specifier = ">=0.0.9,<0.1.0" }, + { name = "spec256k1", specifier = ">=0.2.3,<0.3" }, { name = "typing-extensions", specifier = ">=4.4" }, ] provides-extras = ["optimized"] @@ -1095,7 +1053,6 @@ source = { editable = "packages/testing" } dependencies = [ { name = "ckzg" }, { name = "click" }, - { name = "coincurve" }, { name = "colorlog" }, { name = "eth-abi" }, { name = "ethereum-execution" }, @@ -1124,6 +1081,7 @@ dependencies = [ { name = "rich" }, { name = "ruff" }, { name = "semver" }, + { name = "spec256k1" }, { name = "tenacity" }, { name = "trie" }, { name = "types-pyyaml" }, @@ -1148,7 +1106,6 @@ test = [ requires-dist = [ { name = "ckzg", specifier = ">=2.1.3,<3" }, { name = "click", specifier = ">=8.1.0,<9" }, - { name = "coincurve", specifier = ">=20.0.0,<21" }, { name = "colorlog", specifier = ">=6.7.0,<7" }, { name = "eth-abi", specifier = ">=5.2.0" }, { name = "ethereum-execution", editable = "." }, @@ -1177,6 +1134,7 @@ requires-dist = [ { name = "rich", specifier = ">=13.7.0,<15" }, { name = "ruff", specifier = "==0.13.2" }, { name = "semver", specifier = ">=3.0.1,<4" }, + { name = "spec256k1", specifier = ">=0.2.3,<0.3" }, { name = "tenacity", specifier = ">=9.0.0,<10" }, { name = "trie", specifier = ">=3.1.0,<4" }, { name = "types-pyyaml", specifier = ">=6.0.12.20240917,<7" }, @@ -3005,6 +2963,103 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl", hash = "sha256:0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c", size = 36679, upload-time = "2025-08-27T15:39:50.179Z" }, ] +[[package]] +name = "spec256k1" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/34/dc/51afbffb016b229b4ff8de6dcfde6209cdcb73369b2679c54ed4daa47ae6/spec256k1-0.2.3.tar.gz", hash = "sha256:1bb7b25d8c83445dd11ce676a9152bddc4485d3b8c6ad4a5eec1f9cc5575fa9d", size = 7916, upload-time = "2026-03-01T01:25:31.029Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/bb/5f5dd82973aa321e1838629497765e18e18ce82c64d041636b86c9046946/spec256k1-0.2.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:59a69cde2952fb30e436d72597b896fa5c28b191fba7dfafddcaed92c744786e", size = 1441336, upload-time = "2026-03-01T01:26:35.914Z" }, + { url = "https://files.pythonhosted.org/packages/13/f0/f20348fc5e2c245b33d922a5af1faf5aab0541547476c7b7bc2e9c314377/spec256k1-0.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:00e6b1424d81d489a916740e73a8a5f5178bcf8f7440d847ddb1651d443374e2", size = 1446949, upload-time = "2026-03-01T01:25:58.507Z" }, + { url = "https://files.pythonhosted.org/packages/bb/79/2be6476afbe7a33fdc7815baff97a66181fe8f099d73709ed66ee1b073d6/spec256k1-0.2.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a22fa7a305c7ae592f0b143a51dd7965f50c0e6890fba2035b0b8e86595f45f9", size = 1449966, upload-time = "2026-03-01T01:26:34.224Z" }, + { url = "https://files.pythonhosted.org/packages/a6/0d/efa23cc8e8681bf94bafe2020df018835010ed634c69dfbb2d62985068ac/spec256k1-0.2.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa00e7ca034cd5126c681d229464e2d56b5e8caa9d336606d13fd389c2d0ddda", size = 1453424, upload-time = "2026-03-01T01:26:59.629Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f9/2e99f47b765d874fd96f20c3eba52918df4ad936e1c61d7ed376b04e533a/spec256k1-0.2.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:033bdc5f7b719c8fa69fccd0071b7aadc4259fac673c73418a61c04a40bb6c60", size = 1594266, upload-time = "2026-03-01T01:24:54.46Z" }, + { url = "https://files.pythonhosted.org/packages/17/05/c74e561660ad62ff5ab5fc54b97781b7d4a420b034444d1b977c2b70e326/spec256k1-0.2.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bfa35c8a04a3c7a6c99ab786f21a65c2ad87cdae95d2344cbed4c7c9380e1022", size = 1472100, upload-time = "2026-03-01T01:25:07.573Z" }, + { url = "https://files.pythonhosted.org/packages/24/40/0258072a435199666e10e4947c6cf0c9d85880f4d2f137e4b2c5e4845ffb/spec256k1-0.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3a334db8897aea05b2b50c677c516f71ecb184ea54c9b0f9ccbc7d1f482bf8a", size = 1447970, upload-time = "2026-03-01T01:26:22.281Z" }, + { url = "https://files.pythonhosted.org/packages/0a/1e/dd4d7a184e961fc82343a4af5af9b1dc9041c14a4e76bd1698ee4249dfc8/spec256k1-0.2.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8f88402d76c4c6a1899f757bd13bb8c390a42faafc1d10230fae39d333a9cf55", size = 1469543, upload-time = "2026-03-01T01:26:51.586Z" }, + { url = "https://files.pythonhosted.org/packages/fc/ca/cade421a0e91580b963b7d378c2b79e149da59bf836b0a1b752edbb26539/spec256k1-0.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b4a7bd9673d4b307e0350193bbd9babbce57cdfe8ddca5e2ade25917e41581e", size = 1620416, upload-time = "2026-03-01T01:26:23.733Z" }, + { url = "https://files.pythonhosted.org/packages/ca/6d/ae8ea93d18aa81743af2b029e1a7028e9a6f2070db7ba9e09977f4a064e1/spec256k1-0.2.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:fd4bf37ade339e8014be7813ffff4ba7756577f47fcc1f18932f299e12c62cc6", size = 1723631, upload-time = "2026-03-01T01:26:30.699Z" }, + { url = "https://files.pythonhosted.org/packages/8e/37/aea2d7fa11320e6e74856c4be4b6fae5d64cd8bb9bd820fb8c764b0bd3aa/spec256k1-0.2.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:02e43257bcb66f38795ea94e1343df909dcbee4d2528e2eb474a073b168db69e", size = 1687209, upload-time = "2026-03-01T01:26:19.018Z" }, + { url = "https://files.pythonhosted.org/packages/dd/9e/ddc122f1c431abda269e573eb255701f611e8ded6b6e41e6f05dd7531d23/spec256k1-0.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d6ea85dca435592e4bd3eec4528ade502d8bb13ea8aa07a0ee917c583706b669", size = 1652845, upload-time = "2026-03-01T01:26:32.414Z" }, + { url = "https://files.pythonhosted.org/packages/1f/fd/812083baddd3e937f8a459f39304c0d39e784d464e2a5879fe89427e2b8b/spec256k1-0.2.3-cp311-cp311-win32.whl", hash = "sha256:2c23df50642faa655c2c01a31359d0c5faaa39b44a792ee443adecf51c296515", size = 1280359, upload-time = "2026-03-01T01:24:59.052Z" }, + { url = "https://files.pythonhosted.org/packages/50/ae/d517b6a2728efbaedd8ee828aeea5da30b9a9f9739f493cc5aaf3139d346/spec256k1-0.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:80d3629f3cf5f59211b5da44ff5288301ab5dcb9029b33ccc7e69bba751520ef", size = 1293201, upload-time = "2026-03-01T01:26:56.399Z" }, + { url = "https://files.pythonhosted.org/packages/d1/d6/cf103cc72b7f5870afe86252b0e41bf0bedbdd24c686e4963dd1db3fc23e/spec256k1-0.2.3-cp311-cp311-win_arm64.whl", hash = "sha256:1c3a3a6593bdce64529f0c46c44bd178b9d4495bcab1c79be597cd0b77a12675", size = 1288696, upload-time = "2026-03-01T01:25:55.039Z" }, + { url = "https://files.pythonhosted.org/packages/6b/3f/224cfc6b7516d2df3181e5749addf0c34449b64906a77340b4f8e18386f1/spec256k1-0.2.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:590653b1fd53dfff98460a10afa21e79b763e20c7560a72355fc6f5e7a85e3b2", size = 1440397, upload-time = "2026-03-01T01:24:52.427Z" }, + { url = "https://files.pythonhosted.org/packages/34/6a/448afa50c18b2d85ecf9353a29916a758d68cd46b8de8a713bf3b763be66/spec256k1-0.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b80aa8c180d8fe866e2a429e29e3d69c805d2f16368de6392bdc054c45b25881", size = 1445980, upload-time = "2026-03-01T01:26:43.893Z" }, + { url = "https://files.pythonhosted.org/packages/32/58/d945c49839d1ea106d6f066b10f47fd6b49e69f1bc1e848b87b892228e41/spec256k1-0.2.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:86162c48a601eeb75278dde57343594eddee262911002c9a41dfe24ace6960ae", size = 1448230, upload-time = "2026-03-01T01:25:37.616Z" }, + { url = "https://files.pythonhosted.org/packages/ab/58/4c450bc11442f46b198ad043e3fdf81ed55af8c56e5157e77e151199386a/spec256k1-0.2.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b7042d6c24a514fc5bdb1b42d0a37359e6f8bb3cfb6a294e2fdb52d203387e2c", size = 1451390, upload-time = "2026-03-01T01:25:03.422Z" }, + { url = "https://files.pythonhosted.org/packages/de/28/29c7a9b13b7fed7c8064f17e2056ea8375b0d711171c3d8f02c02b27efe8/spec256k1-0.2.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:42417dbac8a3c00239dcb393a14b546e429c5ff40bc39820d868ab602c2bf384", size = 1592608, upload-time = "2026-03-01T01:25:23.288Z" }, + { url = "https://files.pythonhosted.org/packages/33/1b/5444481498c68ddfee3de035d3fb2dc42077475ebb6c7188c7c2927f102d/spec256k1-0.2.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9340a3d1894ed4f9708d82cf0c4da893b10bef074e3abd811f551579c2db7698", size = 1469789, upload-time = "2026-03-01T01:25:04.808Z" }, + { url = "https://files.pythonhosted.org/packages/9b/fe/6081ffbc4c32e3487c73560913d75030ec46bda691aa45f7c54e3aaf476b/spec256k1-0.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d71974ed4307494d4c550cde12ef2f330463b58121c64c038b0e061dc0dcf37", size = 1445834, upload-time = "2026-03-01T01:25:32.222Z" }, + { url = "https://files.pythonhosted.org/packages/eb/5e/44b8b501ad105ea90aa1f5ce3f527dfe5056d15d4a36e8e0da4c092a2b7d/spec256k1-0.2.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7142e180f32d586c565fd0c7097146c5cb5d7ea3904b13aa1fea2356b82475a5", size = 1466680, upload-time = "2026-03-01T01:25:51.788Z" }, + { url = "https://files.pythonhosted.org/packages/4d/58/340f97a637cf2bac410b918b0086020c88ffa91bc84195480aba67a420ee/spec256k1-0.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e87475fcea385a43325efbd0eea8cd3f37829da62306f19dfa739749953d688f", size = 1618324, upload-time = "2026-03-01T01:26:25.061Z" }, + { url = "https://files.pythonhosted.org/packages/eb/26/f4fc3f7bff698a3650044a3c55f6ea8572786c9a741f4c174cba5e216ad4/spec256k1-0.2.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:474b2754085974a751a01348f2d121835bf6b0b9dc8c06c0e8c5046739dc52b4", size = 1721425, upload-time = "2026-03-01T01:25:47.524Z" }, + { url = "https://files.pythonhosted.org/packages/04/a4/f9984125819c1215d70a78b43f799312cc0d6d5684a2da83369b2cdda10e/spec256k1-0.2.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:947be8deb13aed15909abcb75bdc778f43a737ea211ac07f1f4f3fce831ee345", size = 1683750, upload-time = "2026-03-01T01:26:00.031Z" }, + { url = "https://files.pythonhosted.org/packages/8b/c8/537ee8862a1b2ba2337fab0bb85e2c9a35b138de9a0ac61be48c9c99eb45/spec256k1-0.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7352de9cf7f85e3bd50c06b7fcc954cf9ebd11d8949fcab86b87af5932c6bd02", size = 1650783, upload-time = "2026-03-01T01:25:12.668Z" }, + { url = "https://files.pythonhosted.org/packages/57/88/e584bf7fd65d61ecec0b86e66d2b194b68ea6fcebb02c2863875010b840b/spec256k1-0.2.3-cp312-cp312-win32.whl", hash = "sha256:3b4c03ece84e453aa42d416ff6e13f7b56b97bb0f24e52fa694ee82191de49e2", size = 1278872, upload-time = "2026-03-01T01:25:53.657Z" }, + { url = "https://files.pythonhosted.org/packages/45/98/c7a3d33e53a769241b72c68737aaf6971fdca758987497ba25ddf43fdfa6/spec256k1-0.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:d00628217df1dd1fefea51b68a12409e5d88667d686f544c5ac52809024b8574", size = 1291623, upload-time = "2026-03-01T01:25:26.347Z" }, + { url = "https://files.pythonhosted.org/packages/bd/6e/0bced3476e6d60951594a13bcb4ac4ba4a8663f3fa29d75b36a176ce7b2c/spec256k1-0.2.3-cp312-cp312-win_arm64.whl", hash = "sha256:cf93a956519d8a5145d3f56645d154994dd31145cc20f5e032e7b95404294749", size = 1287074, upload-time = "2026-03-01T01:25:50.531Z" }, + { url = "https://files.pythonhosted.org/packages/ef/e4/b948b0ae56ae1fa7b01c2509f0433c9119150d46c71f76e0be38a7cfbd10/spec256k1-0.2.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:8f6727c0024ec0b3fcc09566bb29d27a70a2e0fbca6abe6e499b47f1d5d9a36d", size = 1439973, upload-time = "2026-03-01T01:25:10.144Z" }, + { url = "https://files.pythonhosted.org/packages/0f/d8/f1164fe7aacd051135c0f9e169ec6ab3bbe7f3dcc8b3b16ab361c758d1c2/spec256k1-0.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a443f38c37ffe7f2c90ef246edec07b5f0d5fb39aa20698ff96cb73024893cf5", size = 1445884, upload-time = "2026-03-01T01:25:06.244Z" }, + { url = "https://files.pythonhosted.org/packages/c9/5e/2a8da00d1699058f06e7cfb3854710c8875435089cabae0b644b10f89d90/spec256k1-0.2.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6de0cc4087c5199a391ee0812f1e5b25f435f2da6f36f76cc2c6c2a83aba351e", size = 1447895, upload-time = "2026-03-01T01:25:28.353Z" }, + { url = "https://files.pythonhosted.org/packages/f8/74/90bd78313ad67a0c67a6b5047f1536ce40d90bafd65a840865e7221a5d06/spec256k1-0.2.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0492eb194f45af578610caee939a546953fab1903928ca28766eef4862e4ff94", size = 1450869, upload-time = "2026-03-01T01:25:01.913Z" }, + { url = "https://files.pythonhosted.org/packages/bc/d5/e624e816a2f022eba0054032dbe406ca54455fbd4e6eee98592fe289a532/spec256k1-0.2.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2d905290d5b9e8fef7d32557155579752cf86a7eb1e15beb3d08240584b65eb8", size = 1590526, upload-time = "2026-03-01T01:26:12.023Z" }, + { url = "https://files.pythonhosted.org/packages/25/68/0db55f19854b8bc79b78c8ea516a226a962162bf2cf02258c897813b8d90/spec256k1-0.2.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a31edf0e902058fed2efc67b2e76f6a3cdcf203788fb5e85df39538367019f", size = 1469763, upload-time = "2026-03-01T01:26:40.769Z" }, + { url = "https://files.pythonhosted.org/packages/a6/9a/c3fe809ec508f5c6b1b53cc8ddb10e26a6e9c6558905d8590f15d251d702/spec256k1-0.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a57a56226d4f88802214dad38841e0aab2361c200543d52e4ed8384379a8c8e5", size = 1444736, upload-time = "2026-03-01T01:25:44.455Z" }, + { url = "https://files.pythonhosted.org/packages/a4/85/2d5fef2b69b1ad6da0192297434a758556736b369c47895d2b68b145030e/spec256k1-0.2.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b7f4a1b40f12bc41879a8690d04b8cc7a4f8fb51ea6fe28ad891659df07aa596", size = 1466188, upload-time = "2026-03-01T01:27:01.287Z" }, + { url = "https://files.pythonhosted.org/packages/6a/6d/6a0334b561e1be3d33bfb8ce62e6a0710bc67756ad3d1dadbc3ae55b7ca1/spec256k1-0.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8f6ee353b008d2d9a07b0af32b7f91d33565907c0548a10ebf67ee10b405193e", size = 1617982, upload-time = "2026-03-01T01:25:33.475Z" }, + { url = "https://files.pythonhosted.org/packages/8a/4d/dbacce7b149575808d7f24a39834252d8a9498cd8da043e8b18954c02ff3/spec256k1-0.2.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:02f86dd6c256d7d3113e1cad774f4ed346cea2e6f625cb53bd61a034e2d34012", size = 1720691, upload-time = "2026-03-01T01:26:50.211Z" }, + { url = "https://files.pythonhosted.org/packages/24/8e/1e81a624c013b24bd338b3b03a3b1ef90f5f75d5944ee1f783ab10c51f26/spec256k1-0.2.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:99e8669fa714310b6527aa2d2bcd6d4d716d0f38644b053fc24f8eace43c3e80", size = 1683330, upload-time = "2026-03-01T01:25:36.397Z" }, + { url = "https://files.pythonhosted.org/packages/47/3d/75a24aed44d8ac325342d40a7c67974f4929382a2e155bcf8aa2eff21090/spec256k1-0.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2ba1ddad6077241e23d005faa64ab5597bd4d8b8d0b8d1b82d4591b071e88de5", size = 1650023, upload-time = "2026-03-01T01:25:13.993Z" }, + { url = "https://files.pythonhosted.org/packages/ad/2e/4ae105bc321fdc0b67707c9e5dc2a27a2801b34f46812bbc391eb6a1b95b/spec256k1-0.2.3-cp313-cp313-win32.whl", hash = "sha256:2bccfe4774bbeaa0a518072c693af50ff8ed9175969ea835a7d240f811790dd1", size = 1278699, upload-time = "2026-03-01T01:26:58.137Z" }, + { url = "https://files.pythonhosted.org/packages/5e/e7/d5f6fabb52d23860f9f106136b507c4244cea119b84bd0ca5bd4fc981a42/spec256k1-0.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:327346c72f7d02b558b1245df99e34559704501de6b100933a4cdbb6a3301149", size = 1291234, upload-time = "2026-03-01T01:26:39.12Z" }, + { url = "https://files.pythonhosted.org/packages/6c/28/8f8af2b6abd4a10a306035d750beeb873e73c0cce960eaefbf0e773b2c62/spec256k1-0.2.3-cp313-cp313-win_arm64.whl", hash = "sha256:35a2008c0c7d32983832d8b8c8175beda5bab4965b25b49d59da17eec2447d29", size = 1286826, upload-time = "2026-03-01T01:24:57.657Z" }, + { url = "https://files.pythonhosted.org/packages/77/dd/f9f1882a26792d42128e71538387bb2ae4995bfc4e9e92abb1fceae6d96d/spec256k1-0.2.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03d251311eb97b5960cf2af6cdd64d569fa2d3a60b4a32afdf345da697965f8a", size = 1448279, upload-time = "2026-03-01T01:26:45.525Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e8/89a8a53577e9c8f40e2fa2788968efd70153e3cc617506c297c4ac070c75/spec256k1-0.2.3-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d360ed247de49db1b249dfa7e4a8b07f2b6a8a4719c79336dd314d581cc220bf", size = 1450376, upload-time = "2026-03-01T01:25:24.639Z" }, + { url = "https://files.pythonhosted.org/packages/17/ae/737f1f5e9f8c67332f8ef7ec4b7a8216f8e9ba24c41fb3420ddaab4142eb/spec256k1-0.2.3-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:42e9e03b82601e97473764e22b7211b14db97fe0a97fc991b162e39ab7334906", size = 1591350, upload-time = "2026-03-01T01:26:20.704Z" }, + { url = "https://files.pythonhosted.org/packages/8e/f7/c3e2c596a6b490d2cb58a08ffcf04e0d7516d838b3942a48e43452f945a7/spec256k1-0.2.3-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:79f71adbaa317e6a390e7e500226b67b1d42c3a291af1108d7c8d9ca704410cf", size = 1470259, upload-time = "2026-03-01T01:26:26.398Z" }, + { url = "https://files.pythonhosted.org/packages/ce/2a/25ff400b3003adb97e3c176c8add4c7c957fba2fd752d7b6e4d8ea48b790/spec256k1-0.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f6e83c34ad62699c71377d0c52d33cff7999ff1a834d5476d30eff0bff250eee", size = 1618293, upload-time = "2026-03-01T01:26:06.17Z" }, + { url = "https://files.pythonhosted.org/packages/fa/ea/14d3a6e557cb3393db525d7ccd6a26abb7223d01455b32f47cf926adf410/spec256k1-0.2.3-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:8880b1e75b2d6754534e99fe54b3330d762a9039c3b5db0ac11e29c91104d207", size = 1720157, upload-time = "2026-03-01T01:24:56.325Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f6/33fd221bce9437e5f7f0611d79afa1cf7ac6bd261f8026a0cd656518f57e/spec256k1-0.2.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:0e49117c863a19a3ba3c5cbde2d9538c44697ca71c29baae0e67c1a7ce2bb0fd", size = 1683309, upload-time = "2026-03-01T01:26:54.992Z" }, + { url = "https://files.pythonhosted.org/packages/b1/01/c25489fd32f885923d5f8c77b477d9bc3333767c34f3f8d901a79a21a80b/spec256k1-0.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e5cf17fb8aeabfbade210f6f29ca10f34f605d28f802dd2429dd93178e0117ad", size = 1651110, upload-time = "2026-03-01T01:25:42.969Z" }, + { url = "https://files.pythonhosted.org/packages/c8/7e/3d0738bad624f1bca68e33c19d7847475ffdc6244378a0c1b4cc6d444b89/spec256k1-0.2.3-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:eef3f1728c1064a2c50f03f64f4876a2c9dcaf679759e73f704356271f691dc9", size = 1440190, upload-time = "2026-03-01T01:26:16.318Z" }, + { url = "https://files.pythonhosted.org/packages/58/ac/61bf98c25436cbe22e0723a916489221a51a75ecbd6a6a09bb5640b3cc69/spec256k1-0.2.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9426796762d208d6a014dc8facc172af89fec6d28b6458e87c68fc033c20b8f0", size = 1445930, upload-time = "2026-03-01T01:26:13.601Z" }, + { url = "https://files.pythonhosted.org/packages/0f/50/8b6e0c4df6478c53d4d912085dfa8938be2623c5b68ec9e18ad1e6ca0ffb/spec256k1-0.2.3-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d11adc881e4eabc64ef021abfab5df8ebd612afbca6fa1e76fe1d6d2e5bfcd6d", size = 1448018, upload-time = "2026-03-01T01:26:47.169Z" }, + { url = "https://files.pythonhosted.org/packages/ee/50/4ab5bd79ece322febb1aecdc3bf68e629c02e511c16a0424ef6ef8fcfc9d/spec256k1-0.2.3-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:606af0b08c5968d961ab5ec335d46e088609288d230b39206de81565e24a1b8f", size = 1451056, upload-time = "2026-03-01T01:26:04.477Z" }, + { url = "https://files.pythonhosted.org/packages/23/65/bb760d44fdbbb8eb08e114294be8847e305b9ae007403fe81ce93b252231/spec256k1-0.2.3-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05b1529531846e001f21429fdff948d397c0aba29977e394fc22ec3d00000820", size = 1590389, upload-time = "2026-03-01T01:25:38.979Z" }, + { url = "https://files.pythonhosted.org/packages/24/c1/05912c3c81cda316926b40a713519119025bafe29862498c1f464ddffa64/spec256k1-0.2.3-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f288a3a257bb2b240a70517aa2a70f25444b91dd0103e9db88a1e10a66b32e63", size = 1469858, upload-time = "2026-03-01T01:26:14.918Z" }, + { url = "https://files.pythonhosted.org/packages/9d/cf/760779d3139d3a80aff2c6f89a5d194546b399da01e7e9bbd87651f2c754/spec256k1-0.2.3-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1001125bf9e0a68d5cdf4f08a4a3655f1a07217c83ac96dd474975060f8b9684", size = 1444844, upload-time = "2026-03-01T01:26:03.109Z" }, + { url = "https://files.pythonhosted.org/packages/87/db/cda60af72124b98393660d6a0610e6803de33f9ae5a9246e95a85ff408d8/spec256k1-0.2.3-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2e30b23e94a28653dc32b53c9c19270ed4e7fa98364c6093c8b7623390eff0d", size = 1466204, upload-time = "2026-03-01T01:25:57.161Z" }, + { url = "https://files.pythonhosted.org/packages/10/f7/9dd0ba375a5477db1b9620007bb051afcca9fe4c12f79136c971191c585c/spec256k1-0.2.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:aa5fdc4931374f1550093bc45deca77cd7d394a4db97f998c5173b605401045e", size = 1618057, upload-time = "2026-03-01T01:26:27.765Z" }, + { url = "https://files.pythonhosted.org/packages/1d/31/d756078954976813c6eb74b648c5d988e266d4a86a0bbd38c1b3a3e3c113/spec256k1-0.2.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:5626a36e4bd11321a8ab1fa8ecc7fb3708a1d7774911064a9c5f2bf27e28dc86", size = 1720833, upload-time = "2026-03-01T01:25:11.43Z" }, + { url = "https://files.pythonhosted.org/packages/0e/2f/95341e63c27681ed7feb9d22206c5bb0f1dcf854cf22e865e4119f310b8f/spec256k1-0.2.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:aee5000abc7a520d3bb9ab587d0426cf43fda6474851ca44485a66a706b56a46", size = 1683320, upload-time = "2026-03-01T01:25:34.837Z" }, + { url = "https://files.pythonhosted.org/packages/75/7c/60ceeb4ee26a8db4451ac9eda5db76e6954b9ea3975ddc4cb1d383ec65ba/spec256k1-0.2.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:0d03ffb16c3cccb651ca4f4b5c2751b658df72a0a38d62faacf0cdb7ef57e9d7", size = 1650163, upload-time = "2026-03-01T01:24:47.948Z" }, + { url = "https://files.pythonhosted.org/packages/9b/86/43414c9fb42ee5c634f1e31347612f2277532e1a1e75a92a4dd175babe23/spec256k1-0.2.3-cp314-cp314-win32.whl", hash = "sha256:09d633dbecf81f34c5f2632de88fba7a051c8b0968196b95308b2923417ea678", size = 1278818, upload-time = "2026-03-01T01:25:15.522Z" }, + { url = "https://files.pythonhosted.org/packages/b6/33/a6d16e5057522965562578f2164f8cbc2c71899f67f85dc475ee8cab233b/spec256k1-0.2.3-cp314-cp314-win_amd64.whl", hash = "sha256:42a6f956629fadee79ac15fa88ff9227f84bfbab8a964d3ed20d5787eaeb7601", size = 1291401, upload-time = "2026-03-01T01:26:07.402Z" }, + { url = "https://files.pythonhosted.org/packages/26/e8/fb36236bedc04f002e9c1b4bdcc4e733f6e203636d815c00d477d959b201/spec256k1-0.2.3-cp314-cp314-win_arm64.whl", hash = "sha256:08513f7b608dcd2e896dec7c4ced9efaa6a59441c4a5e7cbb85eac74d97580cc", size = 1287009, upload-time = "2026-03-01T01:24:50.91Z" }, + { url = "https://files.pythonhosted.org/packages/b2/62/36361204b3c986538e96798d0ef5065524588eccf74d48be09e7132c2e10/spec256k1-0.2.3-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:5b6a0d4de6a1783e79af058a226d7371444f6a403e24723f25726c534dcecfc5", size = 1439337, upload-time = "2026-03-01T01:26:48.932Z" }, + { url = "https://files.pythonhosted.org/packages/50/84/eca1447e5d19963af955f40a9d4ba78b14e48256efb4b4e1c3a686442062/spec256k1-0.2.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:27ecb88749b7348da44b16b77466215410d5e96260c0c2207b2d6ce0ff25fdaa", size = 1445105, upload-time = "2026-03-01T01:25:29.588Z" }, + { url = "https://files.pythonhosted.org/packages/92/5a/622a740fb18b849171077b9c1a24606446b52cee6fd24a97d24d3af688e1/spec256k1-0.2.3-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc60476d169214919aca03c0609314e08fb12a5d06a4937c77bcbd159fdd63cc", size = 1447529, upload-time = "2026-03-01T01:24:49.509Z" }, + { url = "https://files.pythonhosted.org/packages/c8/48/bdf74e2fdabb018e2a4589c4738aa94fa6b30b1c74b2f04660c3c2d81971/spec256k1-0.2.3-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed684a5ee96d599bcb3acb41dc29d736833bccb0b047bb3c1935772da0f2d808", size = 1450474, upload-time = "2026-03-01T01:25:08.901Z" }, + { url = "https://files.pythonhosted.org/packages/38/2e/16ee4571ad43c5d1fd7d53229512bf05ea7f5ec8e7b00775f4a3b9f70e06/spec256k1-0.2.3-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9edc54879e00a39b710464aa50850c34bad23d2c672d70d9798b4c57dbc6473", size = 1587976, upload-time = "2026-03-01T01:25:49.173Z" }, + { url = "https://files.pythonhosted.org/packages/e5/4a/4fb826560e4ac844544c091d1f069de62f6953cea1d59df5e98b7ef0a548/spec256k1-0.2.3-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b86ffe3c178144c39fddf97a36d22a2ce60ed9e770dfc1dc90ad5e74c3d4ae5", size = 1469209, upload-time = "2026-03-01T01:25:00.594Z" }, + { url = "https://files.pythonhosted.org/packages/67/ae/7800c0e16fdda27723cdedf11fc96de1daaf8fddbfcd5aa9638c807c096a/spec256k1-0.2.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c40433af36144b99e831443f17de87afd06efbc5d7228d4d0d531b8d606634b4", size = 1617711, upload-time = "2026-03-01T01:26:29.396Z" }, + { url = "https://files.pythonhosted.org/packages/bd/4f/f7710933e6574bf2586d4e71e856ed97af4f892e16d9cd76a73c1a4d5107/spec256k1-0.2.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:ec863a7a9d5e37f28125dcbabb681d0052185993f0d74380b46b94232153d8cf", size = 1720260, upload-time = "2026-03-01T01:26:53.547Z" }, + { url = "https://files.pythonhosted.org/packages/73/71/c63913a8cc364be62f39f7cca156e446dc1a24515b20bce8233e141026d4/spec256k1-0.2.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:83eb1aee4b380f5f0aa66e6896dd5f0da2fd2219087d072062e9a74b55b26de4", size = 1683322, upload-time = "2026-03-01T01:26:17.656Z" }, + { url = "https://files.pythonhosted.org/packages/fa/6b/f8784bee7170f1b3bc1b9593a78f97abbb22c1380bd440e725a3c23a7aa2/spec256k1-0.2.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2946f7474f3aa88a6d53abbac0b7004db224d2bba03bb64bfb90493ba8c6cea2", size = 1649828, upload-time = "2026-03-01T01:26:37.625Z" }, + { url = "https://files.pythonhosted.org/packages/25/d9/2eacd6eb4b557d0aaee921a8e2928aca771d9d0a262b7f9159bcc5e1fe04/spec256k1-0.2.3-cp314-cp314t-win_amd64.whl", hash = "sha256:6c3427adca3e7e8c62591d9ef9b7e5b58919d051d9d03c91717ad376185f6bef", size = 1291307, upload-time = "2026-03-01T01:25:41.554Z" }, + { url = "https://files.pythonhosted.org/packages/19/45/46180d1d8e106fbdb76178ba9988b50d97f3a003483390b77b9878d25d18/spec256k1-0.2.3-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5212ad33b074b55c47e50c0c60b21823f443278893777640d74d5c20f671a40f", size = 1450381, upload-time = "2026-03-01T01:26:42.463Z" }, + { url = "https://files.pythonhosted.org/packages/8f/fc/c81e1a80e70fe8ccd2b296ccfe6dbbe2c69faf522b52099fa132de1dcbe4/spec256k1-0.2.3-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:220deec56c06efaa72d5f5d47bff185692377da67922f2e19a117d6cb5258968", size = 1454226, upload-time = "2026-03-01T01:25:18.977Z" }, + { url = "https://files.pythonhosted.org/packages/50/f9/2e013eb09fdfb69c7f437cb8e78f5098d3775e95479d686a3b2826b09c1c/spec256k1-0.2.3-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9bae99cac293686babcec12d5fc6b87fbba5f2e4ef850b9e7f9ec93b2123a616", size = 1595037, upload-time = "2026-03-01T01:25:17.184Z" }, + { url = "https://files.pythonhosted.org/packages/49/11/3797fd444e0e2b6a02893b946ff8e81e6bf6ad89e3fbb0a4d6b6c0edb2f2/spec256k1-0.2.3-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a6c23d0d3f815fa199f0b1b68116869ed3255ea6a529145d06d4020ab71c2ee", size = 1472375, upload-time = "2026-03-01T01:25:20.273Z" }, + { url = "https://files.pythonhosted.org/packages/8b/1d/9e8a2ce8e77f7bf329563ebe92425f02488fa5d872170d9d415982c11cb7/spec256k1-0.2.3-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c1d76610d339936acfd2ca824611953be9172686a876a0d5d61e80f2114d927", size = 1448688, upload-time = "2026-03-01T01:26:01.526Z" }, + { url = "https://files.pythonhosted.org/packages/a6/5e/6b418d3fe674b0a044c6e74d4717ae030dff56ebc162480a145a3a64d5df/spec256k1-0.2.3-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:eb55ca60f15d4d957d834fd5af923a2bb338ea8d1f611ea39dbefae4e676c4ad", size = 1470298, upload-time = "2026-03-01T01:26:09.039Z" }, + { url = "https://files.pythonhosted.org/packages/63/53/70ce8c7f8d47eef54b75ba36ca424c703ed73afaabf05c09f2f89ca64510/spec256k1-0.2.3-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:f27cd39a26ea5ad3a9bc2f77fc26d32527ac498e582ba99e15cdbef3b0f3cba0", size = 1621075, upload-time = "2026-03-01T01:25:45.977Z" }, + { url = "https://files.pythonhosted.org/packages/53/9a/70a82521e36b659ffb9b0c45ba47a7592977d134b726d1139013752461d5/spec256k1-0.2.3-pp311-pypy311_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:bc6cf226a5d83733751e3ae89d12903d25c8b14c6230c512563c25eb7a20cffb", size = 1724592, upload-time = "2026-03-01T01:25:22.065Z" }, + { url = "https://files.pythonhosted.org/packages/13/8d/f0818213a49b4283db829502511bfb4577d31d6d7dc0414aaa333c45f2e8/spec256k1-0.2.3-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:6f4c3a95ab2eaf05930fd7f3448b435ac2e8eb55db5f1ea4c8c2758c2f1576e7", size = 1688064, upload-time = "2026-03-01T01:25:40.338Z" }, + { url = "https://files.pythonhosted.org/packages/fe/9a/ba05a57f8fc100cff80d9d378ebf0a6cc5c330e237a4272d255de694ed56/spec256k1-0.2.3-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:de2f7c9688e3722e2a72777f75f4d11cb827fc04a55ea313aabba2b0987709e7", size = 1653368, upload-time = "2026-03-01T01:26:10.414Z" }, +] + [[package]] name = "tenacity" version = "9.1.2" diff --git a/whitelist.txt b/whitelist.txt index 47c7f750b07..45853dd37b6 100644 --- a/whitelist.txt +++ b/whitelist.txt @@ -301,7 +301,6 @@ coeffs cofactor cofactors coinbase -coincurve collectonly commandline commonpath From 59940cae760bca899677c1bf4e50124131a3f004 Mon Sep 17 00:00:00 2001 From: spencer <spencer.taylor-brown@ethereum.org> Date: Tue, 14 Jul 2026 12:12:17 +0100 Subject: [PATCH 124/233] fix(spec-specs, tests): EIP-2780 review follow-ups (#3164) --- .../forks/amsterdam/vm/eoa_delegation.py | 17 +++++---- .../forks/amsterdam/vm/interpreter.py | 7 ++++ .../test_state_gas_create.py | 36 +++++++++++-------- 3 files changed, 39 insertions(+), 21 deletions(-) diff --git a/src/ethereum/forks/amsterdam/vm/eoa_delegation.py b/src/ethereum/forks/amsterdam/vm/eoa_delegation.py index 7454ee0f115..f2c69d5e7d8 100644 --- a/src/ethereum/forks/amsterdam/vm/eoa_delegation.py +++ b/src/ethereum/forks/amsterdam/vm/eoa_delegation.py @@ -208,10 +208,11 @@ def set_delegation(evm: Evm) -> None: account leaf does not yet exist. - ``GasCosts.ACCOUNT_WRITE`` (regular) when applying the authorization is the transaction's first write to the authority's - leaf. The sender's leaf was already written at inclusion (priced - into ``TX_BASE``), so a self-sponsored authority pays no - ``ACCOUNT_WRITE``, and repeated authorizations on one authority - pay it once. + leaf. Writes the transaction already prices elsewhere are + exempt: the sender's, covered by ``TX_BASE``, and, for a + value-bearing transaction, the recipient's, covered by + ``TX_VALUE_COST``. Repeated authorizations on one authority pay + it once. - ``StateGasCosts.AUTH_BASE`` (state) when a net-new delegation indicator is written: the authority held no delegation before the transaction, none was set for it earlier in the transaction, and @@ -232,9 +233,11 @@ def set_delegation(evm: Evm) -> None: """ message = evm.message tx_state = message.tx_env.state - # Accounts this transaction has already written: the sender's leaf - # was written at inclusion (nonce bump and fee deduction). The - # recipient is written when value is transferred. + # Accounts whose write the transaction has already priced: the + # sender's leaf was written at inclusion (nonce bump and fee + # deduction), and a value-bearing transaction prepays the + # recipient's balance write -- the transfer itself only happens at + # frame entry, after these charges. written_accounts: Set[Address] = {message.tx_env.origin} if evm.message.tx_env.value > U256(0): written_accounts.add(evm.message.current_target) diff --git a/src/ethereum/forks/amsterdam/vm/interpreter.py b/src/ethereum/forks/amsterdam/vm/interpreter.py index d24f930726c..c820a1b8748 100644 --- a/src/ethereum/forks/amsterdam/vm/interpreter.py +++ b/src/ethereum/forks/amsterdam/vm/interpreter.py @@ -260,6 +260,13 @@ def prepare_dispatch(evm: Evm) -> None: cold account access and pointing the frame at the delegated code. + The creation target is checked against the transaction pre-state: + ``process_create_message`` has already bumped the target's nonce + by the time this runs, so a live check would always see the + account. The recipient check is live, so an authority + materialized earlier in the transaction is not charged + ``NEW_ACCOUNT`` again. + This function must not mutate the transaction state. Every charge here pays for state that only materializes inside the dispatched frame and rolls back with it, so these charges stay refillable -- diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py index f277307697f..ec35b027114 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py @@ -1374,6 +1374,9 @@ def test_create_tx_header_gas_used( floor = fork.transaction_data_floor_cost_calculator()( data=bytes(initcode), contract_creation=True ) + assert floor > regular_intrinsic, ( + "the floor must bind for this arm to pin floor-in-header" + ) expected_gas_used = max(regular_intrinsic, floor) else: # For a minimal CREATE tx deploying Op.STOP (1 byte), @@ -2145,12 +2148,15 @@ def test_create_account_charge_reduces_child_gas( @pytest.mark.parametrize( - "init_code", + ("init_code", "floor_binds"), [ pytest.param( - Op.REVERT(0, 10_000, new_memory_size=10_000), id="revert" + Op.REVERT(0, 10_000, new_memory_size=10_000), + False, + id="revert", ), - pytest.param(Op.INVALID, id="halt"), + pytest.param(Op.REVERT(0, 0), True, id="revert_floor_bound"), + pytest.param(Op.INVALID, None, id="halt"), ], ) @pytest.mark.valid_from("EIP8037") @@ -2159,6 +2165,7 @@ def test_failed_create_tx_refills_top_frame_new_account( pre: Alloc, fork: Fork, init_code: Bytecode, + floor_binds: bool | None, ) -> None: """ Verify the top-frame NEW_ACCOUNT of a creation tx is refilled when the @@ -2172,10 +2179,11 @@ def test_failed_create_tx_refills_top_frame_new_account( * REVERT preserves ``gas_left`` and ``refill_frame_state_gas`` returns the spilled ``NEW_ACCOUNT`` to it, so the state block nets to zero - and only the regular consumption counts as work. The tiny init code - leaves the decomposed calldata floor above that consumption, so the - amount billed (receipt) is pinned to the floor while the header - excludes the floor top-up. + and only the regular consumption counts as work. The calldata floor + tops up the billed amount and the block-level regular gas alike, so + receipt and header agree at the greater of consumption and floor: + the memory expansion keeps ``revert`` above the floor, while the + bare ``revert_floor_bound`` pins the floor in both. * HALT (INVALID) refills the spilled ``NEW_ACCOUNT`` to ``gas_left`` and then burns all of it, so the sender pays the full ``gas_limit``. """ @@ -2200,19 +2208,19 @@ def test_failed_create_tx_refills_top_frame_new_account( # Exceptional halt burns all gas_left (the refilled NEW_ACCOUNT # included). expected_gas_used = gas_limit - expected_header_gas = gas_limit else: # REVERT refills the spilled NEW_ACCOUNT, netting the state block - # to zero, so only the regular consumption counts as work. + # to zero, so only the regular consumption counts as work. The + # calldata floor binds the billed amount and the block-level + # regular gas alike, so receipt and header agree either way. regular_consumed = intrinsic_regular + init_code.regular_cost(fork) - # The tiny init code leaves the decomposed calldata floor above - # the regular gas consumed: the receipt bills at the floor, while - # the header's regular-gas accounting excludes the floor top-up. floor = fork.transaction_data_floor_cost_calculator()( data=bytes(init_code), contract_creation=True ) + assert (floor > regular_consumed) == floor_binds, ( + "init code lands on the wrong side of the floor" + ) expected_gas_used = max(regular_consumed, floor) - expected_header_gas = regular_consumed sender = pre.fund_eoa() created = compute_create_address(address=sender, nonce=0) @@ -2231,7 +2239,7 @@ def test_failed_create_tx_refills_top_frame_new_account( pre=pre, post={created: Account.NONEXISTENT}, tx=tx, - blockchain_test_header_verify=Header(gas_used=expected_header_gas), + blockchain_test_header_verify=Header(gas_used=expected_gas_used), ) From f34ff59046e7c31ffbd2dd18124554c71fe28e41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= <pawel@hepcolgum.band> Date: Tue, 14 Jul 2026 14:32:03 +0200 Subject: [PATCH 125/233] feat(tests): restore sstore_combinations as a hand-written test (#3154) * feat(tests): restore sstore_combinations as a hand-written test test_sstore_combinations_initial.py, added in #2623 as the consolidated replacement for the twelve legacy sstore_combinations fillers, was deleted accidentally by the ported-static regeneration sweep in #2695: the sweep only preserves generator outputs and files tagged @manually-enhanced, and the slow-marked test was also absent from the trace-verification baseline, so the deletion went unnoticed and the twelve fillers were left with no coverage at all. Restore it as tests/istanbul/eip2200_net_gas_metering/ test_sstore_combinations.py, where hand-written tests are never touched by regeneration, keeping the ported_from lineage. Delete the twelve docstring-only stub files: the regeneration guard they provided is now a CONSOLIDATED_FILLERS skip set in the filler_to_python generator itself. Also shrink the matrix from 5187 to 774 cases per fork, with each reduction step verified by tracing EELS before and after. The middle-action parametrization drops from 12 to 8 combinations per slot: for the no-op and reverting side contracts CALL and CALLCODE produce identical executions, as do DELEGATECALL and STATICCALL, so one opcode of each pair is kept. CALLCODE is dropped from the update-contract slots (call_1, call_3) because it produces the same storage transitions as the retained DELEGATECALL. STATICCALL leaves those slots for a dedicated staticcall_only test: in the matrix it contributed a single faulting behavior already covered extensively by the stStaticCall and eip214_staticcall tests, and the variant unique to this file, a faulting SSTORE that observes a dirty slot, is kept via the new dirty parameter. Each call forwards SSTORE_TOGGLE_CODE.gas_cost(fork), a worst-case bound on any callee's consumption, instead of the legacy fixed 0x493E0 that made many variants run out of gas mid-call; traces of the final test contain no OutOfGas, so every storage scenario executes in full. The transaction gas_limit is left for the framework to auto-fill. The slow marker is dropped so the test actually runs in CI. * refactor(tests): drop CALLCODE from sstore_combinations update visits CALLCODE and DELEGATECALL execute the update contract's code against the same creator-frame storage with the same writes, so they produce identical storage transitions; the only difference is the constant code-size delta of the extra value argument. Keep DELEGATECALL and drop CALLCODE from the call_1 and call_3 slots (2307 to 1155 cases). CALLCODE stays in MIDDLE_ACTIONS where it targets the storage-toggling contract. Traced before and after: the only removed SSTORE signatures are the three CALLCODE-chain variants whose DELEGATECALL twins remain. * refactor(tests): pull STATICCALL out of the sstore_combinations matrix A STATICCALL to the update contract faults on the first SSTORE, so as a call_3 value it contributed one behavior while occupying a third of the matrix. The basic SSTORE-in-static-context fault is already covered extensively elsewhere (tests/ported_static/stStaticCall, over 270 files, and tests/byzantium/eip214_staticcall). The only variants unique to this file, faulting attempts observing a dirty slot whose current value differs from the original, move to test_sstore_combinations_initial_staticcall_only via a new dirty parameter that runs a plain CALL before the STATICCALL. 1155 to 774 cases. Traced before and after: the SSTORE signature set is unchanged, including all six static-attempt variants. * refactor(tests): apply review suggestions to sstore_combinations Import Fork and Op from the execution_testing top level, reuse UPDATE_CONTRACT_CODE for the update-contract deployments, size the per-call gas from SSTORE_TOGGLE_CODE.gas_cost(fork) so that no variant runs out of gas at any point, and drop the explicit transaction gas_limit so it auto-fills to the fork maximum. --- scripts/filler_to_python/__main__.py | 26 ++ .../eip2200_net_gas_metering/__init__.py | 3 + .../test_sstore_combinations.py | 239 ++++++++++++++++++ .../ported_static/stTimeConsuming/__init__.py | 1 - ...t_sstore_combinations_initial00_2_paris.py | 9 - ...est_sstore_combinations_initial00_paris.py | 9 - ...t_sstore_combinations_initial01_2_paris.py | 9 - ...est_sstore_combinations_initial01_paris.py | 9 - ...t_sstore_combinations_initial10_2_paris.py | 9 - ...est_sstore_combinations_initial10_paris.py | 9 - ...t_sstore_combinations_initial11_2_paris.py | 9 - ...est_sstore_combinations_initial11_paris.py | 9 - ...t_sstore_combinations_initial20_2_paris.py | 9 - ...est_sstore_combinations_initial20_paris.py | 9 - ...t_sstore_combinations_initial21_2_paris.py | 9 - ...est_sstore_combinations_initial21_paris.py | 9 - 16 files changed, 268 insertions(+), 109 deletions(-) create mode 100644 tests/istanbul/eip2200_net_gas_metering/__init__.py create mode 100644 tests/istanbul/eip2200_net_gas_metering/test_sstore_combinations.py delete mode 100644 tests/ported_static/stTimeConsuming/__init__.py delete mode 100644 tests/ported_static/stTimeConsuming/test_sstore_combinations_initial00_2_paris.py delete mode 100644 tests/ported_static/stTimeConsuming/test_sstore_combinations_initial00_paris.py delete mode 100644 tests/ported_static/stTimeConsuming/test_sstore_combinations_initial01_2_paris.py delete mode 100644 tests/ported_static/stTimeConsuming/test_sstore_combinations_initial01_paris.py delete mode 100644 tests/ported_static/stTimeConsuming/test_sstore_combinations_initial10_2_paris.py delete mode 100644 tests/ported_static/stTimeConsuming/test_sstore_combinations_initial10_paris.py delete mode 100644 tests/ported_static/stTimeConsuming/test_sstore_combinations_initial11_2_paris.py delete mode 100644 tests/ported_static/stTimeConsuming/test_sstore_combinations_initial11_paris.py delete mode 100644 tests/ported_static/stTimeConsuming/test_sstore_combinations_initial20_2_paris.py delete mode 100644 tests/ported_static/stTimeConsuming/test_sstore_combinations_initial20_paris.py delete mode 100644 tests/ported_static/stTimeConsuming/test_sstore_combinations_initial21_2_paris.py delete mode 100644 tests/ported_static/stTimeConsuming/test_sstore_combinations_initial21_paris.py diff --git a/scripts/filler_to_python/__main__.py b/scripts/filler_to_python/__main__.py index 83b13924c95..35d7d6c6edb 100644 --- a/scripts/filler_to_python/__main__.py +++ b/scripts/filler_to_python/__main__.py @@ -18,6 +18,25 @@ MANUALLY_ENHANCED_TAG = "@manually-enhanced" +# Fillers consolidated into hand-written tests outside +# ``tests/ported_static``; never regenerate them. +# sstore_combinations_*: +# tests/istanbul/eip2200_net_gas_metering/test_sstore_combinations.py +CONSOLIDATED_FILLERS = { + "sstore_combinations_initial00_ParisFiller", + "sstore_combinations_initial00_2_ParisFiller", + "sstore_combinations_initial01_ParisFiller", + "sstore_combinations_initial01_2_ParisFiller", + "sstore_combinations_initial10_ParisFiller", + "sstore_combinations_initial10_2_ParisFiller", + "sstore_combinations_initial11_ParisFiller", + "sstore_combinations_initial11_2_ParisFiller", + "sstore_combinations_initial20_ParisFiller", + "sstore_combinations_initial20_2_ParisFiller", + "sstore_combinations_initial21_ParisFiller", + "sstore_combinations_initial21_2_ParisFiller", +} + def _has_manually_enhanced_tag(file_path: Path) -> bool: """Check if a file has @manually-enhanced in its module docstring.""" @@ -136,6 +155,13 @@ def process_single_filler( Return "ok", "fail", "warn", or "skip". """ try: + if filler_path.stem in CONSOLIDATED_FILLERS: + logger.info( + "SKIP: %s (consolidated into a hand-written test)", + filler_path, + ) + return "skip" + # Relative path for the generated test's ported_from marker try: rel_path = filler_path.relative_to(fillers_base.parent) diff --git a/tests/istanbul/eip2200_net_gas_metering/__init__.py b/tests/istanbul/eip2200_net_gas_metering/__init__.py new file mode 100644 index 00000000000..01c4ace8dd6 --- /dev/null +++ b/tests/istanbul/eip2200_net_gas_metering/__init__.py @@ -0,0 +1,3 @@ +""" +Tests [EIP-2200: Structured Definitions for Net Gas Metering](https://eips.ethereum.org/EIPS/eip-2200). +""" diff --git a/tests/istanbul/eip2200_net_gas_metering/test_sstore_combinations.py b/tests/istanbul/eip2200_net_gas_metering/test_sstore_combinations.py new file mode 100644 index 00000000000..825296ba985 --- /dev/null +++ b/tests/istanbul/eip2200_net_gas_metering/test_sstore_combinations.py @@ -0,0 +1,239 @@ +"""Tests for SSTORE combinations across nested call types.""" + +from enum import StrEnum + +import pytest +from execution_testing import ( + Account, + Address, + Alloc, + Bytecode, + Fork, + Op, + StateTestFiller, + Transaction, + compute_create_address, +) + +REFERENCE_SPEC_GIT_PATH = "EIPS/eip-2200.md" +REFERENCE_SPEC_VERSION = "ad4eaaa1fe5c7aa394b2ab09e885b73b898f5da0" + +pytestmark = [ + pytest.mark.ported_from( + "state_tests/stTimeConsuming/sstore_combinations_initial00_ParisFiller.json", + "state_tests/stTimeConsuming/sstore_combinations_initial00_2_ParisFiller.json", + "state_tests/stTimeConsuming/sstore_combinations_initial01_ParisFiller.json", + "state_tests/stTimeConsuming/sstore_combinations_initial01_2_ParisFiller.json", + "state_tests/stTimeConsuming/sstore_combinations_initial10_ParisFiller.json", + "state_tests/stTimeConsuming/sstore_combinations_initial10_2_ParisFiller.json", + "state_tests/stTimeConsuming/sstore_combinations_initial11_ParisFiller.json", + "state_tests/stTimeConsuming/sstore_combinations_initial11_2_ParisFiller.json", + "state_tests/stTimeConsuming/sstore_combinations_initial20_ParisFiller.json", + "state_tests/stTimeConsuming/sstore_combinations_initial20_2_ParisFiller.json", + "state_tests/stTimeConsuming/sstore_combinations_initial21_ParisFiller.json", + "state_tests/stTimeConsuming/sstore_combinations_initial21_2_ParisFiller.json", + ), + pytest.mark.valid_from("Byzantium"), +] + + +# Writes slots 0..2 of the executing frame's storage account. +UPDATE_CONTRACT_CODE = ( + Op.SSTORE(key=0x0, value=0x0) + + Op.SSTORE(key=0x1, value=0x1) + + Op.SSTORE(key=0x2, value=0x2) + + Op.STOP +) + +# Flip slots 1..16 to accumulate net-metering refunds, then set slot 1. +SSTORE_TOGGLE_CODE = ( + sum( + Op.SSTORE(key=i, value=0x1) + Op.SSTORE(key=i, value=0x0) + for i in range(0x1, 0x10 + 1) + ) + + Op.SSTORE(key=0x1, value=0x1) + + Op.STOP +) + + +class MidContractActions(StrEnum): + """List of actions the middle contracts can perform.""" + + NOOP = "noop" + SSTORE_TOGGLE = "sstore-toggle" + REVERT = "revert" + + +# Middle-action combinations: (call_opcode, side_contract_kind). +# The no-op contract has no code and the reverting contract performs no +# SSTORE, so for those two targets the calling opcode is unobservable: +# CALL and CALLCODE produce identical executions, as do DELEGATECALL and +# STATICCALL (confirmed by tracing all combinations), and one opcode of +# each pair is kept. The storage-toggling contract behaves differently +# under each opcode, so all four are kept there. +MIDDLE_ACTIONS = [ + (op, MidContractActions.SSTORE_TOGGLE) + for op in [ + Op.CALL, + Op.CALLCODE, + Op.DELEGATECALL, + Op.STATICCALL, + ] +] + [ + (op, t) + for op in [Op.CALL, Op.DELEGATECALL] + for t in [MidContractActions.NOOP, MidContractActions.REVERT] +] + + +@pytest.mark.parametrize("initial", range(3)) +@pytest.mark.parametrize("call_4, call_4_target", MIDDLE_ACTIONS) +@pytest.mark.parametrize("call_3", [Op.CALL, Op.DELEGATECALL]) +@pytest.mark.parametrize("call_2, call_2_target", MIDDLE_ACTIONS) +@pytest.mark.parametrize("call_1", [Op.CALL, Op.DELEGATECALL]) +def test_sstore_combinations_initial( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + initial: int, + call_1: Op, + call_2: Op, + call_2_target: MidContractActions, + call_3: Op, + call_4: Op, + call_4_target: MidContractActions, +) -> None: + """ + Test SSTORE with four interleaved calls. + + Exercises every combination of call types across four call slots, + varying the update-contract's initial storage state (0, 1, or 2). + Valid from Byzantium (REVERT and STATICCALL availability) so the + pre-EIP-2200 storage gas rules are covered as a baseline. + Consolidated replacement for the twelve legacy + ``sstore_combinations_initial*_ParisFiller.json`` fillers from + ``state_tests/stTimeConsuming/``. + """ + sender = pre.fund_eoa() + + def deploy_side(kind: MidContractActions) -> Address: + if kind == MidContractActions.NOOP: + return pre.deploy_contract(Bytecode()) + if kind == MidContractActions.SSTORE_TOGGLE: + return pre.deploy_contract(code=SSTORE_TOGGLE_CODE) + return pre.deploy_contract( + code=Op.REVERT(offset=0x0, size=0x20) + Op.STOP, + ) + + side = { + kind: deploy_side(kind) + for kind in MidContractActions + if kind + in {call_2_target, call_4_target, MidContractActions.SSTORE_TOGGLE} + } + + update_contract = pre.deploy_contract( + code=UPDATE_CONTRACT_CODE, + storage={0: initial, 1: initial, 2: initial} if initial > 0 else {}, + ) + sstore_toggle = side[MidContractActions.SSTORE_TOGGLE] + + call_gas = SSTORE_TOGGLE_CODE.gas_cost(fork) + + initcode = ( + Op.MSTORE(offset=0x64, value=0x0) + + Op.POP( + call_1( + gas=call_gas, + address=update_contract, + args_size=0x20, + ) + ) + + Op.POP(call_2(gas=call_gas, address=side[call_2_target])) + + Op.POP( + call_3( + gas=call_gas, + address=update_contract, + args_size=0x20, + ) + ) + + Op.POP(call_4(gas=call_gas, address=side[call_4_target])) + + Op.CALL(gas=call_gas, address=sstore_toggle) + + Op.STOP + ) + + tx = Transaction( + sender=sender, + to=None, + data=initcode, + value=1, + protected=fork.supports_protected_txs(), + ) + + post = { + sstore_toggle: Account(storage={1: 1}), + compute_create_address(address=sender, nonce=0): Account(nonce=1), + } + + state_test(pre=pre, post=post, tx=tx) + + +@pytest.mark.parametrize("dirty", [False, True]) +@pytest.mark.parametrize("initial", range(3)) +def test_sstore_combinations_initial_staticcall_only( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + initial: int, + dirty: bool, +) -> None: + """ + Test a STATICCALL to the update contract, whose SSTORE must fault. + + With dirty=True a plain CALL to the update contract runs first, so + the faulting SSTORE observes a slot whose current value already + differs from its original value. + """ + sender = pre.fund_eoa() + + update_contract = pre.deploy_contract( + code=UPDATE_CONTRACT_CODE, + storage={0: initial, 1: initial, 2: initial} if initial > 0 else {}, + ) + sstore_toggle = pre.deploy_contract(code=SSTORE_TOGGLE_CODE) + + call_gas = SSTORE_TOGGLE_CODE.gas_cost(fork) + + dirtying_call = ( + Op.POP(Op.CALL(gas=call_gas, address=update_contract, args_size=0x20)) + if dirty + else Bytecode() + ) + initcode = ( + Op.MSTORE(offset=0x64, value=0x0) + + dirtying_call + + Op.POP( + Op.STATICCALL( + gas=call_gas, + address=update_contract, + args_size=0x20, + ) + ) + + Op.CALL(gas=call_gas, address=sstore_toggle) + + Op.STOP + ) + + tx = Transaction( + sender=sender, + to=None, + data=initcode, + value=1, + protected=fork.supports_protected_txs(), + ) + + post = { + sstore_toggle: Account(storage={1: 1}), + compute_create_address(address=sender, nonce=0): Account(nonce=1), + } + + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stTimeConsuming/__init__.py b/tests/ported_static/stTimeConsuming/__init__.py deleted file mode 100644 index 08a79b03774..00000000000 --- a/tests/ported_static/stTimeConsuming/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Ported static tests: stTimeConsuming.""" # noqa: N999 diff --git a/tests/ported_static/stTimeConsuming/test_sstore_combinations_initial00_2_paris.py b/tests/ported_static/stTimeConsuming/test_sstore_combinations_initial00_2_paris.py deleted file mode 100644 index f3a7206c048..00000000000 --- a/tests/ported_static/stTimeConsuming/test_sstore_combinations_initial00_2_paris.py +++ /dev/null @@ -1,9 +0,0 @@ -""" -Sstore 0 -> {calltype} -> change to {0, 1, 2} |-> {calltype} -> {non,... - -Ported from: -state_tests/stTimeConsuming/sstore_combinations_initial00_2_ParisFiller.json - -@manually-enhanced: Do not overwrite. This test has been manually reviewed and -enhanced. -""" diff --git a/tests/ported_static/stTimeConsuming/test_sstore_combinations_initial00_paris.py b/tests/ported_static/stTimeConsuming/test_sstore_combinations_initial00_paris.py deleted file mode 100644 index 9c821887c4e..00000000000 --- a/tests/ported_static/stTimeConsuming/test_sstore_combinations_initial00_paris.py +++ /dev/null @@ -1,9 +0,0 @@ -""" -Sstore 0 -> {calltype} -> change to {0, 1, 2} |-> {calltype} -> {non,... - -Ported from: -state_tests/stTimeConsuming/sstore_combinations_initial00_ParisFiller.json - -@manually-enhanced: Do not overwrite. This test has been manually reviewed and -enhanced. -""" diff --git a/tests/ported_static/stTimeConsuming/test_sstore_combinations_initial01_2_paris.py b/tests/ported_static/stTimeConsuming/test_sstore_combinations_initial01_2_paris.py deleted file mode 100644 index dc958c5727b..00000000000 --- a/tests/ported_static/stTimeConsuming/test_sstore_combinations_initial01_2_paris.py +++ /dev/null @@ -1,9 +0,0 @@ -""" -Sstore 0 -> {calltype} -> change to {0, 1, 2} |-> {calltype} -> {non,... - -Ported from: -state_tests/stTimeConsuming/sstore_combinations_initial01_2_ParisFiller.json - -@manually-enhanced: Do not overwrite. This test has been manually reviewed and -enhanced. -""" diff --git a/tests/ported_static/stTimeConsuming/test_sstore_combinations_initial01_paris.py b/tests/ported_static/stTimeConsuming/test_sstore_combinations_initial01_paris.py deleted file mode 100644 index a250ef8d6d3..00000000000 --- a/tests/ported_static/stTimeConsuming/test_sstore_combinations_initial01_paris.py +++ /dev/null @@ -1,9 +0,0 @@ -""" -Sstore 0 -> {calltype} -> change to {0, 1, 2} |-> {calltype} -> {non,... - -Ported from: -state_tests/stTimeConsuming/sstore_combinations_initial01_ParisFiller.json - -@manually-enhanced: Do not overwrite. This test has been manually reviewed and -enhanced. -""" diff --git a/tests/ported_static/stTimeConsuming/test_sstore_combinations_initial10_2_paris.py b/tests/ported_static/stTimeConsuming/test_sstore_combinations_initial10_2_paris.py deleted file mode 100644 index 6683ac46497..00000000000 --- a/tests/ported_static/stTimeConsuming/test_sstore_combinations_initial10_2_paris.py +++ /dev/null @@ -1,9 +0,0 @@ -""" -Sstore 1 -> {calltype} -> change to {0, 1, 2} |-> {calltype} -> {non,... - -Ported from: -state_tests/stTimeConsuming/sstore_combinations_initial10_2_ParisFiller.json - -@manually-enhanced: Do not overwrite. This test has been manually reviewed and -enhanced. -""" diff --git a/tests/ported_static/stTimeConsuming/test_sstore_combinations_initial10_paris.py b/tests/ported_static/stTimeConsuming/test_sstore_combinations_initial10_paris.py deleted file mode 100644 index 4d42979ef1a..00000000000 --- a/tests/ported_static/stTimeConsuming/test_sstore_combinations_initial10_paris.py +++ /dev/null @@ -1,9 +0,0 @@ -""" -Sstore 1 -> {calltype} -> change to {0, 1, 2} |-> {calltype} -> {non,... - -Ported from: -state_tests/stTimeConsuming/sstore_combinations_initial10_ParisFiller.json - -@manually-enhanced: Do not overwrite. This test has been manually reviewed and -enhanced. -""" diff --git a/tests/ported_static/stTimeConsuming/test_sstore_combinations_initial11_2_paris.py b/tests/ported_static/stTimeConsuming/test_sstore_combinations_initial11_2_paris.py deleted file mode 100644 index b5dbadc5c67..00000000000 --- a/tests/ported_static/stTimeConsuming/test_sstore_combinations_initial11_2_paris.py +++ /dev/null @@ -1,9 +0,0 @@ -""" -Sstore 1 -> {calltype} -> change to {0, 1, 2} |-> {calltype} -> {non,... - -Ported from: -state_tests/stTimeConsuming/sstore_combinations_initial11_2_ParisFiller.json - -@manually-enhanced: Do not overwrite. This test has been manually reviewed and -enhanced. -""" diff --git a/tests/ported_static/stTimeConsuming/test_sstore_combinations_initial11_paris.py b/tests/ported_static/stTimeConsuming/test_sstore_combinations_initial11_paris.py deleted file mode 100644 index b736702bf1e..00000000000 --- a/tests/ported_static/stTimeConsuming/test_sstore_combinations_initial11_paris.py +++ /dev/null @@ -1,9 +0,0 @@ -""" -Sstore 1 -> {calltype} -> change to {0, 1, 2} |-> {calltype} -> {non,... - -Ported from: -state_tests/stTimeConsuming/sstore_combinations_initial11_ParisFiller.json - -@manually-enhanced: Do not overwrite. This test has been manually reviewed and -enhanced. -""" diff --git a/tests/ported_static/stTimeConsuming/test_sstore_combinations_initial20_2_paris.py b/tests/ported_static/stTimeConsuming/test_sstore_combinations_initial20_2_paris.py deleted file mode 100644 index 769752765cc..00000000000 --- a/tests/ported_static/stTimeConsuming/test_sstore_combinations_initial20_2_paris.py +++ /dev/null @@ -1,9 +0,0 @@ -""" -Sstore 2 -> {calltype} -> change to {0, 1, 2} |-> {calltype} -> {non,... - -Ported from: -state_tests/stTimeConsuming/sstore_combinations_initial20_2_ParisFiller.json - -@manually-enhanced: Do not overwrite. This test has been manually reviewed and -enhanced. -""" diff --git a/tests/ported_static/stTimeConsuming/test_sstore_combinations_initial20_paris.py b/tests/ported_static/stTimeConsuming/test_sstore_combinations_initial20_paris.py deleted file mode 100644 index 80db78e743d..00000000000 --- a/tests/ported_static/stTimeConsuming/test_sstore_combinations_initial20_paris.py +++ /dev/null @@ -1,9 +0,0 @@ -""" -Sstore 2 -> {calltype} -> change to {0, 1, 2} |-> {calltype} -> {non,... - -Ported from: -state_tests/stTimeConsuming/sstore_combinations_initial20_ParisFiller.json - -@manually-enhanced: Do not overwrite. This test has been manually reviewed and -enhanced. -""" diff --git a/tests/ported_static/stTimeConsuming/test_sstore_combinations_initial21_2_paris.py b/tests/ported_static/stTimeConsuming/test_sstore_combinations_initial21_2_paris.py deleted file mode 100644 index 6e11d14c05a..00000000000 --- a/tests/ported_static/stTimeConsuming/test_sstore_combinations_initial21_2_paris.py +++ /dev/null @@ -1,9 +0,0 @@ -""" -Sstore 2 -> {calltype} -> change to {0, 1, 2} |-> {calltype} -> {non,... - -Ported from: -state_tests/stTimeConsuming/sstore_combinations_initial21_2_ParisFiller.json - -@manually-enhanced: Do not overwrite. This test has been manually reviewed and -enhanced. -""" diff --git a/tests/ported_static/stTimeConsuming/test_sstore_combinations_initial21_paris.py b/tests/ported_static/stTimeConsuming/test_sstore_combinations_initial21_paris.py deleted file mode 100644 index 5d34ffec3c6..00000000000 --- a/tests/ported_static/stTimeConsuming/test_sstore_combinations_initial21_paris.py +++ /dev/null @@ -1,9 +0,0 @@ -""" -Sstore 2 -> {calltype} -> change to {0, 1, 2} |-> {calltype} -> {non,... - -Ported from: -state_tests/stTimeConsuming/sstore_combinations_initial21_ParisFiller.json - -@manually-enhanced: Do not overwrite. This test has been manually reviewed and -enhanced. -""" From 5c024cbbd8963ea23c6a081ca7b8ce327c3d5224 Mon Sep 17 00:00:00 2001 From: spencer <spencer.taylor-brown@ethereum.org> Date: Tue, 14 Jul 2026 20:38:26 +0100 Subject: [PATCH 126/233] feat(spec-specs, tests): charge EIP-8037 account creation at access (#3116) * feat(spec-specs, tests): charge EIP-8037 account creation at access * fix(spec-specs): decide EIP-8037 create collision charge by existence alone * refactor(test-forks): Allow to set `account_new` in `CREATE*` opcodes * fix(test-vm): Docstrings --------- Co-authored-by: Mario Vega <marioevz@gmail.com> --- .../forks/forks/eips/amsterdam/eip_8037.py | 13 ++-- .../src/execution_testing/vm/opcodes.py | 4 + .../forks/amsterdam/vm/instructions/system.py | 46 +++++------ .../test_block_access_lists_opcodes.py | 9 ++- .../test_state_gas_create.py | 77 +++++++------------ 5 files changed, 64 insertions(+), 85 deletions(-) diff --git a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8037.py b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8037.py index daab14316c8..d3ed10edd2c 100644 --- a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8037.py +++ b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8037.py @@ -356,13 +356,14 @@ def _calculate_create_state_gas( ) -> int: """ Calculate the CREATE and CREATE2 state gas cost, which is - `NEW_ACCOUNT`. Before EIP-8037 this was folded into - `OPCODE_CREATE_BASE`. Under EIP-8037 it is exposed here so that - `OPCODE_CREATE_BASE` stays regular only and matches the spec - EVM constant. + `NEW_ACCOUNT` (if the account did not exist before). + Before EIP-8037 this was folded into `OPCODE_CREATE_BASE`. Under + EIP-8037 it is exposed here so that `OPCODE_CREATE_BASE` stays regular + only and matches the spec EVM constant. """ - del opcode - return gas_costs.NEW_ACCOUNT + if opcode.metadata["account_new"]: + return gas_costs.NEW_ACCOUNT + return 0 @classmethod def _calculate_selfdestruct_state_gas( diff --git a/packages/testing/src/execution_testing/vm/opcodes.py b/packages/testing/src/execution_testing/vm/opcodes.py index cfe8c4d52b1..7402e55dcca 100644 --- a/packages/testing/src/execution_testing/vm/opcodes.py +++ b/packages/testing/src/execution_testing/vm/opcodes.py @@ -5381,6 +5381,7 @@ class Opcodes(Opcode, Enum): "init_code_size": 0, "new_memory_size": 0, "old_memory_size": 0, + "account_new": True, }, ) """ @@ -5424,6 +5425,7 @@ class Opcodes(Opcode, Enum): - init_code_size: size of the initialization code in bytes (default: 0) - new_memory_size: memory size after expansion in bytes (default: 0) - old_memory_size: memory size before expansion in bytes (default: 0) + - account_new: whether creating a new account (default: True) Source: [evm.codes/#F0](https://www.evm.codes/#F0) """ @@ -5718,6 +5720,7 @@ class Opcodes(Opcode, Enum): "init_code_size": 0, "new_memory_size": 0, "old_memory_size": 0, + "account_new": True, }, ) """ @@ -5763,6 +5766,7 @@ class Opcodes(Opcode, Enum): - init_code_size: size of the initialization code in bytes (default: 0) - new_memory_size: memory size after expansion in bytes (default: 0) - old_memory_size: memory size before expansion in bytes (default: 0) + - account_new: whether creating a new account (default: True) Source: [evm.codes/#F5](https://www.evm.codes/#F5) """ diff --git a/src/ethereum/forks/amsterdam/vm/instructions/system.py b/src/ethereum/forks/amsterdam/vm/instructions/system.py index 9d4e4fa815d..06ba36f47e1 100644 --- a/src/ethereum/forks/amsterdam/vm/instructions/system.py +++ b/src/ethereum/forks/amsterdam/vm/instructions/system.py @@ -84,24 +84,12 @@ def generic_create( if memory_size > U256(MAX_INIT_CODE_SIZE): raise OutOfGasError - # Charge state gas for account creation (pay-before-execute). - # Refunded to the reservoir on any failure path below. - charge_state_gas(evm, StateGasCosts.NEW_ACCOUNT) - tx_state = evm.message.tx_env.state call_data = memory_read_bytes( evm.memory, memory_start_position, memory_size ) - create_message_gas = max_message_call_gas(Uint(evm.gas_left)) - evm.gas_left -= create_message_gas - - # Move full reservoir to child (no 63/64 rule for state gas). Parent's - # `state_gas_left` is zeroed and restored when the child returns. - create_message_state_gas_reservoir = evm.state_gas_left - evm.state_gas_left = Uint(0) - evm.return_data = b"" sender_address = evm.message.current_target @@ -112,26 +100,36 @@ def generic_create( or sender.nonce == Uint(2**64 - 1) or evm.message.depth + Uint(1) > STACK_DEPTH_LIMIT ): - evm.gas_left += create_message_gas - evm.state_gas_left += create_message_state_gas_reservoir - credit_state_gas_refund(evm, StateGasCosts.NEW_ACCOUNT) push(evm.stack, U256(0)) return evm.accessed_addresses.add(contract_address) + # The charge is decided by existence alone, independently of the + # collision outcome. + new_account_charged = not is_account_alive(tx_state, contract_address) + if new_account_charged: + charge_state_gas(evm, StateGasCosts.NEW_ACCOUNT) + + create_message_gas = max_message_call_gas(Uint(evm.gas_left)) + evm.gas_left -= create_message_gas + if not account_deployable(tx_state, contract_address): - increment_nonce(tx_state, evm.message.current_target) + increment_nonce(tx_state, sender_address) evm.regular_gas_used += create_message_gas - evm.state_gas_left += create_message_state_gas_reservoir - # Address collision — no account created, refund state gas. - credit_state_gas_refund(evm, StateGasCosts.NEW_ACCOUNT) + # A storage-only collision target is non-existent: charged + # above, refilled here. + if new_account_charged: + credit_state_gas_refund(evm, StateGasCosts.NEW_ACCOUNT) push(evm.stack, U256(0)) return - target_alive = is_account_alive(tx_state, contract_address) + # Move full reservoir to child (no 63/64 rule for state gas). Parent's + # `state_gas_left` is zeroed and restored when the child returns. + create_message_state_gas_reservoir = evm.state_gas_left + evm.state_gas_left = Uint(0) - increment_nonce(tx_state, evm.message.current_target) + increment_nonce(tx_state, sender_address) child_message = Message( block_env=evm.message.block_env, @@ -157,14 +155,12 @@ def generic_create( if child_evm.error: incorporate_child_on_error(evm, child_evm) - # No account created, refund parent's CREATE state gas. - credit_state_gas_refund(evm, StateGasCosts.NEW_ACCOUNT) + if new_account_charged: + credit_state_gas_refund(evm, StateGasCosts.NEW_ACCOUNT) evm.return_data = child_evm.output push(evm.stack, U256(0)) else: incorporate_child_on_success(evm, child_evm) - if target_alive: - credit_state_gas_refund(evm, StateGasCosts.NEW_ACCOUNT) evm.return_data = b"" push(evm.stack, U256.from_be_bytes(child_evm.message.current_target)) diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py index ec3e199084f..3392b80ff0a 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py @@ -3314,6 +3314,7 @@ def test_bal_create_and_oog( offset=32 - len(init_code_bytes), size=len(init_code_bytes), init_code_size=len(init_code_bytes), + account_new=False, ) factory_sstore = Op.SSTORE(0x00, 1) oog_sink_memory_size = 10000 * 32 @@ -3339,6 +3340,8 @@ def test_bal_create_and_oog( initcode=init_code_bytes, opcode=create_opcode, ) + # Pre-fund the address so no new account is created + pre.fund_address(created_address, 1) intrinsic_cost = fork.transaction_intrinsic_cost_calculator()() create_static_cost = factory_mstore.gas_cost( @@ -3388,7 +3391,7 @@ def test_bal_create_and_oog( post = { alice: Account(nonce=1), factory: Account(nonce=1, storage={0x00: 0xDEAD}), - created_address: Account.NONEXISTENT, + created_address: Account(balance=1, code=b"", nonce=0), } elif oog_boundary == OutOfGasBoundary.OOG_AFTER_TARGET_ACCESS: # Created address IS in BAL (accessed during collision check), @@ -3406,7 +3409,7 @@ def test_bal_create_and_oog( post = { alice: Account(nonce=1), factory: Account(nonce=1, storage={0x00: 0xDEAD}), - created_address: Account.NONEXISTENT, + created_address: Account(balance=1, code=b"", nonce=0), } else: # SUCCESS: created address in BAL with nonce and code changes @@ -3446,7 +3449,7 @@ def test_bal_create_and_oog( post = { alice: Account(nonce=1), factory: Account(nonce=2, storage={0x00: 1}), - created_address: Account(code=Op.STOP), + created_address: Account(balance=1, code=Op.STOP, nonce=1), } blockchain_test( diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py index ec35b027114..4034be8dcb3 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py @@ -2908,28 +2908,44 @@ def test_create_collision_burned_gas_counted_in_block_regular( """ init_code = Op.STOP mstore_value, size = init_code_at_high_bytes(init_code) - salt = 0 - - create_call = ( - create_opcode(value=0, offset=0, size=size, salt=salt) - if create_opcode == Op.CREATE2 - else create_opcode(value=0, offset=0, size=size) - ) - factory_code = Op.MSTORE(0, mstore_value) + Op.POP(create_call) + Op.STOP + factory_create_code = Op.MSTORE( + 0, mstore_value, new_memory_size=32 + ) + create_opcode(value=0, offset=0, size=size, account_new=False) + factory_post_create_code = Op.POP + Op.STOP + factory_code = factory_create_code + factory_post_create_code factory = pre.deploy_contract(code=factory_code) collision_target = compute_create_address( address=factory, nonce=1, - salt=salt, + salt=0, initcode=bytes(init_code), opcode=create_opcode, ) pre.deploy_contract(code=Op.STOP, address=collision_target) + # CPSB-agnostic baseline: block_state_gas is zero for this tx (the + # existent collision target is not charged), so header.gas_used + # equals the regular-gas total. Decompose the parent + inner frame + # accounting from fork APIs so the baseline tracks future cost + # changes automatically. + gas_used_until_collision = ( + fork.transaction_intrinsic_cost_calculator()() + + factory_create_code.gas_cost(fork) + ) # Fixed-size budget so the forwarded create_message_gas is # deterministic and the baseline below is reproducible. - gas_limit = 250_000 + gas_limit = gas_used_until_collision * 2 + # Remaining gas can be derived due to the fixed gas limit + gas_at_create = gas_limit - gas_used_until_collision + # Inner burns 63/64 of the available gas on collision; the parent + # retains 1/64. Post-CREATE consumes from the retained pool. A + # mutation that drops the burned forwarded gas from regular + # accounting would reduce this baseline. + retained = gas_at_create // 64 + gas_post_create = factory_post_create_code.gas_cost(fork) + assert retained >= gas_post_create + baseline_gas_used = gas_limit - retained + gas_post_create tx = Transaction( to=factory, @@ -2937,47 +2953,6 @@ def test_create_collision_burned_gas_counted_in_block_regular( sender=pre.fund_eoa(), ) - # CPSB-agnostic baseline: block_state_gas is zero for this tx (the - # collision refunds the NEW_ACCOUNT state charge), so header.gas_used - # equals the regular-gas total. Decompose the parent + inner frame - # accounting from fork APIs so the baseline tracks future cost - # changes automatically. - intrinsic = fork.transaction_intrinsic_cost_calculator()() - new_account = fork.gas_costs().NEW_ACCOUNT - create_base = fork.gas_costs().OPCODE_CREATE_BASE - # POP + STOP run in the parent frame after CREATE returns; their - # cost comes out of the 1/64 retained gas. - post_create_static = (Op.POP + Op.STOP).gas_cost(fork) - # factory_code.gas_cost(fork) folds NEW_ACCOUNT into the CREATE op - # (state gas is treated as part of the opcode total). Strip it - # back out and split off the post-CREATE tail to isolate the - # pre-CREATE static gas. - factory_pre_create = ( - factory_code.gas_cost(fork) - - new_account - - create_base - - post_create_static - ) - # MSTORE writes the initcode at memory[0:32] (one word). - memory_expansion = fork.memory_expansion_gas_calculator()(new_bytes=32) - # gas_left at the moment NEW_ACCOUNT spills into the regular pool - # (reservoir is empty for tx_gas_limit < TX_MAX_GAS_LIMIT). - gas_at_create_after_state = ( - gas_limit - - intrinsic - - factory_pre_create - - memory_expansion - - create_base - - new_account - ) - # Inner burns 63/64 of the available gas on collision; the parent - # retains 1/64. The state-spill of NEW_ACCOUNT is refunded back to - # gas_left on collision (nets zero). Post-CREATE consumes from the - # retained pool. A mutation that drops the burned forwarded gas - # from regular accounting would reduce this baseline. - retained = gas_at_create_after_state // 64 - baseline_gas_used = gas_limit - retained - new_account + post_create_static - blockchain_test( pre=pre, blocks=[ From dcde331ff4a40cc3dce240312601a697ab9f94b9 Mon Sep 17 00:00:00 2001 From: danceratopz <danceratopz@gmail.com> Date: Wed, 15 Jul 2026 10:30:02 +0200 Subject: [PATCH 127/233] chore(ci): run `just test-ci-scripts` in CI (#3172) --- .github/workflows/test.yaml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 7fa70f2676e..d0483c5206a 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -204,3 +204,14 @@ jobs: env: PYPY_GC_MAX: "2G" PYPY_GC_MIN: "1G" + + test-ci-scripts: + runs-on: ubuntu-latest + needs: static + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + submodules: recursive + - uses: ./.github/actions/setup-uv + - name: Run test-ci-scripts + run: just test-ci-scripts From fc3bede68d46be416fb3507b29ee456afe46a8b5 Mon Sep 17 00:00:00 2001 From: danceratopz <danceratopz@gmail.com> Date: Wed, 15 Jul 2026 14:13:55 +0200 Subject: [PATCH 128/233] fix(test-consume): accept bad-block-cache errors for resubmitted blocks (#3146) --- docs/running_tests/running.md | 7 + .../plugins/consume/simulators/base.py | 15 ++ .../simulators/helpers/rejected_blocks.py | 126 +++++++++++++ .../simulator_logic/test_via_engine.py | 59 +++--- .../consume/tests/test_rejected_blocks.py | 172 ++++++++++++++++++ .../src/execution_testing/rpc/rpc_types.py | 12 +- 6 files changed, 356 insertions(+), 35 deletions(-) create mode 100644 packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/helpers/rejected_blocks.py create mode 100644 packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/tests/test_rejected_blocks.py diff --git a/docs/running_tests/running.md b/docs/running_tests/running.md index 62970bf4fc3..2515cb3677e 100644 --- a/docs/running_tests/running.md +++ b/docs/running_tests/running.md @@ -105,6 +105,12 @@ Every test after the first in a pre-allocation group consequently exercises the It does mean, however, that a test which fails under `consume enginex` but passes under `consume engine` is more likely to indicate a bug in the client's reorg, head state rollback or block caching logic than in its EVM or block validation logic. +### Bad-Block Cache Handling + +Clients typically cache the blocks they reject. Because a client is reused across a pre-allocation group, its bad-block cache persists between tests: if two tests in a group contain an identical invalid block, the client validates the first submission for real and returns the specific validation error, but may answer the resubmission from its cache with a generic error (e.g. geth's and reth's "links to previously rejected block" or Nethermind's "is known to be a part of an invalid chain") that maps to no known exception. + +The simulator therefore remembers the first validation error each client returns per invalid block. When a rejection does not match the test's expected exception, it is verified against the client's first rejection of the same block: it is accepted and logged ("Accepting mismatched validation error") if that first rejection matched the expected exception, and fails the test as before otherwise. This is sound because an identical block hash implies an identical block built on an identical parent chain, so the real validation outcome is deterministic. `consume engine` starts a fresh client per test and is unaffected. + ### Engine vs EngineX | | `consume engine` | `consume enginex` | @@ -116,6 +122,7 @@ Every test after the first in a pre-allocation group consequently exercises the | **Execution speed** | Slower (client startup overhead) | Faster (amortized startup cost) | | **Test isolation** | Full isolation | Shared client and genesis state within group; the chain head is reset to genesis for each test | | **Chain reorgs** | Not exercised; each client executes one test's payloads only | [Implicitly exercised](#implicit-chain-reorg-coverage) by every test after the first in a group | +| **Exception matching** | Response validated directly against the expected exception | Identical, except a mismatched rejection is [accepted](#bad-block-cache-handling) if the client's first rejection of the identical block matched | EngineX achieves faster execution by: diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/base.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/base.py index 5d44d99f758..35edcedba81 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/base.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/base.py @@ -17,6 +17,7 @@ from execution_testing.rpc import EthRPC from ..consume import FixturesSource +from .helpers.rejected_blocks import BlockRejectionTracker @pytest.fixture(scope="function") @@ -38,6 +39,20 @@ def genesis_verified_clients() -> set[str]: return set() +@pytest.fixture(scope="session") +def block_rejection_tracker() -> BlockRejectionTracker: + """ + Return the tracker of invalid blocks rejected by each client. + + In enginex mode a client is reused across a pre-alloc group, so a later + test can resubmit a block that the client already rejected for an earlier + test and receive a generic bad-block-cache error instead of the specific + validation error. The tracker remembers each client's first rejection so + the expected exception can be verified against it in that case. + """ + return BlockRejectionTracker() + + @pytest.fixture(scope="function") def check_live_port(test_suite_name: str) -> Literal[8545, 8551]: """Port used by hive to check for liveness of the client.""" diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/helpers/rejected_blocks.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/helpers/rejected_blocks.py new file mode 100644 index 00000000000..06f3dc9ac1f --- /dev/null +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/helpers/rejected_blocks.py @@ -0,0 +1,126 @@ +"""Track and verify invalid blocks rejected by a client instance.""" + +from execution_testing.base_types import Hash +from execution_testing.exceptions import ( + ExceptionInstanceOrList, + UndefinedException, +) +from execution_testing.logging import get_logger +from execution_testing.rpc.rpc_types import ( + BlockTransactionExceptionWithMessage, + ClientValidationError, +) + +from .exceptions import LoggedError + +logger = get_logger(__name__) + + +class BlockRejectionTracker: + """ + Track the first validation error a client returns per invalid block. + + Clients keep a bad-block cache: resubmitting an already-rejected block + is answered from the cache with a generic error (e.g. reth's "links to + previously rejected block") instead of being re-validated and + rejected with the specific error again. In enginex mode a client + instance is reused across all tests of a pre-allocation group, so two + tests containing an identical invalid block trigger this cache: the + first submission is validated for real, the resubmission + short-circuits. + + Remember the first (real) validation error per client and block so + that the simulator can verify the expected exception against it when + the response to a resubmission does not match. + """ + + def __init__(self) -> None: + """Initialize the tracker with no recorded rejections.""" + self._first_errors: dict[tuple[str, Hash], ClientValidationError] = {} + + def track( + self, + client_id: str, + block_hash: Hash, + error: ClientValidationError, + ) -> ClientValidationError: + """ + Track a block rejection and return the client's first error for it. + + Record `error` as the client's canonical validation error for the + block if this is the first time the client rejects it; later + rejections of the same block by the same client do not overwrite + it. Return the recorded first error, which is `error` itself when + this is the first rejection. + """ + return self._first_errors.setdefault((client_id, block_hash), error) + + +def matches_expected_exception( + first_rejection: ClientValidationError, + expected_exception: ExceptionInstanceOrList | None, +) -> bool: + """ + Return whether a tracked rejection matched the expected exception. + + Return `False` if the rejection's error could not be mapped to any + exception (`UndefinedException`) or if there is no expected exception + to match against. + """ + if expected_exception is None: + return False + return ( + isinstance(first_rejection, BlockTransactionExceptionWithMessage) + and expected_exception in first_rejection + ) + + +def verify_block_rejection( + expected_exception: ExceptionInstanceOrList | None, + returned_error: ClientValidationError, + first_rejection: ClientValidationError, + block_hash: Hash, + strict_exception_matching: bool, +) -> None: + """ + Verify a client's block rejection against the expected exception. + + Raise `LoggedError` if `returned_error` does not match + `expected_exception` (or could not be mapped to any exception at all) + and strict exception matching is enabled; without strict matching only + log a warning. + + Accept and log a mismatched `returned_error` when `first_rejection`, + the client's first rejection of the same block, matched the expected + exception: the client has answered a resubmission of the block from + its bad-block cache with a generic error instead of re-validating it. + A mismatched or unmappable error can never match itself, so a block's + first rejection is always verified strictly. + """ + if isinstance(returned_error, UndefinedException): + message = ( + "Undefined exception message: " + f'expected exception: "{expected_exception}", ' + f'returned exception: "{returned_error}" ' + f'(mapper: "{returned_error.mapper_name}")' + ) + elif expected_exception not in returned_error: + message = ( + "Client returned unexpected validation error: " + f'got: "{returned_error}" ' + f'expected: "{expected_exception}"' + ) + else: + return + if matches_expected_exception(first_rejection, expected_exception): + logger.info( + f"Accepting mismatched validation error for block {block_hash}: " + "this client already rejected the same block with an error " + f'matching the expected exception ("{first_rejection}") and ' + "has rejected the resubmission from its bad-block cache. " + f"{message}" + ) + elif strict_exception_matching: + raise LoggedError(message) + else: + logger.warning(message) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/simulator_logic/test_via_engine.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/simulator_logic/test_via_engine.py index f064c4f48e2..521128b36d1 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/simulator_logic/test_via_engine.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/simulator_logic/test_via_engine.py @@ -17,7 +17,6 @@ from hive.client import Client -from execution_testing.exceptions import UndefinedException from execution_testing.fixtures import ( BlockchainEngineFixture, BlockchainEngineXFixture, @@ -39,6 +38,10 @@ GenesisBlockMismatchExceptionError, LoggedError, ) +from ..helpers.rejected_blocks import ( + BlockRejectionTracker, + verify_block_rejection, +) from ..helpers.timing import TimingData logger = get_logger(__name__) @@ -50,6 +53,7 @@ def test_blockchain_via_engine( engine_rpc: EngineRPC, client: Client, genesis_verified_clients: set[str], + block_rejection_tracker: BlockRejectionTracker, fixture: Union[BlockchainEngineFixture, BlockchainEngineXFixture], strict_exception_matching: bool, genesis_header: FixtureHeader, @@ -69,6 +73,14 @@ def test_blockchain_via_engine( done once per client and skipped for later tests in the group. 3. Execute test fixture blocks using engine_newPayloadVX. 4. For valid payloads, send FCU to advance the chain head. + + A client's bad-block cache persists across the tests of a pre-alloc + group in enginex mode: a block that an earlier test already got + rejected may be rejected again with a generic cache error (e.g. reth's + "links to previously rejected block") instead of being re-validated. + When the returned error does not match the expected exception, it is + therefore verified against the error from the client's first rejection + of the same block before failing the test. """ with timing_data.time("Initial forkchoice update"): logger.info("Sending initial forkchoice update to genesis block...") @@ -162,40 +174,19 @@ def test_blockchain_via_engine( "Client returned INVALID but no " "validation error was provided." ) - if isinstance( + block_hash = payload.params[0].block_hash + first_rejection = block_rejection_tracker.track( + client.id, + block_hash, payload_response.validation_error, - UndefinedException, - ): - message = ( - "Undefined exception message: " - f"expected exception: " - f'"{payload.validation_error}", ' - f"returned exception: " - f'"{payload_response.validation_error}" ' - f"(mapper: " - f'"{payload_response.validation_error.mapper_name}")' # noqa: E501 - ) - if strict_exception_matching: - raise LoggedError(message) - else: - logger.warning(message) - else: - if ( - payload.validation_error - not in payload_response.validation_error - ): - message = ( - "Client returned unexpected " - "validation error: " - f"got: " - f'"{payload_response.validation_error}" ' # noqa: E501 - f"expected: " - f'"{payload.validation_error}"' - ) - if strict_exception_matching: - raise LoggedError(message) - else: - logger.warning(message) + ) + verify_block_rejection( + payload.validation_error, + payload_response.validation_error, + first_rejection, + block_hash, + strict_exception_matching, + ) except JSONRPCError as e: logger.info( diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/tests/test_rejected_blocks.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/tests/test_rejected_blocks.py new file mode 100644 index 00000000000..068a6a5f660 --- /dev/null +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/tests/test_rejected_blocks.py @@ -0,0 +1,172 @@ +"""Tests for the block rejection tracker used by the engine simulators.""" + +import pytest + +from execution_testing.base_types import Hash +from execution_testing.exceptions import ( + BlockException, + UndefinedException, +) +from execution_testing.rpc.rpc_types import ( + BlockTransactionExceptionWithMessage, +) + +from ..simulators.helpers.exceptions import LoggedError +from ..simulators.helpers.rejected_blocks import ( + BlockRejectionTracker, + matches_expected_exception, + verify_block_rejection, +) + +CLIENT_A = "client-a" +CLIENT_B = "client-b" +BLOCK_1 = Hash(1) +BLOCK_2 = Hash(2) + +EXCESS_BLOB_GAS_ERROR = BlockTransactionExceptionWithMessage( + exceptions=[BlockException.INCORRECT_EXCESS_BLOB_GAS], + message="invalid excess blob gas: got 1179648, expected 1048576", +) +CACHED_REJECTION_ERROR = UndefinedException( + "links to previously rejected block", + mapper_name="RethExceptionMapper", +) + + +def test_first_rejection_returns_the_error_itself() -> None: + """The first rejection of a block records and returns its own error.""" + tracker = BlockRejectionTracker() + assert ( + tracker.track(CLIENT_A, BLOCK_1, EXCESS_BLOB_GAS_ERROR) + is EXCESS_BLOB_GAS_ERROR + ) + + +def test_resubmission_returns_first_error() -> None: + """Rejections after the first return the first recorded error.""" + tracker = BlockRejectionTracker() + tracker.track(CLIENT_A, BLOCK_1, EXCESS_BLOB_GAS_ERROR) + assert ( + tracker.track(CLIENT_A, BLOCK_1, CACHED_REJECTION_ERROR) + is EXCESS_BLOB_GAS_ERROR + ) + # The first error is not overwritten by later rejections. + assert ( + tracker.track(CLIENT_A, BLOCK_1, CACHED_REJECTION_ERROR) + is EXCESS_BLOB_GAS_ERROR + ) + + +def test_rejections_are_tracked_per_client() -> None: + """A rejection by one client is not the first error for another.""" + tracker = BlockRejectionTracker() + tracker.track(CLIENT_A, BLOCK_1, EXCESS_BLOB_GAS_ERROR) + assert ( + tracker.track(CLIENT_B, BLOCK_1, CACHED_REJECTION_ERROR) + is CACHED_REJECTION_ERROR + ) + + +def test_rejections_are_tracked_per_block() -> None: + """A rejection of one block is not the first error for another.""" + tracker = BlockRejectionTracker() + tracker.track(CLIENT_A, BLOCK_1, EXCESS_BLOB_GAS_ERROR) + assert ( + tracker.track(CLIENT_A, BLOCK_2, CACHED_REJECTION_ERROR) + is CACHED_REJECTION_ERROR + ) + + +def test_matching_first_rejection() -> None: + """A tracked rejection matches its mapped exception.""" + assert matches_expected_exception( + EXCESS_BLOB_GAS_ERROR, BlockException.INCORRECT_EXCESS_BLOB_GAS + ) + + +def test_matching_first_rejection_with_exception_list() -> None: + """A tracked rejection matches a list containing its exception.""" + assert matches_expected_exception( + EXCESS_BLOB_GAS_ERROR, + [ + BlockException.INCORRECT_BLOB_GAS_USED, + BlockException.INCORRECT_EXCESS_BLOB_GAS, + ], + ) + + +def test_mismatching_first_rejection() -> None: + """A tracked rejection does not match a different exception.""" + assert not matches_expected_exception( + EXCESS_BLOB_GAS_ERROR, BlockException.INCORRECT_BLOB_GAS_USED + ) + + +def test_undefined_first_rejection_never_matches() -> None: + """An unmappable tracked rejection cannot be verified as a match.""" + assert not matches_expected_exception( + CACHED_REJECTION_ERROR, + BlockException.INCORRECT_EXCESS_BLOB_GAS, + ) + + +def test_no_expected_exception_never_matches() -> None: + """Without an expected exception there is nothing to match.""" + assert not matches_expected_exception(EXCESS_BLOB_GAS_ERROR, None) + + +def test_verify_matching_error_passes() -> None: + """A rejection with the expected exception verifies silently.""" + verify_block_rejection( + BlockException.INCORRECT_EXCESS_BLOB_GAS, + EXCESS_BLOB_GAS_ERROR, + EXCESS_BLOB_GAS_ERROR, + BLOCK_1, + strict_exception_matching=True, + ) + + +def test_verify_first_rejection_mismatch_raises() -> None: + """A block's first rejection is verified strictly.""" + with pytest.raises(LoggedError, match="Undefined exception message"): + verify_block_rejection( + BlockException.INCORRECT_EXCESS_BLOB_GAS, + CACHED_REJECTION_ERROR, + CACHED_REJECTION_ERROR, + BLOCK_1, + strict_exception_matching=True, + ) + + +def test_verify_unexpected_error_raises() -> None: + """A mapped but unexpected error fails strict verification.""" + with pytest.raises(LoggedError, match="unexpected validation error"): + verify_block_rejection( + BlockException.INCORRECT_BLOB_GAS_USED, + EXCESS_BLOB_GAS_ERROR, + EXCESS_BLOB_GAS_ERROR, + BLOCK_1, + strict_exception_matching=True, + ) + + +def test_verify_accepts_cached_resubmission_rejection() -> None: + """A cache error is accepted if the first rejection matched.""" + verify_block_rejection( + BlockException.INCORRECT_EXCESS_BLOB_GAS, + CACHED_REJECTION_ERROR, + EXCESS_BLOB_GAS_ERROR, + BLOCK_1, + strict_exception_matching=True, + ) + + +def test_verify_mismatch_only_warns_without_strict_matching() -> None: + """A mismatched rejection does not fail without strict matching.""" + verify_block_rejection( + BlockException.INCORRECT_EXCESS_BLOB_GAS, + CACHED_REJECTION_ERROR, + CACHED_REJECTION_ERROR, + BLOCK_1, + strict_exception_matching=False, + ) diff --git a/packages/testing/src/execution_testing/rpc/rpc_types.py b/packages/testing/src/execution_testing/rpc/rpc_types.py index c9a9fc7d959..d4e7784a6c0 100644 --- a/packages/testing/src/execution_testing/rpc/rpc_types.py +++ b/packages/testing/src/execution_testing/rpc/rpc_types.py @@ -182,6 +182,16 @@ class BlockTransactionExceptionWithMessage( pass +ClientValidationError = ( + BlockTransactionExceptionWithMessage | UndefinedException +) +""" +A client's validation error for a rejected block: the mapped exceptions +with the verbatim message, or `UndefinedException` if the message could +not be mapped. +""" + + class PayloadStatus(CamelModel): """Represents the status of a payload after execution.""" @@ -189,7 +199,7 @@ class PayloadStatus(CamelModel): latest_valid_hash: Hash | None validation_error: ( Annotated[ - BlockTransactionExceptionWithMessage | UndefinedException, + ClientValidationError, ExceptionMapperValidator, ] | None From e32f4b9ff8eabe20f21a9b804fbc73b1e1e603a5 Mon Sep 17 00:00:00 2001 From: danceratopz <danceratopz@gmail.com> Date: Wed, 15 Jul 2026 14:25:13 +0200 Subject: [PATCH 129/233] chore(ci): right-size runners for spec-tools and test-tests-pypy (#3177) Both jobs held a 16C/32G `size-xl-x64` runner they cannot use: - `spec-tools` runs 40 tests in ~13s; move it to `ubuntu-latest` like `static` and `test-ci-scripts`, freeing a self-hosted VM entirely. - `test-tests-pypy` is capped at 6 xdist workers in the Justfile, leaving 10 cores idle; `size-l-x64` (8C/16G) fits its worker count and PyPy heap caps (6 x PYPY_GC_MAX=2G = 12G). --- .github/workflows/test.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index d0483c5206a..249c03e369e 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -157,7 +157,7 @@ jobs: token: ${{ secrets.CODECOV_TOKEN }} spec-tools: - runs-on: [self-hosted-ghr, size-xl-x64] + runs-on: ubuntu-latest needs: static steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -188,7 +188,7 @@ jobs: PYTEST_XDIST_AUTO_NUM_WORKERS: auto test-tests-pypy: - runs-on: [self-hosted-ghr, size-xl-x64] + runs-on: [self-hosted-ghr, size-l-x64] needs: static steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 From 87aba1a38a476b31f819a2390eb481527e6dc683 Mon Sep 17 00:00:00 2001 From: danceratopz <danceratopz@gmail.com> Date: Wed, 15 Jul 2026 14:31:17 +0200 Subject: [PATCH 130/233] fix(tests,test-fill): isolate the scenarios BLOCKHASH program pre-alloc group (#3176) --- .../pytest_commands/plugins/filler/filler.py | 9 +++- .../filler/tests/test_prealloc_group.py | 53 +++++++++++++++++-- .../fixtures/engine_x_checks.py | 16 +++--- tests/frontier/scenarios/test_scenarios.py | 11 +++- 4 files changed, 77 insertions(+), 12 deletions(-) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py index 148b5dd1ea3..3f6484b42ba 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py @@ -1657,10 +1657,15 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: "pre_alloc_group" ): # Get the group name/salt from marker args - if pre_alloc_group_marker.args: + if ( + pre_alloc_group_marker.args + and pre_alloc_group_marker.args[0] != "separate" + ): group_salt = str(pre_alloc_group_marker.args[0]) else: - # We got the marker but unspecified, pass test name + # "separate" (or a bare marker): salt with the + # test's node id so the test gets its own genesis + # instead of a group named literally "separate". group_salt = _strip_xdist_group_suffix( request.node.nodeid ) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_prealloc_group.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_prealloc_group.py index ce5b86832af..cf4026269ed 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_prealloc_group.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_prealloc_group.py @@ -397,7 +397,7 @@ class FormattedTest: template: ClassVar[str] def __init__(self, **kwargs: str) -> None: # noqa: D107 - self.kwargs = kwargs + self.kwargs = {"markers": ""} | kwargs def format(self) -> str: # noqa: D102 return self.template.format(**self.kwargs) @@ -418,7 +418,7 @@ class StateTest(FormattedTest): # noqa: D101 ) @pytest.mark.valid_from("Istanbul") - def test_chainid(state_test: StateTestFiller, pre: Alloc) -> None: + {markers}def test_chainid(state_test: StateTestFiller, pre: Alloc) -> None: contract_address = pre.deploy_contract(Op.SSTORE(1, Op.CHAINID) + Op.STOP) sender = pre.fund_eoa() @@ -455,7 +455,7 @@ class BlockchainTest(FormattedTest): # noqa: D101 ) @pytest.mark.valid_from("Istanbul") - def test_chainid_blockchain(blockchain_test: BlockchainTestFiller, pre: Alloc) -> None: + {markers}def test_chainid_blockchain(blockchain_test: BlockchainTestFiller, pre: Alloc) -> None: contract_address = pre.deploy_contract(Op.SSTORE(1, Op.CHAINID) + Op.STOP) sender = pre.fund_eoa() @@ -590,6 +590,53 @@ def test_chainid_blockchain(blockchain_test: BlockchainTestFiller, pre: Alloc) - 2, id="different_excess_blob_gas", ), + # The `pre_alloc_group` marker + pytest.param( + [ + StateTest( + env="Environment()", + markers="@pytest.mark.pre_alloc_group(" + '"separate", reason="isolate")\n', + ), + StateTest( + env="Environment()", + markers="@pytest.mark.pre_alloc_group(" + '"separate", reason="isolate")\n', + ), + ], + 2, + id="separate_marker_isolates_each_test", + ), + pytest.param( + [ + StateTest( + env="Environment()", + markers='@pytest.mark.pre_alloc_group(reason="isolate")\n', + ), + StateTest( + env="Environment()", + markers='@pytest.mark.pre_alloc_group(reason="isolate")\n', + ), + ], + 2, + id="bare_marker_isolates_each_test", + ), + pytest.param( + [ + StateTest( + env="Environment()", + markers="@pytest.mark.pre_alloc_group(" + '"custom_group", reason="shared setup")\n', + ), + StateTest( + env="Environment()", + markers="@pytest.mark.pre_alloc_group(" + '"custom_group", reason="shared setup")\n', + ), + ], + 1, + id="named_group_marker_shares_one_group", + ), ], ) def test_pre_alloc_grouping_by_test_type( diff --git a/packages/testing/src/execution_testing/fixtures/engine_x_checks.py b/packages/testing/src/execution_testing/fixtures/engine_x_checks.py index f3f43d78cde..b81eaf38363 100644 --- a/packages/testing/src/execution_testing/fixtures/engine_x_checks.py +++ b/packages/testing/src/execution_testing/fixtures/engine_x_checks.py @@ -20,9 +20,11 @@ class EngineXExecutionDriftError(Exception): An Engine X fixture is filled against its group's merged genesis, while the test's `blockchain_test_engine` sibling is filled against the test's own pre-allocation. Their per-payload execution outputs (gas used, - receipts root, logs bloom, ...) must be identical; a difference means an - account introduced by pre-alloc group packing leaked into the test's - execution (see `pack_pre_alloc_groups`). + receipts root, logs bloom, ...) must be identical; a difference means + either an account introduced by pre-alloc group packing leaked into the + test's execution (see `pack_pre_alloc_groups`), or the test observes the + genesis hash itself (e.g. via `BLOCKHASH(0)`), which depends on every + account in the genesis and so cannot survive any grouping. """ def __init__(self, mismatches: List[Tuple[str, str]], compared: int): @@ -39,9 +41,11 @@ def __init__(self, mismatches: List[Tuple[str, str]], compared: int): "differently against their packed pre-allocation group's genesis " "than against their own pre-allocation:\n" f"{details}\n" - "An account introduced by pre-alloc group packing leaked into " - "these tests' execution. Isolate the affected tests with " - "@pytest.mark.pre_alloc_group and re-fill." + "Sharing a genesis changed these tests' execution: either an " + "account introduced by pre-alloc group packing leaked into " + "their execution, or they observe the genesis hash itself " + "(e.g. via BLOCKHASH(0)). Isolate the affected tests with " + '@pytest.mark.pre_alloc_group("separate") and re-fill.' ) diff --git a/tests/frontier/scenarios/test_scenarios.py b/tests/frontier/scenarios/test_scenarios.py index f046ef6ad0d..1a2c2972291 100644 --- a/tests/frontier/scenarios/test_scenarios.py +++ b/tests/frontier/scenarios/test_scenarios.py @@ -166,7 +166,16 @@ def scenarios( ProgramReturnDataSize(), ProgramReturnDataCopy(), ProgramExtCodehash(), - ProgramBlockhash(), + pytest.param( + ProgramBlockhash(), + marks=pytest.mark.pre_alloc_group( + "separate", + reason="The program feeds BLOCKHASH(0), the genesis hash, " + "to the gas-hash contract, so gas usage depends on the " + "exact genesis pre-allocation; sharing a genesis with any " + "other test changes the execution.", + ), + ), ProgramCoinbase(), ProgramTimestamp(), ProgramNumber(), From 7e95c5e5fcdb7c7a06fdfcba45cf72179cdde2e5 Mon Sep 17 00:00:00 2001 From: danceratopz <danceratopz@gmail.com> Date: Wed, 15 Jul 2026 16:06:33 +0200 Subject: [PATCH 131/233] chore(tests): fix joblib cache race in eip2537 BLS precompile tests (#3174) --- tests/prague/eip2537_bls_12_381_precompiles/helpers.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/prague/eip2537_bls_12_381_precompiles/helpers.py b/tests/prague/eip2537_bls_12_381_precompiles/helpers.py index 565e0e32380..c0a0ca25d3a 100644 --- a/tests/prague/eip2537_bls_12_381_precompiles/helpers.py +++ b/tests/prague/eip2537_bls_12_381_precompiles/helpers.py @@ -167,8 +167,14 @@ class BLSPointGenerator: # G2 cofactor h₂: (x⁸ - 4x⁷ + 5x⁶ - 4x⁴ + 6x³ - 4x² - 4x + 13)/9 G2_COFACTOR = 0x5D543A95414E7F1091D50792876A202CD91DE4547085ABAA68A205B2E5A7DDFA628F1CB4D9E82EF21537E293A6691AE1616EC6E786F0C70CF1C38E31C7238E5 # noqa: E501 - # Memory cache for expensive functions - memory = Memory(location=".cache", verbose=0) + # Memory cache for expensive functions. Give each xdist worker its own + # cache dir: joblib's disk cache races on concurrent writes, and these + # functions run at collection time in every worker. + _xdist_worker = os.environ.get("PYTEST_XDIST_WORKER", "") + _cache_location = ( + f".cache/joblib-{_xdist_worker}" if _xdist_worker else ".cache" + ) + memory = Memory(location=_cache_location, verbose=0) @staticmethod def is_on_curve_g1(x: int, y: int) -> bool: From 49a20ed6c57eb523d0a35bca3c4bd8c8ea31949d Mon Sep 17 00:00:00 2001 From: danceratopz <danceratopz@gmail.com> Date: Wed, 15 Jul 2026 16:36:43 +0200 Subject: [PATCH 132/233] chore(ci): don't fetch the benchmark assets submodule where unused (#3175) The `.worst_case_miner` submodule (~82 MB of pre-mined benchmark assets) is only needed when filling `tests/benchmark/`, which is excluded from collection unless `--include-benchmark` is passed. Fetching it adds up to ~18s to every checkout on GitHub-hosted runners. Drop `submodules: recursive` from `test.yaml`, `docs-build.yaml` and `gh-pages.yaml`; `benchmark.yaml`, `release_fixtures.yaml` and `hive-execute.yaml` are unchanged. --- .github/workflows/docs-build.yaml | 2 -- .github/workflows/gh-pages.yaml | 1 - .github/workflows/test.yaml | 16 ---------------- 3 files changed, 19 deletions(-) diff --git a/.github/workflows/docs-build.yaml b/.github/workflows/docs-build.yaml index 4d167b159f1..fce8558d996 100644 --- a/.github/workflows/docs-build.yaml +++ b/.github/workflows/docs-build.yaml @@ -220,7 +220,6 @@ jobs: with: ref: ${{ github.event_name == 'workflow_dispatch' && needs.check-should-publish.outputs.commit_sha || '' }} fetch-depth: 0 - submodules: recursive - uses: ./.github/actions/setup-uv @@ -253,7 +252,6 @@ jobs: with: ref: ${{ github.event_name == 'workflow_dispatch' && needs.check-should-publish.outputs.commit_sha || '' }} fetch-depth: 0 - submodules: recursive - uses: ./.github/actions/setup-uv diff --git a/.github/workflows/gh-pages.yaml b/.github/workflows/gh-pages.yaml index eb5d3ae78bf..c099ee69bb4 100644 --- a/.github/workflows/gh-pages.yaml +++ b/.github/workflows/gh-pages.yaml @@ -29,7 +29,6 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - submodules: recursive - uses: ./.github/actions/setup-uv diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 249c03e369e..6835d40dd67 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -34,8 +34,6 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - submodules: recursive - name: Ensure SHA pinned actions uses: zgosalvez/github-actions-ensure-sha-pinned-actions@70c4af2ed5282c51ba40566d026d6647852ffa3e # v5.0.1 - uses: ./.github/actions/setup-uv @@ -98,8 +96,6 @@ jobs: until_fork: Amsterdam steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - submodules: recursive - uses: ./.github/actions/setup-uv with: python-version: "3.14" @@ -122,8 +118,6 @@ jobs: needs: static steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - submodules: recursive - uses: ./.github/actions/setup-uv with: python-version: "pypy3.11" @@ -139,8 +133,6 @@ jobs: needs: static steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - submodules: recursive - uses: ./.github/actions/setup-uv with: python-version: "3.14" @@ -161,8 +153,6 @@ jobs: needs: static steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - submodules: recursive - uses: ./.github/actions/setup-uv with: python-version: "3.14" @@ -177,8 +167,6 @@ jobs: needs: static steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - submodules: recursive - uses: ./.github/actions/setup-uv - uses: ./.github/actions/setup-env - uses: ./.github/actions/build-evmone @@ -192,8 +180,6 @@ jobs: needs: static steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - submodules: recursive - uses: ./.github/actions/setup-uv with: python-version: "pypy3.11" @@ -210,8 +196,6 @@ jobs: needs: static steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - submodules: recursive - uses: ./.github/actions/setup-uv - name: Run test-ci-scripts run: just test-ci-scripts From 56e615e65e0f55ce2485c5c7634683cdd29bdd9d Mon Sep 17 00:00:00 2001 From: danceratopz <danceratopz@gmail.com> Date: Wed, 15 Jul 2026 19:54:00 +0200 Subject: [PATCH 133/233] chore(ci): only install a native build toolchain for PyPy jobs (#3171) Co-authored-by: spencer <spencer.tb@ethereum.org> --- .github/actions/build-evmone/action.yaml | 4 ++-- .github/actions/build-fixtures/action.yaml | 1 - .github/actions/setup-env-pypy/action.yaml | 15 +++++++++++++++ .github/actions/setup-env/action.yaml | 14 -------------- .github/workflows/hive-consume.yaml | 4 ---- .github/workflows/hive-execute.yaml | 3 --- .github/workflows/test.yaml | 11 ++--------- Justfile | 4 ++-- docs/dev/deps_and_packaging.md | 3 ++- docs/specs/writing_specs.md | 2 +- pyproject.toml | 7 ++++++- uv.lock | 6 ++++-- 12 files changed, 34 insertions(+), 40 deletions(-) create mode 100644 .github/actions/setup-env-pypy/action.yaml delete mode 100644 .github/actions/setup-env/action.yaml diff --git a/.github/actions/build-evmone/action.yaml b/.github/actions/build-evmone/action.yaml index f5dc63e9a58..64490d3b4f3 100644 --- a/.github/actions/build-evmone/action.yaml +++ b/.github/actions/build-evmone/action.yaml @@ -66,10 +66,10 @@ runs: with: cmake-version: '3.x' - - name: Install GMP Linux + - name: Install build dependencies Linux if: runner.os == 'Linux' && steps.cache-restore.outputs.cache-hit != 'true' shell: bash - run: sudo apt-get -q update && sudo apt-get -qy install libgmp-dev + run: sudo apt-get -q update && sudo apt-get -qy install build-essential libgmp-dev - name: Install GMP macOS if: runner.os == 'macOS' && steps.cache-restore.outputs.cache-hit != 'true' diff --git a/.github/actions/build-fixtures/action.yaml b/.github/actions/build-fixtures/action.yaml index 719c3221a75..4152101136c 100644 --- a/.github/actions/build-fixtures/action.yaml +++ b/.github/actions/build-fixtures/action.yaml @@ -28,7 +28,6 @@ runs: - uses: ./.github/actions/setup-uv with: enable-cache: "false" - - uses: ./.github/actions/setup-env - name: Install EEST shell: bash run: uv sync --no-progress diff --git a/.github/actions/setup-env-pypy/action.yaml b/.github/actions/setup-env-pypy/action.yaml new file mode 100644 index 00000000000..ddc23d6304d --- /dev/null +++ b/.github/actions/setup-env-pypy/action.yaml @@ -0,0 +1,15 @@ +name: Setup PyPy Build Toolchain +description: | + Install the native toolchain needed to build sdists for PyPy jobs. + Several dependencies publish no PyPy wheels and are built from source: + libcst needs Rust; pycryptodome, PyYAML, regex, and MarkupSafe need a C + compiler. CPython jobs install wheels only and must not use this action. +runs: + using: "composite" + steps: + - name: Install C and Rust toolchains + shell: bash + run: | + sudo apt-get -q update + sudo DEBIAN_FRONTEND=noninteractive apt-get -qy install build-essential rustup + rustup update --no-self-update 1.89.0 && rustup default 1.89.0 diff --git a/.github/actions/setup-env/action.yaml b/.github/actions/setup-env/action.yaml deleted file mode 100644 index 57bcf5c06c9..00000000000 --- a/.github/actions/setup-env/action.yaml +++ /dev/null @@ -1,14 +0,0 @@ -name: Setup Environment -description: Common setup for Ethereum Spec jobs -runs: - using: "composite" - steps: - - name: Install Rust - shell: bash - run: | - sudo DEBIAN_FRONTEND=noninteractive apt-get install --yes --force-yes build-essential rustup - rustup update --no-self-update 1.89.0 && rustup default 1.89.0 - - - name: Install build dependencies - shell: bash - run: sudo DEBIAN_FRONTEND=noninteractive apt-get install --yes --force-yes build-essential pkg-config libsecp256k1-dev diff --git a/.github/workflows/hive-consume.yaml b/.github/workflows/hive-consume.yaml index e08a8c61808..a87c39fac8e 100644 --- a/.github/workflows/hive-consume.yaml +++ b/.github/workflows/hive-consume.yaml @@ -161,10 +161,6 @@ jobs: with: cache-dependency-glob: "execution-specs/uv.lock" - - name: Setup environment - if: matrix.mode == 'dev' - uses: ./execution-specs/.github/actions/setup-env - - name: Load cached Docker images uses: ./execution-specs/.github/actions/load-docker-images diff --git a/.github/workflows/hive-execute.yaml b/.github/workflows/hive-execute.yaml index 4719a1faeb6..06344cbe9c2 100644 --- a/.github/workflows/hive-execute.yaml +++ b/.github/workflows/hive-execute.yaml @@ -71,9 +71,6 @@ jobs: with: cache-dependency-glob: "execution-specs/uv.lock" - - name: Setup environment - uses: ./execution-specs/.github/actions/setup-env - - name: Load cached Docker images uses: ./execution-specs/.github/actions/load-docker-images diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 6835d40dd67..5270d87a51b 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -37,9 +37,6 @@ jobs: - name: Ensure SHA pinned actions uses: zgosalvez/github-actions-ensure-sha-pinned-actions@70c4af2ed5282c51ba40566d026d6647852ffa3e # v5.0.1 - uses: ./.github/actions/setup-uv - - name: Install build dependencies - shell: bash - run: sudo DEBIAN_FRONTEND=noninteractive apt-get install --yes --force-yes build-essential pkg-config - name: Detect Python version id: python shell: bash @@ -99,7 +96,6 @@ jobs: - uses: ./.github/actions/setup-uv with: python-version: "3.14" - - uses: ./.github/actions/setup-env - name: Run fill (${{ matrix.label }}) run: > just fill --from ${{ matrix.from_fork }} --until ${{ matrix.until_fork }} @@ -121,7 +117,7 @@ jobs: - uses: ./.github/actions/setup-uv with: python-version: "pypy3.11" - - uses: ./.github/actions/setup-env + - uses: ./.github/actions/setup-env-pypy - name: Run fill-pypy tests run: just fill-pypy env: @@ -136,7 +132,6 @@ jobs: - uses: ./.github/actions/setup-uv with: python-version: "3.14" - - uses: ./.github/actions/setup-env - name: Fill and run json-loader tests run: just json-loader env: @@ -156,7 +151,6 @@ jobs: - uses: ./.github/actions/setup-uv with: python-version: "3.14" - - uses: ./.github/actions/setup-env - name: Run spec-tools tests run: just spec-tools env: @@ -168,7 +162,6 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: ./.github/actions/setup-uv - - uses: ./.github/actions/setup-env - uses: ./.github/actions/build-evmone - name: Run test-tests run: just test-tests @@ -183,7 +176,7 @@ jobs: - uses: ./.github/actions/setup-uv with: python-version: "pypy3.11" - - uses: ./.github/actions/setup-env + - uses: ./.github/actions/setup-env-pypy - uses: ./.github/actions/build-evmone - name: Run test-tests-pypy run: just test-tests-pypy diff --git a/Justfile b/Justfile index a068c1f1d05..3d3aabac781 100644 --- a/Justfile +++ b/Justfile @@ -64,10 +64,10 @@ deadcode: format-check *args: uv run ruff format --check "$@" -# Run type checking with mypy +# Run type checking with mypy (installs the optimized dependency group) [group('static analysis')] typecheck *args: - uv run mypy "$@" + uv run --group optimized mypy "$@" # Check EELS import isolation [group('static analysis')] diff --git a/docs/dev/deps_and_packaging.md b/docs/dev/deps_and_packaging.md index a0d6997fe9e..c04e4d01b4b 100644 --- a/docs/dev/deps_and_packaging.md +++ b/docs/dev/deps_and_packaging.md @@ -61,7 +61,8 @@ Development dependencies are grouped into `[dependency-groups]`, one group per c Groups defined by the specs package: - `test`, `lint`, `actionlint`, `doc`, `mkdocs`. -- `dev` includes all of the above plus the `optimized` extra. +- `dev` includes all of the above. +- `optimized` pulls in the `optimized` extra for the sync tool. It is not part of `dev` because `ethash` has no CPython 3.14 wheels and would require a C toolchain to install; enable it with `uv sync --group optimized` when needed. Groups defined by the testing package: diff --git a/docs/specs/writing_specs.md b/docs/specs/writing_specs.md index 03f862251c6..caa2704dd9c 100644 --- a/docs/specs/writing_specs.md +++ b/docs/specs/writing_specs.md @@ -161,7 +161,7 @@ The following must be updated manually afterwards: The sync tool uses an RPC provider to fetch and validate blocks against EELS. The validated state can be stored in a local DB. Because syncing directly with the specs is very slow, the sync tool can also leverage the `ethereum_optimized` module, which contains alternative implementations of routines in EELS optimized for speed rather than clarity/readability. -Invoke the tool with `ethereum-spec-sync`. Arguments: +Invoke the tool with `uv run --group optimized ethereum-spec-sync` (the `optimized` dependency group provides the `ethereum_optimized` module). Arguments: - `rpc-url`: Endpoint providing the Ethereum RPC API. Defaults to `http://localhost:8545/`. - `unoptimized`: Don't use the optimized state/ethash (this can be extremely slow). diff --git a/pyproject.toml b/pyproject.toml index bf89f3593cb..79341714348 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -255,9 +255,14 @@ dev = [ { include-group = "actionlint" }, { include-group = "doc" }, { include-group = "mkdocs" }, - "ethereum-execution[optimized]", "psutil>=7.2.2", ] +# Opt-in (not part of dev): native-accelerated state and ethash for the +# sync tool. ethash has no CPython 3.14 wheels, so installing this group +# requires a C toolchain on 3.14. +optimized = [ + "ethereum-execution[optimized]", +] [tool.setuptools.dynamic] version = { attr = "ethereum.__version__" } diff --git a/uv.lock b/uv.lock index 667058a8cc2..3f44340f65a 100644 --- a/uv.lock +++ b/uv.lock @@ -858,7 +858,6 @@ dev = [ { name = "cairosvg" }, { name = "codespell" }, { name = "docc" }, - { name = "ethereum-execution", extra = ["optimized"] }, { name = "ethereum-execution-testing" }, { name = "filelock" }, { name = "fladrif" }, @@ -926,6 +925,9 @@ mkdocs = [ { name = "pillow" }, { name = "pyspelling" }, ] +optimized = [ + { name = "ethereum-execution", extra = ["optimized"] }, +] test = [ { name = "ethereum-execution-testing" }, { name = "filelock" }, @@ -966,7 +968,6 @@ dev = [ { name = "codespell", specifier = "==2.4.1" }, { name = "codespell", specifier = ">=2.4.1,<3" }, { name = "docc", specifier = ">=0.6.1,<0.7.0" }, - { name = "ethereum-execution", extras = ["optimized"] }, { name = "ethereum-execution-testing", editable = "packages/testing" }, { name = "filelock", specifier = ">=3.15.1,<4" }, { name = "fladrif", specifier = ">=0.2.0,<0.3.0" }, @@ -1034,6 +1035,7 @@ mkdocs = [ { name = "pillow", specifier = ">=12,<13" }, { name = "pyspelling", specifier = ">=2.8.2,<3" }, ] +optimized = [{ name = "ethereum-execution", extras = ["optimized"] }] test = [ { name = "ethereum-execution-testing", editable = "packages/testing" }, { name = "filelock", specifier = ">=3.15.1,<4" }, From e6382bc2a1a5a331234f39b82f71b3c659743199 Mon Sep 17 00:00:00 2001 From: Rafael Matias <rafael@skyle.net> Date: Thu, 16 Jul 2026 06:30:51 +0200 Subject: [PATCH 134/233] feat(test-cli): add `--extract-opcode-count` opcode tracing (#3124) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(fill-stateful): add --extract-opcode-count opcode tracing Trace each execution-phase block via debug_traceBlockByHash with a JS opcode-counting tracer and record per-opcode execution counts in the fixture's _info.metadata.opcode_count, matching the standard fill benchmark path. - DebugRPC.trace_block_by_hash wraps debug_traceBlockByHash. - ClientBackend gains debug_rpc + extract_opcode_count; a JS tracer constant; extract_block_opcode_count() aggregating per-tx opcode counts; reset_opcode_count() now zeroes the per-test tally. - make_stateful_fixture traces execution-phase blocks only (setup blocks skipped) and accumulates onto the backend, which the filler already serializes into _info.metadata.opcode_count. - fill_stateful plugin: --extract-opcode-count flag, debug_rpc wired into the backend, per-test reset_opcode_count in the t8n override. * fix(fill-stateful): harden opcode extraction; decouple from verification Address review of the --extract-opcode-count feature: - Decouple metadata from benchmark verification: only accumulate onto the metadata opcode_count, never set benchmark_opcode_count. The live-client trace is recorded for analysis; it no longer activates target-opcode verification (which stays a t8n-path concern and could otherwise fail benchmark tests on >5% trace divergence). - Make extract_block_opcode_count non-fatal: a debug_traceBlockByHash RPC error is logged and skipped instead of aborting the fill. - Normalize opcode names: geth emits 'opcode 0xNN not defined' for undefined opcodes, which OpcodeCount could not parse and would raise on. Reduce such names to the bare 0xNN (UndefinedOpcode); drop any still-unrecognized name with a warning. * feat(fill-stateful): verify opcode counts against declared targets With --extract-opcode-count on, feed the live-client traced count to benchmark opcode verification: a test declaring fixed/expected_opcode_ count now fails the fill when its live-client count diverges >5% from the target. Stays None (verification skipped) when the flag is off. * feat(fill-stateful): opcode counting across clients (normalize names + struct-log fallback) - Normalize tracer opcode names case-insensitively so clients emitting non-canonical names (nethermind returns calldatasize / pusH1) are counted instead of dropped as unrecognized. - Fall back to the universally-supported struct-log tracer (disableStack/Memory/Storage, counting structLogs[].op) when a client's JS tracer is unavailable — erroring or empty — so besu (no JS tracer) is covered. Cached per client to avoid re-probing the JS tracer every block. - Update the --extract-opcode-count help text accordingly. Verified against geth (JS), nethermind (JS + normalize) and besu (struct-log fallback): all three produce identical opcode counts. * feat(fill-stateful): record per-payload opcode counts as opcode_counts array Replace the single aggregated _info.metadata.opcode_count with _info.metadata.opcode_counts — an array with one entry per engineNewPayloads block (opcode_counts[i] is the count for engineNewPayloads[i], or null if its trace was unavailable), so multi-block benchmarks keep per-payload granularity. The list is emitted via FillResult.metadata (only when --extract-opcode-count is on); reset_opcode_count is now a no-op so the filler no longer writes the singular opcode_count for stateful fixtures. * refactor: add fixture and reduce comment * refactor: opcode count trace logic * refactor: error handling for rpc call --------- Co-authored-by: LouisTsai <q1030176@gmail.com> --- docs/filling_tests/fill_stateful.md | 5 +- .../plugins/fill_stateful/fill_stateful.py | 28 +++- .../client_clis/client_backend.py | 140 +++++++++++++++++- .../testing/src/execution_testing/rpc/rpc.py | 9 ++ .../src/execution_testing/specs/blockchain.py | 32 ++-- 5 files changed, 200 insertions(+), 14 deletions(-) diff --git a/docs/filling_tests/fill_stateful.md b/docs/filling_tests/fill_stateful.md index 4bc31c64af1..a6a4eec654b 100644 --- a/docs/filling_tests/fill_stateful.md +++ b/docs/filling_tests/fill_stateful.md @@ -13,7 +13,7 @@ The target client must expose: - `testing` (`testing_buildBlockV1`) — block construction with explicit transaction ordering. - `engine` — `engine_newPayloadVX`, `engine_forkchoiceUpdatedVX`. -- `eth`, `debug` — chain queries and `debug_setHead` (or `debug_resetHead` on clients like Nethermind that lack `debug_setHead`) for between-test rewind. +- `eth`, `debug` — chain queries and `debug_setHead` (or `debug_resetHead` on clients like Nethermind that lack `debug_setHead`) for between-test rewind. `--extract-opcode-count` additionally needs `debug_traceBlockByHash` with JS tracer support. - `web3` (optional) — `web3_clientVersion` is recorded into the fixture's `_info.filling-transition-tool` for traceability. The production-ready filler is `ethpandaops/geth:master`. @@ -122,6 +122,7 @@ Optional: - `--default-{gas-price,max-fee-per-gas,max-priority-fee-per-gas,max-fee-per-blob-gas}` — pin per-session fees; defaults bump live-query values by `1.5×`. - `--output PATH` — default `./fixtures`. - `--clean` — wipe the output dir before filling. +- `--extract-opcode-count` — after building each block, trace it via `debug_traceBlockByHash` (a JS opcode-counting tracer) and record per-opcode execution counts (execution-phase blocks only) in the fixture's `_info.metadata.opcode_counts` — an array with one entry per `engineNewPayloads` block (`opcode_counts[i]` is the count for `engineNewPayloads[i]`, or `null` if its trace was unavailable), so multi-block benchmarks keep per-payload granularity. When a benchmark test declares a target opcode count (`fixed_opcode_count`/`expected_opcode_count`), the live-client count is verified against it and the fill fails on >5% divergence. Requires the `debug` namespace with JS tracer support. Adds a full re-execution trace per block, so it is slow and opt-in. ## Output layout @@ -135,7 +136,7 @@ Optional: └── <test>.json # per-test setup + execution payloads ``` -Each `pre_run/<start_block_hash>.json` (a `StatefulPreRunFixture`) is replayed once per `benchmarkoor` run. Per-test fixtures (`BlockchainEngineStatefulFixture`) reference their setup file by hash: a fixture with `startBlockHash = 0xabc...` is preceded by `pre_run/0xabc....json`. Each per-test fixture carries `snapshotBlockNumber`/`Hash`, `startBlockNumber`/`Hash`, `setupEngineNewPayloads`, `engineNewPayloads`, plus a `benchmarkGasUsed` field and the EL build in `_info.filling-transition-tool`. The hash-based filename leaves room for multiple pre-run files (e.g. different setup variants off one snapshot) without coordinating names. +Each `pre_run/<start_block_hash>.json` (a `StatefulPreRunFixture`) is replayed once per `benchmarkoor` run. Per-test fixtures (`BlockchainEngineStatefulFixture`) reference their setup file by hash: a fixture with `startBlockHash = 0xabc...` is preceded by `pre_run/0xabc....json`. Each per-test fixture carries `snapshotBlockNumber`/`Hash`, `startBlockNumber`/`Hash`, `setupEngineNewPayloads`, `engineNewPayloads`, plus a `benchmarkGasUsed` field and the EL build in `_info.filling-transition-tool`. With `--extract-opcode-count`, it also carries `_info.metadata.opcode_counts` (an array of per-opcode execution counts, one entry per `engineNewPayloads` block). The hash-based filename leaves room for multiple pre-run files (e.g. different setup variants off one snapshot) without coordinating names. !!! warning "Snapshot anchoring" `--snapshot-block` accepts a hash on purpose. Anchoring to `latest` works against a quiescent client, but a live reorg between session start and fixture write would silently re-anchor the fixture to a different block. The hash form rejects that. diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/fill_stateful/fill_stateful.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/fill_stateful/fill_stateful.py index 587f856f1cc..c98280b986f 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/fill_stateful/fill_stateful.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/fill_stateful/fill_stateful.py @@ -132,6 +132,21 @@ def pytest_addoption(parser: pytest.Parser) -> None: "by hash. The produced fixtures are anchored by hash regardless." ), ) + group.addoption( + "--extract-opcode-count", + action="store_true", + dest="extract_opcode_count", + default=False, + help=( + "Trace each built block via debug_traceBlockByHash and record " + "per-opcode execution counts in the fixture's " + "_info.metadata.opcode_counts. Uses a client-side JS tracer where " + "supported (geth/nethermind/erigon/reth) and falls back to the " + "struct-log tracer otherwise (besu). Requires the `debug` " + "namespace. Adds a full re-execution trace per block — slow; " + "opt-in." + ), + ) def _resolve_session_fork( @@ -475,9 +490,17 @@ def debug_rpc(eth_rpc: EthRPC) -> DebugRPC: return DebugRPC(eth_rpc.url) +@pytest.fixture(scope="session") +def extract_opcode_count(request: pytest.FixtureRequest) -> bool: + """Whether --extract-opcode-count block tracing is enabled.""" + return request.config.getoption("extract_opcode_count") + + @pytest.fixture(scope="session") def client_backend( eth_rpc: ChainBuilderEthRPC, + debug_rpc: DebugRPC, + extract_opcode_count: bool, session_fork: Fork | TransitionFork, default_gas_price: int | None, default_max_fee_per_gas: int | None, @@ -501,6 +524,8 @@ def client_backend( engine_rpc=eth_rpc.engine_rpc, eth_rpc=eth_rpc, fork=session_fork, + debug_rpc=debug_rpc, + extract_opcode_count=extract_opcode_count, ) priority_fee = default_max_priority_fee_per_gas @@ -719,7 +744,8 @@ def session_t8n( def t8n( session_t8n: ClientBackend, ) -> Generator[ClientBackend, None, None]: - """Override: no per-test reset needed for ClientBackend.""" + """Override: zero per-test opcode counts (no-op unless enabled).""" + session_t8n.reset_opcode_count() yield session_t8n diff --git a/packages/testing/src/execution_testing/client_clis/client_backend.py b/packages/testing/src/execution_testing/client_clis/client_backend.py index eebd02d278d..73252b132fc 100644 --- a/packages/testing/src/execution_testing/client_clis/client_backend.py +++ b/packages/testing/src/execution_testing/client_clis/client_backend.py @@ -11,16 +11,25 @@ info, block boundaries, and pre-alloc declarations flow unchanged. """ +import re from pathlib import Path from typing import Any, ClassVar, Dict, List, Optional from execution_testing.base_types import Bytes, Hash from execution_testing.exceptions import ExceptionBase, ExceptionMapper from execution_testing.forks import Fork, TransitionFork -from execution_testing.rpc import EngineRPC, EthRPC, TestingRPC, Web3RPC +from execution_testing.logging import get_logger +from execution_testing.rpc import ( + DebugRPC, + EngineRPC, + EthRPC, + TestingRPC, + Web3RPC, +) from execution_testing.rpc.rpc_types import ( ForkchoiceState, GetPayloadResponse, + JSONRPCError, PayloadAttributes, PayloadStatusEnum, ) @@ -35,9 +44,91 @@ Result, Traces, TransitionToolOutput, + validate_opcode, ) from .transition_tool import TransitionTool +logger = get_logger(__name__) + +# Per-tx opcode tally; clients ship no built-in opcode-count tracer. +OPCODE_COUNT_TRACER_JS = """{ + counts: {}, + step: function(log) { + var op = log.op.toString(); + this.counts[op] = (this.counts[op] || 0) + 1; + }, + fault: function() {}, + result: function() { return this.counts; } +}""" + +# Keep each struct-log step small. +STRUCT_LOG_TRACER_CONFIG = { + "disableStack": True, + "disableMemory": True, + "disableStorage": True, +} + + +def _normalize_opcode_name(name: str) -> str | None: + """ + Map a tracer opcode name to one ``OpcodeCount`` accepts. + + Handles non-canonical casing (nethermind) and geth's + ``"opcode 0xNN not defined"``; unrecognized names are dropped. + """ + for candidate in (name, name.upper()): + try: + validate_opcode(candidate) + return candidate + except Exception: + continue + match = re.search(r"0x[0-9a-fA-F]+", name) + if match is not None: + return match.group(0) + logger.warning(f"opcode trace: dropping unrecognized {name!r}") + return None + + +def _opcode_count_from_js_tracer(traces: Any) -> OpcodeCount: + """Aggregate the per-tx ``{opcode: count}`` maps the JS tracer emits.""" + counts: Dict[str, int] = {} + for entry in traces or []: + if not isinstance(entry, dict): + continue + tx_counts = entry.get("result") + # Clients that ignore the JS tracer echo struct logs. + if not isinstance(tx_counts, dict) or "structLogs" in tx_counts: + continue + for opcode, count in tx_counts.items(): + key = _normalize_opcode_name(opcode) + if key is None or not isinstance(count, int): + continue + counts[key] = counts.get(key, 0) + count + return OpcodeCount.model_validate(counts) + + +def _opcode_count_from_struct_logs(traces: Any) -> OpcodeCount: + """Count ``structLogs[].op`` entries, one per executed opcode.""" + counts: Dict[str, int] = {} + for entry in traces or []: + if not isinstance(entry, dict): + continue + # Some clients return the struct-log result unwrapped. + result = entry.get("result") + if not isinstance(result, dict): + result = entry + for step in result.get("structLogs") or []: + if not isinstance(step, dict): + continue + op = step.get("op") + if not op: + continue + key = _normalize_opcode_name(op) + if key is None: + continue + counts[key] = counts.get(key, 0) + 1 + return OpcodeCount.model_validate(counts) + class ClientBackendExceptionMapper(ExceptionMapper): """ @@ -89,12 +180,18 @@ def __init__( engine_rpc: EngineRPC, eth_rpc: EthRPC, fork: Fork | TransitionFork, + debug_rpc: DebugRPC | None = None, + extract_opcode_count: bool = False, ) -> None: """Initialize with the RPC clients and the session fork.""" self.testing_rpc = testing_rpc self.engine_rpc = engine_rpc self.eth_rpc = eth_rpc self.fork = fork + self.debug_rpc = debug_rpc + self.extract_opcode_count = extract_opcode_count + # Sticky fallback to struct logs (besu has no JS tracer). + self._js_tracer_unsupported = False self.exception_mapper = ClientBackendExceptionMapper() self.snapshot_block = None self.start_block = None @@ -126,7 +223,7 @@ def reset_traces(self) -> None: return def reset_opcode_count(self) -> None: - """No-op — opcode counting not supported.""" + """No-op — ``opcode_count`` stays ``None`` so the filler skips it.""" return def set_cache(self, *, key: str) -> bool: @@ -209,6 +306,45 @@ def evaluate( ), ) + def extract_block_opcode_count( + self, block_hash: Hash + ) -> OpcodeCount | None: + """ + Tally executed opcodes for a block via ``debug_traceBlockByHash``. + + ``None`` when ``--extract-opcode-count`` is off or the trace + fails (logged, never fatal). Prefers the JS tracer; a client + that rejects it falls back to struct logs for the session, + while transient errors only skip the block. + """ + if not self.extract_opcode_count or self.debug_rpc is None: + return None + + try: + if not self._js_tracer_unsupported: + try: + traces = self._trace_block( + block_hash, {"tracer": OPCODE_COUNT_TRACER_JS} + ) + return _opcode_count_from_js_tracer(traces) + except JSONRPCError as e: + logger.info(f"JS tracer rejected ({e}); using struct logs") + self._js_tracer_unsupported = True + traces = self._trace_block(block_hash, STRUCT_LOG_TRACER_CONFIG) + return _opcode_count_from_struct_logs(traces) + except Exception as e: + logger.warning(f"opcode trace failed for block {block_hash}: {e}") + return None + + def _trace_block( + self, block_hash: Hash, tracer_config: Dict[str, Any] + ) -> Any: + """Raw ``debug_traceBlockByHash`` call; exceptions propagate.""" + assert self.debug_rpc is not None + return self.debug_rpc.trace_block_by_hash( + str(block_hash), tracer_config + ) + def _payload_attributes( self, env: Any, diff --git a/packages/testing/src/execution_testing/rpc/rpc.py b/packages/testing/src/execution_testing/rpc/rpc.py index 316f8a09ec2..a8fa1816687 100644 --- a/packages/testing/src/execution_testing/rpc/rpc.py +++ b/packages/testing/src/execution_testing/rpc/rpc.py @@ -1301,6 +1301,15 @@ def trace_call(self, tr: dict[str, str], block_number: str) -> Any | None: request=RPCCall(method="traceCall", params=params) ).result_or_raise() + def trace_block_by_hash( + self, block_hash: str, tracer_config: dict[str, Any] + ) -> Any: + """`debug_traceBlockByHash`: Trace every transaction in a block.""" + params = [block_hash, tracer_config] + return self.post_request( + request=RPCCall(method="traceBlockByHash", params=params) + ).result_or_raise() + def set_head(self, block_number: str) -> None: """`debug_setHead`: Reset chain head to the given block number.""" self.post_request( diff --git a/packages/testing/src/execution_testing/specs/blockchain.py b/packages/testing/src/execution_testing/specs/blockchain.py index 6f927f24729..61dc378252e 100644 --- a/packages/testing/src/execution_testing/specs/blockchain.py +++ b/packages/testing/src/execution_testing/specs/blockchain.py @@ -1465,6 +1465,8 @@ def make_stateful_fixture( setup_payloads: List[FixtureEngineNewPayload] = [] execution_payloads: List[FixtureEngineNewPayload] = [] + # Aligned 1:1 with execution_payloads; None when no trace. + execution_opcode_counts: List[Dict[str, int] | None] = [] head_hash = start_block_hash benchmark_gas_used: int | None = None benchmark_opcode_count: OpcodeCount | None = None @@ -1485,21 +1487,29 @@ def make_stateful_fixture( payload = payload_metadata_to_fixture( built_block.engine_payload, phase=block.phase ) + # The client's authoritative block hash (the FixtureHeader RLP + # hash diverges — the client picks fields like gas_limit). + client_hash = Hash( + built_block.engine_payload.payload_response.execution_payload.block_hash + ) if payload.phase == TestPhase.SETUP: setup_payloads.append(payload) else: execution_payloads.append(payload) + block_opcode_count = t8n.extract_block_opcode_count( + client_hash + ) + execution_opcode_counts.append( + block_opcode_count.model_dump() + if block_opcode_count is not None + else None + ) if self.operation_mode == OpMode.BENCHMARKING: benchmark_gas_used = int(built_block.result.gas_used) - benchmark_opcode_count = built_block.result.opcode_count - # Overwrite the block_hash apply_new_parent just recorded — - # it's the FixtureHeader-recomputed RLP hash, which diverges - # from the client's authoritative hash (client picks fields - # like gas_limit). The next block's parent_hash must point at - # what the client actually built. - client_hash = Hash( - built_block.engine_payload.payload_response.execution_payload.block_hash - ) + # Consumed by BenchmarkTest's opcode-count verification. + benchmark_opcode_count = block_opcode_count + # apply_new_parent records the RLP hash; the next block's + # parent_hash must point at what the client actually built. env = apply_new_parent(built_block.env, built_block.header) env = env.copy( block_hashes={ @@ -1525,11 +1535,15 @@ def make_stateful_fixture( else None ), ) + metadata: Dict[str, Any] = {} + if t8n.extract_opcode_count: + metadata["opcode_counts"] = execution_opcode_counts return FillResult( fixture=fixture, gas_optimization=None, benchmark_gas_used=benchmark_gas_used, benchmark_opcode_count=benchmark_opcode_count, + metadata=metadata, post_verifications=PostVerifications.from_alloc(self.post), ) From 693dbbda7c9fe200ffe9762472bf8d9ff6e0a054 Mon Sep 17 00:00:00 2001 From: Jochem Brouwer <jochembrouwer96@gmail.com> Date: Thu, 16 Jul 2026 07:45:36 +0200 Subject: [PATCH 135/233] fix(fill-stateful): verify receipt status when filling stateful fixtures (#3142) * fix(fill-stateful): verify receipt status when filling stateful fixtures * chore: small suggestion * fix(fill-stateful): build Osaka-valid headers in receipt-status unit tests * refactor: add missing attribute - add extract_opcode_count, debug_rpc to client_backend --------- Co-authored-by: LouisTsai <q1030176@gmail.com> --- .../src/execution_testing/specs/base.py | 2 +- .../src/execution_testing/specs/blockchain.py | 10 + .../tests/test_stateful_receipt_status.py | 256 ++++++++++++++++++ 3 files changed, 267 insertions(+), 1 deletion(-) create mode 100644 packages/testing/src/execution_testing/specs/tests/test_stateful_receipt_status.py diff --git a/packages/testing/src/execution_testing/specs/base.py b/packages/testing/src/execution_testing/specs/base.py index 5a330a5e7e9..a5e53669978 100644 --- a/packages/testing/src/execution_testing/specs/base.py +++ b/packages/testing/src/execution_testing/specs/base.py @@ -307,7 +307,7 @@ def validate_receipt_status( receipts match. Catches silent OOG failures that roll back state and invalidate benchmarks. """ - if "expected_receipt_status" not in self.model_fields_set: + if self.expected_receipt_status is None: return for i, receipt in enumerate(receipts): if receipt.status is not None and ( diff --git a/packages/testing/src/execution_testing/specs/blockchain.py b/packages/testing/src/execution_testing/specs/blockchain.py index 61dc378252e..a7ab9a9ecde 100644 --- a/packages/testing/src/execution_testing/specs/blockchain.py +++ b/packages/testing/src/execution_testing/specs/blockchain.py @@ -606,6 +606,8 @@ class TestingBuildBlock(BuiltBlock): so ``make_stateful_fixture`` can record what the client built. """ + __test__ = False # "Test" prefix; keep pytest from collecting it + model_config = CamelModel.model_config | {"arbitrary_types_allowed": True} engine_payload: EnginePayloadMetadata @@ -1504,6 +1506,14 @@ def make_stateful_fixture( if block_opcode_count is not None else None ) + # Setup blocks (pre-alloc funding/deploys) are exempt: + # ``expected_receipt_status`` describes the test's own + # transactions, and setup txs always succeed. + if built_block.result.receipts: + self.validate_receipt_status( + receipts=built_block.result.receipts, + block_number=int(built_block.header.number), + ) if self.operation_mode == OpMode.BENCHMARKING: benchmark_gas_used = int(built_block.result.gas_used) # Consumed by BenchmarkTest's opcode-count verification. diff --git a/packages/testing/src/execution_testing/specs/tests/test_stateful_receipt_status.py b/packages/testing/src/execution_testing/specs/tests/test_stateful_receipt_status.py new file mode 100644 index 00000000000..77fff4a2cd2 --- /dev/null +++ b/packages/testing/src/execution_testing/specs/tests/test_stateful_receipt_status.py @@ -0,0 +1,256 @@ +"""Test suite for receipt-status verification in ``make_stateful_fixture``.""" + +from typing import Any, Iterator, List + +import pytest + +from execution_testing.base_types import Address, Bloom, Hash +from execution_testing.client_clis import ClientBackend +from execution_testing.client_clis.cli_types import ( + EnginePayloadMetadata, + LazyAllocJson, + Result, +) +from execution_testing.fixtures.blockchain import ( + BlockchainEngineStatefulFixture, + FixtureExecutionPayload, + FixtureHeader, +) +from execution_testing.forks import Osaka +from execution_testing.rpc.rpc_types import GetPayloadResponse +from execution_testing.test_types import ( + Alloc, + Environment, + TestPhase, + Transaction, +) +from execution_testing.test_types.receipt_types import TransactionReceipt + +from ..base import FillResult +from ..blockchain import Block, BlockchainTest, TestingBuildBlock + +FORK = Osaka +START_BLOCK_NUMBER = 1 + + +def _header(number: int) -> FixtureHeader: + """Build a minimal valid header for the test fork.""" + return FixtureHeader( + fork=FORK, + fee_recipient=Address(0), + state_root=Hash(0), + number=number, + gas_limit=30_000_000, + gas_used=21_000, + timestamp=number * 12, + extra_data=b"\x00", + base_fee_per_gas=7, + withdrawals_root=Hash(0), + blob_gas_used=0, + excess_blob_gas=0, + parent_beacon_block_root=Hash(0), + requests_hash=Hash(0), + ) + + +def _tx(phase: TestPhase) -> Transaction: + """Build a Transaction tagged with the given test phase.""" + tx = Transaction() + tx.test_phase = phase + return tx + + +def _built_block(number: int, statuses: List[int]) -> TestingBuildBlock: + """ + Build a ``TestingBuildBlock`` whose receipts carry ``statuses``, + mimicking what ``ClientBackend.evaluate`` assembles from a live + client's ``testing_buildBlockV1`` + ``eth_getTransactionReceipt``. + """ + header = _header(number) + payload = FixtureExecutionPayload.from_fixture_header( + header=header, + transactions=[], + withdrawals=None, + ) + new_payload_version = FORK.engine_new_payload_version() + forkchoice_updated_version = FORK.engine_forkchoice_updated_version() + assert new_payload_version is not None + assert forkchoice_updated_version is not None + return TestingBuildBlock( + header=header, + env=Environment(number=number, timestamp=number * 12), + alloc=LazyAllocJson(raw={}, _state_root=Hash(0)), + state_root=Hash(0), + txs=[], + ommers=[], + withdrawals=None, + requests=None, + result=Result( + state_root=Hash(0), + transactions_trie=Hash(0), + receipts_root=Hash(0), + logs_hash=Hash(0), + logs_bloom=Bloom(0), + receipts=[TransactionReceipt(status=s) for s in statuses], + gas_used=21_000, + ), + fork=FORK, + block_access_list=None, + engine_payload=EnginePayloadMetadata( + payload_response=GetPayloadResponse(execution_payload=payload), + new_payload_version=new_payload_version, + forkchoice_updated_version=forkchoice_updated_version, + parent_beacon_block_root=Hash(0), + ), + ) + + +@pytest.fixture +def client_backend() -> ClientBackend: + """ + Stub ``ClientBackend`` with snapshot/start blocks pre-captured. + """ + # ``__new__`` skips ``__init__``: no live RPC endpoints are needed + # because ``generate_block_data`` is monkeypatched below. + backend = ClientBackend.__new__(ClientBackend) + start_header = _header(START_BLOCK_NUMBER) + block_dict = start_header.model_dump( + by_alias=True, mode="json", exclude_none=True + ) + block_dict["hash"] = str(start_header.block_hash) + backend.fork = FORK + backend.snapshot_block = block_dict + backend.start_block = block_dict + backend.extract_opcode_count = False + backend.debug_rpc = None + return backend + + +def _fill_stateful( + monkeypatch: pytest.MonkeyPatch, + client_backend: ClientBackend, + statuses_per_block: List[List[int]], + phases: List[TestPhase], + **kwargs: Any, +) -> FillResult: + """ + Run ``make_stateful_fixture`` with one block per entry of ``phases``, + stubbing ``generate_block_data`` to return receipts with the matching + ``statuses_per_block`` entry. + """ + assert len(statuses_per_block) == len(phases) + calls: Iterator[List[int]] = iter(statuses_per_block) + block_numbers = iter( + range(START_BLOCK_NUMBER + 1, START_BLOCK_NUMBER + 1 + len(phases)) + ) + + def fake_generate_block_data( + _self: BlockchainTest, **_kwargs: Any + ) -> TestingBuildBlock: + return _built_block(next(block_numbers), next(calls)) + + monkeypatch.setattr( + BlockchainTest, "generate_block_data", fake_generate_block_data + ) + test = BlockchainTest( + fork=FORK, + pre=Alloc(), + post=Alloc(), + blocks=[Block(txs=[_tx(phase)]) for phase in phases], + **kwargs, + ) + return test.make_stateful_fixture(client_backend) + + +def test_execution_status_mismatch_raises( + monkeypatch: pytest.MonkeyPatch, client_backend: ClientBackend +) -> None: + """A status-0 receipt with ``expected_receipt_status=1`` must throw.""" + with pytest.raises(Exception, match=r"receipt status 0, expected 1"): + _fill_stateful( + monkeypatch, + client_backend, + statuses_per_block=[[0]], + phases=[TestPhase.EXECUTION], + expected_receipt_status=1, + ) + + +def test_expected_failure_but_tx_succeeded_raises( + monkeypatch: pytest.MonkeyPatch, client_backend: ClientBackend +) -> None: + """A status-1 receipt with ``expected_receipt_status=0`` must throw.""" + with pytest.raises(Exception, match=r"receipt status 1, expected 0"): + _fill_stateful( + monkeypatch, + client_backend, + statuses_per_block=[[1]], + phases=[TestPhase.EXECUTION], + expected_receipt_status=0, + ) + + +def test_single_failed_receipt_in_block_raises( + monkeypatch: pytest.MonkeyPatch, client_backend: ClientBackend +) -> None: + """One bad receipt among good ones is enough to throw.""" + with pytest.raises( + Exception, + match=r"Transaction 2 in block \d+ has receipt status 0", + ): + _fill_stateful( + monkeypatch, + client_backend, + statuses_per_block=[[1, 1, 0]], + phases=[TestPhase.EXECUTION], + expected_receipt_status=1, + ) + + +@pytest.mark.parametrize("status", [0, 1]) +def test_matching_status_fills( + monkeypatch: pytest.MonkeyPatch, + client_backend: ClientBackend, + status: int, +) -> None: + """Receipts matching ``expected_receipt_status`` fill normally.""" + result = _fill_stateful( + monkeypatch, + client_backend, + statuses_per_block=[[status, status]], + phases=[TestPhase.EXECUTION], + expected_receipt_status=status, + ) + assert isinstance(result.fixture, BlockchainEngineStatefulFixture) + + +def test_setup_phase_blocks_are_exempt( + monkeypatch: pytest.MonkeyPatch, client_backend: ClientBackend +) -> None: + """ + ``expected_receipt_status`` describes the test's own transactions; + a mismatching status in a SETUP-phase block must not throw. + """ + result = _fill_stateful( + monkeypatch, + client_backend, + statuses_per_block=[[0], [1]], + phases=[TestPhase.SETUP, TestPhase.EXECUTION], + expected_receipt_status=1, + ) + assert isinstance(result.fixture, BlockchainEngineStatefulFixture) + assert len(result.fixture.setup_payloads) == 1 + assert len(result.fixture.payloads) == 1 + + +def test_unset_expected_status_skips_validation( + monkeypatch: pytest.MonkeyPatch, client_backend: ClientBackend +) -> None: + """Without ``expected_receipt_status``, any status fills normally.""" + result = _fill_stateful( + monkeypatch, + client_backend, + statuses_per_block=[[0, 1]], + phases=[TestPhase.EXECUTION], + ) + assert isinstance(result.fixture, BlockchainEngineStatefulFixture) From a45205c64a2bde60e8fd7c7bd05f9ae0073ab127 Mon Sep 17 00:00:00 2001 From: spencer <spencer.taylor-brown@ethereum.org> Date: Thu, 16 Jul 2026 11:06:44 +0100 Subject: [PATCH 136/233] feat(ci): fill mainnet fixtures nightly, draft cached `tests@` releases (#3100) Co-authored-by: danceratopz <danceratopz@gmail.com> --- .github/actions/build-fixtures/action.yaml | 17 +- .github/configs/evm.yaml | 6 - .github/configs/feature.yaml | 3 + .github/scripts/check_new_commits.py | 158 ++++++ .github/scripts/generate_build_matrix.py | 20 +- .github/scripts/resolve_cached_release.py | 296 ++++++++++ .github/scripts/tests/test_release_scripts.py | 518 ++++++++++++++++++ .github/workflows/release_fixtures.yaml | 138 ++++- Justfile | 17 + docs/dev/releasing_tests.md | 95 ++-- 10 files changed, 1181 insertions(+), 87 deletions(-) create mode 100644 .github/scripts/check_new_commits.py create mode 100644 .github/scripts/resolve_cached_release.py diff --git a/.github/actions/build-fixtures/action.yaml b/.github/actions/build-fixtures/action.yaml index 4152101136c..61d255c22ab 100644 --- a/.github/actions/build-fixtures/action.yaml +++ b/.github/actions/build-fixtures/action.yaml @@ -13,6 +13,9 @@ inputs: split_label: description: "Label for this fork-range split. Empty for unsplit builds." default: "" + split_retention_days: + description: "retention-days for the split fixture artifact. Empty for the repo default." + default: "" evm: description: "Override the evm impl. Defaults to the feature's evm-type." default: "" @@ -49,6 +52,8 @@ runs: run: sudo apt-get install -y pigz - name: Generate fixtures using fill shell: bash + env: + PYTEST_XDIST_AUTO_NUM_WORKERS: ${{ steps.evm-builder.outputs.xdist }} run: | IS_SPLIT="${{ inputs.split_label }}" @@ -60,13 +65,14 @@ runs: FORK_ARGS="" fi + EVM_ARGS="" + if [ "${{ steps.evm-builder.outputs.impl }}" != "eels" ]; then + EVM_ARGS="--evm-bin=${{ steps.evm-builder.outputs.evm-bin }}" + fi + # Allow exit code 5 (NO_TESTS_COLLECTED) for fork ranges with no tests. EXIT_CODE=0 - if [ "${{ steps.evm-builder.outputs.impl }}" = "eels" ]; then - uv run fill -n ${{ steps.evm-builder.outputs.xdist }} ${{ steps.properties.outputs.fill-params }} $FORK_ARGS $OUTPUT_ARG --build-name ${{ inputs.release_name }} --no-html --durations=100 --log-level=DEBUG || EXIT_CODE=$? - else - uv run fill -n ${{ steps.evm-builder.outputs.xdist }} --evm-bin=${{ steps.evm-builder.outputs.evm-bin }} ${{ steps.properties.outputs.fill-params }} $FORK_ARGS $OUTPUT_ARG --build-name ${{ inputs.release_name }} --no-html --durations=100 --log-level=DEBUG || EXIT_CODE=$? - fi + just fill-release $EVM_ARGS ${{ steps.properties.outputs.fill-params }} $FORK_ARGS $OUTPUT_ARG --build-name ${{ inputs.release_name }} || EXIT_CODE=$? if [ "$EXIT_CODE" -ne 0 ] && [ "$EXIT_CODE" -ne 5 ]; then exit "$EXIT_CODE" fi @@ -95,3 +101,4 @@ runs: include-hidden-files: true path: fixtures_${{ inputs.release_name }}/ if-no-files-found: ignore + retention-days: ${{ inputs.split_retention_days }} diff --git a/.github/configs/evm.yaml b/.github/configs/evm.yaml index 621ad39623d..b36b347d981 100644 --- a/.github/configs/evm.yaml +++ b/.github/configs/evm.yaml @@ -17,12 +17,6 @@ evmone: evm-bin: evmone xdist: auto targets: ["evmone-cli"] -benchmark: - impl: geth - repo: ethereum/go-ethereum - ref: master - evm-bin: evm - xdist: auto besu: impl: besu repo: hyperledger/besu diff --git a/.github/configs/feature.yaml b/.github/configs/feature.yaml index fcb18a91525..1b47c731cd9 100644 --- a/.github/configs/feature.yaml +++ b/.github/configs/feature.yaml @@ -5,6 +5,9 @@ # Any `<feat>-devnet` input resolves to the shared `devnet` entry but keeps # its name in the tag; the devnet number lives in the version (X), not the # feature name, so this file needs no edits for new devnets. +# `tests` is also what the scheduled nightly run of the `release_fixtures` +# workflow fills: mainnet forks only, so the rotating nightly artifact is a +# release-ready rehearsal of the next tests@ release. tests: evm-type: eels fill-params: --until=BPO4 --generate-all-formats diff --git a/.github/scripts/check_new_commits.py b/.github/scripts/check_new_commits.py new file mode 100644 index 00000000000..da778daf459 --- /dev/null +++ b/.github/scripts/check_new_commits.py @@ -0,0 +1,158 @@ +#!/usr/bin/env -S uv run --script +# +# /// script +# requires-python = ">=3.12" +# /// +""" +Decide whether a scheduled nightly fill has new commits to fill. + +Usage: `check_new_commits.py` (all inputs come from the environment). + +Compare the current commit against the head SHA of the last scheduled +run of the release workflow that actually filled: the newest successful +*scheduled* run that uploaded artifacts. A quiet nightly skips its +build jobs yet still concludes as a successful run, so plain success is +no evidence of a fill. Anchoring on real fills means a nightly that +fails or skips keeps re-running until a fill goes green and no commit +slips through unfilled; filtering on scheduled runs means manual +releases never advance the nightly baseline. Manual +(`workflow_dispatch`) runs always run. + +A quiet stretch with no new commits still refreshes: once the last +fill is `REFRESH_AGE` old -- or its artifact is no longer live -- the +nightly re-runs anyway, so a live artifact always exists within the +workflow's five-day retention and the release pipeline keeps getting +exercised. + +Read `GITHUB_EVENT_NAME`, `GITHUB_REPOSITORY` and `GITHUB_SHA` from the +environment and query the GitHub API via the `gh` CLI (authenticated by +`GH_TOKEN`). Print `run=true|false` to stdout for `$GITHUB_OUTPUT` and +append the new-commit list (or a skip notice) to the +`$GITHUB_STEP_SUMMARY` file. +""" + +import json +import os +import subprocess +import sys +from datetime import datetime, timedelta, timezone + +WORKFLOW_FILE = "release_fixtures.yaml" + +# Re-run a quiet nightly once the last successful fill is this old, so +# a fresh artifact is uploaded before the previous one lapses (the +# workflow retains scheduled tarballs for five days). +REFRESH_AGE = timedelta(days=4) + + +def gh_api(path: str) -> str: + """Return the stdout of `gh api <path>`, exiting non-zero on error.""" + result = subprocess.run( + ["gh", "api", path], capture_output=True, text=True + ) + if result.returncode != 0: + print(f"Error: gh api {path} failed:", file=sys.stderr) + print(result.stderr, file=sys.stderr) + sys.exit(1) + return result.stdout + + +def last_real_nightly(repository: str) -> tuple[str, str, bool]: + """ + Return the head SHA, creation time and artifact liveness of the + last scheduled run that actually filled. + + A skipped nightly still concludes as a successful scheduled run, + so taking the newest success as the baseline would let skip-runs + keep resetting the refresh clock while the last real artifact + quietly expires. Walk the recent successful scheduled runs and + take the newest one that uploaded artifacts, reporting whether any + of them is still live. Return empty strings when none exists yet. + """ + runs = json.loads( + gh_api( + f"repos/{repository}/actions/workflows/{WORKFLOW_FILE}" + "/runs?status=success&event=schedule&per_page=10" + ) + )["workflow_runs"] + for run in runs: + artifacts = json.loads( + gh_api(f"repos/{repository}/actions/runs/{run['id']}/artifacts") + )["artifacts"] + if artifacts: + live = any(not a["expired"] for a in artifacts) + return str(run["head_sha"]), str(run["created_at"]), live + return "", "", False + + +def is_stale(created_at: str) -> bool: + """Return whether a run created at *created_at* is due a refresh.""" + created = datetime.fromisoformat(created_at) + return datetime.now(timezone.utc) - created >= REFRESH_AGE + + +def commits_since(repository: str, last_sha: str, head_sha: str) -> list[str]: + """Return `- <sha> <subject>` lines for commits after *last_sha*.""" + compare = json.loads( + gh_api(f"repos/{repository}/compare/{last_sha}...{head_sha}") + ) + return [ + f"- {c['sha'][:7]} {(c['commit']['message'].splitlines() or [''])[0]}" + for c in compare["commits"] + ] + + +def append_summary(text: str) -> None: + """Append *text* to the GitHub step summary, or stderr if unset.""" + summary_path = os.environ.get("GITHUB_STEP_SUMMARY") + if summary_path: + with open(summary_path, "a") as f: + f.write(text + "\n") + else: + print(text, file=sys.stderr) + + +def main() -> None: + """Print `run=true|false` and write the step summary.""" + if os.environ["GITHUB_EVENT_NAME"] != "schedule": + # Manual releases always run. + print("run=true") + return + + repository = os.environ["GITHUB_REPOSITORY"] + head_sha = os.environ["GITHUB_SHA"] + + last_sha, last_created, artifact_live = last_real_nightly(repository) + if last_sha: + commits = commits_since(repository, last_sha, head_sha) + else: + # No prior successful nightly recorded; fill to get a baseline. + commits = ["- (no previous successful nightly fill found)"] + + if commits: + print("run=true") + append_summary( + "### Commits since last successful nightly fill\n" + + "\n".join(commits) + ) + elif not artifact_live: + print("run=true") + append_summary( + "No new commits, but no live fixture artifact exists; refilling." + ) + elif is_stale(last_created): + print("run=true") + append_summary( + "No new commits, but the last nightly fill is older than " + f"{REFRESH_AGE.days} days; refreshing before its artifact " + "retention lapses." + ) + else: + print("run=false") + append_summary( + "No new commits since the last successful nightly fill; skipping." + ) + + +if __name__ == "__main__": + main() diff --git a/.github/scripts/generate_build_matrix.py b/.github/scripts/generate_build_matrix.py index 02fde3087dc..200bdb788ad 100644 --- a/.github/scripts/generate_build_matrix.py +++ b/.github/scripts/generate_build_matrix.py @@ -10,7 +10,7 @@ Validate release inputs and generate the build matrix for release fixture workflows. -Usage: `generate_build_matrix.py <feature> <version> [branch]`. +Usage: `generate_build_matrix.py <feature> <version> [branch] [evm]`. First validate the dispatch inputs (see `validate_inputs`), then read `.github/configs/feature.yaml` and emit a flat JSON build matrix suitable @@ -31,6 +31,7 @@ FEATURE_CONFIG = Path(".github/configs/feature.yaml") FORK_RANGES_CONFIG = Path(".github/configs/fork-ranges.yaml") +EVM_CONFIG = Path(".github/configs/evm.yaml") VERSION_RE = re.compile(r"^v[0-9]+\.[0-9]+\.[0-9]+$") @@ -81,12 +82,13 @@ def fail(message: str) -> NoReturn: sys.exit(1) -def validate_inputs(feature: str, version: str, branch: str) -> None: +def validate_inputs(feature: str, version: str, branch: str, evm: str) -> None: """ Validate the release dispatch inputs before building a matrix. - Centralize the feature/version checks here so they are unit-testable - rather than living as inline bash in the release workflow. + Centralize the feature/version/evm checks here so they are + unit-testable rather than living as inline bash in the release + workflow. For `<feat>-devnet` releases the major version (`X` of `vX.Y.Z`) must equal the devnet number encoded in the release branch, so a @@ -97,6 +99,10 @@ def validate_inputs(feature: str, version: str, branch: str) -> None: if not VERSION_RE.match(version): fail(f"version '{version}' must match vX.Y.Z (e.g. v20.0.0)") + # An `evm` override must name a key in evm.yaml. + if evm and evm not in load_config(EVM_CONFIG): + fail(f"evm '{evm}' is not a key in {EVM_CONFIG}") + # A bare `devnet` has no friendly `<feat>-` prefix to tag with. if feature in ("devnet", "-devnet"): fail("devnet releases require a <feat>- prefix, e.g. bal-devnet") @@ -207,7 +213,8 @@ def main() -> None: args = sys.argv[1:] if len(args) < 2: print( - "Usage: generate_build_matrix.py <feature> <version> [branch]", + "Usage: generate_build_matrix.py " + "<feature> <version> [branch] [evm]", file=sys.stderr, ) sys.exit(1) @@ -215,8 +222,9 @@ def main() -> None: name = args[0] version = args[1] branch = args[2] if len(args) > 2 else "" + evm = args[3] if len(args) > 3 else "" - validate_inputs(name, version, branch) + validate_inputs(name, version, branch, evm) config = load_config(FEATURE_CONFIG) fork_ranges = load_config(FORK_RANGES_CONFIG) or [] diff --git a/.github/scripts/resolve_cached_release.py b/.github/scripts/resolve_cached_release.py new file mode 100644 index 00000000000..6c8f9450133 --- /dev/null +++ b/.github/scripts/resolve_cached_release.py @@ -0,0 +1,296 @@ +#!/usr/bin/env -S uv run --script +# +# /// script +# requires-python = ">=3.12" +# /// +""" +Resolve the nightly fill whose artifact a cached release reuses. + +Usage: `resolve_cached_release.py` (all inputs come from the +environment). + +Dispatching `release_fixtures.yaml` with the `cached` flag drafts a +`tests@` release from the newest nightly artifact instead of +refilling: the scheduled nightly runs already build the mainnet +`tests` feature into a release-shaped `fixtures_<commit>` artifact. +The `commit` input picks the nightly built at that commit instead of +the newest one. This script validates the request, picks the nightly +run whose artifact the release job downloads, and pins the exact +commit that run built so the release tag lands on it. + +Checks performed, failing fast on the first violation: + +- The release is for the `tests` feature on the default branch (no + `branch` input): that is what the nightly fills. +- `INPUT_VERSION` matches `vX.Y.Z` and is greater than the newest + existing `tests@` tag (releases always move forward; anything + unusual belongs in a fresh fill). +- The resolved run is a successful *scheduled* run of + `release_fixtures.yaml` with a live (unexpired) tarball artifact + named for the run's own commit: the newest one, or with + `INPUT_COMMIT` (7+ hex characters) the one built at that commit. + Skip-runs upload no artifacts and expired fills cannot be + downloaded, so both are passed over. +- The resolved commit contains the newest existing `tests@` release, + so a cached release never regresses content (re-releasing the same + commit stays allowed). +- The resolved commit is an ancestor of the current branch head. + Commits after it are listed in the step summary so the releaser can + see what the release will NOT contain. + +Read `GITHUB_REPOSITORY`, `GITHUB_SHA`, `INPUT_FEATURE`, +`INPUT_BRANCH`, `INPUT_VERSION` and `INPUT_COMMIT` from the +environment and query the +GitHub API via the `gh` CLI (authenticated by `GH_TOKEN`). Print +`run_id`, `target_sha` and `artifact_name` as `key=value` lines for +`$GITHUB_OUTPUT`. +""" + +import json +import os +import re +import subprocess +import sys +from typing import NoReturn + +WORKFLOW_FILE = "release_fixtures.yaml" + +# The combined-tarball artifact a nightly `tests` fill uploads is +# named for the short hash of the built commit; only that artifact is +# ever reused by a cached release. +ARTIFACT_PREFIX = "fixtures" + +VERSION_RE = re.compile(r"^v([0-9]+)\.([0-9]+)\.([0-9]+)$") +COMMIT_RE = re.compile(r"^[0-9a-f]{7,40}$") + + +def artifact_name(head_sha: str) -> str: + """Return the tarball artifact name of a nightly built at *head_sha*.""" + return f"{ARTIFACT_PREFIX}_{head_sha[:7]}" + + +def fail(message: str) -> NoReturn: + """Print an error to stderr and exit non-zero.""" + print(f"Error: {message}", file=sys.stderr) + sys.exit(1) + + +def gh_api(path: str, paginate: bool = False) -> str: + """ + Return the stdout of `gh api <path>`, exiting non-zero on error. + + With *paginate*, follow the Link header through every page and + return a JSON array of per-page responses (`--slurp`). + """ + flags = ["--paginate", "--slurp"] if paginate else [] + result = subprocess.run( + ["gh", "api", *flags, path], capture_output=True, text=True + ) + if result.returncode != 0: + print(f"Error: gh api {path} failed:", file=sys.stderr) + print(result.stderr, file=sys.stderr) + sys.exit(1) + return result.stdout + + +def append_summary(text: str) -> None: + """Append *text* to the GitHub step summary, or stderr if unset.""" + summary_path = os.environ.get("GITHUB_STEP_SUMMARY") + if summary_path: + with open(summary_path, "a") as f: + f.write(text + "\n") + else: + print(text, file=sys.stderr) + + +def parse_version(version: str) -> tuple[int, int, int]: + """Return the (major, minor, patch) tuple of a `vX.Y.Z` version.""" + m = VERSION_RE.match(version) + if not m: + fail(f"version '{version}' must match vX.Y.Z (e.g. v5.0.0)") + major, minor, patch = (int(g) for g in m.groups()) + return major, minor, patch + + +def newest_tests_tag(repository: str) -> str: + """ + Return the newest existing `tests@vX.Y.Z` tag, or "" when none. + + The `tests@` ref prefix cannot match any other feature's tags + (those are namespaced `tests-<feature>@`), so every match is a + mainnet tests release. + + The listing is paginated in ref-name order, not version order + (`tests@v9...` sorts after `tests@v20...`), so every page must be + fetched before taking the maximum. + """ + pages = json.loads( + gh_api( + f"repos/{repository}/git/matching-refs/tags/tests@", + paginate=True, + ) + ) + refs = [ref for page in pages for ref in page] + tags = [ref["ref"].removeprefix("refs/tags/") for ref in refs] + versioned = [ + (parse_version(tag.removeprefix("tests@")), tag) + for tag in tags + if VERSION_RE.match(tag.removeprefix("tests@")) + ] + if not versioned: + return "" + return max(versioned)[1] + + +def has_live_tests_artifact( + repository: str, run_id: str, head_sha: str +) -> bool: + """ + Return whether *run_id* has a live tarball artifact. + + The artifact name is derived from the run's own head SHA, so a + name that does not match the commit it claims to be built from is + passed over. + """ + artifacts = json.loads( + gh_api(f"repos/{repository}/actions/runs/{run_id}/artifacts") + )["artifacts"] + name = artifact_name(head_sha) + return any(a["name"] == name and not a["expired"] for a in artifacts) + + +def cached_run(repository: str, commit: str) -> tuple[str, str]: + """ + Return the (run id, head SHA) of the nightly run to reuse. + + Take the newest successful scheduled run with a live artifact, or + with *commit* the run built at that commit (skip-runs upload no + artifacts and expired fills cannot be downloaded, so both are + passed over). On a commit miss, list the reusable nightlies. + """ + runs = json.loads( + gh_api( + f"repos/{repository}/actions/workflows/{WORKFLOW_FILE}" + "/runs?status=success&event=schedule&per_page=10" + ) + )["workflow_runs"] + live: list[str] = [] + for run in runs: + run_id, head_sha = str(run["id"]), str(run["head_sha"]) + if not has_live_tests_artifact(repository, run_id, head_sha): + continue + if not commit or head_sha.startswith(commit): + return run_id, head_sha + live.append(head_sha[:7]) + if commit: + available = ", ".join(live) if live else "none" + fail( + f"no nightly with a live artifact was built at {commit} " + f"(reusable nightlies: {available}); dispatch a fresh fill " + "instead" + ) + fail( + f"no scheduled run of {WORKFLOW_FILE} with a live " + f"`{ARTIFACT_PREFIX}_<commit>` artifact found; dispatch a fresh " + "fill instead" + ) + + +def ensure_not_behind(repository: str, prev_tag: str, target_sha: str) -> None: + """ + Fail when *target_sha* does not contain the *prev_tag* release. + + A cached release must never regress content: the resolved nightly + has to be at or after the newest `tests@` tag. Re-releasing the + identical commit stays allowed. + """ + compare = json.loads( + gh_api(f"repos/{repository}/compare/{prev_tag}...{target_sha}") + ) + if compare["status"] not in ("identical", "ahead"): + fail( + f"the resolved nightly ({target_sha}) does not contain the " + f"newest tests release ({prev_tag}); a cached release must " + "not regress content" + ) + + +def commits_after( + repository: str, target_sha: str, head_sha: str +) -> list[str]: + """ + Return `- <sha> <subject>` lines for commits after *target_sha*. + + Fail when *target_sha* is not an ancestor of *head_sha*: a nightly + built from a rewritten or foreign branch must not be released. + """ + compare = json.loads( + gh_api(f"repos/{repository}/compare/{target_sha}...{head_sha}") + ) + if compare["status"] not in ("identical", "ahead"): + fail( + f"nightly commit {target_sha} is not an ancestor of " + f"{head_sha} (compare status: {compare['status']})" + ) + return [ + f"- {c['sha'][:7]} {(c['commit']['message'].splitlines() or [''])[0]}" + for c in compare["commits"] + ] + + +def main() -> None: + """Print the resolved run for `$GITHUB_OUTPUT` and the summary.""" + repository = os.environ["GITHUB_REPOSITORY"] + head_sha = os.environ["GITHUB_SHA"] + version = os.environ["INPUT_VERSION"] + + if os.environ.get("INPUT_FEATURE") != "tests": + fail("cached releases are only available for feature=tests") + if os.environ.get("INPUT_BRANCH"): + fail( + "cached releases reuse a default-branch nightly; drop the " + "`branch` input or dispatch a fresh fill" + ) + + commit = os.environ.get("INPUT_COMMIT", "") + if commit and not COMMIT_RE.match(commit): + fail(f"commit '{commit}' must be 7 to 40 lowercase hex characters") + + requested = parse_version(version) + prev_tag = newest_tests_tag(repository) + if prev_tag and requested <= parse_version( + prev_tag.removeprefix("tests@") + ): + fail( + f"version '{version}' must be greater than the newest " + f"tests release ({prev_tag})" + ) + + run_id, target_sha = cached_run(repository, commit) + if prev_tag: + ensure_not_behind(repository, prev_tag, target_sha) + missing = commits_after(repository, target_sha, head_sha) + + print(f"run_id={run_id}") + print(f"target_sha={target_sha}") + print(f"artifact_name={artifact_name(target_sha)}") + + run_url = f"https://github.com/{repository}/actions/runs/{run_id}" + append_summary( + f"Reusing nightly fill run [{run_id}]({run_url}) " + f"(built at `{target_sha}`) for the `tests@{version}` draft." + ) + if missing: + append_summary( + "### Commits NOT included in this release\n" + + "\n".join(missing) + + "\n\nDispatch a fresh fill to include them." + ) + else: + append_summary( + "The nightly is up to date with the current branch head." + ) + + +if __name__ == "__main__": + main() diff --git a/.github/scripts/tests/test_release_scripts.py b/.github/scripts/tests/test_release_scripts.py index a74c4c67c08..44e6dbcab77 100644 --- a/.github/scripts/tests/test_release_scripts.py +++ b/.github/scripts/tests/test_release_scripts.py @@ -6,8 +6,10 @@ """ import json +import os import subprocess import tarfile +from datetime import datetime, timedelta, timezone from pathlib import Path SCRIPTS_DIR = Path(__file__).parent.parent @@ -16,6 +18,8 @@ BUILD_MATRIX_SCRIPT = SCRIPTS_DIR / "generate_build_matrix.py" TARBALL_SCRIPT = SCRIPTS_DIR / "create_release_tarball.py" MERGE_INDEX_SCRIPT = SCRIPTS_DIR / "merge_index_files.py" +CHECK_COMMITS_SCRIPT = SCRIPTS_DIR / "check_new_commits.py" +RESOLVE_CACHED_SCRIPT = SCRIPTS_DIR / "resolve_cached_release.py" def run_script(script: Path, *args: str) -> subprocess.CompletedProcess: @@ -170,6 +174,520 @@ def test_devnet_matching_major_passes(self): out = parse_matrix_output(result.stdout) assert out["feature_name"] == "glamsterdam-devnet" + def test_unknown_evm_fails(self): + """Verify an evm override missing from evm.yaml is rejected.""" + result = run_script( + BUILD_MATRIX_SCRIPT, "tests", "v24.0.0", "", "nonexistent" + ) + assert result.returncode == 1 + assert "not a key" in result.stderr + + def test_known_evm_passes(self): + """Verify an evm override that is a key in evm.yaml passes.""" + result = run_script( + BUILD_MATRIX_SCRIPT, "tests", "v24.0.0", "", "evmone" + ) + assert result.returncode == 0 + + +# Fake `gh` served from PATH: answers the API calls the commit-check +# and cached-release scripts make with canned JSON from env vars, and +# fails loudly on any other (or unconfigured) call. The API path is +# the last argument (flags such as `--paginate --slurp` may precede +# it). Per-run artifact responses come from +# `FAKE_GH_ARTIFACTS_<run_id>`, falling back to `FAKE_GH_ARTIFACTS`. +FAKE_GH = """#!/usr/bin/env bash +path="${@: -1}" +case "$path" in + *actions/workflows*) response="$FAKE_GH_RUNS" ;; + */artifacts) + run_id="${path##*/runs/}" + run_id="${run_id%%/*}" + var="FAKE_GH_ARTIFACTS_${run_id}" + response="${!var:-$FAKE_GH_ARTIFACTS}" + ;; + *matching-refs*) response="$FAKE_GH_TAGS" ;; + *compare/tests@*) response="$FAKE_GH_COMPARE_TAG" ;; + *compare*) response="$FAKE_GH_COMPARE" ;; + *) response="" ;; +esac +if [ -z "$response" ]; then + echo "unexpected gh call: $*" >&2 + exit 1 +fi +printf '%s' "$response" +""" + +# Canned artifact-list responses for the fake `gh`. +LIVE_ARTIFACTS = '{"artifacts": [{"expired": false}]}' +EXPIRED_ARTIFACTS = '{"artifacts": [{"expired": true}]}' +NO_ARTIFACTS = '{"artifacts": []}' + + +class TestCheckNewCommits: + """Test check_new_commits.py.""" + + def run_check( + self, + tmp_path: Path, + event_name: str, + runs: str = "", + compare: str = "", + artifacts: str = "", + per_run_artifacts: dict[int, str] | None = None, + ) -> tuple[subprocess.CompletedProcess, Path]: + """Run the script with a fake `gh` on PATH; return it + summary.""" + bin_dir = tmp_path / "bin" + bin_dir.mkdir(exist_ok=True) + fake_gh = bin_dir / "gh" + fake_gh.write_text(FAKE_GH) + fake_gh.chmod(0o755) + + summary = tmp_path / "summary.md" + env = os.environ.copy() + env["PATH"] = f"{bin_dir}:{env['PATH']}" + env["GITHUB_EVENT_NAME"] = event_name + env["GITHUB_REPOSITORY"] = "ethereum/execution-specs" + env["GITHUB_SHA"] = "b" * 40 + env["GITHUB_STEP_SUMMARY"] = str(summary) + env["FAKE_GH_RUNS"] = runs + env["FAKE_GH_COMPARE"] = compare + env["FAKE_GH_ARTIFACTS"] = artifacts + for run_id, response in (per_run_artifacts or {}).items(): + env[f"FAKE_GH_ARTIFACTS_{run_id}"] = response + + result = subprocess.run( + ["uv", "run", "-q", str(CHECK_COMMITS_SCRIPT)], + capture_output=True, + text=True, + cwd=REPO_ROOT, + env=env, + ) + return result, summary + + def test_dispatch_always_runs_without_api_calls(self, tmp_path): + """Verify a manual dispatch runs and never calls the API.""" + # The fake `gh` fails every call (no canned responses), so a + # zero exit proves the script made no API call. + result, summary = self.run_check(tmp_path, "workflow_dispatch") + assert result.returncode == 0 + assert result.stdout.strip() == "run=true" + assert not summary.exists() + + def test_schedule_without_prior_run_fills_baseline(self, tmp_path): + """Verify the first scheduled run fills to get a baseline.""" + result, summary = self.run_check( + tmp_path, "schedule", runs='{"workflow_runs": []}' + ) + assert result.returncode == 0 + assert result.stdout.strip() == "run=true" + assert "no previous successful" in summary.read_text() + + @staticmethod + def run_json( + age: timedelta, head_sha: str = "b" * 40, run_id: int = 1 + ) -> dict: + """Return one workflow-run object created *age* ago.""" + created = datetime.now(timezone.utc) - age + return { + "id": run_id, + "head_sha": head_sha, + "created_at": created.isoformat(), + } + + @classmethod + def runs_json( + cls, age: timedelta, head_sha: str = "b" * 40, run_id: int = 1 + ) -> str: + """Return a last-successful-run response created *age* ago.""" + return json.dumps( + {"workflow_runs": [cls.run_json(age, head_sha, run_id)]} + ) + + def test_schedule_with_new_commits_runs(self, tmp_path): + """Verify new commits since the baseline trigger a run.""" + commit = { + "sha": "abcdef1" + "0" * 33, + "commit": {"message": "feat(x): subject\n\nbody"}, + } + result, summary = self.run_check( + tmp_path, + "schedule", + runs=self.runs_json(timedelta(hours=25), head_sha="a" * 40), + compare=json.dumps({"commits": [commit]}), + artifacts=LIVE_ARTIFACTS, + ) + assert result.returncode == 0 + assert result.stdout.strip() == "run=true" + text = summary.read_text() + assert "### Commits since last successful nightly fill" in text + # Short SHA plus the commit subject, without the body. + assert "- abcdef1 feat(x): subject" in text + assert "body" not in text + + def test_schedule_without_new_commits_skips(self, tmp_path): + """Verify no commits since a recent baseline skips the run.""" + result, summary = self.run_check( + tmp_path, + "schedule", + # Just inside the refresh age: pin the four-day boundary. + runs=self.runs_json(timedelta(days=3, hours=23)), + compare=json.dumps({"commits": []}), + artifacts=LIVE_ARTIFACTS, + ) + assert result.returncode == 0 + assert result.stdout.strip() == "run=false" + assert "skipping" in summary.read_text() + + def test_schedule_stale_quiet_baseline_refreshes(self, tmp_path): + """Verify a quiet nightly re-runs once its artifact nears expiry.""" + result, summary = self.run_check( + tmp_path, + "schedule", + runs=self.runs_json(timedelta(days=4, hours=1)), + compare=json.dumps({"commits": []}), + artifacts=LIVE_ARTIFACTS, + ) + assert result.returncode == 0 + assert result.stdout.strip() == "run=true" + assert "refreshing" in summary.read_text() + + def test_schedule_skip_runs_do_not_reset_refresh(self, tmp_path): + """ + Verify skip-runs neither advance the baseline nor its clock. + + A quiet nightly that skips its build still concludes as a + successful scheduled run; if it counted as the baseline, a + stretch of skip-runs would keep resetting the refresh clock + while the last real artifact quietly expired. + """ + runs = json.dumps( + { + "workflow_runs": [ + # Newest success skipped its build: no artifacts. + self.run_json(timedelta(hours=1), run_id=2), + # The last real fill is past the refresh age. + self.run_json(timedelta(days=4, hours=1), run_id=1), + ] + } + ) + result, summary = self.run_check( + tmp_path, + "schedule", + runs=runs, + compare=json.dumps({"commits": []}), + per_run_artifacts={2: NO_ARTIFACTS, 1: LIVE_ARTIFACTS}, + ) + assert result.returncode == 0 + assert result.stdout.strip() == "run=true" + assert "refreshing" in summary.read_text() + + def test_schedule_dead_artifact_refills(self, tmp_path): + """Verify a fill whose artifact is gone refills immediately.""" + result, summary = self.run_check( + tmp_path, + "schedule", + runs=self.runs_json(timedelta(days=1)), + compare=json.dumps({"commits": []}), + artifacts=EXPIRED_ARTIFACTS, + ) + assert result.returncode == 0 + assert result.stdout.strip() == "run=true" + assert "no live fixture artifact" in summary.read_text() + + def test_gh_failure_fails_the_check(self, tmp_path): + """Verify a failing `gh` call fails the script.""" + result, _ = self.run_check(tmp_path, "schedule") + assert result.returncode == 1 + assert "gh api" in result.stderr + + +# Canned responses for the cached-release script. Unlike the commit +# check, it matches artifacts by the commit-derived +# `fixtures_<short sha>` name, so the canned listings are built +# per head SHA. The tag listing is fetched with `--paginate --slurp` +# (a JSON array of pages); spreading the refs over two pages makes +# every test exercise the page flattening. +TESTS_TAGS = json.dumps( + [ + [{"ref": "refs/tags/tests@v3.1.2"}], + [{"ref": "refs/tags/tests@v4.0.0"}], + ] +) +NO_TAGS = "[[]]" +UP_TO_DATE = json.dumps({"status": "identical", "commits": []}) + + +def artifact_listing(head_sha: str, expired: bool = False) -> str: + """Return an artifact listing with a tarball named for *head_sha*.""" + return json.dumps( + { + "artifacts": [ + { + "name": f"fixtures_{head_sha[:7]}", + "expired": expired, + } + ] + } + ) + + +class TestResolveCachedRelease: + """Test resolve_cached_release.py.""" + + def run_resolve( + self, + tmp_path: Path, + version: str, + feature: str = "tests", + branch: str = "", + commit: str = "", + runs: str = "", + artifacts: str = "", + per_run_artifacts: dict[int, str] | None = None, + tags: str = "", + compare: str = "", + tag_compare: str = UP_TO_DATE, + ) -> tuple[subprocess.CompletedProcess, Path]: + """Run the script with a fake `gh` on PATH; return it + summary.""" + bin_dir = tmp_path / "bin" + bin_dir.mkdir(exist_ok=True) + fake_gh = bin_dir / "gh" + fake_gh.write_text(FAKE_GH) + fake_gh.chmod(0o755) + + summary = tmp_path / "summary.md" + env = os.environ.copy() + env["PATH"] = f"{bin_dir}:{env['PATH']}" + env["GITHUB_REPOSITORY"] = "ethereum/execution-specs" + env["GITHUB_SHA"] = "b" * 40 + env["GITHUB_STEP_SUMMARY"] = str(summary) + env["INPUT_VERSION"] = version + env["INPUT_FEATURE"] = feature + env["INPUT_BRANCH"] = branch + env["INPUT_COMMIT"] = commit + env["FAKE_GH_RUNS"] = runs + env["FAKE_GH_ARTIFACTS"] = artifacts + env["FAKE_GH_TAGS"] = tags + env["FAKE_GH_COMPARE"] = compare + env["FAKE_GH_COMPARE_TAG"] = tag_compare + for run_id, response in (per_run_artifacts or {}).items(): + env[f"FAKE_GH_ARTIFACTS_{run_id}"] = response + + result = subprocess.run( + ["uv", "run", "-q", str(RESOLVE_CACHED_SCRIPT)], + capture_output=True, + text=True, + cwd=REPO_ROOT, + env=env, + ) + return result, summary + + @staticmethod + def parse_outputs(stdout: str) -> dict[str, str]: + """Parse the key=value lines written for `$GITHUB_OUTPUT`.""" + return dict(line.split("=", 1) for line in stdout.strip().splitlines()) + + @staticmethod + def runs_json(*runs: dict) -> str: + """Return a workflow-runs listing response.""" + return json.dumps({"workflow_runs": list(runs)}) + + def test_reuses_newest_run_with_live_artifact(self, tmp_path): + """Verify skip-runs and expired fills are passed over.""" + commit = { + "sha": "abcdef1" + "0" * 33, + "commit": {"message": "feat(x): subject\n\nbody"}, + } + result, summary = self.run_resolve( + tmp_path, + "v4.0.1", + runs=self.runs_json( + # Newest success skipped its build: no artifacts. + {"id": 3, "head_sha": "c" * 40}, + {"id": 2, "head_sha": "a" * 40}, + {"id": 1, "head_sha": "d" * 40}, + ), + per_run_artifacts={ + 3: NO_ARTIFACTS, + 2: artifact_listing("a" * 40), + 1: artifact_listing("d" * 40, expired=True), + }, + tags=TESTS_TAGS, + compare=json.dumps({"status": "ahead", "commits": [commit]}), + ) + assert result.returncode == 0 + out = self.parse_outputs(result.stdout) + assert out["run_id"] == "2" + assert out["target_sha"] == "a" * 40 + assert out["artifact_name"] == "fixtures_aaaaaaa" + text = summary.read_text() + assert "### Commits NOT included in this release" in text + # Short SHA plus the commit subject, without the body. + assert "- abcdef1 feat(x): subject" in text + assert "body" not in text + + def test_up_to_date_nightly_resolves_cleanly(self, tmp_path): + """Verify no missing-commit section when nothing landed since.""" + result, summary = self.run_resolve( + tmp_path, + "v4.0.1", + runs=self.runs_json({"id": 2, "head_sha": "b" * 40}), + artifacts=artifact_listing("b" * 40), + tags=TESTS_TAGS, + compare=UP_TO_DATE, + ) + assert result.returncode == 0 + text = summary.read_text() + assert "up to date" in text + assert "NOT included" not in text + + def test_first_release_without_tags_resolves(self, tmp_path): + """Verify a cached release works before any tests@ tag exists.""" + result, _ = self.run_resolve( + tmp_path, + "v1.0.0", + runs=self.runs_json({"id": 2, "head_sha": "b" * 40}), + artifacts=artifact_listing("b" * 40), + tags=NO_TAGS, + compare=UP_TO_DATE, + ) + assert result.returncode == 0 + assert self.parse_outputs(result.stdout)["run_id"] == "2" + + def test_non_tests_feature_fails(self, tmp_path): + """Verify only the tests feature can release cached.""" + # The fake `gh` fails every call (no canned responses), so a + # clean feature error proves the guard fires before the API. + result, _ = self.run_resolve(tmp_path, "v4.0.1", feature="bal-devnet") + assert result.returncode == 1 + assert "only available for feature=tests" in result.stderr + + def test_branch_input_fails(self, tmp_path): + """Verify a cached release rejects a branch input.""" + result, _ = self.run_resolve( + tmp_path, "v4.0.1", branch="devnets/bal/7" + ) + assert result.returncode == 1 + assert "drop the `branch` input" in result.stderr + + def test_bad_version_format_fails(self, tmp_path): + """Verify a non vX.Y.Z version is rejected before any API call.""" + result, _ = self.run_resolve(tmp_path, "4.0.1") + assert result.returncode == 1 + assert "must match vX.Y.Z" in result.stderr + + def test_version_not_greater_than_newest_tag_fails(self, tmp_path): + """Verify the version must move past the newest tests@ tag.""" + result, _ = self.run_resolve(tmp_path, "v4.0.0", tags=TESTS_TAGS) + assert result.returncode == 1 + assert "must be greater" in result.stderr + assert "tests@v4.0.0" in result.stderr + + def test_no_reusable_run_fails(self, tmp_path): + """Verify a helpful error when every artifact has expired.""" + result, _ = self.run_resolve( + tmp_path, + "v4.0.1", + runs=self.runs_json({"id": 2, "head_sha": "a" * 40}), + artifacts=artifact_listing("a" * 40, expired=True), + tags=TESTS_TAGS, + ) + assert result.returncode == 1 + assert "dispatch a fresh fill instead" in result.stderr + + def test_mismatched_artifact_name_is_skipped(self, tmp_path): + """Verify an artifact named for another commit is not reused.""" + result, _ = self.run_resolve( + tmp_path, + "v4.0.1", + runs=self.runs_json({"id": 2, "head_sha": "a" * 40}), + # Live, but named for a different commit than the run built. + artifacts=artifact_listing("f" * 40), + tags=TESTS_TAGS, + ) + assert result.returncode == 1 + assert "dispatch a fresh fill instead" in result.stderr + + def test_commit_input_selects_that_nightly(self, tmp_path): + """Verify `commit` picks an older nightly over the newest.""" + result, _ = self.run_resolve( + tmp_path, + "v4.0.1", + commit="d" * 7, + runs=self.runs_json( + {"id": 3, "head_sha": "c" * 40}, + {"id": 2, "head_sha": "a" * 40}, + {"id": 1, "head_sha": "d" * 40}, + ), + per_run_artifacts={ + 3: NO_ARTIFACTS, + 2: artifact_listing("a" * 40), + 1: artifact_listing("d" * 40), + }, + tags=TESTS_TAGS, + compare=UP_TO_DATE, + ) + assert result.returncode == 0 + out = self.parse_outputs(result.stdout) + assert out["run_id"] == "1" + assert out["target_sha"] == "d" * 40 + assert out["artifact_name"] == "fixtures_ddddddd" + + def test_commit_input_without_match_fails(self, tmp_path): + """Verify a commit with no live nightly lists the candidates.""" + result, _ = self.run_resolve( + tmp_path, + "v4.0.1", + commit="beef111", + runs=self.runs_json({"id": 2, "head_sha": "a" * 40}), + artifacts=artifact_listing("a" * 40), + tags=TESTS_TAGS, + ) + assert result.returncode == 1 + assert "was built at beef111" in result.stderr + assert "aaaaaaa" in result.stderr + + def test_bad_commit_format_fails(self, tmp_path): + """Verify a malformed commit is rejected before any lookup.""" + result, _ = self.run_resolve( + tmp_path, "v4.0.1", commit="xyz", tags=TESTS_TAGS + ) + assert result.returncode == 1 + assert "hex characters" in result.stderr + + def test_release_behind_previous_fails(self, tmp_path): + """Verify a nightly older than the newest release is rejected.""" + result, _ = self.run_resolve( + tmp_path, + "v4.0.1", + runs=self.runs_json({"id": 2, "head_sha": "a" * 40}), + artifacts=artifact_listing("a" * 40), + tags=TESTS_TAGS, + tag_compare=json.dumps({"status": "behind", "commits": []}), + ) + assert result.returncode == 1 + assert "must not regress content" in result.stderr + + def test_diverged_nightly_fails(self, tmp_path): + """Verify a nightly off the branch history is not reused.""" + result, _ = self.run_resolve( + tmp_path, + "v4.0.1", + runs=self.runs_json({"id": 2, "head_sha": "a" * 40}), + artifacts=artifact_listing("a" * 40), + tags=TESTS_TAGS, + compare=json.dumps({"status": "diverged", "commits": []}), + ) + assert result.returncode == 1 + assert "not an ancestor" in result.stderr + + def test_gh_failure_fails_the_resolution(self, tmp_path): + """Verify a failing `gh` call fails the script.""" + result, _ = self.run_resolve(tmp_path, "v4.0.1") + assert result.returncode == 1 + assert "gh api" in result.stderr + class TestCreateReleaseTarball: """Test create_release_tarball.py.""" diff --git a/.github/workflows/release_fixtures.yaml b/.github/workflows/release_fixtures.yaml index 7ac44ac4f66..63e950cf7f5 100644 --- a/.github/workflows/release_fixtures.yaml +++ b/.github/workflows/release_fixtures.yaml @@ -1,6 +1,21 @@ name: Create Fixture Release +run-name: ${{ github.event_name == 'schedule' && 'Nightly Fill' || format('Create Fixture Release {0}@{1}{2}', inputs.feature, inputs.version, (inputs.cached || inputs.commit != '') && ' (cached)' || '') }} + +# Scheduled runs fill the mainnet `tests` feature (all tests, all fixture +# formats, up to the latest mainnet fork -- no dev forks) through the exact +# release pipeline, but skip the `release` job, so no tag or draft release +# is created: a rotating, always-available artifact of the mainnet +# fixtures. The cron fires at 02:00 UTC: the self-hosted runners are past +# the EU/US daytime peaks and results are ready before the EU morning. +# +# A manual `tests` release can reuse the newest of those artifacts +# instead of refilling via the `cached` checkbox: `build` and `combine` +# are skipped and the `release` job drafts from the nightly's tarball, +# tagged at the commit the nightly built. Runs in minutes. on: + schedule: + - cron: "0 2 * * *" workflow_dispatch: inputs: feature: @@ -27,15 +42,39 @@ on: description: "Override the t8n tool branch / tag / commit" required: false type: string + cached: + description: "Draft from the newest nightly artifact instead of refilling (tests only)" + required: false + type: boolean + default: false + commit: + description: "Release the nightly built at this commit (7+ hex chars); implies cached. Empty = newest." + required: false + type: string + +concurrency: + # Scheduled runs queue behind an in-flight nightly (never cancel a + # fill). Cached releases serialize too: drafts do not reserve their + # tag name, so parallel dispatches of the same version would silently + # coexist. Fresh releases are unconstrained (unique group per run). + group: ${{ github.event_name == 'schedule' && 'nightly-fill' || (inputs.cached || inputs.commit != '') && 'cached-release' || github.run_id }} + cancel-in-progress: false jobs: setup: runs-on: ubuntu-latest outputs: + # A cached release skips the fill: `build` (and with it `combine`) + # keys off `run`, and the release job downloads the resolved + # nightly's artifact and tags the commit it was built from. + run: ${{ (inputs.cached || inputs.commit != '') && 'false' || steps.check.outputs.run }} build_matrix: ${{ steps.matrix.outputs.build_matrix }} feature_name: ${{ steps.matrix.outputs.feature_name }} combine_labels: ${{ steps.matrix.outputs.combine_labels }} - target_sha: ${{ steps.target_sha.outputs.sha }} + target_sha: ${{ steps.cached.outputs.target_sha || steps.target_sha.outputs.sha }} + short_sha: ${{ steps.target_sha.outputs.short_sha }} + artifact_run_id: ${{ steps.cached.outputs.run_id }} + artifact_name: ${{ steps.cached.outputs.artifact_name }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: @@ -44,36 +83,67 @@ jobs: - name: Resolve target SHA id: target_sha shell: bash - run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" + # The short form is a fixed 7-character slice (not `git + # rev-parse --short`, whose length can drift): the cached-release + # resolver derives artifact names from the API's full SHA and the + # two must always agree. + run: | + sha="$(git rev-parse HEAD)" + echo "sha=$sha" >> "$GITHUB_OUTPUT" + echo "short_sha=${sha:0:7}" >> "$GITHUB_OUTPUT" - uses: ./.github/actions/setup-uv + - name: Check for new commits (scheduled runs) + id: check + env: + GH_TOKEN: ${{ github.token }} + run: | + uv run -q .github/scripts/check_new_commits.py >> "$GITHUB_OUTPUT" + + - name: Resolve the nightly artifact to reuse (cached releases) + id: cached + if: inputs.cached || inputs.commit != '' + env: + GH_TOKEN: ${{ github.token }} + INPUT_FEATURE: ${{ inputs.feature }} + INPUT_VERSION: ${{ inputs.version }} + INPUT_BRANCH: ${{ inputs.branch }} + INPUT_COMMIT: ${{ inputs.commit }} + run: | + # The feature/branch/version guards, run resolution and + # ancestry check live in (and are unit-tested via) + # resolve_cached_release.py. + uv run -q .github/scripts/resolve_cached_release.py >> "$GITHUB_OUTPUT" + - name: Validate input and generate build matrix id: matrix shell: bash env: - INPUT_FEATURE: ${{ inputs.feature }} - INPUT_VERSION: ${{ inputs.version }} + # Scheduled runs have no inputs: fill the mainnet `tests` + # feature; the placeholder version passes validation and is + # otherwise unused because the `release` job is skipped for + # scheduled runs. + INPUT_FEATURE: ${{ inputs.feature || 'tests' }} + INPUT_VERSION: ${{ inputs.version || 'v0.0.0' }} INPUT_BRANCH: ${{ inputs.branch }} INPUT_EVM: ${{ inputs.evm }} run: | - # An `evm` override must name a key in evm.yaml; the feature, - # version and devnet-branch validation lives in (and is unit-tested - # via) generate_build_matrix.py. - if [ -n "$INPUT_EVM" ] && ! grep -qE "^${INPUT_EVM}:" .github/configs/evm.yaml; then - echo "::error::evm '$INPUT_EVM' is not a key in .github/configs/evm.yaml" - exit 1 - fi - + # The feature, version, devnet-branch and evm-override validation + # lives in (and is unit-tested via) generate_build_matrix.py. uv run -q .github/scripts/generate_build_matrix.py \ - "$INPUT_FEATURE" "$INPUT_VERSION" "$INPUT_BRANCH" >> "$GITHUB_OUTPUT" + "$INPUT_FEATURE" "$INPUT_VERSION" "$INPUT_BRANCH" "$INPUT_EVM" \ + >> "$GITHUB_OUTPUT" build: name: fill (${{ matrix.label || matrix.feature }}) needs: setup + if: needs.setup.outputs.run == 'true' runs-on: [self-hosted-ghr, size-gigachungus-x64] timeout-minutes: 1440 strategy: - fail-fast: true + # A release must be complete, so abort on the first failed range; a + # nightly wants every range's result for debugging. + fail-fast: ${{ github.event_name != 'schedule' }} matrix: include: ${{ fromJson(needs.setup.outputs.build_matrix) }} steps: @@ -88,6 +158,9 @@ jobs: from_fork: ${{ matrix.from_fork }} until_fork: ${{ matrix.until_fork }} split_label: ${{ matrix.label }} + # Nightly splits are intermediates consumed by `combine` right + # away; don't retain them for the repo-default period. + split_retention_days: ${{ github.event_name == 'schedule' && '1' || '' }} evm: ${{ inputs.evm }} evm_repo: ${{ inputs.evm_repo }} evm_ref: ${{ inputs.evm_ref }} @@ -152,15 +225,32 @@ jobs: - name: Upload combined fixture tarball uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: - name: fixtures_${{ needs.setup.outputs.feature_name }} + # Name the artifact for the built commit so the rolling + # nightly artifacts are tellable apart at a glance; the + # cached-release resolver derives this exact name from each + # run's head SHA. The tarball inside carries the feature name. + name: fixtures_${{ needs.setup.outputs.short_sha }} path: ${{ steps.tarball.outputs.path }} + # Keep nightly tarballs for five days; a quiet nightly re-runs + # after four (see check_new_commits.py), so a live artifact + # always exists. Release tarballs keep the repo default since + # the release job attaches them to a draft release anyway. + retention-days: ${{ github.event_name == 'schedule' && '5' || '' }} release: runs-on: ubuntu-latest needs: [setup, build, combine] - if: always() && needs.build.result == 'success' && (needs.combine.result == 'success' || needs.combine.result == 'skipped') + # Scheduled runs stop after `combine`: no tag, no draft release. + # Cached releases skip the fill, so a skipped `build` is expected; + # `setup` must have succeeded explicitly, because a failed `setup` + # also leaves `build` skipped and would otherwise start this job + # with empty outputs. + if: always() && github.event_name == 'workflow_dispatch' && needs.setup.result == 'success' && (needs.build.result == 'success' || ((inputs.cached || inputs.commit != '') && needs.build.result == 'skipped')) && (needs.combine.result == 'success' || needs.combine.result == 'skipped') permissions: contents: write + # Cached releases download the artifact from the resolved + # nightly run rather than this one. + actions: read steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: @@ -171,11 +261,21 @@ jobs: - name: Download release artifacts shell: bash run: | - gh run download ${{ github.run_id }} -p "fixtures_*" --dir ./artifacts - rm -rf ./artifacts/fixtures__*/ - gh run download ${{ github.run_id }} -p "benchmark_genesis_*" --dir ./artifacts || true + if [ -n "$ARTIFACT_RUN_ID" ]; then + # Cached release: download the resolved nightly's tarball by + # exact name -- a young nightly's live split directories + # would also match the fixtures_* pattern. + gh run download "$ARTIFACT_RUN_ID" -n "$ARTIFACT_NAME" \ + --dir "./artifacts/$ARTIFACT_NAME" + else + gh run download ${{ github.run_id }} -p "fixtures_*" --dir ./artifacts + rm -rf ./artifacts/fixtures__*/ + gh run download ${{ github.run_id }} -p "benchmark_genesis_*" --dir ./artifacts || true + fi env: GH_TOKEN: ${{ github.token }} + ARTIFACT_RUN_ID: ${{ needs.setup.outputs.artifact_run_id }} + ARTIFACT_NAME: ${{ needs.setup.outputs.artifact_name }} - name: Draft release shell: bash diff --git a/Justfile b/Justfile index 3d3aabac781..bd5e5869067 100644 --- a/Justfile +++ b/Justfile @@ -10,6 +10,11 @@ list: root := justfile_directory() output_dir := root / ".just" xdist_workers := env("PYTEST_XDIST_AUTO_NUM_WORKERS", "6") + +# The env var's job ends with the `-n` value above; export it empty so +# pytest-xdist, which reads it as a numeric worker-count override in +# `-n auto` mode, does not warn on non-numeric values such as "auto". +export PYTEST_XDIST_AUTO_NUM_WORKERS := "" evm_bin := env("EVM_BIN", "evm") latest_fork := "Amsterdam" @@ -127,6 +132,18 @@ fill *args: "$@" \ tests +# Callers append the feature params, fork range and output; last flag wins. +# Fill fixtures with the flags shared by all fixture releases +[group('consensus tests')] +fill-release *args: + uv run fill \ + -n {{ xdist_workers }} \ + --output="{{ output_dir }}/fill-release/fixtures" \ + --no-html \ + --durations=100 \ + --log-level=DEBUG \ + "$@" + # --- Integration Tests --- # Fill the base coverage consensus tests using EELS with PyPy diff --git a/docs/dev/releasing_tests.md b/docs/dev/releasing_tests.md index 0259159c0f8..ffbfe1cf504 100644 --- a/docs/dev/releasing_tests.md +++ b/docs/dev/releasing_tests.md @@ -1,13 +1,8 @@ # Releasing Test Fixtures -This page covers the mechanics of cutting a test fixture release. For the release types, -their versioning, and consumption guidance, see -[EELS Fixture Releases](../running_tests/releases.md). +This page covers the mechanics of cutting a test fixture release. For the release types, their versioning, and consumption guidance, see [EELS Fixture Releases](../running_tests/releases.md). -Fixture releases are produced by manually dispatching the -[`release_fixtures.yaml`](https://github.com/ethereum/execution-specs/blob/master/.github/workflows/release_fixtures.yaml) -workflow. There is no tag to push by hand. The workflow builds the fixtures and, only on -success, creates the tag and the (draft) GitHub release. +Fixture releases are produced by manually dispatching the [`release_fixtures.yaml`](https://github.com/ethereum/execution-specs/blob/master/.github/workflows/release_fixtures.yaml) workflow. There is no tag to push by hand. The workflow builds the fixtures and, only on success, drafts the GitHub release; publishing the draft creates the tag. ```bash gh workflow run release_fixtures.yaml -f feature=<feature> -f version=vX.Y.Z [-f branch=<branch>] @@ -23,31 +18,22 @@ gh workflow run release_fixtures.yaml -f feature=<feature> -f version=vX.Y.Z [-f | `evm` | no | Override the evm impl (e.g. `geth`, `evmone`). Defaults to the feature's `evm-type` in `feature.yaml`. | | `evm_repo` | no | Override the t8n tool repo (e.g. `ethereum/go-ethereum`). | | `evm_ref` | no | Override the t8n tool branch / tag / commit. | +| `cached` | no | Draft from the newest nightly artifact instead of refilling (`tests` only): `build` and `combine` are skipped and the tag targets the nightly's commit. See [Cached releases](#cached-releases). | +| `commit` | no | Release the nightly built at this commit (7+ hex chars) instead of the newest one; implies `cached`. | -`<feature>` must be a key in -[`.github/configs/feature.yaml`](https://github.com/ethereum/execution-specs/blob/master/.github/configs/feature.yaml) -(e.g. `tests`, `benchmark`), or a `<feat>-devnet` name that resolves to the shared `devnet` -feature. +`<feature>` must be a key in [`.github/configs/feature.yaml`](https://github.com/ethereum/execution-specs/blob/master/.github/configs/feature.yaml) (e.g. `tests`, `benchmark`), or a `<feat>-devnet` name that resolves to the shared `devnet` feature. -Input validation runs in -[`generate_build_matrix.py`](https://github.com/ethereum/execution-specs/blob/master/.github/scripts/generate_build_matrix.py) -(unit-tested) before any fixtures are built, and fails fast on: +Input validation runs in [`generate_build_matrix.py`](https://github.com/ethereum/execution-specs/blob/master/.github/scripts/generate_build_matrix.py) (unit-tested) before any fixtures are built, and fails fast on: - an empty `feature` or a `version` that is not `vX.Y.Z`; +- an `evm` override that is not a key in `.github/configs/evm.yaml`; - a bare `devnet` feature name (must carry a `<feat>-` prefix, e.g. `bal-devnet`); -- a `<feat>-devnet-<n>` feature name — the devnet index belongs in the `version` major, not - the feature name (so `feature=bal-devnet-7` is rejected in favour of - `feature=bal-devnet version=v7.0.0`); -- a `*-devnet` release missing a `branch`, a `branch` outside the `devnets/<feat>/<n>` shape - (e.g. `devnets/bal/7`), or a `version` major that does not equal the devnet number `<n>` in - the branch (so `feature=bal-devnet branch=devnets/bal/7` must use `version=v7.*.*`). +- a `<feat>-devnet-<n>` feature name — the devnet index belongs in the `version` major, not the feature name (so `feature=bal-devnet-7` is rejected in favour of `feature=bal-devnet version=v7.0.0`); +- a `*-devnet` release missing a `branch`, a `branch` outside the `devnets/<feat>/<n>` shape (e.g. `devnets/bal/7`), or a `version` major that does not equal the devnet number `<n>` in the branch (so `feature=bal-devnet branch=devnets/bal/7` must use `version=v7.*.*`). ## Devnet releases -Devnet releases must use a `<feat>-devnet` feature name (e.g. `feature=bal-devnet`) and must -specify the branch to release from. Devnet branches follow the `devnets/<feat>/<n>` scheme -(e.g. `devnets/bal/7`), and the `version` major must match the devnet number `<n>` in the -branch: +Devnet releases must use a `<feat>-devnet` feature name (e.g. `feature=bal-devnet`) and must specify the branch to release from. Devnet branches follow the `devnets/<feat>/<n>` scheme (e.g. `devnets/bal/7`), and the `version` major must match the devnet number `<n>` in the branch: ```bash gh workflow run release_fixtures.yaml -f feature=bal-devnet -f version=v7.0.0 -f branch=devnets/bal/7 @@ -57,14 +43,9 @@ gh workflow run release_fixtures.yaml -f feature=bal-devnet -f version=v7.0.0 -f On success the workflow: -1. Builds `fixtures_<feature>.tar.gz` (the `tests` feature builds `fixtures.tar.gz`) for the - resolved feature (per its `evm-type` and `fill-params` in `feature.yaml`). -2. Creates the git tag `tests-<feature>@vX.Y.Z` (the `tests` feature tags as `tests@vX.Y.Z`, - no doubled prefix) on the released commit (the SHA resolved once from the `branch` HEAD when - given, otherwise the dispatch commit). -3. Publishes a **draft pre-release** to - [`ethereum/execution-specs`](https://github.com/ethereum/execution-specs/releases), titled - the same as the git tag, with the fixture tarball(s) attached. +1. Builds `fixtures_<feature>.tar.gz` (the `tests` feature builds `fixtures.tar.gz`) for the resolved feature (per its `evm-type` and `fill-params` in `feature.yaml`). +2. Drafts a **pre-release** to [`ethereum/execution-specs`](https://github.com/ethereum/execution-specs/releases) with the fixture tarball(s) attached, titled and tagged `tests-<feature>@vX.Y.Z` (the `tests` feature tags as `tests@vX.Y.Z`, no doubled prefix). +3. Targets the tag at the released commit (the SHA resolved once from the `branch` HEAD when given, otherwise the dispatch commit). The tag name and target are stored as draft metadata; the git tag itself is only created when the draft is published, so an unpublished draft can be edited or deleted without leaving a tag behind. | Example dispatch | Git tag | Release title | Artifact | | ---------------- | ------- | ------------- | -------- | @@ -75,13 +56,8 @@ The release is created as a draft; review and publish it from the GitHub release ## Cutting a release -1. **Pick the next version** per the - [Versioning Scheme](../running_tests/releases.md#versioning-scheme) for the feature you're - releasing (e.g. the next `tests` release after `tests@v24.1.0` is `tests@v24.1.1` for a - non-breaking/new-tests bump, or `tests@v24.2.0` for a consensus-breaking spec change). -2. **Dispatch the workflow** from the - [Actions tab](https://github.com/ethereum/execution-specs/actions/workflows/release_fixtures.yaml) - or via the CLI: +1. **Pick the next version** per the [Versioning Scheme](../running_tests/releases.md#versioning-scheme) for the feature you're releasing (e.g. the next `tests` release after `tests@v24.1.0` is `tests@v24.1.1` for a non-breaking/new-tests bump, or `tests@v24.2.0` for a consensus-breaking spec change). +2. **Dispatch the workflow** from the [Actions tab](https://github.com/ethereum/execution-specs/actions/workflows/release_fixtures.yaml) or via the CLI: ```bash gh workflow run release_fixtures.yaml -f feature=tests -f version=v24.1.1 @@ -89,22 +65,39 @@ The release is created as a draft; review and publish it from the GitHub release gh workflow run release_fixtures.yaml -f feature=bal-devnet -f version=v7.0.0 -f branch=devnets/bal/7 ``` -3. **Wait for the build to succeed.** On success the workflow creates the - `tests-<feature>@vX.Y.Z` tag on the target commit and drafts the GitHub release with the - fixture tarball attached. If any job fails, no tag or release is created — fix the cause - and re-dispatch. -4. **Review and publish the draft.** Open the draft on the - [releases page](https://github.com/ethereum/execution-specs/releases), check the - auto-generated notes (anchored at the prior release on the same feature via - `--notes-start-tag`), and click *Publish release* when ready. +3. **Wait for the build to succeed.** On success the workflow drafts the GitHub release with the fixture tarball attached. If any job fails, no release is drafted: fix the cause and re-dispatch. +4. **Review and publish the draft.** Open the draft on the [releases page](https://github.com/ethereum/execution-specs/releases), check the auto-generated notes (anchored at the prior release on the same feature via `--notes-start-tag`), and click *Publish release* when ready. Publishing creates the `tests-<feature>@vX.Y.Z` tag on the target commit; until then a mispicked version can be fixed by editing the draft, with no stray tag to delete. !!! tip "Release features opt into all fixture formats via `feature.yaml`" - Tarball output (`.tar.gz`) does not by itself include the pre-allocation group formats - (`BlockchainEngineXFixture`, `BlockchainEngineStatefulFixture`). A release feature - requests them by adding `--generate-all-formats` to its `fill-params` in - `.github/configs/feature.yaml`: + Tarball output (`.tar.gz`) does not by itself include the pre-allocation group formats (`BlockchainEngineXFixture`, `BlockchainEngineStatefulFixture`). A release feature requests them by adding `--generate-all-formats` to its `fill-params` in `.github/configs/feature.yaml`: ```console # .tar.gz no longer auto-enables all formats (changed in #2702); request # them explicitly with --generate-all-formats uv run fill --generate-all-formats --output=fixtures.tar.gz tests/ ``` + +## Nightly fill + +The same workflow also runs on a nightly schedule (02:00 UTC) as a release rehearsal: it fills the mainnet `tests` feature (all tests, slow included, all fixture formats, up to the latest mainnet fork — dev forks are not included) through the exact release pipeline, but stops after `combine`, so no tag or release is created. Each run uploads a `fixtures_<commit>` workflow artifact (short hash of the built commit, containing `fixtures.tar.gz`) with a 5-day retention: a rotating, always-available build of the mainnet fixtures, effectively a `tests@` release candidate on demand. A scheduled run skips itself when there are no new commits since the last nightly that actually filled — a skipped or failed nightly never advances that baseline, so no commit slips through unfilled. A quiet stretch without commits still re-fills once the last fill is four days old, or its artifact is gone, so a live artifact always exists within the five-day retention. + +## Cached releases + +A `tests@` release can reuse the newest nightly artifact instead of refilling: tick the `cached` checkbox in the dispatch UI, or pass the flag on the CLI: + +```bash +gh workflow run release_fixtures.yaml -f feature=tests -f version=vX.Y.Z -f cached=true +# or release the nightly built at a specific commit (implies cached): +gh workflow run release_fixtures.yaml -f feature=tests -f version=vX.Y.Z -f commit=<hash> +``` + +The `build` and `combine` jobs are skipped; the `release` job downloads the `fixtures_<commit>` artifact from the newest nightly run that actually filled — or, with the `commit` input, from the nightly built at that commit — and drafts the same release a fresh fill would produce, targeted at the exact commit the nightly built, so publishing it creates the `tests@vX.Y.Z` tag on that commit. The whole run takes minutes on a hosted runner. Review and publish the draft exactly as in [Cutting a release](#cutting-a-release). + +The cached path's validation (unit-tested in [`resolve_cached_release.py`](https://github.com/ethereum/execution-specs/blob/master/.github/scripts/resolve_cached_release.py)) fails fast when: + +- the `feature` is not `tests`, or a `branch` is given (the nightly fills the default branch); +- the `version` is not `vX.Y.Z` or does not exceed the newest existing `tests@` tag; +- no nightly run with a live `fixtures_<commit>` artifact exists — or none matching the `commit` input; the error lists the reusable nightlies (artifacts are retained for five days: past that, dispatch a fresh fill); +- the resolved nightly does not contain the newest existing `tests@` release (a cached release must never regress content; re-releasing the identical commit is allowed); +- the nightly's commit is not an ancestor of the current default branch head. + +A cached release contains exactly what the resolved nightly filled: commits that landed after it are **not** included, and the run's step summary lists them so the releaser can decide between the cached artifact and a fresh fill. From 2119b382cebe57d9953eb6252e927e130b1ca51c Mon Sep 17 00:00:00 2001 From: Guruprasad Kamath <48196632+gurukamath@users.noreply.github.com> Date: Fri, 17 Jul 2026 14:01:16 +0200 Subject: [PATCH 137/233] refactor(spec-specs): organize gas logic in amsterdam EIP-8037/8038/2780 (#3159) * refactor(spec-specs, tools): bundle amsterdam frame gas into GasMeter Gather the flat gas fields on Evm into a GasMeter dataclass in vm/gas.py and replace the scattered hand-rolled gas arithmetic with named operations: - State-gas rollback becomes self-contained on the meter: a state_gas_baseline field records the reservoir level a rollback refills to (the frame's grant at entry), commit_state_gas folds everything consumed since the baseline into state_gas_committed and moves the baseline down (zeroing the spill, so post-commit refunds route to the reservoir), restore_state_gas refills to the baseline on revert or halt, and restore_state_gas_to_entry undoes the commit when the transaction rollback also reverts the applied delegations. Message.state_gas_reservoir is never mutated after construction. - Frame lifecycle helpers (refill/commit and credit_state_gas_refund) move from vm/__init__.py into vm/gas.py; incorporate_child_on_* stay as thin wrappers over the new absorb_child_gas_on_* meter operations. - Inline arithmetic in interpreter.py (burn-all-gas), system.py (reservoir handoffs) and fork.py (EIP-8037 split, refund + floor settlement) becomes forfeit_remaining_gas, withhold_create_gas, drain_state_gas_reservoir, restore_child_gas, allocate_execution_gas and settle_transaction_gas; tx_state_gas_used measures the top frame's net state gas at settlement. - t8n tracer protocols gain gas-layout accessors so both the flat and gas-meter Evm shapes trace through one code path. The meter carries no per-frame regular-gas counter: since #3005 the block's regular-gas dimension is derived from transaction totals (tx.gas - gas_left - state_gas_left, floor-bound), so a frame-level counter would only ever be charged, discounted across sub-calls, and forfeited on halts without being read. The identifiers avoid "frame" (restore_child_gas, tx_state_gas_used, withhold_create_gas) ahead of EIP-8141, which will give the term a protocol meaning. Behavior-preserving: verified with ruff, mypy, ethereum-spec-lint, vulture, spec-tools, json_loader tracing on Amsterdam and Osaka, and the EIP-8037/2780/7778 fill battery. * refactor(spec-specs): name the gas stages in amsterdam opcodes Give the two-dimensional gas choreography named stages so structurally corresponding code sits in structurally corresponding places: - CALL*/CREATE* wrappers price the opcode in labeled sections -- GAS (STATE-INDEPENDENT) computes and affordability-checks everything that needs no state access, STATE ACCESS (STATE-DEPENDENT GAS) performs the accesses and completes the pricing, STATE GAS holds the account-creation charge, and CHILD GRANT withholds the child's regular share and drains the reservoir in one block. - generic_call and generic_create reduce to the child-frame lifecycle (PREFLIGHT, DESTINATION ACCESS, DISPATCH, OUTCOME): grant withholding and all charging move to the wrappers, and the balance preflight folds into generic_call via GenericCall.insufficient_balance, unifying the abort-without-spawn paths. - The same stage labels apply to sstore and selfdestruct, the two other opcodes with state-gas stages; single-stage opcodes keep the bare GAS marker. - Frame-settlement comments on the process_message handlers state the postcondition the parent-side absorb relies on. Behavior-preserving: statement order of every charge, check, and trace event is unchanged; verified with the EIP-8037/2780/7778 fill battery and the full static suite. * refactor(spec-specs): unify amsterdam child-frame incorporation Discard a failed frame's refunds in restore_state_gas, alongside the state gas rollback, instead of ignoring them at the absorption sites. Every settled meter now states exactly what the frame gives back, so a single incorporate_child replaces the on-success/on-error pair (and the absorb_child_gas_on_* helpers): gas is absorbed unconditionally, while logs, self-destructs, and warmed access sets survive only on success. The top frame's refund read in process_message_call becomes unconditional for the same reason. * chore(tooling): document gas handling rules in implement-eip skill Capture the two-dimensional gas philosophy from the Amsterdam gas refactors so future sessions do not re-derive it: gas movements are named GasMeter helpers in vm/gas.py, opcodes price in labeled stages with all charging before the operation, generic_call/generic_create run only the child-frame lifecycle, and gas changes are verified by preserving charge/check/trace order against the fill tests. * fix static test fails --- .claude/commands/implement-eip.md | 14 +- src/ethereum/forks/amsterdam/fork.py | 52 +- src/ethereum/forks/amsterdam/vm/__init__.py | 188 ++----- src/ethereum/forks/amsterdam/vm/gas.py | 492 +++++++++++++++++- .../amsterdam/vm/instructions/control_flow.py | 2 +- .../amsterdam/vm/instructions/storage.py | 22 +- .../forks/amsterdam/vm/instructions/system.py | 323 +++++++----- .../forks/amsterdam/vm/interpreter.py | 79 +-- .../evm_tools/t8n/evm_trace/eip3155.py | 19 +- .../evm_tools/t8n/evm_trace/protocols.py | 65 ++- .../test_state_gas_create.py | 10 +- 11 files changed, 872 insertions(+), 394 deletions(-) diff --git a/.claude/commands/implement-eip.md b/.claude/commands/implement-eip.md index c06c6e22d19..4465317f1e3 100644 --- a/.claude/commands/implement-eip.md +++ b/.claude/commands/implement-eip.md @@ -28,10 +28,22 @@ Each fork lives at `src/ethereum/forks/<fork_name>/`. Explore the latest fork di ## Adding a New Opcode 1. Add to `Ops` enum in `vm/instructions/__init__.py` with hex value -2. Implement function in appropriate `vm/instructions/<category>.py` — follows pattern: STACK → GAS (`charge_gas`) → OPERATION → PROGRAM COUNTER +2. Implement function in appropriate `vm/instructions/<category>.py` — follows pattern: STACK → GAS (`charge_gas`) → OPERATION → PROGRAM COUNTER. Opcodes that touch state use the staged gas labels — see "Gas Handling" below. 3. Register in `op_implementation` dict in `vm/instructions/__init__.py` 4. Add gas constant in `vm/gas.py` if needed +## Gas Handling + +Recent forks meter two gas dimensions: regular gas and state gas (for durable state growth). Key rules: + +1. Gas constants and calculations go in `vm/gas.py`; a frame's mutable gas state lives on `Evm.gas_meter`. +2. Extend the named helper vocabulary (`charge_*`, `credit_*`, `restore_*`, `withhold_*`, ...) instead of doing gas arithmetic by hand at call sites; encode each helper's invariant as an assert. +3. State gas is charged by the frame whose opcode causes the creation, before the child's regular-gas share is withheld; the whole reservoir passes to the child. +4. A failing frame settles its own meter before returning, so parents incorporate children unconditionally. +5. Opcodes that touch state use labeled stages, with all charging before the operation: `GAS (STATE-INDEPENDENT)` → `STATE ACCESS (STATE-DEPENDENT GAS)` → `STATE GAS` → `CHILD GRANT` → `OPERATION`. Simple opcodes keep the bare `GAS` marker. `generic_call`/`generic_create` contain no pricing; they run the child lifecycle: `PREFLIGHT` → `DESTINATION ACCESS` → `CHILD GRANT` → `DISPATCH` → `OUTCOME`. +6. Avoid "frame" in gas identifiers (a future EIP claims the term); when a name diverges from the spec's variable name, cross-reference the spec name in the docstring. +7. A gas change is behavior-preserving only if the relative order of every charge, check, and trace event is unchanged; verify with the gas-related fill tests under `tests/<fork>/`. + ## Adding a New Precompile 1. Define address constant in `vm/precompiled_contracts/__init__.py` using `hex_to_address("0x...")` diff --git a/src/ethereum/forks/amsterdam/fork.py b/src/ethereum/forks/amsterdam/fork.py index fc66f86744d..302e0887ed2 100644 --- a/src/ethereum/forks/amsterdam/fork.py +++ b/src/ethereum/forks/amsterdam/fork.py @@ -103,10 +103,12 @@ from .vm.gas import ( GasCosts, StateGasCosts, + allocate_execution_gas, calculate_blob_gas_price, calculate_data_fee, calculate_excess_blob_gas, calculate_total_blob_gas, + settle_transaction_gas, ) from .vm.interpreter import MessageCallOutput, process_message_call @@ -1032,8 +1034,6 @@ def process_transaction( sender = recover_sender(tx) intrinsic = validate_transaction(tx, sender) - intrinsic_gas = Uint(intrinsic.regular) - ( effective_gas_price, blob_versioned_hashes, @@ -1055,12 +1055,9 @@ def process_transaction( effective_gas_fee = tx.gas * effective_gas_price - # Split execution gas into gas_left (capped by remaining regular gas - # budget) and state_gas_reservoir. - execution_gas = tx.gas - intrinsic_gas - regular_gas_budget = TX_MAX_GAS_LIMIT - intrinsic.regular - gas = min(regular_gas_budget, execution_gas) - state_gas_reservoir = Uint(execution_gas - gas) + # Split execution gas into a regular grant (capped by the remaining + # regular-gas budget) and a state gas reservoir. + allocation = allocate_execution_gas(tx.gas, intrinsic) increment_nonce(tx_state, sender) @@ -1087,8 +1084,8 @@ def process_transaction( recipient=tx.to, value=tx.value, gas_price=effective_gas_price, - gas=gas, - state_gas_reservoir=state_gas_reservoir, + gas=allocation.regular_gas, + state_gas_reservoir=allocation.state_gas_reservoir, access_list_addresses=access_list_addresses, access_list_storage_keys=access_list_storage_keys, state=tx_state, @@ -1102,24 +1099,20 @@ def process_transaction( tx_output = process_message_call(message) - tx_gas_used_before_refund = ( - tx.gas - tx_output.gas_left - tx_output.state_gas_left - ) - tx_gas_refund = min( - tx_gas_used_before_refund // Uint(5), Uint(tx_output.refund_counter) + settlement = settle_transaction_gas( + tx.gas, + intrinsic, + tx_output.gas_left, + tx_output.state_gas_left, + tx_output.refund_counter, + tx_output.state_gas_used, ) - tx_gas_used_after_refund = tx_gas_used_before_refund - tx_gas_refund - # Transactions with less execution_gas_used than the floor pay at the - # floor cost. - tx_gas_used = max(tx_gas_used_after_refund, intrinsic.calldata_floor) - - tx_gas_left = tx.gas - tx_gas_used - gas_refund_amount = tx_gas_left * effective_gas_price + gas_refund_amount = settlement.gas_left * effective_gas_price # For non-1559 transactions effective_gas_price == tx.gas_price priority_fee_per_gas = effective_gas_price - block_env.base_fee_per_gas - transaction_fee = tx_gas_used * priority_fee_per_gas + transaction_fee = settlement.gas_used * priority_fee_per_gas # refund gas create_ether(tx_state, sender, U256(gas_refund_amount)) @@ -1127,18 +1120,11 @@ def process_transaction( # transfer miner fees create_ether(tx_state, block_env.coinbase, U256(transaction_fee)) - tx_state_gas = tx_output.state_gas_used - # The calldata floor binds the regular-gas dimension: subtract state gas - # first so the floor is not discounted by a transaction's state spending. - tx_regular_gas = max( - tx_gas_used_before_refund - Uint(max(0, tx_state_gas)), - intrinsic.calldata_floor, - ) - block_output.block_gas_used += tx_regular_gas - block_output.block_state_gas_used += Uint(max(0, tx_state_gas)) + block_output.block_gas_used += settlement.regular_gas_used + block_output.block_state_gas_used += settlement.state_gas_used block_output.blob_gas_used += tx_blob_gas_used - block_output.cumulative_gas_used += tx_gas_used + block_output.cumulative_gas_used += settlement.gas_used receipt = make_receipt( tx, tx_output.error, block_output.cumulative_gas_used, tx_output.logs ) diff --git a/src/ethereum/forks/amsterdam/vm/__init__.py b/src/ethereum/forks/amsterdam/vm/__init__.py index 608eae89056..c7563ab5fd2 100644 --- a/src/ethereum/forks/amsterdam/vm/__init__.py +++ b/src/ethereum/forks/amsterdam/vm/__init__.py @@ -26,9 +26,10 @@ from ..block_access_lists import BlockAccessList, BlockAccessListBuilder from ..blocks import Log, Receipt, Withdrawal -from ..fork_types import Authorization, StateGas, VersionedHash +from ..fork_types import Authorization, VersionedHash from ..state_tracker import BlockState, TransactionState from ..transactions import LegacyTransaction +from .gas import GasMeter __all__ = ("Environment", "Evm", "Message") TRANSFER_TOPIC = keccak256(b"Transfer(address,address,uint256)") @@ -170,11 +171,9 @@ class Evm: stack: List[U256] memory: bytearray code: Bytes - gas_left: Uint - state_gas_left: Uint + gas_meter: GasMeter valid_jump_destinations: Set[Uint] logs: Tuple[Log, ...] - refund_counter: int running: bool message: Message output: Bytes @@ -183,43 +182,21 @@ class Evm: error: Optional[EthereumException] accessed_addresses: Set[Address] accessed_storage_keys: Set[Tuple[Address, Bytes32]] - regular_gas_used: Uint = Uint(0) - state_gas_spilled: Uint = Uint(0) - committed_state_gas: int = 0 - """ - State gas locked in by [`commit_frame_state_gas`] because the state - it paid for outlives a later failure in the frame. - - [`commit_frame_state_gas`]: ref:ethereum.forks.amsterdam.vm.commit_frame_state_gas - """ # noqa: E501 -def credit_state_gas_refund(evm: Evm, amount: StateGas) -> None: +def incorporate_child(evm: Evm, child_evm: Evm) -> None: """ - Credit a state gas refund to the local frame, in LIFO order. - - State-gas charges draw from the reservoir first and from `gas_left` - last, so refills credit the pool charged last first: `gas_left` up - to `state_gas_spilled`, then the reservoir. This restores the - exact pools the charge drew from, so the two never drift. - - Parameters - ---------- - evm : - The frame crediting the refund. - amount : - The refund amount to credit. - - """ - from_gas_left = min(amount, evm.state_gas_spilled) - evm.gas_left += from_gas_left - evm.state_gas_spilled -= from_gas_left - evm.state_gas_left += amount - from_gas_left - - -def incorporate_child_on_success(evm: Evm, child_evm: Evm) -> None: - """ - Incorporate the state of a successful `child_evm` into the parent `evm`. + Incorporate the state of a returning `child_evm` into the parent + `evm`. + + Gas flows back to the parent regardless of the child's fate. A + failed child settles its own meter before returning -- its state + gas rolled back to the baseline, its [spill] refilled, and its + refunds discarded -- so absorbing the meter unconditionally + reclaims exactly the gas the child gives back. Everything else the + child accumulated -- logs, scheduled self-destructs, refunds, and + warmed access sets -- survives only on success, dying with a + failed child's reverted state. Parameters ---------- @@ -228,117 +205,34 @@ def incorporate_child_on_success(evm: Evm, child_evm: Evm) -> None: child_evm : The child evm to incorporate. - """ - evm.gas_left += child_evm.gas_left - evm.state_gas_left += child_evm.state_gas_left - evm.state_gas_spilled += child_evm.state_gas_spilled - evm.logs += child_evm.logs - evm.refund_counter += child_evm.refund_counter - evm.accounts_to_delete.update(child_evm.accounts_to_delete) - evm.accessed_addresses.update(child_evm.accessed_addresses) - evm.accessed_storage_keys.update(child_evm.accessed_storage_keys) - evm.regular_gas_used += child_evm.regular_gas_used - - -def refill_frame_state_gas(evm: Evm) -> None: - """ - Roll back the frame's state gas in LIFO order on revert or halt. - - The frame's state changes are undone, so the state gas it consumed - is credited back to `gas_left` first and then to the reservoir, - restoring the pools the charges drew from. - - Parameters - ---------- - evm : - The frame whose state gas is rolled back. - - """ - evm.gas_left += evm.state_gas_spilled - evm.state_gas_left = evm.message.state_gas_reservoir - evm.state_gas_spilled = Uint(0) - - -def frame_state_gas_used(evm: Evm) -> int: - """ - Return the net state gas consumed by a finished frame, including - any state gas committed as non-refillable earlier in the frame. - - Equal to the reservoir drawn down ([`state_gas_reservoir`][sgr] at - the last commit -- or frame entry, absent one -- minus the - reservoir now) plus [`state_gas_spilled`][sgs] plus - [`committed_state_gas`][csg]. May be negative when refunds exceed - charges. - - Parameters - ---------- - evm : - The finished frame. - - [sgr]: ref:ethereum.forks.amsterdam.vm.Message.state_gas_reservoir - [sgs]: ref:ethereum.forks.amsterdam.vm.Evm.state_gas_spilled - [csg]: ref:ethereum.forks.amsterdam.vm.Evm.committed_state_gas - - """ - return ( - int(evm.message.state_gas_reservoir) - - int(evm.state_gas_left) - + int(evm.state_gas_spilled) - + evm.committed_state_gas - ) - - -def commit_frame_state_gas(evm: Evm) -> None: - """ - Mark the state gas consumed so far as non-refillable and reset the - refill baseline. - - The state this gas paid for (the delegations applied by - [`set_delegation`][sd]) outlives a later failure of the dispatched - code, so a subsequent [`refill_frame_state_gas`][refill] must not - credit it back. The consumption so far is folded into - [`committed_state_gas`][csg] and the reservoir baseline moves down - to the current level, so only charges made after this commit are - refillable. - - Parameters - ---------- - evm : - The frame whose state gas consumption is committed. - - [sd]: ref:ethereum.forks.amsterdam.vm.eoa_delegation.set_delegation - [refill]: ref:ethereum.forks.amsterdam.vm.refill_frame_state_gas - [csg]: ref:ethereum.forks.amsterdam.vm.Evm.committed_state_gas - - """ - evm.committed_state_gas = frame_state_gas_used(evm) - evm.message.state_gas_reservoir = evm.state_gas_left - evm.state_gas_spilled = Uint(0) - - -def incorporate_child_on_error( - evm: Evm, - child_evm: Evm, -) -> None: - """ - Incorporate the state of an unsuccessful `child_evm` into the parent `evm`. - - The child rolls back its own state gas via `refill_frame_state_gas` - before returning (on both reverts and exceptional halts), so its - `gas_left` and reservoir already reflect the LIFO refill. The parent - therefore only reabsorbs the child's `gas_left` and reservoir. - - Parameters - ---------- - evm : - The parent `EVM`. - child_evm : - The child evm to incorporate. + [spill]: ref:ethereum.forks.amsterdam.vm.gas.GasMeter.state_gas_spilled """ - evm.gas_left += child_evm.gas_left - evm.state_gas_left += child_evm.state_gas_left - evm.regular_gas_used += child_evm.regular_gas_used + child_meter = child_evm.gas_meter + # Only the top frame commits state gas; a child never carries any. + assert child_meter.state_gas_committed_spill == Uint(0) + + if child_evm.error: + # A failed child arrives settled: rolled back to its baseline, + # spill refilled, refunds discarded. + assert child_meter.state_gas_spilled == Uint(0) + assert child_meter.refund_counter == 0 + assert child_meter.state_gas_left == child_meter.state_gas_baseline + + # Gas returns to the parent regardless of the child's fate. + # Note that upon failure, the child already arrives settled. + gas_meter = evm.gas_meter + gas_meter.gas_left += child_meter.gas_left + gas_meter.state_gas_left += child_meter.state_gas_left + gas_meter.state_gas_spilled += child_meter.state_gas_spilled + gas_meter.refund_counter += child_meter.refund_counter + + # Everything else survives only on success. + if not child_evm.error: + evm.logs += child_evm.logs + evm.accounts_to_delete.update(child_evm.accounts_to_delete) + evm.accessed_addresses.update(child_evm.accessed_addresses) + evm.accessed_storage_keys.update(child_evm.accessed_storage_keys) def emit_transfer_log( diff --git a/src/ethereum/forks/amsterdam/vm/gas.py b/src/ethereum/forks/amsterdam/vm/gas.py index 91a5810b7d2..028b4ac4318 100644 --- a/src/ethereum/forks/amsterdam/vm/gas.py +++ b/src/ethereum/forks/amsterdam/vm/gas.py @@ -12,7 +12,7 @@ """ from dataclasses import dataclass -from typing import Final, List, Tuple, final +from typing import TYPE_CHECKING, Final, List, Tuple, final from ethereum_types.numeric import U64, U256, Uint, ulen @@ -22,10 +22,17 @@ from ..blocks import Header from ..fork_types import StateGas, StateGasPerByte -from ..transactions import BlobTransaction, Transaction -from . import Evm +from ..transactions import ( + TX_MAX_GAS_LIMIT, + BlobTransaction, + IntrinsicGasCost, + Transaction, +) from .exceptions import OutOfGasError +if TYPE_CHECKING: + from . import Evm + # These may be patched at runtime by a future gas repricing utility to # fast-iterate on state-byte costs. @@ -233,6 +240,71 @@ class GasCosts: OPCODE_SELFDESTRUCT_BASE: Final[Uint] = Uint(5000) +@final +@dataclass +class GasMeter: + """ + Track a frame's gas consumption across both gas dimensions. + + Bundle every mutable gas quantity a frame maintains, so the frame + and its settlement work against one object instead of a scatter of + fields on the [`Evm`]. + + [`Evm`]: ref:ethereum.forks.amsterdam.vm.Evm + """ + + gas_left: Uint + """ + Gas still available from the frame's regular grant. Pays regular + charges, and state charges as [spill] once the reservoir empties. + + [spill]: ref:ethereum.forks.amsterdam.vm.gas.GasMeter.state_gas_spilled + """ + + state_gas_left: Uint + """ + State gas still available in the frame's reservoir. Charges draw + from here first and spill into `gas_left` once it is empty. + """ + + state_gas_baseline: Uint + """ + Reservoir level a rollback refills to: the frame's grant at entry, + moved down by [`commit_state_gas`][commit] when charges become + non-refillable. + + [commit]: ref:ethereum.forks.amsterdam.vm.gas.commit_state_gas + """ + + refund_counter: int = 0 + """Gas eligible for refund at the end of the transaction.""" + + state_gas_spilled: Uint = Uint(0) + """ + Regular gas spent covering state charges after the reservoir + emptied. Credited back to `gas_left` first, in LIFO order, on a + refund or failure. [EIP-8037] names this quantity + `state_gas_from_gas_left`. + + [EIP-8037]: https://eips.ethereum.org/EIPS/eip-8037 + """ + + state_gas_committed_spill: Uint = Uint(0) + """ + [Spill] that [`commit_state_gas`][commit] marked non-refillable. + It outlives the rollbacks [`restore_state_gas`][restore] performs; + only [`restore_state_gas_to_entry`][entry] credits it back to + `gas_left`. Committed reservoir draw needs no counter of its own: + each commit lowers the baseline, so it is the frame's grant minus + `state_gas_baseline`. + + [Spill]: ref:ethereum.forks.amsterdam.vm.gas.GasMeter.state_gas_spilled + [commit]: ref:ethereum.forks.amsterdam.vm.gas.commit_state_gas + [restore]: ref:ethereum.forks.amsterdam.vm.gas.restore_state_gas + [entry]: ref:ethereum.forks.amsterdam.vm.gas.restore_state_gas_to_entry + """ + + @final @dataclass class ExtendMemory: @@ -268,7 +340,7 @@ class MessageCallGas: sub_call: Uint -def check_gas(evm: Evm, amount: Uint) -> None: +def check_gas(evm: "Evm", amount: Uint) -> None: """ Checks if `amount` gas is available without charging it. Raises OutOfGasError if insufficient gas. @@ -281,13 +353,13 @@ def check_gas(evm: Evm, amount: Uint) -> None: The amount of gas to check. """ - if evm.gas_left < amount: + if evm.gas_meter.gas_left < amount: raise OutOfGasError -def charge_gas(evm: Evm, amount: Uint) -> None: +def charge_gas(evm: "Evm", amount: Uint) -> None: """ - Subtracts `amount` from `evm.gas_left` (regular gas) and records usage. + Subtracts `amount` from `gas_left` (regular gas). Parameters ---------- @@ -299,17 +371,16 @@ def charge_gas(evm: Evm, amount: Uint) -> None: """ evm_trace(evm, GasAndRefund(int(amount))) - if evm.gas_left < amount: + gas_meter = evm.gas_meter + if gas_meter.gas_left < amount: raise OutOfGasError - evm.gas_left -= amount - - evm.regular_gas_used += amount + gas_meter.gas_left -= amount -def charge_state_gas(evm: Evm, amount: StateGas) -> None: +def charge_state_gas(evm: "Evm", amount: StateGas) -> None: """ Subtracts `amount` from the state gas reservoir, then from - `evm.gas_left` when the reservoir is empty, tracking any [spill]. + `gas_left` when the reservoir is empty, tracking any [spill]. Parameters ---------- @@ -318,22 +389,264 @@ def charge_state_gas(evm: Evm, amount: StateGas) -> None: amount : The amount of state gas the current operation requires. - [spill]: ref:ethereum.forks.amsterdam.vm.Evm.state_gas_spilled + [spill]: ref:ethereum.forks.amsterdam.vm.gas.GasMeter.state_gas_spilled """ evm_trace(evm, StateGasAndRefund(int(amount))) - if evm.state_gas_left >= amount: - evm.state_gas_left -= amount - elif evm.state_gas_left + evm.gas_left >= amount: - remainder = amount - evm.state_gas_left - evm.state_gas_left = Uint(0) - evm.gas_left -= remainder - evm.state_gas_spilled += remainder + gas_meter = evm.gas_meter + if gas_meter.state_gas_left >= amount: + gas_meter.state_gas_left -= amount + elif gas_meter.state_gas_left + gas_meter.gas_left >= amount: + remainder = amount - gas_meter.state_gas_left + gas_meter.state_gas_left = Uint(0) + gas_meter.gas_left -= remainder + gas_meter.state_gas_spilled += remainder else: raise OutOfGasError +def commit_state_gas(gas_meter: GasMeter) -> None: + """ + Mark the state gas spent so far as non-refillable. + + A later rollback via [`restore_state_gas`][restore] leaves the + state bought so far in place, so it must not credit this gas back. + In the top frame that protects the delegations applied by + [`set_delegation`][sd], which survive a failure of the dispatched + code. A failure that reverts the committed state as well -- one + raised before dispatch -- must instead undo the commit with + [`restore_state_gas_to_entry`][entry]. + + Move the baseline down to the current reservoir level and fold the + spill into `state_gas_committed_spill`, so later refunds route to + the reservoir instead of back into `gas_left`. + + Parameters + ---------- + gas_meter : + The frame's gas meter. + + [sd]: ref:ethereum.forks.amsterdam.vm.eoa_delegation.set_delegation + [restore]: ref:ethereum.forks.amsterdam.vm.gas.restore_state_gas + [entry]: ref:ethereum.forks.amsterdam.vm.gas.restore_state_gas_to_entry + + """ + # Only charges precede a commit, so no refund has pushed the + # reservoir above the baseline: a commit only ever lowers it. + assert gas_meter.state_gas_left <= gas_meter.state_gas_baseline + gas_meter.state_gas_committed_spill += gas_meter.state_gas_spilled + gas_meter.state_gas_baseline = gas_meter.state_gas_left + gas_meter.state_gas_spilled = Uint(0) + + +def restore_state_gas(gas_meter: GasMeter) -> None: + """ + Roll the frame's state gas back to the baseline on revert or halt. + + The frame's state changes are undone, so the state gas consumed + since the [baseline] is credited back in LIFO order: the [spill] + returns to `gas_left` first, then the reservoir resets to the + baseline. The refunds accrued on the undone changes are discarded + with them. State gas committed as non-refillable stays charged. + + Parameters + ---------- + gas_meter : + The frame's gas meter. + + [baseline]: ref:ethereum.forks.amsterdam.vm.gas.GasMeter.state_gas_baseline + [spill]: ref:ethereum.forks.amsterdam.vm.gas.GasMeter.state_gas_spilled + + """ # noqa: E501 + gas_meter.gas_left += gas_meter.state_gas_spilled + gas_meter.state_gas_spilled = Uint(0) + gas_meter.state_gas_left = gas_meter.state_gas_baseline + gas_meter.refund_counter = 0 + + +def restore_state_gas_to_entry( + gas_meter: GasMeter, state_gas_reservoir: Uint +) -> None: + """ + Roll the frame's state gas back to frame entry, undoing any commit. + + Used when the transaction-state rollback also reverts the applied + delegations a [`commit_state_gas`][commit] protected: every state + charge refills -- all spill, committed or not, returns to + `gas_left` -- and the baseline resets to the frame's [grant]. + + Parameters + ---------- + gas_meter : + The frame's gas meter. + state_gas_reservoir : + The frame's immutable state gas grant. + + [commit]: ref:ethereum.forks.amsterdam.vm.gas.commit_state_gas + [grant]: ref:ethereum.forks.amsterdam.vm.Message.state_gas_reservoir + + """ + # The baseline starts at the grant and only ever moves down. + assert gas_meter.state_gas_baseline <= state_gas_reservoir + # Only pre-dispatch failures roll back to entry, and no refund + # accrues before dispatch. + assert gas_meter.refund_counter == 0 + gas_meter.gas_left += ( + gas_meter.state_gas_spilled + gas_meter.state_gas_committed_spill + ) + gas_meter.state_gas_spilled = Uint(0) + gas_meter.state_gas_committed_spill = Uint(0) + gas_meter.state_gas_left = state_gas_reservoir + gas_meter.state_gas_baseline = state_gas_reservoir + + +def tx_state_gas_used(gas_meter: GasMeter, state_gas_reservoir: Uint) -> int: + """ + Return the net state gas a transaction's execution consumed. + + Measured off the top frame's finished gas meter: the reservoir + drawn down since the transaction's grant plus the [spill], + outstanding or committed. May be negative when refunds exceed + charges. + + Parameters + ---------- + gas_meter : + The top frame's finished gas meter. + state_gas_reservoir : + The transaction's immutable state gas grant. + + Returns + ------- + state_gas_used : `int` + The net state gas consumed. + + [spill]: ref:ethereum.forks.amsterdam.vm.gas.GasMeter.state_gas_spilled + + """ + # The baseline starts at the grant and only ever moves down. + assert gas_meter.state_gas_baseline <= state_gas_reservoir + return ( + int(state_gas_reservoir) + - int(gas_meter.state_gas_left) + + int(gas_meter.state_gas_spilled) + + int(gas_meter.state_gas_committed_spill) + ) + + +def credit_state_gas_refund(gas_meter: GasMeter, amount: StateGas) -> None: + """ + Credit a state gas refund to the local frame, in LIFO order. + + State-gas charges draw from the reservoir first and from `gas_left` + last, so refunds credit the pool charged last first: `gas_left` up + to the [spill], then the reservoir. This restores the exact pools + the charge drew from, so the two never drift. + + Parameters + ---------- + gas_meter : + The gas meter crediting the refund. + amount : + The refund amount to credit. + + [spill]: ref:ethereum.forks.amsterdam.vm.gas.GasMeter.state_gas_spilled + + """ + from_gas_left = min(amount, gas_meter.state_gas_spilled) + gas_meter.gas_left += from_gas_left + gas_meter.state_gas_spilled -= from_gas_left + gas_meter.state_gas_left += amount - from_gas_left + + +def forfeit_remaining_gas(gas_meter: GasMeter) -> None: + """ + Consume all remaining regular gas on an exceptional halt. + + Parameters + ---------- + gas_meter : + The halted frame's gas meter. + + """ + # A rollback owes any outstanding spill back to `gas_left`; it + # must be restored before the remainder burns. + assert gas_meter.state_gas_spilled == Uint(0) + gas_meter.gas_left = Uint(0) + + +def withhold_create_gas(gas_meter: GasMeter) -> Uint: + """ + Withhold and return the gas made available to a `CREATE*` child. + + Deduct the all-but-one-64th share from the frame's `gas_left` and + return it as the child frame's regular gas grant. + + Parameters + ---------- + gas_meter : + The creating frame's gas meter. + + Returns + ------- + child_gas : `ethereum.base_types.Uint` + The regular gas granted to the child frame. + + """ + child_gas = max_message_call_gas(gas_meter.gas_left) + gas_meter.gas_left -= child_gas + return child_gas + + +def drain_state_gas_reservoir(gas_meter: GasMeter) -> Uint: + """ + Empty the frame's state gas reservoir for a child frame. + + A child frame receives the parent's entire reservoir; there is no + all-but-one-64th rule for state gas. The parent's reservoir is + restored when the child returns. + + Parameters + ---------- + gas_meter : + The parent frame's gas meter. + + Returns + ------- + reservoir : `ethereum.base_types.Uint` + The state gas granted to the child frame. + + """ + reservoir = gas_meter.state_gas_left + gas_meter.state_gas_left = Uint(0) + return reservoir + + +def restore_child_gas( + gas_meter: GasMeter, gas: Uint, state_gas_reservoir: Uint +) -> None: + """ + Return a child frame's unused gas grant to the parent. + + Used when the child frame is never entered (for example, a stack + depth or balance check fails): the withheld regular gas and drained + reservoir are returned untouched. + + Parameters + ---------- + gas_meter : + The parent frame's gas meter. + gas : + The regular gas grant to return. + state_gas_reservoir : + The state gas reservoir to return. + + """ + gas_meter.gas_left += gas + gas_meter.state_gas_left += state_gas_reservoir + + def calculate_memory_gas_cost(size_in_bytes: Uint) -> Uint: """ Calculates the gas cost for allocating memory @@ -594,3 +907,140 @@ def calculate_data_fee(excess_blob_gas: U64, tx: Transaction) -> Uint: return Uint(calculate_total_blob_gas(tx)) * calculate_blob_gas_price( excess_blob_gas ) + + +@final +@dataclass +class ExecutionGasAllocation: + """ + Split of a transaction's execution gas across the two dimensions. + """ + + regular_gas: Uint + """Regular gas granted to the top frame, capped by the budget.""" + + state_gas_reservoir: Uint + """State gas set aside for the top frame's reservoir.""" + + +def allocate_execution_gas( + tx_gas: Uint, intrinsic: IntrinsicGasCost +) -> ExecutionGasAllocation: + """ + Split execution gas into a regular grant and a state reservoir. + + After the intrinsic cost is removed, the remaining execution gas is + divided into regular gas -- capped by the regular-gas budget that + remains below `TX_MAX_GAS_LIMIT` -- and a state gas reservoir that + holds whatever exceeds that cap. + + Only valid once `validate_transaction` has confirmed the transaction + can afford its intrinsic cost, which guarantees the subtractions + below do not underflow. + + Parameters + ---------- + tx_gas : + The transaction's gas limit. + intrinsic : + The transaction's intrinsic gas cost. + + Returns + ------- + allocation : `ExecutionGasAllocation` + The regular gas grant and state gas reservoir. + + """ + execution_gas = tx_gas - Uint(intrinsic.regular) + regular_gas_budget = TX_MAX_GAS_LIMIT - intrinsic.regular + regular_gas = min(regular_gas_budget, execution_gas) + state_gas_reservoir = Uint(execution_gas - regular_gas) + return ExecutionGasAllocation(regular_gas, state_gas_reservoir) + + +@final +@dataclass +class TransactionGasSettlement: + """ + Settled gas amounts for a finished transaction. + + Hold only gas figures; the caller turns them into fee payments and + block-accounting updates. + """ + + gas_used: Uint + """Total gas charged to the sender, after refund and floor.""" + + gas_left: Uint + """Gas returned to the sender, priced at the effective gas price.""" + + regular_gas_used: Uint + """Regular gas the transaction contributes to the block total.""" + + state_gas_used: Uint + """State gas the transaction contributes to the block total.""" + + +def settle_transaction_gas( + tx_gas: Uint, + intrinsic: IntrinsicGasCost, + gas_left: Uint, + state_gas_left: Uint, + refund_counter: U256, + state_gas_used: int, +) -> TransactionGasSettlement: + """ + Settle a transaction's gas after execution. + + Compute, in order: + + - the gas used before refunds, from the gas limit less the regular + gas and reservoir the top frame returned; + - the refund, capped at one fifth of that pre-refund usage; + - the gas used, taken as the larger of the post-refund usage and the + calldata floor, so a transaction never pays below the floor; and + - the per-dimension block amounts: the state gas used (clamped to + zero, since refunds can drive it negative) and the regular gas + used, which carries the floor because the floor binds the regular + dimension. Unlike the sender-facing `gas_used`, it ignores + refunds: block accounting counts pre-refund gas ([EIP-7778]). + + Parameters + ---------- + tx_gas : + The transaction's gas limit. + intrinsic : + The transaction's intrinsic gas cost. + gas_left : + Regular gas the top frame returned. + state_gas_left : + State gas reservoir the top frame returned. + refund_counter : + The refund the top frame accrued. + state_gas_used : + Net state gas the top frame consumed, possibly negative. + + Returns + ------- + settlement : `TransactionGasSettlement` + The settled gas amounts. + + [EIP-7778]: https://eips.ethereum.org/EIPS/eip-7778 + + """ + gas_used_before_refund = tx_gas - gas_left - state_gas_left + gas_refund = min(gas_used_before_refund // Uint(5), Uint(refund_counter)) + gas_used_after_refund = gas_used_before_refund - gas_refund + gas_used = max(gas_used_after_refund, intrinsic.calldata_floor) + + settled_state_gas_used = Uint(max(0, state_gas_used)) + regular_gas_used = max( + gas_used_before_refund - settled_state_gas_used, + intrinsic.calldata_floor, + ) + return TransactionGasSettlement( + gas_used=gas_used, + gas_left=tx_gas - gas_used, + regular_gas_used=regular_gas_used, + state_gas_used=settled_state_gas_used, + ) diff --git a/src/ethereum/forks/amsterdam/vm/instructions/control_flow.py b/src/ethereum/forks/amsterdam/vm/instructions/control_flow.py index 548a05d3163..76a7695c6f4 100644 --- a/src/ethereum/forks/amsterdam/vm/instructions/control_flow.py +++ b/src/ethereum/forks/amsterdam/vm/instructions/control_flow.py @@ -143,7 +143,7 @@ def gas_left(evm: Evm) -> None: charge_gas(evm, GasCosts.OPCODE_GAS) # OPERATION - push(evm.stack, U256(evm.gas_left)) + push(evm.stack, U256(evm.gas_meter.gas_left)) # PROGRAM COUNTER evm.pc += Uint(1) diff --git a/src/ethereum/forks/amsterdam/vm/instructions/storage.py b/src/ethereum/forks/amsterdam/vm/instructions/storage.py index 5d7ded542e2..b63a8ea2a42 100644 --- a/src/ethereum/forks/amsterdam/vm/instructions/storage.py +++ b/src/ethereum/forks/amsterdam/vm/instructions/storage.py @@ -21,7 +21,7 @@ set_storage, set_transient_storage, ) -from .. import Evm, credit_state_gas_refund +from .. import Evm from ..exceptions import WriteInStaticContext from ..gas import ( GasCosts, @@ -29,6 +29,7 @@ charge_gas, charge_state_gas, check_gas, + credit_state_gas_refund, ) from ..stack import pop, push @@ -81,6 +82,9 @@ def sstore(evm: Evm) -> None: key = pop(evm.stack).to_be_bytes32() new_value = pop(evm.stack) + # GAS (STATE-INDEPENDENT) + # Price what is computable without touching state, and check it is + # affordable before any state access is performed. gas_cost = Uint(0) # Access cost: cold or warm, always charged. @@ -98,6 +102,11 @@ def sstore(evm: Evm) -> None: # access cost can exceed the stipend, so the EIP-2200 stipend sentry # (`gas_left > CALL_STIPEND`) is no longer sufficient on its own. check_gas(evm, max(gas_cost, GasCosts.CALL_STIPEND + Uint(1))) + + # STATE ACCESS (STATE-DEPENDENT GAS) + # Perform the access and complete the state-dependent pricing from + # the slot's original and current values, adjusting the + # transaction's refunds. if is_cold_access: evm.accessed_storage_keys.add((evm.message.current_target, key)) @@ -117,17 +126,20 @@ def sstore(evm: Evm) -> None: if current_value != new_value: if original_value != 0 and current_value != 0 and new_value == 0: # Storage is cleared for the first time in the transaction - evm.refund_counter += GasCosts.REFUND_STORAGE_CLEAR + evm.gas_meter.refund_counter += GasCosts.REFUND_STORAGE_CLEAR if original_value != 0 and current_value == 0: # Gas refund issued earlier to be reversed - evm.refund_counter -= GasCosts.REFUND_STORAGE_CLEAR + evm.gas_meter.refund_counter -= GasCosts.REFUND_STORAGE_CLEAR if original_value == new_value: # Slot restored to its original value: refund the STORAGE_WRITE # charged on the first-time change earlier this transaction. - evm.refund_counter += int(GasCosts.STORAGE_WRITE) + evm.gas_meter.refund_counter += int(GasCosts.STORAGE_WRITE) + # STATE GAS + # A first-time set of a zero slot pays for the state it creates; a + # slot set then cleared refills the earlier charge. if original_value == current_value and current_value != new_value: if original_value == 0: state_gas = StateGasCosts.STORAGE_SET @@ -135,7 +147,7 @@ def sstore(evm: Evm) -> None: if current_value != new_value and original_value == new_value: if original_value == 0: # Slot set then cleared: refund the state gas charge. - credit_state_gas_refund(evm, StateGasCosts.STORAGE_SET) + credit_state_gas_refund(evm.gas_meter, StateGasCosts.STORAGE_SET) # Charge regular gas before state gas so that a regular-gas OOG # does not consume state gas that would inflate the parent's diff --git a/src/ethereum/forks/amsterdam/vm/instructions/system.py b/src/ethereum/forks/amsterdam/vm/instructions/system.py index 06ba36f47e1..7d5a23a0173 100644 --- a/src/ethereum/forks/amsterdam/vm/instructions/system.py +++ b/src/ethereum/forks/amsterdam/vm/instructions/system.py @@ -41,10 +41,8 @@ CALL_SUCCESS, Evm, Message, - credit_state_gas_refund, emit_transfer_log, - incorporate_child_on_error, - incorporate_child_on_success, + incorporate_child, ) from ..exceptions import OutOfGasError, Revert, WriteInStaticContext from ..gas import ( @@ -55,8 +53,11 @@ charge_gas, charge_state_gas, check_gas, + credit_state_gas_refund, + drain_state_gas_reservoir, init_code_cost, - max_message_call_gas, + restore_child_gas, + withhold_create_gas, ) from ..memory import memory_read_bytes, memory_write from ..stack import pop, push @@ -70,19 +71,17 @@ def generic_create( memory_size: U256, ) -> None: """ - Core logic used by the `CREATE*` family of opcodes. + Run the child-frame lifecycle for the `CREATE*` family of opcodes. + + The opcode has already priced the operation itself; this function + runs the lifecycle: preflight checks that abort without spawning, + the destination access with its account-creation charge and + collision check, the child's gas grant, the child frame itself, + and the resolution of its outcome back into the creating frame. """ # This import causes a circular import error # if it's not moved inside this method - from ...vm.interpreter import ( - MAX_INIT_CODE_SIZE, - STACK_DEPTH_LIMIT, - process_create_message, - ) - - # Check max init code size early before memory read - if memory_size > U256(MAX_INIT_CODE_SIZE): - raise OutOfGasError + from ...vm.interpreter import STACK_DEPTH_LIMIT, process_create_message tx_state = evm.message.tx_env.state @@ -92,6 +91,9 @@ def generic_create( evm.return_data = b"" + # PREFLIGHT + # Abort without spawning the child: nothing has been charged or + # withheld for it yet. sender_address = evm.message.current_target sender = get_account(tx_state, sender_address) @@ -103,34 +105,39 @@ def generic_create( push(evm.stack, U256(0)) return + # DESTINATION ACCESS + # The account-creation charge is decided by existence alone, + # independently of the collision outcome below. evm.accessed_addresses.add(contract_address) - # The charge is decided by existence alone, independently of the - # collision outcome. new_account_charged = not is_account_alive(tx_state, contract_address) if new_account_charged: charge_state_gas(evm, StateGasCosts.NEW_ACCOUNT) - create_message_gas = max_message_call_gas(Uint(evm.gas_left)) - evm.gas_left -= create_message_gas + # CHILD GRANT + # Withhold all but one 64th of the regular gas. + create_message_gas = withhold_create_gas(evm.gas_meter) + # On a collision the child's regular grant is consumed and no + # account is created; a storage-only collision target is + # non-existent: charged above, refilled here. if not account_deployable(tx_state, contract_address): increment_nonce(tx_state, sender_address) - evm.regular_gas_used += create_message_gas - # A storage-only collision target is non-existent: charged - # above, refilled here. if new_account_charged: - credit_state_gas_refund(evm, StateGasCosts.NEW_ACCOUNT) + credit_state_gas_refund(evm.gas_meter, StateGasCosts.NEW_ACCOUNT) push(evm.stack, U256(0)) return - # Move full reservoir to child (no 63/64 rule for state gas). Parent's - # `state_gas_left` is zeroed and restored when the child returns. - create_message_state_gas_reservoir = evm.state_gas_left - evm.state_gas_left = Uint(0) + # The whole state gas reservoir rides along (no 63/64 rule for + # state gas) and is restored when the child returns. + create_message_state_gas_reservoir = drain_state_gas_reservoir( + evm.gas_meter + ) increment_nonce(tx_state, sender_address) + # DISPATCH + child_message = Message( block_env=evm.message.block_env, tx_env=evm.message.tx_env, @@ -153,14 +160,17 @@ def generic_create( ) child_evm = process_create_message(child_message) + # OUTCOME + # The child settled its own gas; absorb it and resolve the + # account-creation charge by the state's fate: it refills when a + # charged creation failed. + incorporate_child(evm, child_evm) if child_evm.error: - incorporate_child_on_error(evm, child_evm) if new_account_charged: - credit_state_gas_refund(evm, StateGasCosts.NEW_ACCOUNT) + credit_state_gas_refund(evm.gas_meter, StateGasCosts.NEW_ACCOUNT) evm.return_data = child_evm.output push(evm.stack, U256(0)) else: - incorporate_child_on_success(evm, child_evm) evm.return_data = b"" push(evm.stack, U256.from_be_bytes(child_evm.message.current_target)) @@ -175,6 +185,10 @@ def create(evm: Evm) -> None: The current EVM frame. """ + # This import causes a circular import error + # if it's not moved inside this method + from ...vm.interpreter import MAX_INIT_CODE_SIZE + if evm.message.is_static: raise WriteInStaticContext @@ -193,6 +207,9 @@ def create(evm: Evm) -> None: GasCosts.CREATE_ACCESS + extend_memory.cost + init_code_gas, ) + if memory_size > U256(MAX_INIT_CODE_SIZE): + raise OutOfGasError + # OPERATION evm.memory += b"\x00" * extend_memory.expand_by contract_address = compute_contract_address( @@ -227,6 +244,10 @@ def create2(evm: Evm) -> None: The current EVM frame. """ + # This import causes a circular import error + # if it's not moved inside this method + from ...vm.interpreter import MAX_INIT_CODE_SIZE + if evm.message.is_static: raise WriteInStaticContext @@ -250,6 +271,9 @@ def create2(evm: Evm) -> None: + init_code_gas, ) + if memory_size > U256(MAX_INIT_CODE_SIZE): + raise OutOfGasError + # OPERATION evm.memory += b"\x00" * extend_memory.expand_by contract_address = compute_create2_contract_address( @@ -325,24 +349,42 @@ class GenericCall: code: Bytes disable_precompiles: bool new_account_charged: bool = False + insufficient_balance: bool = False + """ + True when the calling account cannot cover `value`; the call then + aborts in preflight without spawning the child frame. + """ def generic_call(evm: Evm, params: GenericCall) -> None: """ - Perform the core logic of the `CALL*` family of opcodes. + Run the child-frame lifecycle for the `CALL*` family of opcodes. + + The opcode has already priced the call and withheld the child's + grant; this function only runs the lifecycle: preflight checks + that abort without spawning, the child frame itself, and the + resolution of its outcome back into the calling frame. """ from ...vm.interpreter import STACK_DEPTH_LIMIT, process_message evm.return_data = b"" - if evm.message.depth + Uint(1) > STACK_DEPTH_LIMIT: - evm.gas_left += params.gas - evm.state_gas_left += params.state_gas_reservoir + # PREFLIGHT + # Abort without spawning the child: both grants return untouched + # and any account-creation charge refills. + if ( + evm.message.depth + Uint(1) > STACK_DEPTH_LIMIT + or params.insufficient_balance + ): + restore_child_gas( + evm.gas_meter, params.gas, params.state_gas_reservoir + ) if params.new_account_charged: - credit_state_gas_refund(evm, StateGasCosts.NEW_ACCOUNT) + credit_state_gas_refund(evm.gas_meter, StateGasCosts.NEW_ACCOUNT) push(evm.stack, U256(0)) return + # DISPATCH call_data = memory_read_bytes( evm.memory, params.memory_input_start_position, @@ -372,15 +414,16 @@ def generic_call(evm: Evm, params: GenericCall) -> None: child_evm = process_message(child_message) + # OUTCOME + # The child settled its own gas; absorb it and resolve the + # account-creation charge by the state's fate. + incorporate_child(evm, child_evm) + evm.return_data = child_evm.output if child_evm.error: - incorporate_child_on_error(evm, child_evm) if params.new_account_charged: - credit_state_gas_refund(evm, StateGasCosts.NEW_ACCOUNT) - evm.return_data = child_evm.output + credit_state_gas_refund(evm.gas_meter, StateGasCosts.NEW_ACCOUNT) push(evm.stack, U256(0)) else: - incorporate_child_on_success(evm, child_evm) - evm.return_data = child_evm.output push(evm.stack, CALL_SUCCESS) actual_output_size = min( @@ -415,7 +458,9 @@ def call(evm: Evm) -> None: if evm.message.is_static and value != U256(0): raise WriteInStaticContext - # GAS + # GAS (STATE-INDEPENDENT) + # Price what is computable without touching state, and check it is + # affordable before any state access is performed. extend_memory = calculate_gas_extend_memory( evm.memory, [ @@ -432,13 +477,15 @@ def call(evm: Evm) -> None: transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE - # check static gas before state access check_gas( evm, access_gas_cost + transfer_gas_cost + extend_memory.cost, ) - # STATE ACCESS + # STATE ACCESS (STATE-DEPENDENT GAS) + # Perform the accesses and complete the state-dependent pricing -- + # a delegation adds its access cost -- then charge the regular + # gas. tx_state = evm.message.tx_env.state if is_cold_access: evm.accessed_addresses.add(to) @@ -461,57 +508,56 @@ def call(evm: Evm) -> None: code = get_code(tx_state, code_hash) charge_gas(evm, extra_gas + extend_memory.cost) + + # STATE GAS + # A value transfer that will create the recipient is charged by + # the frame whose opcode causes it; refilled in `generic_call` + # whenever the creation fails or never happens. has_value = value != 0 new_account_charged = has_value and not is_account_alive(tx_state, to) if new_account_charged: charge_state_gas(evm, StateGasCosts.NEW_ACCOUNT) + # CHILD GRANT + # Computed after every charge above, so any state-gas spill has + # already thinned `gas_left`. The whole reservoir rides along (no + # 63/64 rule for state gas). message_call_gas = calculate_message_call_gas( value, gas, - Uint(evm.gas_left), + Uint(evm.gas_meter.gas_left), memory_cost=Uint(0), extra_gas=Uint(0), ) charge_gas(evm, message_call_gas.cost) - evm.regular_gas_used -= message_call_gas.sub_call + call_state_gas_reservoir = drain_state_gas_reservoir(evm.gas_meter) # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - # Pass full reservoir to child (no 63/64 rule for state gas) - call_state_gas_reservoir = evm.state_gas_left - evm.state_gas_left = Uint(0) - sender_balance = get_account(tx_state, evm.message.current_target).balance - if sender_balance < value: - push(evm.stack, U256(0)) - evm.return_data = b"" - evm.gas_left += message_call_gas.sub_call - evm.state_gas_left += call_state_gas_reservoir - if new_account_charged: - credit_state_gas_refund(evm, StateGasCosts.NEW_ACCOUNT) - else: - generic_call( - evm, - GenericCall( - gas=message_call_gas.sub_call, - state_gas_reservoir=call_state_gas_reservoir, - value=value, - caller=evm.message.current_target, - to=to, - code_address=code_address, - should_transfer_value=True, - is_staticcall=False, - memory_input_start_position=memory_input_start_position, - memory_input_size=memory_input_size, - memory_output_start_position=memory_output_start_position, - memory_output_size=memory_output_size, - code=code, - disable_precompiles=is_delegated, - new_account_charged=new_account_charged, - ), - ) + + generic_call( + evm, + GenericCall( + gas=message_call_gas.sub_call, + state_gas_reservoir=call_state_gas_reservoir, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + code=code, + disable_precompiles=is_delegated, + new_account_charged=new_account_charged, + insufficient_balance=sender_balance < value, + ), + ) # PROGRAM COUNTER evm.pc += Uint(1) @@ -536,7 +582,9 @@ def callcode(evm: Evm) -> None: memory_output_start_position = pop(evm.stack) memory_output_size = pop(evm.stack) - # GAS + # GAS (STATE-INDEPENDENT) + # Price what is computable without touching state, and check it is + # affordable before any state access is performed. to = evm.message.current_target extend_memory = calculate_gas_extend_memory( @@ -555,13 +603,15 @@ def callcode(evm: Evm) -> None: transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE - # check static gas before state access check_gas( evm, access_gas_cost + extend_memory.cost + transfer_gas_cost, ) - # STATE ACCESS + # STATE ACCESS (STATE-DEPENDENT GAS) + # Perform the accesses and complete the state-dependent pricing -- + # a delegation adds its access cost; the regular gas is charged + # with the child grant. tx_state = evm.message.tx_env.state if is_cold_access: evm.accessed_addresses.add(code_address) @@ -583,50 +633,45 @@ def callcode(evm: Evm) -> None: code_hash = get_account(tx_state, code_address).code_hash code = get_code(tx_state, code_hash) + # CHILD GRANT + # Charge the call's cost and withhold the child's regular gas + # share in one step. The whole reservoir rides along (no 63/64 + # rule for state gas). message_call_gas = calculate_message_call_gas( value, gas, - Uint(evm.gas_left), + Uint(evm.gas_meter.gas_left), extend_memory.cost, extra_gas, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) - evm.regular_gas_used -= message_call_gas.sub_call + call_state_gas_reservoir = drain_state_gas_reservoir(evm.gas_meter) # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - # Pass full reservoir to child (no 63/64 rule for state gas) - call_state_gas_reservoir = evm.state_gas_left - evm.state_gas_left = Uint(0) - sender_balance = get_account(tx_state, evm.message.current_target).balance - if sender_balance < value: - push(evm.stack, U256(0)) - evm.return_data = b"" - evm.gas_left += message_call_gas.sub_call - evm.state_gas_left += call_state_gas_reservoir - else: - generic_call( - evm, - GenericCall( - gas=message_call_gas.sub_call, - state_gas_reservoir=call_state_gas_reservoir, - value=value, - caller=evm.message.current_target, - to=to, - code_address=code_address, - should_transfer_value=True, - is_staticcall=False, - memory_input_start_position=memory_input_start_position, - memory_input_size=memory_input_size, - memory_output_start_position=memory_output_start_position, - memory_output_size=memory_output_size, - code=code, - disable_precompiles=is_delegated, - ), - ) + generic_call( + evm, + GenericCall( + gas=message_call_gas.sub_call, + state_gas_reservoir=call_state_gas_reservoir, + value=value, + caller=evm.message.current_target, + to=to, + code_address=code_address, + should_transfer_value=True, + is_staticcall=False, + memory_input_start_position=memory_input_start_position, + memory_input_size=memory_input_size, + memory_output_start_position=memory_output_start_position, + memory_output_size=memory_output_size, + code=code, + disable_precompiles=is_delegated, + insufficient_balance=sender_balance < value, + ), + ) # PROGRAM COUNTER evm.pc += Uint(1) @@ -648,21 +693,28 @@ def selfdestruct(evm: Evm) -> None: # STACK beneficiary = to_address_masked(pop(evm.stack)) - # GAS + # GAS (STATE-INDEPENDENT) + # Price what is computable without touching state, and check it is + # affordable before any state access is performed. gas_cost = GasCosts.OPCODE_SELFDESTRUCT_BASE is_cold_access = beneficiary not in evm.accessed_addresses if is_cold_access: gas_cost += GasCosts.COLD_ACCOUNT_ACCESS - # check access gas cost before state access check_gas(evm, gas_cost) - # STATE ACCESS + # STATE ACCESS (STATE-DEPENDENT GAS) + # Perform the access; the pricing completes with the state gas + # below. tx_state = evm.message.tx_env.state if is_cold_access: evm.accessed_addresses.add(beneficiary) + # STATE GAS + # A sweep that will create the beneficiary pays the account write + # and the creation, charged by the frame whose opcode causes it; + # it refills only through the frame's own rollback. state_gas = StateGas(Uint(0)) account_write_gas = Uint(0) if ( @@ -678,6 +730,7 @@ def selfdestruct(evm: Evm) -> None: charge_gas(evm, gas_cost + account_write_gas) charge_state_gas(evm, state_gas) + # OPERATION originator = evm.message.current_target originator_balance = get_account(tx_state, originator).balance @@ -717,7 +770,9 @@ def delegatecall(evm: Evm) -> None: memory_output_start_position = pop(evm.stack) memory_output_size = pop(evm.stack) - # GAS + # GAS (STATE-INDEPENDENT) + # Price what is computable without touching state, and check it is + # affordable before any state access is performed. extend_memory = calculate_gas_extend_memory( evm.memory, [ @@ -732,10 +787,12 @@ def delegatecall(evm: Evm) -> None: else: access_gas_cost = GasCosts.WARM_ACCESS - # check static gas before state access check_gas(evm, access_gas_cost + extend_memory.cost) - # STATE ACCESS + # STATE ACCESS (STATE-DEPENDENT GAS) + # Perform the accesses and complete the state-dependent pricing -- + # a delegation adds its access cost; the regular gas is charged + # with the child grant. if is_cold_access: evm.accessed_addresses.add(code_address) @@ -757,23 +814,23 @@ def delegatecall(evm: Evm) -> None: code_hash = get_account(tx_state, code_address).code_hash code = get_code(tx_state, code_hash) + # CHILD GRANT + # Charge the call's cost and withhold the child's regular gas + # share in one step. The whole reservoir rides along (no 63/64 + # rule for state gas). message_call_gas = calculate_message_call_gas( U256(0), gas, - Uint(evm.gas_left), + Uint(evm.gas_meter.gas_left), extend_memory.cost, extra_gas, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) - evm.regular_gas_used -= message_call_gas.sub_call + call_state_gas_reservoir = drain_state_gas_reservoir(evm.gas_meter) # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - # Pass full reservoir to child (no 63/64 rule for state gas) - call_state_gas_reservoir = evm.state_gas_left - evm.state_gas_left = Uint(0) - generic_call( evm, GenericCall( @@ -816,7 +873,9 @@ def staticcall(evm: Evm) -> None: memory_output_start_position = pop(evm.stack) memory_output_size = pop(evm.stack) - # GAS + # GAS (STATE-INDEPENDENT) + # Price what is computable without touching state, and check it is + # affordable before any state access is performed. extend_memory = calculate_gas_extend_memory( evm.memory, [ @@ -831,10 +890,12 @@ def staticcall(evm: Evm) -> None: else: access_gas_cost = GasCosts.WARM_ACCESS - # check static gas before state access check_gas(evm, access_gas_cost + extend_memory.cost) - # STATE ACCESS + # STATE ACCESS (STATE-DEPENDENT GAS) + # Perform the accesses and complete the state-dependent pricing -- + # a delegation adds its access cost; the regular gas is charged + # with the child grant. if is_cold_access: evm.accessed_addresses.add(to) @@ -856,23 +917,23 @@ def staticcall(evm: Evm) -> None: code_hash = get_account(tx_state, code_address).code_hash code = get_code(tx_state, code_hash) + # CHILD GRANT + # Charge the call's cost and withhold the child's regular gas + # share in one step. The whole reservoir rides along (no 63/64 + # rule for state gas). message_call_gas = calculate_message_call_gas( U256(0), gas, - Uint(evm.gas_left), + Uint(evm.gas_meter.gas_left), extend_memory.cost, extra_gas, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) - evm.regular_gas_used -= message_call_gas.sub_call + call_state_gas_reservoir = drain_state_gas_reservoir(evm.gas_meter) # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - # Pass full reservoir to child (no 63/64 rule for state gas) - call_state_gas_reservoir = evm.state_gas_left - evm.state_gas_left = Uint(0) - generic_call( evm, GenericCall( diff --git a/src/ethereum/forks/amsterdam/vm/interpreter.py b/src/ethereum/forks/amsterdam/vm/interpreter.py index c820a1b8748..8d0a3fe26ce 100644 --- a/src/ethereum/forks/amsterdam/vm/interpreter.py +++ b/src/ethereum/forks/amsterdam/vm/interpreter.py @@ -50,17 +50,20 @@ from ..vm.eoa_delegation import get_delegated_code_address, set_delegation from ..vm.gas import ( GasCosts, + GasMeter, StateGasCosts, charge_gas, charge_state_gas, + commit_state_gas, + forfeit_remaining_gas, + restore_state_gas, + restore_state_gas_to_entry, + tx_state_gas_used, ) from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS from . import ( Evm, - commit_frame_state_gas, emit_transfer_log, - frame_state_gas_used, - refill_frame_state_gas, ) from .exceptions import ( AddressCollision, @@ -93,7 +96,7 @@ class MessageCallOutput: 4. `accounts_to_delete`: Contracts which have self-destructed. 5. `error`: The error from the execution if any. 6. `return_data`: The output of the execution. - 7. `regular_gas_used`: Regular gas used during execution. + 7. `state_gas_left`: remaining state gas after execution. 8. `state_gas_used`: State gas used during execution. """ @@ -104,7 +107,6 @@ class MessageCallOutput: error: Optional[EthereumException] return_data: Bytes state_gas_left: Uint - regular_gas_used: Uint state_gas_used: int @@ -137,7 +139,6 @@ def process_message_call(message: Message) -> MessageCallOutput: error=AddressCollision(), return_data=Bytes(b""), state_gas_left=message.state_gas_reservoir, - regular_gas_used=message.gas, state_gas_used=0, ) else: @@ -150,27 +151,28 @@ def process_message_call(message: Message) -> MessageCallOutput: if evm.error: logs: Tuple[Log, ...] = () accounts_to_delete = set() - refund_counter = U256(0) else: logs = evm.logs accounts_to_delete = evm.accounts_to_delete - refund_counter = U256(evm.refund_counter) tx_end = TransactionEnd( - int(message.gas) - int(evm.gas_left), evm.output, evm.error + int(message.gas) - int(evm.gas_meter.gas_left), evm.output, evm.error ) evm_trace(evm, tx_end) + # A failed frame settles its meter with a zero refund counter, so + # the refunds can be read unconditionally. return MessageCallOutput( - gas_left=evm.gas_left, - refund_counter=refund_counter, + gas_left=evm.gas_meter.gas_left, + refund_counter=U256(evm.gas_meter.refund_counter), logs=logs, accounts_to_delete=accounts_to_delete, error=evm.error, return_data=evm.output, - state_gas_left=evm.state_gas_left, - regular_gas_used=evm.regular_gas_used, - state_gas_used=frame_state_gas_used(evm), + state_gas_left=evm.gas_meter.state_gas_left, + state_gas_used=tx_state_gas_used( + evm.gas_meter, message.state_gas_reservoir + ), ) @@ -232,9 +234,10 @@ def process_create_message(message: Message) -> Evm: charge_state_gas(evm, code_deposit_state_gas) except ExceptionalHalt as error: restore_tx_state(tx_state, snapshot) - refill_frame_state_gas(evm) - evm.regular_gas_used += evm.gas_left - evm.gas_left = Uint(0) + # A create frame never applies authorizations, so its + # baseline is still the frame's entry reservoir. + restore_state_gas(evm.gas_meter) + forfeit_remaining_gas(evm.gas_meter) evm.output = b"" evm.error = error else: @@ -339,11 +342,13 @@ def process_message(message: Message) -> Evm: stack=[], memory=bytearray(), code=Bytes(b""), - gas_left=message.gas, - state_gas_left=message.state_gas_reservoir, + gas_meter=GasMeter( + gas_left=message.gas, + state_gas_left=message.state_gas_reservoir, + state_gas_baseline=message.state_gas_reservoir, + ), valid_jump_destinations=set(), logs=(), - refund_counter=0, running=True, message=message, output=b"", @@ -356,26 +361,25 @@ def process_message(message: Message) -> Evm: if message.depth == Uint(0): prep_snapshot = copy_tx_state(tx_state) - prep_reservoir = message.state_gas_reservoir try: if message.tx_env.authorizations != (): set_delegation(evm) # The applied delegations outlive a failure of the - # dispatched code, so their state gas must not refill - # with it. - commit_frame_state_gas(evm) + # dispatched code, so their state gas is committed as + # non-refillable; a later failure restores only to the + # post-commit baseline. + commit_state_gas(evm.gas_meter) prepare_dispatch(evm) except ExceptionalHalt as error: evm_trace(evm, OpException(error)) restore_tx_state(tx_state, prep_snapshot) # The rollback reverts any applied delegations, so the - # commit above is undone with it and every state charge is - # refilled. - message.state_gas_reservoir = prep_reservoir - evm.committed_state_gas = 0 - refill_frame_state_gas(evm) - evm.regular_gas_used += evm.gas_left - evm.gas_left = Uint(0) + # commit above is undone with it: roll state gas back to + # frame entry, refilling every state charge. + restore_state_gas_to_entry( + evm.gas_meter, message.state_gas_reservoir + ) + forfeit_remaining_gas(evm.gas_meter) evm.error = error return evm @@ -421,14 +425,19 @@ def process_message(message: Message) -> Evm: except ExceptionalHalt as error: evm_trace(evm, OpException(error)) - refill_frame_state_gas(evm) - evm.regular_gas_used += evm.gas_left - evm.gas_left = Uint(0) + # Frame settlement: refill state gas to the baseline, then + # forfeit -- a halted frame returns no regular gas to its + # parent. After these handlers the meter states exactly what + # the frame gives back, so parents absorb unconditionally. + restore_state_gas(evm.gas_meter) + forfeit_remaining_gas(evm.gas_meter) evm.output = b"" evm.error = error except Revert as error: evm_trace(evm, OpException(error)) - refill_frame_state_gas(evm) + # Frame settlement: refill state gas to the baseline -- a + # reverted frame returns its unspent `gas_left` to its parent. + restore_state_gas(evm.gas_meter) evm.error = error if evm.error: diff --git a/src/ethereum_spec_tools/evm_tools/t8n/evm_trace/eip3155.py b/src/ethereum_spec_tools/evm_tools/t8n/evm_trace/eip3155.py index 9f503c85154..b2b26008a8e 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/evm_trace/eip3155.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/evm_trace/eip3155.py @@ -27,8 +27,10 @@ from .protocols import ( Evm, EvmWithReturnData, - EvmWithStateGas, TransactionEnvironment, + evm_gas_left, + evm_refund_counter, + evm_state_gas_left, ) EXCLUDE_FROM_OUTPUT = [ @@ -132,10 +134,10 @@ def __call__(self, evm: Any, event: TraceEvent) -> None: if self.active_traces: last_trace = self.active_traces[-1] - refund_counter = evm.refund_counter + refund_counter = evm_refund_counter(evm) parent_evm = evm.message.parent_evm while parent_evm is not None: - refund_counter += parent_evm.refund_counter + refund_counter += evm_refund_counter(parent_evm) parent_evm = parent_evm.message.parent_evm len_memory = len(evm.memory) @@ -168,7 +170,7 @@ def __call__(self, evm: Any, event: TraceEvent) -> None: new_trace = Trace( pc=int(evm.pc), op="0x" + event.address.hex().lstrip("0"), - gas=hex(evm.gas_left), + gas=hex(evm_gas_left(evm)), gasCost="0x0", memory=memory, memSize=len_memory, @@ -193,13 +195,14 @@ def __call__(self, evm: Any, event: TraceEvent) -> None: op = "Invalid" state_gas = None - if isinstance(evm, EvmWithStateGas): - state_gas = hex(evm.state_gas_left) + state_gas_left = evm_state_gas_left(evm) + if state_gas_left is not None: + state_gas = hex(state_gas_left) new_trace = Trace( pc=int(evm.pc), op=op, - gas=hex(evm.gas_left), + gas=hex(evm_gas_left(evm)), gasCost="0x0", memory=memory, memSize=len_memory, @@ -244,7 +247,7 @@ def __call__(self, evm: Any, event: TraceEvent) -> None: new_trace = Trace( pc=int(evm.pc), op=event.error.code, - gas=hex(evm.gas_left), + gas=hex(evm_gas_left(evm)), gasCost="0x0", memory=memory, memSize=len_memory, diff --git a/src/ethereum_spec_tools/evm_tools/t8n/evm_trace/protocols.py b/src/ethereum_spec_tools/evm_tools/t8n/evm_trace/protocols.py index 74ec4cb0cb3..1b0a2271eff 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/evm_trace/protocols.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/evm_trace/protocols.py @@ -32,19 +32,50 @@ class Message(Protocol): @runtime_checkable class Evm(Protocol): """ - The class describes the EVM interface for pre-byzantium forks trace. + The class describes the EVM interface common to every fork's trace. """ pc: Uint stack: list[U256] memory: bytearray code: Bytes - gas_left: Uint - refund_counter: int running: bool message: Message +@runtime_checkable +class GasMeter(Protocol): + """ + The class describes the gas meter of forks that bundle gas + accounting into a dedicated object (EIP-8037). + """ + + gas_left: Uint + state_gas_left: Uint + refund_counter: int + + +@runtime_checkable +class EvmWithFlatGas(Evm, Protocol): + """ + The class describes the EVM interface for forks that track gas in + flat fields on the EVM itself. + """ + + gas_left: Uint + refund_counter: int + + +@runtime_checkable +class EvmWithGasMeter(Evm, Protocol): + """ + The class describes the EVM interface for forks that track gas in a + dedicated gas meter (EIP-8037). + """ + + gas_meter: GasMeter + + @runtime_checkable class EvmWithReturnData(Evm, Protocol): """ @@ -54,10 +85,30 @@ class EvmWithReturnData(Evm, Protocol): return_data: Bytes -@runtime_checkable -class EvmWithStateGas(EvmWithReturnData, Protocol): +def evm_gas_left(evm: Evm) -> Uint: """ - The class describes the EVM interface for forks with state gas (EIP-8037). + Read the regular gas remaining, whichever gas layout the fork uses. """ + if isinstance(evm, EvmWithGasMeter): + return evm.gas_meter.gas_left + assert isinstance(evm, EvmWithFlatGas) + return evm.gas_left - state_gas_left: Uint + +def evm_refund_counter(evm: Evm) -> int: + """ + Read the refund counter, whichever gas layout the fork uses. + """ + if isinstance(evm, EvmWithGasMeter): + return evm.gas_meter.refund_counter + assert isinstance(evm, EvmWithFlatGas) + return evm.refund_counter + + +def evm_state_gas_left(evm: Evm) -> Uint | None: + """ + Read the state gas remaining, or `None` for forks without state gas. + """ + if isinstance(evm, EvmWithGasMeter): + return evm.gas_meter.state_gas_left + return None diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py index 4034be8dcb3..882872cff2d 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py @@ -1723,8 +1723,8 @@ def test_create_child_halt_refunds_state_gas( Verify CREATE/CREATE2 child halt refunds parent's account gas. Exceptional halts (invalid opcode, EIP-3541 invalid prefix) - consume all forwarded gas as `regular_gas_used`, so block - accounting cannot strictly discriminate via header gas. Tight + consume all forwarded regular gas, so block accounting cannot + strictly discriminate via header gas. Tight gas tuning via a caller wrapper leaves the factory with just enough `gas_left` to pay the probe SSTORE's regular portion but not enough to spill the state portion, so the probe SSTORE @@ -1758,8 +1758,8 @@ def test_create_child_halt_refunds_state_gas( ), ) - # Tight gas tuning: child halt consumes all forwarded gas as - # regular_gas_used. Factory retains + # Tight gas tuning: child halt consumes all forwarded regular + # gas. Factory retains # ~(forwarded - pre_sstore_regular) / 64 after CREATE. Target # the discrimination window `(probe_regular, # probe_regular + sstore_state_gas)` so the probe SSTORE @@ -2177,7 +2177,7 @@ def test_failed_create_tx_refills_top_frame_new_account( initcode then fails the whole creation rolls back and no account persists: - * REVERT preserves ``gas_left`` and ``refill_frame_state_gas`` returns + * REVERT preserves ``gas_left`` and ``restore_state_gas`` returns the spilled ``NEW_ACCOUNT`` to it, so the state block nets to zero and only the regular consumption counts as work. The calldata floor tops up the billed amount and the block-level regular gas alike, so From 9e9bf9068c5bbc337cbbb6794d14786d61840a6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=94=A1=E4=BD=B3=E8=AA=A0=20Louis=20Tsai?= <72684086+LouisTsai-Csie@users.noreply.github.com> Date: Mon, 20 Jul 2026 23:37:35 +0800 Subject: [PATCH 138/233] refactor(test-benchmark): align test suite with `glam-devnet-7` spec (#3187) Co-authored-by: marioevz <marioevz@gmail.com> Co-authored-by: Jochem Brouwer <jochembrouwer96@gmail.com> Co-authored-by: spencer-tb <spencer.tb@ethereum.org> --- .github/configs/evm.yaml | 2 +- .github/configs/feature.yaml | 4 +- Justfile | 16 +- .../testing/src/execution_testing/__init__.py | 2 + .../benchmark/benchmark_code_generator.py | 37 +- .../plugins/execute/pre_alloc.py | 39 +- .../pytest_commands/plugins/filler/filler.py | 3 + .../forks/forks/eips/amsterdam/eip_8037.py | 39 ++ .../src/execution_testing/specs/base.py | 30 +- .../src/execution_testing/specs/benchmark.py | 90 ++-- .../src/execution_testing/specs/blockchain.py | 35 +- .../src/execution_testing/tools/__init__.py | 2 + .../tools/tests/test_iterating_bytecode.py | 233 +++++++++- .../tools/tools_code/__init__.py | 2 + .../tools/tools_code/generators.py | 393 +++++++++++++---- .../test_block_access_list.py | 17 +- tests/benchmark/compute/helpers.py | 31 +- .../compute/instruction/test_arithmetic.py | 46 +- .../compute/instruction/test_memory.py | 32 +- .../compute/instruction/test_storage.py | 12 +- .../compute/instruction/test_system.py | 412 +++++++++++------- .../compute/precompile/test_alt_bn128.py | 87 +++- .../compute/precompile/test_bls12_381.py | 97 +++-- .../scenario/test_transaction_types.py | 241 ++++++---- .../scenario/test_unchunkified_bytecode.py | 6 + tests/benchmark/helper/__init__.py | 1 + tests/benchmark/helper/account_creator.py | 356 +++++++++++++++ .../helper/account_sender_receiver.py | 80 ++++ .../stateful/bloatnet/test_account_query.py | 248 ++++++++++- .../stateful/bloatnet/test_create2_access.py | 81 ++-- .../bloatnet/test_delegatecall_chain.py | 103 ----- .../test_extcodesize_bytecode_sizes.py | 90 +--- .../stateful/bloatnet/test_multi_opcode.py | 146 ++----- .../stateful/bloatnet/test_single_opcode.py | 227 +--------- .../bloatnet/test_transaction_types.py | 242 ++++------ .../bloatnet/test_transient_storage.py | 47 +- tests/benchmark/stateful/helpers.py | 73 ++-- 37 files changed, 2277 insertions(+), 1325 deletions(-) create mode 100644 tests/benchmark/helper/__init__.py create mode 100644 tests/benchmark/helper/account_creator.py create mode 100644 tests/benchmark/helper/account_sender_receiver.py delete mode 100644 tests/benchmark/stateful/bloatnet/test_delegatecall_chain.py diff --git a/.github/configs/evm.yaml b/.github/configs/evm.yaml index b36b347d981..981615796f2 100644 --- a/.github/configs/evm.yaml +++ b/.github/configs/evm.yaml @@ -1,7 +1,7 @@ benchmark: impl: geth repo: ethereum/go-ethereum - ref: master + ref: glamsterdam-devnet-7 evm-bin: evm xdist: auto eels: diff --git a/.github/configs/feature.yaml b/.github/configs/feature.yaml index 1b47c731cd9..735fffa0cd9 100644 --- a/.github/configs/feature.yaml +++ b/.github/configs/feature.yaml @@ -14,12 +14,12 @@ tests: benchmark: evm-type: benchmark - fill-params: --fork=Osaka --generate-all-formats --gas-benchmark-values 1,5,10,30,60,100,150 ./tests/benchmark/compute --maxprocesses=30 --dist=worksteal + fill-params: --fork=Amsterdam --generate-all-formats --gas-benchmark-values 1,5,10,30,60,100,150 ./tests/benchmark/compute --maxprocesses=30 --dist=worksteal feature_only: true benchmark_fast: evm-type: benchmark - fill-params: --fork=Osaka --generate-all-formats --gas-benchmark-values 100 ./tests/benchmark/compute + fill-params: --fork=Amsterdam --generate-all-formats --gas-benchmark-values 100 ./tests/benchmark/compute feature_only: true # Shared entry for all `<feat>-devnet` releases; matched by `-devnet` suffix. diff --git a/Justfile b/Justfile index bd5e5869067..75e38c246df 100644 --- a/Justfile +++ b/Justfile @@ -261,7 +261,7 @@ bench-gas *args: --generate-pre-alloc-groups \ --evm-bin="{{ evm_bin }}" \ --gas-benchmark-values 1 \ - --fork Osaka \ + --fork Amsterdam \ -m "not slow" \ -n auto --maxprocesses 10 --dist=loadgroup \ --output="{{ output_dir }}/bench-gas/pre-alloc" \ @@ -274,7 +274,7 @@ bench-gas *args: uv run fill \ --evm-bin="{{ evm_bin }}" \ --gas-benchmark-values 1 \ - --fork Osaka \ + --fork Amsterdam \ -m "blockchain_test and (not derived_test) and (not slow)" \ -n auto --maxprocesses 10 --dist=loadgroup \ --durations=20 \ @@ -288,7 +288,7 @@ bench-gas *args: @rm -rf tests/json_loader/bench_gas_fixtures ln -sfn "{{ output_dir }}/bench-gas/fixtures" tests/json_loader/bench_gas_fixtures cd tests/json_loader && uv run --python pypy3.11 --no-dev --group test pytest \ - --fork Osaka \ + --fork Amsterdam \ --allow-post-state-hash \ -n auto --maxprocesses 10 --dist=loadfile \ --durations=20 \ @@ -302,8 +302,8 @@ bench-opcode *args: uv run fill \ --evm-bin="{{ evm_bin }}" \ --fixed-opcode-count 1 \ - --fork Osaka \ - -m repricing \ + --fork Amsterdam \ + -m "repricing and not slow" \ -n auto --maxprocesses 10 --dist=loadgroup \ -k "not test_alt_bn128 and not test_bls12_381 and not test_modexp and not uncachable" \ --output="{{ output_dir }}/bench-opcode/fixtures" \ @@ -321,10 +321,10 @@ bench-opcode-config *args: uv run fill \ --evm-bin="{{ evm_bin }}" \ --fixed-opcode-count \ - --fork Osaka \ - -m repricing \ + --fork Amsterdam \ + -m "repricing and not slow" \ -n auto --maxprocesses 10 --dist=loadgroup \ - -k "not test_alt_bn128 and not test_bls12_381 and not test_modexp and not test_point_evaluation_uncachable" \ + -k "not test_alt_bn128 and not test_bls12_381 and not test_modexp and not uncachable" \ --output="{{ output_dir }}/bench-opcode-config/fixtures" \ --basetemp="{{ output_dir }}/bench-opcode-config/tmp" \ --log-to "{{ output_dir }}/bench-opcode-config/logs" \ diff --git a/packages/testing/src/execution_testing/__init__.py b/packages/testing/src/execution_testing/__init__.py index 87e87352186..0b47af7a9be 100644 --- a/packages/testing/src/execution_testing/__init__.py +++ b/packages/testing/src/execution_testing/__init__.py @@ -110,6 +110,7 @@ SequentialAddressLayout, Switch, TransactionWithCost, + TxOutcome, While, WhileGas, extend_with_defaults, @@ -226,6 +227,7 @@ "TransactionTestFiller", "TransactionType", "TransactionWithCost", + "TxOutcome", "TransitionFork", "While", "WhileGas", diff --git a/packages/testing/src/execution_testing/benchmark/benchmark_code_generator.py b/packages/testing/src/execution_testing/benchmark/benchmark_code_generator.py index 8a77c4eb963..b868855cdf3 100644 --- a/packages/testing/src/execution_testing/benchmark/benchmark_code_generator.py +++ b/packages/testing/src/execution_testing/benchmark/benchmark_code_generator.py @@ -100,21 +100,38 @@ def deploy_contracts(self, *, pre: Alloc, fork: Fork) -> Address: # Create caller contract that repeatedly calls the target contract # attack = POP( - # STATICCALL(GAS, target_contract_address, 0, 0, 0, 0) + # (STATIC)CALL(GAS, target_contract_address, ...) # ) # # setup + JUMPDEST + attack + attack + ... + attack + # JUMP(setup_length) - code_sequence = Op.POP( - Op.STATICCALL( - Op.GAS, - self._target_contract_address, - Op.PUSH0, - Op.CALLDATASIZE, - Op.PUSH0, - Op.PUSH0, + # + # The target must be entered via CALL when it contains + # state-changing opcodes: a STATICCALL'd frame faults on the + # first one and the target executes nothing. + # A state-changing target must be entered via CALL, not STATICCALL. + # CALL takes a value argument STATICCALL lacks; push the zero value + # with PUSH0, one gas cheaper than the default PUSH1 0x00. + if self.uses_state_changing_opcode(): + wrapper_call = Op.CALL( + gas=Op.GAS, + address=self._target_contract_address, + value=Op.PUSH0, + args_offset=Op.PUSH0, + args_size=Op.CALLDATASIZE, + ret_offset=Op.PUSH0, + ret_size=Op.PUSH0, ) - ) + else: + wrapper_call = Op.STATICCALL( + gas=Op.GAS, + address=self._target_contract_address, + args_offset=Op.PUSH0, + args_size=Op.CALLDATASIZE, + ret_offset=Op.PUSH0, + ret_size=Op.PUSH0, + ) + code_sequence = Op.POP(wrapper_call) caller_code = self.generate_repeated_code( setup=Op.CALLDATACOPY(Op.PUSH0, Op.PUSH0, Op.CALLDATASIZE), diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py index f8712d21f41..9658852cda8 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py @@ -29,6 +29,7 @@ ) from execution_testing.forks import Fork, TransitionFork from execution_testing.logging import get_logger +from execution_testing.recipient_type import RecipientType from execution_testing.rpc import EthRPC from execution_testing.rpc.rpc_types import TransactionByHashResponse from execution_testing.test_types import ( @@ -287,8 +288,11 @@ def _compute_deploy_gas_limit( else: regular_gas = buffered_regular_gas - # State portion, from the block reservoir. + # State portion, from the block reservoir. The created account's + # NEW_ACCOUNT is charged at the top frame for create transactions + # and by CREATE2 at access for proxy deploys — same amount. state_gas = fork.code_deposit_state_gas(code_size=deploy_code_size) + state_gas += fork.transaction_top_frame_state_gas(contract_creation=True) state_gas += storage_slots * sstore_state_gas deploy_gas_limit = regular_gas + state_gas @@ -630,6 +634,35 @@ def _fund_eoa( ) intrinsic_calc = fork.transaction_intrinsic_cost_calculator() + worst_case_auth = [ + AuthorizationTuple( + address=Address(0), + v=0, + r=0, + s=0, + creates_account=True, + writes_delegation=True, + first_write=True, + ) + ] + auth_fund_gas_limit = ( + intrinsic_calc( + authorization_list_or_count=1, + sends_value=True, + recipient_type=RecipientType.EMPTY_ACCOUNT, + ) + + fork.transaction_top_frame_gas_calculator()( + sends_value=True, + recipient_type=RecipientType.EMPTY_ACCOUNT, + authorizations=worst_case_auth, + ) + + fork.transaction_top_frame_state_gas( + sends_value=True, + recipient_type=RecipientType.EMPTY_ACCOUNT, + authorizations=worst_case_auth, + ) + ) + if storage is not None: if not isinstance(storage, Storage): storage = Storage.model_validate(storage) @@ -703,7 +736,7 @@ def _fund_eoa( signer=eoa, ), ], - gas_limit=(intrinsic_calc(authorization_list_or_count=1)), + gas_limit=auth_fund_gas_limit, ) eoa.nonce = Number(eoa.nonce + 1) else: @@ -721,7 +754,7 @@ def _fund_eoa( signer=eoa, ), ], - gas_limit=intrinsic_calc(authorization_list_or_count=1), + gas_limit=auth_fund_gas_limit, ) eoa.nonce = Number(eoa.nonce + 1) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py index 3f6484b42ba..0163d5b2432 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py @@ -1754,6 +1754,9 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: # If operation mode is benchmarking, check the gas used. self.validate_benchmark_gas( benchmark_gas_used=fill_result.benchmark_gas_used, + benchmark_block_gas_used=( + fill_result.benchmark_block_gas_used + ), gas_benchmark_value=gas_benchmark_value, ) diff --git a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8037.py b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8037.py index d3ed10edd2c..f3428f7ec10 100644 --- a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8037.py +++ b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8037.py @@ -150,6 +150,9 @@ def opcode_state_map( Opcodes.SELFDESTRUCT: ( lambda op: cls._calculate_selfdestruct_state_gas(op, gas_costs) ), + Opcodes.CALL: lambda op: cls._calculate_call_state_gas( + op, gas_costs + ), } @classmethod @@ -400,3 +403,39 @@ def _calculate_selfdestruct_gas( if opcode.metadata["account_new"]: gas_cost -= gas_costs.NEW_ACCOUNT return gas_cost + + @classmethod + def _calculate_call_state_gas( + cls, opcode: OpcodeBase, gas_costs: GasCosts + ) -> int: + """ + Calculate the CALL state gas cost: `NEW_ACCOUNT` when a value + transfer funds a new account. Before EIP-8037 this was folded + into the regular CALL cost (EIP-161); under EIP-8037 it is + exposed here as state gas, mirroring + `_calculate_selfdestruct_state_gas`. + """ + metadata = opcode.metadata + if "value_transfer" in metadata and metadata["value_transfer"]: + if metadata["account_new"]: + return gas_costs.NEW_ACCOUNT + return 0 + + @classmethod + def _calculate_call_gas( + cls, opcode: OpcodeBase, gas_costs: GasCosts + ) -> int: + """ + Calculate the regular CALL gas cost. The EIP-161 base + calculation folds `NEW_ACCOUNT` into the regular cost when a + value transfer funds a new account; EIP-8037 moves that charge + to the state-gas dimension (see `_calculate_call_state_gas`), + so this subtracts the `NEW_ACCOUNT` term back out of the + inherited regular cost. + """ + gas_cost = super()._calculate_call_gas(opcode, gas_costs) + metadata = opcode.metadata + if "value_transfer" in metadata and metadata["value_transfer"]: + if metadata["account_new"]: + gas_cost -= gas_costs.NEW_ACCOUNT + return gas_cost diff --git a/packages/testing/src/execution_testing/specs/base.py b/packages/testing/src/execution_testing/specs/base.py index a5e53669978..27278494403 100644 --- a/packages/testing/src/execution_testing/specs/base.py +++ b/packages/testing/src/execution_testing/specs/base.py @@ -94,6 +94,7 @@ class FillResult(BaseModel): fixture: BaseFixture gas_optimization: int | None benchmark_gas_used: int | None = None + benchmark_block_gas_used: int | None = None benchmark_opcode_count: OpcodeCount | None = None post_verifications: PostVerifications | None = None metadata: Dict[str, Any] = Field(default_factory=dict) @@ -259,12 +260,25 @@ def get_genesis_environment(self) -> Environment: ) def validate_benchmark_gas( - self, *, benchmark_gas_used: int | None, gas_benchmark_value: int + self, + *, + benchmark_gas_used: int | None, + gas_benchmark_value: int, + benchmark_block_gas_used: int | None = None, ) -> None: """ Validates the total consumed gas of the last block in the test matches the expectation of the benchmark test. + ``benchmark_gas_used`` is the combined gas across all dimensions (the + receipt ``cumulativeGasUsed``) and is checked against + ``expected_benchmark_gas_used``. ``benchmark_block_gas_used`` is the + block-header gas, i.e. the maximum across the independent gas + dimensions (EIP-8037); it is what must stay within the block gas + limit, because the combined value can legitimately exceed it. When it + is not available (e.g. execute mode), the combined value is used for + the ceiling check instead. + Requires the following fields to be set: - expected_benchmark_gas_used - operation_mode @@ -287,9 +301,17 @@ def validate_benchmark_gas( f"({expected_benchmark_gas_used}), " f"difference: {diff}" ) - # Gas used should never exceed the maximum benchmark gas allowed. - assert benchmark_gas_used <= gas_benchmark_value, ( - f"benchmark_gas_used ({benchmark_gas_used}) exceeds maximum " + # No single gas dimension may exceed the block gas limit. The + # block-header gas is the max across dimensions; the combined + # regular+state gas may exceed the target under EIP-8037, so the + # ceiling is checked against the header value when available. + block_gas_used = ( + benchmark_block_gas_used + if benchmark_block_gas_used is not None + else benchmark_gas_used + ) + assert block_gas_used <= gas_benchmark_value, ( + f"benchmark block gas used ({block_gas_used}) exceeds maximum " "benchmark gas allowed for this configuration: " f"{gas_benchmark_value}" ) diff --git a/packages/testing/src/execution_testing/specs/benchmark.py b/packages/testing/src/execution_testing/specs/benchmark.py index ae283b98fa2..c1c2d5358e6 100644 --- a/packages/testing/src/execution_testing/specs/benchmark.py +++ b/packages/testing/src/execution_testing/specs/benchmark.py @@ -64,6 +64,30 @@ def __str__(self) -> str: return self.name +STATE_CHANGING_OPCODES = ( + Op.SSTORE, + Op.TSTORE, + Op.CREATE, + Op.CREATE2, + Op.CALL, + Op.SELFDESTRUCT, + Op.LOG0, + Op.LOG1, + Op.LOG2, + Op.LOG3, + Op.LOG4, +) +""" +Opcodes that fault inside a static context (EIP-214), so a target using +one must be entered via CALL rather than STATICCALL. CALLCODE is +excluded: EIP-214 exempts it even with a non-zero value. CALL is kept +unconditionally; it only faults when it sends value, but the value is +not always statically known, so treating every CALL as state-changing +is the conservative choice, it can only widen a STATICCALL wrapper to +CALL, never break a benchmark. +""" + + @dataclass(kw_only=True) class BenchmarkCodeGenerator(ABC): """Abstract base class for generating benchmark bytecode.""" @@ -82,6 +106,18 @@ def deploy_contracts(self, *, pre: Alloc, fork: Fork) -> Address: """Deploy any contracts needed for the benchmark.""" ... + def uses_state_changing_opcode(self) -> bool: + """ + Return whether the setup or attack block contains an opcode that + is illegal in a static context, in which case the target contract + must be entered via CALL instead of STATICCALL (a STATICCALL'd + frame faults on the first such opcode and executes nothing). + """ + target_code = bytes(self.setup) + bytes(self.attack_block) + return any( + bytes(opcode) in target_code for opcode in STATE_CHANGING_OPCODES + ) + def deploy_fix_count_contracts(self, *, pre: Alloc, fork: Fork) -> Address: """Deploy the contract with a fixed opcode count.""" code = self.generate_repeated_code( @@ -107,41 +143,33 @@ def deploy_fix_count_contracts(self, *, pre: Alloc, fork: Fork) -> Address: Op.PUSH0, Op.PUSH0, Op.CALLDATASIZE ) + Op.PUSH4(iterations) - is_state_changing_set = [ - Op.SSTORE, - Op.TSTORE, - Op.CREATE, - Op.CREATE2, - Op.CALL, - Op.CALLCODE, - Op.SELFDESTRUCT, - Op.LOG0, - Op.LOG1, - Op.LOG2, - Op.LOG3, - Op.LOG4, - ] - - # Select CALL for state-changing opcodes, STATICCALL otherwise - uses_state_changing_opcode = any( - bytes(opcode) in bytes(self.attack_block) - for opcode in is_state_changing_set - ) - call_opcode = Op.CALL if uses_state_changing_opcode else Op.STATICCALL + # Select CALL for state-changing opcodes, STATICCALL otherwise. + # CALL takes a value argument STATICCALL lacks; push the zero value + # with PUSH0, one gas cheaper than the default PUSH1 0x00. + if self.uses_state_changing_opcode(): + wrapper_call = Op.CALL( + gas=Op.GAS, + address=self._target_contract_address, + value=Op.PUSH0, + args_offset=0, + args_size=Op.CALLDATASIZE, + ret_offset=0, + ret_size=0, + ) + else: + wrapper_call = Op.STATICCALL( + gas=Op.GAS, + address=self._target_contract_address, + args_offset=0, + args_size=Op.CALLDATASIZE, + ret_offset=0, + ret_size=0, + ) opcode = ( prefix + Op.JUMPDEST - + Op.POP( - call_opcode( - gas=Op.GAS, - address=self._target_contract_address, - args_offset=0, - args_size=Op.CALLDATASIZE, - ret_offset=0, - ret_size=0, - ) - ) + + Op.POP(wrapper_call) + Op.PUSH1(1) + Op.SWAP1 + Op.SUB diff --git a/packages/testing/src/execution_testing/specs/blockchain.py b/packages/testing/src/execution_testing/specs/blockchain.py index a7ab9a9ecde..55822c72567 100644 --- a/packages/testing/src/execution_testing/specs/blockchain.py +++ b/packages/testing/src/execution_testing/specs/blockchain.py @@ -464,6 +464,26 @@ class BuiltBlock(CamelModel): block_access_list: BlockAccessList | None engine_new_payload_block_access_list: Bytes | None = None + def cumulative_gas_used(self) -> int: + """Return the last receipt's cumulative gas used.""" + if not self.result.receipts: + return int(self.result.gas_used) + cumulative_gas_used = self.result.receipts[-1].cumulative_gas_used + if cumulative_gas_used is None: + return int(self.result.gas_used) + return int(cumulative_gas_used) + + def block_gas_used(self) -> int: + """ + Return the block-header gas used. + + Under EIP-8037 this is the maximum across the independent gas + dimensions (regular vs state), i.e. the value that counts against the + block gas limit, as opposed to ``cumulative_gas_used`` which is their + combined sum. + """ + return int(self.result.gas_used) + def get_fixture_block( self, *, include_receipts: bool = True ) -> FixtureBlock | InvalidFixtureBlock: @@ -1110,6 +1130,7 @@ def make_fixture( head = genesis.header.block_hash invalid_blocks = 0 benchmark_gas_used: int | None = None + benchmark_block_gas_used: int | None = None benchmark_opcode_count: OpcodeCount | None = None for block in self.blocks: # This is the most common case, the RLP needs to be constructed @@ -1124,7 +1145,8 @@ def make_fixture( block_number = int(built_block.header.number) is_last_block = block is self.blocks[-1] if is_last_block and self.operation_mode == OpMode.BENCHMARKING: - benchmark_gas_used = int(built_block.result.gas_used) + benchmark_gas_used = built_block.cumulative_gas_used() + benchmark_block_gas_used = built_block.block_gas_used() benchmark_opcode_count = built_block.result.opcode_count if built_block.result.receipts: self.validate_receipt_status( @@ -1190,6 +1212,7 @@ def make_fixture( fixture=fixture, gas_optimization=None, benchmark_gas_used=benchmark_gas_used, + benchmark_block_gas_used=benchmark_block_gas_used, benchmark_opcode_count=benchmark_opcode_count, post_verifications=PostVerifications.from_alloc(self.post), ) @@ -1212,6 +1235,7 @@ def make_hive_fixture( head_hash = genesis.header.block_hash invalid_blocks = 0 benchmark_gas_used: int | None = None + benchmark_block_gas_used: int | None = None benchmark_opcode_count: OpcodeCount | None = None for block in self.blocks: built_block = self.generate_block_data( @@ -1223,7 +1247,8 @@ def make_hive_fixture( block_number = int(built_block.header.number) is_last_block = block is self.blocks[-1] if is_last_block and self.operation_mode == OpMode.BENCHMARKING: - benchmark_gas_used = int(built_block.result.gas_used) + benchmark_gas_used = built_block.cumulative_gas_used() + benchmark_block_gas_used = built_block.block_gas_used() benchmark_opcode_count = built_block.result.opcode_count if built_block.result.receipts: self.validate_receipt_status( @@ -1336,6 +1361,7 @@ def make_hive_fixture( fixture=fixture, gas_optimization=None, benchmark_gas_used=benchmark_gas_used, + benchmark_block_gas_used=benchmark_block_gas_used, benchmark_opcode_count=benchmark_opcode_count, post_verifications=PostVerifications.from_alloc(self.post), ) @@ -1471,6 +1497,7 @@ def make_stateful_fixture( execution_opcode_counts: List[Dict[str, int] | None] = [] head_hash = start_block_hash benchmark_gas_used: int | None = None + benchmark_block_gas_used: int | None = None benchmark_opcode_count: OpcodeCount | None = None # Alloc is not authoritative in stateful mode; pass self.pre as a # placeholder — ClientBackend ignores it. @@ -1515,7 +1542,8 @@ def make_stateful_fixture( block_number=int(built_block.header.number), ) if self.operation_mode == OpMode.BENCHMARKING: - benchmark_gas_used = int(built_block.result.gas_used) + benchmark_gas_used = built_block.cumulative_gas_used() + benchmark_block_gas_used = built_block.block_gas_used() # Consumed by BenchmarkTest's opcode-count verification. benchmark_opcode_count = block_opcode_count # apply_new_parent records the RLP hash; the next block's @@ -1552,6 +1580,7 @@ def make_stateful_fixture( fixture=fixture, gas_optimization=None, benchmark_gas_used=benchmark_gas_used, + benchmark_block_gas_used=benchmark_block_gas_used, benchmark_opcode_count=benchmark_opcode_count, metadata=metadata, post_verifications=PostVerifications.from_alloc(self.post), diff --git a/packages/testing/src/execution_testing/tools/__init__.py b/packages/testing/src/execution_testing/tools/__init__.py index 0164e2fcccf..02d98442882 100644 --- a/packages/testing/src/execution_testing/tools/__init__.py +++ b/packages/testing/src/execution_testing/tools/__init__.py @@ -16,6 +16,7 @@ SequentialAddressLayout, Switch, TransactionWithCost, + TxOutcome, While, WhileGas, ) @@ -43,6 +44,7 @@ "SequentialAddressLayout", "Switch", "TransactionWithCost", + "TxOutcome", "While", "WhileGas", "extend_with_defaults", diff --git a/packages/testing/src/execution_testing/tools/tests/test_iterating_bytecode.py b/packages/testing/src/execution_testing/tools/tests/test_iterating_bytecode.py index 6e82db00bad..a0d3ac643cd 100644 --- a/packages/testing/src/execution_testing/tools/tests/test_iterating_bytecode.py +++ b/packages/testing/src/execution_testing/tools/tests/test_iterating_bytecode.py @@ -4,10 +4,15 @@ import pytest -from execution_testing.forks import Osaka +from execution_testing.forks import Amsterdam, Osaka from execution_testing.vm import Op -from ..tools_code import FixedIterationsBytecode, IteratingBytecode +from ..tools_code import ( + FixedIterationsBytecode, + IteratingBytecode, + TransactionWithCost, + TxOutcome, +) OSAKA_GAS_COSTS = Osaka.gas_costs() @@ -94,7 +99,7 @@ def test_iterating_bytecode_gas_cost( iterating_bytecode: IteratingBytecode, iterations: int, expected_cost: int ) -> None: """Test the gas cost calculating function of an iterating bytecode.""" - calculated_cost = iterating_bytecode.gas_cost_by_iteration_count( + calculated_cost = iterating_bytecode.regular_gas_cost_by_iteration_count( fork=Osaka, iteration_count=iterations ) assert calculated_cost == expected_cost, ( @@ -133,6 +138,27 @@ def test_iterating_subcall_reserve() -> None: assert reserve == 100 +def test_iterating_subcall_reserve_includes_state_gas() -> None: + """ + The 63/64 reserve covers the subcall's state gas too: once the state + reservoir is exhausted, the child pays its state charges (e.g. the + EIP-8037 per-byte code deposit) from forwarded regular gas. + """ + # Initcode depositing 2 bytes: tiny regular cost, 2 * 1530 state gas. + initcode = Op.RETURN(0, 2, code_deposit_size=2) + bytecode = IteratingBytecode( + iterating=Op.CREATE2(offset=0, size=2, salt=0), + iterating_subcall=initcode, + ) + combined = initcode.regular_cost(fork=Amsterdam) + initcode.state_cost( + fork=Amsterdam + ) + assert initcode.state_cost(fork=Amsterdam) == 2 * 1530 + reserve = bytecode.iterating_subcall_reserve(fork=Amsterdam) + assert reserve == (combined * 64 // 63) - combined + assert reserve > 0, "state-charging subcall must have a reserve" + + def test_with_fixed_iteration_count() -> None: """Test conversion to FixedIterationsBytecode.""" iterating_bytecode = IteratingBytecode( @@ -146,7 +172,7 @@ def test_with_fixed_iteration_count() -> None: assert fixed.iteration_count == 10 assert fixed.gas_cost( Osaka - ) == iterating_bytecode.gas_cost_by_iteration_count( + ) == iterating_bytecode.regular_gas_cost_by_iteration_count( fork=Osaka, iteration_count=10 ) @@ -158,24 +184,26 @@ def test_tx_gas_cost_by_iteration_count() -> None: ) intrinsic_gas_cost_calc = Osaka.transaction_intrinsic_cost_calculator() - tx_gas = bytecode.tx_gas_cost_by_iteration_count( + tx_gas = bytecode.tx_regular_gas_cost_by_iteration_count( fork=Osaka, iteration_count=5, ) expected = ( - bytecode.gas_cost_by_iteration_count(fork=Osaka, iteration_count=5) + bytecode.regular_gas_cost_by_iteration_count( + fork=Osaka, iteration_count=5 + ) + intrinsic_gas_cost_calc() ) assert tx_gas == expected # With calldata - tx_gas = bytecode.tx_gas_cost_by_iteration_count( + tx_gas = bytecode.tx_regular_gas_cost_by_iteration_count( fork=Osaka, iteration_count=5, calldata=b"hello", ) - expected = bytecode.gas_cost_by_iteration_count( + expected = bytecode.regular_gas_cost_by_iteration_count( fork=Osaka, iteration_count=5 ) + intrinsic_gas_cost_calc( calldata=b"hello", return_cost_deducted_prior_execution=True @@ -193,13 +221,15 @@ def test_tx_gas_limit_by_iteration_count() -> None: tx_gas_limit = bytecode.tx_gas_limit_by_iteration_count( fork=Osaka, iteration_count=5, + include_state_gas_reservoir=True, ) - tx_gas_cost = bytecode.tx_gas_cost_by_iteration_count( + tx_gas_cost = bytecode.tx_regular_gas_cost_by_iteration_count( fork=Osaka, iteration_count=5, ) reserve = bytecode.iterating_subcall_reserve(fork=Osaka) + # Osaka has no state-gas reservoir, so the limit is regular + reserve. assert tx_gas_limit == tx_gas_cost + reserve @@ -248,7 +278,7 @@ def test_tx_iterations_by_gas_limit( # Check total gas used is close to target total_gas = sum( bytecode.tx_gas_limit_by_iteration_count( - fork=fork, iteration_count=iters + fork=fork, iteration_count=iters, include_state_gas_reservoir=True ) for iters in result ) @@ -258,7 +288,9 @@ def test_tx_iterations_by_gas_limit( if gas_limit_cap is not None: for iters in result: tx_gas = bytecode.tx_gas_limit_by_iteration_count( - fork=fork, iteration_count=iters + fork=fork, + iteration_count=iters, + include_state_gas_reservoir=True, ) assert tx_gas <= gas_limit_cap @@ -311,7 +343,9 @@ def test_tx_iterations_by_total_iteration_count( if gas_limit_cap is not None: for iters in result: tx_gas = bytecode.tx_gas_limit_by_iteration_count( - fork=Osaka, iteration_count=iters + fork=Osaka, + iteration_count=iters, + include_state_gas_reservoir=True, ) assert tx_gas <= gas_limit_cap @@ -325,8 +359,7 @@ def test_tx_iterations_by_total_iteration_count_raises_on_impossible() -> None: with pytest.raises( ValueError, - match="Single iteration gas cost exceeds gas_limit " - "or compute_gas_limit.", + match="Single iteration gas cost is greater than gas constraints.", ): list( bytecode.tx_iterations_by_total_iteration_count( @@ -334,3 +367,175 @@ def test_tx_iterations_by_total_iteration_count_raises_on_impossible() -> None: total_iterations=10, ) ) + + +class CustomAmsterdam(Amsterdam): + """ + Amsterdam fork with a configurable EIP-7825 transaction gas limit cap. + """ + + tx_gas_limit_cap: int | None = 1_000_000 + + @classmethod + def with_tx_gas_limit_cap(cls, tx_gas_limit_cap: int | None) -> Type[Self]: + """Return a new fork with the given transaction gas limit cap.""" + return type( + cls.__name__, (cls,), {"tx_gas_limit_cap": tx_gas_limit_cap} + ) + + @classmethod + def transaction_gas_limit_cap(cls) -> int | None: + """Return the transaction gas limit cap.""" + return cls.tx_gas_limit_cap + + +def test_tx_gas_limit_includes_state_gas_reservoir() -> None: + """ + Under EIP-8037 ``include_state_gas_reservoir`` adds the per-iteration + state gas to the transaction gas limit; otherwise the limit is the + regular gas plus the 63/64 subcall reserve only. + """ + # SSTORE of a fresh slot from zero charges STORAGE_SET state gas. + bytecode = IteratingBytecode(iterating=Op.SSTORE(0, 1)) + + regular = bytecode.tx_regular_gas_cost_by_iteration_count( + fork=Amsterdam, iteration_count=5 + ) + state = bytecode.state_gas_cost_by_iteration_count( + fork=Amsterdam, iteration_count=5 + ) + reserve = bytecode.iterating_subcall_reserve(fork=Amsterdam) + assert state > 0, "SSTORE-set should charge state gas under EIP-8037" + + without_state = bytecode.tx_gas_limit_by_iteration_count( + fork=Amsterdam, + iteration_count=5, + include_state_gas_reservoir=False, + ) + with_state = bytecode.tx_gas_limit_by_iteration_count( + fork=Amsterdam, + iteration_count=5, + include_state_gas_reservoir=True, + ) + + assert without_state == regular + reserve + assert with_state == regular + reserve + state + + +def test_state_reservoir_lets_tx_gas_exceed_regular_gas_limit_cap() -> None: + """ + Under EIP-8037 the EIP-7825 transaction gas limit cap binds regular gas + only. A state-heavy transaction can therefore pack more iterations than + that cap alone would allow, because its state gas draws from a separate + reservoir and the combined ``tx.gas`` grows past the cap. + """ + cap = 5_000_000 + fork = CustomAmsterdam.with_tx_gas_limit_cap(cap) + bytecode = IteratingBytecode(iterating=Op.SSTORE(0, 1)) + + total_iterations = (cap // Op.SSTORE(0, 1).regular_cost(fork=fork)) - 1 + counts = list( + bytecode.tx_iterations_by_total_iteration_count( + fork=fork, total_iterations=total_iterations + ) + ) + + # Regular gas stays under the cap, so all iterations fit in one tx even + # though their combined (regular + state) gas far exceeds the cap. + assert counts == [total_iterations] + + regular = bytecode.tx_regular_gas_cost_by_iteration_count( + fork=fork, iteration_count=total_iterations + ) + combined = bytecode.tx_gas_limit_by_iteration_count( + fork=fork, + iteration_count=total_iterations, + include_state_gas_reservoir=True, + ) + assert regular <= cap, "regular gas must respect the EIP-7825 cap" + assert combined > cap, ( + "combined tx.gas exceeds the cap via state reservoir" + ) + + +@pytest.mark.parametrize( + "outcome,expected_billed,expected_block", + [ + pytest.param(TxOutcome.SUCCESS, 100_000, 60_000, id="success"), + pytest.param(TxOutcome.REVERT, 60_000, 60_000, id="revert"), + pytest.param(TxOutcome.OUT_OF_GAS, 150_000, 150_000, id="out_of_gas"), + ], +) +def test_transaction_with_cost_billing_by_outcome( + outcome: TxOutcome, expected_billed: int, expected_block: int +) -> None: + """ + Billed gas and block-header contribution follow the expected outcome: + combined regular + state on success, regular only on revert (state gas + is refunded), and the whole gas limit on an exceptional halt. + """ + tx = TransactionWithCost( + gas_limit=150_000, + regular_cost=60_000, + state_cost=40_000, + outcome=outcome, + ) + assert tx.gas_cost == expected_billed + assert tx.block_gas_cost == expected_block + + +def test_tx_iterations_by_gas_limit_outcome_packing() -> None: + """ + The block budget is consumed according to the expected outcome: the + max-dimension gas on success, the regular gas only on revert, and the + whole gas limit (including the subcall reserve, without any state + allowance) on out-of-gas. + """ + budget = 1_000_000 + fork = CustomAmsterdam.with_tx_gas_limit_cap(16_777_216) + # SSTORE of a fresh slot from zero: state gas dominates regular gas. + bytecode = IteratingBytecode( + iterating=Op.SSTORE(0, 1), iterating_subcall=6300 + ) + reserve = bytecode.iterating_subcall_reserve(fork=fork) + assert reserve > 0 + + def regular(iterations: int) -> int: + return bytecode.tx_regular_gas_cost_by_iteration_count( + fork=fork, iteration_count=iterations + ) + + def state(iterations: int) -> int: + return bytecode.state_gas_cost_by_iteration_count( + fork=fork, iteration_count=iterations + ) + + success = list( + bytecode.tx_iterations_by_gas_limit(fork=fork, gas_limit=budget) + ) + revert = list( + bytecode.tx_iterations_by_gas_limit( + fork=fork, gas_limit=budget, outcome=TxOutcome.REVERT + ) + ) + out_of_gas = list( + bytecode.tx_iterations_by_gas_limit( + fork=fork, gas_limit=budget, outcome=TxOutcome.OUT_OF_GAS + ) + ) + + # Success packing is bound by the dominant (state) dimension. + assert sum(max(regular(i), state(i)) for i in success) <= budget + assert state(sum(success) + 1) > budget, ( + "one more iteration should overflow the state dimension" + ) + + # Revert packing bills regular gas only, so far more iterations fit. + assert sum(revert) > sum(success) + assert sum(regular(i) for i in revert) <= budget + + # Out-of-gas packing counts the whole gas limit, reserve included. + assert sum(regular(i) + reserve for i in out_of_gas) <= budget + assert regular(sum(out_of_gas) + 1) + reserve > budget, ( + "one more iteration should overflow the regular budget" + ) diff --git a/packages/testing/src/execution_testing/tools/tools_code/__init__.py b/packages/testing/src/execution_testing/tools/tools_code/__init__.py index 7782729b900..3eb13abd3ee 100644 --- a/packages/testing/src/execution_testing/tools/tools_code/__init__.py +++ b/packages/testing/src/execution_testing/tools/tools_code/__init__.py @@ -13,6 +13,7 @@ SequentialAddressLayout, Switch, TransactionWithCost, + TxOutcome, While, WhileGas, ) @@ -32,6 +33,7 @@ "Solc", "Switch", "TransactionWithCost", + "TxOutcome", "While", "WhileGas", "Yul", diff --git a/packages/testing/src/execution_testing/tools/tools_code/generators.py b/packages/testing/src/execution_testing/tools/tools_code/generators.py index ae9026c7fa3..75ef1a354b0 100644 --- a/packages/testing/src/execution_testing/tools/tools_code/generators.py +++ b/packages/testing/src/execution_testing/tools/tools_code/generators.py @@ -1,6 +1,7 @@ """Code generating classes and functions.""" from dataclasses import dataclass +from enum import Enum, auto from typing import Any, Dict, Generator, List, Self, SupportsBytes, Tuple, Type from pydantic import Field @@ -767,10 +768,94 @@ def increment_address_op(self, increment: int | None = None) -> Bytecode: ) +class TxOutcome(Enum): + """ + Expected outcome of a generated transaction. + + Under EIP-8037 the outcome decides how gas is billed: on success the + sender pays regular plus state gas, on revert the runtime state gas is + rolled back into the reservoir and refunded, and on an exceptional halt + the whole declared gas limit burns in the regular dimension. + """ + + SUCCESS = auto() + REVERT = auto() + OUT_OF_GAS = auto() + + class TransactionWithCost(Transaction): """Transaction object that can include the expected gas to be consumed.""" - gas_cost: int = Field(..., exclude=True) + regular_cost: int = Field(..., exclude=True) + state_cost: int = Field(..., exclude=True) + outcome: TxOutcome = Field(TxOutcome.SUCCESS, exclude=True) + + @property + def gas_cost(self) -> int: + """ + Gas billed to the sender, i.e. the value the receipt's + `cumulativeGasUsed` reflects. Use for + `expected_benchmark_gas_used`. + + On success this is the combined regular + state gas. On revert only + the regular gas is billed (runtime state gas is refunded; intrinsic + state gas, e.g. for authorizations, is not modeled here). On an + exceptional halt the whole gas limit burns: the generators size + out-of-gas transactions below the EIP-7825 cap, where the state + reservoir is empty. + """ + match self.outcome: + case TxOutcome.REVERT: + return self.regular_cost + case TxOutcome.OUT_OF_GAS: + return int(self.gas_limit) + case _: + return self.regular_cost + self.state_cost + + @property + def block_gas_cost(self) -> int: + """ + Return the gas this transaction contributes to the block-header gas. + + The block-header gas is the maximum across the independent gas + dimensions (EIP-8037: `max(regular, state)`), not their sum, so this + is the right per-transaction quantity for block-fitting decisions + (e.g. how many transactions fit under a gas target). On revert only + the regular gas lands; on an exceptional halt the whole gas limit + lands in the regular dimension. + + Summing this over a block is exact only when a single dimension + dominates every transaction uniformly (the common benchmark shape); + for a mixed block the exact occupancy is + `max(sum(regular_cost), sum(state_cost))`. + """ + match self.outcome: + case TxOutcome.REVERT: + return self.regular_cost + case TxOutcome.OUT_OF_GAS: + return int(self.gas_limit) + case _: + return max(self.regular_cost, self.state_cost) + + +@dataclass(kw_only=True, slots=True) +class GasCaps: + """ + Small helper class to represent multidimensional gas caps. + """ + + regular: int + state: int | None + gas_limit: int | None + + +TOP_FRAME_COST_KWARGS = ("contract_creation", "sends_value", "recipient_type") +""" +Keyword arguments that describe the transaction for gas-cost calculation but +are not ``Transaction`` fields. They feed the intrinsic and top-frame gas +calculators (e.g. ``recipient_type=RecipientType.DELEGATION_7702``) and must +be stripped before constructing the ``Transaction``. +""" class IteratingBytecode(Bytecode): @@ -806,10 +891,6 @@ class IteratingBytecode(Bytecode): """ cleanup: Bytecode """Bytecode executed once at the end after all iterations complete.""" - iterating_state_gas: int - """ - State-gas portion (EIP-8037) charged per loop iteration. - """ def __new__( cls, @@ -819,7 +900,6 @@ def __new__( cleanup: Bytecode | None = None, warm_iterating: Bytecode | None = None, iterating_subcall: Bytecode | int | None = None, - iterating_state_gas: int = 0, ) -> Self: """ Create a new iterating bytecode. @@ -838,8 +918,6 @@ def __new__( calculation. The value can also be an integer, in which case it represents the gas cost of the subcall (e.g. the subcall is a precompiled contract). - iterating_state_gas: EIP-8037 state-gas portion charged - per iteration, defaults to 0. Returns: A new IteratingBytecode instance. @@ -867,7 +945,6 @@ def __new__( if cleanup is None: cleanup = Bytecode() instance.cleanup = cleanup - instance.iterating_state_gas = iterating_state_gas return instance def iterating_subcall_gas_cost( @@ -876,7 +953,15 @@ def iterating_subcall_gas_cost( """Return the gas cost of the iterating subcall.""" if isinstance(self.iterating_subcall, int): return self.iterating_subcall - return self.iterating_subcall.gas_cost(fork=fork) + return self.iterating_subcall.regular_cost(fork=fork) + + def iterating_subcall_state_gas_cost( + self, *, fork: Type[ForkOpcodeInterface] + ) -> int: + """Return the gas cost of the iterating subcall.""" + if isinstance(self.iterating_subcall, int): + return 0 + return self.iterating_subcall.state_cost(fork=fork) def iterating_subcall_reserve( self, *, fork: Type[ForkOpcodeInterface] @@ -884,22 +969,27 @@ def iterating_subcall_reserve( """ Return the gas reserve needed so that the last iterating subcall does not fail due to the 63/64 rule. + + Last iteration also contains state gas in case the reservoir is not + active. """ - iterating_subcall_gas_cost = self.iterating_subcall_gas_cost(fork=fork) + iterating_subcall_gas_cost = self.iterating_subcall_gas_cost( + fork=fork + ) + self.iterating_subcall_state_gas_cost(fork=fork) return ( iterating_subcall_gas_cost * 64 // 63 ) - iterating_subcall_gas_cost - def gas_cost_by_iteration_count( + def regular_gas_cost_by_iteration_count( self, *, fork: Type[ForkOpcodeInterface], iteration_count: int ) -> int: """Return the cost of iterating through the bytecode N times.""" loop_gas_cost = 0 if iteration_count > 0: # Cold cost is just charged for the first iteration - loop_gas_cost = self.iterating.gas_cost(fork=fork) + loop_gas_cost = self.iterating.regular_cost(fork=fork) # Warm cost is charged for all iterations except the first - loop_gas_cost += self.warm_iterating.gas_cost(fork=fork) * ( + loop_gas_cost += self.warm_iterating.regular_cost(fork=fork) * ( iteration_count - 1 ) # Subcall cost is charged for all iterations. @@ -907,9 +997,32 @@ def gas_cost_by_iteration_count( self.iterating_subcall_gas_cost(fork=fork) * iteration_count ) return ( - self.setup.gas_cost(fork=fork) + self.setup.regular_cost(fork=fork) + loop_gas_cost - + self.cleanup.gas_cost(fork=fork) + + self.cleanup.regular_cost(fork=fork) + ) + + def state_gas_cost_by_iteration_count( + self, *, fork: Type[ForkOpcodeInterface], iteration_count: int + ) -> int: + """Return the cost of iterating through the bytecode N times.""" + loop_gas_cost = 0 + if iteration_count > 0: + # Cold cost is just charged for the first iteration + loop_gas_cost = self.iterating.state_cost(fork=fork) + # Warm cost is charged for all iterations except the first + loop_gas_cost += self.warm_iterating.state_cost(fork=fork) * ( + iteration_count - 1 + ) + # Subcall cost is charged for all iterations. + loop_gas_cost += ( + self.iterating_subcall_state_gas_cost(fork=fork) + * iteration_count + ) + return ( + self.setup.state_cost(fork=fork) + + loop_gas_cost + + self.cleanup.state_cost(fork=fork) ) def with_fixed_iteration_count( @@ -930,7 +1043,7 @@ def with_fixed_iteration_count( # Methods to calculate transactions that call a contract containing the # iterating bytecode. - def tx_gas_cost_by_iteration_count( + def tx_regular_gas_cost_by_iteration_count( self, *, fork: Fork, @@ -967,9 +1080,20 @@ def tx_gas_cost_by_iteration_count( iteration_count=iteration_count, start_iteration=start_iteration, ) - return self.gas_cost_by_iteration_count( - fork=fork, iteration_count=iteration_count - ) + intrinsic_gas_cost_calc(**intrinsic_cost_kwargs) + top_frame_gas = fork.transaction_top_frame_gas_calculator()( + **{ + key: intrinsic_cost_kwargs[key] + for key in TOP_FRAME_COST_KWARGS + if key in intrinsic_cost_kwargs + } + ) + return ( + self.regular_gas_cost_by_iteration_count( + fork=fork, iteration_count=iteration_count + ) + + intrinsic_gas_cost_calc(**intrinsic_cost_kwargs) + + top_frame_gas + ) def tx_gas_limit_by_iteration_count( self, @@ -977,6 +1101,7 @@ def tx_gas_limit_by_iteration_count( fork: Fork, iteration_count: int, start_iteration: int = 0, + include_state_gas_reservoir: bool, **intrinsic_cost_kwargs: Any, ) -> int: """ @@ -986,83 +1111,87 @@ def tx_gas_limit_by_iteration_count( The gas limit is calculated by adding the required extra gas for the last iteration due to the 63/64 rule. """ - return self.tx_gas_cost_by_iteration_count( + tx_gas_limit = self.tx_regular_gas_cost_by_iteration_count( fork=fork, iteration_count=iteration_count, start_iteration=start_iteration, **intrinsic_cost_kwargs, - ) + self.iterating_subcall_reserve(fork=fork) + ) + tx_gas_limit += self.iterating_subcall_reserve(fork=fork) + if include_state_gas_reservoir: + tx_gas_limit += self.state_gas_cost_by_iteration_count( + fork=fork, iteration_count=iteration_count + ) + return tx_gas_limit - def _iterations_fit_within_gas_limits( + def _iteration_count_exceeds_caps( self, - *, fork: Fork, iteration_count: int, + caps: GasCaps, start_iteration: int, - gas_limit: int, - compute_gas_limit: int | None = None, **intrinsic_cost_kwargs: Any, ) -> bool: """ - Check whether iteration_count iterations fit within the gas limits. - - Returns True when both: - - The combined regular+state gas (i.e. tx.gas) is <= - gas_limit (block-budget constraint). - - The regular gas, computed as - combined - iteration_count * iterating_state_gas, - respects the compute_gas_limit. + Evaluate whether the iteration count exceeds any of the constraints. """ - if iteration_count <= 0: - return True - combined = self.tx_gas_limit_by_iteration_count( + tx_regular_gas_cost = self.tx_regular_gas_cost_by_iteration_count( fork=fork, iteration_count=iteration_count, start_iteration=start_iteration, **intrinsic_cost_kwargs, ) - if combined > gas_limit: - return False - if compute_gas_limit is not None: - compute = combined - iteration_count * self.iterating_state_gas - if compute > compute_gas_limit: - return False - return True + + if tx_regular_gas_cost > caps.regular: + return True + + if caps.gas_limit is not None and ( + self.iterating_subcall_reserve(fork=fork) + tx_regular_gas_cost + > caps.gas_limit + ): + return True + + if caps.state is not None and ( + self.state_gas_cost_by_iteration_count( + fork=fork, iteration_count=iteration_count + ) + > caps.state + ): + return True + return False def _binary_search_iterations( self, *, fork: Fork, - gas_limit: int, + caps: GasCaps, start_iteration: int, - compute_gas_limit: int | None = None, **intrinsic_cost_kwargs: Any, - ) -> Tuple[int, int]: + ) -> Tuple[int, int, int]: """ - Binary search for the maximum iterations that fit within a gas limit. + Binary search for the maximum iterations that fit within the regular + gas, state gas and gas limit cap constraints. """ - fits_kwargs: Dict[str, Any] = { - "fork": fork, - "start_iteration": start_iteration, - "gas_limit": gas_limit, - "compute_gas_limit": compute_gas_limit, + if self._iteration_count_exceeds_caps( + fork=fork, + iteration_count=1, + caps=caps, + start_iteration=start_iteration, **intrinsic_cost_kwargs, - } - - if not self._iterations_fit_within_gas_limits( - iteration_count=1, **fits_kwargs ): raise ValueError( - "Single iteration gas cost exceeds gas_limit " - "or compute_gas_limit." + "Single iteration gas cost is greater than gas constraints." ) - low = 1 high = 2 # Exponential search to find upper bound - while self._iterations_fit_within_gas_limits( - iteration_count=high, **fits_kwargs + while not self._iteration_count_exceeds_caps( + fork=fork, + iteration_count=high, + caps=caps, + start_iteration=start_iteration, + **intrinsic_cost_kwargs, ): low = high high *= 2 @@ -1070,21 +1199,35 @@ def _binary_search_iterations( # Binary search for exact fit while low < high: mid = (low + high) // 2 - if not self._iterations_fit_within_gas_limits( - iteration_count=mid, **fits_kwargs + + if self._iteration_count_exceeds_caps( + fork=fork, + iteration_count=mid, + caps=caps, + start_iteration=start_iteration, + **intrinsic_cost_kwargs, ): high = mid else: low = mid + 1 best_iterations = low - 1 - best_iterations_gas = self.tx_gas_limit_by_iteration_count( - fork=fork, - iteration_count=best_iterations, - start_iteration=start_iteration, - **intrinsic_cost_kwargs, + best_iterations_regular_gas = ( + self.tx_regular_gas_cost_by_iteration_count( + fork=fork, + iteration_count=best_iterations, + start_iteration=start_iteration, + **intrinsic_cost_kwargs, + ) + ) + best_iterations_state_gas = self.state_gas_cost_by_iteration_count( + fork=fork, iteration_count=best_iterations + ) + return ( + best_iterations, + best_iterations_regular_gas, + best_iterations_state_gas, ) - return best_iterations, best_iterations_gas def tx_iterations_by_gas_limit( self, @@ -1092,6 +1235,7 @@ def tx_iterations_by_gas_limit( fork: Fork, gas_limit: int, start_iteration: int = 0, + outcome: TxOutcome = TxOutcome.SUCCESS, **intrinsic_cost_kwargs: Any, ) -> Generator[int, None, None]: """ @@ -1105,31 +1249,69 @@ def tx_iterations_by_gas_limit( list will contain one item per transaction that represents the iteration count for that transaction, and no transaction will exceed the gas limit cap. + + The gas each transaction counts against the budget follows its + expected outcome (see `TransactionWithCost.block_gas_cost`): the + max-dimension gas on success, the regular gas only on revert (state + gas is refunded), and the whole gas limit including the subcall + reserve on out-of-gas. """ gas_limit_cap = fork.transaction_gas_limit_cap() remaining_gas = gas_limit + # An out-of-gas transaction burns its whole gas limit, including + # the 63/64 subcall reserve, so the reserve counts against the + # budget too. + reserve = ( + self.iterating_subcall_reserve(fork=fork) + if outcome is TxOutcome.OUT_OF_GAS + else 0 + ) - while remaining_gas >= self.tx_gas_limit_by_iteration_count( + def current_caps() -> GasCaps: + return GasCaps( + regular=remaining_gas - reserve, + # State gas only counts against the block budget when the + # transaction succeeds; on revert or halt it is refunded. + state=( + remaining_gas if outcome is TxOutcome.SUCCESS else None + ), + gas_limit=gas_limit_cap, + ) + + while not self._iteration_count_exceeds_caps( fork=fork, iteration_count=1, + caps=current_caps(), start_iteration=start_iteration, **intrinsic_cost_kwargs, ): - best_iterations, best_iterations_gas = ( - self._binary_search_iterations( - fork=fork, - gas_limit=remaining_gas, - compute_gas_limit=gas_limit_cap, - start_iteration=start_iteration, - **intrinsic_cost_kwargs, - ) + # Binary search for the maximum number of iterations that fits + # within remaining_gas + ( + best_iterations, + best_iterations_regular_gas, + best_iterations_state_gas, + ) = self._binary_search_iterations( + fork=fork, + caps=current_caps(), + start_iteration=start_iteration, + **intrinsic_cost_kwargs, ) yield best_iterations - remaining_gas -= best_iterations_gas + match outcome: + case TxOutcome.REVERT: + remaining_gas -= best_iterations_regular_gas + case TxOutcome.OUT_OF_GAS: + remaining_gas -= best_iterations_regular_gas + reserve + case _: + remaining_gas -= max( + best_iterations_regular_gas, + best_iterations_state_gas, + ) start_iteration += best_iterations + @staticmethod def _intrinsic_cost_is_constant( - self, intrinsic_cost_kwargs: Dict[str, Any], ) -> bool: """If none of the kwarg values is callable, return True.""" @@ -1166,10 +1348,13 @@ def tx_iterations_by_total_iteration_count( while remaining_iterations > 0: if best_iterations is None or not constant_intrinsic_gas_cost: - best_iterations, _ = self._binary_search_iterations( + best_iterations, _, _ = self._binary_search_iterations( fork=fork, - gas_limit=gas_limit_cap, - compute_gas_limit=gas_limit_cap, + caps=GasCaps( + regular=gas_limit_cap, + state=None, + gas_limit=gas_limit_cap, + ), start_iteration=start_iteration, **intrinsic_cost_kwargs, ) @@ -1193,6 +1378,7 @@ def transactions_by_gas_limit( sender: EOA, to: Address | None, tx_gas_limit_delta: int = 0, + outcome: TxOutcome = TxOutcome.SUCCESS, **tx_kwargs: Any, ) -> Generator[TransactionWithCost, None, None]: """ @@ -1209,7 +1395,13 @@ def transactions_by_gas_limit( dynamically by passing a callable to the calldata keyword argument. The returned object also contains an extra field with the expected - gas cost of the transaction by the end of execution. + gas cost of the transaction by the end of execution, billed + according to `outcome`. + + Out-of-gas transactions are sized without the state gas allowance, + so the whole gas limit burns as regular gas and the billed amount is + exact; the caller must still make the bytecode inexhaustible (e.g. + with a negative `tx_gas_limit_delta` or a loop with no exit). """ intrinsic_cost_kwargs = tx_kwargs.copy() @@ -1217,24 +1409,33 @@ def transactions_by_gas_limit( tx_kwargs["data"] = tx_kwargs.pop("calldata") if "return_cost_deducted_prior_execution" in tx_kwargs: tx_kwargs.pop("return_cost_deducted_prior_execution") + for cost_only_key in TOP_FRAME_COST_KWARGS: + tx_kwargs.pop(cost_only_key, None) for iteration_count in self.tx_iterations_by_gas_limit( fork=fork, gas_limit=gas_limit, start_iteration=start_iteration, + outcome=outcome, **intrinsic_cost_kwargs, ): tx_gas_limit = self.tx_gas_limit_by_iteration_count( fork=fork, iteration_count=iteration_count, start_iteration=start_iteration, + include_state_gas_reservoir=( + outcome is not TxOutcome.OUT_OF_GAS + ), **intrinsic_cost_kwargs, ) - tx_gas_cost = self.tx_gas_cost_by_iteration_count( + tx_regular_cost = self.tx_regular_gas_cost_by_iteration_count( fork=fork, iteration_count=iteration_count, start_iteration=start_iteration, **intrinsic_cost_kwargs, ) + tx_state_cost = self.state_gas_cost_by_iteration_count( + fork=fork, iteration_count=iteration_count + ) current_tx_kwargs = tx_kwargs.copy() for key, value in current_tx_kwargs.items(): @@ -1247,7 +1448,9 @@ def transactions_by_gas_limit( to=to, gas_limit=tx_gas_limit + tx_gas_limit_delta, sender=sender, - gas_cost=tx_gas_cost, + regular_cost=tx_regular_cost, + state_cost=tx_state_cost, + outcome=outcome, **current_tx_kwargs, ) start_iteration += iteration_count @@ -1285,6 +1488,8 @@ def transactions_by_total_iteration_count( tx_kwargs["data"] = tx_kwargs.pop("calldata") if "return_cost_deducted_prior_execution" in tx_kwargs: tx_kwargs.pop("return_cost_deducted_prior_execution") + for cost_only_key in TOP_FRAME_COST_KWARGS: + tx_kwargs.pop(cost_only_key, None) for iteration_count in self.tx_iterations_by_total_iteration_count( fork=fork, total_iterations=total_iterations, @@ -1295,14 +1500,18 @@ def transactions_by_total_iteration_count( fork=fork, iteration_count=iteration_count, start_iteration=start_iteration, + include_state_gas_reservoir=True, **intrinsic_cost_kwargs, ) - tx_gas_cost = self.tx_gas_cost_by_iteration_count( + tx_regular_cost = self.tx_regular_gas_cost_by_iteration_count( fork=fork, iteration_count=iteration_count, start_iteration=start_iteration, **intrinsic_cost_kwargs, ) + tx_state_cost = self.state_gas_cost_by_iteration_count( + fork=fork, iteration_count=iteration_count + ) current_tx_kwargs = tx_kwargs.copy() for key, value in current_tx_kwargs.items(): @@ -1315,7 +1524,8 @@ def transactions_by_total_iteration_count( to=to, gas_limit=tx_gas_limit + tx_gas_limit_delta, sender=sender, - gas_cost=tx_gas_cost, + regular_cost=tx_regular_cost, + state_cost=tx_state_cost, **current_tx_kwargs, ) start_iteration += iteration_count @@ -1381,7 +1591,10 @@ def __new__( def gas_cost(self, fork: Type[ForkOpcodeInterface]) -> int: """Return the cost of iterating through the bytecode N times.""" - return self.gas_cost_by_iteration_count( + return self.regular_gas_cost_by_iteration_count( + fork=fork, + iteration_count=self.iteration_count, + ) + self.state_gas_cost_by_iteration_count( fork=fork, iteration_count=self.iteration_count, ) diff --git a/tests/benchmark/compute/eip7928_block_level_access_lists/test_block_access_list.py b/tests/benchmark/compute/eip7928_block_level_access_lists/test_block_access_list.py index 7c5c5e0c41f..42953d01339 100644 --- a/tests/benchmark/compute/eip7928_block_level_access_lists/test_block_access_list.py +++ b/tests/benchmark/compute/eip7928_block_level_access_lists/test_block_access_list.py @@ -16,6 +16,7 @@ import pytest from execution_testing import ( + Account, Alloc, BenchmarkTestFiller, Block, @@ -648,11 +649,18 @@ def test_deploy_then_interact( new_value=1, ) initcode_exec_gas = initcode_sstore.gas_cost(fork) - code_deposit_gas = 200 * len(runtime_code) + code_deposit_gas = fork.code_deposit_state_gas(code_size=len(runtime_code)) + top_frame_state_gas = fork.transaction_top_frame_state_gas( + contract_creation=True + ) # Buffer for Initcode wrapper overhead (CODECOPY + RETURN + memory). deploy_gas_limit = ( - intrinsic_gas_create + initcode_exec_gas + code_deposit_gas + 10000 + intrinsic_gas_create + + initcode_exec_gas + + code_deposit_gas + + top_frame_state_gas + + 10000 ) min_call_gas = intrinsic_gas + setup_gas + reserve_gas @@ -693,6 +701,7 @@ def test_deploy_then_interact( num_pairs = 1 blocks: list[Block] = [] + post = {} with TestPhaseManager.execution(): exec_txs: list[Transaction] = [] @@ -709,6 +718,7 @@ def test_deploy_then_interact( ) ) contract = compute_create_address(address=deployer, nonce=0) + post[contract] = Account(nonce=1, code=runtime_code) exec_txs.append( Transaction( to=contract, @@ -728,6 +738,7 @@ def test_deploy_then_interact( ) ) contract = compute_create_address(address=deployer, nonce=0) + post[contract] = Account(nonce=1, code=runtime_code) for _ in range(num_call_txs): exec_txs.append( Transaction( @@ -739,7 +750,7 @@ def test_deploy_then_interact( blocks.append(Block(txs=exec_txs)) - benchmark_test(blocks=blocks, skip_gas_used_validation=True) + benchmark_test(blocks=blocks, post=post, skip_gas_used_validation=True) @pytest.mark.parametrize( diff --git a/tests/benchmark/compute/helpers.py b/tests/benchmark/compute/helpers.py index a05203445a9..ec758afe32d 100644 --- a/tests/benchmark/compute/helpers.py +++ b/tests/benchmark/compute/helpers.py @@ -18,6 +18,7 @@ Op, OpcodeTarget, TransactionWithCost, + TxOutcome, While, compute_create2_address, compute_deterministic_create2_address, @@ -69,12 +70,8 @@ class StorageAction: WRITE_NEW_VALUE = auto() -class TransactionResult: - """Enum for the possible transaction outcomes.""" - - SUCCESS = auto() - OUT_OF_GAS = auto() - REVERT = auto() +TransactionResult = TxOutcome +"""Alias for the framework outcome enum used to bill transaction gas.""" class ReturnDataStyle(Enum): @@ -474,8 +471,10 @@ def transactions_by_total_contract_count( ) -> Generator[ContractDeploymentTransaction, None, None]: """ Create a list of transactions calling the factory to create the - given number of contracts, each capped tx properly capped by the - gas limit cap of the fork. + given number of contracts, each transaction capped by the fork's + regular-gas limit cap (EIP-7825). Under EIP-8037 the per-byte code + deposit is state gas drawn from a separate reservoir, so the split + bounds regular gas only and lets the combined gas exceed the cap. """ to = self.address() @@ -491,7 +490,8 @@ def calldata(iteration_count: int, start_iteration: int) -> bytes: start_iteration: int = contract_start_index tx_gas_limit: int | None = None - tx_gas_cost: int | None = None + tx_regular_cost: int | None = None + tx_state_cost: int | None = None last_iteration_count: int = 0 for iteration_count in self.tx_iterations_by_total_iteration_count( @@ -502,21 +502,27 @@ def calldata(iteration_count: int, start_iteration: int) -> bytes: ): if ( tx_gas_limit is None - or tx_gas_cost is None + or tx_regular_cost is None + or tx_state_cost is None or iteration_count != last_iteration_count ): tx_gas_limit = self.tx_gas_limit_by_iteration_count( fork=fork, iteration_count=iteration_count, start_iteration=start_iteration, + include_state_gas_reservoir=True, calldata=calldata_max, ) - tx_gas_cost = self.tx_gas_cost_by_iteration_count( + tx_regular_cost = self.tx_regular_gas_cost_by_iteration_count( fork=fork, iteration_count=iteration_count, start_iteration=start_iteration, calldata=calldata_max, ) + tx_state_cost = self.state_gas_cost_by_iteration_count( + fork=fork, + iteration_count=iteration_count, + ) deployed_contracts = [ self.created_contract_address( salt=i, @@ -529,7 +535,8 @@ def calldata(iteration_count: int, start_iteration: int) -> bytes: to=to, gas_limit=tx_gas_limit, sender=sender, - gas_cost=tx_gas_cost, + regular_cost=tx_regular_cost, + state_cost=tx_state_cost, data=calldata(iteration_count, start_iteration), deployed_contracts=deployed_contracts, ) diff --git a/tests/benchmark/compute/instruction/test_arithmetic.py b/tests/benchmark/compute/instruction/test_arithmetic.py index 937c3618215..bb8a08ac263 100644 --- a/tests/benchmark/compute/instruction/test_arithmetic.py +++ b/tests/benchmark/compute/instruction/test_arithmetic.py @@ -97,18 +97,6 @@ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFCD, ), ), - pytest.param( - # Not suitable for MOD, as values quickly become zero. - Op.MOD, - DEFAULT_BINOP_ARGS, - marks=pytest.mark.repricing, - ), - pytest.param( - # Not suitable for SMOD, as values quickly become zero. - Op.SMOD, - DEFAULT_BINOP_ARGS, - marks=pytest.mark.repricing, - ), pytest.param( # This keeps the values unchanged # pow(2**256-1, 2**256-1, 2**256) == 2**256-1. @@ -128,24 +116,6 @@ ), marks=pytest.mark.repricing, ), - pytest.param( - Op.ADDMOD, - ( - 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F, - 0x73EDA753299D7D483339D80809A1D80553BDA402FFFE5BFEFFFFFFFF00000001, - 0x100000000000000000000000000000033, - ), - marks=pytest.mark.repricing, - ), - pytest.param( - Op.MULMOD, - ( - 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F, - 0x73EDA753299D7D483339D80809A1D80553BDA402FFFE5BFEFFFFFFFF00000001, - 0x100000000000000000000000000000033, - ), - marks=pytest.mark.repricing, - ), ], ids=lambda param: "" if isinstance(param, tuple) else param, ) @@ -405,15 +375,13 @@ def test_mod_arithmetic( ) + Op.POP ) - # Construct the final code. Because of the usage of PUSH32 the code segment - # is very long, so don't try to include multiple of these. - code = ( - code_constant_pool - + Op.JUMPDEST - + code_segment - + Op.JUMP(len(code_constant_pool)) - ) - assert (max_code_size - len(code_segment)) < len(code) <= max_code_size + + code_prefix = code_constant_pool + Op.JUMPDEST + code_suffix = Op.JUMP(len(code_constant_pool)) + overhead = len(code_prefix) + len(code_suffix) + num_segments = (max_code_size - overhead) // len(code_segment) + code = code_prefix + code_segment * num_segments + code_suffix + assert len(code) <= max_code_size tx = Transaction( to=pre.deploy_contract(code=code), diff --git a/tests/benchmark/compute/instruction/test_memory.py b/tests/benchmark/compute/instruction/test_memory.py index 9a245684168..561e85e88f3 100644 --- a/tests/benchmark/compute/instruction/test_memory.py +++ b/tests/benchmark/compute/instruction/test_memory.py @@ -11,28 +11,50 @@ import pytest from execution_testing import ( + BenchmarkCodeGenerator, BenchmarkTestFiller, Bytecode, ExtCallGenerator, + Fork, JumpLoopGenerator, Op, ) @pytest.mark.repricing(mem_size=1) +# MSIZE should be O(1), but sweep mem_size so a size-dependent +# implementation shows up as a regression. ExtCallGenerator re-expands +# memory in every call frame, so once the expansion outweighs a frame's +# MSIZE work (~16 KiB), loop in a single frame instead, paying one POP +# per MSIZE but expanding only once. @pytest.mark.parametrize("mem_size", [0, 1, 1_000, 100_000, 1_000_000]) def test_msize( benchmark_test: BenchmarkTestFiller, + fork: Fork, mem_size: int, ) -> None: """Benchmark MSIZE instruction.""" - benchmark_test( - target_opcode=Op.MSIZE, - code_generator=ExtCallGenerator( - setup=Op.POP(Op.MLOAD(Op.SELFBALANCE)), + setup = Op.POP(Op.MLOAD(Op.SELFBALANCE)) + expansion_gas = fork.memory_expansion_gas_calculator()(new_bytes=mem_size) + frame_msize_gas = fork.max_stack_height() * fork.gas_costs().BASE + + code_generator: BenchmarkCodeGenerator + if expansion_gas <= frame_msize_gas: + code_generator = ExtCallGenerator( + setup=setup, attack_block=Op.MSIZE, contract_balance=mem_size, - ), + ) + else: + code_generator = JumpLoopGenerator( + setup=setup, + attack_block=Op.POP(Op.MSIZE), + contract_balance=mem_size, + ) + + benchmark_test( + target_opcode=Op.MSIZE, + code_generator=code_generator, ) diff --git a/tests/benchmark/compute/instruction/test_storage.py b/tests/benchmark/compute/instruction/test_storage.py index 98f7dfd89eb..bad640402e6 100644 --- a/tests/benchmark/compute/instruction/test_storage.py +++ b/tests/benchmark/compute/instruction/test_storage.py @@ -23,6 +23,7 @@ IteratingBytecode, JumpLoopGenerator, Op, + RecipientType, TestPhaseManager, Transaction, While, @@ -288,6 +289,8 @@ def calldata_generator( fork=fork, gas_limit=gas_benchmark_value, calldata=calldata_generator, + recipient_type=RecipientType.DELEGATION_7702, + outcome=tx_result, ) ) @@ -323,6 +326,7 @@ def calldata_generator( to=authority, start_iteration=1, calldata=calldata_generator, + recipient_type=RecipientType.DELEGATION_7702, ) ) @@ -347,6 +351,7 @@ def calldata_generator( expected_gas_used = 0 with TestPhaseManager.execution(): + # One gas short so the out-of-gas variants cannot terminate cleanly. tx_gas_limit_delta = ( -1 if tx_result == TransactionResult.OUT_OF_GAS else 0 ) @@ -358,14 +363,13 @@ def calldata_generator( to=authority, calldata=calldata_generator, start_iteration=1, + recipient_type=RecipientType.DELEGATION_7702, tx_gas_limit_delta=tx_gas_limit_delta, + outcome=tx_result, ) ) for exec_tx in exec_txs: - if tx_result == TransactionResult.OUT_OF_GAS: - expected_gas_used += exec_tx.gas_limit - else: - expected_gas_used += exec_tx.gas_cost + expected_gas_used += exec_tx.gas_cost blocks.append(Block(txs=exec_txs)) diff --git a/tests/benchmark/compute/instruction/test_system.py b/tests/benchmark/compute/instruction/test_system.py index 551a1e242d0..0bc561435ee 100644 --- a/tests/benchmark/compute/instruction/test_system.py +++ b/tests/benchmark/compute/instruction/test_system.py @@ -13,6 +13,8 @@ - SELFDESTRUCT """ +from typing import Any + import pytest from execution_testing import ( AccessList, @@ -30,7 +32,6 @@ JumpLoopGenerator, Op, TestPhaseManager, - Transaction, While, compute_create2_address, compute_create_address, @@ -48,64 +49,117 @@ def test_contract_calling_many_addresses( opcode: Op, access_warm: bool, gas_benchmark_value: int, - tx_gas_limit: int, + fixed_opcode_count: float | None, ) -> None: - """Benchmark a contract that calls many addresses.""" - warm_start_addr = 2**80 - 1 - setup = Op.PUSH20(warm_start_addr) if access_warm else Op.GAS - - def loop(threshold: int) -> Bytecode: - return ( - Op.JUMPDEST - + opcode(address=Op.DUP6, value=transfer_amount) - + Op.SWAP1 - + Op.SUB - + Op.JUMPI(Op.GT(Op.GAS, threshold), len(setup)) - ) + """Benchmark a contract that calls many distinct addresses.""" + start_addr = 2**80 - 1 - cost = loop(0xFFFF).gas_cost(fork) - code = setup + loop(cost) + value_transfer = transfer_amount > 0 + # Only CALL creates accounts on value transfer (CALLCODE doesn't). + account_creation = value_transfer and opcode == Op.CALL + + setup = ( + Op.ADD(1, Op.CALLDATALOAD(32)) # [end+1 = limit] + + Op.CALLDATALOAD(0) # [start = index, limit] + ) - contract_addr = pre.deploy_contract( + iterating = While( + body=Op.POP( + opcode( + address=Op.ADD(start_addr, Op.DUP6), + value=transfer_amount, + # gas accounting + address_warm=access_warm, + value_transfer=value_transfer, + account_new=account_creation, + ) + ), + condition=Op.PUSH1(1) # [1, index, limit] + + Op.ADD # [index+1, limit] + + Op.DUP1 # [index+1, index+1, limit] + + Op.DUP3 # [limit, index+1, index+1, limit] + + Op.GT, # [limit > index+1, index+1, limit] + ) + code = IteratingBytecode( + setup=setup, + iterating=iterating, + cleanup=Op.STOP, + ) + + contract_address = pre.deploy_contract( code=code, - balance=10**18 if transfer_amount > 0 else 0, + balance=10**9 if value_transfer else 0, ) - intrinsic_cost_calc = fork.transaction_intrinsic_cost_calculator() - intrinsic_cost = intrinsic_cost_calc() - access_list_addr_cost = fork.gas_costs().TX_ACCESS_LIST_ADDRESS + def calldata(iteration_count: int, start_iteration: int) -> bytes: + index_end = start_iteration + iteration_count - 1 + return Hash(start_iteration) + Hash(index_end) + + def access_list( + iteration_count: int, start_iteration: int + ) -> list[AccessList]: + return [ + AccessList(address=Address(start_addr + i), storage_keys=[]) + for i in range(start_iteration, start_iteration + iteration_count) + ] + + tx_kwargs: dict = { + "calldata": calldata, + "access_list": access_list if access_warm else None, + } + + total_iterations = ( + sum( + code.tx_iterations_by_gas_limit( + fork=fork, gas_limit=gas_benchmark_value, **tx_kwargs + ) + ) + if fixed_opcode_count is None + else int(fixed_opcode_count * 1000) + ) - txs = [] - remaining_gas = gas_benchmark_value - while remaining_gas > intrinsic_cost: - per_tx_gas = min(tx_gas_limit, remaining_gas) - remaining_gas -= per_tx_gas + if total_iterations == 0: + pytest.skip( + "Benchmark gas value cannot cover a single call to the contract." + ) - access_list = None - if access_warm: - iterations = (per_tx_gas - intrinsic_cost) // ( - access_list_addr_cost + cost + with TestPhaseManager.execution(): + sender = pre.fund_eoa() + if fixed_opcode_count is not None: + exec_txs = list( + code.transactions_by_total_iteration_count( + fork=fork, + total_iterations=total_iterations, + sender=sender, + to=contract_address, + **tx_kwargs, + ) ) - if iterations <= 0: - break - access_list = [ - AccessList( - address=Address(warm_start_addr - i), - storage_keys=[], + else: + exec_txs = list( + code.transactions_by_gas_limit( + fork=fork, + gas_limit=gas_benchmark_value, + sender=sender, + to=contract_address, + **tx_kwargs, ) - for i in range(iterations) - ] - - txs.append( - Transaction( - to=contract_addr, - sender=pre.fund_eoa(), - gas_limit=per_tx_gas, - access_list=access_list, ) - ) + total_gas_cost = sum(tx.gas_cost for tx in exec_txs) + if value_transfer: + total_gas_cost -= fork.gas_costs().CALL_STIPEND * total_iterations - benchmark_test(blocks=[Block(txs=txs)]) + post = { + Address(start_addr + i): Account(balance=transfer_amount) + for i in range(total_iterations) + if account_creation + } + + benchmark_test( + post=post, + blocks=[Block(txs=exec_txs)], + expected_benchmark_gas_used=total_gas_cost, + ) @pytest.mark.repricing(max_code_size_ratio=0) @@ -148,90 +202,155 @@ def test_create( max_code_size_ratio: float, non_zero_data: bool, value: int, + gas_benchmark_value: int, + fixed_opcode_count: float | None, ) -> None: """Benchmark CREATE and CREATE2 instructions.""" max_code_size = fork.max_code_size() code_size = int(max_code_size * max_code_size_ratio) - # Deploy the initcode template which has following design: - # ``` - # PUSH3(code_size) - # [CODECOPY(DUP1) -- Conditional that non_zero_data is True] - # RETURN(0, DUP1) - # [<pad to code_size>] -- Conditional that non_zero_data is True] - # ``` - code = ( - Op.PUSH3(code_size) - + (Op.CODECOPY(size=Op.DUP1) if non_zero_data else Bytecode()) - + Op.RETURN(0, Op.DUP1) + copy = ( + Op.CODECOPY( + dest_offset=0, + offset=0, + size=code_size, + # gas accounting + data_size=code_size, + new_memory_size=code_size, + ) + if non_zero_data + else Bytecode() ) - if non_zero_data: # Pad to code_size. - code += bytes([i % 256 for i in range(code_size - len(code))]) - initcode_template_contract = pre.deploy_contract(code=code) + initcode_body = copy + Op.RETURN( + 0, + code_size, + # gas accounting + code_deposit_size=code_size, + new_memory_size=0 if non_zero_data else code_size, + ) - # Create the benchmark contract which has the following design: - # ``` - # PUSH(value) - # [EXTCODECOPY(full initcode_template_contract) - # -> Conditional that non_zero_data is True] - # - # JUMPDEST (#) - # (CREATE|CREATE2) - # (CREATE|CREATE2) - # ... - # JUMP(#) - # ``` + initcode = initcode_body + + if non_zero_data: # Pad to code_size so CODECOPY has code_size bytes. + initcode += bytes( + [i % 256 for i in range(code_size - len(initcode_body))] + ) + + initcode_template_contract = pre.deploy_contract(code=initcode) + + # CALLDATA[0:32] = start index + # CALLDATA[32:64] = end index setup = ( - Op.PUSH3(code_size) - + Op.PUSH1(value) - + Op.EXTCODECOPY( + Op.EXTCODECOPY( address=initcode_template_contract, - size=Op.DUP2, # DUP2 refers to the EXTCODESIZE value above. + dest_offset=0, + offset=0, + size=len(initcode), + # gas accounting + data_size=len(initcode), + new_memory_size=len(initcode), ) + + Op.ADD(1, Op.CALLDATALOAD(32)) # [end+1 = limit] + + Op.CALLDATALOAD(0) # [start = index, limit] ) + # CREATE2 takes the loop index (stack top) as its salt; + salt_kwarg: dict[str, Any] = {} if opcode == Op.CREATE2: - # For CREATE2, load salt from storage (persist across outer loop calls) - # If storage is 0 (first call), use initial salt of 42. - # Stack after setup: [..., value, code_size, salt] - setup += ( - Op.SLOAD(0) # Load saved salt - + Op.DUP1 # Duplicate for check - + Op.ISZERO # Check if zero - + Op.PUSH1(42) # Default salt - + Op.MUL # 42 if zero, 0 if not - + Op.ADD # Add to get final salt (saved or 42) - ) + salt_kwarg = {"salt": Op.DUP1} - attack_block = ( - # For CREATE: - # - DUP2 refers to the EXTOCODESIZE value pushed in code_prefix. - # - DUP3 refers to PUSH1(value) above. - Op.POP(Op.CREATE(value=Op.DUP3, offset=0, size=Op.DUP2)) - if opcode == Op.CREATE - # For CREATE2: we manually push the arguments because we leverage the - # return value of previous CREATE2 calls as salt for the next CREATE2 - # call. After CREATE2, save result to storage for next outer loop call. - # - DUP4 is targeting the PUSH1(value) from the code_prefix. - # - DUP3 is targeting the EXTCODESIZE value pushed in code_prefix. - else Op.DUP3 - + Op.PUSH0 - + Op.DUP4 - + Op.CREATE2 - + Op.DUP1 - + Op.PUSH0 - + Op.SSTORE + create_op = opcode( + value=value, + offset=0, + size=len(initcode), + init_code_size=len(initcode), + **salt_kwarg, ) + loop = While( + body=Op.POP(create_op), # [index, limit] + condition=Op.PUSH1(1) # [1, index, limit] + + Op.ADD # [index+1, limit] + + Op.DUP1 # [index+1, index+1, limit] + + Op.DUP3 # [limit, index+1, index+1, limit] + + Op.GT, # [limit > index+1, index+1, limit] + ) + + code = IteratingBytecode( + setup=setup, + iterating=loop, + iterating_subcall=initcode_body, + cleanup=Op.STOP, + ) + + contract_address = pre.deploy_contract( + code=code, + balance=10**9 if value > 0 else 0, + ) + + def calldata(iteration_count: int, start_iteration: int) -> bytes: + index_end = iteration_count + start_iteration - 1 + return Hash(start_iteration) + Hash(index_end) + + num_contracts = ( + sum( + code.tx_iterations_by_gas_limit( + fork=fork, + gas_limit=gas_benchmark_value, + calldata=calldata, + ) + ) + if fixed_opcode_count is None + else int(fixed_opcode_count * 1000) + ) + + if num_contracts == 0: + pytest.skip( + "Benchmark gas value cannot cover a single contract creation." + ) + + with TestPhaseManager.execution(): + sender = pre.fund_eoa() + if fixed_opcode_count is not None: + exec_txs = list( + code.transactions_by_total_iteration_count( + fork=fork, + total_iterations=num_contracts, + sender=sender, + to=contract_address, + calldata=calldata, + ) + ) + else: + exec_txs = list( + code.transactions_by_gas_limit( + fork=fork, + gas_limit=gas_benchmark_value, + sender=sender, + to=contract_address, + calldata=calldata, + ) + ) + total_gas_cost = sum(tx.gas_cost for tx in exec_txs) + + post = { + compute_create_address( + address=contract_address, + nonce=1 + i, + salt=i, + initcode=initcode, + opcode=opcode, + ): Account(nonce=1) + for i in range(num_contracts) + } + benchmark_test( + post=post, target_opcode=opcode, - code_generator=JumpLoopGenerator( - setup=setup, - attack_block=attack_block, - contract_balance=1_000_000_000 if value > 0 else 0, - ), + blocks=[Block(txs=exec_txs)], + expected_benchmark_gas_used=total_gas_cost, ) @@ -249,6 +368,7 @@ def test_creates_collisions( fork: Fork, opcode: Op, gas_benchmark_value: int, + fixed_opcode_count: float | None, ) -> None: """Benchmark CREATE and CREATE2 instructions with collisions.""" # We deploy a "proxy contract" which is the contract that will be called in @@ -265,18 +385,31 @@ def test_creates_collisions( # Note that these CREATE(2) calls will fail because in (**) below we pre- # alloc contracts with the same address as the ones that CREATE(2) will try # to create. + # The collision targets pre-exist (**), so per EIP-8037 the + # CREATE(2) never charges NEW_ACCOUNT state gas. proxy_contract_code = ( Op.CREATE2( - value=Op.PUSH0, salt=Op.PUSH0, offset=Op.PUSH0, size=Op.PUSH0 + value=Op.PUSH0, + salt=Op.PUSH0, + offset=Op.PUSH0, + size=Op.PUSH0, + # gas accounting + account_new=False, ) if opcode == Op.CREATE2 - else Op.CREATE(value=Op.PUSH0, offset=Op.PUSH0, size=Op.PUSH0) + else Op.CREATE( + value=Op.PUSH0, + offset=Op.PUSH0, + size=Op.PUSH0, + # gas accounting + account_new=False, + ) ) proxy_contract = pre.deploy_contract(code=proxy_contract_code) - # The CALL to the proxy contract needs at a minimum gas corresponding to - # the CREATE(2) plus extra required PUSH0s for arguments. - min_gas_required = proxy_contract_code.gas_cost(fork) + min_gas_required = proxy_contract_code.regular_cost( + fork + ) + proxy_contract_code.state_cost(fork) setup = Op.PUSH20(proxy_contract) + Op.PUSH3(min_gas_required) attack_block = Op.POP( # DUP7 refers to the PUSH3 above. @@ -292,9 +425,12 @@ def test_creates_collisions( ) pre.deploy_contract(address=addr, code=Op.INVALID) else: - # Heuristic to have an upper bound. - creation_cost = proxy_contract_code.gas_cost(fork) - max_contract_count = 2 * gas_benchmark_value // creation_cost + creation_cost = proxy_contract_code.regular_cost(fork) + max_contract_count = ( + 2 * gas_benchmark_value // creation_cost + if fixed_opcode_count is None + else int(fixed_opcode_count * 1000) + ) for nonce in range(max_contract_count): addr = compute_create_address(address=proxy_contract, nonce=nonce) pre.deploy_contract(address=addr, code=Op.INVALID) @@ -464,17 +600,6 @@ def calldata(iteration_count: int, start_iteration: int) -> bytes: ) num_contracts = sum(iteration_counts) - start = 0 - total_gas_cost = 0 - for iters in iteration_counts: - total_gas_cost += attack_code.tx_gas_cost_by_iteration_count( - fork=fork, - iteration_count=iters, - start_iteration=start, - calldata=calldata, - ) - start += iters - def factory_calldata(iteration_count: int, start_iteration: int) -> bytes: index_end = iteration_count + start_iteration - 1 return Hash(start_iteration) + Hash(index_end) @@ -503,6 +628,8 @@ def factory_calldata(iteration_count: int, start_iteration: int) -> bytes: ) ) + total_gas_cost = sum(tx.gas_cost for tx in exec_txs) + post = {} for i in range(num_contracts): deployed_contract_address = compute_create2_address( @@ -582,8 +709,7 @@ def test_selfdestruct_created( attack_code = IteratingBytecode( setup=setup, iterating=loop, - iterating_subcall=selfdestructable_contract.gas_cost(fork) - + initcode.gas_cost(fork), + iterating_subcall=initcode + selfdestructable_contract, cleanup=Op.STOP, ) @@ -600,15 +726,6 @@ def calldata(iteration_count: int, start_iteration: int) -> bytes: ) num_iterations = sum(iteration_counts) - total_gas_cost = sum( - attack_code.tx_gas_cost_by_iteration_count( - fork=fork, - iteration_count=iters, - calldata=calldata, - ) - for iters in iteration_counts - ) - attack_code_address = pre.deploy_contract( code=attack_code, balance=num_iterations if value_bearing else 0, @@ -626,6 +743,8 @@ def calldata(iteration_count: int, start_iteration: int) -> bytes: ) ) + total_gas_cost = sum(tx.gas_cost for tx in exec_txs) + post = { attack_code_address: Account( balance=num_iterations if value_bearing else 0 @@ -698,15 +817,6 @@ def calldata(iteration_count: int, start_iteration: int) -> bytes: ) num_iterations = sum(iteration_counts) - total_gas_cost = sum( - attack_code.tx_gas_cost_by_iteration_count( - fork=fork, - iteration_count=iters, - calldata=calldata, - ) - for iters in iteration_counts - ) - attack_code_address = pre.deploy_contract( code=attack_code, balance=num_iterations if value_bearing else 0, @@ -724,6 +834,8 @@ def calldata(iteration_count: int, start_iteration: int) -> bytes: ) ) + total_gas_cost = sum(tx.gas_cost for tx in exec_txs) + post = { attack_code_address: Account( balance=num_iterations if value_bearing else 0 diff --git a/tests/benchmark/compute/precompile/test_alt_bn128.py b/tests/benchmark/compute/precompile/test_alt_bn128.py index 9d2943b2e7f..b65f2ee968f 100644 --- a/tests/benchmark/compute/precompile/test_alt_bn128.py +++ b/tests/benchmark/compute/precompile/test_alt_bn128.py @@ -179,6 +179,12 @@ id="bn128_mul_32_byte_coord_and_scalar", marks=pytest.mark.repricing, ), + # Pairing inputs below are py_ecc-generated (not external vectors), + # so every point is on-curve and in the prime-order subgroup: each + # G1 is k*G1, each G2 is k*G2 with FQ2 coeffs swapped to the + # precompile's (imag, real) decode order. Scalars are small and + # arbitrary - 1_pair (3, 5); 2_sets adds (7, 11); 3_pair (2,3), + # (5,7), (11,13); 1_pair_empty is one 192-byte (inf, inf) pair. pytest.param( EIP197Spec.ECPAIRING, # First pairing @@ -251,17 +257,17 @@ ) # Second pairing + PointG1( - x=0x0000000000000000000000000000000000000000000000000000000000000013, - y=0x0644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD451, + x=0x17072B2ED3BB8D759A5325F477629386CB6FC6ECB801BD76983A6B86ABFFE078, + y=0x168ADA6CD130DD52017BB54BFA19377AADFE3BF05D18F41B77809F7F60D4AF9E, ) + PointG2( x=( - 0x971FF0471B09FA93CAAF13CBF443C1AEDE09CC4328F5A62AAD45F40EC133EB40, - 0x91058A3141822985733CBDDDFED0FD8D6C104E9E9EFF40BF5ABFEF9AB163BC72, + 0x228B515A17F28B89920873207477F8C7FC05582DEBAF3184FEBF1CFDEDC5CE88, + 0x12BB1156A9F6B360FCB2614E15D8A3FF07F2C699DC69CA830B20D2DF91FE9CD3, ), y=( - 0xA23AF9A5CE2BA2796C1F4E453A370EB0AF8C212D9DC9ACD8FC02C2E907BAEA22, - 0x3A8EB0B0996252CB548A4487DA97B02422EBC0E834613F954DE6C7E0AFDC1FC0, + 0x2B15DC62A5C9E36597914DDBBFDE48806A8EABE45C8D3CCCF9578AD08E058F92, + 0x02A4FD764F52470E2FCFFF325FB9692F55D6B8B077EEFEAA04E07152B4D1FA94, ), ), Precompile.BN128_PAIRING, @@ -269,7 +275,20 @@ ), pytest.param( EIP197Spec.ECPAIRING, - b"", + PointG1( + x=0x0769BF9AC56BEA3FF40232BCB1B6BD159315D84715B8E679F2D355961915ABF0, + y=0x2AB799BEE0489429554FDB7C8D086475319E63B40B9C5B57CDF1FF3DD9FE2261, + ) + + PointG2( + x=( + 0x0A09CCF561B55FD99D1C1208DEE1162457B57AC5AF3759D50671E510E428B2A1, + 0x2E539C423B302D13F4E5773C603948EAF5DB5DF8AE8A9A9113708390A06410D8, + ), + y=( + 0x19B763513924A736E4EEBD0D78C91C1BC1D657FEE4214057D21414011CFCC763, + 0x2F8D9F9AB83727C77A2FEC063CB7B6E5EB23044CCF535AD49D46D394FB6F6BF6, + ), + ), Precompile.BN128_PAIRING, id="ec_pairing_1_pair", ), @@ -302,23 +321,50 @@ pytest.param( EIP197Spec.ECPAIRING, # First pairing - PointG1(x=0, y=0) + PointG1( + x=0x030644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD3, + y=0x15ED738C0E0A7C92E7845F96B2AE9C0A68A6A449E3538FC7FF3EBF7A5A18A2C4, + ) + PointG2( x=( - 0x0EF4AAC9B7954D5FC6EAFAE7F4F4C2A732AB05B45F8D50D102CEE4973F36EB2C, - 0x23DB7D30C99E0A2A7F3BB5CD1F04635AAEA58732B58887DF93D9239C28230D28, + 0x1014772F57BB9742735191CD5DCFE4EBBC04156B6878A0A7C9824F32FFB66E85, + 0x06064E784DB10E9051E52826E192715E8D7E478CB09A5E0012DEFA0694FBC7F5, ), y=( - 0x2BD99D31A5054F2556D226F2E5EF0E075423D8604178B2E2C08006311CAEE54F, - 0x0F11AFB0C6073D12D21B13F4F78210E8CA9A66729206D3FCC2C1B04824C425F2, + 0x021E2335F3354BB7922FFCC2F38D3323DD9453AC49B55441452AEACA147711B2, + 0x058E1D5681B5B9E0074B0F9C8D2C68A069B920D74521E79765036D57666C5597, ), ) - # Second pairing (32 zero + G2 generator = 160 bytes) - + bytes(32) - + EIP197Spec.G2 - # Third pairing (same structure as second) - + bytes(32) - + EIP197Spec.G2, + # Second pairing + + PointG1( + x=0x17C139DF0EFEE0F766BC0204762B774362E4DED88953A39CE849A8A7FA163FA9, + y=0x01E0559BACB160664764A357AF8A9FE70BAA9258E0B959273FFC5718C6D4CC7C, + ) + + PointG2( + x=( + 0x2903BA015A9ABDE26A5D081E84551E63BE0FD4516E46EE6D593EDEBA46362455, + 0x224BDC5D4327FCF8ED702E01DE1C2F1657A253BA75E32A89C390142AAA28B308, + ), + y=( + 0x03C8B7CDA6B2DEDB7AEEAF5FDA464AD17036BEA1C4E6F7ADBAED1EBE0335E0D8, + 0x1D92FFF52A265017EECCB372E37D7A7BD431800ECA28DFD82E21E8054114233F, + ), + ) + # Third pairing + + PointG1( + x=0x2A14705537B009189DA8808651EECDB82482477FE92AC12CA8B71F80FC3D49EF, + y=0x2DF7EE7F243EA8B38E1DDF14029258877A618C779FD4717DB6177E19EA67EC38, + ) + + PointG2( + x=( + 0x009EDAF0698A8C56F51139588ACC094CEE3C37D427BB6D2EAB830AAE529097D1, + 0x23AD66F3A7CCA9DC75049635FAEBD124316244B91DE5FB2764CD151572A905F7, + ), + y=( + 0x2700E8A29B7BB45F3022A18A07BDC66D0254559E17CCE64E3B4AD21578FCF410, + 0x1AD4F87D3B4375A39988AC099B042B1E7C0C715678E4C2BEA8905F607CF950F8, + ), + ), Precompile.BN128_PAIRING, id="ec_pairing_3_pair", ), @@ -464,7 +510,10 @@ ), pytest.param( EIP197Spec.ECPAIRING, - bytes(32), + # One correctly sized (192-byte) pair of infinity points: the + # minimal input the precompile still accepts and charges a full + # pair for. bytes(32) would be rejected as a bad length. + bytes(192), Precompile.BN128_PAIRING, id="ec_pairing_1_pair_empty", ), diff --git a/tests/benchmark/compute/precompile/test_bls12_381.py b/tests/benchmark/compute/precompile/test_bls12_381.py index d6e08db6597..ce457d237f2 100644 --- a/tests/benchmark/compute/precompile/test_bls12_381.py +++ b/tests/benchmark/compute/precompile/test_bls12_381.py @@ -38,16 +38,6 @@ id="bls12_g1add", marks=pytest.mark.repricing, ), - pytest.param( - bls12381_spec.Spec.G1MSM, - ( - bls12381_spec.Spec.P1 - + bls12381_spec.Scalar(bls12381_spec.Spec.Q) - ) - * (len(bls12381_spec.Spec.G1MSM_DISCOUNT_TABLE) - 1), - Precompile.BLS12_G1MSM, - id="bls12_g1msm", - ), pytest.param( bls12381_spec.Spec.G2ADD, bls12381_spec.Spec.G2 + bls12381_spec.Spec.P2, @@ -55,20 +45,6 @@ id="bls12_g2add", marks=pytest.mark.repricing, ), - pytest.param( - bls12381_spec.Spec.G2MSM, - # TODO: the //2 is required due to a limitation of the max - # contract size limit. In a further iteration we can insert - # inputs as calldata or storage and avoid doing PUSHes which - # has this limitation. This also applies to G1MSM. - ( - bls12381_spec.Spec.P2 - + bls12381_spec.Scalar(bls12381_spec.Spec.Q) - ) - * (len(bls12381_spec.Spec.G2MSM_DISCOUNT_TABLE) // 2), - Precompile.BLS12_G2MSM, - id="bls12_g2msm", - ), pytest.param( bls12381_spec.Spec.PAIRING, bls12381_spec.Spec.G1 + bls12381_spec.Spec.G2, @@ -96,6 +72,7 @@ def test_bls12_381( benchmark_test: BenchmarkTestFiller, fork: Fork, + gas_benchmark_value: int, precompile_address: Address, calldata: bytes, target: OpcodeTarget, @@ -104,6 +81,12 @@ def test_bls12_381( if precompile_address not in fork.precompiles(): pytest.skip("Precompile not enabled") + intrinsic_gas_cost = fork.transaction_intrinsic_cost_calculator()( + calldata=calldata + ) + if intrinsic_gas_cost > gas_benchmark_value: + pytest.skip("calldata intrinsic gas cost exceeds the gas limit") + attack_block = Op.POP( Op.STATICCALL( gas=Op.GAS, address=precompile_address, args_size=Op.CALLDATASIZE @@ -125,6 +108,7 @@ def test_bls12_381( def test_bls12_g1_msm( benchmark_test: BenchmarkTestFiller, fork: Fork, + gas_benchmark_value: int, k: int, ) -> None: """Benchmark BLS12_G1_MSM precompile with varying number of points.""" @@ -132,11 +116,13 @@ def test_bls12_g1_msm( if precompile_address not in fork.precompiles(): pytest.skip("BLS12_G1_MSM precompile not enabled") - # Generate k pairs of (point, scalar) - calldata = Bytes( - (bls12381_spec.Spec.P1 + bls12381_spec.Scalar(bls12381_spec.Spec.Q)) - * k + calldata = _g1msm_worstcase_calldata(k) + + intrinsic_gas_cost = fork.transaction_intrinsic_cost_calculator()( + calldata=calldata ) + if intrinsic_gas_cost > gas_benchmark_value: + pytest.skip("k configuration exceeds the gas limit") attack_block = Op.POP( Op.STATICCALL( @@ -176,11 +162,7 @@ def test_bls12_g2_msm( if precompile_address not in fork.precompiles(): pytest.skip("BLS12_G2_MSM precompile not enabled") - # Generate k pairs of (point, scalar) - calldata = Bytes( - (bls12381_spec.Spec.P2 + bls12381_spec.Scalar(bls12381_spec.Spec.Q)) - * k - ) + calldata = _g2msm_worstcase_calldata(k) intrinsic_gas_cost = fork.transaction_intrinsic_cost_calculator()( calldata=calldata @@ -265,6 +247,40 @@ def _generate_bls12_g2_point(seed: int) -> Bytes: ) +def _g1msm_worstcase_calldata(k: int) -> Bytes: + """ + Build a k-pair G1MSM input that resists MSM shortcuts. + + Identical points would let the precompile factor out (sum sᵢ)·P, and + identical scalars would let Pippenger bucket every term together. Pairing + distinct points with distinct full-width scalars below the subgroup order + (so no term collapses to the point at infinity) forces the full k-term + computation. + """ + rng = random.Random(0) + parts = [ + bytes(_generate_bls12_g1_point(i)) + + bytes( + bls12381_spec.Scalar(rng.randint(2**254, bls12381_spec.Spec.Q - 1)) + ) + for i in range(k) + ] + return Bytes(b"".join(parts)) + + +def _g2msm_worstcase_calldata(k: int) -> Bytes: + """G2MSM counterpart of `_g1msm_worstcase_calldata`.""" + rng = random.Random(0) + parts = [ + bytes(_generate_bls12_g2_point(i)) + + bytes( + bls12381_spec.Scalar(rng.randint(2**254, bls12381_spec.Spec.Q - 1)) + ) + for i in range(k) + ] + return Bytes(b"".join(parts)) + + def _generate_bls12_pairs(n: int, seed: int = 0) -> Bytes: """Generate n valid BLS12-381 (G1, G2) pairs.""" calldata = Bytes() @@ -285,19 +301,26 @@ def _g2add_calldata(seed: int) -> Bytes: return Bytes(_generate_bls12_g2_point(seed) + bls12381_spec.Spec.P2) +# Full-width scalar just below the subgroup order. Using Q itself would be +# congruent to 0 mod the subgroup order, so Q * P == O and the output fed +# back into the next call would multiply the point at infinity forever. Q - 2 +# keeps every call a real scalar multiplication over a long, non-repeating +# sequence of distinct points (its order mod Q exceeds any per-block +# iteration count), preserving both the workload and the anti-caching intent. +_MSM_SCALAR = bls12381_spec.Spec.Q - 2 + + def _g1msm_calldata(seed: int) -> Bytes: """Generate G1MSM calldata with unique point.""" return Bytes( - _generate_bls12_g1_point(seed) - + bls12381_spec.Scalar(bls12381_spec.Spec.Q) + _generate_bls12_g1_point(seed) + bls12381_spec.Scalar(_MSM_SCALAR) ) def _g2msm_calldata(seed: int) -> Bytes: """Generate G2MSM calldata with unique point.""" return Bytes( - _generate_bls12_g2_point(seed) - + bls12381_spec.Scalar(bls12381_spec.Spec.Q) + _generate_bls12_g2_point(seed) + bls12381_spec.Scalar(_MSM_SCALAR) ) diff --git a/tests/benchmark/compute/scenario/test_transaction_types.py b/tests/benchmark/compute/scenario/test_transaction_types.py index 5d4c3d1b002..78746fd188a 100644 --- a/tests/benchmark/compute/scenario/test_transaction_types.py +++ b/tests/benchmark/compute/scenario/test_transaction_types.py @@ -17,6 +17,7 @@ Fork, Hash, Op, + RecipientType, Transaction, compute_create_address, ) @@ -184,42 +185,83 @@ def test_ether_transfers( senders, receivers = ether_transfer_case balance = receiver_account_type.balance + sends_value = transfer_amount > 0 + distinct_receivers = case_id in ("a_to_diff_acc", "diff_acc_to_diff_acc") + + if case_id == "a_to_a": + recipient_type = RecipientType.SELF + elif receiver_account_type.delegated: + recipient_type = RecipientType.DELEGATION_7702 + else: + recipient_type = RecipientType.CONTRACT + + warm_list = ( + [AccessList(address=Address(0x100), storage_keys=[])] + if warm_access + else None + ) + + transfer_cost = fork.transaction_intrinsic_cost_calculator()( + access_list=warm_list, + sends_value=sends_value, + recipient_type=recipient_type, + ) + fork.transaction_top_frame_gas_calculator()( + sends_value=sends_value, + recipient_type=recipient_type, + ) + + creates_account = ( + sends_value + and balance == 0 + and not receiver_account_type.delegated + and recipient_type != RecipientType.SELF + ) + + new_account_cost = ( + fork.transaction_top_frame_state_gas( + sends_value=True, + recipient_type=RecipientType.EMPTY_ACCOUNT, + ) + if creates_account + else 0 + ) txs = [] token_transfers: dict[Address, int] = {} - iteration_cost = fork.transaction_intrinsic_cost_calculator()( - access_list=( - [AccessList(address=Address(0x100), storage_keys=[])] - if warm_access - else None - ), - ) - iteration_count = gas_benchmark_value // iteration_cost + gas_used = 0 + tx_index = 0 + while True: + creating = creates_account and (distinct_receivers or tx_index == 0) + tx_cost = transfer_cost + (new_account_cost if creating else 0) + if gas_used + tx_cost > gas_benchmark_value: + break + + gas_used += tx_cost - for _ in range(iteration_count): receiver = next(receivers) token_transfers[receiver] = ( token_transfers.get(receiver, 0) + transfer_amount ) - access_list = ( - [AccessList(address=receiver, storage_keys=[])] - if warm_access - else None - ) + txs.append( Transaction( to=receiver, value=transfer_amount, - gas_limit=iteration_cost, + gas_limit=tx_cost, sender=next(senders), - access_list=access_list, + access_list=( + [AccessList(address=receiver, storage_keys=[])] + if warm_access + else None + ), ) ) + tx_index += 1 post_state = ( {} - if case_id == "a_to_a" + if recipient_type == RecipientType.SELF else { receiver: Account(balance=balance + transferred_amount) for receiver, transferred_amount in token_transfers.items() @@ -231,7 +273,7 @@ def test_ether_transfers( pre=pre, post=post_state, blocks=[Block(txs=txs)], - expected_benchmark_gas_used=iteration_count * iteration_cost, + expected_benchmark_gas_used=gas_used, ) @@ -241,19 +283,25 @@ def test_ether_transfers_to_precompile( benchmark_test: BenchmarkTestFiller, pre: Alloc, precompile: int, + fork: Fork, gas_benchmark_value: int, transfer_amount: int, - intrinsic_cost: int, ) -> None: """Test a block full of ether transfers to a precompile address.""" - iteration_count = gas_benchmark_value // intrinsic_cost + # A precompile already exists, so a value transfer pays the EIP-2780 + # value-transfer charge but never the new-account state gas. + iteration_cost = fork.transaction_intrinsic_cost_calculator()( + sends_value=transfer_amount > 0, + recipient_type=RecipientType.PRECOMPILE, + ) + iteration_count = gas_benchmark_value // iteration_cost txs = [] for _ in range(iteration_count): txs.append( Transaction( to=Address(precompile), value=transfer_amount, - gas_limit=intrinsic_cost, + gas_limit=iteration_cost, sender=pre.fund_eoa(), ) ) @@ -261,7 +309,7 @@ def test_ether_transfers_to_precompile( benchmark_test( pre=pre, blocks=[Block(txs=txs)], - expected_benchmark_gas_used=iteration_count * intrinsic_cost, + expected_benchmark_gas_used=iteration_count * iteration_cost, ) @@ -280,7 +328,7 @@ def total_cost_standard_per_token(fork: Fork) -> int: def calldata_generator( gas_amount: int, zero_byte: int, - total_cost_floor_per_token: int, + fork: Fork, ) -> bytes: """Calculate the calldata based on the gas amount and zero byte.""" # Gas cost calculation based on EIP-7683: (https://eips.ethereum.org/EIPS/eip-7683) @@ -301,20 +349,9 @@ def calldata_generator( # max(TX_DATA_TOKEN_STANDARD, TX_DATA_TOKEN_FLOOR) # tx.gasUsed = 21000 + tokens_in_calldata * max_token_cost # - # Since max(TX_DATA_TOKEN_STANDARD, TX_DATA_TOKEN_FLOOR) = 10: - # tx.gasUsed = 21000 + tokens_in_calldata * 10 - # - # Token accounting: - # tokens_in_calldata = zero_bytes + 4 * non_zero_bytes - # - # So we calculate how many bytes we can fit into calldata based on - # available gas. - max_tokens_in_calldata = gas_amount // total_cost_floor_per_token - num_of_bytes = ( - max_tokens_in_calldata if zero_byte else max_tokens_in_calldata // 4 - ) byte_data = b"\x00" if zero_byte else b"\xff" - return byte_data * num_of_bytes + gas_per_byte = fork.calldata_gas_calculator()(data=byte_data, floor=True) + return byte_data * (gas_amount // gas_per_byte) @pytest.mark.parametrize("zero_byte", [True, False]) @@ -323,7 +360,6 @@ def test_block_full_data( pre: Alloc, zero_byte: bool, intrinsic_cost: int, - total_cost_floor_per_token: int, gas_benchmark_value: int, tx_gas_limit: int, fork: Fork, @@ -339,11 +375,8 @@ def test_block_full_data( # Max calldata bytes at 99% of limit (Osaka: 8,388,608 * 0.99 ≈ 8.3 MB) safe_calldata_bytes = int(block_rlp_limit * 0.99) - # convert to gas: zero bytes = 10 gas/byte, non-zero = 40 gas/byte - gas_per_byte = ( - total_cost_floor_per_token - if zero_byte - else total_cost_floor_per_token * 4 + gas_per_byte = fork.calldata_gas_calculator()( + data=b"\x00" if zero_byte else b"\xff", floor=True ) # For zero bytes: 8.3MB * 10 = 83M gas just for calldata max_calldata_gas = safe_calldata_bytes * gas_per_byte @@ -363,7 +396,7 @@ def test_block_full_data( data = calldata_generator( gas_available, zero_byte, - total_cost_floor_per_token, + fork, ) total_gas_used += fork.transaction_intrinsic_cost_calculator()( @@ -522,31 +555,70 @@ def test_auth_transaction( tx_gas_limit: int, ) -> None: """Test an auth block.""" - gas_costs = fork.gas_costs() intrinsic_cost_calc = fork.transaction_intrinsic_cost_calculator() + top_frame_calc = fork.transaction_top_frame_gas_calculator() code = Op.INVALID * fork.max_code_size() auth_target = ( Address(0) if zero_delegation else pre.deploy_contract(code=code) ) + sends_value = bool(transfer_amount) + + receiver_type = ( + RecipientType.EMPTY_ACCOUNT + if empty_account + else RecipientType.CONTRACT + ) + + auth_effects = AuthorizationTuple( + address=auth_target, + v=0, + r=0, + s=0, + creates_account=empty_authority, + writes_delegation=empty_authority and not zero_delegation, + first_write=True, + ) + + def auth_tx_gas(count: int) -> int: + """ + Return the full gas consumed by a transaction carrying `count` + authorizations: intrinsic gas plus the state-conditional + top-frame charges, which have no refunds per EIP-2780. + """ + auths = [auth_effects] * count + return ( + intrinsic_cost_calc( + authorization_list_or_count=count, + sends_value=sends_value, + recipient_type=receiver_type, + ) + + top_frame_calc( + sends_value=sends_value, + recipient_type=receiver_type, + authorizations=auths, + ) + + fork.transaction_top_frame_state_gas( + sends_value=sends_value, + recipient_type=receiver_type, + authorizations=auths, + ) + ) + remaining_gas = gas_benchmark_value authorizations_per_tx: List[int] = [] - min_authorization_intrinsic_gas = intrinsic_cost_calc( - authorization_list_or_count=1 - ) + min_authorization_tx_gas = auth_tx_gas(1) - while remaining_gas >= min_authorization_intrinsic_gas: + while remaining_gas >= min_authorization_tx_gas: tx_max_gas = min(remaining_gas, tx_gas_limit) low = 1 high = 2 # Exponential search to find upper bound - while ( - intrinsic_cost_calc(authorization_list_or_count=high) < tx_max_gas - ): + while auth_tx_gas(high) < tx_max_gas: low = high high *= 2 @@ -554,23 +626,22 @@ def test_auth_transaction( while low < high: mid = (low + high) // 2 - if ( - intrinsic_cost_calc(authorization_list_or_count=mid) - > tx_max_gas - ): + if auth_tx_gas(mid) > tx_max_gas: high = mid else: low = mid + 1 best_iterations = low - 1 authorizations_per_tx.append(best_iterations) - remaining_gas -= intrinsic_cost_calc( - authorization_list_or_count=best_iterations - ) + remaining_gas -= auth_tx_gas(best_iterations) - total_gas_used = 0 - total_refund = 0 + delegation_code = ( + b"" if zero_delegation else b"\xef\x01\x00" + bytes(auth_target) + ) + + expected_gas_usage = 0 txs = [] + post = {} for auths_in_this_tx in authorizations_per_tx: auth_tuples = [] @@ -581,47 +652,38 @@ def test_auth_transaction( else pre.fund_eoa(amount=0, delegation=auth_target) ) auth_tuple = AuthorizationTuple( - address=auth_target, nonce=signer.nonce, signer=signer + address=auth_target, + nonce=signer.nonce, + signer=signer, + creates_account=auth_effects.creates_account, + writes_delegation=auth_effects.writes_delegation, ) auth_tuples.append(auth_tuple) - - tx_gas_used = intrinsic_cost_calc( - authorization_list_or_count=auth_tuples - ) - total_gas_used += tx_gas_used - - if not empty_authority: - total_refund += min( - tx_gas_used // 5, - ( - gas_costs.AUTH_PER_EMPTY_ACCOUNT - - gas_costs.REFUND_AUTH_PER_EXISTING_ACCOUNT - ) - * auths_in_this_tx, + post[signer] = Account( + nonce=signer.nonce + 1, code=delegation_code ) + # The gas limit is exact, so an under-estimate halts the top + # frame (rolling back every delegation) and fails the post + # check. + tx_gas = auth_tx_gas(auths_in_this_tx) + expected_gas_usage += tx_gas + receiver = pre.fund_eoa(0 if empty_account else 1) txs.append( Transaction( to=receiver, value=transfer_amount, - gas_limit=tx_gas_used, + gas_limit=tx_gas, sender=pre.fund_eoa(), authorization_list=auth_tuples, ) ) - # EIP-7778: refunds no longer reduce block-level gas accounting - expected_gas_usage = ( - total_gas_used - if fork.is_eip_enabled(7778) - else total_gas_used - total_refund - ) - benchmark_test( pre=pre, - post={}, + post=post, blocks=[Block(txs=txs)], expected_benchmark_gas_used=expected_gas_usage, ) @@ -652,19 +714,28 @@ def test_contract_creation( code_deposit_size=contract_size, ) intrinsic_gas_calc = fork.transaction_intrinsic_cost_calculator() + sends_value = transfer_amount > 0 # EIP-7623: actual gas used = max(standard + execution, floor) standard_intrinsic = intrinsic_gas_calc( calldata=bytes(initcode), contract_creation=True, + sends_value=sends_value, return_cost_deducted_prior_execution=True, ) floor_intrinsic = intrinsic_gas_calc( calldata=bytes(initcode), contract_creation=True, + sends_value=sends_value, ) execution_gas = initcode.gas_cost(fork) - tx_cost = max(standard_intrinsic + execution_gas, floor_intrinsic) + top_frame_state_gas = fork.transaction_top_frame_state_gas( + contract_creation=True + ) + tx_cost = max( + standard_intrinsic + execution_gas + top_frame_state_gas, + floor_intrinsic, + ) iteration_count = gas_benchmark_value // tx_cost diff --git a/tests/benchmark/compute/scenario/test_unchunkified_bytecode.py b/tests/benchmark/compute/scenario/test_unchunkified_bytecode.py index 45be1a5413c..7bbcba3bfe3 100644 --- a/tests/benchmark/compute/scenario/test_unchunkified_bytecode.py +++ b/tests/benchmark/compute/scenario/test_unchunkified_bytecode.py @@ -149,6 +149,12 @@ def calldata(iteration_count: int, start_iteration: int) -> bytes: nonce=1 ) + total_deployment_gas = sum( + tx.block_gas_cost for tx in contracts_deployment_txs + ) + if total_deployment_gas > gas_benchmark_value: + pytest.skip("contract deployment gas exceeds the benchmark gas value") + with TestPhaseManager.execution(): attack_sender = pre.fund_eoa() if fixed_opcode_count is not None: diff --git a/tests/benchmark/helper/__init__.py b/tests/benchmark/helper/__init__.py new file mode 100644 index 00000000000..f2cab960abc --- /dev/null +++ b/tests/benchmark/helper/__init__.py @@ -0,0 +1 @@ +"""Shared helpers reused across benchmark test suites.""" diff --git a/tests/benchmark/helper/account_creator.py b/tests/benchmark/helper/account_creator.py new file mode 100644 index 00000000000..2bb72d09cc6 --- /dev/null +++ b/tests/benchmark/helper/account_creator.py @@ -0,0 +1,356 @@ +"""Benchmark target accounts of various kinds for creation and location..""" + +from abc import ABC, abstractmethod +from dataclasses import dataclass +from enum import Enum, auto +from typing import ClassVar, Self + +from execution_testing import ( + DETERMINISTIC_FACTORY_ADDRESS, + Bytecode, + Create2PreimageLayout, + Hash, + Op, + SequentialAddressLayout, + keccak256, +) +from execution_testing.forks import Osaka + +DEFAULT_CODE_SIZE = Osaka.max_code_size() + + +class AccountMode(Enum): + """Benchmark target account variant.""" + + # Minimal contract: single STOP byte. + EXISTING_CONTRACT_MINIMAL = auto() + + # Max-size contract: byte-identical across copies. + EXISTING_CONTRACT_SAME_MAX = auto() + + # Max-size contract: ADDRESS-embedded, each copy unique. + EXISTING_CONTRACT_DIFF_MAX = auto() + + # Max-size contract: exercises JUMPDEST analysis. The code is unique. + EXISTING_CONTRACT_JUMPDEST = auto() + + # EOA with balance. + EXISTING_EOA = auto() + + # Empty account + NON_EXISTING_ACCOUNT = auto() + + +class ContractInitcode(Bytecode): + """Initcode for target contract receiver.""" + + @property + def runtime_size(self) -> int: + """Size in bytes of the deployed runtime.""" + raise NotImplementedError + + @property + def execution_code(self) -> Bytecode: + """Model of the code executed when the contract is called.""" + raise NotImplementedError + + +class MinimalContractInitcode(ContractInitcode): + """Initcode whose deployed runtime is a single STOP opcode.""" + + def __new__(cls) -> Self: + """Assemble the initcode.""" + return super().__new__(cls, Op.RETURN(Op.PUSH1(0), Op.PUSH1(1))) + + @property + def runtime_size(self) -> int: + """Size in bytes of the deployed runtime.""" + return len(Op.STOP) + + @property + def execution_code(self) -> Bytecode: + """A single STOP halts the call immediately.""" + return Op.STOP + + +class StopJumpdestInitcode(ContractInitcode): + """ + Initcode for a JUMPDEST-filled runtime contract starting with STOP. + + If `code_size` is not supplied, Osaka max code size will + be used, resulting in: + + offset size contents + ------ ---- -------------------------------- + 0x0000 1 STOP <- a call halts here + 0x0001 11 00 padding <- diff=True only + 0x000C 20 contract ADDRESS <- diff=True only + 0x0020 24544 JUMPDEST <- fills up to 0x6000 + + *diff* embeds ADDRESS (bytes 12-31), making each copy unique. + Without it, all copies are identical (JUMPDEST bytes 1-31) + """ + + code_size: int + + def __new__( + cls, *, code_size: int = DEFAULT_CODE_SIZE, diff: bool = False + ) -> Self: + """Assemble the initcode.""" + # Each MCOPY doubles the JUMPDEST-filled span (the first copy is + # MCOPY(32, 0, 32), since 1 << 5 = 32) until it covers code_size. + code = Op.MSTORE(0, bytes(Op.JUMPDEST * 32)) + for size in (1 << s for s in range(5, (code_size - 1).bit_length())): + code += Op.MCOPY(size, 0, size) + + if diff: + # Embeds ADDRESS in the runtime to make each copy unique + code += Op.MSTORE(0, Op.ADDRESS) + else: + # Without embedding, all copies are byte-identical; + code += Op.MSTORE8(0, 0) + code += Op.RETURN(0, code_size) + instance = super().__new__(cls, code) + instance.code_size = code_size + return instance + + @property + def runtime_size(self) -> int: + """Size in bytes of the deployed runtime.""" + return self.code_size + + @property + def execution_code(self) -> Bytecode: + """The leading STOP halts the call immediately.""" + return Op.STOP + + +class JochemnetPredeployContractInitcode(ContractInitcode): + """ + Initcode whose deployed runtime embeds its own contract ADDRESS. + + offset size contents + ------ ---- -------------------------------- + 0x0000 4 PUSH2 0x5FFF; JUMP <- entry + 0x0004 28 JUMPDEST padding + 0x0020 12 JUMPDEST padding + 0x002C 20 contract ADDRESS <- unique + 0x0040 24512 JUMPDEST <- 0x5FFF lands here + + Embedded ADDRESS makes the runtime unique per contract; initcode and + its CREATE2 hash are shared across all salts. + """ + + code_size: int + + def __new__(cls, *, code_size: int = DEFAULT_CODE_SIZE) -> Self: + """Assemble the initcode.""" + # Each MCOPY doubles the JUMPDEST-filled span (the first copy is + # MCOPY(32, 0, 32), since 1 << 5 = 32) until it covers code_size. + code = Op.MSTORE(0, bytes(Op.JUMPDEST * 32)) + for size in (1 << s for s in range(5, (code_size - 1).bit_length())): + code += Op.MCOPY(size, 0, size) + + # Runtime entry: JUMP to final JUMPDEST, then STOP. + entry = Op.JUMP(code_size - 1) + entry += Op.JUMPDEST * (32 - len(entry)) # Padding + + code += Op.MSTORE(0, bytes(entry)) + + # Mask ADDRESS into a JUMPDEST template via OR: + # bytes 0..12 bytes 12..32 + # ----------- ------------ + # ADDRESS 00 .. 00 <20-byte address> + # addr_slot 5b .. 5b 00 .. 00 + # OR result 5b .. 5b <20-byte address> + addr_slot = Op.JUMPDEST * 12 + Op.STOP * 20 + code += Op.MSTORE(0x20, Op.OR(Op.ADDRESS, bytes(addr_slot))) + + code += Op.RETURN(0, code_size) + instance = super().__new__(cls, code) + instance.code_size = code_size + return instance + + @property + def runtime_size(self) -> int: + """Size in bytes of the deployed runtime.""" + return self.code_size + + @property + def execution_code(self) -> Bytecode: + """Jump to the final JUMPDEST, then halt.""" + # Entry jumps to the final JUMPDEST, then halts. + return Op.JUMP(Op.PUSH2(self.code_size - 1)) + Op.JUMPDEST + + +class AddressSource(ABC): + """ + Locates and iterates over target addresses. + + Provides a unified interface for layout initialization, + reading the current target, and advancing to the next one. + """ + + @property + @abstractmethod + def setup(self) -> Bytecode: + """Bytecode that initializes the in-memory address layout.""" + + @property + @abstractmethod + def memory_size(self) -> int: + """Bytes of memory occupied by the address layout.""" + + @abstractmethod + def address_op(self) -> Bytecode: + """Bytecode that reads the current target address.""" + + @abstractmethod + def next_op(self) -> Bytecode: + """Bytecode that advances to the next target address.""" + + +class Create2AddressSource(AddressSource): + """Targets derived from a CREATE2 factory deployment.""" + + def __init__(self, *, init_code: bytes, index_op: Bytecode) -> None: + """Build the CREATE2 preimage layout for *init_code*.""" + self._layout = Create2PreimageLayout( + factory_address=DETERMINISTIC_FACTORY_ADDRESS, + salt=index_op, + init_code_hash=keccak256(init_code), + ) + + @property + def setup(self) -> Bytecode: + """Bytecode that initializes the in-memory address layout.""" + return self._layout + + @property + def memory_size(self) -> int: + """Bytes of memory occupied by the CREATE2 preimage layout.""" + return self._layout.offset + 96 + + def address_op(self) -> Bytecode: + """Bytecode that reads the current target address.""" + return self._layout.address_op() + + def next_op(self) -> Bytecode: + """Bytecode that advances to the next target address.""" + return self._layout.increment_salt_op() + + +class SequentialAddressSource(AddressSource): + """Targets at a contiguous address range starting from a base.""" + + def __init__(self, *, base_addr: Hash, index_op: Bytecode) -> None: + """Build a sequential layout starting at *base_addr*.""" + self._layout = SequentialAddressLayout( + starting_address=Op.ADD(base_addr, index_op), + increment=1, + ) + + @property + def setup(self) -> Bytecode: + """Bytecode that initializes the in-memory address layout.""" + return self._layout + + @property + def memory_size(self) -> int: + """Bytes of memory occupied by the sequential address layout.""" + return self._layout.offset + 32 + + def address_op(self) -> Bytecode: + """Bytecode that reads the current target address.""" + return self._layout.address_op() + + def next_op(self) -> Bytecode: + """Bytecode that advances to the next target address.""" + return self._layout.increment_address_op() + + +@dataclass(frozen=True) +class AccountCreator: + """Account creation and location helper with address iteration.""" + + # Modes whose target is a CREATE2-deployed contract. + contract_modes: ClassVar[frozenset[AccountMode]] = frozenset( + { + AccountMode.EXISTING_CONTRACT_MINIMAL, + AccountMode.EXISTING_CONTRACT_SAME_MAX, + AccountMode.EXISTING_CONTRACT_DIFF_MAX, + AccountMode.EXISTING_CONTRACT_JUMPDEST, + } + ) + + mode: AccountMode + code_size: int = DEFAULT_CODE_SIZE + + def __post_init__(self) -> None: + """Reject anything that is not a known `AccountMode`.""" + if not isinstance(self.mode, AccountMode): + raise ValueError(f"unknown account mode: {self.mode!r}") + + @property + def derives_address_via_create2(self) -> bool: + """Whether the target address is derived via CREATE2.""" + return self.mode in self.contract_modes + + @property + def contract_initcode(self) -> ContractInitcode: + """Return the initcode generator that deploys this account.""" + match self.mode: + case AccountMode.EXISTING_CONTRACT_MINIMAL: + return MinimalContractInitcode() + case AccountMode.EXISTING_CONTRACT_SAME_MAX: + return StopJumpdestInitcode( + code_size=self.code_size, diff=False + ) + case AccountMode.EXISTING_CONTRACT_DIFF_MAX: + return StopJumpdestInitcode( + code_size=self.code_size, diff=True + ) + case AccountMode.EXISTING_CONTRACT_JUMPDEST: + return JochemnetPredeployContractInitcode( + code_size=self.code_size + ) + case _: + raise ValueError(f"{self.mode.name} is not a contract") + + @property + def initcode(self) -> bytes: + """Return the CREATE2 initcode that deploys this account.""" + return bytes(self.contract_initcode) + + @property + def runtime_size(self) -> int: + """Return the deployed runtime size in bytes.""" + return self.contract_initcode.runtime_size + + @property + def has_execution_code(self) -> bool: + """Whether a call into this account executes deployed code.""" + return self.mode in self.contract_modes + + @property + def execution_code(self) -> Bytecode: + """Return the code executed when this account is called.""" + return self.contract_initcode.execution_code + + def address_source(self, index_op: Bytecode) -> AddressSource: + """Return the source that yields successive target addresses.""" + if self.derives_address_via_create2: + return Create2AddressSource( + init_code=self.initcode, index_op=index_op + ) + match self.mode: + case AccountMode.EXISTING_EOA: + # Spamoor EOA creator starts created accounts at 0x1000. + # https://github.com/CPerezz/spamoor/pull/12 + base_addr = Hash(0x1000) + case AccountMode.NON_EXISTING_ACCOUNT: + # An address range that is never funded. + base_addr = keccak256(b"random") + case _: + raise ValueError(f"{self.mode.name} has no address source") + return SequentialAddressSource(base_addr=base_addr, index_op=index_op) diff --git a/tests/benchmark/helper/account_sender_receiver.py b/tests/benchmark/helper/account_sender_receiver.py new file mode 100644 index 00000000000..7a860607935 --- /dev/null +++ b/tests/benchmark/helper/account_sender_receiver.py @@ -0,0 +1,80 @@ +"""Deterministic benchmark sender and receiver accounts.""" + +import itertools +from typing import Generator + +from execution_testing import ( + DETERMINISTIC_FACTORY_ADDRESS, + EOA, + Address, + compute_create2_address, + compute_create_address, + keccak256, +) + +# Deterministic sender pool, pre-funded via system-contract withdrawals +# (funding.txt) during payload generation. Kept out of the pre-allocation so +# the accounts stay uncached. +SENDER_BASE_KEY = int.from_bytes( + keccak256(b"gas-repricings-private-key"), "big" +) + +# Deterministic EIP-7702 delegate authorities: +# Authority i delegates to EXISTING_CONTRACT_DIFF_MAX receiver i. +DELEGATE_BASE_KEY = int.from_bytes( + keccak256(b"gas-repricings-7702-delegate"), "big" +) + +# Bittrex controller mainnet address: it created 1.5M contracts via CREATE with +# deterministic addresses, none self-destructed. Used as existing-contract +# receivers. +BITTREX_CONTROLLER_ADDRESS = Address( + 0xA3C1E324CA1CE40DB73ED6026C4A177F099B5770 +) + + +def yield_distinct_sender() -> Generator[EOA, None, None]: + """Yield deterministic sender EOAs pre-funded on-chain.""" + for i in itertools.count(0): + yield EOA(key=SENDER_BASE_KEY + i) + + +def yield_distinct_create2_receiver( + initcode: bytes, +) -> Generator[Address, None, None]: + """Yield addresses deployed by the deterministic CREATE2 factory.""" + for salt in itertools.count(0): + yield compute_create2_address( + address=DETERMINISTIC_FACTORY_ADDRESS, + salt=salt, + initcode=initcode, + ) + + +def yield_distinct_contract_receiver() -> Generator[Address, None, None]: + """Yield contracts created by the Bittrex controller via CREATE.""" + for nonce in itertools.count(2): + yield compute_create_address( + address=BITTREX_CONTROLLER_ADDRESS, nonce=nonce + ) + + +def yield_distinct_existent_receiver() -> Generator[Address, None, None]: + """ + Yield existing balance-only EOAs on bloatnet, pre-funded by Spamoor + (https://github.com/CPerezz/spamoor/pull/12). + """ + for address in itertools.count(0x1000): + yield Address(address) + + +def yield_distinct_nonexistent_receiver() -> Generator[Address, None, None]: + """Yield non-existent accounts starting from keccak256('random').""" + for address in itertools.count(0xF3CF193BB4AF1022AF7D2089F37D8BAE7157B85F): + yield Address(address) + + +def yield_distinct_delegate_receiver() -> Generator[Address, None, None]: + """Yield EOA delegating to a distinct EXISTING_CONTRACT_DIFF_MAX.""" + for i in itertools.count(0): + yield EOA(key=DELEGATE_BASE_KEY + i) diff --git a/tests/benchmark/stateful/bloatnet/test_account_query.py b/tests/benchmark/stateful/bloatnet/test_account_query.py index bbcaa785d7b..c96e2420958 100644 --- a/tests/benchmark/stateful/bloatnet/test_account_query.py +++ b/tests/benchmark/stateful/bloatnet/test_account_query.py @@ -1,16 +1,4 @@ -""" -Benchmark operations that require querying the account state, either on the -current executing account or on a target account. - -Supported Opcodes: -- SELFBALANCE -- CODESIZE -- CODECOPY -- EXTCODESIZE -- EXTCODEHASH -- EXTCODECOPY -- BALANCE -""" +"""Benchmark operations that query the state of a target account.""" from typing import Any @@ -19,8 +7,24 @@ Account, Alloc, BenchmarkTestFiller, + Bytecode, + Fork, + Hash, + IteratingBytecode, JumpLoopGenerator, Op, + TestPhaseManager, + Transaction, + While, +) + +from tests.benchmark.helper.account_creator import ( + AccountCreator, + AccountMode, +) +from tests.benchmark.stateful.helpers import ( + CacheStrategy, + build_cache_strategy_blocks, ) @@ -106,3 +110,221 @@ def test_ext_account_query_warm( attack_block=Op.POP(opcode(address=Op.MLOAD(0))), ), ) + + +def account_access_params() -> list: + """Generate (opcode, value_sent, account_mode, overhead_baseline).""" + target_opcodes = [ + Op.BALANCE, + # CALL* + Op.CALL, + Op.CALLCODE, + Op.STATICCALL, + Op.DELEGATECALL, + # EXTCODE* + Op.EXTCODECOPY, + Op.EXTCODESIZE, + Op.EXTCODEHASH, + ] + value_bearing_opcodes = {Op.CALL, Op.CALLCODE} + params = [] + for mode in AccountMode: + for op in target_opcodes: + values = (0, 1) if op in value_bearing_opcodes else (0,) + for value_sent in values: + params.append(pytest.param(op, value_sent, mode, False)) + if AccountCreator(mode).derives_address_via_create2: + params.append(pytest.param(op, value_sent, mode, True)) + return params + + +@pytest.mark.repricing +@pytest.mark.parametrize("cache_strategy", [CacheStrategy.NO_CACHE]) +@pytest.mark.parametrize( + "opcode,value_sent,account_mode,overhead_baseline", account_access_params() +) +def test_account_access( + benchmark_test: BenchmarkTestFiller, + pre: Alloc, + fork: Fork, + opcode: Op, + value_sent: int, + gas_benchmark_value: int, + fixed_opcode_count: int | None, + account_mode: AccountMode, + overhead_baseline: bool, + cache_strategy: CacheStrategy, +) -> None: + """Benchmark account access with caching strategies.""" + account_creator = AccountCreator(account_mode) + address_source = account_creator.address_source(Op.CALLDATALOAD(0)) + increment_op = address_source.next_op() + + cache_op = ( + Op.POP( + Op.BALANCE( + address=address_source.address_op(), + # Gas accounting + address_warm=False, + ) + ) + if cache_strategy == CacheStrategy.CACHE_TX + else Bytecode() + ) + + access_warm = cache_strategy == CacheStrategy.CACHE_TX + + setup_code = address_source.setup + + if opcode == Op.EXTCODECOPY: + copy_size = 1024 + copy_dest = address_source.memory_size + attack_call = opcode( + address=address_source.address_op(), + dest_offset=copy_dest, + size=copy_size, + # Gas accounting + data_size=copy_size, + address_warm=access_warm, + ) + # Expand memory during setup so the loop cost is constant. + setup_code += Op.MSTORE8( + copy_dest + copy_size - 1, + 0, + # Gas accounting + old_memory_size=address_source.memory_size, + new_memory_size=copy_dest + copy_size, + ) + elif opcode in (Op.CALL, Op.CALLCODE): + attack_call = Op.POP( + opcode( + address=address_source.address_op(), + value=value_sent, + # Gas accounting + address_warm=access_warm, + value_transfer=value_sent > 0, + account_new=( + opcode == Op.CALL + and value_sent > 0 + and account_mode == AccountMode.NON_EXISTING_ACCOUNT + ), + ) + ) + else: + # BALANCE, STATICCALL, DELEGATECALL, EXTCODESIZE, EXTCODEHASH + attack_call = Op.POP( + opcode( + address=address_source.address_op(), + # Gas accounting + address_warm=access_warm, + ) + ) + + setup_code += Op.ADD(1, Op.CALLDATALOAD(32)) + Op.CALLDATALOAD(0) + + loop_code = While( + body=cache_op + attack_call + increment_op, + condition=Op.PUSH1(1) + Op.ADD + Op.DUP1 + Op.DUP3 + Op.GT, + ) + + call_operations = (Op.CALL, Op.CALLCODE, Op.DELEGATECALL, Op.STATICCALL) + executes_contract_code = ( + opcode in call_operations and account_creator.has_execution_code + ) + iterating_subcall: Bytecode = ( + account_creator.execution_code if executes_contract_code else Op.STOP + ) + + attack_code = IteratingBytecode( + setup=setup_code, + iterating=loop_code, + iterating_subcall=iterating_subcall, + ) + + # Calldata generator for each transaction of the iterating bytecode. + def calldata(iteration_count: int, start_iteration: int) -> bytes: + index_end = start_iteration + iteration_count - 1 + return Hash(start_iteration) + Hash(index_end) + + run_code = attack_code + target_opcode = opcode + + if overhead_baseline: + keccak_op = Op.POP(address_source.address_op()) + if cache_strategy == CacheStrategy.CACHE_TX: + keccak_op = keccak_op * 2 + + run_code = IteratingBytecode( + setup=address_source.setup + + Op.ADD(1, Op.CALLDATALOAD(32)) + + Op.CALLDATALOAD(0), + iterating=While( + body=keccak_op + increment_op, + condition=Op.PUSH1(1) + Op.ADD + Op.DUP1 + Op.DUP3 + Op.GT, + ), + ) + target_opcode = Op.SHA3 + + total_iterations = None + if fixed_opcode_count is not None: + total_iterations = int(fixed_opcode_count * 1000) + elif overhead_baseline: + total_iterations = sum( + attack_code.tx_iterations_by_gas_limit( + fork=fork, + gas_limit=gas_benchmark_value, + calldata=calldata, + ) + ) + + attack_address = pre.deploy_contract(code=run_code, balance=10**21) + + post: dict = {} + cache_txs = [] + + with TestPhaseManager.execution(): + attack_sender = pre.fund_eoa() + if total_iterations is not None: + attack_txs = list( + run_code.transactions_by_total_iteration_count( + fork=fork, + total_iterations=total_iterations, + sender=attack_sender, + to=attack_address, + calldata=calldata, + ) + ) + else: + attack_txs = list( + run_code.transactions_by_gas_limit( + fork=fork, + gas_limit=gas_benchmark_value, + sender=attack_sender, + to=attack_address, + calldata=calldata, + ) + ) + + if cache_strategy == CacheStrategy.CACHE_PREVIOUS_BLOCK: + with TestPhaseManager.setup(): + cache_sender = pre.fund_eoa() + for tx in attack_txs: + cache_txs.append( + Transaction( + gas_limit=tx.gas_limit, + data=tx.data, + to=attack_address, + sender=cache_sender, + ) + ) + + blocks = build_cache_strategy_blocks(cache_strategy, attack_txs, cache_txs) + + benchmark_test( + pre=pre, + post=post, + blocks=blocks, + target_opcode=target_opcode, + skip_gas_used_validation=True, + expected_receipt_status=1, + ) diff --git a/tests/benchmark/stateful/bloatnet/test_create2_access.py b/tests/benchmark/stateful/bloatnet/test_create2_access.py index 5ae01e25cc0..9d29933434f 100644 --- a/tests/benchmark/stateful/bloatnet/test_create2_access.py +++ b/tests/benchmark/stateful/bloatnet/test_create2_access.py @@ -1,24 +1,21 @@ -""" -abstract: CREATE2 deploy + immediate access benchmark cases. - - These tests benchmark the deploy-then-access pattern: CREATE2 a - contract, then immediately query it with EXTCODEHASH, BALANCE, or - EXTCODECOPY in the same transaction. This tests whether clients - efficiently serve state that was just written to the trie. -""" +"""CREATE2 deploy-then-immediate-access benchmarks.""" import pytest from execution_testing import ( + Account, + Address, Alloc, BenchmarkTestFiller, Block, Bytecode, Fork, Hash, + Header, Initcode, IteratingBytecode, Op, While, + compute_create2_address, ) from tests.benchmark.stateful.helpers import ( @@ -29,31 +26,6 @@ REFERENCE_SPEC_VERSION = "1.0" -# CREATE2 + ACCESS BENCHMARK ARCHITECTURE: -# -# [Init Code Holder Contract] ──── Runtime code = init code bytes -# │ -# │ EXTCODECOPY by attack contract during setup -# │ -# [Attack Contract] -# │ Setup: -# │ 1. EXTCODECOPY init code from holder into MEM[0..N] -# │ 2. Store starting counter at MEM[N..N+32] -# │ -# │ Loop(i=0 to M): -# │ 1. CREATE2(value=0, offset=0, size=N, salt=counter) -# │ → deploys new contract, returns address -# │ 2. EXTCODEHASH / BALANCE / EXTCODECOPY on address -# │ 3. Increment counter -# -# WHY IT STRESSES CLIENTS: -# - Each CREATE2 inserts a new account + code into the trie -# - Immediate access tests if the just-written data is efficiently -# served from write caches vs requiring a trie re-read -# - Code deposit cost (200 gas/byte) dominates: larger code = -# fewer iterations but more trie data per cycle - - @pytest.mark.parametrize( "code_size", [32, 256, 1024], @@ -71,13 +43,7 @@ def test_create2_immediate_access( code_size: int, access_opcode: Op, ) -> None: - """ - Benchmark CREATE2 followed by immediate opcode access. - - Deploy a contract via CREATE2, then immediately query it with the - specified access opcode. Each iteration creates a new trie entry - and reads from it, stressing the deploy-then-access path. - """ + """Benchmark CREATE2 followed by immediate opcode access.""" # Build init code that deploys `code_size` bytes of zeros deploy_code = bytes(code_size) initcode = Initcode(deploy_code=deploy_code) @@ -161,12 +127,10 @@ def test_create2_immediate_access( condition=DECREMENT_COUNTER_CONDITION, ) - subcall_cost = initcode.execution_gas(fork) + initcode.deployment_gas(fork) - code = IteratingBytecode( setup=setup, iterating=loop, - iterating_subcall=subcall_cost, + iterating_subcall=initcode, ) attack_contract_address = pre.deploy_contract(code=code) @@ -183,8 +147,35 @@ def calldata_builder(iteration_count: int, start_iteration: int) -> bytes: ) ) + # Salts are contiguous from 0: calldata[0:32] holds each tx's + # iteration count. + total_iterations = sum(int.from_bytes(tx.data[:32], "big") for tx in txs) + + post: dict[Address, Account | None] = { + compute_create2_address( + address=attack_contract_address, salt=salt, initcode=initcode + ): Account(nonce=1, code=deploy_code) + for salt in range(total_iterations) + } + post[ + compute_create2_address( + address=attack_contract_address, + salt=total_iterations, + initcode=initcode, + ) + ] = Account.NONEXISTENT + + expected_block_gas_used = sum(tx.block_gas_cost for tx in txs) + expected_benchmark_gas_used = sum(tx.gas_cost for tx in txs) + + block = Block( + txs=txs, + header_verify=Header(gas_used=expected_block_gas_used), + ) + benchmark_test( pre=pre, - blocks=[Block(txs=txs)], - skip_gas_used_validation=True, + post=post, + blocks=[block], + expected_benchmark_gas_used=expected_benchmark_gas_used, ) diff --git a/tests/benchmark/stateful/bloatnet/test_delegatecall_chain.py b/tests/benchmark/stateful/bloatnet/test_delegatecall_chain.py deleted file mode 100644 index 51eb392c235..00000000000 --- a/tests/benchmark/stateful/bloatnet/test_delegatecall_chain.py +++ /dev/null @@ -1,103 +0,0 @@ -""" -abstract: DELEGATECALL chain benchmark cases (TODO — needs spamoor deploy). - - This file is a placeholder for DELEGATECALL chain benchmarks that - require heavy pre-deployed state via spamoor. See the design notes - at the end of this file for the planned test architecture. -""" - -REFERENCE_SPEC_GIT_PATH = "DUMMY/bloatnet.md" -REFERENCE_SPEC_VERSION = "1.0" - - -# ═══════════════════════════════════════════════════════════════════════ -# TODO: DELEGATECALL Chain + Cold Code Loading + SSTORE Benchmarks -# ═══════════════════════════════════════════════════════════════════════ -# -# STATUS: Not implemented. Requires 50-100 small "library" contracts -# pre-deployed and spread across the trie for realistic cold-access -# patterns. These should be deployed via spamoor and the test run -# with `--execute remote`. -# -# ─── CONCEPT ────────────────────────────────────────────────────────── -# -# DELEGATECALL preserves the caller's storage context while loading -# code from a cold contract. A chain A→B→C→D→E means 4 cold code -# loads (2,600 gas each) but all SSTOREs write to A's storage. This -# is the real-world pattern used by diamond proxies and modular -# contract architectures (e.g., EIP-2535 Diamonds). -# -# [Caller EOA] -# │ -# └──► [Entry Contract A] (via EIP-7702 delegation) -# │ DELEGATECALL ──► [Library B] (cold code load) -# │ │ DELEGATECALL ──► [Library C] -# │ │ │ ... -# │ │ └── SSTORE -# │ │ (writes -# │ │ to A's -# │ │ storage) -# └── All storage mutations land on A -# -# ─── DEPLOYMENT REQUIREMENTS ───────────────────────────────────────── -# -# 1. Deploy 50-100 small "library" contracts via spamoor -# - Each library is ~50 bytes: DELEGATECALL forward + SLOAD/SSTORE -# - Libraries should be spread across the trie (different address -# prefixes) to ensure cold account access on each DELEGATECALL -# - Use CREATE2 with varied salts for deterministic, spread addresses -# -# 2. Deploy an "entry" contract that knows the library addresses -# - Takes a chain depth parameter and the library address list -# - Initiates the DELEGATECALL chain -# -# 3. Alternatively, use EIP-7702 delegation on an EOA: -# - Authority EOA delegates to a "chain executor" contract -# - Chain executor DELEGATECALLs through the library contracts -# - SSTOREs land on the authority's storage -# -# ─── PLANNED VARIANTS ──────────────────────────────────────────────── -# -# | Variant | Depth | Gas/chain | Stress target | -# |----------------------------|-------|-----------|-------------------| -# | Pure cold chain | 3,5,8 | ~8K–21K | Cold code loading | -# | Chain + SSTORE at leaf | 5 | ~35K | Cold + storage | -# | Chain + SLOAD at each hop | 5 | ~24K | Storage reads | -# | | | | through delegation| -# -# Gas breakdown per hop (cold DELEGATECALL): -# - GAS_COLD_ACCOUNT_ACCESS: 2,600 (includes DELEGATECALL base) -# - Code loading overhead: varies by library size -# -# At depth 5 (all cold): -# - 5 * 2,600 = 13,000 gas for cold access -# - Plus SSTORE at leaf: 22,100 (cold SET = 2,100 + 20,000) -# or 5,000 (cold RESET = 2,100 + 2,900) -# -# ─── WHY THIS NEEDS SPAMOOR ───────────────────────────────────────── -# -# For realistic cold-access patterns, the library contracts must be: -# 1. Deployed at addresses spread across the trie (not sequential) -# 2. Present in the actual chain state (not just test pre-state) -# 3. Numerous enough (50-100) that a single block's DELEGATECALL -# chains encounter many cold accounts -# -# With `--execute remote`, the libraries persist across test runs and -# the trie structure reflects real-world deployment patterns. -# -# ─── IMPLEMENTATION NOTES ──────────────────────────────────────────── -# -# Library bytecode template (minimal DELEGATECALL forwarder): -# - Read next-hop address from calldata -# - DELEGATECALL(gas=GAS, next_hop, 0, CALLDATASIZE, 0, 0) -# - Or at leaf: SSTORE(CALLDATALOAD(0), CALLDATALOAD(32)) -# -# The entry contract / EIP-7702 executor: -# - Receives: [chain_depth, library_addrs[], slot, value] -# - Loops: for i in 0..chain_depth, DELEGATECALL to library[i] -# - Each library forwards to the next, final one does SSTORE -# -# Key metric: ratio of cold code loading gas to useful work (SSTORE). -# At depth 5 with cold SET: ~13,000 gas for cold access vs ~22,100 -# for SSTORE = 37% overhead just from the delegation chain. -# ═══════════════════════════════════════════════════════════════════════ diff --git a/tests/benchmark/stateful/bloatnet/test_extcodesize_bytecode_sizes.py b/tests/benchmark/stateful/bloatnet/test_extcodesize_bytecode_sizes.py index 8aedd7c376d..386b4a81792 100644 --- a/tests/benchmark/stateful/bloatnet/test_extcodesize_bytecode_sizes.py +++ b/tests/benchmark/stateful/bloatnet/test_extcodesize_bytecode_sizes.py @@ -1,66 +1,4 @@ -r""" -Test EXTCODESIZE with parametrized bytecode sizes using CREATE2 factory. - -This benchmark measures the performance impact of `EXTCODESIZE` operations -on contracts of varying sizes (0.5KB to 24KB). -It stresses client state loading by maximizing **cold** EXTCODESIZE calls. - -Designed for execute mode only - contracts must be pre-deployed. - -## Gas-Based Loop Strategy - -The attack contract uses a gas-based loop exit (per Jochem's suggestion): -1. Reads current salt from storage slot 0 -2. Loops while gas > 50K, calling EXTCODESIZE on CREATE2 addresses -3. Saves final salt to storage slot 0 when exiting -4. Next TX automatically resumes from where previous left off - -This eliminates manual gas calculations - the contract self-regulates. - -## Test Block Structure - -┌───────────────────────────────────────────────────────────────┐ -│ Test Block │ -├───────────────────────────────────────────────────────────────┤ -│ TX1: Attack (~16M gas) │ -│ └─> Loops EXTCODESIZE until gas < 50K, saves salt │ -│ │ -│ TX2: Attack (~16M gas) │ -│ └─> Resumes from TX1's salt, continues looping │ -│ │ -│ TX3: Attack (~16M gas) │ -│ └─> Resumes from TX2's salt, continues looping │ -└───────────────────────────────────────────────────────────────┘ - -Post-state verification checks attack contract's slot 1 for expected size. - -### Execute a Single Size - -```bash -uv run execute remote \\ - --fork Osaka \\ - --rpc-endpoint http://127.0.0.1:8545 \\ - --rpc-seed-key <SEED_KEY> \\ - --rpc-chain-id 1337 \\ - --address-stubs tests/benchmark/stateful/bloatnet/stubs.json \\ - -- --gas-benchmark-values 60 \\ - tests/benchmark/stateful/bloatnet/test_extcodesize_bytecode_sizes.py \\ - -k '24KB' -v -``` - -### Execute All Sizes - -```bash -uv run execute remote \\ - --fork Osaka \\ - --rpc-endpoint http://127.0.0.1:8545 \\ - --rpc-seed-key <SEED_KEY> \\ - --rpc-chain-id 1337 \\ - --address-stubs tests/benchmark/stateful/bloatnet/stubs.json \\ - -- --gas-benchmark-values 60 \\ - tests/benchmark/stateful/bloatnet/test_extcodesize_bytecode_sizes.py -v -``` -""" +"""Cold EXTCODESIZE benchmarks across pre-deployed bytecode sizes.""" import pytest from execution_testing import ( @@ -101,19 +39,7 @@ def get_factory_stub_name(size_kb: float) -> str: def build_attack_contract(factory_address: Address) -> Bytecode: - """ - Benchmark EXTCODESIZE calls with gas-based loop exit. - - Storage Layout: - - Slot 0: current salt (persists across transactions) - - Slot 1: last EXTCODESIZE result (for verification) - - CREATE2 Memory Layout (85 bytes from offset 11): - - MEM[11] = 0xFF prefix - - MEM[12-31] = factory address (20 bytes) - - MEM[32-63] = salt (32 bytes) - - MEM[64-95] = init_code_hash (32 bytes) - """ + """Build the EXTCODESIZE attack contract with a gas-based loop exit.""" gas_reserve = 50_000 # Reserve for 2x SSTORE + cleanup num_deployed_offset = 96 init_code_hash_offset = num_deployed_offset + 32 @@ -177,17 +103,7 @@ def test_extcodesize_bytecode_sizes( gas_benchmark_value: int, tx_gas_limit: int, ) -> None: - """ - Execute EXTCODESIZE benchmark against pre-deployed contracts. - - Uses a gas-based loop exit strategy: - 1. Attack contract reads/writes salt from storage slot 0 - 2. Loop exits when gas < 50K, saves salt for next TX - 3. Each TX automatically resumes from where previous left off - - Post-state verifies that the attack contract's slot 1 contains the - expected bytecode size (last EXTCODESIZE result). - """ + """Execute EXTCODESIZE benchmark against pre-deployed contracts.""" expected_size_bytes = int(bytecode_size_kb * 1024) # Get factory stub name for this size diff --git a/tests/benchmark/stateful/bloatnet/test_multi_opcode.py b/tests/benchmark/stateful/bloatnet/test_multi_opcode.py index 4e38786bb37..19301c47e74 100755 --- a/tests/benchmark/stateful/bloatnet/test_multi_opcode.py +++ b/tests/benchmark/stateful/bloatnet/test_multi_opcode.py @@ -1,14 +1,10 @@ -""" -abstract: BloatNet bench cases extracted from https://hackmd.io/9icZeLN7R0Sk5mIjKlZAHQ. - - The idea of all these tests is to stress client implementations to find out - where the limits of processing are focusing specifically on state-related - operations. -""" +"""BloatNet benchmarks from https://hackmd.io/9icZeLN7R0Sk5mIjKlZAHQ.""" import pytest from execution_testing import ( AccessList, + Account, + Address, Alloc, BenchmarkTestFiller, Block, @@ -16,9 +12,12 @@ Conditional, Create2PreimageLayout, Fork, + Hash, + IteratingBytecode, Op, Transaction, While, + keccak256, ) from tests.benchmark.stateful.helpers import ( @@ -32,30 +31,6 @@ REFERENCE_SPEC_VERSION = "1.0" -# BLOATNET ARCHITECTURE: -# -# [Initcode Contract] [Factory Contract] [Deployed Contracts] -# (varies by stub) (varies by stub) (N x each) -# │ │ │ -# │ EXTCODECOPY │ CREATE2(salt++) │ -# └──────────────► ├────────────────► Contract_0 -# ├────────────────► Contract_1 -# └────────────────► Contract_N -# -# [Attack Contract] ──STATICCALL──► [Factory.getConfig()] -# │ returns: (N, hash) -# └─► Loop(i=0 to N): -# 1. Compute CREATE2 addr from factory|salt|hash -# 2. BALANCE(addr) → 2600 gas (cold) -# 3. <second_opcode>(addr) → varies (warm) -# -# HOW IT WORKS: -# 1. Factory uses EXTCODECOPY to load initcode -# 2. Each CREATE2 produces unique bytecode (via ADDRESS) -# 3. Shared initcode hash enables deterministic addresses -# 4. Attack rapidly accesses all contracts per factory stub - - @pytest.mark.stub_parametrize("factory_stub", "bloatnet_factory_") @pytest.mark.parametrize( "second_opcode", @@ -76,10 +51,7 @@ def test_bloatnet_balance_opcode( second_opcode: Op, factory_stub: str, ) -> None: - """ - Benchmark BALANCE paired with a second opcode on bloatnet - factory contracts. - """ + """Benchmark BALANCE paired with a second opcode on bloatnet factories.""" factory_address = pre.deploy_contract( code=Bytecode(), stub=factory_stub, @@ -203,22 +175,6 @@ def test_bloatnet_balance_opcode( ) -# CALL+VALUE BENCHMARK ARCHITECTURE: -# -# test_bloatnet_call_value_existing: -# Same factory pattern as test_bloatnet_balance_opcode, but performs -# CALL with value=1 wei to each factory contract. The subcall fails -# (insufficient gas for 24KB bytecode), but CALL_VALUE (9000 gas) -# is still charged on top of the cold account access cost. -# -# test_bloatnet_call_value_new_account: -# Generates unique addresses from keccak256(counter) and CALLs each with -# value=1 wei. Since these addresses have no code, the subcall succeeds -# (via the 2300 gas stipend), transferring value and creating a new account. -# Each iteration costs ~36,600 gas (cold + value + new_account), -# stressing trie expansion through massive new account creation. - - @pytest.mark.stub_parametrize("factory_stub", "bloatnet_factory_") def test_bloatnet_call_value_existing( benchmark_test: BenchmarkTestFiller, @@ -228,15 +184,7 @@ def test_bloatnet_call_value_existing( tx_gas_limit: int, factory_stub: str, ) -> None: - """ - Benchmark CALL with value transfer to cold existing factory contracts. - - Unlike the existing CALL test which uses gas=1 and value=0, this test - passes value=1 wei per call, adding CALL_VALUE (9000 gas) to each - cold account access. The subcall fails (insufficient gas for bytecode - execution), so value is not actually transferred, but the gas penalty - is still charged. - """ + """Benchmark CALL with value transfer to cold existing contracts.""" factory_address = pre.deploy_contract( code=Bytecode(), stub=factory_stub, @@ -322,21 +270,8 @@ def test_bloatnet_call_value_new_account( pre: Alloc, fork: Fork, gas_benchmark_value: int, - tx_gas_limit: int, ) -> None: - """ - Benchmark CALL with value transfer to non-existent accounts. - - Generate unique addresses via keccak256(counter) and CALL each with - value=1 wei. Since these addresses have no code, the subcall succeeds - (via the 2300 gas stipend), transferring value and creating a new - account in the trie. Each iteration costs ~36,600 gas: - - GAS_COLD_ACCOUNT_ACCESS: 2,600 - - CALL_VALUE: 9,000 - - NEW_ACCOUNT: 25,000 - - This stresses trie expansion through massive new account creation. - """ + """Benchmark CALL with value transfer to non-existent accounts.""" # Memory layout: MEM[0..31] = counter (incremented each iteration) setup = ( Op.MSTORE( @@ -376,28 +311,53 @@ def test_bloatnet_call_value_new_account( ) # Contract Deployment — needs balance for value transfers (1 wei each) - code = setup + loop + code = IteratingBytecode( + setup=setup, + iterating=loop, + ) + + initial_balance = 10**9 attack_contract_address = pre.deploy_contract( code=code, - balance=10**18, # 1 ETH, enough for all iterations + balance=initial_balance, ) - # Gas Accounting - txs, total_gas_consumed = build_benchmark_txs( - pre=pre, - fork=fork, - gas_benchmark_value=gas_benchmark_value, - tx_gas_limit=tx_gas_limit, - attack_contract_address=attack_contract_address, - setup_cost=setup.gas_cost(fork), - iteration_cost=loop.gas_cost(fork), + def calldata_builder(iteration_count: int, start_iteration: int) -> bytes: + return bytes(Hash(iteration_count) + Hash(start_iteration)) + + txs = list( + code.transactions_by_gas_limit( + fork=fork, + gas_limit=gas_benchmark_value, + sender=pre.fund_eoa(), + to=attack_contract_address, + calldata=calldata_builder, + ) + ) + + total_iterations = sum(int.from_bytes(tx.data[:32], "big") for tx in txs) + + def new_account_address(counter: int) -> Address: + return Address(bytes(keccak256(counter.to_bytes(32, "big")))[12:]) + + post = { + new_account_address(counter): Account(balance=1) + for counter in range(total_iterations) + } + post[attack_contract_address] = Account( + balance=initial_balance - total_iterations + ) + + expected_gas_used = ( + sum(tx.gas_cost for tx in txs) + - fork.gas_costs().CALL_STIPEND * total_iterations ) benchmark_test( pre=pre, + post=post, blocks=[Block(txs=txs)], - expected_benchmark_gas_used=total_gas_consumed, - skip_gas_used_validation=True, + expected_benchmark_gas_used=expected_gas_used, ) @@ -422,17 +382,7 @@ def test_mixed_sload_sstore( sload_percent: int, sstore_percent: int, ) -> None: - """ - Benchmark mixed SLOAD/SSTORE on bloatnet. - - Uses runtime gas checking instead of pre-calculated iteration - counts. Each ERC20 contract has its own implementation with - different per-call gas costs, so a single gas model cannot - predict the right iteration count. Instead the contract - checks remaining gas via the GAS opcode each iteration and - splits the budget between SLOAD and SSTORE phases using a - pre-computed gas floor. - """ + """Benchmark mixed SLOAD/SSTORE ratios on bloatnet ERC20 contracts.""" # The gas threshold is the minimum gas reserved to exit the # loops and execute cleanup (SSTORE to persist slot offset). # 150_000 is conservative: cold approve ~25K + cleanup ~20K. diff --git a/tests/benchmark/stateful/bloatnet/test_single_opcode.py b/tests/benchmark/stateful/bloatnet/test_single_opcode.py index eb5cb4c25a3..5a52f083807 100644 --- a/tests/benchmark/stateful/bloatnet/test_single_opcode.py +++ b/tests/benchmark/stateful/bloatnet/test_single_opcode.py @@ -7,7 +7,6 @@ to benchmark specific state-handling bottlenecks. """ -from enum import Enum, auto from functools import partial from typing import Any, Callable, Generator, List @@ -25,18 +24,16 @@ Block, BlockAccessListExpectation, Bytecode, - CreatePreimageLayout, Fork, Hash, IteratingBytecode, JumpLoopGenerator, Op, - SequentialAddressLayout, + RecipientType, Storage, TestPhaseManager, Transaction, While, - keccak256, ) from execution_testing.base_types.base_types import Number @@ -691,7 +688,6 @@ def test_sstore_bloated( setup=setup, iterating=loop, cleanup=Op.STOP, - iterating_state_gas=loop.state_cost(fork), ) authority = pre.stub_eoa(token_name) @@ -709,6 +705,7 @@ def tx_generator(sender: EOA) -> list[Transaction]: to=authority, start_iteration=start_slot, calldata=calldata_gen, + recipient_type=RecipientType.DELEGATION_7702, ) ) @@ -1181,6 +1178,7 @@ def test_sstore_variants( calldata=calldata_gen, access_list=access_list_gen, start_iteration=1, + recipient_type=RecipientType.DELEGATION_7702, ) ) @@ -1214,6 +1212,7 @@ def test_sstore_variants( calldata=calldata_gen, start_iteration=1, access_list=access_list_gen, + recipient_type=RecipientType.DELEGATION_7702, ) ) @@ -1275,6 +1274,9 @@ def test_sstore_variants( 0, [1, 0, 1, 0], id="oscillation_4x_from_zero", + marks=pytest.mark.skip( + reason="net-zero state gas; degenerates to a regular-gas loop" + ), ), pytest.param( 0, @@ -1337,6 +1339,7 @@ def test_sstore_dirty_transitions( calldata=calldata_gen, access_list=access_list_gen, start_iteration=1, + recipient_type=RecipientType.DELEGATION_7702, ) ) @@ -1369,6 +1372,7 @@ def test_sstore_dirty_transitions( calldata=calldata_gen, start_iteration=1, access_list=access_list_gen, + recipient_type=RecipientType.DELEGATION_7702, ) ) @@ -1470,6 +1474,7 @@ def test_storage_sload_benchmark( calldata=calldata_gen, access_list=access_list_gen, start_iteration=1, + recipient_type=RecipientType.DELEGATION_7702, ) ) @@ -1503,6 +1508,7 @@ def test_storage_sload_benchmark( calldata=calldata_gen, start_iteration=1, access_list=access_list_gen, + recipient_type=RecipientType.DELEGATION_7702, ) ) @@ -1541,214 +1547,3 @@ def test_storage_sload_same_key_benchmark( contract_storage=contract_storage, ), ) - - -def account_access_params() -> list: - """Generate (opcode, value_sent, account_mode) triples.""" - params = [] - - for mode in AccountMode: - for op in [Op.CALL, Op.CALLCODE]: - params.append(pytest.param(op, 0, mode)) - params.append(pytest.param(op, 1, mode)) - - for op in [Op.BALANCE, Op.STATICCALL, Op.DELEGATECALL]: - params.append(pytest.param(op, 0, mode)) - - for op in [Op.EXTCODECOPY, Op.EXTCODESIZE, Op.EXTCODEHASH]: - for mode in [ - AccountMode.EXISTING_CONTRACT, - AccountMode.NON_EXISTING_ACCOUNT, - ]: - params.append(pytest.param(op, 0, mode)) - - return params - - -class AccountMode(Enum): - """Target Account Mode.""" - - EXISTING_CONTRACT = auto() - EXISTING_EOA = auto() - NON_EXISTING_ACCOUNT = auto() - - -@pytest.mark.repricing -@pytest.mark.parametrize("cache_strategy", [CacheStrategy.NO_CACHE]) -@pytest.mark.parametrize( - "opcode,value_sent,account_mode", account_access_params() -) -def test_account_access( - benchmark_test: BenchmarkTestFiller, - pre: Alloc, - fork: Fork, - opcode: Op, - value_sent: int, - gas_benchmark_value: int, - fixed_opcode_count: int | None, - account_mode: AccountMode, - cache_strategy: CacheStrategy, -) -> None: - """Benchmark account access with caching strategies.""" - address_retriever: Bytecode - # Read start_iteration from calldata so that when transactions are - # split across gas limits, each transaction continues from where - # the previous one left off instead of re-targeting the same accounts. - calldataload_start = Op.CALLDATALOAD(0) - if account_mode == AccountMode.EXISTING_CONTRACT: - # Use Bittrex Controller as target. Created 1586350 contracts, - # which cannot selfdestruct, so guaranteed to be on-chain. - # This is safe for a gas benchmark up to 300M. (300_000_000 / 2000) - # (2000 is the min cost to target a cold address) - target_address = Address(0xA3C1E324CA1CE40DB73ED6026C4A177F099B5770) - address_retriever = CreatePreimageLayout( - sender_address=target_address, - nonce=Op.ADD(1, calldataload_start), - ) - increment_op = address_retriever.increment_nonce_op() - elif account_mode == AccountMode.EXISTING_EOA: - # Spamoor EOA creator (https://github.com/CPerezz/spamoor/pull/12) - # created these accounts on bloatnet with these values (are also the - # defaults of SequentialAddressLayout) - address_retriever = SequentialAddressLayout( - starting_address=Op.ADD(0x1000, calldataload_start), - increment=1, - ) - increment_op = address_retriever.increment_address_op() - else: - address_retriever = SequentialAddressLayout( - starting_address=Op.ADD(keccak256(b"random"), calldataload_start), - increment=1, - ) - increment_op = address_retriever.increment_address_op() - - setup_code: Bytecode = address_retriever - - cache_op = ( - Op.POP( - Op.BALANCE( - address=address_retriever.address_op(), - # Gas accounting - address_warm=False, - ) - ) - if cache_strategy == CacheStrategy.CACHE_TX - else Bytecode() - ) - - access_warm = cache_strategy == CacheStrategy.CACHE_TX - - if opcode == Op.EXTCODECOPY: - attack_call = opcode( - address=address_retriever.address_op(), - size=1024, - # Gas accounting - address_warm=access_warm, - ) - elif opcode in (Op.CALL, Op.CALLCODE): - attack_call = Op.POP( - opcode( - address=address_retriever.address_op(), - value=value_sent, - # Gas accounting - address_warm=access_warm, - value_transfer=value_sent > 0, - account_new=value_sent > 0 - and account_mode == AccountMode.NON_EXISTING_ACCOUNT, - ) - ) - elif opcode in (Op.STATICCALL, Op.DELEGATECALL): - attack_call = Op.POP( - opcode( - address=address_retriever.address_op(), - # Gas accounting - address_warm=access_warm, - ) - ) - else: - # BALANCE, EXTCODESIZE, EXTCODEHASH - attack_call = Op.POP( - opcode( - address=address_retriever.address_op(), - # Gas accounting - address_warm=access_warm, - ) - ) - - loop_code = While( - body=cache_op + attack_call + increment_op, - condition=Op.GT(Op.GAS, 0x9000) if value_sent > 0 else None, - ) - - attack_code = IteratingBytecode( - setup=setup_code, - iterating=loop_code, - # Since the target contract is guaranteed to have a STOP as the first - # instruction, we can use a STOP as the iterating subcall code. - iterating_subcall=Op.STOP, - ) - - # Calldata generator for each transaction of the iterating bytecode. - # Start from 1 to skip the Bittrex Controller's nonce=1 contract - # which has a non-payable fallback that reverts when receiving value. - calldata_offset = 1 if account_mode == AccountMode.EXISTING_CONTRACT else 0 - - def calldata(iteration_count: int, start_iteration: int) -> bytes: - del iteration_count - return Hash(start_iteration + calldata_offset) - - attack_address = pre.deploy_contract(code=attack_code, balance=10**21) - - post: dict = {} - cache_txs = [] - - with TestPhaseManager.execution(): - attack_sender = pre.fund_eoa() - if fixed_opcode_count is not None: - attack_txs = list( - attack_code.transactions_by_total_iteration_count( - fork=fork, - total_iterations=int(fixed_opcode_count * 1000), - sender=attack_sender, - to=attack_address, - calldata=calldata, - ) - ) - else: - attack_txs = list( - attack_code.transactions_by_gas_limit( - fork=fork, - gas_limit=gas_benchmark_value, - sender=attack_sender, - to=attack_address, - calldata=calldata, - ) - ) - - if cache_strategy == CacheStrategy.CACHE_PREVIOUS_BLOCK: - with TestPhaseManager.setup(): - cache_sender = pre.fund_eoa() - for tx in attack_txs: - cache_txs.append( - Transaction( - gas_limit=tx.gas_limit, - data=tx.data, - to=attack_address, - sender=cache_sender, - ) - ) - - blocks = ( - [Block(txs=attack_txs)] - if cache_strategy != CacheStrategy.CACHE_PREVIOUS_BLOCK - else [Block(txs=cache_txs), Block(txs=attack_txs)] - ) - - benchmark_test( - pre=pre, - post=post, - blocks=blocks, - target_opcode=opcode, - skip_gas_used_validation=True, - expected_receipt_status=1, - ) diff --git a/tests/benchmark/stateful/bloatnet/test_transaction_types.py b/tests/benchmark/stateful/bloatnet/test_transaction_types.py index 171c12a9e08..8094b0869a7 100644 --- a/tests/benchmark/stateful/bloatnet/test_transaction_types.py +++ b/tests/benchmark/stateful/bloatnet/test_transaction_types.py @@ -1,139 +1,46 @@ """Benchmark ether transfers to receivers that exist on-chain.""" -import itertools from typing import Generator import pytest from execution_testing import ( - DETERMINISTIC_FACTORY_ADDRESS, - EOA, Address, Alloc, BenchmarkTestFiller, Block, Fork, Op, + RecipientType, Transaction, - compute_create2_address, - compute_create_address, - keccak256, ) -# Deterministic sender pool of 15K accounts. -# Funded via system contract withdrawals (funding.txt) in payload generation. -# Placed outside pre-allocation to ensure accounts remain uncached. -SENDER_BASE_KEY = int.from_bytes( - keccak256(b"gas-repricings-private-key"), "big" +from tests.benchmark.helper.account_creator import ( + AccountCreator, + AccountMode, ) - - -def yield_distinct_sender() -> Generator[EOA, None, None]: - """Yield deterministic sender EOAs pre-funded on-chain.""" - for i in itertools.count(0): - yield EOA(key=SENDER_BASE_KEY + i) - - -def build_unique_contract_initcode() -> bytes: - """ - Deployed runtime contract layout. - - offset size contents - ------ ---- -------------------------------- - 0x0000 4 PUSH2 0x5FFF; JUMP <- entry - 0x0004 28 JUMPDEST padding - 0x0020 12 JUMPDEST padding - 0x002C 20 contract ADDRESS <- unique - 0x0040 24512 JUMPDEST <- 0x5FFF lands here - 0x6000 STOP - - Embedded ADDRESS makes runtime unique per contract; - initcode and its CREATE2 hash is shared across all salts. - """ - max_code_size = 0x6000 # EIP-170 contract code size limit - - # MCOPY fills MEM[0:0x8000] with JUMPDEST. - # Runtime only uses MEM[0:0x6000]. - code = Op.MSTORE(0, bytes(Op.JUMPDEST * 32)) - for size in (1 << s for s in range(5, 15)): - code += Op.MCOPY(size, 0, size) - - # Runtime entry: JUMP to final JUMPDEST, then STOP. - entry = Op.JUMP(max_code_size - 1) - entry += Op.JUMPDEST * (32 - len(entry)) # Padding - - code += Op.MSTORE(0, bytes(entry)) - - # Mask ADDRESS into a JUMPDEST template via OR: - # bytes 0..12 bytes 12..32 - # ----------- ------------ - # ADDRESS 00 .. 00 <20-byte address> - # addr_slot 5b .. 5b 00 .. 00 - # OR result 5b .. 5b <20-byte address> - addr_slot = Op.JUMPDEST * 12 + Op.STOP * 20 - code += Op.MSTORE(0x20, Op.OR(Op.ADDRESS, bytes(addr_slot))) - - code += Op.RETURN(0, max_code_size) - - return bytes(code) - - -JOCHEMNET_UNIQUE_CONTRACT_INITCODE = build_unique_contract_initcode() - - -def yield_distinct_unique_code_jumpdest_receiver() -> Generator[ - Address, None, None -]: - """ - Yield contract addresses deployed by the deterministic CREATE2 factory. - """ - for salt in itertools.count(0): - yield compute_create2_address( - address=DETERMINISTIC_FACTORY_ADDRESS, - salt=salt, - initcode=JOCHEMNET_UNIQUE_CONTRACT_INITCODE, - ) - - -# Bittrex controller mainnet address -# Creates 1.5M contracts with deterministic address via CREATE -# It is guaranteed no contract is destructed -# Used for existing contract targets in benchmark -BITTREX_CONTROLLER_ADDRESS = Address( - 0xA3C1E324CA1CE40DB73ED6026C4A177F099B5770 +from tests.benchmark.helper.account_sender_receiver import ( + yield_distinct_contract_receiver, + yield_distinct_create2_receiver, + yield_distinct_delegate_receiver, + yield_distinct_existent_receiver, + yield_distinct_nonexistent_receiver, + yield_distinct_sender, ) -def yield_distinct_contract_receiver() -> Generator[Address, None, None]: - """Yield contract account created by Bittrex controller via CREATE.""" - for nonce in itertools.count(2): - yield compute_create_address( - address=BITTREX_CONTROLLER_ADDRESS, nonce=nonce - ) - - -def yield_distinct_existent_receiver() -> Generator[Address, None, None]: - """ - Yield existing balance-only EOA on bloatnet. pre-funded by Spamoor - (https://github.com/CPerezz/spamoor/pull/12). - """ - for address in itertools.count(0x1000): - yield Address(address) - - -def yield_distinct_nonexistent_receiver() -> Generator[Address, None, None]: - """Yield non-existent accounts starting from keccak256('random').""" - for address in itertools.count(0xF3CF193BB4AF1022AF7D2089F37D8BAE7157B85F): - yield Address(address) - - @pytest.mark.repricing @pytest.mark.parametrize( "case_id", [ + "diff_to_self", "diff_to_nonexistent", "diff_to_existent", "diff_to_contract", "diff_to_unique_code_jumpdest_contract", + "diff_to_contract_minimal", + "diff_to_contract_same_max", + "diff_to_contract_diff_max", + "diff_to_delegated_contract_diff", ], ) @pytest.mark.parametrize("transfer_amount", [0, 1]) @@ -145,59 +52,84 @@ def test_ether_transfers_onchain_receivers( fork: Fork, gas_benchmark_value: int, ) -> None: - """ - Ether transfers to receivers that exist on-chain at run time. - - Scenarios: - - diff_to_nonexistent: distinct nonexistent receivers - (matches AccountMode.NON_EXISTING_ACCOUNT) - - diff_to_existent: distinct existent EOA receivers - (matches AccountMode.EXISTING_EOA) - - diff_to_contract: distinct contract receivers - (matches AccountMode.EXISTING_CONTRACT) - - diff_to_unique_code_jumpdest_contract: distinct CREATE2 contract - receivers each holding unique deployed code - """ + """Benchmark ether transfers across different receiver account types.""" senders = yield_distinct_sender() receiver_execution_gas = 0 - if case_id == "diff_to_nonexistent": - receivers = yield_distinct_nonexistent_receiver() - elif case_id == "diff_to_existent": - receivers = yield_distinct_existent_receiver() - elif case_id == "diff_to_contract": - receivers = yield_distinct_contract_receiver() - # Runtime code is the same across all the receivers - # Example contract: https://etherscan.io/address/0xa888df3ef62286dde06a79395760b9bce6c83c83#code - runtime = ( - Op.MSTORE(0x40, 0x60, new_memory_size=0x60) - + Op.JUMPI(Op.PUSH2(0x49), Op.ISZERO(Op.CALLDATASIZE)) - + Op.JUMPDEST * 3 - + Op.JUMP(Op.PUSH2(0x50)) - + Op.JUMPDEST - ) - receiver_execution_gas = runtime.gas_cost(fork) - elif case_id == "diff_to_unique_code_jumpdest_contract": - receivers = yield_distinct_unique_code_jumpdest_receiver() - # Runtime code aligns entry code path. - runtime = Op.JUMP(Op.PUSH2(0x5FFF)) + Op.JUMPDEST - receiver_execution_gas = runtime.gas_cost(fork) - else: - raise ValueError(f"Unknown case: {case_id}") - + recipient_type = RecipientType.CONTRACT + receivers: Generator[Address, None, None] + match case_id: + case "diff_to_self": + receivers = senders + recipient_type = RecipientType.SELF + case "diff_to_nonexistent": + receivers = yield_distinct_nonexistent_receiver() + recipient_type = RecipientType.EMPTY_ACCOUNT + case "diff_to_existent": + receivers = yield_distinct_existent_receiver() + recipient_type = RecipientType.EOA + case "diff_to_contract": + receivers = yield_distinct_contract_receiver() + # Runtime code is the same across all the receivers + # Example contract: https://etherscan.io/address/0xa888df3ef62286dde06a79395760b9bce6c83c83#code + executed_code = ( + Op.MSTORE(0x40, 0x60, new_memory_size=0x60) + + Op.JUMPI(Op.PUSH2(0x49), Op.ISZERO(Op.CALLDATASIZE)) + + Op.JUMPDEST * 3 + + Op.JUMP(Op.PUSH2(0x50)) + + Op.JUMPDEST + ) + receiver_execution_gas = executed_code.gas_cost(fork) + case "diff_to_unique_code_jumpdest_contract": + creator = AccountCreator(AccountMode.EXISTING_CONTRACT_JUMPDEST) + receivers = yield_distinct_create2_receiver(creator.initcode) + receiver_execution_gas = creator.execution_code.gas_cost(fork) + case "diff_to_contract_minimal": + receivers = yield_distinct_create2_receiver( + AccountCreator(AccountMode.EXISTING_CONTRACT_MINIMAL).initcode + ) + case "diff_to_contract_same_max": + receivers = yield_distinct_create2_receiver( + AccountCreator(AccountMode.EXISTING_CONTRACT_SAME_MAX).initcode + ) + case "diff_to_contract_diff_max": + receivers = yield_distinct_create2_receiver( + AccountCreator(AccountMode.EXISTING_CONTRACT_DIFF_MAX).initcode + ) + case "diff_to_delegated_contract_diff": + receivers = yield_distinct_delegate_receiver() + recipient_type = RecipientType.DELEGATION_7702 + case _: + raise ValueError(f"Unknown case: {case_id}") + + sends_value = transfer_amount > 0 iteration_cost = ( - fork.transaction_intrinsic_cost_calculator()() + receiver_execution_gas + fork.transaction_intrinsic_cost_calculator()( + sends_value=sends_value, + recipient_type=recipient_type, + ) + + fork.transaction_top_frame_gas_calculator()( + sends_value=sends_value, + recipient_type=recipient_type, + ) + + fork.transaction_top_frame_state_gas( + sends_value=sends_value, + recipient_type=recipient_type, + ) + + receiver_execution_gas ) iteration_count = gas_benchmark_value // iteration_cost - txs = [ - Transaction( - to=next(receivers), - value=transfer_amount, - gas_limit=iteration_cost, - sender=next(senders), + txs = [] + for _ in range(iteration_count): + sender = next(senders) + txs.append( + Transaction( + to=sender if case_id == "diff_to_self" else next(receivers), + value=transfer_amount, + gas_limit=iteration_cost, + sender=sender, + ) ) - for _ in range(iteration_count) - ] benchmark_test( pre=pre, diff --git a/tests/benchmark/stateful/bloatnet/test_transient_storage.py b/tests/benchmark/stateful/bloatnet/test_transient_storage.py index 92d372e299e..32e330a0cca 100644 --- a/tests/benchmark/stateful/bloatnet/test_transient_storage.py +++ b/tests/benchmark/stateful/bloatnet/test_transient_storage.py @@ -1,11 +1,4 @@ -""" -abstract: Transient storage benchmark cases for TSTORE/TLOAD saturation. - - These tests stress transient storage (EIP-1153) by performing - massive numbers of TSTORE/TLOAD operations within a single block. - Unlike persistent SSTORE (20K gas), TSTORE costs only 100 gas with - no cold/warm distinction, enabling vastly more writes per block. -""" +"""Transient storage benchmarks for TSTORE/TLOAD.""" import pytest from execution_testing import ( @@ -28,29 +21,6 @@ REFERENCE_SPEC_VERSION = "1.0" -# TSTORE SATURATION BENCHMARK ARCHITECTURE: -# -# test_tstore_unique_keys: -# Simple loop contract that TSTOREs at incrementing keys. -# At 100 gas per TSTORE + ~56 gas loop overhead, each iteration -# costs ~156 gas, yielding ~64K iterations per 10M gas benchmark. -# Creates massive in-memory trie pressure without persistent state -# overhead — fundamentally different stress than SSTORE. -# -# test_tstore_same_key: -# Repeatedly TSTOREs the same key (slot 0). Uses JumpLoopGenerator -# (max code fill) for maximum throughput. Tests the transient -# storage hot-path optimization in clients. -# -# WHY IT STRESSES CLIENTS: -# - TSTORE at 100 gas enables ~300K ops per 30M gas block -# - Each unique key expands the in-memory transient trie -# - Transient storage is cleared per-transaction, so clients -# must allocate and deallocate rapidly -# - No persistent state: tests pure in-memory data structure -# performance without disk I/O - - @pytest.mark.parametrize("with_tload", [True, False]) def test_tstore_unique_keys( benchmark_test: BenchmarkTestFiller, @@ -60,13 +30,7 @@ def test_tstore_unique_keys( tx_gas_limit: int, with_tload: bool, ) -> None: - """ - Benchmark TSTORE with unique keys per iteration. - - Saturate transient storage by writing incrementing keys. - Optionally follow each TSTORE with a TLOAD readback to stress - both write and read paths. - """ + """Benchmark TSTORE with a unique key per iteration.""" # Memory layout: MEM[0..31] = counter (incrementing) setup = ( Op.MSTORE( @@ -119,12 +83,7 @@ def test_tstore_same_key( benchmark_test: BenchmarkTestFiller, with_tload: bool, ) -> None: - """ - Benchmark TSTORE writing the same key repeatedly. - - Measure transient storage hot-path performance by repeatedly - writing to slot 0. Uses JumpLoopGenerator for maximum code fill. - """ + """Benchmark TSTORE writing the same key repeatedly.""" attack_block = Op.TSTORE(0, 1) if with_tload: diff --git a/tests/benchmark/stateful/helpers.py b/tests/benchmark/stateful/helpers.py index 033d5320499..d9c028ba55b 100644 --- a/tests/benchmark/stateful/helpers.py +++ b/tests/benchmark/stateful/helpers.py @@ -1,6 +1,6 @@ """Shared constants and helpers for stateful benchmark tests.""" -from collections.abc import Callable +from collections.abc import Callable, Sequence from dataclasses import dataclass from enum import Enum from functools import partial @@ -16,6 +16,7 @@ Hash, IteratingBytecode, Op, + RecipientType, Transaction, ) from execution_testing.base_types.base_types import Number @@ -125,8 +126,8 @@ def build_benchmark_txs( def build_cache_strategy_blocks( cache_strategy: CacheStrategy, - txs: list[Transaction], - cache_txs: list[Transaction], + txs: Sequence[Transaction], + cache_txs: Sequence[Transaction], ) -> list[Block]: """ Assemble benchmark blocks based on cache strategy. @@ -219,28 +220,20 @@ def build_delegated_storage_setup( ) authority_nonce += 1 - # Calculate max slots per transaction based on gas cost - iteration_cost = initializer_code.tx_gas_limit_by_iteration_count( - fork=fork, - iteration_count=1, - start_iteration=1, - calldata=initializer_calldata_generator, - ) - iteration_count = max(1, tx_gas_limit // iteration_cost) - - init_txs: list[Transaction] = [] - for start in range(1, num_target_slots + 1, iteration_count): - chunk_size = min(iteration_count, num_target_slots - start + 1) - init_txs.extend( - initializer_code.transactions_by_total_iteration_count( - fork=fork, - total_iterations=chunk_size, - sender=pre.fund_eoa(), - to=authority, - start_iteration=start, - calldata=initializer_calldata_generator, - ) + # transactions_by_total_iteration_count splits the slots across + # transactions capped by the fork gas limit, so no manual chunking + # is required. + init_txs: list[Transaction] = list( + initializer_code.transactions_by_total_iteration_count( + fork=fork, + total_iterations=num_target_slots, + sender=pre.fund_eoa(), + to=authority, + start_iteration=1, + calldata=initializer_calldata_generator, + recipient_type=RecipientType.DELEGATION_7702, ) + ) # Pack init transactions into blocks blocks.extend(pack_transactions_into_blocks(init_txs, tx_gas_limit)) @@ -424,27 +417,19 @@ def build_sequential_storage_init( sequential_initializer_calldata_generator, offset=r.offset, ) - iteration_cost = initializer_code.tx_gas_limit_by_iteration_count( - fork=fork, - iteration_count=1, - start_iteration=max(1, r.start_slot), - calldata=calldata_gen, - ) - iteration_count = max(1, tx_gas_limit // iteration_cost) - - end_slot = r.start_slot + r.num_slots - for start in range(r.start_slot, end_slot, iteration_count): - chunk = min(iteration_count, end_slot - start) - init_txs.extend( - initializer_code.transactions_by_total_iteration_count( - fork=fork, - total_iterations=chunk, - sender=pre.fund_eoa(), - to=authority, - start_iteration=start, - calldata=calldata_gen, - ) + # transactions_by_total_iteration_count splits the range across + # transactions capped by the fork gas limit; no manual chunking needed. + init_txs.extend( + initializer_code.transactions_by_total_iteration_count( + fork=fork, + total_iterations=r.num_slots, + sender=pre.fund_eoa(), + to=authority, + start_iteration=r.start_slot, + calldata=calldata_gen, + recipient_type=RecipientType.DELEGATION_7702, ) + ) blocks: list[Block] = [Block(txs=[auth_tx])] blocks.extend(pack_transactions_into_blocks(init_txs, tx_gas_limit)) From 0b1e6985e5afd8380194901b9ef9cc7ce4fd0835 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=94=A1=E4=BD=B3=E8=AA=A0=20Louis=20Tsai?= <72684086+LouisTsai-Csie@users.noreply.github.com> Date: Tue, 14 Jul 2026 15:05:25 +0800 Subject: [PATCH 139/233] refactor(test-benchmark): organize stateful benchmark folder structure (#3152) * chore: relocate balance stateful benchmark - Rename test_bloatnet_balance_opcode to test_balance_query for readability - Move `test_balance_query` from `test_multi_opocode` to `test_account_query` folder * refactor: rename test_create2_access to test_create_operation * chore: organize call related stateful benchmark - create test_call_operation file - move test_call_value_existing and test_call_value_new_account - rename test name * refactor: relocate erc20 elated benchmark - add test_erc20_operation file - move test_sload_erc20_generic, test_sstore_erc20_generic, test_mixed_sload_sstore to same file * refactor: relocate sload benchmark - add test_sload file - move sload related benchmark * refactor: relocate sstore benchmark - add test_sstore file - move test_sstore_bloated, test_sstore_dirty_transitions to new file * chore: move test_extcodesize_bytecode_sizes to test_account_query * chore: docstring, comment refactor * docs: update stub-dependent test examples after benchmark reorg * chore(tests): fix markdown rendering in bloatnet benchmark docstrings * refactor(test-benchmark): file renaming, remove _operation suffix --------- Co-authored-by: danceratopz <danceratopz@gmail.com> --- docs/filling_tests/fill_stateful.md | 2 +- tests/benchmark/stateful/bloatnet/README.md | 94 - .../bloatnet/depth_benchmarks/README.md | 2 +- .../bloatnet/depth_benchmarks/__init__.py | 4 +- .../depth_benchmarks/test_deep_branch.py | 8 +- .../stateful/bloatnet/test_account_query.py | 290 +++ .../benchmark/stateful/bloatnet/test_call.py | 210 +++ ...{test_create2_access.py => test_create.py} | 5 +- .../benchmark/stateful/bloatnet/test_erc20.py | 399 +++++ .../test_extcodesize_bytecode_sizes.py | 159 -- .../stateful/bloatnet/test_multi_opcode.py | 517 ------ .../stateful/bloatnet/test_single_opcode.py | 1549 ----------------- .../benchmark/stateful/bloatnet/test_sload.py | 620 +++++++ .../stateful/bloatnet/test_sstore.py | 572 ++++++ .../bloatnet/test_transient_storage.py | 5 +- tests/benchmark/stateful/helpers.py | 163 ++ 16 files changed, 2265 insertions(+), 2334 deletions(-) delete mode 100644 tests/benchmark/stateful/bloatnet/README.md create mode 100644 tests/benchmark/stateful/bloatnet/test_call.py rename tests/benchmark/stateful/bloatnet/{test_create2_access.py => test_create.py} (97%) create mode 100644 tests/benchmark/stateful/bloatnet/test_erc20.py delete mode 100644 tests/benchmark/stateful/bloatnet/test_extcodesize_bytecode_sizes.py delete mode 100755 tests/benchmark/stateful/bloatnet/test_multi_opcode.py delete mode 100644 tests/benchmark/stateful/bloatnet/test_single_opcode.py create mode 100644 tests/benchmark/stateful/bloatnet/test_sload.py create mode 100644 tests/benchmark/stateful/bloatnet/test_sstore.py diff --git a/docs/filling_tests/fill_stateful.md b/docs/filling_tests/fill_stateful.md index a6a4eec654b..ebbb52bba90 100644 --- a/docs/filling_tests/fill_stateful.md +++ b/docs/filling_tests/fill_stateful.md @@ -149,7 +149,7 @@ Each `pre_run/<start_block_hash>.json` (a `StatefulPreRunFixture`) is replayed o ## Stub-dependent tests -Some stateful tests (e.g. `test_single_opcode.py`, `test_multi_opcode.py`) target on-chain accounts the snapshot already contains. They reach them two ways: +Some stateful tests (e.g. `test_erc20_operation.py`, `test_sload.py`) target on-chain accounts the snapshot already contains. They reach them two ways: - `@pytest.mark.stub_parametrize("name", "prefix_")` — parametrize values pulled from `--address-stubs` matching `prefix_`. - `pre.deploy_contract(stub="<label>", ...)` — direct runtime lookup. diff --git a/tests/benchmark/stateful/bloatnet/README.md b/tests/benchmark/stateful/bloatnet/README.md deleted file mode 100644 index c3bcf9cfc71..00000000000 --- a/tests/benchmark/stateful/bloatnet/README.md +++ /dev/null @@ -1,94 +0,0 @@ -# BloatNet Single-Opcode Benchmarks - -This directory contains benchmarks for testing single EVM opcodes (SLOAD, SSTORE) under state-heavy conditions using pre-deployed contracts. - -## Test Setup - -### Prerequisites - -1. Pre-deployed ERC20 contracts on the target network -2. A JSON file containing contract addresses (stubs) - -### Address Stubs Format - -Create a JSON file (`stubs.json`) mapping test-specific stub names to deployed contract addresses: - -```json -{ - "test_sload_empty_erc20_balanceof_USDT": "0x1234567890123456789012345678901234567890", - "test_sload_empty_erc20_balanceof_USDC": "0x2345678901234567890123456789012345678901", - "test_sload_empty_erc20_balanceof_DAI": "0x3456789012345678901234567890123456789012", - "test_sload_empty_erc20_balanceof_WETH": "0x4567890123456789012345678901234567890123", - "test_sload_empty_erc20_balanceof_WBTC": "0x5678901234567890123456789012345678901234", - - "test_sstore_erc20_approve_USDT": "0x1234567890123456789012345678901234567890", - "test_sstore_erc20_approve_USDC": "0x2345678901234567890123456789012345678901", - "test_sstore_erc20_approve_DAI": "0x3456789012345678901234567890123456789012", - "test_sstore_erc20_approve_WETH": "0x4567890123456789012345678901234567890123", - "test_sstore_erc20_approve_WBTC": "0x5678901234567890123456789012345678901234", - - "test_mixed_sload_sstore_USDT": "0x1234567890123456789012345678901234567890", - "test_mixed_sload_sstore_USDC": "0x2345678901234567890123456789012345678901", - "test_mixed_sload_sstore_DAI": "0x3456789012345678901234567890123456789012", - "test_mixed_sload_sstore_WETH": "0x4567890123456789012345678901234567890123", - "test_mixed_sload_sstore_WBTC": "0x5678901234567890123456789012345678901234" -} -``` - -**Naming Convention:** -- Stub names MUST start with the test function name -- Format: `{test_function_name}_{identifier}` -- Example: `test_sload_empty_erc20_balanceof_USDT` - - -### Running the Tests - -#### Execute Mode (Against Live Network) - -```bash -# Run with specific number of contracts (e.g., only the 5-contract variant) -uv run execute remote \ - --rpc-endpoint http://localhost:8545 \ - --rpc-seed-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ - --chain-id 1337 \ - --address-stubs geth_stubs.json \ - --fork Prague \ - tests/benchmark/stateful/bloatnet/test_single_opcode.py::test_sload_empty_erc20_balanceof \ - -k "[5]" \ - - - -## Test Parametrization - -Both single-opcode tests are parametrized with `num_contracts = [1, 5, 10, 20, 100]`, generating 5 test variants each: - -- **1 contract**: Baseline single-contract performance -- **5 contracts**: Small-scale multi-contract scenario -- **10 contracts**: Medium-scale multi-contract scenario -- **20 contracts**: Large-scale multi-contract scenario -- **100 contracts**: Very large-scale multi-contract stress test - -The mixed SLOAD/SSTORE test additionally parametrizes operation ratios: - -- **50-50**: Equal mix of SLOAD and SSTORE operations -- **70-30**: 70% SLOAD, 30% SSTORE operations -- **90-10**: 90% SLOAD, 10% SSTORE operations - -### How Stub Filtering Works - -1. Test extracts its function name (e.g., `test_sload_empty_erc20_balanceof`) -2. Filters stubs starting with that name from `stubs.json` -3. Selects the **first N** matching stubs based on `num_contracts` parameter -4. Errors if insufficient matching stubs found - - -## Benchmark Descriptions - -### test_sload_empty_erc20_balanceof -Tests SLOAD operations by calling `balanceOf()` on ERC20 contracts with random addresses, forcing cold storage reads of likely-empty slots. - -### test_sstore_erc20_approve -Tests SSTORE operations by calling `approve()` on ERC20 contracts with incrementing spender addresses, forcing cold storage writes to new allowance slots. - -### test_mixed_sload_sstore -Tests mixed SLOAD/SSTORE workloads with configurable ratios, simulating realistic DeFi application patterns with combined read/write operations. \ No newline at end of file diff --git a/tests/benchmark/stateful/bloatnet/depth_benchmarks/README.md b/tests/benchmark/stateful/bloatnet/depth_benchmarks/README.md index a0075d0e02f..0f57d322386 100644 --- a/tests/benchmark/stateful/bloatnet/depth_benchmarks/README.md +++ b/tests/benchmark/stateful/bloatnet/depth_benchmarks/README.md @@ -15,7 +15,7 @@ The test measures the performance impact of state root recomputation and IO when ## Contract Sources -- **Pre-mined assets** (depth\__.sol, s_\_acc\*.json): https://github.com/CPerezz/worst_case_miner/tree/master/mined_assets +- **Pre-mined assets** (`depth_*.sol`, `s*_acc*.json`): https://github.com/CPerezz/worst_case_miner/tree/master/mined_assets For complete deployment setup and instructions, see the gist: https://gist.github.com/CPerezz/44d521c0f9e6adf7d84187a4f2c11978 diff --git a/tests/benchmark/stateful/bloatnet/depth_benchmarks/__init__.py b/tests/benchmark/stateful/bloatnet/depth_benchmarks/__init__.py index 132a529652b..941dea48133 100644 --- a/tests/benchmark/stateful/bloatnet/depth_benchmarks/__init__.py +++ b/tests/benchmark/stateful/bloatnet/depth_benchmarks/__init__.py @@ -1,3 +1 @@ -""" -abstract: BloatNet worst-case attack benchmark for maximum SSTORE stress. -""" +"""BloatNet worst-case attack benchmark for maximum SSTORE stress.""" diff --git a/tests/benchmark/stateful/bloatnet/depth_benchmarks/test_deep_branch.py b/tests/benchmark/stateful/bloatnet/depth_benchmarks/test_deep_branch.py index 07f94e8d9be..21f0eade4f1 100644 --- a/tests/benchmark/stateful/bloatnet/depth_benchmarks/test_deep_branch.py +++ b/tests/benchmark/stateful/bloatnet/depth_benchmarks/test_deep_branch.py @@ -1,5 +1,5 @@ """ -abstract: BloatNet worst-case depth benchmarks for deep SSTORE and SLOAD. +BloatNet worst-case depth benchmarks for deep SSTORE and SLOAD. This test implements a worst-case scenario for Ethereum block processing that exploits the computational complexity of Patricia Merkle Trie @@ -7,6 +7,7 @@ with shared prefixes, maximizing trie traversal depth. Key features: + - Accesses pre-deployed contracts via CREATE2 address derivation - Each contract has deep storage slots with configurable trie depth - Includes both a mutating `attack(uint256)` benchmark and a read-only @@ -15,11 +16,13 @@ - Verifies correctness via post-state checks or receipt-status validation Test parameters: + - storage_depth: Depth of storage slots (e.g., 10, 11) - account_depth: Account address prefix sharing depth (e.g., 6, 7) Contract sources: -- Pre-mined assets (depth_*.sol, s*_acc*.json): + +- Pre-mined assets (`depth_*.sol`, `s*_acc*.json`): https://github.com/CPerezz/worst_case_miner/tree/master/mined_assets """ @@ -500,6 +503,7 @@ def test_worst_depth_stateroot_recomp( BloatNet worst-case SSTORE attack benchmark with pre-deployed contracts. This test: + 1. Derives CREATE2 addresses from initcode_hash + Nick's deployer 2. Deploys AttackOrchestrator that calls attack() on each target 3. Fills blocks with 16M gas transactions attacking contracts diff --git a/tests/benchmark/stateful/bloatnet/test_account_query.py b/tests/benchmark/stateful/bloatnet/test_account_query.py index c96e2420958..099241797ed 100644 --- a/tests/benchmark/stateful/bloatnet/test_account_query.py +++ b/tests/benchmark/stateful/bloatnet/test_account_query.py @@ -5,14 +5,20 @@ import pytest from execution_testing import ( Account, + Address, Alloc, BenchmarkTestFiller, + Block, + BlockchainTestFiller, Bytecode, + Conditional, + Create2PreimageLayout, Fork, Hash, IteratingBytecode, JumpLoopGenerator, Op, + Storage, TestPhaseManager, Transaction, While, @@ -23,7 +29,9 @@ AccountMode, ) from tests.benchmark.stateful.helpers import ( + DECREMENT_COUNTER_CONDITION, CacheStrategy, + build_benchmark_txs, build_cache_strategy_blocks, ) @@ -328,3 +336,285 @@ def calldata(iteration_count: int, start_iteration: int) -> bytes: skip_gas_used_validation=True, expected_receipt_status=1, ) + + +@pytest.mark.stub_parametrize("factory_stub", "bloatnet_factory_") +@pytest.mark.parametrize( + "second_opcode", + [Op.EXTCODESIZE, Op.EXTCODECOPY, Op.EXTCODEHASH, Op.STATICCALL, Op.CALL], +) +@pytest.mark.parametrize( + "balance_first", + [True, False], + ids=["balance_first", "opcode_first"], +) +def test_balance_query( + benchmark_test: BenchmarkTestFiller, + pre: Alloc, + fork: Fork, + gas_benchmark_value: int, + tx_gas_limit: int, + balance_first: bool, + second_opcode: Op, + factory_stub: str, +) -> None: + """Benchmark BALANCE paired with a second opcode on factory contracts.""" + factory_address = pre.deploy_contract( + code=Bytecode(), + stub=factory_stub, + ) + + # Contract Construction + setup = Bytecode() + + setup += Conditional( + condition=Op.STATICCALL( + gas=Op.GAS, + address=factory_address, + args_offset=0, + args_size=0, + ret_offset=96, + ret_size=64, + # gas accounting + address_warm=False, + old_memory_size=0, + new_memory_size=160, + ), + if_false=Op.INVALID, + ) + + create2_preimage = Create2PreimageLayout( + factory_address=factory_address, + salt=Op.CALLDATALOAD(32), + init_code_hash=Op.MLOAD(128), + old_memory_size=160, + ) + + setup += create2_preimage + setup += Op.CALLDATALOAD(0) # [num_contract] + + # Build the second opcode's bytecode + balance_op = Op.POP(Op.BALANCE) + + if second_opcode == Op.EXTCODESIZE: + other_op = Op.POP(Op.EXTCODESIZE) + elif second_opcode == Op.EXTCODECOPY: + max_contract_size = fork.max_code_size() + other_op = Op.POP( + Op.EXTCODECOPY( + address=Op.DUP4, + dest_offset=Op.ADD(Op.MLOAD(32), 96), + offset=max_contract_size - 1, + size=1, + data_size=1, + ) + ) + elif second_opcode == Op.EXTCODEHASH: + other_op = Op.POP(Op.EXTCODEHASH) + elif second_opcode == Op.STATICCALL: + # gas=1: forces account/code loading, then fails + other_op = ( + Op.POP( + Op.STATICCALL( + gas=1, + address=Op.DUP5, + args_offset=0, + args_size=0, + ret_offset=0, + ret_size=0, + ) + ) + + Op.POP + ) + elif second_opcode == Op.CALL: + # gas=1: forces account/code loading, then fails + other_op = ( + Op.POP( + Op.CALL( + gas=1, + address=Op.DUP6, + value=0, + args_offset=0, + args_size=0, + ret_offset=0, + ret_size=0, + ) + ) + + Op.POP + ) + else: + raise ValueError(f"Unsupported opcode: {second_opcode}") + + benchmark_ops = ( + (balance_op + other_op) if balance_first else (other_op + balance_op) + ) + + loop = While( + body=( + create2_preimage.address_op() + + Op.DUP1 + + benchmark_ops + + create2_preimage.increment_salt_op() + ), + condition=DECREMENT_COUNTER_CONDITION, + ) + + # Contract Deployment + code = setup + loop + attack_contract_address = pre.deploy_contract(code=code) + + # Gas Accounting + txs, total_gas_consumed = build_benchmark_txs( + pre=pre, + fork=fork, + gas_benchmark_value=gas_benchmark_value, + tx_gas_limit=tx_gas_limit, + attack_contract_address=attack_contract_address, + setup_cost=setup.gas_cost(fork), + iteration_cost=loop.gas_cost(fork), + ) + + benchmark_test( + pre=pre, + blocks=[Block(txs=txs)], + expected_benchmark_gas_used=total_gas_consumed, + skip_gas_used_validation=True, + ) + + +def get_factory_stub_name(size_kb: float) -> str: + """Generate stub name for factory based on size.""" + if size_kb == 0.5: + return "bloatnet_factory_0_5kb" + elif size_kb == 1.0: + return "bloatnet_factory_1kb" + elif size_kb == 2.0: + return "bloatnet_factory_2kb" + elif size_kb == 5.0: + return "bloatnet_factory_5kb" + elif size_kb == 10.0: + return "bloatnet_factory_10kb" + elif size_kb == 24.0: + return "bloatnet_factory_24kb" + else: + raise ValueError(f"Unsupported size: {size_kb}KB") + + +def build_attack_contract(factory_address: Address) -> Bytecode: + """Build the EXTCODESIZE attack contract with a gas-based loop exit.""" + gas_reserve = 50_000 # Reserve for 2x SSTORE + cleanup + num_deployed_offset = 96 + init_code_hash_offset = num_deployed_offset + 32 + return_size = 64 + return ( + # Call factory.getConfig() -> (num_deployed, init_code_hash) + Conditional( + condition=Op.STATICCALL( + gas=Op.GAS, + address=factory_address, + args_offset=0, + args_size=0, + # MEM[num_deployed_offset]=num_deployed + # MEM[num_deployed_offset + 32]=init_code_hash + ret_offset=num_deployed_offset, + ret_size=return_size, + ), + if_false=Op.REVERT(0, 0), + ) + + ( + create2_preimage := Create2PreimageLayout( + factory_address=factory_address, + salt=Op.SLOAD(0), + init_code_hash=Op.MLOAD(init_code_hash_offset), + old_memory_size=num_deployed_offset + return_size, + ) + ) + + Op.MSTORE(160, 0) # Initialize last_size + + While( + body=( + Op.MSTORE(160, Op.EXTCODESIZE(create2_preimage.address_op())) + + create2_preimage.increment_salt_op() + ), + condition=( + Op.AND( + Op.GT(Op.GAS, gas_reserve), + # num_deployed > salt + Op.GT( + Op.MLOAD(num_deployed_offset), + Op.MLOAD(create2_preimage.salt_offset), + ), + ) + ), + ) + + Op.SSTORE(0, Op.MLOAD(32)) # Save final salt + + Op.SSTORE(1, Op.MLOAD(160)) # Save last result + + Op.STOP + ) + + +@pytest.mark.parametrize( + "bytecode_size_kb", + [0.5, 1.0, 2.0, 5.0, 10.0, 24.0], + ids=lambda size: f"{size}KB", +) +def test_extcodesize_bytecode_sizes( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + bytecode_size_kb: float, + gas_benchmark_value: int, + tx_gas_limit: int, +) -> None: + """Execute EXTCODESIZE benchmark against pre-deployed contracts.""" + expected_size_bytes = int(bytecode_size_kb * 1024) + + # Get factory stub name for this size + factory_stub = get_factory_stub_name(bytecode_size_kb) + + # Deploy factory stub (address comes from stub file) + factory_address = pre.deploy_contract( + code=Bytecode(), # Empty bytecode - address from stub + stub=factory_stub, + ) + + # Build and deploy the attack contract + attack_code = build_attack_contract(factory_address) + attack_address = pre.deploy_contract(code=attack_code) + + # Calculate how many transactions we need to fill the block + num_attack_txs = gas_benchmark_value // tx_gas_limit + if num_attack_txs == 0: + num_attack_txs = 1 + + # Fund the sender + sender = pre.fund_eoa() + + # Build transactions + txs = [] + + # Attack transactions: all identical, no calldata needed + for _ in range(num_attack_txs): + attack_tx = Transaction( + gas_limit=tx_gas_limit, + to=attack_address, + sender=sender, + ) + txs.append(attack_tx) + + # Create block with all transactions + block = Block(txs=txs) + + # Post-state verification: + # Attack contract slot 1 = expected size (last EXTCODESIZE result) + # Slot 0 can be any value (final salt depends on gas used) + attack_storage = Storage({1: expected_size_bytes}) # type: ignore[dict-item] + attack_storage.set_expect_any(0) + + post = { + attack_address: Account(storage=attack_storage), + } + + blockchain_test( + pre=pre, + post=post, + blocks=[block], + ) diff --git a/tests/benchmark/stateful/bloatnet/test_call.py b/tests/benchmark/stateful/bloatnet/test_call.py new file mode 100644 index 00000000000..225ba567d4d --- /dev/null +++ b/tests/benchmark/stateful/bloatnet/test_call.py @@ -0,0 +1,210 @@ +"""Benchmark call operations with value transfer on target accounts.""" + +import pytest +from execution_testing import ( + Account, + Address, + Alloc, + BenchmarkTestFiller, + Block, + Bytecode, + Conditional, + Create2PreimageLayout, + Fork, + Hash, + IteratingBytecode, + Op, + While, + keccak256, +) + +from tests.benchmark.stateful.helpers import ( + DECREMENT_COUNTER_CONDITION, + build_benchmark_txs, +) + + +@pytest.mark.stub_parametrize("factory_stub", "bloatnet_factory_") +def test_call_value_to_existing( + benchmark_test: BenchmarkTestFiller, + pre: Alloc, + fork: Fork, + gas_benchmark_value: int, + tx_gas_limit: int, + factory_stub: str, +) -> None: + """Benchmark CALL with value transfer to cold existing contracts.""" + factory_address = pre.deploy_contract( + code=Bytecode(), + stub=factory_stub, + ) + + # Contract Construction + setup = Bytecode() + + setup += Conditional( + condition=Op.STATICCALL( + gas=Op.GAS, + address=factory_address, + args_offset=0, + args_size=0, + ret_offset=96, + ret_size=64, + # gas accounting + address_warm=False, + old_memory_size=0, + new_memory_size=160, + ), + if_false=Op.INVALID, + ) + + create2_preimage = Create2PreimageLayout( + factory_address=factory_address, + salt=Op.CALLDATALOAD(32), + init_code_hash=Op.MLOAD(128), + old_memory_size=160, + ) + + setup += create2_preimage + setup += Op.CALLDATALOAD(0) # [num_contract] + + # CALL with value=1 to factory contracts. + # The address is computed inline via SHA3, avoiding DUP depth issues. + # gas=1: subcall gets 1 + 2300 stipend, still not enough for 24KB + # bytecode → subcall fails, but cold + value gas costs are charged. + call_value_op = Op.POP( + Op.CALL( + gas=1, + address=create2_preimage.address_op(), + value=1, + args_offset=0, + args_size=0, + ret_offset=0, + ret_size=0, + # gas accounting + value_transfer=True, + ) + ) + + loop = While( + body=(call_value_op + create2_preimage.increment_salt_op()), + condition=DECREMENT_COUNTER_CONDITION, + ) + + # Contract Deployment + code = setup + loop + attack_contract_address = pre.deploy_contract(code=code) + + # Gas Accounting + txs, total_gas_consumed = build_benchmark_txs( + pre=pre, + fork=fork, + gas_benchmark_value=gas_benchmark_value, + tx_gas_limit=tx_gas_limit, + attack_contract_address=attack_contract_address, + setup_cost=setup.gas_cost(fork), + iteration_cost=loop.gas_cost(fork), + ) + + benchmark_test( + pre=pre, + blocks=[Block(txs=txs)], + expected_benchmark_gas_used=total_gas_consumed, + skip_gas_used_validation=True, + ) + + +def test_call_value_to_empty( + benchmark_test: BenchmarkTestFiller, + pre: Alloc, + fork: Fork, + gas_benchmark_value: int, +) -> None: + """Benchmark CALL with value transfer to non-existent accounts.""" + # Memory layout: MEM[0..31] = counter (incremented each iteration) + setup = ( + Op.MSTORE( + 0, + Op.CALLDATALOAD(32), # salt_offset (starting counter) + # gas accounting + old_memory_size=0, + new_memory_size=32, + ) + + Op.CALLDATALOAD(0) # [num_calls] + ) + + # CALL with value=1 to keccak256-derived addresses. + # gas=0: subcall gets 0 + 2300 stipend. No code at target → succeeds. + # Value is transferred, new account is created in trie. + call_value_op = Op.POP( + Op.CALL( + gas=0, + address=Op.SHA3(0, 32, data_size=32), + value=1, + args_offset=0, + args_size=0, + ret_offset=0, + ret_size=0, + # gas accounting + value_transfer=True, + account_new=True, + ) + ) + + # Increment counter in memory for next address + increment_counter = Op.MSTORE(0, Op.ADD(Op.MLOAD(0), 1)) + + loop = While( + body=(call_value_op + increment_counter), + condition=DECREMENT_COUNTER_CONDITION, + ) + + # Contract Deployment — needs balance for value transfers (1 wei each) + code = IteratingBytecode( + setup=setup, + iterating=loop, + ) + + initial_balance = 10**9 + attack_contract_address = pre.deploy_contract( + code=code, + balance=initial_balance, + ) + + def calldata_builder(iteration_count: int, start_iteration: int) -> bytes: + return bytes(Hash(iteration_count) + Hash(start_iteration)) + + txs = list( + code.transactions_by_gas_limit( + fork=fork, + gas_limit=gas_benchmark_value, + sender=pre.fund_eoa(), + to=attack_contract_address, + calldata=calldata_builder, + ) + ) + + total_iterations = sum(int.from_bytes(tx.data[:32], "big") for tx in txs) + + def new_account_address(counter: int) -> Address: + return Address(bytes(keccak256(counter.to_bytes(32, "big")))[12:]) + + post = { + new_account_address(counter): Account(balance=1) + for counter in range(total_iterations) + } + post[attack_contract_address] = Account( + balance=initial_balance - total_iterations + ) + + expected_gas_used = ( + sum(tx.gas_cost for tx in txs) + - fork.gas_costs().CALL_STIPEND * total_iterations + ) + + benchmark_test( + pre=pre, + post=post, + blocks=[Block(txs=txs)], + expected_benchmark_gas_used=expected_gas_used, + ) diff --git a/tests/benchmark/stateful/bloatnet/test_create2_access.py b/tests/benchmark/stateful/bloatnet/test_create.py similarity index 97% rename from tests/benchmark/stateful/bloatnet/test_create2_access.py rename to tests/benchmark/stateful/bloatnet/test_create.py index 9d29933434f..bb21c99aea6 100644 --- a/tests/benchmark/stateful/bloatnet/test_create2_access.py +++ b/tests/benchmark/stateful/bloatnet/test_create.py @@ -1,4 +1,4 @@ -"""CREATE2 deploy-then-immediate-access benchmarks.""" +"""Benchmark CREATE2 deployment with immediate access to the new account.""" import pytest from execution_testing import ( @@ -22,9 +22,6 @@ DECREMENT_COUNTER_CONDITION, ) -REFERENCE_SPEC_GIT_PATH = "DUMMY/bloatnet.md" -REFERENCE_SPEC_VERSION = "1.0" - @pytest.mark.parametrize( "code_size", diff --git a/tests/benchmark/stateful/bloatnet/test_erc20.py b/tests/benchmark/stateful/bloatnet/test_erc20.py new file mode 100644 index 00000000000..6d2ccb97263 --- /dev/null +++ b/tests/benchmark/stateful/bloatnet/test_erc20.py @@ -0,0 +1,399 @@ +"""Benchmark storage operations through ERC20 calls.""" + +import pytest +from execution_testing import ( + AccessList, + Alloc, + BenchmarkTestFiller, + Block, + Bytecode, + Fork, + Op, + TestPhaseManager, + Transaction, + While, +) + +from tests.benchmark.stateful.helpers import ( + APPROVE_SELECTOR, + BALANCEOF_SELECTOR, +) + +# SLOAD BENCHMARK ARCHITECTURE: +# +# [Pre-deployed ERC20 Contract] ──── Storage slots for balances +# │ +# │ balanceOf(address) → SLOAD(keccak256(address || slot)) +# │ +# [Attack Contract] ──CALL──► ERC20.balanceOf(random_address) +# │ +# └─► Loop(i=0 to N): +# 1. Generate random address from counter +# 2. CALL balanceOf(random_address) → forces cold SLOAD +# 3. Most addresses have zero balance → empty storage slots +# +# WHY IT STRESSES CLIENTS: +# - Each balanceOf() call forces a cold SLOAD on a likely-empty slot +# - Storage slot = keccak256(address || balances_slot) +# - Random addresses ensure maximum cache misses +# - Tests client's sparse storage handling efficiency + + +@pytest.mark.stub_parametrize( + "erc20_stub", "test_sload_empty_erc20_balanceof_" +) +def test_sload_erc20_generic( + benchmark_test: BenchmarkTestFiller, + pre: Alloc, + fork: Fork, + gas_benchmark_value: int, + tx_gas_limit: int, + erc20_stub: str, +) -> None: + """Benchmark SLOAD using ERC20 balanceOf.""" + # Stub Account + erc20_address = pre.deploy_contract( + code=Bytecode(), + stub=erc20_stub, + ) + threshold = 100000 + + # MEM[0] = function selector + # MEM[32] = starting address offset + setup = Op.MSTORE( + 0, + BALANCEOF_SELECTOR, + # gas accounting + old_memory_size=0, + new_memory_size=32, + ) + Op.MSTORE( + 32, + Op.SLOAD(0), # Address Offset + # gas accounting + old_memory_size=32, + new_memory_size=64, + ) + + call_balance_of = Op.POP( + Op.CALL( + address=erc20_address, + args_offset=32 - 4, + args_size=32 + 4, + ) + ) + + loop = While( + body=call_balance_of + Op.MSTORE(32, Op.ADD(Op.MLOAD(32), 1)), + condition=Op.GT(Op.GAS, threshold), + ) + + teardown = Op.SSTORE(0, Op.MLOAD(32)) + + # Contract Deployment + code = setup + loop + teardown + attack_contract_address = pre.deploy_contract(code=code) + + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() + + # Transaction Loops + txs = [] + gas_remaining = gas_benchmark_value + + sender = pre.fund_eoa() + + while gas_remaining > intrinsic_gas: + gas_available = min(gas_remaining, tx_gas_limit) + + if gas_available < intrinsic_gas: + break + + with TestPhaseManager.execution(): + txs.append( + Transaction( + gas_limit=gas_available, + to=attack_contract_address, + sender=sender, + ) + ) + + gas_remaining -= gas_available + + blocks = [Block(txs=txs)] + benchmark_test( + pre=pre, + blocks=blocks, + skip_gas_used_validation=True, + expected_receipt_status=True, + ) + + +# SSTORE BENCHMARK ARCHITECTURE: +# +# [Pre-deployed ERC20 Contract] ──── Storage slots for allowances +# │ +# │ approve(spender, amount) +# │ → SSTORE(keccak256(spender || slot), amount) +# │ +# [Attack Contract] +# ──CALL──► ERC20.approve(counter_as_spender, counter_as_amount) +# │ +# └─► Loop(i=0 to N): +# 1. Use counter as both spender address and amount +# 2. CALL approve(counter, counter) → forces cold SSTORE +# 3. Writes to new allowance slots in sparse storage +# +# WHY IT STRESSES CLIENTS: +# - Each approve() call forces an SSTORE to a new storage slot +# - Storage slot = keccak256( +# msg.sender || keccak256(spender || allowances_slot) +# ) +# - Sequential counter ensures unique storage locations +# - Tests client's ability to handle many storage writes +# - Simulates real-world contract state accumulation over time + + +@pytest.mark.stub_parametrize("erc20_stub", "test_sstore_erc20_approve_") +def test_sstore_erc20_generic( + benchmark_test: BenchmarkTestFiller, + pre: Alloc, + fork: Fork, + gas_benchmark_value: int, + tx_gas_limit: int, + erc20_stub: str, +) -> None: + """Benchmark SSTORE using ERC20 approve.""" + sender = pre.fund_eoa() + + threshold = 100_000 + + # Stub Account + erc20_address = pre.deploy_contract( + code=Bytecode(), + stub=erc20_stub, + ) + + # MEM[0] = function selector + # MEM[32] = starting address offset + setup = Op.MSTORE( + 0, + APPROVE_SELECTOR, + ) + Op.MSTORE( + 32, + Op.SLOAD(0), # Address Offset + ) + + call_approve = Op.MSTORE( + 64, + Op.ADD(1, Op.MLOAD(32)), + ) + Op.POP( + Op.CALL( + address=erc20_address, + args_offset=28, + args_size=68, + ) + ) + + loop = While( + body=call_approve + Op.MSTORE(32, Op.ADD(Op.MLOAD(32), 1)), + condition=Op.GT(Op.GAS, threshold), + ) + + teardown = Op.SSTORE(0, Op.MLOAD(32)) + + # Contract Deployment + code = setup + loop + teardown + attack_contract_address = pre.deploy_contract(code=code) + + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() + + # Transaction Loops + gas_remaining = gas_benchmark_value + + # Collect tx params first, then build Transaction objects + # so that nonces are allocated contiguously per block. + tx_gas: list[int] = [] + while gas_remaining > intrinsic_gas: + gas_available = min(gas_remaining, tx_gas_limit) + + if gas_available < intrinsic_gas: + break + + tx_gas.append(gas_available) + + gas_remaining -= gas_available + + txs = [] + with TestPhaseManager.execution(): + for gas_available in tx_gas: + txs.append( + Transaction( + gas_limit=gas_available, + to=attack_contract_address, + sender=sender, + ) + ) + + blocks = [Block(txs=txs)] + + benchmark_test( + pre=pre, + blocks=blocks, + skip_gas_used_validation=True, + expected_receipt_status=True, + ) + + +@pytest.mark.stub_parametrize("erc20_stub", "test_mixed_sload_sstore_") +@pytest.mark.parametrize( + "sload_percent,sstore_percent", + [ + pytest.param(10, 90, id="10-90"), + pytest.param(30, 70, id="30-70"), + pytest.param(50, 50, id="50-50"), + pytest.param(70, 30, id="70-30"), + pytest.param(90, 10, id="90-10"), + ], +) +def test_mixed_sload_sstore( + benchmark_test: BenchmarkTestFiller, + pre: Alloc, + fork: Fork, + gas_benchmark_value: int, + tx_gas_limit: int, + erc20_stub: str, + sload_percent: int, + sstore_percent: int, +) -> None: + """Benchmark mixed SLOAD/SSTORE ratios on ERC20 contracts.""" + # The gas threshold is the minimum gas reserved to exit the + # loops and execute cleanup (SSTORE to persist slot offset). + # 150_000 is conservative: cold approve ~25K + cleanup ~20K. + gas_threshold = 150_000 + slot_offset_key = 0 # storage slot for persistent offset + + # Stub Account + erc20_address = pre.deploy_contract( + code=Bytecode(), + stub=erc20_stub, + ) + + # Contract Construction + # MEM[0] = function selector + # MEM[32] = address/slot offset (incremented each iteration) + # MEM[64] = spender/amount for approve (copied from MEM[32]) + # MEM[96] = initial_gas snapshot + # MEM[128] = gas_floor for SLOAD phase + setup = ( + Op.MSTORE( + 0, + BALANCEOF_SELECTOR, + old_memory_size=0, + new_memory_size=32, + ) + + Op.MSTORE( + 32, + Op.SLOAD(slot_offset_key), + old_memory_size=32, + new_memory_size=64, + ) + + Op.MSTORE( + 96, + Op.GAS, + old_memory_size=64, + new_memory_size=128, + ) + # gas_floor = initial_gas * sstore_percent / 100 + # This is the gas level at which SLOADs stop and + # SSTOREs begin, leaving sstore_percent of the + # initial gas for the SSTORE phase. + + Op.MSTORE( + 128, + Op.DIV(Op.MUL(Op.MLOAD(96), sstore_percent), 100), + old_memory_size=128, + new_memory_size=160, + ) + ) + + # SLOAD loop — STATICCALL since balanceOf is a view function. + # Continues while both: gas is above the sload/sstore + # transition floor AND above the safety threshold. + sload_loop = While( + body=Op.POP( + Op.STATICCALL( + address=erc20_address, + args_offset=28, + args_size=36, + ret_offset=0, + ret_size=0, + address_warm=True, + ) + ) + + Op.MSTORE(32, Op.ADD(Op.MLOAD(32), 1)), + condition=Op.AND( + Op.GT(Op.GAS, Op.MLOAD(128)), + Op.GT(Op.GAS, gas_threshold), + ), + ) + + transition = Op.MSTORE(0, APPROVE_SELECTOR) + + # SSTORE loop — runs until gas drops below safety threshold. + sstore_loop = While( + body=( + Op.MSTORE(64, Op.MLOAD(32)) + + Op.POP( + Op.CALL( + address=erc20_address, + value=0, + args_offset=28, + args_size=68, + ret_offset=0, + ret_size=0, + address_warm=True, + ) + ) + + Op.MSTORE(32, Op.ADD(Op.MLOAD(32), 1)) + ), + condition=Op.GT(Op.GAS, gas_threshold), + ) + + # Persist the final slot offset so the next tx continues + # from where this one left off. + cleanup = Op.SSTORE(slot_offset_key, Op.MLOAD(32)) + + # Contract Deployment + code = setup + sload_loop + transition + sstore_loop + cleanup + attack_contract_address = pre.deploy_contract( + code=code, + storage={slot_offset_key: 0}, + ) + + # Transaction Construction — no iteration count math. + # Each tx gets up to tx_gas_limit gas; the contract + # self-regulates via the GAS opcode. + access_list = [AccessList(address=erc20_address, storage_keys=[])] + intrinsic_gas_cost = fork.transaction_intrinsic_cost_calculator()( + access_list=access_list, + ) + + gas_remaining = gas_benchmark_value + txs = [] + while gas_remaining >= intrinsic_gas_cost + gas_threshold: + gas_limit = min(gas_remaining, tx_gas_limit) + txs.append( + Transaction( + gas_limit=gas_limit, + to=attack_contract_address, + sender=pre.fund_eoa(), + access_list=access_list, + ) + ) + gas_remaining -= gas_limit + + assert txs, "Gas loop produced zero transactions" + benchmark_test( + pre=pre, + blocks=[Block(txs=txs)], + skip_gas_used_validation=True, + expected_receipt_status=True, + ) diff --git a/tests/benchmark/stateful/bloatnet/test_extcodesize_bytecode_sizes.py b/tests/benchmark/stateful/bloatnet/test_extcodesize_bytecode_sizes.py deleted file mode 100644 index 386b4a81792..00000000000 --- a/tests/benchmark/stateful/bloatnet/test_extcodesize_bytecode_sizes.py +++ /dev/null @@ -1,159 +0,0 @@ -"""Cold EXTCODESIZE benchmarks across pre-deployed bytecode sizes.""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Block, - BlockchainTestFiller, - Bytecode, - Conditional, - Create2PreimageLayout, - Op, - Storage, - Transaction, - While, -) - -REFERENCE_SPEC_GIT_PATH = "DUMMY/bloatnet.md" -REFERENCE_SPEC_VERSION = "1.0" - - -def get_factory_stub_name(size_kb: float) -> str: - """Generate stub name for factory based on size.""" - if size_kb == 0.5: - return "bloatnet_factory_0_5kb" - elif size_kb == 1.0: - return "bloatnet_factory_1kb" - elif size_kb == 2.0: - return "bloatnet_factory_2kb" - elif size_kb == 5.0: - return "bloatnet_factory_5kb" - elif size_kb == 10.0: - return "bloatnet_factory_10kb" - elif size_kb == 24.0: - return "bloatnet_factory_24kb" - else: - raise ValueError(f"Unsupported size: {size_kb}KB") - - -def build_attack_contract(factory_address: Address) -> Bytecode: - """Build the EXTCODESIZE attack contract with a gas-based loop exit.""" - gas_reserve = 50_000 # Reserve for 2x SSTORE + cleanup - num_deployed_offset = 96 - init_code_hash_offset = num_deployed_offset + 32 - return_size = 64 - return ( - # Call factory.getConfig() -> (num_deployed, init_code_hash) - Conditional( - condition=Op.STATICCALL( - gas=Op.GAS, - address=factory_address, - args_offset=0, - args_size=0, - # MEM[num_deployed_offset]=num_deployed - # MEM[num_deployed_offset + 32]=init_code_hash - ret_offset=num_deployed_offset, - ret_size=return_size, - ), - if_false=Op.REVERT(0, 0), - ) - + ( - create2_preimage := Create2PreimageLayout( - factory_address=factory_address, - salt=Op.SLOAD(0), - init_code_hash=Op.MLOAD(init_code_hash_offset), - old_memory_size=num_deployed_offset + return_size, - ) - ) - + Op.MSTORE(160, 0) # Initialize last_size - + While( - body=( - Op.MSTORE(160, Op.EXTCODESIZE(create2_preimage.address_op())) - + create2_preimage.increment_salt_op() - ), - condition=( - Op.AND( - Op.GT(Op.GAS, gas_reserve), - # num_deployed > salt - Op.GT( - Op.MLOAD(num_deployed_offset), - Op.MLOAD(create2_preimage.salt_offset), - ), - ) - ), - ) - + Op.SSTORE(0, Op.MLOAD(32)) # Save final salt - + Op.SSTORE(1, Op.MLOAD(160)) # Save last result - + Op.STOP - ) - - -@pytest.mark.parametrize( - "bytecode_size_kb", - [0.5, 1.0, 2.0, 5.0, 10.0, 24.0], - ids=lambda size: f"{size}KB", -) -@pytest.mark.valid_from("Prague") -def test_extcodesize_bytecode_sizes( - blockchain_test: BlockchainTestFiller, - pre: Alloc, - bytecode_size_kb: float, - gas_benchmark_value: int, - tx_gas_limit: int, -) -> None: - """Execute EXTCODESIZE benchmark against pre-deployed contracts.""" - expected_size_bytes = int(bytecode_size_kb * 1024) - - # Get factory stub name for this size - factory_stub = get_factory_stub_name(bytecode_size_kb) - - # Deploy factory stub (address comes from stub file) - factory_address = pre.deploy_contract( - code=Bytecode(), # Empty bytecode - address from stub - stub=factory_stub, - ) - - # Build and deploy the attack contract - attack_code = build_attack_contract(factory_address) - attack_address = pre.deploy_contract(code=attack_code) - - # Calculate how many transactions we need to fill the block - num_attack_txs = gas_benchmark_value // tx_gas_limit - if num_attack_txs == 0: - num_attack_txs = 1 - - # Fund the sender - sender = pre.fund_eoa() - - # Build transactions - txs = [] - - # Attack transactions: all identical, no calldata needed - for _ in range(num_attack_txs): - attack_tx = Transaction( - gas_limit=tx_gas_limit, - to=attack_address, - sender=sender, - ) - txs.append(attack_tx) - - # Create block with all transactions - block = Block(txs=txs) - - # Post-state verification: - # Attack contract slot 1 = expected size (last EXTCODESIZE result) - # Slot 0 can be any value (final salt depends on gas used) - attack_storage = Storage({1: expected_size_bytes}) # type: ignore[dict-item] - attack_storage.set_expect_any(0) - - post = { - attack_address: Account(storage=attack_storage), - } - - blockchain_test( - pre=pre, - post=post, - blocks=[block], - ) diff --git a/tests/benchmark/stateful/bloatnet/test_multi_opcode.py b/tests/benchmark/stateful/bloatnet/test_multi_opcode.py deleted file mode 100755 index 19301c47e74..00000000000 --- a/tests/benchmark/stateful/bloatnet/test_multi_opcode.py +++ /dev/null @@ -1,517 +0,0 @@ -"""BloatNet benchmarks from https://hackmd.io/9icZeLN7R0Sk5mIjKlZAHQ.""" - -import pytest -from execution_testing import ( - AccessList, - Account, - Address, - Alloc, - BenchmarkTestFiller, - Block, - Bytecode, - Conditional, - Create2PreimageLayout, - Fork, - Hash, - IteratingBytecode, - Op, - Transaction, - While, - keccak256, -) - -from tests.benchmark.stateful.helpers import ( - APPROVE_SELECTOR, - BALANCEOF_SELECTOR, - DECREMENT_COUNTER_CONDITION, - build_benchmark_txs, -) - -REFERENCE_SPEC_GIT_PATH = "DUMMY/bloatnet.md" -REFERENCE_SPEC_VERSION = "1.0" - - -@pytest.mark.stub_parametrize("factory_stub", "bloatnet_factory_") -@pytest.mark.parametrize( - "second_opcode", - [Op.EXTCODESIZE, Op.EXTCODECOPY, Op.EXTCODEHASH, Op.STATICCALL, Op.CALL], -) -@pytest.mark.parametrize( - "balance_first", - [True, False], - ids=["balance_first", "opcode_first"], -) -def test_bloatnet_balance_opcode( - benchmark_test: BenchmarkTestFiller, - pre: Alloc, - fork: Fork, - gas_benchmark_value: int, - tx_gas_limit: int, - balance_first: bool, - second_opcode: Op, - factory_stub: str, -) -> None: - """Benchmark BALANCE paired with a second opcode on bloatnet factories.""" - factory_address = pre.deploy_contract( - code=Bytecode(), - stub=factory_stub, - ) - - # Contract Construction - setup = Bytecode() - - setup += Conditional( - condition=Op.STATICCALL( - gas=Op.GAS, - address=factory_address, - args_offset=0, - args_size=0, - ret_offset=96, - ret_size=64, - # gas accounting - address_warm=False, - old_memory_size=0, - new_memory_size=160, - ), - if_false=Op.INVALID, - ) - - create2_preimage = Create2PreimageLayout( - factory_address=factory_address, - salt=Op.CALLDATALOAD(32), - init_code_hash=Op.MLOAD(128), - old_memory_size=160, - ) - - setup += create2_preimage - setup += Op.CALLDATALOAD(0) # [num_contract] - - # Build the second opcode's bytecode - balance_op = Op.POP(Op.BALANCE) - - if second_opcode == Op.EXTCODESIZE: - other_op = Op.POP(Op.EXTCODESIZE) - elif second_opcode == Op.EXTCODECOPY: - max_contract_size = fork.max_code_size() - other_op = Op.POP( - Op.EXTCODECOPY( - address=Op.DUP4, - dest_offset=Op.ADD(Op.MLOAD(32), 96), - offset=max_contract_size - 1, - size=1, - data_size=1, - ) - ) - elif second_opcode == Op.EXTCODEHASH: - other_op = Op.POP(Op.EXTCODEHASH) - elif second_opcode == Op.STATICCALL: - # gas=1: forces account/code loading, then fails - other_op = ( - Op.POP( - Op.STATICCALL( - gas=1, - address=Op.DUP5, - args_offset=0, - args_size=0, - ret_offset=0, - ret_size=0, - ) - ) - + Op.POP - ) - elif second_opcode == Op.CALL: - # gas=1: forces account/code loading, then fails - other_op = ( - Op.POP( - Op.CALL( - gas=1, - address=Op.DUP6, - value=0, - args_offset=0, - args_size=0, - ret_offset=0, - ret_size=0, - ) - ) - + Op.POP - ) - else: - raise ValueError(f"Unsupported opcode: {second_opcode}") - - benchmark_ops = ( - (balance_op + other_op) if balance_first else (other_op + balance_op) - ) - - loop = While( - body=( - create2_preimage.address_op() - + Op.DUP1 - + benchmark_ops - + create2_preimage.increment_salt_op() - ), - condition=DECREMENT_COUNTER_CONDITION, - ) - - # Contract Deployment - code = setup + loop - attack_contract_address = pre.deploy_contract(code=code) - - # Gas Accounting - txs, total_gas_consumed = build_benchmark_txs( - pre=pre, - fork=fork, - gas_benchmark_value=gas_benchmark_value, - tx_gas_limit=tx_gas_limit, - attack_contract_address=attack_contract_address, - setup_cost=setup.gas_cost(fork), - iteration_cost=loop.gas_cost(fork), - ) - - benchmark_test( - pre=pre, - blocks=[Block(txs=txs)], - expected_benchmark_gas_used=total_gas_consumed, - skip_gas_used_validation=True, - ) - - -@pytest.mark.stub_parametrize("factory_stub", "bloatnet_factory_") -def test_bloatnet_call_value_existing( - benchmark_test: BenchmarkTestFiller, - pre: Alloc, - fork: Fork, - gas_benchmark_value: int, - tx_gas_limit: int, - factory_stub: str, -) -> None: - """Benchmark CALL with value transfer to cold existing contracts.""" - factory_address = pre.deploy_contract( - code=Bytecode(), - stub=factory_stub, - ) - - # Contract Construction - setup = Bytecode() - - setup += Conditional( - condition=Op.STATICCALL( - gas=Op.GAS, - address=factory_address, - args_offset=0, - args_size=0, - ret_offset=96, - ret_size=64, - # gas accounting - address_warm=False, - old_memory_size=0, - new_memory_size=160, - ), - if_false=Op.INVALID, - ) - - create2_preimage = Create2PreimageLayout( - factory_address=factory_address, - salt=Op.CALLDATALOAD(32), - init_code_hash=Op.MLOAD(128), - old_memory_size=160, - ) - - setup += create2_preimage - setup += Op.CALLDATALOAD(0) # [num_contract] - - # CALL with value=1 to factory contracts. - # The address is computed inline via SHA3, avoiding DUP depth issues. - # gas=1: subcall gets 1 + 2300 stipend, still not enough for 24KB - # bytecode → subcall fails, but cold + value gas costs are charged. - call_value_op = Op.POP( - Op.CALL( - gas=1, - address=create2_preimage.address_op(), - value=1, - args_offset=0, - args_size=0, - ret_offset=0, - ret_size=0, - # gas accounting - value_transfer=True, - ) - ) - - loop = While( - body=(call_value_op + create2_preimage.increment_salt_op()), - condition=DECREMENT_COUNTER_CONDITION, - ) - - # Contract Deployment - code = setup + loop - attack_contract_address = pre.deploy_contract(code=code) - - # Gas Accounting - txs, total_gas_consumed = build_benchmark_txs( - pre=pre, - fork=fork, - gas_benchmark_value=gas_benchmark_value, - tx_gas_limit=tx_gas_limit, - attack_contract_address=attack_contract_address, - setup_cost=setup.gas_cost(fork), - iteration_cost=loop.gas_cost(fork), - ) - - benchmark_test( - pre=pre, - blocks=[Block(txs=txs)], - expected_benchmark_gas_used=total_gas_consumed, - skip_gas_used_validation=True, - ) - - -def test_bloatnet_call_value_new_account( - benchmark_test: BenchmarkTestFiller, - pre: Alloc, - fork: Fork, - gas_benchmark_value: int, -) -> None: - """Benchmark CALL with value transfer to non-existent accounts.""" - # Memory layout: MEM[0..31] = counter (incremented each iteration) - setup = ( - Op.MSTORE( - 0, - Op.CALLDATALOAD(32), # salt_offset (starting counter) - # gas accounting - old_memory_size=0, - new_memory_size=32, - ) - + Op.CALLDATALOAD(0) # [num_calls] - ) - - # CALL with value=1 to keccak256-derived addresses. - # gas=0: subcall gets 0 + 2300 stipend. No code at target → succeeds. - # Value is transferred, new account is created in trie. - call_value_op = Op.POP( - Op.CALL( - gas=0, - address=Op.SHA3(0, 32, data_size=32), - value=1, - args_offset=0, - args_size=0, - ret_offset=0, - ret_size=0, - # gas accounting - value_transfer=True, - account_new=True, - ) - ) - - # Increment counter in memory for next address - increment_counter = Op.MSTORE(0, Op.ADD(Op.MLOAD(0), 1)) - - loop = While( - body=(call_value_op + increment_counter), - condition=DECREMENT_COUNTER_CONDITION, - ) - - # Contract Deployment — needs balance for value transfers (1 wei each) - code = IteratingBytecode( - setup=setup, - iterating=loop, - ) - - initial_balance = 10**9 - attack_contract_address = pre.deploy_contract( - code=code, - balance=initial_balance, - ) - - def calldata_builder(iteration_count: int, start_iteration: int) -> bytes: - return bytes(Hash(iteration_count) + Hash(start_iteration)) - - txs = list( - code.transactions_by_gas_limit( - fork=fork, - gas_limit=gas_benchmark_value, - sender=pre.fund_eoa(), - to=attack_contract_address, - calldata=calldata_builder, - ) - ) - - total_iterations = sum(int.from_bytes(tx.data[:32], "big") for tx in txs) - - def new_account_address(counter: int) -> Address: - return Address(bytes(keccak256(counter.to_bytes(32, "big")))[12:]) - - post = { - new_account_address(counter): Account(balance=1) - for counter in range(total_iterations) - } - post[attack_contract_address] = Account( - balance=initial_balance - total_iterations - ) - - expected_gas_used = ( - sum(tx.gas_cost for tx in txs) - - fork.gas_costs().CALL_STIPEND * total_iterations - ) - - benchmark_test( - pre=pre, - post=post, - blocks=[Block(txs=txs)], - expected_benchmark_gas_used=expected_gas_used, - ) - - -@pytest.mark.stub_parametrize("erc20_stub", "test_mixed_sload_sstore_") -@pytest.mark.parametrize( - "sload_percent,sstore_percent", - [ - pytest.param(10, 90, id="10-90"), - pytest.param(30, 70, id="30-70"), - pytest.param(50, 50, id="50-50"), - pytest.param(70, 30, id="70-30"), - pytest.param(90, 10, id="90-10"), - ], -) -def test_mixed_sload_sstore( - benchmark_test: BenchmarkTestFiller, - pre: Alloc, - fork: Fork, - gas_benchmark_value: int, - tx_gas_limit: int, - erc20_stub: str, - sload_percent: int, - sstore_percent: int, -) -> None: - """Benchmark mixed SLOAD/SSTORE ratios on bloatnet ERC20 contracts.""" - # The gas threshold is the minimum gas reserved to exit the - # loops and execute cleanup (SSTORE to persist slot offset). - # 150_000 is conservative: cold approve ~25K + cleanup ~20K. - gas_threshold = 150_000 - slot_offset_key = 0 # storage slot for persistent offset - - # Stub Account - erc20_address = pre.deploy_contract( - code=Bytecode(), - stub=erc20_stub, - ) - - # Contract Construction - # MEM[0] = function selector - # MEM[32] = address/slot offset (incremented each iteration) - # MEM[64] = spender/amount for approve (copied from MEM[32]) - # MEM[96] = initial_gas snapshot - # MEM[128] = gas_floor for SLOAD phase - setup = ( - Op.MSTORE( - 0, - BALANCEOF_SELECTOR, - old_memory_size=0, - new_memory_size=32, - ) - + Op.MSTORE( - 32, - Op.SLOAD(slot_offset_key), - old_memory_size=32, - new_memory_size=64, - ) - + Op.MSTORE( - 96, - Op.GAS, - old_memory_size=64, - new_memory_size=128, - ) - # gas_floor = initial_gas * sstore_percent / 100 - # This is the gas level at which SLOADs stop and - # SSTOREs begin, leaving sstore_percent of the - # initial gas for the SSTORE phase. - + Op.MSTORE( - 128, - Op.DIV(Op.MUL(Op.MLOAD(96), sstore_percent), 100), - old_memory_size=128, - new_memory_size=160, - ) - ) - - # SLOAD loop — STATICCALL since balanceOf is a view function. - # Continues while both: gas is above the sload/sstore - # transition floor AND above the safety threshold. - sload_loop = While( - body=Op.POP( - Op.STATICCALL( - address=erc20_address, - args_offset=28, - args_size=36, - ret_offset=0, - ret_size=0, - address_warm=True, - ) - ) - + Op.MSTORE(32, Op.ADD(Op.MLOAD(32), 1)), - condition=Op.AND( - Op.GT(Op.GAS, Op.MLOAD(128)), - Op.GT(Op.GAS, gas_threshold), - ), - ) - - transition = Op.MSTORE(0, APPROVE_SELECTOR) - - # SSTORE loop — runs until gas drops below safety threshold. - sstore_loop = While( - body=( - Op.MSTORE(64, Op.MLOAD(32)) - + Op.POP( - Op.CALL( - address=erc20_address, - value=0, - args_offset=28, - args_size=68, - ret_offset=0, - ret_size=0, - address_warm=True, - ) - ) - + Op.MSTORE(32, Op.ADD(Op.MLOAD(32), 1)) - ), - condition=Op.GT(Op.GAS, gas_threshold), - ) - - # Persist the final slot offset so the next tx continues - # from where this one left off. - cleanup = Op.SSTORE(slot_offset_key, Op.MLOAD(32)) - - # Contract Deployment - code = setup + sload_loop + transition + sstore_loop + cleanup - attack_contract_address = pre.deploy_contract( - code=code, - storage={slot_offset_key: 0}, - ) - - # Transaction Construction — no iteration count math. - # Each tx gets up to tx_gas_limit gas; the contract - # self-regulates via the GAS opcode. - access_list = [AccessList(address=erc20_address, storage_keys=[])] - intrinsic_gas_cost = fork.transaction_intrinsic_cost_calculator()( - access_list=access_list, - ) - - gas_remaining = gas_benchmark_value - txs = [] - while gas_remaining >= intrinsic_gas_cost + gas_threshold: - gas_limit = min(gas_remaining, tx_gas_limit) - txs.append( - Transaction( - gas_limit=gas_limit, - to=attack_contract_address, - sender=pre.fund_eoa(), - access_list=access_list, - ) - ) - gas_remaining -= gas_limit - - assert txs, "Gas loop produced zero transactions" - benchmark_test( - pre=pre, - blocks=[Block(txs=txs)], - skip_gas_used_validation=True, - expected_receipt_status=True, - ) diff --git a/tests/benchmark/stateful/bloatnet/test_single_opcode.py b/tests/benchmark/stateful/bloatnet/test_single_opcode.py deleted file mode 100644 index 5a52f083807..00000000000 --- a/tests/benchmark/stateful/bloatnet/test_single_opcode.py +++ /dev/null @@ -1,1549 +0,0 @@ -""" -abstract: BloatNet single-opcode benchmark cases for state-related operations. - - These tests focus on individual EVM opcodes (SLOAD, SSTORE) to measure - their performance when accessing many storage slots across pre-deployed - contracts. Unlike multi-opcode tests, these isolate single operations - to benchmark specific state-handling bottlenecks. -""" - -from functools import partial -from typing import Any, Callable, Generator, List - -import pytest -from execution_testing import ( - EOA, - AccessList, - Address, - Alloc, - AuthorizationTuple, - BalAccountExpectation, - BalNonceChange, - BalStorageSlot, - BenchmarkTestFiller, - Block, - BlockAccessListExpectation, - Bytecode, - Fork, - Hash, - IteratingBytecode, - JumpLoopGenerator, - Op, - RecipientType, - Storage, - TestPhaseManager, - Transaction, - While, -) -from execution_testing.base_types.base_types import Number - -from tests.benchmark.stateful.helpers import ( - APPROVE_SELECTOR, - BALANCEOF_SELECTOR, - CacheStrategy, - build_cache_strategy_blocks, - build_delegated_storage_setup, - create_sstore_initializer, - initializer_calldata_generator, -) - -REFERENCE_SPEC_GIT_PATH = "DUMMY/bloatnet.md" -REFERENCE_SPEC_VERSION = "1.0" - -# keccak256("random") for non-existing slots, masked as address, -# Solidity does input checks on the size and throws if we input -# something different than an address -START_SLOT = ( - 0xA4896A3F93BF4BF58378E579F3CF193BB4AF1022AF7D2089F37D8BAE7157B85F - % (2**160) -) - - -def _max_sloads_per_tx(tx_gas_limit: int, fork: Fork) -> int: - """ - Conservative upper bound on cold SLOADs that fit in a max-gas tx. - - Derived from the cold SLOAD cost (EIP-2929: 2100 gas) and used by - the bloated SLOAD benchmarks both as the inter-tx offset stride - (to keep consecutive txs' SLOAD ranges disjoint) and as the - per-target storage pre-load count. - """ - cold_sload_cost = Op.SLOAD(key_warm=False).gas_cost(fork) - return tx_gas_limit // cold_sload_cost - - -def _sender_generator( - pre: Alloc, distinct_senders: bool -) -> Generator[EOA, None, None]: - """ - Yield one sender per tx. - - In distinct mode, yields a fresh EOA per call. Otherwise, yields - the same shared sender for every call. Used by the bloated SLOAD - benchmarks so the BAL builder can group nonce changes by sender - uniformly regardless of mode. - """ - shared_sender = pre.fund_eoa() if not distinct_senders else None - while True: - yield pre.fund_eoa() if shared_sender is None else shared_sender - - -def delegate_with_calldata( - pre: Alloc, - fork: Fork, - authority: EOA, - address: Address, - calldata: Hash, -) -> Transaction: - """ - Create a tx that delegates the authority and calls it with calldata. - - The delegated code determines what happens with the calldata. - The authority nonce is incremented in-place. - """ - intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( - calldata=bytes(calldata), - authorization_list_or_count=1, - ) - gas_limit = intrinsic_gas + 500_000 - tx = Transaction( - gas_limit=gas_limit, - to=authority, - value=0, - data=calldata, - sender=pre.fund_eoa(), - authorization_list=[ - AuthorizationTuple( - chain_id=0, - address=address, - nonce=authority.nonce, - signer=authority, - ), - ], - ) - authority.nonce = Number(authority.nonce + 1) - return tx - - -def run_bloated_eoa_benchmark( - *, - benchmark_test: BenchmarkTestFiller, - pre: Alloc, - fork: Fork, - gas_benchmark_value: int, - tx_gas_limit: int, - authority: EOA, - existing_slots: bool, - runtime_code: Bytecode, - cache_strategy: CacheStrategy, - tx_generator: Callable[[EOA], list[Transaction]] | None = None, -) -> None: - """ - Run a bloated-EOA benchmark with the given runtime delegation code. - """ - slot_0_value = Hash(1) if existing_slots else Hash(START_SLOT) - - setter_address = pre.deploy_contract(code=Op.SSTORE(0, Op.CALLDATALOAD(0))) - runtime_address = pre.deploy_contract(code=runtime_code) - - init_tx = delegate_with_calldata( - pre, - fork, - authority, - setter_address, - slot_0_value, - ) - runtime_tx = delegate_with_calldata( - pre, - fork, - authority, - runtime_address, - Hash(0), - ) - - blocks: list[Block] = [Block(txs=[init_tx, runtime_tx])] - - sender = pre.fund_eoa() - - txs: list[Transaction] = [] - with TestPhaseManager.execution(): - if tx_generator is not None: - txs = tx_generator(sender) - else: - gas_available = gas_benchmark_value - intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() - while gas_available >= intrinsic_gas: - tx_gas = min(gas_available, tx_gas_limit) - txs.append( - Transaction( - gas_limit=tx_gas, - to=authority, - sender=sender, - ) - ) - gas_available -= tx_gas - - cache_txs: list[Transaction] = [] - if cache_strategy == CacheStrategy.CACHE_PREVIOUS_BLOCK: - with TestPhaseManager.setup(): - cache_sender = pre.fund_eoa() - for tx in txs: - cache_txs.append( - Transaction( - gas_limit=tx.gas_limit, - data=tx.data, - to=authority, - sender=cache_sender, - ) - ) - - blocks += build_cache_strategy_blocks(cache_strategy, txs, cache_txs) - - benchmark_test( - pre=pre, - blocks=blocks, - skip_gas_used_validation=True, - expected_receipt_status=True, - ) - - -@pytest.mark.repricing -@pytest.mark.stub_parametrize("token_name", "bloated_eoa_") -@pytest.mark.parametrize("existing_slots", [False, True]) -@pytest.mark.parametrize("cache_strategy", [CacheStrategy.NO_CACHE]) -def test_sload_bloated( - benchmark_test: BenchmarkTestFiller, - pre: Alloc, - fork: Fork, - gas_benchmark_value: int, - tx_gas_limit: int, - token_name: str, - existing_slots: bool, - cache_strategy: CacheStrategy, -) -> None: - """ - Benchmark SLOAD opcodes targeting an EOA with storage bloated. - - The storage is assumed to be filled from 0-N linearly, where - each slot has the value of the key. If this is not the - storage layout of the target account, then the existing_slots - parameter will not be correct. - """ - slot_access = ( - Op.DUP1 # [index, index] - + Op.SLOAD # [s[index], index] - + Op.POP # [index] - ) - # CACHE_TX: access each slot twice so the second hit is uncached - if cache_strategy == CacheStrategy.CACHE_TX: - slot_access *= 2 - - runtime_code = ( - Op.PUSH0 # [0] - + Op.SLOAD # [index], s[0] = index - + While( - body=( - slot_access - + Op.PUSH1(1) # [1, index] - + Op.ADD # [index+1] - ), - condition=Op.GT(Op.GAS, 0xFFFF), - ) - + Op.PUSH0 # [0, index+1] - + Op.SSTORE # s[0] = index+1 - ) - - run_bloated_eoa_benchmark( - benchmark_test=benchmark_test, - pre=pre, - fork=fork, - gas_benchmark_value=gas_benchmark_value, - tx_gas_limit=tx_gas_limit, - authority=pre.stub_eoa(token_name), - existing_slots=existing_slots, - runtime_code=runtime_code, - cache_strategy=cache_strategy, - ) - - -@pytest.mark.stub_parametrize("token_name", "bloated_eoa_") -@pytest.mark.parametrize("distinct_senders", [False, True]) -@pytest.mark.parametrize("existing_slots", [False, True]) -def test_sload_bloated_prefetch_miss( - benchmark_test: BenchmarkTestFiller, - pre: Alloc, - fork: Fork, - gas_benchmark_value: int, - tx_gas_limit: int, - token_name: str, - existing_slots: bool, - distinct_senders: bool, -) -> None: - """ - Benchmark SLOAD with calldata-driven offsets to defeat prefetching. - - A small first transaction writes an initial offset into the - authority's slot 0 via calldata. Subsequent max-gas transactions - each read the previous offset from slot 0, immediately overwrite - slot 0 with a new offset from their own calldata, then SLOAD - sequentially from the previous offset. Because each transaction's - SLOAD range depends on state written by its predecessor, a - prefetcher that predicts SLOAD targets from pre-block state - without simulating intra-block writes will pre-warm incorrect - storage slots. The minimal first tx is load-bearing: it lives - inside the benchmark block so every subsequent max-gas tx reads - a slot 0 value that differs from the prefetcher's pre-block - snapshot, achieving a 100% miss rate. - - When ``distinct_senders`` is True every transaction uses a fresh - sender. This additionally defeats per-sender prewarm - serialization (e.g. Nethermind) that groups txs by sender and - runs them sequentially to propagate state changes — forcing - every tx's prewarm scope to restart from pre-block state. - """ - # Runtime: read old offset from slot 0, write new offset from - # calldata to slot 0, then SLOAD sequentially from old offset. - runtime_code = ( - Op.SLOAD(Op.PUSH0) - + Op.SSTORE(Op.PUSH0, Op.CALLDATALOAD(Op.PUSH0)) - + While( - body=(Op.DUP1 + Op.SLOAD + Op.POP + Op.PUSH1(1) + Op.ADD), - condition=Op.GT(Op.GAS, 0xFFFF), - ) - ) - - authority = pre.stub_eoa(token_name) - runtime_address = pre.deploy_contract(code=runtime_code) - - # Setup: delegate authority to the runtime contract. Slot 0 is - # left at 0 (the delegation tx's calldata) so the benchmark - # block's pre-state has slot 0 = 0; the first benchmark tx - # then plants base_offset in slot 0 inside the benchmark block, - # forcing the prefetcher's pre-block snapshot to disagree with - # the actual slot 0 value seen by every max-gas tx that follows. - delegation_tx = delegate_with_calldata( - pre, - fork, - authority, - runtime_address, - Hash(0), - ) - - blocks: list[Block] = [Block(txs=[delegation_tx])] - - # Offset spacing: upper bound on SLOADs per tx ensures each - # transaction reads a completely disjoint slot range. - max_sloads_per_tx = _max_sloads_per_tx(tx_gas_limit, fork) - - # The base offset must be at least max_sloads_per_tx away from - # the pre-block slot 0 value (0) so the prefetcher's predicted - # SLOAD range is completely disjoint from the actual range. - base_offset = max_sloads_per_tx if existing_slots else START_SLOT - intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( - calldata=b"\xff" * 32, - ) - - # senders_iter yields one sender per tx (fresh per call in - # distinct mode, a single shared sender otherwise). The senders - # list collects one entry per tx so the BAL builder below can - # group nonce changes by sender uniformly. - senders_iter = _sender_generator(pre, distinct_senders) - senders: list[EOA] = [] - - gas_available = gas_benchmark_value - txs: list[Transaction] = [] - - # First transaction: minimal gas, only writes the initial - # offset. Gas limit ensures remaining gas after the SLOAD + - # SSTORE setup falls below the 0xFFFF loop threshold so the - # SLOAD loop does not run. This tx's job is to change slot 0 - # inside the benchmark block so every subsequent max-gas tx - # reads an offset the prefetcher's pre-block snapshot does - # not see, achieving a 100% prefetch miss rate on max-gas txs. - first_tx_gas = min(gas_available, intrinsic_gas + 30_000) - sender = next(senders_iter) - senders.append(sender) - txs.append( - Transaction( - gas_limit=first_tx_gas, - to=authority, - data=Hash(base_offset), - sender=sender, - ) - ) - gas_available -= first_tx_gas - - # Subsequent transactions: max gas, each shifts the offset - # so the next transaction SLOADs from a different range. - tx_index = 1 - while gas_available >= intrinsic_gas: - tx_gas = min(gas_available, tx_gas_limit) - new_offset = base_offset + tx_index * max_sloads_per_tx - sender = next(senders_iter) - senders.append(sender) - txs.append( - Transaction( - gas_limit=tx_gas, - to=authority, - data=Hash(new_offset), - sender=sender, - ) - ) - gas_available -= tx_gas - tx_index += 1 - - expectations: dict[Address, BalAccountExpectation] = { - authority: BalAccountExpectation( - storage_reads=[base_offset], - storage_changes=[ - BalStorageSlot( - slot=0, - validate_any_change=True, - ), - ], - ), - } - sender_nonces: dict[Address, list[BalNonceChange]] = {} - for i, s in enumerate(senders): - changes = sender_nonces.setdefault(s, []) - changes.append( - BalNonceChange( - block_access_index=i + 1, - post_nonce=len(changes) + 1, - ) - ) - for addr, nonces in sender_nonces.items(): - expectations[addr] = BalAccountExpectation(nonce_changes=nonces) - blocks.append( - Block( - txs=txs, - expected_block_access_list=BlockAccessListExpectation( - account_expectations=expectations, - ), - ) - ) - - benchmark_test( - pre=pre, - blocks=blocks, - skip_gas_used_validation=True, - expected_receipt_status=True, - ) - - -@pytest.mark.parametrize("distinct_senders", [False, True]) -@pytest.mark.parametrize("existing_slots", [False, True]) -def test_sload_bloated_multi_contract( - benchmark_test: BenchmarkTestFiller, - pre: Alloc, - fork: Fork, - gas_benchmark_value: int, - tx_gas_limit: int, - existing_slots: bool, - distinct_senders: bool, -) -> None: - """ - Benchmark SLOAD across a distinct contract per transaction. - - Each transaction calls a freshly-deployed contract whose slot 0 - is pre-loaded with the starting offset; the contract then runs a - SLOAD loop over sequential slots until gas runs low. Unlike - test_sload_bloated_prefetch_miss which hammers one account's - storage trie via an EIP-7702 delegated EOA, every transaction - here opens a different storage trie, stressing cross-account - state access and state-trie breadth in a single block. - - Every target contract first CALLs a shared offset_holder - contract whose slot 0 is read, incremented, and written back. - This mirrors the first test's "same-contract slot 0" dependency - pattern via cross-contract CALL: every transaction forms a - read-after-write edge on offset_holder's slot 0, preventing - parallel execution. - - When ``distinct_senders`` is True every transaction uses a fresh - sender. This additionally exercises per-sender prewarm - serialization (e.g. Nethermind) differently than the shared- - sender case; we run both so clients can be measured in both - regimes. - """ - # Shared offset_holder: reads, increments, and writes its own - # slot 0. Every target CALLs this to create an inter-tx RAW - # dependency chain on a single shared storage slot. - offset_holder = pre.deploy_contract( - code=Op.SSTORE(0, Op.ADD(Op.SLOAD(0), 1)), - ) - - # Target runtime: CALL offset_holder (for the dependency), then - # run the same SLOAD loop as test_sload_bloated in its own - # storage. Final counter is written back to slot 0. - runtime_code = ( - Op.POP( - Op.CALL( - address=offset_holder, - ) - ) - + Op.SLOAD(Op.PUSH0) - + While( - body=(Op.DUP1 + Op.SLOAD + Op.POP + Op.PUSH1(1) + Op.ADD), - condition=Op.GT(Op.GAS, 0xFFFF), - ) - + Op.PUSH0 - + Op.SSTORE - ) - - base_offset = 1 if existing_slots else START_SLOT - max_sloads_per_tx = _max_sloads_per_tx(tx_gas_limit, fork) - - # Pre-load slot 0 with the starting offset. For existing_slots, - # also fill the slot range the loop will read so SLOADs land on - # populated entries rather than empty slots. A fresh Storage - # instance is built per deployment (below) so that every target - # gets an independent root dict, not an alias of the same one. - storage_data: Storage.StorageDictType = {0: base_offset} - if existing_slots: - for i in range(base_offset, base_offset + max_sloads_per_tx): - storage_data[i] = i - - intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() - # Minimum per-tx gas ensuring the SLOAD loop runs at least one - # iteration so every target satisfies storage_reads=[base_offset]: - # intrinsic + CALL + offset_holder + setup + 0xFFFF loop threshold - # + one iteration + final SSTORE, with buffer. - min_tx_gas = intrinsic_gas + 130_000 - - # senders_iter yields one sender per tx (fresh per call in - # distinct mode, a single shared sender otherwise). The senders - # list collects one entry per tx so the BAL builder below can - # group nonce changes by sender uniformly. - senders_iter = _sender_generator(pre, distinct_senders) - senders: list[EOA] = [] - - gas_available = gas_benchmark_value - targets: list[Address] = [] - txs: list[Transaction] = [] - - # Each tx targets a freshly-deployed contract with identical code - # and storage layout. - while gas_available >= min_tx_gas: - tx_gas = min(gas_available, tx_gas_limit) - target = pre.deploy_contract( - code=runtime_code, - storage=Storage(storage_data), - ) - targets.append(target) - sender = next(senders_iter) - senders.append(sender) - txs.append( - Transaction( - gas_limit=tx_gas, - to=target, - sender=sender, - ) - ) - gas_available -= tx_gas - - expectations: dict[Address, BalAccountExpectation] = { - offset_holder: BalAccountExpectation( - storage_changes=[ - BalStorageSlot( - slot=0, - validate_any_change=True, - ), - ], - ), - } - for t in targets: - expectations[t] = BalAccountExpectation( - storage_reads=[base_offset], - storage_changes=[ - BalStorageSlot( - slot=0, - validate_any_change=True, - ), - ], - ) - sender_nonces: dict[Address, list[BalNonceChange]] = {} - for i, s in enumerate(senders): - changes = sender_nonces.setdefault(s, []) - changes.append( - BalNonceChange( - block_access_index=i + 1, - post_nonce=len(changes) + 1, - ) - ) - for addr, nonces in sender_nonces.items(): - expectations[addr] = BalAccountExpectation(nonce_changes=nonces) - - blocks = [ - Block( - txs=txs, - expected_block_access_list=BlockAccessListExpectation( - account_expectations=expectations, - ), - ) - ] - - benchmark_test( - pre=pre, - blocks=blocks, - skip_gas_used_validation=True, - expected_receipt_status=True, - ) - - -@pytest.mark.repricing -@pytest.mark.stub_parametrize("token_name", "bloated_eoa_") -@pytest.mark.parametrize("write_new_value", [False, True]) -@pytest.mark.parametrize("existing_slots", [True, False]) -@pytest.mark.parametrize("cache_strategy", [CacheStrategy.NO_CACHE]) -def test_sstore_bloated( - benchmark_test: BenchmarkTestFiller, - pre: Alloc, - fork: Fork, - gas_benchmark_value: int, - tx_gas_limit: int, - token_name: str, - write_new_value: bool, - existing_slots: bool, - cache_strategy: CacheStrategy, -) -> None: - """ - Benchmark SSTORE opcodes targeting an EOA with storage bloated. - """ - sstore_metadata: dict[str, Any] = {} - # If CACHE_TX, there would be one cold SLOAD before SSTORE - sstore_metadata["key_warm"] = cache_strategy == CacheStrategy.CACHE_TX - - # SSTORE metadata matrix: - # - # existing_slots | write_new_value | original | current | new - # ---------------+-----------------+----------+---------+----- - # True | True | 1 | 1 | 2 - # True | False | 1 | 1 | 1 - # False | True | 0 | 0 | 1 - # False | False | 0 | 0 | 0 - - initial_value = int(existing_slots) - - # When existing_slots is False, the initial value is always 0 - # Otherwise, the initial value starts at 1 instead. - sstore_metadata["original_value"] = initial_value - sstore_metadata["current_value"] = initial_value - - # If not writing a new value, the new value is the same as the current one - # If writing a new value, the new value is current value + 1 - sstore_metadata["new_value"] = ( - initial_value if not write_new_value else initial_value + 1 - ) - - setup = ( - Op.CALLDATALOAD(32) # [end_slot] - + Op.CALLDATALOAD(0) # [counter, end_slot] - ) - - # stack element: [counter, end_slot] - - loop = Bytecode() - loop += Op.JUMPDEST # jump target - - # If CACHE_TX, warm the slot with a cold SLOAD before the SSTORE loop - if cache_strategy == CacheStrategy.CACHE_TX: - loop += Op.POP(Op.SLOAD(Op.DUP1, key_warm=False)) - - sstore_op: Bytecode = Bytecode() - if write_new_value: - # s[counter] = counter + 1 - sstore_op = ( - Op.DUP1 # [counter, counter, end_slot] - + Op.DUP1 # [counter, counter, counter, end_slot] - + Op.PUSH1(1) # [1, counter, counter, counter, end_slot] - + Op.ADD # [counter+1, counter, counter, end_slot] - + Op.SWAP1 # [counter, counter+1, counter, end_slot] - + Op.SSTORE(**sstore_metadata) # [counter, end_slot] - ) - else: - # s[counter] = counter (existing slot) or 0 (non existing slot) - push_value = Op.DUP1 if existing_slots else Op.PUSH1(0) - sstore_op = ( - push_value # [value, counter, end_slot] - + Op.DUP2 # [counter, value, counter, end_slot] - + Op.SSTORE(**sstore_metadata) # [counter, end_slot] - ) - - loop += sstore_op - - # stack element: [counter, end_slot] - - loop += ( - Op.PUSH1(1) # [1, counter, end_slot] - + Op.ADD # [counter+1, end_slot] - + Op.DUP2 # [end_slot, counter+1, end_slot] - + Op.DUP2 # [counter+1, end_slot, counter+1, end_slot] - + Op.LT # [counter+1<end_slot, counter+1, end_slot] - + Op.PUSH1(len(setup)) # [dest, condition, counter+1, end_slot] - + Op.JUMPI # [counter+1, end_slot] - ) - - runtime_code = IteratingBytecode( - setup=setup, - iterating=loop, - cleanup=Op.STOP, - ) - - authority = pre.stub_eoa(token_name) - start_slot = 1 if existing_slots else START_SLOT - - def calldata_gen(iteration_count: int, start_iteration: int) -> bytes: - return Hash(start_iteration) + Hash(start_iteration + iteration_count) - - def tx_generator(sender: EOA) -> list[Transaction]: - return list( - runtime_code.transactions_by_gas_limit( - fork=fork, - gas_limit=gas_benchmark_value, - sender=sender, - to=authority, - start_iteration=start_slot, - calldata=calldata_gen, - recipient_type=RecipientType.DELEGATION_7702, - ) - ) - - run_bloated_eoa_benchmark( - benchmark_test=benchmark_test, - pre=pre, - fork=fork, - gas_benchmark_value=gas_benchmark_value, - tx_gas_limit=tx_gas_limit, - authority=authority, - existing_slots=existing_slots, - runtime_code=runtime_code, - cache_strategy=cache_strategy, - tx_generator=tx_generator, - ) - - -@pytest.mark.stub_parametrize( - "erc20_stub", "test_sload_empty_erc20_balanceof_" -) -def test_sload_erc20_generic( - benchmark_test: BenchmarkTestFiller, - pre: Alloc, - fork: Fork, - gas_benchmark_value: int, - tx_gas_limit: int, - erc20_stub: str, -) -> None: - """Benchmark SLOAD using ERC20 balanceOf on bloatnet.""" - # Stub Account - erc20_address = pre.deploy_contract( - code=Bytecode(), - stub=erc20_stub, - ) - threshold = 100000 - - # MEM[0] = function selector - # MEM[32] = starting address offset - setup = Op.MSTORE( - 0, - BALANCEOF_SELECTOR, - # gas accounting - old_memory_size=0, - new_memory_size=32, - ) + Op.MSTORE( - 32, - Op.SLOAD(0), # Address Offset - # gas accounting - old_memory_size=32, - new_memory_size=64, - ) - - call_balance_of = Op.POP( - Op.CALL( - address=erc20_address, - args_offset=32 - 4, - args_size=32 + 4, - ) - ) - - loop = While( - body=call_balance_of + Op.MSTORE(32, Op.ADD(Op.MLOAD(32), 1)), - condition=Op.GT(Op.GAS, threshold), - ) - - teardown = Op.SSTORE(0, Op.MLOAD(32)) - - # Contract Deployment - code = setup + loop + teardown - attack_contract_address = pre.deploy_contract(code=code) - - intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() - - # Transaction Loops - txs = [] - gas_remaining = gas_benchmark_value - - sender = pre.fund_eoa() - - while gas_remaining > intrinsic_gas: - gas_available = min(gas_remaining, tx_gas_limit) - - if gas_available < intrinsic_gas: - break - - with TestPhaseManager.execution(): - txs.append( - Transaction( - gas_limit=gas_available, - to=attack_contract_address, - sender=sender, - ) - ) - - gas_remaining -= gas_available - - blocks = [Block(txs=txs)] - benchmark_test( - pre=pre, - blocks=blocks, - skip_gas_used_validation=True, - expected_receipt_status=True, - ) - - -# SLOAD BENCHMARK ARCHITECTURE: -# -# [Pre-deployed ERC20 Contract] ──── Storage slots for balances -# │ -# │ balanceOf(address) → SLOAD(keccak256(address || slot)) -# │ -# [Attack Contract] ──CALL──► ERC20.balanceOf(random_address) -# │ -# └─► Loop(i=0 to N): -# 1. Generate random address from counter -# 2. CALL balanceOf(random_address) → forces cold SLOAD -# 3. Most addresses have zero balance → empty storage slots -# -# WHY IT STRESSES CLIENTS: -# - Each balanceOf() call forces a cold SLOAD on a likely-empty slot -# - Storage slot = keccak256(address || balances_slot) -# - Random addresses ensure maximum cache misses -# - Tests client's sparse storage handling efficiency - - -# SSTORE BENCHMARK ARCHITECTURE: -# -# [Pre-deployed ERC20 Contract] ──── Storage slots for allowances -# │ -# │ approve(spender, amount) -# │ → SSTORE(keccak256(spender || slot), amount) -# │ -# [Attack Contract] -# ──CALL──► ERC20.approve(counter_as_spender, counter_as_amount) -# │ -# └─► Loop(i=0 to N): -# 1. Use counter as both spender address and amount -# 2. CALL approve(counter, counter) → forces cold SSTORE -# 3. Writes to new allowance slots in sparse storage -# -# WHY IT STRESSES CLIENTS: -# - Each approve() call forces an SSTORE to a new storage slot -# - Storage slot = keccak256( -# msg.sender || keccak256(spender || allowances_slot) -# ) -# - Sequential counter ensures unique storage locations -# - Tests client's ability to handle many storage writes -# - Simulates real-world contract state accumulation over time - - -@pytest.mark.stub_parametrize("erc20_stub", "test_sstore_erc20_approve_") -def test_sstore_erc20_generic( - benchmark_test: BenchmarkTestFiller, - pre: Alloc, - fork: Fork, - gas_benchmark_value: int, - tx_gas_limit: int, - erc20_stub: str, -) -> None: - """Benchmark SSTORE using ERC20 approve.""" - sender = pre.fund_eoa() - - threshold = 100_000 - - # Stub Account - erc20_address = pre.deploy_contract( - code=Bytecode(), - stub=erc20_stub, - ) - - # MEM[0] = function selector - # MEM[32] = starting address offset - setup = Op.MSTORE( - 0, - APPROVE_SELECTOR, - ) + Op.MSTORE( - 32, - Op.SLOAD(0), # Address Offset - ) - - call_approve = Op.MSTORE( - 64, - Op.ADD(1, Op.MLOAD(32)), - ) + Op.POP( - Op.CALL( - address=erc20_address, - args_offset=28, - args_size=68, - ) - ) - - loop = While( - body=call_approve + Op.MSTORE(32, Op.ADD(Op.MLOAD(32), 1)), - condition=Op.GT(Op.GAS, threshold), - ) - - teardown = Op.SSTORE(0, Op.MLOAD(32)) - - # Contract Deployment - code = setup + loop + teardown - attack_contract_address = pre.deploy_contract(code=code) - - intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() - - # Transaction Loops - gas_remaining = gas_benchmark_value - - # Collect tx params first, then build Transaction objects - # so that nonces are allocated contiguously per block. - tx_gas: list[int] = [] - while gas_remaining > intrinsic_gas: - gas_available = min(gas_remaining, tx_gas_limit) - - if gas_available < intrinsic_gas: - break - - tx_gas.append(gas_available) - - gas_remaining -= gas_available - - txs = [] - with TestPhaseManager.execution(): - for gas_available in tx_gas: - txs.append( - Transaction( - gas_limit=gas_available, - to=attack_contract_address, - sender=sender, - ) - ) - - blocks = [Block(txs=txs)] - - benchmark_test( - pre=pre, - blocks=blocks, - skip_gas_used_validation=True, - expected_receipt_status=True, - ) - - -def create_sstore_executor( - sloads_before_sstore: bool, - key_warm: bool, - original_value: int, - new_value: int, -) -> IteratingBytecode: - """ - Create a contract that executes SSTORE benchmark operations. - - - CALLDATA[0..32] start slot (index) - - CALLDATA[32..64] ending slot (end_slot) - - CALLDATA[64..96] value to write - - Returns: IteratingBytecode representing the benchmark executor. - """ - setup = ( - Op.CALLDATALOAD(32) # end_slot - + Op.CALLDATALOAD(64) # value - + Op.CALLDATALOAD(0) # start_slot = counter - ) - # [counter, value, end_slot] - - loop = Bytecode() - loop += Op.JUMPDEST - # Loop Body: Store Value at Start Slot + Counter - if sloads_before_sstore: - loop += Op.DUP1 # [counter, counter, value, end_slot] - loop += Op.SLOAD( - # gas accounting - key_warm=key_warm - ) - loop += Op.POP - loop += Op.DUP2 # [value, counter, value, end_slot] - loop += Op.DUP2 # [counter, value, counter, value, end_slot] - loop += Op.SSTORE( # STORAGE[counter] = value - key_warm=True, - original_value=original_value, - current_value=original_value, - new_value=new_value, - ) - else: - loop += Op.DUP2 # [value, counter, value, end_slot] - loop += Op.DUP2 # [counter, value, counter, value, end_slot] - loop += Op.SSTORE( # STORAGE[counter] = value - key_warm=key_warm, - original_value=original_value, - current_value=original_value, - new_value=new_value, - ) - # [counter, value, end_slot] - - # Loop Post: Increment Counter - loop += Op.PUSH1(1) - loop += Op.ADD - # [counter + 1, value, end_slot] - - # Loop Condition: Counter < end_slot - loop += Op.DUP3 # [end_slot, counter + 1, value, end_slot] - loop += Op.DUP2 # [counter + 1, end_slot, counter + 1, value, end_slot] - loop += Op.LT # [counter + 1 < end_slot, counter + 1, value, end_slot] - loop += Op.PUSH1(len(setup)) - loop += Op.JUMPI - # [counter + 1, value, end_slot] - - cleanup = Bytecode() - cleanup += Op.STOP - - return IteratingBytecode(setup=setup, iterating=loop, cleanup=cleanup) - - -def create_sstore_dirty_executor( - write_values: List[int], - key_warm: bool, - initial_value: int, -) -> IteratingBytecode: - """ - Create executor that writes multiple values to each slot. - - Exercise dirty state transitions by performing a sequence of SSTOREs - to the same slot within a single loop iteration. After the first - SSTORE, the slot is warm and subsequent writes hit the dirty - (100 gas) path when original != current. - - - CALLDATA[0..32] start slot (index) - - CALLDATA[32..64] ending slot (end_slot) - - Return an IteratingBytecode for the dirty-write benchmark executor. - """ - setup = ( - Op.CALLDATALOAD(32) # end_slot - + Op.CALLDATALOAD(0) # start_slot = counter - ) - # Stack: [counter, end_slot] - - loop = Bytecode() - loop += Op.JUMPDEST - - for i, val in enumerate(write_values): - is_first = i == 0 - current_val = initial_value if is_first else write_values[i - 1] - # DUP2 reaches counter through the pushed value - loop += Op.SSTORE( - Op.DUP2, - val, - key_warm=key_warm if is_first else True, - original_value=initial_value, - current_value=current_val, - new_value=val, - ) - # Stack after all writes: [counter, end_slot] - - # Increment counter - loop += Op.PUSH1(1) - loop += Op.ADD - # [counter + 1, end_slot] - - # Loop while counter + 1 < end_slot - loop += Op.DUP2 - loop += Op.DUP2 - loop += Op.LT - loop += Op.PUSH1(len(setup)) - loop += Op.JUMPI - - cleanup = Bytecode() - cleanup += Op.STOP - - return IteratingBytecode(setup=setup, iterating=loop, cleanup=cleanup) - - -def access_list_generator( - iteration_count: int, - start_iteration: int, - access_warm: bool, - authority: Address, -) -> list[AccessList] | None: - """Access list generator for warming storage slots.""" - if access_warm: - storage_keys = [ - Hash(i) - for i in range(start_iteration, start_iteration + iteration_count) - ] - return [AccessList(address=authority, storage_keys=storage_keys)] - return None - - -def executor_calldata_generator( - iteration_count: int, - start_iteration: int, - write_value: int | None = None, -) -> bytes: - """ - Calldata generator for executor operations. - - Generates: Hash(start) + Hash(start + count) [+ Hash(write_value)] - """ - result = Hash(start_iteration) + Hash(start_iteration + iteration_count) - if write_value is not None: - result += Hash(write_value) - return result - - -@pytest.mark.parametrize("access_warm", [True, False]) -@pytest.mark.parametrize("sloads_before_sstore", [True, False]) -@pytest.mark.parametrize( - "initial_value,write_value", - [ - pytest.param(0, 0, id="zero_to_zero"), - pytest.param(0, 0xDEADBEEF, id="zero_to_nonzero"), - # TODO: Resolve refund mechanism - # pytest.param(0xDEADBEEF, 0, id="nonzero_to_zero"), - pytest.param(0xDEADBEEF, 0xBEEFBEEF, id="nonzero_to_diff"), - pytest.param(0xDEADBEEF, 0xDEADBEEF, id="nonzero_to_same"), - ], -) -def test_sstore_variants( - benchmark_test: BenchmarkTestFiller, - fork: Fork, - pre: Alloc, - tx_gas_limit: int, - gas_benchmark_value: int, - access_warm: bool, - sloads_before_sstore: bool, - initial_value: int, - write_value: int, -) -> None: - """ - Benchmark SSTORE instruction with various configurations. - - Uses EIP-7702 delegation. The authority EOA delegates to: - - StorageInitializer: storage[i] = initial_value (initial_value != 0) - - BenchmarkExecutor: performs the benchmark operation (SSTORE) - - Variants: - - access_warm: Warm storage slots via access list - - sloads_before_sstore: SLOADs per slot before SSTORE - - initial_value/write_value: Storage transitions - (zero_to_zero, zero_to_nonzero, nonzero_to_zero, nonzero_to_nonzero) - """ - # Initial Storage Construction - initializer_code = create_sstore_initializer(initial_value) - initializer_addr = pre.deploy_contract(code=initializer_code) - - # Actual Benchmark Execution - executor_code = create_sstore_executor( - sloads_before_sstore=sloads_before_sstore, - key_warm=access_warm, - original_value=initial_value, - new_value=write_value, - ) - executor_addr = pre.deploy_contract(code=executor_code) - - authority = pre.fund_eoa(amount=0) - authority_nonce = 0 - - delegation_sender = pre.fund_eoa() - - calldata_gen = partial( - executor_calldata_generator, write_value=write_value - ) - access_list_gen = partial( - access_list_generator, access_warm=access_warm, authority=authority - ) - - # Number of slots that can be processed in the execution phase - num_target_slots = sum( - executor_code.tx_iterations_by_gas_limit( - fork=fork, - gas_limit=gas_benchmark_value, - calldata=calldata_gen, - access_list=access_list_gen, - start_iteration=1, - recipient_type=RecipientType.DELEGATION_7702, - ) - ) - - # Setup phase: initialize storage slots (if initial_value != 0) - with TestPhaseManager.setup(): - blocks = build_delegated_storage_setup( - pre=pre, - fork=fork, - tx_gas_limit=tx_gas_limit, - needs_init=initial_value != 0, - num_target_slots=num_target_slots, - initializer_code=initializer_code, - initializer_addr=initializer_addr, - executor_addr=executor_addr, - authority=authority, - authority_nonce=authority_nonce, - delegation_sender=delegation_sender, - initializer_calldata_generator=initializer_calldata_generator, - ) - - # Execution phase - expected_gas_used = 0 - - with TestPhaseManager.execution(): - exec_txs = list( - executor_code.transactions_by_gas_limit( - fork=fork, - gas_limit=gas_benchmark_value, - sender=pre.fund_eoa(), - to=authority, - calldata=calldata_gen, - start_iteration=1, - access_list=access_list_gen, - recipient_type=RecipientType.DELEGATION_7702, - ) - ) - - expected_gas_used = sum(tx.gas_cost for tx in exec_txs) - - blocks.append(Block(txs=exec_txs)) - - benchmark_test( - pre=pre, - blocks=blocks, - expected_benchmark_gas_used=expected_gas_used, - ) - - -# SSTORE DIRTY TRANSITIONS BENCHMARK ARCHITECTURE: -# -# [Authority EOA] -# │ -# │ Phase 1: Delegate to StorageInitializer -# │ ──► SSTORE(slot, initial_value) for N slots -# │ -# │ Phase 2: Delegate to DirtyExecutor -# │ ──► For each slot: -# │ SSTORE(slot, v1) → SSTORE(slot, v2) → ... -# │ -# WHY IT STRESSES CLIENTS: -# - Multiple writes per slot exercise EIP-2200/EIP-3529 refund -# branching: clean (original==current) vs dirty (original!=current) -# - Oscillation causes refund counter to swing up/down each write -# - Refund cap (gas_used/5) saturates with enough iterations -# - Tests correct tracking of original vs current vs new values - - -@pytest.mark.parametrize("access_warm", [True, False]) -@pytest.mark.parametrize( - "initial_value,write_values", - [ - pytest.param( - 0xDEADBEEF, - [0, 0xDEADBEEF, 0, 0xDEADBEEF], - id="oscillation_4x", - ), - pytest.param( - 0xDEADBEEF, - [0, 0xDEADBEEF, 0, 0xDEADBEEF, 0, 0xDEADBEEF], - id="oscillation_6x", - ), - pytest.param( - 0xDEADBEEF, - [0xBEEFBEEF, 0xCAFECAFE, 0xDEADBEEF], - id="triple_write_restore", - ), - pytest.param( - 0xDEADBEEF, - [0], - id="mass_clear", - ), - pytest.param( - 0, - [1, 0, 1, 0], - id="oscillation_4x_from_zero", - marks=pytest.mark.skip( - reason="net-zero state gas; degenerates to a regular-gas loop" - ), - ), - pytest.param( - 0, - [1], - id="mass_set_from_zero", - ), - ], -) -def test_sstore_dirty_transitions( - benchmark_test: BenchmarkTestFiller, - fork: Fork, - pre: Alloc, - tx_gas_limit: int, - gas_benchmark_value: int, - access_warm: bool, - initial_value: int, - write_values: List[int], -) -> None: - """ - Benchmark SSTORE dirty state transitions. - - Exercise EIP-2200/EIP-3529 refund logic by writing the same slot - multiple times per iteration. Uses EIP-7702 delegation: authority - EOA delegates to initializer then to dirty-write executor. - - Variants: - - oscillation: X→0→X→0, alternates clean (2900) and dirty (100) - - triple_write_restore: X→B→C→X, all SSTORE branches - - mass_clear: X→0, maximum per-slot refund generation - """ - # Initial Storage Construction - initializer_code = create_sstore_initializer(initial_value) - initializer_addr = pre.deploy_contract(code=initializer_code) - - # Benchmark Executor — multi-write per slot - executor_code = create_sstore_dirty_executor( - write_values=write_values, - key_warm=access_warm, - initial_value=initial_value, - ) - executor_addr = pre.deploy_contract(code=executor_code) - - authority = pre.fund_eoa(amount=0) - authority_nonce = 0 - - delegation_sender = pre.fund_eoa() - - calldata_gen = partial(executor_calldata_generator) - access_list_gen = partial( - access_list_generator, - access_warm=access_warm, - authority=authority, - ) - - # Number of slots processable in execution phase - num_target_slots = sum( - executor_code.tx_iterations_by_gas_limit( - fork=fork, - gas_limit=gas_benchmark_value, - calldata=calldata_gen, - access_list=access_list_gen, - start_iteration=1, - recipient_type=RecipientType.DELEGATION_7702, - ) - ) - - # Setup phase: initialize all slots to initial_value - with TestPhaseManager.setup(): - blocks = build_delegated_storage_setup( - pre=pre, - fork=fork, - tx_gas_limit=tx_gas_limit, - needs_init=initial_value != 0, - num_target_slots=num_target_slots, - initializer_code=initializer_code, - initializer_addr=initializer_addr, - executor_addr=executor_addr, - authority=authority, - authority_nonce=authority_nonce, - delegation_sender=delegation_sender, - initializer_calldata_generator=(initializer_calldata_generator), - ) - - # Execution phase — no expected_benchmark_gas_used because - # refund cap (gas_used/5) makes actual consumption non-trivial - with TestPhaseManager.execution(): - exec_txs = list( - executor_code.transactions_by_gas_limit( - fork=fork, - gas_limit=gas_benchmark_value, - sender=pre.fund_eoa(), - to=authority, - calldata=calldata_gen, - start_iteration=1, - access_list=access_list_gen, - recipient_type=RecipientType.DELEGATION_7702, - ) - ) - - blocks.append(Block(txs=exec_txs)) - - benchmark_test( - pre=pre, - blocks=blocks, - skip_gas_used_validation=True, - ) - - -def create_sload_executor(key_warm: bool) -> IteratingBytecode: - """ - Create a contract that executes SLOAD benchmark operations. - - - CALLDATA[0..32] start slot (index) - - CALLDATA[32..64] ending slot (end_slot) - - Returns: IteratingBytecode representing the benchmark executor. - """ - setup = ( - Op.CALLDATALOAD(32) # end_slot - + Op.CALLDATALOAD(0) # start_slot = counter - ) - # [counter, end_slot] - - loop = Bytecode() - loop += Op.JUMPDEST - # Loop Body: Load from current slot - loop += Op.DUP1 # [counter, counter, end_slot] - loop += Op.SLOAD(key_warm=key_warm) - loop += Op.POP # [counter, end_slot] - - # Loop Post: Increment Counter - loop += Op.PUSH1(1) - loop += Op.ADD - # [counter + 1, end_slot] - - # Loop Condition: Counter < end_slot - loop += Op.DUP2 # [end_slot, counter + 1, end_slot] - loop += Op.DUP2 # [counter + 1, end_slot, counter + 1, end_slot] - loop += Op.LT # [counter + 1 < end_slot, counter + 1, end_slot] - loop += Op.PUSH1(len(setup)) - loop += Op.JUMPI - # [counter + 1, end_slot] - - cleanup = Bytecode() - cleanup += Op.STOP - - return IteratingBytecode(setup=setup, iterating=loop, cleanup=cleanup) - - -@pytest.mark.parametrize("access_warm", [True, False]) -@pytest.mark.parametrize("storage_keys_pre_set", [True, False]) -def test_storage_sload_benchmark( - benchmark_test: BenchmarkTestFiller, - fork: Fork, - pre: Alloc, - tx_gas_limit: int, - gas_benchmark_value: int, - access_warm: bool, - storage_keys_pre_set: bool, -) -> None: - """ - Benchmark SLOAD instruction with various configurations. - - Uses EIP-7702 delegation. The authority EOA delegates to: - - StorageInitializer: storage[i] = 1 (if storage_keys_pre_set) - - BenchmarkExecutor: performs the benchmark operation (SLOAD) - - Variants: - - access_warm: Warm storage slots via access list - - storage_keys_pre_set: Whether the storage keys are pre-set - """ - # Initial Storage Construction - initializer_code = create_sstore_initializer(init_val=1) - initializer_addr = pre.deploy_contract(code=initializer_code) - - # Actual Benchmark Execution - executor_code = create_sload_executor(key_warm=access_warm) - executor_addr = pre.deploy_contract(code=executor_code) - - authority = pre.fund_eoa(amount=0) - authority_nonce = 0 - - delegation_sender = pre.fund_eoa() - - calldata_gen = partial(executor_calldata_generator) - access_list_gen = partial( - access_list_generator, access_warm=access_warm, authority=authority - ) - - # Number of slots that can be processed in the execution phase - num_target_slots = sum( - executor_code.tx_iterations_by_gas_limit( - fork=fork, - gas_limit=gas_benchmark_value, - calldata=calldata_gen, - access_list=access_list_gen, - start_iteration=1, - recipient_type=RecipientType.DELEGATION_7702, - ) - ) - - # Setup phase: initialize storage slots (if storage_keys_pre_set) - with TestPhaseManager.setup(): - blocks = build_delegated_storage_setup( - pre=pre, - fork=fork, - tx_gas_limit=tx_gas_limit, - needs_init=storage_keys_pre_set, - num_target_slots=num_target_slots, - initializer_code=initializer_code, - initializer_addr=initializer_addr, - executor_addr=executor_addr, - authority=authority, - authority_nonce=authority_nonce, - delegation_sender=delegation_sender, - initializer_calldata_generator=initializer_calldata_generator, - ) - - # Execution phase - expected_gas_used = 0 - - with TestPhaseManager.execution(): - exec_txs = list( - executor_code.transactions_by_gas_limit( - fork=fork, - gas_limit=gas_benchmark_value, - sender=pre.fund_eoa(), - to=authority, - calldata=calldata_gen, - start_iteration=1, - access_list=access_list_gen, - recipient_type=RecipientType.DELEGATION_7702, - ) - ) - - expected_gas_used = sum(tx.gas_cost for tx in exec_txs) - - blocks.append(Block(txs=exec_txs)) - - benchmark_test( - pre=pre, - blocks=blocks, - expected_benchmark_gas_used=expected_gas_used, - ) - - -@pytest.mark.repricing -@pytest.mark.parametrize("storage_keys_pre_set", [False, True]) -def test_storage_sload_same_key_benchmark( - benchmark_test: BenchmarkTestFiller, - storage_keys_pre_set: bool, -) -> None: - """ - Benchmark SLOAD instruction when loading the same key over and over. - - Variants: - - storage_keys_pre_set: The key is pre-set to a non-zero value. - """ - contract_storage = Storage() - if storage_keys_pre_set: - contract_storage[1] = 1 - - benchmark_test( - target_opcode=Op.SLOAD, - code_generator=JumpLoopGenerator( - setup=Op.PUSH1(1) if storage_keys_pre_set else Op.PUSH0, - attack_block=Op.SLOAD, - contract_storage=contract_storage, - ), - ) diff --git a/tests/benchmark/stateful/bloatnet/test_sload.py b/tests/benchmark/stateful/bloatnet/test_sload.py new file mode 100644 index 00000000000..63004fd85ac --- /dev/null +++ b/tests/benchmark/stateful/bloatnet/test_sload.py @@ -0,0 +1,620 @@ +"""Benchmark SLOAD operations on bloated and delegated storage.""" + +from functools import partial +from typing import Generator + +import pytest +from execution_testing import ( + EOA, + Address, + Alloc, + BalAccountExpectation, + BalNonceChange, + BalStorageSlot, + BenchmarkTestFiller, + Block, + BlockAccessListExpectation, + Bytecode, + Fork, + Hash, + IteratingBytecode, + JumpLoopGenerator, + Op, + RecipientType, + Storage, + TestPhaseManager, + Transaction, + While, +) + +from tests.benchmark.stateful.helpers import ( + START_SLOT, + CacheStrategy, + access_list_generator, + build_delegated_storage_setup, + create_sstore_initializer, + delegate_with_calldata, + executor_calldata_generator, + initializer_calldata_generator, + run_bloated_eoa_benchmark, +) + + +def _max_sloads_per_tx(tx_gas_limit: int, fork: Fork) -> int: + """ + Conservative upper bound on cold SLOADs that fit in a max-gas tx. + + Derived from the cold SLOAD cost (EIP-2929: 2100 gas) and used by + the bloated SLOAD benchmarks both as the inter-tx offset stride + (to keep consecutive txs' SLOAD ranges disjoint) and as the + per-target storage pre-load count. + """ + cold_sload_cost = Op.SLOAD(key_warm=False).gas_cost(fork) + return tx_gas_limit // cold_sload_cost + + +def _sender_generator( + pre: Alloc, distinct_senders: bool +) -> Generator[EOA, None, None]: + """ + Yield one sender per tx. + + In distinct mode, yields a fresh EOA per call. Otherwise, yields + the same shared sender for every call. Used by the bloated SLOAD + benchmarks so the BAL builder can group nonce changes by sender + uniformly regardless of mode. + """ + shared_sender = pre.fund_eoa() if not distinct_senders else None + while True: + yield pre.fund_eoa() if shared_sender is None else shared_sender + + +def create_sload_executor(key_warm: bool) -> IteratingBytecode: + """ + Create a contract that executes SLOAD benchmark operations. + + - CALLDATA[0..32] start slot (index) + - CALLDATA[32..64] ending slot (end_slot) + + Returns: IteratingBytecode representing the benchmark executor. + """ + setup = ( + Op.CALLDATALOAD(32) # end_slot + + Op.CALLDATALOAD(0) # start_slot = counter + ) + # [counter, end_slot] + + loop = Bytecode() + loop += Op.JUMPDEST + # Loop Body: Load from current slot + loop += Op.DUP1 # [counter, counter, end_slot] + loop += Op.SLOAD(key_warm=key_warm) + loop += Op.POP # [counter, end_slot] + + # Loop Post: Increment Counter + loop += Op.PUSH1(1) + loop += Op.ADD + # [counter + 1, end_slot] + + # Loop Condition: Counter < end_slot + loop += Op.DUP2 # [end_slot, counter + 1, end_slot] + loop += Op.DUP2 # [counter + 1, end_slot, counter + 1, end_slot] + loop += Op.LT # [counter + 1 < end_slot, counter + 1, end_slot] + loop += Op.PUSH1(len(setup)) + loop += Op.JUMPI + # [counter + 1, end_slot] + + cleanup = Bytecode() + cleanup += Op.STOP + + return IteratingBytecode(setup=setup, iterating=loop, cleanup=cleanup) + + +@pytest.mark.parametrize("access_warm", [True, False]) +@pytest.mark.parametrize("storage_keys_pre_set", [True, False]) +def test_sload_benchmark( + benchmark_test: BenchmarkTestFiller, + fork: Fork, + pre: Alloc, + tx_gas_limit: int, + gas_benchmark_value: int, + access_warm: bool, + storage_keys_pre_set: bool, +) -> None: + """ + Benchmark SLOAD instruction with various configurations. + + Uses EIP-7702 delegation. The authority EOA delegates to: + + - StorageInitializer: storage[i] = 1 (if storage_keys_pre_set) + - BenchmarkExecutor: performs the benchmark operation (SLOAD) + + Variants: + + - access_warm: Warm storage slots via access list + - storage_keys_pre_set: Whether the storage keys are pre-set + """ + # Initial Storage Construction + initializer_code = create_sstore_initializer(init_val=1) + initializer_addr = pre.deploy_contract(code=initializer_code) + + # Actual Benchmark Execution + executor_code = create_sload_executor(key_warm=access_warm) + executor_addr = pre.deploy_contract(code=executor_code) + + authority = pre.fund_eoa(amount=0) + authority_nonce = 0 + + delegation_sender = pre.fund_eoa() + + calldata_gen = partial(executor_calldata_generator) + access_list_gen = partial( + access_list_generator, access_warm=access_warm, authority=authority + ) + + # Number of slots that can be processed in the execution phase + num_target_slots = sum( + executor_code.tx_iterations_by_gas_limit( + fork=fork, + gas_limit=gas_benchmark_value, + calldata=calldata_gen, + access_list=access_list_gen, + start_iteration=1, + recipient_type=RecipientType.DELEGATION_7702, + ) + ) + + # Setup phase: initialize storage slots (if storage_keys_pre_set) + with TestPhaseManager.setup(): + blocks = build_delegated_storage_setup( + pre=pre, + fork=fork, + tx_gas_limit=tx_gas_limit, + needs_init=storage_keys_pre_set, + num_target_slots=num_target_slots, + initializer_code=initializer_code, + initializer_addr=initializer_addr, + executor_addr=executor_addr, + authority=authority, + authority_nonce=authority_nonce, + delegation_sender=delegation_sender, + initializer_calldata_generator=initializer_calldata_generator, + ) + + # Execution phase + expected_gas_used = 0 + + with TestPhaseManager.execution(): + exec_txs = list( + executor_code.transactions_by_gas_limit( + fork=fork, + gas_limit=gas_benchmark_value, + sender=pre.fund_eoa(), + to=authority, + calldata=calldata_gen, + start_iteration=1, + access_list=access_list_gen, + recipient_type=RecipientType.DELEGATION_7702, + ) + ) + + expected_gas_used = sum(tx.gas_cost for tx in exec_txs) + + blocks.append(Block(txs=exec_txs)) + + benchmark_test( + pre=pre, + blocks=blocks, + expected_benchmark_gas_used=expected_gas_used, + ) + + +@pytest.mark.repricing +@pytest.mark.parametrize("storage_keys_pre_set", [False, True]) +def test_sload_same_key_benchmark( + benchmark_test: BenchmarkTestFiller, + storage_keys_pre_set: bool, +) -> None: + """ + Benchmark SLOAD instruction when loading the same key over and over. + + Variants: + + - storage_keys_pre_set: The key is pre-set to a non-zero value. + """ + contract_storage = Storage() + if storage_keys_pre_set: + contract_storage[1] = 1 + + benchmark_test( + target_opcode=Op.SLOAD, + code_generator=JumpLoopGenerator( + setup=Op.PUSH1(1) if storage_keys_pre_set else Op.PUSH0, + attack_block=Op.SLOAD, + contract_storage=contract_storage, + ), + ) + + +@pytest.mark.repricing +@pytest.mark.stub_parametrize("token_name", "bloated_eoa_") +@pytest.mark.parametrize("existing_slots", [False, True]) +@pytest.mark.parametrize("cache_strategy", [CacheStrategy.NO_CACHE]) +def test_sload_bloated( + benchmark_test: BenchmarkTestFiller, + pre: Alloc, + fork: Fork, + gas_benchmark_value: int, + tx_gas_limit: int, + token_name: str, + existing_slots: bool, + cache_strategy: CacheStrategy, +) -> None: + """ + Benchmark SLOAD opcodes targeting an EOA with storage bloated. + + The storage is assumed to be filled from 0-N linearly, where + each slot has the value of the key. If this is not the + storage layout of the target account, then the existing_slots + parameter will not be correct. + """ + slot_access = ( + Op.DUP1 # [index, index] + + Op.SLOAD # [s[index], index] + + Op.POP # [index] + ) + # CACHE_TX: access each slot twice so the second hit is uncached + if cache_strategy == CacheStrategy.CACHE_TX: + slot_access *= 2 + + runtime_code = ( + Op.PUSH0 # [0] + + Op.SLOAD # [index], s[0] = index + + While( + body=( + slot_access + + Op.PUSH1(1) # [1, index] + + Op.ADD # [index+1] + ), + condition=Op.GT(Op.GAS, 0xFFFF), + ) + + Op.PUSH0 # [0, index+1] + + Op.SSTORE # s[0] = index+1 + ) + + run_bloated_eoa_benchmark( + benchmark_test=benchmark_test, + pre=pre, + fork=fork, + gas_benchmark_value=gas_benchmark_value, + tx_gas_limit=tx_gas_limit, + authority=pre.stub_eoa(token_name), + existing_slots=existing_slots, + runtime_code=runtime_code, + cache_strategy=cache_strategy, + ) + + +@pytest.mark.stub_parametrize("token_name", "bloated_eoa_") +@pytest.mark.parametrize("distinct_senders", [False, True]) +@pytest.mark.parametrize("existing_slots", [False, True]) +def test_sload_bloated_prefetch_miss( + benchmark_test: BenchmarkTestFiller, + pre: Alloc, + fork: Fork, + gas_benchmark_value: int, + tx_gas_limit: int, + token_name: str, + existing_slots: bool, + distinct_senders: bool, +) -> None: + """ + Benchmark SLOAD with calldata-driven offsets to defeat prefetching. + + A small first transaction writes an initial offset into the + authority's slot 0 via calldata. Subsequent max-gas transactions + each read the previous offset from slot 0, immediately overwrite + slot 0 with a new offset from their own calldata, then SLOAD + sequentially from the previous offset. Because each transaction's + SLOAD range depends on state written by its predecessor, a + prefetcher that predicts SLOAD targets from pre-block state + without simulating intra-block writes will pre-warm incorrect + storage slots. The minimal first tx is load-bearing: it lives + inside the benchmark block so every subsequent max-gas tx reads + a slot 0 value that differs from the prefetcher's pre-block + snapshot, achieving a 100% miss rate. + + When ``distinct_senders`` is True every transaction uses a fresh + sender. This additionally defeats per-sender prewarm + serialization (e.g. Nethermind) that groups txs by sender and + runs them sequentially to propagate state changes — forcing + every tx's prewarm scope to restart from pre-block state. + """ + # Runtime: read old offset from slot 0, write new offset from + # calldata to slot 0, then SLOAD sequentially from old offset. + runtime_code = ( + Op.SLOAD(Op.PUSH0) + + Op.SSTORE(Op.PUSH0, Op.CALLDATALOAD(Op.PUSH0)) + + While( + body=(Op.DUP1 + Op.SLOAD + Op.POP + Op.PUSH1(1) + Op.ADD), + condition=Op.GT(Op.GAS, 0xFFFF), + ) + ) + + authority = pre.stub_eoa(token_name) + runtime_address = pre.deploy_contract(code=runtime_code) + + # Setup: delegate authority to the runtime contract. Slot 0 is + # left at 0 (the delegation tx's calldata) so the benchmark + # block's pre-state has slot 0 = 0; the first benchmark tx + # then plants base_offset in slot 0 inside the benchmark block, + # forcing the prefetcher's pre-block snapshot to disagree with + # the actual slot 0 value seen by every max-gas tx that follows. + delegation_tx = delegate_with_calldata( + pre, + fork, + authority, + runtime_address, + Hash(0), + ) + + blocks: list[Block] = [Block(txs=[delegation_tx])] + + # Offset spacing: upper bound on SLOADs per tx ensures each + # transaction reads a completely disjoint slot range. + max_sloads_per_tx = _max_sloads_per_tx(tx_gas_limit, fork) + + # The base offset must be at least max_sloads_per_tx away from + # the pre-block slot 0 value (0) so the prefetcher's predicted + # SLOAD range is completely disjoint from the actual range. + base_offset = max_sloads_per_tx if existing_slots else START_SLOT + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( + calldata=b"\xff" * 32, + ) + + # senders_iter yields one sender per tx (fresh per call in + # distinct mode, a single shared sender otherwise). The senders + # list collects one entry per tx so the BAL builder below can + # group nonce changes by sender uniformly. + senders_iter = _sender_generator(pre, distinct_senders) + senders: list[EOA] = [] + + gas_available = gas_benchmark_value + txs: list[Transaction] = [] + + # First transaction: minimal gas, only writes the initial + # offset. Gas limit ensures remaining gas after the SLOAD + + # SSTORE setup falls below the 0xFFFF loop threshold so the + # SLOAD loop does not run. This tx's job is to change slot 0 + # inside the benchmark block so every subsequent max-gas tx + # reads an offset the prefetcher's pre-block snapshot does + # not see, achieving a 100% prefetch miss rate on max-gas txs. + first_tx_gas = min(gas_available, intrinsic_gas + 30_000) + sender = next(senders_iter) + senders.append(sender) + txs.append( + Transaction( + gas_limit=first_tx_gas, + to=authority, + data=Hash(base_offset), + sender=sender, + ) + ) + gas_available -= first_tx_gas + + # Subsequent transactions: max gas, each shifts the offset + # so the next transaction SLOADs from a different range. + tx_index = 1 + while gas_available >= intrinsic_gas: + tx_gas = min(gas_available, tx_gas_limit) + new_offset = base_offset + tx_index * max_sloads_per_tx + sender = next(senders_iter) + senders.append(sender) + txs.append( + Transaction( + gas_limit=tx_gas, + to=authority, + data=Hash(new_offset), + sender=sender, + ) + ) + gas_available -= tx_gas + tx_index += 1 + + expectations: dict[Address, BalAccountExpectation] = { + authority: BalAccountExpectation( + storage_reads=[base_offset], + storage_changes=[ + BalStorageSlot( + slot=0, + validate_any_change=True, + ), + ], + ), + } + sender_nonces: dict[Address, list[BalNonceChange]] = {} + for i, s in enumerate(senders): + changes = sender_nonces.setdefault(s, []) + changes.append( + BalNonceChange( + block_access_index=i + 1, + post_nonce=len(changes) + 1, + ) + ) + for addr, nonces in sender_nonces.items(): + expectations[addr] = BalAccountExpectation(nonce_changes=nonces) + blocks.append( + Block( + txs=txs, + expected_block_access_list=BlockAccessListExpectation( + account_expectations=expectations, + ), + ) + ) + + benchmark_test( + pre=pre, + blocks=blocks, + skip_gas_used_validation=True, + expected_receipt_status=True, + ) + + +@pytest.mark.parametrize("distinct_senders", [False, True]) +@pytest.mark.parametrize("existing_slots", [False, True]) +def test_sload_bloated_multi_contract( + benchmark_test: BenchmarkTestFiller, + pre: Alloc, + fork: Fork, + gas_benchmark_value: int, + tx_gas_limit: int, + existing_slots: bool, + distinct_senders: bool, +) -> None: + """ + Benchmark SLOAD across a distinct contract per transaction. + + Each transaction calls a freshly-deployed contract whose slot 0 + is pre-loaded with the starting offset; the contract then runs a + SLOAD loop over sequential slots until gas runs low. Unlike + test_sload_bloated_prefetch_miss which hammers one account's + storage trie via an EIP-7702 delegated EOA, every transaction + here opens a different storage trie, stressing cross-account + state access and state-trie breadth in a single block. + + Every target contract first CALLs a shared offset_holder + contract whose slot 0 is read, incremented, and written back. + This mirrors the first test's "same-contract slot 0" dependency + pattern via cross-contract CALL: every transaction forms a + read-after-write edge on offset_holder's slot 0, preventing + parallel execution. + + When ``distinct_senders`` is True every transaction uses a fresh + sender. This additionally exercises per-sender prewarm + serialization (e.g. Nethermind) differently than the shared- + sender case; we run both so clients can be measured in both + regimes. + """ + # Shared offset_holder: reads, increments, and writes its own + # slot 0. Every target CALLs this to create an inter-tx RAW + # dependency chain on a single shared storage slot. + offset_holder = pre.deploy_contract( + code=Op.SSTORE(0, Op.ADD(Op.SLOAD(0), 1)), + ) + + # Target runtime: CALL offset_holder (for the dependency), then + # run the same SLOAD loop as test_sload_bloated in its own + # storage. Final counter is written back to slot 0. + runtime_code = ( + Op.POP( + Op.CALL( + address=offset_holder, + ) + ) + + Op.SLOAD(Op.PUSH0) + + While( + body=(Op.DUP1 + Op.SLOAD + Op.POP + Op.PUSH1(1) + Op.ADD), + condition=Op.GT(Op.GAS, 0xFFFF), + ) + + Op.PUSH0 + + Op.SSTORE + ) + + base_offset = 1 if existing_slots else START_SLOT + max_sloads_per_tx = _max_sloads_per_tx(tx_gas_limit, fork) + + # Pre-load slot 0 with the starting offset. For existing_slots, + # also fill the slot range the loop will read so SLOADs land on + # populated entries rather than empty slots. A fresh Storage + # instance is built per deployment (below) so that every target + # gets an independent root dict, not an alias of the same one. + storage_data: Storage.StorageDictType = {0: base_offset} + if existing_slots: + for i in range(base_offset, base_offset + max_sloads_per_tx): + storage_data[i] = i + + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() + # Minimum per-tx gas ensuring the SLOAD loop runs at least one + # iteration so every target satisfies storage_reads=[base_offset]: + # intrinsic + CALL + offset_holder + setup + 0xFFFF loop threshold + # + one iteration + final SSTORE, with buffer. + min_tx_gas = intrinsic_gas + 130_000 + + # senders_iter yields one sender per tx (fresh per call in + # distinct mode, a single shared sender otherwise). The senders + # list collects one entry per tx so the BAL builder below can + # group nonce changes by sender uniformly. + senders_iter = _sender_generator(pre, distinct_senders) + senders: list[EOA] = [] + + gas_available = gas_benchmark_value + targets: list[Address] = [] + txs: list[Transaction] = [] + + # Each tx targets a freshly-deployed contract with identical code + # and storage layout. + while gas_available >= min_tx_gas: + tx_gas = min(gas_available, tx_gas_limit) + target = pre.deploy_contract( + code=runtime_code, + storage=Storage(storage_data), + ) + targets.append(target) + sender = next(senders_iter) + senders.append(sender) + txs.append( + Transaction( + gas_limit=tx_gas, + to=target, + sender=sender, + ) + ) + gas_available -= tx_gas + + expectations: dict[Address, BalAccountExpectation] = { + offset_holder: BalAccountExpectation( + storage_changes=[ + BalStorageSlot( + slot=0, + validate_any_change=True, + ), + ], + ), + } + for t in targets: + expectations[t] = BalAccountExpectation( + storage_reads=[base_offset], + storage_changes=[ + BalStorageSlot( + slot=0, + validate_any_change=True, + ), + ], + ) + sender_nonces: dict[Address, list[BalNonceChange]] = {} + for i, s in enumerate(senders): + changes = sender_nonces.setdefault(s, []) + changes.append( + BalNonceChange( + block_access_index=i + 1, + post_nonce=len(changes) + 1, + ) + ) + for addr, nonces in sender_nonces.items(): + expectations[addr] = BalAccountExpectation(nonce_changes=nonces) + + blocks = [ + Block( + txs=txs, + expected_block_access_list=BlockAccessListExpectation( + account_expectations=expectations, + ), + ) + ] + + benchmark_test( + pre=pre, + blocks=blocks, + skip_gas_used_validation=True, + expected_receipt_status=True, + ) diff --git a/tests/benchmark/stateful/bloatnet/test_sstore.py b/tests/benchmark/stateful/bloatnet/test_sstore.py new file mode 100644 index 00000000000..7e4572a5177 --- /dev/null +++ b/tests/benchmark/stateful/bloatnet/test_sstore.py @@ -0,0 +1,572 @@ +"""Benchmark SSTORE operations on bloated and delegated storage.""" + +from functools import partial +from typing import Any, List + +import pytest +from execution_testing import ( + EOA, + Alloc, + BenchmarkTestFiller, + Block, + Bytecode, + Fork, + Hash, + IteratingBytecode, + Op, + RecipientType, + TestPhaseManager, + Transaction, +) + +from tests.benchmark.stateful.helpers import ( + START_SLOT, + CacheStrategy, + access_list_generator, + build_delegated_storage_setup, + create_sstore_initializer, + executor_calldata_generator, + initializer_calldata_generator, + run_bloated_eoa_benchmark, +) + + +@pytest.mark.repricing +@pytest.mark.stub_parametrize("token_name", "bloated_eoa_") +@pytest.mark.parametrize("write_new_value", [False, True]) +@pytest.mark.parametrize("existing_slots", [True, False]) +@pytest.mark.parametrize("cache_strategy", [CacheStrategy.NO_CACHE]) +def test_sstore_bloated( + benchmark_test: BenchmarkTestFiller, + pre: Alloc, + fork: Fork, + gas_benchmark_value: int, + tx_gas_limit: int, + token_name: str, + write_new_value: bool, + existing_slots: bool, + cache_strategy: CacheStrategy, +) -> None: + """ + Benchmark SSTORE opcodes targeting an EOA with storage bloated. + """ + sstore_metadata: dict[str, Any] = {} + # If CACHE_TX, there would be one cold SLOAD before SSTORE + sstore_metadata["key_warm"] = cache_strategy == CacheStrategy.CACHE_TX + + # SSTORE metadata matrix: + # + # existing_slots | write_new_value | original | current | new + # ---------------+-----------------+----------+---------+----- + # True | True | 1 | 1 | 2 + # True | False | 1 | 1 | 1 + # False | True | 0 | 0 | 1 + # False | False | 0 | 0 | 0 + + initial_value = int(existing_slots) + + # When existing_slots is False, the initial value is always 0 + # Otherwise, the initial value starts at 1 instead. + sstore_metadata["original_value"] = initial_value + sstore_metadata["current_value"] = initial_value + + # If not writing a new value, the new value is the same as the current one + # If writing a new value, the new value is current value + 1 + sstore_metadata["new_value"] = ( + initial_value if not write_new_value else initial_value + 1 + ) + + setup = ( + Op.CALLDATALOAD(32) # [end_slot] + + Op.CALLDATALOAD(0) # [counter, end_slot] + ) + + # stack element: [counter, end_slot] + + loop = Bytecode() + loop += Op.JUMPDEST # jump target + + # If CACHE_TX, warm the slot with a cold SLOAD before the SSTORE loop + if cache_strategy == CacheStrategy.CACHE_TX: + loop += Op.POP(Op.SLOAD(Op.DUP1, key_warm=False)) + + sstore_op: Bytecode = Bytecode() + if write_new_value: + # s[counter] = counter + 1 + sstore_op = ( + Op.DUP1 # [counter, counter, end_slot] + + Op.DUP1 # [counter, counter, counter, end_slot] + + Op.PUSH1(1) # [1, counter, counter, counter, end_slot] + + Op.ADD # [counter+1, counter, counter, end_slot] + + Op.SWAP1 # [counter, counter+1, counter, end_slot] + + Op.SSTORE(**sstore_metadata) # [counter, end_slot] + ) + else: + # s[counter] = counter (existing slot) or 0 (non existing slot) + push_value = Op.DUP1 if existing_slots else Op.PUSH1(0) + sstore_op = ( + push_value # [value, counter, end_slot] + + Op.DUP2 # [counter, value, counter, end_slot] + + Op.SSTORE(**sstore_metadata) # [counter, end_slot] + ) + + loop += sstore_op + + # stack element: [counter, end_slot] + + loop += ( + Op.PUSH1(1) # [1, counter, end_slot] + + Op.ADD # [counter+1, end_slot] + + Op.DUP2 # [end_slot, counter+1, end_slot] + + Op.DUP2 # [counter+1, end_slot, counter+1, end_slot] + + Op.LT # [counter+1<end_slot, counter+1, end_slot] + + Op.PUSH1(len(setup)) # [dest, condition, counter+1, end_slot] + + Op.JUMPI # [counter+1, end_slot] + ) + + runtime_code = IteratingBytecode( + setup=setup, + iterating=loop, + cleanup=Op.STOP, + ) + + authority = pre.stub_eoa(token_name) + start_slot = 1 if existing_slots else START_SLOT + + def calldata_gen(iteration_count: int, start_iteration: int) -> bytes: + return Hash(start_iteration) + Hash(start_iteration + iteration_count) + + def tx_generator(sender: EOA) -> list[Transaction]: + return list( + runtime_code.transactions_by_gas_limit( + fork=fork, + gas_limit=gas_benchmark_value, + sender=sender, + to=authority, + start_iteration=start_slot, + calldata=calldata_gen, + recipient_type=RecipientType.DELEGATION_7702, + ) + ) + + run_bloated_eoa_benchmark( + benchmark_test=benchmark_test, + pre=pre, + fork=fork, + gas_benchmark_value=gas_benchmark_value, + tx_gas_limit=tx_gas_limit, + authority=authority, + existing_slots=existing_slots, + runtime_code=runtime_code, + cache_strategy=cache_strategy, + tx_generator=tx_generator, + ) + + +def create_sstore_executor( + sloads_before_sstore: bool, + key_warm: bool, + original_value: int, + new_value: int, +) -> IteratingBytecode: + """ + Create a contract that executes SSTORE benchmark operations. + + - CALLDATA[0..32] start slot (index) + - CALLDATA[32..64] ending slot (end_slot) + - CALLDATA[64..96] value to write + + Returns: IteratingBytecode representing the benchmark executor. + """ + setup = ( + Op.CALLDATALOAD(32) # end_slot + + Op.CALLDATALOAD(64) # value + + Op.CALLDATALOAD(0) # start_slot = counter + ) + # [counter, value, end_slot] + + loop = Bytecode() + loop += Op.JUMPDEST + # Loop Body: Store Value at Start Slot + Counter + if sloads_before_sstore: + loop += Op.DUP1 # [counter, counter, value, end_slot] + loop += Op.SLOAD( + # gas accounting + key_warm=key_warm + ) + loop += Op.POP + loop += Op.DUP2 # [value, counter, value, end_slot] + loop += Op.DUP2 # [counter, value, counter, value, end_slot] + loop += Op.SSTORE( # STORAGE[counter] = value + key_warm=True, + original_value=original_value, + current_value=original_value, + new_value=new_value, + ) + else: + loop += Op.DUP2 # [value, counter, value, end_slot] + loop += Op.DUP2 # [counter, value, counter, value, end_slot] + loop += Op.SSTORE( # STORAGE[counter] = value + key_warm=key_warm, + original_value=original_value, + current_value=original_value, + new_value=new_value, + ) + # [counter, value, end_slot] + + # Loop Post: Increment Counter + loop += Op.PUSH1(1) + loop += Op.ADD + # [counter + 1, value, end_slot] + + # Loop Condition: Counter < end_slot + loop += Op.DUP3 # [end_slot, counter + 1, value, end_slot] + loop += Op.DUP2 # [counter + 1, end_slot, counter + 1, value, end_slot] + loop += Op.LT # [counter + 1 < end_slot, counter + 1, value, end_slot] + loop += Op.PUSH1(len(setup)) + loop += Op.JUMPI + # [counter + 1, value, end_slot] + + cleanup = Bytecode() + cleanup += Op.STOP + + return IteratingBytecode(setup=setup, iterating=loop, cleanup=cleanup) + + +def create_sstore_dirty_executor( + write_values: List[int], + key_warm: bool, + initial_value: int, +) -> IteratingBytecode: + """ + Create executor that writes multiple values to each slot. + + Exercise dirty state transitions by performing a sequence of SSTOREs + to the same slot within a single loop iteration. After the first + SSTORE, the slot is warm and subsequent writes hit the dirty + (100 gas) path when original != current. + + - CALLDATA[0..32] start slot (index) + - CALLDATA[32..64] ending slot (end_slot) + + Return an IteratingBytecode for the dirty-write benchmark executor. + """ + setup = ( + Op.CALLDATALOAD(32) # end_slot + + Op.CALLDATALOAD(0) # start_slot = counter + ) + # Stack: [counter, end_slot] + + loop = Bytecode() + loop += Op.JUMPDEST + + for i, val in enumerate(write_values): + is_first = i == 0 + current_val = initial_value if is_first else write_values[i - 1] + # DUP2 reaches counter through the pushed value + loop += Op.SSTORE( + Op.DUP2, + val, + key_warm=key_warm if is_first else True, + original_value=initial_value, + current_value=current_val, + new_value=val, + ) + # Stack after all writes: [counter, end_slot] + + # Increment counter + loop += Op.PUSH1(1) + loop += Op.ADD + # [counter + 1, end_slot] + + # Loop while counter + 1 < end_slot + loop += Op.DUP2 + loop += Op.DUP2 + loop += Op.LT + loop += Op.PUSH1(len(setup)) + loop += Op.JUMPI + + cleanup = Bytecode() + cleanup += Op.STOP + + return IteratingBytecode(setup=setup, iterating=loop, cleanup=cleanup) + + +@pytest.mark.parametrize("access_warm", [True, False]) +@pytest.mark.parametrize("sloads_before_sstore", [True, False]) +@pytest.mark.parametrize( + "initial_value,write_value", + [ + pytest.param(0, 0, id="zero_to_zero"), + pytest.param(0, 0xDEADBEEF, id="zero_to_nonzero"), + # TODO: Resolve refund mechanism + # pytest.param(0xDEADBEEF, 0, id="nonzero_to_zero"), + pytest.param(0xDEADBEEF, 0xBEEFBEEF, id="nonzero_to_diff"), + pytest.param(0xDEADBEEF, 0xDEADBEEF, id="nonzero_to_same"), + ], +) +def test_sstore_variants( + benchmark_test: BenchmarkTestFiller, + fork: Fork, + pre: Alloc, + tx_gas_limit: int, + gas_benchmark_value: int, + access_warm: bool, + sloads_before_sstore: bool, + initial_value: int, + write_value: int, +) -> None: + """ + Benchmark SSTORE instruction with various configurations. + + Uses EIP-7702 delegation. The authority EOA delegates to: + + - StorageInitializer: storage[i] = initial_value (initial_value != 0) + - BenchmarkExecutor: performs the benchmark operation (SSTORE) + + Variants: + + - access_warm: Warm storage slots via access list + - sloads_before_sstore: SLOADs per slot before SSTORE + - initial_value/write_value: Storage transitions + (zero_to_zero, zero_to_nonzero, nonzero_to_zero, nonzero_to_nonzero) + """ + # Initial Storage Construction + initializer_code = create_sstore_initializer(initial_value) + initializer_addr = pre.deploy_contract(code=initializer_code) + + # Actual Benchmark Execution + executor_code = create_sstore_executor( + sloads_before_sstore=sloads_before_sstore, + key_warm=access_warm, + original_value=initial_value, + new_value=write_value, + ) + executor_addr = pre.deploy_contract(code=executor_code) + + authority = pre.fund_eoa(amount=0) + authority_nonce = 0 + + delegation_sender = pre.fund_eoa() + + calldata_gen = partial( + executor_calldata_generator, write_value=write_value + ) + access_list_gen = partial( + access_list_generator, access_warm=access_warm, authority=authority + ) + + # Number of slots that can be processed in the execution phase + num_target_slots = sum( + executor_code.tx_iterations_by_gas_limit( + fork=fork, + gas_limit=gas_benchmark_value, + calldata=calldata_gen, + access_list=access_list_gen, + start_iteration=1, + recipient_type=RecipientType.DELEGATION_7702, + ) + ) + + # Setup phase: initialize storage slots (if initial_value != 0) + with TestPhaseManager.setup(): + blocks = build_delegated_storage_setup( + pre=pre, + fork=fork, + tx_gas_limit=tx_gas_limit, + needs_init=initial_value != 0, + num_target_slots=num_target_slots, + initializer_code=initializer_code, + initializer_addr=initializer_addr, + executor_addr=executor_addr, + authority=authority, + authority_nonce=authority_nonce, + delegation_sender=delegation_sender, + initializer_calldata_generator=initializer_calldata_generator, + ) + + # Execution phase + expected_gas_used = 0 + + with TestPhaseManager.execution(): + exec_txs = list( + executor_code.transactions_by_gas_limit( + fork=fork, + gas_limit=gas_benchmark_value, + sender=pre.fund_eoa(), + to=authority, + calldata=calldata_gen, + start_iteration=1, + access_list=access_list_gen, + recipient_type=RecipientType.DELEGATION_7702, + ) + ) + + expected_gas_used = sum(tx.gas_cost for tx in exec_txs) + + blocks.append(Block(txs=exec_txs)) + + benchmark_test( + pre=pre, + blocks=blocks, + expected_benchmark_gas_used=expected_gas_used, + ) + + +# SSTORE DIRTY TRANSITIONS BENCHMARK ARCHITECTURE: +# +# [Authority EOA] +# │ +# │ Phase 1: Delegate to StorageInitializer +# │ ──► SSTORE(slot, initial_value) for N slots +# │ +# │ Phase 2: Delegate to DirtyExecutor +# │ ──► For each slot: +# │ SSTORE(slot, v1) → SSTORE(slot, v2) → ... +# │ +# WHY IT STRESSES CLIENTS: +# - Multiple writes per slot exercise EIP-2200/EIP-3529 refund +# branching: clean (original==current) vs dirty (original!=current) +# - Oscillation causes refund counter to swing up/down each write +# - Refund cap (gas_used/5) saturates with enough iterations +# - Tests correct tracking of original vs current vs new values + + +@pytest.mark.parametrize("access_warm", [True, False]) +@pytest.mark.parametrize( + "initial_value,write_values", + [ + pytest.param( + 0xDEADBEEF, + [0, 0xDEADBEEF, 0, 0xDEADBEEF], + id="oscillation_4x", + ), + pytest.param( + 0xDEADBEEF, + [0, 0xDEADBEEF, 0, 0xDEADBEEF, 0, 0xDEADBEEF], + id="oscillation_6x", + ), + pytest.param( + 0xDEADBEEF, + [0xBEEFBEEF, 0xCAFECAFE, 0xDEADBEEF], + id="triple_write_restore", + ), + pytest.param( + 0xDEADBEEF, + [0], + id="mass_clear", + ), + pytest.param( + 0, + [1, 0, 1, 0], + id="oscillation_4x_from_zero", + marks=pytest.mark.skip( + reason="net-zero state gas; degenerates to a regular-gas loop" + ), + ), + pytest.param( + 0, + [1], + id="mass_set_from_zero", + ), + ], +) +def test_sstore_dirty_transitions( + benchmark_test: BenchmarkTestFiller, + fork: Fork, + pre: Alloc, + tx_gas_limit: int, + gas_benchmark_value: int, + access_warm: bool, + initial_value: int, + write_values: List[int], +) -> None: + """ + Benchmark SSTORE dirty state transitions. + + Exercise EIP-2200/EIP-3529 refund logic by writing the same slot + multiple times per iteration. Uses EIP-7702 delegation: authority + EOA delegates to initializer then to dirty-write executor. + + Variants: + + - oscillation: X→0→X→0, alternates clean (2900) and dirty (100) + - triple_write_restore: X→B→C→X, all SSTORE branches + - mass_clear: X→0, maximum per-slot refund generation + """ + # Initial Storage Construction + initializer_code = create_sstore_initializer(initial_value) + initializer_addr = pre.deploy_contract(code=initializer_code) + + # Benchmark Executor — multi-write per slot + executor_code = create_sstore_dirty_executor( + write_values=write_values, + key_warm=access_warm, + initial_value=initial_value, + ) + executor_addr = pre.deploy_contract(code=executor_code) + + authority = pre.fund_eoa(amount=0) + authority_nonce = 0 + + delegation_sender = pre.fund_eoa() + + calldata_gen = partial(executor_calldata_generator) + access_list_gen = partial( + access_list_generator, + access_warm=access_warm, + authority=authority, + ) + + # Number of slots processable in execution phase + num_target_slots = sum( + executor_code.tx_iterations_by_gas_limit( + fork=fork, + gas_limit=gas_benchmark_value, + calldata=calldata_gen, + access_list=access_list_gen, + start_iteration=1, + recipient_type=RecipientType.DELEGATION_7702, + ) + ) + + # Setup phase: initialize all slots to initial_value + with TestPhaseManager.setup(): + blocks = build_delegated_storage_setup( + pre=pre, + fork=fork, + tx_gas_limit=tx_gas_limit, + needs_init=initial_value != 0, + num_target_slots=num_target_slots, + initializer_code=initializer_code, + initializer_addr=initializer_addr, + executor_addr=executor_addr, + authority=authority, + authority_nonce=authority_nonce, + delegation_sender=delegation_sender, + initializer_calldata_generator=(initializer_calldata_generator), + ) + + # Execution phase — no expected_benchmark_gas_used because + # refund cap (gas_used/5) makes actual consumption non-trivial + with TestPhaseManager.execution(): + exec_txs = list( + executor_code.transactions_by_gas_limit( + fork=fork, + gas_limit=gas_benchmark_value, + sender=pre.fund_eoa(), + to=authority, + calldata=calldata_gen, + start_iteration=1, + access_list=access_list_gen, + recipient_type=RecipientType.DELEGATION_7702, + ) + ) + + blocks.append(Block(txs=exec_txs)) + + benchmark_test( + pre=pre, + blocks=blocks, + skip_gas_used_validation=True, + ) diff --git a/tests/benchmark/stateful/bloatnet/test_transient_storage.py b/tests/benchmark/stateful/bloatnet/test_transient_storage.py index 32e330a0cca..c1d19d8046e 100644 --- a/tests/benchmark/stateful/bloatnet/test_transient_storage.py +++ b/tests/benchmark/stateful/bloatnet/test_transient_storage.py @@ -1,4 +1,4 @@ -"""Transient storage benchmarks for TSTORE/TLOAD.""" +"""Benchmark transient storage operations (TSTORE/TLOAD).""" import pytest from execution_testing import ( @@ -17,9 +17,6 @@ build_benchmark_txs, ) -REFERENCE_SPEC_GIT_PATH = "DUMMY/bloatnet.md" -REFERENCE_SPEC_VERSION = "1.0" - @pytest.mark.parametrize("with_tload", [True, False]) def test_tstore_unique_keys( diff --git a/tests/benchmark/stateful/helpers.py b/tests/benchmark/stateful/helpers.py index d9c028ba55b..cd313544881 100644 --- a/tests/benchmark/stateful/helpers.py +++ b/tests/benchmark/stateful/helpers.py @@ -11,12 +11,15 @@ Address, Alloc, AuthorizationTuple, + BenchmarkTestFiller, Block, + Bytecode, Fork, Hash, IteratingBytecode, Op, RecipientType, + TestPhaseManager, Transaction, ) from execution_testing.base_types.base_types import Number @@ -37,6 +40,15 @@ ) +# keccak256("random") for non-existing slots, masked as address, +# Solidity does input checks on the size and throws if we input +# something different than an address +START_SLOT = ( + 0xA4896A3F93BF4BF58378E579F3CF193BB4AF1022AF7D2089F37D8BAE7157B85F + % (2**160) +) + + class CacheStrategy(str, Enum): """Defines cache assumptions for benchmarked state access.""" @@ -434,3 +446,154 @@ def build_sequential_storage_init( blocks: list[Block] = [Block(txs=[auth_tx])] blocks.extend(pack_transactions_into_blocks(init_txs, tx_gas_limit)) return blocks + + +def delegate_with_calldata( + pre: Alloc, + fork: Fork, + authority: EOA, + address: Address, + calldata: Hash, +) -> Transaction: + """ + Create a tx that delegates the authority and calls it with calldata. + + The delegated code determines what happens with the calldata. + The authority nonce is incremented in-place. + """ + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( + calldata=bytes(calldata), + authorization_list_or_count=1, + ) + gas_limit = intrinsic_gas + 500_000 + tx = Transaction( + gas_limit=gas_limit, + to=authority, + value=0, + data=calldata, + sender=pre.fund_eoa(), + authorization_list=[ + AuthorizationTuple( + chain_id=0, + address=address, + nonce=authority.nonce, + signer=authority, + ), + ], + ) + authority.nonce = Number(authority.nonce + 1) + return tx + + +def run_bloated_eoa_benchmark( + *, + benchmark_test: BenchmarkTestFiller, + pre: Alloc, + fork: Fork, + gas_benchmark_value: int, + tx_gas_limit: int, + authority: EOA, + existing_slots: bool, + runtime_code: Bytecode, + cache_strategy: CacheStrategy, + tx_generator: Callable[[EOA], list[Transaction]] | None = None, +) -> None: + """ + Run a bloated-EOA benchmark with the given runtime delegation code. + """ + slot_0_value = Hash(1) if existing_slots else Hash(START_SLOT) + + setter_address = pre.deploy_contract(code=Op.SSTORE(0, Op.CALLDATALOAD(0))) + runtime_address = pre.deploy_contract(code=runtime_code) + + init_tx = delegate_with_calldata( + pre, + fork, + authority, + setter_address, + slot_0_value, + ) + runtime_tx = delegate_with_calldata( + pre, + fork, + authority, + runtime_address, + Hash(0), + ) + + blocks: list[Block] = [Block(txs=[init_tx, runtime_tx])] + + sender = pre.fund_eoa() + + txs: list[Transaction] = [] + with TestPhaseManager.execution(): + if tx_generator is not None: + txs = tx_generator(sender) + else: + gas_available = gas_benchmark_value + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() + while gas_available >= intrinsic_gas: + tx_gas = min(gas_available, tx_gas_limit) + txs.append( + Transaction( + gas_limit=tx_gas, + to=authority, + sender=sender, + ) + ) + gas_available -= tx_gas + + cache_txs: list[Transaction] = [] + if cache_strategy == CacheStrategy.CACHE_PREVIOUS_BLOCK: + with TestPhaseManager.setup(): + cache_sender = pre.fund_eoa() + for tx in txs: + cache_txs.append( + Transaction( + gas_limit=tx.gas_limit, + data=tx.data, + to=authority, + sender=cache_sender, + ) + ) + + blocks += build_cache_strategy_blocks(cache_strategy, txs, cache_txs) + + benchmark_test( + pre=pre, + blocks=blocks, + skip_gas_used_validation=True, + expected_receipt_status=True, + ) + + +def access_list_generator( + iteration_count: int, + start_iteration: int, + access_warm: bool, + authority: Address, +) -> list[AccessList] | None: + """Access list generator for warming storage slots.""" + if access_warm: + storage_keys = [ + Hash(i) + for i in range(start_iteration, start_iteration + iteration_count) + ] + return [AccessList(address=authority, storage_keys=storage_keys)] + return None + + +def executor_calldata_generator( + iteration_count: int, + start_iteration: int, + write_value: int | None = None, +) -> bytes: + """ + Calldata generator for executor operations. + + Generates: Hash(start) + Hash(start + count) [+ Hash(write_value)] + """ + result = Hash(start_iteration) + Hash(start_iteration + iteration_count) + if write_value is not None: + result += Hash(write_value) + return result From c5043ec2ab1ba6008b874a2cc9b56499bd3d3e92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=94=A1=E4=BD=B3=E8=AA=A0=20Louis=20Tsai?= <72684086+LouisTsai-Csie@users.noreply.github.com> Date: Wed, 15 Jul 2026 23:49:25 +0800 Subject: [PATCH 140/233] refactor(test-benchmark): centralize benchmark helper utilities (#3166) --- .../compute/instruction/test_arithmetic.py | 6 +- .../compute/instruction/test_bitwise.py | 2 +- .../compute/instruction/test_call_context.py | 4 +- .../compute/instruction/test_storage.py | 2 +- .../compute/precompile/test_alt_bn128.py | 2 +- .../compute/precompile/test_blake2f.py | 3 +- .../compute/precompile/test_bls12_381.py | 2 +- .../compute/precompile/test_ecrecover.py | 2 +- .../compute/precompile/test_identity.py | 2 +- .../compute/precompile/test_modexp.py | 3 +- .../compute/precompile/test_p256verify.py | 2 +- .../precompile/test_point_evaluation.py | 2 +- .../compute/precompile/test_ripemd160.py | 2 +- .../compute/precompile/test_sha256.py | 2 +- .../scenario/test_unchunkified_bytecode.py | 5 +- .../helpers.py => helper/contract_factory.py} | 229 +------ tests/benchmark/helper/delegation.py | 234 +++++++ tests/benchmark/helper/enums.py | 37 ++ tests/benchmark/helper/loops.py | 11 + tests/benchmark/helper/numeric.py | 48 ++ tests/benchmark/helper/precompile.py | 101 +++ tests/benchmark/helper/storage.py | 237 +++++++ tests/benchmark/helper/transactions.py | 140 ++++ .../stateful/bloatnet/test_account_query.py | 6 +- .../benchmark/stateful/bloatnet/test_call.py | 6 +- .../stateful/bloatnet/test_create.py | 4 +- .../benchmark/stateful/bloatnet/test_erc20.py | 7 +- .../benchmark/stateful/bloatnet/test_sload.py | 12 +- .../stateful/bloatnet/test_sstore.py | 10 +- .../bloatnet/test_transient_storage.py | 6 +- .../helpers.py | 2 +- tests/benchmark/stateful/helpers.py | 599 ------------------ 32 files changed, 857 insertions(+), 873 deletions(-) rename tests/benchmark/{compute/helpers.py => helper/contract_factory.py} (62%) create mode 100644 tests/benchmark/helper/delegation.py create mode 100644 tests/benchmark/helper/enums.py create mode 100644 tests/benchmark/helper/loops.py create mode 100644 tests/benchmark/helper/numeric.py create mode 100644 tests/benchmark/helper/precompile.py create mode 100644 tests/benchmark/helper/storage.py create mode 100644 tests/benchmark/helper/transactions.py delete mode 100644 tests/benchmark/stateful/helpers.py diff --git a/tests/benchmark/compute/instruction/test_arithmetic.py b/tests/benchmark/compute/instruction/test_arithmetic.py index bb8a08ac263..2582503d905 100644 --- a/tests/benchmark/compute/instruction/test_arithmetic.py +++ b/tests/benchmark/compute/instruction/test_arithmetic.py @@ -30,7 +30,11 @@ Transaction, ) -from ..helpers import DEFAULT_BINOP_ARGS, make_dup, neg +from tests.benchmark.helper.numeric import ( + DEFAULT_BINOP_ARGS, + make_dup, + neg, +) @pytest.mark.parametrize( diff --git a/tests/benchmark/compute/instruction/test_bitwise.py b/tests/benchmark/compute/instruction/test_bitwise.py index d062cdd0a2e..044613d5f42 100644 --- a/tests/benchmark/compute/instruction/test_bitwise.py +++ b/tests/benchmark/compute/instruction/test_bitwise.py @@ -27,7 +27,7 @@ Transaction, ) -from ..helpers import ( +from tests.benchmark.helper.numeric import ( DEFAULT_BINOP_ARGS, make_dup, sar, diff --git a/tests/benchmark/compute/instruction/test_call_context.py b/tests/benchmark/compute/instruction/test_call_context.py index bda9ae6c5ee..bca3b62f1eb 100644 --- a/tests/benchmark/compute/instruction/test_call_context.py +++ b/tests/benchmark/compute/instruction/test_call_context.py @@ -24,9 +24,7 @@ Op, ) -from ..helpers import ( - ReturnDataStyle, -) +from tests.benchmark.helper.enums import ReturnDataStyle @pytest.mark.repricing diff --git a/tests/benchmark/compute/instruction/test_storage.py b/tests/benchmark/compute/instruction/test_storage.py index bad640402e6..c44f3c76b56 100644 --- a/tests/benchmark/compute/instruction/test_storage.py +++ b/tests/benchmark/compute/instruction/test_storage.py @@ -30,7 +30,7 @@ compute_create_address, ) -from ..helpers import StorageAction, TransactionResult +from tests.benchmark.helper.enums import StorageAction, TransactionResult @pytest.mark.repricing(fixed_key=True, fixed_value=True) diff --git a/tests/benchmark/compute/precompile/test_alt_bn128.py b/tests/benchmark/compute/precompile/test_alt_bn128.py index b65f2ee968f..2047023c37f 100644 --- a/tests/benchmark/compute/precompile/test_alt_bn128.py +++ b/tests/benchmark/compute/precompile/test_alt_bn128.py @@ -21,7 +21,7 @@ from py_ecc.bn128 import G1, G2, multiply from py_ecc.fields import bn128_FQ2 -from tests.benchmark.compute.helpers import Precompile +from tests.benchmark.helper.precompile import Precompile from tests.byzantium.eip196_ec_add_mul.spec import ( PointG1, Scalar, diff --git a/tests/benchmark/compute/precompile/test_blake2f.py b/tests/benchmark/compute/precompile/test_blake2f.py index 0fad03827b0..f94b954b856 100644 --- a/tests/benchmark/compute/precompile/test_blake2f.py +++ b/tests/benchmark/compute/precompile/test_blake2f.py @@ -16,11 +16,10 @@ WhileGas, ) +from tests.benchmark.helper.precompile import Precompile from tests.istanbul.eip152_blake2.common import Blake2bInput from tests.istanbul.eip152_blake2.spec import Spec as Blake2bSpec -from ..helpers import Precompile - @pytest.mark.parametrize( "precompile_address,calldata", diff --git a/tests/benchmark/compute/precompile/test_bls12_381.py b/tests/benchmark/compute/precompile/test_bls12_381.py index ce457d237f2..770ffd59ee8 100644 --- a/tests/benchmark/compute/precompile/test_bls12_381.py +++ b/tests/benchmark/compute/precompile/test_bls12_381.py @@ -21,7 +21,7 @@ ) from py_ecc import optimized_bls12_381 as bls_curve -from tests.benchmark.compute.helpers import Precompile +from tests.benchmark.helper.precompile import Precompile from tests.prague.eip2537_bls_12_381_precompiles import spec as bls12381_spec from tests.prague.eip2537_bls_12_381_precompiles.spec import ( build_gas_calculation_function_map, diff --git a/tests/benchmark/compute/precompile/test_ecrecover.py b/tests/benchmark/compute/precompile/test_ecrecover.py index 6d7dec43e11..686bb29e335 100644 --- a/tests/benchmark/compute/precompile/test_ecrecover.py +++ b/tests/benchmark/compute/precompile/test_ecrecover.py @@ -9,7 +9,7 @@ Op, ) -from tests.benchmark.compute.helpers import Precompile +from tests.benchmark.helper.precompile import Precompile from tests.frontier.precompiles.spec import EcrecoverInput from tests.frontier.precompiles.spec import Spec as EcrecoverSpec diff --git a/tests/benchmark/compute/precompile/test_identity.py b/tests/benchmark/compute/precompile/test_identity.py index 759ad8d30a6..b467b6bf522 100644 --- a/tests/benchmark/compute/precompile/test_identity.py +++ b/tests/benchmark/compute/precompile/test_identity.py @@ -16,7 +16,7 @@ WhileGas, ) -from tests.benchmark.compute.helpers import ( +from tests.benchmark.helper.precompile import ( Precompile, calculate_optimal_input_length, ) diff --git a/tests/benchmark/compute/precompile/test_modexp.py b/tests/benchmark/compute/precompile/test_modexp.py index 2906bf7b730..384f25450f1 100644 --- a/tests/benchmark/compute/precompile/test_modexp.py +++ b/tests/benchmark/compute/precompile/test_modexp.py @@ -17,11 +17,10 @@ ) from execution_testing.forks import Osaka +from tests.benchmark.helper.precompile import Precompile from tests.byzantium.eip198_modexp_precompile.helpers import ModExpInput from tests.osaka.eip7883_modexp_gas_increase.spec import Spec, Spec7883 -from ..helpers import Precompile - def create_random_modexp_test_case( test_id: str, diff --git a/tests/benchmark/compute/precompile/test_p256verify.py b/tests/benchmark/compute/precompile/test_p256verify.py index fedb648c51b..0ae19db5cb7 100644 --- a/tests/benchmark/compute/precompile/test_p256verify.py +++ b/tests/benchmark/compute/precompile/test_p256verify.py @@ -14,7 +14,7 @@ WhileGas, ) -from tests.benchmark.compute.helpers import Precompile +from tests.benchmark.helper.precompile import Precompile from tests.osaka.eip7951_p256verify_precompiles import spec as p256verify_spec from tests.osaka.eip7951_p256verify_precompiles.spec import H, R, S, X, Y diff --git a/tests/benchmark/compute/precompile/test_point_evaluation.py b/tests/benchmark/compute/precompile/test_point_evaluation.py index e8ab7eac799..a9bdad6b64e 100644 --- a/tests/benchmark/compute/precompile/test_point_evaluation.py +++ b/tests/benchmark/compute/precompile/test_point_evaluation.py @@ -18,7 +18,7 @@ ) from execution_testing.test_types.blob_types import Blob -from tests.benchmark.compute.helpers import Precompile +from tests.benchmark.helper.precompile import Precompile from tests.cancun.eip4844_blobs.spec import PointEvaluationInput from tests.cancun.eip4844_blobs.spec import Spec as BlobsSpec diff --git a/tests/benchmark/compute/precompile/test_ripemd160.py b/tests/benchmark/compute/precompile/test_ripemd160.py index 7527eb48f3e..263a7479ede 100644 --- a/tests/benchmark/compute/precompile/test_ripemd160.py +++ b/tests/benchmark/compute/precompile/test_ripemd160.py @@ -16,7 +16,7 @@ WhileGas, ) -from tests.benchmark.compute.helpers import ( +from tests.benchmark.helper.precompile import ( Precompile, calculate_optimal_input_length, ) diff --git a/tests/benchmark/compute/precompile/test_sha256.py b/tests/benchmark/compute/precompile/test_sha256.py index 2960fae90b4..ffc699f03de 100644 --- a/tests/benchmark/compute/precompile/test_sha256.py +++ b/tests/benchmark/compute/precompile/test_sha256.py @@ -16,7 +16,7 @@ WhileGas, ) -from tests.benchmark.compute.helpers import ( +from tests.benchmark.helper.precompile import ( Precompile, calculate_optimal_input_length, ) diff --git a/tests/benchmark/compute/scenario/test_unchunkified_bytecode.py b/tests/benchmark/compute/scenario/test_unchunkified_bytecode.py index 7bbcba3bfe3..7f738db0b25 100644 --- a/tests/benchmark/compute/scenario/test_unchunkified_bytecode.py +++ b/tests/benchmark/compute/scenario/test_unchunkified_bytecode.py @@ -21,7 +21,10 @@ While, ) -from ..helpers import ContractDeploymentTransaction, CustomSizedContractFactory +from tests.benchmark.helper.contract_factory import ( + ContractDeploymentTransaction, + CustomSizedContractFactory, +) @pytest.mark.parametrize( diff --git a/tests/benchmark/compute/helpers.py b/tests/benchmark/helper/contract_factory.py similarity index 62% rename from tests/benchmark/compute/helpers.py rename to tests/benchmark/helper/contract_factory.py index ec758afe32d..27176c325cf 100644 --- a/tests/benchmark/compute/helpers.py +++ b/tests/benchmark/helper/contract_factory.py @@ -1,254 +1,29 @@ -"""Helper functions for the EVM benchmark worst-case tests.""" +"""Custom-sized contract initcode and CREATE2 deployment factory.""" -import math -from enum import Enum, auto -from typing import Dict, Generator, List, Self, Sequence, cast +from typing import Dict, Generator, List, Self from execution_testing import ( EOA, Address, Alloc, Bytecode, - BytesConcatenation, FixedIterationsBytecode, Fork, Hash, Initcode, IteratingBytecode, Op, - OpcodeTarget, TransactionWithCost, - TxOutcome, While, compute_create2_address, compute_deterministic_create2_address, ) from pydantic import Field -from tests.osaka.eip7951_p256verify_precompiles.spec import ( - FieldElement, -) - - -class Precompile: - """Target opcode labels for precompile benchmarks.""" - - ECRECOVER = OpcodeTarget("ECRECOVER", Op.STATICCALL) - SHA256 = OpcodeTarget("SHA2-256", Op.STATICCALL) - RIPEMD160 = OpcodeTarget("RIPEMD-160", Op.STATICCALL) - IDENTITY = OpcodeTarget("IDENTITY", Op.STATICCALL) - MODEXP = OpcodeTarget("MODEXP", Op.STATICCALL) - BN128_ADD = OpcodeTarget("BN128_ADD", Op.STATICCALL) - BN128_MUL = OpcodeTarget("BN128_MUL", Op.STATICCALL) - BN128_PAIRING = OpcodeTarget("BN128_PAIRING", Op.STATICCALL) - BLAKE2F = OpcodeTarget("BLAKE2F", Op.STATICCALL) - POINT_EVALUATION = OpcodeTarget("POINT_EVALUATION", Op.STATICCALL) - P256VERIFY = OpcodeTarget("P256VERIFY", Op.STATICCALL) - BLS12_G1ADD = OpcodeTarget("BLS12_G1ADD", Op.STATICCALL) - BLS12_G1MSM = OpcodeTarget("BLS12_G1MSM", Op.STATICCALL) - BLS12_G2ADD = OpcodeTarget("BLS12_G2ADD", Op.STATICCALL) - BLS12_G2MSM = OpcodeTarget("BLS12_G2MSM", Op.STATICCALL) - BLS12_PAIRING = OpcodeTarget("BLS12_PAIRING", Op.STATICCALL) - BLS12_MAP_FP_TO_G1 = OpcodeTarget("BLS12_MAP_FP_TO_G1", Op.STATICCALL) - BLS12_MAP_FP2_TO_G2 = OpcodeTarget("BLS12_MAP_FP2_TO_G2", Op.STATICCALL) - - -DEFAULT_BINOP_ARGS = ( - 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F, - 0x73EDA753299D7D483339D80809A1D80553BDA402FFFE5BFEFFFFFFFF00000001, -) - XOR_TABLE_SIZE = 256 XOR_TABLE = [Hash(i).sha256() for i in range(XOR_TABLE_SIZE)] -class StorageAction: - """Enum for storage actions.""" - - READ = auto() - WRITE_SAME_VALUE = auto() - WRITE_NEW_VALUE = auto() - - -TransactionResult = TxOutcome -"""Alias for the framework outcome enum used to bill transaction gas.""" - - -class ReturnDataStyle(Enum): - """Helper enum to specify how return data is returned to the caller.""" - - RETURN = auto() - REVERT = auto() - IDENTITY = auto() - - -class CallDataOrigin: - """Enum for calldata origins.""" - - TRANSACTION = auto() - CALL = auto() - - -def neg(x: int) -> int: - """Negate the given integer in the two's complement 256-bit range.""" - assert 0 <= x < 2**256 - return 2**256 - x - - -def make_dup(index: int) -> Op: - """ - Create a DUP instruction which duplicates the index-th (counting from 0) - element from the top of the stack. E.g. make_dup(0) → DUP1. - """ - assert 0 <= index < 16, f"DUP index {index} out of range [0, 15]" - return getattr(Op, f"DUP{index + 1}") - - -def to_signed(x: int) -> int: - """Convert an unsigned integer to a signed integer.""" - return x if x < 2**255 else x - 2**256 - - -def to_unsigned(x: int) -> int: - """Convert a signed integer to an unsigned integer.""" - return x if x >= 0 else x + 2**256 - - -def shr(x: int, s: int) -> int: - """Shift right.""" - return x >> s - - -def shl(x: int, s: int) -> int: - """Shift left.""" - return x << s - - -def sar(x: int, s: int) -> int: - """Arithmetic shift right.""" - return to_unsigned(to_signed(x) >> s) - - -def concatenate_parameters( - parameters: ( - Sequence[str] | Sequence[BytesConcatenation] | Sequence[bytes] - ), -) -> bytes: - """ - Concatenate precompile parameters into bytes. - - Args: - parameters: List of parameters, either as hex strings or byte objects - (bytes, BytesConcatenation, or FieldElement). - - Returns: - Concatenated bytes from all parameters. - - """ - if all(isinstance(p, str) for p in parameters): - parameters_str = cast(Sequence[str], parameters) - concatenated_hex_string = "".join(parameters_str) - return bytes.fromhex(concatenated_hex_string) - elif all( - isinstance( - p, - ( - bytes, - BytesConcatenation, - FieldElement, - ), - ) - for p in parameters - ): - parameters_bytes_list = [ - bytes(p) - for p in cast( - Sequence[BytesConcatenation | bytes | FieldElement], - parameters, - ) - ] - return b"".join(parameters_bytes_list) - else: - raise TypeError( - "parameters must be a sequence of strings (hex) " - "or a sequence of byte-like objects (bytes, BytesConcatenation or " - "FieldElement)." - ) - - -def calculate_optimal_input_length( - available_gas: int, - fork: Fork, - static_cost: int, - per_word_dynamic_cost: int, - bytes_per_unit_of_work: int, -) -> int: - """ - Calculate the optimal input length to maximize precompile work. - - This function finds the input size that maximizes the total amount of - work (in terms of bytes processed) a precompile can perform given a - fixed gas budget. It balances the trade-off between making more calls - with smaller inputs versus fewer calls with larger inputs. - - Args: - available_gas: Total gas available for precompile calls. - fork: The fork to use for gas cost calculations. - static_cost: Static gas cost per precompile call. - per_word_dynamic_cost: Dynamic gas cost per 32-byte word of input. - bytes_per_unit_of_work: Number of bytes processed per unit of work. - - Returns: - The optimal input length in bytes that maximizes total work. - - """ - mem_exp_gas_calculator = fork.memory_expansion_gas_calculator() - - precompile_call = Op.POP( - Op.STATICCALL( - gas=Op.GAS, - address=0x01, # Placeholder Address - args_offset=Op.PUSH0, - args_size=Op.PUSH0, - ret_offset=Op.PUSH0, - ret_size=Op.PUSH0, - # gas cost - address_warm=True, - ) - ) - basic_gas = precompile_call.gas_cost(fork) - - max_work = 0 - optimal_input_length = 0 - - for input_length in range(1, 1_000_000, 32): - iteration_gas_cost = ( - basic_gas - + static_cost # Precompile static cost - + math.ceil(input_length / 32) * per_word_dynamic_cost - # Precompile dynamic cost - ) - - # From the available gas, subtract the memory expansion costs - # considering the current input size length. - available_gas_after_expansion = max( - 0, available_gas - mem_exp_gas_calculator(new_bytes=input_length) - ) - - # Calculate how many calls we can do. - num_calls = available_gas_after_expansion // iteration_gas_cost - total_work = num_calls * math.ceil( - input_length / bytes_per_unit_of_work - ) - - # If we found an input size with better total work, save it. - if total_work > max_work: - max_work = total_work - optimal_input_length = input_length - - return optimal_input_length - - class CustomSizedContractInitcode(FixedIterationsBytecode): """ Initcode that deploys a random contract with a custom size. diff --git a/tests/benchmark/helper/delegation.py b/tests/benchmark/helper/delegation.py new file mode 100644 index 00000000000..dff4d46f5f3 --- /dev/null +++ b/tests/benchmark/helper/delegation.py @@ -0,0 +1,234 @@ +"""EIP-7702 delegation helpers for stateful benchmarks.""" + +from collections.abc import Callable + +from execution_testing import ( + EOA, + Address, + Alloc, + AuthorizationTuple, + BenchmarkTestFiller, + Block, + Bytecode, + Fork, + Hash, + IteratingBytecode, + Op, + RecipientType, + TestPhaseManager, + Transaction, +) +from execution_testing.base_types.base_types import Number + +from .enums import CacheStrategy +from .storage import START_SLOT +from .transactions import ( + build_cache_strategy_blocks, + pack_transactions_into_blocks, +) + + +def build_delegated_storage_setup( + *, + pre: Alloc, + fork: Fork, + tx_gas_limit: int, + needs_init: bool, + num_target_slots: int, + initializer_code: IteratingBytecode, + initializer_addr: Address, + executor_addr: Address, + authority: EOA, + authority_nonce: int, + delegation_sender: EOA, + initializer_calldata_generator: Callable[[int, int], bytes], +) -> list[Block]: + """ + Build setup blocks for delegated storage benchmarks. + + Use EIP-7702 authorization to delegate an authority EOA first to + a storage-initializer contract (if *needs_init*), then to the + benchmark executor contract. Return the list of setup blocks. + """ + blocks: list[Block] = [] + + if needs_init: + # Block 1: Authorize to initializer + blocks.append( + Block( + txs=[ + Transaction( + to=delegation_sender, + gas_limit=tx_gas_limit, + sender=delegation_sender, + authorization_list=[ + AuthorizationTuple( + address=initializer_addr, + nonce=authority_nonce, + signer=authority, + ), + ], + ) + ] + ) + ) + authority_nonce += 1 + + # transactions_by_total_iteration_count splits the slots across + # transactions capped by the fork gas limit, so no manual chunking + # is required. + init_txs: list[Transaction] = list( + initializer_code.transactions_by_total_iteration_count( + fork=fork, + total_iterations=num_target_slots, + sender=pre.fund_eoa(), + to=authority, + start_iteration=1, + calldata=initializer_calldata_generator, + recipient_type=RecipientType.DELEGATION_7702, + ) + ) + + # Pack init transactions into blocks + blocks.extend(pack_transactions_into_blocks(init_txs, tx_gas_limit)) + + # Final block: Authorize to executor + blocks.append( + Block( + txs=[ + Transaction( + to=delegation_sender, + gas_limit=tx_gas_limit, + sender=delegation_sender, + authorization_list=[ + AuthorizationTuple( + address=executor_addr, + nonce=authority_nonce, + signer=authority, + ), + ], + ) + ] + ) + ) + + return blocks + + +def delegate_with_calldata( + pre: Alloc, + fork: Fork, + authority: EOA, + address: Address, + calldata: Hash, +) -> Transaction: + """ + Create a tx that delegates the authority and calls it with calldata. + + The delegated code determines what happens with the calldata. + The authority nonce is incremented in-place. + """ + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( + calldata=bytes(calldata), + authorization_list_or_count=1, + ) + gas_limit = intrinsic_gas + 500_000 + tx = Transaction( + gas_limit=gas_limit, + to=authority, + value=0, + data=calldata, + sender=pre.fund_eoa(), + authorization_list=[ + AuthorizationTuple( + chain_id=0, + address=address, + nonce=authority.nonce, + signer=authority, + ), + ], + ) + authority.nonce = Number(authority.nonce + 1) + return tx + + +def run_bloated_eoa_benchmark( + *, + benchmark_test: BenchmarkTestFiller, + pre: Alloc, + fork: Fork, + gas_benchmark_value: int, + tx_gas_limit: int, + authority: EOA, + existing_slots: bool, + runtime_code: Bytecode, + cache_strategy: CacheStrategy, + tx_generator: Callable[[EOA], list[Transaction]] | None = None, +) -> None: + """ + Run a bloated-EOA benchmark with the given runtime delegation code. + """ + slot_0_value = Hash(1) if existing_slots else Hash(START_SLOT) + + setter_address = pre.deploy_contract(code=Op.SSTORE(0, Op.CALLDATALOAD(0))) + runtime_address = pre.deploy_contract(code=runtime_code) + + init_tx = delegate_with_calldata( + pre, + fork, + authority, + setter_address, + slot_0_value, + ) + runtime_tx = delegate_with_calldata( + pre, + fork, + authority, + runtime_address, + Hash(0), + ) + + blocks: list[Block] = [Block(txs=[init_tx, runtime_tx])] + + sender = pre.fund_eoa() + + txs: list[Transaction] = [] + with TestPhaseManager.execution(): + if tx_generator is not None: + txs = tx_generator(sender) + else: + gas_available = gas_benchmark_value + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() + while gas_available >= intrinsic_gas: + tx_gas = min(gas_available, tx_gas_limit) + txs.append( + Transaction( + gas_limit=tx_gas, + to=authority, + sender=sender, + ) + ) + gas_available -= tx_gas + + cache_txs: list[Transaction] = [] + if cache_strategy == CacheStrategy.CACHE_PREVIOUS_BLOCK: + with TestPhaseManager.setup(): + cache_sender = pre.fund_eoa() + for tx in txs: + cache_txs.append( + Transaction( + gas_limit=tx.gas_limit, + data=tx.data, + to=authority, + sender=cache_sender, + ) + ) + + blocks += build_cache_strategy_blocks(cache_strategy, txs, cache_txs) + + benchmark_test( + pre=pre, + blocks=blocks, + skip_gas_used_validation=True, + expected_receipt_status=True, + ) diff --git a/tests/benchmark/helper/enums.py b/tests/benchmark/helper/enums.py new file mode 100644 index 00000000000..2ff638712c5 --- /dev/null +++ b/tests/benchmark/helper/enums.py @@ -0,0 +1,37 @@ +"""Parametrization enums shared across benchmark scenarios.""" + +from enum import Enum, auto + +from execution_testing import TxOutcome + + +class StorageAction: + """Enum for storage actions.""" + + READ = auto() + WRITE_SAME_VALUE = auto() + WRITE_NEW_VALUE = auto() + + +TransactionResult = TxOutcome +"""Alias for the framework outcome enum used to bill transaction gas.""" + + +class ReturnDataStyle(Enum): + """Helper enum to specify how return data is returned to the caller.""" + + RETURN = auto() + REVERT = auto() + IDENTITY = auto() + + +class CacheStrategy(str, Enum): + """Defines cache assumptions for benchmarked state access.""" + + # No caching strategy: target state is cold in EVM and cache + NO_CACHE = "no_cache" + # Caching at tx level: target state is warm in EVM and cache + CACHE_TX = "cache_tx" + # Caching at previous block: + # Target state is cold in EVM but (assumed) to be cached + CACHE_PREVIOUS_BLOCK = "cache_previous_block" diff --git a/tests/benchmark/helper/loops.py b/tests/benchmark/helper/loops.py new file mode 100644 index 00000000000..4cf901d0c94 --- /dev/null +++ b/tests/benchmark/helper/loops.py @@ -0,0 +1,11 @@ +"""Loop-construction helpers for benchmark attack contracts.""" + +from execution_testing import Op + +# Standard While-loop decrement-and-test condition. +# +# Expects the iteration counter on top of the stack: +# [counter] → SUB(counter, 1) → continue if nonzero +DECREMENT_COUNTER_CONDITION = ( + Op.PUSH1(1) + Op.SWAP1 + Op.SUB + Op.DUP1 + Op.ISZERO + Op.ISZERO +) diff --git a/tests/benchmark/helper/numeric.py b/tests/benchmark/helper/numeric.py new file mode 100644 index 00000000000..e96548ed3ed --- /dev/null +++ b/tests/benchmark/helper/numeric.py @@ -0,0 +1,48 @@ +"""Numeric and stack-manipulation helpers for benchmark bytecode.""" + +from execution_testing import Op + +DEFAULT_BINOP_ARGS = ( + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F, + 0x73EDA753299D7D483339D80809A1D80553BDA402FFFE5BFEFFFFFFFF00000001, +) + + +def neg(x: int) -> int: + """Negate the given integer in the two's complement 256-bit range.""" + assert 0 <= x < 2**256 + return 2**256 - x + + +def make_dup(index: int) -> Op: + """ + Create a DUP instruction which duplicates the index-th (counting from 0) + element from the top of the stack. E.g. make_dup(0) → DUP1. + """ + assert 0 <= index < 16, f"DUP index {index} out of range [0, 15]" + return getattr(Op, f"DUP{index + 1}") + + +def to_signed(x: int) -> int: + """Convert an unsigned integer to a signed integer.""" + return x if x < 2**255 else x - 2**256 + + +def to_unsigned(x: int) -> int: + """Convert a signed integer to an unsigned integer.""" + return x if x >= 0 else x + 2**256 + + +def shr(x: int, s: int) -> int: + """Shift right.""" + return x >> s + + +def shl(x: int, s: int) -> int: + """Shift left.""" + return x << s + + +def sar(x: int, s: int) -> int: + """Arithmetic shift right.""" + return to_unsigned(to_signed(x) >> s) diff --git a/tests/benchmark/helper/precompile.py b/tests/benchmark/helper/precompile.py new file mode 100644 index 00000000000..e8f680b40bf --- /dev/null +++ b/tests/benchmark/helper/precompile.py @@ -0,0 +1,101 @@ +"""Precompile benchmark targets and input-size tuning.""" + +import math + +from execution_testing import Fork, Op, OpcodeTarget + + +class Precompile: + """Target opcode labels for precompile benchmarks.""" + + ECRECOVER = OpcodeTarget("ECRECOVER", Op.STATICCALL) + SHA256 = OpcodeTarget("SHA2-256", Op.STATICCALL) + RIPEMD160 = OpcodeTarget("RIPEMD-160", Op.STATICCALL) + IDENTITY = OpcodeTarget("IDENTITY", Op.STATICCALL) + MODEXP = OpcodeTarget("MODEXP", Op.STATICCALL) + BN128_ADD = OpcodeTarget("BN128_ADD", Op.STATICCALL) + BN128_MUL = OpcodeTarget("BN128_MUL", Op.STATICCALL) + BN128_PAIRING = OpcodeTarget("BN128_PAIRING", Op.STATICCALL) + BLAKE2F = OpcodeTarget("BLAKE2F", Op.STATICCALL) + POINT_EVALUATION = OpcodeTarget("POINT_EVALUATION", Op.STATICCALL) + P256VERIFY = OpcodeTarget("P256VERIFY", Op.STATICCALL) + BLS12_G1ADD = OpcodeTarget("BLS12_G1ADD", Op.STATICCALL) + BLS12_G1MSM = OpcodeTarget("BLS12_G1MSM", Op.STATICCALL) + BLS12_G2ADD = OpcodeTarget("BLS12_G2ADD", Op.STATICCALL) + BLS12_G2MSM = OpcodeTarget("BLS12_G2MSM", Op.STATICCALL) + BLS12_PAIRING = OpcodeTarget("BLS12_PAIRING", Op.STATICCALL) + BLS12_MAP_FP_TO_G1 = OpcodeTarget("BLS12_MAP_FP_TO_G1", Op.STATICCALL) + BLS12_MAP_FP2_TO_G2 = OpcodeTarget("BLS12_MAP_FP2_TO_G2", Op.STATICCALL) + + +def calculate_optimal_input_length( + available_gas: int, + fork: Fork, + static_cost: int, + per_word_dynamic_cost: int, + bytes_per_unit_of_work: int, +) -> int: + """ + Calculate the optimal input length to maximize precompile work. + + This function finds the input size that maximizes the total amount of + work (in terms of bytes processed) a precompile can perform given a + fixed gas budget. It balances the trade-off between making more calls + with smaller inputs versus fewer calls with larger inputs. + + Args: + available_gas: Total gas available for precompile calls. + fork: The fork to use for gas cost calculations. + static_cost: Static gas cost per precompile call. + per_word_dynamic_cost: Dynamic gas cost per 32-byte word of input. + bytes_per_unit_of_work: Number of bytes processed per unit of work. + + Returns: + The optimal input length in bytes that maximizes total work. + + """ + mem_exp_gas_calculator = fork.memory_expansion_gas_calculator() + + precompile_call = Op.POP( + Op.STATICCALL( + gas=Op.GAS, + address=0x01, # Placeholder Address + args_offset=Op.PUSH0, + args_size=Op.PUSH0, + ret_offset=Op.PUSH0, + ret_size=Op.PUSH0, + # gas cost + address_warm=True, + ) + ) + basic_gas = precompile_call.gas_cost(fork) + + max_work = 0 + optimal_input_length = 0 + + for input_length in range(1, 1_000_000, 32): + iteration_gas_cost = ( + basic_gas + + static_cost # Precompile static cost + + math.ceil(input_length / 32) * per_word_dynamic_cost + # Precompile dynamic cost + ) + + # From the available gas, subtract the memory expansion costs + # considering the current input size length. + available_gas_after_expansion = max( + 0, available_gas - mem_exp_gas_calculator(new_bytes=input_length) + ) + + # Calculate how many calls we can do. + num_calls = available_gas_after_expansion // iteration_gas_cost + total_work = num_calls * math.ceil( + input_length / bytes_per_unit_of_work + ) + + # If we found an input size with better total work, save it. + if total_work > max_work: + max_work = total_work + optimal_input_length = input_length + + return optimal_input_length diff --git a/tests/benchmark/helper/storage.py b/tests/benchmark/helper/storage.py new file mode 100644 index 00000000000..54eaa3e1e7f --- /dev/null +++ b/tests/benchmark/helper/storage.py @@ -0,0 +1,237 @@ +"""Storage initialization helpers for stateful benchmarks.""" + +from dataclasses import dataclass +from functools import partial + +from execution_testing import ( + EOA, + AccessList, + Address, + Alloc, + AuthorizationTuple, + Block, + Fork, + Hash, + IteratingBytecode, + Op, + RecipientType, + Transaction, +) +from execution_testing.base_types.base_types import Number + +from .transactions import pack_transactions_into_blocks + +# keccak256("random") for non-existing slots, masked as address, +# Solidity does input checks on the size and throws if we input +# something different than an address +START_SLOT = ( + 0xA4896A3F93BF4BF58378E579F3CF193BB4AF1022AF7D2089F37D8BAE7157B85F + % (2**160) +) + + +def create_sstore_initializer(init_val: int) -> IteratingBytecode: + """ + Create a contract that initializes storage slots from calldata. + + - CALLDATA[0..32] start slot (index) + - CALLDATA[32..64] slot count (num) + + storage[i] = init_val for i in [index, index + num). + """ + # Setup: [index, index + num] + prefix = ( + Op.CALLDATALOAD(0) # [index] + + Op.DUP1 # [index, index] + + Op.CALLDATALOAD(32) # [index, index, num] + + Op.ADD # [index, index + num] + ) + + # Loop: decrement counter and store at current position + # Stack after subtraction: [index, current] + # where current goes from index+num-1 down to index + loop = ( + Op.JUMPDEST + + Op.PUSH1(1) # [index, current, 1] + + Op.SWAP1 # [index, 1, current] + + Op.SUB # [index, current - 1] + + Op.SSTORE( # STORAGE[current-1] = initial_value + Op.DUP2, + init_val, + key_warm=False, + # gas accounting + original_value=0, + current_value=0, + new_value=init_val, + ) + # After SSTORE: [index, current - 1] + # Continue while current - 1 > index + + Op.JUMPI(len(prefix), Op.GT(Op.DUP2, Op.DUP2)) + ) + + return IteratingBytecode(setup=prefix, iterating=loop) + + +def initializer_calldata_generator( + iteration_count: int, start_iteration: int +) -> bytes: + """Generate calldata for the storage initializer.""" + return Hash(start_iteration) + Hash(iteration_count) + + +def create_sequential_sstore_initializer() -> IteratingBytecode: + """ + Create a contract that initializes storage with slot-dependent values. + + - CALLDATA[0..32] start slot (index) + - CALLDATA[32..64] slot count (num) + - CALLDATA[64..96] value offset + + storage[i] = i + offset for i in [index, index + num). + """ + # Setup: [offset, index, index + num] + prefix = ( + Op.CALLDATALOAD(64) # [offset] + + Op.CALLDATALOAD(0) # [index, offset] + + Op.DUP1 # [index, index, offset] + + Op.CALLDATALOAD(32) # [num, index, index, offset] + + Op.ADD # [num + index, index, offset] + ) + + # Loop: decrement current and store slot-dependent value + # Stack: [current, index, offset] + # current goes from index+num down; stores at current-1 + loop = ( + Op.JUMPDEST + + Op.PUSH1(1) # [1, current, index, offset] + + Op.SWAP1 # [current, 1, index, offset] + + Op.SUB # [current-1, index, offset] + + Op.DUP1 # [current-1, current-1, index, offset] + + Op.DUP1 # [current-1, current-1, current-1, index, offset] + + Op.DUP5 # [offset, current-1, current-1, current-1, index, offset] + + Op.ADD # [current-1 + offset, current-1, current-1, index, offset] + + Op.SWAP1 # [current-1, current-1 + offset, current-1, index, offset] + + Op.SSTORE( # SSTORE(current-1, current-1 + offset) + key_warm=False, + original_value=0, + current_value=0, + new_value=1, + ) + # Stack: [current-1, index, offset] + # Continue while current-1 > index + + Op.JUMPI(len(prefix), Op.GT(Op.DUP2, Op.DUP2)) + ) + + return IteratingBytecode(setup=prefix, iterating=loop) + + +def sequential_initializer_calldata_generator( + iteration_count: int, + start_iteration: int, + *, + offset: int = 0, +) -> bytes: + """Generate calldata for the sequential storage initializer.""" + return Hash(start_iteration) + Hash(iteration_count) + Hash(offset) + + +@dataclass(frozen=True) +class StorageInitRange: + """One contiguous range of storage to initialize.""" + + start_slot: int + num_slots: int + offset: int + + +def build_sequential_storage_init( + *, + pre: Alloc, + fork: Fork, + tx_gas_limit: int, + authority: EOA, + storage_init_ranges: list[StorageInitRange], +) -> list[Block]: + """ + Build blocks that initialize storage with slot-dependent values. + + Deploy a sequential-SSTORE initializer, delegate *authority* to it, + and emit transactions that write + ``storage[i] = i + range.offset`` for every range. The authority's + nonce is incremented in-place. + """ + initializer_code = create_sequential_sstore_initializer() + initializer_addr = pre.deploy_contract(code=initializer_code) + + delegation_sender = pre.fund_eoa() + auth_tx = Transaction( + to=delegation_sender, + gas_limit=tx_gas_limit, + sender=delegation_sender, + authorization_list=[ + AuthorizationTuple( + address=initializer_addr, + nonce=authority.nonce, + signer=authority, + ), + ], + ) + authority.nonce = Number(authority.nonce + 1) + + init_txs: list[Transaction] = [] + for r in storage_init_ranges: + if r.num_slots == 0: + continue + calldata_gen = partial( + sequential_initializer_calldata_generator, + offset=r.offset, + ) + # transactions_by_total_iteration_count splits the range across + # transactions capped by the fork gas limit; no manual chunking needed. + init_txs.extend( + initializer_code.transactions_by_total_iteration_count( + fork=fork, + total_iterations=r.num_slots, + sender=pre.fund_eoa(), + to=authority, + start_iteration=r.start_slot, + calldata=calldata_gen, + recipient_type=RecipientType.DELEGATION_7702, + ) + ) + + blocks: list[Block] = [Block(txs=[auth_tx])] + blocks.extend(pack_transactions_into_blocks(init_txs, tx_gas_limit)) + return blocks + + +def access_list_generator( + iteration_count: int, + start_iteration: int, + access_warm: bool, + authority: Address, +) -> list[AccessList] | None: + """Access list generator for warming storage slots.""" + if access_warm: + storage_keys = [ + Hash(i) + for i in range(start_iteration, start_iteration + iteration_count) + ] + return [AccessList(address=authority, storage_keys=storage_keys)] + return None + + +def executor_calldata_generator( + iteration_count: int, + start_iteration: int, + write_value: int | None = None, +) -> bytes: + """ + Calldata generator for executor operations. + + Generates: Hash(start) + Hash(start + count) [+ Hash(write_value)] + """ + result = Hash(start_iteration) + Hash(start_iteration + iteration_count) + if write_value is not None: + result += Hash(write_value) + return result diff --git a/tests/benchmark/helper/transactions.py b/tests/benchmark/helper/transactions.py new file mode 100644 index 00000000000..70a623fdaa5 --- /dev/null +++ b/tests/benchmark/helper/transactions.py @@ -0,0 +1,140 @@ +"""Transaction and block packing helpers for benchmark gas budgets.""" + +from collections.abc import Callable, Sequence + +from execution_testing import ( + AccessList, + Address, + Alloc, + Block, + Fork, + Hash, + Transaction, +) + +from .enums import CacheStrategy + + +def build_benchmark_txs( + *, + pre: Alloc, + fork: Fork, + gas_benchmark_value: int, + tx_gas_limit: int, + attack_contract_address: Address, + setup_cost: int, + iteration_cost: int, + calldata_builder: Callable[[int, int], bytes] | None = None, + access_list: list[AccessList] | None = None, +) -> tuple[list[Transaction], int]: + """ + Build benchmark transactions filling gas_benchmark_value. + + Partition the total gas budget into transactions, each + containing as many loop iterations as the per-tx gas limit + allows. Return (txs, total_gas_consumed). + + The default calldata layout is ``Hash(num_iters) + + Hash(counter_offset)``. Pass *calldata_builder* to override. + """ + intrinsic_cost_calc = fork.transaction_intrinsic_cost_calculator() + max_intrinsic = intrinsic_cost_calc( + access_list=access_list or [], + calldata=b"\xff" * 64, + ) + + gas_remaining = gas_benchmark_value + txs: list[Transaction] = [] + counter_offset = 0 + total_gas_consumed = 0 + + while gas_remaining > (max_intrinsic + setup_cost + iteration_cost): + gas_available = min(gas_remaining, tx_gas_limit) + + if gas_available < max_intrinsic + setup_cost: + break + + num_iters = ( + gas_available - max_intrinsic - setup_cost + ) // iteration_cost + + if num_iters == 0: + break + + if calldata_builder is not None: + calldata = calldata_builder(num_iters, counter_offset) + else: + calldata = bytes(Hash(num_iters) + Hash(counter_offset)) + actual_intrinsic = intrinsic_cost_calc( + access_list=access_list or [], + calldata=calldata, + return_cost_deducted_prior_execution=True, + ) + tx_gas = actual_intrinsic + setup_cost + num_iters * iteration_cost + + txs.append( + Transaction( + gas_limit=tx_gas, + data=calldata, + to=attack_contract_address, + sender=pre.fund_eoa(), + access_list=access_list or [], + ) + ) + + total_gas_consumed += tx_gas + gas_remaining -= gas_available + counter_offset += num_iters + + assert txs, "Gas loop produced zero transactions" + return txs, total_gas_consumed + + +def build_cache_strategy_blocks( + cache_strategy: CacheStrategy, + txs: Sequence[Transaction], + cache_txs: Sequence[Transaction], +) -> list[Block]: + """ + Assemble benchmark blocks based on cache strategy. + + For CACHE_PREVIOUS_BLOCK, prepend a warmup block before the + execution block so that client caches are hot but EVM state is + cold. Otherwise return a single execution block. + """ + if cache_strategy != CacheStrategy.CACHE_PREVIOUS_BLOCK: + return [Block(txs=txs)] + return [Block(txs=cache_txs), Block(txs=txs)] + + +def pack_transactions_into_blocks( + transactions: list[Transaction], + gas_limit: int, +) -> list[Block]: + """ + Pack transactions into blocks without exceeding gas_limit per block. + + Greedily add transactions to the current block until adding another + would exceed the gas limit, then start a new block. + """ + if not transactions: + return [] + + blocks: list[Block] = [] + current_txs: list[Transaction] = [] + current_gas = 0 + + for tx in transactions: + tx_gas_limit = tx.gas_limit + if current_gas + tx_gas_limit > gas_limit and current_txs: + blocks.append(Block(txs=current_txs)) + current_txs = [] + current_gas = 0 + + current_txs.append(tx) + current_gas += tx_gas_limit + + if current_txs: + blocks.append(Block(txs=current_txs)) + + return blocks diff --git a/tests/benchmark/stateful/bloatnet/test_account_query.py b/tests/benchmark/stateful/bloatnet/test_account_query.py index 099241797ed..fd44cabce5d 100644 --- a/tests/benchmark/stateful/bloatnet/test_account_query.py +++ b/tests/benchmark/stateful/bloatnet/test_account_query.py @@ -28,9 +28,9 @@ AccountCreator, AccountMode, ) -from tests.benchmark.stateful.helpers import ( - DECREMENT_COUNTER_CONDITION, - CacheStrategy, +from tests.benchmark.helper.enums import CacheStrategy +from tests.benchmark.helper.loops import DECREMENT_COUNTER_CONDITION +from tests.benchmark.helper.transactions import ( build_benchmark_txs, build_cache_strategy_blocks, ) diff --git a/tests/benchmark/stateful/bloatnet/test_call.py b/tests/benchmark/stateful/bloatnet/test_call.py index 225ba567d4d..32062de5319 100644 --- a/tests/benchmark/stateful/bloatnet/test_call.py +++ b/tests/benchmark/stateful/bloatnet/test_call.py @@ -18,10 +18,8 @@ keccak256, ) -from tests.benchmark.stateful.helpers import ( - DECREMENT_COUNTER_CONDITION, - build_benchmark_txs, -) +from tests.benchmark.helper.loops import DECREMENT_COUNTER_CONDITION +from tests.benchmark.helper.transactions import build_benchmark_txs @pytest.mark.stub_parametrize("factory_stub", "bloatnet_factory_") diff --git a/tests/benchmark/stateful/bloatnet/test_create.py b/tests/benchmark/stateful/bloatnet/test_create.py index bb21c99aea6..442aee3f085 100644 --- a/tests/benchmark/stateful/bloatnet/test_create.py +++ b/tests/benchmark/stateful/bloatnet/test_create.py @@ -18,9 +18,7 @@ compute_create2_address, ) -from tests.benchmark.stateful.helpers import ( - DECREMENT_COUNTER_CONDITION, -) +from tests.benchmark.helper.loops import DECREMENT_COUNTER_CONDITION @pytest.mark.parametrize( diff --git a/tests/benchmark/stateful/bloatnet/test_erc20.py b/tests/benchmark/stateful/bloatnet/test_erc20.py index 6d2ccb97263..5f1207fab93 100644 --- a/tests/benchmark/stateful/bloatnet/test_erc20.py +++ b/tests/benchmark/stateful/bloatnet/test_erc20.py @@ -14,10 +14,9 @@ While, ) -from tests.benchmark.stateful.helpers import ( - APPROVE_SELECTOR, - BALANCEOF_SELECTOR, -) +# ERC20 function selectors +BALANCEOF_SELECTOR = 0x70A08231 # balanceOf(address) +APPROVE_SELECTOR = 0x095EA7B3 # approve(address,uint256) # SLOAD BENCHMARK ARCHITECTURE: # diff --git a/tests/benchmark/stateful/bloatnet/test_sload.py b/tests/benchmark/stateful/bloatnet/test_sload.py index 63004fd85ac..4fa2dd8bcbb 100644 --- a/tests/benchmark/stateful/bloatnet/test_sload.py +++ b/tests/benchmark/stateful/bloatnet/test_sload.py @@ -27,16 +27,18 @@ While, ) -from tests.benchmark.stateful.helpers import ( +from tests.benchmark.helper.delegation import ( + build_delegated_storage_setup, + delegate_with_calldata, + run_bloated_eoa_benchmark, +) +from tests.benchmark.helper.enums import CacheStrategy +from tests.benchmark.helper.storage import ( START_SLOT, - CacheStrategy, access_list_generator, - build_delegated_storage_setup, create_sstore_initializer, - delegate_with_calldata, executor_calldata_generator, initializer_calldata_generator, - run_bloated_eoa_benchmark, ) diff --git a/tests/benchmark/stateful/bloatnet/test_sstore.py b/tests/benchmark/stateful/bloatnet/test_sstore.py index 7e4572a5177..5e612d5c118 100644 --- a/tests/benchmark/stateful/bloatnet/test_sstore.py +++ b/tests/benchmark/stateful/bloatnet/test_sstore.py @@ -19,15 +19,17 @@ Transaction, ) -from tests.benchmark.stateful.helpers import ( +from tests.benchmark.helper.delegation import ( + build_delegated_storage_setup, + run_bloated_eoa_benchmark, +) +from tests.benchmark.helper.enums import CacheStrategy +from tests.benchmark.helper.storage import ( START_SLOT, - CacheStrategy, access_list_generator, - build_delegated_storage_setup, create_sstore_initializer, executor_calldata_generator, initializer_calldata_generator, - run_bloated_eoa_benchmark, ) diff --git a/tests/benchmark/stateful/bloatnet/test_transient_storage.py b/tests/benchmark/stateful/bloatnet/test_transient_storage.py index c1d19d8046e..98545d82d50 100644 --- a/tests/benchmark/stateful/bloatnet/test_transient_storage.py +++ b/tests/benchmark/stateful/bloatnet/test_transient_storage.py @@ -12,10 +12,8 @@ While, ) -from tests.benchmark.stateful.helpers import ( - DECREMENT_COUNTER_CONDITION, - build_benchmark_txs, -) +from tests.benchmark.helper.loops import DECREMENT_COUNTER_CONDITION +from tests.benchmark.helper.transactions import build_benchmark_txs @pytest.mark.parametrize("with_tload", [True, False]) diff --git a/tests/benchmark/stateful/eip7928_block_level_access_lists/helpers.py b/tests/benchmark/stateful/eip7928_block_level_access_lists/helpers.py index 5efcb83a71b..53bc6c46d85 100644 --- a/tests/benchmark/stateful/eip7928_block_level_access_lists/helpers.py +++ b/tests/benchmark/stateful/eip7928_block_level_access_lists/helpers.py @@ -31,7 +31,7 @@ ) from execution_testing.base_types.base_types import Number -from tests.benchmark.stateful.helpers import ( +from tests.benchmark.helper.storage import ( StorageInitRange, build_sequential_storage_init, ) diff --git a/tests/benchmark/stateful/helpers.py b/tests/benchmark/stateful/helpers.py deleted file mode 100644 index cd313544881..00000000000 --- a/tests/benchmark/stateful/helpers.py +++ /dev/null @@ -1,599 +0,0 @@ -"""Shared constants and helpers for stateful benchmark tests.""" - -from collections.abc import Callable, Sequence -from dataclasses import dataclass -from enum import Enum -from functools import partial - -from execution_testing import ( - EOA, - AccessList, - Address, - Alloc, - AuthorizationTuple, - BenchmarkTestFiller, - Block, - Bytecode, - Fork, - Hash, - IteratingBytecode, - Op, - RecipientType, - TestPhaseManager, - Transaction, -) -from execution_testing.base_types.base_types import Number - -# ERC20 function selectors -BALANCEOF_SELECTOR = 0x70A08231 # balanceOf(address) -APPROVE_SELECTOR = 0x095EA7B3 # approve(address,uint256) -ALLOWANCE_SELECTOR = 0xDD62ED3E # allowance(address,address) -MINT_SELECTOR = 0x40C10F19 # mint(address,uint256) - - -# Standard While-loop decrement-and-test condition. -# -# Expects the iteration counter on top of the stack: -# [counter] → SUB(counter, 1) → continue if nonzero -DECREMENT_COUNTER_CONDITION = ( - Op.PUSH1(1) + Op.SWAP1 + Op.SUB + Op.DUP1 + Op.ISZERO + Op.ISZERO -) - - -# keccak256("random") for non-existing slots, masked as address, -# Solidity does input checks on the size and throws if we input -# something different than an address -START_SLOT = ( - 0xA4896A3F93BF4BF58378E579F3CF193BB4AF1022AF7D2089F37D8BAE7157B85F - % (2**160) -) - - -class CacheStrategy(str, Enum): - """Defines cache assumptions for benchmarked state access.""" - - # No caching strategy: target state is cold in EVM and cache - NO_CACHE = "no_cache" - # Caching at tx level: target state is warm in EVM and cache - CACHE_TX = "cache_tx" - # Caching at previous block: - # Target state is cold in EVM but (assumed) to be cached - CACHE_PREVIOUS_BLOCK = "cache_previous_block" - - -def build_benchmark_txs( - *, - pre: Alloc, - fork: Fork, - gas_benchmark_value: int, - tx_gas_limit: int, - attack_contract_address: Address, - setup_cost: int, - iteration_cost: int, - calldata_builder: Callable[[int, int], bytes] | None = None, - access_list: list[AccessList] | None = None, -) -> tuple[list[Transaction], int]: - """ - Build benchmark transactions filling gas_benchmark_value. - - Partition the total gas budget into transactions, each - containing as many loop iterations as the per-tx gas limit - allows. Return (txs, total_gas_consumed). - - The default calldata layout is ``Hash(num_iters) + - Hash(counter_offset)``. Pass *calldata_builder* to override. - """ - intrinsic_cost_calc = fork.transaction_intrinsic_cost_calculator() - max_intrinsic = intrinsic_cost_calc( - access_list=access_list or [], - calldata=b"\xff" * 64, - ) - - gas_remaining = gas_benchmark_value - txs: list[Transaction] = [] - counter_offset = 0 - total_gas_consumed = 0 - - while gas_remaining > (max_intrinsic + setup_cost + iteration_cost): - gas_available = min(gas_remaining, tx_gas_limit) - - if gas_available < max_intrinsic + setup_cost: - break - - num_iters = ( - gas_available - max_intrinsic - setup_cost - ) // iteration_cost - - if num_iters == 0: - break - - if calldata_builder is not None: - calldata = calldata_builder(num_iters, counter_offset) - else: - calldata = bytes(Hash(num_iters) + Hash(counter_offset)) - actual_intrinsic = intrinsic_cost_calc( - access_list=access_list or [], - calldata=calldata, - return_cost_deducted_prior_execution=True, - ) - tx_gas = actual_intrinsic + setup_cost + num_iters * iteration_cost - - txs.append( - Transaction( - gas_limit=tx_gas, - data=calldata, - to=attack_contract_address, - sender=pre.fund_eoa(), - access_list=access_list or [], - ) - ) - - total_gas_consumed += tx_gas - gas_remaining -= gas_available - counter_offset += num_iters - - assert txs, "Gas loop produced zero transactions" - return txs, total_gas_consumed - - -def build_cache_strategy_blocks( - cache_strategy: CacheStrategy, - txs: Sequence[Transaction], - cache_txs: Sequence[Transaction], -) -> list[Block]: - """ - Assemble benchmark blocks based on cache strategy. - - For CACHE_PREVIOUS_BLOCK, prepend a warmup block before the - execution block so that client caches are hot but EVM state is - cold. Otherwise return a single execution block. - """ - if cache_strategy != CacheStrategy.CACHE_PREVIOUS_BLOCK: - return [Block(txs=txs)] - return [Block(txs=cache_txs), Block(txs=txs)] - - -def pack_transactions_into_blocks( - transactions: list[Transaction], - gas_limit: int, -) -> list[Block]: - """ - Pack transactions into blocks without exceeding gas_limit per block. - - Greedily add transactions to the current block until adding another - would exceed the gas limit, then start a new block. - """ - if not transactions: - return [] - - blocks: list[Block] = [] - current_txs: list[Transaction] = [] - current_gas = 0 - - for tx in transactions: - tx_gas_limit = tx.gas_limit - if current_gas + tx_gas_limit > gas_limit and current_txs: - blocks.append(Block(txs=current_txs)) - current_txs = [] - current_gas = 0 - - current_txs.append(tx) - current_gas += tx_gas_limit - - if current_txs: - blocks.append(Block(txs=current_txs)) - - return blocks - - -def build_delegated_storage_setup( - *, - pre: Alloc, - fork: Fork, - tx_gas_limit: int, - needs_init: bool, - num_target_slots: int, - initializer_code: IteratingBytecode, - initializer_addr: Address, - executor_addr: Address, - authority: EOA, - authority_nonce: int, - delegation_sender: EOA, - initializer_calldata_generator: Callable[[int, int], bytes], -) -> list[Block]: - """ - Build setup blocks for delegated storage benchmarks. - - Use EIP-7702 authorization to delegate an authority EOA first to - a storage-initializer contract (if *needs_init*), then to the - benchmark executor contract. Return the list of setup blocks. - """ - blocks: list[Block] = [] - - if needs_init: - # Block 1: Authorize to initializer - blocks.append( - Block( - txs=[ - Transaction( - to=delegation_sender, - gas_limit=tx_gas_limit, - sender=delegation_sender, - authorization_list=[ - AuthorizationTuple( - address=initializer_addr, - nonce=authority_nonce, - signer=authority, - ), - ], - ) - ] - ) - ) - authority_nonce += 1 - - # transactions_by_total_iteration_count splits the slots across - # transactions capped by the fork gas limit, so no manual chunking - # is required. - init_txs: list[Transaction] = list( - initializer_code.transactions_by_total_iteration_count( - fork=fork, - total_iterations=num_target_slots, - sender=pre.fund_eoa(), - to=authority, - start_iteration=1, - calldata=initializer_calldata_generator, - recipient_type=RecipientType.DELEGATION_7702, - ) - ) - - # Pack init transactions into blocks - blocks.extend(pack_transactions_into_blocks(init_txs, tx_gas_limit)) - - # Final block: Authorize to executor - blocks.append( - Block( - txs=[ - Transaction( - to=delegation_sender, - gas_limit=tx_gas_limit, - sender=delegation_sender, - authorization_list=[ - AuthorizationTuple( - address=executor_addr, - nonce=authority_nonce, - signer=authority, - ), - ], - ) - ] - ) - ) - - return blocks - - -def create_sstore_initializer(init_val: int) -> IteratingBytecode: - """ - Create a contract that initializes storage slots from calldata. - - - CALLDATA[0..32] start slot (index) - - CALLDATA[32..64] slot count (num) - - storage[i] = init_val for i in [index, index + num). - """ - # Setup: [index, index + num] - prefix = ( - Op.CALLDATALOAD(0) # [index] - + Op.DUP1 # [index, index] - + Op.CALLDATALOAD(32) # [index, index, num] - + Op.ADD # [index, index + num] - ) - - # Loop: decrement counter and store at current position - # Stack after subtraction: [index, current] - # where current goes from index+num-1 down to index - loop = ( - Op.JUMPDEST - + Op.PUSH1(1) # [index, current, 1] - + Op.SWAP1 # [index, 1, current] - + Op.SUB # [index, current - 1] - + Op.SSTORE( # STORAGE[current-1] = initial_value - Op.DUP2, - init_val, - key_warm=False, - # gas accounting - original_value=0, - current_value=0, - new_value=init_val, - ) - # After SSTORE: [index, current - 1] - # Continue while current - 1 > index - + Op.JUMPI(len(prefix), Op.GT(Op.DUP2, Op.DUP2)) - ) - - return IteratingBytecode(setup=prefix, iterating=loop) - - -def initializer_calldata_generator( - iteration_count: int, start_iteration: int -) -> bytes: - """Generate calldata for the storage initializer.""" - return Hash(start_iteration) + Hash(iteration_count) - - -def create_sequential_sstore_initializer() -> IteratingBytecode: - """ - Create a contract that initializes storage with slot-dependent values. - - - CALLDATA[0..32] start slot (index) - - CALLDATA[32..64] slot count (num) - - CALLDATA[64..96] value offset - - storage[i] = i + offset for i in [index, index + num). - """ - # Setup: [offset, index, index + num] - prefix = ( - Op.CALLDATALOAD(64) # [offset] - + Op.CALLDATALOAD(0) # [index, offset] - + Op.DUP1 # [index, index, offset] - + Op.CALLDATALOAD(32) # [num, index, index, offset] - + Op.ADD # [num + index, index, offset] - ) - - # Loop: decrement current and store slot-dependent value - # Stack: [current, index, offset] - # current goes from index+num down; stores at current-1 - loop = ( - Op.JUMPDEST - + Op.PUSH1(1) # [1, current, index, offset] - + Op.SWAP1 # [current, 1, index, offset] - + Op.SUB # [current-1, index, offset] - + Op.DUP1 # [current-1, current-1, index, offset] - + Op.DUP1 # [current-1, current-1, current-1, index, offset] - + Op.DUP5 # [offset, current-1, current-1, current-1, index, offset] - + Op.ADD # [current-1 + offset, current-1, current-1, index, offset] - + Op.SWAP1 # [current-1, current-1 + offset, current-1, index, offset] - + Op.SSTORE( # SSTORE(current-1, current-1 + offset) - key_warm=False, - original_value=0, - current_value=0, - new_value=1, - ) - # Stack: [current-1, index, offset] - # Continue while current-1 > index - + Op.JUMPI(len(prefix), Op.GT(Op.DUP2, Op.DUP2)) - ) - - return IteratingBytecode(setup=prefix, iterating=loop) - - -def sequential_initializer_calldata_generator( - iteration_count: int, - start_iteration: int, - *, - offset: int = 0, -) -> bytes: - """Generate calldata for the sequential storage initializer.""" - return Hash(start_iteration) + Hash(iteration_count) + Hash(offset) - - -@dataclass(frozen=True) -class StorageInitRange: - """One contiguous range of storage to initialize.""" - - start_slot: int - num_slots: int - offset: int - - -def build_sequential_storage_init( - *, - pre: Alloc, - fork: Fork, - tx_gas_limit: int, - authority: EOA, - storage_init_ranges: list[StorageInitRange], -) -> list[Block]: - """ - Build blocks that initialize storage with slot-dependent values. - - Deploy a sequential-SSTORE initializer, delegate *authority* to it, - and emit transactions that write - ``storage[i] = i + range.offset`` for every range. The authority's - nonce is incremented in-place. - """ - initializer_code = create_sequential_sstore_initializer() - initializer_addr = pre.deploy_contract(code=initializer_code) - - delegation_sender = pre.fund_eoa() - auth_tx = Transaction( - to=delegation_sender, - gas_limit=tx_gas_limit, - sender=delegation_sender, - authorization_list=[ - AuthorizationTuple( - address=initializer_addr, - nonce=authority.nonce, - signer=authority, - ), - ], - ) - authority.nonce = Number(authority.nonce + 1) - - init_txs: list[Transaction] = [] - for r in storage_init_ranges: - if r.num_slots == 0: - continue - calldata_gen = partial( - sequential_initializer_calldata_generator, - offset=r.offset, - ) - # transactions_by_total_iteration_count splits the range across - # transactions capped by the fork gas limit; no manual chunking needed. - init_txs.extend( - initializer_code.transactions_by_total_iteration_count( - fork=fork, - total_iterations=r.num_slots, - sender=pre.fund_eoa(), - to=authority, - start_iteration=r.start_slot, - calldata=calldata_gen, - recipient_type=RecipientType.DELEGATION_7702, - ) - ) - - blocks: list[Block] = [Block(txs=[auth_tx])] - blocks.extend(pack_transactions_into_blocks(init_txs, tx_gas_limit)) - return blocks - - -def delegate_with_calldata( - pre: Alloc, - fork: Fork, - authority: EOA, - address: Address, - calldata: Hash, -) -> Transaction: - """ - Create a tx that delegates the authority and calls it with calldata. - - The delegated code determines what happens with the calldata. - The authority nonce is incremented in-place. - """ - intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( - calldata=bytes(calldata), - authorization_list_or_count=1, - ) - gas_limit = intrinsic_gas + 500_000 - tx = Transaction( - gas_limit=gas_limit, - to=authority, - value=0, - data=calldata, - sender=pre.fund_eoa(), - authorization_list=[ - AuthorizationTuple( - chain_id=0, - address=address, - nonce=authority.nonce, - signer=authority, - ), - ], - ) - authority.nonce = Number(authority.nonce + 1) - return tx - - -def run_bloated_eoa_benchmark( - *, - benchmark_test: BenchmarkTestFiller, - pre: Alloc, - fork: Fork, - gas_benchmark_value: int, - tx_gas_limit: int, - authority: EOA, - existing_slots: bool, - runtime_code: Bytecode, - cache_strategy: CacheStrategy, - tx_generator: Callable[[EOA], list[Transaction]] | None = None, -) -> None: - """ - Run a bloated-EOA benchmark with the given runtime delegation code. - """ - slot_0_value = Hash(1) if existing_slots else Hash(START_SLOT) - - setter_address = pre.deploy_contract(code=Op.SSTORE(0, Op.CALLDATALOAD(0))) - runtime_address = pre.deploy_contract(code=runtime_code) - - init_tx = delegate_with_calldata( - pre, - fork, - authority, - setter_address, - slot_0_value, - ) - runtime_tx = delegate_with_calldata( - pre, - fork, - authority, - runtime_address, - Hash(0), - ) - - blocks: list[Block] = [Block(txs=[init_tx, runtime_tx])] - - sender = pre.fund_eoa() - - txs: list[Transaction] = [] - with TestPhaseManager.execution(): - if tx_generator is not None: - txs = tx_generator(sender) - else: - gas_available = gas_benchmark_value - intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() - while gas_available >= intrinsic_gas: - tx_gas = min(gas_available, tx_gas_limit) - txs.append( - Transaction( - gas_limit=tx_gas, - to=authority, - sender=sender, - ) - ) - gas_available -= tx_gas - - cache_txs: list[Transaction] = [] - if cache_strategy == CacheStrategy.CACHE_PREVIOUS_BLOCK: - with TestPhaseManager.setup(): - cache_sender = pre.fund_eoa() - for tx in txs: - cache_txs.append( - Transaction( - gas_limit=tx.gas_limit, - data=tx.data, - to=authority, - sender=cache_sender, - ) - ) - - blocks += build_cache_strategy_blocks(cache_strategy, txs, cache_txs) - - benchmark_test( - pre=pre, - blocks=blocks, - skip_gas_used_validation=True, - expected_receipt_status=True, - ) - - -def access_list_generator( - iteration_count: int, - start_iteration: int, - access_warm: bool, - authority: Address, -) -> list[AccessList] | None: - """Access list generator for warming storage slots.""" - if access_warm: - storage_keys = [ - Hash(i) - for i in range(start_iteration, start_iteration + iteration_count) - ] - return [AccessList(address=authority, storage_keys=storage_keys)] - return None - - -def executor_calldata_generator( - iteration_count: int, - start_iteration: int, - write_value: int | None = None, -) -> bytes: - """ - Calldata generator for executor operations. - - Generates: Hash(start) + Hash(start + count) [+ Hash(write_value)] - """ - result = Hash(start_iteration) + Hash(start_iteration + iteration_count) - if write_value is not None: - result += Hash(write_value) - return result From 213f0e8c05fd31ac3d00d4be6d77a71627d0f0c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=94=A1=E4=BD=B3=E8=AA=A0=20Louis=20Tsai?= <72684086+LouisTsai-Csie@users.noreply.github.com> Date: Fri, 17 Jul 2026 22:17:54 +0800 Subject: [PATCH 141/233] feat(test-benchmark): validate post-state for stateful filler (#3148) * feat: add post-state verification to stateful filler * fix(test-plugins): Fix types, simplify --------- Co-authored-by: Mario Vega <marioevz@gmail.com> --- .../pytest_commands/plugins/execute/execute.py | 2 +- .../pytest_commands/plugins/execute/pre_alloc.py | 4 +--- .../plugins/shared/execute_fill.py | 8 ++------ .../client_clis/client_backend.py | 14 +++++++++++++- .../execution/transaction_post.py | 3 ++- packages/testing/src/execution_testing/rpc/rpc.py | 2 +- .../src/execution_testing/specs/blockchain.py | 7 ++++++- .../execution_testing/test_types/account_types.py | 15 ++++++++++----- 8 files changed, 36 insertions(+), 19 deletions(-) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/execute.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/execute.py index 0a69a8c7799..a10cb1ba5c1 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/execute.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/execute.py @@ -10,13 +10,13 @@ from pytest_metadata.plugin import metadata_key from execution_testing.base_types import Account -from execution_testing.base_types import Alloc as BaseAlloc from execution_testing.base_types.base_types import HexNumber from execution_testing.execution import BaseExecute from execution_testing.forks import Fork, TransitionFork from execution_testing.logging import get_logger from execution_testing.rpc import EngineRPC, EthRPC from execution_testing.specs import BaseTest +from execution_testing.test_types import Alloc as BaseAlloc from execution_testing.test_types import ( Environment, EnvironmentDefaults, diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py index 9658852cda8..5f248d6811f 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py @@ -20,9 +20,6 @@ Storage, StorageRootType, ) -from execution_testing.base_types import ( - Alloc as BaseAlloc, -) from execution_testing.base_types.conversions import ( BytesConvertible, NumberConvertible, @@ -42,6 +39,7 @@ TransactionTestMetadata, compute_deterministic_create2_address, ) +from execution_testing.test_types import Alloc as BaseAlloc from execution_testing.tools import Initcode from execution_testing.vm import Bytecode, Op diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/execute_fill.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/execute_fill.py index d827d96927a..db4367f6744 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/execute_fill.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/execute_fill.py @@ -9,11 +9,7 @@ from pytest import StashKey from execution_testing.base_types import Account, Number -from execution_testing.base_types import Alloc as BaseAlloc -from execution_testing.execution import ( - BaseExecute, - LabeledExecuteFormat, -) +from execution_testing.execution import BaseExecute, LabeledExecuteFormat from execution_testing.fixtures import BaseFixture, LabeledFixtureFormat from execution_testing.logging import get_logger from execution_testing.rpc import EthRPC @@ -65,7 +61,7 @@ def _validate_and_cache_address_stubs( eth_rpc = EthRPC(rpc_endpoint) labels = list(address_stubs.root.keys()) addresses = [address_stubs.root[k].addr for k in labels] - query = BaseAlloc(root={addr: Account() for addr in addresses}) + query = Alloc(root={addr: Account() for addr in addresses}) alloc = eth_rpc.get_alloc(query) empty: list[str] = [] accounts: Dict[str, Account] = {} diff --git a/packages/testing/src/execution_testing/client_clis/client_backend.py b/packages/testing/src/execution_testing/client_clis/client_backend.py index 73252b132fc..d3d2607d0ff 100644 --- a/packages/testing/src/execution_testing/client_clis/client_backend.py +++ b/packages/testing/src/execution_testing/client_clis/client_backend.py @@ -20,6 +20,7 @@ from execution_testing.forks import Fork, TransitionFork from execution_testing.logging import get_logger from execution_testing.rpc import ( + BlockNumberType, DebugRPC, EngineRPC, EthRPC, @@ -33,7 +34,12 @@ PayloadAttributes, PayloadStatusEnum, ) -from execution_testing.test_types import Requests, Transaction, Withdrawal +from execution_testing.test_types import ( + Alloc, + Requests, + Transaction, + Withdrawal, +) from execution_testing.test_types.block_access_list import BlockAccessList from execution_testing.test_types.receipt_types import TransactionReceipt @@ -306,6 +312,12 @@ def evaluate( ), ) + def get_post_state_alloc( + self, expected: Alloc, *, block_number: BlockNumberType = "latest" + ) -> Alloc: + """Fetch the post-state that ``expected`` constrains from client.""" + return self.eth_rpc.get_alloc(expected, block_number=block_number) + def extract_block_opcode_count( self, block_hash: Hash ) -> OpcodeCount | None: diff --git a/packages/testing/src/execution_testing/execution/transaction_post.py b/packages/testing/src/execution_testing/execution/transaction_post.py index fc5efce3c99..d76768deb94 100644 --- a/packages/testing/src/execution_testing/execution/transaction_post.py +++ b/packages/testing/src/execution_testing/execution/transaction_post.py @@ -5,7 +5,7 @@ import pytest from pytest import FixtureRequest -from execution_testing.base_types import Address, Alloc, Hash +from execution_testing.base_types import Address, Hash from execution_testing.forks import Fork from execution_testing.logging import get_logger from execution_testing.rpc import ( @@ -14,6 +14,7 @@ SendTransactionExceptionError, ) from execution_testing.test_types import ( + Alloc, Environment, NetworkWrappedTransaction, TestPhase, diff --git a/packages/testing/src/execution_testing/rpc/rpc.py b/packages/testing/src/execution_testing/rpc/rpc.py index a8fa1816687..cee13b98e30 100644 --- a/packages/testing/src/execution_testing/rpc/rpc.py +++ b/packages/testing/src/execution_testing/rpc/rpc.py @@ -37,7 +37,6 @@ from execution_testing.base_types import ( Account, Address, - Alloc, Bytes, Hash, to_json, @@ -45,6 +44,7 @@ from execution_testing.logging import ( get_logger, ) +from execution_testing.test_types import Alloc from .rpc_types import ( EthConfigResponse, diff --git a/packages/testing/src/execution_testing/specs/blockchain.py b/packages/testing/src/execution_testing/specs/blockchain.py index 55822c72567..6eea651a26a 100644 --- a/packages/testing/src/execution_testing/specs/blockchain.py +++ b/packages/testing/src/execution_testing/specs/blockchain.py @@ -1384,7 +1384,8 @@ def make_stateful_fixture( - Payloads are partitioned by ``FixtureEngineNewPayload.phase`` into ``setup_payloads`` (setup-phase txs) and ``payloads`` (execution-phase txs). - - ``verify_post_state`` is skipped: the client is the oracle. + - ``post`` is verified against the live client (the oracle) via + ``get_post_state_alloc``; there is no t8n post alloc to diff. """ if not isinstance(t8n, ClientBackend): raise RuntimeError( @@ -1557,6 +1558,10 @@ def make_stateful_fixture( ) head_hash = client_hash + if self.post.root: + got_alloc = t8n.get_post_state_alloc(self.post) + self.post.verify_post_alloc(got_alloc) + fixture = BlockchainEngineStatefulFixture( fork=self.fork, last_block_hash=head_hash, diff --git a/packages/testing/src/execution_testing/test_types/account_types.py b/packages/testing/src/execution_testing/test_types/account_types.py index 340b61597ba..349a8a715e0 100644 --- a/packages/testing/src/execution_testing/test_types/account_types.py +++ b/packages/testing/src/execution_testing/test_types/account_types.py @@ -324,6 +324,13 @@ def __contains__( address = Address(address) return address in self.root + def get(self, address: Address) -> Account | None: + """Get an account if it's present in the allocation, otherwise None.""" + account = self.root.get(address) + if not account: + return None + return account + def empty_accounts(self) -> List[Address]: """Return list of addresses of empty accounts.""" return [ @@ -372,12 +379,10 @@ def verify_post_alloc(self, got_alloc: "Alloc") -> None: for address, account in self.root.items(): if account is None: # Account must not exist - if ( - address in got_alloc.root - and got_alloc.root[address] is not None - ): + got_account = got_alloc.get(address) + if got_account: raise Alloc.UnexpectedAccountError( - address=address, account=got_alloc.root[address] + address=address, account=got_account ) else: if address in got_alloc.root: From 5fa5938b1ce01c661b3e9beaa403e4edddd11e1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=94=A1=E4=BD=B3=E8=AA=A0=20Louis=20Tsai?= <72684086+LouisTsai-Csie@users.noreply.github.com> Date: Fri, 17 Jul 2026 23:22:53 +0800 Subject: [PATCH 142/233] feat(test-benchmark): support per block opcode count record (#3183) * feat: support per block opcode count record * fix(test-clis): Fix eels opcode count --------- Co-authored-by: Mario Vega <marioevz@gmail.com> --- .../pytest_commands/plugins/filler/filler.py | 5 ++ .../client_clis/cli_types.py | 1 + .../client_clis/client_backend.py | 1 + .../client_clis/clis/execution_specs.py | 9 ++++ .../client_clis/tests/test_execution_specs.py | 5 +- .../client_clis/tests/test_transition_tool.py | 47 +++++++++++++++++++ .../client_clis/transition_tool.py | 4 ++ 7 files changed, 71 insertions(+), 1 deletion(-) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py index 0163d5b2432..37184d04e91 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py @@ -1787,6 +1787,11 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: fill_metadata["opcode_count"] = ( t8n.opcode_count.model_dump() ) + if t8n.opcode_count_per_block: + fill_metadata["opcode_count_per_block"] = [ + block_opcode_count.model_dump() + for block_opcode_count in t8n.opcode_count_per_block + ] if fill_result.metadata: fill_metadata.update(fill_result.metadata) diff --git a/packages/testing/src/execution_testing/client_clis/cli_types.py b/packages/testing/src/execution_testing/client_clis/cli_types.py index 85479f4cae6..769ccfec632 100644 --- a/packages/testing/src/execution_testing/client_clis/cli_types.py +++ b/packages/testing/src/execution_testing/client_clis/cli_types.py @@ -329,6 +329,7 @@ def print(self) -> None: _opcode_synonyms = { "KECCAK256": "SHA3", + "KECCAK": "SHA3", "DIFFICULTY": "PREVRANDAO", } diff --git a/packages/testing/src/execution_testing/client_clis/client_backend.py b/packages/testing/src/execution_testing/client_clis/client_backend.py index d3d2607d0ff..2c2231b9d42 100644 --- a/packages/testing/src/execution_testing/client_clis/client_backend.py +++ b/packages/testing/src/execution_testing/client_clis/client_backend.py @@ -167,6 +167,7 @@ class ClientBackend: # t8n-compatibility stubs — fill's filler reads these on the backend. opcode_count: OpcodeCount | None = None + opcode_count_per_block: List[OpcodeCount] | None = None output_cache: Any = None debug_dump_dir: Path | None = None call_counter: int = 0 diff --git a/packages/testing/src/execution_testing/client_clis/clis/execution_specs.py b/packages/testing/src/execution_testing/client_clis/clis/execution_specs.py index 7df1e8ab2bc..f0be44460e6 100644 --- a/packages/testing/src/execution_testing/client_clis/clis/execution_specs.py +++ b/packages/testing/src/execution_testing/client_clis/clis/execution_specs.py @@ -128,6 +128,9 @@ def _evaluate( # TODO: This should be optimized by the t8n tool instead. t8n_args.append("--input.blobParams=stdin") + if self.supports_opcode_count: + t8n_args.append("--opcode.count=stdout") + if self.trace: t8n_args.extend( [ @@ -148,6 +151,12 @@ def _evaluate( t8n.run() output_dict = json.loads(out_stream.getvalue()) + + if "opcodeCount" in output_dict and "result" in output_dict: + output_dict["result"]["opcodeCount"] = output_dict.pop( + "opcodeCount" + ) + output: TransitionToolOutput = TransitionToolOutput.model_validate( output_dict, context={"exception_mapper": self.exception_mapper} ) diff --git a/packages/testing/src/execution_testing/client_clis/tests/test_execution_specs.py b/packages/testing/src/execution_testing/client_clis/tests/test_execution_specs.py index 4f8964a67f1..a013da0548f 100644 --- a/packages/testing/src/execution_testing/client_clis/tests/test_execution_specs.py +++ b/packages/testing/src/execution_testing/client_clis/tests/test_execution_specs.py @@ -184,6 +184,7 @@ def test_evm_t8n( ), ) assert to_json(t8n_output.alloc.get()) == expected.get("alloc") + t8n_result = to_json(t8n_output.result) if isinstance(default_t8n, ExecutionSpecsTransitionTool): # The expected output was generated with geth, instead of deleting # any info from this expected output, the fields not returned by @@ -198,9 +199,11 @@ def test_evm_t8n( for i, _ in enumerate(expected.get("result")["receipts"]): del expected.get("result")["receipts"][i][key] - t8n_result = to_json(t8n_output.result) for i, _ in enumerate(expected.get("result")["rejected"]): del expected.get("result")["rejected"][i]["error"] del t8n_result["rejected"][i]["error"] + if "opcodeCount" in t8n_result: + t8n_result.pop("opcodeCount") + assert t8n_result == expected.get("result") diff --git a/packages/testing/src/execution_testing/client_clis/tests/test_transition_tool.py b/packages/testing/src/execution_testing/client_clis/tests/test_transition_tool.py index c6f91382478..3c7c1b281df 100644 --- a/packages/testing/src/execution_testing/client_clis/tests/test_transition_tool.py +++ b/packages/testing/src/execution_testing/client_clis/tests/test_transition_tool.py @@ -22,6 +22,7 @@ LazyAllocFile, LazyAllocJson, LazyAllocStr, + OpcodeCount, Result, TransitionToolInput, TransitionToolOutput, @@ -436,3 +437,49 @@ def test_lazy_alloc_file_empty_object_yields_empty_alloc( lazy = LazyAllocFile(raw=alloc_path, _state_root=TEST_ALLOC_STATE_ROOT) assert lazy.get() == Alloc.model_validate({}) + + +def _output_with_opcode_count(counts: dict) -> TransitionToolOutput: + """Build a minimal t8n output carrying the given opcode counts.""" + result = Result.model_validate( + { + "stateRoot": "0x" + "00" * 32, + "txRoot": "0x" + "00" * 32, + "receiptsRoot": "0x" + "00" * 32, + "logsHash": "0x" + "00" * 32, + "logsBloom": "0x" + "00" * 256, + "receipts": [], + "gasUsed": "0x0", + } + ) + result.opcode_count = OpcodeCount.model_validate(counts) + return TransitionToolOutput( + alloc=LazyAllocJson( + raw=TEST_ALLOC.model_dump(), _state_root=TEST_ALLOC_STATE_ROOT + ), + result=result, + ) + + +def test_opcode_count_accumulation() -> None: + """ + `process_result` accumulates the per-test opcode count total and also + records each call's (per-block) count separately. + """ + tool = ExecutionSpecsTransitionTool() + tool.reset_opcode_count() + + tool.process_result(_output_with_opcode_count({"PUSH1": 5, "SSTORE": 2})) + tool.process_result(_output_with_opcode_count({"PUSH1": 3})) + + assert tool.opcode_count == OpcodeCount.model_validate( + {"PUSH1": 8, "SSTORE": 2} + ) + assert tool.opcode_count_per_block == [ + OpcodeCount.model_validate({"PUSH1": 5, "SSTORE": 2}), + OpcodeCount.model_validate({"PUSH1": 3}), + ] + + tool.reset_opcode_count() + assert tool.opcode_count == OpcodeCount({}) + assert tool.opcode_count_per_block == [] diff --git a/packages/testing/src/execution_testing/client_clis/transition_tool.py b/packages/testing/src/execution_testing/client_clis/transition_tool.py index 2e75fcb7fbe..c69e53cf382 100644 --- a/packages/testing/src/execution_testing/client_clis/transition_tool.py +++ b/packages/testing/src/execution_testing/client_clis/transition_tool.py @@ -202,6 +202,7 @@ class TransitionTool(EthereumCLI): debug_dump_dir: Path | None = None call_counter: int = 0 opcode_count: OpcodeCount | None = None + opcode_count_per_block: List[OpcodeCount] | None = None supports_opcode_count: ClassVar[bool] = False supports_xdist: ClassVar[bool] = True @@ -314,6 +315,7 @@ def reset_opcode_count(self) -> None: Reset the opcode count to zero. """ self.opcode_count = OpcodeCount({}) + self.opcode_count_per_block = [] @dataclass class TransitionToolData: @@ -987,6 +989,8 @@ def process_result( and self.opcode_count is not None ): self.opcode_count += result.result.opcode_count + if self.opcode_count_per_block is not None: + self.opcode_count_per_block.append(result.result.opcode_count) return result def evaluate( From 135af0fd4b1ecb389dd1fd0de2b39f78512dedef Mon Sep 17 00:00:00 2001 From: spencer <spencer.taylor-brown@ethereum.org> Date: Wed, 22 Jul 2026 10:03:24 +0100 Subject: [PATCH 143/233] fix(ci): skip new-commit check on manual fixture release dispatches (#3202) --- .github/workflows/release_fixtures.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release_fixtures.yaml b/.github/workflows/release_fixtures.yaml index 63e950cf7f5..d56a9dae918 100644 --- a/.github/workflows/release_fixtures.yaml +++ b/.github/workflows/release_fixtures.yaml @@ -67,7 +67,9 @@ jobs: # A cached release skips the fill: `build` (and with it `combine`) # keys off `run`, and the release job downloads the resolved # nightly's artifact and tags the commit it was built from. - run: ${{ (inputs.cached || inputs.commit != '') && 'false' || steps.check.outputs.run }} + # Manual dispatches skip the new-commit check (its script may not + # exist on the checked-out devnet branch) and default to `true`. + run: ${{ (inputs.cached || inputs.commit != '') && 'false' || steps.check.outputs.run || 'true' }} build_matrix: ${{ steps.matrix.outputs.build_matrix }} feature_name: ${{ steps.matrix.outputs.feature_name }} combine_labels: ${{ steps.matrix.outputs.combine_labels }} @@ -95,6 +97,7 @@ jobs: - name: Check for new commits (scheduled runs) id: check + if: github.event_name == 'schedule' env: GH_TOKEN: ${{ github.token }} run: | From 2282c757b3699d506de112b8a48b6b538df7ed1f Mon Sep 17 00:00:00 2001 From: Guruprasad Kamath <48196632+gurukamath@users.noreply.github.com> Date: Wed, 22 Jul 2026 13:16:12 +0200 Subject: [PATCH 144/233] feat(tests): add more EIP-2780 tests (#3195) * feat(tests): add EIP-2780 creation-tx top-frame NEW_ACCOUNT tests Pin the creation-transaction side of the EIP-2780 top-frame charge layer: - The pre-state-keyed skip: a creation tx whose target leaf already exists (pre-funded in the block pre-state, or by an earlier transaction of the same block) must not pay the top-frame NEW_ACCOUNT. The same-block variant discriminates transaction pre-state from block pre-state and header-pins the block's state dimension. - The Amsterdam transition: the flat pre-fork TX_BASE + TX_CREATE intrinsic decomposes into the CREATE_ACCESS regular intrinsic plus the top-frame NEW_ACCOUNT state charge. - The settlement: init code that SELFDESTRUCTs is a successful halt, so the top-frame NEW_ACCOUNT stays consumed even though the created account is destroyed at tx end (EIP-6780/8246), with a header sibling pinning the surviving charge in the state dimension. * feat(tests): add EIP-2780 floor-shape and BAL delegation-target tests Close two coverage gaps found by probing the EIP-2780 suite: Calldata floor x transaction shapes (test_calldata_floor.py): - parametrize test_calldata_floor over recipient {EOA, SELF}, pinning each shape's decomposed floor base via an exact receipt cumulative_gas_used pin (the self-transfer carve-out anchors its floor on bare TX_BASE) - new test_calldata_floor_contract_creation: all-zero init code sized so the floor exceeds the creation intrinsic plus the created account's NEW_ACCOUNT state charge; a binding floor masks the state charge while the deploy still lands, and one gas below rejects EIP-7928 BAL exclusion for unpaid delegation-target accesses: - test_recipient_charge_oog_rolls_back_delegations: the recipient's delegation target must be absent from the BAL when the cold resolution access is starved, present-unchanged when paid - test_reservoir_settlement_by_failure_point: code_target absent for both OOG points, present past dispatch; the authorities' delegation_target is never read at all, so it is asserted absent in every scenario (also added to the value-to-empty-recipient sibling) - test_top_frame_charges_delegation_in_access_list: an access-list entry warms the delegation target without reading it, so the target must be absent from the BAL when the warm charge is starved and present once it is paid (warmth != access) - new test_top_frame_charges_self_delegation_oog: one gas short of the warm self-access; the delegated address is the recipient itself and appears in the BAL exactly once, with no recorded changes Gas expectations use receipt cumulative_gas_used pins (full gas_limit on OOG) instead of gas_price/sender-balance arithmetic. 180 fixtures fill green on Amsterdam; mypy and ruff clean. * chore(tests): post review clean-ups --- .../test_authorization_oog.py | 38 +- .../test_calldata_floor.py | 262 ++++++++--- .../test_fork_transition.py | 122 +++++ .../test_top_frame_charges.py | 425 +++++++++++++++++- .../test_warmth_invariants.py | 116 ++++- 5 files changed, 902 insertions(+), 61 deletions(-) diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_authorization_oog.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_authorization_oog.py index c9aab1ea12f..2b7065f6363 100644 --- a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_authorization_oog.py +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_authorization_oog.py @@ -400,7 +400,10 @@ def test_recipient_charge_oog_rolls_back_delegations( The recipient and both authorities were accessed before the halt, so per EIP-7928 all three must still appear in the block access - list, with no recorded changes. + list, with no recorded changes. The recipient's delegation target + is only ever loaded by the resolution the starved charge pays for, + so it must be absent from the list on the out-of-gas side and + present (with no changes) on the succeeding side. The ``succeeds`` control restores the one starved gas: the recipient charge is covered exactly, the dispatch completes (the @@ -416,6 +419,7 @@ def test_recipient_charge_oog_rolls_back_delegations( auth_charges = _auth_top_frame_charges(fork, authorization_list) recipient_bal = BalAccountExpectation.empty() + delegation_target_bal: dict[Address, BalAccountExpectation | None] = {} if recipient_charge == "new_account": recipient = pre.fund_eoa(amount=0) value = 1 @@ -443,6 +447,13 @@ def test_recipient_charge_oog_rolls_back_delegations( balance=EOA_INITIAL_BALANCE, code=Spec7702.delegation_designation(delegated_to), ) + # The delegation target is only loaded by the resolution access + # the starved charge pays for: read (unchanged) on success, + # never accessed -- so absent from the block access list -- when + # the charge runs out. + delegation_target_bal = { + delegated_to: BalAccountExpectation.empty() if succeeds else None + } intrinsic_regular = _intrinsic_regular( fork, @@ -481,6 +492,7 @@ def test_recipient_charge_oog_rolls_back_delegations( recipient: recipient_bal, auth_a.authority: _applied_delegation_bal(auth_a), auth_b.authority: _applied_delegation_bal(auth_b), + **delegation_target_bal, } ) else: @@ -494,6 +506,7 @@ def test_recipient_charge_oog_rolls_back_delegations( recipient: BalAccountExpectation.empty(), auth_a.authority: BalAccountExpectation.empty(), auth_b.authority: BalAccountExpectation.empty(), + **delegation_target_bal, } ) @@ -564,6 +577,10 @@ def test_reservoir_settlement_by_failure_point( sender = pre.fund_eoa() + # Two distinct delegation targets are in play. ``delegation_target`` + # is the address every *authority's* authorization designates: + # ``set_delegation`` writes it into the authorities' code but never + # reads the account itself. delegation_target = pre.deploy_contract(code=Op.STOP) recipient_code: Bytecode if failure_point == "execution_halt": @@ -572,6 +589,9 @@ def test_reservoir_settlement_by_failure_point( recipient_code = Op.REVERT(0, 0) else: recipient_code = Op.STOP + # ``code_target`` is the *recipient's* pre-existing delegation + # target: the top-frame dispatch pays a cold access to resolve it + # and, once paid, loads and runs its code. code_target = pre.deploy_contract(code=recipient_code) recipient = pre.fund_eoa( amount=EOA_INITIAL_BALANCE, delegation=code_target @@ -698,7 +718,11 @@ def creation_authorization(authority: EOA) -> AuthorizationTuple: # with their persisted nonce and code writes past an execution # failure, with no recorded changes past a preparation rollback. # The recipient is only loaded once preparation reaches the - # dispatch charge. + # dispatch charge, and its delegation target only once that charge + # is paid and the delegated code loads -- so both out-of-gas + # scenarios must leave the target absent from the list. The + # authorities' delegation target is never read at all: writing a + # designation does not access the designated account. if delegations_persist: authority_bal = BalAccountExpectation( nonce_changes=[BalNonceChange(block_access_index=1, post_nonce=1)], @@ -718,9 +742,14 @@ def creation_authorization(authority: EOA) -> AuthorizationTuple: if failure_point == "set_delegation_oog" else BalAccountExpectation.empty() ) + code_target_bal = ( + BalAccountExpectation.empty() if delegations_persist else None + ) expected_block_access_list = BlockAccessListExpectation( account_expectations={ recipient: recipient_bal, + code_target: code_target_bal, + delegation_target: None, **dict.fromkeys(authorities, authority_bal), } ) @@ -902,7 +931,9 @@ def creation_authorization(authority: EOA) -> AuthorizationTuple: # The recipient is first loaded for its NEW_ACCOUNT alive-check, so # it is absent from the block access list only when the halt lands # inside set_delegation; afterwards it appears with no net change - # (the transfer, if any, rolled back). + # (the transfer, if any, rolled back). The authorities' delegation + # target is never read at all: writing a designation does not + # access the designated account. if delegations_persist: authority_bal = BalAccountExpectation( nonce_changes=[BalNonceChange(block_access_index=1, post_nonce=1)], @@ -925,6 +956,7 @@ def creation_authorization(authority: EOA) -> AuthorizationTuple: expected_block_access_list = BlockAccessListExpectation( account_expectations={ recipient: recipient_bal, + delegation_target: None, **dict.fromkeys(authorities, authority_bal), } ) diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_calldata_floor.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_calldata_floor.py index e5685524768..be3c77466ee 100644 --- a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_calldata_floor.py +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_calldata_floor.py @@ -1,20 +1,4 @@ -""" -EIP-2780 interaction with the EIP-7623/7976 calldata floor. - -A transaction's gas accounting uses ``max(intrinsic, calldata_floor)``. -EIP-2780 decomposes the intrinsic (``TX_BASE`` + recipient access + -value-transfer charges) and lowers ``TX_BASE`` to 12_000; that lowered -base also feeds the calldata floor. These tests pin the data-heavy -regime where the floor dominates: - -- The floor binds, so ``gas_used`` equals the floor and the - recipient/value charges folded into the intrinsic are masked: the - gas paid is identical for a zero-value and a value-bearing - transaction of the same calldata size. -- One gas below the floor, the transaction is rejected with - ``INTRINSIC_GAS_BELOW_FLOOR_GAS_COST`` even though it covers the - (smaller) decomposed intrinsic. -""" +"""EIP-2780 interaction with the EIP-7623/7976 calldata floor.""" import pytest from execution_testing import ( @@ -27,6 +11,8 @@ StateTestFiller, Transaction, TransactionException, + TransactionReceipt, + compute_create_address, ) from ...prague.eip7623_increase_calldata_cost.helpers import ( @@ -44,13 +30,15 @@ def _floor_dominating_calldata(fork: Fork) -> Bytes: """ Return zero-byte calldata sized so its calldata floor strictly - exceeds the decomposed value-transfer intrinsic for a non-create - call to an existing EOA. + exceeds the decomposed intrinsic of every non-create shape + ``test_calldata_floor`` runs. Reuses the shared EIP-7623 ``find_floor_cost_threshold`` binary - search against this transaction shape, then steps one byte past the - threshold (the last size where the floor does not yet dominate) so - the floor strictly binds. + search against the costliest such shape (a value-bearing transfer + to a distinct EOA), then steps one byte past the threshold (the + last size where the floor does not yet dominate) so the floor + strictly binds -- for that shape, and a fortiori for the cheaper + self-transfer shape. """ intrinsic_calc = fork.transaction_intrinsic_cost_calculator() floor_calc = fork.transaction_data_floor_cost_calculator() @@ -94,82 +82,246 @@ def floor(byte_count: int) -> int: pytest.param(1, id="non-zero_value"), ], ) +@pytest.mark.parametrize( + "recipient_type", + [ + pytest.param(RecipientType.EOA, id="other_eoa"), + pytest.param(RecipientType.SELF, id="self_transfer"), + ], +) def test_calldata_floor( fork: Fork, pre: Alloc, state_test: StateTestFiller, outcome: str, value: int, + recipient_type: RecipientType, ) -> None: """ - A data-heavy transaction to an existing EOA whose calldata floor - exceeds the decomposed value-transfer intrinsic. + A data-heavy transaction to an existing EOA -- a distinct account + or the sender itself -- whose calldata floor exceeds the decomposed + value-transfer intrinsic. - ``floor_binds``: with a gas limit above the floor, ``gas_used`` - pins to the floor, so the value-transfer charges - (``TRANSFER_LOG_COST + TX_VALUE_COST``) folded into the intrinsic - are masked -- the gas paid is identical at ``value == 0`` and - ``value == 1`` and only the moved wei differs. + pins to the floor. - ``below_floor``: a gas limit one short of the floor still covers - the (smaller) decomposed intrinsic, so the floor -- built on the - EIP-2780-lowered ``TX_BASE`` -- is the only thing that can reject - it, with ``INTRINSIC_GAS_BELOW_FLOOR_GAS_COST``. + the (smaller) decomposed intrinsic, so the floor is the only + thing that can reject it, with + ``INTRINSIC_GAS_BELOW_FLOOR_GAS_COST``. """ - sender_initial_balance = 10**18 - sender = pre.fund_eoa(sender_initial_balance) - target = pre.fund_eoa(amount=EOA_INITIAL_BALANCE) + sender = pre.fund_eoa() + is_self_transfer = recipient_type == RecipientType.SELF + target = ( + sender + if is_self_transfer + else pre.fund_eoa(amount=EOA_INITIAL_BALANCE) + ) calldata = _floor_dominating_calldata(fork) calldata_floor = fork.transaction_data_floor_cost_calculator()( data=calldata, sends_value=bool(value), - recipient_type=RecipientType.EOA, + recipient_type=recipient_type, + ) + + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( + calldata=calldata, + sends_value=bool(value), + recipient_type=recipient_type, + return_cost_deducted_prior_execution=True, + ) + # The calldata was sized against the costliest shape (value moving + # to a distinct EOA), so the floor dominates the carved-out + # self-transfer intrinsic a fortiori. + assert intrinsic_gas < calldata_floor, ( + "the calldata floor must dominate the decomposed intrinsic" ) - gas_price = 1_000_000_000 post: dict[Address, Account] = {} if outcome == "below_floor": # ``gas_limit`` one short of the floor still covers the # decomposed intrinsic, so the floor is the only thing that can # reject it; the post state is empty (transaction rejected). - intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( - calldata=calldata, - sends_value=bool(value), - recipient_type=RecipientType.EOA, - return_cost_deducted_prior_execution=True, - ) + # The transaction is rejected, never included in a block, so + # there is no receipt to assert against; the ``error`` is the + # whole expectation. gas_limit = calldata_floor - 1 - assert intrinsic_gas <= gas_limit, ( - "gas_limit must still cover the decomposed intrinsic so the " - "rejection is pinned to the calldata floor" - ) tx = Transaction( sender=sender, to=target, value=value, data=calldata, gas_limit=gas_limit, - gas_price=gas_price, error=TransactionException.INTRINSIC_GAS_BELOW_FLOOR_GAS_COST, ) else: # ``floor_binds``: no explicit gas limit (auto-fills above the - # floor). The gas component is the floor regardless of value - # (charges masked); only the transferred wei changes the - # balance. + # floor). Each shape pays exactly its own floor -- the + # decomposed base survives into it -- and the transferred wei + # nets to zero on a self-transfer. tx = Transaction( sender=sender, to=target, value=value, data=calldata, - gas_price=gas_price, + expected_receipt=TransactionReceipt( + cumulative_gas_used=calldata_floor, + ), ) - sender_final_balance = ( - sender_initial_balance - value - calldata_floor * gas_price + post = { + sender: Account(nonce=1), + } + if not is_self_transfer: + post[target] = Account(balance=EOA_INITIAL_BALANCE + value) + + state_test(pre=pre, tx=tx, post=post) + + +def _floor_dominating_initcode(fork: Fork) -> Bytes: + """ + Return zero-byte init code sized so its calldata floor strictly + exceeds a creation transaction's full cost, for + ``test_calldata_floor_contract_creation`` (the only creation- + transaction test in this module; ``test_calldata_floor`` uses + ``_floor_dominating_calldata`` instead). + + All-zero init code executes a single free ``STOP`` and deploys + empty code, so the transaction's cost is the creation intrinsic + plus the created account's top-frame ``NEW_ACCOUNT`` state charge, + with no execution or deposit gas. The threshold search runs against + that total, then steps one byte past it so the floor strictly + binds. + """ + intrinsic_calc = fork.transaction_intrinsic_cost_calculator() + floor_calc = fork.transaction_data_floor_cost_calculator() + new_account_state_gas = fork.transaction_top_frame_state_gas( + contract_creation=True, + ) + + def total(byte_count: int) -> int: + return ( + intrinsic_calc( + calldata=b"\x00" * byte_count, + contract_creation=True, + sends_value=True, + return_cost_deducted_prior_execution=True, + ) + + new_account_state_gas + ) + + def floor(byte_count: int) -> int: + return floor_calc( + data=b"\x00" * byte_count, + contract_creation=True, + ) + + threshold = find_floor_cost_threshold( + floor_data_gas_cost_calculator=floor, + intrinsic_gas_cost_calculator=total, + ) + byte_count = threshold + 1 + + assert floor(byte_count) > total(byte_count) + assert byte_count <= fork.max_initcode_size() + return Bytes(b"\x00" * byte_count) + + +@pytest.mark.parametrize( + "outcome", + [ + pytest.param("floor_binds", id="floor_binds"), + pytest.param( + "below_floor", + id="below_floor_rejected", + marks=pytest.mark.exception_test, + ), + ], +) +@pytest.mark.parametrize( + "value", + [ + pytest.param(0, id="zero_value"), + pytest.param(1, id="non-zero_value"), + ], +) +def test_calldata_floor_contract_creation( + fork: Fork, + pre: Alloc, + state_test: StateTestFiller, + outcome: str, + value: int, +) -> None: + """ + A contract creation whose calldata floor exceeds the creation + intrinsic plus the created account's ``NEW_ACCOUNT`` state charge. + + The init code is all zeros: it executes a free ``STOP``, deploys + empty code, and prices every byte as one floor token. + + - ``floor_binds``: ``gas_used`` pins to the floor, which anchors + on the creation regular base (``TX_BASE + CREATE_ACCESS``, plus + ``TRANSFER_LOG_COST`` when value moves) but excludes the created + account's ``NEW_ACCOUNT`` *state* charge and the init-code word + cost -- both masked by the binding floor -- while the deploy + (and any moved wei) still lands. The receipt pins the floor + exactly, so the value-bearing case sits precisely + ``TRANSFER_LOG_COST`` above the zero-value one. + - ``below_floor``: a gas limit one short of the floor still covers + the creation intrinsic, so the rejection is pinned to the floor, + with ``INTRINSIC_GAS_BELOW_FLOOR_GAS_COST``. + """ + sender = pre.fund_eoa() + created = compute_create_address(address=sender, nonce=sender.nonce) + + init_code = _floor_dominating_initcode(fork) + calldata_floor = fork.transaction_data_floor_cost_calculator()( + data=init_code, + contract_creation=True, + sends_value=bool(value), + ) + + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( + calldata=init_code, + contract_creation=True, + sends_value=bool(value), + return_cost_deducted_prior_execution=True, + ) + assert intrinsic_gas < calldata_floor, ( + "the calldata floor must dominate the creation intrinsic" + ) + + post: dict[Address, Account | None] = {} + if outcome == "below_floor": + # One gas short of the floor still covers the creation + # intrinsic, so the floor is the only thing that can reject it; + # the post state is empty and there is no receipt to assert + # against (transaction rejected, never included). + gas_limit = calldata_floor - 1 + tx = Transaction( + sender=sender, + to=None, + value=value, + data=init_code, + gas_limit=gas_limit, + error=TransactionException.INTRINSIC_GAS_BELOW_FLOOR_GAS_COST, + ) + else: + # ``floor_binds``: headroom above the floor; the receipt pins + # ``gas_used`` to exactly the floor, so the ``NEW_ACCOUNT`` + # state charge is masked while the deploy still happens. + tx = Transaction( + sender=sender, + to=None, + value=value, + data=init_code, + gas_limit=calldata_floor, + expected_receipt=TransactionReceipt( + cumulative_gas_used=calldata_floor, + ), ) post = { - sender: Account(nonce=1, balance=sender_final_balance), - target: Account(balance=EOA_INITIAL_BALANCE + value), + sender: Account(nonce=1), + created: Account(nonce=1, balance=value, code=b""), } state_test(pre=pre, tx=tx, post=post) diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_fork_transition.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_fork_transition.py index d94031fe34b..80b21d0c35a 100644 --- a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_fork_transition.py +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_fork_transition.py @@ -17,6 +17,9 @@ - A self-transfer is fully carved out post-fork: it pays only the lowered ``TX_BASE`` with no recipient or value-transfer charge, regardless of value, the largest reduction. +- A contract creation splits the flat pre-fork ``TX_CREATE`` into the + ``CREATE_ACCESS`` regular intrinsic and a top-frame ``NEW_ACCOUNT`` + state charge. """ import pytest @@ -26,9 +29,11 @@ Alloc, Block, BlockchainTestFiller, + Op, RecipientType, Transaction, TransitionFork, + compute_create_address, ) from .helpers import EOA_INITIAL_BALANCE @@ -152,3 +157,120 @@ def test_intrinsic_reduction_across_amsterdam_transition( post[target] = Account(balance=EOA_INITIAL_BALANCE + value) blockchain_test(pre=pre, blocks=blocks, post=post) + + +@pytest.mark.parametrize( + "value", + [ + pytest.param(0, id="zero_value"), + pytest.param(1, id="non-zero_value"), + ], +) +def test_creation_tx_intrinsic_across_amsterdam_transition( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: TransitionFork, + value: int, +) -> None: + """ + Pin the EIP-2780 creation-transaction change across the Amsterdam + boundary. + + The same creation transaction (``to=None``, ``STOP`` init code that + deploys empty code) is sent in a pre-fork block and a post-fork + block, each from a fresh sender with the gas limit pinned exactly. + Pre-fork the whole cost is regular intrinsic: ``TX_BASE`` plus the + flat ``TX_CREATE``. Post-fork the intrinsic keeps only the + ``CREATE_ACCESS`` regular portion of ``TX_CREATE`` (plus the + transfer-log charge when value moves), while the created account's + ``NEW_ACCOUNT`` is charged as *state* gas at the top frame — the + sender-facing total is the sum of both. + + The per-fork costs are hand-derived from each fork's gas constants + and checked against the calculators, so a calculator regression + fails with a clear message rather than only as a downstream balance + mismatch. + """ + gas_price = 1_000_000_000 + init_code = Op.STOP + + pre_fork = fork.fork_at(timestamp=PRE_FORK_TIMESTAMP) + post_fork = fork.fork_at(timestamp=POST_FORK_TIMESTAMP) + pre_costs = pre_fork.gas_costs() + post_costs = post_fork.gas_costs() + + # Shared calldata terms for the one-byte STOP init code: a single + # zero-byte token, plus the EIP-3860 metering of one 32-byte init + # code word at 2 gas. Identical on both sides of the fork. + assert ( + post_costs.TX_DATA_TOKEN_STANDARD == pre_costs.TX_DATA_TOKEN_STANDARD + ) + init_code_terms = pre_costs.TX_DATA_TOKEN_STANDARD + 2 + + # Pre-fork: flat regular intrinsic, no top-frame charge. + expected_pre = pre_costs.TX_BASE + pre_costs.TX_CREATE + init_code_terms + # Post-fork: EIP-8037 folds ``NEW_ACCOUNT`` into ``TX_CREATE``; + # EIP-2780 moves that state portion to the top frame, leaving the + # ``CREATE_ACCESS`` regular remainder in the intrinsic. + expected_post = ( + post_costs.TX_BASE + + (post_costs.TX_CREATE - post_costs.NEW_ACCOUNT) + + init_code_terms + ) + if value: + expected_post += post_costs.TRANSFER_LOG_COST + expected_post_state = post_costs.NEW_ACCOUNT + + timestamps = [PRE_FORK_TIMESTAMP, POST_FORK_TIMESTAMP] + expected_intrinsics = [expected_pre, expected_post] + expected_top_frame_states = [0, expected_post_state] + blocks = [] + post: dict[Address, Account] = {} + + for timestamp, expected_intrinsic, expected_state in zip( + timestamps, expected_intrinsics, expected_top_frame_states, strict=True + ): + sub_fork = fork.fork_at(timestamp=timestamp) + intrinsic_gas = sub_fork.transaction_intrinsic_cost_calculator()( + calldata=init_code, + contract_creation=True, + sends_value=bool(value), + return_cost_deducted_prior_execution=True, + ) + assert intrinsic_gas == expected_intrinsic, ( + f"creation intrinsic at timestamp {timestamp} ({sub_fork}) is " + f"{intrinsic_gas}, expected {expected_intrinsic}" + ) + top_frame_state_gas = sub_fork.transaction_top_frame_state_gas( + contract_creation=True, + ) + assert top_frame_state_gas == expected_state, ( + f"top-frame state gas at timestamp {timestamp} ({sub_fork}) is " + f"{top_frame_state_gas}, expected {expected_state}" + ) + + sender_initial_balance = 10**18 + sender = pre.fund_eoa(sender_initial_balance) + created = compute_create_address(address=sender, nonce=sender.nonce) + + # The STOP init code costs no execution gas and deploys empty + # code (no deposit charges), so the gas limit is pinned to + # exactly the intrinsic plus the fork's top-frame state charge. + total_gas = intrinsic_gas + top_frame_state_gas + tx = Transaction( + sender=sender, + to=None, + data=init_code, + value=value, + gas_limit=total_gas, + gas_price=gas_price, + ) + blocks.append(Block(timestamp=timestamp, txs=[tx])) + + post[sender] = Account( + nonce=1, + balance=sender_initial_balance - value - total_gas * gas_price, + ) + post[created] = Account(nonce=1, balance=value, code=b"") + + blockchain_test(pre=pre, blocks=blocks, post=post) diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_top_frame_charges.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_top_frame_charges.py index 2af1538f9c2..a89a0fddbbe 100644 --- a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_top_frame_charges.py +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_top_frame_charges.py @@ -6,14 +6,18 @@ charges may fire there, depending on the recipient: - ``NEW_ACCOUNT`` (state gas) when the recipient is empty and the - transaction transfers value. + transaction transfers value, or when a creation transaction's target + leaf did not exist before the transaction. - ``COLD_ACCOUNT_ACCESS`` (regular gas) when the recipient holds an EIP-7702 delegation. Each test parametrizes over the interesting outcomes for that charge: running out of gas at the boundary, succeeding through the charge and into the EVM, and (for the regular charge) succeeding through the -charge but reverting from the delegated code. +charge but reverting from the delegated code. For creation +transactions, the charge keys on the *transaction pre-state* being +empty, and — being consumed on any successful halt — survives the +created account's own destruction. """ import pytest @@ -23,15 +27,19 @@ Alloc, Block, BlockchainTestFiller, + Bytecode, Fork, Header, Op, RecipientType, StateTestFiller, Transaction, + TransactionReceipt, + compute_create_address, ) from ...prague.eip7702_set_code_tx.spec import Spec as Spec7702 +from .helpers import EOA_INITIAL_BALANCE from .spec import ref_spec_2780 REFERENCE_SPEC_GIT_PATH = ref_spec_2780.git_path @@ -296,6 +304,229 @@ def test_top_frame_new_account_skipped_for_nonce_only_recipient( state_test(pre=pre, tx=tx, post=post) +def creation_tx_init_code(fork: Fork) -> tuple[Bytecode, int]: + """ + Build init code for exact-gas creation-transaction tests and return + it with its execution gas. + + The code deploys empty code (no deposit charges) and expands memory + so that its execution gas lifts the transaction's exact total above + the calldata floor, which would otherwise bind once the top-frame + ``NEW_ACCOUNT`` charge is skipped. + """ + memory_offset = 30_000 + init_code = ( + Op.MSTORE.with_metadata( + new_memory_size=memory_offset + 32, old_memory_size=0 + )(memory_offset, 0) + + Op.STOP + ) + return init_code, init_code.gas_cost(fork) + + +@pytest.mark.parametrize( + "value", + [ + pytest.param(0, id="zero_value"), + pytest.param(1, id="non-zero_value"), + ], +) +def test_top_frame_new_account_skipped_for_prefunded_create_target( + fork: Fork, + pre: Alloc, + state_test: StateTestFiller, + value: int, +) -> None: + """ + A creation transaction whose nonce-derived target address already + holds a balance does not incur the top-frame ``NEW_ACCOUNT`` state + charge. + + The create branch of ``prepare_dispatch`` keys the charge on the + *transaction pre-state* being empty — a live check would always see + the account, because ``process_create_message`` bumps the target's + nonce before dispatch. Pre-funding the create address makes the + pre-state leaf non-empty, so the charge must be skipped; a + balance-only leaf does not trigger the create-collision check + (only nonce or code do), so the deployment still succeeds. + + The gas limit carries headroom above the exact total so the + transaction never runs out of gas, and the receipt pins + ``cumulative_gas_used`` to exactly the intrinsic plus the init-code + execution gas: a wrongly charged ``NEW_ACCOUNT`` for the + pre-existing leaf (or a spurious refill) shifts the receipt by + 183,600 in either direction. + """ + sender = pre.fund_eoa() + created = compute_create_address(address=sender, nonce=sender.nonce) + prefund = 1 + pre.fund_address(created, prefund) + + init_code, exec_gas = creation_tx_init_code(fork) + + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( + calldata=init_code, + contract_creation=True, + sends_value=bool(value), + return_cost_deducted_prior_execution=True, + ) + # The amount a fresh create target would be charged at the top + # frame -- the charge this test asserts is skipped. + fresh_target_state_gas = fork.transaction_top_frame_state_gas( + contract_creation=True, + ) + assert fresh_target_state_gas > 0, ( + "a fresh create target must be charged top-frame state gas" + ) + + total_gas = intrinsic_gas + exec_gas + calldata_floor = fork.transaction_data_floor_cost_calculator()( + data=init_code, + contract_creation=True, + sends_value=bool(value), + ) + assert total_gas > calldata_floor, ( + "The exact total must exceed the calldata floor for the " + "gas pin to observe the skipped charge." + "Lift memory expansion in `creation_tx_init_code` to fix." + ) + + tx = Transaction( + sender=sender, + to=None, + data=init_code, + value=value, + gas_limit=total_gas, + expected_receipt=TransactionReceipt(cumulative_gas_used=total_gas), + ) + + post = { + sender: Account(nonce=1), + created: Account(nonce=1, balance=prefund + value, code=b""), + } + + state_test(pre=pre, tx=tx, post=post) + + +@pytest.mark.parametrize( + "value", + [ + pytest.param(0, id="zero_value"), + pytest.param(1, id="non-zero_value"), + ], +) +def test_top_frame_new_account_skipped_for_create_target_funded_same_block( + fork: Fork, + pre: Alloc, + blockchain_test: BlockchainTestFiller, + value: int, +) -> None: + """ + A creation transaction whose target was funded by an *earlier + transaction of the same block* does not incur the top-frame + ``NEW_ACCOUNT`` state charge. + + This discriminates the transaction pre-state from the block + pre-state: ``get_pre_state_account`` consults the block's + accumulated same-block transaction writes before falling back to + the block pre-state, so the funding transaction's new leaf counts + as pre-existing for the creation transaction. An implementation + snapshotting at block start would charge a second ``NEW_ACCOUNT``, + shifting the creation transaction's receipt by 183,600 and + doubling the state dimension pinned by the header. + + The funding transaction pays its own top-frame ``NEW_ACCOUNT`` for + materializing the leaf, which the block header pins as the block's + entire state-gas dimension: ``gas_used = max(regular, state)`` must + equal exactly one ``NEW_ACCOUNT``. + """ + funder = pre.fund_eoa() + sender = pre.fund_eoa() + created = compute_create_address(address=sender, nonce=sender.nonce) + + # Transaction 1: fund the future create address. The value transfer + # to the not-yet-existing leaf pays the top-frame ``NEW_ACCOUNT``. + prefund = 1 + fund_intrinsic = fork.transaction_intrinsic_cost_calculator()( + sends_value=True, + recipient_type=RecipientType.EMPTY_ACCOUNT, + return_cost_deducted_prior_execution=True, + ) + fund_state_gas = fork.transaction_top_frame_state_gas( + sends_value=True, + recipient_type=RecipientType.EMPTY_ACCOUNT, + ) + assert fund_state_gas > 0, ( + "funding an empty leaf must charge top-frame state gas" + ) + fund_total = fund_intrinsic + fund_state_gas + fund_tx = Transaction( + sender=funder, + to=created, + value=prefund, + gas_limit=fund_total, + expected_receipt=TransactionReceipt(cumulative_gas_used=fund_total), + ) + + # Transaction 2: the creation transaction, with gas headroom; the + # receipt pins the consumed gas to exactly the ``NEW_ACCOUNT``-free + # total. + init_code, exec_gas = creation_tx_init_code(fork) + create_intrinsic = fork.transaction_intrinsic_cost_calculator()( + calldata=init_code, + contract_creation=True, + sends_value=bool(value), + return_cost_deducted_prior_execution=True, + ) + create_total = create_intrinsic + exec_gas + calldata_floor = fork.transaction_data_floor_cost_calculator()( + data=init_code, + contract_creation=True, + sends_value=bool(value), + ) + assert create_total > calldata_floor, ( + "the exact total must exceed the calldata floor for the " + "gas pin to observe the skipped charge." + "Lift memory expansion in `creation_tx_init_code` to fix." + ) + create_tx = Transaction( + sender=sender, + to=None, + data=init_code, + value=value, + gas_limit=create_total, + expected_receipt=TransactionReceipt( + cumulative_gas_used=fund_total + create_total + ), + ) + + # Header pin: the block's state dimension is exactly the funding + # transaction's ``NEW_ACCOUNT``; both regular intrinsics sit at or + # above their calldata floors, so no floor term enters the block's + # regular dimension either. + block_regular = fund_intrinsic + create_total + assert fund_state_gas > block_regular, ( + "the state dimension must dominate for the header to pin it" + ) + + post = { + funder: Account(nonce=1), + sender: Account(nonce=1), + created: Account(nonce=1, balance=prefund + value, code=b""), + } + + blockchain_test( + pre=pre, + blocks=[ + Block( + txs=[fund_tx, create_tx], + header_verify=Header(gas_used=fund_state_gas), + ), + ], + post=post, + ) + + @pytest.mark.parametrize("outcome", ["oog", "success", "evm_reverts"]) @pytest.mark.parametrize( "value", @@ -391,3 +622,193 @@ def test_top_frame_regular_charge( } state_test(pre=pre, tx=tx, post=post) + + +@pytest.mark.parametrize( + "beneficiary_kind", + [ + pytest.param("self", id="self_beneficiary"), + pytest.param("funded_external", id="funded_external_beneficiary"), + pytest.param("empty_external", id="empty_external_beneficiary"), + ], +) +@pytest.mark.parametrize( + "value", + [ + pytest.param(0, id="zero_value"), + pytest.param(1, id="non-zero_value"), + ], +) +def test_initcode_selfdestruct_keeps_top_frame_state_charge( + fork: Fork, + pre: Alloc, + state_test: StateTestFiller, + beneficiary_kind: str, + value: int, +) -> None: + """ + A creation transaction whose init code ``SELFDESTRUCT``s keeps the + top-frame ``NEW_ACCOUNT`` state charge consumed. + + ``SELFDESTRUCT`` is a *successful* halt: the frame returns no + output (an empty deposit, so no deposit charges) and no rollback + runs, so the refill machinery that returns state gas on a revert or + exceptional halt never triggers — even though the created account + is destroyed at the end of the transaction (EIP-6780 same-tx + deletion) and its leaf never persists. Deletion itself carries no + state-gas credit: freeing state is not refunded. + + Where the endowment ends up follows EIP-8246: destruction preserves + a nonzero balance, so a self beneficiary leaves a balance-only leaf + behind, while sweeping to an external beneficiary (or a zero + endowment) removes the account entirely. Sweeping value to a + not-yet-existing beneficiary additionally pays the opcode-level + ``NEW_ACCOUNT`` and ``ACCOUNT_WRITE`` for the beneficiary — both + the destroyed target's top-frame charge and the sweep's charge stay + paid. + + The receipt pins the exact total; a regression refilling the + top-frame charge shows up as a 183,600 shortfall in + ``cumulative_gas_used`` and a matching sender refund. + """ + sender = pre.fund_eoa() + created = compute_create_address(address=sender, nonce=sender.nonce) + + beneficiary: Address | None = None + if beneficiary_kind == "self": + # The created address is warmed for the create frame itself. + init_code = Op.SELFDESTRUCT.with_metadata( + address_warm=True, account_new=False + )(Op.ADDRESS) + elif beneficiary_kind == "funded_external": + beneficiary = pre.fund_eoa(amount=EOA_INITIAL_BALANCE) + init_code = Op.SELFDESTRUCT.with_metadata( + address_warm=False, account_new=False + )(beneficiary) + else: + beneficiary = pre.nonexistent_account() + # Sweeping a non-zero balance into a non-existent leaf creates + # the beneficiary, paying NEW_ACCOUNT (state) and ACCOUNT_WRITE + # (regular) at the opcode. + init_code = Op.SELFDESTRUCT.with_metadata( + address_warm=False, account_new=bool(value) + )(beneficiary) + + # Combined regular + state execution gas, including any sweep + # charges modeled by the metadata above. + exec_gas = init_code.gas_cost(fork) + + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( + calldata=init_code, + contract_creation=True, + sends_value=bool(value), + return_cost_deducted_prior_execution=True, + ) + top_frame_state_gas = fork.transaction_top_frame_state_gas( + contract_creation=True, + ) + assert top_frame_state_gas > 0, ( + "a fresh create target must be charged top-frame state gas" + ) + total_gas = intrinsic_gas + top_frame_state_gas + exec_gas + + tx = Transaction( + sender=sender, + to=None, + data=init_code, + value=value, + gas_limit=total_gas, + expected_receipt=TransactionReceipt(cumulative_gas_used=total_gas), + ) + + post: dict[Address, Account | None] = {sender: Account(nonce=1)} + if beneficiary_kind == "self": + # EIP-8246: destruction preserves the balance, so a non-zero + # endowment survives as a balance-only leaf. + post[created] = ( + Account(balance=value, nonce=0, code=b"") if value else None + ) + elif beneficiary_kind == "funded_external": + assert beneficiary is not None + post[created] = None + post[beneficiary] = Account(balance=EOA_INITIAL_BALANCE + value) + else: + assert beneficiary is not None + post[created] = None + # A zero-value sweep does not bring the beneficiary to life. + post[beneficiary] = Account(balance=value) if value else None + + state_test(pre=pre, tx=tx, post=post) + + +def test_initcode_selfdestruct_state_gas_in_header( + fork: Fork, + pre: Alloc, + blockchain_test: BlockchainTestFiller, +) -> None: + """ + The top-frame ``NEW_ACCOUNT`` surviving an init-code + ``SELFDESTRUCT`` stays in the *state* dimension after settlement. + + Receipts and balances only observe the sum of the two gas + dimensions, so the sibling + ``test_initcode_selfdestruct_keeps_top_frame_state_charge`` cannot + distinguish which dimension the surviving charge settled into. The + block header can: ``gas_used = max(block_regular, block_state)``, + and with a zero endowment and a self beneficiary the whole created + account vanishes while the state side (one ``NEW_ACCOUNT``, + dominating the small regular side) must still show in the header. + + Bug signatures: a refill regression collapses the header to the + small regular sum; a regular-gas mis-classification raises it to + ``regular + NEW_ACCOUNT``. + """ + sender = pre.fund_eoa() + created = compute_create_address(address=sender, nonce=sender.nonce) + + init_code = Op.SELFDESTRUCT.with_metadata( + address_warm=True, account_new=False + )(Op.ADDRESS) + exec_regular = init_code.regular_cost(fork) + + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( + calldata=init_code, + contract_creation=True, + return_cost_deducted_prior_execution=True, + ) + state_side = fork.transaction_top_frame_state_gas( + contract_creation=True, + ) + calldata_floor = fork.transaction_data_floor_cost_calculator()( + data=init_code, + contract_creation=True, + ) + # Block accounting carries the calldata floor in the regular + # dimension. + regular_side = max(intrinsic_gas + exec_regular, calldata_floor) + assert state_side > regular_side, ( + "the state dimension must dominate for the header to pin it" + ) + + total_gas = intrinsic_gas + state_side + exec_regular + tx = Transaction( + sender=sender, + to=None, + data=init_code, + gas_limit=total_gas, + expected_receipt=TransactionReceipt(cumulative_gas_used=total_gas), + ) + + blockchain_test( + pre=pre, + blocks=[ + Block( + txs=[tx], + header_verify=Header(gas_used=state_side), + ), + ], + post={ + sender: Account(nonce=1), + created: None, + }, + ) diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_warmth_invariants.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_warmth_invariants.py index c094b5d3bef..202e8442a43 100644 --- a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_warmth_invariants.py +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_warmth_invariants.py @@ -25,12 +25,16 @@ Account, Address, Alloc, + BalAccountExpectation, + BalBalanceChange, + BlockAccessListExpectation, Environment, Fork, Op, RecipientType, StateTestFiller, Transaction, + TransactionReceipt, ) from ...prague.eip7702_set_code_tx.spec import Spec as Spec7702 @@ -215,12 +219,18 @@ def test_top_frame_charges_delegation_in_access_list( total_gas_cost = intrinsic_gas + top_frame_gas gas_price = 1_000_000_000 + delegated_to_bal: BalAccountExpectation | None if outcome == "oog": # Runs out one gas short of the warm charge, before dispatch: # no value moves and the sender pays the full gas_limit. gas_limit = total_gas_cost - 1 sender_final_balance = sender_initial_balance - gas_limit * gas_price target_balance = 0 + # The access-list entry warmed the delegation target but never + # read it, and the starved charge is the one access that would + # have: the target must be absent from the block access list. + delegated_to_bal = None + target_bal = BalAccountExpectation.empty() else: # Exact gas: the delegated STOP costs nothing, so the warm # charge is the last gas spent and the value transfer lands. @@ -229,6 +239,18 @@ def test_top_frame_charges_delegation_in_access_list( sender_initial_balance - value - total_gas_cost * gas_price ) target_balance = value + # The paid warm access loads the target's code for dispatch, so + # it enters the block access list, unchanged. + delegated_to_bal = BalAccountExpectation.empty() + target_bal = ( + BalAccountExpectation( + balance_changes=[ + BalBalanceChange(block_access_index=1, post_balance=value) + ] + ) + if value + else BalAccountExpectation.empty() + ) tx = Transaction( ty=1, @@ -245,7 +267,17 @@ def test_top_frame_charges_delegation_in_access_list( target: Account(balance=target_balance, code=target_code), } - state_test(pre=pre, tx=tx, post=post) + state_test( + pre=pre, + tx=tx, + post=post, + expected_block_access_list=BlockAccessListExpectation( + account_expectations={ + target: target_bal, + delegated_to: delegated_to_bal, + } + ), + ) @pytest.mark.parametrize( @@ -506,6 +538,88 @@ def test_top_frame_charges_delegation_is_recipient( state_test(pre=pre, tx=tx, post=post) +@pytest.mark.parametrize( + "value", + [ + pytest.param(0, id="zero_value"), + pytest.param(1, id="non-zero_value"), + ], +) +def test_top_frame_charges_self_delegation_oog( + fork: Fork, + pre: Alloc, + state_test: StateTestFiller, + value: int, +) -> None: + """ + Recipient holds a pre-existing EIP-7702 delegation pointing back at + itself, and the transaction is one gas short of the delegation + target's ``WARM_ACCESS`` charge. + + The target of the resolution is the recipient itself, which is warm + as ``tx.to``, so the starved charge is the warm access -- + a cold charge here would be a self-delegation warmth bug. The halt + lands before dispatch, so the delegation prefix (whose leading + ``0xef`` decodes as ``INVALID``) never runs; the sender pays the + full ``gas_limit`` and no value moves. + + Unlike a delegation to a distinct never-accessed account, the + delegated address here *is* the recipient, whose code was already + read to discover the delegation: per EIP-7928 it must appear in the + block access list exactly once, with no recorded changes. + """ + sender = pre.fund_eoa() + + # Pre-allocate an EOA that delegates to itself. The 1-wei balance + # keeps the account alive at top-frame check time so the + # ``NEW_ACCOUNT`` charge does not fire. + target = pre.fund_eoa(amount=1, delegation="Self") + target_code = Spec7702.delegation_designation(target) + + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( + sends_value=bool(value), + recipient_type=RecipientType.DELEGATION_7702, + return_cost_deducted_prior_execution=True, + ) + top_frame_gas = fork.transaction_top_frame_gas_calculator()( + sends_value=bool(value), + recipient_type=RecipientType.DELEGATION_7702, + delegation_warm=True, + ) + + # One gas short of the warm self-access: the frame halts before + # dispatching the (self-)delegated code. The receipt pins the full + # ``gas_limit`` as consumed -- the out-of-gas signature (receipt + # ``status`` is not verified by the filler). + gas_limit = intrinsic_gas + top_frame_gas - 1 + + tx = Transaction( + sender=sender, + to=target, + value=value, + gas_limit=gas_limit, + expected_receipt=TransactionReceipt( + cumulative_gas_used=gas_limit, + ), + ) + + post = { + sender: Account(nonce=1), + target: Account(balance=1, code=target_code), + } + + state_test( + pre=pre, + tx=tx, + post=post, + expected_block_access_list=BlockAccessListExpectation( + account_expectations={ + target: BalAccountExpectation.empty(), + } + ), + ) + + @pytest.mark.parametrize( "value", [ From a2e59a6cfbd2de55ae3427673b2cd7db5a42ba38 Mon Sep 17 00:00:00 2001 From: Guruprasad Kamath <48196632+gurukamath@users.noreply.github.com> Date: Thu, 23 Jul 2026 17:34:45 +0200 Subject: [PATCH 145/233] refactor(spec-tools): use testing pydantic models in t8n (#2924) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor(tests): make Alloc implement the PreState protocol Add a CONSTRUCTION/LIVE/FROZEN lifecycle to the testing-side Alloc so it directly satisfies ethereum.state.PreState. The first PreState read transitions the alloc to LIVE and rejects further __setitem__/ __delitem__; apply_diff(BlockDiff) is the sole mutation entry point in LIVE, and freeze() locks the allocation for assertion use. Groundwork for the t8n refactor: with Alloc directly usable as a PreState, fork.BlockState(pre_state=alloc) works without an adapter, and t8n can drop its bespoke Alloc/Env/Result/Txs JSON types. * refactor(spec-tools): rewrite T8N to consume testing pydantic types T8N now wires the testing-package types end-to-end: * __init__ accepts an optional ``t8n_data`` and otherwise parses the JSON inputs into testing ``Alloc``/``Environment``/``Transaction`` via ``model_validate``. The CLI ingress is structured so the future in-process lift only needs to swap the caller, not the constructor. * ``env.py`` drops the bespoke ``Env`` class in favour of ``build_block_environment(fork, env, pre_state, chain_id, ommers, state_test)`` plus a handful of ``_resolve_*`` helpers. ``Ommer`` stays as a small dataclass for the pre-PoS reward path. * ``convert_transaction`` routes through ``TransactionLoad`` rather than ``rlp.decode_to`` so contract-creating typed txs (Blob / SetCode with ``to=null``) construct successfully and let ``check_transaction`` raise the canonical ``TransactionTypeContractCreationError``. * The bespoke ``Result`` / ``Txs`` / ``Alloc`` classes are gone; ``build_result`` and ``get_receipts_from_output`` produce a ``cli_types.Result`` directly, and ``T8N.run()`` emits the ``TransitionToolOutput``-shaped JSON. * The per-tx ``backup_state`` / ``restore_state`` pattern disappears with the snapshot-based State: a failed ``process_transaction`` no longer reaches ``incorporate_tx_into_block``, so ``BlockState`` is untouched without explicit rollback. After execution the block diff is applied in-place via ``Alloc.apply_diff``. JSON ingress smooths two boundary mismatches: ``yParity`` on auth tuples (duplicated by the testing serializer, rejected by the validator) and ``secretKey`` left on already-signed txs (rejected by ``InvalidSignaturePrivateKeyError``). Unsigned txs with only a ``secretKey`` are signed post-validation; pre-Spurious-Dragon forks get ``protected=False`` so the v-value stays in {27, 28}. * refactor(testing): drive ExecutionSpecsTransitionTool's T8N in-process The testing-side EELS caller no longer marshals the input through a JSON ``StringIO`` and back. ``_evaluate`` now hands the testing ``TransitionToolInput`` directly to ``T8N`` via the existing ``t8n_data`` kwarg and assembles the ``TransitionToolOutput`` from ``T8N``'s in-memory ``alloc``/``result``/``body``. To make the in-process path symmetric with the CLI path, ``T8N``: * pulls ``blob_params`` from ``t8n_data.blob_params`` (camelCase dump matches the existing parse), so BPO-fork blob schedules don't have to be re-serialized through ``--input.blobParams=stdin``; * refactor(spec-tools): make T8N JSON-free; CLI wrapper in t8n.cli T8N now takes a testing ``TransitionTool.TransitionToolData`` and nothing else from the JSON/CLI surface. The CLI plumbing (``argparse`` namespace, ``--input.*``/``--output.*`` flags, stdin, file paths, tracer construction from CLI flags) lives in a new ``t8n.cli`` module: * ``build_t8n_from_cli_options(options, in_file, cache) -> T8N`` reads the JSON inputs (stdin / files), validates each piece into testing pydantic types, resolves the fork, bundles everything into a ``TransitionToolData``, builds tracers from the CLI flags, and hands them to ``T8N``. * ``write_t8n_outputs(t8n, output, options, out_file)`` serialises the t8n output + opcode counts per ``--output.*``. * ``run_t8n_cli(options, out_file, in_file, cache) -> int`` chains the two for the CLI entry point. ``T8N`` internally calls ``resolve_fork(t8n_data.fork_name, t8n_data.env)`` to translate the testing-side fork name into a spec ``Hardfork`` + optional ``ByBlockNumber`` criteria (handles both canonical names and CLI exception aliases like ``Paris``, ``ConstantinopleFix``, ``HomesteadToDaoAt5``). ``T8N.run()`` returns the ``TransitionToolOutput`` directly — no more out_file writing. Callers updated: * ``evm_tools.__init__.main`` now calls ``run_t8n_cli``. * ``statetest`` and ``tests/json_loader`` use ``build_t8n_from_cli_options``. * ``tests/evm_tools/test_count_opcodes`` uses ``run_t8n_cli``. * ``ExecutionSpecsTransitionTool._evaluate`` hands its ``transition_tool_data`` straight to ``T8N`` — no argparse dance. The CLI ↔ testing fork-name mapping is title-case + a one-entry override for ``DAOFork`` (testing's irregular capitalisation). ``state_reward=None`` is resolved to the fork's ``BLOCK_REWARD`` (or ``-1`` for PoS forks) in the wrapper before constructing ``TransitionToolData.reward: int``. * refactor(testing): drop duplicate State/trie in test_types ``Alloc`` used to maintain its own parallel ``State`` dataclass plus ``set_account``/``set_storage``/``state_root``/``storage_root`` free functions to compute its root. Now that ``Alloc`` implements the ``PreState`` protocol, ``state_root()`` can route through ``_materialize_state()`` and ``ethereum.state.state_root``, so the in-package trie machinery is redundant. * ``Alloc.state_root()`` reduced to a one-liner over ``spec_state.state_root(self._materialize_state())``. The materialize call doesn't transition the alloc out of ``CONSTRUCTION``, so existing callers that compute a genesis root and then keep mutating the alloc are unaffected. * Local ``State`` dataclass + trie helpers (``set_account``, ``set_storage``, ``storage_root``, ``state_root``) removed; they had no consumers outside the deleted ``Alloc.state_root`` body. * ``test_types/trie.py`` deleted along with its now-tautological ``test_eest_trie_keccak256_matches_eels`` keccak-dispatch check (the module just re-exported ``ethereum.crypto.hash.keccak256``). * refactor(spec-tools): final clean up * refactor(spec-tools): post review update * fix(spec-tools): load txs that carry no signature material The CLI parser builds testing `Transaction` objects and RLP-encodes them for the returned body. A tx with neither `v`/`r`/`s` nor `secretKey` made `Transaction.rlp` auto-sign a key-less tx and die on `assert signing_key is not None`, failing every json_loader case that replays such a fixture (136 in CI, all `test_bad_v_r_s`). Default the missing signature components to zero in `_normalize_tx_json`, matching the previous parser (`t8n_types.Txs.parse_json_tx`): the tx then executes with an invalid signature and the fork rejects it, which is exactly what these fixtures assert via `expectException`. Verified against locally filled `bad_v_r_s` fixtures for Homestead and Prague. * chore: fix-up docstring * chore: fix-up docstring formatting for ruff * chore: just one more docstring fix * refactor(test-clis): Refactor LazyAlloc * refactor(test-clis): Update LazyAlloc * refactor(test-clis): Refactor LazyAlloc * post review updates --------- Co-authored-by: danceratopz <danceratopz@gmail.com> Co-authored-by: Mario Vega <marioevz@gmail.com> --- .../base_types/tests/test_keccak_dispatch.py | 10 - .../client_clis/cli_types.py | 113 +++- .../client_clis/clis/besu.py | 2 +- .../client_clis/clis/execution_specs.py | 117 ++-- .../client_clis/file_utils.py | 28 +- .../client_clis/tests/test_execution_specs.py | 2 +- .../client_clis/tests/test_transition_tool.py | 28 +- .../client_clis/transition_tool.py | 4 +- .../src/execution_testing/specs/blockchain.py | 12 +- .../src/execution_testing/specs/state.py | 6 +- .../test_types/account_types.py | 376 ++++++++--- .../test_types/tests/test_alloc_prestate.py | 277 ++++++++ .../src/execution_testing/test_types/trie.py | 401 ----------- src/ethereum_spec_tools/evm_tools/__init__.py | 6 +- .../evm_tools/loaders/fork_loader.py | 18 - .../evm_tools/statetest/__init__.py | 25 +- .../evm_tools/t8n/__init__.py | 631 ++++++++---------- .../evm_tools/t8n/block_environment.py | 233 +++++++ src/ethereum_spec_tools/evm_tools/t8n/cli.py | 483 ++++++++++++++ src/ethereum_spec_tools/evm_tools/t8n/env.py | 333 --------- .../evm_tools/t8n/result.py | 149 +++++ .../evm_tools/t8n/t8n_types.py | 443 ------------ src/ethereum_spec_tools/evm_tools/utils.py | 67 +- tests/evm_tools/test_count_opcodes.py | 8 +- tests/json_loader/helpers/load_state_tests.py | 12 +- vulture_whitelist.py | 10 +- 26 files changed, 1960 insertions(+), 1834 deletions(-) create mode 100644 packages/testing/src/execution_testing/test_types/tests/test_alloc_prestate.py delete mode 100644 packages/testing/src/execution_testing/test_types/trie.py create mode 100644 src/ethereum_spec_tools/evm_tools/t8n/block_environment.py create mode 100644 src/ethereum_spec_tools/evm_tools/t8n/cli.py delete mode 100644 src/ethereum_spec_tools/evm_tools/t8n/env.py create mode 100644 src/ethereum_spec_tools/evm_tools/t8n/result.py delete mode 100644 src/ethereum_spec_tools/evm_tools/t8n/t8n_types.py diff --git a/packages/testing/src/execution_testing/base_types/tests/test_keccak_dispatch.py b/packages/testing/src/execution_testing/base_types/tests/test_keccak_dispatch.py index 95bd341ba6d..af3fb835d21 100644 --- a/packages/testing/src/execution_testing/base_types/tests/test_keccak_dispatch.py +++ b/packages/testing/src/execution_testing/base_types/tests/test_keccak_dispatch.py @@ -162,13 +162,3 @@ def test_eest_bytes_keccak256_matches_eels() -> None: from_eest = bytes(Bytes(buffer).keccak256()) from_eels = bytes(keccak256(buffer)) assert from_eest == from_eels - - -def test_eest_trie_keccak256_matches_eels() -> None: - """`trie.keccak256` and EELS `keccak256` return identical digests.""" - from ethereum.crypto.hash import keccak256 as eels - - from ...test_types.trie import keccak256 as trie - - for buffer in (b"", b"hashme", bytes(range(256))): - assert bytes(trie(buffer)) == bytes(eels(buffer)) diff --git a/packages/testing/src/execution_testing/client_clis/cli_types.py b/packages/testing/src/execution_testing/client_clis/cli_types.py index 769ccfec632..fbb47bfaf9b 100644 --- a/packages/testing/src/execution_testing/client_clis/cli_types.py +++ b/packages/testing/src/execution_testing/client_clis/cli_types.py @@ -428,8 +428,8 @@ def validate(self) -> Alloc: """Validate the alloc.""" raise NotImplementedError("validate method not implemented.") - def get(self) -> Alloc: - """Model validate the allocation and return it.""" + def materialize(self) -> Alloc: + """Materialize the allocation, validating it on first access.""" if self.alloc is None: self.alloc = self.validate() return self.alloc @@ -438,6 +438,28 @@ def state_root(self) -> Hash: """Return state root of the allocation.""" return self._state_root + def serialize(self, **model_dump_config: Any) -> str: + """ + Serialize the allocation to a JSON string. + + The default materializes the ``Alloc`` and dumps it. Subclasses + backed by already-serialized data override this to return their + cache directly and skip the round trip through ``Alloc``. + """ + return self.materialize().model_dump_json(**model_dump_config) + + def serialize_to_file( + self, file_path: Path, **model_dump_config: Any + ) -> None: + """ + Serialize the allocation to ``file_path`` as JSON. + + Writes whatever :meth:`serialize` produces. ``LazyAllocFile`` + overrides this with a byte-for-byte copy that avoids building + the JSON string at all. + """ + file_path.write_text(self.serialize(**model_dump_config)) + JSONDict = Dict[str, Any] @@ -453,6 +475,19 @@ def validate(self) -> Alloc: """Validate the alloc.""" return Alloc.model_validate(self.raw) + def serialize(self, **model_dump_config: Any) -> str: + """ + Dump the cached JSON dict without round-tripping through ``Alloc``. + + Only ``indent`` applies; the dict is already-serialized data, so + pydantic options such as ``by_alias`` / ``exclude_none`` are moot. + """ + return json.dumps( + self.raw, + ensure_ascii=True, + indent=model_dump_config.get("indent"), + ) + class LazyAllocStr(LazyAlloc[str]): """ @@ -465,6 +500,11 @@ def validate(self) -> Alloc: """Validate the alloc.""" return Alloc.model_validate_json(self.raw) + def serialize(self, **model_dump_config: Any) -> str: + """Return the cached JSON string verbatim (no re-serialization).""" + del model_dump_config # raw already encodes its own formatting + return self.raw + @dataclass(kw_only=True) class LazyAllocFile(LazyAlloc[Path]): @@ -484,7 +524,7 @@ class LazyAllocFile(LazyAlloc[Path]): LazyAllocFile is dropped. That lets a chained next-block t8n call consume the alloc directly from disk (via ``--input.alloc=<path>`` for geth, or ``shutil.copyfile`` for filesystem t8ns) without round-tripping - through ``Alloc.get().model_dump_json()`` in Python. + through ``LazyAlloc.materialize().model_dump_json()`` in Python. """ _keepalive: Optional[tempfile.TemporaryDirectory] = field(default=None) @@ -514,6 +554,46 @@ def validate(self) -> Alloc: ) return Alloc.model_validate(accumulated) + def serialize_to_file( + self, file_path: Path, **model_dump_config: Any + ) -> None: + """ + Copy the backing file byte-for-byte, avoiding a parse/dump cycle. + + If the backing temp dir was already cleaned up (e.g. a + chained-block t8n consumed it on the next block), fall back to + dumping the cached ``Alloc`` so debug output still captures the + input. + """ + if Path(self.raw).exists(): + shutil.copyfile(self.raw, file_path) + else: + super().serialize_to_file(file_path, **model_dump_config) + + +@dataclass(kw_only=True) +class MaterializedAlloc(LazyAlloc[None]): + """ + Allocation already materialized in memory; ``get()`` is a no-op. + + Used by in-process transition tools (EELS) whose ``Alloc`` never + exists in a serialized form — hence ``raw`` is ``None``. The + ``alloc`` field must be provided at construction, so ``get()`` + always short-circuits and ``validate()`` is unreachable. + """ + + raw: None = None + + def __post_init__(self) -> None: + """Require the materialized alloc at construction.""" + assert self.alloc is not None, ( + "MaterializedAlloc requires `alloc` at construction" + ) + + def validate(self) -> Alloc: + """Unreachable: ``alloc`` is always set at construction.""" + raise AssertionError("unreachable: alloc is set at construction") + @dataclass class TransitionToolInput: @@ -534,16 +614,15 @@ def to_files( For ``LazyAllocFile`` inputs whose backing file is still on disk (chained-block handoff: previous t8n call's temp dir is pinned via the keepalive field), the alloc is copied byte-for-byte rather than - round-tripped through ``Alloc.get().model_dump_json()``. + round-tripped through ``LazyAlloc.materialize().model_dump_json()``. """ alloc_path = directory_path / "alloc.json" - if ( - isinstance(self.alloc, LazyAllocFile) - and Path(self.alloc.raw).exists() - ): - shutil.copyfile(self.alloc.raw, alloc_path) + if isinstance(self.alloc, LazyAlloc): + self.alloc.serialize_to_file(alloc_path, **model_dump_config) else: - alloc_path.write_text(self._serialize_alloc(**model_dump_config)) + alloc_path.write_text( + self.alloc.model_dump_json(**model_dump_config) + ) env_contents = self.env.model_dump_json(**model_dump_config) txs_contents = ( @@ -570,13 +649,9 @@ def to_files( def _serialize_alloc(self, **model_dump_config: Any) -> str: """Serialize ``self.alloc`` to a JSON string.""" - if isinstance(self.alloc, Alloc): - return self.alloc.model_dump_json(**model_dump_config) - if isinstance(self.alloc, LazyAllocStr): - return self.alloc.raw - if isinstance(self.alloc, LazyAllocFile): - return self.alloc.get().model_dump_json(**model_dump_config) - raise Exception(f"Invalid alloc type: {type(self.alloc)}") + if isinstance(self.alloc, LazyAlloc): + return self.alloc.serialize(**model_dump_config) + return self.alloc.model_dump_json(**model_dump_config) def model_dump_json( self, *, exclude_alloc: bool = False, **model_dump_config: Any @@ -623,7 +698,7 @@ def model_dump(self, mode: str, **model_dump_config: Any) -> Any: elif isinstance(self.alloc, LazyAllocJson): alloc_contents = self.alloc.raw elif isinstance(self.alloc, LazyAllocFile): - alloc_contents = self.alloc.get().model_dump( + alloc_contents = self.alloc.materialize().model_dump( mode=mode, **model_dump_config ) else: @@ -681,7 +756,7 @@ def model_validate_files( different JSON file. `alloc.json` is referenced by path and parsed incrementally on - `.get()` via `LazyAllocFile`, so the full file is never held in + `.materialize()` via `LazyAllocFile`, so the full file is never held in memory alongside the validated `Alloc`. """ result_data = (directory_path / "result.json").read_text() diff --git a/packages/testing/src/execution_testing/client_clis/clis/besu.py b/packages/testing/src/execution_testing/client_clis/clis/besu.py index c4c17101d5d..f4d5114579a 100644 --- a/packages/testing/src/execution_testing/client_clis/clis/besu.py +++ b/packages/testing/src/execution_testing/client_clis/clis/besu.py @@ -278,7 +278,7 @@ def _evaluate( dump_files_to_directory( debug_output_path, { - "output/alloc.json": output.alloc.raw, + "output/alloc.json": output.alloc, "output/result.json": output.result.model_dump( mode="json", **model_dump_config ), diff --git a/packages/testing/src/execution_testing/client_clis/clis/execution_specs.py b/packages/testing/src/execution_testing/client_clis/clis/execution_specs.py index f0be44460e6..4d5d7d81865 100644 --- a/packages/testing/src/execution_testing/client_clis/clis/execution_specs.py +++ b/packages/testing/src/execution_testing/client_clis/clis/execution_specs.py @@ -2,15 +2,16 @@ Ethereum Specs EVM Transition Tool Interface. """ -import json import tempfile -from io import StringIO from pathlib import Path from typing import TYPE_CHECKING, Any, ClassVar, Dict, Optional from typing_extensions import override -from execution_testing.client_clis.cli_types import TransitionToolOutput +from execution_testing.client_clis.cli_types import ( + OpcodeCount, + TransitionToolOutput, +) from execution_testing.client_clis.file_utils import ( dump_files_to_directory, ) @@ -92,84 +93,76 @@ def _evaluate( profiler: Profiler, ) -> TransitionToolOutput: """ - Evaluate using the EELS T8N entry point. + Evaluate using the EELS T8N entry point in-process. + + ``transition_tool_data`` is handed to ``T8N`` as-is — fork, + chain_id, reward, state_test, blob_schedule all flow through + — and ``T8N.run()`` returns the ``TransitionToolOutput`` + directly. """ - from ethereum_spec_tools.evm_tools import create_parser from ethereum_spec_tools.evm_tools.t8n import T8N + from ethereum_spec_tools.evm_tools.t8n.evm_trace.count import ( + CountTracer, + ) + from ethereum_spec_tools.evm_tools.t8n.evm_trace.eip3155 import ( + Eip3155Tracer, + ) + from ethereum_spec_tools.evm_tools.t8n.evm_trace.group import ( + GroupTracer, + ) del slow_request, profiler - request_data = transition_tool_data.get_request_data() - request_data_json = request_data.model_dump( - mode="json", **model_dump_config - ) temp_dir = tempfile.TemporaryDirectory() - t8n_args = [ - "t8n", - "--input.alloc=stdin", - "--input.env=stdin", - "--input.txs=stdin", - "--output.result=stdout", - "--output.body=stdout", - "--output.alloc=stdout", - f"--output.basedir={temp_dir.name}", - f"--state.fork={request_data_json['state']['fork']}", - f"--state.chainid={request_data_json['state']['chainid']}", - f"--state.reward={request_data_json['state']['reward']}", - ] - - if transition_tool_data.state_test: - t8n_args.append("--state-test") - - if transition_tool_data.blob_params: - fork = transition_tool_data.fork - if fork.bpo_fork() and fork != fork.non_bpo_ancestor(): - # Only send this information for BPO forks. - # TODO: This should be optimized by the t8n tool instead. - t8n_args.append("--input.blobParams=stdin") - - if self.supports_opcode_count: - t8n_args.append("--opcode.count=stdout") + tracers = None if self.trace: - t8n_args.extend( - [ - "--trace", - "--trace.memory", - "--trace.returndata", - ] + # TODO: Eip3155 traces still round-trip through tempfile + # JSON — the tracer writes one ``trace-<i>.jsonl`` per tx + # to ``output_basedir`` and ``collect_traces`` reads them + # back. Same JSON round-trip we eliminated for alloc / + # result / body; a follow-up should wire the tracer + # output through memory like the rest of the in-process + # path. + tracers = GroupTracer() + tracers.add( + Eip3155Tracer( + trace_memory=True, + trace_stack=True, + trace_return_data=True, + output_basedir=temp_dir.name, + ) ) - parser = create_parser() - t8n_options = parser.parse_args(t8n_args) - - out_stream = StringIO() - - in_stream = StringIO(json.dumps(request_data_json["input"])) - - t8n = T8N(t8n_options, out_stream, in_stream, self.fork_cache) - t8n.run() - - output_dict = json.loads(out_stream.getvalue()) + count_tracer = None + if self.supports_opcode_count: + count_tracer = CountTracer() + if tracers is None: + tracers = GroupTracer() + tracers.add(count_tracer) + + t8n = T8N( + transition_tool_data, + cache=self.fork_cache, + tracers=tracers, + exception_mapper=self.exception_mapper, + ) + output = t8n.run() - if "opcodeCount" in output_dict and "result" in output_dict: - output_dict["result"]["opcodeCount"] = output_dict.pop( - "opcodeCount" + if count_tracer is not None: + output.result.opcode_count = OpcodeCount.model_validate( + count_tracer.results() ) - output: TransitionToolOutput = TransitionToolOutput.model_validate( - output_dict, context={"exception_mapper": self.exception_mapper} - ) - if debug_output_path: dump_files_to_directory( debug_output_path, { - "input/alloc.json": request_data.input.alloc, - "input/env.json": request_data.input.env, + "input/alloc.json": transition_tool_data.alloc, + "input/env.json": transition_tool_data.env, "input/txs.json": [ tx.model_dump(mode="json", **model_dump_config) - for tx in request_data.input.txs + for tx in transition_tool_data.txs ], }, ) diff --git a/packages/testing/src/execution_testing/client_clis/file_utils.py b/packages/testing/src/execution_testing/client_clis/file_utils.py index 47c1232dfbc..190700b1d58 100644 --- a/packages/testing/src/execution_testing/client_clis/file_utils.py +++ b/packages/testing/src/execution_testing/client_clis/file_utils.py @@ -1,7 +1,6 @@ """Methods to work with the filesystem and json.""" import os -import shutil import stat from json import dump from pathlib import Path @@ -10,9 +9,7 @@ from pydantic import BaseModel, RootModel from execution_testing.client_clis.cli_types import ( - LazyAllocFile, - LazyAllocJson, - LazyAllocStr, + LazyAlloc, TransitionToolInput, ) @@ -30,28 +27,13 @@ def dump_files_to_directory(output_path: Path, files: Dict[str, Any]) -> None: if rel_path: os.makedirs(output_path / rel_path, exist_ok=True) file_path = output_path / file_rel_path - if ( - isinstance(file_contents, LazyAllocFile) - and Path(file_contents.raw).exists() - ): - shutil.copyfile(file_contents.raw, file_path) - elif isinstance(file_contents, LazyAllocFile): - # Backing temp dir was cleaned up after a previous `.get()` - # (e.g. chained-block t8n on the next block); fall back to - # the cached Alloc so debug dumps still capture the input. - file_path.write_text( - file_contents.get().model_dump_json( - indent=4, exclude_none=True, by_alias=True - ) + if isinstance(file_contents, LazyAlloc): + file_contents.serialize_to_file( + file_path, indent=4, exclude_none=True, by_alias=True ) else: with open(file_path, "w") as f: - if isinstance(file_contents, (LazyAllocStr, LazyAllocJson)): - if isinstance(file_contents, LazyAllocJson): - dump(file_contents.raw, f, ensure_ascii=True, indent=4) - else: - f.write(file_contents.raw) - elif isinstance( + if isinstance( file_contents, (BaseModel, RootModel, TransitionToolInput) ): f.write( diff --git a/packages/testing/src/execution_testing/client_clis/tests/test_execution_specs.py b/packages/testing/src/execution_testing/client_clis/tests/test_execution_specs.py index a013da0548f..2d88da5e8c1 100644 --- a/packages/testing/src/execution_testing/client_clis/tests/test_execution_specs.py +++ b/packages/testing/src/execution_testing/client_clis/tests/test_execution_specs.py @@ -183,7 +183,7 @@ def test_evm_t8n( blob_schedule=Berlin.blob_schedule(), ), ) - assert to_json(t8n_output.alloc.get()) == expected.get("alloc") + assert to_json(t8n_output.alloc.materialize()) == expected.get("alloc") t8n_result = to_json(t8n_output.result) if isinstance(default_t8n, ExecutionSpecsTransitionTool): # The expected output was generated with geth, instead of deleting diff --git a/packages/testing/src/execution_testing/client_clis/tests/test_transition_tool.py b/packages/testing/src/execution_testing/client_clis/tests/test_transition_tool.py index 3c7c1b281df..4e7a7e82ff3 100644 --- a/packages/testing/src/execution_testing/client_clis/tests/test_transition_tool.py +++ b/packages/testing/src/execution_testing/client_clis/tests/test_transition_tool.py @@ -133,7 +133,7 @@ def test_unknown_binary_path() -> None: def test_lazy_alloc(ty: Type[LazyAlloc], raw: Any) -> None: """Test LazyAlloc types.""" lazy_instance = ty(raw=raw, _state_root=TEST_ALLOC_STATE_ROOT) - assert lazy_instance.get() == TEST_ALLOC + assert lazy_instance.materialize() == TEST_ALLOC assert lazy_instance.state_root() == TEST_ALLOC_STATE_ROOT @@ -144,7 +144,7 @@ def test_lazy_alloc_file(tmp_path: Path) -> None: lazy_instance = LazyAllocFile( raw=alloc_path, _state_root=TEST_ALLOC_STATE_ROOT ) - assert lazy_instance.get() == TEST_ALLOC + assert lazy_instance.materialize() == TEST_ALLOC assert lazy_instance.state_root() == TEST_ALLOC_STATE_ROOT @@ -169,7 +169,7 @@ def test_lazy_alloc_file_handles_mixed_entries(tmp_path: Path) -> None: alloc_path = tmp_path / "alloc.json" alloc_path.write_text(alloc.model_dump_json()) lazy_instance = LazyAllocFile(raw=alloc_path, _state_root=state_root) - assert lazy_instance.get() == alloc + assert lazy_instance.materialize() == alloc assert lazy_instance.state_root() == state_root @@ -202,7 +202,7 @@ def test_model_validate_files_uses_lazy_alloc_file(tmp_path: Path) -> None: assert isinstance(output.alloc, LazyAllocFile) assert output.alloc.raw == alloc_path - assert output.alloc.get() == TEST_ALLOC + assert output.alloc.materialize() == TEST_ALLOC def test_transition_tool_input_serializes_lazy_alloc_file( @@ -241,7 +241,7 @@ def test_to_files_copies_chained_lazy_alloc_file_without_serialize( """ Chained-block handoff: `to_files` should copy the backing alloc file byte-for-byte rather than round-tripping through - `LazyAllocFile.get().model_dump_json()`. Verified by populating the + `LazyAllocFile.materialize().model_dump_json()`. Verified by populating the file with bytes that don't match what pydantic would re-emit and asserting those exact bytes survive the dump. """ @@ -313,7 +313,7 @@ def test_lazy_alloc_file_keepalive_pins_temp_dir() -> None: # Releasing our handle leaves the file alive via the keepalive on lazy. del keep assert alloc_path.exists() - assert lazy.get() == TEST_ALLOC + assert lazy.materialize() == TEST_ALLOC # Dropping the LazyAllocFile drops the keepalive; TemporaryDirectory's # finalizer wipes the directory. PyPy doesn't refcount, so trigger GC @@ -351,10 +351,10 @@ def test_dump_files_to_directory_lazy_alloc_file_after_backing_removed( ) -> None: """ On chained blocks, the previous block's t8n temp dir is cleaned up after - its alloc is materialized via ``.get()``. The resulting ``LazyAllocFile`` - still carries a now-stale ``.raw`` path. Debug dumps must fall back to - re-serializing the cached ``Alloc`` instead of attempting to copy the - missing backing file. + its alloc is materialized via ``.materialize()``. The resulting + ``LazyAllocFile`` still carries a now-stale ``.raw`` path. Debug dumps must + fall back to re-serializing the cached ``Alloc`` instead of attempting to + copy the missing backing file. """ from execution_testing.client_clis.file_utils import ( dump_files_to_directory, @@ -363,7 +363,7 @@ def test_dump_files_to_directory_lazy_alloc_file_after_backing_removed( source = tmp_path / "source_alloc.json" source.write_text(TEST_ALLOC.model_dump_json()) lazy = LazyAllocFile(raw=source, _state_root=TEST_ALLOC_STATE_ROOT) - lazy.get() + lazy.materialize() source.unlink() dump_dir = tmp_path / "dump" @@ -397,7 +397,7 @@ def test_lazy_alloc_file_malformed_json_raises( lazy = LazyAllocFile(raw=alloc_path, _state_root=TEST_ALLOC_STATE_ROOT) with pytest.raises(ijson.common.IncompleteJSONError): - lazy.get() + lazy.materialize() @pytest.mark.parametrize( @@ -422,7 +422,7 @@ def test_lazy_alloc_file_non_object_top_level_raises( lazy = LazyAllocFile(raw=alloc_path, _state_root=TEST_ALLOC_STATE_ROOT) with pytest.raises(ValueError, match="Expected JSON object"): - lazy.get() + lazy.materialize() def test_lazy_alloc_file_empty_object_yields_empty_alloc( @@ -436,7 +436,7 @@ def test_lazy_alloc_file_empty_object_yields_empty_alloc( alloc_path.write_bytes(b"{}") lazy = LazyAllocFile(raw=alloc_path, _state_root=TEST_ALLOC_STATE_ROOT) - assert lazy.get() == Alloc.model_validate({}) + assert lazy.materialize() == Alloc.model_validate({}) def _output_with_opcode_count(counts: dict) -> TransitionToolOutput: diff --git a/packages/testing/src/execution_testing/client_clis/transition_tool.py b/packages/testing/src/execution_testing/client_clis/transition_tool.py index c69e53cf382..4417d8f7682 100644 --- a/packages/testing/src/execution_testing/client_clis/transition_tool.py +++ b/packages/testing/src/execution_testing/client_clis/transition_tool.py @@ -169,7 +169,7 @@ def set(self, subkey: int, value: TransitionToolOutput) -> None: # Without this, every cached subcall would retain its own # `output/alloc.json` on disk for the test's lifetime - O(N) for # an N-block chained test. - alloc.get() + alloc.materialize() alloc._keepalive = None self._cache[subkey] = value @@ -671,7 +671,7 @@ def _evaluate_server( dump_files_to_directory( debug_output_path, { - "output/alloc.json": output.alloc.raw, + "output/alloc.json": output.alloc, "output/result.json": output.result, "output/txs.rlp": str(output.body), "response_info.txt": response_info, diff --git a/packages/testing/src/execution_testing/specs/blockchain.py b/packages/testing/src/execution_testing/specs/blockchain.py index 6eea651a26a..09913d04b9b 100644 --- a/packages/testing/src/execution_testing/specs/blockchain.py +++ b/packages/testing/src/execution_testing/specs/blockchain.py @@ -450,7 +450,7 @@ class BuiltBlock(CamelModel): header: FixtureHeader env: Environment - alloc: LazyAlloc + alloc: LazyAlloc | Alloc state_root: Hash txs: List[Transaction] ommers: List[FixtureHeader] @@ -1084,7 +1084,7 @@ def generate_block_data( print_traces(t8n.get_traces()) pprint(transition_tool_output.result) pprint(previous_alloc) - pprint(transition_tool_output.alloc.get()) + pprint(transition_tool_output.alloc.materialize()) raise e if len(rejected_txs) > 0 and block.exception is None: @@ -1179,13 +1179,13 @@ def make_fixture( if block.expected_post_state: self.verify_post_state( t8n, - t8n_state=alloc.get() + t8n_state=alloc.materialize() if isinstance(alloc, LazyAlloc) else alloc, expected_state=block.expected_post_state, ) self.check_exception_test(exception=invalid_blocks > 0) - alloc = alloc.get() if isinstance(alloc, LazyAlloc) else alloc + alloc = alloc.materialize() if isinstance(alloc, LazyAlloc) else alloc self.verify_post_state(t8n, t8n_state=alloc) fixture = BlockchainFixture( fork=self.fork, @@ -1269,7 +1269,7 @@ def make_hive_fixture( if block.expected_post_state: self.verify_post_state( t8n, - t8n_state=alloc.get() + t8n_state=alloc.materialize() if isinstance(alloc, LazyAlloc) else alloc, expected_state=block.expected_post_state, @@ -1283,7 +1283,7 @@ def make_hive_fixture( " The framework should never try to execute this test case." ) - alloc = alloc.get() if isinstance(alloc, LazyAlloc) else alloc + alloc = alloc.materialize() if isinstance(alloc, LazyAlloc) else alloc self.verify_post_state(t8n, t8n_state=alloc) # Create base fixture data, common to all fixture formats diff --git a/packages/testing/src/execution_testing/specs/state.py b/packages/testing/src/execution_testing/specs/state.py index 6e13e07480a..ac050f0c9ba 100644 --- a/packages/testing/src/execution_testing/specs/state.py +++ b/packages/testing/src/execution_testing/specs/state.py @@ -167,7 +167,7 @@ def verify_modified_gas_limit( f"Traces are not equivalent (gas_limit={current_gas_limit})" ) return False - modified_tool_alloc = modified_tool_output.alloc.get() + modified_tool_alloc = modified_tool_output.alloc.materialize() try: self.post.verify_post_alloc(modified_tool_alloc) except Exception as e: @@ -378,7 +378,7 @@ def make_state_test_fixture( ), slow_request=self.is_tx_gas_heavy_test, ) - output_alloc = transition_tool_output.alloc.get() + output_alloc = transition_tool_output.alloc.materialize() try: self.post.verify_post_alloc(output_alloc) @@ -408,7 +408,7 @@ def make_state_test_fixture( self.operation_mode == OpMode.OPTIMIZE_GAS_POST_PROCESSING ) base_tool_output = transition_tool_output - base_tool_alloc = base_tool_output.alloc.get() + base_tool_alloc = base_tool_output.alloc.materialize() base_tool_result = base_tool_output.result assert base_tool_result.traces is not None, "Traces not found." diff --git a/packages/testing/src/execution_testing/test_types/account_types.py b/packages/testing/src/execution_testing/test_types/account_types.py index 349a8a715e0..f0c51791e51 100644 --- a/packages/testing/src/execution_testing/test_types/account_types.py +++ b/packages/testing/src/execution_testing/test_types/account_types.py @@ -1,9 +1,10 @@ """Account-related types for Ethereum tests.""" import json -from dataclasses import dataclass, field +from dataclasses import dataclass from enum import Enum, auto from typing import ( + AbstractSet, Any, Dict, ItemsView, @@ -15,14 +16,20 @@ Tuple, ) -from ethereum_types.bytes import Bytes20 +import ethereum.state as spec_state +from ethereum.crypto.hash import Hash32 +from ethereum.crypto.hash import keccak256 as spec_keccak256 +from ethereum.merkle_patricia_trie import InternalNode +from ethereum_types.bytes import Bytes, Bytes20 from ethereum_types.numeric import U256, Bytes32, Uint +from pydantic import PrivateAttr from spec256k1 import PrivateKey from execution_testing.base_types import ( Account, Address, Hash, + HashInt, Number, Storage, StorageRootType, @@ -34,82 +41,22 @@ NumberConvertible, ) -from .trie import ( - EMPTY_TRIE_ROOT, - FrontierAccount, - Trie, - root, - trie_get, - trie_set, -) from .utils import keccak256 -FrontierAddress = Bytes20 - - -@dataclass -class State: - """Contains all information that is preserved between transactions.""" - - _main_trie: Trie[Bytes20, Optional[FrontierAccount]] = field( - default_factory=lambda: Trie(secured=True, default=None) - ) - _storage_tries: Dict[Bytes20, Trie[Bytes32, U256]] = field( - default_factory=dict - ) - _snapshots: List[ - Tuple[ - Trie[Bytes20, Optional[FrontierAccount]], - Dict[Bytes20, Trie[Bytes32, U256]], - ] - ] = field(default_factory=list) - -def set_account( - state: State, address: Bytes20, account: Optional[FrontierAccount] -) -> None: +class _Phase(Enum): """ - Set the `Account` object at an address. Setting to `None` deletes the - account (but not its storage, see `destroy_account()`). - """ - trie_set(state._main_trie, address, account) - + Lifecycle phase of an `Alloc` instance used as a `PreState`. -def set_storage( - state: State, address: Bytes20, key: Bytes32, value: U256 -) -> None: - """ - Set a value at a storage key on an account. Setting to `U256(0)` deletes - the key. + See `Alloc` for the rules each phase enforces. """ - assert trie_get(state._main_trie, address) is not None - - trie = state._storage_tries.get(address) - if trie is None: - trie = Trie(secured=True, default=U256(0)) - state._storage_tries[address] = trie - trie_set(trie, key, value) - if trie._data == {}: - del state._storage_tries[address] - - -def storage_root(state: State, address: Bytes20) -> Bytes32: - """Calculate the storage root of an account.""" - assert not state._snapshots - if address in state._storage_tries: - return root(state._storage_tries[address]) - else: - return EMPTY_TRIE_ROOT - -def state_root(state: State) -> Bytes32: - """Calculate the state root.""" - assert not state._snapshots - - def get_storage_root(address: Bytes20) -> Bytes32: - return storage_root(state, address) - - return root(state._main_trie, get_storage_root=get_storage_root) + CONSTRUCTION = auto() + """Free mutations on `self.root` are allowed; no cache exists.""" + LIVE = auto() + """Cache built; only `apply_diff` may mutate.""" + FROZEN = auto() + """No mutations are allowed.""" class EOA(Address): @@ -161,7 +108,20 @@ def copy(self) -> Self: class Alloc(BaseAlloc): - """Allocation of accounts in the state, pre and post test execution.""" + """ + Allocation of accounts in the state, pre and post test execution. + + Doubles as a `PreState` provider for the spec's state transition: once + any `PreState` method is called the instance transitions from + `CONSTRUCTION` to `LIVE` (a code-hash → bytes cache is built once) and + further free mutations via `__setitem__`/`__delitem__` are rejected. + The only mutation entry point in `LIVE` is `apply_diff`, which patches + `self.root` and updates the cache in lockstep. `freeze` locks the + allocation for read-only assertion use. + """ + + _phase: _Phase = PrivateAttr(default=_Phase.CONSTRUCTION) + _code_store: Dict[Hash32, Bytes] = PrivateAttr(default_factory=dict) @dataclass(kw_only=True) class UnexpectedAccountError(Exception): @@ -298,6 +258,7 @@ def __setitem__( account: Account | None, ) -> None: """Set account associated with an address.""" + self._require_construction("__setitem__") if not isinstance(address, Address): address = Address(address) self.root[address] = account @@ -306,6 +267,7 @@ def __delitem__( self, address: Address | FixedSizeBytesConvertible ) -> None: """Delete account associated with an address.""" + self._require_construction("__delitem__") if not isinstance(address, Address): address = Address(address) self.root.pop(address, None) @@ -339,34 +301,7 @@ def empty_accounts(self) -> List[Address]: def state_root(self) -> Hash: """Return state root of the allocation.""" - state = State() - for address, account in self.root.items(): - if account is None: - continue - set_account( - state=state, - address=FrontierAddress(address), - account=FrontierAccount( - nonce=Uint(account.nonce) - if account.nonce is not None - else Uint(0), - balance=( - U256(account.balance) - if account.balance is not None - else U256(0) - ), - code=account.code if account.code is not None else b"", - ), - ) - if account.storage is not None: - for key, value in account.storage.root.items(): - set_storage( - state=state, - address=FrontierAddress(address), - key=Bytes32(Hash(key)), - value=U256(value), - ) - return Hash(state_root(state)) + return Hash(spec_state.state_root(self._materialize_state())) def verify_post_alloc(self, got_alloc: "Alloc") -> None: """ @@ -393,6 +328,247 @@ def verify_post_alloc(self, got_alloc: "Alloc") -> None: else: raise Alloc.MissingAccountError(address=address) + # ------------------------------------------------------------------ + # PreState protocol implementation + # ------------------------------------------------------------------ + + def _require_construction(self, operation: str) -> None: + """Reject mutations once the allocation has left construction.""" + if self._phase is not _Phase.CONSTRUCTION: + raise RuntimeError( + f"{operation} not allowed: Alloc is in phase " + f"{self._phase.name}. Mutate via apply_diff during LIVE, " + f"or call freeze() to lock the allocation." + ) + + def _build_cache(self) -> None: + """Populate the code-hash → bytes cache from `self.root`.""" + self._code_store = {spec_state.EMPTY_CODE_HASH: Bytes(b"")} + for account in self.root.values(): + if account is None: + continue + code = bytes(account.code) if account.code else b"" + if not code: + continue + self._code_store[spec_keccak256(code)] = Bytes(code) + + def _ensure_live(self) -> None: + """Transition from `CONSTRUCTION` to `LIVE`, building the cache.""" + if self._phase is _Phase.CONSTRUCTION: + self._build_cache() + self._phase = _Phase.LIVE + + def _materialize_state(self) -> spec_state.State: + """ + Build an in-memory `ethereum.state.State` mirror of `self.root`. + + Used as the trie-backed delegate for + `compute_state_root_and_trie_changes` (a cold, once-per-block call). + The materialized state is not retained. + """ + state = spec_state.State() + for address, account in self.root.items(): + if account is None: + continue + addr = Bytes20(address) + code = bytes(account.code) if account.code else b"" + code_hash = ( + spec_keccak256(code) if code else spec_state.EMPTY_CODE_HASH + ) + spec_state.set_account( + state, + addr, + spec_state.Account( + nonce=Uint(int(account.nonce)), + balance=U256(int(account.balance)), + code_hash=code_hash, + ), + ) + for key_hi, value_hi in account.storage.root.items(): + value_int = int(value_hi) + if value_int == 0: + continue + spec_state.set_storage( + state, + addr, + Bytes32(int(key_hi).to_bytes(32, "big")), + U256(value_int), + ) + state._code_store.update(self._code_store) + return state + + def get_account_optional( + self, address: Bytes20 + ) -> Optional[spec_state.Account]: + """ + Return the spec-side `Account` at `address`, or `None`. + + Conforms to `ethereum.state.PreState.get_account_optional`. + """ + self._ensure_live() + account = self.root.get(Address(address)) + if account is None: + return None + code = bytes(account.code) if account.code else b"" + code_hash = ( + spec_keccak256(code) if code else spec_state.EMPTY_CODE_HASH + ) + return spec_state.Account( + nonce=Uint(int(account.nonce)), + balance=U256(int(account.balance)), + code_hash=code_hash, + ) + + def get_storage(self, address: Bytes20, key: Bytes32) -> U256: + """ + Return the storage value at `key` for `address`, or `U256(0)`. + + Conforms to `ethereum.state.PreState.get_storage`. + """ + self._ensure_live() + account = self.root.get(Address(address)) + if account is None: + return U256(0) + key_int = int.from_bytes(bytes(key), "big") + value_hi = account.storage.root.get(HashInt(key_int)) + if value_hi is None: + return U256(0) + return U256(int(value_hi)) + + def get_code(self, code_hash: Hash32) -> Bytes: + """ + Return the bytecode for `code_hash`. + + Conforms to `ethereum.state.PreState.get_code`. + """ + self._ensure_live() + if code_hash == spec_state.EMPTY_CODE_HASH: + return Bytes(b"") + return self._code_store[code_hash] + + def account_has_storage(self, address: Bytes20) -> bool: + """ + Return whether the account at `address` has any storage slots set. + + Conforms to `ethereum.state.PreState.account_has_storage`. + """ + self._ensure_live() + account = self.root.get(Address(address)) + return account is not None and bool(account.storage.root) + + def compute_state_root_and_trie_changes( + self, + account_changes: Dict[Bytes20, Optional[spec_state.Account]], + storage_changes: Dict[Bytes20, Dict[Bytes32, U256]], + storage_clears: AbstractSet[Bytes20] = frozenset(), + ) -> Tuple[Hash32, List["InternalNode"]]: + """ + Compute the state root after applying `*_changes` to the pre-state. + + Conforms to + `ethereum.state.PreState.compute_state_root_and_trie_changes`. + Builds the trie inline; `Alloc` does not cache `Trie` instances. + """ + self._ensure_live() + state = self._materialize_state() + return state.compute_state_root_and_trie_changes( + account_changes, storage_changes, storage_clears + ) + + # ------------------------------------------------------------------ + # Lifecycle: apply_diff and freeze + # ------------------------------------------------------------------ + + def apply_diff(self, diff: spec_state.BlockDiff) -> None: + """ + Apply a `BlockDiff` to mutate the allocation in place. + + The only mutation entry point in the `LIVE` phase. Writes bypass + `__setitem__` intentionally — `_code_store` is updated additively + in lockstep with `self.root`. + """ + if self._phase is _Phase.FROZEN: + raise RuntimeError("apply_diff not allowed: Alloc is FROZEN") + if self._phase is _Phase.CONSTRUCTION: + raise RuntimeError( + "apply_diff not allowed in CONSTRUCTION: the allocation " + "has not been used as a PreState yet, so its cache is not " + "built. Trigger a PreState method (or hand it to a " + "BlockState) before calling apply_diff." + ) + + for code_hash, code in diff.code_changes.items(): + self._code_store[Hash32(code_hash)] = Bytes(code) + + for address in diff.storage_clears: + addr = Address(address) + current = self.root.get(addr) + if current is not None and current.storage.root: + self.root[addr] = current.model_copy( + update={"storage": Storage(root={})} + ) + + for address, spec_account in diff.account_changes.items(): + addr = Address(address) + if spec_account is None: + self.root.pop(addr, None) + continue + code_hash = Hash32(spec_account.code_hash) + if code_hash == spec_state.EMPTY_CODE_HASH: + code = Bytes(b"") + else: + code = self._code_store[code_hash] + existing = self.root.get(addr) + existing_storage = ( + existing.storage if existing is not None else Storage(root={}) + ) + self.root[addr] = Account( + nonce=int(spec_account.nonce), + balance=int(spec_account.balance), + code=code, + storage=existing_storage, + ) + + for address, slots in diff.storage_changes.items(): + addr = Address(address) + account = self.root.get(addr) + if account is None: + continue + merged: Dict[HashInt, HashInt] = dict(account.storage.root) + for key, value in slots.items(): + key_int = HashInt(int.from_bytes(bytes(key), "big")) + value_int = int(value) + if value_int == 0: + merged.pop(key_int, None) + else: + merged[key_int] = HashInt(value_int) + self.root[addr] = account.model_copy( + update={"storage": Storage(root=merged)} + ) + + # Drop zero-valued storage entries from every account. Ethereum + # treats an absent slot as zero, so a literal ``{0x00: 0x00}`` + # pair carried over untouched from the pre-state JSON would + # otherwise survive into the post-state dump and produce noise + # the spec-state-backed pipeline never had (the spec's + # ``set_storage`` drops zeros on insert). + for addr, account in list(self.root.items()): + if account is None or not account.storage.root: + continue + cleaned = { + key: value + for key, value in account.storage.root.items() + if int(value) != 0 + } + if len(cleaned) != len(account.storage.root): + self.root[addr] = account.model_copy( + update={"storage": Storage(root=cleaned)} + ) + + def freeze(self) -> None: + """Lock the allocation: no further mutations allowed.""" + self._phase = _Phase.FROZEN + def deterministic_deploy_contract( self, *, diff --git a/packages/testing/src/execution_testing/test_types/tests/test_alloc_prestate.py b/packages/testing/src/execution_testing/test_types/tests/test_alloc_prestate.py new file mode 100644 index 00000000000..30cb39af3d6 --- /dev/null +++ b/packages/testing/src/execution_testing/test_types/tests/test_alloc_prestate.py @@ -0,0 +1,277 @@ +""" +Unit tests for `Alloc` acting as a `PreState` provider. + +Covers four invariants of the lifecycle phase machinery: + 1. The code-hash → bytes cache is built correctly when the alloc goes + LIVE, and the PreState read methods agree with the source dict. + 2. `compute_state_root_and_trie_changes` on `Alloc` matches the same + call on a freshly built `ethereum.state.State` over the same data. + 3. Mutating an `Alloc` via `__setitem__`/`__delitem__` is rejected + after it has been used as a PreState. + 4. Building alloc B by `apply_diff`ing a diff onto alloc A produces a + post-state whose root matches an alloc independently constructed + to look like the post-state. +""" + +from typing import Dict, Optional + +import ethereum.state as spec_state +import pytest +from ethereum.crypto.hash import keccak256 +from ethereum_types.bytes import Bytes20, Bytes32 +from ethereum_types.numeric import U256, Uint + +from execution_testing.base_types import Account +from execution_testing.test_types import Alloc +from execution_testing.test_types.account_types import _Phase + + +def _b20(hex_str: str) -> Bytes20: + """Build a `Bytes20` address from a 40-char hex string (no `0x`).""" + return Bytes20(bytes.fromhex(hex_str)) + + +# A small, fixed set of addresses for ergonomic reuse in tests. They are +# `Bytes20` so they satisfy the `PreState` protocol's address parameter +# type, and pydantic re-validates them into `Address` when used as `Alloc` +# keys. +ADDR_A = _b20("000000000000000000000000000000000000aaaa") +ADDR_B = _b20("000000000000000000000000000000000000bbbb") +ADDR_C = _b20("000000000000000000000000000000000000cccc") +ADDR_MISSING = _b20("dead000000000000000000000000000000000000") +CODE = bytes.fromhex("60016002") # PUSH1 1 PUSH1 2 + + +def _fixture_alloc() -> Alloc: + """Build a small alloc with one EOA, one contract, and one empty acct.""" + return Alloc.model_validate( + { + ADDR_A: {"balance": 100, "nonce": 1}, + ADDR_B: { + "balance": 7, + "nonce": 3, + "code": "0x" + CODE.hex(), + "storage": {1: 0x42, 2: 0xCAFE}, + }, + ADDR_C: {"balance": 0, "nonce": 0}, + } + ) + + +def _state_from_alloc(alloc: Alloc) -> spec_state.State: + """Build a spec `State` mirroring `alloc` for parity comparisons.""" + state = spec_state.State() + for address, account in alloc.root.items(): + if account is None: + continue + addr = Bytes20(address) + code = bytes(account.code) if account.code else b"" + code_hash = keccak256(code) if code else spec_state.EMPTY_CODE_HASH + spec_state.set_account( + state, + addr, + spec_state.Account( + nonce=Uint(int(account.nonce)), + balance=U256(int(account.balance)), + code_hash=code_hash, + ), + ) + if code: + state._code_store[code_hash] = code + for key_hi, value_hi in account.storage.root.items(): + if int(value_hi) == 0: + continue + spec_state.set_storage( + state, + addr, + Bytes32(int(key_hi).to_bytes(32, "big")), + U256(int(value_hi)), + ) + return state + + +def test_cache_build_and_read_methods_agree_with_source() -> None: + """PreState reads on the alloc agree with the source dict.""" + alloc = _fixture_alloc() + assert alloc._phase is _Phase.CONSTRUCTION + + # The first PreState call must transition the alloc to LIVE. + acct_b = alloc.get_account_optional(ADDR_B) + assert alloc._phase is _Phase.LIVE + assert acct_b is not None + assert acct_b.nonce == Uint(3) + assert acct_b.balance == U256(7) + assert acct_b.code_hash == keccak256(CODE) + + # _code_store contains the empty hash and the only contract's code. + assert alloc._code_store[spec_state.EMPTY_CODE_HASH] == b"" + assert alloc._code_store[keccak256(CODE)] == CODE + # EOA + empty account contribute no code entries. + assert len(alloc._code_store) == 2 + + # Storage reads agree with the source for set and unset keys. + assert alloc.get_storage(ADDR_B, Bytes32(b"\x00" * 31 + b"\x01")) == U256( + 0x42 + ) + assert alloc.get_storage(ADDR_B, Bytes32(b"\x00" * 31 + b"\x02")) == U256( + 0xCAFE + ) + assert alloc.get_storage(ADDR_B, Bytes32(b"\x00" * 31 + b"\x03")) == U256( + 0 + ) + # Account with no storage returns zero for any key. + assert alloc.get_storage(ADDR_A, Bytes32(b"\x00" * 32)) == U256(0) + # Missing account returns zero. + assert alloc.get_storage(ADDR_MISSING, Bytes32(b"\x00" * 32)) == U256(0) + + # get_code round-trips, including the empty-code sentinel. + assert alloc.get_code(spec_state.EMPTY_CODE_HASH) == b"" + assert alloc.get_code(keccak256(CODE)) == CODE + + # account_has_storage distinguishes the contract from EOAs. + assert alloc.account_has_storage(ADDR_B) is True + assert alloc.account_has_storage(ADDR_A) is False + assert alloc.account_has_storage(ADDR_MISSING) is False + + # Missing accounts return None from get_account_optional. + assert alloc.get_account_optional(ADDR_MISSING) is None + + +def test_state_root_parity_against_spec_state() -> None: + """`Alloc.compute_state_root_and_trie_changes` matches spec `State`.""" + alloc = _fixture_alloc() + state = _state_from_alloc(alloc) + + alloc_root, _ = alloc.compute_state_root_and_trie_changes({}, {}) + spec_root, _ = state.compute_state_root_and_trie_changes({}, {}) + assert alloc_root == spec_root + + # Same parity under non-trivial change sets. + account_changes: Dict[Bytes20, Optional[spec_state.Account]] = { + ADDR_A: spec_state.Account( + nonce=Uint(2), balance=U256(200), code_hash=keccak256(CODE) + ), + } + storage_changes: Dict[Bytes20, Dict[Bytes32, U256]] = { + ADDR_B: {Bytes32(b"\x00" * 31 + b"\x01"): U256(0x99)}, + } + alloc_root_changed, _ = alloc.compute_state_root_and_trie_changes( + account_changes, storage_changes + ) + spec_root_changed, _ = state.compute_state_root_and_trie_changes( + account_changes, storage_changes + ) + assert alloc_root_changed == spec_root_changed + assert alloc_root_changed != alloc_root + + +def test_phase_guard_rejects_mutation_after_live() -> None: + """`__setitem__` and `__delitem__` raise once the alloc is LIVE.""" + alloc = _fixture_alloc() + # Still in CONSTRUCTION — mutations are allowed. + alloc[_b20("000000000000000000000000000000000000dddd")] = Account( + balance=1 + ) + + # Any PreState read transitions to LIVE. + _ = alloc.get_account_optional(ADDR_A) + assert alloc._phase is _Phase.LIVE + + with pytest.raises(RuntimeError, match="not allowed"): + alloc[_b20("000000000000000000000000000000000000eeee")] = Account( + balance=1 + ) + + with pytest.raises(RuntimeError, match="not allowed"): + del alloc[ADDR_A] + + # freeze() locks further mutation including apply_diff. + alloc.freeze() + with pytest.raises(RuntimeError, match="FROZEN"): + alloc.apply_diff( + spec_state.BlockDiff( + account_changes={}, storage_changes={}, code_changes={} + ) + ) + + +def test_apply_diff_round_trip_matches_independent_post_state() -> None: + """A.apply_diff(diff) reproduces an independently built post-state.""" + new_code = bytes.fromhex("6005600555") # arbitrary, distinct from CODE + new_code_hash = keccak256(new_code) + + alloc_pre = _fixture_alloc() + + # Independently build the expected post-state: + # - ADDR_A: nonce 1 → 2, balance 100 → 50 + # - ADDR_B: keeps account, storage slot 1 cleared, slot 3 added, + # slot 2 left alone + # - ADDR_C: deleted + # - new ADDR_NEW: brand-new contract with `new_code` and a slot set + addr_new = _b20("000000000000000000000000000000000000ffff") + alloc_post_expected = Alloc.model_validate( + { + ADDR_A: {"balance": 50, "nonce": 2}, + ADDR_B: { + "balance": 7, + "nonce": 3, + "code": "0x" + CODE.hex(), + "storage": {2: 0xCAFE, 3: 0x77}, + }, + addr_new: { + "balance": 1, + "nonce": 1, + "code": "0x" + new_code.hex(), + "storage": {0: 0x11}, + }, + } + ) + + # Build the diff that, applied to alloc_pre, should produce + # alloc_post_expected. + diff = spec_state.BlockDiff( + account_changes={ + ADDR_A: spec_state.Account( + nonce=Uint(2), + balance=U256(50), + code_hash=spec_state.EMPTY_CODE_HASH, + ), + ADDR_C: None, + addr_new: spec_state.Account( + nonce=Uint(1), + balance=U256(1), + code_hash=new_code_hash, + ), + }, + storage_changes={ + ADDR_B: { + Bytes32(b"\x00" * 31 + b"\x01"): U256(0), + Bytes32(b"\x00" * 31 + b"\x03"): U256(0x77), + }, + addr_new: {Bytes32(b"\x00" * 32): U256(0x11)}, + }, + code_changes={new_code_hash: new_code}, + ) + + # Force LIVE so apply_diff is allowed. + _ = alloc_pre.get_account_optional(ADDR_A) + alloc_pre.apply_diff(diff) + + # State roots should match. + pre_root, _ = alloc_pre.compute_state_root_and_trie_changes({}, {}) + expected_root, _ = alloc_post_expected.compute_state_root_and_trie_changes( + {}, {} + ) + assert pre_root == expected_root + + # Cache must be updated additively with the new code. + assert alloc_pre._code_store[new_code_hash] == new_code + # The contract's pre-existing code is still cached too. + assert alloc_pre._code_store[keccak256(CODE)] == CODE + + # apply_diff is still allowed (alloc stays LIVE) for the next block. + alloc_pre.apply_diff( + spec_state.BlockDiff( + account_changes={}, storage_changes={}, code_changes={} + ) + ) diff --git a/packages/testing/src/execution_testing/test_types/trie.py b/packages/testing/src/execution_testing/test_types/trie.py deleted file mode 100644 index aec7206697e..00000000000 --- a/packages/testing/src/execution_testing/test_types/trie.py +++ /dev/null @@ -1,401 +0,0 @@ -""" -The state trie is the structure responsible for storing Ethereum state. -""" - -import copy -from dataclasses import dataclass, field -from typing import ( - Callable, - Dict, - Generic, - List, - Mapping, - MutableMapping, - Optional, - Sequence, - Tuple, - TypeVar, - cast, -) - -from ethereum_rlp import Extended, rlp -from ethereum_types.bytes import Bytes, Bytes20, Bytes32 -from ethereum_types.frozen import slotted_freezable -from ethereum_types.numeric import U256, Uint -from typing_extensions import assert_type - - -def keccak256(buffer: bytes | bytearray) -> Bytes32: - """ - Compute the keccak256 hash of ``buffer``. - - The spec implementation is imported lazily so that importing this module - does not import the ``ethereum`` package: on xdist workers that import - would otherwise happen before pytest-cov starts the worker's coverage - session, making coverage report ``ethereum`` as "module-not-measured". - """ - from ethereum.crypto.hash import keccak256 as _keccak256 - - return _keccak256(buffer) - - -@slotted_freezable -@dataclass -class FrontierAccount: - """State associated with an address.""" - - nonce: Uint - balance: U256 - code: Bytes - - -def encode_account( - raw_account_data: FrontierAccount, storage_root: Bytes -) -> Bytes: - """ - Encode `Account` dataclass. - - Storage is not stored in the `Account` dataclass, so `Accounts` cannot be - encoded without providing a storage root. - """ - return rlp.encode( - ( - raw_account_data.nonce, - raw_account_data.balance, - storage_root, - keccak256(raw_account_data.code), - ) - ) - - -# note: an empty trie (regardless of whether it is secured) has root: -# keccak256(RLP(b'')) == -# 56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421 -# also: -# keccak256(RLP(())) == -# 1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347 -# which is the sha3Uncles hash in block header with no uncles -EMPTY_TRIE_ROOT = Bytes32( - bytes.fromhex( - "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" - ) -) - -Node = FrontierAccount | Bytes | Uint | U256 | None -K = TypeVar("K", bound=Bytes) -V = TypeVar( - "V", - Optional[FrontierAccount], - Bytes, - Uint, - U256, -) - - -@slotted_freezable -@dataclass -class LeafNode: - """Leaf node in the Merkle Trie.""" - - rest_of_key: Bytes - value: Extended - - -@slotted_freezable -@dataclass -class ExtensionNode: - """Extension node in the Merkle Trie.""" - - key_segment: Bytes - subnode: Extended - - -BranchSubnodes = Tuple[ - Extended, - Extended, - Extended, - Extended, - Extended, - Extended, - Extended, - Extended, - Extended, - Extended, - Extended, - Extended, - Extended, - Extended, - Extended, - Extended, -] - - -@slotted_freezable -@dataclass -class BranchNode: - """Branch node in the Merkle Trie.""" - - subnodes: BranchSubnodes - value: Extended - - -InternalNode = LeafNode | ExtensionNode | BranchNode - - -def encode_internal_node(node: Optional[InternalNode]) -> Extended: - """ - Encode a Merkle Trie node into its RLP form. - - The RLP will then be serialized into a `Bytes` and hashed unless it is less - that 32 bytes when serialized. - - This function also accepts `None`, representing the absence of a node, - which is encoded to `b""`. - """ - unencoded: Extended - match node: - case None: - unencoded = b"" - case LeafNode(): - unencoded = ( - nibble_list_to_compact(node.rest_of_key, True), - node.value, - ) - case ExtensionNode(): - unencoded = ( - nibble_list_to_compact(node.key_segment, False), - node.subnode, - ) - case BranchNode(): - unencoded = list(node.subnodes) + [node.value] - case _: - raise AssertionError(f"Invalid internal node type {type(node)}!") - - encoded = rlp.encode(unencoded) - if len(encoded) < 32: - return unencoded - else: - return keccak256(encoded) - - -def encode_node(node: Node, storage_root: Optional[Bytes] = None) -> Bytes: - """ - Encode a Node for storage in the Merkle Trie. - - Currently mostly an unimplemented stub. - """ - match node: - case FrontierAccount(): - assert storage_root is not None - return encode_account(node, storage_root) - case U256(): - return rlp.encode(node) - case Bytes(): - return node - case _: - raise AssertionError( - f"encoding for {type(node)} is not currently implemented" - ) - - -@dataclass(slots=True) -class Trie(Generic[K, V]): - """The Merkle Trie.""" - - secured: bool - default: V - _data: Dict[K, V] = field(default_factory=dict) - - -def copy_trie(trie: Trie[K, V]) -> Trie[K, V]: - """ - Create a copy of `trie`. Since only frozen objects may be stored in tries, - the contents are reused. - """ - return Trie(trie.secured, trie.default, copy.copy(trie._data)) - - -def trie_set(trie: Trie[K, V], key: K, value: V) -> None: - """ - Store an item in a Merkle Trie. - - This method deletes the key if `value == trie.default`, because the Merkle - Trie represents the default value by omitting it from the trie. - """ - if value == trie.default: - if key in trie._data: - del trie._data[key] - else: - trie._data[key] = value - - -def trie_get(trie: Trie[K, V], key: K) -> V: - """ - Get an item from the Merkle Trie. - - This method returns `trie.default` if the key is missing. - """ - return trie._data.get(key, trie.default) - - -def common_prefix_length(a: Sequence, b: Sequence) -> int: - """Find the longest common prefix of two sequences.""" - for i in range(len(a)): - if i >= len(b) or a[i] != b[i]: - return i - return len(a) - - -def nibble_list_to_compact(x: Bytes, is_leaf: bool) -> Bytes: - """ - Compresses nibble-list into a standard byte array with a flag. - - A nibble-list is a list of byte values no greater than `15`. The flag is - encoded in high nibble of the highest byte. The flag nibble can be broken - down into two two-bit flags. - - Highest nibble:: - - +---+---+----------+--------+ - | _ | _ | is_leaf | parity | - +---+---+----------+--------+ - 3 2 1 0 - - The lowest bit of the nibble encodes the parity of the length of the - remaining nibbles -- `0` when even and `1` when odd. The second lowest bit - is used to distinguish leaf and extension nodes. The other two bits are not - used. - """ - compact = bytearray() - - if len(x) % 2 == 0: # ie even length - compact.append(16 * (2 * is_leaf)) - for i in range(0, len(x), 2): - compact.append(16 * x[i] + x[i + 1]) - else: - compact.append(16 * ((2 * is_leaf) + 1) + x[0]) - for i in range(1, len(x), 2): - compact.append(16 * x[i] + x[i + 1]) - - return Bytes(compact) - - -def bytes_to_nibble_list(bytes_: Bytes) -> Bytes: - """ - Convert a `Bytes` into to a sequence of nibbles (bytes with value < 16). - """ - nibble_list = bytearray(2 * len(bytes_)) - for byte_index, byte in enumerate(bytes_): - nibble_list[byte_index * 2] = (byte & 0xF0) >> 4 - nibble_list[byte_index * 2 + 1] = byte & 0x0F - return Bytes(nibble_list) - - -def _prepare_trie( - trie: Trie[K, V], - get_storage_root: Optional[Callable[[Bytes20], Bytes32]] = None, -) -> Mapping[Bytes, Bytes]: - """ - Prepare the trie for root calculation. Removes values that are empty, - hashes the keys (if `secured == True`) and encodes all the nodes. - """ - mapped: MutableMapping[Bytes, Bytes] = {} - - for preimage, value in trie._data.items(): - if isinstance(value, FrontierAccount): - assert get_storage_root is not None - address = Bytes20(preimage) - encoded_value = encode_node(value, get_storage_root(address)) - else: - encoded_value = encode_node(value) - if encoded_value == b"": - raise AssertionError - key: Bytes - if trie.secured: - # "secure" tries hash keys once before construction - key = keccak256(preimage) - else: - key = preimage - mapped[bytes_to_nibble_list(key)] = encoded_value - - return mapped - - -def root( - trie: Trie[K, V], - get_storage_root: Optional[Callable[[Bytes20], Bytes32]] = None, -) -> Bytes32: - """Compute the root of a modified merkle patricia trie (MPT).""" - obj = _prepare_trie(trie, get_storage_root) - - root_node = encode_internal_node(patricialize(obj, Uint(0))) - if len(rlp.encode(root_node)) < 32: - return keccak256(rlp.encode(root_node)) - else: - assert isinstance(root_node, Bytes) - return Bytes32(root_node) - - -def patricialize( - obj: Mapping[Bytes, Bytes], level: Uint -) -> Optional[InternalNode]: - """ - Structural composition function. - - Used to recursively patricialize and merkleize a dictionary. Includes - memoization of the tree structure and hashes. - """ - if len(obj) == 0: - return None - - arbitrary_key = next(iter(obj)) - - # if leaf node - if len(obj) == 1: - leaf = LeafNode(arbitrary_key[level:], obj[arbitrary_key]) - return leaf - - # prepare for extension node check by finding max j such that all keys in - # obj have the same key[i:j] - substring = arbitrary_key[level:] - prefix_length = len(substring) - for key in obj: - prefix_length = min( - prefix_length, common_prefix_length(substring, key[level:]) - ) - - # finished searching, found another key at the current level - if prefix_length == 0: - break - - # if extension node - if prefix_length > 0: - prefix = arbitrary_key[int(level) : int(level) + prefix_length] - return ExtensionNode( - prefix, - encode_internal_node( - patricialize(obj, level + Uint(prefix_length)) - ), - ) - - branches: List[MutableMapping[Bytes, Bytes]] = [] - for _ in range(16): - branches.append({}) - value = b"" - for key in obj: - if len(key) == level: - # shouldn't ever have an account or receipt in an internal node - if isinstance(obj[key], (FrontierAccount, Uint)): - raise AssertionError - value = obj[key] - else: - branches[key[level]][key] = obj[key] - - subnodes = tuple( - encode_internal_node(patricialize(branches[k], level + Uint(1))) - for k in range(16) - ) - return BranchNode( - cast(BranchSubnodes, assert_type(subnodes, Tuple[Extended, ...])), - value, - ) diff --git a/src/ethereum_spec_tools/evm_tools/__init__.py b/src/ethereum_spec_tools/evm_tools/__init__.py index 96be3137370..bf854c088cd 100644 --- a/src/ethereum_spec_tools/evm_tools/__init__.py +++ b/src/ethereum_spec_tools/evm_tools/__init__.py @@ -14,7 +14,8 @@ from .b11r import B11R, b11r_arguments from .daemon import Daemon, daemon_arguments from .statetest import StateTest, state_test_arguments -from .t8n import T8N, ForkCache, t8n_arguments +from .t8n import ForkCache +from .t8n.cli import run_t8n_cli, t8n_arguments from .utils import get_supported_forks DESCRIPTION = """ @@ -112,8 +113,7 @@ def main( exit_stack.push(fork_cache) if options.evm_tool == "t8n": - t8n_tool = T8N(options, out_file, in_file, fork_cache) - return t8n_tool.run() + return run_t8n_cli(options, out_file, in_file, fork_cache) elif options.evm_tool == "b11r": b11r_tool = B11R(options, out_file, in_file) return b11r_tool.run() diff --git a/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py b/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py index f9ec92d6ded..fda1bac008b 100644 --- a/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py +++ b/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py @@ -134,11 +134,6 @@ def signing_hash_155(self) -> Any: """signing_hash_155 function of the fork.""" return self._module("transactions").signing_hash_155 - @property - def has_signing_hash_155(self) -> bool: - """Check if the fork has a `signing_hash_155` function.""" - return hasattr(self._module("transactions"), "signing_hash_155") - @property def build_block_access_list(self) -> Any: """build_block_access_list function of the fork.""" @@ -259,14 +254,6 @@ def LegacyTransaction(self) -> Any: """Legacytransaction class of the fork.""" return self._module("transactions").LegacyTransaction - @property - def has_legacy_transaction(self) -> bool: - """ - Return `True` if the fork has a `LegacyTransaction` class, or `False` - otherwise. - """ - return hasattr(self._module("transactions"), "LegacyTransaction") - @property def Access(self) -> Any: """Access class of the fork.""" @@ -316,11 +303,6 @@ def decode_transaction(self) -> Any: """decode_transaction function of the fork.""" return self._module("transactions").decode_transaction - @property - def has_decode_transaction(self) -> bool: - """Check if this fork has a `decode_transaction`.""" - return hasattr(self._module("transactions"), "decode_transaction") - @property def BlockState(self) -> Any: """BlockState class of the fork.""" diff --git a/src/ethereum_spec_tools/evm_tools/statetest/__init__.py b/src/ethereum_spec_tools/evm_tools/statetest/__init__.py index 2f8246829c6..e9b74e62e65 100644 --- a/src/ethereum_spec_tools/evm_tools/statetest/__init__.py +++ b/src/ethereum_spec_tools/evm_tools/statetest/__init__.py @@ -9,14 +9,28 @@ from copy import deepcopy from dataclasses import dataclass from io import StringIO -from typing import Any, Dict, Generator, Iterable, List, Optional, TextIO +from typing import ( + TYPE_CHECKING, + Any, + Dict, + Generator, + Iterable, + List, + Optional, + TextIO, +) from ethereum.utils.hexadecimal import hex_to_bytes -from ..t8n import T8N, ForkCache -from ..t8n.t8n_types import Result +from ..t8n import ForkCache +from ..t8n.cli import build_t8n_from_cli_options from ..utils import get_supported_forks +if TYPE_CHECKING: + from execution_testing.client_clis.cli_types import ( + Result as TestingResult, + ) + @dataclass class TestCase: @@ -87,7 +101,7 @@ def run_test_case( fork_cache: ForkCache, t8n_extra: Optional[List[str]] = None, output_basedir: Optional[str | TextIO] = None, -) -> Result: +) -> "TestingResult": """ Runs a single general state test. """ @@ -156,7 +170,8 @@ def run_test_case( if output_basedir is not None: t8n_options.output_basedir = output_basedir - t8n = T8N(t8n_options, out_stream, in_stream, fork_cache) + del out_stream # statetest reads ``t8n.result`` directly. + t8n = build_t8n_from_cli_options(t8n_options, in_stream, fork_cache) t8n.run_state_test() return t8n.result diff --git a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py b/src/ethereum_spec_tools/evm_tools/t8n/__init__.py index cd449ad9f03..5d263f3fc34 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/__init__.py @@ -1,22 +1,31 @@ """ Create a transition tool for the given fork. + +The ``T8N`` class consumes testing-package pydantic types directly; the +JSON CLI surface lives in :mod:`.cli`. """ -import argparse -import fnmatch -import json -import os from contextlib import AbstractContextManager -from typing import Any, Final, TextIO, Type, TypeVar +from typing import ( + TYPE_CHECKING, + Any, + Dict, + Final, + List, + Optional, + Sequence, + Type, + TypeVar, +) from ethereum_rlp import rlp +from ethereum_types.bytes import Bytes from ethereum_types.numeric import U64, U256, Uint from typing_extensions import override from ethereum import trace from ethereum.exceptions import EthereumException, InvalidBlock from ethereum.fork_criteria import ByBlockNumber, ByTimestamp, Unscheduled -from ethereum.merkle_patricia_trie import copy_trie from ethereum_spec_tools.forks import ( ForkOverrides, Hardfork, @@ -24,74 +33,30 @@ ) from ..loaders.fixture_loader import Load -from ..utils import ( - FatalError, - find_fork, - get_stream_logger, - parse_hex_or_int, -) -from .env import Env -from .evm_trace.count import CountTracer -from .evm_trace.eip3155 import Eip3155Tracer +from ..loaders.transaction_loader import TransactionLoad, UnsupportedTxError +from ..utils import get_stream_logger, resolve_fork +from .block_environment import Ommer, build_block_environment from .evm_trace.group import GroupTracer -from .t8n_types import Alloc, Result, Txs - -T = TypeVar("T") - - -def t8n_arguments(subparsers: argparse._SubParsersAction) -> None: - """ - Adds the arguments for the t8n tool subparser. - """ - t8n_parser = subparsers.add_parser("t8n", help="This is the t8n tool.") +from .result import build_result, record_rejected_tx - t8n_parser.add_argument( - "--input.alloc", dest="input_alloc", type=str, default="alloc.json" +if TYPE_CHECKING: + from execution_testing.client_clis.cli_types import ( + TransitionToolOutput, ) - t8n_parser.add_argument( - "--input.env", dest="input_env", type=str, default="env.json" + from execution_testing.client_clis.transition_tool import ( + TransitionTool, ) - t8n_parser.add_argument( - "--input.txs", dest="input_txs", type=str, default="txs.json" + from execution_testing.exceptions import ExceptionMapper + from execution_testing.test_types import ( + Environment as TestingEnvironment, ) - t8n_parser.add_argument( - "--input.blobParams", - dest="blob_parameters", - type=str, - default=None, + from execution_testing.test_types import ( + Transaction as TestingTransaction, ) - t8n_parser.add_argument( - "--output.alloc", dest="output_alloc", type=str, default="alloc.json" - ) - t8n_parser.add_argument( - "--output.basedir", dest="output_basedir", type=str, default="." - ) - t8n_parser.add_argument("--output.body", dest="output_body", type=str) - t8n_parser.add_argument( - "--output.result", - dest="output_result", - type=str, - default="result.json", - ) - t8n_parser.add_argument( - "--state.chainid", dest="state_chainid", type=int, default=1 - ) - t8n_parser.add_argument( - "--state.fork", dest="state_fork", type=str, default="Frontier" - ) - t8n_parser.add_argument( - "--state.reward", dest="state_reward", type=int, default=None - ) - t8n_parser.add_argument("--trace", action="store_true") - t8n_parser.add_argument("--trace.memory", action="store_true") - t8n_parser.add_argument("--trace.nomemory", action="store_true") - t8n_parser.add_argument("--trace.noreturndata", action="store_true") - t8n_parser.add_argument("--trace.nostack", action="store_true") - t8n_parser.add_argument("--trace.returndata", action="store_true") - t8n_parser.add_argument("--opcode.count", dest="opcode_count", type=str) + TransitionToolData = TransitionTool.TransitionToolData - t8n_parser.add_argument("--state-test", action="store_true") +T = TypeVar("T") class ForkCache(AbstractContextManager): @@ -151,66 +116,80 @@ def get( class T8N(Load): - """The class that carries out the transition.""" + """ + Execute the transition function on already-parsed inputs. + + ``T8N`` is JSON-free: callers hand in a testing + ``TransitionTool.TransitionToolData`` (alloc / env / txs / + blob_schedule / fork / chain_id / reward / state_test) plus any + pre-PoS ommer data, and ``run()`` returns a + :class:`~execution_testing.client_clis.cli_types.TransitionToolOutput`. + See :mod:`.cli` for the JSON wrapper used by the + ``ethereum-spec-evm t8n`` entry point. + """ tracers: Final[GroupTracer | None] + alloc: Any + env: "TestingEnvironment" + txs: List["TestingTransaction"] + ommers: List[Ommer] + rejected_transactions: List[Any] + body: Bytes + state_test: bool + state_reward: int + exception_mapper: Optional["ExceptionMapper"] + _block_exception: Optional[str] def __init__( self, - options: Any, - out_file: TextIO, - in_file: TextIO, + t8n_data: "TransitionToolData", + *, cache: ForkCache, + fork_block: Optional[int] = None, + ommers: Sequence[Ommer] = (), + tracers: Optional[GroupTracer] = None, + exception_mapper: Optional["ExceptionMapper"] = None, ) -> None: - self.out_file = out_file - self.in_file = in_file - self.options = options - forks = Hardfork.discover() - - if "stdin" in ( - options.input_env, - options.input_alloc, - options.input_txs, - options.blob_parameters, + # ``resolve_fork`` only maps the testing fork name to a spec + # ``Hardfork`` module — CLI exception aliases like + # ``HomesteadToDaoAt5`` are unfolded by ``find_fork`` in + # :mod:`.cli` before the testing ``Fork`` is constructed. For + # those transition-fork tests the CLI also reports the block + # number at which the resolved fork activates via + # ``fork_block``; the in-process path leaves it ``None``. + fork_module = resolve_fork(t8n_data.fork_name) + fork_criteria: Optional[ByBlockNumber] = None + if fork_block is not None and fork_block != 0: + fork_criteria = ByBlockNumber(fork_block) + + # Translate ``t8n_data.blob_params`` (testing ``ForkBlobSchedule``) + # into the override arguments ``ForkCache.get`` consumes. + # + # Only forward overrides for BPO forks. BPO forks share their + # non-BPO ancestor's spec module and rely on the override to + # differentiate their blob schedule. Non-BPO forks (Cancun, + # Prague, Amsterdam, …) carry the correct schedule built into + # their spec module — overriding here would force ``ForkCache`` + # to clone the fork into a temporary directory whenever the + # override values don't byte-match the constants, attributing + # all opcode coverage to the clone's ``/tmp/...`` paths instead + # of the original ``src/ethereum/forks/<fork>/`` source. + target_blobs_per_block: Optional[U64] = None + max_blobs_per_block: Optional[U64] = None + base_fee_update_fraction: Optional[Uint] = None + if ( + t8n_data.blob_params is not None + and t8n_data.fork.bpo_fork() + and t8n_data.fork != t8n_data.fork.non_bpo_ancestor() ): - stdin = json.load(in_file) - else: - stdin = None - - fork_module, self.fork_block = find_fork(forks, self.options, stdin) - - fork_criteria = None - if self.fork_block is not None and self.fork_block != 0: - # I can't find where `self.fork_block` is even used, and the vast - # majority of the time it's zero anyway. Not changing the fork - # criteria doesn't seem to break the tests, but changing it - # introduces cloning overhead, so... pretend it didn't happen. - fork_criteria = ByBlockNumber(self.fork_block) - - target_blobs_per_block = None - max_blobs_per_block = None - base_fee_update_fraction = None - - blob_parameters = None - if options.blob_parameters == "stdin": - assert stdin is not None - blob_parameters = stdin["blobParams"] - elif options.blob_parameters is not None: - with open(options.blob_parameters, "r") as f: - blob_parameters = json.load(f) - - if blob_parameters is not None: - target_blobs_per_block = parse_hex_or_int( - blob_parameters["target"], - U64, + target_blobs_per_block = U64( + int(t8n_data.blob_params.target_blobs_per_block) ) - max_blobs_per_block = parse_hex_or_int( - blob_parameters["max"], - U64, + max_blobs_per_block = U64( + int(t8n_data.blob_params.max_blobs_per_block) ) - base_fee_update_fraction = parse_hex_or_int( - blob_parameters["baseFeeUpdateFraction"], - Uint, + base_fee_update_fraction = Uint( + int(t8n_data.blob_params.base_fee_update_fraction) ) fork = cache.get( @@ -221,44 +200,35 @@ def __init__( blob_base_fee_update_fraction=base_fee_update_fraction, ) - tracers = GroupTracer() - - if self.options.trace: - trace_memory = getattr(self.options, "trace.memory", False) - trace_stack = not getattr(self.options, "trace.nostack", False) - trace_return_data = getattr(self.options, "trace.returndata") - tracers.add( - Eip3155Tracer( - trace_memory=trace_memory, - trace_stack=trace_stack, - trace_return_data=trace_return_data, - output_basedir=self.options.output_basedir, - ) - ) - - if self.options.opcode_count is not None: - tracers.add(CountTracer()) - - maybe_tracers: GroupTracer | None - if tracers.tracers: + if tracers is not None: trace.set_evm_trace(tracers) - maybe_tracers = tracers - else: - maybe_tracers = None - - self.tracers = maybe_tracers + self.tracers = tracers self.logger = get_stream_logger("T8N") - super().__init__(fork) - self.chain_id = parse_hex_or_int(self.options.state_chainid, U64) - self.alloc = Alloc(self, stdin) - self.env = Env(self, stdin) - self.txs = Txs(self, stdin) - self.result = Result( - self.env.block_difficulty, self.env.base_fee_per_gas - ) + self.chain_id = U64(t8n_data.chain_id) + self.state_test = t8n_data.state_test + self.state_reward = t8n_data.reward + self.exception_mapper = exception_mapper + + from execution_testing.client_clis.cli_types import LazyAlloc + + # Take a defensive copy of the input alloc so ``apply_diff`` + # (and any other in-place mutation T8N does) never escapes + # into the caller's Python object. Without this, multi-block + # tests that contain an invalid block would observe a mutated + # pre-state — the testing framework expects ``previous_alloc`` + # to remain unchanged when ``block.exception`` is set. + input_alloc = t8n_data.alloc + if isinstance(input_alloc, LazyAlloc): + input_alloc = input_alloc.materialize() + self.alloc = input_alloc.model_copy(deep=True) + self.env = t8n_data.env + self.txs = list(t8n_data.txs) + self.ommers = list(ommers) + self.body = Bytes(rlp.encode([tx.rlp() for tx in self.txs])) + self.rejected_transactions = [] def _tracer(self, type_: Type[T]) -> T: group = self.tracers @@ -271,70 +241,65 @@ def _tracer(self, type_: Type[T]) -> T: def block_environment(self) -> Any: """ - Create the environment for the transaction. The keyword - arguments are adjusted according to the fork. - """ - kw_arguments = { - "block_hashes": self.env.block_hashes, - "coinbase": self.env.coinbase, - "number": self.env.block_number, - "time": self.env.block_timestamp, - "block_gas_limit": self.env.block_gas_limit, - "chain_id": self.chain_id, - } - - block_state = self.fork.BlockState(pre_state=self.alloc.state) - kw_arguments["state"] = block_state - self._block_state = block_state - - block_environment = self.fork.BlockEnvironment - - if self.fork.has_calculate_base_fee_per_gas: - kw_arguments["base_fee_per_gas"] = self.env.base_fee_per_gas - - if self.fork.hardfork.consensus.is_pos(): - kw_arguments["prev_randao"] = self.env.prev_randao - else: - kw_arguments["difficulty"] = self.env.block_difficulty - - if self.fork.has_beacon_roots_address: - kw_arguments["parent_beacon_block_root"] = ( - self.env.parent_beacon_block_root - ) - kw_arguments["excess_blob_gas"] = self.env.excess_blob_gas + Build the fork's ``BlockEnvironment`` for the current block. - if self.fork.has_hash_block_access_list: - kw_arguments["block_access_list_builder"] = ( - self.fork.BlockAccessListBuilder() - ) - if self.fork.has_slot_number: - kw_arguments["slot_number"] = self.env.slot_number - - return block_environment(**kw_arguments) - - def backup_state(self) -> None: - """Back up the state in order to restore in case of an error.""" - state = self.alloc.state - main_trie = copy_trie(state._main_trie) - storage_tries = { - k: copy_trie(t) for (k, t) in state._storage_tries.items() - } - self.alloc.state_backup = ( - main_trie, - storage_tries, - dict(state._code_store), + Side effect: stores the resulting ``BlockState`` on ``self`` so + ``extract_block_diff`` can be called after execution. + """ + block_env = build_block_environment( + fork=self.fork, + env=self.env, + pre_state=self.alloc, + chain_id=self.chain_id, + state_test=self.state_test, ) + self._block_state = block_env.state + return block_env - def restore_state(self) -> None: - """Restore the state from the backup.""" - state = self.alloc.state - state._main_trie = self.alloc.state_backup[0] - state._storage_tries = self.alloc.state_backup[1] - state._code_store = self.alloc.state_backup[2] + def convert_transaction(self, tx: "TestingTransaction") -> Any: + """ + Convert a testing ``Transaction`` into the fork's tx object. + + TODO: Replace with ``self.fork.decode_transaction(tx.rlp())`` + once two pieces land in a follow-up PR: + + 1. Pre-Berlin forks gain a ``decode_transaction``. Pre-Berlin forks + predate typed txs and currently expose no decode entry + point — block decoding produces the legacy class directly. + 2. The testing exception_mapper learns to surface + ``DecodingError`` (raised when a contract-creating typed tx + like ``BlobTransaction`` (``to=None``) reaches + ``decode_transaction``) as the canonical + ``TransactionTypeContractCreationError``. Today + ``TransactionLoad`` constructs the tx object even when its + shape is illegal for the fork, so ``check_transaction`` + inside ``process_transaction`` raises the canonical error. + + Until both are in place, we go through ``TransactionLoad`` + (the JSON loader) which handles both concerns. + """ + raw: Dict[str, Any] = tx.model_dump( + mode="json", by_alias=True, exclude_none=True + ) + # Bridge testing-side aliases (geth-compatible) to the names + # ``TransactionLoad`` expects. + if "input" in raw: + raw.setdefault("data", raw["input"]) + if "gas" in raw: + raw.setdefault("gasLimit", raw["gas"]) + # ``to == None`` is dumped as JSON ``null``; ``TransactionLoad`` + # treats the empty string as the contract-creation sentinel. + if raw.get("to") in (None, "0x"): + raw["to"] = "" + # Ensure the ``type`` field is set so ``TransactionLoad`` + # dispatches to the right tx class (testing's dump uses ``ty`` + # which serializes to ``type`` only on some fork variants). + raw.setdefault("type", "0x" + format(int(tx.ty), "02x")) + return TransactionLoad(raw, self.fork).read() def pay_block_rewards(self, block_reward: U256, block_env: Any) -> None: """Apply the block rewards to the block coinbase.""" - ommer_count = U256(len(self.env.ommers)) + ommer_count = U256(len(self.ommers)) miner_reward = block_reward + ( ommer_count * (block_reward // U256(32)) ) @@ -343,42 +308,63 @@ def pay_block_rewards(self, block_reward: U256, block_env: Any) -> None: self.fork.create_ether(rewards_state, block_env.coinbase, miner_reward) - for ommer in self.env.ommers: - # Ommer age with respect to the current block. - ommer_age = U256(block_env.number - ommer.number) + for ommer in self.ommers: + # ``delta`` is the age of the ommer relative to the current block. + ommer_age = U256(int(ommer.delta, 16)) ommer_miner_reward = ( (U256(8) - ommer_age) * block_reward ) // U256(8) self.fork.create_ether( - rewards_state, ommer.coinbase, ommer_miner_reward + rewards_state, ommer.address, ommer_miner_reward ) self.fork.incorporate_tx_into_block(rewards_state) - def run_state_test(self) -> Any: + def _process_txs(self, block_env: Any, block_output: Any) -> None: + """Execute every transaction in ``self.txs`` against ``block_env``.""" + for tx_index, testing_tx in enumerate(self.txs): + try: + fork_tx = self.convert_transaction(testing_tx) + self.fork.process_transaction( + block_env, block_output, fork_tx, Uint(tx_index) + ) + except (EthereumException, UnsupportedTxError) as e: + # `UnsupportedTxError` covers ``convert_transaction`` + # failures when a typed tx is structurally malformed for + # this fork (e.g. a contract-creating BlobTransaction). + record_rejected_tx(self, tx_index, e) + self.logger.warning(f"Transaction {tx_index} failed: {e!r}") + + def run_state_test(self) -> None: """ Apply a single transaction on pre-state. No system operations are performed. """ - block_env = self.block_environment() - block_output = self.fork.BlockOutput() - self.backup_state() - if len(self.txs.transactions) > 0: - tx = self.txs.transactions[0] + self._block_env = self.block_environment() + self._block_output = self.fork.BlockOutput() + + if len(self.txs) > 0: + testing_tx = self.txs[0] try: + fork_tx = self.convert_transaction(testing_tx) self.fork.process_transaction( - block_env=block_env, - block_output=block_output, - tx=tx, + block_env=self._block_env, + block_output=self._block_output, + tx=fork_tx, index=Uint(0), ) - except EthereumException as e: - self.txs.rejected_txs[0] = f"Failed transaction: {e!r}" - self.restore_state() - self.logger.warning(f"Transaction {0} failed: {str(e)}") - - self.result.update(self, block_env, block_output) - self.result.rejected = self.txs.rejected_txs + except (EthereumException, UnsupportedTxError) as e: + record_rejected_tx(self, 0, e) + self.logger.warning(f"Transaction 0 failed: {e!r}") + + self._block_exception = None + self.result = build_result( + self, + self._block_env, + self._block_output, + self._block_exception, + self.rejected_transactions, + ) def _run_blockchain_test(self, block_env: Any, block_output: Any) -> None: if self.fork.has_compute_requests_hash: @@ -395,45 +381,35 @@ def _run_blockchain_test(self, block_env: Any, block_output: Any) -> None: data=block_env.parent_beacon_block_root, ) - for tx_index, (original_idx, tx) in enumerate( - zip( - self.txs.successfully_parsed, - self.txs.transactions, - strict=True, - ) - ): - self.backup_state() - try: - self.fork.process_transaction( - block_env, block_output, tx, Uint(tx_index) - ) - except EthereumException as e: - self.txs.rejected_txs[original_idx] = ( - f"Failed transaction: {e!r}" - ) - self.restore_state() - self.logger.warning( - f"Transaction {original_idx} failed: {e!r}" - ) + self._process_txs(block_env, block_output) # EIP-7928: Post-execution operations use index N+1 - num_txs = len(self.txs.transactions) if self.fork.has_hash_block_access_list: block_env.block_access_list_builder.block_access_index = ( - self.fork.BlockAccessIndex(Uint(num_txs) + Uint(1)) + self.fork.BlockAccessIndex(Uint(len(self.txs)) + Uint(1)) ) - if not self.fork.proof_of_stake: - if self.options.state_reward is None: - self.pay_block_rewards(self.fork.BLOCK_REWARD, block_env) - elif self.options.state_reward != -1: - self.pay_block_rewards( - U256(self.options.state_reward), block_env - ) + if not self.fork.proof_of_stake and self.state_reward != -1: + # ``-1`` is the sentinel for "skip block rewards entirely" + # (testing-side ``TransitionToolData.__post_init__`` sets + # this for genesis blocks; the CLI wrapper resolves a + # ``--state.reward=None`` to the fork's ``BLOCK_REWARD`` + # before constructing the data). + self.pay_block_rewards(U256(self.state_reward), block_env) if self.fork.has_withdrawal: + withdrawals = self.env.withdrawals or [] + fork_withdrawals = tuple( + self.fork.Withdrawal( + Uint(int(w.index)), + Uint(int(w.validator_index)), + self.fork.hex_to_address(w.address.hex()), + U256(int(w.amount)), + ) + for w in withdrawals + ) self.fork.process_withdrawals( - block_env, block_output, self.env.withdrawals + block_env, block_output, fork_withdrawals ) if self.fork.has_compute_requests_hash: @@ -454,102 +430,57 @@ def run_blockchain_test(self) -> None: """ Apply a block on the pre-state. Also includes system operations. """ - block_env = self.block_environment() - block_output = self.fork.BlockOutput() + self._block_env = self.block_environment() + self._block_output = self.fork.BlockOutput() + self._block_exception = None try: - self._run_blockchain_test(block_env, block_output) + self._run_blockchain_test(self._block_env, self._block_output) except InvalidBlock as e: - self.result.block_exception = f"{e}" - - self.result.update(self, block_env, block_output) - self.result.rejected = self.txs.rejected_txs - - def run(self) -> int: - """Run the transition and provide the relevant outputs.""" - # Clear files that may have been created in a previous - # run of the t8n tool. - # Define the specific files and pattern to delete - files_to_delete = [ - self.options.output_result, - self.options.output_alloc, - self.options.output_body, - ] - pattern_to_delete = "trace-*.jsonl" - - # Iterate through the directory - for file in os.listdir(self.options.output_basedir): - file_path = os.path.join(self.options.output_basedir, file) - - # Check if the file matches the specific names or the pattern - if file in files_to_delete or fnmatch.fnmatch( - file, pattern_to_delete - ): - os.remove(file_path) - - try: - if self.options.state_test: - self.run_state_test() - else: - self.run_blockchain_test() - except FatalError as e: - self.logger.error(str(e)) - return 1 - - json_state = self.alloc.to_json() - json_result = self.result.to_json() - - json_output: dict[str, object] = {} - - if self.options.output_body == "stdout": - txs_rlp = "0x" + rlp.encode(self.txs.all_txs).hex() - json_output["body"] = txs_rlp - elif self.options.output_body is not None: - txs_rlp_path = os.path.join( - self.options.output_basedir, - self.options.output_body, - ) - txs_rlp = "0x" + rlp.encode(self.txs.all_txs).hex() - with open(txs_rlp_path, "w") as f: - json.dump(txs_rlp, f) - self.logger.info(f"Wrote transaction rlp to {txs_rlp_path}") + self._block_exception = f"{e}" + + self.result = build_result( + self, + self._block_env, + self._block_output, + self._block_exception, + self.rejected_transactions, + ) - if self.options.output_alloc == "stdout": - json_output["alloc"] = json_state - else: - alloc_output_path = os.path.join( - self.options.output_basedir, - self.options.output_alloc, - ) - with open(alloc_output_path, "w") as f: - json.dump(json_state, f, indent=4) - self.logger.info(f"Wrote alloc to {alloc_output_path}") + def run(self) -> "TransitionToolOutput": + """ + Execute the transition; return the in-memory result. + + The returned ``TransitionToolOutput`` carries the post-state + ``Alloc`` as a ``MaterializedAlloc`` (already in memory, so + ``get()`` is a no-op), the ``Result`` (state root, receipts, + rejected txs, block exception, …), and the encoded transaction + body as raw RLP bytes. The JSON CLI surface lives in + :func:`.cli.write_t8n_outputs`. + """ + from execution_testing.base_types import Bytes as TestingBytes + from execution_testing.client_clis.cli_types import ( + MaterializedAlloc, + TransitionToolOutput, + ) - if self.options.output_result == "stdout": - json_output["result"] = json_result + if self.state_test: + self.run_state_test() else: - result_output_path = os.path.join( - self.options.output_basedir, - self.options.output_result, - ) - with open(result_output_path, "w") as f: - json.dump(json_result, f, indent=4) - self.logger.info(f"Wrote result to {result_output_path}") - - if self.options.opcode_count == "stdout": - opcode_count_results = self._tracer(CountTracer).results() - json_output["opcodeCount"] = opcode_count_results - elif self.options.opcode_count is not None: - opcode_count_results = self._tracer(CountTracer).results() - result_output_path = os.path.join( - self.options.output_basedir, - self.options.opcode_count, - ) - with open(result_output_path, "w") as f: - json.dump(opcode_count_results, f, indent=4) - self.logger.info(f"Wrote opcode counts to {result_output_path}") - - if json_output: - json.dump(json_output, self.out_file, indent=4) - - return 0 + self.run_blockchain_test() + + # Apply the block diff in place so ``self.alloc`` is the + # post-state when the caller reads it. Safe to do + # unconditionally — ``self.alloc`` is a defensive copy taken + # in ``__init__``, so mutating it never escapes to the caller. + diff = self.fork.extract_block_diff(self._block_state) + self.alloc.apply_diff(diff) + + return TransitionToolOutput( + alloc=MaterializedAlloc( + alloc=self.alloc, + _state_root=self.result.state_root, + ), + result=self.result, + body=TestingBytes(self.body), + ) diff --git a/src/ethereum_spec_tools/evm_tools/t8n/block_environment.py b/src/ethereum_spec_tools/evm_tools/t8n/block_environment.py new file mode 100644 index 00000000000..aab52779c9d --- /dev/null +++ b/src/ethereum_spec_tools/evm_tools/t8n/block_environment.py @@ -0,0 +1,233 @@ +""" +Build the spec's per-fork ``BlockEnvironment`` from a testing-package +``Environment``. +""" + +from dataclasses import dataclass +from typing import TYPE_CHECKING, Any, List, Optional + +from ethereum_rlp import rlp +from ethereum_types.bytes import Bytes8, Bytes20, Bytes32, Bytes256 +from ethereum_types.numeric import U64, U256, Uint + +from ethereum.crypto.hash import Hash32, keccak256 + +if TYPE_CHECKING: + from execution_testing.test_types import Environment as TestingEnvironment + + from ..loaders.fork_loader import ForkLoad + + +@dataclass +class Ommer: + """ + Pre-PoS ommer header summary consumed by `pay_block_rewards`. + + Carries the two fields needed for ommer-reward arithmetic + (`block_number - delta` and the ommer coinbase). The testing + `Environment.ommers` field is `List[Hash]` and cannot represent + these — the JSON CLI fallback populates this from the raw env JSON + instead, and the in-process path leaves it empty (PoS has no ommers). + """ + + delta: str + address: Bytes20 + + +def build_block_environment( + fork: "ForkLoad", + env: "TestingEnvironment", + pre_state: Any, + chain_id: U64, + state_test: bool = False, +) -> Any: + """ + Build the fork's `BlockEnvironment` from a testing `Environment`. + + `pre_state` must satisfy the spec's `PreState` protocol (in + practice, a testing `Alloc`). + """ + block_state = fork.BlockState(pre_state=pre_state) + + block_number = Uint(int(env.number)) + block_gas_limit = Uint(int(env.gas_limit)) + block_timestamp = U256(int(env.timestamp)) + coinbase = Bytes20(env.fee_recipient) + + base_fee_per_gas = _resolve_base_fee_per_gas(env, fork, block_gas_limit) + + kw_arguments: dict[str, Any] = { + "block_hashes": _resolve_block_hashes(env.block_hashes, block_number), + "coinbase": coinbase, + "number": block_number, + "time": block_timestamp, + "block_gas_limit": block_gas_limit, + "chain_id": chain_id, + "state": block_state, + } + + if fork.has_calculate_base_fee_per_gas: + assert base_fee_per_gas is not None + kw_arguments["base_fee_per_gas"] = base_fee_per_gas + + if fork.hardfork.consensus.is_pos(): + kw_arguments["prev_randao"] = _resolve_prev_randao(env) + else: + kw_arguments["difficulty"] = _resolve_block_difficulty( + env, fork, block_number, block_timestamp + ) + + if fork.has_beacon_roots_address: + kw_arguments["parent_beacon_block_root"] = ( + None if state_test else _resolve_parent_beacon_block_root(env) + ) + kw_arguments["excess_blob_gas"] = _resolve_excess_blob_gas(env, fork) + + if fork.has_hash_block_access_list: + kw_arguments["block_access_list_builder"] = ( + fork.BlockAccessListBuilder() + ) + + if fork.has_slot_number: + slot_number = env.slot_number + kw_arguments["slot_number"] = ( + U64(int(slot_number)) if slot_number is not None else None + ) + + return fork.BlockEnvironment(**kw_arguments) + + +def _resolve_base_fee_per_gas( + env: "TestingEnvironment", fork: "ForkLoad", block_gas_limit: Uint +) -> Optional[Uint]: + """Use ``currentBaseFee`` if present; otherwise derive from parent.""" + if not fork.has_calculate_base_fee_per_gas: + return None + if env.base_fee_per_gas is not None: + return Uint(int(env.base_fee_per_gas)) + assert env.parent_gas_limit is not None + assert env.parent_gas_used is not None + assert env.parent_base_fee_per_gas is not None + return fork.calculate_base_fee_per_gas( + block_gas_limit, + Uint(int(env.parent_gas_limit)), + Uint(int(env.parent_gas_used)), + Uint(int(env.parent_base_fee_per_gas)), + ) + + +def _resolve_excess_blob_gas( + env: "TestingEnvironment", + fork: "ForkLoad", +) -> Optional[U64]: + """Use ``currentExcessBlobGas`` if present; else derive from parent.""" + if env.excess_blob_gas is not None: + return U64(int(env.excess_blob_gas)) + + parent_blob_gas_used = U64( + int(env.parent_blob_gas_used) if env.parent_blob_gas_used else 0 + ) + parent_excess_blob_gas = U64( + int(env.parent_excess_blob_gas) if env.parent_excess_blob_gas else 0 + ) + # EIP-7918 reads ``parent.base_fee_per_gas`` from the parent header. + parent_base_fee_per_gas = Uint( + int(env.parent_base_fee_per_gas) + if env.parent_base_fee_per_gas is not None + else 0 + ) + + arguments: dict[str, Any] = { + "parent_hash": Hash32(b"\0" * 32), + "ommers_hash": Hash32(b"\0" * 32), + "coinbase": Bytes20(b"\0" * 20), + "state_root": Hash32(b"\0" * 32), + "transactions_root": Hash32(b"\0" * 32), + "receipt_root": Hash32(b"\0" * 32), + "bloom": Bytes256(b"\0" * 256), + "difficulty": Uint(0), + "number": Uint(0), + "gas_limit": Uint(0), + "gas_used": Uint(0), + "timestamp": U256(0), + "extra_data": b"", + "prev_randao": Bytes32(b"\0" * 32), + "nonce": Bytes8(b"\0" * 8), + "withdrawals_root": Hash32(b"\0" * 32), + "parent_beacon_block_root": Hash32(b"\0" * 32), + "base_fee_per_gas": parent_base_fee_per_gas, + "blob_gas_used": parent_blob_gas_used, + "excess_blob_gas": parent_excess_blob_gas, + } + if fork.has_compute_requests_hash: + arguments["requests_hash"] = Hash32(b"\0" * 32) + if fork.has_hash_block_access_list: + arguments["block_access_list_hash"] = Hash32(b"\0" * 32) + if fork.has_slot_number: + arguments["slot_number"] = U64(0) + + parent_header = fork.Header(**arguments) + return fork.calculate_excess_blob_gas(parent_header) + + +def _resolve_block_difficulty( + env: "TestingEnvironment", + fork: "ForkLoad", + block_number: Uint, + block_timestamp: U256, +) -> Optional[Uint]: + """Use ``currentDifficulty`` if present; otherwise derive from parent.""" + if env.difficulty is not None: + return Uint(int(env.difficulty)) + + assert env.parent_timestamp is not None + assert env.parent_difficulty is not None + args: List[Any] = [ + block_number, + block_timestamp, + U256(int(env.parent_timestamp)), + Uint(int(env.parent_difficulty)), + ] + if fork.calculate_block_difficulty_arity > 4: + empty_ommers_hash = keccak256(rlp.encode([])) + parent_ommers_hash = Hash32(env.parent_ommers_hash) + args.append(parent_ommers_hash != empty_ommers_hash) + return fork.calculate_block_difficulty(*args) + + +def _resolve_prev_randao(env: "TestingEnvironment") -> Bytes32: + """Pad the (numeric) ``prev_randao`` field to 32 bytes.""" + value = env.prev_randao + if value is None: + return Bytes32(b"\0" * 32) + return Bytes32(int(value).to_bytes(32, "big")) + + +def _resolve_block_hashes( + block_hashes: Any, block_number: Uint +) -> List[Optional[Hash32]]: + """ + Return up to the last 256 block hashes preceding ``block_number``. + + `block_hashes` is the testing `Environment.block_hashes` dict keyed by + block number; missing entries become `None` placeholders. + """ + result: List[Optional[Hash32]] = [] + if not block_hashes: + return result + normalized = {int(k): Hash32(v) for k, v in block_hashes.items()} + max_blockhash_count = min(Uint(256), block_number) + for number in range( + int(block_number) - int(max_blockhash_count), int(block_number) + ): + result.append(normalized.get(number)) + return result + + +def _resolve_parent_beacon_block_root( + env: "TestingEnvironment", +) -> Optional[Hash32]: + """Return the parent beacon block root, or ``None`` if absent.""" + if env.parent_beacon_block_root is None: + return None + return Hash32(env.parent_beacon_block_root) diff --git a/src/ethereum_spec_tools/evm_tools/t8n/cli.py b/src/ethereum_spec_tools/evm_tools/t8n/cli.py new file mode 100644 index 00000000000..14fb0e737c3 --- /dev/null +++ b/src/ethereum_spec_tools/evm_tools/t8n/cli.py @@ -0,0 +1,483 @@ +""" +CLI / JSON wrapper for the ``T8N`` transition tool. + +``T8N`` itself consumes a testing-package +``TransitionTool.TransitionToolData`` and knows nothing about argparse, +stdin/stdout, or JSON. This module provides the bridge used by the +``ethereum-spec-evm t8n`` entry point and by ``statetest``: + +* :func:`build_t8n_from_cli_options` reads the JSON inputs + (stdin / files), resolves the fork, parses everything into testing + pydantic types, bundles them into a ``TransitionToolData``, builds + the tracer group, and returns a constructed ``T8N``. +* :func:`write_t8n_outputs` serialises the t8n output + opcode-count + results to disk / stdout per ``--output.*`` flags. +* :func:`run_t8n_cli` chains the two for the CLI entry point. +""" + +import argparse +import fnmatch +import json +import os +from typing import Any, Dict, List, Optional, TextIO, Tuple + +from ethereum_rlp import rlp +from ethereum_types.bytes import Bytes +from ethereum_types.numeric import U64 + +from ethereum_spec_tools.forks import Hardfork + +from ..loaders.fork_loader import ForkLoad +from ..utils import FatalError, find_fork, parse_hex_or_int +from . import T8N, ForkCache +from .block_environment import Ommer +from .evm_trace.count import CountTracer +from .evm_trace.eip3155 import Eip3155Tracer +from .evm_trace.group import GroupTracer + + +def t8n_arguments(subparsers: argparse._SubParsersAction) -> None: + """ + Adds the arguments for the t8n tool subparser. + """ + t8n_parser = subparsers.add_parser("t8n", help="This is the t8n tool.") + + t8n_parser.add_argument( + "--input.alloc", dest="input_alloc", type=str, default="alloc.json" + ) + t8n_parser.add_argument( + "--input.env", dest="input_env", type=str, default="env.json" + ) + t8n_parser.add_argument( + "--input.txs", dest="input_txs", type=str, default="txs.json" + ) + t8n_parser.add_argument( + "--input.blobParams", + dest="blob_parameters", + type=str, + default=None, + ) + t8n_parser.add_argument( + "--output.alloc", dest="output_alloc", type=str, default="alloc.json" + ) + t8n_parser.add_argument( + "--output.basedir", dest="output_basedir", type=str, default="." + ) + t8n_parser.add_argument("--output.body", dest="output_body", type=str) + t8n_parser.add_argument( + "--output.result", + dest="output_result", + type=str, + default="result.json", + ) + t8n_parser.add_argument( + "--state.chainid", dest="state_chainid", type=int, default=1 + ) + t8n_parser.add_argument( + "--state.fork", dest="state_fork", type=str, default="Frontier" + ) + t8n_parser.add_argument( + "--state.reward", dest="state_reward", type=int, default=None + ) + t8n_parser.add_argument("--trace", action="store_true") + t8n_parser.add_argument("--trace.memory", action="store_true") + t8n_parser.add_argument("--trace.nomemory", action="store_true") + t8n_parser.add_argument("--trace.noreturndata", action="store_true") + t8n_parser.add_argument("--trace.nostack", action="store_true") + t8n_parser.add_argument("--trace.returndata", action="store_true") + + t8n_parser.add_argument("--opcode.count", dest="opcode_count", type=str) + + t8n_parser.add_argument("--state-test", action="store_true") + + +def _read_json_input( + path_or_stdin: str, stdin: Optional[Dict], key: str +) -> Any: + """Read one of the t8n JSON inputs (alloc / env / txs).""" + if path_or_stdin == "stdin": + assert stdin is not None + return stdin[key] + with open(path_or_stdin, "r") as f: + return json.load(f) + + +def _parse_ommers_from_env_json(env_json: Any, fork: Any) -> List[Ommer]: + """Parse the pre-PoS ``ommers`` block from a raw env JSON dict.""" + ommers: List[Ommer] = [] + for raw in env_json.get("ommers", []): + ommers.append( + Ommer( + delta=raw["delta"], + address=fork.hex_to_address(raw["address"]), + ) + ) + return ommers + + +def _normalize_tx_json(tx: Dict[str, Any]) -> Dict[str, Any]: + """ + Drop fields that the testing ``Transaction`` model rejects. + + Three boundary mismatches to smooth over: + + 1. ``yParity`` on authorization tuples. The testing + ``AuthorizationTuple`` serializer emits both ``v`` and + ``yParity`` (they are guaranteed equal — see the model's + ``duplicate_v_as_y_parity``), but its validator binds only + ``v`` and treats ``yParity`` as an extra-forbidden field. + 2. ``secretKey`` on an already-signed tx. The testing + ``Transaction`` retains the private key after auto-signing in + ``model_post_init``, so the dump still carries ``secretKey`` + alongside the populated ``v``/``r``/``s``. On re-validation + the model rejects the pair with + ``InvalidSignaturePrivateKeyError``. Strip ``secretKey`` + whenever ``v`` is set (i.e. the tx is already signed). + 3. A tx with no signature material at all. Filled state tests + store a tx whose signature is deliberately invalid without + ``v``/``r``/``s`` or ``secretKey`` (the fixture format cannot + express explicit signature values), expecting the fork to + reject it. Default the components to zero; leaving them unset + would make ``Transaction.rlp`` try to auto-sign a key-less tx + and die on an assertion. + """ + auth_list = tx.get("authorizationList") + if isinstance(auth_list, list): + tx["authorizationList"] = [ + {k: v for k, v in entry.items() if k != "yParity"} + if isinstance(entry, dict) + else entry + for entry in auth_list + ] + if "secretKey" in tx and tx.get("v") is not None: + tx = {k: v for k, v in tx.items() if k != "secretKey"} + if not any( + tx.get(key) is not None + for key in ("secretKey", "v", "yParity", "r", "s") + ): + tx["v"] = "0x00" + tx["r"] = "0x00" + tx["s"] = "0x00" + return tx + + +def _parse_txs_json_to_testing( + raw_txs_json: Any, + fork_module: Hardfork, + transaction_cls: Any, +) -> Tuple[List[Any], Bytes]: + """ + Parse a JSON tx array into signed testing ``Transaction`` objects. + + Unsigned txs carrying only ``secretKey`` are signed in place via + ``Transaction.sign``; pre-Spurious-Dragon forks get + ``protected=False`` so the ``v`` value stays in ``{27, 28}``. + + RLP-string input (a single hex string of an encoded tx list) is + rejected — this path only handles JSON arrays. + """ + if raw_txs_json is None: + return [], Bytes(b"") + if isinstance(raw_txs_json, str): + raise NotImplementedError( + "RLP-encoded `txs` input is not supported by the testing " + "T8N entry point; provide a JSON array instead." + ) + + fork_supports_eip155 = hasattr( + fork_module.module("transactions"), "signing_hash_155" + ) + + normalized = [_normalize_tx_json(dict(tx)) for tx in raw_txs_json] + txs: List[Any] = [] + for tx_dict in normalized: + tx = transaction_cls.model_validate(tx_dict) + if "v" not in tx.model_fields_set and tx.secret_key is not None: + if not fork_supports_eip155 and int(tx.ty) == 0: + tx.protected = False + tx.sign() + txs.append(tx) + body = Bytes(rlp.encode([tx.rlp() for tx in txs])) + return txs, body + + +def _parse_blob_params_from_options( + options: Any, stdin: Optional[Dict] +) -> Any: + """ + Load a testing ``ForkBlobSchedule`` from ``--input.blobParams``. + + Returns ``None`` when the flag is unset. Reads from ``stdin`` + (``"blobParams"`` key) or a file path depending on the flag value. + """ + # Function-scoped: see import-cycle note in ``build_t8n_from_cli_options``. + from execution_testing.base_types.composite_types import ( + ForkBlobSchedule, + ) + + if options.blob_parameters == "stdin": + assert stdin is not None + raw = stdin["blobParams"] + elif options.blob_parameters is not None: + with open(options.blob_parameters, "r") as f: + raw = json.load(f) + else: + return None + return ForkBlobSchedule.model_validate(raw) + + +def _build_tracers_from_options( + options: Any, +) -> Optional[GroupTracer]: + """ + Build the tracer group from CLI ``--trace*`` / ``--opcode.count`` + flags. Returns ``None`` if no tracer would be active. + """ + tracers = GroupTracer() + if options.trace: + trace_memory = getattr(options, "trace.memory", False) + trace_stack = not getattr(options, "trace.nostack", False) + trace_return_data = getattr(options, "trace.returndata") + tracers.add( + Eip3155Tracer( + trace_memory=trace_memory, + trace_stack=trace_stack, + trace_return_data=trace_return_data, + output_basedir=options.output_basedir, + ) + ) + if options.opcode_count is not None: + tracers.add(CountTracer()) + return tracers if tracers.tracers else None + + +# Spec ``Hardfork.title_case_name`` matches the testing-side +# ``Fork.name()`` after stripping spaces, except for a handful of +# legacy outliers where the testing class uses a different +# capitalisation convention. +_TESTING_FORK_NAME_OVERRIDES = { + "DaoFork": "DAOFork", +} + + +def _testing_fork_from_spec_hardfork(hardfork: Hardfork) -> Any: + """Map a spec ``Hardfork`` to the matching testing ``Fork`` class.""" + # Function-scoped: see import-cycle note in ``build_t8n_from_cli_options``. + from execution_testing.forks import get_fork_by_name + + name = hardfork.title_case_name.replace(" ", "") + name = _TESTING_FORK_NAME_OVERRIDES.get(name, name) + fork = get_fork_by_name(name) + if fork is None: + raise ValueError( + f"No testing.Fork class for spec hardfork " + f"{hardfork.short_name!r} (looked for {name!r})" + ) + return fork + + +def _resolve_state_reward( + state_reward: Optional[int], fork_module: Hardfork +) -> int: + """ + Resolve a CLI ``--state.reward`` value into the int that + ``TransitionToolData.reward`` expects. + + ``None`` means "use the fork's default ``BLOCK_REWARD``"; an + explicit ``-1`` means "skip block rewards entirely" (the testing + sentinel); any other int passes through unchanged. + """ + if state_reward is None: + fork_load = ForkLoad(fork_module) + if fork_load.proof_of_stake: + return -1 + return int(fork_load.BLOCK_REWARD) + return state_reward + + +def build_t8n_from_cli_options( + options: Any, + in_file: TextIO, + cache: ForkCache, +) -> T8N: + """ + Construct a ``T8N`` from CLI options + JSON stdin / file inputs. + + Reads ``--input.*`` files (or stdin), validates each piece into + testing pydantic types, bundles them into a ``TransitionToolData``, + builds the tracer group, and hands them to ``T8N``. + """ + # Function-scoped imports: ``execution_testing/__init__`` eagerly + # imports ``.specs`` which transitively imports ``client_clis``, + # which imports ``ExecutionSpecsTransitionTool`` — top-level imports + # from ``execution_testing`` would cycle back into spec-tools. + from execution_testing.base_types.composite_types import BlobSchedule + from execution_testing.client_clis.transition_tool import TransitionTool + from execution_testing.test_types import ( + Alloc as TestingAlloc, + ) + from execution_testing.test_types import ( + Environment as TestingEnvironment, + ) + from execution_testing.test_types import ( + Transaction as TestingTransaction, + ) + + forks = Hardfork.discover() + + if "stdin" in ( + options.input_env, + options.input_alloc, + options.input_txs, + options.blob_parameters, + ): + stdin = json.load(in_file) + else: + stdin = None + + fork_module, fork_block = find_fork(forks, options, stdin) + testing_fork = _testing_fork_from_spec_hardfork(fork_module) + + raw_alloc_json = _read_json_input(options.input_alloc, stdin, "alloc") + raw_env_json = _read_json_input(options.input_env, stdin, "env") + raw_txs_json = _read_json_input(options.input_txs, stdin, "txs") + blob_params = _parse_blob_params_from_options(options, stdin) + + alloc = TestingAlloc.model_validate(raw_alloc_json) + env = TestingEnvironment.model_validate(raw_env_json) + txs, _body = _parse_txs_json_to_testing( + raw_txs_json, fork_module, TestingTransaction + ) + + # Wrap the single per-fork blob schedule into a ``BlobSchedule`` + # collection keyed by fork name (the field TransitionToolData + # expects). + blob_schedule: Any = None + if blob_params is not None: + blob_schedule = BlobSchedule() + blob_schedule.append(fork=testing_fork.name(), schedule=blob_params) + + t8n_data = TransitionTool.TransitionToolData( + alloc=alloc, + env=env, + txs=txs, + fork=testing_fork, + chain_id=int(parse_hex_or_int(options.state_chainid, U64)), + reward=_resolve_state_reward(options.state_reward, fork_module), + blob_schedule=blob_schedule, + state_test=options.state_test, + ) + + # ``Ommer.address`` is parsed via the per-fork ``hex_to_address`` + # helper; construct a temporary ``ForkLoad`` from the resolved + # module just to get the conversion. + fork_load = ForkLoad(fork_module) + ommers = _parse_ommers_from_env_json(raw_env_json, fork_load) + + return T8N( + t8n_data, + cache=cache, + fork_block=fork_block, + ommers=ommers, + tracers=_build_tracers_from_options(options), + ) + + +def write_t8n_outputs( + t8n: T8N, + output: Any, + options: Any, + out_file: TextIO, +) -> None: + """Serialise the t8n output + opcode counts per ``--output.*``.""" + json_state = output.alloc.materialize().model_dump( + mode="json", by_alias=True + ) + json_result = output.result.model_dump( + mode="json", by_alias=True, exclude_none=True + ) + json_output: Dict[str, object] = {} + body_hex = "0x" + bytes(output.body or b"").hex() + + if options.output_body == "stdout": + json_output["body"] = body_hex + elif options.output_body is not None: + txs_rlp_path = os.path.join( + options.output_basedir, options.output_body + ) + with open(txs_rlp_path, "w") as f: + json.dump(body_hex, f) + t8n.logger.info(f"Wrote transaction rlp to {txs_rlp_path}") + + if options.output_alloc == "stdout": + json_output["alloc"] = json_state + else: + alloc_output_path = os.path.join( + options.output_basedir, options.output_alloc + ) + with open(alloc_output_path, "w") as f: + json.dump(json_state, f, indent=4) + t8n.logger.info(f"Wrote alloc to {alloc_output_path}") + + if options.output_result == "stdout": + json_output["result"] = json_result + else: + result_output_path = os.path.join( + options.output_basedir, options.output_result + ) + with open(result_output_path, "w") as f: + json.dump(json_result, f, indent=4) + t8n.logger.info(f"Wrote result to {result_output_path}") + + if options.opcode_count == "stdout": + json_output["opcodeCount"] = t8n._tracer(CountTracer).results() + elif options.opcode_count is not None: + result_output_path = os.path.join( + options.output_basedir, options.opcode_count + ) + with open(result_output_path, "w") as f: + json.dump(t8n._tracer(CountTracer).results(), f, indent=4) + t8n.logger.info(f"Wrote opcode counts to {result_output_path}") + + if json_output: + json.dump(json_output, out_file, indent=4) + + +def _clean_output_dir(options: Any) -> None: + """Remove prior output files matching ``--output.*`` from the basedir.""" + files_to_delete = [ + options.output_result, + options.output_alloc, + options.output_body, + ] + pattern_to_delete = "trace-*.jsonl" + for file in os.listdir(options.output_basedir): + file_path = os.path.join(options.output_basedir, file) + if file in files_to_delete or fnmatch.fnmatch(file, pattern_to_delete): + os.remove(file_path) + + +def run_t8n_cli( + options: Any, + out_file: TextIO, + in_file: TextIO, + cache: ForkCache, +) -> int: + """End-to-end CLI entry: read JSON, run ``T8N``, write JSON output.""" + _clean_output_dir(options) + t8n = build_t8n_from_cli_options(options, in_file, cache) + try: + output = t8n.run() + except FatalError as e: + t8n.logger.error(str(e)) + return 1 + write_t8n_outputs(t8n, output, options, out_file) + return 0 + + +__all__ = [ + "build_t8n_from_cli_options", + "run_t8n_cli", + "t8n_arguments", + "write_t8n_outputs", +] diff --git a/src/ethereum_spec_tools/evm_tools/t8n/env.py b/src/ethereum_spec_tools/evm_tools/t8n/env.py deleted file mode 100644 index edf3763d573..00000000000 --- a/src/ethereum_spec_tools/evm_tools/t8n/env.py +++ /dev/null @@ -1,333 +0,0 @@ -""" -Define t8n Env class. -""" - -import json -from dataclasses import dataclass -from typing import TYPE_CHECKING, Any, Dict, List, Optional - -from ethereum_rlp import rlp -from ethereum_types.bytes import Bytes8, Bytes20, Bytes32, Bytes256 -from ethereum_types.numeric import U64, U256, Uint - -from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.utils.byte import left_pad_zero_bytes -from ethereum.utils.hexadecimal import hex_to_bytes - -from ..utils import parse_hex_or_int - -if TYPE_CHECKING: - from ethereum_spec_tools.evm_tools.t8n import T8N - - -@dataclass -class Ommer: - """The Ommer type for the t8n tool.""" - - delta: str - address: Any - - -class Env: - """ - The environment for the transition tool. - """ - - coinbase: Any - block_gas_limit: Uint - block_number: Uint - block_timestamp: U256 - withdrawals: Any - block_difficulty: Optional[Uint] - prev_randao: Optional[Bytes32] - parent_difficulty: Optional[Uint] - parent_timestamp: Optional[U256] - base_fee_per_gas: Optional[Uint] - parent_gas_used: Optional[Uint] - parent_gas_limit: Optional[Uint] - parent_base_fee_per_gas: Optional[Uint] - block_hashes: Optional[List[Any]] - parent_ommers_hash: Optional[Hash32] - ommers: Any - parent_beacon_block_root: Optional[Hash32] - parent_excess_blob_gas: Optional[U64] - parent_blob_gas_used: Optional[U64] - excess_blob_gas: Optional[U64] - slot_number: Optional[U64] - requests: Any - - def __init__(self, t8n: "T8N", stdin: Optional[Dict] = None): - if t8n.options.input_env == "stdin": - assert stdin is not None - data = stdin["env"] - else: - with open(t8n.options.input_env, "r") as f: - data = json.load(f) - - self.coinbase = t8n.fork.hex_to_address(data["currentCoinbase"]) - self.block_gas_limit = parse_hex_or_int(data["currentGasLimit"], Uint) - self.block_number = parse_hex_or_int(data["currentNumber"], Uint) - self.block_timestamp = parse_hex_or_int(data["currentTimestamp"], U256) - - self.read_block_difficulty(data, t8n) - self.read_base_fee_per_gas(data, t8n) - self.read_randao(data, t8n) - self.read_block_hashes(data) - self.read_ommers(data, t8n) - self.read_withdrawals(data, t8n) - - self.parent_beacon_block_root = None - if t8n.fork.has_beacon_roots_address: - if not t8n.options.state_test: - parent_beacon_block_root_hex = data["parentBeaconBlockRoot"] - self.parent_beacon_block_root = ( - Bytes32(hex_to_bytes(parent_beacon_block_root_hex)) - if parent_beacon_block_root_hex is not None - else None - ) - self.read_excess_blob_gas(data, t8n) - - self.read_slot_number(data, t8n) - - def read_excess_blob_gas(self, data: Any, t8n: "T8N") -> None: - """ - Read the excess_blob_gas from the data. If the excess blob gas is - not present, it is calculated from the parent block parameters. - """ - self.parent_blob_gas_used = U64(0) - self.parent_excess_blob_gas = U64(0) - self.excess_blob_gas = None - - if not t8n.fork.has_beacon_roots_address: - return - - if "parentExcessBlobGas" in data: - self.parent_excess_blob_gas = parse_hex_or_int( - data["parentExcessBlobGas"], U64 - ) - - if "parentBlobGasUsed" in data: - self.parent_blob_gas_used = parse_hex_or_int( - data["parentBlobGasUsed"], U64 - ) - - if "currentExcessBlobGas" in data: - self.excess_blob_gas = parse_hex_or_int( - data["currentExcessBlobGas"], U64 - ) - return - - assert self.parent_excess_blob_gas is not None - assert self.parent_blob_gas_used is not None - - arguments = { - # Useless as far as calculate_excess_blob_gas is concerned. - "parent_hash": Hash32(b"\0" * 32), - "ommers_hash": Hash32(b"\0" * 32), - "coinbase": Bytes20(b"\0" * 20), - "state_root": Hash32(b"\0" * 32), - "transactions_root": Hash32(b"\0" * 32), - "receipt_root": Hash32(b"\0" * 32), - "bloom": Bytes256(b"\0" * 256), - "difficulty": Uint(0), - "number": Uint(0), - "gas_limit": Uint(0), - "gas_used": Uint(0), - "timestamp": U256(0), - "extra_data": b"", - "prev_randao": Bytes32(b"\0" * 32), - "nonce": Bytes8(b"\0" * 8), - "withdrawals_root": Hash32(b"\0" * 32), - "parent_beacon_block_root": Hash32(b"\0" * 32), - # Used for calculating excess_blob_gas. - "base_fee_per_gas": self.parent_base_fee_per_gas, - "blob_gas_used": self.parent_blob_gas_used, - "excess_blob_gas": self.parent_excess_blob_gas, - } - - if t8n.fork.has_compute_requests_hash: - arguments["requests_hash"] = Hash32(b"\0" * 32) - - if t8n.fork.has_hash_block_access_list: - arguments["block_access_list_hash"] = Hash32(b"\0" * 32) - if t8n.fork.has_slot_number: - arguments["slot_number"] = U64(0) - - parent_header = t8n.fork.Header(**arguments) - - self.excess_blob_gas = t8n.fork.calculate_excess_blob_gas( - parent_header - ) - - def read_base_fee_per_gas(self, data: Any, t8n: "T8N") -> None: - """ - Read the base_fee_per_gas from the data. If the base fee is - not present, it is calculated from the parent block parameters. - """ - self.parent_gas_used = None - self.parent_gas_limit = None - self.parent_base_fee_per_gas = None - self.base_fee_per_gas = None - - if t8n.fork.has_calculate_base_fee_per_gas: - if "currentBaseFee" in data: - self.base_fee_per_gas = parse_hex_or_int( - data["currentBaseFee"], Uint - ) - - if "parentGasUsed" in data: - self.parent_gas_used = parse_hex_or_int( - data["parentGasUsed"], Uint - ) - - if "parentGasLimit" in data: - self.parent_gas_limit = parse_hex_or_int( - data["parentGasLimit"], Uint - ) - - if "parentBaseFee" in data: - self.parent_base_fee_per_gas = parse_hex_or_int( - data["parentBaseFee"], Uint - ) - - if self.base_fee_per_gas is None: - assert self.parent_gas_limit is not None - assert self.parent_gas_used is not None - assert self.parent_base_fee_per_gas is not None - - parameters: List[object] = [ - self.block_gas_limit, - self.parent_gas_limit, - self.parent_gas_used, - self.parent_base_fee_per_gas, - ] - - self.base_fee_per_gas = t8n.fork.calculate_base_fee_per_gas( - *parameters - ) - - def read_randao(self, data: Any, t8n: "T8N") -> None: - """ - Read the randao from the data. - """ - self.prev_randao = None - if t8n.fork.proof_of_stake: - # tf tool might not always provide an - # even number of nibbles in the randao - # This could create issues in the - # hex_to_bytes function - current_random = data["currentRandom"] - if current_random.startswith("0x"): - current_random = current_random[2:] - - if len(current_random) % 2 == 1: - current_random = "0" + current_random - - self.prev_randao = Bytes32( - left_pad_zero_bytes(hex_to_bytes(current_random), 32) - ) - - def read_slot_number(self, data: Any, t8n: "T8N") -> None: - """ - Read the slot number from the data. - The slot number is provided by the consensus layer. - """ - self.slot_number = None - if t8n.fork.has_slot_number: - if "slotNumber" in data: - self.slot_number = parse_hex_or_int(data["slotNumber"], U64) - - def read_withdrawals(self, data: Any, t8n: "T8N") -> None: - """ - Read the withdrawals from the data. - """ - self.withdrawals = None - if t8n.fork.has_withdrawal: - self.withdrawals = tuple( - t8n.json_to_withdrawals(wd) for wd in data["withdrawals"] - ) - - def read_block_difficulty(self, data: Any, t8n: "T8N") -> None: - """ - Read the block difficulty from the data. - If `currentDifficulty` is present, it is used. Otherwise, - the difficulty is calculated from the parent block. - """ - self.block_difficulty = None - self.parent_timestamp = None - self.parent_difficulty = None - self.parent_ommers_hash = None - if t8n.fork.proof_of_stake: - return - elif "currentDifficulty" in data: - self.block_difficulty = parse_hex_or_int( - data["currentDifficulty"], Uint - ) - else: - self.parent_timestamp = parse_hex_or_int( - data["parentTimestamp"], U256 - ) - self.parent_difficulty = parse_hex_or_int( - data["parentDifficulty"], Uint - ) - args: List[object] = [ - self.block_number, - self.block_timestamp, - self.parent_timestamp, - self.parent_difficulty, - ] - if t8n.fork.calculate_block_difficulty_arity > 4: - if "parentUncleHash" in data: - EMPTY_OMMER_HASH = keccak256(rlp.encode([])) # noqa N806 - self.parent_ommers_hash = Hash32( - hex_to_bytes(data["parentUncleHash"]) - ) - parent_has_ommers = ( - self.parent_ommers_hash != EMPTY_OMMER_HASH - ) - args.append(parent_has_ommers) - else: - args.append(False) - self.block_difficulty = t8n.fork.calculate_block_difficulty(*args) - - def read_block_hashes(self, data: Any) -> None: - """ - Read the block hashes. Returns a maximum of 256 block hashes. - """ - # Read the block hashes - block_hashes: List[Any] = [] - - # The hex key strings provided might not have standard formatting - clean_block_hashes: Dict[int, Hash32] = {} - if "blockHashes" in data: - for key, value in data["blockHashes"].items(): - int_key = int(key, 16) - clean_block_hashes[int_key] = Hash32(hex_to_bytes(value)) - - # Store a maximum of 256 block hashes. - max_blockhash_count = min(Uint(256), self.block_number) - for number in range( - self.block_number - max_blockhash_count, self.block_number - ): - if number in clean_block_hashes.keys(): - block_hashes.append(clean_block_hashes[number]) - else: - block_hashes.append(None) - - self.block_hashes = block_hashes - - def read_ommers(self, data: Any, t8n: "T8N") -> None: - """ - Read the ommers. The ommers data might not have all the details - needed to obtain the Header. - """ - ommers = [] - if "ommers" in data: - for ommer in data["ommers"]: - ommers.append( - Ommer( - ommer["delta"], - t8n.fork.hex_to_address(ommer["address"]), - ) - ) - self.ommers = ommers diff --git a/src/ethereum_spec_tools/evm_tools/t8n/result.py b/src/ethereum_spec_tools/evm_tools/t8n/result.py new file mode 100644 index 00000000000..8e434fdd412 --- /dev/null +++ b/src/ethereum_spec_tools/evm_tools/t8n/result.py @@ -0,0 +1,149 @@ +""" +Build the testing-side ``Result`` from an executed block. + +All construction of ``Result`` and ``TransactionReceipt`` lives here +so the testing-package pydantic types stay isolated to one boundary +module. +""" + +from typing import TYPE_CHECKING, Any, Dict, List, Optional + +from ethereum_rlp import rlp + +from ethereum.crypto.hash import keccak256 +from ethereum.merkle_patricia_trie import root, trie_get + +if TYPE_CHECKING: + from execution_testing.client_clis.cli_types import ( + Result as TestingResult, + ) + + from . import T8N + + +def get_receipts_from_output(t8n: "T8N", block_output: Any) -> List[Any]: + """Build testing-side `TransactionReceipt`s from the block output tries.""" + # Function-scoped: ``execution_testing/__init__`` eagerly imports + # ``.specs`` which transitively imports ``client_clis``, which + # imports ``ExecutionSpecsTransitionTool`` — top-level import would + # cycle back into ``t8n``. + from execution_testing.test_types.receipt_types import ( + TransactionLog, + TransactionReceipt, + ) + + receipts: List[Any] = [] + for key in block_output.receipt_keys: + tx = trie_get(block_output.transactions_trie, key) + receipt = trie_get(block_output.receipts_trie, key) + assert tx is not None + assert receipt is not None + + tx_hash = t8n.fork.get_transaction_hash(tx) + + if hasattr(t8n.fork, "decode_receipt"): + decoded_receipt = t8n.fork.decode_receipt(receipt) + else: + decoded_receipt = receipt + + receipt_kwargs: Dict[str, Any] = { + "transaction_hash": tx_hash, + "cumulative_gas_used": int(decoded_receipt.cumulative_gas_used), + "bloom": decoded_receipt.bloom, + "logs": [ + TransactionLog( + address=log.address, + topics=list(log.topics), + data=log.data, + ) + for log in decoded_receipt.logs + ], + } + if hasattr(decoded_receipt, "succeeded"): + receipt_kwargs["status"] = int(decoded_receipt.succeeded) + elif hasattr(decoded_receipt, "post_state"): + receipt_kwargs["post_state"] = decoded_receipt.post_state + receipts.append(TransactionReceipt(**receipt_kwargs)) + return receipts + + +def build_result( + t8n: "T8N", + block_env: Any, + block_output: Any, + block_exception: Optional[str], + rejected_transactions: List[Any], +) -> "TestingResult": + """Build the testing-side `Result` from the executed block.""" + # Function-scoped: see import-cycle note in ``get_receipts_from_output``. + from execution_testing.client_clis.cli_types import Result as TestingResult + + diff = t8n.fork.extract_block_diff(t8n._block_state) + state_root, _ = t8n.alloc.compute_state_root_and_trie_changes( + diff.account_changes, diff.storage_changes, diff.storage_clears + ) + + arguments: Dict[str, Any] = { + "state_root": state_root, + "transactions_trie": root(block_output.transactions_trie), + "receipts_root": root(block_output.receipts_trie), + "logs_hash": keccak256(rlp.encode(block_output.block_logs)), + "logs_bloom": t8n.fork.logs_bloom(block_output.block_logs), + "receipts": get_receipts_from_output(t8n, block_output), + "rejected_transactions": rejected_transactions, + "gas_used": int(block_output.block_gas_used), + } + if hasattr(block_output, "block_state_gas_used"): + if int(block_output.block_state_gas_used) > arguments["gas_used"]: + arguments["gas_used"] = int(block_output.block_state_gas_used) + if block_exception is not None: + arguments["block_exception"] = block_exception + if hasattr(block_env, "difficulty"): + arguments["difficulty"] = int(block_env.difficulty) + if hasattr(block_env, "base_fee_per_gas"): + arguments["base_fee_per_gas"] = int(block_env.base_fee_per_gas) + if hasattr(block_output, "withdrawals_trie"): + arguments["withdrawals_root"] = root(block_output.withdrawals_trie) + if hasattr(block_env, "excess_blob_gas"): + arguments["excess_blob_gas"] = int(block_env.excess_blob_gas) + arguments["blob_gas_used"] = int(block_output.blob_gas_used) + if hasattr(block_output, "requests"): + arguments["requests"] = list(block_output.requests) + arguments["requests_hash"] = t8n.fork.compute_requests_hash( + block_output.requests + ) + if hasattr(block_output, "block_access_list"): + arguments["block_access_list"] = rlp.encode( + block_output.block_access_list + ) + arguments["block_access_list_hash"] = t8n.fork.hash_block_access_list( + block_output.block_access_list + ) + + context: Optional[Dict[str, Any]] = None + if t8n.exception_mapper is not None: + context = {"exception_mapper": t8n.exception_mapper} + return TestingResult.model_validate(arguments, context=context) + + +def record_rejected_tx(t8n: "T8N", index: int, error: Exception) -> None: + """Append a ``RejectedTransaction`` to ``t8n.rejected_transactions``.""" + # Function-scoped: see import-cycle note in ``get_receipts_from_output``. + from execution_testing.client_clis.cli_types import RejectedTransaction + + context: Optional[Dict[str, Any]] = None + if t8n.exception_mapper is not None: + context = {"exception_mapper": t8n.exception_mapper} + t8n.rejected_transactions.append( + RejectedTransaction.model_validate( + {"index": index, "error": f"Failed transaction: {error!r}"}, + context=context, + ) + ) + + +__all__ = [ + "build_result", + "get_receipts_from_output", + "record_rejected_tx", +] diff --git a/src/ethereum_spec_tools/evm_tools/t8n/t8n_types.py b/src/ethereum_spec_tools/evm_tools/t8n/t8n_types.py deleted file mode 100644 index 9a5a824e76b..00000000000 --- a/src/ethereum_spec_tools/evm_tools/t8n/t8n_types.py +++ /dev/null @@ -1,443 +0,0 @@ -""" -Define the types used by the t8n tool. -""" - -import json -from dataclasses import dataclass -from typing import TYPE_CHECKING, Any, Dict, List, Optional - -from ethereum_rlp import Simple, rlp -from ethereum_types.bytes import Bytes -from ethereum_types.numeric import U64, U256, Uint - -from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.merkle_patricia_trie import root, trie_get -from ethereum.state import EMPTY_CODE_HASH, apply_changes_to_state -from ethereum.utils.hexadecimal import hex_to_bytes, hex_to_u256, hex_to_uint - -from ..loaders.transaction_loader import TransactionLoad, UnsupportedTxError -from ..utils import FatalError, encode_to_hex, secp256k1_sign - -if TYPE_CHECKING: - from . import T8N - - -class Alloc: - """ - The alloc (state) type for the t8n tool. - """ - - state: Any - state_backup: Any - - def __init__(self, t8n: "T8N", stdin: Optional[Dict] = None): - """Read the alloc file and return the state.""" - if t8n.options.input_alloc == "stdin": - assert stdin is not None - data = stdin["alloc"] - else: - with open(t8n.options.input_alloc, "r") as f: - data = json.load(f) - - # The json_to_state function expects the values to be hex - # strings, so we convert them here. - for address, account in data.items(): - for key, value in account.items(): - if key == "storage" or not value: - continue - elif not value.startswith("0x"): - data[address][key] = "0x" + hex(int(value)) - - state = t8n.json_to_state(data) - if t8n.fork.hardfork.short_name == "dao_fork": - t8n.fork.apply_dao(state) - - self.state = state - - def to_json(self) -> Any: - """Encode the state to JSON.""" - data = {} - for address, account in self.state._main_trie._data.items(): - account_data: Dict[str, Any] = {} - - if account.balance: - account_data["balance"] = hex(account.balance) - - if account.nonce: - account_data["nonce"] = hex(account.nonce) - - if account.code_hash != EMPTY_CODE_HASH: - code = self.state._code_store[account.code_hash] - account_data["code"] = "0x" + code.hex() - - if address in self.state._storage_tries: - account_data["storage"] = { - "0x" + k.hex(): hex(v) - for k, v in self.state._storage_tries[ - address - ]._data.items() - } - - data["0x" + address.hex()] = account_data - - return data - - -class Txs: - """ - Read the transactions file, sort out the valid transactions and - return a list of transactions. - """ - - def __init__(self, t8n: "T8N", stdin: Optional[Dict] = None): - self.t8n = t8n - self.successfully_parsed: List[int] = [] - self.transactions: List[Any] = [] - self.rejected_txs = {} - self.rlp_input = False - self.all_txs = [] - - if t8n.options.input_txs == "stdin": - assert stdin is not None - data = stdin["txs"] - else: - with open(t8n.options.input_txs, "r") as f: - data = json.load(f) - - if data is None: - self.data: Simple = [] - elif isinstance(data, str): - self.rlp_input = True - self.data = rlp.decode(hex_to_bytes(data)) - else: - self.data = data - - for idx, raw_tx in enumerate(self.data): - try: - if self.rlp_input: - self.transactions.append(self.parse_rlp_tx(raw_tx)) - self.successfully_parsed.append(idx) - else: - self.transactions.append(self.parse_json_tx(raw_tx)) - self.successfully_parsed.append(idx) - except UnsupportedTxError as e: - self.t8n.logger.warning( - f"Unsupported transaction at index {idx}: " - f"{e.error_message}" - ) - self.rejected_txs[idx] = ( - f"Unsupported transaction type: {e.error_message}" - ) - if e.encoded_params is not None: - self.all_txs.append(e.encoded_params) - except Exception as e: - msg = f"Failed to parse transaction {idx}: {str(e)}" - self.t8n.logger.warning(msg, exc_info=e) - self.rejected_txs[idx] = msg - - def parse_rlp_tx(self, raw_tx: Any) -> Any: - """ - Read transactions from RLP. - """ - t8n = self.t8n - - tx_rlp = rlp.encode(raw_tx) - if t8n.fork.has_legacy_transaction: - if isinstance(raw_tx, Bytes): - transaction = t8n.fork.decode_transaction(raw_tx) - self.all_txs.append(raw_tx) - else: - transaction = rlp.decode_to(t8n.fork.LegacyTransaction, tx_rlp) - self.all_txs.append(transaction) - else: - transaction = rlp.decode_to(t8n.fork.Transaction, tx_rlp) - self.all_txs.append(transaction) - - return transaction - - def parse_json_tx(self, raw_tx: Any) -> Any: - """ - Read the transactions from json. - If a transaction is unsigned but has a `secretKey` field, the - transaction will be signed. - """ - t8n = self.t8n - - # for idx, json_tx in enumerate(self.data): - raw_tx["gasLimit"] = raw_tx["gas"] - raw_tx["data"] = raw_tx["input"] - if "to" not in raw_tx or raw_tx["to"] is None: - raw_tx["to"] = "" - - # tf tool might provide None instead of 0 - # for v, r, s - raw_tx["v"] = raw_tx.get("v") or raw_tx.get("y_parity") or "0x00" - raw_tx["r"] = raw_tx.get("r") or "0x00" - raw_tx["s"] = raw_tx.get("s") or "0x00" - - v = hex_to_u256(raw_tx["v"]) - r = hex_to_u256(raw_tx["r"]) - s = hex_to_u256(raw_tx["s"]) - - if "secretKey" in raw_tx and v == r == s == 0: - self.sign_transaction(raw_tx) - - tx = TransactionLoad(raw_tx, t8n.fork).read() - self.all_txs.append(tx) - - if t8n.fork.has_decode_transaction: - transaction = t8n.fork.decode_transaction(tx) - else: - transaction = tx - - return transaction - - def sign_transaction(self, json_tx: Any) -> None: - """ - Sign a transaction. This function will be invoked if a `secretKey` - is provided in the transaction. - Post spurious dragon, the transaction is signed according to EIP-155 - if the protected flag is missing or set to true. - """ - t8n = self.t8n - protected = json_tx.get("protected", True) - - tx = TransactionLoad(json_tx, t8n.fork).read() - - if isinstance(tx, bytes): - tx_decoded = t8n.fork.decode_transaction(tx) - else: - tx_decoded = tx - - secret_key = hex_to_uint(json_tx["secretKey"][2:]) - if t8n.fork.has_legacy_transaction: - Transaction = t8n.fork.LegacyTransaction # noqa N806 - else: - Transaction = t8n.fork.Transaction # noqa N806 - - v_addend: U256 - if isinstance(tx_decoded, Transaction): - if t8n.fork.has_signing_hash_155: - if protected: - signing_hash = t8n.fork.signing_hash_155( - tx_decoded, self.t8n.chain_id - ) - # EIP-155: CHAIN_ID * 2 + 35 - v_addend = U256(self.t8n.chain_id) * U256(2) + U256(35) - else: - signing_hash = t8n.fork.signing_hash_pre155(tx_decoded) - v_addend = U256(27) - else: - signing_hash = t8n.fork.signing_hash(tx_decoded) - v_addend = U256(27) - elif isinstance(tx_decoded, t8n.fork.AccessListTransaction): - signing_hash = t8n.fork.signing_hash_2930(tx_decoded) - v_addend = U256(0) - elif isinstance(tx_decoded, t8n.fork.FeeMarketTransaction): - signing_hash = t8n.fork.signing_hash_1559(tx_decoded) - v_addend = U256(0) - elif isinstance(tx_decoded, t8n.fork.BlobTransaction): - signing_hash = t8n.fork.signing_hash_4844(tx_decoded) - v_addend = U256(0) - elif isinstance(tx_decoded, t8n.fork.SetCodeTransaction): - signing_hash = t8n.fork.signing_hash_7702(tx_decoded) - v_addend = U256(0) - else: - raise FatalError("Unknown transaction type") - - r, s, y = secp256k1_sign(signing_hash, int(secret_key)) - json_tx["r"] = hex(r) - json_tx["s"] = hex(s) - json_tx["v"] = hex(y + v_addend) - - if v_addend == 0: - json_tx["y_parity"] = json_tx["v"] - - -@dataclass -class Result: - """Type that represents the result of a transition execution.""" - - difficulty: Any - base_fee: Any - state_root: Any = None - tx_root: Any = None - receipt_root: Any = None - withdrawals_root: Any = None - logs_hash: Any = None - bloom: Any = None - receipts: Any = None - rejected: Any = None - gas_used: Any = None - excess_blob_gas: Optional[U64] = None - blob_gas_used: Optional[Uint] = None - requests_hash: Optional[Hash32] = None - requests: Optional[List[Bytes]] = None - block_exception: Optional[str] = None - block_access_list: Optional[Any] = None - block_access_list_hash: Optional[Hash32] = None - - def get_receipts_from_output( - self, - t8n: Any, - block_output: Any, - ) -> List[Any]: - """ - Get receipts from the transaction and receipts tries. - """ - receipts: List[Any] = [] - for key in block_output.receipt_keys: - tx = trie_get(block_output.transactions_trie, key) - receipt = trie_get(block_output.receipts_trie, key) - - assert tx is not None - assert receipt is not None - - tx_hash = t8n.fork.get_transaction_hash(tx) - - if hasattr(t8n.fork, "decode_receipt"): - decoded_receipt = t8n.fork.decode_receipt(receipt) - else: - decoded_receipt = receipt - - receipts.append((tx_hash, decoded_receipt)) - - return receipts - - def update(self, t8n: "T8N", block_env: Any, block_output: Any) -> None: - """ - Update the result after processing the inputs. - """ - self.gas_used = block_output.block_gas_used - if hasattr(block_output, "block_state_gas_used"): - if block_output.block_state_gas_used > self.gas_used: - self.gas_used = block_output.block_state_gas_used - self.tx_root = root(block_output.transactions_trie) - self.receipt_root = root(block_output.receipts_trie) - self.bloom = t8n.fork.logs_bloom(block_output.block_logs) - self.logs_hash = keccak256(rlp.encode(block_output.block_logs)) - block_diff = t8n.fork.extract_block_diff(t8n._block_state) - state_root_value, _ = ( - t8n.alloc.state.compute_state_root_and_trie_changes( - block_diff.account_changes, - block_diff.storage_changes, - block_diff.storage_clears, - ) - ) - self.state_root = state_root_value - # Apply diffs to pre-state for alloc output - apply_changes_to_state(t8n.alloc.state, block_diff) - self.receipts = self.get_receipts_from_output(t8n, block_output) - - if hasattr(block_env, "base_fee_per_gas"): - self.base_fee = block_env.base_fee_per_gas - - if hasattr(block_output, "withdrawals_trie"): - self.withdrawals_root = root(block_output.withdrawals_trie) - - if hasattr(block_env, "excess_blob_gas"): - self.excess_blob_gas = block_env.excess_blob_gas - - if hasattr(block_output, "requests"): - self.requests = block_output.requests - self.requests_hash = t8n.fork.compute_requests_hash(self.requests) - - if hasattr(block_output, "block_access_list"): - self.block_access_list = block_output.block_access_list - self.block_access_list_hash = t8n.fork.hash_block_access_list( - block_output.block_access_list - ) - - def json_encode_receipts(self) -> Any: - """ - Encode receipts to JSON. - """ - receipts_json = [] - for tx_hash, receipt in self.receipts: - receipt_dict = {"transactionHash": "0x" + tx_hash.hex()} - - if hasattr(receipt, "succeeded"): - receipt_dict["succeeded"] = receipt.succeeded - else: - assert hasattr(receipt, "post_state") - receipt_dict["post_state"] = "0x" + receipt.post_state.hex() - - receipt_dict["cumulativeGasUsed"] = hex( - receipt.cumulative_gas_used - ) - receipt_dict["bloom"] = "0x" + receipt.bloom.hex() - - # Add logs to receipts - logs_json = [] - for log in receipt.logs: - log_dict = { - "address": "0x" + log.address.hex(), - "topics": ["0x" + topic.hex() for topic in log.topics], - "data": "0x" + log.data.hex(), - } - logs_json.append(log_dict) - receipt_dict["logs"] = logs_json - - receipts_json.append(receipt_dict) - - return receipts_json - - def to_json(self) -> Any: - """Encode the result to JSON.""" - data = {} - - data["stateRoot"] = "0x" + self.state_root.hex() - data["txRoot"] = "0x" + self.tx_root.hex() - data["receiptsRoot"] = "0x" + self.receipt_root.hex() - if self.withdrawals_root: - data["withdrawalsRoot"] = "0x" + self.withdrawals_root.hex() - data["logsHash"] = "0x" + self.logs_hash.hex() - data["logsBloom"] = "0x" + self.bloom.hex() - data["gasUsed"] = hex(self.gas_used) - if self.difficulty: - data["currentDifficulty"] = hex(self.difficulty) - else: - data["currentDifficulty"] = None - - if self.base_fee: - data["currentBaseFee"] = hex(self.base_fee) - else: - data["currentBaseFee"] = None - - if self.excess_blob_gas is not None: - data["currentExcessBlobGas"] = hex(self.excess_blob_gas) - - if self.blob_gas_used is not None: - data["blobGasUsed"] = hex(self.blob_gas_used) - - data["rejected"] = [ - {"index": idx, "error": error} - for idx, error in self.rejected.items() - ] - - data["receipts"] = self.json_encode_receipts() - - if self.requests_hash is not None: - assert self.requests is not None - - data["requestsHash"] = encode_to_hex(self.requests_hash) - # T8N doesn't consider the request type byte to be part of the - # request - data["requests"] = [encode_to_hex(req) for req in self.requests] - - if self.block_exception is not None: - data["blockException"] = self.block_exception - - if self.block_access_list is not None: - # Output BAL as RLP-encoded hex bytes; the testing framework - # handles JSON serialization. - data["blockAccessList"] = encode_to_hex( - rlp.encode(self.block_access_list) - ) - - if self.block_access_list_hash is not None: - data["blockAccessListHash"] = encode_to_hex( - self.block_access_list_hash - ) - - return data diff --git a/src/ethereum_spec_tools/evm_tools/utils.py b/src/ethereum_spec_tools/evm_tools/utils.py index 15aee92af71..7483c38b34f 100644 --- a/src/ethereum_spec_tools/evm_tools/utils.py +++ b/src/ethereum_spec_tools/evm_tools/utils.py @@ -15,13 +15,10 @@ Sequence, Tuple, TypeVar, - Union, ) -import spec256k1 from ethereum_types.numeric import U64, U256, Uint -from ethereum.crypto.hash import Hash32 from ethereum_spec_tools.forks import Hardfork W = TypeVar("W", Uint, U64, U256) @@ -132,6 +129,44 @@ def find_fork( sys.exit(f"Unsupported state fork: {options.state_fork}") +# Map testing ``Fork.transition_tool_name()`` → spec ``Hardfork.short_name`` +# for cases where CamelCase → snake_case does not produce the spec +# module name: +# * ``Paris`` reports itself as ``"Merge"`` to the t8n protocol. +# * ``DAOFork`` would snake-case to ``d_a_o_fork``. +# * ``ConstantinopleFix`` is a testing-side distinction that the spec +# folds into the ``constantinople`` module. +_SPEC_SHORT_NAME_OVERRIDES: Dict[str, str] = { + "Merge": "paris", + "DAOFork": "dao_fork", + "ConstantinopleFix": "constantinople", +} + + +def resolve_fork(fork_name: str) -> Hardfork: + """ + Resolve a testing ``Fork.transition_tool_name()`` to its matching + spec ``Hardfork``. + + CLI exception aliases like ``HomesteadToDaoAt5`` are resolved by + :func:`find_fork` before the testing ``Fork`` is built, so the name + reaching this function is always post-alias-resolution. + """ + short = _SPEC_SHORT_NAME_OVERRIDES.get(fork_name) + if short is None: + short = re.sub(r"(?<!^)(?=[A-Z])", "_", fork_name).lower() + # ``BPO1`` and friends would otherwise become ``b_p_o1``; mirror + # the ``b_p_o → bpo`` collapse that :func:`find_fork` performs. + short = re.sub(r"^b_p_o", "bpo", short) + for fork in Hardfork.discover(): + if fork.short_name == short: + return fork + raise ValueError( + f"No spec Hardfork matches testing fork name {fork_name!r} " + f"(looked for short_name={short!r})" + ) + + def get_supported_forks() -> List[str]: """ Get the supported forks. @@ -166,29 +201,3 @@ def get_stream_logger(name: str) -> Any: logger.addHandler(stream_handler) return logger - - -def secp256k1_sign(msg_hash: Hash32, secret_key: int) -> Tuple[U256, ...]: - """ - Returns the signature of a message hash given the secret key. - """ - private_key = spec256k1.PrivateKey(secret_key.to_bytes(32, "big")) - signature = private_key.sign_recoverable(msg_hash) - - return ( - U256.from_be_bytes(signature[0:32]), - U256.from_be_bytes(signature[32:64]), - U256(signature[64]), - ) - - -def encode_to_hex(data: Union[bytes, int]) -> str: - """ - Encode the data to a hex string. - """ - if isinstance(data, int): - return hex(data) - elif isinstance(data, bytes): - return "0x" + data.hex() - else: - raise Exception("Invalid data type") diff --git a/tests/evm_tools/test_count_opcodes.py b/tests/evm_tools/test_count_opcodes.py index 4220ffa6586..0ced37e51f5 100644 --- a/tests/evm_tools/test_count_opcodes.py +++ b/tests/evm_tools/test_count_opcodes.py @@ -11,7 +11,8 @@ import pytest from ethereum_spec_tools.evm_tools import create_parser -from ethereum_spec_tools.evm_tools.t8n import T8N, ForkCache +from ethereum_spec_tools.evm_tools.t8n import ForkCache +from ethereum_spec_tools.evm_tools.t8n.cli import run_t8n_cli parser = create_parser() @@ -41,10 +42,7 @@ def test_count_opcodes(root_relative: Callable[[str | Path], Path]) -> None: out_file = StringIO() with ForkCache() as fork_cache: - t8n_tool = T8N( - options, out_file=out_file, in_file=in_file, cache=fork_cache - ) - exit_code = t8n_tool.run() + exit_code = run_t8n_cli(options, out_file, in_file, fork_cache) assert 0 == exit_code results = json.loads(out_file.getvalue()) diff --git a/tests/json_loader/helpers/load_state_tests.py b/tests/json_loader/helpers/load_state_tests.py index 4cfe757d943..52a4557ff53 100644 --- a/tests/json_loader/helpers/load_state_tests.py +++ b/tests/json_loader/helpers/load_state_tests.py @@ -1,7 +1,6 @@ """Helper functions to load and run general state tests for Ethereum forks.""" import json -import sys from io import StringIO from typing import Any, Dict, Final, Iterable, List @@ -14,7 +13,8 @@ from ethereum.utils.hexadecimal import hex_to_bytes from ethereum_spec_tools.evm_tools import create_parser from ethereum_spec_tools.evm_tools.statetest import read_test_case -from ethereum_spec_tools.evm_tools.t8n import T8N, ForkCache +from ethereum_spec_tools.evm_tools.t8n import ForkCache +from ethereum_spec_tools.evm_tools.t8n.cli import build_t8n_from_cli_options from .. import FORKS from ..stash_keys import desired_forks_key, fork_cache_key @@ -144,14 +144,18 @@ def runtest(self) -> None: with ForkCache() as fork_cache: try: - t8n = T8N(t8n_options, sys.stdout, in_stream, fork_cache) + t8n = build_t8n_from_cli_options( + t8n_options, in_stream, fork_cache + ) except StateWithEmptyAccount as e: pytest.xfail(str(e)) t8n.run_state_test() if "expectException" in post: - assert 0 in t8n.txs.rejected_txs + assert any( + int(rej.index) == 0 for rej in t8n.rejected_transactions + ) return assert hex_to_bytes(post_hash) == t8n.result.state_root diff --git a/vulture_whitelist.py b/vulture_whitelist.py index fd1f6712690..e944b4e3f7c 100644 --- a/vulture_whitelist.py +++ b/vulture_whitelist.py @@ -19,7 +19,7 @@ from ethereum_spec_tools.evm_tools.loaders.transaction_loader import ( TransactionLoad, ) -from ethereum_spec_tools.evm_tools.t8n.env import Ommer +from ethereum_spec_tools.evm_tools.t8n.block_environment import Ommer from ethereum_spec_tools.evm_tools.t8n.evm_trace.eip3155 import ( FinalTrace, Trace, @@ -121,9 +121,15 @@ TransactionLoad.json_to_r TransactionLoad.json_to_s -# src/ethereum_spec_tools/evm_tools/t8n/env.py +# src/ethereum_spec_tools/evm_tools/t8n/block_environment.py Ommer.delta +# src/ethereum_spec_tools/evm_tools/t8n/__init__.py +# `protected` is a field on the testing-package `Transaction` model; +# T8N flips it to False for pre-EIP-155 forks before calling `sign()`. +_unused_protected_marker = None +_unused_protected_marker.protected # type: ignore[attr-defined] + # src/ethereum_spec_tools/evm_tools/t8n/evm_trace/eip3155.py Trace.gasCost Trace.memSize From 04e7b0daf64c5490cf0c4a7c132d2ac0c4f5419a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= <pawel@hepcolgum.band> Date: Fri, 24 Jul 2026 08:58:37 +0200 Subject: [PATCH 146/233] feat(tests): add EIP-2681 nonce-reaching-max regression tests (#3226) Add regression tests verifying that reaching the maximum account nonce (2**64-1) during execution is valid: per EIP-2681 only a transaction whose nonce is 2**64-1 is invalid, not one that merely increments an account to that value. Ported from ipsilon/evmone#1608: * top-level CALL from a sender at nonce 2**64-2 * top-level CREATE from a sender at nonce 2**64-2 (created-account nonce fork-gated per EIP-161) * EIP-7702 self-sponsored set-code tx whose authorization drives the sender to 2**64-1 --- .../eip2681_limit_account_nonce/__init__.py | 3 + .../eip2681_limit_account_nonce/spec.py | 23 +++ .../test_nonce_reaching_max.py | 136 ++++++++++++++++++ 3 files changed, 162 insertions(+) create mode 100644 tests/frontier/eip2681_limit_account_nonce/__init__.py create mode 100644 tests/frontier/eip2681_limit_account_nonce/spec.py create mode 100644 tests/frontier/eip2681_limit_account_nonce/test_nonce_reaching_max.py diff --git a/tests/frontier/eip2681_limit_account_nonce/__init__.py b/tests/frontier/eip2681_limit_account_nonce/__init__.py new file mode 100644 index 00000000000..65a94752f70 --- /dev/null +++ b/tests/frontier/eip2681_limit_account_nonce/__init__.py @@ -0,0 +1,3 @@ +""" +Tests [EIP-2681: Limit account nonce to 2^64-1](https://eips.ethereum.org/EIPS/eip-2681). +""" diff --git a/tests/frontier/eip2681_limit_account_nonce/spec.py b/tests/frontier/eip2681_limit_account_nonce/spec.py new file mode 100644 index 00000000000..24395fb3375 --- /dev/null +++ b/tests/frontier/eip2681_limit_account_nonce/spec.py @@ -0,0 +1,23 @@ +"""Defines EIP-2681 specification constants and functions.""" + +from dataclasses import dataclass + + +@dataclass(frozen=True) +class ReferenceSpec: + """Defines the reference spec version and git path.""" + + git_path: str + version: str + + +# EIP-2681 reference specification +ref_spec_2681 = ReferenceSpec( + "EIPS/eip-2681.md", "9e393a79d9937f579acbdcb234a67869259d5a96" +) + + +class Spec: + """Constants for the EIP-2681 account nonce limit tests.""" + + max_nonce = 2**64 - 1 diff --git a/tests/frontier/eip2681_limit_account_nonce/test_nonce_reaching_max.py b/tests/frontier/eip2681_limit_account_nonce/test_nonce_reaching_max.py new file mode 100644 index 00000000000..718c15879bd --- /dev/null +++ b/tests/frontier/eip2681_limit_account_nonce/test_nonce_reaching_max.py @@ -0,0 +1,136 @@ +""" +Tests that reaching the maximum account nonce (`2**64 - 1`) during execution +is valid. + +Per [EIP-2681](https://eips.ethereum.org/EIPS/eip-2681) only a transaction +whose nonce is `2**64 - 1` is invalid; merely incrementing an account to that +value while executing a transaction is permitted. +""" + +import pytest +from execution_testing import ( + Account, + Alloc, + AuthorizationTuple, + Fork, + Op, + StateTestFiller, + Storage, + Transaction, + compute_create_address, +) +from execution_testing.forks import SpuriousDragon + +from ...prague.eip7702_set_code_tx.spec import Spec as Spec7702 +from .spec import Spec, ref_spec_2681 + +REFERENCE_SPEC_GIT_PATH = ref_spec_2681.git_path +REFERENCE_SPEC_VERSION = ref_spec_2681.version + + +@pytest.mark.valid_from("Frontier") +@pytest.mark.pre_alloc_mutable +def test_tx_at_nonce_max_minus_one_call( + state_test: StateTestFiller, + pre: Alloc, +) -> None: + """ + Test that a top-level CALL transaction from a sender at the highest usable + nonce (`2**64 - 2`) executes normally, bumping the sender to the maximum + nonce (`2**64 - 1`). + """ + sender = pre.fund_eoa(nonce=Spec.max_nonce - 1) + to = pre.fund_eoa(amount=0) + + tx = Transaction( + to=to, + nonce=Spec.max_nonce - 1, + sender=sender, + protected=False, + ) + + state_test(pre=pre, post={sender: Account(nonce=Spec.max_nonce)}, tx=tx) + + +@pytest.mark.valid_from("Frontier") +@pytest.mark.pre_alloc_mutable +def test_tx_at_nonce_max_minus_one_create( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Test that a top-level CREATE transaction from a sender at the highest + usable nonce (`2**64 - 2`) executes normally, creating a contract and + bumping the sender to the maximum nonce (`2**64 - 1`). + """ + sender = pre.fund_eoa(nonce=Spec.max_nonce - 1) + + tx = Transaction( + to=None, + nonce=Spec.max_nonce - 1, + sender=sender, + protected=False, + ) + + # EIP-161 (Spurious Dragon) initializes a new contract's nonce to 1. + created_nonce = 1 if fork >= SpuriousDragon else 0 + created = compute_create_address(address=sender, nonce=Spec.max_nonce - 1) + + state_test( + pre=pre, + post={ + sender: Account(nonce=Spec.max_nonce), + created: Account(nonce=created_nonce, code=b""), + }, + tx=tx, + ) + + +@pytest.mark.valid_from("Prague") +@pytest.mark.pre_alloc_mutable +def test_set_code_self_authorization_reaching_nonce_max( + state_test: StateTestFiller, + pre: Alloc, +) -> None: + """ + Test a self-sponsored set-code transaction whose authorization bumps the + sender's nonce to the maximum value (`2**64 - 1`). + + The sender starts at nonce `2**64 - 3`. The transaction increments it to + `2**64 - 2`, then the self-signed authorization (nonce `2**64 - 2`) + applies and increments it to `2**64 - 1`. + """ + storage = Storage() + sender = pre.fund_eoa(nonce=Spec.max_nonce - 2) + delegate = pre.fund_eoa(amount=0) + + # The transaction targets this contract (not the sender), so its SSTORE + # proves the top-level call executed. + set_code_to_address = pre.deploy_contract( + code=Op.SSTORE(storage.store_next(sender), Op.ORIGIN), + ) + + tx = Transaction( + to=set_code_to_address, + authorization_list=[ + AuthorizationTuple( + address=delegate, + nonce=Spec.max_nonce - 1, + signer=sender, + ), + ], + sender=sender, + ) + + state_test( + pre=pre, + tx=tx, + post={ + set_code_to_address: Account(storage=storage), + sender: Account( + nonce=Spec.max_nonce, + code=Spec7702.delegation_designation(delegate), + ), + }, + ) From 1646cf550bfa2b713acb1442551bf47664186285 Mon Sep 17 00:00:00 2001 From: kevaundray <kevtheappdev@gmail.com> Date: Fri, 24 Jul 2026 08:04:46 +0100 Subject: [PATCH 147/233] refactor(test): Make max balance < 2^128 wei (#3227) --- tests/cancun/eip4844_blobs/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cancun/eip4844_blobs/conftest.py b/tests/cancun/eip4844_blobs/conftest.py index 6f93d162f88..d417b91c28c 100644 --- a/tests/cancun/eip4844_blobs/conftest.py +++ b/tests/cancun/eip4844_blobs/conftest.py @@ -315,7 +315,7 @@ def non_zero_blob_gas_used_genesis_block( f"with base_fee_per_gas {block_base_fee_per_gas}" ) - sender = pre.fund_eoa(10**42) + sender = pre.fund_eoa(10**36) empty_account_destination = pre.fund_eoa(0) blob_gas_price_calculator = block_fork.blob_gas_price_calculator() From 6463c0dc37939ca478dd19c5ccac6f5a5bda821b Mon Sep 17 00:00:00 2001 From: spencer <spencer.taylor-brown@ethereum.org> Date: Fri, 24 Jul 2026 12:19:42 +0200 Subject: [PATCH 148/233] chore(tests): improve EIP-7708 coverage, checklist, and ref-spec pin (#3220) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 蔡佳誠 Louis Tsai <72684086+LouisTsai-Csie@users.noreply.github.com> --- .../eip_checklist_not_applicable.txt | 1 + .../eip7708_eth_transfer_logs/spec.py | 3 +- .../test_eip_mainnet.py | 27 +++ .../test_fork_transition.py | 81 ++++++++ .../test_block_access_lists_eip7708.py | 188 ++++++++++++++++++ 5 files changed, 298 insertions(+), 2 deletions(-) create mode 100644 tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip7708.py diff --git a/tests/amsterdam/eip7708_eth_transfer_logs/eip_checklist_not_applicable.txt b/tests/amsterdam/eip7708_eth_transfer_logs/eip_checklist_not_applicable.txt index 48ce20d19f8..fd7d7d0a1f3 100644 --- a/tests/amsterdam/eip7708_eth_transfer_logs/eip_checklist_not_applicable.txt +++ b/tests/amsterdam/eip7708_eth_transfer_logs/eip_checklist_not_applicable.txt @@ -12,3 +12,4 @@ execution_layer_request = EIP does not introduce an execution layer request new_transaction_validity_constraint = EIP does not introduce a new transaction validity constraint modified_transaction_validity_constraint = EIP does not introduce a modified transaction validity constraint block_level_constraint = EIP does not introduce a block-level validation constraint +general/code_coverage/second_client = Optional diff --git a/tests/amsterdam/eip7708_eth_transfer_logs/spec.py b/tests/amsterdam/eip7708_eth_transfer_logs/spec.py index 54088c5217c..56a42e2850a 100644 --- a/tests/amsterdam/eip7708_eth_transfer_logs/spec.py +++ b/tests/amsterdam/eip7708_eth_transfer_logs/spec.py @@ -14,7 +14,7 @@ class ReferenceSpec: ref_spec_7708 = ReferenceSpec( - "EIPS/eip-7708.md", "172188d7b090ed1afb876140f45e19ac00cba4bb" + "EIPS/eip-7708.md", "f7230c46a743313957d8f38a159bda934cc735b2" ) @@ -30,7 +30,6 @@ class Spec: TRANSFER_TOPIC: Hash = Hash( keccak256(b"Transfer(address,address,uint256)") ) - BURN_TOPIC: Hash = Hash(keccak256(b"Burn(address,uint256)")) def transfer_log( diff --git a/tests/amsterdam/eip7708_eth_transfer_logs/test_eip_mainnet.py b/tests/amsterdam/eip7708_eth_transfer_logs/test_eip_mainnet.py index f14be0735dd..3d84ba109dc 100644 --- a/tests/amsterdam/eip7708_eth_transfer_logs/test_eip_mainnet.py +++ b/tests/amsterdam/eip7708_eth_transfer_logs/test_eip_mainnet.py @@ -13,6 +13,7 @@ StateTestFiller, Transaction, TransactionReceipt, + compute_create_address, ) from .spec import ref_spec_7708, transfer_log @@ -84,6 +85,32 @@ def test_call_with_value_mainnet( state_test(pre=pre, post=post, tx=tx) +def test_create_endowment_mainnet( + state_test: StateTestFiller, + pre: Alloc, +) -> None: + """Test that a CREATE endowment emits a transfer log on mainnet.""" + sender = pre.fund_eoa() + create_value = 1 + + contract = pre.deploy_contract( + Op.CREATE(value=create_value, offset=0, size=0), + balance=create_value, + ) + created = compute_create_address(address=contract, nonce=1) + + tx = Transaction( + sender=sender, + to=contract, + expected_receipt=TransactionReceipt( + logs=[transfer_log(contract, created, create_value)] + ), + ) + + post = {created: Account(balance=create_value)} + state_test(pre=pre, post=post, tx=tx) + + def test_selfdestruct_mainnet( state_test: StateTestFiller, pre: Alloc, diff --git a/tests/amsterdam/eip7708_eth_transfer_logs/test_fork_transition.py b/tests/amsterdam/eip7708_eth_transfer_logs/test_fork_transition.py index 2d79177e1c4..f36b05ed9cd 100644 --- a/tests/amsterdam/eip7708_eth_transfer_logs/test_fork_transition.py +++ b/tests/amsterdam/eip7708_eth_transfer_logs/test_fork_transition.py @@ -11,8 +11,10 @@ Alloc, Block, BlockchainTestFiller, + Op, Transaction, TransactionReceipt, + compute_create_address, ) from .spec import ref_spec_7708, transfer_log @@ -81,3 +83,82 @@ def test_transfer_log_fork_transition( recipient: Account(balance=300), }, ) + + +@pytest.mark.parametrize( + "emission_point", + [ + pytest.param("call", id="call"), + pytest.param("create", id="create"), + pytest.param("selfdestruct", id="selfdestruct"), + ], +) +@pytest.mark.valid_at_transition_to("EIP7708") +def test_emission_point_fork_transition( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + emission_point: str, +) -> None: + """ + Test the CALL, CREATE, and SELFDESTRUCT emission points at the fork + transition boundary. + + Clients gate each emission site in a separate code path, so every + site is checked at the transition independently of the + transaction-level log. + """ + sender = pre.fund_eoa() + value = 100 + recipient = pre.deploy_contract(Op.STOP) + + if emission_point == "call": + code = Op.CALL(address=recipient, value=Op.CALLVALUE) + elif emission_point == "create": + code = Op.CREATE(value=Op.CALLVALUE, offset=0, size=0) + else: + code = Op.SELFDESTRUCT(recipient) + contract = pre.deploy_contract(code) + + blocks = [] + for nonce, (timestamp, active) in enumerate( + [(14_999, False), (15_000, True), (15_001, True)], start=1 + ): + if emission_point == "create": + inner_recipient = compute_create_address( + address=contract, nonce=nonce + ) + else: + inner_recipient = recipient + logs = ( + [ + transfer_log(sender, contract, value), + transfer_log(contract, inner_recipient, value), + ] + if active + else [] + ) + blocks.append( + Block( + timestamp=timestamp, + txs=[ + Transaction( + to=contract, + sender=sender, + value=value, + expected_receipt=TransactionReceipt(logs=logs), + ) + ], + ) + ) + + if emission_point == "create": + post = { + compute_create_address(address=contract, nonce=nonce): Account( + balance=value + ) + for nonce in (1, 2, 3) + } + else: + post = {recipient: Account(balance=3 * value)} + + blockchain_test(pre=pre, blocks=blocks, post=post) diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip7708.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip7708.py new file mode 100644 index 00000000000..1698e970731 --- /dev/null +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip7708.py @@ -0,0 +1,188 @@ +""" +Cross-EIP tests for EIP-7928 block-level access lists and EIP-7708 +transfer logs. + +A single block pins both views of the same value flows: the receipts +carry the EIP-7708 Transfer logs while the block access list carries the +matching balance changes, and the priority-fee payment appears in the +access list only, with no Transfer log. +""" + +import pytest +from execution_testing import ( + Account, + Alloc, + BalAccountExpectation, + BalBalanceChange, + BalNonceChange, + Block, + BlockAccessListExpectation, + BlockchainTestFiller, + Environment, + Fork, + Header, + Op, + RecipientType, + Transaction, + TransactionReceipt, +) + +from ..eip7708_eth_transfer_logs.spec import transfer_log +from .spec import ref_spec_7928 + +REFERENCE_SPEC_GIT_PATH = ref_spec_7928.git_path +REFERENCE_SPEC_VERSION = ref_spec_7928.version + +pytestmark = pytest.mark.valid_from("Amsterdam") + + +def test_transfer_logs_and_bal_balance_changes( + pre: Alloc, + blockchain_test: BlockchainTestFiller, + fork: Fork, +) -> None: + """ + Ensure Transfer logs and BAL balance changes stay consistent within + one block. + + The first transaction is a plain value transfer paying a priority + fee: both parties get BAL balance changes, the receipt carries one + Transfer log, and the coinbase tip appears in the BAL only. The + second transaction sweeps a contract balance via SELFDESTRUCT with a + zero tip: the sweep shows up both as a Transfer log and as BAL + balance changes, while the coinbase entry stays fee-only from the + first transaction. + """ + coinbase = pre.fund_eoa(amount=0) + + intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator() + intrinsic_gas = intrinsic_gas_calculator( + calldata=b"", + contract_creation=False, + access_list=[], + recipient_type=RecipientType.EMPTY_ACCOUNT, + sends_value=True, + ) + top_frame_state_gas = fork.transaction_top_frame_state_gas( + sends_value=True, + recipient_type=RecipientType.EMPTY_ACCOUNT, + ) + expected_gas_used = intrinsic_gas + top_frame_state_gas + tx_gas_limit = expected_gas_used + 1000 # add a small buffer + gas_price = 0xA + tx_value = 100 + extra_balance = 1000 + + alice_initial_balance = ( + (tx_gas_limit * gas_price) + tx_value + extra_balance + ) + alice = pre.fund_eoa(amount=alice_initial_balance) + bob = pre.fund_eoa(amount=0) + + genesis_env = Environment(base_fee_per_gas=0x7) + base_fee_per_gas = fork.base_fee_per_gas_calculator()( + parent_base_fee_per_gas=int(genesis_env.base_fee_per_gas or 0), + parent_gas_used=0, + parent_gas_limit=genesis_env.gas_limit, + ) + tip_to_coinbase = (gas_price - base_fee_per_gas) * expected_gas_used + alice_final_balance = ( + alice_initial_balance - tx_value - expected_gas_used * gas_price + ) + + tx_transfer = Transaction( + sender=alice, + to=bob, + value=tx_value, + gas_limit=tx_gas_limit, + gas_price=gas_price, + expected_receipt=TransactionReceipt( + logs=[transfer_log(alice, bob, tx_value)] + ), + ) + + # SELFDESTRUCT sweep with a zero tip, so the coinbase BAL entry + # stays fee-only from the first transaction. + sweep_value = 500 + carol = pre.fund_eoa() + dave = pre.fund_eoa(amount=0) + sweeper = pre.deploy_contract( + code=Op.SELFDESTRUCT(dave), balance=sweep_value + ) + + tx_sweep = Transaction( + sender=carol, + to=sweeper, + max_fee_per_gas=base_fee_per_gas, + max_priority_fee_per_gas=0, + expected_receipt=TransactionReceipt( + logs=[transfer_log(sweeper, dave, sweep_value)] + ), + ) + + block = Block( + txs=[tx_transfer, tx_sweep], + fee_recipient=coinbase, + header_verify=Header(base_fee_per_gas=base_fee_per_gas), + expected_block_access_list=BlockAccessListExpectation( + account_expectations={ + alice: BalAccountExpectation( + nonce_changes=[ + BalNonceChange(block_access_index=1, post_nonce=1) + ], + balance_changes=[ + BalBalanceChange( + block_access_index=1, + post_balance=alice_final_balance, + ) + ], + ), + bob: BalAccountExpectation( + balance_changes=[ + BalBalanceChange( + block_access_index=1, post_balance=tx_value + ) + ], + ), + carol: BalAccountExpectation( + nonce_changes=[ + BalNonceChange(block_access_index=2, post_nonce=1) + ], + ), + sweeper: BalAccountExpectation( + balance_changes=[ + BalBalanceChange(block_access_index=2, post_balance=0) + ], + ), + dave: BalAccountExpectation( + balance_changes=[ + BalBalanceChange( + block_access_index=2, post_balance=sweep_value + ) + ], + ), + # The tip is a BAL-only flow: it must never produce a + # Transfer log, and the zero-tip second transaction must + # not add a second balance change. + coinbase: BalAccountExpectation( + balance_changes=[ + BalBalanceChange( + block_access_index=1, + post_balance=tip_to_coinbase, + ) + ], + ), + } + ), + ) + + blockchain_test( + pre=pre, + blocks=[block], + post={ + bob: Account(balance=tx_value), + dave: Account(balance=sweep_value), + sweeper: Account(balance=0), + }, + genesis_environment=genesis_env, + ) From 6e02b3b1d5fb1eca0331dd74d46820b08499f87b Mon Sep 17 00:00:00 2001 From: spencer <spencer.taylor-brown@ethereum.org> Date: Fri, 24 Jul 2026 12:19:48 +0200 Subject: [PATCH 149/233] chore(tests): improve EIP-7843 coverage, checklist, and ref-spec pin (#3221) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 蔡佳誠 Louis Tsai <72684086+LouisTsai-Csie@users.noreply.github.com> --- .../execution_testing/fixtures/blockchain.py | 20 +- .../src/execution_testing/specs/blockchain.py | 11 + .../eip_checklist_external_coverage.txt | 3 + .../eip_checklist_not_applicable.txt | 27 ++ tests/amsterdam/eip7843_slotnum/spec.py | 6 +- .../eip7843_slotnum/test_eip_mainnet.py | 11 +- .../eip7843_slotnum/test_fork_transition.py | 103 ++++++- .../amsterdam/eip7843_slotnum/test_slotnum.py | 255 +++++++++++++++++- 8 files changed, 412 insertions(+), 24 deletions(-) create mode 100644 tests/amsterdam/eip7843_slotnum/eip_checklist_external_coverage.txt create mode 100644 tests/amsterdam/eip7843_slotnum/eip_checklist_not_applicable.txt diff --git a/packages/testing/src/execution_testing/fixtures/blockchain.py b/packages/testing/src/execution_testing/fixtures/blockchain.py index edd819ac30b..3a840769c06 100644 --- a/packages/testing/src/execution_testing/fixtures/blockchain.py +++ b/packages/testing/src/execution_testing/fixtures/blockchain.py @@ -370,19 +370,18 @@ def genesis(cls, fork: Fork, env: Environment, state_root: Hash) -> Self: env.withdrawals ) environment_values["extra_data"] = env.extra_data - extras = { + extras: Dict[str, Any] = { "state_root": state_root, - "requests_hash": Requests() - if fork.header_requests_required() - else None, - "block_access_list_hash": ( - BlockAccessList().rlp_hash - if fork.header_bal_hash_required() - else None - ), - "slot_number": 0 if fork.header_slot_number_required() else None, "fork": fork, } + if fork.header_requests_required(): + extras["requests_hash"] = Requests() + if fork.header_bal_hash_required(): + extras["block_access_list_hash"] = BlockAccessList().rlp_hash + if fork.header_slot_number_required(): + extras["slot_number"] = ( + int(env.slot_number) if env.slot_number is not None else 0 + ) return cls(**environment_values, **extras) @@ -460,6 +459,7 @@ class FixtureExecutionPayloadModifier(CamelModel): ) block_access_list: Removable | Bytes | None = None + slot_number: Removable | HexNumber | None = None REMOVE_FIELD: ClassVar[Removable] = Removable() """Sentinel to specify that a payload field should be removed.""" diff --git a/packages/testing/src/execution_testing/specs/blockchain.py b/packages/testing/src/execution_testing/specs/blockchain.py index 09913d04b9b..96436819460 100644 --- a/packages/testing/src/execution_testing/specs/blockchain.py +++ b/packages/testing/src/execution_testing/specs/blockchain.py @@ -348,6 +348,8 @@ class Block(Header): """EIP-7928: Block-level access lists (serialized).""" engine_new_payload_block_access_list: Bytes | None = None """EIP-7928: override only the engine newPayload blockAccessList field.""" + engine_new_payload_slot_number: HexNumber | None = None + """EIP-7843: override only the engine payload slotNumber field.""" expected_gas_used: int | None = None """Expected gas used for the block.""" @@ -463,6 +465,7 @@ class BuiltBlock(CamelModel): fork: Fork block_access_list: BlockAccessList | None engine_new_payload_block_access_list: Bytes | None = None + engine_new_payload_slot_number: HexNumber | None = None def cumulative_gas_used(self) -> int: """Return the last receipt's cumulative gas used.""" @@ -544,6 +547,10 @@ def engine_payload_modifier( the ``block_access_list`` body. So a header modifier that touches the BAL hash needs to drive a matching change on the payload body. """ + if self.engine_new_payload_slot_number is not None: + return FixtureExecutionPayloadModifier( + slot_number=self.engine_new_payload_slot_number, + ) if self.engine_new_payload_block_access_list is not None: return FixtureExecutionPayloadModifier( block_access_list=self.engine_new_payload_block_access_list, @@ -1041,6 +1048,9 @@ def generate_block_data( engine_new_payload_block_access_list=( block.engine_new_payload_block_access_list ), + engine_new_payload_slot_number=( + block.engine_new_payload_slot_number + ), ) built_block: BuiltBlock if transition_tool_output.engine_payload is not None: @@ -1061,6 +1071,7 @@ def generate_block_data( and block.requests is None and not block.skip_exception_verification and block.engine_new_payload_block_access_list is None + and block.engine_new_payload_slot_number is None and not ( block.expected_block_access_list is not None and block.expected_block_access_list._modifier is not None diff --git a/tests/amsterdam/eip7843_slotnum/eip_checklist_external_coverage.txt b/tests/amsterdam/eip7843_slotnum/eip_checklist_external_coverage.txt new file mode 100644 index 00000000000..c2ba0cb33d3 --- /dev/null +++ b/tests/amsterdam/eip7843_slotnum/eip_checklist_external_coverage.txt @@ -0,0 +1,3 @@ +general/code_coverage/eels = EIP-7843 adds the slot_number instruction (vm/instructions/block.py), the OPCODE_SLOTNUM gas constant, the header field (blocks.py) and its BlockEnvironment plumbing (fork.py); every line is executed when filling this suite through the EELS t8n +general/code_coverage/test_coverage = suite logic is exercised end-to-end by filling tests/amsterdam/eip7843_slotnum with the EELS filler; every parametrized arm produces a fixture with a discriminating post-state +general/code_coverage/missed_lines = no missed lines; the EIP adds no branches beyond the single instruction body, which every test in this suite executes diff --git a/tests/amsterdam/eip7843_slotnum/eip_checklist_not_applicable.txt b/tests/amsterdam/eip7843_slotnum/eip_checklist_not_applicable.txt new file mode 100644 index 00000000000..62f77e4f6eb --- /dev/null +++ b/tests/amsterdam/eip7843_slotnum/eip_checklist_not_applicable.txt @@ -0,0 +1,27 @@ +precompile = EIP-7843 does not introduce a new precompile +removed_precompile = EIP-7843 does not remove a precompile +system_contract = EIP-7843 does not introduce a new system contract +transaction_type = EIP-7843 does not introduce a new transaction type +block_body_field = EIP-7843 does not add a new block body field +block_level_constraint = EIP-7843 does not introduce a new block-level constraint +gas_cost_changes = EIP-7843 does not modify existing gas costs; it only introduces a new opcode with a fixed cost +gas_refunds_changes = EIP-7843 does not change gas refunds +blob_count_changes = EIP-7843 does not change blob counts +execution_layer_request = EIP-7843 does not introduce an execution layer request +new_transaction_validity_constraint = EIP-7843 does not introduce a new transaction validity constraint +modified_transaction_validity_constraint = EIP-7843 does not modify transaction validity constraints +opcode/test/mem_exp = SLOTNUM does not read or write memory +opcode/test/stack_underflow = SLOTNUM pops nothing and has no minimum stack height +opcode/test/stack_complex_operations = SLOTNUM is a simple push with no data portion +opcode/test/data_portion = SLOTNUM has no data portion +opcode/test/contract_creation = SLOTNUM does not create contracts +opcode/test/terminating = SLOTNUM is not a terminating opcode +opcode/test/return_data = SLOTNUM does not write to the return data buffer +opcode/test/out_of_bounds = SLOTNUM takes no inputs +opcode/test/gas_usage/memory_expansion = SLOTNUM does not access memory +opcode/test/gas_usage/out_of_gas_memory = SLOTNUM does not access memory +opcode/test/gas_usage/order_of_operations = SLOTNUM charges a single fixed fee with no gas components to order +opcode/test/execution_context/tx_context = SLOTNUM does not depend on transaction properties +opcode/test/execution_context/initcode/reentry = SLOTNUM is not a stateful opcode +block_header_field/test/value_behavior/reject = the execution layer does not constrain the slot number value; the consensus layer is the source of truth and any u64 is valid +general/code_coverage/second_client = Optional diff --git a/tests/amsterdam/eip7843_slotnum/spec.py b/tests/amsterdam/eip7843_slotnum/spec.py index db53e40f62f..32627a094e2 100644 --- a/tests/amsterdam/eip7843_slotnum/spec.py +++ b/tests/amsterdam/eip7843_slotnum/spec.py @@ -13,9 +13,5 @@ class ReferenceSpec: ref_spec_7843 = ReferenceSpec( git_path="EIPS/eip-7843.md", - version="6bc5d6b7acbc016a79fa573f98975093b5c2ca52", + version="c3bfd4ba41cf0fcbfe8c404f33ba89f5174971e0", ) - - -class Spec: - """Constants and parameters from EIP-7843.""" diff --git a/tests/amsterdam/eip7843_slotnum/test_eip_mainnet.py b/tests/amsterdam/eip7843_slotnum/test_eip_mainnet.py index 0cca8f2cd21..613dde9b49f 100644 --- a/tests/amsterdam/eip7843_slotnum/test_eip_mainnet.py +++ b/tests/amsterdam/eip7843_slotnum/test_eip_mainnet.py @@ -26,12 +26,13 @@ def test_slotnum_mainnet( pre: Alloc, ) -> None: """ - Test that SLOTNUM is callable and returns a non-zero slot number. + Test that SLOTNUM executes and pushes one stack item. - Asserts on ``POP(SLOTNUM)`` rather than the slot value itself - so the test remains valid when ``execute``-ed against a live network, - where the slot number is whatever the consensus layer transmits and - cannot be controlled by the test. + Asserts on `POP(SLOTNUM)` followed by a storage write rather than + on the slot value itself, so the test remains valid when + `execute`-ed against a live network, where the slot number is + whatever the consensus layer transmits and cannot be controlled by + the test. """ contract = pre.deploy_contract( code=Op.POP(Op.SLOTNUM) + Op.SSTORE(0, 1), diff --git a/tests/amsterdam/eip7843_slotnum/test_fork_transition.py b/tests/amsterdam/eip7843_slotnum/test_fork_transition.py index 4c0bea784a0..0952f4c3078 100644 --- a/tests/amsterdam/eip7843_slotnum/test_fork_transition.py +++ b/tests/amsterdam/eip7843_slotnum/test_fork_transition.py @@ -1,11 +1,17 @@ """Tests for EIP-7843 fork transition behavior.""" +from typing import Any + import pytest from execution_testing import ( Account, Alloc, Block, BlockchainTestFiller, + BlockException, + EIPChecklist, + EngineAPIError, + Header, Op, Transaction, ) @@ -15,7 +21,12 @@ REFERENCE_SPEC_GIT_PATH = ref_spec_7843.git_path REFERENCE_SPEC_VERSION = ref_spec_7843.version +FORK_TIMESTAMP = 15_000 + +@EIPChecklist.Opcode.Test.ForkTransition.Invalid() +@EIPChecklist.Opcode.Test.ForkTransition.At() +@EIPChecklist.BlockHeaderField.Test.ForkTransition.Initial() @pytest.mark.valid_at_transition_to("EIP7843") def test_slotnum_at_fork_transition( blockchain_test: BlockchainTestFiller, @@ -51,9 +62,9 @@ def test_slotnum_at_fork_transition( txs=[Transaction(sender=sender, to=contract)], ) for ts, slot in [ - (14_999, None), - (15_000, at_fork_slot), - (15_001, post_fork_slot), + (FORK_TIMESTAMP - 1, None), + (FORK_TIMESTAMP, at_fork_slot), + (FORK_TIMESTAMP + 1, post_fork_slot), ] ] post = { @@ -67,3 +78,89 @@ def test_slotnum_at_fork_transition( } blockchain_test(pre=pre, blocks=blocks, post=post) + + +@EIPChecklist.BlockHeaderField.Test.ForkTransition.Before() +@pytest.mark.valid_at_transition_to("EIP7843") +@pytest.mark.exception_test +@pytest.mark.parametrize( + "block_kwargs", + [ + pytest.param( + {"rlp_modifier": Header(slot_number=0)}, + id="header_field", + ), + pytest.param( + {"engine_new_payload_slot_number": 0}, + id="engine_payload_field", + marks=pytest.mark.blockchain_test_engine_only, + ), + ], +) +def test_invalid_pre_fork_block_with_slot_number( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + block_kwargs: dict[str, Any], +) -> None: + """ + Reject a pre-fork block that carries the slot number field in its + header or its engine `newPayload`. + + The field must not be present before the fork activates: the extra + header field changes the header shape, while in the payload case + the block is otherwise valid, so clients that silently drop + unknown payload fields would answer VALID and must fail this test. + """ + sender = pre.fund_eoa() + receiver = pre.fund_eoa(amount=0) + + tx = Transaction(sender=sender, to=receiver, value=100) + + blockchain_test( + pre=pre, + post={}, + blocks=[ + Block( + timestamp=FORK_TIMESTAMP - 1, + txs=[tx], + exception=BlockException.INCORRECT_BLOCK_FORMAT, + engine_api_error_code=EngineAPIError.InvalidParams, + **block_kwargs, + ), + ], + ) + + +@EIPChecklist.BlockHeaderField.Test.ForkTransition.After() +@pytest.mark.valid_at_transition_to("EIP7843") +@pytest.mark.exception_test +def test_invalid_post_fork_block_without_slot_number( + blockchain_test: BlockchainTestFiller, + pre: Alloc, +) -> None: + """ + Reject an activation block whose header lacks the `slot_number` + field. + + From the fork activation onward the field is mandatory: a header + without it is malformed and the engine payload is missing a + parameter required by its version. + """ + sender = pre.fund_eoa() + receiver = pre.fund_eoa(amount=0) + + tx = Transaction(sender=sender, to=receiver, value=100) + + blockchain_test( + pre=pre, + post={}, + blocks=[ + Block( + timestamp=FORK_TIMESTAMP, + txs=[tx], + rlp_modifier=Header(slot_number=Header.REMOVE_FIELD), + exception=BlockException.INCORRECT_BLOCK_FORMAT, + engine_api_error_code=EngineAPIError.InvalidParams, + ), + ], + ) diff --git a/tests/amsterdam/eip7843_slotnum/test_slotnum.py b/tests/amsterdam/eip7843_slotnum/test_slotnum.py index c567da8b441..32604c22352 100644 --- a/tests/amsterdam/eip7843_slotnum/test_slotnum.py +++ b/tests/amsterdam/eip7843_slotnum/test_slotnum.py @@ -4,15 +4,19 @@ from execution_testing import ( Account, Alloc, + AuthorizationTuple, Block, BlockchainTestFiller, + EIPChecklist, Environment, Fork, Op, StateTestFiller, Transaction, + compute_create_address, ) +from ...prague.eip7702_set_code_tx.spec import Spec as Spec7702 from .spec import ref_spec_7843 REFERENCE_SPEC_GIT_PATH = ref_spec_7843.git_path @@ -21,6 +25,7 @@ pytestmark = pytest.mark.valid_from("EIP7843") +@EIPChecklist.Opcode.Test.GasUsage.ExtraGas() @pytest.mark.parametrize( "slot_number", [ @@ -41,10 +46,13 @@ def test_slotnum_value( The slot number is provided by the consensus layer and should be accessible via the SLOTNUM opcode (0x4B). + + Storage key 0 starts at a nonzero canary so the zero-slot case is + distinguishable from a transaction that failed before the SSTORE. """ # Store SLOTNUM result at storage key 0 code = Op.SSTORE(0, Op.SLOTNUM) - code_address = pre.deploy_contract(code) + code_address = pre.deploy_contract(code, storage={0: 0xBA5E}) tx = Transaction( sender=pre.fund_eoa(), @@ -65,6 +73,8 @@ def test_slotnum_value( ) +@EIPChecklist.Opcode.Test.GasUsage.Normal() +@EIPChecklist.Opcode.Test.GasUsage.OutOfGasExecution() @pytest.mark.parametrize( "gas_delta,call_succeeds", [ @@ -112,6 +122,8 @@ def test_slotnum_gas_cost( ) +@EIPChecklist.Opcode.Test.ExecutionContext.BlockContext() +@EIPChecklist.BlockHeaderField.Test.ValueBehavior.Accept() def test_slotnum_distinct_per_block( blockchain_test: BlockchainTestFiller, pre: Alloc, @@ -146,3 +158,244 @@ def test_slotnum_distinct_per_block( } blockchain_test(pre=pre, blocks=blocks, post=post) + + +@EIPChecklist.BlockHeaderField.Test.Genesis() +def test_slotnum_genesis( + blockchain_test: BlockchainTestFiller, + pre: Alloc, +) -> None: + """ + Test that the slot number header field can be set at genesis. + + The genesis header of this fixture carries a nonzero `slot_number`, + so a client must decode the field to reproduce the genesis hash. + The following block then exposes its own slot number via SLOTNUM. + """ + genesis_slot = 999 + block_slot = 1000 + + contract = pre.deploy_contract( + Op.SSTORE(0, Op.SLOTNUM), storage={0: 0xBA5E} + ) + tx = Transaction(sender=pre.fund_eoa(), to=contract) + + blockchain_test( + genesis_environment=Environment(slot_number=genesis_slot), + pre=pre, + blocks=[Block(slot_number=block_slot, txs=[tx])], + post={contract: Account(storage={0: block_slot})}, + ) + + +@EIPChecklist.Opcode.Test.StackOverflow() +@EIPChecklist.Opcode.Test.ExceptionalAbort() +@pytest.mark.parametrize( + "push_count,call_succeeds", + [ + pytest.param(1024, True, id="stack_at_limit"), + pytest.param(1025, False, id="stack_overflow"), + ], +) +def test_slotnum_stack_overflow( + state_test: StateTestFiller, + pre: Alloc, + push_count: int, + call_succeeds: bool, +) -> None: + """ + Test that SLOTNUM aborts when pushing past the 1024-item stack limit. + + The callee executes `push_count` consecutive SLOTNUM opcodes: 1024 + pushes fill the stack exactly and succeed, while the 1025th push + aborts the frame exceptionally. The caller stores the call's success + flag over a nonzero canary. + """ + callee_code = Op.SLOTNUM * push_count + Op.STOP + callee_address = pre.deploy_contract(callee_code) + + caller_code = Op.SSTORE(0, Op.CALL(gas=Op.GAS, address=callee_address)) + caller_address = pre.deploy_contract(caller_code, storage={0: 0xBA5E}) + + tx = Transaction( + sender=pre.fund_eoa(), + to=caller_address, + ) + + post = { + caller_address: Account( + storage={0: 1 if call_succeeds else 0}, + ), + } + + state_test( + env=Environment(slot_number=12345), + pre=pre, + tx=tx, + post=post, + ) + + +@EIPChecklist.Opcode.Test.ExecutionContext.Call() +@EIPChecklist.Opcode.Test.ExecutionContext.Callcode() +@EIPChecklist.Opcode.Test.ExecutionContext.Delegatecall() +@EIPChecklist.Opcode.Test.ExecutionContext.Staticcall() +@pytest.mark.with_all_call_opcodes +def test_slotnum_call_contexts( + state_test: StateTestFiller, + pre: Alloc, + call_opcode: Op, +) -> None: + """ + Test that SLOTNUM returns the slot number in every call frame type. + + The callee writes SLOTNUM to memory and returns it, so the check + also holds inside STATICCALL frames where storage writes are banned. + The caller stores the call's success flag and the returned value. + """ + slot_number = 0xC0FFEE + + callee_code = Op.MSTORE(0, Op.SLOTNUM) + Op.RETURN(0, 32) + callee_address = pre.deploy_contract(callee_code) + + caller_code = Op.SSTORE( + 0, call_opcode(address=callee_address, ret_offset=0, ret_size=32) + ) + Op.SSTORE(1, Op.MLOAD(0)) + caller_address = pre.deploy_contract(caller_code) + + tx = Transaction( + sender=pre.fund_eoa(), + to=caller_address, + ) + + post = { + caller_address: Account( + storage={0: 1, 1: slot_number}, + ), + } + + state_test( + env=Environment(slot_number=slot_number), + pre=pre, + tx=tx, + post=post, + ) + + +@EIPChecklist.Opcode.Test.ExecutionContext.SetCode() +def test_slotnum_set_code( + state_test: StateTestFiller, + pre: Alloc, +) -> None: + """ + Test SLOTNUM inside a set-code delegated account (EIP-7702). + """ + slot_number = 0xC0FFEE + + auth_signer = pre.fund_eoa(amount=0) + set_code = Op.SSTORE(0, Op.SLOTNUM) + Op.STOP + set_code_to_address = pre.deploy_contract(set_code) + + tx = Transaction( + to=auth_signer, + authorization_list=[ + AuthorizationTuple( + address=set_code_to_address, + nonce=0, + signer=auth_signer, + ), + ], + sender=pre.fund_eoa(), + ) + + post = { + set_code_to_address: Account(storage={}), + auth_signer: Account( + nonce=1, + code=Spec7702.delegation_designation(set_code_to_address), + storage={0: slot_number}, + ), + } + + state_test( + env=Environment(slot_number=slot_number), + pre=pre, + tx=tx, + post=post, + ) + + +@EIPChecklist.Opcode.Test.ExecutionContext.Initcode.Behavior() +@EIPChecklist.Opcode.Test.ExecutionContext.Initcode.Behavior.Tx() +def test_slotnum_initcode_tx( + state_test: StateTestFiller, + pre: Alloc, +) -> None: + """ + Test SLOTNUM inside the initcode of a contract-creating transaction. + """ + slot_number = 0xC0FFEE + + init_code = Op.SSTORE(0, Op.SLOTNUM) + sender = pre.fund_eoa() + contract_address = compute_create_address(address=sender, nonce=0) + + tx = Transaction(to=None, data=init_code, sender=sender) + + post = { + contract_address: Account(storage={0: slot_number}), + } + + state_test( + env=Environment(slot_number=slot_number), + pre=pre, + tx=tx, + post=post, + ) + + +@EIPChecklist.Opcode.Test.ExecutionContext.Initcode.Behavior() +@EIPChecklist.Opcode.Test.ExecutionContext.Initcode.Behavior.Opcode() +@pytest.mark.parametrize("opcode", [Op.CREATE, Op.CREATE2]) +def test_slotnum_initcode_create( + state_test: StateTestFiller, + pre: Alloc, + opcode: Op, +) -> None: + """ + Test SLOTNUM inside initcode executed via CREATE and CREATE2. + """ + slot_number = 0xC0FFEE + + init_code = Op.SSTORE(0, Op.SLOTNUM) + + factory_code = ( + Op.CALLDATACOPY(offset=0, size=len(init_code)) + + opcode(offset=0, size=len(init_code)) + + Op.STOP + ) + factory_address = pre.deploy_contract(factory_code) + + created_contract_address = compute_create_address( + address=factory_address, + nonce=1, + initcode=init_code, + opcode=opcode, + ) + + tx = Transaction( + to=factory_address, + data=init_code, + sender=pre.fund_eoa(), + ) + + post = { + created_contract_address: Account(storage={0: slot_number}), + } + + state_test( + env=Environment(slot_number=slot_number), + pre=pre, + tx=tx, + post=post, + ) From 3c15c23e7de8f59ebb2a9d22ecc11f0c1f705a98 Mon Sep 17 00:00:00 2001 From: kevaundray <kevtheappdev@gmail.com> Date: Fri, 24 Jul 2026 11:29:29 +0100 Subject: [PATCH 150/233] Open fix(tests): search for parent fork that differs in gas costs from current fork (#3228) --- .../test_exact_balance_no_fallback.py | 34 +++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_exact_balance_no_fallback.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_exact_balance_no_fallback.py index 40376c8af55..0ef7c1f9459 100644 --- a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_exact_balance_no_fallback.py +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_exact_balance_no_fallback.py @@ -18,12 +18,15 @@ the dimension genuinely got more expensive. """ +from typing import Callable, Tuple + import pytest from execution_testing import ( AccessList, Alloc, AuthorizationTuple, Fork, + GasCosts, StateTestFiller, Transaction, TransactionException, @@ -40,6 +43,24 @@ GAS_PRICE = 10 +def gas_costs_before_increase( + fork: Fork, costs: Callable[[GasCosts], Tuple[int, ...]] +) -> GasCosts: + """ + Return the gas cost schedule of the closest ancestor fork whose + constants selected by ``costs`` differ from ``fork``'s. + + Raises if no ancestor differs. When ``costs`` selects several + constants, the walk stops at the first fork where any of them + changed. + """ + current = costs(fork.gas_costs()) + ancestor = fork.parent_or_fail() + while costs(ancestor.gas_costs()) == current: + ancestor = ancestor.parent_or_fail() + return ancestor.gas_costs() + + @EIPChecklist.GasCostChanges.Test.OutOfGas() @pytest.mark.exception_test @pytest.mark.parametrize( @@ -68,7 +89,10 @@ def test_access_list_no_fallback( sender funded to the wei, that fallback must not slip through. """ new_costs = fork.gas_costs() - old_costs = fork.parent_or_fail().gas_costs() + old_costs = gas_costs_before_increase( + fork, + lambda c: (c.TX_ACCESS_LIST_ADDRESS, c.TX_ACCESS_LIST_STORAGE_KEY), + ) addr_delta = ( new_costs.TX_ACCESS_LIST_ADDRESS - old_costs.TX_ACCESS_LIST_ADDRESS ) @@ -138,7 +162,9 @@ def test_authorization_no_fallback( for that fallback. """ new_costs = fork.gas_costs() - old_costs = fork.parent_or_fail().gas_costs() + old_costs = gas_costs_before_increase( + fork, lambda c: (c.AUTH_PER_EMPTY_ACCOUNT,) + ) auth_delta = ( new_costs.AUTH_PER_EMPTY_ACCOUNT - old_costs.AUTH_PER_EMPTY_ACCOUNT ) @@ -198,7 +224,9 @@ def test_cold_account_access_no_fallback( must not execute. """ new_costs = fork.gas_costs() - old_costs = fork.parent_or_fail().gas_costs() + old_costs = gas_costs_before_increase( + fork, lambda c: (c.COLD_ACCOUNT_ACCESS,) + ) fallback_delta = ( new_costs.COLD_ACCOUNT_ACCESS - old_costs.COLD_ACCOUNT_ACCESS ) From 1ac58d7bce64c39731826d8ab0065187acbd4f94 Mon Sep 17 00:00:00 2001 From: kevaundray <kevtheappdev@gmail.com> Date: Fri, 24 Jul 2026 14:20:45 +0100 Subject: [PATCH 151/233] fix: skip ported static tests for amsterdam and later (#3231) --- tests/ported_static/conftest.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/tests/ported_static/conftest.py b/tests/ported_static/conftest.py index b26f10ade4a..c5d9aff2a9d 100644 --- a/tests/ported_static/conftest.py +++ b/tests/ported_static/conftest.py @@ -1,9 +1,10 @@ """ Conftest for ported static tests. -Temporarily skip ported static tests that fail for Amsterdam due to EIP-8037's -two-dimensional gas model. The gas limits in these ported static test cases -have not yet been updated to account for state gas. +Temporarily skip ported static tests that fail on Amsterdam and its +descendant forks due to EIP-8037's two-dimensional gas model. The gas +limits in these ported static test cases have not yet been updated to +account for state gas. TODO: Update gas limits in the 3452 failing ported static test cases and remove this skip list. @@ -12,6 +13,7 @@ from pathlib import Path import pytest +from execution_testing.forks import Amsterdam _SKIP_LIST_PATH = Path(__file__).parent / "amsterdam_skip_list.txt" _AMSTERDAM_SKIP_CASES: frozenset[str] = frozenset( @@ -49,9 +51,17 @@ def pytest_collection_modifyitems( for item in items: if "ported_static" not in item.nodeid: continue - if "fork_Amsterdam" not in item.nodeid: + callspec = getattr(item, "callspec", None) + fork = callspec.params.get("fork") if callspec else None + if fork is None or not fork >= Amsterdam: continue - normalized = _normalize_nodeid(item.nodeid) + # The skip list is written against fork_Amsterdam, but the + # EIP-8037 breakage applies equally to its descendant forks. + # Rewriting the item's fork token to Amsterdam's lets one list + # cover them all. + normalized = _normalize_nodeid(item.nodeid).replace( + f"fork_{fork.name()}", "fork_Amsterdam" + ) for skip_case in _AMSTERDAM_SKIP_CASES: if skip_case in normalized: item.add_marker(skip_marker) From ca7cac5c41b82ec49cbbe0961ba7191411caa5d2 Mon Sep 17 00:00:00 2001 From: danceratopz <danceratopz@gmail.com> Date: Fri, 24 Jul 2026 15:36:29 +0200 Subject: [PATCH 152/233] fix(consume): make release resolution robust to GitHub API rate limits (#3182) Co-authored-by: spencer-tb <spencer.tb@ethereum.org> --- .../plugins/consume/consume.py | 10 +- .../plugins/consume/releases.py | 146 ++++++--- .../tests/test_fixtures_source_input_types.py | 56 ++-- .../plugins/consume/tests/test_releases.py | 287 +++++++++++++++++- 4 files changed, 419 insertions(+), 80 deletions(-) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/consume.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/consume.py index 62ddb494040..339904a4d14 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/consume.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/consume.py @@ -43,10 +43,9 @@ from .releases import ( ReleaseTag, - get_release_page_url, - get_release_url, is_release_url, is_url, + resolve_release, ) CACHED_DOWNLOADS_DIRECTORY = ( @@ -264,8 +263,11 @@ def from_release_spec( """ if cache_folder is None: cache_folder = CACHED_DOWNLOADS_DIRECTORY - url = get_release_url(spec) - release_page = get_release_page_url(url) + # Resolve the spec once; the download URL and the release page + # both derive from the same release information. + release = resolve_release(spec) + url = release.get_asset(ReleaseTag.from_string(spec)).url + release_page = release.url destination_folder = extract_to or FixtureDownloader.get_cache_path( url, cache_folder diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/releases.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/releases.py index f74f68b382e..7c0d503ae8e 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/releases.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/releases.py @@ -1,8 +1,10 @@ """Procedures to consume fixtures from Github releases.""" import json +import logging import os import re +import tempfile from dataclasses import dataclass from datetime import datetime from pathlib import Path @@ -11,7 +13,9 @@ import platformdirs import requests -from pydantic import BaseModel, Field, RootModel +from pydantic import BaseModel, Field, RootModel, ValidationError + +logger = logging.getLogger(__name__) CACHED_RELEASE_INFORMATION_FILE = ( Path(platformdirs.user_cache_dir("ethereum-execution-spec-tests")) @@ -237,7 +241,15 @@ def download_release_information( pagination links up to `max_pages` pages, so resolution sees the 200 most recent releases per repo. Older releases fall outside this window and cannot be resolved. + + Authenticate with `GITHUB_TOKEN` (or `GH_TOKEN`) when set: + authenticated requests get 5000 requests/hour instead of the + unauthenticated 60/hour per IP address. """ + headers = {} + github_token = os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN") + if github_token: + headers["Authorization"] = f"Bearer {github_token}" all_releases = [] for repo in SUPPORTED_REPOS: current_url: str | None = ( @@ -246,7 +258,7 @@ def download_release_information( max_pages = 2 while current_url and max_pages > 0: max_pages -= 1 - response = requests.get(current_url) + response = requests.get(current_url, headers=headers) response.raise_for_status() all_releases.extend(response.json()) current_url = None @@ -260,8 +272,17 @@ def download_release_information( if destination_file: destination_file.parent.mkdir(parents=True, exist_ok=True) - with open(destination_file, "w") as file: + # Write via a uniquely-named temporary file so a concurrent + # reader never sees a partially-written cache and concurrent + # writers never share a path. + with tempfile.NamedTemporaryFile( + "w", + dir=destination_file.parent, + suffix=".tmp", + delete=False, + ) as file: json.dump(all_releases, file) + Path(file.name).replace(destination_file) return parse_release_information(all_releases) @@ -310,6 +331,26 @@ def sort_key( return max(matches, key=sort_key) +def resolves_pinned_release( + release_string: str, + release_information: List[ReleaseInformation], +) -> bool: + """ + Check whether the release information resolves a pinned version. + + A release descriptor with an explicit version refers to an immutable + git tag: once it resolves, a refresh of the release information + cannot change the result. + """ + if ReleaseTag.from_string(release_string).version is None: + return False + try: + find_release(release_string, release_information) + except NoSuchReleaseError: + return False + return True + + def get_release_url_from_release_information( release_string: str, release_information: List[ReleaseInformation] ) -> str: @@ -318,63 +359,72 @@ def get_release_url_from_release_information( return release.get_asset(ReleaseTag.from_string(release_string)).url -def get_release_page_url(release_string: str) -> str: +def resolve_release(release_string: str) -> ReleaseInformation: """ - Return the GitHub Release page URL for a specific release descriptor. - - This function can handle: - - A release string (e.g., "tests@latest" or "bal-devnet@v7.0.0") from - any repo in `SUPPORTED_REPOS`. - - A direct asset download link (e.g., - "https://github.com/ethereum/execution-specs/releases/ - download/tests%40v20.0.0/fixtures.tar.gz"). + Resolve a release descriptor string to its release information. + + Refresh the cached release information beforehand as needed (see + `get_release_information`). """ - release_information = get_release_information() + return find_release( + release_string, get_release_information(release_string) + ) - # Case 1: If it's a direct GitHub Releases download link, find which - # release in `release_information` has an asset with this exact URL. - repo_pattern = "|".join(re.escape(repo) for repo in SUPPORTED_REPOS) - regex_pattern = rf"https://github\.com/({repo_pattern})/releases/download/" - if re.match(regex_pattern, release_string): - for release in release_information: - for asset in release.assets.root: - if asset.url == release_string: - return release.url # The HTML page for this release - raise NoSuchReleaseError( - f"No release found for asset URL: {release_string}" - ) - # Case 2: Otherwise, treat it as a release descriptor (e.g., - # "tests@latest") - return find_release(release_string, release_information).url +def get_release_page_url(release_string: str) -> str: + """Get the GitHub release page URL for a release descriptor.""" + return resolve_release(release_string).url -def get_release_information() -> List[ReleaseInformation]: +def get_release_information( + release_string: str | None = None, +) -> List[ReleaseInformation]: """ - Get the release information. - - First check if the cached release information file exists. If it does, but - it is older than 4 hours, delete the file, unless running inside a CI - environment or a Docker container. Then download the release information - from the Github API and save it to the cache file. + Get the release information, refreshing the cache file as needed. + + Return the cached release information if the cache file is fresh + (younger than 4 hours; any age when running inside a CI environment + or a Docker container). A stale cache is also used without + refreshing when `release_string` pins an exact version that the + cache already resolves: release tags are immutable, so the cached + entry cannot be outdated. Otherwise re-download the release + information, keeping the stale cache as a fallback in case the + GitHub API is unavailable (e.g. rate-limited). """ + cached_information: List[ReleaseInformation] | None = None if CACHED_RELEASE_INFORMATION_FILE.exists(): - last_modified = CACHED_RELEASE_INFORMATION_FILE.stat().st_mtime - if ( - datetime.now().timestamp() - last_modified - ) < 4 * 60 * 60 or is_docker_or_ci(): - return parse_release_information_from_file( + try: + cached_information = parse_release_information_from_file( CACHED_RELEASE_INFORMATION_FILE ) - CACHED_RELEASE_INFORMATION_FILE.unlink() - if not CACHED_RELEASE_INFORMATION_FILE.exists(): + except (json.JSONDecodeError, ValidationError): + logger.warning( + "Ignoring corrupt release information cache at " + f"{CACHED_RELEASE_INFORMATION_FILE}." + ) + else: + last_modified = CACHED_RELEASE_INFORMATION_FILE.stat().st_mtime + cache_age = datetime.now().timestamp() - last_modified + if cache_age < 4 * 60 * 60 or is_docker_or_ci(): + return cached_information + if release_string is not None and resolves_pinned_release( + release_string, cached_information + ): + return cached_information + try: return download_release_information(CACHED_RELEASE_INFORMATION_FILE) - return parse_release_information_from_file(CACHED_RELEASE_INFORMATION_FILE) + except requests.RequestException as error: + if cached_information is None: + raise + logger.warning( + f"Could not refresh release information from the GitHub API " + f"({error}); falling back to the stale cache at " + f"{CACHED_RELEASE_INFORMATION_FILE}." + ) + return cached_information def get_release_url(release_string: str) -> str: - """Get the URL for a specific release.""" - release_information = get_release_information() - return get_release_url_from_release_information( - release_string, release_information - ) + """Get the asset download URL for a release descriptor.""" + release = resolve_release(release_string) + return release.get_asset(ReleaseTag.from_string(release_string)).url diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/tests/test_fixtures_source_input_types.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/tests/test_fixtures_source_input_types.py index 5a5418006d3..97a229c49ef 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/tests/test_fixtures_source_input_types.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/tests/test_fixtures_source_input_types.py @@ -39,34 +39,36 @@ def test_fixtures_source_from_release_spec_makes_api_calls(self) -> None: test_spec = "tests@latest" with patch( - "execution_testing.cli.pytest_commands.plugins.consume.consume.get_release_url" - ) as mock_get_url: - mock_get_url.return_value = "https://github.com/ethereum/execution-specs/releases/download/tests%40v20.0.0/fixtures.tar.gz" + "execution_testing.cli.pytest_commands.plugins.consume.consume.resolve_release" + ) as mock_resolve: + mock_release = MagicMock() + mock_release.url = "https://github.com/ethereum/execution-specs/releases/tag/tests%40v20.0.0" + mock_release.get_asset.return_value.url = "https://github.com/ethereum/execution-specs/releases/download/tests%40v20.0.0/fixtures.tar.gz" + mock_resolve.return_value = mock_release with patch( - "execution_testing.cli.pytest_commands.plugins.consume.consume.get_release_page_url" - ) as mock_get_page: - mock_get_page.return_value = "https://github.com/ethereum/execution-specs/releases/tag/tests%40v20.0.0" - with patch( - "execution_testing.cli.pytest_commands.plugins.consume.consume.FixtureDownloader" - ) as mock_downloader: - mock_instance = MagicMock() - mock_instance.download_and_extract.return_value = ( - False, - Path("/tmp/test"), - ) - mock_downloader.return_value = mock_instance - - source = FixturesSource.from_release_spec(test_spec) - - # Verify API calls were made and release page is set - mock_get_url.assert_called_once_with(test_spec) - mock_get_page.assert_called_once_with( - "https://github.com/ethereum/execution-specs/releases/download/tests%40v20.0.0/fixtures.tar.gz" - ) - assert ( - source.release_page - == "https://github.com/ethereum/execution-specs/releases/tag/tests%40v20.0.0" - ) + "execution_testing.cli.pytest_commands.plugins.consume.consume.FixtureDownloader" + ) as mock_downloader: + mock_instance = MagicMock() + mock_instance.download_and_extract.return_value = ( + False, + Path("/tmp/test"), + ) + mock_downloader.return_value = mock_instance + + source = FixturesSource.from_release_spec(test_spec) + + # The spec is resolved exactly once; the download URL and + # the release page both derive from the same release + # information. + mock_resolve.assert_called_once_with(test_spec) + assert ( + source.url + == "https://github.com/ethereum/execution-specs/releases/download/tests%40v20.0.0/fixtures.tar.gz" + ) + assert ( + source.release_page + == "https://github.com/ethereum/execution-specs/releases/tag/tests%40v20.0.0" + ) def test_fixtures_source_from_regular_url_no_release_page(self) -> None: """Test that regular URLs (non-GitHub) don't have release page.""" diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/tests/test_releases.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/tests/test_releases.py index cb347efc946..fbb0257b71f 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/tests/test_releases.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/tests/test_releases.py @@ -1,15 +1,23 @@ """Test release parsing given the github repository release JSON data.""" +import os +import shutil +import time from os.path import realpath from pathlib import Path -from typing import List +from typing import Any, Dict, List import pytest +import requests +from .. import releases from ..releases import ( SUPPORTED_REPOS, NoSuchReleaseError, ReleaseInformation, + download_release_information, + get_release_page_url, + get_release_url, get_release_url_from_release_information, is_release_url, parse_release_information_from_file, @@ -221,3 +229,280 @@ def test_supported_repos_contains_execution_specs() -> None: `tests-bal@v7.1.0` onward) and must be in `SUPPORTED_REPOS`. """ assert "ethereum/execution-specs" in SUPPORTED_REPOS + + +class FakeResponse: + """A minimal stand-in for `requests.Response`.""" + + def __init__( + self, payload: List[Dict], rate_limited: bool = False + ) -> None: + """Initialize with a JSON payload or a rate-limited failure.""" + self.payload = payload + self.rate_limited = rate_limited + self.headers: Dict[str, str] = {} + + def json(self) -> List[Dict]: + """Return the JSON payload.""" + return self.payload + + def raise_for_status(self) -> None: + """Raise an `HTTPError` if the response is rate-limited.""" + if self.rate_limited: + raise requests.exceptions.HTTPError( + "403 Client Error: rate limit exceeded" + ) + + +def fake_release(tag_name: str, asset_name: str) -> Dict: + """Build a minimal GitHub API release entry.""" + encoded_tag = tag_name.replace("@", "%40") + return { + "html_url": "https://github.com/ethereum/execution-specs/releases/" + f"tag/{encoded_tag}", + "id": 1, + "tag_name": tag_name, + "name": tag_name, + "created_at": "2026-07-15T00:00:00Z", + "published_at": "2026-07-15T00:00:00Z", + "assets": [ + { + "browser_download_url": "https://github.com/ethereum/" + f"execution-specs/releases/download/{encoded_tag}/" + f"{asset_name}", + "id": 1, + "name": asset_name, + "content_type": "application/gzip", + "size": 1, + } + ], + } + + +@pytest.fixture +def release_cache_path( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> Path: + """ + Redirect the release-information cache to a temporary path. + + Also disable the CI/Docker detection so the freshness check applies + (in CI, the cache never expires). + """ + cache_file = tmp_path / "release_information.json" + monkeypatch.setattr( + releases, "CACHED_RELEASE_INFORMATION_FILE", cache_file + ) + monkeypatch.setattr(releases, "is_docker_or_ci", lambda: False) + return cache_file + + +@pytest.fixture +def release_information_cache(release_cache_path: Path) -> Path: + """Populate the redirected cache with a copy of the test manifest.""" + shutil.copyfile( + CURRENT_FOLDER / "release_information.json", release_cache_path + ) + return release_cache_path + + +def make_stale(cache_file: Path) -> None: + """Age the cache file's mtime beyond the 4-hour freshness window.""" + stale_time = time.time() - 5 * 60 * 60 + os.utime(cache_file, (stale_time, stale_time)) + + +def block_api(monkeypatch: pytest.MonkeyPatch) -> None: + """Make any GitHub API request fail the test.""" + + def no_api(*args: Any, **kwargs: Any) -> None: + del args, kwargs + pytest.fail("The GitHub API must not be hit") + + monkeypatch.setattr(releases.requests, "get", no_api) + + +def rate_limited_get(*args: Any, **kwargs: Any) -> FakeResponse: + """Return a rate-limited (403) GitHub API response.""" + del args, kwargs + return FakeResponse([], rate_limited=True) + + +def new_release_get(*args: Any, **kwargs: Any) -> FakeResponse: + """Return a single-page response with a new `tests@v21.0.0` release.""" + del args, kwargs + return FakeResponse([fake_release("tests@v21.0.0", "fixtures.tar.gz")]) + + +def test_pinned_release_resolves_from_stale_cache_without_api( + release_information_cache: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + """ + A pinned version already resolvable from the cache must not refresh. + + Release tags are immutable, so a cached entry for an exact version + cannot be outdated, no matter how old the cache file is. Regression + test for `consume --input=tests@vX.Y.Z` raising INTERNALERROR when + the unauthenticated GitHub API rate limit is exhausted, even though + the (stale) cache resolved the release. + """ + make_stale(release_information_cache) + block_api(monkeypatch) + assert get_release_url("tests@v20.0.0") == ( + "https://github.com/ethereum/execution-specs/releases/download/" + "tests%40v20.0.0/fixtures.tar.gz" + ) + assert get_release_page_url("tests@v20.0.0") == ( + "https://github.com/ethereum/execution-specs/releases/tag/" + "tests%40v20.0.0" + ) + assert release_information_cache.exists() + + +def test_fresh_cache_resolves_latest_without_api( + release_information_cache: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + """A fresh cache resolves unpinned lookups without an API request.""" + del release_information_cache + block_api(monkeypatch) + assert get_release_url("tests@latest").endswith( + "tests%40v20.0.0/fixtures.tar.gz" + ) + + +def test_rate_limited_refresh_falls_back_to_stale_cache( + release_information_cache: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + """ + A failed refresh must fall back to the stale cache, not delete it. + + Previously the stale cache file was deleted before the download was + attempted, so a rate-limited refresh crashed the run and left no + cache at all, forcing every subsequent run onto the API. + """ + make_stale(release_information_cache) + monkeypatch.setattr(releases.requests, "get", rate_limited_get) + assert get_release_url("tests@latest").endswith( + "tests%40v20.0.0/fixtures.tar.gz" + ) + assert release_information_cache.exists() + + +def test_rate_limited_refresh_without_cache_raises( + release_cache_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + """Without a cache file, a failed refresh is a hard error.""" + del release_cache_path + monkeypatch.setattr(releases.requests, "get", rate_limited_get) + with pytest.raises(requests.exceptions.HTTPError): + get_release_url("tests@latest") + + +def test_unpinned_release_refreshes_stale_cache( + release_information_cache: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + """ + An unpinned lookup with a stale cache must refresh from the API. + + `latest` and bare feature names can resolve to a newer release at + any time, so the pinned-release fast path must not apply to them. + """ + make_stale(release_information_cache) + calls: List[str] = [] + + def fake_get(url: str, **kwargs: Any) -> FakeResponse: + calls.append(url) + return new_release_get(url, **kwargs) + + monkeypatch.setattr(releases.requests, "get", fake_get) + assert get_release_url("tests@latest").endswith( + "tests%40v21.0.0/fixtures.tar.gz" + ) + assert len(calls) == len(SUPPORTED_REPOS) + assert "tests@v21.0.0" in release_information_cache.read_text() + + +def test_pinned_release_not_in_stale_cache_refreshes( + release_information_cache: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + """ + A pinned version missing from the stale cache must refresh. + + The pinned-release fast path only applies when the cache already + resolves the requested version. + """ + make_stale(release_information_cache) + monkeypatch.setattr(releases.requests, "get", new_release_get) + assert get_release_url("tests@v21.0.0").endswith( + "tests%40v21.0.0/fixtures.tar.gz" + ) + + +def test_corrupt_cache_file_is_refreshed( + release_information_cache: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + """ + A corrupt cache file must be re-downloaded, not crash the run. + + A partially-written download (e.g. a killed process) must not wedge + every subsequent run until the file is manually deleted. + """ + release_information_cache.write_text("{ not json") + monkeypatch.setattr(releases.requests, "get", new_release_get) + assert get_release_url("tests@v21.0.0").endswith( + "tests%40v21.0.0/fixtures.tar.gz" + ) + + +@pytest.mark.parametrize( + "environment,expected_token", + [ + pytest.param({}, None, id="unauthenticated"), + pytest.param( + {"GITHUB_TOKEN": "ghp_test_token"}, + "ghp_test_token", + id="github_token", + ), + pytest.param( + {"GH_TOKEN": "gho_test_token"}, + "gho_test_token", + id="gh_token", + ), + pytest.param( + {"GITHUB_TOKEN": "ghp_test_token", "GH_TOKEN": "gho_other"}, + "ghp_test_token", + id="github_token_wins", + ), + ], +) +def test_download_release_information_github_token( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, + environment: Dict[str, str], + expected_token: str | None, +) -> None: + """ + Authenticate GitHub API requests iff a GitHub token is set. + + `GITHUB_TOKEN` (preferred) or `GH_TOKEN` (the gh CLI's name) + authenticates the request: 5000 requests/hour instead of the + unauthenticated 60 requests/hour per IP. + """ + for variable in ("GITHUB_TOKEN", "GH_TOKEN"): + monkeypatch.delenv(variable, raising=False) + for variable, token in environment.items(): + monkeypatch.setenv(variable, token) + seen_headers: List[Dict[str, str]] = [] + + def fake_get(url: str, **kwargs: Any) -> FakeResponse: + seen_headers.append(kwargs.get("headers") or {}) + return new_release_get(url, **kwargs) + + monkeypatch.setattr(releases.requests, "get", fake_get) + download_release_information(tmp_path / "release_information.json") + expected_headers = ( + {} + if expected_token is None + else {"Authorization": f"Bearer {expected_token}"} + ) + assert seen_headers == [expected_headers] * len(SUPPORTED_REPOS) From 85aa48c742c38a2d5a876f84ebf8082a50273064 Mon Sep 17 00:00:00 2001 From: Stefan <22667037+qu0b@users.noreply.github.com> Date: Fri, 24 Jul 2026 16:53:32 +0200 Subject: [PATCH 153/233] tests(amsterdam): EIP-2780 - pin receipt status of top-frame OOG tx in multi-tx blocks (#3232) A transaction that out-of-gases on an EIP-2780/EIP-8037 top-frame charge is included in the block but must produce a failed receipt. All existing top-frame OOG tests place the failing transaction alone in a block, so a client that derives the receipt status from stale shared per-block state still passes them: the stale value in a fresh block happens to be "failed". Add a blockchain test to test_top_frame_charges.py that sandwiches the top-frame failure between two successful transactions, making the status byte load-bearing in the header receiptsRoot. Parametrized over the three top-frame charge classes: contract-creation NEW_ACCOUNT state gas, value-to-empty NEW_ACCOUNT state gas, and delegated-recipient COLD_ACCOUNT_ACCESS regular gas. Every receipt is pinned explicitly via expected_receipt (status, cumulative gas, and gas_used on the failing tx). Catches the nimbus-eth1 1f8dd2122 regression that receipted top-frame failures with the previous transaction's status and rejected canonical finalized blocks on glamsterdam-devnet-7 (receiptRoot mismatch). Claude-Session: https://claude.ai/code/session_01GpkqKnXjpdXGJ4ChNGsxEY Co-authored-by: Guruprasad Kamath <guru241987@gmail.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> --- .../test_top_frame_charges.py | 217 ++++++++++++++++++ 1 file changed, 217 insertions(+) diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_top_frame_charges.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_top_frame_charges.py index a89a0fddbbe..9d59d9d7be8 100644 --- a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_top_frame_charges.py +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_top_frame_charges.py @@ -20,6 +20,8 @@ created account's own destruction. """ +from enum import Enum, auto + import pytest from execution_testing import ( Account, @@ -812,3 +814,218 @@ def test_initcode_selfdestruct_state_gas_in_header( created: None, }, ) + + +class TopFrameFailureMode(Enum): + """The top-frame charge the failing transaction out-of-gases on.""" + + CREATE_STATE_OOG = auto() + NEW_ACCOUNT_STATE_OOG = auto() + DELEGATED_REGULAR_OOG = auto() + + +@pytest.mark.parametrize( + "failure_mode", + [ + pytest.param( + TopFrameFailureMode.CREATE_STATE_OOG, + id="create_state_oog", + ), + pytest.param( + TopFrameFailureMode.NEW_ACCOUNT_STATE_OOG, + id="new_account_state_oog", + ), + pytest.param( + TopFrameFailureMode.DELEGATED_REGULAR_OOG, + id="delegated_regular_oog", + ), + ], +) +def test_receipt_status_top_frame_oog_between_successful_txs( + fork: Fork, + pre: Alloc, + blockchain_test: BlockchainTestFiller, + failure_mode: TopFrameFailureMode, +) -> None: + """ + Pin the failed receipt status of a top-frame OOG transaction that + sits between two successful transactions in one block. + + A transaction that out-of-gases on a top-frame charge never + dispatches into the EVM but is still included and must produce a + ``succeeded=False`` receipt, committed to the header + ``receiptsRoot``. The other top-frame OOG tests place the failing + transaction alone in its block, so an implementation that derives + the receipt status from stale shared per-block execution state + still passes them: the stale value in a fresh block happens to be + "failed". Sandwiching the failure between successful transactions + makes the status byte load-bearing. (Regression: nimbus-eth1 + ``1f8dd2122`` receipted top-frame failures with the previous + transaction's status and rejected finalized canonical blocks on + glamsterdam-devnet-7 with ``receiptRoot mismatch``.) + + The middle transaction passes the intrinsic check but out-of-gases + on a top-frame charge before any EVM bytecode runs: + + - ``create_state_oog``: contract creation; the created account's + ``NEW_ACCOUNT`` state charge fires at the top frame and the gas + limit is one short of covering it. + - ``new_account_state_oog``: value transfer to an empty recipient; + the ``NEW_ACCOUNT`` state charge fires and the gas limit is one + short. + - ``delegated_regular_oog``: recipient holds an EIP-7702 + delegation; the ``COLD_ACCOUNT_ACCESS`` regular charge fires and + the gas limit is one short. + + The failing transaction burns its full gas limit, bumps the sender + nonce, and must produce a ``succeeded=False`` receipt between two + ``succeeded=True`` receipts. + """ + gas_price = 1_000_000_000 + value = 1 + + sender_initial_balance = 10**18 + ok_sender_1 = pre.fund_eoa(sender_initial_balance) + ok_sender_2 = pre.fund_eoa(sender_initial_balance) + fail_sender = pre.fund_eoa(sender_initial_balance) + # Alive via balance, so the successful transfers to it incur no + # top-frame charge and consume exactly their intrinsic gas. + ok_recipient = pre.fund_eoa(amount=1) + + intrinsic_cost = fork.transaction_intrinsic_cost_calculator() + + fail_target: Address | None = None + fail_target_post: Account | None = None + if failure_mode is TopFrameFailureMode.CREATE_STATE_OOG: + intrinsic_gas = intrinsic_cost( + contract_creation=True, + return_cost_deducted_prior_execution=True, + ) + top_frame_state_gas = fork.transaction_top_frame_state_gas( + contract_creation=True, + ) + assert top_frame_state_gas > 0, ( + "contract creation must charge NEW_ACCOUNT at the top frame" + ) + fail_gas_limit = intrinsic_gas + top_frame_state_gas - 1 + fail_to: Address | None = None + elif failure_mode is TopFrameFailureMode.NEW_ACCOUNT_STATE_OOG: + intrinsic_gas = intrinsic_cost( + sends_value=True, + recipient_type=RecipientType.EMPTY_ACCOUNT, + return_cost_deducted_prior_execution=True, + ) + top_frame_state_gas = fork.transaction_top_frame_state_gas( + sends_value=True, + recipient_type=RecipientType.EMPTY_ACCOUNT, + ) + assert top_frame_state_gas > 0, ( + "value transfer to an empty recipient must charge " + "NEW_ACCOUNT at the top frame" + ) + fail_gas_limit = intrinsic_gas + top_frame_state_gas - 1 + fail_to = pre.fund_eoa(amount=0) + fail_target = fail_to + # The rolled-back transfer must not bring the recipient into + # existence. + fail_target_post = None + elif failure_mode is TopFrameFailureMode.DELEGATED_REGULAR_OOG: + delegated_to = pre.deploy_contract(code=Op.STOP) + target_code = Spec7702.delegation_designation(delegated_to) + fail_to = pre.deploy_contract(code=target_code) + intrinsic_gas = intrinsic_cost( + recipient_type=RecipientType.DELEGATION_7702, + return_cost_deducted_prior_execution=True, + ) + top_frame_gas = fork.transaction_top_frame_gas_calculator()( + recipient_type=RecipientType.DELEGATION_7702, + ) + assert top_frame_gas > 0, ( + "a delegated recipient must charge COLD_ACCOUNT_ACCESS " + "at the top frame" + ) + fail_gas_limit = intrinsic_gas + top_frame_gas - 1 + fail_target = fail_to + fail_target_post = Account(balance=0, code=target_code) + else: + raise ValueError(f"unhandled failure mode: {failure_mode}") + + # The successful transfers go to an alive EOA: no top-frame charge, + # no EVM execution, so each consumes exactly its intrinsic gas. + ok_intrinsic_gas = intrinsic_cost( + sends_value=True, + recipient_type=RecipientType.EOA, + return_cost_deducted_prior_execution=True, + ) + assert ( + fork.transaction_top_frame_state_gas( + sends_value=True, + recipient_type=RecipientType.EOA, + ) + == 0 + ), "an alive recipient must not incur a top-frame state charge" + + ok_tx_1 = Transaction( + sender=ok_sender_1, + to=ok_recipient, + value=value, + gas_limit=ok_intrinsic_gas, + gas_price=gas_price, + expected_receipt=TransactionReceipt( + status=1, + cumulative_gas_used=ok_intrinsic_gas, + ), + ) + fail_tx = Transaction( + sender=fail_sender, + to=fail_to, + value=( + value + if failure_mode is TopFrameFailureMode.NEW_ACCOUNT_STATE_OOG + else 0 + ), + gas_limit=fail_gas_limit, + gas_price=gas_price, + expected_receipt=TransactionReceipt( + status=0, + gas_used=fail_gas_limit, + cumulative_gas_used=ok_intrinsic_gas + fail_gas_limit, + ), + ) + ok_tx_2 = Transaction( + sender=ok_sender_2, + to=ok_recipient, + value=value, + gas_limit=ok_intrinsic_gas, + gas_price=gas_price, + expected_receipt=TransactionReceipt( + status=1, + cumulative_gas_used=2 * ok_intrinsic_gas + fail_gas_limit, + ), + ) + + ok_sender_final_balance = ( + sender_initial_balance - value - ok_intrinsic_gas * gas_price + ) + post: dict[Address, Account | None] = { + ok_sender_1: Account(nonce=1, balance=ok_sender_final_balance), + ok_sender_2: Account(nonce=1, balance=ok_sender_final_balance), + ok_recipient: Account(balance=1 + 2 * value), + # The failing transaction is included: the nonce bumps and the + # full gas limit is paid, but nothing else happens. + fail_sender: Account( + nonce=1, + balance=sender_initial_balance - fail_gas_limit * gas_price, + ), + } + if failure_mode is TopFrameFailureMode.CREATE_STATE_OOG: + post[fail_tx.created_contract] = None + else: + assert fail_target is not None + post[fail_target] = fail_target_post + + blockchain_test( + pre=pre, + blocks=[Block(txs=[ok_tx_1, fail_tx, ok_tx_2])], + post=post, + ) From 00bd585693f092e2cf7cc82032ec421abefc30d3 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian <jsign.uy@gmail.com> Date: Sun, 26 Jul 2026 07:49:34 -0300 Subject: [PATCH 154/233] fix(tests): update withdrawal and consolidation tests to use Header for requests verification (#3235) --- .../test_modified_withdrawal_contract.py | 21 ++++++++--------- .../test_modified_consolidation_contract.py | 23 ++++++++----------- 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/tests/prague/eip7002_el_triggerable_withdrawals/test_modified_withdrawal_contract.py b/tests/prague/eip7002_el_triggerable_withdrawals/test_modified_withdrawal_contract.py index f218318274f..f83b13d5082 100644 --- a/tests/prague/eip7002_el_triggerable_withdrawals/test_modified_withdrawal_contract.py +++ b/tests/prague/eip7002_el_triggerable_withdrawals/test_modified_withdrawal_contract.py @@ -12,6 +12,7 @@ Block, BlockchainTestFiller, Bytecode, + Header, Op, Requests, SystemContractInteractionTransaction, @@ -94,21 +95,19 @@ def test_extra_withdrawals( """ modified_code: Bytecode = Bytecode() memory_offset: int = 0 - amount_of_requests: int = 0 for withdrawal_request in requests_list: - # update memory_offset with the correct value - withdrawal_request_bytes_amount: int = len(bytes(withdrawal_request)) - assert withdrawal_request_bytes_amount == 76, ( + record = bytes(withdrawal_request) + assert len(record) == 76, ( "Expected withdrawal request to be of size 76 but got size " - f"{withdrawal_request_bytes_amount}" + f"{len(record)}" ) - memory_offset += withdrawal_request_bytes_amount + # Store records contiguously from offset 0 so the returned data is + # exactly the concatenated records (no gap, no trailing padding). + modified_code += Om.MSTORE(record, memory_offset) + memory_offset += len(record) - modified_code += Om.MSTORE(bytes(withdrawal_request), memory_offset) - amount_of_requests += 1 - - modified_code += Op.RETURN(0, Op.MSIZE()) + modified_code += Op.RETURN(0, memory_offset) pre[Spec_EIP7002.WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS] = Account( code=modified_code, @@ -131,7 +130,7 @@ def test_extra_withdrawals( blocks=[ Block( txs=txs, - requests_hash=Requests(*requests_list), + header_verify=Header(requests_hash=Requests(*requests_list)), ), ], post={}, diff --git a/tests/prague/eip7251_consolidations/test_modified_consolidation_contract.py b/tests/prague/eip7251_consolidations/test_modified_consolidation_contract.py index 81def366386..ef63436c21c 100644 --- a/tests/prague/eip7251_consolidations/test_modified_consolidation_contract.py +++ b/tests/prague/eip7251_consolidations/test_modified_consolidation_contract.py @@ -12,6 +12,7 @@ Block, BlockchainTestFiller, Bytecode, + Header, Op, Requests, SystemContractInteractionTransaction, @@ -93,23 +94,19 @@ def test_extra_consolidations( """ modified_code: Bytecode = Bytecode() memory_offset: int = 0 - amount_of_requests: int = 0 for consolidation_request in requests_list: - # update memory_offset with the correct value - consolidation_request_bytes_amount: int = len( - bytes(consolidation_request) - ) - assert consolidation_request_bytes_amount == 116, ( + record = bytes(consolidation_request) + assert len(record) == 116, ( "Expected consolidation request to be of size 116 but got size " - f"{consolidation_request_bytes_amount}" + f"{len(record)}" ) - memory_offset += consolidation_request_bytes_amount - - modified_code += Om.MSTORE(bytes(consolidation_request), memory_offset) - amount_of_requests += 1 + # Store records contiguously from offset 0 so the returned data is + # exactly the concatenated records (no gap, no trailing padding). + modified_code += Om.MSTORE(record, memory_offset) + memory_offset += len(record) - modified_code += Op.RETURN(0, Op.MSIZE()) + modified_code += Op.RETURN(0, memory_offset) pre[Spec_EIP7251.CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS] = Account( code=modified_code, @@ -132,7 +129,7 @@ def test_extra_consolidations( blocks=[ Block( txs=txs, - requests_hash=Requests(*requests_list), + header_verify=Header(requests_hash=Requests(*requests_list)), ), ], post={}, From 853585f326bca3db05af886695db4d48bcb63bc5 Mon Sep 17 00:00:00 2001 From: kevaundray <kevtheappdev@gmail.com> Date: Mon, 27 Jul 2026 10:38:21 +0100 Subject: [PATCH 155/233] fix: max balance < 2^128 in ported static tests (#3230) --- tests/ported_static/stCreate2/test_create2_bounds.py | 4 +--- tests/ported_static/stCreate2/test_create2_bounds2.py | 4 +--- tests/ported_static/stCreate2/test_create2_bounds3.py | 4 +--- tests/ported_static/stMemoryStressTest/test_call_bounds.py | 4 +--- tests/ported_static/stMemoryStressTest/test_call_bounds2.py | 4 +--- tests/ported_static/stMemoryStressTest/test_call_bounds2a.py | 4 +--- tests/ported_static/stMemoryStressTest/test_call_bounds3.py | 4 +--- .../ported_static/stMemoryStressTest/test_callcode_bounds.py | 4 +--- .../ported_static/stMemoryStressTest/test_callcode_bounds2.py | 4 +--- .../ported_static/stMemoryStressTest/test_callcode_bounds3.py | 4 +--- .../ported_static/stMemoryStressTest/test_callcode_bounds4.py | 4 +--- tests/ported_static/stMemoryStressTest/test_create_bounds.py | 4 +--- tests/ported_static/stMemoryStressTest/test_create_bounds2.py | 4 +--- tests/ported_static/stMemoryStressTest/test_create_bounds3.py | 4 +--- .../stMemoryStressTest/test_delegatecall_bounds.py | 4 +--- .../stMemoryStressTest/test_delegatecall_bounds2.py | 4 +--- .../stMemoryStressTest/test_delegatecall_bounds3.py | 4 +--- tests/ported_static/stMemoryStressTest/test_mstore_bounds.py | 4 +--- tests/ported_static/stMemoryStressTest/test_mstore_bounds2.py | 4 +--- tests/ported_static/stMemoryStressTest/test_return_bounds.py | 4 +--- .../stMemoryStressTest/test_static_call_bounds.py | 4 +--- .../stMemoryStressTest/test_static_call_bounds2.py | 4 +--- .../stMemoryStressTest/test_static_call_bounds2a.py | 4 +--- .../stMemoryStressTest/test_static_call_bounds3.py | 4 +--- tests/ported_static/stTransactionTest/test_high_gas_limit.py | 4 +--- 25 files changed, 25 insertions(+), 75 deletions(-) diff --git a/tests/ported_static/stCreate2/test_create2_bounds.py b/tests/ported_static/stCreate2/test_create2_bounds.py index 42c5cb34ec6..ae0bebb10a2 100644 --- a/tests/ported_static/stCreate2/test_create2_bounds.py +++ b/tests/ported_static/stCreate2/test_create2_bounds.py @@ -56,9 +56,7 @@ def test_create2_bounds( """Test_create2_bounds.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) contract_0 = Address(0x1000000000000000000000000000000000000000) - sender = pre.fund_eoa( - amount=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - ) + sender = pre.fund_eoa(amount=2**128 - 1) env = Environment( fee_recipient=coinbase, diff --git a/tests/ported_static/stCreate2/test_create2_bounds2.py b/tests/ported_static/stCreate2/test_create2_bounds2.py index 175cddce83a..edc9271e1cf 100644 --- a/tests/ported_static/stCreate2/test_create2_bounds2.py +++ b/tests/ported_static/stCreate2/test_create2_bounds2.py @@ -56,9 +56,7 @@ def test_create2_bounds2( """Test_create2_bounds2.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) contract_0 = Address(0x1000000000000000000000000000000000000000) - sender = pre.fund_eoa( - amount=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - ) + sender = pre.fund_eoa(amount=2**128 - 1) env = Environment( fee_recipient=coinbase, diff --git a/tests/ported_static/stCreate2/test_create2_bounds3.py b/tests/ported_static/stCreate2/test_create2_bounds3.py index d7c3c6c325f..0bf880de2b3 100644 --- a/tests/ported_static/stCreate2/test_create2_bounds3.py +++ b/tests/ported_static/stCreate2/test_create2_bounds3.py @@ -62,9 +62,7 @@ def test_create2_bounds3( """Test_create2_bounds3.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) contract_0 = Address(0x1000000000000000000000000000000000000000) - sender = pre.fund_eoa( - amount=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - ) + sender = pre.fund_eoa(amount=2**128 - 1) env = Environment( fee_recipient=coinbase, diff --git a/tests/ported_static/stMemoryStressTest/test_call_bounds.py b/tests/ported_static/stMemoryStressTest/test_call_bounds.py index 49a2518ece9..de23205f95c 100644 --- a/tests/ported_static/stMemoryStressTest/test_call_bounds.py +++ b/tests/ported_static/stMemoryStressTest/test_call_bounds.py @@ -54,9 +54,7 @@ def test_call_bounds( ) -> None: """Test_call_bounds.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa( - amount=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - ) + sender = pre.fund_eoa(amount=2**128 - 1) env = Environment( fee_recipient=coinbase, diff --git a/tests/ported_static/stMemoryStressTest/test_call_bounds2.py b/tests/ported_static/stMemoryStressTest/test_call_bounds2.py index 3638beda9b6..3f17cdaf2a9 100644 --- a/tests/ported_static/stMemoryStressTest/test_call_bounds2.py +++ b/tests/ported_static/stMemoryStressTest/test_call_bounds2.py @@ -54,9 +54,7 @@ def test_call_bounds2( ) -> None: """Test_call_bounds2.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa( - amount=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - ) + sender = pre.fund_eoa(amount=2**128 - 1) env = Environment( fee_recipient=coinbase, diff --git a/tests/ported_static/stMemoryStressTest/test_call_bounds2a.py b/tests/ported_static/stMemoryStressTest/test_call_bounds2a.py index 1065f6cec49..d6f4738f0c4 100644 --- a/tests/ported_static/stMemoryStressTest/test_call_bounds2a.py +++ b/tests/ported_static/stMemoryStressTest/test_call_bounds2a.py @@ -54,9 +54,7 @@ def test_call_bounds2a( ) -> None: """Test_call_bounds2a.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa( - amount=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - ) + sender = pre.fund_eoa(amount=2**128 - 1) env = Environment( fee_recipient=coinbase, diff --git a/tests/ported_static/stMemoryStressTest/test_call_bounds3.py b/tests/ported_static/stMemoryStressTest/test_call_bounds3.py index 81ccd53caf4..e255a89a186 100644 --- a/tests/ported_static/stMemoryStressTest/test_call_bounds3.py +++ b/tests/ported_static/stMemoryStressTest/test_call_bounds3.py @@ -60,9 +60,7 @@ def test_call_bounds3( ) -> None: """Test_call_bounds3.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa( - amount=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - ) + sender = pre.fund_eoa(amount=2**128 - 1) env = Environment( fee_recipient=coinbase, diff --git a/tests/ported_static/stMemoryStressTest/test_callcode_bounds.py b/tests/ported_static/stMemoryStressTest/test_callcode_bounds.py index 3a7a1cd03ea..2b32019efda 100644 --- a/tests/ported_static/stMemoryStressTest/test_callcode_bounds.py +++ b/tests/ported_static/stMemoryStressTest/test_callcode_bounds.py @@ -54,9 +54,7 @@ def test_callcode_bounds( ) -> None: """Test_callcode_bounds.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa( - amount=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF # noqa: E501 - ) + sender = pre.fund_eoa(amount=2**128 - 1) env = Environment( fee_recipient=coinbase, diff --git a/tests/ported_static/stMemoryStressTest/test_callcode_bounds2.py b/tests/ported_static/stMemoryStressTest/test_callcode_bounds2.py index 5496732ba6d..6899bd3a562 100644 --- a/tests/ported_static/stMemoryStressTest/test_callcode_bounds2.py +++ b/tests/ported_static/stMemoryStressTest/test_callcode_bounds2.py @@ -54,9 +54,7 @@ def test_callcode_bounds2( ) -> None: """Test_callcode_bounds2.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa( - amount=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF # noqa: E501 - ) + sender = pre.fund_eoa(amount=2**128 - 1) env = Environment( fee_recipient=coinbase, diff --git a/tests/ported_static/stMemoryStressTest/test_callcode_bounds3.py b/tests/ported_static/stMemoryStressTest/test_callcode_bounds3.py index 9bddda9e3be..376b9832100 100644 --- a/tests/ported_static/stMemoryStressTest/test_callcode_bounds3.py +++ b/tests/ported_static/stMemoryStressTest/test_callcode_bounds3.py @@ -54,9 +54,7 @@ def test_callcode_bounds3( ) -> None: """Test_callcode_bounds3.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa( - amount=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF # noqa: E501 - ) + sender = pre.fund_eoa(amount=2**128 - 1) env = Environment( fee_recipient=coinbase, diff --git a/tests/ported_static/stMemoryStressTest/test_callcode_bounds4.py b/tests/ported_static/stMemoryStressTest/test_callcode_bounds4.py index fc8373060be..d0d8eeb698a 100644 --- a/tests/ported_static/stMemoryStressTest/test_callcode_bounds4.py +++ b/tests/ported_static/stMemoryStressTest/test_callcode_bounds4.py @@ -60,9 +60,7 @@ def test_callcode_bounds4( ) -> None: """Test_callcode_bounds4.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa( - amount=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF # noqa: E501 - ) + sender = pre.fund_eoa(amount=2**128 - 1) env = Environment( fee_recipient=coinbase, diff --git a/tests/ported_static/stMemoryStressTest/test_create_bounds.py b/tests/ported_static/stMemoryStressTest/test_create_bounds.py index 992a097d01c..a03f36c4026 100644 --- a/tests/ported_static/stMemoryStressTest/test_create_bounds.py +++ b/tests/ported_static/stMemoryStressTest/test_create_bounds.py @@ -56,9 +56,7 @@ def test_create_bounds( """Test_create_bounds.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) contract_0 = Address(0x1000000000000000000000000000000000000000) - sender = pre.fund_eoa( - amount=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - ) + sender = pre.fund_eoa(amount=2**128 - 1) env = Environment( fee_recipient=coinbase, diff --git a/tests/ported_static/stMemoryStressTest/test_create_bounds2.py b/tests/ported_static/stMemoryStressTest/test_create_bounds2.py index d136a0b2ed3..24898d687b2 100644 --- a/tests/ported_static/stMemoryStressTest/test_create_bounds2.py +++ b/tests/ported_static/stMemoryStressTest/test_create_bounds2.py @@ -56,9 +56,7 @@ def test_create_bounds2( """Test_create_bounds2.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) contract_0 = Address(0x1000000000000000000000000000000000000000) - sender = pre.fund_eoa( - amount=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - ) + sender = pre.fund_eoa(amount=2**128 - 1) env = Environment( fee_recipient=coinbase, diff --git a/tests/ported_static/stMemoryStressTest/test_create_bounds3.py b/tests/ported_static/stMemoryStressTest/test_create_bounds3.py index d78ccc430d7..54b0f78cc8f 100644 --- a/tests/ported_static/stMemoryStressTest/test_create_bounds3.py +++ b/tests/ported_static/stMemoryStressTest/test_create_bounds3.py @@ -62,9 +62,7 @@ def test_create_bounds3( """Test_create_bounds3.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) contract_0 = Address(0x1000000000000000000000000000000000000000) - sender = pre.fund_eoa( - amount=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - ) + sender = pre.fund_eoa(amount=2**128 - 1) env = Environment( fee_recipient=coinbase, diff --git a/tests/ported_static/stMemoryStressTest/test_delegatecall_bounds.py b/tests/ported_static/stMemoryStressTest/test_delegatecall_bounds.py index bdc3e3a6b0d..32b18469cf5 100644 --- a/tests/ported_static/stMemoryStressTest/test_delegatecall_bounds.py +++ b/tests/ported_static/stMemoryStressTest/test_delegatecall_bounds.py @@ -54,9 +54,7 @@ def test_delegatecall_bounds( ) -> None: """Test_delegatecall_bounds.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa( - amount=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF # noqa: E501 - ) + sender = pre.fund_eoa(amount=2**128 - 1) env = Environment( fee_recipient=coinbase, diff --git a/tests/ported_static/stMemoryStressTest/test_delegatecall_bounds2.py b/tests/ported_static/stMemoryStressTest/test_delegatecall_bounds2.py index 4dde13d2569..7a93cbfab0b 100644 --- a/tests/ported_static/stMemoryStressTest/test_delegatecall_bounds2.py +++ b/tests/ported_static/stMemoryStressTest/test_delegatecall_bounds2.py @@ -54,9 +54,7 @@ def test_delegatecall_bounds2( ) -> None: """Test_delegatecall_bounds2.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa( - amount=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF # noqa: E501 - ) + sender = pre.fund_eoa(amount=2**128 - 1) env = Environment( fee_recipient=coinbase, diff --git a/tests/ported_static/stMemoryStressTest/test_delegatecall_bounds3.py b/tests/ported_static/stMemoryStressTest/test_delegatecall_bounds3.py index 804fc02744b..963fd456f91 100644 --- a/tests/ported_static/stMemoryStressTest/test_delegatecall_bounds3.py +++ b/tests/ported_static/stMemoryStressTest/test_delegatecall_bounds3.py @@ -60,9 +60,7 @@ def test_delegatecall_bounds3( ) -> None: """Test_delegatecall_bounds3.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa( - amount=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF # noqa: E501 - ) + sender = pre.fund_eoa(amount=2**128 - 1) env = Environment( fee_recipient=coinbase, diff --git a/tests/ported_static/stMemoryStressTest/test_mstore_bounds.py b/tests/ported_static/stMemoryStressTest/test_mstore_bounds.py index 49713f67bc8..0d101040d1a 100644 --- a/tests/ported_static/stMemoryStressTest/test_mstore_bounds.py +++ b/tests/ported_static/stMemoryStressTest/test_mstore_bounds.py @@ -54,9 +54,7 @@ def test_mstore_bounds( ) -> None: """Test_mstore_bounds.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa( - amount=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF # noqa: E501 - ) + sender = pre.fund_eoa(amount=2**128 - 1) env = Environment( fee_recipient=coinbase, diff --git a/tests/ported_static/stMemoryStressTest/test_mstore_bounds2.py b/tests/ported_static/stMemoryStressTest/test_mstore_bounds2.py index 93358c7451f..ab33179dd2a 100644 --- a/tests/ported_static/stMemoryStressTest/test_mstore_bounds2.py +++ b/tests/ported_static/stMemoryStressTest/test_mstore_bounds2.py @@ -54,9 +54,7 @@ def test_mstore_bounds2( ) -> None: """Test_mstore_bounds2.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa( - amount=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF # noqa: E501 - ) + sender = pre.fund_eoa(amount=2**128 - 1) env = Environment( fee_recipient=coinbase, diff --git a/tests/ported_static/stMemoryStressTest/test_return_bounds.py b/tests/ported_static/stMemoryStressTest/test_return_bounds.py index 8918cd9d9d0..cd09e0777dc 100644 --- a/tests/ported_static/stMemoryStressTest/test_return_bounds.py +++ b/tests/ported_static/stMemoryStressTest/test_return_bounds.py @@ -63,9 +63,7 @@ def test_return_bounds( ) -> None: """Test_return_bounds.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa( - amount=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF # noqa: E501 - ) + sender = pre.fund_eoa(amount=2**128 - 1) env = Environment( fee_recipient=coinbase, diff --git a/tests/ported_static/stMemoryStressTest/test_static_call_bounds.py b/tests/ported_static/stMemoryStressTest/test_static_call_bounds.py index c6a2e86cb91..375e9fe7edc 100644 --- a/tests/ported_static/stMemoryStressTest/test_static_call_bounds.py +++ b/tests/ported_static/stMemoryStressTest/test_static_call_bounds.py @@ -54,9 +54,7 @@ def test_static_call_bounds( ) -> None: """Test_static_call_bounds.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa( - amount=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - ) + sender = pre.fund_eoa(amount=2**128 - 1) env = Environment( fee_recipient=coinbase, diff --git a/tests/ported_static/stMemoryStressTest/test_static_call_bounds2.py b/tests/ported_static/stMemoryStressTest/test_static_call_bounds2.py index b4d70c3de24..f9e56c4eaa8 100644 --- a/tests/ported_static/stMemoryStressTest/test_static_call_bounds2.py +++ b/tests/ported_static/stMemoryStressTest/test_static_call_bounds2.py @@ -54,9 +54,7 @@ def test_static_call_bounds2( ) -> None: """Test_static_call_bounds2.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa( - amount=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - ) + sender = pre.fund_eoa(amount=2**128 - 1) env = Environment( fee_recipient=coinbase, diff --git a/tests/ported_static/stMemoryStressTest/test_static_call_bounds2a.py b/tests/ported_static/stMemoryStressTest/test_static_call_bounds2a.py index c27269691ae..026e3c43c98 100644 --- a/tests/ported_static/stMemoryStressTest/test_static_call_bounds2a.py +++ b/tests/ported_static/stMemoryStressTest/test_static_call_bounds2a.py @@ -54,9 +54,7 @@ def test_static_call_bounds2a( ) -> None: """Test_static_call_bounds2a.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa( - amount=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - ) + sender = pre.fund_eoa(amount=2**128 - 1) env = Environment( fee_recipient=coinbase, diff --git a/tests/ported_static/stMemoryStressTest/test_static_call_bounds3.py b/tests/ported_static/stMemoryStressTest/test_static_call_bounds3.py index fe5980fd434..912976897c3 100644 --- a/tests/ported_static/stMemoryStressTest/test_static_call_bounds3.py +++ b/tests/ported_static/stMemoryStressTest/test_static_call_bounds3.py @@ -54,9 +54,7 @@ def test_static_call_bounds3( ) -> None: """Test_static_call_bounds3.""" coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa( - amount=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - ) + sender = pre.fund_eoa(amount=2**128 - 1) env = Environment( fee_recipient=coinbase, diff --git a/tests/ported_static/stTransactionTest/test_high_gas_limit.py b/tests/ported_static/stTransactionTest/test_high_gas_limit.py index 60069018353..7ef8e7f9202 100644 --- a/tests/ported_static/stTransactionTest/test_high_gas_limit.py +++ b/tests/ported_static/stTransactionTest/test_high_gas_limit.py @@ -55,9 +55,7 @@ def test_high_gas_limit( gas_limit=9223372036854775807, ) - pre[sender] = Account( - balance=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF # noqa: E501 - ) + pre[sender] = Account(balance=2**128 - 1) # EIP-2780 charges ``NEW_ACCOUNT`` state gas at the top frame when # value is sent to an empty recipient; with the default zero From 7c4177ace2fcabd6aaa86be043cbc813065c4bb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= <pawel@hepcolgum.band> Date: Mon, 27 Jul 2026 11:49:45 +0200 Subject: [PATCH 156/233] feat(tests): add ef_prefix deposit-halt mode to EIP-8037 state-gas test (#3233) Co-authored-by: spencer-tb <spencer.tb@ethereum.org> --- .../test_state_gas_create.py | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py index 882872cff2d..b2274b5e5f8 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py @@ -1243,6 +1243,7 @@ def test_create_no_double_charge_new_account( [ pytest.param("oversized_code", id="oversized_code"), pytest.param("oog_deposit", id="oog_deposit"), + pytest.param("ef_prefix", id="ef_prefix"), ], ) @pytest.mark.valid_from("EIP8037") @@ -1258,11 +1259,12 @@ def test_code_deposit_halt_discards_initcode_state_gas( A CREATE tx runs initcode that first performs a state-creating operation (charging GAS_NEW_ACCOUNT state gas), then returns - code that triggers a deposit failure (oversized or OOG). The - exceptional halt reverts all initcode state changes including - the new account. The reverted GAS_NEW_ACCOUNT must NOT count - in block_state_gas_used, which determines the block header - gas_used via max(block_regular_gas, block_state_gas). + code that triggers a deposit failure (oversized, OOG, or an + EIP-3541 0xEF prefix). The exceptional halt reverts all initcode + state changes including the new account. The reverted + GAS_NEW_ACCOUNT must NOT count in block_state_gas_used, which + determines the block header gas_used via + max(block_regular_gas, block_state_gas). """ subcall_forwarded_value = 1 entry_account_value = 1 @@ -1278,11 +1280,15 @@ def test_code_deposit_halt_discards_initcode_state_gas( if deposit_fail_mode == "oversized_code": deposit_fail = Op.RETURN(0, fork.max_code_size() + 1) - else: - # Return code at max size — passes the size check but code + elif deposit_fail_mode == "oog_deposit": + # Return code at max size: passes the size check but code # deposit state gas (max_code_size * cost_per_state_byte) # exceeds available state gas in the child frame, causing OOG. deposit_fail = Op.RETURN(0, fork.max_code_size()) + else: + # Return single 0xEF byte: EIP-3541 rejects the code before + # the size check or any deposit charging, halting the deposit. + deposit_fail = Op.MSTORE8(0, 0xEF) + Op.RETURN(0, 1) initcode = state_op + deposit_fail From 2cc42a8757a4a63d58411aad39e2c09240e8687f Mon Sep 17 00:00:00 2001 From: kevaundray <kevtheappdev@gmail.com> Date: Mon, 27 Jul 2026 18:47:22 +0100 Subject: [PATCH 157/233] refactor(spec): make state interface fully implementation agnostic (#3218) * initial commit * fix(tests): point module-level ethereum.state imports at state_mpt The State class and its helpers moved to ethereum.state_mpt, but two test files import the module itself rather than names from it, which the import rewrite missed: test_optimized_state.py aliases it for state_root calls (caught by mypy in CI) and load_vm_tests.py returns it as the fallback fork state module (hidden behind an Any return). * refactor(t8n): resolve each fork's state provider through the fork The evm tools hardcoded the MPT-backed provider: alloc loading built ethereum.state_mpt.State directly, and t8n applied diffs, serialized allocs, and backed up state by reaching into MPT trie internals. Resolve the provider through the fork instead: every fork's fork module imports its State class, so ForkLoad.state_provider derives the provider module from it. Alloc loading, diff application, serialization, and backup/restore all go through that module. The provider gains the uniform helpers this needs: copy_state, restore_state, all_accounts, and account_storage. With this, a fork whose commitment is not the Merkle Patricia Trie works with the tooling by supplying a provider with the same module surface as ethereum.state_mpt. * guru's comments * remove global _EMPTY_DIFF constant and just use `default` * style: ruff format test_alloc_prestate * minor fixes --------- Co-authored-by: Guruprasad Kamath <guru241987@gmail.com> --- .../test_types/account_types.py | 43 ++- .../test_types/tests/test_alloc_prestate.py | 41 +-- src/ethereum/forks/amsterdam/blocks.py | 4 +- src/ethereum/forks/amsterdam/fork.py | 13 +- src/ethereum/forks/amsterdam/state_tracker.py | 2 +- src/ethereum/forks/arrow_glacier/blocks.py | 4 +- src/ethereum/forks/arrow_glacier/fork.py | 14 +- .../forks/arrow_glacier/state_tracker.py | 2 +- src/ethereum/forks/berlin/blocks.py | 4 +- src/ethereum/forks/berlin/fork.py | 14 +- src/ethereum/forks/berlin/state_tracker.py | 2 +- src/ethereum/forks/bpo1/blocks.py | 4 +- src/ethereum/forks/bpo1/fork.py | 12 +- src/ethereum/forks/bpo1/state_tracker.py | 2 +- src/ethereum/forks/bpo2/blocks.py | 4 +- src/ethereum/forks/bpo2/fork.py | 12 +- src/ethereum/forks/bpo2/state_tracker.py | 2 +- src/ethereum/forks/bpo3/blocks.py | 4 +- src/ethereum/forks/bpo3/fork.py | 12 +- src/ethereum/forks/bpo3/state_tracker.py | 2 +- src/ethereum/forks/bpo4/blocks.py | 4 +- src/ethereum/forks/bpo4/fork.py | 12 +- src/ethereum/forks/bpo4/state_tracker.py | 2 +- src/ethereum/forks/bpo5/blocks.py | 4 +- src/ethereum/forks/bpo5/fork.py | 12 +- src/ethereum/forks/bpo5/state_tracker.py | 2 +- src/ethereum/forks/byzantium/blocks.py | 4 +- src/ethereum/forks/byzantium/fork.py | 14 +- src/ethereum/forks/byzantium/state_tracker.py | 2 +- src/ethereum/forks/cancun/blocks.py | 4 +- src/ethereum/forks/cancun/fork.py | 12 +- src/ethereum/forks/cancun/state_tracker.py | 2 +- src/ethereum/forks/constantinople/blocks.py | 4 +- src/ethereum/forks/constantinople/fork.py | 14 +- .../forks/constantinople/state_tracker.py | 2 +- src/ethereum/forks/dao_fork/blocks.py | 4 +- src/ethereum/forks/dao_fork/dao.py | 4 +- src/ethereum/forks/dao_fork/fork.py | 22 +- src/ethereum/forks/dao_fork/state_tracker.py | 2 +- src/ethereum/forks/frontier/blocks.py | 4 +- src/ethereum/forks/frontier/fork.py | 22 +- src/ethereum/forks/frontier/state_tracker.py | 2 +- src/ethereum/forks/gray_glacier/blocks.py | 4 +- src/ethereum/forks/gray_glacier/fork.py | 14 +- .../forks/gray_glacier/state_tracker.py | 2 +- src/ethereum/forks/homestead/blocks.py | 4 +- src/ethereum/forks/homestead/fork.py | 22 +- src/ethereum/forks/homestead/state_tracker.py | 2 +- src/ethereum/forks/istanbul/blocks.py | 4 +- src/ethereum/forks/istanbul/fork.py | 14 +- src/ethereum/forks/istanbul/state_tracker.py | 2 +- src/ethereum/forks/london/blocks.py | 4 +- src/ethereum/forks/london/fork.py | 14 +- src/ethereum/forks/london/state_tracker.py | 2 +- src/ethereum/forks/muir_glacier/blocks.py | 4 +- src/ethereum/forks/muir_glacier/fork.py | 14 +- .../forks/muir_glacier/state_tracker.py | 2 +- src/ethereum/forks/osaka/blocks.py | 4 +- src/ethereum/forks/osaka/fork.py | 12 +- src/ethereum/forks/osaka/state_tracker.py | 2 +- src/ethereum/forks/paris/blocks.py | 4 +- src/ethereum/forks/paris/fork.py | 14 +- src/ethereum/forks/paris/state_tracker.py | 2 +- src/ethereum/forks/prague/blocks.py | 4 +- src/ethereum/forks/prague/fork.py | 12 +- src/ethereum/forks/prague/state_tracker.py | 2 +- src/ethereum/forks/shanghai/blocks.py | 4 +- src/ethereum/forks/shanghai/fork.py | 14 +- src/ethereum/forks/shanghai/state_tracker.py | 2 +- src/ethereum/forks/spurious_dragon/blocks.py | 4 +- src/ethereum/forks/spurious_dragon/fork.py | 22 +- .../forks/spurious_dragon/state_tracker.py | 2 +- .../forks/tangerine_whistle/blocks.py | 4 +- src/ethereum/forks/tangerine_whistle/fork.py | 22 +- .../forks/tangerine_whistle/state_tracker.py | 2 +- src/ethereum/merkle_patricia_trie.py | 18 +- src/ethereum/state.py | 261 ++---------------- src/ethereum/state_mpt.py | 221 +++++++++++++++ .../evm_tools/loaders/fixture_loader.py | 10 +- .../evm_tools/loaders/fork_loader.py | 12 + .../evm_tools/t8n/result.py | 4 +- .../helpers/load_blockchain_tests.py | 2 +- tests/json_loader/helpers/load_vm_tests.py | 4 +- tests/json_loader/test_genesis.py | 11 +- tests/json_loader/test_optimized_state.py | 2 +- 85 files changed, 478 insertions(+), 658 deletions(-) create mode 100644 src/ethereum/state_mpt.py diff --git a/packages/testing/src/execution_testing/test_types/account_types.py b/packages/testing/src/execution_testing/test_types/account_types.py index f0c51791e51..e5e03717f28 100644 --- a/packages/testing/src/execution_testing/test_types/account_types.py +++ b/packages/testing/src/execution_testing/test_types/account_types.py @@ -4,7 +4,6 @@ from dataclasses import dataclass from enum import Enum, auto from typing import ( - AbstractSet, Any, Dict, ItemsView, @@ -13,13 +12,12 @@ Literal, Optional, Self, - Tuple, ) import ethereum.state as spec_state +import ethereum.state_mpt as spec_state_mpt from ethereum.crypto.hash import Hash32 from ethereum.crypto.hash import keccak256 as spec_keccak256 -from ethereum.merkle_patricia_trie import InternalNode from ethereum_types.bytes import Bytes, Bytes20 from ethereum_types.numeric import U256, Bytes32, Uint from pydantic import PrivateAttr @@ -301,7 +299,7 @@ def empty_accounts(self) -> List[Address]: def state_root(self) -> Hash: """Return state root of the allocation.""" - return Hash(spec_state.state_root(self._materialize_state())) + return Hash(spec_state_mpt.state_root(self._materialize_state())) def verify_post_alloc(self, got_alloc: "Alloc") -> None: """ @@ -358,15 +356,16 @@ def _ensure_live(self) -> None: self._build_cache() self._phase = _Phase.LIVE - def _materialize_state(self) -> spec_state.State: + def _materialize_state(self) -> spec_state_mpt.State: """ - Build an in-memory `ethereum.state.State` mirror of `self.root`. + Build an in-memory `ethereum.state_mpt.State` mirror of + `self.root`. - Used as the trie-backed delegate for - `compute_state_root_and_trie_changes` (a cold, once-per-block call). - The materialized state is not retained. + Used as the trie-backed delegate for `compute_state_root` (a + cold, once-per-block call). The materialized state is not + retained. """ - state = spec_state.State() + state = spec_state_mpt.State() for address, account in self.root.items(): if account is None: continue @@ -375,7 +374,7 @@ def _materialize_state(self) -> spec_state.State: code_hash = ( spec_keccak256(code) if code else spec_state.EMPTY_CODE_HASH ) - spec_state.set_account( + spec_state_mpt.set_account( state, addr, spec_state.Account( @@ -388,7 +387,7 @@ def _materialize_state(self) -> spec_state.State: value_int = int(value_hi) if value_int == 0: continue - spec_state.set_storage( + spec_state_mpt.set_storage( state, addr, Bytes32(int(key_hi).to_bytes(32, "big")), @@ -456,24 +455,18 @@ def account_has_storage(self, address: Bytes20) -> bool: account = self.root.get(Address(address)) return account is not None and bool(account.storage.root) - def compute_state_root_and_trie_changes( - self, - account_changes: Dict[Bytes20, Optional[spec_state.Account]], - storage_changes: Dict[Bytes20, Dict[Bytes32, U256]], - storage_clears: AbstractSet[Bytes20] = frozenset(), - ) -> Tuple[Hash32, List["InternalNode"]]: + def compute_state_root(self, block_diff: spec_state.BlockDiff) -> Hash32: """ - Compute the state root after applying `*_changes` to the pre-state. + Compute the state root after applying `block_diff` to the + pre-state. - Conforms to - `ethereum.state.PreState.compute_state_root_and_trie_changes`. - Builds the trie inline; `Alloc` does not cache `Trie` instances. + Conforms to `ethereum.state.PreState.compute_state_root`. + Builds the trie inline; `Alloc` does not cache `Trie` + instances. """ self._ensure_live() state = self._materialize_state() - return state.compute_state_root_and_trie_changes( - account_changes, storage_changes, storage_clears - ) + return state.compute_state_root(block_diff) # ------------------------------------------------------------------ # Lifecycle: apply_diff and freeze diff --git a/packages/testing/src/execution_testing/test_types/tests/test_alloc_prestate.py b/packages/testing/src/execution_testing/test_types/tests/test_alloc_prestate.py index 30cb39af3d6..cc7798af169 100644 --- a/packages/testing/src/execution_testing/test_types/tests/test_alloc_prestate.py +++ b/packages/testing/src/execution_testing/test_types/tests/test_alloc_prestate.py @@ -4,8 +4,8 @@ Covers four invariants of the lifecycle phase machinery: 1. The code-hash → bytes cache is built correctly when the alloc goes LIVE, and the PreState read methods agree with the source dict. - 2. `compute_state_root_and_trie_changes` on `Alloc` matches the same - call on a freshly built `ethereum.state.State` over the same data. + 2. `compute_state_root` on `Alloc` matches the same call on a + freshly built `ethereum.state_mpt.State` over the same data. 3. Mutating an `Alloc` via `__setitem__`/`__delitem__` is rejected after it has been used as a PreState. 4. Building alloc B by `apply_diff`ing a diff onto alloc A produces a @@ -16,6 +16,7 @@ from typing import Dict, Optional import ethereum.state as spec_state +import ethereum.state_mpt as spec_state_mpt import pytest from ethereum.crypto.hash import keccak256 from ethereum_types.bytes import Bytes20, Bytes32 @@ -58,16 +59,16 @@ def _fixture_alloc() -> Alloc: ) -def _state_from_alloc(alloc: Alloc) -> spec_state.State: +def _state_from_alloc(alloc: Alloc) -> spec_state_mpt.State: """Build a spec `State` mirroring `alloc` for parity comparisons.""" - state = spec_state.State() + state = spec_state_mpt.State() for address, account in alloc.root.items(): if account is None: continue addr = Bytes20(address) code = bytes(account.code) if account.code else b"" code_hash = keccak256(code) if code else spec_state.EMPTY_CODE_HASH - spec_state.set_account( + spec_state_mpt.set_account( state, addr, spec_state.Account( @@ -81,7 +82,7 @@ def _state_from_alloc(alloc: Alloc) -> spec_state.State: for key_hi, value_hi in account.storage.root.items(): if int(value_hi) == 0: continue - spec_state.set_storage( + spec_state_mpt.set_storage( state, addr, Bytes32(int(key_hi).to_bytes(32, "big")), @@ -138,12 +139,12 @@ def test_cache_build_and_read_methods_agree_with_source() -> None: def test_state_root_parity_against_spec_state() -> None: - """`Alloc.compute_state_root_and_trie_changes` matches spec `State`.""" + """`Alloc.compute_state_root` matches spec `State`.""" alloc = _fixture_alloc() state = _state_from_alloc(alloc) - alloc_root, _ = alloc.compute_state_root_and_trie_changes({}, {}) - spec_root, _ = state.compute_state_root_and_trie_changes({}, {}) + alloc_root = alloc.compute_state_root(spec_state.BlockDiff()) + spec_root = state.compute_state_root(spec_state.BlockDiff()) assert alloc_root == spec_root # Same parity under non-trivial change sets. @@ -155,11 +156,19 @@ def test_state_root_parity_against_spec_state() -> None: storage_changes: Dict[Bytes20, Dict[Bytes32, U256]] = { ADDR_B: {Bytes32(b"\x00" * 31 + b"\x01"): U256(0x99)}, } - alloc_root_changed, _ = alloc.compute_state_root_and_trie_changes( - account_changes, storage_changes + alloc_root_changed = alloc.compute_state_root( + spec_state.BlockDiff( + account_changes=account_changes, + storage_changes=storage_changes, + code_changes={}, + ) ) - spec_root_changed, _ = state.compute_state_root_and_trie_changes( - account_changes, storage_changes + spec_root_changed = state.compute_state_root( + spec_state.BlockDiff( + account_changes=account_changes, + storage_changes=storage_changes, + code_changes={}, + ) ) assert alloc_root_changed == spec_root_changed assert alloc_root_changed != alloc_root @@ -258,9 +267,9 @@ def test_apply_diff_round_trip_matches_independent_post_state() -> None: alloc_pre.apply_diff(diff) # State roots should match. - pre_root, _ = alloc_pre.compute_state_root_and_trie_changes({}, {}) - expected_root, _ = alloc_post_expected.compute_state_root_and_trie_changes( - {}, {} + pre_root = alloc_pre.compute_state_root(spec_state.BlockDiff()) + expected_root = alloc_post_expected.compute_state_root( + spec_state.BlockDiff() ) assert pre_root == expected_root diff --git a/src/ethereum/forks/amsterdam/blocks.py b/src/ethereum/forks/amsterdam/blocks.py index 57b0e2c2874..68732a167d4 100644 --- a/src/ethereum/forks/amsterdam/blocks.py +++ b/src/ethereum/forks/amsterdam/blocks.py @@ -109,12 +109,12 @@ class Header: Root hash ([`keccak256`]) of the state trie after executing all transactions in this block. It represents the state of the Ethereum Virtual Machine (EVM) after all transactions in this block have been processed. It - is computed using [`compute_state_root_and_trie_changes()`][changes], + is computed using [`compute_state_root()`][changes], which computes the root of the Merkle-Patricia [Trie] representing the Ethereum world state after applying the block's state changes. [`keccak256`]: ref:ethereum.crypto.hash.keccak256 - [changes]: ref:ethereum.state.State.compute_state_root_and_trie_changes + [changes]: ref:ethereum.state_mpt.State.compute_state_root [Trie]: ref:ethereum.merkle_patricia_trie.Trie """ diff --git a/src/ethereum/forks/amsterdam/fork.py b/src/ethereum/forks/amsterdam/fork.py index 302e0887ed2..d76b92a24cc 100644 --- a/src/ethereum/forks/amsterdam/fork.py +++ b/src/ethereum/forks/amsterdam/fork.py @@ -30,13 +30,8 @@ ) from ethereum.forks.bpo5.blocks import Header as PreviousHeader from ethereum.merkle_patricia_trie import root, trie_set -from ethereum.state import ( - EMPTY_CODE_HASH, - Address, - BlockDiff, - State, - apply_changes_to_state, -) +from ethereum.state import EMPTY_CODE_HASH, Address, BlockDiff +from ethereum.state_mpt import State, apply_changes_to_state from . import vm from .block_access_lists import ( @@ -343,9 +338,7 @@ def execute_block( withdrawals=block.withdrawals, ) block_diff = extract_block_diff(block_state) - block_state_root, _ = pre_state.compute_state_root_and_trie_changes( - block_diff.account_changes, block_diff.storage_changes - ) + block_state_root = pre_state.compute_state_root(block_diff) transactions_root = root(block_output.transactions_trie) receipt_root = root(block_output.receipts_trie) block_logs_bloom = logs_bloom(block_output.block_logs) diff --git a/src/ethereum/forks/amsterdam/state_tracker.py b/src/ethereum/forks/amsterdam/state_tracker.py index 5f7d0eaf33c..9e0e3b24b67 100644 --- a/src/ethereum/forks/amsterdam/state_tracker.py +++ b/src/ethereum/forks/amsterdam/state_tracker.py @@ -3,7 +3,7 @@ Track state changes on top of a read-only ``PreState``. At block end, accumulated diffs feed into -``PreState.compute_state_root_and_trie_changes()``. +``PreState.compute_state_root()``. .. contents:: Table of Contents :backlinks: none diff --git a/src/ethereum/forks/arrow_glacier/blocks.py b/src/ethereum/forks/arrow_glacier/blocks.py index 6d9c41f774d..c15f5bd8334 100644 --- a/src/ethereum/forks/arrow_glacier/blocks.py +++ b/src/ethereum/forks/arrow_glacier/blocks.py @@ -71,12 +71,12 @@ class Header: Root hash ([`keccak256`]) of the state trie after executing all transactions in this block. It represents the state of the Ethereum Virtual Machine (EVM) after all transactions in this block have been processed. It - is computed using [`compute_state_root_and_trie_changes()`][changes], + is computed using [`compute_state_root()`][changes], which computes the root of the Merkle-Patricia [Trie] representing the Ethereum world state after applying the block's state changes. [`keccak256`]: ref:ethereum.crypto.hash.keccak256 - [changes]: ref:ethereum.state.State.compute_state_root_and_trie_changes + [changes]: ref:ethereum.state_mpt.State.compute_state_root [Trie]: ref:ethereum.merkle_patricia_trie.Trie """ # noqa: E501 diff --git a/src/ethereum/forks/arrow_glacier/fork.py b/src/ethereum/forks/arrow_glacier/fork.py index 484d474c983..5a37ddabcec 100644 --- a/src/ethereum/forks/arrow_glacier/fork.py +++ b/src/ethereum/forks/arrow_glacier/fork.py @@ -29,12 +29,8 @@ NonceMismatchError, ) from ethereum.merkle_patricia_trie import root, trie_set -from ethereum.state import ( - EMPTY_CODE_HASH, - Address, - State, - apply_changes_to_state, -) +from ethereum.state import EMPTY_CODE_HASH, Address +from ethereum.state_mpt import State, apply_changes_to_state from . import vm from .blocks import Block, Header, Log, Receipt, encode_receipt @@ -204,11 +200,7 @@ def state_transition(chain: BlockChain, block: Block) -> None: ommers=block.ommers, ) block_diff = extract_block_diff(block_state) - block_state_root, _ = chain.state.compute_state_root_and_trie_changes( - block_diff.account_changes, - block_diff.storage_changes, - block_diff.storage_clears, - ) + block_state_root = chain.state.compute_state_root(block_diff) transactions_root = root(block_output.transactions_trie) receipt_root = root(block_output.receipts_trie) block_logs_bloom = logs_bloom(block_output.block_logs) diff --git a/src/ethereum/forks/arrow_glacier/state_tracker.py b/src/ethereum/forks/arrow_glacier/state_tracker.py index dd7a3c3bb8c..d7a607e6436 100644 --- a/src/ethereum/forks/arrow_glacier/state_tracker.py +++ b/src/ethereum/forks/arrow_glacier/state_tracker.py @@ -3,7 +3,7 @@ Track state changes on top of a read-only ``PreState``. At block end, accumulated diffs feed into -``PreState.compute_state_root_and_trie_changes()``. +``PreState.compute_state_root()``. .. contents:: Table of Contents :backlinks: none diff --git a/src/ethereum/forks/berlin/blocks.py b/src/ethereum/forks/berlin/blocks.py index 0bb4d2103ea..a52bce35813 100644 --- a/src/ethereum/forks/berlin/blocks.py +++ b/src/ethereum/forks/berlin/blocks.py @@ -63,12 +63,12 @@ class Header: Root hash ([`keccak256`]) of the state trie after executing all transactions in this block. It represents the state of the Ethereum Virtual Machine (EVM) after all transactions in this block have been processed. It - is computed using [`compute_state_root_and_trie_changes()`][changes], + is computed using [`compute_state_root()`][changes], which computes the root of the Merkle-Patricia [Trie] representing the Ethereum world state after applying the block's state changes. [`keccak256`]: ref:ethereum.crypto.hash.keccak256 - [changes]: ref:ethereum.state.State.compute_state_root_and_trie_changes + [changes]: ref:ethereum.state_mpt.State.compute_state_root [Trie]: ref:ethereum.merkle_patricia_trie.Trie """ # noqa: E501 diff --git a/src/ethereum/forks/berlin/fork.py b/src/ethereum/forks/berlin/fork.py index 14f72b34c54..e2ee2e0d3cb 100644 --- a/src/ethereum/forks/berlin/fork.py +++ b/src/ethereum/forks/berlin/fork.py @@ -29,12 +29,8 @@ NonceMismatchError, ) from ethereum.merkle_patricia_trie import root, trie_set -from ethereum.state import ( - EMPTY_CODE_HASH, - Address, - State, - apply_changes_to_state, -) +from ethereum.state import EMPTY_CODE_HASH, Address +from ethereum.state_mpt import State, apply_changes_to_state from . import vm from .blocks import Block, Header, Log, Receipt, encode_receipt @@ -196,11 +192,7 @@ def state_transition(chain: BlockChain, block: Block) -> None: ommers=block.ommers, ) block_diff = extract_block_diff(block_state) - block_state_root, _ = chain.state.compute_state_root_and_trie_changes( - block_diff.account_changes, - block_diff.storage_changes, - block_diff.storage_clears, - ) + block_state_root = chain.state.compute_state_root(block_diff) transactions_root = root(block_output.transactions_trie) receipt_root = root(block_output.receipts_trie) block_logs_bloom = logs_bloom(block_output.block_logs) diff --git a/src/ethereum/forks/berlin/state_tracker.py b/src/ethereum/forks/berlin/state_tracker.py index dd7a3c3bb8c..d7a607e6436 100644 --- a/src/ethereum/forks/berlin/state_tracker.py +++ b/src/ethereum/forks/berlin/state_tracker.py @@ -3,7 +3,7 @@ Track state changes on top of a read-only ``PreState``. At block end, accumulated diffs feed into -``PreState.compute_state_root_and_trie_changes()``. +``PreState.compute_state_root()``. .. contents:: Table of Contents :backlinks: none diff --git a/src/ethereum/forks/bpo1/blocks.py b/src/ethereum/forks/bpo1/blocks.py index cd4b2daca34..f3eb9b40bf4 100644 --- a/src/ethereum/forks/bpo1/blocks.py +++ b/src/ethereum/forks/bpo1/blocks.py @@ -109,12 +109,12 @@ class Header: Root hash ([`keccak256`]) of the state trie after executing all transactions in this block. It represents the state of the Ethereum Virtual Machine (EVM) after all transactions in this block have been processed. It - is computed using [`compute_state_root_and_trie_changes()`][changes], + is computed using [`compute_state_root()`][changes], which computes the root of the Merkle-Patricia [Trie] representing the Ethereum world state after applying the block's state changes. [`keccak256`]: ref:ethereum.crypto.hash.keccak256 - [changes]: ref:ethereum.state.State.compute_state_root_and_trie_changes + [changes]: ref:ethereum.state_mpt.State.compute_state_root [Trie]: ref:ethereum.merkle_patricia_trie.Trie """ # noqa: E501 diff --git a/src/ethereum/forks/bpo1/fork.py b/src/ethereum/forks/bpo1/fork.py index c2c43c631dc..71cca148fac 100644 --- a/src/ethereum/forks/bpo1/fork.py +++ b/src/ethereum/forks/bpo1/fork.py @@ -28,12 +28,8 @@ NonceMismatchError, ) from ethereum.merkle_patricia_trie import root, trie_set -from ethereum.state import ( - EMPTY_CODE_HASH, - Address, - State, - apply_changes_to_state, -) +from ethereum.state import EMPTY_CODE_HASH, Address +from ethereum.state_mpt import State, apply_changes_to_state from . import vm from .blocks import Block, Header, Log, Receipt, Withdrawal, encode_receipt @@ -253,9 +249,7 @@ def state_transition(chain: BlockChain, block: Block) -> None: withdrawals=block.withdrawals, ) block_diff = extract_block_diff(block_state) - block_state_root, _ = chain.state.compute_state_root_and_trie_changes( - block_diff.account_changes, block_diff.storage_changes - ) + block_state_root = chain.state.compute_state_root(block_diff) transactions_root = root(block_output.transactions_trie) receipt_root = root(block_output.receipts_trie) block_logs_bloom = logs_bloom(block_output.block_logs) diff --git a/src/ethereum/forks/bpo1/state_tracker.py b/src/ethereum/forks/bpo1/state_tracker.py index 8ce889a833b..52d28c95b7c 100644 --- a/src/ethereum/forks/bpo1/state_tracker.py +++ b/src/ethereum/forks/bpo1/state_tracker.py @@ -3,7 +3,7 @@ Track state changes on top of a read-only ``PreState``. At block end, accumulated diffs feed into -``PreState.compute_state_root_and_trie_changes()``. +``PreState.compute_state_root()``. .. contents:: Table of Contents :backlinks: none diff --git a/src/ethereum/forks/bpo2/blocks.py b/src/ethereum/forks/bpo2/blocks.py index 209862680f1..2fb877682e1 100644 --- a/src/ethereum/forks/bpo2/blocks.py +++ b/src/ethereum/forks/bpo2/blocks.py @@ -109,12 +109,12 @@ class Header: Root hash ([`keccak256`]) of the state trie after executing all transactions in this block. It represents the state of the Ethereum Virtual Machine (EVM) after all transactions in this block have been processed. It - is computed using [`compute_state_root_and_trie_changes()`][changes], + is computed using [`compute_state_root()`][changes], which computes the root of the Merkle-Patricia [Trie] representing the Ethereum world state after applying the block's state changes. [`keccak256`]: ref:ethereum.crypto.hash.keccak256 - [changes]: ref:ethereum.state.State.compute_state_root_and_trie_changes + [changes]: ref:ethereum.state_mpt.State.compute_state_root [Trie]: ref:ethereum.merkle_patricia_trie.Trie """ # noqa: E501 diff --git a/src/ethereum/forks/bpo2/fork.py b/src/ethereum/forks/bpo2/fork.py index c2c43c631dc..71cca148fac 100644 --- a/src/ethereum/forks/bpo2/fork.py +++ b/src/ethereum/forks/bpo2/fork.py @@ -28,12 +28,8 @@ NonceMismatchError, ) from ethereum.merkle_patricia_trie import root, trie_set -from ethereum.state import ( - EMPTY_CODE_HASH, - Address, - State, - apply_changes_to_state, -) +from ethereum.state import EMPTY_CODE_HASH, Address +from ethereum.state_mpt import State, apply_changes_to_state from . import vm from .blocks import Block, Header, Log, Receipt, Withdrawal, encode_receipt @@ -253,9 +249,7 @@ def state_transition(chain: BlockChain, block: Block) -> None: withdrawals=block.withdrawals, ) block_diff = extract_block_diff(block_state) - block_state_root, _ = chain.state.compute_state_root_and_trie_changes( - block_diff.account_changes, block_diff.storage_changes - ) + block_state_root = chain.state.compute_state_root(block_diff) transactions_root = root(block_output.transactions_trie) receipt_root = root(block_output.receipts_trie) block_logs_bloom = logs_bloom(block_output.block_logs) diff --git a/src/ethereum/forks/bpo2/state_tracker.py b/src/ethereum/forks/bpo2/state_tracker.py index 8ce889a833b..52d28c95b7c 100644 --- a/src/ethereum/forks/bpo2/state_tracker.py +++ b/src/ethereum/forks/bpo2/state_tracker.py @@ -3,7 +3,7 @@ Track state changes on top of a read-only ``PreState``. At block end, accumulated diffs feed into -``PreState.compute_state_root_and_trie_changes()``. +``PreState.compute_state_root()``. .. contents:: Table of Contents :backlinks: none diff --git a/src/ethereum/forks/bpo3/blocks.py b/src/ethereum/forks/bpo3/blocks.py index df26affccfb..e5931b35c35 100644 --- a/src/ethereum/forks/bpo3/blocks.py +++ b/src/ethereum/forks/bpo3/blocks.py @@ -109,12 +109,12 @@ class Header: Root hash ([`keccak256`]) of the state trie after executing all transactions in this block. It represents the state of the Ethereum Virtual Machine (EVM) after all transactions in this block have been processed. It - is computed using [`compute_state_root_and_trie_changes()`][changes], + is computed using [`compute_state_root()`][changes], which computes the root of the Merkle-Patricia [Trie] representing the Ethereum world state after applying the block's state changes. [`keccak256`]: ref:ethereum.crypto.hash.keccak256 - [changes]: ref:ethereum.state.State.compute_state_root_and_trie_changes + [changes]: ref:ethereum.state_mpt.State.compute_state_root [Trie]: ref:ethereum.merkle_patricia_trie.Trie """ # noqa: E501 diff --git a/src/ethereum/forks/bpo3/fork.py b/src/ethereum/forks/bpo3/fork.py index c2c43c631dc..71cca148fac 100644 --- a/src/ethereum/forks/bpo3/fork.py +++ b/src/ethereum/forks/bpo3/fork.py @@ -28,12 +28,8 @@ NonceMismatchError, ) from ethereum.merkle_patricia_trie import root, trie_set -from ethereum.state import ( - EMPTY_CODE_HASH, - Address, - State, - apply_changes_to_state, -) +from ethereum.state import EMPTY_CODE_HASH, Address +from ethereum.state_mpt import State, apply_changes_to_state from . import vm from .blocks import Block, Header, Log, Receipt, Withdrawal, encode_receipt @@ -253,9 +249,7 @@ def state_transition(chain: BlockChain, block: Block) -> None: withdrawals=block.withdrawals, ) block_diff = extract_block_diff(block_state) - block_state_root, _ = chain.state.compute_state_root_and_trie_changes( - block_diff.account_changes, block_diff.storage_changes - ) + block_state_root = chain.state.compute_state_root(block_diff) transactions_root = root(block_output.transactions_trie) receipt_root = root(block_output.receipts_trie) block_logs_bloom = logs_bloom(block_output.block_logs) diff --git a/src/ethereum/forks/bpo3/state_tracker.py b/src/ethereum/forks/bpo3/state_tracker.py index 8ce889a833b..52d28c95b7c 100644 --- a/src/ethereum/forks/bpo3/state_tracker.py +++ b/src/ethereum/forks/bpo3/state_tracker.py @@ -3,7 +3,7 @@ Track state changes on top of a read-only ``PreState``. At block end, accumulated diffs feed into -``PreState.compute_state_root_and_trie_changes()``. +``PreState.compute_state_root()``. .. contents:: Table of Contents :backlinks: none diff --git a/src/ethereum/forks/bpo4/blocks.py b/src/ethereum/forks/bpo4/blocks.py index 5fcadec15d9..c09fd2907e1 100644 --- a/src/ethereum/forks/bpo4/blocks.py +++ b/src/ethereum/forks/bpo4/blocks.py @@ -109,12 +109,12 @@ class Header: Root hash ([`keccak256`]) of the state trie after executing all transactions in this block. It represents the state of the Ethereum Virtual Machine (EVM) after all transactions in this block have been processed. It - is computed using [`compute_state_root_and_trie_changes()`][changes], + is computed using [`compute_state_root()`][changes], which computes the root of the Merkle-Patricia [Trie] representing the Ethereum world state after applying the block's state changes. [`keccak256`]: ref:ethereum.crypto.hash.keccak256 - [changes]: ref:ethereum.state.State.compute_state_root_and_trie_changes + [changes]: ref:ethereum.state_mpt.State.compute_state_root [Trie]: ref:ethereum.merkle_patricia_trie.Trie """ # noqa: E501 diff --git a/src/ethereum/forks/bpo4/fork.py b/src/ethereum/forks/bpo4/fork.py index c2c43c631dc..71cca148fac 100644 --- a/src/ethereum/forks/bpo4/fork.py +++ b/src/ethereum/forks/bpo4/fork.py @@ -28,12 +28,8 @@ NonceMismatchError, ) from ethereum.merkle_patricia_trie import root, trie_set -from ethereum.state import ( - EMPTY_CODE_HASH, - Address, - State, - apply_changes_to_state, -) +from ethereum.state import EMPTY_CODE_HASH, Address +from ethereum.state_mpt import State, apply_changes_to_state from . import vm from .blocks import Block, Header, Log, Receipt, Withdrawal, encode_receipt @@ -253,9 +249,7 @@ def state_transition(chain: BlockChain, block: Block) -> None: withdrawals=block.withdrawals, ) block_diff = extract_block_diff(block_state) - block_state_root, _ = chain.state.compute_state_root_and_trie_changes( - block_diff.account_changes, block_diff.storage_changes - ) + block_state_root = chain.state.compute_state_root(block_diff) transactions_root = root(block_output.transactions_trie) receipt_root = root(block_output.receipts_trie) block_logs_bloom = logs_bloom(block_output.block_logs) diff --git a/src/ethereum/forks/bpo4/state_tracker.py b/src/ethereum/forks/bpo4/state_tracker.py index 8ce889a833b..52d28c95b7c 100644 --- a/src/ethereum/forks/bpo4/state_tracker.py +++ b/src/ethereum/forks/bpo4/state_tracker.py @@ -3,7 +3,7 @@ Track state changes on top of a read-only ``PreState``. At block end, accumulated diffs feed into -``PreState.compute_state_root_and_trie_changes()``. +``PreState.compute_state_root()``. .. contents:: Table of Contents :backlinks: none diff --git a/src/ethereum/forks/bpo5/blocks.py b/src/ethereum/forks/bpo5/blocks.py index eed86b7e175..83e98d6345f 100644 --- a/src/ethereum/forks/bpo5/blocks.py +++ b/src/ethereum/forks/bpo5/blocks.py @@ -109,12 +109,12 @@ class Header: Root hash ([`keccak256`]) of the state trie after executing all transactions in this block. It represents the state of the Ethereum Virtual Machine (EVM) after all transactions in this block have been processed. It - is computed using [`compute_state_root_and_trie_changes()`][changes], + is computed using [`compute_state_root()`][changes], which computes the root of the Merkle-Patricia [Trie] representing the Ethereum world state after applying the block's state changes. [`keccak256`]: ref:ethereum.crypto.hash.keccak256 - [changes]: ref:ethereum.state.State.compute_state_root_and_trie_changes + [changes]: ref:ethereum.state_mpt.State.compute_state_root [Trie]: ref:ethereum.merkle_patricia_trie.Trie """ # noqa: E501 diff --git a/src/ethereum/forks/bpo5/fork.py b/src/ethereum/forks/bpo5/fork.py index c2c43c631dc..71cca148fac 100644 --- a/src/ethereum/forks/bpo5/fork.py +++ b/src/ethereum/forks/bpo5/fork.py @@ -28,12 +28,8 @@ NonceMismatchError, ) from ethereum.merkle_patricia_trie import root, trie_set -from ethereum.state import ( - EMPTY_CODE_HASH, - Address, - State, - apply_changes_to_state, -) +from ethereum.state import EMPTY_CODE_HASH, Address +from ethereum.state_mpt import State, apply_changes_to_state from . import vm from .blocks import Block, Header, Log, Receipt, Withdrawal, encode_receipt @@ -253,9 +249,7 @@ def state_transition(chain: BlockChain, block: Block) -> None: withdrawals=block.withdrawals, ) block_diff = extract_block_diff(block_state) - block_state_root, _ = chain.state.compute_state_root_and_trie_changes( - block_diff.account_changes, block_diff.storage_changes - ) + block_state_root = chain.state.compute_state_root(block_diff) transactions_root = root(block_output.transactions_trie) receipt_root = root(block_output.receipts_trie) block_logs_bloom = logs_bloom(block_output.block_logs) diff --git a/src/ethereum/forks/bpo5/state_tracker.py b/src/ethereum/forks/bpo5/state_tracker.py index 8ce889a833b..52d28c95b7c 100644 --- a/src/ethereum/forks/bpo5/state_tracker.py +++ b/src/ethereum/forks/bpo5/state_tracker.py @@ -3,7 +3,7 @@ Track state changes on top of a read-only ``PreState``. At block end, accumulated diffs feed into -``PreState.compute_state_root_and_trie_changes()``. +``PreState.compute_state_root()``. .. contents:: Table of Contents :backlinks: none diff --git a/src/ethereum/forks/byzantium/blocks.py b/src/ethereum/forks/byzantium/blocks.py index 26091316a9c..39d50db5a77 100644 --- a/src/ethereum/forks/byzantium/blocks.py +++ b/src/ethereum/forks/byzantium/blocks.py @@ -62,12 +62,12 @@ class Header: Root hash ([`keccak256`]) of the state trie after executing all transactions in this block. It represents the state of the Ethereum Virtual Machine (EVM) after all transactions in this block have been processed. It - is computed using [`compute_state_root_and_trie_changes()`][changes], + is computed using [`compute_state_root()`][changes], which computes the root of the Merkle-Patricia [Trie] representing the Ethereum world state after applying the block's state changes. [`keccak256`]: ref:ethereum.crypto.hash.keccak256 - [changes]: ref:ethereum.state.State.compute_state_root_and_trie_changes + [changes]: ref:ethereum.state_mpt.State.compute_state_root [Trie]: ref:ethereum.merkle_patricia_trie.Trie """ # noqa: E501 diff --git a/src/ethereum/forks/byzantium/fork.py b/src/ethereum/forks/byzantium/fork.py index a83087f4f0a..6d0d1b461b2 100644 --- a/src/ethereum/forks/byzantium/fork.py +++ b/src/ethereum/forks/byzantium/fork.py @@ -28,12 +28,8 @@ NonceMismatchError, ) from ethereum.merkle_patricia_trie import root, trie_set -from ethereum.state import ( - EMPTY_CODE_HASH, - Address, - State, - apply_changes_to_state, -) +from ethereum.state import EMPTY_CODE_HASH, Address +from ethereum.state_mpt import State, apply_changes_to_state from . import vm from .blocks import Block, Header, Log, Receipt @@ -191,11 +187,7 @@ def state_transition(chain: BlockChain, block: Block) -> None: ommers=block.ommers, ) block_diff = extract_block_diff(block_state) - block_state_root, _ = chain.state.compute_state_root_and_trie_changes( - block_diff.account_changes, - block_diff.storage_changes, - block_diff.storage_clears, - ) + block_state_root = chain.state.compute_state_root(block_diff) transactions_root = root(block_output.transactions_trie) receipt_root = root(block_output.receipts_trie) block_logs_bloom = logs_bloom(block_output.block_logs) diff --git a/src/ethereum/forks/byzantium/state_tracker.py b/src/ethereum/forks/byzantium/state_tracker.py index dd7a3c3bb8c..d7a607e6436 100644 --- a/src/ethereum/forks/byzantium/state_tracker.py +++ b/src/ethereum/forks/byzantium/state_tracker.py @@ -3,7 +3,7 @@ Track state changes on top of a read-only ``PreState``. At block end, accumulated diffs feed into -``PreState.compute_state_root_and_trie_changes()``. +``PreState.compute_state_root()``. .. contents:: Table of Contents :backlinks: none diff --git a/src/ethereum/forks/cancun/blocks.py b/src/ethereum/forks/cancun/blocks.py index 3507f8a3284..d1697870b1e 100644 --- a/src/ethereum/forks/cancun/blocks.py +++ b/src/ethereum/forks/cancun/blocks.py @@ -108,12 +108,12 @@ class Header: Root hash ([`keccak256`]) of the state trie after executing all transactions in this block. It represents the state of the Ethereum Virtual Machine (EVM) after all transactions in this block have been processed. It - is computed using [`compute_state_root_and_trie_changes()`][changes], + is computed using [`compute_state_root()`][changes], which computes the root of the Merkle-Patricia [Trie] representing the Ethereum world state after applying the block's state changes. [`keccak256`]: ref:ethereum.crypto.hash.keccak256 - [changes]: ref:ethereum.state.State.compute_state_root_and_trie_changes + [changes]: ref:ethereum.state_mpt.State.compute_state_root [Trie]: ref:ethereum.merkle_patricia_trie.Trie """ # noqa: E501 diff --git a/src/ethereum/forks/cancun/fork.py b/src/ethereum/forks/cancun/fork.py index 67e3b0197d0..5d3e2c56040 100644 --- a/src/ethereum/forks/cancun/fork.py +++ b/src/ethereum/forks/cancun/fork.py @@ -28,12 +28,8 @@ NonceMismatchError, ) from ethereum.merkle_patricia_trie import root, trie_set -from ethereum.state import ( - EMPTY_CODE_HASH, - Address, - State, - apply_changes_to_state, -) +from ethereum.state import EMPTY_CODE_HASH, Address +from ethereum.state_mpt import State, apply_changes_to_state from . import vm from .blocks import Block, Header, Log, Receipt, Withdrawal, encode_receipt @@ -224,9 +220,7 @@ def state_transition(chain: BlockChain, block: Block) -> None: withdrawals=block.withdrawals, ) block_diff = extract_block_diff(block_state) - block_state_root, _ = chain.state.compute_state_root_and_trie_changes( - block_diff.account_changes, block_diff.storage_changes - ) + block_state_root = chain.state.compute_state_root(block_diff) transactions_root = root(block_output.transactions_trie) receipt_root = root(block_output.receipts_trie) block_logs_bloom = logs_bloom(block_output.block_logs) diff --git a/src/ethereum/forks/cancun/state_tracker.py b/src/ethereum/forks/cancun/state_tracker.py index 8ce889a833b..52d28c95b7c 100644 --- a/src/ethereum/forks/cancun/state_tracker.py +++ b/src/ethereum/forks/cancun/state_tracker.py @@ -3,7 +3,7 @@ Track state changes on top of a read-only ``PreState``. At block end, accumulated diffs feed into -``PreState.compute_state_root_and_trie_changes()``. +``PreState.compute_state_root()``. .. contents:: Table of Contents :backlinks: none diff --git a/src/ethereum/forks/constantinople/blocks.py b/src/ethereum/forks/constantinople/blocks.py index 94dd37899b8..48582187726 100644 --- a/src/ethereum/forks/constantinople/blocks.py +++ b/src/ethereum/forks/constantinople/blocks.py @@ -62,12 +62,12 @@ class Header: Root hash ([`keccak256`]) of the state trie after executing all transactions in this block. It represents the state of the Ethereum Virtual Machine (EVM) after all transactions in this block have been processed. It - is computed using [`compute_state_root_and_trie_changes()`][changes], + is computed using [`compute_state_root()`][changes], which computes the root of the Merkle-Patricia [Trie] representing the Ethereum world state after applying the block's state changes. [`keccak256`]: ref:ethereum.crypto.hash.keccak256 - [changes]: ref:ethereum.state.State.compute_state_root_and_trie_changes + [changes]: ref:ethereum.state_mpt.State.compute_state_root [Trie]: ref:ethereum.merkle_patricia_trie.Trie """ # noqa: E501 diff --git a/src/ethereum/forks/constantinople/fork.py b/src/ethereum/forks/constantinople/fork.py index 62654a56386..46a71bb0e36 100644 --- a/src/ethereum/forks/constantinople/fork.py +++ b/src/ethereum/forks/constantinople/fork.py @@ -28,12 +28,8 @@ NonceMismatchError, ) from ethereum.merkle_patricia_trie import root, trie_set -from ethereum.state import ( - EMPTY_CODE_HASH, - Address, - State, - apply_changes_to_state, -) +from ethereum.state import EMPTY_CODE_HASH, Address +from ethereum.state_mpt import State, apply_changes_to_state from . import vm from .blocks import Block, Header, Log, Receipt @@ -191,11 +187,7 @@ def state_transition(chain: BlockChain, block: Block) -> None: ommers=block.ommers, ) block_diff = extract_block_diff(block_state) - block_state_root, _ = chain.state.compute_state_root_and_trie_changes( - block_diff.account_changes, - block_diff.storage_changes, - block_diff.storage_clears, - ) + block_state_root = chain.state.compute_state_root(block_diff) transactions_root = root(block_output.transactions_trie) receipt_root = root(block_output.receipts_trie) block_logs_bloom = logs_bloom(block_output.block_logs) diff --git a/src/ethereum/forks/constantinople/state_tracker.py b/src/ethereum/forks/constantinople/state_tracker.py index dd7a3c3bb8c..d7a607e6436 100644 --- a/src/ethereum/forks/constantinople/state_tracker.py +++ b/src/ethereum/forks/constantinople/state_tracker.py @@ -3,7 +3,7 @@ Track state changes on top of a read-only ``PreState``. At block end, accumulated diffs feed into -``PreState.compute_state_root_and_trie_changes()``. +``PreState.compute_state_root()``. .. contents:: Table of Contents :backlinks: none diff --git a/src/ethereum/forks/dao_fork/blocks.py b/src/ethereum/forks/dao_fork/blocks.py index 7e6320bb827..138ac721c88 100644 --- a/src/ethereum/forks/dao_fork/blocks.py +++ b/src/ethereum/forks/dao_fork/blocks.py @@ -62,12 +62,12 @@ class Header: Root hash ([`keccak256`]) of the state trie after executing all transactions in this block. It represents the state of the Ethereum Virtual Machine (EVM) after all transactions in this block have been processed. It - is computed using [`compute_state_root_and_trie_changes()`][changes], + is computed using [`compute_state_root()`][changes], which computes the root of the Merkle-Patricia [Trie] representing the Ethereum world state after applying the block's state changes. [`keccak256`]: ref:ethereum.crypto.hash.keccak256 - [changes]: ref:ethereum.state.State.compute_state_root_and_trie_changes + [changes]: ref:ethereum.state_mpt.State.compute_state_root [Trie]: ref:ethereum.merkle_patricia_trie.Trie """ # noqa: E501 diff --git a/src/ethereum/forks/dao_fork/dao.py b/src/ethereum/forks/dao_fork/dao.py index 93bed096cdb..714841d10bd 100644 --- a/src/ethereum/forks/dao_fork/dao.py +++ b/src/ethereum/forks/dao_fork/dao.py @@ -5,7 +5,7 @@ The recovery contract was previously created using normal contract deployment. """ -from ethereum.state import State +from ethereum.state_mpt import State from .state_tracker import TransactionState, get_account, move_ether from .utils.hexadecimal import hex_to_address @@ -354,7 +354,7 @@ def apply_dao(state: State) -> None: [`DAO_ACCOUNTS`]: ref:ethereum.forks.dao_fork.dao.DAO_ACCOUNTS [`DAO_RECOVERY`]: ref:ethereum.forks.dao_fork.dao.DAO_RECOVERY """ - from ethereum.state import apply_changes_to_state + from ethereum.state_mpt import apply_changes_to_state from .state_tracker import ( BlockState, diff --git a/src/ethereum/forks/dao_fork/fork.py b/src/ethereum/forks/dao_fork/fork.py index f12f29e3923..c3a1c327e59 100644 --- a/src/ethereum/forks/dao_fork/fork.py +++ b/src/ethereum/forks/dao_fork/fork.py @@ -31,12 +31,8 @@ ) from ethereum.fork_criteria import ByBlockNumber from ethereum.merkle_patricia_trie import root, trie_set -from ethereum.state import ( - EMPTY_CODE_HASH, - Address, - State, - apply_changes_to_state, -) +from ethereum.state import EMPTY_CODE_HASH, Address +from ethereum.state_mpt import State, apply_changes_to_state from . import FORK_CRITERIA, vm from .blocks import Block, Header, Log, Receipt @@ -197,11 +193,7 @@ def state_transition(chain: BlockChain, block: Block) -> None: ommers=block.ommers, ) block_diff = extract_block_diff(block_state) - block_state_root, _ = chain.state.compute_state_root_and_trie_changes( - block_diff.account_changes, - block_diff.storage_changes, - block_diff.storage_clears, - ) + block_state_root = chain.state.compute_state_root(block_diff) transactions_root = root(block_output.transactions_trie) receipt_root = root(block_output.receipts_trie) block_logs_bloom = logs_bloom(block_output.block_logs) @@ -710,12 +702,8 @@ def process_transaction( block_state = block_env.state block_diff = extract_block_diff(block_state) - intermediate_state_root, _ = ( - block_state.pre_state.compute_state_root_and_trie_changes( - block_diff.account_changes, - block_diff.storage_changes, - block_diff.storage_clears, - ) + intermediate_state_root = block_state.pre_state.compute_state_root( + block_diff ) receipt = make_receipt( diff --git a/src/ethereum/forks/dao_fork/state_tracker.py b/src/ethereum/forks/dao_fork/state_tracker.py index dd7a3c3bb8c..d7a607e6436 100644 --- a/src/ethereum/forks/dao_fork/state_tracker.py +++ b/src/ethereum/forks/dao_fork/state_tracker.py @@ -3,7 +3,7 @@ Track state changes on top of a read-only ``PreState``. At block end, accumulated diffs feed into -``PreState.compute_state_root_and_trie_changes()``. +``PreState.compute_state_root()``. .. contents:: Table of Contents :backlinks: none diff --git a/src/ethereum/forks/frontier/blocks.py b/src/ethereum/forks/frontier/blocks.py index b6a4db96624..090f85eaf7c 100644 --- a/src/ethereum/forks/frontier/blocks.py +++ b/src/ethereum/forks/frontier/blocks.py @@ -62,12 +62,12 @@ class Header: Root hash ([`keccak256`]) of the state trie after executing all transactions in this block. It represents the state of the Ethereum Virtual Machine (EVM) after all transactions in this block have been processed. It - is computed using [`compute_state_root_and_trie_changes()`][changes], + is computed using [`compute_state_root()`][changes], which computes the root of the Merkle-Patricia [Trie] representing the Ethereum world state after applying the block's state changes. [`keccak256`]: ref:ethereum.crypto.hash.keccak256 - [changes]: ref:ethereum.state.State.compute_state_root_and_trie_changes + [changes]: ref:ethereum.state_mpt.State.compute_state_root [Trie]: ref:ethereum.merkle_patricia_trie.Trie """ # noqa: E501 diff --git a/src/ethereum/forks/frontier/fork.py b/src/ethereum/forks/frontier/fork.py index 820baf10184..f2cd3ca61b7 100644 --- a/src/ethereum/forks/frontier/fork.py +++ b/src/ethereum/forks/frontier/fork.py @@ -28,12 +28,8 @@ NonceMismatchError, ) from ethereum.merkle_patricia_trie import root, trie_set -from ethereum.state import ( - EMPTY_CODE_HASH, - Address, - State, - apply_changes_to_state, -) +from ethereum.state import EMPTY_CODE_HASH, Address +from ethereum.state_mpt import State, apply_changes_to_state from . import vm from .blocks import Block, Header, Log, Receipt @@ -185,11 +181,7 @@ def state_transition(chain: BlockChain, block: Block) -> None: ommers=block.ommers, ) block_diff = extract_block_diff(block_state) - block_state_root, _ = chain.state.compute_state_root_and_trie_changes( - block_diff.account_changes, - block_diff.storage_changes, - block_diff.storage_clears, - ) + block_state_root = chain.state.compute_state_root(block_diff) transactions_root = root(block_output.transactions_trie) receipt_root = root(block_output.receipts_trie) block_logs_bloom = logs_bloom(block_output.block_logs) @@ -689,12 +681,8 @@ def process_transaction( block_state = block_env.state block_diff = extract_block_diff(block_state) - intermediate_state_root, _ = ( - block_state.pre_state.compute_state_root_and_trie_changes( - block_diff.account_changes, - block_diff.storage_changes, - block_diff.storage_clears, - ) + intermediate_state_root = block_state.pre_state.compute_state_root( + block_diff ) receipt = make_receipt( diff --git a/src/ethereum/forks/frontier/state_tracker.py b/src/ethereum/forks/frontier/state_tracker.py index dd7a3c3bb8c..d7a607e6436 100644 --- a/src/ethereum/forks/frontier/state_tracker.py +++ b/src/ethereum/forks/frontier/state_tracker.py @@ -3,7 +3,7 @@ Track state changes on top of a read-only ``PreState``. At block end, accumulated diffs feed into -``PreState.compute_state_root_and_trie_changes()``. +``PreState.compute_state_root()``. .. contents:: Table of Contents :backlinks: none diff --git a/src/ethereum/forks/gray_glacier/blocks.py b/src/ethereum/forks/gray_glacier/blocks.py index e36a9a38aba..17fb9c1afc2 100644 --- a/src/ethereum/forks/gray_glacier/blocks.py +++ b/src/ethereum/forks/gray_glacier/blocks.py @@ -71,12 +71,12 @@ class Header: Root hash ([`keccak256`]) of the state trie after executing all transactions in this block. It represents the state of the Ethereum Virtual Machine (EVM) after all transactions in this block have been processed. It - is computed using [`compute_state_root_and_trie_changes()`][changes], + is computed using [`compute_state_root()`][changes], which computes the root of the Merkle-Patricia [Trie] representing the Ethereum world state after applying the block's state changes. [`keccak256`]: ref:ethereum.crypto.hash.keccak256 - [changes]: ref:ethereum.state.State.compute_state_root_and_trie_changes + [changes]: ref:ethereum.state_mpt.State.compute_state_root [Trie]: ref:ethereum.merkle_patricia_trie.Trie """ # noqa: E501 diff --git a/src/ethereum/forks/gray_glacier/fork.py b/src/ethereum/forks/gray_glacier/fork.py index fca169f4123..921551c7b05 100644 --- a/src/ethereum/forks/gray_glacier/fork.py +++ b/src/ethereum/forks/gray_glacier/fork.py @@ -29,12 +29,8 @@ NonceMismatchError, ) from ethereum.merkle_patricia_trie import root, trie_set -from ethereum.state import ( - EMPTY_CODE_HASH, - Address, - State, - apply_changes_to_state, -) +from ethereum.state import EMPTY_CODE_HASH, Address +from ethereum.state_mpt import State, apply_changes_to_state from . import vm from .blocks import Block, Header, Log, Receipt, encode_receipt @@ -204,11 +200,7 @@ def state_transition(chain: BlockChain, block: Block) -> None: ommers=block.ommers, ) block_diff = extract_block_diff(block_state) - block_state_root, _ = chain.state.compute_state_root_and_trie_changes( - block_diff.account_changes, - block_diff.storage_changes, - block_diff.storage_clears, - ) + block_state_root = chain.state.compute_state_root(block_diff) transactions_root = root(block_output.transactions_trie) receipt_root = root(block_output.receipts_trie) block_logs_bloom = logs_bloom(block_output.block_logs) diff --git a/src/ethereum/forks/gray_glacier/state_tracker.py b/src/ethereum/forks/gray_glacier/state_tracker.py index dd7a3c3bb8c..d7a607e6436 100644 --- a/src/ethereum/forks/gray_glacier/state_tracker.py +++ b/src/ethereum/forks/gray_glacier/state_tracker.py @@ -3,7 +3,7 @@ Track state changes on top of a read-only ``PreState``. At block end, accumulated diffs feed into -``PreState.compute_state_root_and_trie_changes()``. +``PreState.compute_state_root()``. .. contents:: Table of Contents :backlinks: none diff --git a/src/ethereum/forks/homestead/blocks.py b/src/ethereum/forks/homestead/blocks.py index bc21e326b4e..19936d6b4b8 100644 --- a/src/ethereum/forks/homestead/blocks.py +++ b/src/ethereum/forks/homestead/blocks.py @@ -62,12 +62,12 @@ class Header: Root hash ([`keccak256`]) of the state trie after executing all transactions in this block. It represents the state of the Ethereum Virtual Machine (EVM) after all transactions in this block have been processed. It - is computed using [`compute_state_root_and_trie_changes()`][changes], + is computed using [`compute_state_root()`][changes], which computes the root of the Merkle-Patricia [Trie] representing the Ethereum world state after applying the block's state changes. [`keccak256`]: ref:ethereum.crypto.hash.keccak256 - [changes]: ref:ethereum.state.State.compute_state_root_and_trie_changes + [changes]: ref:ethereum.state_mpt.State.compute_state_root [Trie]: ref:ethereum.merkle_patricia_trie.Trie """ # noqa: E501 diff --git a/src/ethereum/forks/homestead/fork.py b/src/ethereum/forks/homestead/fork.py index cf0a29d826f..64bb280734a 100644 --- a/src/ethereum/forks/homestead/fork.py +++ b/src/ethereum/forks/homestead/fork.py @@ -28,12 +28,8 @@ NonceMismatchError, ) from ethereum.merkle_patricia_trie import root, trie_set -from ethereum.state import ( - EMPTY_CODE_HASH, - Address, - State, - apply_changes_to_state, -) +from ethereum.state import EMPTY_CODE_HASH, Address +from ethereum.state_mpt import State, apply_changes_to_state from . import vm from .blocks import Block, Header, Log, Receipt @@ -185,11 +181,7 @@ def state_transition(chain: BlockChain, block: Block) -> None: ommers=block.ommers, ) block_diff = extract_block_diff(block_state) - block_state_root, _ = chain.state.compute_state_root_and_trie_changes( - block_diff.account_changes, - block_diff.storage_changes, - block_diff.storage_clears, - ) + block_state_root = chain.state.compute_state_root(block_diff) transactions_root = root(block_output.transactions_trie) receipt_root = root(block_output.receipts_trie) block_logs_bloom = logs_bloom(block_output.block_logs) @@ -689,12 +681,8 @@ def process_transaction( block_state = block_env.state block_diff = extract_block_diff(block_state) - intermediate_state_root, _ = ( - block_state.pre_state.compute_state_root_and_trie_changes( - block_diff.account_changes, - block_diff.storage_changes, - block_diff.storage_clears, - ) + intermediate_state_root = block_state.pre_state.compute_state_root( + block_diff ) receipt = make_receipt( diff --git a/src/ethereum/forks/homestead/state_tracker.py b/src/ethereum/forks/homestead/state_tracker.py index dd7a3c3bb8c..d7a607e6436 100644 --- a/src/ethereum/forks/homestead/state_tracker.py +++ b/src/ethereum/forks/homestead/state_tracker.py @@ -3,7 +3,7 @@ Track state changes on top of a read-only ``PreState``. At block end, accumulated diffs feed into -``PreState.compute_state_root_and_trie_changes()``. +``PreState.compute_state_root()``. .. contents:: Table of Contents :backlinks: none diff --git a/src/ethereum/forks/istanbul/blocks.py b/src/ethereum/forks/istanbul/blocks.py index 3cdd475a33a..4a03bd2223d 100644 --- a/src/ethereum/forks/istanbul/blocks.py +++ b/src/ethereum/forks/istanbul/blocks.py @@ -62,12 +62,12 @@ class Header: Root hash ([`keccak256`]) of the state trie after executing all transactions in this block. It represents the state of the Ethereum Virtual Machine (EVM) after all transactions in this block have been processed. It - is computed using [`compute_state_root_and_trie_changes()`][changes], + is computed using [`compute_state_root()`][changes], which computes the root of the Merkle-Patricia [Trie] representing the Ethereum world state after applying the block's state changes. [`keccak256`]: ref:ethereum.crypto.hash.keccak256 - [changes]: ref:ethereum.state.State.compute_state_root_and_trie_changes + [changes]: ref:ethereum.state_mpt.State.compute_state_root [Trie]: ref:ethereum.merkle_patricia_trie.Trie """ # noqa: E501 diff --git a/src/ethereum/forks/istanbul/fork.py b/src/ethereum/forks/istanbul/fork.py index da84266e3f9..a1bfce15ab8 100644 --- a/src/ethereum/forks/istanbul/fork.py +++ b/src/ethereum/forks/istanbul/fork.py @@ -28,12 +28,8 @@ NonceMismatchError, ) from ethereum.merkle_patricia_trie import root, trie_set -from ethereum.state import ( - EMPTY_CODE_HASH, - Address, - State, - apply_changes_to_state, -) +from ethereum.state import EMPTY_CODE_HASH, Address +from ethereum.state_mpt import State, apply_changes_to_state from . import vm from .blocks import Block, Header, Log, Receipt @@ -191,11 +187,7 @@ def state_transition(chain: BlockChain, block: Block) -> None: ommers=block.ommers, ) block_diff = extract_block_diff(block_state) - block_state_root, _ = chain.state.compute_state_root_and_trie_changes( - block_diff.account_changes, - block_diff.storage_changes, - block_diff.storage_clears, - ) + block_state_root = chain.state.compute_state_root(block_diff) transactions_root = root(block_output.transactions_trie) receipt_root = root(block_output.receipts_trie) block_logs_bloom = logs_bloom(block_output.block_logs) diff --git a/src/ethereum/forks/istanbul/state_tracker.py b/src/ethereum/forks/istanbul/state_tracker.py index dd7a3c3bb8c..d7a607e6436 100644 --- a/src/ethereum/forks/istanbul/state_tracker.py +++ b/src/ethereum/forks/istanbul/state_tracker.py @@ -3,7 +3,7 @@ Track state changes on top of a read-only ``PreState``. At block end, accumulated diffs feed into -``PreState.compute_state_root_and_trie_changes()``. +``PreState.compute_state_root()``. .. contents:: Table of Contents :backlinks: none diff --git a/src/ethereum/forks/london/blocks.py b/src/ethereum/forks/london/blocks.py index d6708cc2f68..44bba156b59 100644 --- a/src/ethereum/forks/london/blocks.py +++ b/src/ethereum/forks/london/blocks.py @@ -71,12 +71,12 @@ class Header: Root hash ([`keccak256`]) of the state trie after executing all transactions in this block. It represents the state of the Ethereum Virtual Machine (EVM) after all transactions in this block have been processed. It - is computed using [`compute_state_root_and_trie_changes()`][changes], + is computed using [`compute_state_root()`][changes], which computes the root of the Merkle-Patricia [Trie] representing the Ethereum world state after applying the block's state changes. [`keccak256`]: ref:ethereum.crypto.hash.keccak256 - [changes]: ref:ethereum.state.State.compute_state_root_and_trie_changes + [changes]: ref:ethereum.state_mpt.State.compute_state_root [Trie]: ref:ethereum.merkle_patricia_trie.Trie """ # noqa: E501 diff --git a/src/ethereum/forks/london/fork.py b/src/ethereum/forks/london/fork.py index c457b885c6d..fd5155d1c82 100644 --- a/src/ethereum/forks/london/fork.py +++ b/src/ethereum/forks/london/fork.py @@ -30,12 +30,8 @@ ) from ethereum.fork_criteria import ByBlockNumber from ethereum.merkle_patricia_trie import root, trie_set -from ethereum.state import ( - EMPTY_CODE_HASH, - Address, - State, - apply_changes_to_state, -) +from ethereum.state import EMPTY_CODE_HASH, Address +from ethereum.state_mpt import State, apply_changes_to_state from . import FORK_CRITERIA, vm from .blocks import Block, Header, Log, Receipt, encode_receipt @@ -206,11 +202,7 @@ def state_transition(chain: BlockChain, block: Block) -> None: ommers=block.ommers, ) block_diff = extract_block_diff(block_state) - block_state_root, _ = chain.state.compute_state_root_and_trie_changes( - block_diff.account_changes, - block_diff.storage_changes, - block_diff.storage_clears, - ) + block_state_root = chain.state.compute_state_root(block_diff) transactions_root = root(block_output.transactions_trie) receipt_root = root(block_output.receipts_trie) block_logs_bloom = logs_bloom(block_output.block_logs) diff --git a/src/ethereum/forks/london/state_tracker.py b/src/ethereum/forks/london/state_tracker.py index dd7a3c3bb8c..d7a607e6436 100644 --- a/src/ethereum/forks/london/state_tracker.py +++ b/src/ethereum/forks/london/state_tracker.py @@ -3,7 +3,7 @@ Track state changes on top of a read-only ``PreState``. At block end, accumulated diffs feed into -``PreState.compute_state_root_and_trie_changes()``. +``PreState.compute_state_root()``. .. contents:: Table of Contents :backlinks: none diff --git a/src/ethereum/forks/muir_glacier/blocks.py b/src/ethereum/forks/muir_glacier/blocks.py index 426cfdb5dde..0fd12b2efc0 100644 --- a/src/ethereum/forks/muir_glacier/blocks.py +++ b/src/ethereum/forks/muir_glacier/blocks.py @@ -62,12 +62,12 @@ class Header: Root hash ([`keccak256`]) of the state trie after executing all transactions in this block. It represents the state of the Ethereum Virtual Machine (EVM) after all transactions in this block have been processed. It - is computed using [`compute_state_root_and_trie_changes()`][changes], + is computed using [`compute_state_root()`][changes], which computes the root of the Merkle-Patricia [Trie] representing the Ethereum world state after applying the block's state changes. [`keccak256`]: ref:ethereum.crypto.hash.keccak256 - [changes]: ref:ethereum.state.State.compute_state_root_and_trie_changes + [changes]: ref:ethereum.state_mpt.State.compute_state_root [Trie]: ref:ethereum.merkle_patricia_trie.Trie """ # noqa: E501 diff --git a/src/ethereum/forks/muir_glacier/fork.py b/src/ethereum/forks/muir_glacier/fork.py index e64fcf08654..580281b7905 100644 --- a/src/ethereum/forks/muir_glacier/fork.py +++ b/src/ethereum/forks/muir_glacier/fork.py @@ -28,12 +28,8 @@ NonceMismatchError, ) from ethereum.merkle_patricia_trie import root, trie_set -from ethereum.state import ( - EMPTY_CODE_HASH, - Address, - State, - apply_changes_to_state, -) +from ethereum.state import EMPTY_CODE_HASH, Address +from ethereum.state_mpt import State, apply_changes_to_state from . import vm from .blocks import Block, Header, Log, Receipt @@ -191,11 +187,7 @@ def state_transition(chain: BlockChain, block: Block) -> None: ommers=block.ommers, ) block_diff = extract_block_diff(block_state) - block_state_root, _ = chain.state.compute_state_root_and_trie_changes( - block_diff.account_changes, - block_diff.storage_changes, - block_diff.storage_clears, - ) + block_state_root = chain.state.compute_state_root(block_diff) transactions_root = root(block_output.transactions_trie) receipt_root = root(block_output.receipts_trie) block_logs_bloom = logs_bloom(block_output.block_logs) diff --git a/src/ethereum/forks/muir_glacier/state_tracker.py b/src/ethereum/forks/muir_glacier/state_tracker.py index dd7a3c3bb8c..d7a607e6436 100644 --- a/src/ethereum/forks/muir_glacier/state_tracker.py +++ b/src/ethereum/forks/muir_glacier/state_tracker.py @@ -3,7 +3,7 @@ Track state changes on top of a read-only ``PreState``. At block end, accumulated diffs feed into -``PreState.compute_state_root_and_trie_changes()``. +``PreState.compute_state_root()``. .. contents:: Table of Contents :backlinks: none diff --git a/src/ethereum/forks/osaka/blocks.py b/src/ethereum/forks/osaka/blocks.py index f1f174f892a..1055ba3407c 100644 --- a/src/ethereum/forks/osaka/blocks.py +++ b/src/ethereum/forks/osaka/blocks.py @@ -109,12 +109,12 @@ class Header: Root hash ([`keccak256`]) of the state trie after executing all transactions in this block. It represents the state of the Ethereum Virtual Machine (EVM) after all transactions in this block have been processed. It - is computed using [`compute_state_root_and_trie_changes()`][changes], + is computed using [`compute_state_root()`][changes], which computes the root of the Merkle-Patricia [Trie] representing the Ethereum world state after applying the block's state changes. [`keccak256`]: ref:ethereum.crypto.hash.keccak256 - [changes]: ref:ethereum.state.State.compute_state_root_and_trie_changes + [changes]: ref:ethereum.state_mpt.State.compute_state_root [Trie]: ref:ethereum.merkle_patricia_trie.Trie """ # noqa: E501 diff --git a/src/ethereum/forks/osaka/fork.py b/src/ethereum/forks/osaka/fork.py index c2c43c631dc..71cca148fac 100644 --- a/src/ethereum/forks/osaka/fork.py +++ b/src/ethereum/forks/osaka/fork.py @@ -28,12 +28,8 @@ NonceMismatchError, ) from ethereum.merkle_patricia_trie import root, trie_set -from ethereum.state import ( - EMPTY_CODE_HASH, - Address, - State, - apply_changes_to_state, -) +from ethereum.state import EMPTY_CODE_HASH, Address +from ethereum.state_mpt import State, apply_changes_to_state from . import vm from .blocks import Block, Header, Log, Receipt, Withdrawal, encode_receipt @@ -253,9 +249,7 @@ def state_transition(chain: BlockChain, block: Block) -> None: withdrawals=block.withdrawals, ) block_diff = extract_block_diff(block_state) - block_state_root, _ = chain.state.compute_state_root_and_trie_changes( - block_diff.account_changes, block_diff.storage_changes - ) + block_state_root = chain.state.compute_state_root(block_diff) transactions_root = root(block_output.transactions_trie) receipt_root = root(block_output.receipts_trie) block_logs_bloom = logs_bloom(block_output.block_logs) diff --git a/src/ethereum/forks/osaka/state_tracker.py b/src/ethereum/forks/osaka/state_tracker.py index 8ce889a833b..52d28c95b7c 100644 --- a/src/ethereum/forks/osaka/state_tracker.py +++ b/src/ethereum/forks/osaka/state_tracker.py @@ -3,7 +3,7 @@ Track state changes on top of a read-only ``PreState``. At block end, accumulated diffs feed into -``PreState.compute_state_root_and_trie_changes()``. +``PreState.compute_state_root()``. .. contents:: Table of Contents :backlinks: none diff --git a/src/ethereum/forks/paris/blocks.py b/src/ethereum/forks/paris/blocks.py index 15852ce82a1..84b89949ee0 100644 --- a/src/ethereum/forks/paris/blocks.py +++ b/src/ethereum/forks/paris/blocks.py @@ -73,12 +73,12 @@ class Header: Root hash ([`keccak256`]) of the state trie after executing all transactions in this block. It represents the state of the Ethereum Virtual Machine (EVM) after all transactions in this block have been processed. It - is computed using [`compute_state_root_and_trie_changes()`][changes], + is computed using [`compute_state_root()`][changes], which computes the root of the Merkle-Patricia [Trie] representing the Ethereum world state after applying the block's state changes. [`keccak256`]: ref:ethereum.crypto.hash.keccak256 - [changes]: ref:ethereum.state.State.compute_state_root_and_trie_changes + [changes]: ref:ethereum.state_mpt.State.compute_state_root [Trie]: ref:ethereum.merkle_patricia_trie.Trie """ # noqa: E501 diff --git a/src/ethereum/forks/paris/fork.py b/src/ethereum/forks/paris/fork.py index 318cbdf2163..cfe47564565 100644 --- a/src/ethereum/forks/paris/fork.py +++ b/src/ethereum/forks/paris/fork.py @@ -28,12 +28,8 @@ NonceMismatchError, ) from ethereum.merkle_patricia_trie import root, trie_set -from ethereum.state import ( - EMPTY_CODE_HASH, - Address, - State, - apply_changes_to_state, -) +from ethereum.state import EMPTY_CODE_HASH, Address +from ethereum.state_mpt import State, apply_changes_to_state from . import vm from .blocks import Block, Header, Log, Receipt, encode_receipt @@ -197,11 +193,7 @@ def state_transition(chain: BlockChain, block: Block) -> None: transactions=block.transactions, ) block_diff = extract_block_diff(block_state) - block_state_root, _ = chain.state.compute_state_root_and_trie_changes( - block_diff.account_changes, - block_diff.storage_changes, - block_diff.storage_clears, - ) + block_state_root = chain.state.compute_state_root(block_diff) transactions_root = root(block_output.transactions_trie) receipt_root = root(block_output.receipts_trie) block_logs_bloom = logs_bloom(block_output.block_logs) diff --git a/src/ethereum/forks/paris/state_tracker.py b/src/ethereum/forks/paris/state_tracker.py index 964acb0682a..1de225db4bc 100644 --- a/src/ethereum/forks/paris/state_tracker.py +++ b/src/ethereum/forks/paris/state_tracker.py @@ -3,7 +3,7 @@ Track state changes on top of a read-only ``PreState``. At block end, accumulated diffs feed into -``PreState.compute_state_root_and_trie_changes()``. +``PreState.compute_state_root()``. .. contents:: Table of Contents :backlinks: none diff --git a/src/ethereum/forks/prague/blocks.py b/src/ethereum/forks/prague/blocks.py index e34b1dc4e6f..2b2c50cbba9 100644 --- a/src/ethereum/forks/prague/blocks.py +++ b/src/ethereum/forks/prague/blocks.py @@ -109,12 +109,12 @@ class Header: Root hash ([`keccak256`]) of the state trie after executing all transactions in this block. It represents the state of the Ethereum Virtual Machine (EVM) after all transactions in this block have been processed. It - is computed using [`compute_state_root_and_trie_changes()`][changes], + is computed using [`compute_state_root()`][changes], which computes the root of the Merkle-Patricia [Trie] representing the Ethereum world state after applying the block's state changes. [`keccak256`]: ref:ethereum.crypto.hash.keccak256 - [changes]: ref:ethereum.state.State.compute_state_root_and_trie_changes + [changes]: ref:ethereum.state_mpt.State.compute_state_root [Trie]: ref:ethereum.merkle_patricia_trie.Trie """ # noqa: E501 diff --git a/src/ethereum/forks/prague/fork.py b/src/ethereum/forks/prague/fork.py index f5c4f81a6f8..9a322c36626 100644 --- a/src/ethereum/forks/prague/fork.py +++ b/src/ethereum/forks/prague/fork.py @@ -28,12 +28,8 @@ NonceMismatchError, ) from ethereum.merkle_patricia_trie import root, trie_set -from ethereum.state import ( - EMPTY_CODE_HASH, - Address, - State, - apply_changes_to_state, -) +from ethereum.state import EMPTY_CODE_HASH, Address +from ethereum.state_mpt import State, apply_changes_to_state from . import vm from .blocks import Block, Header, Log, Receipt, Withdrawal, encode_receipt @@ -243,9 +239,7 @@ def state_transition(chain: BlockChain, block: Block) -> None: withdrawals=block.withdrawals, ) block_diff = extract_block_diff(block_state) - block_state_root, _ = chain.state.compute_state_root_and_trie_changes( - block_diff.account_changes, block_diff.storage_changes - ) + block_state_root = chain.state.compute_state_root(block_diff) transactions_root = root(block_output.transactions_trie) receipt_root = root(block_output.receipts_trie) block_logs_bloom = logs_bloom(block_output.block_logs) diff --git a/src/ethereum/forks/prague/state_tracker.py b/src/ethereum/forks/prague/state_tracker.py index 8ce889a833b..52d28c95b7c 100644 --- a/src/ethereum/forks/prague/state_tracker.py +++ b/src/ethereum/forks/prague/state_tracker.py @@ -3,7 +3,7 @@ Track state changes on top of a read-only ``PreState``. At block end, accumulated diffs feed into -``PreState.compute_state_root_and_trie_changes()``. +``PreState.compute_state_root()``. .. contents:: Table of Contents :backlinks: none diff --git a/src/ethereum/forks/shanghai/blocks.py b/src/ethereum/forks/shanghai/blocks.py index 04990ab04ff..ad2b2b01293 100644 --- a/src/ethereum/forks/shanghai/blocks.py +++ b/src/ethereum/forks/shanghai/blocks.py @@ -107,12 +107,12 @@ class Header: Root hash ([`keccak256`]) of the state trie after executing all transactions in this block. It represents the state of the Ethereum Virtual Machine (EVM) after all transactions in this block have been processed. It - is computed using [`compute_state_root_and_trie_changes()`][changes], + is computed using [`compute_state_root()`][changes], which computes the root of the Merkle-Patricia [Trie] representing the Ethereum world state after applying the block's state changes. [`keccak256`]: ref:ethereum.crypto.hash.keccak256 - [changes]: ref:ethereum.state.State.compute_state_root_and_trie_changes + [changes]: ref:ethereum.state_mpt.State.compute_state_root [Trie]: ref:ethereum.merkle_patricia_trie.Trie """ # noqa: E501 diff --git a/src/ethereum/forks/shanghai/fork.py b/src/ethereum/forks/shanghai/fork.py index c038d947014..16c4207aca4 100644 --- a/src/ethereum/forks/shanghai/fork.py +++ b/src/ethereum/forks/shanghai/fork.py @@ -28,12 +28,8 @@ NonceMismatchError, ) from ethereum.merkle_patricia_trie import root, trie_set -from ethereum.state import ( - EMPTY_CODE_HASH, - Address, - State, - apply_changes_to_state, -) +from ethereum.state import EMPTY_CODE_HASH, Address +from ethereum.state_mpt import State, apply_changes_to_state from . import vm from .blocks import Block, Header, Log, Receipt, Withdrawal, encode_receipt @@ -198,11 +194,7 @@ def state_transition(chain: BlockChain, block: Block) -> None: withdrawals=block.withdrawals, ) block_diff = extract_block_diff(block_state) - block_state_root, _ = chain.state.compute_state_root_and_trie_changes( - block_diff.account_changes, - block_diff.storage_changes, - block_diff.storage_clears, - ) + block_state_root = chain.state.compute_state_root(block_diff) transactions_root = root(block_output.transactions_trie) receipt_root = root(block_output.receipts_trie) block_logs_bloom = logs_bloom(block_output.block_logs) diff --git a/src/ethereum/forks/shanghai/state_tracker.py b/src/ethereum/forks/shanghai/state_tracker.py index 964acb0682a..1de225db4bc 100644 --- a/src/ethereum/forks/shanghai/state_tracker.py +++ b/src/ethereum/forks/shanghai/state_tracker.py @@ -3,7 +3,7 @@ Track state changes on top of a read-only ``PreState``. At block end, accumulated diffs feed into -``PreState.compute_state_root_and_trie_changes()``. +``PreState.compute_state_root()``. .. contents:: Table of Contents :backlinks: none diff --git a/src/ethereum/forks/spurious_dragon/blocks.py b/src/ethereum/forks/spurious_dragon/blocks.py index f1f063a18b3..cdb35bed696 100644 --- a/src/ethereum/forks/spurious_dragon/blocks.py +++ b/src/ethereum/forks/spurious_dragon/blocks.py @@ -62,12 +62,12 @@ class Header: Root hash ([`keccak256`]) of the state trie after executing all transactions in this block. It represents the state of the Ethereum Virtual Machine (EVM) after all transactions in this block have been processed. It - is computed using [`compute_state_root_and_trie_changes()`][changes], + is computed using [`compute_state_root()`][changes], which computes the root of the Merkle-Patricia [Trie] representing the Ethereum world state after applying the block's state changes. [`keccak256`]: ref:ethereum.crypto.hash.keccak256 - [changes]: ref:ethereum.state.State.compute_state_root_and_trie_changes + [changes]: ref:ethereum.state_mpt.State.compute_state_root [Trie]: ref:ethereum.merkle_patricia_trie.Trie """ # noqa: E501 diff --git a/src/ethereum/forks/spurious_dragon/fork.py b/src/ethereum/forks/spurious_dragon/fork.py index 01c8b269d16..f04455028e9 100644 --- a/src/ethereum/forks/spurious_dragon/fork.py +++ b/src/ethereum/forks/spurious_dragon/fork.py @@ -28,12 +28,8 @@ NonceMismatchError, ) from ethereum.merkle_patricia_trie import root, trie_set -from ethereum.state import ( - EMPTY_CODE_HASH, - Address, - State, - apply_changes_to_state, -) +from ethereum.state import EMPTY_CODE_HASH, Address +from ethereum.state_mpt import State, apply_changes_to_state from . import vm from .blocks import Block, Header, Log, Receipt @@ -189,11 +185,7 @@ def state_transition(chain: BlockChain, block: Block) -> None: ommers=block.ommers, ) block_diff = extract_block_diff(block_state) - block_state_root, _ = chain.state.compute_state_root_and_trie_changes( - block_diff.account_changes, - block_diff.storage_changes, - block_diff.storage_clears, - ) + block_state_root = chain.state.compute_state_root(block_diff) transactions_root = root(block_output.transactions_trie) receipt_root = root(block_output.receipts_trie) block_logs_bloom = logs_bloom(block_output.block_logs) @@ -712,12 +704,8 @@ def process_transaction( block_state = block_env.state block_diff = extract_block_diff(block_state) - intermediate_state_root, _ = ( - block_state.pre_state.compute_state_root_and_trie_changes( - block_diff.account_changes, - block_diff.storage_changes, - block_diff.storage_clears, - ) + intermediate_state_root = block_state.pre_state.compute_state_root( + block_diff ) receipt = make_receipt( diff --git a/src/ethereum/forks/spurious_dragon/state_tracker.py b/src/ethereum/forks/spurious_dragon/state_tracker.py index dd7a3c3bb8c..d7a607e6436 100644 --- a/src/ethereum/forks/spurious_dragon/state_tracker.py +++ b/src/ethereum/forks/spurious_dragon/state_tracker.py @@ -3,7 +3,7 @@ Track state changes on top of a read-only ``PreState``. At block end, accumulated diffs feed into -``PreState.compute_state_root_and_trie_changes()``. +``PreState.compute_state_root()``. .. contents:: Table of Contents :backlinks: none diff --git a/src/ethereum/forks/tangerine_whistle/blocks.py b/src/ethereum/forks/tangerine_whistle/blocks.py index ddc52041b03..e97790708a8 100644 --- a/src/ethereum/forks/tangerine_whistle/blocks.py +++ b/src/ethereum/forks/tangerine_whistle/blocks.py @@ -62,12 +62,12 @@ class Header: Root hash ([`keccak256`]) of the state trie after executing all transactions in this block. It represents the state of the Ethereum Virtual Machine (EVM) after all transactions in this block have been processed. It - is computed using [`compute_state_root_and_trie_changes()`][changes], + is computed using [`compute_state_root()`][changes], which computes the root of the Merkle-Patricia [Trie] representing the Ethereum world state after applying the block's state changes. [`keccak256`]: ref:ethereum.crypto.hash.keccak256 - [changes]: ref:ethereum.state.State.compute_state_root_and_trie_changes + [changes]: ref:ethereum.state_mpt.State.compute_state_root [Trie]: ref:ethereum.merkle_patricia_trie.Trie """ # noqa: E501 diff --git a/src/ethereum/forks/tangerine_whistle/fork.py b/src/ethereum/forks/tangerine_whistle/fork.py index cf0a29d826f..64bb280734a 100644 --- a/src/ethereum/forks/tangerine_whistle/fork.py +++ b/src/ethereum/forks/tangerine_whistle/fork.py @@ -28,12 +28,8 @@ NonceMismatchError, ) from ethereum.merkle_patricia_trie import root, trie_set -from ethereum.state import ( - EMPTY_CODE_HASH, - Address, - State, - apply_changes_to_state, -) +from ethereum.state import EMPTY_CODE_HASH, Address +from ethereum.state_mpt import State, apply_changes_to_state from . import vm from .blocks import Block, Header, Log, Receipt @@ -185,11 +181,7 @@ def state_transition(chain: BlockChain, block: Block) -> None: ommers=block.ommers, ) block_diff = extract_block_diff(block_state) - block_state_root, _ = chain.state.compute_state_root_and_trie_changes( - block_diff.account_changes, - block_diff.storage_changes, - block_diff.storage_clears, - ) + block_state_root = chain.state.compute_state_root(block_diff) transactions_root = root(block_output.transactions_trie) receipt_root = root(block_output.receipts_trie) block_logs_bloom = logs_bloom(block_output.block_logs) @@ -689,12 +681,8 @@ def process_transaction( block_state = block_env.state block_diff = extract_block_diff(block_state) - intermediate_state_root, _ = ( - block_state.pre_state.compute_state_root_and_trie_changes( - block_diff.account_changes, - block_diff.storage_changes, - block_diff.storage_clears, - ) + intermediate_state_root = block_state.pre_state.compute_state_root( + block_diff ) receipt = make_receipt( diff --git a/src/ethereum/forks/tangerine_whistle/state_tracker.py b/src/ethereum/forks/tangerine_whistle/state_tracker.py index dd7a3c3bb8c..d7a607e6436 100644 --- a/src/ethereum/forks/tangerine_whistle/state_tracker.py +++ b/src/ethereum/forks/tangerine_whistle/state_tracker.py @@ -3,7 +3,7 @@ Track state changes on top of a read-only ``PreState``. At block end, accumulated diffs feed into -``PreState.compute_state_root_and_trie_changes()``. +``PreState.compute_state_root()``. .. contents:: Table of Contents :backlinks: none diff --git a/src/ethereum/merkle_patricia_trie.py b/src/ethereum/merkle_patricia_trie.py index cb088793454..76b26896fc5 100644 --- a/src/ethereum/merkle_patricia_trie.py +++ b/src/ethereum/merkle_patricia_trie.py @@ -44,7 +44,6 @@ import copy from dataclasses import dataclass, field from typing import ( - TYPE_CHECKING, Callable, Dict, Generic, @@ -65,16 +64,11 @@ from ethereum_types.numeric import Uint from typing_extensions import assert_type -from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.crypto.hash import keccak256 +from ethereum.state import Account, Address, Root from ethereum.utils.hexadecimal import hex_to_bytes -if TYPE_CHECKING: - from ethereum.state import Account, Address, Root - -# Note: `Hash32` is used here rather than `Root` because `Root` is defined in -# `ethereum.state`, which imports from this module — referring to it at module -# scope would create a circular import. -EMPTY_TRIE_ROOT = Hash32( +EMPTY_TRIE_ROOT = Root( hex_to_bytes( "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" ) @@ -266,8 +260,6 @@ def encode_node(node: Extended, storage_root: Bytes | None = None) -> Bytes: [`Account`]: ref:ethereum.state.Account [`encode_account`]: ref:ethereum.merkle_patricia_trie.encode_account """ - from ethereum.state import Account - if isinstance(node, Account): assert storage_root is not None return encode_account(node, storage_root) @@ -432,8 +424,6 @@ def _prepare_trie( [bnl]: ref:ethereum.merkle_patricia_trie.bytes_to_nibble_list [`keccak256`]: ref:ethereum.crypto.hash.keccak256 """ - from ethereum.state import Account, Address - mapped: MutableMapping[Bytes, Bytes] = {} for preimage, value in trie._data.items(): @@ -477,8 +467,6 @@ def root( [`Hash32`]: ref:ethereum.crypto.hash.Hash32 [`Account`]: ref:ethereum.state.Account """ - from ethereum.state import Root - obj = _prepare_trie(trie, get_storage_root) root_node = encode_internal_node(patricialize(obj, Uint(0))) diff --git a/src/ethereum/state.py b/src/ethereum/state.py index 5a34472390e..e7903318c83 100644 --- a/src/ethereum/state.py +++ b/src/ethereum/state.py @@ -1,27 +1,26 @@ """ -Shared state types and the `PreState` protocol used by the state transition -function. +Shared state model and the `PreState` protocol used by the state +transition function. -The `PreState` protocol specifies the operations that any pre-execution state -provider must support, allowing multiple backing implementations (in-memory -`dict`, on-disk database, witness, etc.). - -The `State` class is the in-memory implementation of `PreState`. It consists -of a main account trie and storage tries for each contract. +The `PreState` protocol specifies the operations that any +pre-execution state provider must support, allowing multiple backing +implementations (in-memory `dict`, on-disk database, witness, etc.). +This module is commitment-agnostic: it defines what state *is*, not +how it is committed to. The Merkle-Patricia-Trie-backed in-memory +implementation lives in [`ethereum.state_mpt`]. There is a distinction between an account that does not exist and `EMPTY_ACCOUNT`. + +[`ethereum.state_mpt`]: ref:ethereum.state_mpt """ from dataclasses import dataclass, field from typing import ( - AbstractSet, Dict, - List, Optional, Protocol, Set, - Tuple, final, ) @@ -30,15 +29,6 @@ from ethereum_types.numeric import U256, Uint from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.merkle_patricia_trie import ( - EMPTY_TRIE_ROOT, - InternalNode, - Trie, - copy_trie, - root, - trie_get, - trie_set, -) Address = Bytes20 Root = Hash32 @@ -73,13 +63,17 @@ class BlockDiff: State changes produced by executing a block. """ - account_changes: Dict[Address, Optional[Account]] + account_changes: Dict[Address, Optional[Account]] = field( + default_factory=dict + ) """Per-address account diffs produced by execution.""" - storage_changes: Dict[Address, Dict[Bytes32, U256]] + storage_changes: Dict[Address, Dict[Bytes32, U256]] = field( + default_factory=dict + ) """Per-address storage diffs produced by execution.""" - code_changes: Dict[Hash32, Bytes] + code_changes: Dict[Hash32, Bytes] = field(default_factory=dict) """New bytecodes (keyed by code hash) introduced by execution.""" storage_clears: Set[Address] = field(default_factory=set) @@ -133,218 +127,19 @@ def account_has_storage(self, address: Address) -> bool: """ ... - def compute_state_root_and_trie_changes( - self, - account_changes: Dict[Address, Optional[Account]], - storage_changes: Dict[Address, Dict[Bytes32, U256]], - storage_clears: AbstractSet[Address] = frozenset(), - ) -> Tuple[Root, List["InternalNode"]]: + def compute_state_root(self, block_diff: BlockDiff) -> Root: """ - Compute the state root after applying changes to the pre-state. + Compute the state root after applying `block_diff` to the + pre-state. The pre-state itself is not modified. - ``storage_clears`` lists addresses whose pre-existing storage - tries must be dropped before ``storage_changes`` is applied, so - any post-wipe writes begin from empty storage. + The diff carries bytecode deployed during the block in + ``code_changes``, keyed by code hash. Commitments over code + hashes alone can ignore it; a commitment over code contents + resolves each account's bytecode through its ``code_hash``, + joining ``account_changes`` to ``code_changes``, because the + new bytecode is not yet in the provider's code store when the + root is computed. - Return the new state root together with the internal trie nodes - that were created or modified. + Return the new state root. """ ... - - -@final -@dataclass -class State: - """ - Contains all information that is preserved between transactions. - """ - - _main_trie: Trie[Address, Optional[Account]] = field( - default_factory=lambda: Trie(secured=True, default=None) - ) - _storage_tries: Dict[Address, Trie[Bytes32, U256]] = field( - default_factory=dict - ) - _code_store: Dict[Hash32, Bytes] = field( - default_factory=dict, compare=False - ) - - def get_code(self, code_hash: Hash32) -> Bytes: - """ - Get the bytecode for a given code hash. - - Return ``b""`` for ``EMPTY_CODE_HASH``. - """ - if code_hash == EMPTY_CODE_HASH: - return b"" - return self._code_store[code_hash] - - def get_account_optional(self, address: Address) -> Optional[Account]: - """ - Get the account at an address. - - Return ``None`` if there is no account at the address. - """ - return trie_get(self._main_trie, address) - - def get_storage(self, address: Address, key: Bytes32) -> U256: - """ - Get a storage value. - - Return ``U256(0)`` if the key has not been set. - """ - trie = self._storage_tries.get(address) - if trie is None: - return U256(0) - - value = trie_get(trie, key) - - assert isinstance(value, U256) - return value - - def account_has_storage(self, address: Address) -> bool: - """ - Check whether an account has any storage. - - Only needed for EIP-7610. - """ - return address in self._storage_tries - - def compute_state_root_and_trie_changes( - self, - account_changes: Dict[Address, Optional[Account]], - storage_changes: Dict[Address, Dict[Bytes32, U256]], - storage_clears: AbstractSet[Address] = frozenset(), - ) -> Tuple[Root, List["InternalNode"]]: - """ - Compute the state root after applying changes to the pre-state. - - ``storage_clears`` lists addresses whose pre-existing storage - tries are dropped before ``storage_changes`` is applied, so any - post-wipe writes begin from empty storage. - - Return the new state root together with the internal trie nodes - that were created or modified. - """ - main_trie = copy_trie(self._main_trie) - storage_tries = { - k: copy_trie(v) - for k, v in self._storage_tries.items() - if k not in storage_clears - } - - for address, account in account_changes.items(): - trie_set(main_trie, address, account) - - for address, slots in storage_changes.items(): - trie = storage_tries.get(address) - if trie is None: - trie = Trie(secured=True, default=U256(0)) - storage_tries[address] = trie - for key, value in slots.items(): - trie_set(trie, key, value) - if trie._data == {}: - del storage_tries[address] - - def get_storage_root(addr: Address) -> Root: - if addr in storage_tries: - return root(storage_tries[addr]) - return EMPTY_TRIE_ROOT - - state_root_value = root(main_trie, get_storage_root=get_storage_root) - - return state_root_value, [] - - -def close_state(state: State) -> None: - """ - Free resources held by the state. Used by optimized implementations to - release file descriptors. - """ - del state._main_trie - del state._storage_tries - del state._code_store - - -def apply_changes_to_state(state: State, diff: BlockDiff) -> None: - """ - Apply block-level diff to the ``State`` for the next block. - - Parameters - ---------- - state : - The state to update. - diff : - Account, storage, and code changes to apply. - - """ - for address in diff.storage_clears: - state._storage_tries.pop(address, None) - - for address, account in diff.account_changes.items(): - trie_set(state._main_trie, address, account) - - for address, slots in diff.storage_changes.items(): - trie = state._storage_tries.get(address) - if trie is None: - trie = Trie(secured=True, default=U256(0)) - state._storage_tries[address] = trie - for key, value in slots.items(): - trie_set(trie, key, value) - if trie._data == {}: - del state._storage_tries[address] - - state._code_store.update(diff.code_changes) - - -def store_code(state: State, code: Bytes) -> Hash32: - """ - Store bytecode in ``State``. - """ - code_hash = keccak256(code) - if code_hash != EMPTY_CODE_HASH: - state._code_store[code_hash] = code - return code_hash - - -def set_account( - state: State, - address: Address, - account: Optional[Account], -) -> None: - """ - Set an account in a ``State``. - - Setting to ``None`` deletes the account. - """ - trie_set(state._main_trie, address, account) - - -def set_storage( - state: State, - address: Address, - key: Bytes32, - value: U256, -) -> None: - """ - Set a storage value in a ``State``. - - Setting to ``U256(0)`` deletes the key. - """ - assert trie_get(state._main_trie, address) is not None - - trie = state._storage_tries.get(address) - if trie is None: - trie = Trie(secured=True, default=U256(0)) - state._storage_tries[address] = trie - trie_set(trie, key, value) - if trie._data == {}: - del state._storage_tries[address] - - -def state_root(state: State) -> Root: - """ - Compute the state root of the current state. - """ - root_value, _ = state.compute_state_root_and_trie_changes({}, {}) - return root_value diff --git a/src/ethereum/state_mpt.py b/src/ethereum/state_mpt.py new file mode 100644 index 00000000000..dbdd7a718e0 --- /dev/null +++ b/src/ethereum/state_mpt.py @@ -0,0 +1,221 @@ +""" +Merkle-Patricia-Trie-backed implementation of the shared state model. + +The [`State`] class here is the in-memory implementation of the +[`PreState`] protocol used on Ethereum mainnet: accounts and storage +live in Merkle Patricia Tries and the state root is the MPT +commitment. Other providers, such as databases, witnesses, or other +commitment schemes, are separate implementations of [`PreState`]. + +[`State`]: ref:ethereum.state_mpt.State +[`PreState`]: ref:ethereum.state.PreState +""" + +from dataclasses import dataclass, field +from typing import Dict, Optional, final + +from ethereum_types.bytes import Bytes, Bytes32 +from ethereum_types.numeric import U256 + +from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.merkle_patricia_trie import ( + EMPTY_TRIE_ROOT, + Trie, + copy_trie, + root, + trie_get, + trie_set, +) +from ethereum.state import EMPTY_CODE_HASH, Account, Address, BlockDiff, Root + + +@final +@dataclass +class State: + """ + Contains all information that is preserved between transactions. + """ + + _main_trie: Trie[Address, Optional[Account]] = field( + default_factory=lambda: Trie(secured=True, default=None) + ) + _storage_tries: Dict[Address, Trie[Bytes32, U256]] = field( + default_factory=dict + ) + _code_store: Dict[Hash32, Bytes] = field( + default_factory=dict, compare=False + ) + + def get_code(self, code_hash: Hash32) -> Bytes: + """ + Get the bytecode for a given code hash. + + Return ``b""`` for ``EMPTY_CODE_HASH``. + """ + if code_hash == EMPTY_CODE_HASH: + return b"" + return self._code_store[code_hash] + + def get_account_optional(self, address: Address) -> Optional[Account]: + """ + Get the account at an address. + + Return ``None`` if there is no account at the address. + """ + return trie_get(self._main_trie, address) + + def get_storage(self, address: Address, key: Bytes32) -> U256: + """ + Get a storage value. + + Return ``U256(0)`` if the key has not been set. + """ + trie = self._storage_tries.get(address) + if trie is None: + return U256(0) + + value = trie_get(trie, key) + + assert isinstance(value, U256) + return value + + def account_has_storage(self, address: Address) -> bool: + """ + Check whether an account has any storage. + + Only needed for EIP-7610. + """ + return address in self._storage_tries + + def compute_state_root(self, block_diff: BlockDiff) -> Root: + """ + Compute the state root after applying `block_diff` to the + pre-state. The pre-state itself is not modified. + + The diff's ``code_changes`` play no part: the Merkle Patricia + Trie commits to accounts' code hashes, never to code + contents, so account diffs alone determine the root. + + Return the new state root. + """ + main_trie = copy_trie(self._main_trie) + storage_tries = { + k: copy_trie(v) + for k, v in self._storage_tries.items() + if k not in block_diff.storage_clears + } + + for address, account in block_diff.account_changes.items(): + trie_set(main_trie, address, account) + + for address, slots in block_diff.storage_changes.items(): + trie = storage_tries.get(address) + if trie is None: + trie = Trie(secured=True, default=U256(0)) + storage_tries[address] = trie + for key, value in slots.items(): + trie_set(trie, key, value) + if trie._data == {}: + del storage_tries[address] + + def get_storage_root(addr: Address) -> Root: + if addr in storage_tries: + return root(storage_tries[addr]) + return EMPTY_TRIE_ROOT + + state_root_value = root(main_trie, get_storage_root=get_storage_root) + + return state_root_value + + +def close_state(state: State) -> None: + """ + Free resources held by the state. Used by optimized implementations to + release file descriptors. + """ + del state._main_trie + del state._storage_tries + del state._code_store + + +def apply_changes_to_state(state: State, diff: BlockDiff) -> None: + """ + Apply block-level diff to the ``State`` for the next block. + + Parameters + ---------- + state : + The state to update. + diff : + Account, storage, and code changes to apply. + + """ + for address in diff.storage_clears: + state._storage_tries.pop(address, None) + + for address, account in diff.account_changes.items(): + trie_set(state._main_trie, address, account) + + for address, slots in diff.storage_changes.items(): + trie = state._storage_tries.get(address) + if trie is None: + trie = Trie(secured=True, default=U256(0)) + state._storage_tries[address] = trie + for key, value in slots.items(): + trie_set(trie, key, value) + if trie._data == {}: + del state._storage_tries[address] + + state._code_store.update(diff.code_changes) + + +def store_code(state: State, code: Bytes) -> Hash32: + """ + Store bytecode in ``State``. + """ + code_hash = keccak256(code) + if code_hash != EMPTY_CODE_HASH: + state._code_store[code_hash] = code + return code_hash + + +def set_account( + state: State, + address: Address, + account: Optional[Account], +) -> None: + """ + Set an account in a ``State``. + + Setting to ``None`` deletes the account. + """ + trie_set(state._main_trie, address, account) + + +def set_storage( + state: State, + address: Address, + key: Bytes32, + value: U256, +) -> None: + """ + Set a storage value in a ``State``. + + Setting to ``U256(0)`` deletes the key. + """ + assert trie_get(state._main_trie, address) is not None + + trie = state._storage_tries.get(address) + if trie is None: + trie = Trie(secured=True, default=U256(0)) + state._storage_tries[address] = trie + trie_set(trie, key, value) + if trie._data == {}: + del state._storage_tries[address] + + +def state_root(state: State) -> Root: + """ + Compute the state root of the current state. + """ + return state.compute_state_root(BlockDiff()) diff --git a/src/ethereum_spec_tools/evm_tools/loaders/fixture_loader.py b/src/ethereum_spec_tools/evm_tools/loaders/fixture_loader.py index 3a93f850fbd..f4bf3aa4e5b 100644 --- a/src/ethereum_spec_tools/evm_tools/loaders/fixture_loader.py +++ b/src/ethereum_spec_tools/evm_tools/loaders/fixture_loader.py @@ -12,7 +12,6 @@ from ethereum.crypto.hash import Hash32, keccak256 from ethereum.exceptions import StateWithEmptyAccount -from ethereum.state import State, set_account, set_storage, store_code from ethereum.utils.hexadecimal import ( hex_to_bytes, hex_to_bytes8, @@ -60,7 +59,8 @@ def __init__(self, fork_module: str | Hardfork): def json_to_state(self, raw: Any) -> Any: """Converts json state data to a state object.""" - state = State() + provider = self.fork.state_provider + state = provider.State() EMPTY_ACCOUNT = self.fork.EMPTY_ACCOUNT # noqa N806 for address_hex, account_state in raw.items(): @@ -69,7 +69,7 @@ def json_to_state(self, raw: Any) -> Any: balance = U256(hex_to_uint(account_state.get("balance", "0x0"))) code = hex_to_bytes(account_state.get("code", "")) - code_hash = store_code(state, code) + code_hash = provider.store_code(state, code) account = self.fork.Account( nonce=nonce, balance=balance, @@ -79,10 +79,10 @@ def json_to_state(self, raw: Any) -> Any: if self.fork.proof_of_stake and account == EMPTY_ACCOUNT: raise StateWithEmptyAccount(f"Empty account at {address_hex}.") - set_account(state, address, account) + provider.set_account(state, address, account) for k, v in account_state.get("storage", {}).items(): - set_storage( + provider.set_storage( state, address, hex_to_bytes32(k), diff --git a/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py b/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py index fda1bac008b..eebd406f7c7 100644 --- a/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py +++ b/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py @@ -2,6 +2,7 @@ Loader for code from the relevant fork. """ +from importlib import import_module from inspect import signature from typing import Any, Final @@ -303,6 +304,17 @@ def decode_transaction(self) -> Any: """decode_transaction function of the fork.""" return self._module("transactions").decode_transaction + @property + def state_provider(self) -> Any: + """ + Module implementing the fork's state provider. + + Resolved through the ``State`` class the fork's ``fork`` + module imports, so each fork selects its own commitment + scheme (``ethereum.state_mpt``, ``ethereum.state_pbt``, ...). + """ + return import_module(self._module("fork").State.__module__) + @property def BlockState(self) -> Any: """BlockState class of the fork.""" diff --git a/src/ethereum_spec_tools/evm_tools/t8n/result.py b/src/ethereum_spec_tools/evm_tools/t8n/result.py index 8e434fdd412..5bab2af3b75 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/result.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/result.py @@ -79,9 +79,7 @@ def build_result( from execution_testing.client_clis.cli_types import Result as TestingResult diff = t8n.fork.extract_block_diff(t8n._block_state) - state_root, _ = t8n.alloc.compute_state_root_and_trie_changes( - diff.account_changes, diff.storage_changes, diff.storage_clears - ) + state_root = t8n.alloc.compute_state_root(diff) arguments: Dict[str, Any] = { "state_root": state_root, diff --git a/tests/json_loader/helpers/load_blockchain_tests.py b/tests/json_loader/helpers/load_blockchain_tests.py index d4d21a43012..feec8e44b0f 100644 --- a/tests/json_loader/helpers/load_blockchain_tests.py +++ b/tests/json_loader/helpers/load_blockchain_tests.py @@ -12,7 +12,7 @@ from ethereum.crypto.hash import keccak256 from ethereum.exceptions import EthereumException, StateWithEmptyAccount -from ethereum.state import close_state +from ethereum.state_mpt import close_state from ethereum.utils.hexadecimal import hex_to_bytes from ethereum_spec_tools.evm_tools.loaders.fixture_loader import Load diff --git a/tests/json_loader/helpers/load_vm_tests.py b/tests/json_loader/helpers/load_vm_tests.py index 73660b42a05..d0acecbab25 100644 --- a/tests/json_loader/helpers/load_vm_tests.py +++ b/tests/json_loader/helpers/load_vm_tests.py @@ -92,9 +92,9 @@ def _state_module(self) -> Any: try: return self._module("state") except ModuleNotFoundError: - import ethereum.state + import ethereum.state_mpt - return ethereum.state + return ethereum.state_mpt def run_test_from_dict(self, json_data: Dict[str, Any]) -> None: """ diff --git a/tests/json_loader/test_genesis.py b/tests/json_loader/test_genesis.py index 5fb45fcd887..a90210fd1ff 100644 --- a/tests/json_loader/test_genesis.py +++ b/tests/json_loader/test_genesis.py @@ -15,8 +15,8 @@ get_genesis_configuration, ) from ethereum.merkle_patricia_trie import Trie, root -from ethereum.state import ( - Address, +from ethereum.state import Address +from ethereum.state_mpt import ( State, set_account, set_storage, @@ -68,11 +68,10 @@ def fork_name(fork: Hardfork) -> str: def test_genesis(fork: Hardfork) -> None: """Tests genesis block creation for all hardforks.""" # TODO: remove once the changes have been back-ported - from ethereum.merkle_patricia_trie import Trie - from ethereum.state import ( - Address, + from ethereum.merkle_patricia_trie import Trie, root + from ethereum.state import Address + from ethereum.state_mpt import ( State, - root, set_account, set_storage, state_root, diff --git a/tests/json_loader/test_optimized_state.py b/tests/json_loader/test_optimized_state.py index 28763cee324..c910a99835b 100644 --- a/tests/json_loader/test_optimized_state.py +++ b/tests/json_loader/test_optimized_state.py @@ -5,7 +5,7 @@ import pytest from ethereum_types.numeric import U256 -import ethereum.state as state +import ethereum.state_mpt as state from ethereum.forks.tangerine_whistle.utils.hexadecimal import hex_to_address from ethereum.state import EMPTY_ACCOUNT from ethereum_spec_tools.forks import Hardfork From d5d230ca57ead2a208e0f9241b7a97793fffb72f Mon Sep 17 00:00:00 2001 From: Kumarutkarsh9470 <kutkarsh517@gmail.com> Date: Mon, 27 Jul 2026 23:17:37 +0530 Subject: [PATCH 158/233] bug(spec-tools): make t8n daemon module importable on Windows (#3212) `daemon.py` defined `_UnixSocketHttpServer` by subclassing `socketserver.UnixStreamServer`, which does not exist on Windows. Since `ethereum_spec_tools.evm_tools` imports this module at load time, importing the tooling (and therefore collecting the test suite) crashed on Windows with `AttributeError`. Select the base class per platform so the module stays importable everywhere, and reject running the daemon on Windows with a clear error, as it inherently relies on Unix domain sockets. Add a regression test for the platform guard. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> --- src/ethereum_spec_tools/evm_tools/daemon.py | 18 +++++++++++++++++- tests/evm_tools/test_daemon.py | 18 ++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 tests/evm_tools/test_daemon.py diff --git a/src/ethereum_spec_tools/evm_tools/daemon.py b/src/ethereum_spec_tools/evm_tools/daemon.py index 39268f04df2..4ee58fc6872 100644 --- a/src/ethereum_spec_tools/evm_tools/daemon.py +++ b/src/ethereum_spec_tools/evm_tools/daemon.py @@ -6,6 +6,7 @@ import json import os.path import socketserver +import sys import time from http.server import BaseHTTPRequestHandler from io import StringIO, TextIOWrapper @@ -116,7 +117,17 @@ def do_POST(self) -> None: # noqa N802 main(args=args, out_file=out_wrapper, in_file=input) -class _UnixSocketHttpServer(socketserver.UnixStreamServer): +if sys.platform == "win32": + # Windows has no Unix domain sockets, so ``socketserver.UnixStreamServer`` + # is undefined there. The daemon cannot run on Windows, but this module + # must stay importable (``Daemon.run`` rejects the platform explicitly), + # so fall back to a base class that exists everywhere. + _UnixStreamServerBase = socketserver.TCPServer +else: + _UnixStreamServerBase = socketserver.UnixStreamServer + + +class _UnixSocketHttpServer(_UnixStreamServerBase): last_response: float shutdown_timeout: int @@ -179,6 +190,11 @@ def __init__(self, options: argparse.Namespace) -> None: self.timeout = options.timeout def _run(self) -> int: + if sys.platform == "win32": + raise RuntimeError( + "The t8n daemon relies on Unix domain sockets, which are " + "not available on Windows." + ) try: os.remove(self.uds) except IOError: diff --git a/tests/evm_tools/test_daemon.py b/tests/evm_tools/test_daemon.py new file mode 100644 index 00000000000..6d4d08b74c3 --- /dev/null +++ b/tests/evm_tools/test_daemon.py @@ -0,0 +1,18 @@ +"""Test platform handling in the t8n daemon.""" + +import argparse + +import pytest + +from ethereum_spec_tools.evm_tools import daemon +from ethereum_spec_tools.evm_tools.daemon import Daemon + + +def test_daemon_run_rejects_windows( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """`Daemon.run` fails clearly on Windows, which has no Unix sockets.""" + monkeypatch.setattr(daemon.sys, "platform", "win32") + instance = Daemon(argparse.Namespace(uds="daemon.sock", timeout=0)) + with pytest.raises(RuntimeError, match="Unix domain sockets"): + instance.run() From 3d3afa6cce22956c806f5d0178d8dd564ffe21b3 Mon Sep 17 00:00:00 2001 From: Kumarutkarsh9470 <kutkarsh517@gmail.com> Date: Mon, 27 Jul 2026 23:32:07 +0530 Subject: [PATCH 159/233] bug(test-eest): reconfigure output streams to UTF-8 on Windows consoles (#3209) The `eest` commands print Unicode characters (box drawing in `info`, emoji in `clean` and `make`) via `click.echo`. On a Windows console using a legacy code page such as `cp1252`, these characters cannot be encoded and the command aborts with `UnicodeEncodeError`. Reconfigure `sys.stdout`/`sys.stderr` to UTF-8 in the `eest` group callback, guarded so streams that do not support reconfiguration (for example captured output under tests) are left untouched. Add regression tests covering a legacy-encoded stdout. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> --- .../src/execution_testing/cli/eest/cli.py | 24 +++++++++- .../cli/eest/tests/__init__.py | 1 + .../cli/eest/tests/test_cli.py | 47 +++++++++++++++++++ 3 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 packages/testing/src/execution_testing/cli/eest/tests/__init__.py create mode 100644 packages/testing/src/execution_testing/cli/eest/tests/test_cli.py diff --git a/packages/testing/src/execution_testing/cli/eest/cli.py b/packages/testing/src/execution_testing/cli/eest/cli.py index b93469a5cb1..70b5cdc2889 100644 --- a/packages/testing/src/execution_testing/cli/eest/cli.py +++ b/packages/testing/src/execution_testing/cli/eest/cli.py @@ -3,12 +3,34 @@ Invoke using `uv run eest`. """ +import sys + import click from .commands import clean, info from .make.cli import make +def ensure_utf8_output() -> None: + """ + Reconfigure the standard streams to UTF-8 so output cannot crash. + + The `eest` commands print Unicode characters (box drawing, emoji) + that a legacy console code page such as Windows `cp1252` cannot + encode, otherwise raising `UnicodeEncodeError` mid-command. Streams + that do not support reconfiguration (for example when output is + captured in tests) are left untouched. + """ + for stream in (sys.stdout, sys.stderr): + reconfigure = getattr(stream, "reconfigure", None) + if reconfigure is None: + continue + try: + reconfigure(encoding="utf-8") + except (OSError, ValueError): + pass + + @click.group( context_settings={ "help_option_names": ["-h", "--help"], @@ -17,7 +39,7 @@ ) def eest() -> None: """`eest` is a CLI tool that helps with routine tasks.""" - pass + ensure_utf8_output() """ diff --git a/packages/testing/src/execution_testing/cli/eest/tests/__init__.py b/packages/testing/src/execution_testing/cli/eest/tests/__init__.py new file mode 100644 index 00000000000..a3645c910f2 --- /dev/null +++ b/packages/testing/src/execution_testing/cli/eest/tests/__init__.py @@ -0,0 +1 @@ +"""Test cases for the `eest` CLI group.""" diff --git a/packages/testing/src/execution_testing/cli/eest/tests/test_cli.py b/packages/testing/src/execution_testing/cli/eest/tests/test_cli.py new file mode 100644 index 00000000000..90d3ec80412 --- /dev/null +++ b/packages/testing/src/execution_testing/cli/eest/tests/test_cli.py @@ -0,0 +1,47 @@ +"""Tests for the `eest` CLI group.""" + +import io +import sys + +import pytest +from click.testing import CliRunner + +from ..cli import eest, ensure_utf8_output + + +def test_info_runs_successfully() -> None: + """`eest info` exits cleanly and reports the EEST banner.""" + result = CliRunner().invoke(eest, ["info"]) + assert result.exit_code == 0 + assert "EEST" in result.output + + +def test_info_survives_legacy_console_encoding( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """ + `eest info` must not crash on a non-UTF-8 console code page. + + Regression test for the Windows `cp1252` console, whose codec + cannot encode the box-drawing characters printed by the command. + """ + stream = io.TextIOWrapper(io.BytesIO(), encoding="cp1252") + monkeypatch.setattr(sys, "stdout", stream) + + # Without the UTF-8 reconfiguration this raises UnicodeEncodeError. + eest.main(["info"], standalone_mode=False) + + stream.flush() + assert "EEST" in stream.buffer.getvalue().decode("utf-8") + + +def test_ensure_utf8_output_reconfigures_stream( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """`ensure_utf8_output` switches a legacy stream to UTF-8.""" + stream = io.TextIOWrapper(io.BytesIO(), encoding="cp1252") + monkeypatch.setattr(sys, "stdout", stream) + + ensure_utf8_output() + + assert stream.encoding.lower() == "utf-8" From 36fbbabeef91eccc47459e954f12c62f77e44c16 Mon Sep 17 00:00:00 2001 From: cui <cuiweixie@gmail.com> Date: Tue, 28 Jul 2026 06:28:14 +0800 Subject: [PATCH 160/233] fix(test-specs): parenthesize walrus when counting failing txs (#3190) Without parentheses, `:=` binds after `>`, so failing_tx_count became a bool and the multi-failure check never fired. --- packages/testing/src/execution_testing/specs/blockchain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/testing/src/execution_testing/specs/blockchain.py b/packages/testing/src/execution_testing/specs/blockchain.py index 96436819460..72145a35e4f 100644 --- a/packages/testing/src/execution_testing/specs/blockchain.py +++ b/packages/testing/src/execution_testing/specs/blockchain.py @@ -875,7 +875,7 @@ def generate_block_data( ] txs = [tx.with_signature_and_sender() for tx in txs] - if failing_tx_count := len([tx for tx in txs if tx.error]) > 0: + if (failing_tx_count := len([tx for tx in txs if tx.error])) > 0: if failing_tx_count > 1: raise Exception( "test correctness: only one transaction can produce " From 85a36ccae03b0958d9bfb0a6e6d9e08f0e5c79db Mon Sep 17 00:00:00 2001 From: cui <cuiweixie@gmail.com> Date: Tue, 28 Jul 2026 06:41:34 +0800 Subject: [PATCH 161/233] fix(test-specs): use integer division for genesis base fee (#3189) Avoid float64 (53-bit mantissa) precision loss when scaling base_fee_per_gas by 8/7 for values beyond 2^53. --- packages/testing/src/execution_testing/specs/state.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/testing/src/execution_testing/specs/state.py b/packages/testing/src/execution_testing/specs/state.py index ac050f0c9ba..f5c0cea7a19 100644 --- a/packages/testing/src/execution_testing/specs/state.py +++ b/packages/testing/src/execution_testing/specs/state.py @@ -280,7 +280,7 @@ def _generate_blockchain_genesis_environment(self) -> Environment: if self.env.base_fee_per_gas: # Calculate genesis base fee per gas from state test's block#1 env kwargs["base_fee_per_gas"] = HexNumber( - int(int(str(self.env.base_fee_per_gas), 0) * 8 / 7) + int(str(self.env.base_fee_per_gas), 0) * 8 // 7 ) if self.env.excess_blob_gas: From c69d54ba274a222412a0ee897cccf1af6a4e8671 Mon Sep 17 00:00:00 2001 From: Mario Vega <marioevz@gmail.com> Date: Tue, 28 Jul 2026 10:59:44 +0200 Subject: [PATCH 162/233] fix(test-ci): Skip `test_cli.py` until #3241 is resolved (#3242) --- .../testing/src/execution_testing/cli/eest/tests/test_cli.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/testing/src/execution_testing/cli/eest/tests/test_cli.py b/packages/testing/src/execution_testing/cli/eest/tests/test_cli.py index 90d3ec80412..22dee2deb9a 100644 --- a/packages/testing/src/execution_testing/cli/eest/tests/test_cli.py +++ b/packages/testing/src/execution_testing/cli/eest/tests/test_cli.py @@ -8,6 +8,10 @@ from ..cli import eest, ensure_utf8_output +pytestmark = pytest.mark.skip( + "Issue #3241: eest info queries github.com to get release information" +) + def test_info_runs_successfully() -> None: """`eest info` exits cleanly and reports the EEST banner.""" From 608f8783af569bd2833e90c42eb617439045412d Mon Sep 17 00:00:00 2001 From: Jochem Brouwer <jochembrouwer96@gmail.com> Date: Tue, 28 Jul 2026 16:32:50 +0200 Subject: [PATCH 163/233] feat(tests): add EIP-7997 case where factory is not present at fork block (#3243) --- .../test_fork_transition.py | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/tests/amsterdam/eip7997_deterministic_factory_predeploy/test_fork_transition.py b/tests/amsterdam/eip7997_deterministic_factory_predeploy/test_fork_transition.py index b124a0c5884..8f3d20b983b 100644 --- a/tests/amsterdam/eip7997_deterministic_factory_predeploy/test_fork_transition.py +++ b/tests/amsterdam/eip7997_deterministic_factory_predeploy/test_fork_transition.py @@ -104,3 +104,71 @@ def test_factory_deploys_across_transition( ), }, ) + + +@pytest.mark.valid_at_transition_to("Amsterdam") +@pytest.mark.pre_alloc_mutable +def test_factory_absent_across_transition( + blockchain_test: BlockchainTestFiller, + pre: Alloc, +) -> None: + """ + A chain that never deployed the factory transitions to Amsterdam + through valid blocks, and the factory address stays nonexistent. + + The client MUST NOT check for the existence of the contract at + the fork boundary. Therefore, we verify that the BAL does + not contain the factory account read. + The block itself is valid. It is the responsibility of the + chain activating EIP-7997 to ensure the factory is valid + at the start of the fork block. + """ + factory = Address(Spec.FACTORY_ADDRESS) + # Merging an all-zero account into the fork's pre-allocation removes + # the factory predeploy from the genesis allocation entirely. + pre[factory] = Account(nonce=0, balance=0, code=b"") + + sender = pre.fund_eoa() + receiver = pre.fund_eoa(amount=0) + transfer_value = 1 + + timestamps = [FORK_TIMESTAMP - 1, FORK_TIMESTAMP, FORK_TIMESTAMP + 1] + + blocks = [] + for i, timestamp in enumerate(timestamps): + blocks.append( + Block( + timestamp=timestamp, + txs=[ + Transaction( + sender=sender, + to=receiver, + value=transfer_value, + ) + ], + expected_block_access_list=BlockAccessListExpectation( + account_expectations={ + factory: None, + sender: BalAccountExpectation( + nonce_changes=[ + BalNonceChange( + block_access_index=1, + post_nonce=i + 1, + ) + ], + ), + } + ) + if timestamp >= FORK_TIMESTAMP + else None, + ) + ) + + blockchain_test( + pre=pre, + blocks=blocks, + post={ + factory: Account.NONEXISTENT, + receiver: Account(balance=len(timestamps) * transfer_value), + }, + ) From 3850ce51d490d7e4a37a517b16a8e16f40ee0bf4 Mon Sep 17 00:00:00 2001 From: Mario Vega <marioevz@gmail.com> Date: Tue, 28 Jul 2026 09:22:35 -0600 Subject: [PATCH 164/233] refactor(tests): Remove `fork.gas_costs()` reconstruction from Amsterdam EIP-8037/8038 tests (#3169) Co-authored-by: spencer-tb <spencer.tb@ethereum.org> --- .claude/commands/write-test.md | 16 +- docs/writing_tests/fork_methods.md | 9 +- docs/writing_tests/opcode_metadata.md | 15 + .../src/execution_testing/forks/base_fork.py | 14 + .../test_block_2d_gas_accounting.py | 15 +- .../test_state_gas_call.py | 329 ++++++++------- .../test_state_gas_calldata_floor.py | 42 +- .../test_state_gas_create.py | 376 +++++++----------- .../test_state_gas_delegation_pointer.py | 18 +- .../test_state_gas_multi_block.py | 30 +- .../test_state_gas_ordering.py | 100 ++--- .../test_state_gas_pricing.py | 22 +- .../test_state_gas_reservoir.py | 15 +- .../test_state_gas_selfdestruct.py | 68 ++-- .../test_state_gas_set_code.py | 1 - .../test_state_gas_sstore.py | 47 +-- .../test_access_list_gas.py | 56 +-- .../test_call_gas.py | 156 +++----- .../test_create_gas.py | 67 +--- .../test_eip_mainnet.py | 2 +- .../test_ext_code_opcodes_gas.py | 40 +- .../test_fork_transition.py | 66 +-- .../test_selfdestruct_gas.py | 69 +--- .../test_set_code_auth_gas.py | 62 ++- .../test_sstore_gas.py | 4 +- .../test_sstore_refunds.py | 25 +- .../test_transient_storage_regression.py | 25 +- 27 files changed, 686 insertions(+), 1003 deletions(-) diff --git a/.claude/commands/write-test.md b/.claude/commands/write-test.md index 3895d81e0ef..bf6e8eee42a 100644 --- a/.claude/commands/write-test.md +++ b/.claude/commands/write-test.md @@ -43,8 +43,20 @@ Conventions and patterns for writing consensus tests. Run this skill before writ ## Fork-Aware Logic - `fork >= Cancun` for conditional behavior based on fork -- `fork.gas_costs()` returns `GasCosts` dataclass with constants like `G_WARM_SLOAD`, `G_COLD_ACCOUNT_ACCESS`, `G_BASE`, etc. -- `fork.transaction_intrinsic_cost_calculator()` for computing tx intrinsic gas +- `fork.fork_at(timestamp=...)` gives the fork active before/after a transition boundary +- For gas amounts, see **Gas Cost Expectations** below — prefer framework cost constructs over reading `fork.gas_costs()` constants directly + +## Gas Cost Expectations + +Never hand-reconstruct a gas amount by summing `fork.gas_costs()` constants (`NEW_ACCOUNT`, `CALL_VALUE`, `COLD_STORAGE_WRITE`, `VERY_LOW`, ...). Re-deriving the schedule duplicates the framework's own calculation and silently breaks when a future fork reprices. Instead: + +- **Read the cost off the bytecode under test.** Set the relevant opcode metadata (`account_new`, `value_transfer`, `address_warm`, `key_warm`/`original_value`/`current_value`/`new_value`, `init_code_size`, `code_deposit_size`, `new_memory_size`, ...) and use `bytecode.gas_cost(fork)` (regular + state), `.regular_cost(fork)`, `.state_cost(fork)`, or `.refund(fork)`. Link the exact opcode to the behavior — e.g. `Op.SELFDESTRUCT(account_new=True).state_cost(fork)`. +- **Transaction-level costs:** `fork.transaction_intrinsic_cost_calculator()`; `fork.transaction_top_frame_state_gas(contract_creation=True)` for the created account's `NEW_ACCOUNT` (under EIP-2780 it is NOT part of the intrinsic — never subtract it from the intrinsic); `fork.transaction_data_floor_cost_calculator()`; `fork.call_value_stipend()`. +- **A single bare opcode/schedule cost** (e.g. an account-access constant) comes from a metadata-only opcode: `Op.BALANCE.with_metadata(address_warm=False).gas_cost(fork)`. +- **Fork-transition / cross-fork comparisons:** evaluate the same bytecode or intrinsic at each fork (`before = fork.fork_at(timestamp=...)`, `after = ...`) and compare `before` vs `after` costs — do not compare raw schedule constants. +- **Do not add "self-check" asserts** that compare a framework-computed value against a `fork.gas_costs()` decomposition of the same fork; they add no coverage over the runtime behavior the test already exercises and only break on repricing. +- **If the framework cannot express a cost, fix the framework** (wire the opcode into its gas/state map, add an accessor) rather than reconstructing it in the test. If the use case does not support the framework, the framework needs an update. +- **Exception:** a test whose *subject* is a specific schedule value (e.g. a regression that an opcode's cost is unchanged) may compare a runtime measurement (`CodeGasMeasure`) against `fork.gas_costs().OPCODE_*`. Even then, never hardcode the literal value. ## Transactions diff --git a/docs/writing_tests/fork_methods.md b/docs/writing_tests/fork_methods.md index efb4c90d4e3..6f29d6f3a0c 100644 --- a/docs/writing_tests/fork_methods.md +++ b/docs/writing_tests/fork_methods.md @@ -38,11 +38,13 @@ def test_some_feature(fork): ```python def test_transaction_gas(fork, state_test): - gas_cost = fork.gas_costs().GAS_TX_BASE + # Derive the fork's intrinsic gas from the calculator rather than + # summing raw `gas_costs()` constants (see the Gas Parameters warning). + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() # Create a transaction with the correct gas parameters for this fork tx = Transaction( - gas_limit=gas_cost + 10000, + gas_limit=intrinsic_gas + 10000, # ... ) @@ -114,6 +116,9 @@ fork.memory_expansion_gas_calculator() # Returns a callable fork.transaction_intrinsic_cost_calculator() # Returns a callable ``` +!!! warning "Do not reconstruct expected gas from `gas_costs()` constants" + `fork.gas_costs()` exposes the raw schedule for framework internals. When a test needs an *expected* gas amount, derive it from a cost construct that tracks the live schedule (`bytecode.gas_cost(fork)` / `.regular_cost(fork)` / `.state_cost(fork)` / `.refund(fork)`, opcode metadata, the intrinsic/top-frame/data-floor calculators, `fork.call_value_stipend()`) rather than hand-summing constants — hand-built expectations silently break when a fork reprices. See [Opcode Metadata and Gas Calculations](opcode_metadata.md#do-not-hand-reconstruct-gas-from-constants). + ### Transaction Types Methods for determining valid transaction types: diff --git a/docs/writing_tests/opcode_metadata.md b/docs/writing_tests/opcode_metadata.md index e6530e42674..5149fb9ea0b 100644 --- a/docs/writing_tests/opcode_metadata.md +++ b/docs/writing_tests/opcode_metadata.md @@ -9,6 +9,21 @@ The execution testing package provides capabilities to calculate gas costs and r - Validating gas cost calculations for specific opcode scenarios - Future-proofing tests against breaking in upcoming forks that change gas rules +## Do Not Hand-Reconstruct Gas From Constants + +Never build an expected gas amount by summing `fork.gas_costs()` constants (`NEW_ACCOUNT`, `CALL_VALUE`, `COLD_STORAGE_WRITE`, `VERY_LOW`, ...). Re-deriving the schedule by hand duplicates the framework's own calculation and silently breaks when a future fork reprices or restructures a cost. Always derive the expectation from a framework construct that tracks the live schedule: + +- **The bytecode/opcode under test:** set the relevant metadata (see below) and read `bytecode.gas_cost(fork)` (regular + state), `.regular_cost(fork)`, `.state_cost(fork)`, or `.refund(fork)`. Link the exact opcode to the behavior, e.g. `Op.SELFDESTRUCT(account_new=True).state_cost(fork)`. +- **A single bare opcode/schedule cost** comes from a metadata-only opcode: `Op.BALANCE.with_metadata(address_warm=False).gas_cost(fork)` yields the cold account-access cost with no operand pushes. +- **Transaction-level costs:** `fork.transaction_intrinsic_cost_calculator()`, `fork.transaction_top_frame_state_gas(contract_creation=True)` (the created account's new-account state gas — on recent forks it is charged at the top frame, *not* in the intrinsic, so never subtract it from the intrinsic), `fork.transaction_data_floor_cost_calculator()`, and `fork.call_value_stipend()`. +- **Cross-fork / fork-transition comparisons:** evaluate the *same* bytecode or intrinsic at each fork (`before = fork.fork_at(timestamp=...)`, `after = ...`) and compare the resulting costs — do not compare raw schedule constants. + +Additional rules: + +- **Do not add "self-check" assertions** that compare a framework-computed value against a `fork.gas_costs()` decomposition of the same fork. They add no coverage over the runtime behavior the test already exercises and only break on repricing. +- **If the framework cannot express a cost, extend the framework** (wire the opcode into its gas/state map, add an accessor) rather than working around it in the test. +- **Exception:** a test whose *subject* is a specific schedule value — for example a regression asserting that an opcode's cost is unchanged across a fork — may compare a runtime measurement (`CodeGasMeasure`) against the fork's declared `fork.gas_costs().OPCODE_*` value. Even then, never hardcode the literal number. + ## Opcode Metadata Many opcodes accept metadata parameters that affect their gas cost calculations. Metadata represents runtime state information that influences gas consumption. diff --git a/packages/testing/src/execution_testing/forks/base_fork.py b/packages/testing/src/execution_testing/forks/base_fork.py index 911867deec8..f24bc228d77 100644 --- a/packages/testing/src/execution_testing/forks/base_fork.py +++ b/packages/testing/src/execution_testing/forks/base_fork.py @@ -826,6 +826,20 @@ def transaction_top_frame_state_gas( del contract_creation, sends_value, recipient_type, authorizations return 0 + @classmethod + def call_value_stipend(cls) -> int: + """ + Return the gas stipend forwarded to the callee of a value-bearing + CALL/CALLCODE. + + The stipend is added to the child frame's gas and returned to the + caller when the callee does not consume it, so tests that pin + value-call gas at an exact boundary subtract it from the charged + total. Exposed as a named accessor so tests need not read + ``gas_costs().CALL_STIPEND`` directly. + """ + return cls.gas_costs().CALL_STIPEND + @classmethod def system_call_gas_limit(cls) -> int: """ diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py index 42775006b8c..662af4980f2 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py @@ -368,24 +368,27 @@ def test_block_gas_used_call_new_account( GAS_NEW_ACCOUNT state gas) then SSTORE. Combined with a STOP tx, the 2D max must reflect state gas from account creation. """ - new_account_state_gas = fork.gas_costs().NEW_ACCOUNT sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) target = pre.fund_eoa(amount=0) + call = Op.CALL( + gas=100_000, + address=target, + value=1, + value_transfer=True, + account_new=True, + ) parent_storage = Storage() parent = pre.deploy_contract( - code=( - Op.CALL(gas=100_000, address=target, value=1) - + Op.SSTORE(parent_storage.store_next(1), 1) - ), + code=(call + Op.SSTORE(parent_storage.store_next(1), 1)), balance=10**18, ) txs = [ Transaction( to=parent, - state_gas_reservoir=new_account_state_gas + sstore_state_gas, + state_gas_reservoir=call.state_cost(fork) + sstore_state_gas, sender=pre.fund_eoa(), ), ] + stop_txs(pre, fork, 1) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py index 62f3a91eeca..170217fa7cf 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py @@ -433,26 +433,36 @@ def test_call_value_transfer_new_account( A CALL that transfers value to a non-existent account creates a new account, charging new-account state gas of state gas. """ - gas_costs = fork.gas_costs() - new_account_state_gas = gas_costs.NEW_ACCOUNT - # Target address that doesn't exist in pre-state - target = 0xDEAD + target = pre.nonexistent_account() parent_storage = Storage() - parent = pre.deploy_contract( - code=( - Op.SSTORE( - parent_storage.store_next(1), - Op.CALL(gas=100_000, address=target, value=1), - ) + # Capture the CALL result in a pre-existing slot (2 -> 1) so the + # instrumentation SSTORE modifies rather than creates a key and + # adds no state gas; the reservoir then covers exactly the CALL's + # new-account charge. + slot = parent_storage.store_next(1) + parent_code = Op.SSTORE( + slot, + Op.CALL( + gas=100_000, + address=target, + value=1, + value_transfer=True, + account_new=True, ), - balance=1, + original_value=2, + current_value=2, + new_value=1, + key_warm=False, + ) + parent = pre.deploy_contract( + code=parent_code, balance=1, storage={slot: 2} ) tx = Transaction( to=parent, - state_gas_reservoir=new_account_state_gas, + state_gas_reservoir=parent_code.state_cost(fork), sender=pre.fund_eoa(), ) @@ -803,7 +813,6 @@ def test_call_pre_charged_costs_excluded_from_forwarding( pre-charged costs (access gas, memory expansion, or both) causes the child to OOG and the SSTORE to revert. """ - gas_costs = fork.gas_costs() sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) # Child: SSTORE(0, 1) as proof of execution @@ -817,9 +826,9 @@ def test_call_pre_charged_costs_excluded_from_forwarding( ret_size = 512 * 32 # 512 words memory_cost = fork.memory_expansion_gas_calculator()(new_bytes=ret_size) - extra_gas = gas_costs.COLD_ACCOUNT_ACCESS # cold call, value=0 - - # Wrapper: CALL child requesting max gas with memory expansion + # Wrapper: CALL child requesting max gas with memory expansion. The + # memory metadata makes `wrapper_code.regular_cost(fork)` fold the + # cold access, the 7 argument pushes and the memory expansion. wrapper_code = Op.CALL( gas=0xFFFFFFFF, address=child, @@ -828,17 +837,16 @@ def test_call_pre_charged_costs_excluded_from_forwarding( args_size=0, ret_offset=0, ret_size=ret_size, + new_memory_size=ret_size, ) wrapper = pre.deploy_contract(wrapper_code) - wrapper_pushes = 7 * gas_costs.VERY_LOW # 7 CALL args - - # After the pre-charge of extra_gas + memory_cost, the wrapper has - # gas_remaining left. The 63/64 rule should forward - # gas_remaining * 63/64 to the child — just enough for its SSTORE. + # After the up-front pre-charge, the wrapper has gas_remaining left. + # The 63/64 rule should forward gas_remaining * 63/64 to the child — + # just enough for its SSTORE. gas_remaining = child_regular_gas * 64 // 63 + memory_cost // 2 - wrapper_gas = wrapper_pushes + extra_gas + memory_cost + gas_remaining + wrapper_gas = wrapper_code.regular_cost(fork) + gas_remaining caller = pre.deploy_contract( Op.POP(Op.CALL(gas=wrapper_gas, address=wrapper)) @@ -871,25 +879,35 @@ def test_call_new_account_header_gas_used( GAS_NEW_ACCOUNT state gas. The block must be accepted with correct 2D max(regular, state) accounting in the header. """ - gas_costs = fork.gas_costs() - new_account_state_gas = gas_costs.NEW_ACCOUNT - target = pre.fund_eoa(amount=0) storage = Storage() - contract = pre.deploy_contract( - code=( - Op.SSTORE( - storage.store_next(1, "call_succeeds"), - Op.CALL(gas=100_000, address=target, value=1), - ) + # Capture the CALL result in a pre-existing slot (2 -> 1) so the + # instrumentation SSTORE modifies rather than creates a key and + # adds no state gas; the reservoir then covers exactly the CALL's + # new-account charge. + slot = storage.store_next(1, "call_succeeds") + contract_code = Op.SSTORE( + slot, + Op.CALL( + gas=100_000, + address=target, + value=1, + value_transfer=True, + account_new=True, ), - balance=1, + original_value=2, + current_value=2, + new_value=1, + key_warm=False, + ) + contract = pre.deploy_contract( + code=contract_code, balance=1, storage={slot: 2} ) tx = Transaction( to=contract, - state_gas_reservoir=new_account_state_gas, + state_gas_reservoir=contract_code.state_cost(fork), sender=pre.fund_eoa(), ) @@ -929,34 +947,29 @@ def test_call_value_to_self_destructed_same_tx_account( the no charge behavior lives in `test_call_value_to_self_destructed_header_gas_used`. """ - new_account_state_gas = fork.gas_costs().NEW_ACCOUNT - sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) - inner_code = Op.SELFDESTRUCT(Op.ADDRESS) mstore_value, size = init_code_at_high_bytes(inner_code) storage = Storage() - orchestrator = pre.deploy_contract( - code=( - Op.MSTORE(0, mstore_value) - + ( - Op.CREATE2(1, 0, size, 0) - if create_opcode == Op.CREATE2 - else Op.CREATE(1, 0, size) - ) - + Op.MSTORE(0x20, Op.DUP1) - + Op.POP - + Op.SSTORE( - storage.store_next(1, "call_succeeds"), - Op.CALL(gas=Op.GAS, address=Op.MLOAD(0x20), value=1), - ) - ), - balance=3, + orchestrator_code = ( + Op.MSTORE(0, mstore_value) + + ( + Op.CREATE2(1, 0, size, 0) + if create_opcode == Op.CREATE2 + else Op.CREATE(1, 0, size) + ) + + Op.MSTORE(0x20, Op.DUP1) + + Op.POP + + Op.SSTORE( + storage.store_next(1, "call_succeeds"), + Op.CALL(gas=Op.GAS, address=Op.MLOAD(0x20), value=1), + ) ) + orchestrator = pre.deploy_contract(code=orchestrator_code, balance=3) tx = Transaction( to=orchestrator, - state_gas_reservoir=new_account_state_gas + sstore_state_gas, + state_gas_reservoir=orchestrator_code.state_cost(fork), sender=pre.fund_eoa(), ) @@ -998,8 +1011,6 @@ def test_call_value_to_self_destructed_header_gas_used( targeted itself or an external beneficiary, so the no charge behavior holds across both cases. """ - new_account_state_gas = fork.gas_costs().NEW_ACCOUNT - if selfdestruct_beneficiary == "self": inner_code = Op.SELFDESTRUCT(Op.ADDRESS) else: @@ -1009,24 +1020,22 @@ def test_call_value_to_self_destructed_header_gas_used( inner_code = Op.SELFDESTRUCT(alive_beneficiary) mstore_value, size = init_code_at_high_bytes(inner_code) - orchestrator = pre.deploy_contract( - code=( - Op.MSTORE(0, mstore_value) - + ( - Op.CREATE2(1, 0, size, 0) - if create_opcode == Op.CREATE2 - else Op.CREATE(1, 0, size) - ) - + Op.MSTORE(0x20, Op.DUP1) - + Op.POP - + Op.POP(Op.CALL(gas=Op.GAS, address=Op.MLOAD(0x20), value=1)) - ), - balance=3, + orchestrator_code = ( + Op.MSTORE(0, mstore_value) + + ( + Op.CREATE2(1, 0, size, 0) + if create_opcode == Op.CREATE2 + else Op.CREATE(1, 0, size) + ) + + Op.MSTORE(0x20, Op.DUP1) + + Op.POP + + Op.POP(Op.CALL(gas=Op.GAS, address=Op.MLOAD(0x20), value=1)) ) + orchestrator = pre.deploy_contract(code=orchestrator_code, balance=3) tx = Transaction( to=orchestrator, - state_gas_reservoir=new_account_state_gas, + state_gas_reservoir=orchestrator_code.state_cost(fork), sender=pre.fund_eoa(), ) @@ -1069,31 +1078,29 @@ def test_call_value_to_self_destructed_burns_value( address. At the end of the transaction the account is removed and the accumulated balance is lost. """ - new_account_state_gas = fork.gas_costs().NEW_ACCOUNT - inner_code = Op.SELFDESTRUCT(Op.ADDRESS) mstore_value, size = init_code_at_high_bytes(inner_code) initial_balance = 2 * call_value - orchestrator = pre.deploy_contract( - code=( - Op.MSTORE(0, mstore_value) - + ( - Op.CREATE2(call_value, 0, size, 0) - if create_opcode == Op.CREATE2 - else Op.CREATE(call_value, 0, size) - ) - + Op.MSTORE(0x20, Op.DUP1) - + Op.POP - + Op.POP( - Op.CALL( - gas=Op.GAS, - address=Op.MLOAD(0x20), - value=call_value, - ) + orchestrator_code = ( + Op.MSTORE(0, mstore_value) + + ( + Op.CREATE2(call_value, 0, size, 0) + if create_opcode == Op.CREATE2 + else Op.CREATE(call_value, 0, size) + ) + + Op.MSTORE(0x20, Op.DUP1) + + Op.POP + + Op.POP( + Op.CALL( + gas=Op.GAS, + address=Op.MLOAD(0x20), + value=call_value, ) - ), - balance=initial_balance, + ) + ) + orchestrator = pre.deploy_contract( + code=orchestrator_code, balance=initial_balance ) created_address = compute_create_address( address=orchestrator, @@ -1105,7 +1112,7 @@ def test_call_value_to_self_destructed_burns_value( tx = Transaction( to=orchestrator, - state_gas_reservoir=new_account_state_gas, + state_gas_reservoir=orchestrator_code.state_cost(fork), sender=pre.fund_eoa(), ) @@ -1147,29 +1154,25 @@ def test_call_zero_value_to_self_destructed_same_tx_account( value CALL (value gate broken) would double the state gas component. """ - new_account_state_gas = fork.gas_costs().NEW_ACCOUNT - inner_code = Op.SELFDESTRUCT(Op.ADDRESS) mstore_value, size = init_code_at_high_bytes(inner_code) - orchestrator = pre.deploy_contract( - code=( - Op.MSTORE(0, mstore_value) - + ( - Op.CREATE2(1, 0, size, 0) - if create_opcode == Op.CREATE2 - else Op.CREATE(1, 0, size) - ) - + Op.MSTORE(0x20, Op.DUP1) - + Op.POP - + Op.POP(Op.CALL(gas=Op.GAS, address=Op.MLOAD(0x20), value=0)) - ), - balance=3, + orchestrator_code = ( + Op.MSTORE(0, mstore_value) + + ( + Op.CREATE2(1, 0, size, 0) + if create_opcode == Op.CREATE2 + else Op.CREATE(1, 0, size) + ) + + Op.MSTORE(0x20, Op.DUP1) + + Op.POP + + Op.POP(Op.CALL(gas=Op.GAS, address=Op.MLOAD(0x20), value=0)) ) + orchestrator = pre.deploy_contract(code=orchestrator_code, balance=3) tx = Transaction( to=orchestrator, - state_gas_reservoir=new_account_state_gas, + state_gas_reservoir=orchestrator_code.state_cost(fork), sender=pre.fund_eoa(), ) @@ -1455,12 +1458,20 @@ def test_call_new_account_no_regular_account_creation_cost( Verify CALL with value to a non-existent account does not charge a regular account-creation cost on top of state gas. """ - gas_costs = fork.gas_costs() - new_account_state_gas = gas_costs.NEW_ACCOUNT - target = pre.fund_eoa(amount=0) - caller_code = Op.POP(Op.CALL(gas=0, address=target, value=1)) + Op.STOP + caller_code = ( + Op.POP( + Op.CALL( + gas=0, + address=target, + value=1, + value_transfer=True, + account_new=True, + ) + ) + + Op.STOP + ) caller = pre.deploy_contract(code=caller_code, balance=1) # Tight budget: slack is less than the old pre-Amsterdam regular @@ -1468,13 +1479,7 @@ def test_call_new_account_no_regular_account_creation_cost( intrinsic = fork.transaction_intrinsic_cost_calculator()() tx = Transaction( to=caller, - gas_limit=( - intrinsic - + caller_code.gas_cost(fork) - + gas_costs.CALL_VALUE - + new_account_state_gas - + 20_000 - ), + gas_limit=(intrinsic + caller_code.gas_cost(fork) + 20_000), sender=pre.fund_eoa(), ) @@ -1498,20 +1503,26 @@ def test_call_new_account_state_gas_boundary( materialized; one gas short the caller frame goes out of gas, so nothing is created and the value transfer is rolled back. """ - gas_costs = fork.gas_costs() - target = 0xDEAD - caller_code = Op.CALL(gas=0, address=target, value=1) + Op.STOP + target = pre.nonexistent_account() + caller_code = ( + Op.CALL( + gas=0, + address=target, + value=1, + value_transfer=True, + account_new=True, + ) + + Op.STOP + ) caller = pre.deploy_contract(code=caller_code, balance=1) exact_fit = ( fork.transaction_intrinsic_cost_calculator()() + caller_code.gas_cost(fork) - + gas_costs.CALL_VALUE - + gas_costs.NEW_ACCOUNT ) post: dict if gas_delta == 0: - gas_used = exact_fit - gas_costs.CALL_STIPEND + gas_used = exact_fit - fork.call_value_stipend() post = {target: Account(balance=1), caller: Account(balance=0)} else: gas_used = exact_fit + gas_delta @@ -1555,7 +1566,6 @@ def test_child_failure_refunds_state_gas_to_reservoir_not_gas_left( tight regular stipend. Covers SSTORE and CALL-value (new account) state-gas charge paths. """ - gas_costs = fork.gas_costs() sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) probe_storage = Storage() @@ -1564,14 +1574,19 @@ def test_child_failure_refunds_state_gas_to_reservoir_not_gas_left( if charge_via == "sstore": child_code: Bytecode = Op.SSTORE(0, 1) + Op.REVERT(0, 0) child_balance = 0 - child_state_charge = sstore_state_gas else: fresh_target = pre.fund_eoa(amount=0) child_code = Op.POP( - Op.CALL(gas=Op.GAS, address=fresh_target, value=1) + Op.CALL( + gas=Op.GAS, + address=fresh_target, + value=1, + value_transfer=True, + account_new=True, + ) ) + Op.REVERT(0, 0) child_balance = 1 - child_state_charge = gas_costs.NEW_ACCOUNT + child_state_charge = child_code.state_cost(fork) child = pre.deploy_contract(code=child_code, balance=child_balance) probe = pre.deploy_contract(probe_code) @@ -1620,9 +1635,7 @@ def test_call_insufficient_balance_refunds_new_account_state_gas( Refill NEW_ACCOUNT state gas on a value CALL that fails the balance check before the child frame. """ - gas_costs = fork.gas_costs() sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) - new_account_state_gas = gas_costs.NEW_ACCOUNT probe_storage = Storage() probe_code = Op.SSTORE(probe_storage.store_next(1, "probe_ran"), 1) @@ -1632,14 +1645,22 @@ def test_call_insufficient_balance_refunds_new_account_state_gas( non_existent_account = pre.nonexistent_account() + value_call = Op.CALL( + gas=Op.GAS, + address=non_existent_account, + value=1, + value_transfer=True, + account_new=True, + ) parent = pre.deploy_contract( code=( - Op.POP(Op.CALL(gas=Op.GAS, address=non_existent_account, value=1)) + Op.POP(value_call) + Op.POP(Op.CALL(gas=probe_stipend, address=probe)) ), balance=0, ) + new_account_state_gas = value_call.state_cost(fork) assert new_account_state_gas >= sstore_state_gas reservoir = new_account_state_gas @@ -1663,9 +1684,7 @@ def test_call_value_precompile_halt_refunds_new_account_state_gas( Refill NEW_ACCOUNT state gas on a value CALL to an unfunded precompile that halts in the child frame. """ - gas_costs = fork.gas_costs() sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) - new_account_state_gas = gas_costs.NEW_ACCOUNT probe_storage = Storage() probe_code = Op.SSTORE(probe_storage.store_next(1, "probe_ran"), 1) @@ -1675,14 +1694,18 @@ def test_call_value_precompile_halt_refunds_new_account_state_gas( ecpairing = 0x08 + value_call = Op.CALL( + 1, ecpairing, 1, 0, 0, 0, 0, value_transfer=True, account_new=True + ) parent = pre.deploy_contract( code=( - Op.POP(Op.CALL(1, ecpairing, 1, 0, 0, 0, 0)) + Op.POP(value_call) + Op.POP(Op.CALL(gas=probe_stipend, address=probe)) ), balance=1, ) + new_account_state_gas = value_call.state_cost(fork) assert new_account_state_gas >= sstore_state_gas reservoir = new_account_state_gas @@ -1734,10 +1757,17 @@ def test_call_value_new_account_state_gas_consumed_on_caller_halt( if target_kind == "precompile" else pre.nonexistent_account() ) - caller = pre.deploy_contract( - code=Op.CALL(gas=0, address=target, value=value) + Op.INVALID, - balance=value, + caller_code = ( + Op.CALL( + gas=0, + address=target, + value=value, + value_transfer=True, + account_new=True, + ) + + Op.INVALID ) + caller = pre.deploy_contract(code=caller_code, balance=value) sender = pre.fund_eoa() gas_limit_cap = fork.transaction_gas_limit_cap() @@ -1745,7 +1775,7 @@ def test_call_value_new_account_state_gas_consumed_on_caller_halt( if reservoir == "over_cap": # The excess over the EIP-7825 cap becomes the reservoir. - gas_limit = gas_limit_cap + fork.gas_costs().NEW_ACCOUNT // 2 + gas_limit = gas_limit_cap + caller_code.state_cost(fork) // 2 expected_gas_used = gas_limit_cap else: gas_limit = 1_000_000 @@ -1787,27 +1817,32 @@ def test_call_value_new_account_state_gas_returned_on_caller_revert( """ value = 1 target = pre.nonexistent_account() - caller_code = Op.CALL(gas=0, address=target, value=value) + Op.REVERT(0, 0) + caller_code = Op.CALL( + gas=0, + address=target, + value=value, + value_transfer=True, + account_new=True, + ) + Op.REVERT(0, 0) caller = pre.deploy_contract(code=caller_code, balance=value) sender = pre.fund_eoa() - gas_costs = fork.gas_costs() - # Only regular execution is billed: the spilled and reservoir-funded parts - # of the NEW_ACCOUNT charge are both refunded, so the cost matches in-cap - # and over-cap. `gas_cost` covers the pushes and cold access; the value - # transfer is added on top and the empty child returns its stipend unused. + # Only regular execution is billed: the spilled and reservoir-funded + # parts of the NEW_ACCOUNT charge are both refunded, so the cost + # matches in-cap and over-cap. `regular_cost` covers the pushes, cold + # access and the value transfer (NEW_ACCOUNT lands in the state + # dimension); the empty child returns its stipend unused. expected_gas_used = ( fork.transaction_intrinsic_cost_calculator()() - + caller_code.gas_cost(fork) - + gas_costs.CALL_VALUE - - gas_costs.CALL_STIPEND + + caller_code.regular_cost(fork) + - fork.call_value_stipend() ) receipt = TransactionReceipt(cumulative_gas_used=expected_gas_used) gas_limit_cap = fork.transaction_gas_limit_cap() assert gas_limit_cap is not None gas_limit = ( - gas_limit_cap + gas_costs.NEW_ACCOUNT // 2 + gas_limit_cap + caller_code.state_cost(fork) // 2 if reservoir == "over_cap" else 1_000_000 ) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_calldata_floor.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_calldata_floor.py index d0c8f7e4ed9..2a58f8e6d55 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_calldata_floor.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_calldata_floor.py @@ -157,33 +157,29 @@ def test_calldata_floor_exceeding_tx_gas_limit_cap( exceeds_cap: one byte more tips the floor over the cap — transaction rejected. """ - gas_costs = fork.gas_costs() cap = fork.transaction_gas_limit_cap() assert cap is not None floor_cost = fork.transaction_data_floor_cost_calculator() - floor_token = gas_costs.TX_DATA_TOKEN_FLOOR - # EIP-2780 anchors the floor on the decomposed intrinsic base; the tx - # targets a contract, so the base includes the recipient-access charge. - floor_base = gas_costs.TX_BASE + gas_costs.COLD_ACCOUNT_ACCESS - max_tokens = (cap - floor_base) // floor_token - - if fork.is_eip_enabled(7976): - # EIP-7976: all bytes contribute 4 floor tokens regardless of - # value, so the token count is len(data) * 4. - tokens_per_byte = 4 - max_bytes = max_tokens // tokens_per_byte - if exceeds_cap: - max_bytes += 1 - calldata = b"\x01" * max_bytes - else: - # EIP-7623: non-zero bytes contribute 4 tokens, zero bytes 1. - tokens_per_nonzero = 4 - nonzero_bytes = max_tokens // tokens_per_nonzero - zero_bytes = max_tokens - nonzero_bytes * tokens_per_nonzero - if exceeds_cap: - zero_bytes += 1 - calldata = b"\x01" * nonzero_bytes + b"\x00" * zero_bytes + # Binary-search the largest all-nonzero calldata whose floor cost fits + # within the gas cap; `exceeds_cap` adds one more byte to tip the floor + # over. Driven by the floor calculator directly so it tracks the + # per-byte token pricing across forks. + def floor_fits(num_bytes: int) -> bool: + return floor_cost(data=b"\x01" * num_bytes) <= cap + + high = 1 + while floor_fits(high): + high *= 2 + low = high // 2 + while low < high: + mid = (low + high + 1) // 2 + if floor_fits(mid): + low = mid + else: + high = mid - 1 + max_bytes = low + 1 if exceeds_cap else low + calldata = b"\x01" * max_bytes contract = pre.deploy_contract(Op.STOP) floor = floor_cost(data=calldata) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py index b2274b5e5f8..fe8a624fc16 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py @@ -17,7 +17,6 @@ Block, BlockchainTestFiller, Bytecode, - CodeGasMeasure, Fork, Header, Initcode, @@ -97,9 +96,6 @@ def test_create_with_reservoir( Provide gas above TX_MAX_GAS_LIMIT so the new account state gas is drawn from the reservoir rather than gas_left. """ - gas_costs = fork.gas_costs() - create_state_gas = gas_costs.NEW_ACCOUNT - storage = Storage() init_code = Op.STOP @@ -124,7 +120,7 @@ def test_create_with_reservoir( tx = Transaction( to=contract, - state_gas_reservoir=create_state_gas, + state_gas_reservoir=create_call.state_cost(fork), sender=pre.fund_eoa(), ) @@ -266,18 +262,14 @@ def test_code_deposit_state_gas_exact_fit_boundary( ``gas_left`` and burns it all, billing the full ``gas_limit``. The scaling tests assert success only. """ - gas_costs = fork.gas_costs() cap = fork.transaction_gas_limit_cap() assert cap is not None code_size = fork.max_code_size() if funding == "reservoir" else 1000 - words = (code_size + 31) // 32 - memory_gas = gas_costs.MEMORY_PER_WORD * words + words * words // 512 - init_code = Op.RETURN(0, code_size) - init_exec_regular = init_code.regular_cost(fork) + memory_gas - keccak_gas = gas_costs.OPCODE_KECCAK256_PER_WORD * words - deposit_state_gas = fork.code_deposit_state_gas(code_size=code_size) + init_code = Op.RETURN( + 0, code_size, code_deposit_size=code_size, new_memory_size=code_size + ) intrinsic_regular = fork.transaction_intrinsic_cost_calculator()( calldata=bytes(init_code), @@ -285,13 +277,13 @@ def test_code_deposit_state_gas_exact_fit_boundary( return_cost_deducted_prior_execution=True, ) # The fresh target's NEW_ACCOUNT is a top-frame state charge under - # EIP-2780, no longer folded into the intrinsic. + # EIP-2780, no longer folded into the intrinsic. The RETURN metadata + # folds the memory expansion, code-hash keccak and code-deposit state + # gas into `init_code`'s own cost. exact_fit_gas = ( intrinsic_regular - + gas_costs.NEW_ACCOUNT - + init_exec_regular - + keccak_gas - + deposit_state_gas + + fork.transaction_top_frame_state_gas(contract_creation=True) + + init_code.gas_cost(fork) ) if funding == "reservoir": assert exact_fit_gas > cap @@ -469,6 +461,7 @@ def test_create_insufficient_state_gas( returning 0. """ init_code = Op.STOP + create_call = Op.CREATE(0, 0, len(init_code)) storage = Storage() contract = pre.deploy_contract( @@ -480,17 +473,15 @@ def test_create_insufficient_state_gas( ) + Op.SSTORE( storage.store_next(0), # CREATE returns 0 on OOG - Op.CREATE(0, 0, len(init_code)), + create_call, ) ), ) # Tight gas — enough for intrinsic + CREATE regular gas but not # enough for the new account state gas - gas_costs = fork.gas_costs() intrinsic_cost = fork.transaction_intrinsic_cost_calculator() - regular_create_gas = gas_costs.OPCODE_CREATE_BASE - gas_limit = intrinsic_cost() + regular_create_gas + 10_000 + gas_limit = intrinsic_cost() + create_call.regular_cost(fork) + 10_000 tx = Transaction( to=contract, @@ -658,14 +649,17 @@ def test_code_deposit_oog_preserves_parent_reservoir( CREATE proves the reservoir was not inflated by a spill-then-halt refund. """ - gas_costs = fork.gas_costs() - new_account_state_gas = gas_costs.NEW_ACCOUNT sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) # Small deploy size; code deposit state gas will exceed the # limited gas available in the CREATE child frame. deploy_size = 4096 init_code = Op.RETURN(0, deploy_size) + create_call = Op.CREATE( + value=0, + offset=32 - len(init_code), + size=len(init_code), + ) # Limited regular gas forwarded to the factory. After CREATE # takes 63/64, the factory retains ~23 K for its SSTOREs. @@ -677,11 +671,7 @@ def test_code_deposit_oog_preserves_parent_reservoir( Op.MSTORE(0, Op.PUSH32(bytes(init_code))) + Op.SSTORE( factory_storage.store_next(0, "create_fails"), - Op.CREATE( - value=0, - offset=32 - len(init_code), - size=len(init_code), - ), + create_call, ) # Reservoir must be fully preserved after failed CREATE; # parent can still perform its own SSTORE. @@ -702,7 +692,7 @@ def test_code_deposit_oog_preserves_parent_reservoir( # gas_left, which the limited CALL gas cannot cover. tx = Transaction( to=caller, - state_gas_reservoir=new_account_state_gas + sstore_state_gas, + state_gas_reservoir=create_call.state_cost(fork) + sstore_state_gas, sender=pre.fund_eoa(), ) @@ -754,26 +744,34 @@ def test_parent_state_gas_after_child_failure( """ gas_limit_cap = fork.transaction_gas_limit_cap() assert gas_limit_cap is not None - gas_costs = fork.gas_costs() intrinsic_cost = fork.transaction_intrinsic_cost_calculator()() sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) - new_account_state_gas = gas_costs.NEW_ACCOUNT initcode = Op.SSTORE(0, 1, original_value=0, new_value=1) + failure_op + create_call = Op.CREATE( + value=0, + offset=32 - len(initcode), + size=len(initcode), + init_code_size=len(initcode), + ) + factory_storage = Storage() - factory_code = ( - Op.MSTORE(0, Op.PUSH32(bytes(initcode))) - + Op.SSTORE( - factory_storage.store_next(0, "create_fails"), - Op.CREATE( - value=0, - offset=32 - len(initcode), - size=len(initcode), - ), - original_value=0, - new_value=0, + # Split the factory into the CREATE run (memory setup + CREATE, whose + # result is left on the stack) and the post-CREATE stores, so each + # step's regular gas is read off `.regular_cost(fork)` rather than + # rebuilt from constants. + factory_create_code = ( + Op.MSTORE(0, Op.PUSH32(bytes(initcode)), new_memory_size=32) + + create_call + ) + factory_post_create_code = ( + # Store the CREATE result (0 on failure): a cold 0 -> 0 no-op. + Op.PUSH1(factory_storage.store_next(0, "create_fails")) + + Op.SSTORE.with_metadata(original_value=0, new_value=0)( + unchecked=True ) + # Factory's own cold 0 -> 1 SSTORE. + Op.SSTORE( factory_storage.store_next(1, "post_create"), 1, @@ -781,50 +779,16 @@ def test_parent_state_gas_after_child_failure( new_value=1, ) ) + factory_code = factory_create_code + factory_post_create_code factory = pre.deploy_contract(code=factory_code) + new_account_state_gas = create_call.state_cost(fork) gas_limit = ( gas_limit_cap + new_account_state_gas + sstore_state_gas * 2 if with_reservoir else 5_000_000 ) - # `bytecode.gas_cost(fork)` accounts for opcode base costs and - # state-gas charges, but does NOT track memory-expansion or CREATE - # init-code word costs. Add those back to recover runtime regular - # gas consumption. - init_code_word_count = (len(initcode) + 31) // 32 - init_code_word_cost = gas_costs.CODE_INIT_PER_WORD * init_code_word_count - mstore_memory_expansion = gas_costs.MEMORY_PER_WORD # 1 word - gas_cost_helper_extras = init_code_word_cost + mstore_memory_expansion - - # Factory bytecode shape costs, derived from fork.gas_costs(): - # pre-CREATE: PUSH32 + PUSH1 + MSTORE (with 1-word expansion) - # + 3 PUSHes for CREATE inputs - # post-CREATE: PUSH key + SSTORE (cold no-op: access cost only) - # + 2 PUSHes + SSTORE (cold zero-to-nonzero: - # access + write, the compound COLD_STORAGE_WRITE) - factory_pre_create_regular = ( - gas_costs.VERY_LOW * 2 - + gas_costs.OPCODE_MSTORE_BASE - + mstore_memory_expansion - + gas_costs.VERY_LOW * 3 - ) - factory_post_create_regular = ( - gas_costs.VERY_LOW - + gas_costs.COLD_STORAGE_ACCESS - + gas_costs.VERY_LOW * 2 - + gas_costs.COLD_STORAGE_WRITE - ) - - factory_regular = ( - factory_code.gas_cost(fork) - - new_account_state_gas - - sstore_state_gas - + gas_cost_helper_extras - ) - initcode_regular_revert = initcode.gas_cost(fork) - sstore_state_gas - if failure_op == Op.INVALID: # Simulate runtime gas for HALT under EIP-8037 LIFO refills: # 1. Regular pool capped by transaction_gas_limit_cap. The @@ -846,8 +810,9 @@ def test_parent_state_gas_after_child_failure( sim_gas_left = min(regular_budget, execution_gas) sim_state_gas_left = execution_gas - sim_gas_left - sim_gas_left -= factory_pre_create_regular - sim_gas_left -= gas_costs.OPCODE_CREATE_BASE + init_code_word_cost + # Memory setup, the CREATE arg pushes and the CREATE regular + # cost are all consumed before the 63/64 split. + sim_gas_left -= factory_create_code.regular_cost(fork) # CREATE new_account state gas: reservoir first, spill tracked. new_account_from_reservoir = min( @@ -871,7 +836,7 @@ def test_parent_state_gas_after_child_failure( sim_gas_left += new_account_spill sim_state_gas_left += new_account_from_reservoir - sim_gas_left -= factory_post_create_regular + sim_gas_left -= factory_post_create_code.regular_cost(fork) # Factory post-CREATE SSTORE: reservoir first, spill otherwise. if sim_state_gas_left >= sstore_state_gas: @@ -887,8 +852,9 @@ def test_parent_state_gas_after_child_failure( # factory's own post-CREATE SSTORE consumes net state gas. expected_cumulative = ( intrinsic_cost - + factory_regular - + initcode_regular_revert + + factory_create_code.regular_cost(fork) + + factory_post_create_code.regular_cost(fork) + + initcode.regular_cost(fork) + sstore_state_gas ) @@ -922,9 +888,8 @@ def test_nested_create_code_deposit_cannot_borrow_parent_gas( code deposit after init code runs. The CREATE increments the factory nonce but code deposit fails, so no contract is deployed. """ - init_code = Op.RETURN(0, 1) - gas_costs = fork.gas_costs() - code_deposit_state = fork.code_deposit_state_gas(code_size=1) + init_code = Op.RETURN(0, 1, new_memory_size=32) + code_deposit_state = Op.RETURN(0, 1, code_deposit_size=1).state_cost(fork) factory_mstore = Op.MSTORE( 0, Op.PUSH32(bytes(init_code)), new_memory_size=32 @@ -942,7 +907,7 @@ def test_nested_create_code_deposit_cannot_borrow_parent_gas( # Init code child execution: PUSH1 + PUSH1 + RETURN's mem_exp. # Code deposit (keccak + state) is charged AFTER the child returns. - init_cost = 2 * gas_costs.VERY_LOW + gas_costs.MEMORY_PER_WORD + init_cost = init_code.regular_cost(fork) # Target child: enough for init, not enough for code deposit state. target_child = (init_cost + code_deposit_state) // 2 # Invert EIP-150 63/64ths rule: ceil(target_child * 64 / 63). @@ -955,7 +920,7 @@ def test_nested_create_code_deposit_cannot_borrow_parent_gas( intrinsic_cost + factory_mstore.regular_cost(fork) + factory_create.regular_cost(fork) - + gas_costs.NEW_ACCOUNT + + factory_create.state_cost(fork) + factory_remaining ) @@ -1343,7 +1308,6 @@ def test_create_tx_header_gas_used( regular intrinsic and the floor, and fails if a stray NEW_ACCOUNT is charged. """ - gas_costs = fork.gas_costs() initcode = Op.STOP create_state_gas = fork.create_state_gas(code_size=1) @@ -1387,7 +1351,9 @@ def test_create_tx_header_gas_used( else: # For a minimal CREATE tx deploying Op.STOP (1 byte), # state gas (new account) dominates regular gas. - expected_gas_used = gas_costs.NEW_ACCOUNT + expected_gas_used = fork.transaction_top_frame_state_gas( + contract_creation=True + ) blockchain_test( pre=pre, @@ -1587,7 +1553,6 @@ def test_create_silent_failure_refunds_state_gas( balance) refund `GAS_NEW_ACCOUNT` to the reservoir. Block state gas reflects only the probe SSTORE, not the refunded CREATE. """ - gas_costs = fork.gas_costs() sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) intrinsic_cost = fork.transaction_intrinsic_cost_calculator()() @@ -1613,13 +1578,8 @@ def test_create_silent_failure_refunds_state_gas( # CREATE's GAS_NEW_ACCOUNT is refunded (silent failure, no child # spawned). SSTORE's state portion is tracked separately in - # tx_state. - tx_regular = ( - intrinsic_cost - + factory_code.gas_cost(fork) - - gas_costs.NEW_ACCOUNT - - sstore_state_gas - ) + # tx_state, so only the regular dimension remains here. + tx_regular = intrinsic_cost + factory_code.regular_cost(fork) tx_state = sstore_state_gas expected = max(tx_regular, tx_state) blockchain_test( @@ -1657,7 +1617,6 @@ def test_create_child_revert_refunds_state_gas( """ gas_limit_cap = fork.transaction_gas_limit_cap() assert gas_limit_cap is not None - gas_costs = fork.gas_costs() sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) intrinsic_cost = fork.transaction_intrinsic_cost_calculator()() @@ -1695,9 +1654,7 @@ def test_create_child_revert_refunds_state_gas( # incorporate_child_on_error. tx_regular = ( intrinsic_cost - + factory_code.gas_cost(fork) - - gas_costs.NEW_ACCOUNT - - sstore_state_gas + + factory_code.regular_cost(fork) + init_code.gas_cost(fork) ) tx_state = sstore_state_gas @@ -1736,9 +1693,7 @@ def test_create_child_halt_refunds_state_gas( but not enough to spill the state portion, so the probe SSTORE can only succeed via the refunded reservoir. """ - gas_costs = fork.gas_costs() sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) - new_account_state_gas = gas_costs.NEW_ACCOUNT init_code: Op | Bytecode if failure_mode == "initcode_halt": @@ -1772,9 +1727,9 @@ def test_create_child_halt_refunds_state_gas( # regular fits but state gas spillover from `gas_left` under # the old behavior OOGs. pre_sstore_code = Op.MSTORE(0, mstore_value) + Op.POP(create_call) - pre_sstore_regular = pre_sstore_code.gas_cost(fork) - new_account_state_gas + pre_sstore_regular = pre_sstore_code.regular_cost(fork) probe_code = Op.SSTORE(0, 1) - probe_regular = probe_code.gas_cost(fork) - sstore_state_gas + probe_regular = probe_code.regular_cost(fork) target_gas_left = probe_regular + sstore_state_gas // 2 forwarded_gas = target_gas_left * 64 + pre_sstore_regular # Reservoir sized for CREATE charge only — SSTORE must pull @@ -1784,7 +1739,7 @@ def test_create_child_halt_refunds_state_gas( ) tx = Transaction( to=caller, - state_gas_reservoir=new_account_state_gas, + state_gas_reservoir=create_call.state_cost(fork), sender=pre.fund_eoa(), ) @@ -1866,9 +1821,7 @@ def test_create_collision_refunds_state_gas( probe SSTORE can only succeed via the refunded reservoir, not by spilling state gas from `gas_left`. """ - gas_costs = fork.gas_costs() sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) - new_account_state_gas = gas_costs.NEW_ACCOUNT init_code = Op.STOP mstore_value, size = init_code_at_high_bytes(init_code) @@ -1903,9 +1856,9 @@ def test_create_collision_refunds_state_gas( # the probe SSTORE regular fits but state gas spillover from # `gas_left` under the old behavior OOGs. pre_sstore_code = Op.MSTORE(0, mstore_value) + Op.POP(create_call) - pre_sstore_regular = pre_sstore_code.gas_cost(fork) - new_account_state_gas + pre_sstore_regular = pre_sstore_code.regular_cost(fork) probe_code = Op.SSTORE(0, 1) - probe_regular = probe_code.gas_cost(fork) - sstore_state_gas + probe_regular = probe_code.regular_cost(fork) target_gas_left = probe_regular + sstore_state_gas // 2 forwarded_gas = target_gas_left * 64 + pre_sstore_regular # Reservoir sized for CREATE charge only — SSTORE must pull from @@ -1915,7 +1868,7 @@ def test_create_collision_refunds_state_gas( ) tx = Transaction( to=caller, - state_gas_reservoir=new_account_state_gas, + state_gas_reservoir=create_call.state_cost(fork), sender=pre.fund_eoa(), ) @@ -1939,9 +1892,7 @@ def test_create_code_deposit_oog_refunds_state_gas( `gas_left` so the probe SSTORE can only succeed via the refunded reservoir, not by spilling state gas from `gas_left`. """ - gas_costs = fork.gas_costs() sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) - new_account_state_gas = gas_costs.NEW_ACCOUNT max_code_size = fork.max_code_size() # Init code returns (max_code_size + 1) bytes, triggering the @@ -1969,9 +1920,9 @@ def test_create_code_deposit_oog_refunds_state_gas( # discrimination window so SSTORE regular fits but state gas # spillover fails. pre_sstore_code = Op.MSTORE(0, mstore_value) + Op.POP(create_call) - pre_sstore_regular = pre_sstore_code.gas_cost(fork) - new_account_state_gas + pre_sstore_regular = pre_sstore_code.regular_cost(fork) probe_code = Op.SSTORE(0, 1) - probe_regular = probe_code.gas_cost(fork) - sstore_state_gas + probe_regular = probe_code.regular_cost(fork) target_gas_left = probe_regular + sstore_state_gas // 2 forwarded_gas = target_gas_left * 64 + pre_sstore_regular caller = pre.deploy_contract( @@ -1979,7 +1930,7 @@ def test_create_code_deposit_oog_refunds_state_gas( ) tx = Transaction( to=caller, - state_gas_reservoir=new_account_state_gas, + state_gas_reservoir=create_call.state_cost(fork), sender=pre.fund_eoa(), ) @@ -2071,7 +2022,7 @@ def test_create_account_charge_reduces_child_gas( The target is a pre-existing balance-only leaf, the EIP-8037 success-refund path that the old conditional charge skipped. """ - new_account = fork.gas_costs().NEW_ACCOUNT + new_account = create_opcode(account_new=True).state_cost(fork) memory_gas = fork.memory_expansion_gas_calculator() # Factory `gas_left` at the NEW_ACCOUNT charge. Three times @@ -2134,12 +2085,9 @@ def test_create_account_charge_reduces_child_gas( pre.fund_address(create_address, amount=1) # Regular gas the factory spends before the NEW_ACCOUNT charge: the - # initcode setup MSTORE plus the create opcode regular portion - # (`gas_cost` folds NEW_ACCOUNT into the create op, so strip it). + # initcode setup MSTORE plus the create opcode's regular portion. setup = Op.MSTORE(0, mstore_value) - pre_charge_regular = ( - setup.gas_cost(fork) + create_call.gas_cost(fork) - new_account - ) + pre_charge_regular = setup.gas_cost(fork) + create_call.regular_cost(fork) forwarded_gas = gas_at_charge + pre_charge_regular caller = pre.deploy_contract( code=Op.CALL(gas=forwarded_gas, address=factory) @@ -2193,7 +2141,6 @@ def test_failed_create_tx_refills_top_frame_new_account( * HALT (INVALID) refills the spilled ``NEW_ACCOUNT`` to ``gas_left`` and then burns all of it, so the sender pays the full ``gas_limit``. """ - gas_costs = fork.gas_costs() intrinsic_calc = fork.transaction_intrinsic_cost_calculator() intrinsic_regular = intrinsic_calc( @@ -2205,7 +2152,7 @@ def test_failed_create_tx_refills_top_frame_new_account( # regular execution so the initcode runs to completion. gas_limit = ( intrinsic_regular - + gas_costs.NEW_ACCOUNT + + fork.transaction_top_frame_state_gas(contract_creation=True) + init_code.regular_cost(fork) + 1000 ) @@ -2387,9 +2334,7 @@ def test_create_onto_alive_refunds_to_gas_left( pre.fund_address(target, amount=1) gas_limit = ( - fork.transaction_intrinsic_cost_calculator()() - + create.regular_cost(fork) - + fork.gas_costs().NEW_ACCOUNT + fork.transaction_intrinsic_cost_calculator()() + create.gas_cost(fork) ) tx = Transaction(to=contract, gas_limit=gas_limit, sender=pre.fund_eoa()) @@ -2481,9 +2426,6 @@ def test_oversized_initcode_opcode_no_state_gas( initcode = Initcode(deploy_code=Op.STOP, initcode_length=size) initcode_bytes = bytes(initcode) - gas_costs = fork.gas_costs() - create_state_gas = gas_costs.NEW_ACCOUNT - create_call = ( create_opcode( value=0, @@ -2515,7 +2457,7 @@ def test_oversized_initcode_opcode_no_state_gas( sender=pre.fund_eoa(), to=factory, data=initcode_bytes, - state_gas_reservoir=create_state_gas, + state_gas_reservoir=create_call.state_cost(fork), ) post: dict = {factory: Account(storage=storage)} @@ -2547,7 +2489,6 @@ def test_selfdestruct_in_create_tx_initcode( created contract's ``NEW_ACCOUNT`` plus the fresh beneficiary's ``NEW_ACCOUNT`` charged by the SELFDESTRUCT. """ - gas_costs = fork.gas_costs() create_state_gas = fork.create_state_gas(code_size=0) beneficiary = 0xDEAD @@ -2563,10 +2504,10 @@ def test_selfdestruct_in_create_tx_initcode( # State: the created contract's top-frame NEW_ACCOUNT plus the fresh # beneficiary's NEW_ACCOUNT from the SELFDESTRUCT. - expected_state = create_state_gas + gas_costs.NEW_ACCOUNT + expected_state = create_state_gas + initcode.state_cost(fork) initcode_gas = initcode.gas_cost(fork) - gas_limit = intrinsic_regular + gas_costs.NEW_ACCOUNT + initcode_gas + 1000 + gas_limit = intrinsic_regular + create_state_gas + initcode_gas + 1000 tx = Transaction( sender=sender, @@ -2615,17 +2556,15 @@ def test_inner_create_succeeds_code_deposit_state_gas( gas. On success the block state gas is the outer ``NEW_ACCOUNT`` plus the inner account creation and code deposit. """ - gas_costs = fork.gas_costs() outer_state_gas = fork.create_state_gas(code_size=0) - inner_code_deposit = fork.code_deposit_state_gas(code_size=1) - inner_state_gas = gas_costs.NEW_ACCOUNT + inner_code_deposit deploy_code = Op.STOP inner_initcode = Op.MSTORE( 0, int.from_bytes(bytes(deploy_code), "big") << 248, - ) + Op.RETURN(31, 1) + ) + Op.RETURN(31, 1, code_deposit_size=len(deploy_code)) inner_bytes = bytes(inner_initcode) + inner_code_deposit = inner_initcode.state_cost(fork) setup = Op.MSTORE( 0, @@ -2635,6 +2574,8 @@ def test_inner_create_succeeds_code_deposit_state_gas( inner_create = Op.POP(Op.CREATE2(0, 0, len(inner_bytes), 0)) else: inner_create = Op.POP(Op.CREATE(0, 0, len(inner_bytes))) + # Inner account creation plus the inner contract's code deposit. + inner_state_gas = inner_create.state_cost(fork) + inner_code_deposit if outer_outcome == "succeeds": termination = Op.RETURN(0, 0) @@ -2660,7 +2601,7 @@ def test_inner_create_succeeds_code_deposit_state_gas( # the inner code deposit. gas_limit = ( intrinsic_total - + gas_costs.NEW_ACCOUNT + + outer_state_gas + initcode_gas + inner_code_deposit + 1000 @@ -2716,9 +2657,6 @@ def test_nested_create_fail_parent_revert_state_gas( Verify factory nonce is rolled back when the factory reverts after a failed inner CREATE, and preserved when the factory returns. """ - gas_costs = fork.gas_costs() - create_state_gas = gas_costs.NEW_ACCOUNT - if child_failure == "revert": init_code = Op.REVERT(0, 0) else: @@ -2729,6 +2667,7 @@ def test_nested_create_fail_parent_revert_state_gas( if create_opcode == Op.CREATE2 else create_opcode(value=0, offset=0, size=len(init_code)) ) + create_state_gas = create_call.state_cost(fork) factory = pre.deploy_contract( code=( @@ -2838,7 +2777,6 @@ def test_inner_create_fail_refunds_in_creation_tx( Verify failed inner CREATEs inside a creation tx refund state gas so only the outer intrinsic state gas remains. """ - gas_costs = fork.gas_costs() outer_state_gas = fork.create_state_gas(code_size=0) inner_initcode = bytes(Op.REVERT(0, 0)) @@ -2871,10 +2809,11 @@ def test_inner_create_fail_refunds_in_creation_tx( initcode_gas = initcode.gas_cost(fork) per_inner_slack = 2_000 + new_account = create_opcode(account_new=True).state_cost(fork) gas_limit = ( intrinsic_total + initcode_gas - + num_inner_ops * (gas_costs.NEW_ACCOUNT + per_inner_slack) + + num_inner_ops * (new_account + per_inner_slack) ) create_address = compute_create_address(address=sender, nonce=0) @@ -2972,10 +2911,10 @@ def test_create_collision_burned_gas_counted_in_block_regular( @pytest.mark.parametrize( - "target", + "account_new", [ - pytest.param("new", id="new_account"), - pytest.param("existing", id="existing_account"), + pytest.param(True, id="new_account"), + pytest.param(False, id="existing_account"), ], ) @pytest.mark.with_all_create_opcodes() @@ -2985,133 +2924,122 @@ def test_create_account_creation_charge( pre: Alloc, fork: Fork, create_opcode: Op, - target: str, + account_new: bool, ) -> None: """ - Verify NEW_ACCOUNT is charged for a new account and refunded for a - pre-existing balance-only leaf. + Verify NEW_ACCOUNT is charged only when the created account does not + already exist in the trie. Empty init code means zero code deposit, so NEW_ACCOUNT is the only create state cost. A fresh target is charged it; a pre-existing - balance-only target (balance, no code, zero nonce) refunds it on - success. The probe SSTORE both confirms the create succeeded and - makes state gas dominate, so gas_used drops by exactly NEW_ACCOUNT - when refunded. + balance-only target (balance, no code, zero nonce) is not. The probe + SSTORE both confirms the create succeeded and makes state gas dominate + the header, so gas_used differs by exactly NEW_ACCOUNT between the two + cases. """ - new_account = fork.gas_costs().NEW_ACCOUNT - sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) mstore_value, size = init_code_at_high_bytes(Op.STOP) - create_call = ( - create_opcode(value=0, offset=0, size=size, salt=0) - if create_opcode == Op.CREATE2 - else create_opcode(value=0, offset=0, size=size) + create_call = create_opcode( + value=0, offset=0, size=size, account_new=account_new ) - storage = Storage() - factory = pre.deploy_contract( - code=Op.MSTORE(0, mstore_value) - + Op.SSTORE( - storage.store_next(1, "create_succeeds"), Op.GT(create_call, 0) - ) + factory_code = Op.MSTORE(0, mstore_value) + Op.SSTORE( + storage.store_next(1, "create_succeeds"), Op.GT(create_call, 0) ) + factory = pre.deploy_contract(code=factory_code) # Factory deployed via deploy_contract starts at nonce 1. - if create_opcode == Op.CREATE2: - create_address = compute_create2_address( - address=factory, salt=0, initcode=bytes(Op.STOP) - ) - else: - create_address = compute_create_address(address=factory, nonce=1) - if target == "existing": + create_address = compute_create_address( + address=factory, + nonce=1, + salt=0, + initcode=bytes(Op.STOP), + opcode=create_opcode, + ) + if not account_new: pre.fund_address(create_address, amount=1) + # State gas dominates the header, so gas_used equals the factory's + # state cost: NEW_ACCOUNT plus the probe SSTORE for a fresh target, + # just the SSTORE for a pre-existing one. + state_cost = factory_code.state_cost(fork) tx = Transaction( to=factory, - state_gas_reservoir=new_account + sstore_state_gas, + state_gas_reservoir=state_cost, sender=pre.fund_eoa(), ) - # State gas dominates regular: a new account adds NEW_ACCOUNT on top - # of the probe SSTORE, a pre-existing target refunds it. - expected = sstore_state_gas + (new_account if target == "new" else 0) state_test( pre=pre, tx=tx, post={factory: Account(storage=storage)}, - blockchain_test_header_verify=Header(gas_used=expected), + blockchain_test_header_verify=Header(gas_used=state_cost), ) @pytest.mark.with_all_create_opcodes() +@pytest.mark.parametrize( + "sufficient_gas", + [ + pytest.param(True, id="sufficient_gas"), + pytest.param(False, id="insufficient_gas"), + ], +) @pytest.mark.valid_from("EIP8037") -def test_create_refund_credited_against_child_spill( +def test_no_account_charge_on_existing_account( state_test: StateTestFiller, pre: Alloc, fork: Fork, create_opcode: Op, + sufficient_gas: bool, ) -> None: """ - Verify the NEW_ACCOUNT refund routing is visible through GAS. + Verify the create opcode is not charged NEW_ACCOUNT when the target + account already exists in the trie. - The reservoir covers exactly the CREATE NEW_ACCOUNT charge, leaving - none for the child frame, whose initcode SSTOREs then spill more - than NEW_ACCOUNT of state gas from gas_left. The target is alive - (pre-funded), so NEW_ACCOUNT is refunded and credited LIFO against - the incorporated child spill, landing in the parent's gas_left - where GAS (which excludes the reservoir) observes it. + The factory is forwarded exactly the create's regular gas, with no + NEW_ACCOUNT included. Because the target is pre-funded (alive), that + budget is sufficient and the create succeeds, deploying empty code + (created nonce 1). With one gas less it runs out of gas at the + create's upfront charge, before the nonce bump, leaving the target + untouched (nonce 0). The empty reservoir keeps the state-gas + dimension from masking the boundary. """ - gas_costs = fork.gas_costs() - - initcode = Op.SSTORE(0, 1) + Op.SSTORE(1, 1) + Op.STOP - child_spill = initcode.state_cost(fork) - assert child_spill >= gas_costs.NEW_ACCOUNT - - mstore_value, initcode_size = init_code_at_high_bytes(initcode) - create_call = ( - create_opcode( - value=0, - offset=0, - size=initcode_size, - salt=0, - init_code_size=initcode_size, - ) - if create_opcode == Op.CREATE2 - else create_opcode( - value=0, - offset=0, - size=initcode_size, - init_code_size=initcode_size, - ) + factory_code = create_opcode( + value=0, + offset=0, + size=1, # Nothing in memory, equivalent to Op.STOP + # Gas accounting + init_code_size=1, + new_memory_size=1, + account_new=False, ) - factory = pre.deploy_contract( - code=Op.MSTORE(0, mstore_value) - + CodeGasMeasure(code=create_call, extra_stack_items=1), - ) + factory = pre.deploy_contract(code=factory_code) + created = compute_create_address( address=factory, nonce=1, salt=0, - initcode=initcode, + initcode=Op.STOP, opcode=create_opcode, ) pre.fund_address(created, amount=1) - expected_gas = ( - create_call.regular_cost(fork) - + initcode.regular_cost(fork) - + child_spill - - gas_costs.NEW_ACCOUNT # refund credited to gas_left - ) + call_gas = factory_code.gas_cost(fork) + if not sufficient_gas: + call_gas -= 1 + entry_code = Op.CALL(gas=call_gas, address=factory) + entry = pre.deploy_contract(code=entry_code) tx = Transaction( - to=factory, - state_gas_reservoir=gas_costs.NEW_ACCOUNT, + to=entry, + state_gas_reservoir=0, # To allow subcall to run OOG sender=pre.fund_eoa(), ) post = { - factory: Account(storage={0: expected_gas}), - created: Account(nonce=1, balance=1, storage={0: 1, 1: 1}), + created: Account( + nonce=1 if sufficient_gas else 0, balance=1, code=b"" + ), } state_test(pre=pre, post=post, tx=tx) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_delegation_pointer.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_delegation_pointer.py index 4f31f16b85d..406550445af 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_delegation_pointer.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_delegation_pointer.py @@ -121,21 +121,21 @@ def test_delegation_pointer_new_account_state_gas( via a delegation pointer, the new-account state gas is charged identically to a direct call. """ - gas_costs = fork.gas_costs() - new_account_state_gas = gas_costs.NEW_ACCOUNT - target = pre.nonexistent_account() parent_storage = Storage() + call = Op.CALL( + gas=100_000, + address=target, + value=1, + value_transfer=True, + account_new=True, + ) contract = pre.deploy_contract( - code=( - Op.SSTORE( - parent_storage.store_next(1), - Op.CALL(gas=100_000, address=target, value=1), - ) - ), + code=Op.SSTORE(parent_storage.store_next(1), call), balance=1, ) + new_account_state_gas = call.state_cost(fork) # EOA delegates to the contract delegator = pre.fund_eoa(delegation=contract, amount=1) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_multi_block.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_multi_block.py index 04a109b5cd8..fecc4871775 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_multi_block.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_multi_block.py @@ -52,28 +52,20 @@ def test_exact_coinbase_fee_simple_sstore( Motivated by BAL devnet-3 ethrex/besu coinbase balance mismatch where clients diverged on cumulative `receipt_gas_used`. """ - gas_costs = fork.gas_costs() - sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) - - # Gas breakdown for tx 1 (SSTORE zero-to-nonzero, no calldata): - # PUSH1(1) + PUSH1(0) + SSTORE(cold, zero-to-nonzero) + STOP - intrinsic_regular = gas_costs.TX_BASE - if fork.is_eip_enabled(2780): - # EIP-2780 surfaces an explicit recipient-access charge for - # non-self, non-create transactions on top of ``TX_BASE``. - intrinsic_regular += gas_costs.COLD_ACCOUNT_ACCESS - evm_regular = ( - 2 * gas_costs.VERY_LOW # PUSH1 + PUSH1 - + gas_costs.COLD_STORAGE_WRITE # SSTORE cold zero-to-nonzero - ) - tx1_gas_used = intrinsic_regular + evm_regular + sstore_state_gas - expected_coinbase = tx1_gas_used - # Tx 1: single SSTORE zero-to-nonzero sstore_storage = Storage() - sstore_contract = pre.deploy_contract( - code=(Op.SSTORE(sstore_storage.store_next(1), 1)), + sstore_code = Op.SSTORE(sstore_storage.store_next(1), 1, new_value=1) + sstore_state_gas = sstore_code.state_cost(fork) + sstore_contract = pre.deploy_contract(code=sstore_code) + + # tx 1 gas used: the intrinsic (TX_BASE plus the EIP-2780 + # recipient-access charge) plus the SSTORE code's own regular and + # state cost. + tx1_gas_used = ( + fork.transaction_intrinsic_cost_calculator()() + + sstore_code.gas_cost(fork) ) + expected_coinbase = tx1_gas_used # Tx 2: reporter reads BALANCE(COINBASE) into slot 0 reporter = pre.deploy_contract( diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_ordering.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_ordering.py index d834129c84a..b9b1f8c5074 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_ordering.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_ordering.py @@ -43,11 +43,7 @@ def _single_sstore_probe_gas(fork: Fork) -> int: The probe bytecode is Op.SSTORE(0, 1): two pushes + SSTORE. """ - gas_costs = fork.gas_costs() - sstore_regular = gas_costs.COLD_STORAGE_WRITE - sstore_state = Op.SSTORE(new_value=1).state_cost(fork) - push_gas = 2 * gas_costs.VERY_LOW - return push_gas + sstore_regular + sstore_state - 1 + return Op.SSTORE(0, 1).gas_cost(fork) - 1 @pytest.mark.valid_from("EIP8037") @@ -69,7 +65,6 @@ def test_sstore_oog_reservoir_inflation_detection( With wrong ordering (state gas first): reservoir is inflated, probe succeeds. """ - gas_costs = fork.gas_costs() initcode = Initcode(deploy_code=Op.STOP) initcode_len = len(initcode) @@ -105,14 +100,13 @@ def test_sstore_oog_reservoir_inflation_detection( # Compute probe gas: enough for 4 SSTOREs' regular gas + pushes, # but after 4th regular charge, gas_left < the state gas spill. - sstore_regular = gas_costs.COLD_STORAGE_WRITE sstore_state = Op.SSTORE(new_value=1).state_cost(fork) - push_per_sstore = 2 * gas_costs.VERY_LOW + sstore_regular = Op.SSTORE(0, 1).regular_cost(fork) create_state_gas = fork.create_state_gas( code_size=len(initcode.deploy_code) ) spill = 4 * sstore_state - create_state_gas - probe_gas = 4 * (push_per_sstore + sstore_regular) + spill // 2 + probe_gas = 4 * sstore_regular + spill // 2 caller_storage = Storage() caller = pre.deploy_contract( @@ -165,9 +159,6 @@ def test_call_oog_reservoir_inflation_detection( A single-SSTORE probe detects the inflation: with correct reservoir (0) it OOGs; with inflated reservoir it succeeds. """ - gas_costs = fork.gas_costs() - new_account_state_gas = gas_costs.NEW_ACCOUNT - dead_address = 0xDEAD child_code = Op.CALL( gas=0, @@ -177,10 +168,12 @@ def test_call_oog_reservoir_inflation_detection( args_size=0, ret_offset=0, ret_size=0, + value_transfer=True, + account_new=True, ) - pushes_gas = 7 * gas_costs.VERY_LOW - call_regular_gas = gas_costs.COLD_ACCOUNT_ACCESS + gas_costs.CALL_VALUE - child_gas = pushes_gas + call_regular_gas + new_account_state_gas - 1 + # One gas short of the CALL's full cost (regular plus the NEW_ACCOUNT + # state charge), so it OOGs on the account-creation charge. + child_gas = child_code.gas_cost(fork) - 1 child = pre.deploy_contract(child_code) probe = pre.deploy_contract(Op.SSTORE(0, 1)) @@ -221,18 +214,11 @@ def test_selfdestruct_oog_reservoir_inflation_detection( Single-SSTORE probe detects the inflation. """ - gas_costs = fork.gas_costs() - new_account_state_gas = gas_costs.NEW_ACCOUNT - dead_beneficiary = 0xBEEF - child_code = Op.SELFDESTRUCT(dead_beneficiary) - pushes_gas = gas_costs.VERY_LOW - selfdestruct_regular_gas = ( - gas_costs.OPCODE_SELFDESTRUCT_BASE + gas_costs.COLD_ACCOUNT_ACCESS - ) - child_gas = ( - pushes_gas + selfdestruct_regular_gas + new_account_state_gas - 1 - ) + child_code = Op.SELFDESTRUCT(dead_beneficiary, account_new=True) + # One gas short of the SELFDESTRUCT's full cost (regular plus the + # NEW_ACCOUNT state charge), so it OOGs on the account-creation charge. + child_gas = child_code.gas_cost(fork) - 1 child = pre.deploy_contract(child_code, balance=1) probe = pre.deploy_contract(Op.SSTORE(0, 1)) @@ -280,39 +266,32 @@ def test_create_oog_reservoir_inflation_detection( (empty initcode) and `oog_on_init_code_word_cost` (32-byte initcode). """ - gas_costs = fork.gas_costs() - new_account_state_gas = gas_costs.NEW_ACCOUNT - if oog_step == "create_base": initcode_size = 0 - setup_gas = 0 - init_code_word_cost = 0 else: initcode_size = WORD_SIZE - setup_gas = ( - Op.MSTORE.popped_stack_items * gas_costs.VERY_LOW - + gas_costs.OPCODE_MSTORE_BASE - + gas_costs.MEMORY_PER_WORD - ) - init_code_word_cost = gas_costs.CODE_INIT_PER_WORD if create_opcode == Op.CREATE: - create_op = create_opcode(value=0, offset=0, size=initcode_size) + create_op = create_opcode( + value=0, offset=0, size=initcode_size, init_code_size=initcode_size + ) else: create_op = create_opcode( - value=0, offset=0, size=initcode_size, salt=0 + value=0, + offset=0, + size=initcode_size, + salt=0, + init_code_size=initcode_size, ) - pushes_gas = create_opcode.popped_stack_items * gas_costs.VERY_LOW if oog_step == "create_base": child_code = create_op else: - child_code = Op.MSTORE(0, 0) + create_op + child_code = Op.MSTORE(0, 0, new_memory_size=WORD_SIZE) + create_op - create_regular_gas = gas_costs.OPCODE_CREATE_BASE + init_code_word_cost - child_gas = ( - setup_gas + pushes_gas + create_regular_gas + new_account_state_gas - 1 - ) + # One gas short of the CREATE's full cost (regular plus the NEW_ACCOUNT + # state charge), so it OOGs on the account-creation charge. + child_gas = child_code.gas_cost(fork) - 1 child = pre.deploy_contract(child_code) probe = pre.deploy_contract(Op.SSTORE(0, 1)) @@ -358,40 +337,33 @@ def test_create_oog_full_burn_no_state_credit( Verify a CREATE OOG inside a non-creation tx burns the whole tx gas_limit — no state-gas leftover is credited at tx-end. """ - gas_costs = fork.gas_costs() - new_account_state_gas = gas_costs.NEW_ACCOUNT - if oog_step == "create_base": initcode_size = 0 - setup_gas = 0 - init_code_word_cost = 0 else: initcode_size = WORD_SIZE - setup_gas = ( - 2 * gas_costs.VERY_LOW - + gas_costs.OPCODE_MSTORE_BASE - + gas_costs.MEMORY_PER_WORD - ) - init_code_word_cost = gas_costs.CODE_INIT_PER_WORD if create_opcode == Op.CREATE: - create_op = create_opcode(value=0, offset=0, size=initcode_size) + create_op = create_opcode( + value=0, offset=0, size=initcode_size, init_code_size=initcode_size + ) else: create_op = create_opcode( - value=0, offset=0, size=initcode_size, salt=0 + value=0, + offset=0, + size=initcode_size, + salt=0, + init_code_size=initcode_size, ) - pushes_gas = create_opcode.popped_stack_items * gas_costs.VERY_LOW if oog_step == "create_base": factory_code = create_op else: - factory_code = Op.MSTORE(0, 0) + create_op + factory_code = Op.MSTORE(0, 0, new_memory_size=WORD_SIZE) + create_op factory = pre.deploy_contract(factory_code) - create_regular_gas = gas_costs.OPCODE_CREATE_BASE + init_code_word_cost - body_gas = ( - setup_gas + pushes_gas + create_regular_gas + new_account_state_gas - 1 - ) + # One gas short of the CREATE's full cost (regular plus the NEW_ACCOUNT + # state charge), so it OOGs on the account-creation charge. + body_gas = factory_code.gas_cost(fork) - 1 intrinsic_calc = fork.transaction_intrinsic_cost_calculator() tx_gas_limit = intrinsic_calc() + body_gas diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_pricing.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_pricing.py index d52ac0d60b1..99af67b5a2c 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_pricing.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_pricing.py @@ -556,22 +556,21 @@ def test_call_new_account_state_gas_scales_with_cpsb( gas_limit_cap = fork.transaction_gas_limit_cap() assert gas_limit_cap is not None env = Environment(gas_limit=block_gas_limit) - gas_costs = fork.gas_costs() - new_account_state_gas = gas_costs.NEW_ACCOUNT - empty = pre.fund_eoa(0) + call = Op.CALL( + gas=100_000, + address=empty, + value=1, + value_transfer=True, + account_new=True, + ) storage = Storage() contract = pre.deploy_contract( - code=( - Op.SSTORE( - storage.store_next(1, "call_success"), - Op.CALL(gas=100_000, address=empty, value=1), - ) - ), + code=Op.SSTORE(storage.store_next(1, "call_success"), call), balance=1, ) - tx_gas = min(gas_limit_cap + new_account_state_gas, block_gas_limit) + tx_gas = min(gas_limit_cap + call.state_cost(fork), block_gas_limit) tx = Transaction( to=contract, gas_limit=tx_gas, @@ -599,8 +598,7 @@ def test_selfdestruct_new_beneficiary_scales_with_cpsb( gas_limit_cap = fork.transaction_gas_limit_cap() assert gas_limit_cap is not None env = Environment(gas_limit=block_gas_limit) - gas_costs = fork.gas_costs() - new_account_state_gas = gas_costs.NEW_ACCOUNT + new_account_state_gas = Op.SELFDESTRUCT(account_new=True).state_cost(fork) beneficiary = pre.fund_eoa(0) storage = Storage() diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_reservoir.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_reservoir.py index 445cb1d885c..5ac3ba9226c 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_reservoir.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_reservoir.py @@ -174,15 +174,13 @@ def test_insufficient_gas_for_sstore_state_cost( gas, but not enough to also cover the SSTORE state gas. The SSTORE should OOG, leaving storage slot 0 unchanged at zero. """ - gas_costs = fork.gas_costs() - contract = pre.deploy_contract( - code=Op.SSTORE(0, 1), - ) + contract_code = Op.SSTORE(0, 1) + contract = pre.deploy_contract(code=contract_code) # Enough for intrinsic + warm SSTORE regular gas, but not the # state gas cost for zero-to-nonzero transition intrinsic_cost = fork.transaction_intrinsic_cost_calculator() - gas_limit = intrinsic_cost() + gas_costs.COLD_STORAGE_WRITE + gas_limit = intrinsic_cost() + contract_code.regular_cost(fork) tx = Transaction( to=contract, @@ -690,12 +688,13 @@ def test_create_tx_reservoir( beyond TX_MAX_GAS_LIMIT feeds the reservoir. When False, all state gas comes from gas_left (reservoir is zero). """ - gas_costs = fork.gas_costs() gas_limit_cap = fork.transaction_gas_limit_cap() assert gas_limit_cap is not None init_code = Op.STOP - create_state_gas = gas_costs.NEW_ACCOUNT + create_state_gas = fork.transaction_top_frame_state_gas( + contract_creation=True + ) if gas_above_cap: gas_limit = gas_limit_cap + create_state_gas @@ -1247,7 +1246,7 @@ def test_nested_failure_resets_to_tx_reservoir( gas_limit_cap = fork.transaction_gas_limit_cap() assert gas_limit_cap is not None sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) - new_account_state_gas = fork.gas_costs().NEW_ACCOUNT + new_account_state_gas = Op.CREATE(account_new=True).state_cost(fork) intrinsic_cost = fork.transaction_intrinsic_cost_calculator()() body_state_total = sum(b.state_cost(fork) for b in frame_bodies) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_selfdestruct.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_selfdestruct.py index ff0fec86530..f02eaca1336 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_selfdestruct.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_selfdestruct.py @@ -50,7 +50,7 @@ def test_selfdestruct_new_beneficiary_state_gas( spilled into `gas_left` (in-cap tx): the block bills NEW_ACCOUNT in the state dimension and the beneficiary is created. """ - new_account_state_gas = fork.gas_costs().NEW_ACCOUNT + new_account_state_gas = Op.SELFDESTRUCT(account_new=True).state_cost(fork) beneficiary = 0xDEAD contract = pre.deploy_contract( @@ -184,8 +184,7 @@ def test_selfdestruct_new_beneficiary_header_gas_used( beneficiary, charging GAS_NEW_ACCOUNT state gas. The block must be accepted with correct 2D gas accounting in the header. """ - gas_costs = fork.gas_costs() - new_account_state_gas = gas_costs.NEW_ACCOUNT + new_account_state_gas = Op.SELFDESTRUCT(account_new=True).state_cost(fork) beneficiary = pre.fund_eoa(amount=0) @@ -232,7 +231,7 @@ def test_selfdestruct_state_gas_refilled_on_ancestor_revert( transfer remains billed. """ beneficiary = 0xDEAD - inner_code = Op.SELFDESTRUCT(beneficiary) + inner_code = Op.SELFDESTRUCT(beneficiary, account_new=True) inner = pre.deploy_contract(code=inner_code, balance=1) caller_code = Op.POP(Op.CALL(gas=Op.GAS, address=inner)) + Op.REVERT(0, 0) caller = pre.deploy_contract(code=caller_code) @@ -240,8 +239,7 @@ def test_selfdestruct_state_gas_refilled_on_ancestor_revert( expected_regular = ( fork.transaction_intrinsic_cost_calculator()() + caller_code.gas_cost(fork) - + inner_code.gas_cost(fork) - + fork.gas_costs().ACCOUNT_WRITE + + inner_code.regular_cost(fork) ) tx = Transaction(to=caller, sender=pre.fund_eoa()) @@ -271,8 +269,6 @@ def test_create_selfdestruct_no_refund_account_and_storage( num_slots: int, ) -> None: """Verify same tx CREATE+SELFDESTRUCT does not refund state gas.""" - new_account_state_gas = fork.gas_costs().NEW_ACCOUNT - sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() init_code = Bytecode() @@ -299,7 +295,9 @@ def test_create_selfdestruct_no_refund_account_and_storage( factory_code = mstore + Op.POP(create_call) factory = pre.deploy_contract(code=factory_code) - total_state_gas = new_account_state_gas + num_slots * sstore_state_gas + total_state_gas = factory_code.state_cost(fork) + init_code.state_cost( + fork + ) regular_used = ( intrinsic_gas + factory_code.gas_cost(fork) @@ -344,8 +342,6 @@ def test_create_selfdestruct_no_refund_code_deposit_state_gas( state gas. """ assert code_size >= 2 - new_account_state_gas = fork.gas_costs().NEW_ACCOUNT - code_deposit_state_gas = fork.code_deposit_state_gas(code_size=code_size) if beneficiary_type == "self": selfdestruct = Op.SELFDESTRUCT(Op.ADDRESS) @@ -382,7 +378,7 @@ def test_create_selfdestruct_no_refund_code_deposit_state_gas( factory = pre.deploy_contract(code=factory_code) created_address = compute_create_address(address=factory, nonce=1) - total_state_gas = new_account_state_gas + code_deposit_state_gas + total_state_gas = factory_code.state_cost(fork) + initcode.state_cost(fork) tx = Transaction( to=factory, data=bytes(initcode), @@ -407,9 +403,6 @@ def test_create_selfdestruct_code_deposit_no_refund_header_check( Verify block header gas reflects the full account plus code-deposit state-gas charge on a same-tx CREATE+SELFDESTRUCT. """ - gas_costs = fork.gas_costs() - new_account_state_gas = gas_costs.NEW_ACCOUNT - selfdestruct = Op.SELFDESTRUCT(Op.ADDRESS) sd_len = len(bytes(selfdestruct)) code_size = 256 @@ -417,7 +410,6 @@ def test_create_selfdestruct_code_deposit_no_refund_header_check( deployed = bytes(selfdestruct) + b"\x00" * (code_size - sd_len) initcode = Initcode(deploy_code=deployed) initcode_len = len(initcode) - code_deposit_state_gas = fork.code_deposit_state_gas(code_size=code_size) factory_code = Op.CALLDATACOPY( 0, @@ -439,7 +431,7 @@ def test_create_selfdestruct_code_deposit_no_refund_header_check( factory = pre.deploy_contract(code=factory_code) created_address = compute_create_address(address=factory, nonce=1) - total_state_gas = new_account_state_gas + code_deposit_state_gas + total_state_gas = factory_code.state_cost(fork) + initcode.state_cost(fork) tx = Transaction( to=factory, data=bytes(initcode), @@ -472,7 +464,6 @@ def test_create_selfdestruct_sstore_restoration_refund( Verify SSTORE restoration still refunds its slot state gas when the surrounding contract SELFDESTRUCTs. """ - new_account_state_gas = fork.gas_costs().NEW_ACCOUNT sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() @@ -500,6 +491,7 @@ def test_create_selfdestruct_sstore_restoration_refund( factory_code = mstore + Op.POP(create_call) factory = pre.deploy_contract(code=factory_code) + new_account_state_gas = factory_code.state_cost(fork) state_used = new_account_state_gas regular_used = ( intrinsic_gas @@ -595,8 +587,6 @@ def test_selfdestruct_via_delegatecall_chain_no_refund( Verify SELFDESTRUCT in a nested DELEGATECALL/CALLCODE frame below a same-tx-created contract does not refund state gas. """ - new_account_state_gas = fork.gas_costs().NEW_ACCOUNT - sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() # Bottom of the chain does the SELFDESTRUCT; intermediate helpers @@ -627,9 +617,6 @@ def test_selfdestruct_via_delegatecall_chain_no_refund( + Op.STOP ) deployed = bytes(deployed_code) - code_deposit_state_gas = fork.code_deposit_state_gas( - code_size=len(deployed) - ) initcode = Initcode(deploy_code=deployed) initcode_len = len(initcode) @@ -678,18 +665,14 @@ def test_selfdestruct_via_delegatecall_chain_no_refund( factory = pre.deploy_contract(code=factory_code) created_address = compute_create_address(address=factory, nonce=1) - total_state_gas = ( - new_account_state_gas + code_deposit_state_gas + 2 * sstore_state_gas - ) + total_state_gas = factory_code.state_cost(fork) + initcode.state_cost(fork) regular_used = ( intrinsic_gas + factory_code.gas_cost(fork) + initcode.gas_cost(fork) + deployed_code.gas_cost(fork) + chain_regular_gas - - new_account_state_gas - - code_deposit_state_gas - - 2 * sstore_state_gas + - total_state_gas ) expected_gas_used = max(regular_used, total_state_gas) @@ -768,9 +751,8 @@ def test_create_tx_selfdestruct_initcode_state_gas( ) -> None: """ Verify a creation tx whose initcode SELFDESTRUCTs the new contract - still pays the intrinsic NEW_ACCOUNT state gas. + still pays the top-frame NEW_ACCOUNT state gas. """ - new_account_state_gas = fork.gas_costs().NEW_ACCOUNT intrinsic_calc = fork.transaction_intrinsic_cost_calculator() sender = pre.fund_eoa(amount=10**18) @@ -783,30 +765,32 @@ def test_create_tx_selfdestruct_initcode_state_gas( else: beneficiary = pre.fund_eoa(amount=0) + creates_new_beneficiary = beneficiary_kind == "empty" and tx_value > 0 + # `current_target` is added to `accessed_addresses` at message # entry, so SELFDESTRUCT to self skips the cold-access surcharge. if beneficiary_kind == "self": - init_code = Op.SELFDESTRUCT.with_metadata(address_warm=True)( - beneficiary - ) + init_code = Op.SELFDESTRUCT.with_metadata( + address_warm=True, account_new=creates_new_beneficiary + )(beneficiary) else: - init_code = Op.SELFDESTRUCT(beneficiary) - intrinsic_total = intrinsic_calc( + init_code = Op.SELFDESTRUCT.with_metadata( + account_new=creates_new_beneficiary + )(beneficiary) + intrinsic_regular = intrinsic_calc( calldata=bytes(init_code), contract_creation=True ) - intrinsic_regular = intrinsic_total - new_account_state_gas - creates_new_beneficiary = beneficiary_kind == "empty" and tx_value > 0 - expected_state = new_account_state_gas + ( - new_account_state_gas if creates_new_beneficiary else 0 - ) + expected_state = fork.transaction_top_frame_state_gas( + contract_creation=True + ) + init_code.state_cost(fork) expected_regular = intrinsic_regular + init_code.regular_cost(fork) expected_gas_used = max(expected_regular, expected_state) tx = Transaction( to=None, data=init_code, - gas_limit=intrinsic_total + 100_000 + expected_state, + gas_limit=intrinsic_regular + 100_000 + expected_state, sender=sender, value=tx_value, ) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py index f0df65f9569..c537b42088b 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py @@ -2092,7 +2092,6 @@ def test_same_tx_clear_then_reset_pre_delegated( intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( fork, authorization_list ) - assert top_frame_regular == fork.gas_costs().ACCOUNT_WRITE assert top_frame_state == 0 cumulative_gas_used, header_gas_used = _receipt_and_header( intrinsic_regular, top_frame_regular, top_frame_state diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_sstore.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_sstore.py index 653a986f8cc..71dcef99b11 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_sstore.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_sstore.py @@ -436,8 +436,7 @@ def test_sstore_stipend_check_excludes_reservoir( With below_stipend: SSTORE fails (gas_left too low, reservoir ignored). With at_stipend: SSTORE has full regular gas and proceeds. """ - gas_costs = fork.gas_costs() - stipend = gas_costs.CALL_STIPEND + 1 + stipend = fork.call_value_stipend() + 1 sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) # Child: Op.SSTORE(0, 1) = 2 pushes + SSTORE opcode. @@ -446,12 +445,12 @@ def test_sstore_stipend_check_excludes_reservoir( # Full regular gas for the child (pushes + SSTORE regular cost). # State gas comes from the reservoir so it doesn't affect gas_left. - child_full_regular = child_code.gas_cost(fork) - sstore_state_gas + child_full_regular = child_code.regular_cost(fork) # below_stipend: give 1 less than stipend after pushes, fails check. # at_stipend: give full regular gas, passes check and completes. if gas_above_stipend < 0: - push_gas = 2 * gas_costs.VERY_LOW + push_gas = 2 * Op.PUSH1(0).regular_cost(fork) child_gas = push_gas + stipend - 1 else: child_gas = child_full_regular @@ -812,14 +811,8 @@ def test_sstore_restoration_charge_in_ancestor( refund must propagate up the chain to the ancestor that charged the 0 to x. A probe SSTORE sized to OOG by 1 detects any loss. """ - gas_costs = fork.gas_costs() sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) - probe_gas = ( - 2 * gas_costs.VERY_LOW - + gas_costs.COLD_STORAGE_WRITE - + sstore_state_gas - - 1 - ) + probe_gas = Op.SSTORE(0, 1).gas_cost(fork) - 1 # Innermost frame does x to 0; each hop above delegates down. delegate_target = pre.deploy_contract( @@ -885,15 +878,9 @@ def test_sstore_restoration_sub_frame_revert( to OOG by 1 then fails, since its fixed forwarded gas cannot reach the `gas_left` refund. """ - gas_costs = fork.gas_costs() # Probe SSTORE(0, 1): 2 pushes + cold write + state gas - 1. OOGs by # 1 when the reservoir is 0, as forwarded gas misses gas_left. - probe_gas = ( - 2 * gas_costs.VERY_LOW - + gas_costs.COLD_STORAGE_WRITE - + Op.SSTORE(new_value=1).state_cost(fork) - - 1 - ) + probe_gas = Op.SSTORE(0, 1).gas_cost(fork) - 1 child_code = Op.SSTORE(0, 1) + Op.SSTORE(0, 0) + Op.REVERT(0, 0) child = pre.deploy_contract(code=child_code) @@ -940,16 +927,10 @@ def test_sstore_restoration_ancestor_revert( sized to OOG by 1 fails, since its fixed forwarded gas cannot reach the `gas_left` refund. """ - gas_costs = fork.gas_costs() intrinsic_cost = fork.transaction_intrinsic_cost_calculator()() # Probe SSTORE(0, 1): 2 pushes + cold write + state gas - 1. OOGs by # 1 when the reservoir is 0, as forwarded gas misses gas_left. - probe_gas = ( - 2 * gas_costs.VERY_LOW - + gas_costs.COLD_STORAGE_WRITE - + Op.SSTORE(new_value=1).state_cost(fork) - - 1 - ) + probe_gas = Op.SSTORE(0, 1).gas_cost(fork) - 1 set_op = Op.SSTORE.with_metadata( key_warm=False, @@ -1039,17 +1020,11 @@ def test_sstore_restoration_charge_in_ancestor_intermediate_revert( amount must reach the caller via `incorporate_child_on_error`. A probe SSTORE sized to OOG by 1 detects loss. """ - gas_costs = fork.gas_costs() sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) intrinsic_cost = fork.transaction_intrinsic_cost_calculator()() # Probe SSTORE(0, 1): 2 pushes + cold storage write + state gas - 1, # so it OOGs by 1 when the reservoir is 0 and succeeds otherwise. - probe_gas = ( - 2 * gas_costs.VERY_LOW - + gas_costs.COLD_STORAGE_WRITE - + sstore_state_gas - - 1 - ) + probe_gas = Op.SSTORE(0, 1).gas_cost(fork) - 1 inner_code = ( Op.SSTORE.with_metadata( @@ -1137,15 +1112,9 @@ def test_sstore_restoration_create_init_revert( fails, since its fixed forwarded gas cannot reach the `gas_left` refund. """ - gas_costs = fork.gas_costs() # Probe SSTORE(0, 1): 2 pushes + cold write + state gas - 1. OOGs by # 1 when the reservoir is 0, as forwarded gas misses gas_left. - probe_gas = ( - 2 * gas_costs.VERY_LOW - + gas_costs.COLD_STORAGE_WRITE - + Op.SSTORE(new_value=1).state_cost(fork) - - 1 - ) + probe_gas = Op.SSTORE(0, 1).gas_cost(fork) - 1 init_code = Op.SSTORE(0, 1) + Op.SSTORE(0, 0) + Op.REVERT(0, 0) probe = pre.deploy_contract(code=Op.SSTORE(0, 1)) diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_access_list_gas.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_access_list_gas.py index 1337c77b230..d214e83bc21 100644 --- a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_access_list_gas.py +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_access_list_gas.py @@ -41,25 +41,6 @@ pytestmark = pytest.mark.valid_from("Amsterdam") -def _access_list_floor_token_gas( - access_list: List[AccessList], fork: Fork -) -> int: - """ - Return the EIP-7981 calldata-floor-token gas the Amsterdam intrinsic - calculator charges for an access list. - - Every byte of each address (20) and storage key (32) is four floor - tokens, each priced at ``TX_DATA_TOKEN_FLOOR``. Subtracting this from - the measured intrinsic delta isolates the pure EIP-8038 per-entry - surcharge. - """ - total_bytes = 0 - for access in access_list: - total_bytes += len(access.address) - total_bytes += 32 * len(access.storage_keys) - return total_bytes * 4 * fork.gas_costs().TX_DATA_TOKEN_FLOOR - - def _make_access_list( n_addr: int, n_keys_each: int, *, duplicate: bool = False ) -> List[AccessList]: @@ -101,25 +82,7 @@ def test_access_list_intrinsic_surcharge( A simple value-less transaction then exercises the access list end to end. """ - gas_costs = fork.gas_costs() - intrinsic = fork.transaction_intrinsic_cost_calculator() - access_list = _make_access_list(n_addr, n_keys_each, duplicate=duplicate) - n_keys = n_addr * n_keys_each - - base = intrinsic(return_cost_deducted_prior_execution=True) - with_al = intrinsic( - access_list=access_list, - return_cost_deducted_prior_execution=True, - ) - surcharge = ( - with_al - base - _access_list_floor_token_gas(access_list, fork) - ) - expected = ( - n_addr * gas_costs.TX_ACCESS_LIST_ADDRESS - + n_keys * gas_costs.TX_ACCESS_LIST_STORAGE_KEY - ) - assert surcharge == expected contract = pre.deploy_contract(code=Op.STOP) tx = Transaction( @@ -149,8 +112,6 @@ def test_access_list_duplicate_address_key_intrinsic_and_warmth( runtime the slot is nonetheless warm on its first ``SLOAD`` (``WARM_SLOAD``), since warmth is set-membership, not a counter. """ - gas_costs = fork.gas_costs() - intrinsic = fork.transaction_intrinsic_cost_calculator() slot = 0x42 # First runtime SLOAD of the listed slot stores the warm access cost. @@ -175,20 +136,6 @@ def test_access_list_duplicate_address_key_intrinsic_and_warmth( AccessList(address=contract, storage_keys=[slot]), ] - base = intrinsic(return_cost_deducted_prior_execution=True) - with_al = intrinsic( - access_list=access_list, - return_cost_deducted_prior_execution=True, - ) - surcharge = ( - with_al - base - _access_list_floor_token_gas(access_list, fork) - ) - expected_surcharge = ( - 2 * gas_costs.TX_ACCESS_LIST_ADDRESS - + 2 * gas_costs.TX_ACCESS_LIST_STORAGE_KEY - ) - assert surcharge == expected_surcharge - expected_gas = Op.SLOAD(key_warm=True).gas_cost(fork) tx = Transaction( to=contract, @@ -218,8 +165,7 @@ def test_access_list_warms_storage_slot( overwrite of a non-zero original to a new non-zero value pays ``WARM_SLOAD + STORAGE_WRITE``. """ - gas_costs = fork.gas_costs() - very_low = gas_costs.VERY_LOW + very_low = Op.PUSH1(0).regular_cost(fork) slot = 0x42 if op == "SLOAD": diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_call_gas.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_call_gas.py index 37e543f4376..c58fc42934d 100644 --- a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_call_gas.py +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_call_gas.py @@ -89,8 +89,6 @@ def test_call_access_gas( EIP-8038 charges ``COLD_ACCOUNT_ACCESS`` (3,000) cold and ``WARM_ACCESS`` (100) warm for all four call opcodes. """ - gas_costs = fork.gas_costs() - target = pre.deploy_contract(Op.STOP) measured_code = call_opcode(gas=0, address=target) @@ -99,11 +97,8 @@ def test_call_access_gas( pre, fork, measured_code, call_opcode(address_warm=False) ) - expected_gas = ( - gas_costs.WARM_ACCESS if warm else gas_costs.COLD_ACCOUNT_ACCESS - ) - # Cross-check the framework opcode model agrees with the formula. - assert expected_gas == cost_metadata.gas_cost(fork) + # The opcode's own cost is the expected access gas. + expected_gas = cost_metadata.gas_cost(fork) access_list = ( [AccessList(address=target, storage_keys=[])] if warm else None @@ -143,24 +138,14 @@ def test_call_value_alive_target_gas( the caller is ``access + ACCOUNT_WRITE`` while the *charged* schedule is ``access + CALL_VALUE``. Both are asserted. """ - gas_costs = fork.gas_costs() transfers_value = call_opcode in (Op.CALL, Op.CALLCODE) - # Verify the EIP-8038 decomposition of the value-transfer charge. - assert gas_costs.CALL_VALUE == gas_costs.ACCOUNT_WRITE + ( - gas_costs.CALL_STIPEND - ) # The measured-vs-charged duality below hinges on the callee being a - # pure `STOP`: it executes no opcodes, so the forwarded `CALL_STIPEND` - # is wholly unused and returned. Pin that the callee is exactly the - # single zero byte with no gas cost, and that the returned stipend is - # precisely `CALL_VALUE - ACCOUNT_WRITE`. + # pure `STOP`: it executes no opcodes, so the forwarded value-call + # stipend is wholly unused and returned. callee = Op.STOP assert bytes(callee) == b"\x00" assert callee.gas_cost(fork) == 0 - assert gas_costs.CALL_VALUE - gas_costs.ACCOUNT_WRITE == ( - gas_costs.CALL_STIPEND - ) # Alive target with balance so no account creation occurs. target = pre.deploy_contract(callee, balance=1) @@ -191,22 +176,14 @@ def test_call_value_alive_target_gas( pre, fork, measured_code, own_cold, balance=1 ) - access_cost = ( - gas_costs.WARM_ACCESS if warm else gas_costs.COLD_ACCOUNT_ACCESS - ) - # Charged schedule: access + CALL_VALUE (verified via the opcode - # model). CALL gas is wholly regular under EIP-8038 (no state map). - charged_gas = access_cost + ( - gas_costs.CALL_VALUE if transfers_value else 0 - ) - assert charged_gas == cost_metadata.gas_cost(fork) + # CALL gas is wholly regular under EIP-8038 (no state map). assert cost_metadata.state_cost(fork) == 0 - # Consumed gas: the STOP callee returns the forwarded CALL_STIPEND, - # so the caller's measured consumption is access + ACCOUNT_WRITE for - # value transfers, and just access otherwise. - measured_gas = access_cost + ( - gas_costs.ACCOUNT_WRITE if transfers_value else 0 + # Consumed gas: the STOP callee returns the forwarded stipend, so the + # caller's measured consumption is the opcode's charged cost minus the + # stipend for value transfers, and just the access cost otherwise. + measured_gas = cost_metadata.gas_cost(fork) - ( + fork.call_value_stipend() if transfers_value else 0 ) access_list = ( @@ -237,7 +214,6 @@ def test_callcode_value_to_nonexistent_no_new_account( created. The block ``gas_used`` therefore equals the regular tx cost with ``CALL_VALUE`` but with no 183,600 state-gas component. """ - gas_costs = fork.gas_costs() intrinsic = fork.transaction_intrinsic_cost_calculator()() target = 0xDEAD # non-existent @@ -260,24 +236,18 @@ def test_callcode_value_to_nonexistent_no_new_account( caller_code = Op.POP(callcode) + Op.STOP caller = pre.deploy_contract(code=caller_code, balance=1) - # CALLCODE-to-nonexistent regular charge: access + CALL_VALUE, no - # NEW_ACCOUNT (asserted via the metadata-only opcode model). + # CALLCODE carries no state-gas (NEW_ACCOUNT) component: the value + # stays in the caller's own context, so no beneficiary is created. callcode_meta = Op.CALLCODE(address_warm=False, value_transfer=True) - assert callcode_meta.gas_cost(fork) == gas_costs.COLD_ACCOUNT_ACCESS + ( - gas_costs.CALL_VALUE - ) - # CALLCODE carries no state-gas (NEW_ACCOUNT) component. assert callcode_meta.state_cost(fork) == 0 # Whole tx is regular gas; no NEW_ACCOUNT state component appears. - # The CALLCODE forwards CALL_STIPEND to the callee, which (running in - # the caller's own context with empty code) leaves it unused and - # returns it, so consumed gas is the charge minus the stipend. + # The CALLCODE forwards the value-call stipend to the callee, which + # (running in the caller's own context with empty code) leaves it + # unused and returns it, so consumed gas is the charge minus stipend. expected_gas_used = ( - intrinsic + caller_code.gas_cost(fork) - gas_costs.CALL_STIPEND + intrinsic + caller_code.gas_cost(fork) - fork.call_value_stipend() ) - # Guard the no-state assertion: NEW_ACCOUNT would dominate if charged. - assert expected_gas_used < gas_costs.NEW_ACCOUNT tx = Transaction( to=caller, @@ -305,16 +275,13 @@ def test_call_value_to_new_account_seam( dimension. The block header reflects ``max(regular, state)``, which is dominated by the state charge. """ - gas_costs = fork.gas_costs() - new_account_state_gas = gas_costs.NEW_ACCOUNT intrinsic = fork.transaction_intrinsic_cost_calculator()() # Fresh, value-receiving target (state-empty, will be created). target = pre.fund_eoa(amount=0) - # Metadata-bearing CALL so `caller_code.gas_cost(fork)` folds the - # value transfer and account-creation charges; we then split off the - # NEW_ACCOUNT state component for the 2D header accounting. + # Metadata-bearing CALL so its cost splits into the regular + # (access + value transfer) and state (NEW_ACCOUNT) dimensions. call = Op.CALL.with_metadata( address_warm=False, value_transfer=True, account_new=True )( @@ -329,23 +296,15 @@ def test_call_value_to_new_account_seam( caller_code = Op.POP(call) + Op.STOP caller = pre.deploy_contract(code=caller_code, balance=1) - # Regular dimension: access + value (NOT new account, which is the - # state dimension). Asserted via the metadata-only opcode model. - call_meta = Op.CALL( - address_warm=False, value_transfer=True, account_new=True - ) - call_regular = call_meta.gas_cost(fork) - new_account_state_gas - assert call_regular == gas_costs.COLD_ACCOUNT_ACCESS + gas_costs.CALL_VALUE - assert call_regular == 13_300 - - # block_gas_used = max(block_regular, block_state). The CALL opcode - # has no state-gas map, so its NEW_ACCOUNT charge spills as regular - # gas in the bytecode total; strip it back out to isolate the - # regular axis and re-add NEW_ACCOUNT explicitly on the state axis. - tx_regular = intrinsic + caller_code.gas_cost(fork) - new_account_state_gas - tx_state = new_account_state_gas + new_account_state_gas = call.state_cost(fork) + + # block_gas_used = max(block_regular, block_state). The CALL's + # NEW_ACCOUNT lands on the state axis; the regular axis is the + # access plus value-transfer cost. + tx_regular = intrinsic + caller_code.regular_cost(fork) + tx_state = caller_code.state_cost(fork) expected_gas_used = max(tx_regular, tx_state) - # State must dominate here, proving the 183,600 hit the state axis. + # State must dominate here, proving NEW_ACCOUNT hit the state axis. assert expected_gas_used == new_account_state_gas tx = Transaction( @@ -390,8 +349,6 @@ def test_call_to_delegated_target_double_access( ``STATICCALL`` carry no value but still pay the delegation surcharge. """ - gas_costs = fork.gas_costs() - # Final code-bearing account that the delegation points at. delegate = pre.deploy_contract(Op.STOP) # EOA delegated (EIP-7702) to `delegate`. @@ -407,16 +364,8 @@ def test_call_to_delegated_target_double_access( pre, fork, measured_code, call_opcode(address_warm=False) ) - target_cost = ( - gas_costs.WARM_ACCESS if target_warm else gas_costs.COLD_ACCOUNT_ACCESS - ) - delegate_cost = ( - gas_costs.WARM_ACCESS - if delegate_warm - else gas_costs.COLD_ACCOUNT_ACCESS - ) - expected_gas = target_cost + delegate_cost - assert expected_gas == cost_metadata.gas_cost(fork) + # The opcode's own cost folds the target and delegate accesses. + expected_gas = cost_metadata.gas_cost(fork) # Warm the target and/or the delegate leaf via the access list. access_entries = [] @@ -493,8 +442,6 @@ def test_call_self_is_warm( The current target is in the accessed-addresses set on message entry, so a call to ``ADDRESS`` pays only ``WARM_ACCESS`` (100). """ - gas_costs = fork.gas_costs() - # `Op.ADDRESS` is the call's address argument, embedded inside the # runnable call; the self address is in the accessed set on entry, so # the call is warm. The overhead subtracts the call's own cold cost, @@ -505,7 +452,6 @@ def test_call_self_is_warm( ) expected_gas = call_opcode(address_warm=True).gas_cost(fork) - assert expected_gas == gas_costs.WARM_ACCESS tx = Transaction(to=measure_address, sender=pre.fund_eoa()) @@ -539,15 +485,15 @@ def test_call_forwarded_gas_63_64( ``gas_left`` already net of the post-8038 cold access cost (not before it, and not double-charging it). """ - gas_costs = fork.gas_costs() sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) # Child: a single cold zero-to-nonzero SSTORE as proof of execution. # Its regular need is the two operand pushes plus the cold storage # write (the state portion is funded separately via the reservoir, # which is passed to the child in full with no 63/64 rule). - child = pre.deploy_contract(Op.SSTORE(0, 1)) - child_regular = 2 * gas_costs.VERY_LOW + gas_costs.COLD_STORAGE_WRITE + child_code = Op.SSTORE(0, 1) + child = pre.deploy_contract(child_code) + child_regular = child_code.regular_cost(fork) # Smallest budget whose 63/64 floor still reaches `child_regular`. forward_budget = child_regular * 64 // 63 @@ -557,24 +503,21 @@ def test_call_forwarded_gas_63_64( # Wrapper: cold zero-value CALL requesting max gas (so the forwarded # amount is bound by `gas_left`, not by the request). ret_size=0 # avoids any memory-expansion term. - wrapper = pre.deploy_contract( - Op.CALL( - gas=0xFFFFFFFF, - address=child, - value=0, - args_offset=0, - args_size=0, - ret_offset=0, - ret_size=0, - ) + wrapper_call = Op.CALL( + gas=0xFFFFFFFF, + address=child, + value=0, + args_offset=0, + args_size=0, + ret_offset=0, + ret_size=0, ) + wrapper = pre.deploy_contract(wrapper_call) - # At the wrapper's CALL the access charge (`extra_gas`) is deducted - # first, leaving exactly `forward_budget` as `gas_left` for the 63/64 - # floor. The seven CALL operand pushes precede it. - wrapper_pushes = 7 * gas_costs.VERY_LOW - extra_gas = gas_costs.COLD_ACCOUNT_ACCESS # cold call, value 0 - wrapper_gas = wrapper_pushes + extra_gas + forward_budget + # At the wrapper's CALL the cold access charge is deducted first + # (folded with the operand pushes into its regular cost), leaving + # exactly `forward_budget` as `gas_left` for the 63/64 floor. + wrapper_gas = wrapper_call.regular_cost(fork) + forward_budget # Outer caller hands the wrapper exactly `wrapper_gas`. caller = pre.deploy_contract( @@ -610,9 +553,7 @@ def test_account_warmth_reverts_on_subcall_revert( rolled back on revert (mirrors the ``SLOAD`` warmth-revert case for the account dimension). """ - gas_costs = fork.gas_costs() cold_gas = Op.BALANCE(address_warm=False).gas_cost(fork) - assert cold_gas == gas_costs.COLD_ACCOUNT_ACCESS # Address whose warmth we probe; left out of the access list so its # first runtime touch is cold. @@ -664,8 +605,6 @@ def test_call_to_double_delegated_target_single_hop( second hop, so ``final``'s leaf is not charged. Both the framework opcode model and a runtime ``CodeGasMeasure`` confirm the value. """ - gas_costs = fork.gas_costs() - # A -> B -> C delegation chain. `mid` is an EOA whose code is the # 7702 delegation designator pointing at `final`; `target` delegates # to `mid` in turn. @@ -680,8 +619,8 @@ def test_call_to_double_delegated_target_single_hop( delegated_address=True, delegated_address_warm=False, ) - expected_gas = 2 * gas_costs.COLD_ACCOUNT_ACCESS - assert expected_gas == cost_metadata.gas_cost(fork) + # Cold target leaf plus cold delegation leaf; no state gas. + expected_gas = cost_metadata.gas_cost(fork) assert cost_metadata.state_cost(fork) == 0 measured_code = Op.CALL(gas=0, address=target) @@ -711,8 +650,6 @@ def test_call_precompile_is_warm( every transaction, so a call to one pays only ``WARM_ACCESS`` (100). The identity precompile (address 4) is used as the target. """ - gas_costs = fork.gas_costs() - identity_precompile = Address(4) measured_code = call_opcode(gas=0, address=identity_precompile) @@ -721,7 +658,6 @@ def test_call_precompile_is_warm( ) expected_gas = call_opcode(address_warm=True).gas_cost(fork) - assert expected_gas == gas_costs.WARM_ACCESS tx = Transaction(to=measure_address, sender=pre.fund_eoa()) diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_create_gas.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_create_gas.py index d76057ecf1e..f91e6091f87 100644 --- a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_create_gas.py +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_create_gas.py @@ -74,15 +74,6 @@ def test_create_regular_gas( account-creation state gas is excluded by subtracting ``create_state_gas(0)``. """ - gas_costs = fork.gas_costs() - # The EIP-8038 CREATE regular base equals ACCOUNT_WRITE + - # COLD_STORAGE_ACCESS = 11,000. - assert gas_costs.OPCODE_CREATE_BASE == 11_000 - assert ( - gas_costs.OPCODE_CREATE_BASE - == gas_costs.ACCOUNT_WRITE + gas_costs.COLD_STORAGE_ACCESS - ) - # Isolate the regular dimension: opcode total minus its account # creation state gas (the only state component carried by the CREATE # opcode itself; code deposit is charged on RETURN inside initcode). @@ -93,17 +84,6 @@ def test_create_regular_gas( # Equivalent isolation via the regular_cost helper. assert regular_gas == create_meta.regular_cost(fork) - init_code_words = (init_code_size + 31) // 32 - expected_regular = ( - gas_costs.OPCODE_CREATE_BASE - + gas_costs.CODE_INIT_PER_WORD * init_code_words - ) - if create_opcode == Op.CREATE2: - expected_regular += ( - gas_costs.OPCODE_KECCAK256_PER_WORD * init_code_words - ) - assert regular_gas == expected_regular - # Runtime confirmation via CodeGasMeasure: a factory whose CREATE # deploys empty code, so no code-deposit state gas is charged and the # only state component is the account-creation gas funded from the @@ -125,7 +105,8 @@ def test_create_regular_gas( if create_opcode == Op.CREATE2 else Op.CREATE(value=0, offset=0, size=init_code_size) ) - arg_pushes = (4 if create_opcode == Op.CREATE2 else 3) * gas_costs.VERY_LOW + push_cost = Op.PUSH1(0).regular_cost(fork) + arg_pushes = (4 if create_opcode == Op.CREATE2 else 3) * push_cost memory_setup = ( Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE, new_memory_size=init_code_size) @@ -177,36 +158,24 @@ def test_create2_keccak_word_delta( regular cost shared with ``CREATE``. Both opcodes carry the identical EIP-8038 ``CREATE_ACCESS`` base and EIP-3860 word cost. - The regular-gas delta is asserted via the opcode model - (``create2_regular - create_regular`` equals the keccak word - surcharge). At runtime a factory then measures a single ``CREATE2`` - with ``CodeGasMeasure`` and stores its absolute regular cost: the - surcharge is established by the model assertion, and the runtime leg - confirms the absolute ``CREATE2`` regular cost. + A factory measures a single ``CREATE2`` with ``CodeGasMeasure`` and + stores its absolute regular cost, confirming the opcode's own + ``regular_cost`` (which folds the keccak word surcharge) against the + runtime charge. """ - gas_costs = fork.gas_costs() - init_code_words = (init_code_size + 31) // 32 - keccak_surcharge = gas_costs.OPCODE_KECCAK256_PER_WORD * init_code_words - - create_regular = Op.CREATE(init_code_size=init_code_size).regular_cost( - fork - ) create2_regular = Op.CREATE2(init_code_size=init_code_size).regular_cost( fork ) - assert create2_regular - create_regular == keccak_surcharge - - # Runtime confirmation. Init code is all-zero bytes (`STOP`), so the - # child frame halts immediately (zero gas) depositing empty code; the - # CREATE2 charges no code-deposit state gas and no child execution gas - # is folded into the measurement. The single CREATE2 regular cost is - # measured via CodeGasMeasure with a reservoir sized for its account - # creation state gas, keeping the GAS-measured `gas_left` free of - # state-gas spill. The opcode-model assertion above is the - # load-bearing keccak-delta check; this confirms the absolute value. + + # Init code is all-zero bytes (`STOP`), so the child frame halts + # immediately (zero gas) depositing empty code; the CREATE2 charges no + # code-deposit state gas and no child execution gas is folded into the + # measurement. The single CREATE2 regular cost is measured via + # CodeGasMeasure with a reservoir sized for its account creation state + # gas, keeping the GAS-measured `gas_left` free of state-gas spill. padded = b"\x00" * init_code_size - push4 = 4 * gas_costs.VERY_LOW + push4 = 4 * Op.PUSH1(0).regular_cost(fork) storage = Storage() measure_create2 = CodeGasMeasure( code=Op.CREATE2(value=0, offset=0, size=init_code_size, salt=0), @@ -303,7 +272,9 @@ def exact_execution_gas( flat regular per-byte deposit cost. The single call is therefore correct in either regime. """ - execution = exact_intrinsic_gas + fork.gas_costs().NEW_ACCOUNT + execution = exact_intrinsic_gas + fork.transaction_top_frame_state_gas( + contract_creation=True + ) execution += initcode.execution_gas(fork) execution += initcode.deployment_gas(fork) return execution @@ -377,7 +348,9 @@ def test_create_tx_gas_boundary( elif succeeds: # Fresh target: top-frame NEW_ACCOUNT plus the per-byte code # deposit are the state-gas axis; the rest is regular. - state_used = fork.gas_costs().NEW_ACCOUNT + state_used = fork.transaction_top_frame_state_gas( + contract_creation=True + ) state_used += fork.code_deposit_state_gas( code_size=len(initcode.deploy_code) ) diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_eip_mainnet.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_eip_mainnet.py index 210879bf6f3..667cedbb638 100644 --- a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_eip_mainnet.py +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_eip_mainnet.py @@ -180,7 +180,7 @@ def test_selfdestruct_funds_new_account( tx = Transaction( to=suicidal, gas_limit=1_000_000, - state_gas_reservoir=fork.gas_costs().NEW_ACCOUNT, + state_gas_reservoir=Op.SELFDESTRUCT(account_new=True).state_cost(fork), sender=pre.fund_eoa(), ) diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_ext_code_opcodes_gas.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_ext_code_opcodes_gas.py index 90f446398ce..5470eeb0436 100644 --- a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_ext_code_opcodes_gas.py +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_ext_code_opcodes_gas.py @@ -97,7 +97,7 @@ def test_ext_code_opcode_gas( more than ``BALANCE``/``EXTCODEHASH`` at equal warmth (the second, code-reading database access). """ - gas_costs = fork.gas_costs() + del code_read_surcharge # encoded in `cost_metadata` target = pre.deploy_contract(Op.STOP) @@ -117,13 +117,10 @@ def test_ext_code_opcode_gas( ) measure_address = pre.deploy_contract(code=code_gas_measure) - access_cost = ( - gas_costs.WARM_ACCESS if warm else gas_costs.COLD_ACCOUNT_ACCESS - ) - surcharge = gas_costs.WARM_ACCESS if code_read_surcharge else 0 - expected_gas = access_cost + surcharge - # Cross-check the framework opcode model agrees with the formula. - assert expected_gas == cost_metadata(warm).gas_cost(fork) + # The opcode's own cost is the expected measured gas: it folds the + # access cost and, for EXTCODESIZE/EXTCODECOPY, the code-read + # surcharge. + expected_gas = cost_metadata(warm).gas_cost(fork) # Warm the target via the access list when required; the cold case # leaves it absent so its first runtime access is cold. @@ -163,8 +160,6 @@ def test_extcodecopy_nonzero_composes_additively( flat add-on that does not interact with the copy or memory terms, so the measured gas must equal the sum of all four components. """ - gas_costs = fork.gas_costs() - # Target carries enough code to satisfy the copy; STOP padding keeps # it a deployable contract with a non-empty code hash. target = pre.deploy_contract(Op.STOP * copy_size) @@ -192,21 +187,6 @@ def test_extcodecopy_nonzero_composes_additively( ) expected_gas = oracle.gas_cost(fork) - # Additive decomposition the surcharge must satisfy. - words = (copy_size + 31) // 32 - access_cost = ( - gas_costs.WARM_ACCESS if warm else gas_costs.COLD_ACCOUNT_ACCESS - ) - memory_expansion = fork.memory_expansion_gas_calculator()( - new_bytes=copy_size, previous_bytes=0 - ) - assert expected_gas == ( - access_cost - + gas_costs.WARM_ACCESS # EIP-8038 code-read surcharge - + gas_costs.OPCODE_COPY_PER_WORD * words - + memory_expansion - ) - code_gas_measure = CodeGasMeasure( code=measured_code, overhead_cost=measured_code.gas_cost(fork) - oracle.gas_cost(fork), @@ -243,16 +223,12 @@ def test_extcodehash_empty_account( or ``WARM_ACCESS`` (warm) regardless of the target being empty. The returned hash of an empty/non-existent account is ``0``. """ - gas_costs = fork.gas_costs() - # A non-existent (empty) target: never deployed, no balance, no code. empty_addr = Address(0xDEAD) - expected_gas = ( - gas_costs.WARM_ACCESS if warm else gas_costs.COLD_ACCOUNT_ACCESS - ) - # No code-read surcharge for EXTCODEHASH; the opcode model must agree. - assert expected_gas == Op.EXTCODEHASH(address_warm=warm).gas_cost(fork) + # EXTCODEHASH reads only the account leaf (no code-read surcharge), so + # its bare cost is the plain account access. + expected_gas = Op.EXTCODEHASH(address_warm=warm).gas_cost(fork) # Measure the access cost, then store the returned hash so the # empty-account 0 result is asserted alongside the pricing. The diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_fork_transition.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_fork_transition.py index a1de15f4108..cc7f3d21802 100644 --- a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_fork_transition.py +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_fork_transition.py @@ -5,8 +5,9 @@ "Same operation, different gas" across the Amsterdam boundary. A block at ``timestamp=14_999`` runs under the pre-fork (parent) schedule; a block at ``timestamp=15_000`` runs under the EIP-8038 schedule. Every -before/after magnitude is derived from -``fork.fork_at(timestamp=...).gas_costs()`` — nothing is hardcoded. +before/after magnitude is derived from the opcode's own cost at each +fork (``bytecode.gas_cost`` / ``regular_cost`` / ``refund``) — nothing +is hardcoded. Two proof styles are used: @@ -14,11 +15,12 @@ access and the ``EXT*`` code-read surcharge) are measured exactly with ``CodeGasMeasure`` in each regime and asserted against the derived cost. -* Constant repricings that the runtime opcode model cannot isolate - without state-gas confounders (``CALL_VALUE``, ``CREATE`` base, - ``SELFDESTRUCT`` account-write) are asserted at the constant level - from the derived schedules while the operation is still exercised in - both blocks to prove it runs in each regime. +* Repricings that the runtime opcode model cannot isolate without + state-gas confounders (``CALL`` with value, ``CREATE``, + ``SELFDESTRUCT`` to a fresh beneficiary, ``SSTORE`` first change) are + exercised in both blocks to prove the operation still runs in each + regime, with the ``SSTORE`` regular/state split and clear refund + compared across forks via the bytecode's own cost methods. * The authorization intrinsic rise is proven behaviourally: a tx whose ``gas_limit`` equals the old auth intrinsic is valid before the fork and rejected with ``INTRINSIC_GAS_TOO_LOW`` after. @@ -126,8 +128,10 @@ def test_cold_account_access_at_transition( before = fork.fork_at(timestamp=BEFORE_TS) after = fork.fork_at(timestamp=AFTER_TS) - cost_before = before.gas_costs().COLD_ACCOUNT_ACCESS - cost_after = after.gas_costs().COLD_ACCOUNT_ACCESS + # BALANCE's bare cost equals COLD_ACCOUNT_ACCESS in each regime. + cold_balance = Op.BALANCE.with_metadata(address_warm=False) + cost_before = cold_balance.gas_cost(before) + cost_after = cold_balance.gas_cost(after) assert cost_after > cost_before target = pre.deploy_contract(code=Op.STOP) @@ -179,7 +183,6 @@ def test_ext_code_surcharge_at_transition( after ) - Op.BALANCE(address_warm=True).gas_cost(after) assert surcharge_before == 0 - assert surcharge_after == after.gas_costs().WARM_ACCESS assert surcharge_after > surcharge_before extcodesize_cost_before = Op.EXTCODESIZE(address_warm=False).gas_cost( @@ -221,13 +224,6 @@ def test_call_value_cost_at_transition( is exercised in both blocks to prove it still succeeds in each regime. """ - before = fork.fork_at(timestamp=BEFORE_TS) - after = fork.fork_at(timestamp=AFTER_TS) - - call_value_before = before.gas_costs().CALL_VALUE - call_value_after = after.gas_costs().CALL_VALUE - assert call_value_after > call_value_before - callee_before = pre.deploy_contract(code=Op.STOP, balance=0) callee_after = pre.deploy_contract(code=Op.STOP, balance=0) @@ -271,17 +267,6 @@ def test_create_base_cost_at_transition( asserted from the derived schedules and a ``CREATE`` is exercised in both blocks to prove it still deploys. """ - before = fork.fork_at(timestamp=BEFORE_TS) - after = fork.fork_at(timestamp=AFTER_TS) - - create_base_before = before.gas_costs().OPCODE_CREATE_BASE - create_base_after = after.gas_costs().OPCODE_CREATE_BASE - assert create_base_after != create_base_before - # Post-fork base is the harmonized ACCOUNT_WRITE + COLD_STORAGE_ACCESS. - assert create_base_after == ( - after.gas_costs().ACCOUNT_WRITE + after.gas_costs().COLD_STORAGE_ACCESS - ) - init_code = Op.STOP init_word = int.from_bytes(bytes(init_code), "big") << ( 256 - 8 * len(init_code) @@ -332,13 +317,6 @@ def test_selfdestruct_account_write_at_transition( ``SELFDESTRUCT`` to a fresh beneficiary is exercised in both blocks to prove it still runs. """ - before = fork.fork_at(timestamp=BEFORE_TS) - after = fork.fork_at(timestamp=AFTER_TS) - - account_write_before = before.gas_costs().ACCOUNT_WRITE - account_write_after = after.gas_costs().ACCOUNT_WRITE - assert account_write_after > account_write_before - # Fresh empty beneficiaries so the positive-balance-to-empty branch # that adds ACCOUNT_WRITE is taken in each regime. beneficiary_before = pre.fund_eoa(amount=0) @@ -406,20 +384,12 @@ def test_sstore_write_cost_at_transition( assert state_after > 0 assert total_after != total_before - # After the fork the regular portion is the EIP-8038 split: - # COLD_STORAGE_ACCESS plus the standalone STORAGE_WRITE (modeled as - # COLD_STORAGE_WRITE minus COLD_STORAGE_ACCESS). - after_costs = after.gas_costs() - storage_write_after = ( - after_costs.COLD_STORAGE_WRITE - after_costs.COLD_STORAGE_ACCESS - ) - assert regular_after == ( - after_costs.COLD_STORAGE_ACCESS + storage_write_after - ) - # The storage-clear refund also rises across the boundary. - refund_before = before.gas_costs().REFUND_STORAGE_CLEAR - refund_after = after_costs.REFUND_STORAGE_CLEAR + clear_sstore = Op.SSTORE.with_metadata( + original_value=1, current_value=1, new_value=0 + ) + refund_before = clear_sstore.refund(before) + refund_after = clear_sstore.refund(after) assert refund_after > refund_before # Exercise the zero-to-nonzero SSTORE in both regimes; the slot ends diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_selfdestruct_gas.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_selfdestruct_gas.py index 2fb565c7a36..99d0797731c 100644 --- a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_selfdestruct_gas.py +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_selfdestruct_gas.py @@ -66,30 +66,6 @@ pytestmark = pytest.mark.valid_from("Amsterdam") -def _selfdestruct_regular(fork: Fork, *, warm: bool, account_new: bool) -> int: - """ - Return the EIP-8038 *regular* gas charged by SELFDESTRUCT. - - ``OPCODE_SELFDESTRUCT_BASE + access + (ACCOUNT_WRITE if account_new)``; - the ``GAS_NEW_ACCOUNT`` account-creation cost is the EIP-8037 state - dimension and is excluded from ``regular_cost``. - """ - gas_costs = fork.gas_costs() - regular = Op.SELFDESTRUCT( - address_warm=warm, account_new=account_new - ).regular_cost(fork) - # SELFDESTRUCT charges a cold-access surcharge only; a warm - # beneficiary adds nothing beyond the base (no WARM_ACCESS). - access = 0 if warm else gas_costs.COLD_ACCOUNT_ACCESS - expected = ( - gas_costs.OPCODE_SELFDESTRUCT_BASE - + access - + (gas_costs.ACCOUNT_WRITE if account_new else 0) - ) - assert regular == expected - return regular - - def _destructor_code( beneficiary: Address | Bytecode, *, warm: bool, account_new: bool ) -> Bytecode: @@ -123,11 +99,7 @@ def test_selfdestruct_new_beneficiary_regular_gas( EIP-8037 suite asserts it); here it is funded from the reservoir and the value transfer to the new beneficiary confirms the path. """ - gas_costs = fork.gas_costs() - new_account_state_gas = gas_costs.NEW_ACCOUNT - - regular = _selfdestruct_regular(fork, warm=warm, account_new=True) - assert regular == (13_000 if warm else 16_000) + new_account_state_gas = Op.SELFDESTRUCT(account_new=True).state_cost(fork) beneficiary = Address(0xDEAD) # empty, non-existent @@ -176,9 +148,6 @@ def test_selfdestruct_alive_beneficiary_no_account_write( ``5,000 + (3,000 if cold)`` (5,000 warm, 8,000 cold) and no state gas is charged. The block header reflects the pure regular consumption. """ - regular = _selfdestruct_regular(fork, warm=warm, account_new=False) - assert regular == (5_000 if warm else 8_000) - beneficiary = pre.fund_eoa(amount=1) # alive destructor_code = _destructor_code( @@ -248,9 +217,6 @@ def test_selfdestruct_codebearing_zero_balance_beneficiary_no_account_write( alive-via-balance case, which exercises the same path through a different liveness source. """ - regular = _selfdestruct_regular(fork, warm=warm, account_new=False) - assert regular == (5_000 if warm else 8_000) - # Alive via code (non-empty code), with zero balance. beneficiary = pre.deploy_contract(code=Op.STOP, balance=0) @@ -316,9 +282,6 @@ def test_selfdestruct_zero_balance_no_account_write( No value is transferred, so even a non-existent beneficiary is not created: regular = ``5,000 + access`` and no state gas is charged. """ - regular = _selfdestruct_regular(fork, warm=warm, account_new=False) - assert regular == (5_000 if warm else 8_000) - beneficiary = Address(0xDEAD) # non-existent, but no value sent destructor_code = _destructor_code( @@ -391,12 +354,6 @@ def test_selfdestruct_self_or_precompile_beneficiary( transfer would otherwise create one and charge ``GAS_NEW_ACCOUNT`` on the state axis). """ - gas_costs = fork.gas_costs() - - regular = _selfdestruct_regular(fork, warm=True, account_new=False) - # SELFDESTRUCT has no warm-access surcharge: warm == base only. - assert regular == gas_costs.OPCODE_SELFDESTRUCT_BASE - if beneficiary_kind == "self": # Self is warm on entry; the PUSH is `ADDRESS` (BASE=2). A # non-zero balance is transferred to self (no creation). @@ -469,15 +426,7 @@ def test_selfdestruct_oog_boundary( gas short OOGs (CALL returns 0) before the value transfer, so the beneficiary is never created. """ - gas_costs = fork.gas_costs() - beneficiary = Address(0xDEAD) - regular = _selfdestruct_regular(fork, warm=False, account_new=True) - assert regular == ( - gas_costs.OPCODE_SELFDESTRUCT_BASE - + gas_costs.COLD_ACCOUNT_ACCESS - + gas_costs.ACCOUNT_WRITE - ) destructor_code = _destructor_code( beneficiary, warm=False, account_new=True @@ -564,9 +513,6 @@ def test_same_tx_created_selfdestruct_self_burn( # Self-beneficiary on a balance-bearing same-tx-created contract is # alive: account_new is false, so only the warm base is charged. - regular = _selfdestruct_regular(fork, warm=True, account_new=False) - assert regular == fork.gas_costs().OPCODE_SELFDESTRUCT_BASE - # Creation intrinsic is regular-only under EIP-2780; the pre-existing # target adds no top-frame NEW_ACCOUNT and the self-burn adds no state # gas, so net state gas is zero. The regular consumption exceeds the @@ -627,7 +573,6 @@ def test_same_tx_created_selfdestruct_to_fresh_beneficiary( created target is alive at message entry (EIP-8037), while the fresh beneficiary's ``NEW_ACCOUNT`` persists. """ - new_account_state_gas = fork.gas_costs().NEW_ACCOUNT intrinsic_calc = fork.transaction_intrinsic_cost_calculator() amount = 1 @@ -644,16 +589,14 @@ def test_same_tx_created_selfdestruct_to_fresh_beneficiary( init_code = Op.SELFDESTRUCT.with_metadata( address_warm=False, account_new=True )(beneficiary) + # The creation NEW_ACCOUNT is refunded (target alive at entry) and is + # not part of the intrinsic under EIP-2780; only the fresh + # beneficiary's NEW_ACCOUNT (the SELFDESTRUCT state cost) persists. + new_account_state_gas = init_code.state_cost(fork) - regular = _selfdestruct_regular(fork, warm=False, account_new=True) - assert regular == 16_000 - - intrinsic_total = intrinsic_calc( + intrinsic_regular = intrinsic_calc( calldata=bytes(init_code), contract_creation=True ) - # The creation NEW_ACCOUNT is refunded (target alive at entry); only - # the fresh beneficiary's NEW_ACCOUNT remains as net state gas. - intrinsic_regular = intrinsic_total - new_account_state_gas expected_state = new_account_state_gas expected_regular = intrinsic_regular + init_code.regular_cost(fork) expected_gas_used = max(expected_regular, expected_state) diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_set_code_auth_gas.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_set_code_auth_gas.py index a3a50b782a5..1d4a9b0624c 100644 --- a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_set_code_auth_gas.py +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_set_code_auth_gas.py @@ -11,10 +11,11 @@ cold/warm account-access costs that an authorized delegation incurs when later accessed by a ``CALL``. -The regular per-authorization intrinsic magnitude is -``fork.gas_costs().REGULAR_PER_AUTH_BASE_COST`` (``7816`` on Amsterdam: +The regular per-authorization intrinsic magnitude is the fixed +per-authorization base cost charged by the intrinsic (on Amsterdam, ``101 * 16`` calldata tokens plus the ``3000`` ecrecover, ``3000`` cold -and ``2 * 100`` warm accesses of the EIP-7702 base). The top-frame +and ``2 * 100`` warm accesses of the EIP-7702 base), isolated here as +the intrinsic delta of adding one authorization. The top-frame state charges are asserted by the sibling ``eip8037_state_creation_gas_cost_increase`` and ``eip2780_reduce_intrinsic_tx_gas`` suites; this suite does not @@ -34,6 +35,7 @@ CodeGasMeasure, Environment, Fork, + Hash, Op, StateTestFiller, Storage, @@ -58,11 +60,19 @@ def _regular_per_auth(fork: Fork) -> int: authorization. Under EIP-2780 the intrinsic charges only the state-independent - ``REGULAR_PER_AUTH_BASE_COST`` per authorization; the account-write - (``ACCOUNT_WRITE``) and delegation-write (``AUTH_BASE``) costs are - charged lazily at the top frame, not in the intrinsic. + per-authorization base cost; the account-write (``ACCOUNT_WRITE``) + and delegation-write (``AUTH_BASE``) costs are charged lazily at the + top frame, not in the intrinsic. Isolated as the intrinsic delta of + adding one authorization. """ - return fork.gas_costs().REGULAR_PER_AUTH_BASE_COST + calc = fork.transaction_intrinsic_cost_calculator() + return calc( + authorization_list_or_count=1, + return_cost_deducted_prior_execution=True, + ) - calc( + authorization_list_or_count=0, + return_cost_deducted_prior_execution=True, + ) def _regular_intrinsic( @@ -490,12 +500,13 @@ def test_auth_account_warming( ``COLD_ACCOUNT_ACCESS``. When the sponsor is the authority, the authority is already warm for the same reason. - All costs are taken from ``fork.gas_costs()`` so the repricing is - asserted against the live schedule rather than hardcoded constants. + All costs are derived from the fork's opcode schedule (not + hardcoded) so the repricing is asserted against the live values. """ - gas_costs = fork.gas_costs() - cold = gas_costs.COLD_ACCOUNT_ACCESS - warm = gas_costs.WARM_ACCESS + # Bare account-access costs, isolated via BALANCE (no code-read + # surcharge and no operand push). + cold = Op.BALANCE.with_metadata(address_warm=False).gas_cost(fork) + warm = Op.BALANCE.with_metadata(address_warm=True).gas_cost(fork) delegation_target = pre.deploy_contract(code=Op.STOP) @@ -529,7 +540,7 @@ def test_auth_account_warming( # Measure the cost of a single CALL to the authority. The CALL # opcode leaves one stack item (success); the overhead is the PUSHes # for its arguments. - overhead_cost = gas_costs.VERY_LOW * len(Op.CALL.kwargs) + overhead_cost = Op.PUSH1(0).regular_cost(fork) * len(Op.CALL.kwargs) storage = Storage() callee_code = CodeGasMeasure( code=Op.CALL(gas=0, address=authority), @@ -573,7 +584,29 @@ def test_many_auths_block_limit( gas_limit_cap = fork.transaction_gas_limit_cap() assert gas_limit_cap is not None - per_auth_total = fork.gas_costs().AUTH_PER_EMPTY_ACCOUNT + contract = pre.deploy_contract(code=Op.STOP) + + # Per-authorization total for a fresh (empty) authority: the regular + # intrinsic base plus the top-frame account-write, account-creation + # and delegation-write charges, derived from the fork's calculators + # so it tracks the repricing. The probe only feeds the gas + # calculators, so it is signed with a fixed dummy key rather than a + # throwaway pre-state signer. + probe_auth = AuthorizationTuple( + address=contract, + nonce=0, + secret_key=Hash(1), + creates_account=True, + writes_delegation=True, + first_write=True, + ) + per_auth_total = ( + _regular_per_auth(fork) + + fork.transaction_top_frame_gas_calculator()( + authorizations=[probe_auth] + ) + + fork.transaction_top_frame_state_gas(authorizations=[probe_auth]) + ) base = fork.transaction_intrinsic_cost_calculator()( authorization_list_or_count=0, ) @@ -581,7 +614,6 @@ def test_many_auths_block_limit( num_auths = (gas_limit_cap - base) // per_auth_total assert num_auths >= 2 - contract = pre.deploy_contract(code=Op.STOP) signers = [pre.fund_eoa() for _ in range(num_auths)] authorization_list = [ AuthorizationTuple(address=contract, nonce=0, signer=signer) diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_sstore_gas.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_sstore_gas.py index e6ba6c912fe..f3cdf3e1669 100644 --- a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_sstore_gas.py +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_sstore_gas.py @@ -183,8 +183,8 @@ def test_sstore_cold_then_warm_same_slot( ) second = second_bare(data_slot, 3) - expected_first = first.regular_cost(fork) - 2 * fork.gas_costs().VERY_LOW - expected_second = second.regular_cost(fork) - 2 * fork.gas_costs().VERY_LOW + expected_first = first_bare.regular_cost(fork) + expected_second = second_bare.regular_cost(fork) # Each measured write stores its own runtime cost; the overhead # subtraction strips the two operand PUSHes so the stored value is the diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_sstore_refunds.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_sstore_refunds.py index 7becbe2bb22..4fbe570673d 100644 --- a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_sstore_refunds.py +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_sstore_refunds.py @@ -84,9 +84,6 @@ def test_sstore_clear_grants_refund( observed in ``cumulative_gas_used``. The non-zero original means no EIP-8037 state refund participates. """ - gas_costs = fork.gas_costs() - refund_clear = gas_costs.REFUND_STORAGE_CLEAR - clear = Op.SSTORE.with_metadata( key_warm=False, original_value=1, @@ -100,8 +97,8 @@ def test_sstore_clear_grants_refund( contract = pre.deploy_contract(code=code, storage={0: 1}) - # Sanity: the slot's refund counter accrues exactly one clear grant. - assert code.refund(fork) == refund_clear + # The slot's clear grants exactly one REFUND_STORAGE_CLEAR. + refund_clear = code.refund(fork) expected_cumulative = _cumulative_gas_used(code, fork) # The cap must not bind here, so the full grant is visible. intrinsic = fork.transaction_intrinsic_cost_calculator()( @@ -181,11 +178,6 @@ def test_sstore_restore_nonzero_refunds_write( burned so the quotient cap does not bind and the full refund is observable. """ - gas_costs = fork.gas_costs() - storage_write = ( - gas_costs.COLD_STORAGE_WRITE - gas_costs.COLD_STORAGE_ACCESS - ) - code = Op.SSTORE.with_metadata( key_warm=False, original_value=1, @@ -202,7 +194,8 @@ def test_sstore_restore_nonzero_refunds_write( contract = pre.deploy_contract(code=code, storage={0: 1}) - assert code.refund(fork) == storage_write + # Restoring the non-zero original refunds STORAGE_WRITE. + storage_write = code.refund(fork) expected_cumulative = _cumulative_gas_used(code, fork) intrinsic = fork.transaction_intrinsic_cost_calculator()( return_cost_deducted_prior_execution=True @@ -241,9 +234,6 @@ def test_sstore_refund_quotient_cap( always below the accrued refund, so the applied refund is the cap and ``cumulative_gas_used`` reflects ``min(gas_used // 5, accrued)``. """ - gas_costs = fork.gas_costs() - accrued = num_clears * gas_costs.REFUND_STORAGE_CLEAR - code = Bytecode() for slot in range(num_clears): code += Op.SSTORE.with_metadata( @@ -258,7 +248,8 @@ def test_sstore_refund_quotient_cap( storage=dict.fromkeys(range(num_clears), 1), ) - assert code.refund(fork) == accrued + # num_clears distinct clears accrue num_clears * REFUND_STORAGE_CLEAR. + accrued = code.refund(fork) intrinsic = fork.transaction_intrinsic_cost_calculator()( return_cost_deducted_prior_execution=True ) @@ -299,9 +290,7 @@ def test_sstore_refund_cap_exact_equality( *exactly*, the boundary between the cap binding and not binding. The full refund applies and ``cumulative_gas_used`` is ``gross - accrued``. """ - gas_costs = fork.gas_costs() quotient = fork.max_refund_quotient() - accrued = gas_costs.REFUND_STORAGE_CLEAR clear = Op.SSTORE.with_metadata( key_warm=False, @@ -309,6 +298,7 @@ def test_sstore_refund_cap_exact_equality( current_value=1, new_value=0, )(0, 0) + accrued = clear.refund(fork) intrinsic = fork.transaction_intrinsic_cost_calculator()( return_cost_deducted_prior_execution=True @@ -330,7 +320,6 @@ def test_sstore_refund_cap_exact_equality( code = clear + Op.JUMPDEST * num_jumpdest contract = pre.deploy_contract(code=code, storage={0: 1}) - assert code.refund(fork) == accrued gross = intrinsic + code.regular_cost(fork) + code.state_cost(fork) # Exact equality: the cap is neither under nor over the accrued refund. assert gross == target_gross diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_transient_storage_regression.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_transient_storage_regression.py index fc79ee51299..5716c9dc277 100644 --- a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_transient_storage_regression.py +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_transient_storage_regression.py @@ -46,28 +46,23 @@ def test_transient_storage_gas_unchanged( write repricing did not bleed into transient storage. """ gas_costs = fork.gas_costs() - very_low = gas_costs.VERY_LOW - - # Bare opcode costs: subtract the PUSH wrapper from each. - tload_bare = Op.TLOAD(0).gas_cost(fork) - 1 * very_low - tstore_bare = Op.TSTORE(0, 1).gas_cost(fork) - 2 * very_low - - assert tload_bare == gas_costs.OPCODE_TLOAD == 100 - assert tstore_bare == gas_costs.OPCODE_TSTORE == 100 - # Guard against over-eager repricing: transient write must not have - # been folded into the (repriced) persistent cold write cost. + # Guard against over-eager repricing: the transient write must not + # have been folded into the (repriced) persistent cold write cost. assert gas_costs.OPCODE_TSTORE != gas_costs.COLD_STORAGE_WRITE - # Measure TSTORE then TLOAD of the same transient slot in one frame. + # Measure TSTORE then TLOAD of the same transient slot in one frame, + # subtracting the PUSH wrapper so the stored value is the bare opcode + # cost. + push_cost = Op.PUSH1(0).regular_cost(fork) tstore_code = CodeGasMeasure( code=Op.TSTORE(0, 1), - overhead_cost=2 * very_low, + overhead_cost=2 * push_cost, extra_stack_items=0, sstore_key=0, ) tload_code = CodeGasMeasure( code=Op.TLOAD(0), - overhead_cost=1 * very_low, + overhead_cost=1 * push_cost, extra_stack_items=1, sstore_key=1, ) @@ -75,7 +70,9 @@ def test_transient_storage_gas_unchanged( tx = Transaction(to=contract, sender=pre.fund_eoa()) - # Slot 0: measured TSTORE cost. Slot 1: measured TLOAD cost. + # Slot 0: measured TSTORE cost. Slot 1: measured TLOAD cost. Both must + # equal the fork's declared transient-storage opcode costs, which + # EIP-8038 leaves unchanged. post = { contract: Account( storage={ From e0ce65c82db289e07f01378491c1150fffb1edb4 Mon Sep 17 00:00:00 2001 From: spencer <spencer.taylor-brown@ethereum.org> Date: Tue, 28 Jul 2026 17:39:01 +0200 Subject: [PATCH 165/233] chore(tests): improve EIP-7981 coverage, checklist, and ref-spec pin (#3223) --- .../eip_checklist_external_coverage.txt | 3 + .../eip_checklist_not_applicable.txt | 13 + .../eip7981_increase_access_list_cost/spec.py | 13 +- .../test_access_list_cost.py | 99 ++++- .../test_eip_mainnet.py | 3 + .../test_floor_boundary_exact_balance.py | 2 + .../test_fork_transition.py | 377 ++++++++++++++++++ .../test_transaction_validity.py | 83 ++++ 8 files changed, 580 insertions(+), 13 deletions(-) create mode 100644 tests/amsterdam/eip7981_increase_access_list_cost/eip_checklist_external_coverage.txt create mode 100644 tests/amsterdam/eip7981_increase_access_list_cost/eip_checklist_not_applicable.txt create mode 100644 tests/amsterdam/eip7981_increase_access_list_cost/test_fork_transition.py diff --git a/tests/amsterdam/eip7981_increase_access_list_cost/eip_checklist_external_coverage.txt b/tests/amsterdam/eip7981_increase_access_list_cost/eip_checklist_external_coverage.txt new file mode 100644 index 00000000000..33168127d48 --- /dev/null +++ b/tests/amsterdam/eip7981_increase_access_list_cost/eip_checklist_external_coverage.txt @@ -0,0 +1,3 @@ +general/code_coverage/eels = Covered in EELS +general/code_coverage/test_coverage = Run locally +general/code_coverage/missed_lines = No missed lines; the EIP adds only the access list token accounting in calculate_intrinsic_cost, fully exercised by this suite diff --git a/tests/amsterdam/eip7981_increase_access_list_cost/eip_checklist_not_applicable.txt b/tests/amsterdam/eip7981_increase_access_list_cost/eip_checklist_not_applicable.txt new file mode 100644 index 00000000000..3d9be76c246 --- /dev/null +++ b/tests/amsterdam/eip7981_increase_access_list_cost/eip_checklist_not_applicable.txt @@ -0,0 +1,13 @@ +general/code_coverage/second_client = Optional +opcode = EIP does not introduce or modify an opcode +precompile = EIP does not introduce a precompile +removed_precompile = EIP does not remove a precompile +system_contract = EIP does not introduce a system contract +transaction_type = EIP does not introduce a new transaction type +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_refunds_changes = EIP does not introduce any gas refund changes +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 modifies the existing intrinsic and floor gas validity constraints rather than introducing a new one +block_level_constraint = EIP does not introduce a block-level validation constraint diff --git a/tests/amsterdam/eip7981_increase_access_list_cost/spec.py b/tests/amsterdam/eip7981_increase_access_list_cost/spec.py index 92f945e1bc1..2bc5a72f1f4 100644 --- a/tests/amsterdam/eip7981_increase_access_list_cost/spec.py +++ b/tests/amsterdam/eip7981_increase_access_list_cost/spec.py @@ -12,16 +12,5 @@ class ReferenceSpec: ref_spec_7981 = ReferenceSpec( - "EIPS/eip-7981.md", "954963fb6315dffadd9c40d48e4dae313e20cff5" + "EIPS/eip-7981.md", "747b78c0edfdf04e9e2933ad1bec592d3318e1d9" ) - - -# Constants -class Spec: - """ - Parameters from the EIP-7981 specifications as defined at - https://eips.ethereum.org/EIPS/eip-7981. - """ - - ACCESS_LIST_ADDRESS_COST = 2400 - ACCESS_LIST_STORAGE_KEY_COST = 1900 diff --git a/tests/amsterdam/eip7981_increase_access_list_cost/test_access_list_cost.py b/tests/amsterdam/eip7981_increase_access_list_cost/test_access_list_cost.py index 411b96322ad..2d093e6d0bb 100644 --- a/tests/amsterdam/eip7981_increase_access_list_cost/test_access_list_cost.py +++ b/tests/amsterdam/eip7981_increase_access_list_cost/test_access_list_cost.py @@ -8,8 +8,10 @@ Address, Alloc, Bytes, + EIPChecklist, Fork, Hash, + Op, StateTestFiller, Transaction, TransactionReceipt, @@ -24,6 +26,7 @@ pytestmark = pytest.mark.valid_at("EIP7981") +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() @pytest.mark.with_all_tx_types(selector=lambda tx_type: tx_type >= 1) @pytest.mark.parametrize( "access_list,expected_floor_tokens", @@ -137,6 +140,7 @@ def test_access_list_token_calculation( ) +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() @pytest.mark.with_all_tx_types(selector=lambda tx_type: tx_type >= 1) @pytest.mark.parametrize( "access_list,tx_data", @@ -190,6 +194,7 @@ def test_access_list_floor_cost_with_calldata( ) +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() @pytest.mark.with_all_tx_types(selector=lambda tx_type: tx_type >= 1) @pytest.mark.parametrize( "access_list", @@ -220,7 +225,8 @@ def test_large_access_list_cost( Test gas costs for large access lists. With EIP-7981, large access lists should incur: - 1. Storage access costs (2400 per address + 1900 per key) + 1. Storage access costs (per-address and per-key charges, priced + at the fork's cold access costs since EIP-8038) 2. Data footprint costs (16 per floor token) """ state_test( @@ -230,6 +236,7 @@ def test_large_access_list_cost( ) +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() @pytest.mark.with_all_tx_types(selector=lambda tx_type: tx_type >= 1) @pytest.mark.parametrize( "access_list", @@ -264,3 +271,93 @@ def test_duplicate_access_list_entries( post={}, tx=tx, ) + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +@pytest.mark.with_all_tx_types(selector=lambda tx_type: tx_type in (1, 2)) +@pytest.mark.parametrize( + "access_list", + [ + pytest.param( + [ + AccessList( + address=Address(1), + storage_keys=[Hash(0), Hash(1)], + ) + ], + id="single_address_two_keys", + ), + ], +) +def test_access_list_data_cost_with_execution( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + tx_type: int, + access_list: list, +) -> None: + """ + Test that the access list data cost is charged when execution gas + dominates. + + EIP-7981 charges the access list data cost as a flat surcharge on + both sides of the gas-used max, so it is paid in full even when the + intrinsic-plus-execution side exceeds the floor. An implementation + that only counts access list bytes toward the floor undercharges + exactly the surcharge here, failing the receipt pin. + """ + gas_costs = fork.gas_costs() + surcharge = ( + calculate_access_list_floor_tokens(access_list) + * gas_costs.TX_DATA_TOKEN_FLOOR + ) + # One gas per JUMPDEST, sized so the execution gas strictly exceeds + # the surcharge under test. + code = Op.JUMPDEST * (surcharge + 1) + Op.STOP + contract = pre.deploy_contract(code) + execution_gas = code.gas_cost(fork) + assert execution_gas > surcharge + + intrinsic_cost_calculator = fork.transaction_intrinsic_cost_calculator() + intrinsic_gas = intrinsic_cost_calculator( + access_list=access_list, + return_cost_deducted_prior_execution=True, + ) + # The surcharge must be an explicit term of the intrinsic cost, on + # top of the per-entry access charges of the same transaction + # without an access list. + entry_charges = ( + gas_costs.TX_ACCESS_LIST_ADDRESS + + 2 * gas_costs.TX_ACCESS_LIST_STORAGE_KEY + ) + intrinsic_gas_no_access_list = intrinsic_cost_calculator( + return_cost_deducted_prior_execution=True, + ) + assert ( + intrinsic_gas + == intrinsic_gas_no_access_list + entry_charges + surcharge + ) + + # The execution side must win the max against the floor. + expected_gas_used = intrinsic_gas + execution_gas + floor_gas = fork.transaction_data_floor_cost_calculator()( + data=b"", access_list=access_list + ) + assert expected_gas_used > floor_gas + + tx = Transaction( + ty=tx_type, + sender=pre.fund_eoa(), + to=contract, + access_list=access_list, + gas_limit=expected_gas_used, + expected_receipt=TransactionReceipt( + cumulative_gas_used=expected_gas_used + ), + ) + + state_test( + pre=pre, + post={}, + tx=tx, + ) diff --git a/tests/amsterdam/eip7981_increase_access_list_cost/test_eip_mainnet.py b/tests/amsterdam/eip7981_increase_access_list_cost/test_eip_mainnet.py index 8a20c3c6bed..3090870f27f 100644 --- a/tests/amsterdam/eip7981_increase_access_list_cost/test_eip_mainnet.py +++ b/tests/amsterdam/eip7981_increase_access_list_cost/test_eip_mainnet.py @@ -7,6 +7,7 @@ AccessList, Address, Alloc, + EIPChecklist, Hash, StateTestFiller, Transaction, @@ -20,6 +21,7 @@ pytestmark = [pytest.mark.valid_at("EIP7981"), pytest.mark.mainnet] +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() @pytest.mark.with_all_tx_types(selector=lambda tx_type: tx_type >= 1) @pytest.mark.parametrize( "access_list", @@ -92,6 +94,7 @@ def test_access_list_gas_cost( ) +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() @pytest.mark.with_all_tx_types(selector=lambda tx_type: tx_type >= 1) @pytest.mark.parametrize( "access_list", diff --git a/tests/amsterdam/eip7981_increase_access_list_cost/test_floor_boundary_exact_balance.py b/tests/amsterdam/eip7981_increase_access_list_cost/test_floor_boundary_exact_balance.py index c961dfe18ab..a6ddbcc7747 100644 --- a/tests/amsterdam/eip7981_increase_access_list_cost/test_floor_boundary_exact_balance.py +++ b/tests/amsterdam/eip7981_increase_access_list_cost/test_floor_boundary_exact_balance.py @@ -9,6 +9,7 @@ Address, Alloc, Bytes, + EIPChecklist, Fork, Hash, StateTestFiller, @@ -24,6 +25,7 @@ pytestmark = pytest.mark.valid_at("EIP7981") +@EIPChecklist.GasCostChanges.Test.OutOfGas() @pytest.mark.exception_test @pytest.mark.parametrize( "tx_type", diff --git a/tests/amsterdam/eip7981_increase_access_list_cost/test_fork_transition.py b/tests/amsterdam/eip7981_increase_access_list_cost/test_fork_transition.py new file mode 100644 index 00000000000..62a899b346a --- /dev/null +++ b/tests/amsterdam/eip7981_increase_access_list_cost/test_fork_transition.py @@ -0,0 +1,377 @@ +""" +Fork-transition tests for [EIP-7981: Increase Access List Cost](https://eips.ethereum.org/EIPS/eip-7981). + +EIP-7981 adds a data-footprint surcharge for access list bytes at the +Amsterdam fork boundary. These tests send identical access-list +transactions in a pre-fork block and a post-fork block (straddling the +transition timestamp) and pin the per-transaction gas paid on each side, +plus the validity flip for gas limits inside the uplift gap. + +The post-fork intrinsic composes three repricings; the hand-derived +expectations below keep each term explicit so the EIP-7981 surcharge is +individually visible: + +- EIP-2780 decomposes the flat pre-fork `TX_BASE` into the lowered base + plus the `COLD_ACCOUNT_ACCESS` recipient charge. +- EIP-8038 reprices the per-address and per-storage-key access list + charges to the fork's cold access costs. +- EIP-7981 adds four floor tokens per access list byte, charged at + `TX_DATA_TOKEN_FLOOR` in the intrinsic and counted in the floor. +""" + +import pytest +from execution_testing import ( + AccessList, + Account, + Address, + Alloc, + Block, + BlockchainTestFiller, + EIPChecklist, + Hash, + Transaction, + TransactionException, + TransactionReceipt, + TransitionFork, +) + +from .helpers import calculate_access_list_floor_tokens +from .spec import ref_spec_7981 + +REFERENCE_SPEC_GIT_PATH = ref_spec_7981.git_path +REFERENCE_SPEC_VERSION = ref_spec_7981.version + +pytestmark = pytest.mark.valid_at_transition_to("EIP7981") + +# Transition forks switch at timestamp 15_000. +PRE_FORK_TIMESTAMP = 14_999 +POST_FORK_TIMESTAMP = 15_000 + + +def access_list_shape(addresses: int, keys_per_address: int) -> list: + """Build an access list with the given shape.""" + return [ + AccessList( + address=Address(i + 1), + storage_keys=[Hash(k) for k in range(keys_per_address)], + ) + for i in range(addresses) + ] + + +@EIPChecklist.GasCostChanges.Test.ForkTransition.Before() +@EIPChecklist.GasCostChanges.Test.ForkTransition.After() +@pytest.mark.parametrize( + "addresses,keys_per_address", + [ + pytest.param(1, 0, id="single_address_no_keys"), + pytest.param(1, 2, id="single_address_two_keys"), + pytest.param(2, 3, id="two_addresses_three_keys_each"), + ], +) +def test_access_list_intrinsic_across_amsterdam_transition( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: TransitionFork, + addresses: int, + keys_per_address: int, +) -> None: + """ + Pin the access list intrinsic change across the Amsterdam boundary. + + The same access-list transaction shape is sent in a pre-fork block + (flat base plus the EIP-2930 per-entry charges, no data cost) and a + post-fork block (decomposed base, repriced entries, plus the + EIP-7981 byte surcharge). Each block uses a distinct sender so its + post-tx balance pins the fork-appropriate intrinsic; the recipient + is an existing EOA, so no EVM bytecode runs and `gas_used` equals + the intrinsic exactly. + + The per-fork intrinsic returned by the calculator is also checked + against a hand-derived per-EIP decomposition, so a calculator + regression fails here with a clear message rather than only as a + downstream balance mismatch. + """ + gas_price = 1_000_000_000 + access_list = access_list_shape(addresses, keys_per_address) + total_keys = addresses * keys_per_address + + pre_fork = fork.fork_at(timestamp=PRE_FORK_TIMESTAMP) + post_fork = fork.fork_at(timestamp=POST_FORK_TIMESTAMP) + pre_costs = pre_fork.gas_costs() + post_costs = post_fork.gas_costs() + + # Pre-fork: flat base plus the EIP-2930 per-entry charges; access + # list bytes carry no data cost. + expected_pre = ( + pre_costs.TX_BASE + + addresses * pre_costs.TX_ACCESS_LIST_ADDRESS + + total_keys * pre_costs.TX_ACCESS_LIST_STORAGE_KEY + ) + # Post-fork: EIP-2780 decomposed base and recipient charge, EIP-8038 + # repriced entry charges, and the EIP-7981 byte surcharge. + surcharge = ( + calculate_access_list_floor_tokens(access_list) + * post_costs.TX_DATA_TOKEN_FLOOR + ) + expected_post = ( + post_costs.TX_BASE + + post_costs.COLD_ACCOUNT_ACCESS + + addresses * post_costs.TX_ACCESS_LIST_ADDRESS + + total_keys * post_costs.TX_ACCESS_LIST_STORAGE_KEY + + surcharge + ) + + timestamps = [PRE_FORK_TIMESTAMP, POST_FORK_TIMESTAMP] + expected_intrinsics = [expected_pre, expected_post] + blocks = [] + post: dict[Address, Account] = {} + + for timestamp, expected_intrinsic in zip( + timestamps, expected_intrinsics, strict=True + ): + sub_fork = fork.fork_at(timestamp=timestamp) + intrinsic_gas = sub_fork.transaction_intrinsic_cost_calculator()( + access_list=access_list, + return_cost_deducted_prior_execution=True, + ) + assert intrinsic_gas == expected_intrinsic, ( + f"intrinsic at timestamp {timestamp} ({sub_fork}) is " + f"{intrinsic_gas}, expected {expected_intrinsic}" + ) + # The intrinsic side must bind so gas_used equals the intrinsic. + floor_gas = sub_fork.transaction_data_floor_cost_calculator()( + data=b"", access_list=access_list + ) + assert floor_gas <= intrinsic_gas + + sender_initial_balance = 10**18 + sender = pre.fund_eoa(sender_initial_balance) + target = pre.fund_eoa(amount=0) + + tx = Transaction( + sender=sender, + to=target, + gas_limit=intrinsic_gas, + gas_price=gas_price, + access_list=access_list, + ) + blocks.append(Block(timestamp=timestamp, txs=[tx])) + + post[sender] = Account( + nonce=1, + balance=sender_initial_balance - intrinsic_gas * gas_price, + ) + + blockchain_test(pre=pre, blocks=blocks, post=post) + + +@EIPChecklist.ModifiedTransactionValidityConstraint.Test.ForkTransition.AcceptedBeforeFork() +@EIPChecklist.ModifiedTransactionValidityConstraint.Test.ForkTransition.RejectedBeforeFork() +@EIPChecklist.ModifiedTransactionValidityConstraint.Test.ForkTransition.AcceptedAfterFork() +@EIPChecklist.ModifiedTransactionValidityConstraint.Test.ForkTransition.RejectedAfterFork() +@pytest.mark.exception_test +def test_access_list_validity_across_amsterdam_transition( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: TransitionFork, +) -> None: + """ + Pin the intrinsic-validity flip across the Amsterdam boundary. + + For an access list with one address and two storage keys the + EIP-7981 byte surcharge (plus the EIP-8038 entry repricing) outgrows + the EIP-2780 base reduction, so the post-fork intrinsic is strictly + higher than the pre-fork one. Off-by-one gas limits around each + fork's requirement then pin all four boundary behaviors: + + 1. Pre-fork block with `gas_limit` one below the pre-fork intrinsic + is rejected. + 2. Pre-fork block accepts both the exact pre-fork intrinsic and a + gas limit one below the post-fork intrinsic (the new constraint + is not met, the old one is). + 3. Post-fork block with that same one-below gas limit is rejected. + 4. Post-fork block with the exact post-fork intrinsic is accepted. + """ + gas_price = 1_000_000_000 + access_list = access_list_shape(addresses=1, keys_per_address=2) + + pre_fork = fork.fork_at(timestamp=PRE_FORK_TIMESTAMP) + post_fork = fork.fork_at(timestamp=POST_FORK_TIMESTAMP) + + intrinsic_pre = pre_fork.transaction_intrinsic_cost_calculator()( + access_list=access_list, + return_cost_deducted_prior_execution=True, + ) + intrinsic_post = post_fork.transaction_intrinsic_cost_calculator()( + access_list=access_list, + return_cost_deducted_prior_execution=True, + ) + # The gas limit straddling the boundary must be valid pre-fork and + # invalid post-fork. + straddle_gas_limit = intrinsic_post - 1 + assert intrinsic_pre <= straddle_gas_limit, ( + f"access list shape does not discriminate: pre-fork intrinsic " + f"{intrinsic_pre} exceeds post-fork intrinsic - 1 " + f"({straddle_gas_limit})" + ) + # The intrinsic side must bind over the floor on both forks. + for sub_fork, intrinsic in [ + (pre_fork, intrinsic_pre), + (post_fork, intrinsic_post), + ]: + floor_gas = sub_fork.transaction_data_floor_cost_calculator()( + data=b"", access_list=access_list + ) + assert floor_gas <= intrinsic + + def make_tx( + gas_limit: int, error: TransactionException | None = None + ) -> Transaction: + return Transaction( + sender=pre.fund_eoa(), + to=pre.fund_eoa(amount=0), + gas_limit=gas_limit, + gas_price=gas_price, + access_list=access_list, + error=error, + ) + + blocks = [ + # 1. Rejected before the fork: below the pre-fork intrinsic. + Block( + timestamp=PRE_FORK_TIMESTAMP, + txs=[ + make_tx( + intrinsic_pre - 1, + error=TransactionException.INTRINSIC_GAS_TOO_LOW, + ) + ], + exception=TransactionException.INTRINSIC_GAS_TOO_LOW, + ), + # 2. Accepted before the fork: the exact pre-fork intrinsic and + # the straddling gas limit that the post-fork rules will reject. + Block( + timestamp=PRE_FORK_TIMESTAMP, + txs=[make_tx(intrinsic_pre), make_tx(straddle_gas_limit)], + ), + # 3. Rejected after the fork: the same straddling gas limit. + Block( + timestamp=POST_FORK_TIMESTAMP, + txs=[ + make_tx( + straddle_gas_limit, + error=TransactionException.INTRINSIC_GAS_TOO_LOW, + ) + ], + exception=TransactionException.INTRINSIC_GAS_TOO_LOW, + ), + # 4. Accepted after the fork: the exact post-fork intrinsic. + Block( + timestamp=POST_FORK_TIMESTAMP, + txs=[make_tx(intrinsic_post)], + ), + ] + + blockchain_test(pre=pre, blocks=blocks, post={}) + + +@EIPChecklist.GasCostChanges.Test.ForkTransition.Before() +@EIPChecklist.GasCostChanges.Test.ForkTransition.After() +def test_access_list_floor_across_amsterdam_transition( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: TransitionFork, +) -> None: + """ + Pin access list bytes entering the calldata floor at the boundary. + + A calldata-heavy access-list transaction binds the floor on both + sides of the transition: pre-fork the floor counts calldata bytes + only (access list bytes contribute nothing), post-fork the EIP-7981 + tokens raise it. Each block's gas limit is pinned to its fork's + floor, so the billed gas equals the floor exactly and an + implementation that mistimes the floor change fails the receipt and + balance pins. + """ + gas_price = 1_000_000_000 + # Sized so the floor dominates the intrinsic on both sides + # (asserted below): each non-zero byte adds 40 - 16 = 24 gas of + # floor headroom pre-fork and 64 - 16 = 48 post-fork, outgrowing + # the per-entry access charges that only the intrinsic carries. + data = b"\x01" * 400 + access_list = access_list_shape(addresses=1, keys_per_address=2) + + pre_fork = fork.fork_at(timestamp=PRE_FORK_TIMESTAMP) + post_fork = fork.fork_at(timestamp=POST_FORK_TIMESTAMP) + pre_costs = pre_fork.gas_costs() + post_costs = post_fork.gas_costs() + + # Pre-fork (EIP-7623): content-weighted calldata tokens only; the + # access list bytes contribute nothing to the floor. + pre_tokens = len(data) * 4 + expected_pre = int( + pre_costs.TX_BASE + pre_tokens * pre_costs.TX_DATA_TOKEN_FLOOR + ) + assert pre_fork.transaction_data_floor_cost_calculator()( + data=data, access_list=access_list + ) == pre_fork.transaction_data_floor_cost_calculator()(data=data) + # Post-fork: uniform calldata tokens plus the EIP-7981 access list + # tokens, anchored on the EIP-2780 decomposed base. + post_tokens = len(data) * int( + post_costs.TX_DATA_TOKEN_STANDARD + ) + calculate_access_list_floor_tokens(access_list) + expected_post = int( + post_costs.TX_BASE + + post_costs.COLD_ACCOUNT_ACCESS + + post_tokens * post_costs.TX_DATA_TOKEN_FLOOR + ) + + timestamps = [PRE_FORK_TIMESTAMP, POST_FORK_TIMESTAMP] + expected_floors = [expected_pre, expected_post] + blocks = [] + post: dict[Address, Account] = {} + + for timestamp, expected_floor in zip( + timestamps, expected_floors, strict=True + ): + sub_fork = fork.fork_at(timestamp=timestamp) + floor_gas = sub_fork.transaction_data_floor_cost_calculator()( + data=data, access_list=access_list + ) + assert floor_gas == expected_floor, ( + f"floor at timestamp {timestamp} ({sub_fork}) is {floor_gas}, " + f"expected {expected_floor}" + ) + # The floor must dominate the intrinsic so the transaction is + # billed exactly the floor. + intrinsic_gas = sub_fork.transaction_intrinsic_cost_calculator()( + calldata=data, + access_list=access_list, + return_cost_deducted_prior_execution=True, + ) + assert floor_gas > intrinsic_gas, ( + f"floor {floor_gas} does not dominate intrinsic " + f"{intrinsic_gas} at timestamp {timestamp} ({sub_fork})" + ) + + sender_initial_balance = 10**18 + sender = pre.fund_eoa(sender_initial_balance) + + tx = Transaction( + sender=sender, + to=pre.fund_eoa(amount=0), + data=data, + gas_limit=floor_gas, + gas_price=gas_price, + access_list=access_list, + expected_receipt=TransactionReceipt(cumulative_gas_used=floor_gas), + ) + blocks.append(Block(timestamp=timestamp, txs=[tx])) + + post[sender] = Account( + nonce=1, + balance=sender_initial_balance - floor_gas * gas_price, + ) + + blockchain_test(pre=pre, blocks=blocks, post=post) diff --git a/tests/amsterdam/eip7981_increase_access_list_cost/test_transaction_validity.py b/tests/amsterdam/eip7981_increase_access_list_cost/test_transaction_validity.py index 97ddba5bc45..dbeed9ded11 100644 --- a/tests/amsterdam/eip7981_increase_access_list_cost/test_transaction_validity.py +++ b/tests/amsterdam/eip7981_increase_access_list_cost/test_transaction_validity.py @@ -5,12 +5,17 @@ import pytest from execution_testing import ( AccessList, + Account, Address, Alloc, Bytes, + EIPChecklist, + Fork, Hash, StateTestFiller, Transaction, + TransactionException, + compute_create_address, ) from .spec import ref_spec_7981 @@ -21,6 +26,7 @@ pytestmark = pytest.mark.valid_at("EIP7981") +@EIPChecklist.GasCostChanges.Test.OutOfGas() @pytest.mark.exception_test @pytest.mark.with_all_tx_types(selector=lambda tx_type: tx_type >= 1) @pytest.mark.parametrize( @@ -76,6 +82,7 @@ def test_insufficient_gas_for_access_list( ) +@EIPChecklist.GasCostChanges.Test.OutOfGas() @pytest.mark.exception_test @pytest.mark.with_all_tx_types(selector=lambda tx_type: tx_type >= 1) @pytest.mark.parametrize( @@ -122,6 +129,7 @@ def test_floor_cost_validation_with_access_list( ) +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() @pytest.mark.with_all_tx_types(selector=lambda tx_type: tx_type >= 1) @pytest.mark.parametrize( "access_list,tx_gas_delta", @@ -168,6 +176,7 @@ def test_valid_gas_limits_with_access_list( ) +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() @pytest.mark.with_all_tx_types(selector=lambda tx_type: tx_type >= 1) @pytest.mark.parametrize( "access_list,tx_data", @@ -223,6 +232,7 @@ def test_mixed_zero_nonzero_bytes_floor_cost( ) +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() @pytest.mark.parametrize( "tx_type,access_list", [ @@ -277,3 +287,76 @@ def test_transactions_without_access_list( post={}, tx=tx, ) + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +@EIPChecklist.GasCostChanges.Test.OutOfGas() +@pytest.mark.with_all_tx_types(selector=lambda tx_type: tx_type in (1, 2)) +@pytest.mark.parametrize( + "valid", + [ + pytest.param(True, id="exact_gas"), + pytest.param( + False, + id="insufficient_gas_by_one", + marks=pytest.mark.exception_test, + ), + ], +) +def test_contract_creation_with_access_list( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + tx_type: int, + valid: bool, +) -> None: + """ + Test the intrinsic boundary of a contract-creating transaction with + an access list. + + The EIP-7981 access list data cost stacks on top of the creation + intrinsic (creation access and init code charges). The created + account's state charge is applied at the top frame, after intrinsic + validation, so the exact-gas arm funds it separately while the + off-by-one arm pins the intrinsic requirement alone. + """ + access_list = [ + AccessList(address=Address(1), storage_keys=[Hash(0), Hash(1)]) + ] + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( + contract_creation=True, + access_list=access_list, + return_cost_deducted_prior_execution=True, + ) + floor_gas = fork.transaction_data_floor_cost_calculator()( + data=b"", access_list=access_list, contract_creation=True + ) + assert floor_gas <= intrinsic_gas + + sender = pre.fund_eoa() + post: dict = {} + if valid: + gas_limit = intrinsic_gas + fork.transaction_top_frame_state_gas( + contract_creation=True + ) + error = None + created = compute_create_address(address=sender, nonce=sender.nonce) + post[created] = Account(nonce=1, code=b"") + else: + gas_limit = intrinsic_gas - 1 + error = TransactionException.INTRINSIC_GAS_TOO_LOW + + tx = Transaction( + ty=tx_type, + sender=sender, + to=None, + access_list=access_list, + gas_limit=gas_limit, + error=error, + ) + + state_test( + pre=pre, + post=post, + tx=tx, + ) From 44d2b9cbd028b48f13e6ebf2635f977141cc397b Mon Sep 17 00:00:00 2001 From: Tamaghna Choudhuri <tamaghna.official@gmail.com> Date: Wed, 29 Jul 2026 03:43:29 +0530 Subject: [PATCH 166/233] feat(test-types): Introduce SSZ model into base types (#3196) * init ssz-aware testing:wq * add progressive support * add scaffolding * refactor * fix pypy problem * nits * add ssz none functionality Claude-Session: https://claude.ai/code/session_01LxVkSo6sGs8bsNz4yD8KiJ * resolve reviews --- packages/testing/pyproject.toml | 1 + .../src/execution_testing/base_types/ssz.py | 955 +++++++++++++++++ .../base_types/tests/test_ssz.py | 960 ++++++++++++++++++ .../execution_testing/tools/ssz_vectors.py | 476 +++++++++ .../tools/tests/test_ssz_vectors.py | 296 ++++++ pyproject.toml | 5 + uv.lock | 11 + 7 files changed, 2704 insertions(+) create mode 100644 packages/testing/src/execution_testing/base_types/ssz.py create mode 100644 packages/testing/src/execution_testing/base_types/tests/test_ssz.py create mode 100644 packages/testing/src/execution_testing/tools/ssz_vectors.py create mode 100644 packages/testing/src/execution_testing/tools/tests/test_ssz_vectors.py diff --git a/packages/testing/pyproject.toml b/packages/testing/pyproject.toml index 11577956ef7..bca724ebe52 100644 --- a/packages/testing/pyproject.toml +++ b/packages/testing/pyproject.toml @@ -54,6 +54,7 @@ dependencies = [ "tenacity>=9.0.0,<10", "Jinja2>=3,<4", "ijson>=3.3,<4", + "eth-remerkleable==0.1.31", ] [project.urls] diff --git a/packages/testing/src/execution_testing/base_types/ssz.py b/packages/testing/src/execution_testing/base_types/ssz.py new file mode 100644 index 00000000000..4605aaf3147 --- /dev/null +++ b/packages/testing/src/execution_testing/base_types/ssz.py @@ -0,0 +1,955 @@ +""" +Native SSZ serialization for base_types models. + +Declare a container once as a pydantic SszModel, in the ordinary base types, +and get SSZ encoding, hash_tree_root, and defaults for them. + +Each field's SSZ type is derived from its Python type, so the model stays the +single source of truth: + +* fixed byte types self-describe by byte_length + (Hash -> ByteVector[32], Address -> ByteVector[20]); +* the width ints defined here carry it (Uint64 -> uint64); +* bool -> boolean; a nested SszModel -> Container; +* the only facts a Python type cannot express -- list / vector / bytelist / bit + caps -- ride as Annotated markers (ssz_list(N), ssz_vector(N), byte_list(N), + bitvector(N), bitlist(N)). Element types are derived from the annotation, so + a marker carries only the cap/length, never a duplicated element spec. + +Each field's SSZ type is described by an SszType value (SszUint, SszByteList, +SszList, SszContainer, ...). The engine turns that into a remerkleable type +on demand (build_ssz_type) and delegates the actual encoding, merkleization, +and default (zero) values to it. + +Fork-scoped models: one model can serve every fork. Future-fork fields are +declared T | None (None == absent in older forks, omitted from JSON), and a +__ssz_schema__ = SszForkSchema(...) table beside the fields says which fork +introduces what, in canonical SSZ order (the class body's order stays free +for JSON). Such models require fork= on encode / hash_tree_root / decode / +ssz_default / describe_schema / build_ssz_type +""" + +from dataclasses import dataclass +from functools import lru_cache +from types import UnionType +from typing import ( + Any, + ClassVar, + List, + Mapping, + Optional, + Sequence, + Tuple, + Type, + TypeVar, + Union, + get_args, + get_origin, +) + +from remerkleable.basic import ( + boolean, + uint8, + uint16, + uint32, + uint64, + uint128, + uint256, +) +from remerkleable.bitfields import Bitlist as RmkBitlist +from remerkleable.bitfields import Bitvector as RmkBitvector +from remerkleable.byte_arrays import ByteList, ByteVector +from remerkleable.complex import Container +from remerkleable.complex import List as RmkList +from remerkleable.complex import Vector as RmkVector +from remerkleable.core import View +from remerkleable.progressive import ( + ProgressiveBitlist as RmkProgressiveBitlist, +) +from remerkleable.progressive import ProgressiveContainer +from remerkleable.progressive import ProgressiveList as RmkProgressiveList + +from .base_types import Bytes, FixedSizeBytes, HexNumber +from .pydantic import CamelModel + +_UINTS = { + 8: uint8, + 16: uint16, + 32: uint32, + 64: uint64, + 128: uint128, + 256: uint256, +} + + +class SszType: + """A description of a field's SSZ type.""" + + +@dataclass(frozen=True) +class SszUint(SszType): + """An unsigned integer of bits width (8/16/32/64/128/256).""" + + bits: int + + +@dataclass(frozen=True) +class SszByteVector(SszType): + """A fixed-length byte vector of length bytes.""" + + length: int + + +@dataclass(frozen=True) +class SszByteList(SszType): + """A variable byte list capped at limit bytes.""" + + limit: int + + +@dataclass(frozen=True) +class SszList(SszType): + """A list of element capped at limit items.""" + + element: SszType + limit: int + + +@dataclass(frozen=True) +class SszVector(SszType): + """A fixed-length vector of exactly length element items.""" + + element: SszType + length: int + + +@dataclass(frozen=True) +class SszBitvector(SszType): + """A fixed-length bit vector of length bits.""" + + length: int + + +@dataclass(frozen=True) +class SszBitlist(SszType): + """A variable bit list capped at limit bits.""" + + limit: int + + +@dataclass(frozen=True) +class SszBool(SszType): + """The SSZ boolean type.""" + + +@dataclass(frozen=True) +class SszContainer(SszType): + """A nested container backed by pydantic model.""" + + model: Type["SszModel"] + + +@dataclass(frozen=True) +class SszProgressiveList(SszType): + """An uncapped progressive list of element (EIP-7916).""" + + element: SszType + + +@dataclass(frozen=True) +class SszProgressiveBitlist(SszType): + """An uncapped progressive bit list.""" + + +@dataclass(frozen=True) +class SszProgressiveContainer(SszType): + """A forward-compatible progressive container backed by model.""" + + model: Type["SszModel"] + + +_M = TypeVar("_M", bound="SszModel") + + +@dataclass(frozen=True, eq=False) +class SszForkSchema: + """ + Fork-scoped field sets for a fork-evolving container. + + One model declares every fork's fields; this table says which fields + exist at which fork and in which SSZ order. base holds the fields of + base_fork; appended maps each later fork (in order) to the fields it + adds, which must be declared Optional (T | None) on the model. + + Fork keys are opaque strings: base_types knows nothing about forks; + """ + + base_fork: str + base: Tuple[str, ...] + appended: Mapping[str, Tuple[str, ...]] + + def forks(self) -> Tuple[str, ...]: + """Every known fork key, oldest first.""" + return (self.base_fork, *self.appended) + + def fields_at(self, fork: str) -> Tuple[str, ...]: + """The SSZ field names of fork, in canonical order.""" + if fork == self.base_fork: + return self.base + if fork not in self.appended: + raise TypeError( + f"unknown fork {fork!r}; known forks: {self.forks()}" + ) + names = list(self.base) + for key, fields in self.appended.items(): + names.extend(fields) + if key == fork: + break + return tuple(names) + + def all_fields(self) -> Tuple[str, ...]: + """Every field of the newest fork, in canonical order.""" + keys = self.forks() + return self.fields_at(keys[-1]) + + +def _unwrap_optional(annotation: Any) -> Tuple[Any, bool]: + """Strip a T | None union; return.""" + if get_origin(annotation) in (Union, UnionType): + args = [a for a in get_args(annotation) if a is not type(None)] + if len(args) != 1: + raise TypeError( + f"only T | None unions are supported: {annotation!r}" + ) + return args[0], True + return annotation, False + + +def _is_fork_optional(model_cls: Type["SszModel"], name: str) -> bool: + ann = model_cls.model_fields[name].annotation + return _unwrap_optional(ann)[1] + + +def _is_ssz_excluded(model_cls: Type["SszModel"], name: str) -> bool: + """Whether name carries the ssz_exclude() marker (JSON-only).""" + metadata = model_cls.model_fields[name].metadata + return any(isinstance(m, _SszExclude) for m in metadata) + + +def _included_fields(model_cls: Type["SszModel"]) -> Tuple[str, ...]: + """Every SSZ-participating field, in declaration order.""" + return tuple( + name + for name in model_cls.model_fields + if not _is_ssz_excluded(model_cls, name) + ) + + +def _check_fork_schema(model_cls: Type["SszModel"]) -> None: + """ + Validate a model's __ssz_schema__ against its fields, at class + definition. + + Optional (T | None) fields require a schema naming their fork; the + schema must cover exactly the model's SSZ fields; base fields must + be required and appended fields Optional with a None default -- so + a mis-declared container fails at import. + """ + schema = model_cls.__ssz_schema__ + included = _included_fields(model_cls) + optional = { + name for name in included if _is_fork_optional(model_cls, name) + } + progressive = globals().get("ProgressiveModel") + if progressive is not None and issubclass(model_cls, progressive): + if schema is not None: + raise TypeError( + f"{model_cls.__name__}: __ssz_schema__ is not supported " + f"on ProgressiveModel (progressive containers evolve via " + f"__active_fields__)" + ) + if optional: + raise TypeError( + f"{model_cls.__name__}: T | None fields are not supported " + f"on ProgressiveModel; reserve future slots with 0s in " + f"__active_fields__ instead" + ) + return + if schema is None: + if optional: + raise TypeError( + f"{model_cls.__name__} has fork-optional fields " + f"{sorted(optional)} but no __ssz_schema__ declaring " + f"which fork introduces them" + ) + return + all_names = schema.all_fields() + dupes = sorted({n for n in all_names if all_names.count(n) > 1}) + if dupes: + raise TypeError( + f"{model_cls.__name__}.__ssz_schema__ names fields more than " + f"once: {dupes}" + ) + declared = set(all_names) + fields = set(included) + if declared != fields: + raise TypeError( + f"{model_cls.__name__}.__ssz_schema__ does not match the " + f"model: schema-only={sorted(declared - fields)} " + f"model-only={sorted(fields - declared)}" + ) + appended = fields - set(schema.base) + if optional != appended: + raise TypeError( + f"{model_cls.__name__}: appended fields must be T | None and " + f"base fields required; non-optional appended=" + f"{sorted(appended - optional)} optional base=" + f"{sorted(optional - appended)}" + ) + no_default = sorted( + name for name in appended if model_cls.model_fields[name].is_required() + ) + if no_default: + raise TypeError( + f"{model_cls.__name__}: appended fields must default to None " + f"(decode of older forks constructs without them): " + f"{no_default}" + ) + + +class _Marker: + """Base for cap-only Annotated markers resolved by spec_of.""" + + +@dataclass(frozen=True) +class _ListCap(_Marker): + limit: int + + +@dataclass(frozen=True) +class _VectorLen(_Marker): + length: int + + +@dataclass(frozen=True) +class _ProgressiveListMark(_Marker): + pass + + +@dataclass(frozen=True) +class _SszExclude(_Marker): + pass + + +def byte_list(limit: int) -> SszByteList: + """Annotate a Bytes field as a capped SSZ byte list.""" + return SszByteList(limit) + + +def ssz_list(limit: int) -> _ListCap: + """Annotate a list[...] field as a capped SSZ list.""" + return _ListCap(limit) + + +def ssz_vector(length: int) -> _VectorLen: + """Annotate a list[...] field as a fixed SSZ vector.""" + return _VectorLen(length) + + +def bitvector(length: int) -> SszBitvector: + """Annotate a list[bool] field as a fixed SSZ bit vector.""" + return SszBitvector(length) + + +def bitlist(limit: int) -> SszBitlist: + """Annotate a list[bool] field as a capped SSZ bit list.""" + return SszBitlist(limit) + + +def progressive_list() -> _ProgressiveListMark: + """Annotate a list[...] field as an uncapped progressive list.""" + return _ProgressiveListMark() + + +def progressive_bitlist() -> SszProgressiveBitlist: + """Annotate a list[bool] field as an uncapped progressive bit list.""" + return SszProgressiveBitlist() + + +def ssz_exclude() -> _SszExclude: + """ + Annotate a field as JSON-only: SSZ ignores it entirely. + + Such a field must carry a default: decode never sees it on the wire + and so cannot reconstruct it. + """ + return _SszExclude() + + +class SszModel(CamelModel): + """ + A pydantic model whose fields carry SSZ types. + + Every field must resolve to an SszType, or be excluded from SSZ with + an ssz_exclude() marker (JSON-only fields); each Annotated marker + must be consistent with the field's Python type. + """ + + __ssz_schema__: ClassVar[Optional[SszForkSchema]] = None + + @classmethod + def __pydantic_init_subclass__(cls, **kwargs: Any) -> None: + """Validate every field resolves to a consistent SSZ type.""" + super().__pydantic_init_subclass__(**kwargs) + for name in cls.model_fields: + if _is_ssz_excluded(cls, name): + if cls.model_fields[name].is_required(): + raise TypeError( + f"{cls.__name__}.{name} is SSZ-excluded but has " + f"no default; decode cannot reconstruct it" + ) + continue + spec_of(cls, name) # raises TypeError on unmapped/inconsistent + _check_fork_schema(cls) + + +class ProgressiveModel(SszModel): + """ + A forward-compatible progressive container. + + __active_fields__ is the active-field bitvector; it defaults to all SSZ + fields active. A 0 marks a reserved gap with no declared field, so new + fields can be slotted in later without shifting existing roots -- the + SSZ fields fill the 1 positions in order. + """ + + __active_fields__: ClassVar[Sequence[int]] = () + + @classmethod + def __pydantic_init_subclass__(cls, **kwargs: Any) -> None: + """Check the active-field bitvector agrees with the field count.""" + super().__pydantic_init_subclass__(**kwargs) + active = cls.__active_fields__ + included = len(_included_fields(cls)) + if active and sum(active) != included: + raise TypeError( + f"{cls.__name__}.__active_fields__ has {sum(active)} active " + f"entries but the container declares " + f"{included} SSZ fields" + ) + + +def _marker_in(metadata: Any) -> Any: + """The first SSZ marker in metadata.""" + return next( + (m for m in metadata if isinstance(m, (SszType, _Marker))), None + ) + + +def _spec_for_type_bare(annotation: Any) -> SszType: + """Derive the SSZ type of a plain Python type.""" + ssz = getattr(annotation, "__ssz__", None) + if isinstance(ssz, SszType): + return ssz + if isinstance(annotation, type): + if issubclass(annotation, FixedSizeBytes): + return SszByteVector(annotation.byte_length) + if issubclass(annotation, ProgressiveModel): + return SszProgressiveContainer(annotation) + if issubclass(annotation, SszModel): + return SszContainer(annotation) + if annotation is bool: + return SszBool() + raise TypeError(f"no SSZ type for {annotation!r}") + + +def _spec_for_type(annotation: Any) -> SszType: + """Resolve an SSZ type, honoring an inner Annotated marker if present.""" + meta = getattr(annotation, "__metadata__", None) + if meta is not None: + return _resolve(_marker_in(meta), annotation.__origin__) + return _spec_for_type_bare(annotation) + + +def _element_of(annotation: Any, ctx: str) -> SszType: + """Resolve the element SSZ type of a list[...] annotation.""" + if get_origin(annotation) not in (list, List): + raise TypeError(f"{ctx} requires a list[...] field: {annotation!r}") + args = get_args(annotation) + if len(args) != 1: + raise TypeError(f"{ctx} needs a single list element type") + return _spec_for_type(args[0]) + + +def _resolve(marker: Any, annotation: Any) -> SszType: + """ + Resolve a field/element into an SSZ type. + + Cap-only markers (ssz_list/ssz_vector/progressive_list) derive their + element from the annotation; complete markers (byte_list/bitvector/...) + are checked for consistency with it. Byte-list elements are expressed as + Annotated[Bytes, byte_list(N)] so the inner cap lives on the element. + """ + if marker is None: + return _spec_for_type(annotation) + if isinstance(marker, _ListCap): + return SszList(_element_of(annotation, "ssz_list"), marker.limit) + if isinstance(marker, _VectorLen): + return SszVector(_element_of(annotation, "ssz_vector"), marker.length) + if isinstance(marker, _ProgressiveListMark): + return SszProgressiveList(_element_of(annotation, "progressive_list")) + if isinstance(marker, SszByteList): + is_bytes = isinstance(annotation, type) and issubclass( + annotation, Bytes + ) + if not is_bytes: + raise TypeError( + f"byte_list requires a Bytes field/element: {annotation!r}" + ) + return marker + if isinstance(marker, (SszBitvector, SszBitlist, SszProgressiveBitlist)): + if not isinstance(_element_of(annotation, "bit markers"), SszBool): + raise TypeError( + f"bit markers require a list[bool] field: {annotation!r}" + ) + return marker + if isinstance(marker, _SszExclude): + raise TypeError( + f"field is ssz_exclude()d; it has no SSZ type: {annotation!r}" + ) + # Raw SszType instances (SszUint, SszContainer, ...) as markers would + # bypass the consistency checks above; only the marker helpers are + # supported. + raise TypeError( + f"unsupported Annotated SSZ marker {marker!r}; use the marker " + f"helpers (ssz_list, ssz_vector, byte_list, bitvector, ...)" + ) + + +@lru_cache(maxsize=None) +def spec_of(model_cls: Type["SszModel"], name: str) -> SszType: + """ + The resolved SSZ type of a field. + + An Annotated marker takes precedence over the bare type; cap-only markers + derive their element from the annotation, and every marker is checked for + consistency with it. A T | None union resolves to T's SSZ type -- the + None arm means "absent in older forks" (see SszForkSchema), which is a + schema fact, not an SSZ type. Cached per (model_cls, name). + """ + field = model_cls.model_fields[name] + annotation, _ = _unwrap_optional(field.annotation) + if _is_ssz_excluded(model_cls, name): + raise TypeError( + f"{model_cls.__name__}.{name} is SSZ-excluded; it has no " + f"SSZ type: {annotation!r}" + ) + return _resolve(_marker_in(field.metadata), annotation) + + +def _rmk_type(spec: SszType, fork: Optional[str] = None) -> Type[View]: + if isinstance(spec, SszUint): + return _UINTS[spec.bits] + if isinstance(spec, SszByteVector): + return ByteVector[spec.length] + if isinstance(spec, SszByteList): + return ByteList[spec.limit] + if isinstance(spec, SszList): + return RmkList[_rmk_type(spec.element, fork), spec.limit] + if isinstance(spec, SszVector): + return RmkVector[_rmk_type(spec.element, fork), spec.length] + if isinstance(spec, SszBitvector): + return RmkBitvector[spec.length] + if isinstance(spec, SszBitlist): + return RmkBitlist[spec.limit] + if isinstance(spec, SszProgressiveList): + return RmkProgressiveList[_rmk_type(spec.element, fork)] + if isinstance(spec, SszProgressiveBitlist): + return RmkProgressiveBitlist + if isinstance(spec, (SszContainer, SszProgressiveContainer)): + return build_ssz_type(spec.model, _nested_fork(spec.model, fork)) + if isinstance(spec, SszBool): + return boolean + raise TypeError(f"unhandled SSZ type {spec!r}") + + +def _active_fields(model_cls: Type["SszModel"]) -> Sequence[int]: + """The active-field bitvector, defaulting to every SSZ field active.""" + declared = getattr(model_cls, "__active_fields__", ()) + return declared if declared else [1] * len(_included_fields(model_cls)) + + +def _nested_fork( + model_cls: Type["SszModel"], fork: Optional[str] +) -> Optional[str]: + """ + The fork a nested container is projected at. + + One fork propagates down the whole value tree: everything inside one + message is at the same chain fork, so a fork-scoped nested model + inherits the outer fork, while a + complete nested model takes no fork at all. + """ + return fork if model_cls.__ssz_schema__ is not None else None + + +def _schema_fields( + model_cls: Type["SszModel"], fork: Optional[str] +) -> Tuple[str, ...]: + """ + The SSZ field names of model_cls, in canonical order. + + A fork-scoped model requires fork and gets + that fork's fields in the schema's order; a complete model forbids + fork and gets every non-excluded field in declaration order. + """ + schema = model_cls.__ssz_schema__ + if schema is None: + if fork is not None: + raise TypeError( + f"{model_cls.__name__} is not fork-scoped; do not pass fork" + ) + return _included_fields(model_cls) + if fork is None: + raise TypeError( + f"{model_cls.__name__} is fork-scoped; pass fork= " + f"(one of {schema.forks()})" + ) + return schema.fields_at(fork) + + +def ssz_fields( + model_cls: Type["SszModel"], fork: Optional[str] = None +) -> Tuple[str, ...]: + """ + The SSZ field names of model_cls, in canonical (wire) order. + + The public twin of the engine's internal field selection: callers + (vector generators, fixtures tooling) can enumerate exactly the + fields a model encodes -- per fork for fork-scoped models. + """ + return _schema_fields(model_cls, fork) + + +def _check_populated( + model: "SszModel", names: Tuple[str, ...], fork: str +) -> None: + """Raise unless the populated fields exactly match the fork schema.""" + missing = [n for n in names if getattr(model, n) is None] + unexpected = sorted( + n + for n in _included_fields(type(model)) + if n not in names and getattr(model, n) is not None + ) + if missing or unexpected: + raise TypeError( + f"{type(model).__name__} does not fit the {fork!r} SSZ schema: " + f"missing={missing} unexpected={unexpected}; " + f"refusing to drop data" + ) + + +def build_ssz_type( + model_cls: Type["SszModel"], fork: Optional[str] = None +) -> Type[Container]: + """ + Build the remerkleable container type mirroring model_cls. + + Cached per (class object, fork) -- distinct same-named models get + distinct types, and each fork of a fork-scoped model gets its own + genuinely distinct container (different offsets and merkle shape). + """ + return _build_ssz_type(model_cls, fork) + + +@lru_cache(maxsize=None) +def _build_ssz_type( + model_cls: Type["SszModel"], fork: Optional[str] +) -> Type[Container]: + names = _schema_fields(model_cls, fork) + anns = {name: _rmk_type(spec_of(model_cls, name), fork) for name in names} + if issubclass(model_cls, ProgressiveModel): + base: Any = ProgressiveContainer( + active_fields=list(_active_fields(model_cls)) + ) + else: + base = Container + cls_name = model_cls.__name__ + (fork if fork else "") + return type(cls_name, (base,), {"__annotations__": anns}) + + +def _to_rmk(spec: SszType, value: Any, fork: Optional[str] = None) -> Any: + if isinstance(spec, (SszContainer, SszProgressiveContainer)): + return _rmk_instance(value, _nested_fork(spec.model, fork)) + if isinstance(spec, (SszList, SszVector, SszProgressiveList)): + return [_to_rmk(spec.element, v, fork) for v in value] + if isinstance(spec, (SszBitvector, SszBitlist, SszProgressiveBitlist)): + return list(value) + return value # scalar / byte-vector / byte-list: remerkleable coerces + + +def _rmk_instance(model: "SszModel", fork: Optional[str] = None) -> Container: + model_cls: Type[SszModel] = type(model) + names = _schema_fields(model_cls, fork) + if fork is not None: + _check_populated(model, names, fork) + container = build_ssz_type(model_cls, fork) + values = { + name: _to_rmk(spec_of(model_cls, name), getattr(model, name), fork) + for name in names + } + return container(**values) + + +def _to_py(spec: SszType, value: Any, fork: Optional[str] = None) -> Any: + if isinstance(spec, (SszContainer, SszProgressiveContainer)): + nested = _nested_fork(spec.model, fork) + return _view_to_model( + spec.model, value, _schema_fields(spec.model, nested), nested + ) + if isinstance(spec, (SszList, SszVector, SszProgressiveList)): + return [_to_py(spec.element, v, fork) for v in value] + if isinstance(spec, (SszBitvector, SszBitlist, SszProgressiveBitlist)): + return [bool(b) for b in value] + if isinstance(spec, (SszByteVector, SszByteList)): + return bytes(value) + if isinstance(spec, SszUint): + return int(value) + if isinstance(spec, SszBool): + return bool(value) + raise TypeError(f"unhandled SSZ type {spec!r}") + + +def _view_to_model( + model_cls: Type[_M], + view: Container, + names: Optional[Tuple[str, ...]] = None, + fork: Optional[str] = None, +) -> _M: + if names is None: + names = _included_fields(model_cls) + # Fields beyond `names` (older-fork decodes) keep their None default. + return model_cls( + **{ + name: _to_py(spec_of(model_cls, name), getattr(view, name), fork) + for name in names + } + ) + + +def default_value(spec: SszType, fork: Optional[str] = None) -> Any: + """Return the SSZ default (zero) value for spec as a pydantic value.""" + if isinstance(spec, SszUint): + return 0 + if isinstance(spec, SszByteVector): + return b"\x00" * spec.length + if isinstance( + spec, + (SszByteList, SszList, SszBitlist, SszProgressiveList), + ): + return [] + if isinstance(spec, SszProgressiveBitlist): + return [] + if isinstance(spec, SszVector): + # A fresh value per slot: container defaults are mutable, so a shared + # [x] * n would alias one instance across every position. + return [default_value(spec.element, fork) for _ in range(spec.length)] + if isinstance(spec, SszBitvector): + return [False] * spec.length + if isinstance(spec, (SszContainer, SszProgressiveContainer)): + return ssz_default(spec.model, _nested_fork(spec.model, fork)) + if isinstance(spec, SszBool): + return False + raise TypeError(f"no default for SSZ type {spec!r}") + + +def ssz_default(model_cls: Type[_M], fork: Optional[str] = None) -> _M: + """ + Build the SSZ default (all-zero) instance of model_cls. + + Fork-scoped models require fork; fields beyond it stay None. + """ + return model_cls( + **{ + name: default_value(spec_of(model_cls, name), fork) + for name in _schema_fields(model_cls, fork) + } + ) + + +def describe_type(spec: SszType) -> str: + """Render an SSZ type as text (uint64, List[T, N], ...).""" + if isinstance(spec, SszUint): + return f"uint{spec.bits}" + if isinstance(spec, SszByteVector): + return f"ByteVector[{spec.length}]" + if isinstance(spec, SszByteList): + return f"ByteList[{spec.limit}]" + if isinstance(spec, SszList): + return f"List[{describe_type(spec.element)}, {spec.limit}]" + if isinstance(spec, SszVector): + return f"Vector[{describe_type(spec.element)}, {spec.length}]" + if isinstance(spec, SszBitvector): + return f"Bitvector[{spec.length}]" + if isinstance(spec, SszBitlist): + return f"Bitlist[{spec.limit}]" + if isinstance(spec, SszProgressiveList): + return f"ProgressiveList[{describe_type(spec.element)}]" + if isinstance(spec, SszProgressiveBitlist): + return "ProgressiveBitlist" + if isinstance(spec, SszContainer): + return spec.model.__name__ + if isinstance(spec, SszProgressiveContainer): + return f"Progressive[{spec.model.__name__}]" + if isinstance(spec, SszBool): + return "boolean" + raise TypeError(f"unhandled SSZ type {spec!r}") + + +def describe_schema( + model_cls: Type["SszModel"], fork: Optional[str] = None +) -> str: + """ + Render the resolved SSZ layout, one 'field: type' line per field. + + Fork-scoped models require fork and render that fork's projection. + """ + title = model_cls.__name__ + (f" @ {fork}" if fork else "") + lines = [f"{title}:"] + for name in _schema_fields(model_cls, fork): + lines.append(f" {name}: {describe_type(spec_of(model_cls, name))}") + return "\n".join(lines) + + +def encode(model: "SszModel", fork: Optional[str] = None) -> bytes: + """ + Return the SSZ wire bytes of model. + + A fork-scoped model requires fork and is + checked against that fork's schema before encoding. + """ + return _rmk_instance(model, fork).encode_bytes() + + +def hash_tree_root(model: "SszModel", fork: Optional[str] = None) -> bytes: + """ + Return the 32-byte SSZ hash_tree_root of model. + + Fork-scoped models require fork, exactly as encode does. + """ + return bytes(_rmk_instance(model, fork).hash_tree_root()) + + +def decode(model_cls: Type[_M], data: bytes, fork: Optional[str] = None) -> _M: + """ + Decode SSZ data into an instance of model_cls. + + For a fork-scoped model, data is decoded as fork's container and + fields beyond that fork come back as None. + """ + view = build_ssz_type(model_cls, fork).decode_bytes(data) + return _view_to_model( + model_cls, view, _schema_fields(model_cls, fork), fork + ) + + +# width-carrying integer types (base_types.HexNumber underneath) +class _SizedUint(HexNumber): + """ + A width-checked unsigned integer. + """ + + __bits__: ClassVar[int] = 0 + + def __new__(cls, input_number: Any) -> "_SizedUint": + """Create the integer, enforcing 0 <= value < 2**bits.""" + value = super().__new__(cls, input_number) + if not 0 <= int(value) < (1 << cls.__bits__): + raise ValueError(f"{cls.__name__} out of range: {int(value)}") + return value + + +class Uint8(_SizedUint): + """An 8-bit unsigned integer.""" + + __bits__: ClassVar[int] = 8 + __ssz__: ClassVar[SszType] = SszUint(8) + + +class Uint16(_SizedUint): + """A 16-bit unsigned integer.""" + + __bits__: ClassVar[int] = 16 + __ssz__: ClassVar[SszType] = SszUint(16) + + +class Uint32(_SizedUint): + """A 32-bit unsigned integer.""" + + __bits__: ClassVar[int] = 32 + __ssz__: ClassVar[SszType] = SszUint(32) + + +class Uint64(_SizedUint): + """A 64-bit unsigned integer.""" + + __bits__: ClassVar[int] = 64 + __ssz__: ClassVar[SszType] = SszUint(64) + + +class Uint128(_SizedUint): + """A 128-bit unsigned integer.""" + + __bits__: ClassVar[int] = 128 + __ssz__: ClassVar[SszType] = SszUint(128) + + +class Uint256(_SizedUint): + """A 256-bit unsigned integer.""" + + __bits__: ClassVar[int] = 256 + __ssz__: ClassVar[SszType] = SszUint(256) + + +__all__ = [ + "ProgressiveModel", + "SszBitlist", + "SszBitvector", + "SszBool", + "SszByteList", + "SszByteVector", + "SszContainer", + "SszForkSchema", + "SszList", + "SszModel", + "SszProgressiveBitlist", + "SszProgressiveContainer", + "SszProgressiveList", + "SszType", + "SszUint", + "SszVector", + "Uint128", + "Uint16", + "Uint256", + "Uint32", + "Uint64", + "Uint8", + "bitlist", + "bitvector", + "byte_list", + "build_ssz_type", + "decode", + "default_value", + "describe_schema", + "describe_type", + "encode", + "hash_tree_root", + "progressive_bitlist", + "progressive_list", + "spec_of", + "ssz_default", + "ssz_exclude", + "ssz_fields", + "ssz_list", + "ssz_vector", +] diff --git a/packages/testing/src/execution_testing/base_types/tests/test_ssz.py b/packages/testing/src/execution_testing/base_types/tests/test_ssz.py new file mode 100644 index 00000000000..7b1b8ee649f --- /dev/null +++ b/packages/testing/src/execution_testing/base_types/tests/test_ssz.py @@ -0,0 +1,960 @@ +""" +Tests for SSZ support in base_types. +""" + +from typing import Annotated, Callable, List, Optional, Tuple + +import pytest +from pydantic import ValidationError +from remerkleable.basic import boolean, uint8, uint64, uint256 +from remerkleable.bitfields import Bitlist as RmkBitlist +from remerkleable.bitfields import Bitvector as RmkBitvector +from remerkleable.byte_arrays import ByteList, ByteVector +from remerkleable.complex import Container +from remerkleable.complex import List as RmkList +from remerkleable.complex import Vector as RmkVector +from remerkleable.progressive import ( + ProgressiveBitlist as RmkProgressiveBitlist, +) +from remerkleable.progressive import ProgressiveContainer +from remerkleable.progressive import ProgressiveList as RmkProgressiveList + +from execution_testing.base_types import Address, Bloom, Bytes, Hash +from execution_testing.base_types.ssz import ( + ProgressiveModel, + SszForkSchema, + SszModel, + SszUint, + Uint8, + Uint16, + Uint32, + Uint64, + Uint128, + Uint256, + bitlist, + bitvector, + build_ssz_type, + byte_list, + decode, + describe_schema, + encode, + hash_tree_root, + progressive_bitlist, + progressive_list, + spec_of, + ssz_default, + ssz_exclude, + ssz_fields, + ssz_list, + ssz_vector, +) + +MAX_EXTRA = 32 +MAX_BYTES_PER_TX = 2**30 +MAX_TXS = 2**20 +MAX_WITHDRAWALS = 16 +CELLS = 128 +BITS = [i % 3 == 0 for i in range(CELLS)] + + +class Withdrawal(SszModel): + """A pydantic model declared to check the SSZ machinery.""" + + index: Uint64 + validator_index: Uint64 + address: Address + amount: Uint64 + + +class ExecutionPayload(SszModel): + """An Amsterdam-shaped payload exercising every field kind.""" + + parent_hash: Hash + fee_recipient: Address + state_root: Hash + logs_bloom: Bloom + block_number: Uint64 + base_fee_per_gas: Uint256 + extra_data: Annotated[Bytes, byte_list(MAX_EXTRA)] + transactions: Annotated[ + List[Annotated[Bytes, byte_list(MAX_BYTES_PER_TX)]], + ssz_list(MAX_TXS), + ] + withdrawals: Annotated[List[Withdrawal], ssz_list(MAX_WITHDRAWALS)] + + +class Status(SszModel): + """A boolean and a fixed bit vector.""" + + ok: bool + columns: Annotated[List[bool], bitvector(CELLS)] + + +class Committee(SszModel): + """A fixed Vector[uint64, N] and a variable Bitlist[N].""" + + seats: Annotated[List[Uint64], ssz_vector(3)] + flags: Annotated[List[bool], bitlist(8)] + + +class Ballot(SszModel): + """An uncapped progressive bit list.""" + + votes: Annotated[List[bool], progressive_bitlist()] + + +class Prog(ProgressiveModel): + """EIP-7916 progressive container with a progressive list.""" + + a: Uint64 + b: Uint8 + items: Annotated[List[Uint64], progressive_list()] + + +class GapProg(ProgressiveModel): + """Two fields around a reserved (0) middle slot.""" + + __active_fields__ = [1, 0, 1] + + a: Uint64 + c: Uint64 + + +class MixedProg(ProgressiveModel): + """A progressive container carrying a JSON-only (excluded) field.""" + + a: Uint64 + note: Annotated[str, ssz_exclude()] = "json-only" + c: Uint64 + + +class ForkedPayload(SszModel): + """One model for every fork.""" + + parent_hash: Hash + blob_gas_used: Uint64 | None = None + block_number: Uint64 + transactions: Annotated[ # JSON order differs from SSZ. + List[Annotated[Bytes, byte_list(MAX_BYTES_PER_TX)]], + ssz_list(MAX_TXS), + ] + withdrawals: ( + Annotated[List[Withdrawal], ssz_list(MAX_WITHDRAWALS)] | None + ) = None + + __ssz_schema__ = SszForkSchema( + base_fork="Paris", + base=("parent_hash", "block_number", "transactions"), + appended={ + "Shanghai": ("withdrawals",), + "Cancun": ("blob_gas_used",), + }, + ) + + +class Mixed(SszModel): + """An SSZ container carrying a JSON-only (excluded) field.""" + + a: Uint64 + note: Annotated[str, ssz_exclude()] = "json-only" + + +class RefWithdrawal(Container): + """Hand-written twin of Withdrawal.""" + + index: uint64 + validator_index: uint64 + address: ByteVector[20] + amount: uint64 + + +class RefPayload(Container): + """Hand-written twin of ExecutionPayload.""" + + parent_hash: ByteVector[32] + fee_recipient: ByteVector[20] + state_root: ByteVector[32] + logs_bloom: ByteVector[256] + block_number: uint64 + base_fee_per_gas: uint256 + extra_data: ByteList[MAX_EXTRA] + transactions: RmkList[ByteList[MAX_BYTES_PER_TX], MAX_TXS] + withdrawals: RmkList[RefWithdrawal, MAX_WITHDRAWALS] + + +class RefStatus(Container): + """Hand-written twin of Status.""" + + ok: boolean + columns: RmkBitvector[CELLS] + + +class RefCommittee(Container): + """Hand-written twin of Committee.""" + + seats: RmkVector[uint64, 3] + flags: RmkBitlist[8] + + +class RefBallot(Container): + """Hand-written twin of Ballot.""" + + votes: RmkProgressiveBitlist + + +class RefProg(ProgressiveContainer(active_fields=[1, 1, 1])): # type: ignore[misc] + """Hand-written twin of Prog.""" + + a: uint64 + b: uint8 + items: RmkProgressiveList[uint64] + + +class RefGapProg(ProgressiveContainer(active_fields=[1, 0, 1])): # type: ignore[misc] + """Hand-written twin of GapProg.""" + + a: uint64 + c: uint64 + + +class RefMixedProg(ProgressiveContainer(active_fields=[1, 1])): # type: ignore[misc] + """Hand-written twin of MixedProg: the excluded field takes no slot.""" + + a: uint64 + c: uint64 + + +class RefForkedParis(Container): + """Hand-written twin of ForkedPayload at Paris.""" + + parent_hash: ByteVector[32] + block_number: uint64 + transactions: RmkList[ByteList[MAX_BYTES_PER_TX], MAX_TXS] + + +class RefForkedShanghai(Container): + """Hand-written twin of ForkedPayload at Shanghai.""" + + parent_hash: ByteVector[32] + block_number: uint64 + transactions: RmkList[ByteList[MAX_BYTES_PER_TX], MAX_TXS] + withdrawals: RmkList[RefWithdrawal, MAX_WITHDRAWALS] + + +class RefMixed(Container): # the excluded field simply does not exist + """Hand-written twin of Mixed (no excluded field).""" + + a: uint64 + + +def _withdrawal() -> Withdrawal: + return Withdrawal( + index=7, + validator_index=42, + address=Address(b"\x11" * 20), + amount=32_000_000_000, + ) + + +def _ref_withdrawal() -> Container: + return RefWithdrawal( + index=7, + validator_index=42, + address=b"\x11" * 20, + amount=32_000_000_000, + ) + + +def _payload() -> ExecutionPayload: + return ExecutionPayload( + parent_hash=Hash(b"\xaa" * 32), + fee_recipient=Address(b"\xbb" * 20), + state_root=Hash(b"\xcc" * 32), + logs_bloom=Bloom(b"\x00" * 256), + block_number=21_000_000, + base_fee_per_gas=10**18, + extra_data=Bytes(b"\xde\xad"), + transactions=[Bytes(b"\x02\xf8"), Bytes(b"\x03" * 5)], + withdrawals=[_withdrawal()], + ) + + +def _ref_payload() -> Container: + return RefPayload( + parent_hash=b"\xaa" * 32, + fee_recipient=b"\xbb" * 20, + state_root=b"\xcc" * 32, + logs_bloom=b"\x00" * 256, + block_number=21_000_000, + base_fee_per_gas=10**18, + extra_data=b"\xde\xad", + transactions=[b"\x02\xf8", b"\x03" * 5], + withdrawals=[_ref_withdrawal()], + ) + + +def _paris_payload() -> ForkedPayload: + return ForkedPayload( + parent_hash=Hash(b"\xaa" * 32), + block_number=100, + transactions=[Bytes(b"\x02\xf8")], + ) + + +def _shanghai_payload() -> ForkedPayload: + return ForkedPayload( + parent_hash=Hash(b"\xaa" * 32), + block_number=100, + transactions=[Bytes(b"\x02\xf8")], + withdrawals=[_withdrawal()], + ) + + +def assert_matches_reference( + model: SszModel, ref: Container, fork: Optional[str] = None +) -> None: + """ + Compare the engine against a hand-written remerkleable twin. + + The twin is the ground truth: everything observable must + """ + model_cls = type(model) + ref_cls = type(ref) + raw = encode(model, fork) + # populated instance: wire bytes + merkle root + assert raw == ref.encode_bytes() + assert hash_tree_root(model, fork) == bytes(ref.hash_tree_root()) + # decode round-trips losslessly, back to an equal pydantic model + restored = decode(model_cls, raw, fork) + assert encode(restored, fork) == raw + assert restored == model + # both sides agree on the zero value + zero = ssz_default(model_cls, fork) + assert encode(zero, fork) == ref_cls().encode_bytes() + assert hash_tree_root(zero, fork) == bytes(ref_cls().hash_tree_root()) + + +TWIN_CASES: List[ + Tuple[ + str, + Callable[[], SszModel], + Callable[[], Container], + Optional[str], + ] +] = [ + ("withdrawal", _withdrawal, _ref_withdrawal, None), + ("payload", _payload, _ref_payload, None), + ( + "bool-bitvector", + lambda: Status(ok=True, columns=BITS), + lambda: RefStatus(ok=True, columns=BITS), + None, + ), + ( + "vector-bitlist", + lambda: Committee(seats=[1, 2, 3], flags=[True, False, True]), + lambda: RefCommittee(seats=[1, 2, 3], flags=[True, False, True]), + None, + ), + ( + "progressive-bitlist", + lambda: Ballot(votes=[True, False, True, True]), + lambda: RefBallot(votes=[True, False, True, True]), + None, + ), + ( + "progressive", + lambda: Prog(a=5, b=9, items=[10, 20, 30]), + lambda: RefProg(a=5, b=9, items=[10, 20, 30]), + None, + ), + ( + "progressive-gap", + lambda: GapProg(a=1, c=3), + lambda: RefGapProg(a=1, c=3), + None, + ), + ( + "progressive-excluded", + lambda: MixedProg(a=1, c=3), + lambda: RefMixedProg(a=1, c=3), + None, + ), + ( + # default-valued excluded field: decode restores the default, so + # the harness's restored == model leg holds; the non-default case + # is covered by test_excluded_field_is_json_only. + "excluded-field", + lambda: Mixed(a=7), + lambda: RefMixed(a=7), + None, + ), + ( + "forked-paris", + _paris_payload, + lambda: RefForkedParis( + parent_hash=b"\xaa" * 32, + block_number=100, + transactions=[b"\x02\xf8"], + ), + "Paris", + ), + ( + "forked-shanghai", + _shanghai_payload, + lambda: RefForkedShanghai( + parent_hash=b"\xaa" * 32, + block_number=100, + transactions=[b"\x02\xf8"], + withdrawals=[_ref_withdrawal()], + ), + "Shanghai", + ), +] + + +@pytest.mark.parametrize( + "make_model,make_ref,fork", + [pytest.param(m, r, f, id=name) for name, m, r, f in TWIN_CASES], +) +def test_matches_remerkleable_reference( + make_model: Callable[[], SszModel], + make_ref: Callable[[], Container], + fork: Optional[str], +) -> None: + """Every model kind is byte-identical to its hand-written twin.""" + assert_matches_reference(make_model(), make_ref(), fork) + + +def test_full_payload_round_trips() -> None: + """A container with every field kind round-trips pydantic<->SSZ.""" + payload = _payload() + restored = decode(ExecutionPayload, encode(payload)) + assert restored.parent_hash == payload.parent_hash + assert int(restored.base_fee_per_gas) == 10**18 + assert [bytes(t) for t in restored.transactions] == [ + b"\x02\xf8", + b"\x03" * 5, + ] + assert int(restored.withdrawals[0].amount) == 32_000_000_000 + assert len(hash_tree_root(payload)) == 32 + + +def test_ssz_default_matches_remerkleable_zero() -> None: + """ssz_default builds the SSZ zero value, like remerkleable's default.""" + zero = ssz_default(ExecutionPayload) + assert int(zero.block_number) == 0 + assert zero.transactions == [] + assert zero.withdrawals == [] + assert bytes(zero.parent_hash) == b"\x00" * 32 + # zero encodes identically to a freshly-defaulted remerkleable container + assert encode(zero) == build_ssz_type(ExecutionPayload)().encode_bytes() + + +def test_describe_schema_renders_every_field_kind() -> None: + """describe_schema renders the resolved SSZ type of each field.""" + schema = describe_schema(ExecutionPayload) + assert "block_number: uint64" in schema + assert "base_fee_per_gas: uint256" in schema + assert "parent_hash: ByteVector[32]" in schema + assert f"extra_data: ByteList[{MAX_EXTRA}]" in schema + assert ( + f"transactions: List[ByteList[{MAX_BYTES_PER_TX}], {MAX_TXS}]" + in schema + ) + assert f"withdrawals: List[Withdrawal, {MAX_WITHDRAWALS}]" in schema + # progressive kinds render their consensus-style names + assert "items: ProgressiveList[uint64]" in describe_schema(Prog) + assert "votes: ProgressiveBitlist" in describe_schema(Ballot) + + +def test_default_vector_of_container_has_independent_slots() -> None: + """A defaulted Vector-of-container has independent (non-aliased) slots.""" + + class Inner(SszModel): + x: Uint64 + + class Outer(SszModel): + items: Annotated[List[Inner], ssz_vector(3)] + + zero = ssz_default(Outer) + assert len(zero.items) == 3 + zero.items[0].x = Uint64(99) + # Mutating one slot must not bleed into its siblings. + assert int(zero.items[1].x) == 0 + assert int(zero.items[2].x) == 0 + + +def test_forked_model_json_omission_unchanged() -> None: + """The JSON leg keeps today's exclude_none single-model behavior.""" + dumped = _shanghai_payload().model_dump( + mode="json", by_alias=True, exclude_none=True + ) + assert "withdrawals" in dumped + assert "blobGasUsed" not in dumped # pre-Cancun: key simply absent + + +def test_forked_model_decode_fills_none() -> None: + """Decoding an older fork's bytes restores the one model with None.""" + shanghai = _shanghai_payload() + raw = encode(shanghai, fork="Shanghai") + restored = decode(ForkedPayload, raw, fork="Shanghai") + assert restored == shanghai + assert restored.blob_gas_used is None # beyond-fork field stays None + assert restored.withdrawals is not None + + +def test_fork_scoped_nested_in_complete_model_raises() -> None: + """ + A COMPLETE model cannot carry a fork-scoped one. + + Without a fork at the outer encode there is nothing to propagate to + the nested container, so the encode must refuse rather than pick a + schema silently. (Fork-scoped outer models propagate their fork; see + test_fork_propagates_to_nested_containers.) + """ + + class Wrapper(SszModel): + payload: ForkedPayload + + wrapper = Wrapper(payload=_shanghai_payload()) + with pytest.raises(TypeError, match="fork-scoped"): + encode(wrapper) + + +def test_fork_propagates_to_nested_containers() -> None: + """ + One fork projects the whole value tree (envelope contains payload). + + This is the general #793 shape: fork-evolving containers nest other + fork-evolving containers, and everything inside one message is at + the same chain fork. The outer fork= selects every nested projection. + """ + + class Envelope(SszModel): + payload: ForkedPayload + blob_count: Uint64 | None = None # Shanghai-era envelope field + + __ssz_schema__ = SszForkSchema( + base_fork="Paris", + base=("payload",), + appended={"Shanghai": ("blob_count",)}, + ) + + class RefEnvelopeShanghai(Container): + payload: RefForkedShanghai + blob_count: uint64 + + envelope = Envelope(payload=_shanghai_payload(), blob_count=3) + ref = RefEnvelopeShanghai( + payload=RefForkedShanghai( + parent_hash=b"\xaa" * 32, + block_number=100, + transactions=[b"\x02\xf8"], + withdrawals=[_ref_withdrawal()], + ), + blob_count=3, + ) + assert_matches_reference(envelope, ref, fork="Shanghai") + # decode restores both levels, beyond-fork fields None at each level + restored = decode(Envelope, encode(envelope, "Shanghai"), fork="Shanghai") + assert restored.payload.blob_gas_used is None + # a nested payload that does not fit the propagated fork still raises + paris_inside = Envelope(payload=_paris_payload(), blob_count=1) + with pytest.raises(TypeError, match="missing=\\['withdrawals'\\]"): + encode(paris_inside, fork="Shanghai") + + +def test_forked_model_describe_schema_per_fork() -> None: + """describe_schema renders each fork's projection in SSZ order.""" + paris = describe_schema(ForkedPayload, fork="Paris") + cancun = describe_schema(ForkedPayload, fork="Cancun") + assert "blob_gas_used" not in paris + assert cancun.splitlines()[-1].strip() == "blob_gas_used: uint64" + # SSZ order comes from the schema tuples, not the class body: the + # model declares blob_gas_used second, but it encodes LAST. + assert cancun.splitlines()[1].strip() == "parent_hash: ByteVector[32]" + + +def _bad_vector_marker_on_scalar() -> None: + class Bad(SszModel): + seats: Annotated[Uint64, ssz_vector(3)] # not a list + + +def _bad_byte_list_on_int() -> None: + class Bad(SszModel): + data: Annotated[Uint64, byte_list(8)] # not Bytes + + +def _bad_bit_marker_on_ints() -> None: + class Bad(SszModel): + flags: Annotated[List[Uint64], bitlist(8)] # not list[bool] + + +def _bad_raw_ssz_type_marker() -> None: + class Bad(SszModel): + x: Annotated[Uint64, SszUint(32)] # raw SszType, not a helper + + +def _bad_unmapped_str() -> None: + class Bad(SszModel): + s: str # no SSZ mapping and not excluded + + +def _bad_bare_bytes() -> None: + class Bad(SszModel): + data: Bytes # variable bytes need a byte_list cap + + +def _bad_bare_list() -> None: + class Bad(SszModel): + items: List[Uint64] # lists need a cap/length marker + + +def _bad_multi_arm_union() -> None: + class Bad(SszModel): + x: Uint64 | Uint8 | None = None # only T | None supported + + +def _bad_optional_without_schema() -> None: + class Bad(SszModel): + a: Uint64 + b: Uint64 | None = None # optional but no schema + + +def _bad_schema_field_typo() -> None: + class Bad(SszModel): + a: Uint64 + b: Uint64 | None = None + + __ssz_schema__ = SszForkSchema( + base_fork="Paris", + base=("a",), + appended={"Shanghai": ("typo",)}, + ) + + +def _bad_required_appended() -> None: + class Bad(SszModel): + a: Uint64 + b: Uint64 # appended but not optional + + __ssz_schema__ = SszForkSchema( + base_fork="Paris", + base=("a",), + appended={"Shanghai": ("b",)}, + ) + + +def _bad_optional_base() -> None: + class Bad(SszModel): + a: Uint64 + b: Uint64 | None = None # optional but declared in base + + __ssz_schema__ = SszForkSchema( + base_fork="Paris", + base=("a", "b"), + appended={}, + ) + + +def _bad_appended_no_default() -> None: + class Bad(SszModel): + a: Uint64 + b: Uint64 | None # optional type but NO None default + + __ssz_schema__ = SszForkSchema( + base_fork="Paris", + base=("a",), + appended={"Shanghai": ("b",)}, + ) + + +def _bad_duplicate_schema_names() -> None: + class Bad(SszModel): + a: Uint64 + b: Uint64 | None = None + + __ssz_schema__ = SszForkSchema( + base_fork="Paris", + base=("a", "a"), + appended={"Shanghai": ("b",)}, + ) + + +def _bad_required_excluded() -> None: + class Bad(SszModel): + a: Uint64 + note: Annotated[str, ssz_exclude()] # excluded but required + + +def _bad_progressive_with_schema() -> None: + class Bad(ProgressiveModel): + a: Uint64 + + __ssz_schema__ = SszForkSchema( + base_fork="Paris", base=("a",), appended={} + ) + + +def _bad_progressive_with_optional() -> None: + class Bad(ProgressiveModel): + a: Uint64 + b: Uint64 | None = None + + +def _bad_progressive_active_count() -> None: + class Bad(ProgressiveModel): + __active_fields__ = [1, 1] # two active, three declared fields + + a: Uint64 + b: Uint64 + c: Uint64 + + +def _bad_progressive_active_counts_excluded() -> None: + # An excluded field takes no slot, so the third 1 has no field to + # fill it: caught here rather than inside remerkleable at first + # build_ssz_type. + class Bad(ProgressiveModel): + __active_fields__ = [1, 1, 1] # three active, two SSZ fields + + a: Uint64 + note: Annotated[str, ssz_exclude()] = "json-only" + c: Uint64 + + +BAD_DECLARATIONS: List[Tuple[str, Callable[[], None], str]] = [ + ("vector-on-scalar", _bad_vector_marker_on_scalar, "requires a list"), + ("byte-list-on-int", _bad_byte_list_on_int, "byte_list requires"), + ("bits-on-ints", _bad_bit_marker_on_ints, "list\\[bool\\]"), + ("raw-marker", _bad_raw_ssz_type_marker, "unsupported Annotated"), + ("unmapped-str", _bad_unmapped_str, "no SSZ type"), + ("bare-bytes", _bad_bare_bytes, "no SSZ type"), + ("bare-list", _bad_bare_list, "no SSZ type"), + ("multi-arm-union", _bad_multi_arm_union, "only T \\| None"), + ("optional-no-schema", _bad_optional_without_schema, "no __ssz_schema__"), + ("schema-typo", _bad_schema_field_typo, "does not match the model"), + ("required-appended", _bad_required_appended, "must be T \\| None"), + ("optional-base", _bad_optional_base, "optional base"), + ("appended-no-default", _bad_appended_no_default, "default to None"), + ("dup-schema-names", _bad_duplicate_schema_names, "more than once"), + ("required-excluded", _bad_required_excluded, "no default"), + ("progressive-schema", _bad_progressive_with_schema, "not supported"), + ("progressive-optional", _bad_progressive_with_optional, "not supported"), + ("progressive-count", _bad_progressive_active_count, "active"), + ( + "progressive-count-excluded", + _bad_progressive_active_counts_excluded, + "3 active entries but the container declares 2 SSZ fields", + ), +] + + +@pytest.mark.parametrize( + "define,match", + [pytest.param(fn, match, id=name) for name, fn, match in BAD_DECLARATIONS], +) +def test_bad_declaration_fails_at_import( + define: Callable[[], None], match: str +) -> None: + """Every mis-declared container fails at class definition, named.""" + with pytest.raises(TypeError, match=match): + define() + + +STRICTNESS: List[Tuple[str, Optional[str], str]] = [ + ("bare-encode", None, "fork-scoped"), + ("older-fork", "Paris", "unexpected=\\['withdrawals'\\]"), + ("newer-fork", "Cancun", "missing=\\['blob_gas_used'\\]"), + ("unknown-fork", "Osaka", "unknown fork"), +] + + +@pytest.mark.parametrize( + "fork,match", + [pytest.param(f, m, id=name) for name, f, m in STRICTNESS], +) +def test_forked_model_strictness(fork: Optional[str], match: str) -> None: + """A Shanghai payload only encodes under the Shanghai schema.""" + with pytest.raises(TypeError, match=match): + encode(_shanghai_payload(), fork=fork) + + +NOT_FORK_SCOPED: List[Tuple[str, Callable[[], object]]] = [ + ("encode", lambda: encode(_withdrawal(), fork="Paris")), + ("decode", lambda: decode(Withdrawal, b"", fork="Paris")), + ("describe", lambda: describe_schema(Withdrawal, fork="Paris")), + ("default", lambda: ssz_default(Withdrawal, "Paris")), + ("fields", lambda: ssz_fields(Withdrawal, "Paris")), +] + + +@pytest.mark.parametrize( + "call", + [pytest.param(c, id=name) for name, c in NOT_FORK_SCOPED], +) +def test_fork_on_complete_model_raises(call: Callable[[], object]) -> None: + """Passing fork= to a non-fork-scoped model raises on every path.""" + with pytest.raises(TypeError, match="is not fork-scoped"): + call() + + +def test_ssz_default_per_fork() -> None: + """ssz_default(fork) zeroes that fork's fields, leaves the rest None.""" + zero = ssz_default(ForkedPayload, "Shanghai") + assert zero.withdrawals == [] + assert zero.blob_gas_used is None # beyond Shanghai: absent, not zero + assert encode(zero, "Shanghai") == RefForkedShanghai().encode_bytes() + with pytest.raises(TypeError, match="fork-scoped"): + ssz_default(ForkedPayload) # bare default: must name the fork + + +@pytest.mark.parametrize("mutation", ["truncate", "extend"]) +def test_decode_of_malformed_bytes_raises(mutation: str) -> None: + """Truncated or oversized SSZ data raises, never mis-decodes.""" + raw = encode(_withdrawal()) + data = raw[:-1] if mutation == "truncate" else raw + b"\x00" + with pytest.raises(ValueError): + decode(Withdrawal, data) + + +def test_decode_under_wrong_fork_does_not_silently_succeed() -> None: + """Shanghai bytes decoded as Cancun raise (schema sizes differ).""" + raw = encode(_shanghai_payload(), fork="Shanghai") + with pytest.raises(Exception): # noqa: B017 - remerkleable's error + decode(ForkedPayload, raw, fork="Cancun") + + +def test_build_ssz_type_cache_identity() -> None: + """One cache entry per (class, fork); distinct classes never share.""" + assert build_ssz_type(Withdrawal) is build_ssz_type(Withdrawal, None) + assert build_ssz_type(ForkedPayload, "Paris") is build_ssz_type( + ForkedPayload, "Paris" + ) + assert build_ssz_type(ForkedPayload, "Paris") is not build_ssz_type( + ForkedPayload, "Shanghai" + ) + + def make_dup() -> type: + class Dup(SszModel): + a: Uint64 + + return Dup + + first, second = make_dup(), make_dup() + assert first is not second + assert build_ssz_type(first) is not build_ssz_type(second) + + +UINT_WIDTHS = [ + (Uint8, 8), + (Uint16, 16), + (Uint32, 32), + (Uint64, 64), + (Uint128, 128), + (Uint256, 256), +] + + +@pytest.mark.parametrize( + "uint_cls,bits", + [pytest.param(c, b, id=c.__name__) for c, b in UINT_WIDTHS], +) +def test_uint_width_checked_at_construction(uint_cls: type, bits: int) -> None: + """A wrong-width value fails when built, not at first encode.""" + assert int(uint_cls((1 << bits) - 1)) == (1 << bits) - 1 + with pytest.raises(ValueError, match="out of range"): + uint_cls(1 << bits) + with pytest.raises(ValueError, match="out of range"): + uint_cls(-1) + + +def test_uint_width_checked_at_model_parse() -> None: + """Pydantic parsing of an overflowing value fails loudly.""" + with pytest.raises(ValidationError): + Withdrawal( + index=1, + validator_index=2, + address=Address(b"\x00" * 20), + amount=1 << 64, # one past uint64 + ) + + +def test_excluded_field_is_json_only() -> None: + """ssz_exclude()d fields exist in JSON but are invisible to SSZ.""" + value = Mixed(a=7, note="kept in JSON") + assert "note" in value.model_dump(mode="json") + assert ssz_fields(Mixed) == ("a",) + # decode cannot see the field; it comes back as the default + restored = decode(Mixed, encode(value)) + assert int(restored.a) == 7 + assert restored.note == "json-only" + + +def test_excluded_field_on_fork_scoped_model() -> None: + """Exclusion composes with __ssz_schema__ (schema skips the field).""" + + class ForkedMixed(SszModel): + a: Uint64 + b: Uint64 | None = None + note: Annotated[str, ssz_exclude()] = "aux" + + __ssz_schema__ = SszForkSchema( + base_fork="One", + base=("a",), + appended={"Two": ("b",)}, + ) + + value = ForkedMixed(a=1, note="ride-along") + assert ssz_fields(ForkedMixed, "One") == ("a",) + restored = decode(ForkedMixed, encode(value, "One"), "One") + assert restored.b is None + assert restored.note == "aux" + + +def test_excluded_field_takes_no_active_slot() -> None: + """An excluded field is absent from the active-field bitvector.""" + assert ssz_fields(MixedProg) == ("a", "c") + + class GapMixedProg(ProgressiveModel): + __active_fields__ = [1, 0, 1] # two active, two SSZ fields + + a: Uint64 + note: Annotated[str, ssz_exclude()] = "json-only" + c: Uint64 + + assert ssz_fields(GapMixedProg) == ("a", "c") + assert hash_tree_root(GapMixedProg(a=1, c=3)) == hash_tree_root( + GapProg(a=1, c=3) + ) + + +def test_exclusion_is_inherited() -> None: + """A subclass keeps the base's excluded fields excluded.""" + + class MixedChild(Mixed): + b: Uint64 + + assert ssz_fields(MixedChild) == ("a", "b") + + +def test_spec_of_rejects_excluded_field() -> None: + """spec_of refuses excluded fields instead of resolving the type.""" + with pytest.raises(TypeError, match="SSZ-excluded"): + spec_of(Mixed, "note") + + +def test_single_fork_schema_works_end_to_end() -> None: + """A schema with no appended forks is valid and encodable.""" + + class OnlyFork(SszModel): + a: Uint64 + + __ssz_schema__ = SszForkSchema( + base_fork="Only", base=("a",), appended={} + ) + + value = OnlyFork(a=5) + assert ssz_fields(OnlyFork, "Only") == ("a",) + assert decode(OnlyFork, encode(value, "Only"), "Only") == value diff --git a/packages/testing/src/execution_testing/tools/ssz_vectors.py b/packages/testing/src/execution_testing/tools/ssz_vectors.py new file mode 100644 index 00000000000..2bc26b7a9dc --- /dev/null +++ b/packages/testing/src/execution_testing/tools/ssz_vectors.py @@ -0,0 +1,476 @@ +""" +SSZ static-vector generation on top of the base_types SSZ engine. + +Suites mirror consensus-specs exactly, one per RandomizationMode plus a chaos +suite (ssz_random, ssz_zero, ssz_max, ssz_nil, ssz_one, ssz_lengthy, +ssz_random_chaos). Mode semantics are: +zero/max pin scalar CONTENT (0 / all-ones) but keep collections short +(1-byte byte-lists), while emptiness and saturation are their own modes +(nil_count / max_count). Changing modes (random / one_count / max_count / +chaos) yield several cases; the rest are fully determined by one. +""" + +import hashlib +import random +from dataclasses import dataclass +from enum import Enum +from pathlib import Path +from typing import ( + Any, + Dict, + Iterator, + List, + Optional, + Sequence, + Tuple, + Type, + TypeVar, + Union, +) + +import yaml + +from execution_testing.base_types.ssz import ( + SszBitlist, + SszBitvector, + SszBool, + SszByteList, + SszByteVector, + SszContainer, + SszList, + SszModel, + SszProgressiveBitlist, + SszProgressiveContainer, + SszProgressiveList, + SszType, + SszUint, + SszVector, + encode, + hash_tree_root, + spec_of, + ssz_fields, +) + +MAX_LIST_LENGTH = 10 +MAX_BYTES_LENGTH = 1000 + +RANDOM_CASE_COUNT = 30 + +_M = TypeVar("_M", bound=SszModel) + +_MODE_NAMES = ( + "random", + "zero", + "max", + "nil", + "one", + "lengthy", +) + + +class RandomizationMode(Enum): + """ + How a value's scalar and collection fields are filled. + """ + + mode_random = 0 + mode_zero = 1 + mode_max = 2 + mode_nil_count = 3 + mode_one_count = 4 + mode_max_count = 5 + + def is_changing(self) -> bool: + """ + Return whether the mode yields varying values across cases. + + True for random, one_count and max_count -- those randomize content, + so several cases are worth generating; the rest are fully determined + by a single case. + """ + return self in ( + RandomizationMode.mode_random, + RandomizationMode.mode_one_count, + RandomizationMode.mode_max_count, + ) + + def to_name(self) -> str: + """Return the canonical short name for this mode.""" + return _MODE_NAMES[self.value] + + +def deterministic_seed(*parts: object) -> int: + """ + Return a stable integer seed derived from parts. + + Uses SHA-256 over the slash-joined string parts. + """ + joined = "/".join(str(part) for part in parts) + digest = hashlib.sha256(joined.encode("utf-8")).digest() + return int.from_bytes(digest, "big") + + +@dataclass(frozen=True) +class VectorCase: + """One ssz_static case: the value, its SSZ bytes, and its root.""" + + value: Any # value.yaml + serialized: bytes # serialized.ssz + root: bytes # roots.yaml + + +def _random_bytes(rng: random.Random, length: int) -> bytes: + return bytes(rng.getrandbits(8) for _ in range(length)) + + +def _bits(rng: random.Random, length: int, mode: RandomizationMode) -> Any: + if mode == RandomizationMode.mode_zero: + return [False] * length + if mode == RandomizationMode.mode_max: + return [True] * length + return [bool(rng.getrandbits(1)) for _ in range(length)] + + +def _bitlist_length( + rng: random.Random, cap: int, mode: RandomizationMode +) -> int: + # Consensus semantics: only the *count* modes pin the length; + # zero/max keep a random length. + if mode == RandomizationMode.mode_nil_count: + return 0 + if mode == RandomizationMode.mode_one_count: + return min(1, cap) + if mode == RandomizationMode.mode_max_count: + return cap + return rng.randint(0, cap) + + +def random_value( + rng: random.Random, + spec: SszType, + mode: RandomizationMode, + *, + max_bytes_length: int = MAX_BYTES_LENGTH, + max_list_length: int = MAX_LIST_LENGTH, + chaos: bool = False, +) -> Any: + """ + Build a pydantic value of spec filled with random data per mode. + + A port of the consensus-specs get_random_ssz_object, branching on the + engine's SszType descriptors. With chaos, the mode is re-drawn at every + level of the value tree. + """ + if chaos: + mode = rng.choice(list(RandomizationMode)) + if isinstance(spec, SszByteList): + if mode == RandomizationMode.mode_nil_count: + return b"" + if mode == RandomizationMode.mode_max_count: + return _random_bytes(rng, min(max_bytes_length, spec.limit)) + if mode == RandomizationMode.mode_one_count: + return _random_bytes(rng, min(1, spec.limit)) + if mode == RandomizationMode.mode_zero: + return b"\x00" * min(1, spec.limit) + if mode == RandomizationMode.mode_max: + return b"\xff" * min(1, spec.limit) + return _random_bytes( + rng, rng.randint(0, min(max_bytes_length, spec.limit)) + ) + if isinstance(spec, SszByteVector): + # Byte vectors are fixed length; no max-bytes cap applies. + if mode == RandomizationMode.mode_zero: + return b"\x00" * spec.length + if mode == RandomizationMode.mode_max: + return b"\xff" * spec.length + return _random_bytes(rng, spec.length) + if isinstance(spec, SszUint): + if mode == RandomizationMode.mode_zero: + return 0 + if mode == RandomizationMode.mode_max: + return (1 << spec.bits) - 1 + return rng.randint(0, (1 << spec.bits) - 1) + if isinstance(spec, SszBool): + if mode == RandomizationMode.mode_zero: + return False + if mode == RandomizationMode.mode_max: + return True + return bool(rng.getrandbits(1)) + if isinstance(spec, SszBitvector): + # Bit vectors are fixed length; no cap applies. + return _bits(rng, spec.length, mode) + if isinstance(spec, SszBitlist): + # Consensus caps bit lists by the LIST cap, not the byte cap. + cap = min(max_list_length, spec.limit) + length = _bitlist_length(rng, cap, mode) + return _bits(rng, length, mode) + if isinstance(spec, SszProgressiveBitlist): + # Progressive bit lists are uncapped; the list cap bounds them. + length = _bitlist_length(rng, max_list_length, mode) + return _bits(rng, length, mode) + if isinstance(spec, (SszList, SszProgressiveList)): + # Progressive lists are uncapped; the list cap bounds them. + limit = max_list_length + if isinstance(spec, SszList) and spec.limit < limit: + limit = spec.limit + length = rng.randint(0, limit) + if mode == RandomizationMode.mode_one_count: + length = 1 + elif mode == RandomizationMode.mode_max_count: + length = limit + elif mode == RandomizationMode.mode_nil_count: + length = 0 + # Shrink the cap for nested collections, as consensus-specs does. + max_list_length = 1 << (max_list_length.bit_length() >> 1) + return [ + random_value( + rng, + spec.element, + mode, + max_bytes_length=max_bytes_length, + max_list_length=max_list_length, + chaos=chaos, + ) + for _ in range(length) + ] + if isinstance(spec, SszVector): + return [ + random_value( + rng, + spec.element, + mode, + max_bytes_length=max_bytes_length, + max_list_length=max_list_length, + chaos=chaos, + ) + for _ in range(spec.length) + ] + if isinstance(spec, (SszContainer, SszProgressiveContainer)): + return random_model( + rng, + spec.model, + mode, + max_bytes_length=max_bytes_length, + max_list_length=max_list_length, + chaos=chaos, + ) + raise TypeError(f"no random value for SSZ type {spec!r}") + + +def random_model( + rng: random.Random, + model_cls: Type[_M], + mode: RandomizationMode, + *, + fork: Optional[str] = None, + max_bytes_length: int = MAX_BYTES_LENGTH, + max_list_length: int = MAX_LIST_LENGTH, + chaos: bool = False, +) -> _M: + """ + Build a model_cls instance filled with random data per mode. + + For a fork-scoped model, fork selects which fields get values; + fields beyond that fork keep their None default. + """ + return model_cls( + **{ + name: random_value( + rng, + spec_of(model_cls, name), + mode, + max_bytes_length=max_bytes_length, + max_list_length=max_list_length, + chaos=chaos, + ) + for name in ssz_fields(model_cls, fork) + } + ) + + +def make_case(model: SszModel, fork: Optional[str] = None) -> VectorCase: + """Turn a model instance into its ssz_static case triple.""" + return VectorCase( + value=model.model_dump(mode="json", exclude_none=True), + serialized=encode(model, fork), + root=hash_tree_root(model, fork), + ) + + +def suite_name(mode: RandomizationMode, chaos: bool = False) -> str: + """Return the consensus suite name for a mode (ssz_random, ...).""" + return f"ssz_{mode.to_name()}" + ("_chaos" if chaos else "") + + +def suite_plan( + count: int = RANDOM_CASE_COUNT, +) -> List[Tuple[str, RandomizationMode, bool, int]]: + """ + Return every suite as (name, mode, chaos, case_count). + + One suite per RandomizationMode plus ssz_random_chaos; changing modes + get count cases, deterministic ones a single case. + """ + plan = [ + ( + suite_name(mode), + mode, + False, + count if mode.is_changing() else 1, + ) + for mode in RandomizationMode + ] + plan.append( + ( + suite_name(RandomizationMode.mode_random, chaos=True), + RandomizationMode.mode_random, + True, + count, + ) + ) + return plan + + +ModelSpec = Union[Type[SszModel], Tuple[Type[SszModel], str]] + + +def _normalize_models( + models: Sequence[ModelSpec], +) -> List[Tuple[Type[SszModel], Optional[str]]]: + """Normalize entries to (model, fork) and reject output collisions.""" + entries: List[Tuple[Type[SszModel], Optional[str]]] = [ + m if isinstance(m, tuple) else (m, None) for m in models + ] + seen: Dict[Tuple[str, Optional[str]], Type[SszModel]] = {} + for model_cls, fork in entries: + key = (model_cls.__name__, fork) + other = seen.setdefault(key, model_cls) + if other is not model_cls: + raise ValueError( + f"two distinct models would share vector output " + f"{key[0]!r} (fork={fork!r}); rename one" + ) + return entries + + +def generate_cases( + models: Sequence[ModelSpec], + *, + count: int = RANDOM_CASE_COUNT, + max_bytes_length: int = MAX_BYTES_LENGTH, + max_list_length: int = MAX_LIST_LENGTH, +) -> Iterator[Tuple[str, Optional[str], str, int, VectorCase]]: + """ + Yield (container_name, fork, suite, case_index, case) per vector. + + Entries are complete models or (fork-scoped model, fork) pairs. The + RNG is seeded per (container, [fork,] suite, index) so output is + fully deterministic across runs. + """ + for model_cls, fork in _normalize_models(models): + name = model_cls.__name__ + seed_head = (name, fork) if fork else (name,) + for suite, mode, chaos, n in suite_plan(count): + for i in range(n): + rng = random.Random(deterministic_seed(*seed_head, suite, i)) + model = random_model( + rng, + model_cls, + mode, + fork=fork, + max_bytes_length=max_bytes_length, + max_list_length=max_list_length, + chaos=chaos, + ) + yield name, fork, suite, i, make_case(model, fork) + + +class _HexQuotingDumper(yaml.SafeDumper): + """SafeDumper that single-quotes 0x-hex strings (see _yaml_dump).""" + + +def _represent_str(dumper: Any, data: str) -> Any: + style = "'" if data.startswith("0x") else None + return dumper.represent_scalar("tag:yaml.org,2002:str", data, style=style) + + +_HexQuotingDumper.add_representer(str, _represent_str) + + +def _yaml_dump(obj: Any) -> bytes: + """ + Dump YAML with 0x-hex strings explicitly single-quoted. + + PyYAML's emitter is not consistent across interpreters about quoting + strings that look like YAML 1.1 ints (PyPy emits root: 0x... bare, which + a loader would read back as an integer). Consensus vectors always quote + them, so force the style instead of trusting the emitter. + """ + return yaml.dump(obj, Dumper=_HexQuotingDumper, sort_keys=False).encode() + + +def case_files(case: VectorCase) -> Dict[str, bytes]: + """The on-disk files for a case (bytes), mirroring the consensus layout.""" + return { + "value.yaml": _yaml_dump(case.value), + "serialized.ssz": case.serialized, + "roots.yaml": _yaml_dump({"root": "0x" + case.root.hex()}), + } + + +def case_dir( + output_dir: Path, + container_name: str, + suite: str, + case_index: int, + fork: Optional[str] = None, +) -> Path: + """Return the per-case output directory for a given case.""" + base = output_dir / container_name + if fork is not None: + base = base / fork + return base / suite / f"case_{case_index}" + + +def write_case(directory: Path, case: VectorCase) -> None: + """Write a case's value.yaml / serialized.ssz / roots.yaml.""" + directory.mkdir(parents=True, exist_ok=True) + for name, data in case_files(case).items(): + (directory / name).write_bytes(data) + + +def write_vectors( + models: Sequence[ModelSpec], + output_dir: Path, + *, + count: int = RANDOM_CASE_COUNT, +) -> int: + """Write every vector case under output_dir; return the count.""" + written = 0 + for name, fork, suite, case_index, case in generate_cases( + models, count=count + ): + write_case(case_dir(output_dir, name, suite, case_index, fork), case) + written += 1 + return written + + +__all__ = [ + "MAX_BYTES_LENGTH", + "MAX_LIST_LENGTH", + "RANDOM_CASE_COUNT", + "ModelSpec", + "RandomizationMode", + "VectorCase", + "case_dir", + "case_files", + "deterministic_seed", + "generate_cases", + "make_case", + "random_model", + "random_value", + "suite_name", + "suite_plan", + "write_case", + "write_vectors", +] diff --git a/packages/testing/src/execution_testing/tools/tests/test_ssz_vectors.py b/packages/testing/src/execution_testing/tools/tests/test_ssz_vectors.py new file mode 100644 index 00000000000..fa07717bede --- /dev/null +++ b/packages/testing/src/execution_testing/tools/tests/test_ssz_vectors.py @@ -0,0 +1,296 @@ +""" +Tests for SSZ static-vector generation (consensus-specs-style suites). + +Every generated case must be internally consistent with the engine (the +ground truth for bytes/roots), round-trip losslessly, and match pinned +known-answer values; the suites and mode semantics mirror consensus-specs' +ssz_static generator. +""" + +import random +from pathlib import Path +from typing import Annotated, List + +import pytest +import yaml + +from execution_testing.base_types import Address, Bytes, Hash +from execution_testing.base_types.ssz import ( + SszForkSchema, + SszModel, + Uint64, + Uint256, + byte_list, + decode, + encode, + hash_tree_root, + spec_of, + ssz_list, +) +from execution_testing.tools.ssz_vectors import ( + RandomizationMode, + case_files, + deterministic_seed, + generate_cases, + make_case, + random_model, + random_value, + suite_plan, + write_vectors, +) + +MAX_TX = 2**20 +MAX_BYTES_PER_TX = 2**30 +MAX_WITHDRAWALS = 16 + + +class Withdrawal(SszModel): + """A withdrawal container.""" + + index: Uint64 + validator_index: Uint64 + address: Address + amount: Uint64 + + +class Payload(SszModel): + """A container with a byte-list, a capped list, and a nested list.""" + + parent_hash: Hash + base_fee_per_gas: Uint256 + extra_data: Annotated[Bytes, byte_list(32)] + transactions: Annotated[ + List[Annotated[Bytes, byte_list(MAX_BYTES_PER_TX)]], + ssz_list(MAX_TX), + ] + withdrawals: Annotated[List[Withdrawal], ssz_list(MAX_WITHDRAWALS)] + + +class ForkedPayload(SszModel): + """A fork-scoped model, for the generator's fork axis.""" + + parent_hash: Hash + block_number: Uint64 + withdrawals: ( + Annotated[List[Withdrawal], ssz_list(MAX_WITHDRAWALS)] | None + ) = None + + __ssz_schema__ = SszForkSchema( + base_fork="Paris", + base=("parent_hash", "block_number"), + appended={"Shanghai": ("withdrawals",)}, + ) + + +def assert_roundtrip(model: SszModel) -> None: + """Reusable harness: encode -> decode reconstructs the SSZ value.""" + restored = decode(type(model), encode(model)) + assert encode(restored) == encode(model) + assert hash_tree_root(restored) == hash_tree_root(model) + + +def test_case_triple_is_consistent() -> None: + """A case's serialized/root match the engine, and the value round-trips.""" + w = Withdrawal( + index=7, + validator_index=42, + address=Address(b"\x11" * 20), + amount=32_000_000_000, + ) + case = make_case(w) + assert case.serialized == encode(w) + assert case.root == hash_tree_root(w) + assert case.value["amount"] == "0x773594000" + assert_roundtrip(w) + + +def test_suite_plan_mirrors_consensus() -> None: + """The published CL suite names; changing modes get several cases.""" + plan = suite_plan(count=30) + names = [name for name, *_rest in plan] + assert names == [ + "ssz_random", + "ssz_zero", + "ssz_max", + "ssz_nil", + "ssz_one", + "ssz_lengthy", + "ssz_random_chaos", + ] + counts = {name: n for name, _m, _c, n in plan} + # consensus-specs: cases_if_random if chaos or is_changing() else 1 + assert counts["ssz_random"] == 30 + assert counts["ssz_one"] == 30 + assert counts["ssz_lengthy"] == 30 + assert counts["ssz_random_chaos"] == 30 + assert counts["ssz_zero"] == 1 + assert counts["ssz_max"] == 1 + assert counts["ssz_nil"] == 1 + + +CONTENT_MODES = [ + ("zero", RandomizationMode.mode_zero, 0, b"\x00"), + ("max", RandomizationMode.mode_max, 2**256 - 1, b"\xff"), +] + + +@pytest.mark.parametrize( + "mode,fee,fill", + [pytest.param(m, v, b, id=name) for name, m, v, b in CONTENT_MODES], +) +def test_content_modes_pin_values_not_lengths( + mode: RandomizationMode, fee: int, fill: bytes +) -> None: + """zero/max pin scalar CONTENT; collections stay short (1 byte).""" + rng = random.Random(0) + model = random_model(rng, Payload, mode) + assert int(model.base_fee_per_gas) == fee + assert bytes(model.parent_hash) == fill * 32 + # consensus semantics: byte-lists get ONE fill byte, not emptiness + assert bytes(model.extra_data) == fill + + +COUNT_MODES = [ + ("nil", RandomizationMode.mode_nil_count, 0), + ("one", RandomizationMode.mode_one_count, 1), + ("lengthy", RandomizationMode.mode_max_count, 10), +] + + +@pytest.mark.parametrize( + "mode,length", + [pytest.param(m, n, id=name) for name, m, n in COUNT_MODES], +) +def test_count_modes_pin_lengths(mode: RandomizationMode, length: int) -> None: + """nil/one/lengthy pin list LENGTHS (up to the generator cap of 10).""" + rng = random.Random(0) + model = random_model(rng, Payload, mode) + assert len(model.transactions) == length + assert len(model.withdrawals) == length + + +def test_generate_cases_covers_models_and_suites() -> None: + """Every model x suite is emitted with the planned case counts.""" + cases = list(generate_cases([Withdrawal, Payload], count=2)) + names = {name for name, _fork, _suite, _i, _c in cases} + assert names == {"Withdrawal", "Payload"} + # 4 changing suites x 2 cases + 3 deterministic suites x 1 = 11 each + assert len(cases) == 2 * 11 + + # Every generated case is internally consistent and round-trips. + for name, _fork, _suite, _i, case in cases: + assert len(case.root) == 32 + model_cls = Withdrawal if name == "Withdrawal" else Payload + assert encode(decode(model_cls, case.serialized)) == case.serialized + + +def test_value_yaml_matches_serialized() -> None: + """ + The written value.yaml re-encodes to the written serialized.ssz. + + This is the contract an ssz_static consumer relies on: value, + serialized bytes, and root must all describe the same object. + """ + for _n, _f, _s, _i, case in generate_cases([Withdrawal], count=2): + files = case_files(case) + value = yaml.safe_load(files["value.yaml"]) + rebuilt = Withdrawal.model_validate(value) + assert encode(rebuilt) == files["serialized.ssz"] + root = yaml.safe_load(files["roots.yaml"])["root"] + assert hash_tree_root(rebuilt).hex() == root.removeprefix("0x") + + +def test_deterministic() -> None: + """Generation is deterministic across runs.""" + a = [c.root for *_h, c in generate_cases([Payload], count=2)] + b = [c.root for *_h, c in generate_cases([Payload], count=2)] + assert a == b + assert deterministic_seed("a", "b", 0) == deterministic_seed("a", "b", 0) + assert deterministic_seed("a", "b", 0) != deterministic_seed("a", "b", 1) + + +def test_chaos_redraws_modes() -> None: + """Chaos re-draws the mode per node yet still builds valid values.""" + rng = random.Random(deterministic_seed("chaos-test")) + for _ in range(5): + model = random_model( + rng, Payload, RandomizationMode.mode_random, chaos=True + ) + assert_roundtrip(model) + + +@pytest.mark.parametrize("name", list(Payload.model_fields)) +def test_random_value_covers_field_spec(name: str) -> None: + """random_value handles every SszType the test containers use.""" + rng = random.Random(1) + value = random_value( + rng, spec_of(Payload, name), RandomizationMode.mode_random + ) + assert value is not None + + +def test_case_files_layout() -> None: + """A case serializes to the consensus value/serialized/roots files.""" + w = Withdrawal( + index=1, + validator_index=2, + address=Address(b"\x00" * 20), + amount=3, + ) + files = case_files(make_case(w)) + assert set(files) == {"value.yaml", "serialized.ssz", "roots.yaml"} + assert files["serialized.ssz"] == encode(w) + # Hex strings must be single-quoted (a bare 0x... reads back as int). + assert b"root: '0x" in files["roots.yaml"] + assert b"amount: '0x3'" in files["value.yaml"] + assert b"address: '0x" in files["value.yaml"] + + +def test_write_vectors_emits_consensus_tree(tmp_path: Path) -> None: + """write_vectors lays out <Container>/<suite>/case_<n>/ triples.""" + written = write_vectors([Withdrawal], tmp_path, count=2) + assert written == 11 # 4 changing x 2 + 3 deterministic x 1 + zero_case = tmp_path / "Withdrawal" / "ssz_zero" / "case_0" + assert (zero_case / "value.yaml").is_file() + assert (zero_case / "serialized.ssz").is_file() + assert (zero_case / "roots.yaml").is_file() + # The serialized bytes reload and re-encode identically via the engine. + raw = (zero_case / "serialized.ssz").read_bytes() + assert encode(decode(Withdrawal, raw)) == raw + # Changing suites have every planned case on disk. + random_dir = tmp_path / "Withdrawal" / "ssz_random" + assert sorted(p.name for p in random_dir.iterdir()) == [ + "case_0", + "case_1", + ] + + +def test_fork_scoped_vectors(tmp_path: Path) -> None: + """(model, fork) entries emit per-fork projections under fork dirs.""" + written = write_vectors( + [(ForkedPayload, "Paris"), (ForkedPayload, "Shanghai")], + tmp_path, + count=1, + ) + assert written == 2 * 7 # all 7 suites x 1 case, per fork entry + paris_zero = tmp_path / "ForkedPayload" / "Paris" / "ssz_zero" / "case_0" + raw = (paris_zero / "serialized.ssz").read_bytes() + restored = decode(ForkedPayload, raw, fork="Paris") + assert restored.withdrawals is None # beyond-fork field absent + shanghai_zero = ( + tmp_path / "ForkedPayload" / "Shanghai" / "ssz_zero" / "case_0" + ) + assert (shanghai_zero / "roots.yaml").is_file() + + +def test_duplicate_vector_targets_rejected(tmp_path: Path) -> None: + """Two distinct same-named models cannot share an output directory.""" + + def make_dup() -> type: + class Withdrawal(SszModel): # same __name__, different class + a: Uint64 + + return Withdrawal + + with pytest.raises(ValueError, match="share vector output"): + write_vectors([Withdrawal, make_dup()], tmp_path, count=1) diff --git a/pyproject.toml b/pyproject.toml index 79341714348..23a1eff6527 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -536,6 +536,11 @@ exclude = [ ] plugins = ["pydantic.mypy"] +[[tool.mypy.overrides]] +# remerkleable ships no type stubs / py.typed marker. +module = "remerkleable.*" +ignore_missing_imports = true + [tool.uv] required-version = ">=0.7.0" extra-build-dependencies = { ethash = ["setuptools", "cmake>=4.2.1,<5"] } diff --git a/uv.lock b/uv.lock index 3f44340f65a..c00273eacbf 100644 --- a/uv.lock +++ b/uv.lock @@ -777,6 +777,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/eb/db/f8775490669d28aca24871c67dd56b3e72105cb3bcae9a4ec65dd70859b3/eth_hash-0.7.1-py3-none-any.whl", hash = "sha256:0fb1add2adf99ef28883fd6228eb447ef519ea72933535ad1a0b28c6f65f868a", size = 8028, upload-time = "2025-01-13T21:29:19.365Z" }, ] +[[package]] +name = "eth-remerkleable" +version = "0.1.31" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/51/ac/40fde655f67fd02f07b28e3fe4b9bb4af521388ca8aa48462d622ce4fa03/eth_remerkleable-0.1.31.tar.gz", hash = "sha256:94df4b18a50dfc46f55b2e790cfece384e64032e9cb82d185cff09224682ad1d", size = 49525, upload-time = "2026-06-11T13:23:38.552Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/a1/87f7c996f344c5f213c2bca657e579c3696bb99737238c0cf9f04867386a/eth_remerkleable-0.1.31-py3-none-any.whl", hash = "sha256:7e48ea1b80935977effc02300f3f9b1db19153ab87ffbcea14a6e6429bbdc56b", size = 57192, upload-time = "2026-06-11T13:23:37.208Z" }, +] + [[package]] name = "eth-typing" version = "5.2.1" @@ -1057,6 +1066,7 @@ dependencies = [ { name = "click" }, { name = "colorlog" }, { name = "eth-abi" }, + { name = "eth-remerkleable" }, { name = "ethereum-execution" }, { name = "ethereum-hive" }, { name = "ethereum-rlp" }, @@ -1110,6 +1120,7 @@ requires-dist = [ { name = "click", specifier = ">=8.1.0,<9" }, { name = "colorlog", specifier = ">=6.7.0,<7" }, { name = "eth-abi", specifier = ">=5.2.0" }, + { name = "eth-remerkleable", specifier = "==0.1.31" }, { name = "ethereum-execution", editable = "." }, { name = "ethereum-hive", specifier = ">=0.1.0a5,<1.0.0" }, { name = "ethereum-rlp", specifier = ">=0.1.6,<0.2" }, From 46364f77ab5b72376d47d1f62380f4b9901ab87e Mon Sep 17 00:00:00 2001 From: Mario Vega <marioevz@gmail.com> Date: Wed, 29 Jul 2026 02:44:38 -0600 Subject: [PATCH 167/233] refactor(ci): run geth benchmark CI only on `benchmarks/**` branches (#3249) Co-authored-by: spencer-tb <spencer.tb@ethereum.org> --- .github/workflows/benchmark.yaml | 40 ++++--------------- .github/workflows/test.yaml | 11 +++++ Justfile | 11 ----- docs/getting_started/verifying_changes.md | 2 +- .../plugins/filler/tests/test_benchmarking.py | 22 +++++++--- tox.ini | 2 +- 6 files changed, 36 insertions(+), 52 deletions(-) diff --git a/.github/workflows/benchmark.yaml b/.github/workflows/benchmark.yaml index c4466f8bd15..98ffa7ab187 100644 --- a/.github/workflows/benchmark.yaml +++ b/.github/workflows/benchmark.yaml @@ -3,22 +3,19 @@ name: Benchmarking on: push: branches: - - mainnet - - "forks/**" - paths: - - "tests/benchmark/**" - - "packages/testing/src/execution_testing/benchmark/**" - - "packages/testing/src/execution_testing/cli/pytest_commands/plugins/**" - - ".github/workflows/benchmark.yaml" - pull_request: - paths-ignore: + - "benchmarks/**" + paths-ignore: &non_benchmark_paths - "**.md" - "LICENSE*" - ".gitignore" - ".vscode/**" - "whitelist.txt" - - "docs/**" + - "docs/**" - "mkdocs.yml" + pull_request: + branches: + - "benchmarks/**" + paths-ignore: *non_benchmark_paths workflow_dispatch: concurrency: @@ -26,31 +23,8 @@ concurrency: cancel-in-progress: ${{ github.ref_name != github.event.repository.default_branch }} jobs: - unit-tests: - name: Benchmark Unit Tests - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - submodules: true - - - uses: ./.github/actions/setup-uv - with: - enable-cache: "false" - - - uses: ./.github/actions/build-evm-base - id: evm-builder - with: - type: benchmark - - - name: Run benchmark unit tests - run: just test-tests-bench - env: - EVM_BIN: ${{ steps.evm-builder.outputs.evm-bin }} - sanity-checks: name: ${{ matrix.name }} - needs: [unit-tests] runs-on: ubuntu-latest strategy: fail-fast: false diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 5270d87a51b..9060ce9c2ea 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -6,6 +6,7 @@ on: - master - mainnet - "forks/**" + - "benchmarks/**" paths-ignore: - "**.md" - "LICENSE*" @@ -163,10 +164,20 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: ./.github/actions/setup-uv - uses: ./.github/actions/build-evmone + # On benchmarks/** branches, build the pinned geth so the benchmark + # plugin tests fill against a real client (via EVM_BIN). On every other + # branch this step is skipped, EVM_BIN stays empty, and those fills fall + # through to fill's in-repo EELS t8n. + - uses: ./.github/actions/build-evm-base + id: evm-builder + if: ${{ startsWith(github.ref, 'refs/heads/benchmarks/') || startsWith(github.base_ref, 'benchmarks/') }} + with: + type: benchmark - name: Run test-tests run: just test-tests env: PYTEST_XDIST_AUTO_NUM_WORKERS: auto + EVM_BIN: ${{ steps.evm-builder.outputs.evm-bin }} test-tests-pypy: runs-on: [self-hosted-ghr, size-l-x64] diff --git a/Justfile b/Justfile index 75e38c246df..377a4520762 100644 --- a/Justfile +++ b/Justfile @@ -220,7 +220,6 @@ test-tests *args: cd packages/testing && uv run pytest \ -n {{ xdist_workers }} \ --basetemp="{{ output_dir }}/test-tests/tmp" \ - --ignore=src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_benchmarking.py \ "$@" \ src @@ -235,16 +234,6 @@ test-tests-pypy *args: "$@" \ src -# Run benchmark framework unit tests (with Python) -[group('unit tests')] -[group('benchmark tests')] -test-tests-bench *args: - @mkdir -p "{{ output_dir }}/test-tests-bench/tmp" - uv run pytest \ - --basetemp="{{ output_dir }}/test-tests-bench/tmp" \ - "$@" \ - packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_benchmarking.py - # Run CI release script integration tests [group('unit tests')] test-ci-scripts *args: diff --git a/docs/getting_started/verifying_changes.md b/docs/getting_started/verifying_changes.md index 7bbe5e7d427..660c8d5ebc6 100644 --- a/docs/getting_started/verifying_changes.md +++ b/docs/getting_started/verifying_changes.md @@ -13,7 +13,7 @@ Some CI jobs are slow. Only run the checks relevant to your change. | Any PR (baseline) | `just static` | Lint, format, mypy, spellcheck, import isolation, workflow lint. | | Added or modified tests | `just fill tests/path/to/new/tests` | See [Filling Tests](../filling_tests/index.md). | | Framework changes (`packages/testing/`) | `just test-tests` | Framework unit tests. Mirrors the `test-tests` CI job. | -| Benchmark framework changes | `just test-tests-bench`, `just bench-gas`, `just bench-opcode`, `just bench-opcode-config` | Benchmark unit tests and sanity checks. Mirrors the benchmark CI workflow. | +| Benchmark framework changes | `just test-tests`, `just bench-gas`, `just bench-opcode`, `just bench-opcode-config` | Benchmark plugin unit tests now run within `test-tests`; the `bench-*` recipes fill/verify the suite (geth-backed on `benchmarks/**`). | | Markdown touched | `just lint-md` | Requires `markdownlint-cli2`; see [Linting Markdown](#linting-markdown). | | Docs touched | `just docs` or `just docs-fast` | `docs-fast` skips the Test Case Reference section for faster iteration. | diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_benchmarking.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_benchmarking.py index f611ccdb4d1..0d8bacfd5bd 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_benchmarking.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_benchmarking.py @@ -18,8 +18,18 @@ format_fork_subdir, ) -# EVM binary for fill tests; defaults to geth evm -BENCHMARK_EVM_T8N = os.environ.get("EVM_BIN", "evm") +# EVM binary for fill tests. Unset (or empty) -> the in-repo EELS t8n +# (fill's default when --evm-bin is omitted). Set EVM_BIN to fill +# against a specific binary, e.g. geth's `evm`. +BENCHMARK_EVM_T8N = os.environ.get("EVM_BIN") or None + + +def _evm_bin_args() -> List[str]: + """Return `--evm-bin` args, or none to use fill's EELS default.""" + if BENCHMARK_EVM_T8N is None: + return [] + return [f"--evm-bin={BENCHMARK_EVM_T8N}"] + test_module_dummy = textwrap.dedent( """\ @@ -321,7 +331,7 @@ def test_fixed_opcode_count_split_into_subdirs( "--no-html", "--skip-index", f"--output={output_dir}", - f"--evm-bin={BENCHMARK_EVM_T8N}", + *_evm_bin_args(), "tests/benchmark/dummy_test_module/", "-q", ) @@ -937,7 +947,7 @@ def test_fixed_opcode_count_config_file_parametrized( "--fork", "Prague", "tests/benchmark/dummy_test_module/", - f"--evm-bin={BENCHMARK_EVM_T8N}", + *_evm_bin_args(), "--fixed-opcode-count", "-v", ) @@ -1069,7 +1079,7 @@ def test_fixed_opcode_count_per_parameter_patterns( "--fork", "Prague", "tests/benchmark/dummy_test_module/", - f"--evm-bin={BENCHMARK_EVM_T8N}", + *_evm_bin_args(), "--fixed-opcode-count", "-v", ) @@ -1109,7 +1119,7 @@ def test_cli_mode_ignores_per_parameter_patterns( "Prague", "--fixed-opcode-count=1,5", "tests/benchmark/dummy_test_module/", - f"--evm-bin={BENCHMARK_EVM_T8N}", + *_evm_bin_args(), "-v", ) diff --git a/tox.ini b/tox.ini index f390f3ada51..c661849876e 100644 --- a/tox.ini +++ b/tox.ini @@ -46,7 +46,7 @@ commands = [testenv:tests_benchmark_pytest_py3] commands = - printf '\n\033[1m execution-specs has migrated from tox to just.\033[0m\n Install just: https://just.systems/man/en/pre-built-binaries.html\n\n This tox env can be run using just via:\n \033[1mjust test-tests-bench\033[0m\n\n' + printf '\n\033[1m execution-specs has migrated from tox to just.\033[0m\n Install just: https://just.systems/man/en/pre-built-binaries.html\n\n This tox env can be run using just via:\n \033[1mjust test-tests\033[0m\n\n' false [testenv:benchmark-gas-values] From 178d9f9257fbf7ec8c9b55c3e5da3c6f20c583db Mon Sep 17 00:00:00 2001 From: spencer <spencer.taylor-brown@ethereum.org> Date: Wed, 29 Jul 2026 12:04:09 +0200 Subject: [PATCH 168/233] chore(tests): improve EIP-7976 coverage, checklist, and ref-spec pin (#3222) --- .../eip_checklist_external_coverage.txt | 3 + .../eip_checklist_not_applicable.txt | 14 + .../spec.py | 2 +- .../test_additional_coverage.py | 2 + .../test_execution_gas.py | 3 + .../test_fork_transition.py | 317 ++++++++++++++++++ 6 files changed, 340 insertions(+), 1 deletion(-) create mode 100644 tests/amsterdam/eip7976_increase_calldata_floor_cost/eip_checklist_external_coverage.txt create mode 100644 tests/amsterdam/eip7976_increase_calldata_floor_cost/eip_checklist_not_applicable.txt create mode 100644 tests/amsterdam/eip7976_increase_calldata_floor_cost/test_fork_transition.py diff --git a/tests/amsterdam/eip7976_increase_calldata_floor_cost/eip_checklist_external_coverage.txt b/tests/amsterdam/eip7976_increase_calldata_floor_cost/eip_checklist_external_coverage.txt new file mode 100644 index 00000000000..3bc37707305 --- /dev/null +++ b/tests/amsterdam/eip7976_increase_calldata_floor_cost/eip_checklist_external_coverage.txt @@ -0,0 +1,3 @@ +general/code_coverage/eels = EIP-7976 floor logic lives in calculate_intrinsic_gas_cost (transactions.py) and GasCosts (vm/gas.py); exercised end-to-end by this suite's exact-receipt and calculator cross-check tests, line coverage tracked via codecov on src/ethereum/forks/amsterdam/transactions.py +general/code_coverage/test_coverage = Suite asserts exact cumulative_gas_used and hand-derived calculator cross-checks throughout (token calculation, execution gas, validity matrices, fork transition) +general/code_coverage/missed_lines = No EIP-7976-specific lines excluded; shared intrinsic-gas infrastructure is covered by the type 0-4 validity matrices diff --git a/tests/amsterdam/eip7976_increase_calldata_floor_cost/eip_checklist_not_applicable.txt b/tests/amsterdam/eip7976_increase_calldata_floor_cost/eip_checklist_not_applicable.txt new file mode 100644 index 00000000000..3fd6e4de6db --- /dev/null +++ b/tests/amsterdam/eip7976_increase_calldata_floor_cost/eip_checklist_not_applicable.txt @@ -0,0 +1,14 @@ +opcode = EIP does not introduce or modify an opcode +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 +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/test/out_of_gas = The raised floor cannot cause a runtime out-of-gas, it is a validity threshold plus end-of-transaction minimum billing; below-floor gas limits reject as invalid (insufficient_gas arms in test_transaction_validity.py) +gas_refunds_changes = EIP does not change refund rules, only how the billed floor interacts with refunded execution gas (covered under gas_cost_changes) +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 = The floor gas-limit reservation exists since EIP-7623, this EIP modifies the threshold (see modified_transaction_validity_constraint) +block_level_constraint = Block-level floor accounting is EIP-2780/EIP-8037 scope +general/code_coverage/second_client = Optional diff --git a/tests/amsterdam/eip7976_increase_calldata_floor_cost/spec.py b/tests/amsterdam/eip7976_increase_calldata_floor_cost/spec.py index df03e7071f8..5c130eeacf2 100644 --- a/tests/amsterdam/eip7976_increase_calldata_floor_cost/spec.py +++ b/tests/amsterdam/eip7976_increase_calldata_floor_cost/spec.py @@ -12,7 +12,7 @@ class ReferenceSpec: ref_spec_7976 = ReferenceSpec( - "EIPS/eip-7976.md", "83d473b0504d316a06ce58ae581e7f03b5d54fe1" + "EIPS/eip-7976.md", "c998ef94eb16a6af8a9b8e2084f947b17ea14865" ) diff --git a/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_additional_coverage.py b/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_additional_coverage.py index 0b745830914..8a19de64cec 100644 --- a/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_additional_coverage.py +++ b/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_additional_coverage.py @@ -21,6 +21,7 @@ AuthorizationTuple, Bytecode, Bytes, + EIPChecklist, Fork, Op, StateTestFiller, @@ -90,6 +91,7 @@ def to(self, pre: Alloc) -> Address: ), ], ) + @EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() def test_token_calculation_verification( self, state_test: StateTestFiller, diff --git a/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_execution_gas.py b/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_execution_gas.py index a46c9d5daf6..209916b9fcf 100644 --- a/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_execution_gas.py +++ b/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_execution_gas.py @@ -11,6 +11,7 @@ Alloc, AuthorizationTuple, Bytes, + EIPChecklist, Fork, Op, StateTestFiller, @@ -75,6 +76,7 @@ def to( pytest.param(0, id="exact_gas"), ], ) + @EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() def test_full_gas_consumption( self, state_test: StateTestFiller, @@ -153,6 +155,7 @@ def to( pytest.param(0, id="exact_gas"), ], ) + @EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() def test_gas_consumption_below_data_floor( self, state_test: StateTestFiller, diff --git a/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_fork_transition.py b/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_fork_transition.py new file mode 100644 index 00000000000..b78f996801b --- /dev/null +++ b/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_fork_transition.py @@ -0,0 +1,317 @@ +""" +Fork-transition tests for EIP-7976. + +EIP-7976 raises the calldata floor price at the Amsterdam fork +boundary: the EIP-7623 floor of 10 gas per token (10/40 per zero or +non-zero byte) becomes 16 gas per floor token with floor tokens counted +uniformly as four per calldata byte (64/64). These tests send identical +data-heavy transactions in a pre-fork block and a post-fork block and +assert that the floor changes exactly at the boundary, both as billed +gas and as the transaction-validity threshold. + +The calldata sizes sit above the crossover where the new floor exceeds +the old one: EIP-2780 lowers the floor anchor (the decomposed intrinsic +base) below the flat pre-fork 21_000, so for small calldata the new +floor is the lower of the two even though the per-byte rate rises. +""" + +import pytest +from execution_testing import ( + Account, + Address, + Alloc, + Block, + BlockchainTestFiller, + EIPChecklist, + RecipientType, + Transaction, + TransactionException, + TransactionReceipt, + TransitionFork, +) + +from .spec import ref_spec_7976 + +REFERENCE_SPEC_GIT_PATH = ref_spec_7976.git_path +REFERENCE_SPEC_VERSION = ref_spec_7976.version + +pytestmark = pytest.mark.valid_at_transition_to("EIP7976") + +# Transition forks switch at timestamp 15_000. +PRE_FORK_TIMESTAMP = 14_999 +POST_FORK_TIMESTAMP = 15_000 + +# Calldata shapes sized above the old-floor/new-floor crossover so the +# post-fork floor is strictly larger (see module docstring). +ALL_ZERO_DATA = b"\x00" * 200 +ALL_NONZERO_DATA = b"\x01" * 400 + + +def expected_floors(fork: TransitionFork, data: bytes) -> tuple[int, int]: + """ + Hand-derive the pre- and post-fork calldata floors for `data`. + + Pre-fork (EIP-7623): 10 gas per token, one token per zero byte and + four per non-zero byte, anchored on the flat `TX_BASE`. Post-fork + (EIP-7976): 16 gas per token, four tokens per calldata byte + regardless of content, anchored on the EIP-2780 decomposed base, + which includes the recipient-access charge for a plain call. + """ + pre_costs = fork.fork_at(timestamp=PRE_FORK_TIMESTAMP).gas_costs() + post_costs = fork.fork_at(timestamp=POST_FORK_TIMESTAMP).gas_costs() + + zero_bytes = data.count(0) + nonzero_bytes = len(data) - zero_bytes + + pre_tokens = zero_bytes + nonzero_bytes * 4 + expected_pre = int( + pre_costs.TX_BASE + pre_tokens * pre_costs.TX_DATA_TOKEN_FLOOR + ) + + post_floor_tokens = len(data) * int(post_costs.TX_DATA_TOKEN_STANDARD) + expected_post = int( + post_costs.TX_BASE + + post_costs.COLD_ACCOUNT_ACCESS + + post_floor_tokens * post_costs.TX_DATA_TOKEN_FLOOR + ) + + return expected_pre, expected_post + + +@EIPChecklist.GasCostChanges.Test.ForkTransition.Before() +@EIPChecklist.GasCostChanges.Test.ForkTransition.After() +@pytest.mark.parametrize( + "data", + [ + pytest.param(ALL_ZERO_DATA, id="all_zero_bytes"), + pytest.param(ALL_NONZERO_DATA, id="all_nonzero_bytes"), + ], +) +def test_floor_cost_across_amsterdam_transition( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: TransitionFork, + data: bytes, +) -> None: + """ + Pin the EIP-7976 floor increase across the Amsterdam boundary. + + The same data-heavy transaction to an existing EOA (no EVM + execution) is sent in a pre-fork block and a post-fork block with + the gas limit pinned to the fork-appropriate floor, so the billed + gas equals the calldata floor exactly on both sides. The zero-byte + arm discriminates the uniform token counting (zero bytes lose their + floor discount); the non-zero arm discriminates the per-token price + alone. + + The per-fork floor returned by the calculator is also checked + against a hand-derived value built from each fork's gas constants, + so a calculator regression fails here with a clear message rather + than only as a downstream balance mismatch. + """ + gas_price = 1_000_000_000 + target = pre.fund_eoa(amount=1) + + expected_pre, expected_post = expected_floors(fork, data) + + timestamps = [PRE_FORK_TIMESTAMP, POST_FORK_TIMESTAMP] + expected_floors_per_block = [expected_pre, expected_post] + blocks = [] + post: dict[Address, Account] = {} + + for timestamp, expected_floor in zip( + timestamps, expected_floors_per_block, strict=True + ): + sub_fork = fork.fork_at(timestamp=timestamp) + floor = sub_fork.transaction_data_floor_cost_calculator()( + data=data, + recipient_type=RecipientType.EOA, + ) + assert floor == expected_floor, ( + f"floor at timestamp {timestamp} ({sub_fork}) is {floor}, " + f"expected {expected_floor}" + ) + # The floor must dominate the standard-side intrinsic so the + # transaction is billed exactly the floor. + intrinsic = sub_fork.transaction_intrinsic_cost_calculator()( + calldata=data, + recipient_type=RecipientType.EOA, + return_cost_deducted_prior_execution=True, + ) + assert floor > intrinsic, ( + f"floor {floor} does not dominate intrinsic {intrinsic} at " + f"timestamp {timestamp} ({sub_fork})" + ) + + sender_initial_balance = 10**18 + sender = pre.fund_eoa(sender_initial_balance) + + # The recipient is an EOA, so no EVM bytecode runs and the + # billed gas is exactly the floor; the gas limit is pinned to + # the floor, leaving no buffer. + tx = Transaction( + sender=sender, + to=target, + data=data, + gas_limit=floor, + gas_price=gas_price, + expected_receipt=TransactionReceipt(cumulative_gas_used=floor), + ) + blocks.append(Block(timestamp=timestamp, txs=[tx])) + + post[sender] = Account( + nonce=1, + balance=sender_initial_balance - floor * gas_price, + ) + + blockchain_test(pre=pre, blocks=blocks, post=post) + + +@EIPChecklist.ModifiedTransactionValidityConstraint.Test.ForkTransition.AcceptedBeforeFork() +@EIPChecklist.ModifiedTransactionValidityConstraint.Test.ForkTransition.RejectedBeforeFork() +@EIPChecklist.ModifiedTransactionValidityConstraint.Test.ForkTransition.AcceptedAfterFork() +@EIPChecklist.ModifiedTransactionValidityConstraint.Test.ForkTransition.RejectedAfterFork() +@pytest.mark.parametrize( + "data", + [ + pytest.param(ALL_ZERO_DATA, id="all_zero_bytes"), + pytest.param(ALL_NONZERO_DATA, id="all_nonzero_bytes"), + ], +) +@pytest.mark.parametrize( + "scenario", + [ + pytest.param("exact_floors_accepted", id="exact_floors_accepted"), + pytest.param( + "below_old_floor_rejected_before_fork", + marks=pytest.mark.exception_test, + id="below_old_floor_rejected_before_fork", + ), + pytest.param( + "old_floor_rejected_after_fork", + marks=pytest.mark.exception_test, + id="old_floor_rejected_after_fork", + ), + pytest.param( + "below_new_floor_rejected_after_fork", + marks=pytest.mark.exception_test, + id="below_new_floor_rejected_after_fork", + ), + ], +) +def test_floor_validity_across_amsterdam_transition( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: TransitionFork, + scenario: str, + data: bytes, +) -> None: + """ + Pin the EIP-7976 validity-threshold change across the boundary. + + The gas limit must reserve the calldata floor for the transaction to + be valid. A transaction whose gas limit exactly meets the old + (EIP-7623) floor is accepted in the pre-fork block, but the + identical shape is rejected once the fork activates because the new + floor is higher for this calldata; one below the old floor is + already rejected pre-fork, one just below the new floor is rejected + post-fork, and one at the new floor is accepted post-fork. + + The zero-byte arm pins the uniform token counting on the validity + threshold itself, independently of the billed-gas path. + """ + gas_price = 1_000_000_000 + target = pre.fund_eoa(amount=1) + + old_floor, new_floor = expected_floors(fork, data) + # The old-floor-rejected-after-fork arm only exists because the new + # floor exceeds the old one for this calldata size. + assert new_floor > old_floor + + below_floor_error = TransactionException.INTRINSIC_GAS_BELOW_FLOOR_GAS_COST + sender_initial_balance = 10**18 + pre_fork_sender = pre.fund_eoa(sender_initial_balance) + post_fork_sender = pre.fund_eoa(sender_initial_balance) + + def transfer_tx( + sender: Address, gas_limit: int, valid: bool + ) -> Transaction: + return Transaction( + sender=sender, + to=target, + data=data, + gas_limit=gas_limit, + gas_price=gas_price, + error=None if valid else below_floor_error, + ) + + untouched = Account(nonce=0, balance=sender_initial_balance) + blocks: list[Block] + + if scenario == "exact_floors_accepted": + blocks = [ + Block( + timestamp=PRE_FORK_TIMESTAMP, + txs=[transfer_tx(pre_fork_sender, old_floor, valid=True)], + ), + Block( + timestamp=POST_FORK_TIMESTAMP, + txs=[transfer_tx(post_fork_sender, new_floor, valid=True)], + ), + ] + post = { + pre_fork_sender: Account( + nonce=1, + balance=sender_initial_balance - old_floor * gas_price, + ), + post_fork_sender: Account( + nonce=1, + balance=sender_initial_balance - new_floor * gas_price, + ), + } + elif scenario == "below_old_floor_rejected_before_fork": + blocks = [ + Block( + timestamp=PRE_FORK_TIMESTAMP, + txs=[transfer_tx(pre_fork_sender, old_floor - 1, valid=False)], + exception=below_floor_error, + ), + ] + post = {pre_fork_sender: untouched, post_fork_sender: untouched} + elif scenario == "old_floor_rejected_after_fork": + blocks = [ + Block( + timestamp=PRE_FORK_TIMESTAMP, + txs=[transfer_tx(pre_fork_sender, old_floor, valid=True)], + ), + # The identical gas limit that was accepted pre-fork no + # longer reserves the raised floor. + Block( + timestamp=POST_FORK_TIMESTAMP, + txs=[transfer_tx(post_fork_sender, old_floor, valid=False)], + exception=below_floor_error, + ), + ] + post = { + pre_fork_sender: Account( + nonce=1, + balance=sender_initial_balance - old_floor * gas_price, + ), + post_fork_sender: untouched, + } + else: + # One below the new floor is still rejected post-fork; together + # with the exact-floor acceptance this pins the post-fork + # threshold at exactly the new floor. + blocks = [ + Block( + timestamp=POST_FORK_TIMESTAMP, + txs=[ + transfer_tx(post_fork_sender, new_floor - 1, valid=False) + ], + exception=below_floor_error, + ), + ] + post = {pre_fork_sender: untouched, post_fork_sender: untouched} + + blockchain_test(pre=pre, blocks=blocks, post=post) From f79c4c7e9084b9b583a9232e128cc69caec71471 Mon Sep 17 00:00:00 2001 From: Aliaksei Osipau <me@flcl.me> Date: Wed, 29 Jul 2026 17:40:54 +0300 Subject: [PATCH 169/233] feat(tests): EIP-7928 - cover system-address zero-tip coinbase BAL (#3239) * Add system-address zero-tip BAL fixture * Update tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py Co-authored-by: Mario Vega <marioevz@gmail.com> * Update tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py Co-authored-by: Mario Vega <marioevz@gmail.com> * Update tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py Co-authored-by: Mario Vega <marioevz@gmail.com> * Update tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py Co-authored-by: Mario Vega <marioevz@gmail.com> * Update tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py Co-authored-by: Mario Vega <marioevz@gmail.com> * Update tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py Co-authored-by: Mario Vega <marioevz@gmail.com> * Update tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py Co-authored-by: Mario Vega <marioevz@gmail.com> * fix(tests): apply ruff format to system-address coinbase BAL test --------- Co-authored-by: Mario Vega <marioevz@gmail.com> --- .../test_block_access_lists.py | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py index e34f8cb13e2..b59367bfb09 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py @@ -44,6 +44,7 @@ REFERENCE_SPEC_VERSION = ref_spec_7928.version pytestmark = pytest.mark.valid_from("Amsterdam") +SYSTEM_ADDRESS = Address(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE) @EIPChecklist.BlockHeaderField.Test.ValueBehavior.Accept() @@ -1631,6 +1632,65 @@ def test_bal_coinbase_zero_tip( ) +def test_bal_system_address_coinbase_zero_tip( + pre: Alloc, + blockchain_test: BlockchainTestFiller, + fork: Fork, +) -> None: + """ + Ensure BAL includes SYSTEM_ADDRESS when it is the zero-tip fee recipient. + """ + bob = pre.fund_eoa(amount=0) + + genesis_env = Environment(base_fee_per_gas=0x7) + base_fee_per_gas = fork.base_fee_per_gas_calculator()( + parent_base_fee_per_gas=int(genesis_env.base_fee_per_gas or 0), + parent_gas_used=0, + parent_gas_limit=genesis_env.gas_limit, + ) + + tx_value = 5 + alice = pre.fund_eoa() + tx = Transaction( + sender=alice, + to=bob, + value=tx_value, + gas_price=base_fee_per_gas, + ) + + block = Block( + txs=[tx], + fee_recipient=SYSTEM_ADDRESS, + header_verify=Header(base_fee_per_gas=base_fee_per_gas), + expected_block_access_list=BlockAccessListExpectation( + account_expectations={ + alice: BalAccountExpectation( + nonce_changes=[ + BalNonceChange(block_access_index=1, post_nonce=1) + ], + ), + bob: BalAccountExpectation( + balance_changes=[ + BalBalanceChange(block_access_index=1, post_balance=5) + ] + ), + SYSTEM_ADDRESS: BalAccountExpectation.empty(), + } + ), + ) + + blockchain_test( + pre=pre, + blocks=[block], + post={ + alice: Account(nonce=1), + bob: Account(balance=5), + SYSTEM_ADDRESS: Account.NONEXISTENT, + }, + genesis_environment=genesis_env, + ) + + @pytest.mark.parametrize( "value", [ From 1311ff376d2f7fbd5270e38e7bf13847c14b883e Mon Sep 17 00:00:00 2001 From: Guruprasad Kamath <48196632+gurukamath@users.noreply.github.com> Date: Wed, 29 Jul 2026 16:51:22 +0200 Subject: [PATCH 170/233] feat(specs,amsterdam): EIP-2780 - fold transfer log cost into value transfer cost (#3214) Align the EIP-2780 implementation with the changes proposed in https://github.com/ethereum/EIPs/pull/11997 --- .../forks/forks/eips/amsterdam/eip_2780.py | 20 ++++------------ .../src/execution_testing/forks/gas_costs.py | 1 - src/ethereum/forks/amsterdam/transactions.py | 10 +++----- src/ethereum/forks/amsterdam/vm/gas.py | 3 +-- .../eip2780_reduce_intrinsic_tx_gas/spec.py | 2 +- .../test_calldata_floor.py | 12 ++++------ .../test_fork_transition.py | 11 +++------ .../test_value_moving_transactions.py | 24 +++++++------------ 8 files changed, 26 insertions(+), 57 deletions(-) diff --git a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_2780.py b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_2780.py index 28bbcb60b84..c001d74e6e4 100644 --- a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_2780.py +++ b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_2780.py @@ -40,8 +40,7 @@ def gas_costs(cls) -> GasCosts: return replace( parent, TX_BASE=12_000, - TRANSFER_LOG_COST=1_756, - TX_VALUE_COST=4_244, + TX_VALUE_COST=6_000, ) @classmethod @@ -75,14 +74,10 @@ def fn( # CREATE_ACCESS regular gas; TX_CREATE folds in the # NEW_ACCOUNT state gas, which the floor excludes. floor += gas_costs.TX_CREATE - gas_costs.NEW_ACCOUNT - if sends_value: - floor += gas_costs.TRANSFER_LOG_COST elif not is_self_transfer: floor += gas_costs.COLD_ACCOUNT_ACCESS if sends_value: - floor += ( - gas_costs.TRANSFER_LOG_COST + gas_costs.TX_VALUE_COST - ) + floor += gas_costs.TX_VALUE_COST return floor return fn @@ -97,9 +92,8 @@ def transaction_intrinsic_cost_calculator( Non-create, non-self targets pay ``COLD_ACCOUNT_ACCESS`` unconditionally; access lists do not warm transaction-level - accounts. Value-bearing transactions pay - ``TRANSFER_LOG_COST`` plus ``TX_VALUE_COST``; self-transfers - suppress the value-transfer charge entirely. + accounts. Value-bearing transactions pay ``TX_VALUE_COST``; + self-transfers suppress the value-transfer charge entirely. """ super_fn = super(EIP2780, cls).transaction_intrinsic_cost_calculator() gas_costs = cls.gas_costs() @@ -147,14 +141,10 @@ def fn( # remove it here, mirroring value transfer to an empty # account whose NEW_ACCOUNT is likewise top-frame. intrinsic_cost -= gas_costs.NEW_ACCOUNT - if sends_value: - intrinsic_cost += gas_costs.TRANSFER_LOG_COST elif not is_self_transfer: intrinsic_cost += gas_costs.COLD_ACCOUNT_ACCESS if sends_value: - intrinsic_cost += ( - gas_costs.TRANSFER_LOG_COST + gas_costs.TX_VALUE_COST - ) + intrinsic_cost += gas_costs.TX_VALUE_COST if return_cost_deducted_prior_execution: return intrinsic_cost diff --git a/packages/testing/src/execution_testing/forks/gas_costs.py b/packages/testing/src/execution_testing/forks/gas_costs.py index 0113895108f..81e660fdaa5 100644 --- a/packages/testing/src/execution_testing/forks/gas_costs.py +++ b/packages/testing/src/execution_testing/forks/gas_costs.py @@ -38,7 +38,6 @@ class GasCosts: NEW_ACCOUNT: int ACCOUNT_WRITE: int = 0 CREATE_ACCESS: int = 0 - TRANSFER_LOG_COST: int = 0 TX_VALUE_COST: int = 0 # Contract Creation diff --git a/src/ethereum/forks/amsterdam/transactions.py b/src/ethereum/forks/amsterdam/transactions.py index ceae8f63168..251c80472e2 100644 --- a/src/ethereum/forks/amsterdam/transactions.py +++ b/src/ethereum/forks/amsterdam/transactions.py @@ -639,8 +639,8 @@ def calculate_intrinsic_cost( call, or `CREATE_ACCESS` for a contract creation). The created account's `NEW_ACCOUNT` state gas is state-dependent and is charged at the top frame, not here. - 3. Value cost (`TRANSFER_LOG_COST`, plus `TX_VALUE_COST` for a - non-self-transfer call) when ``tx.value > 0``. + 3. Value cost (`TX_VALUE_COST` for a non-self-transfer call) when + ``tx.value > 0``. 4. Calldata cost (zero and non-zero bytes). 5. Access list entries (if applicable). 6. Authorizations (if applicable): only the state-independent base @@ -671,14 +671,10 @@ def calculate_intrinsic_cost( if is_create: recipient_regular_gas = GasCosts.CREATE_ACCESS init_code_gas = init_code_cost(ulen(tx.data)) - if tx.value > U256(0): - recipient_regular_gas += GasCosts.TRANSFER_LOG_COST elif not is_self_transfer: recipient_regular_gas = GasCosts.COLD_ACCOUNT_ACCESS if tx.value > U256(0): - recipient_regular_gas += ( - GasCosts.TRANSFER_LOG_COST + GasCosts.TX_VALUE_COST - ) + recipient_regular_gas += GasCosts.TX_VALUE_COST access_list_cost = Uint(0) tokens_in_access_list = Uint(0) diff --git a/src/ethereum/forks/amsterdam/vm/gas.py b/src/ethereum/forks/amsterdam/vm/gas.py index 028b4ac4318..d4fd188946f 100644 --- a/src/ethereum/forks/amsterdam/vm/gas.py +++ b/src/ethereum/forks/amsterdam/vm/gas.py @@ -137,8 +137,7 @@ class GasCosts: # Transactions TX_BASE: Final[Uint] = Uint(12000) TX_CREATE: Final[Uint] = Uint(32000) - TX_VALUE_COST: Final[Uint] = Uint(4244) - TRANSFER_LOG_COST: Final[Uint] = Uint(1756) + TX_VALUE_COST: Final[Uint] = Uint(6000) TX_DATA_TOKEN_STANDARD: Final[Uint] = Uint(4) TX_DATA_TOKEN_FLOOR: Final[Uint] = Uint(16) TX_ACCESS_LIST_ADDRESS: Final[Uint] = COLD_ACCOUNT_ACCESS diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/spec.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/spec.py index 5ac7b7e50bf..33b12ffbd2f 100644 --- a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/spec.py +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/spec.py @@ -13,5 +13,5 @@ class ReferenceSpec: ref_spec_2780 = ReferenceSpec( git_path="EIPS/eip-2780.md", - version="e6d8f589d355e891c37ff479d3ce668352e5b1be", + version="04dd54c2e7ec1f408cf4a150d5c1aa43573bd025", ) diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_calldata_floor.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_calldata_floor.py index be3c77466ee..b0ba400f25e 100644 --- a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_calldata_floor.py +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_calldata_floor.py @@ -259,13 +259,11 @@ def test_calldata_floor_contract_creation( empty code, and prices every byte as one floor token. - ``floor_binds``: ``gas_used`` pins to the floor, which anchors - on the creation regular base (``TX_BASE + CREATE_ACCESS``, plus - ``TRANSFER_LOG_COST`` when value moves) but excludes the created - account's ``NEW_ACCOUNT`` *state* charge and the init-code word - cost -- both masked by the binding floor -- while the deploy - (and any moved wei) still lands. The receipt pins the floor - exactly, so the value-bearing case sits precisely - ``TRANSFER_LOG_COST`` above the zero-value one. + on the creation regular base (``TX_BASE + CREATE_ACCESS``) + but excludes the created account's ``NEW_ACCOUNT`` *state* charge + and the init-code word cost -- both masked by the binding floor -- + while the deploy (and any moved wei) still lands. + The receipt pins the floor exactly. - ``below_floor``: a gas limit one short of the floor still covers the creation intrinsic, so the rejection is pinned to the floor, with ``INTRINSIC_GAS_BELOW_FLOOR_GAS_COST``. diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_fork_transition.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_fork_transition.py index 80b21d0c35a..4f0cffce3c8 100644 --- a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_fork_transition.py +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_fork_transition.py @@ -102,9 +102,7 @@ def test_intrinsic_reduction_across_amsterdam_transition( if not self_transfer: expected_post += post_gas_costs.COLD_ACCOUNT_ACCESS if value: - expected_post += ( - post_gas_costs.TRANSFER_LOG_COST + post_gas_costs.TX_VALUE_COST - ) + expected_post += post_gas_costs.TX_VALUE_COST timestamps = [PRE_FORK_TIMESTAMP, POST_FORK_TIMESTAMP] expected_intrinsics = [expected_pre, expected_post] @@ -181,9 +179,8 @@ def test_creation_tx_intrinsic_across_amsterdam_transition( block, each from a fresh sender with the gas limit pinned exactly. Pre-fork the whole cost is regular intrinsic: ``TX_BASE`` plus the flat ``TX_CREATE``. Post-fork the intrinsic keeps only the - ``CREATE_ACCESS`` regular portion of ``TX_CREATE`` (plus the - transfer-log charge when value moves), while the created account's - ``NEW_ACCOUNT`` is charged as *state* gas at the top frame — the + ``CREATE_ACCESS`` regular portion of ``TX_CREATE``, while the created + account's ``NEW_ACCOUNT`` is charged as *state* gas at the top frame — the sender-facing total is the sum of both. The per-fork costs are hand-derived from each fork's gas constants @@ -217,8 +214,6 @@ def test_creation_tx_intrinsic_across_amsterdam_transition( + (post_costs.TX_CREATE - post_costs.NEW_ACCOUNT) + init_code_terms ) - if value: - expected_post += post_costs.TRANSFER_LOG_COST expected_post_state = post_costs.NEW_ACCOUNT timestamps = [PRE_FORK_TIMESTAMP, POST_FORK_TIMESTAMP] diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_value_moving_transactions.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_value_moving_transactions.py index a329470b6ba..42d694f0c2f 100644 --- a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_value_moving_transactions.py +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_value_moving_transactions.py @@ -67,11 +67,10 @@ def test_value_moving_transactions( ``NEW_ACCOUNT`` state charge when value is transferred. The EIP-7708 transfer log is asserted to fire exactly when - ``TRANSFER_LOG_COST`` is charged: for a non-self value transfer, + ``TX_VALUE_COST`` is charged: for a non-self value transfer, and never for a self-transfer (carve-out) or a zero-value tx. """ - sender_initial_balance = 10**18 - sender = pre.fund_eoa(sender_initial_balance) + sender = pre.fund_eoa() target = setup_target(pre, recipient_type, sender) target_initial_balance = ( @@ -95,13 +94,12 @@ def test_value_moving_transactions( # spills entirely into regular gas. total_gas_cost = intrinsic_gas + top_frame_gas + top_frame_state_gas - tx_gas_limit = total_gas_cost + 1000 # add a small buffer - gas_price = 1_000_000_000 + tx_gas_limit = total_gas_cost is_self_transfer = recipient_type == RecipientType.SELF # A transfer log is emitted iff value moves to a distinct account, - # which is exactly when the intrinsic includes ``TRANSFER_LOG_COST``. + # which is exactly when the intrinsic includes ``TX_VALUE_COST``. # ``logs=[]`` asserts no log fires for the carved-out cases. if value > 0 and not is_self_transfer: expected_logs = [transfer_log(sender, target, value)] @@ -113,19 +111,13 @@ def test_value_moving_transactions( to=target, value=value, gas_limit=tx_gas_limit, - gas_price=gas_price, - expected_receipt=TransactionReceipt(logs=expected_logs), - ) - - sender_value_delta = 0 if is_self_transfer else value - sender_final_balance = ( - sender_initial_balance - - sender_value_delta - - total_gas_cost * gas_price + expected_receipt=TransactionReceipt( + cumulative_gas_used=tx_gas_limit, logs=expected_logs + ), ) post: dict[Address, Account | None] = { - sender: Account(nonce=1, balance=sender_final_balance), + sender: Account(nonce=1), } if not is_self_transfer: if recipient_type == RecipientType.EMPTY_ACCOUNT and value == 0: From f1c3408bde8d5509f302eee92586cfa30a462ea4 Mon Sep 17 00:00:00 2001 From: Mario Vega <marioevz@gmail.com> Date: Wed, 29 Jul 2026 10:46:03 -0600 Subject: [PATCH 171/233] fix(tests): enhance & un-skip Amsterdam ported static tests (Pt. 1) (#3215) Co-authored-by: spencer <spencer.tb@ethereum.org> --- .claude/commands/enhance-ported-test.md | 530 +++++++++++++++ CLAUDE.md | 2 + tests/ported_static/amsterdam_skip_list.txt | 127 +--- .../test_add_non_const.py | 103 +-- .../test_create_empty_contract.py | 88 +-- .../test_create_empty_contract_and_call_it.py | 111 ++++ ..._create_empty_contract_and_call_it_0wei.py | 92 --- ..._create_empty_contract_and_call_it_1wei.py | 95 --- ...test_create_empty_contract_with_balance.py | 79 --- .../test_create_transaction_call_data.py | 151 ++--- ...est_deleagate_call_after_value_transfer.py | 69 +- .../test_delegatecall_emptycontract.py | 57 +- .../test_raw_call_code_gas.py | 84 --- .../test_raw_call_code_gas_ask.py | 84 --- .../test_raw_call_code_gas_memory.py | 86 --- .../test_raw_call_code_gas_memory_ask.py | 86 --- .../test_raw_call_code_gas_value_transfer.py | 87 --- ...st_raw_call_code_gas_value_transfer_ask.py | 87 --- ...raw_call_code_gas_value_transfer_memory.py | 87 --- ...call_code_gas_value_transfer_memory_ask.py | 87 --- .../test_raw_call_gas.py | 184 ++++-- .../test_raw_call_gas_ask.py | 205 ++++-- .../test_raw_call_gas_value_transfer.py | 87 --- .../test_raw_call_gas_value_transfer_ask.py | 87 --- ...test_raw_call_gas_value_transfer_memory.py | 87 --- ..._raw_call_gas_value_transfer_memory_ask.py | 87 --- .../test_raw_call_memory_gas.py | 84 --- .../test_raw_call_memory_gas_ask.py | 84 --- ...test_raw_create_fail_gas_value_transfer.py | 75 --- ...est_raw_create_fail_gas_value_transfer2.py | 75 --- .../test_raw_create_gas.py | 115 ++-- .../test_raw_create_gas_memory.py | 72 -- .../test_raw_create_gas_value_transfer.py | 75 --- ...st_raw_create_gas_value_transfer_memory.py | 75 --- .../test_raw_delegate_call_gas.py | 83 --- .../test_raw_delegate_call_gas_ask.py | 85 --- .../test_raw_delegate_call_gas_memory.py | 85 --- .../test_raw_delegate_call_gas_memory_ask.py | 85 --- .../stEIP1559/test_sender_balance.py | 83 +-- .../stEIP3855_push0/test_push0_gas.py | 50 +- .../stEIP3855_push0/test_push0_gas2.py | 155 +---- .../stEIP5656_MCOPY/test_mcopy_copy_cost.py | 613 ++---------------- .../test_call_data_copy_offset.py | 97 --- .../stMemoryTest/test_code_copy_offset.py | 93 --- .../stMemoryTest/test_copy_offset.py | 77 +++ .../stNonZeroCallsTest/test_non_zero_value.py | 185 ++++++ .../test_non_zero_value_call.py | 88 --- ...test_non_zero_value_call_to_empty_paris.py | 83 --- ...ero_value_call_to_one_storage_key_paris.py | 89 --- .../test_non_zero_value_callcode.py | 88 --- ..._non_zero_value_callcode_to_empty_paris.py | 83 --- ...value_callcode_to_one_storage_key_paris.py | 89 --- .../test_non_zero_value_delegatecall.py | 87 --- ..._zero_value_delegatecall_to_empty_paris.py | 81 --- ...ue_delegatecall_to_non_non_zero_balance.py | 81 --- ...e_delegatecall_to_one_storage_key_paris.py | 87 --- .../stSpecialTest/test_make_money.py | 86 +-- ...est_static_call_value_inherit_from_call.py | 83 +-- 58 files changed, 1669 insertions(+), 4631 deletions(-) create mode 100644 .claude/commands/enhance-ported-test.md create mode 100644 tests/ported_static/stCreateTest/test_create_empty_contract_and_call_it.py delete mode 100644 tests/ported_static/stCreateTest/test_create_empty_contract_and_call_it_0wei.py delete mode 100644 tests/ported_static/stCreateTest/test_create_empty_contract_and_call_it_1wei.py delete mode 100644 tests/ported_static/stCreateTest/test_create_empty_contract_with_balance.py delete mode 100644 tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_code_gas.py delete mode 100644 tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_code_gas_ask.py delete mode 100644 tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_code_gas_memory.py delete mode 100644 tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_code_gas_memory_ask.py delete mode 100644 tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_code_gas_value_transfer.py delete mode 100644 tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_code_gas_value_transfer_ask.py delete mode 100644 tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_code_gas_value_transfer_memory.py delete mode 100644 tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_code_gas_value_transfer_memory_ask.py delete mode 100644 tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_gas_value_transfer.py delete mode 100644 tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_gas_value_transfer_ask.py delete mode 100644 tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_gas_value_transfer_memory.py delete mode 100644 tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_gas_value_transfer_memory_ask.py delete mode 100644 tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_memory_gas.py delete mode 100644 tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_memory_gas_ask.py delete mode 100644 tests/ported_static/stEIP150singleCodeGasPrices/test_raw_create_fail_gas_value_transfer.py delete mode 100644 tests/ported_static/stEIP150singleCodeGasPrices/test_raw_create_fail_gas_value_transfer2.py delete mode 100644 tests/ported_static/stEIP150singleCodeGasPrices/test_raw_create_gas_memory.py delete mode 100644 tests/ported_static/stEIP150singleCodeGasPrices/test_raw_create_gas_value_transfer.py delete mode 100644 tests/ported_static/stEIP150singleCodeGasPrices/test_raw_create_gas_value_transfer_memory.py delete mode 100644 tests/ported_static/stEIP150singleCodeGasPrices/test_raw_delegate_call_gas.py delete mode 100644 tests/ported_static/stEIP150singleCodeGasPrices/test_raw_delegate_call_gas_ask.py delete mode 100644 tests/ported_static/stEIP150singleCodeGasPrices/test_raw_delegate_call_gas_memory.py delete mode 100644 tests/ported_static/stEIP150singleCodeGasPrices/test_raw_delegate_call_gas_memory_ask.py delete mode 100644 tests/ported_static/stMemoryTest/test_call_data_copy_offset.py delete mode 100644 tests/ported_static/stMemoryTest/test_code_copy_offset.py create mode 100644 tests/ported_static/stMemoryTest/test_copy_offset.py create mode 100644 tests/ported_static/stNonZeroCallsTest/test_non_zero_value.py delete mode 100644 tests/ported_static/stNonZeroCallsTest/test_non_zero_value_call.py delete mode 100644 tests/ported_static/stNonZeroCallsTest/test_non_zero_value_call_to_empty_paris.py delete mode 100644 tests/ported_static/stNonZeroCallsTest/test_non_zero_value_call_to_one_storage_key_paris.py delete mode 100644 tests/ported_static/stNonZeroCallsTest/test_non_zero_value_callcode.py delete mode 100644 tests/ported_static/stNonZeroCallsTest/test_non_zero_value_callcode_to_empty_paris.py delete mode 100644 tests/ported_static/stNonZeroCallsTest/test_non_zero_value_callcode_to_one_storage_key_paris.py delete mode 100644 tests/ported_static/stNonZeroCallsTest/test_non_zero_value_delegatecall.py delete mode 100644 tests/ported_static/stNonZeroCallsTest/test_non_zero_value_delegatecall_to_empty_paris.py delete mode 100644 tests/ported_static/stNonZeroCallsTest/test_non_zero_value_delegatecall_to_non_non_zero_balance.py delete mode 100644 tests/ported_static/stNonZeroCallsTest/test_non_zero_value_delegatecall_to_one_storage_key_paris.py diff --git a/.claude/commands/enhance-ported-test.md b/.claude/commands/enhance-ported-test.md new file mode 100644 index 00000000000..f6d3f6852e5 --- /dev/null +++ b/.claude/commands/enhance-ported-test.md @@ -0,0 +1,530 @@ +# Enhance Ported Test + +Future-proof and clean up a test under `tests/ported_static/`. These tests were +machine-ported from the legacy `ethereum/tests` static fillers (YAML/JSON) and +carry a lot of boilerplate, hardcoded values, and weak/incomplete post-state +checks. This skill is the ordered methodology for turning one into idiomatic, +robust Python. + +This skill is a **living document**: it captures the cases we have validated so +far. Real tests will hit shapes not covered here — that is expected. When you +find one, solve it, then add the new case/step to this file. + +## Goal + +The end state is a test that **passes on every fork from its `valid_from` +onward** (not just the baseline), expresses its intent explicitly, and has no +fragile hardcoded constants. "Future-proof" = a later fork that re-prices gas, +adds state costs, or changes account rules should not silently break it. + +## Core loop (subtractive) + +Most of the work is **removing** boilerplate one piece at a time and proving the +test still passes after each removal: + +1. **Baseline first.** Before touching anything, fill the test and confirm it is + green: `uv run fill <path> --fork=<valid_from-fork> -q --clean`. +2. Make **one** change. +3. Fill again (same fast command). Green → keep, move on. +4. **Red → roll back that one change and analyze.** A break is information: it + tells you the thing you removed was load-bearing. Understand *why* before + deciding whether to keep it, replace it with a dynamic equivalent, or leave + it. Never paste a new expected value just to make red go green without + understanding the change (see "Re-pinning" below). + +Do low-risk, independent removals in small batches if you like, but anything +that can plausibly interact (addresses, contracts, gas) goes **one at a time** +so a failure is attributable. + +## Verification cadence + +- **Iterating:** `--fork=<baseline>` (usually Cancun) — fast. +- **Checkpoint / done:** fill the whole `valid_from` range (omit `--fork`) so all + deployed forks are exercised. +- **Probe the future fork:** explicitly `--fork Amsterdam` (or the latest fork + that enables new EIPs). A ported test listed in `amsterdam_skip_list.txt` will + always show `sss` there — to see its *real* behavior, temporarily remove its + entry from that file, fill, then restore (or, once fixed, remove it for good — + see Finishing). A gas/state-cost change there is the most likely future + breakage. +- **`fill` output:** writes to `./fixtures` (`--clean` resets it), or pass + `--output <dir>` for a scratch location. Do **not** use `-o` — that is + pytest's `--override-ini`, not the output dir. + +## Ordered steps + +Do them roughly in this order. Earlier steps unblock later ones (notably: max +out gas *before* strengthening post-state, so added opcodes don't hit a gas +ceiling). + +### 1. Remove `env` +Delete the `Environment(...)` block, the `env=env` arg to `state_test`, and any +now-orphaned vars (`coinbase`) and the `Environment` import. The framework +supplies sensible defaults. +**Keep `env` only if** the post asserts on the coinbase/`fee_recipient` balance, +or the bytecode reads block fields (`NUMBER`, `TIMESTAMP`, `PREVRANDAO`, +`BASEFEE`, `GASLIMIT`, `COINBASE`). `fee_recipient=sender` alone is not a reason +to keep it. + +### 2. Remove `gas_limit` from the transaction (if gas is not the subject) +This is the common case and belongs early. Omitting `gas_limit` maxes out the +gas the tx receives, so the body executes fully. See `write-test.md` "Transactions". +- **Remove it** when the test is about *behavior* and just needs to run to + completion. This also lets you delete any per-fork gas band-aids (e.g. + `fork.is_eip_enabled(8037)` budget bumps) and often the `fork` param itself. +- **Keep it** only for genuinely gas-sensitive tests (OOG boundaries, + intrinsic-gas, code-deposit limits, or gas metering) — see step 10. +- **Gas-snapshot tests are gas-sensitive.** If the post asserts a stored `GAS` + reading or a `SUB(@gas_before, GAS)` delta (legacy slots `0` / `0x64`), the + test *measures gas* — handle it under step 10 (preserve via `CodeGasMeasure`), + do not just strip `gas_limit`. This is the dominant `amsterdam_skip_list.txt` + shape: the stored gas value is exactly what EIP-8037 re-prices and breaks. +- **EIP-8037 caveat:** when you omit `gas_limit` on a test that *measures* an + operation incurring **state gas** (account creation, storage writes), add + `state_gas_reservoir=0` to the tx, or that state gas is silently dropped from + the measurement on EIP-8037 forks (see step 10). Pure-execution opcodes + (e.g. `PUSH0`, arithmetic) have no state gas and do not need it. +- Do **not** add a comment explaining the absence of `gas_limit`; omission is + the default. + +### 3. Remove hardcoded contract `nonce` +Drop `nonce=0` from `pre.deploy_contract(...)`. If a `compute_create_address(..., +nonce=N)` in the post depends on it, keep them consistent. + +### 4. Remove hardcoded addresses (one contract at a time) +Two sub-cases: +- **Value discarded:** a `contract = Address(0x...)` literal that is immediately + overwritten by `pre.deploy_contract(...)` (no `address=`). Just delete the + literal; the deploy returns a `fill`-generated address. +- **Value passed to `address=`:** remove both the literal *and* the `address=` + argument, per contract, filling after each. +- **No-op case:** `to=None` creation tests often have no hardcoded address at all + (the created address is `compute_create_address(sender, nonce=0)`). Confirm by + grepping for `Address(0x` / `address=`. +- **On break:** some bytecode hardcodes that address as a CALL/CREATE target (or + the tx `to`/`data`). Thread the dynamic address through the caller and the tx + entry point instead. +- **Self-reference:** a contract that hardcodes its *own* deploy address (e.g. + `Op.BALANCE(0xF172…)` where `0xF172…` is its own `address=`). Threading a + `fill`-generated address in is impossible (chicken-and-egg), so replace the + self-reference with the opcode that yields it at runtime — `Op.BALANCE(Op. + ADDRESS)`. Don't substitute a *different* opcode that happens to be shorter + (e.g. `Op.SELFBALANCE`) if it changes what the test exercises. +- **Remove `@pytest.mark.pre_alloc_mutable`** once the test no longer hardcodes + addresses/nonces or assigns `pre[...]` directly — i.e. all allocation now goes + through `fund_eoa` / `deploy_contract` / `nonexistent_account`. Fill to confirm. + +### 5. Remove easy boilerplate values +Independent and usually safe (batchable): `pre.fund_eoa(amount=...)` → `fund_eoa()`; +tx `value`; tx `data` when it is empty (`Bytes("")`); explicit gas price fields. +Keep any of these that the post actually checks or that triggers the behavior +under test. +- **Drop opcode args that just pass their default.** Ported bytecode often spells + out zero operands that are already the default, e.g. `Op.CALL(..., args_offset=0, + args_size=0, ret_offset=0, ret_size=0)` — all four are `0` by default. Removing + them is a no-op on the assembled bytecode (verify once with + `bytes(a) == bytes(b)`) and cuts noise. Applies to any opcode arg equal to its + default. +- **Drop the hardcoded subcall `gas` operand — this is a correctness fix, not + cosmetics.** `Op.CALL`/`CALLCODE`/`DELEGATECALL`/`STATICCALL` default `gas` to + `Op.GAS` (forward all remaining). Ported fillers hardcode a constant + (`gas=0xEA60`, `gas=0x186A0`) that was sized for the *old* gas schedule; once + EIP-8037 inflates the callee's state gas (e.g. a zero→non-zero SSTORE jumps to + ~97920), that fixed budget no longer covers the callee and the subcall OOGs on + Amsterdam — a common reason a pure-behavior test lands on the skip list. Omit + the operand so it forwards everything. **Caveat:** forwarding all gas via + `Op.GAS` misbehaves on **pre-EIP-150 (Homestead)** — the sweep (step 11) fails + only there, so such tests floor at **TangerineWhistle**. Keep an explicit `gas` + operand *only* when the amount forwarded is the subject (an OOG-boundary test). + **Budget vs. subject:** before dropping the operand, ask *why* the constant + has its value. A mid-sized constant (`0xEA60`) is a *budget* sized for the old + schedule — drop it. An absurd or boundary constant (`2**256 - 20`) is the + *subject*: it exercises the 63/64 clamp on an oversized ask (a client that + computed e.g. `requested + stipend` in wrapping arithmetic would forward + almost nothing and fail). Keep it, name it (`OVERSIZED_GAS_ASK`), and state + the intent in a comment. Validated on `test_make_money`. +- **A codeless / absent call target is `pre.nonexistent_account()`**, not + `pre.fund_eoa(amount=0)`. It yields an address guaranteed to hold no code and + no state, which is what "call an empty contract" tests mean. +- **Drop a stale `# noqa: F841`** on `contract = pre.deploy_contract(...)` once the + variable is actually used (in `to=` / the post); leaving it triggers `RUF100`. + +### 6. (Parametrized tests) Analyze what the `data` parameter is +Look at `tx.data` / `tx.to`: +- **Scenario A — data is a target contract address:** the tx lands in a thin + entry-point contract that just `CALL`s the address from calldata. Usually you + can **delete the entry-point** and call the target directly, and the N targets + are near-identical → replace N bytecode copies with a **dynamic generator** + parameterized by the small difference. When the targets are *gas-measurement* + contracts differing only by the measured opcode, the dedup collapses all the + way to a single `CodeGasMeasure(code=opcode)` parametrized on the opcode + (step 10) — the entry-point's `CALL` was only a delivery mechanism. Validated + on `test_push0_gas2` (PUSH0 vs PUSH1 0x00). +- **Scenario B — data is initcode:** spotted by **`to=None`**. Decide whether + running inside initcode is *required* by the test (e.g. the test is about + initcode-context behavior, per its title/docstring) or just an artifact of the + static-filler format (most common — then the logic can move to a normal + deployed contract). If required, convert the `tx_data` array into an + `initcode(d)` **generator function**: even when variants are genuinely + different programs, the function form lets each branch be labeled by intent, + surfacing the one thing that varies. + +### 7. (Parametrized tests) Simplify `expect_entries_` / `resolve_expect_post` +**First, identify which index actually discriminates — it is *not* always `d`.** +Ported tests also key on `g` (gas) or `v` (value); check both the +`expect_entries_` `indexes` (which axis is non-`-1`) and which of +`tx_data[d]`/`tx_gas[g]`/`tx_value[v]` is the list with >1 entry. The other two +indexes are pinned/wildcard. (Example: `test_add_non_const` varies `v` — +`d`/`g` are fixed at 0 and the `indexes` match on `"value"`.) +**Precondition** (to collapse to a per-case form): every entry's `network` is +implied by `valid_from` and there is no `expect_exception`. Then the post is a +pure function of the discriminating index. +- Convert `expect_entries_` into a plain **list of `result` dicts indexed by the + discriminator** — duplicating identical entries (e.g. data `[0,1]` → two + slots) is fine and preferred; an explicit flat list is easiest to reason about. +- **When the discriminator is a real quantity** (the tx `value` or `gas`), + parametrize *directly on that quantity* (`parametrize("tx_value", [0, 1])`) + rather than an opaque index, feed it straight into the `Transaction`, and + express the post as a function of it. A clean closed form is ideal — + e.g. `Account(storage={0: 2 * tx_value})` for a contract that stores + `ADD(BALANCE, BALANCE)` of a balance equal to the sent value (this is the + "encode relationships" idea from step 9 applied to the post). +- Cascade: delete the `resolve_expect_post` import, the `_exc` it returned, and + the tx's `error=_exc`. +- **Optionally merge** the data-generator and the post-list into **one + `if/elif/else` on `d`** that sets both `initcode` and `post` per case. This + co-locates each case's bytecode with its expected state — the strongest + readability win, and it tends to *reveal* incomplete verification. Use a final + `else` so every branch binds both vars; declare `initcode: Bytecode` and + `post: dict` above the switch. Prefer the array form when cases are many or + the switch would be unwieldy; this is a judgment call. +- **Clean up the `parametrize` signature.** The ported `"d, g, v"` triple is + usually overkill: drop the pinned/unused indexes from both the `parametrize` + and the function signature, keep the discriminator, and rename it to something + meaningful (and `fork` too, if no longer used). Parametrize on the renamed axis: + - **String values** (e.g. `parametrize("opcode", ["calldataload", + "calldatacopy", "codecopy"])`) read best when the cases are distinct + programs; pytest derives the test ids straight from the strings (matching the + old `id=`s), and the switch branches become `if opcode == "calldataload"`. + - **`Op` values** (e.g. `parametrize("opcode", [Op.SLOAD, Op.TLOAD])`) are + cleaner *only* when the opcode plugs directly into a shared bytecode template; + avoid forcing it when each case needs structurally different code. + - Drop the verbose `pytest.param(..., id=...)` wrapping when the bare values + already give good ids. + +### 7b. Consolidate near-identical sibling files +Ported fillers often arrive as a fan of files with near-identical names that +differ in one axis — `test_non_zero_value_{call,callcode,delegatecall}` × +`{,_to_empty,_to_one_storage_key,…}`. Once enhanced to the same shape, **join +them into one parametrized test** (`parametrize("opcode, target_kind", …)` with +ids matching the old filenames), set up the varying piece (call op, target +pre-state) from the params, and merge every source into a single `ported_from` +list. One readable file replaces N. Validated: 10 `NonZeroValue_*` files → +`test_non_zero_value.py`. + +### 8. Strengthen post-state verification +Co-locating bytecode and post (step 7) often exposes that the ported test barely +verifies anything. Improve coupling and observability: +- **Couple the expectation to the bytecode.** If a contract returns its own code + (`CODECOPY`+`RETURN`), assert `code=initcode` instead of a hand-copied + `bytes.fromhex(...)` — change the bytecode and the expectation follows. +- **Make no-op results observable.** Storing `0` is indistinguishable from not + storing (and `storage={}` already asserts "all slots zero" — see + `Storage.must_be_equal`). To genuinely prove a read returned zero, store a + derived non-zero value (e.g. `Op.ADD(Op.CALLDATALOAD(0), 1)` → assert `1`). +- **Zero source data makes offset tests vacuous.** A test that asserts an + out-of-bounds read yields zeros proves nothing if the *in-bounds* data is + also all zeros — any offset, right or wrong, reads zero. Supply non-zero + source bytes (e.g. `data=bytes(range(1, 33))` for a CALLDATACOPY test) so a + client reading from a wrong in-bounds offset produces a visible mismatch. + Ported fillers often ship all-zero calldata; the rewrite is the moment to + fix it. Validated on `test_copy_offset`. +- **Preserve every assertion the legacy filler made — count its slots.** A + ported post often pins *two* observables (e.g. the ask fillers stored both + the callee-observed gas *and* the caller's net gas, which proves unused + forwarded gas is credited back). When reframing, it is easy to carry over + the headline assertion and silently drop the second. Diff the old post's + slots against the new one and re-express each dropped slot dynamically (or + justify its removal explicitly). Validated on `test_raw_call_gas_ask` (the + caller reports its remaining gas up the stack as a second return word). +- **Add a canary.** Write a distinctive non-zero sentinel to an extra slot as the + *final* step (e.g. `Op.SSTORE(0x2, 0xC0DE)`), and assert it. If creation + reverts or the code doesn't run to completion, the slot stays zero and the + test fails loudly instead of silently passing on a coincidentally-matching + (often empty) account. +- Adding `SSTORE`s costs gas — this is why step 2 (max out gas) comes first. +- **Spot a *degraded* port and restore its stated intent.** A ported test whose + name/source promises a scenario its values don't actually exercise is a bug in + the port, not something to preserve faithfully. Classic tell: a + `*_after_value_transfer` / `*_with_value` test that sends `value=0`, so the + observable it names (a callee's `CALLVALUE`, a recipient's balance) is + vacuously zero and would pass even if the behavior were broken. Fix it by + supplying the missing ingredient (a non-zero tx `value`) and asserting the + now-meaningful result (`CALLVALUE == transferred`, recipient balance moved) — + note the restoration in the `@manually-enhanced` line. Validated on + `test_deleagate_call_after_value_transfer` (DELEGATECALL preserves the + enclosing frame's value). Read the test's *name and source comment* against + what it actually checks; the gap is the enhancement. + +### 9. Introduce variables that encode relationships +Whenever a literal carries intent or two literals are logically linked, lift them +into named variables that express the *relationship*, not just the value. E.g. +`create_value = 0xB` fed to both `Op.CREATE(value=create_value, ...)` and the tx +`value=create_value - 1` documents an intentional off-by-one (insufficient +balance) and keeps the two coupled so a future edit can't desync them. Same idea +ties a `CREATE`'s `size` operand to the memory/gas math that depends on it. +- **Post-state derived from gas/fees.** When the asserted value is a function of + the gas charge (e.g. an origin `BALANCE` read mid-execution equals + `sender_balance - gas_limit * effective_gas_price`), express it as that formula + rather than a hardcoded number. Such a test is gas-sensitive — keep an explicit + `gas_limit` (step 10), since the observable depends on it, but **derive that + `gas_limit` too** — `fork.transaction_intrinsic_cost_calculator()() + + code.gas_cost(fork) + buffer` (conservative metadata so it can't undershoot) — + so it is neither a magic number nor fork-fragile. Validated on + `test_sender_balance` (EIP-1559 effective-vs-max price). +- **But first ask whether the gas-derived value is the *subject* or just + noise.** A ported test often pins the `sender` balance to `initial − value − + gas_used * price` — pure filler bookkeeping, not what the test is about. If the + real subject is a gas-*independent* fact (a value flow `tx → caller → callee`, + a storage write, a created account), drop the `gas_limit` (step 2), drop the + fragile `sender`-balance assertion, and instead assert the gas-independent + facts, encoding them as a relationship (`caller: INITIAL + tx_value - + call_value`, `callee: INITIAL + call_value`). Only reach for the "derive the + fee formula" machinery above when the fee itself is the observable. Validated + on `test_make_money`. + +### 10. (Gas-subject / gas-snapshot tests) Replace hardcoded gas with dynamic calculation +Covers both tests that *assert* a gas amount and the dominant +`amsterdam_skip_list.txt` shape: a legacy `GAS` snapshot / `SUB(@gas_before, +GAS)` delta stored to slot `0`/`0x64`. That stored value is *why* EIP-8037 +breaks the test, but it is real coverage — **preserve and fork-robustify it, do +not drop it.** + +**The `CodeGasMeasure` workflow:** +- **Isolate** the bytecode under measurement into a variable + (`call_code = Op.CALL(...)`). This often reveals the legacy measured window + bundled extra ops — e.g. it wrapped an `SSTORE`, inflating the value by a cold + `SSTORE` (~22100). Isolating the opcode measures only it (a large but + *explainable* re-pin — see Re-pinning). +- **Wrap** it: `CodeGasMeasure(code=call_code, extra_stack_items=N, sstore_key=K)`. + It self-calibrates (subtracts its own `GAS` ops and `overhead_cost`) so the + stored value is the opcode's real cost. `extra_stack_items` = items the + measured code leaves on the stack (`CREATE`/`CALL` leave 1) — wrong value + corrupts the result. `sstore_key` = the slot the post asserts. +- **`extra_stack_items=1` silently discards a call's success flag — keep it + observable.** `CodeGasMeasure` SWAP/POPs the extra item, and gas alone + cannot replace it: a wrongly *failed* call refunds the child gas + stipend, + so it measures identically to a *success* into an empty callee, and for + `CALLCODE`/`DELEGATECALL` no balance moves either — the whole post-state is + then blind to the failure. When the measured op is a call whose success is + not otherwise observable, fold the flag into the measured window: + `store_code = Op.SSTORE(flag_slot, call_code, key_warm=False, + original_value=0, new_value=1)` with `extra_stack_items=0`, assert + `flag_slot: 1` in the post, and expect `store_code.gas_cost(fork)` (the + SSTORE's cost is now part of the measurement — and a failed call would + store 0, shifting the measured gas too, so the failure is doubly loud). + Validated on `test_non_zero_value`. +- **Apply opcode metadata from the test's context** so `gas_cost(fork)` is + correct (see `docs/writing_tests/opcode_metadata.md`). For `CALL`: + `address_warm` (is the target pre-accessed?), `value_transfer` (value > 0?), + `account_new` (target absent/empty and receiving value → created?). Use + `pre.nonexistent_account()` for a target that must stay **cold + non-existent** + so `account_new` holds — a `fund_eoa()` target already exists (warm/created) and + would change the cost. + - For `CREATE`/`CREATE2`: `new_memory_size` (the init-code window the offset/ + size operands touch, e.g. `size=0x20` → `new_memory_size=0x20`) **and** + `init_code_size` (drives the EIP-3860 per-word cost, Shanghai+). Omitting + `init_code_size` silently under-predicts by `CODE_INIT_PER_WORD * + ceil(size/32)` (2/word) — a small, easily-missed miss. `CREATE` leaves the + created address on the stack → `extra_stack_items=1`. + - **A runtime address threaded via `SLOAD`** (the create-then-call idiom: + store `CREATE`'s result, then `CALL(address=Op.SLOAD(slot))`) must mark that + `SLOAD` `key_warm=True` — the slot was just written so it is warm at runtime, + but the metadata default is cold and `gas_cost(fork)` would over-predict by + `cold − warm` (2000). An account freshly made by `CREATE` is **warm + already + existing**: `address_warm=True, account_new=False` on the following `CALL`. +- **Express the expected value dynamically** from the same metadata-bearing + variable: `call_code.gas_cost(fork)` (add `fork: Fork`). Both the bytecode and + the expectation are now fork-aware. + +**CALL value-transfer stipend.** A value-bearing `CALL` whose callee consumes +nothing (empty account / EOA) measures `gas_cost(fork) - +fork.gas_costs().CALL_STIPEND`: `gas_cost` counts the full value cost, but the +2300 stipend is forwarded to the callee and returned unused. Confirm the +`- CALL_STIPEND` holds on *every* fork (it is a fork-stable relationship, not a +coincidence). + +**EIP-8037 state-gas reservoir — critical.** Omitting `gas_limit` (step 2) on an +EIP-8037 fork *maxes the state-gas reservoir*, so state gas (e.g. account +creation) is **not** charged against what the `GAS` opcode sees — the measurement +silently loses it (observed 192921 → 9321) and only the future fork breaks. Fix: +keep `gas_limit` omitted **and** add an explicit `state_gas_reservoir=0` to the +`Transaction`. That pins the gas limit to exactly the cap (no reservoir) so state +gas is charged and measurable, and is a no-op on pre-EIP-8037 forks (a *positive* +reservoir there raises; `0` does not, and it must be set explicitly — the default +is treated as "unset"). This keeps a `CodeGasMeasure` test clean (no magic +`gas_limit`) yet correct on Amsterdam. + +**Absolute `GAS` readings are unsalvageable — convert to a delta.** A test that +stores a *raw* `GAS` value (not a `SUB(before, GAS)` delta) — e.g. `SSTORE(0, +GAS)` right after entry — pins `gas_limit - intrinsic - overhead`. Amsterdam +re-priced the **intrinsic transaction cost** (EIP-2780: base 21000 → 15000), so +that stored value shifts by a fixed amount (observed 578998 → 584998, a 6000 +jump) *independent of any state gas* — `state_gas_reservoir=0` does **not** fix +it. The only robust move is to stop storing absolute readings: wrap the measured +op in `CodeGasMeasure` (which stores the *delta* between two `GAS` reads, immune +to intrinsic) and assert `code.gas_cost(fork)`. A legacy `[[0]](GAS) … +[[100]](GAS)` snapshot pair *is* such a delta in disguise — the pair brackets one +operation (e.g. a `CREATE`); collapse it to a single `CodeGasMeasure` around that +op and drop both raw slots. Validated on the `CREATE_EmptyContract*` family. + +**Decompose the constant empirically** when no single helper applies (throwaway +script against the fork): pin each term to the known-good number, then assemble. +Map terms to fork-derived helpers: opcode base+pushes → `bytecode.gas_cost(fork)`; +memory growth → `fork.memory_expansion_gas_calculator()(new_bytes=, +previous_bytes=)`; EIP-3860 init-code words → `fork.gas_costs().CODE_INIT_PER_WORD +* ceil(size/32)`. You can also call `.gas_cost` / `.regular_cost` / `.state_cost` +on exactly the measured bytecode. + +**Nested / callee-side measurements.** When the measured op is a `CALL` whose +callee does real work, the measured cost = `call_code.gas_cost(fork) + +callee_code.gas_cost(fork)` (the CALL's own cost plus what the callee consumed). +Attach the callee's opcode metadata (e.g. SSTORE `key_warm`/`original_value`/ +`new_value`) so its `gas_cost` is right, and decompose against the callee's +*actual* bytecode rather than a reconstruction — a value supplied by `GAS` costs +2, not a `PUSH`'s 3, and that off-by-3 is a real trap. A callee-side gas snapshot +(`SSTORE(k, GAS)`) stores `forward_gas - Op.GAS.gas_cost(fork)`. Derive the +forwarded gas dynamically — `forward_gas = callee_store.gas_cost(fork) + buffer` +— rather than a magic number; under EIP-8037 a cold zero->non-zero SSTORE can +cost ~100k, so a fixed value is both fork-fragile and brittle. (Size the SSTORE +with a placeholder `new_value`: its cost depends only on the zero->non-zero +transition, not the magnitude — which also breaks the `forward_gas`/`new_value` +circularity.) Set `state_gas_reservoir=0` so the state gas is captured. +Validated on `test_raw_call_gas`. + +**Measuring forwarded gas / the EIP-150 63/64 rule (the `*_gas_ask` shape).** +Ported fillers probe "how much gas does a subcall receive when it asks for more +than is available" by pinning an absolute forwarded amount — fork-fragile, +because "available" moves with the EIP-2780 intrinsic change. Make it robust +with three moves: (1) **cap the caller frame's gas to a known budget** with an +*outer* call (`entry → CALL(gas=CALLER_GAS) → caller`); because `CALLER_GAS` is +far below the outer frame's 63/64, the caller receives exactly `CALLER_GAS` +independent of the tx gas limit. (2) **Return the observed `GAS` up the stack** +(`MSTORE(0, GAS) + RETURN(0, 32)` in the callee, `RETURN` again in the caller, +`SSTORE` only in the top frame) instead of `SSTORE`-ing in a lower frame — +avoids the EIP-8037 state-gas trap. (3) **Derive the expectation from the fork:** +``` +available = CALLER_GAS - caller_call_code.gas_cost(fork) +forwarded = available - available // 64 # NOT available * 63 // 64 +expected_gas = forwarded + stipend - Op.GAS.gas_cost(fork) +``` +where `stipend = fork.gas_costs().CALL_STIPEND` for a value-bearing call (0 +otherwise). **The `// 64` form is the trap:** `available - available // 64` and +`available * 63 // 64` differ by exactly 1 whenever `available % 64 != 0` (the +EVM uses the former). One parametrize over `(opcode, value, memory)` covers the +whole CALL/CALLCODE/DELEGATECALL family; floor **Berlin** (the call metadata). +Validated on `test_raw_call_gas_ask` (10 RawCall*GasAsk fillers). + +**Error paths charge regular gas only — assert `regular_cost(fork)`.** A failed +`CREATE`/`CALL` still charges its regular costs (base, memory, init-code words) +but creates no account, so **no state gas is charged** under EIP-8037. For a +success/failure parametrize, that is exactly the `gas_cost(fork)` vs +`regular_cost(fork)` split: success measures `code.gas_cost(fork)` (regular + +state), failure measures `code.regular_cost(fork)` (regular only). On pre-8037 +forks `state_cost` is 0 so the two coincide — one expression, correct on every +fork. Drive a `CREATE` down the balance-failure path by funding the creator one +wei short of the transferred `value` (`balance = value - 1`); the created +address is then `Account.NONEXISTENT`. Validated end-to-end on +`test_raw_create_gas` (6 RawCreate*Gas fillers consolidated). + +### 11. Lower `valid_from` to extend coverage +The ported `valid_from` (often `Cancun`) is usually higher than necessary — lower +it to widen coverage. Find the true floor empirically: temporarily delete the +`valid_from` marker and fill with no `--fork` (the framework then runs from +Frontier up); the earliest fork that *passes* is your floor. Set +`@pytest.mark.valid_from("<that fork>")` — the marker is mandatory, so this is a +lowering, never a true removal. +- **Gas tests floor at the EIP that introduced their metadata.** A test using + `address_warm` / cold-access metadata + `gas_cost(fork)` is only valid from + **Berlin (EIP-2929)**: earlier forks have no warm/cold distinction, so + `gas_cost` over-predicts by `cold − flat` (2600 − 700 = 1900) and every + pre-Berlin fork fails the measurement. Same shape elsewhere — EIP-3860 + init-code metering floors at Shanghai, etc. The floor is whichever EIP the + test's behavior/metadata depends on, which the empirical sweep reveals directly. +- **Behavioral floors show up as non-gas mismatches in the sweep.** A CREATE + test asserting the created account has `nonce=1` floors at **SpuriousDragon + (EIP-161)** — earlier forks start contract nonces at 0, so Frontier/Homestead/ + TangerineWhistle fail on the nonce, not the gas. Read *what* the sweep's + earliest-passing fork is gated on; it is not always a gas-schedule change. +- **A `bad v` / `INVALID_SIGNATURE_VRS` failure is a signature floor, not a + real one — don't raise `valid_from` for it.** The default `Transaction` is + EIP-155-protected, which pre-SpuriousDragon forks reject. Instead set + `protected=fork.supports_protected_txs()` (add `fork: Fork`): it goes + unprotected on Frontier/Homestead/TangerineWhistle and protected from + SpuriousDragon on. This keeps the floor at the *behavior's* real EIP (e.g. + Homestead for `DELEGATECALL`) instead of masking it at SpuriousDragon. + Validated on `test_delegatecall_emptycontract`. + +## Re-pinning expected values + +When a measurement rewrite (step 10) or bytecode change shifts a stored value, +the workflow is: change → `fill` → read the `KeyValueMismatchError` (`want … got +…`) → update the expected value to the `got` → `fill` again. +**Sanity gate:** the shift must be *explainable* — either small (the gas of +removed framing ops) or large-but-precisely-accounted (e.g. isolating an opcode +in `CodeGasMeasure` drops a cold `SSTORE` ~22100 the legacy window had bundled). +A jump you cannot account for means the rewrite changed *what* is being measured +— stop and investigate, don't just paste the number. + +## `@manually-enhanced` markers + +A docstring `@manually-enhanced: Do not overwrite` marks a deliberate prior fix. +Respect it by default. It may be removed only when a *better* enhancement makes +the workaround it documents obsolete (e.g. maxing out gas removes a per-fork gas +budget hack) — and only under explicit direction. +**Add the marker as the closing step** once a test's enhancements are intentional +(genuinely-verifying post, dynamic addresses/gas) so future auto-porting won't +regress them; briefly state what was enhanced. Place it in the **module +docstring**, after the `Ported from:` block (blank line before), as a single +line: `@manually-enhanced: Do not overwrite. <what changed>.` (keep it ≤79 +chars). + +## Known gaps (extend me) + +Not yet covered by a validated walkthrough; figure out and append when hit: +- Tests where **more than one** parametrize index varies at once (a genuine 2-D + `data` × `value`/`gas` matrix) — single-axis `d`/`g`/`v` discrimination is now + handled (step 7), but a multi-axis post is not yet exercised. +- Multi-block / `blockchain_test` ported tests. + +## Finishing + +**Remove the skip-list entry.** Once the test passes on the future fork, delete +its line from `tests/ported_static/amsterdam_skip_list.txt` and decrement both +its per-directory count header (`# stXxx (N)`) and the `# Total entries:` count. +Confirm with a full-range fill (`--fork` omitted) with the entry gone — that is +the definition of done. + +**Final sweep checklist** — each of these has been missed in practice; check +them one by one before calling the test done: +- `@pytest.mark.pre_alloc_mutable` removed if no hardcoded addresses/ + nonces/`pre[...]` remain (it silently skips the test in execute mode). +- No machine-port placeholder docstrings left (`Test_<filename>.`) — the + module and function docstrings say what the test verifies, in + imperative mood ("Verify/Measure ...", not "Gas cost of ..."). +- Docstrings re-read against the *final* architecture: collapsing a + delivery CALL or moving value onto the tx makes "inherited from the + enclosing CALL"-style prose stale. +- Inline magic operands named (`FORWARDED_GAS`, `GAS_SLOT`, ...) — + consistent with sibling files in the same directory. +- Pinned budget constants guarded: anything like + `available = BUDGET - code.gas_cost(fork)` gets an + `assert available > 0, ...` so a future repricing that outgrows the + budget fails loudly at fill time instead of producing a garbage + expectation. +- The old post's slots all accounted for (see step 8's "count its + slots"). + +When done, offer to run `/lint`. Note that pydantic coercion warnings +(`dict→Alloc/Storage`, `Bytecode→Bytes`, unfilled optional `Transaction` params) +are false positives from the type checker, not real issues. diff --git a/CLAUDE.md b/CLAUDE.md index 806edfc565b..e29e0084072 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -42,6 +42,7 @@ When reviewing PRs that implement or test EIPs: ## When to Use Skills - Writing or modifying tests → run `/write-test` first +- Cleaning up or future-proofing a `tests/ported_static/` test → run `/enhance-ported-test` first - Writing or modifying pytester-based plugin tests → run `/pytester` first - Filling test fixtures → run `/fill-tests` first - Implementing an EIP or modifying fork code in `src/` → run `/implement-eip` first @@ -55,6 +56,7 @@ When reviewing PRs that implement or test EIPs: ## Available Skills - `/write-test` — test writing patterns, fixtures, markers, bytecode helpers +- `/enhance-ported-test` — ordered methodology to clean up & future-proof `tests/ported_static/` tests - `/pytester` — pytester execution modes, isolation, output handling for plugin tests - `/fill-tests` — `fill` CLI reference, flags, debugging, benchmark tests - `/implement-eip` — fork structure, import rules, adding opcodes/precompiles/tx types diff --git a/tests/ported_static/amsterdam_skip_list.txt b/tests/ported_static/amsterdam_skip_list.txt index 4423a666f35..7aba6f38bb0 100644 --- a/tests/ported_static/amsterdam_skip_list.txt +++ b/tests/ported_static/amsterdam_skip_list.txt @@ -8,7 +8,7 @@ # Entries are substring-matched against each pytest nodeid (after # stripping the fixture-format suffix in conftest.py). # -# Total entries: 258 +# Total entries: 153 # stAttackTest (1) stAttackTest/test_crashing_transaction.py::test_crashing_transaction[fork_Amsterdam] @@ -73,7 +73,7 @@ stCreate2/test_revert_depth_create_address_collision_berlin.py::test_revert_dept stCreate2/test_revert_depth_create_address_collision_berlin.py::test_revert_depth_create_address_collision_berlin[fork_Amsterdam-d1-g1-v0] stCreate2/test_revert_depth_create_address_collision_berlin.py::test_revert_depth_create_address_collision_berlin[fork_Amsterdam-d1-g1-v1] -# stCreateTest (40) +# stCreateTest (36) stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create-0xef-v1] stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create-contructor-revert-v1] stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create-high-nonce-v1] @@ -90,10 +90,6 @@ stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_af stCreateTest/test_create_e_contract_create_ne_contract_in_init_oog_tr.py::test_create_e_contract_create_ne_contract_in_init_oog_tr[fork_Amsterdam--g0] stCreateTest/test_create_e_contract_create_ne_contract_in_init_oog_tr.py::test_create_e_contract_create_ne_contract_in_init_oog_tr[fork_Amsterdam--g1] stCreateTest/test_create_e_contract_then_call_to_non_existent_acc.py::test_create_e_contract_then_call_to_non_existent_acc[fork_Amsterdam] -stCreateTest/test_create_empty_contract.py::test_create_empty_contract[fork_Amsterdam] -stCreateTest/test_create_empty_contract_and_call_it_0wei.py::test_create_empty_contract_and_call_it_0wei[fork_Amsterdam] -stCreateTest/test_create_empty_contract_and_call_it_1wei.py::test_create_empty_contract_and_call_it_1wei[fork_Amsterdam] -stCreateTest/test_create_empty_contract_with_balance.py::test_create_empty_contract_with_balance[fork_Amsterdam] stCreateTest/test_create_empty_contract_with_storage.py::test_create_empty_contract_with_storage[fork_Amsterdam] stCreateTest/test_create_empty_contract_with_storage_and_call_it_0wei.py::test_create_empty_contract_with_storage_and_call_it_0wei[fork_Amsterdam] stCreateTest/test_create_empty_contract_with_storage_and_call_it_1wei.py::test_create_empty_contract_with_storage_and_call_it_1wei[fork_Amsterdam] @@ -115,12 +111,10 @@ stCreateTest/test_transaction_collision_to_empty_but_code.py::test_transaction_c stCreateTest/test_transaction_collision_to_empty_but_nonce.py::test_transaction_collision_to_empty_but_nonce[fork_Amsterdam--g1-v0] stCreateTest/test_transaction_collision_to_empty_but_nonce.py::test_transaction_collision_to_empty_but_nonce[fork_Amsterdam--g1-v1] -# stDelegatecallTestHomestead (6) +# stDelegatecallTestHomestead (4) stDelegatecallTestHomestead/test_call1024_oog.py::test_call1024_oog[fork_Amsterdam--g0] stDelegatecallTestHomestead/test_call1024_oog.py::test_call1024_oog[fork_Amsterdam--g1] -stDelegatecallTestHomestead/test_deleagate_call_after_value_transfer.py::test_deleagate_call_after_value_transfer[fork_Amsterdam] stDelegatecallTestHomestead/test_delegatecall1024_oog.py::test_delegatecall1024_oog[fork_Amsterdam] -stDelegatecallTestHomestead/test_delegatecall_emptycontract.py::test_delegatecall_emptycontract[fork_Amsterdam] stDelegatecallTestHomestead/test_delegatecall_in_initcode_to_existing_contract.py::test_delegatecall_in_initcode_to_existing_contract[fork_Amsterdam] # stEIP150Specific (7) @@ -132,104 +126,13 @@ stEIP150Specific/test_transaction64_rule_d64e0.py::test_transaction64_rule_d64e0 stEIP150Specific/test_transaction64_rule_d64m1.py::test_transaction64_rule_d64m1[fork_Amsterdam] stEIP150Specific/test_transaction64_rule_d64p1.py::test_transaction64_rule_d64p1[fork_Amsterdam] -# stEIP150singleCodeGasPrices (28) +# stEIP150singleCodeGasPrices (2) stEIP150singleCodeGasPrices/test_gas_cost.py::test_gas_cost[fork_Amsterdam-d40] stEIP150singleCodeGasPrices/test_gas_cost_berlin.py::test_gas_cost_berlin[fork_Amsterdam-d40] -stEIP150singleCodeGasPrices/test_raw_call_code_gas.py::test_raw_call_code_gas[fork_Amsterdam] -stEIP150singleCodeGasPrices/test_raw_call_code_gas_ask.py::test_raw_call_code_gas_ask[fork_Amsterdam] -stEIP150singleCodeGasPrices/test_raw_call_code_gas_memory.py::test_raw_call_code_gas_memory[fork_Amsterdam] -stEIP150singleCodeGasPrices/test_raw_call_code_gas_memory_ask.py::test_raw_call_code_gas_memory_ask[fork_Amsterdam] -stEIP150singleCodeGasPrices/test_raw_call_code_gas_value_transfer.py::test_raw_call_code_gas_value_transfer[fork_Amsterdam] -stEIP150singleCodeGasPrices/test_raw_call_code_gas_value_transfer_ask.py::test_raw_call_code_gas_value_transfer_ask[fork_Amsterdam] -stEIP150singleCodeGasPrices/test_raw_call_code_gas_value_transfer_memory.py::test_raw_call_code_gas_value_transfer_memory[fork_Amsterdam] -stEIP150singleCodeGasPrices/test_raw_call_code_gas_value_transfer_memory_ask.py::test_raw_call_code_gas_value_transfer_memory_ask[fork_Amsterdam] -stEIP150singleCodeGasPrices/test_raw_call_gas.py::test_raw_call_gas[fork_Amsterdam] -stEIP150singleCodeGasPrices/test_raw_call_gas_ask.py::test_raw_call_gas_ask[fork_Amsterdam] -stEIP150singleCodeGasPrices/test_raw_call_gas_value_transfer.py::test_raw_call_gas_value_transfer[fork_Amsterdam] -stEIP150singleCodeGasPrices/test_raw_call_gas_value_transfer_ask.py::test_raw_call_gas_value_transfer_ask[fork_Amsterdam] -stEIP150singleCodeGasPrices/test_raw_call_gas_value_transfer_memory.py::test_raw_call_gas_value_transfer_memory[fork_Amsterdam] -stEIP150singleCodeGasPrices/test_raw_call_gas_value_transfer_memory_ask.py::test_raw_call_gas_value_transfer_memory_ask[fork_Amsterdam] -stEIP150singleCodeGasPrices/test_raw_call_memory_gas.py::test_raw_call_memory_gas[fork_Amsterdam] -stEIP150singleCodeGasPrices/test_raw_call_memory_gas_ask.py::test_raw_call_memory_gas_ask[fork_Amsterdam] -stEIP150singleCodeGasPrices/test_raw_create_fail_gas_value_transfer.py::test_raw_create_fail_gas_value_transfer[fork_Amsterdam] -stEIP150singleCodeGasPrices/test_raw_create_fail_gas_value_transfer2.py::test_raw_create_fail_gas_value_transfer2[fork_Amsterdam] -stEIP150singleCodeGasPrices/test_raw_create_gas.py::test_raw_create_gas[fork_Amsterdam] -stEIP150singleCodeGasPrices/test_raw_create_gas_memory.py::test_raw_create_gas_memory[fork_Amsterdam] -stEIP150singleCodeGasPrices/test_raw_create_gas_value_transfer.py::test_raw_create_gas_value_transfer[fork_Amsterdam] -stEIP150singleCodeGasPrices/test_raw_create_gas_value_transfer_memory.py::test_raw_create_gas_value_transfer_memory[fork_Amsterdam] -stEIP150singleCodeGasPrices/test_raw_delegate_call_gas.py::test_raw_delegate_call_gas[fork_Amsterdam] -stEIP150singleCodeGasPrices/test_raw_delegate_call_gas_ask.py::test_raw_delegate_call_gas_ask[fork_Amsterdam] -stEIP150singleCodeGasPrices/test_raw_delegate_call_gas_memory.py::test_raw_delegate_call_gas_memory[fork_Amsterdam] -stEIP150singleCodeGasPrices/test_raw_delegate_call_gas_memory_ask.py::test_raw_delegate_call_gas_memory_ask[fork_Amsterdam] - -# stEIP1559 (1) -stEIP1559/test_sender_balance.py::test_sender_balance[fork_Amsterdam] # stEIP158Specific (1) stEIP158Specific/test_exp_empty.py::test_exp_empty[fork_Amsterdam] -# stEIP3855_push0 (3) -stEIP3855_push0/test_push0_gas.py::test_push0_gas[fork_Amsterdam] -stEIP3855_push0/test_push0_gas2.py::test_push0_gas2[fork_Amsterdam-use_push0] -stEIP3855_push0/test_push0_gas2.py::test_push0_gas2[fork_Amsterdam-use_push1_00] - -# stEIP5656_MCOPY (55) -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src0_size0-g0] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src0_size0-g1] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src0_size1-g0] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src0_size1-g1] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src0_size31-g0] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src0_size31-g1] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src0_size32-g0] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src0_size32-g1] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src0_size33-g0] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src0_size33-g1] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src0_size44767-g0] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src0_size44767-g1] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src0_size44768-g0] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src0_size44768-g1] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src0_size44769-g0] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src0_size44769-g1] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src1_size0-g0] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src1_size0-g1] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src1_size1-g0] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src1_size1-g1] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src1_size31-g0] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src1_size31-g1] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src1_size32-g0] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src1_size32-g1] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src1_size33-g0] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src1_size33-g1] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src1_size44767-g0] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src1_size44768-g0] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src1_size44769-g0] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src31_size0-g0] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src31_size0-g1] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src31_size1-g0] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src31_size1-g1] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src31_size31-g0] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src31_size31-g1] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src31_size32-g0] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src31_size32-g1] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src31_size33-g0] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src31_size33-g1] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src31_size44767-g0] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src31_size44768-g0] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src31_size44769-g0] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src32_size0-g0] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src32_size0-g1] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src32_size1-g0] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src32_size1-g1] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src32_size31-g0] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src32_size31-g1] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src32_size32-g0] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src32_size32-g1] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src32_size33-g0] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src32_size33-g1] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src32_size44767-g0] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src32_size44768-g0] -stEIP5656_MCOPY/test_mcopy_copy_cost.py::test_mcopy_copy_cost[fork_Amsterdam-src32_size44769-g0] - # stHomesteadSpecific (1) stHomesteadSpecific/test_contract_creation_oo_gdont_leave_empty_contract_via_transaction.py::test_contract_creation_oo_gdont_leave_empty_contract_via_transaction[fork_Amsterdam] @@ -248,24 +151,10 @@ stMemExpandingEIP150Calls/test_call_goes_oog_on_second_level_with_mem_expanding_ stMemExpandingEIP150Calls/test_create_and_gas_inside_create_with_mem_expanding_calls.py::test_create_and_gas_inside_create_with_mem_expanding_calls[fork_Amsterdam] stMemExpandingEIP150Calls/test_new_gas_price_for_codes_with_mem_expanding_calls.py::test_new_gas_price_for_codes_with_mem_expanding_calls[fork_Amsterdam] -# stMemoryTest (4) -stMemoryTest/test_call_data_copy_offset.py::test_call_data_copy_offset[fork_Amsterdam] -stMemoryTest/test_code_copy_offset.py::test_code_copy_offset[fork_Amsterdam] +# stMemoryTest (2) stMemoryTest/test_oog.py::test_oog[fork_Amsterdam-success14] stMemoryTest/test_oog.py::test_oog[fork_Amsterdam-success15] -# stNonZeroCallsTest (10) -stNonZeroCallsTest/test_non_zero_value_call.py::test_non_zero_value_call[fork_Amsterdam] -stNonZeroCallsTest/test_non_zero_value_call_to_empty_paris.py::test_non_zero_value_call_to_empty_paris[fork_Amsterdam] -stNonZeroCallsTest/test_non_zero_value_call_to_one_storage_key_paris.py::test_non_zero_value_call_to_one_storage_key_paris[fork_Amsterdam] -stNonZeroCallsTest/test_non_zero_value_callcode.py::test_non_zero_value_callcode[fork_Amsterdam] -stNonZeroCallsTest/test_non_zero_value_callcode_to_empty_paris.py::test_non_zero_value_callcode_to_empty_paris[fork_Amsterdam] -stNonZeroCallsTest/test_non_zero_value_callcode_to_one_storage_key_paris.py::test_non_zero_value_callcode_to_one_storage_key_paris[fork_Amsterdam] -stNonZeroCallsTest/test_non_zero_value_delegatecall.py::test_non_zero_value_delegatecall[fork_Amsterdam] -stNonZeroCallsTest/test_non_zero_value_delegatecall_to_empty_paris.py::test_non_zero_value_delegatecall_to_empty_paris[fork_Amsterdam] -stNonZeroCallsTest/test_non_zero_value_delegatecall_to_non_non_zero_balance.py::test_non_zero_value_delegatecall_to_non_non_zero_balance[fork_Amsterdam] -stNonZeroCallsTest/test_non_zero_value_delegatecall_to_one_storage_key_paris.py::test_non_zero_value_delegatecall_to_one_storage_key_paris[fork_Amsterdam] - # stRefundTest (7) stRefundTest/test_refund50_2.py::test_refund50_2[fork_Amsterdam] stRefundTest/test_refund50percent_cap.py::test_refund50percent_cap[fork_Amsterdam] @@ -300,11 +189,7 @@ stSolidityTest/test_recursive_create_contracts.py::test_recursive_create_contrac stSolidityTest/test_test_contract_interaction.py::test_test_contract_interaction[fork_Amsterdam] stSolidityTest/test_test_contract_suicide.py::test_test_contract_suicide[fork_Amsterdam] -# stSpecialTest (1) -stSpecialTest/test_make_money.py::test_make_money[fork_Amsterdam] - -# stStaticCall (4) -stStaticCall/test_static_call_value_inherit_from_call.py::test_static_call_value_inherit_from_call[fork_Amsterdam] +# stStaticCall (3) stStaticCall/test_static_create_empty_contract_and_call_it_0wei.py::test_static_create_empty_contract_and_call_it_0wei[fork_Amsterdam] stStaticCall/test_static_create_empty_contract_with_storage_and_call_it_0wei.py::test_static_create_empty_contract_with_storage_and_call_it_0wei[fork_Amsterdam] stStaticCall/test_static_execute_call_that_ask_fore_gas_then_trabsaction_has.py::test_static_execute_call_that_ask_fore_gas_then_trabsaction_has[fork_Amsterdam-d0] diff --git a/tests/ported_static/stArgsZeroOneBalance/test_add_non_const.py b/tests/ported_static/stArgsZeroOneBalance/test_add_non_const.py index 0a24f5ffbc1..c0b91379c99 100644 --- a/tests/ported_static/stArgsZeroOneBalance/test_add_non_const.py +++ b/tests/ported_static/stArgsZeroOneBalance/test_add_non_const.py @@ -1,116 +1,67 @@ """ -Test_add_non_const. +Verify ADD over non-constant operands: the contract adds its own balance to +itself, where that balance equals the value sent by the transaction. Ported from: state_tests/stArgsZeroOneBalance/addNonConstFiller.yml + +@manually-enhanced: Do not overwrite. Parametrized on the transaction value +(the real discriminator), the self-referential balance reads use +`BALANCE(ADDRESS)` instead of a hardcoded address, and the post asserts the +`2 * tx_value` result directly; env/gas boilerplate removed. A canary slot +keeps the `tx_value=0` arm observable (its result slot stays zero). """ import pytest from execution_testing import ( Account, - Address, Alloc, - Bytes, - Environment, + Fork, StateTestFiller, Transaction, ) -from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( - resolve_expect_post, -) from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +CANARY = 0xC0DE + @pytest.mark.ported_from( ["state_tests/stArgsZeroOneBalance/addNonConstFiller.yml"], ) -@pytest.mark.valid_from("Cancun") -@pytest.mark.parametrize( - "d, g, v", - [ - pytest.param( - 0, - 0, - 0, - id="-v0", - ), - pytest.param( - 0, - 0, - 1, - id="-v1", - ), - ], -) -@pytest.mark.pre_alloc_mutable +@pytest.mark.valid_from("Frontier") +@pytest.mark.parametrize("tx_value", [0, 1]) def test_add_non_const( state_test: StateTestFiller, pre: Alloc, fork: Fork, - d: int, - g: int, - v: int, + tx_value: int, ) -> None: - """Test_add_non_const.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) + """Add the contract's own balance to itself and store the result.""" + sender = pre.fund_eoa() - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=1000000, - ) - - # Source: lll - # { [[ 0 ]](ADD (BALANCE <contract:target:0x095e7baea6a6c7c4c2dfeb977efac326af552d87>) (BALANCE <contract:target:0x095e7baea6a6c7c4c2dfeb977efac326af552d87>)) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 + # ADD with non-constant operands: the contract's own balance added to + # itself. The balance equals the value sent by the transaction. The + # canary proves the code ran even when the stored result is zero. + target = pre.deploy_contract( code=Op.SSTORE( key=0x0, - value=Op.ADD( - Op.BALANCE(address=0xF1722FE346FA35E045DE07E47CF6AF9BAE8ADE0A), - Op.BALANCE(address=0xF1722FE346FA35E045DE07E47CF6AF9BAE8ADE0A), - ), + value=Op.ADD(Op.BALANCE(Op.ADDRESS), Op.BALANCE(Op.ADDRESS)), ) + + Op.SSTORE(key=0x1, value=CANARY) + Op.STOP, - nonce=0, - address=Address(0xF1722FE346FA35E045DE07E47CF6AF9BAE8ADE0A), # noqa: E501 ) - expect_entries_: list[dict] = [ - { - "indexes": {"data": -1, "gas": -1, "value": 0}, - "network": [">=Cancun"], - "result": {target: Account(storage={0: 0})}, - }, - { - "indexes": {"data": -1, "gas": -1, "value": 1}, - "network": [">=Cancun"], - "result": {target: Account(storage={0: 2})}, - }, - ] - - post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork) - - tx_data = [ - Bytes(""), - ] - tx_gas = [400000] - tx_value = [0, 1] + # ADD(BALANCE, BALANCE) over a balance equal to the sent value. + post = {target: Account(storage={0: 2 * tx_value, 1: CANARY})} tx = Transaction( sender=sender, to=target, - data=tx_data[d], - gas_limit=tx_gas[g], - value=tx_value[v], - error=_exc, + value=tx_value, + protected=fork.supports_protected_txs(), ) - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stCreateTest/test_create_empty_contract.py b/tests/ported_static/stCreateTest/test_create_empty_contract.py index 9790df29d6c..469d1a3a521 100644 --- a/tests/ported_static/stCreateTest/test_create_empty_contract.py +++ b/tests/ported_static/stCreateTest/test_create_empty_contract.py @@ -1,17 +1,20 @@ """ -Test_create_empty_contract. +Test CREATE of an empty contract and measure the CREATE gas cost. Ported from: state_tests/stCreateTest/CREATE_EmptyContractFiller.json +state_tests/stCreateTest/CREATE_EmptyContractWithBalanceFiller.json + +@manually-enhanced: Do not overwrite. CREATE gas via CodeGasMeasure; dynamic +address + fork-derived cost; empty/with-balance folded into one parametrize. """ import pytest from execution_testing import ( Account, - Address, Alloc, - Bytes, - Environment, + CodeGasMeasure, + Fork, StateTestFiller, Transaction, compute_create_address, @@ -21,56 +24,61 @@ REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +GAS_SLOT = 0x64 + @pytest.mark.ported_from( - ["state_tests/stCreateTest/CREATE_EmptyContractFiller.json"], + [ + "state_tests/stCreateTest/CREATE_EmptyContractFiller.json", + "state_tests/stCreateTest/CREATE_EmptyContractWithBalanceFiller.json", + ], +) +@pytest.mark.valid_from("SpuriousDragon") +@pytest.mark.parametrize( + "create_value", + [ + pytest.param(0, id="empty_contract"), + pytest.param(1, id="with_balance"), + ], ) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable def test_create_empty_contract( state_test: StateTestFiller, pre: Alloc, + fork: Fork, + create_value: int, ) -> None: - """Test_create_empty_contract.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, + """CREATE an empty contract (empty init code) and measure its gas.""" + # CREATE with size=0x20 over never-written memory runs 32 zero bytes as + # init code (STOP on the first byte), depositing no code -> an empty + # account with nonce 1 (and the transferred value as balance). + create_code = Op.CREATE( + value=create_value, + offset=0x0, + size=0x20, + new_memory_size=0x20, + init_code_size=0x20, ) - - # Source: lll - # { [[0]](GAS) [[1]] (CREATE 0 0 32) [[100]] (GAS) } - contract_0 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=Op.GAS) - + Op.SSTORE(key=0x1, value=Op.CREATE(value=0x0, offset=0x0, size=0x20)) - + Op.SSTORE(key=0x64, value=Op.GAS) - + Op.STOP, - nonce=0, + contract = pre.deploy_contract( + code=CodeGasMeasure( + code=create_code, + extra_stack_items=1, + sstore_key=GAS_SLOT, + ), + balance=create_value, ) tx = Transaction( - sender=sender, - to=contract_0, - data=Bytes(""), - gas_limit=600000, + sender=pre.fund_eoa(), + to=contract, + state_gas_reservoir=0, ) + created = compute_create_address(address=contract, nonce=1) post = { - compute_create_address(address=contract_0, nonce=0): Account(nonce=1), - contract_0: Account( - storage={ - 0: 0x8D5B6, - 1: compute_create_address(address=contract_0, nonce=0), - 100: 0x7ABF8, - }, + contract: Account( + storage={GAS_SLOT: create_code.gas_cost(fork)}, balance=0 ), + created: Account(nonce=1, balance=create_value), } - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stCreateTest/test_create_empty_contract_and_call_it.py b/tests/ported_static/stCreateTest/test_create_empty_contract_and_call_it.py new file mode 100644 index 00000000000..cb9c8b21fb7 --- /dev/null +++ b/tests/ported_static/stCreateTest/test_create_empty_contract_and_call_it.py @@ -0,0 +1,111 @@ +""" +Test CREATE of an empty contract followed by a CALL to it, measuring the +CALL gas cost. + +Ported from: +state_tests/stCreateTest/CREATE_EmptyContractAndCallIt_0weiFiller.json +state_tests/stCreateTest/CREATE_EmptyContractAndCallIt_1weiFiller.json + +@manually-enhanced: Do not overwrite. CALL gas via CodeGasMeasure; dynamic +address (runtime SLOAD); 0wei/1wei folded into one parametrize. +""" + +import pytest +from execution_testing import ( + Account, + Alloc, + CodeGasMeasure, + Fork, + StateTestFiller, + Transaction, + compute_create_address, +) +from execution_testing.vm import Op + +REFERENCE_SPEC_GIT_PATH = "N/A" +REFERENCE_SPEC_VERSION = "N/A" + +ADDRESS_SLOT = 0x1 +GAS_SLOT = 0x64 + +FORWARDED_GAS = 0xEA60 + + +@pytest.mark.ported_from( + [ + "state_tests/stCreateTest/CREATE_EmptyContractAndCallIt_0weiFiller.json", # noqa: E501 + "state_tests/stCreateTest/CREATE_EmptyContractAndCallIt_1weiFiller.json", # noqa: E501 + ], +) +@pytest.mark.valid_from("Berlin") +@pytest.mark.parametrize( + "call_value", + [ + pytest.param(0, id="0wei"), + pytest.param(1, id="1wei"), + ], +) +def test_create_empty_contract_and_call_it( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + call_value: int, +) -> None: + """CREATE an empty contract, then CALL it and measure the CALL gas.""" + # CREATE over never-written memory deposits no code -> an empty account + # with nonce 1. Its address is stored so the CALL can target it at + # runtime (it is not known when the caller code is assembled). + create_code = Op.CREATE( + value=0x0, + offset=0x0, + size=0x20, + new_memory_size=0x20, + init_code_size=0x20, + ) + # The created account already exists (CREATE set its nonce) and is warm + # (CREATE accessed it), so the CALL is a warm call to an existing account. + call_code = Op.CALL( + gas=FORWARDED_GAS, + address=Op.SLOAD(key=ADDRESS_SLOT, key_warm=True), + value=call_value, + args_offset=0x0, + args_size=0x0, + ret_offset=0x0, + ret_size=0x0, + address_warm=True, + value_transfer=call_value > 0, + account_new=False, + ) + contract = pre.deploy_contract( + code=Op.SSTORE(key=ADDRESS_SLOT, value=create_code) + + CodeGasMeasure( + code=call_code, + extra_stack_items=1, + sstore_key=GAS_SLOT, + ), + balance=call_value, + ) + + tx = Transaction( + sender=pre.fund_eoa(), + to=contract, + state_gas_reservoir=0, + ) + + # A value-bearing CALL whose empty callee consumes nothing measures + # gas_cost minus the stipend (forwarded then returned unused). + stipend = fork.gas_costs().CALL_STIPEND if call_value else 0 + created = compute_create_address(address=contract, nonce=1) + post = { + contract: Account( + storage={ + ADDRESS_SLOT: created, + GAS_SLOT: call_code.gas_cost(fork) - stipend, + }, + balance=0, + ), + # The transferred value on the 1wei case proves the CALL executed. + created: Account(nonce=1, balance=call_value), + } + + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stCreateTest/test_create_empty_contract_and_call_it_0wei.py b/tests/ported_static/stCreateTest/test_create_empty_contract_and_call_it_0wei.py deleted file mode 100644 index b8efc72b9a5..00000000000 --- a/tests/ported_static/stCreateTest/test_create_empty_contract_and_call_it_0wei.py +++ /dev/null @@ -1,92 +0,0 @@ -""" -Test_create_empty_contract_and_call_it_0wei. - -Ported from: -state_tests/stCreateTest/CREATE_EmptyContractAndCallIt_0weiFiller.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, - compute_create_address, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - ["state_tests/stCreateTest/CREATE_EmptyContractAndCallIt_0weiFiller.json"], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_create_empty_contract_and_call_it_0wei( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_create_empty_contract_and_call_it_0wei.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - # Source: lll - # { [[0]](GAS) [[1]] (CREATE 0 0 32) [[2]](GAS) [[3]] (CALL 60000 (SLOAD 1) 0 0 0 0 0) [[100]] (GAS) } # noqa: E501 - contract_0 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=Op.GAS) - + Op.SSTORE(key=0x1, value=Op.CREATE(value=0x0, offset=0x0, size=0x20)) - + Op.SSTORE(key=0x2, value=Op.GAS) - + Op.SSTORE( - key=0x3, - value=Op.CALL( - gas=0xEA60, - address=Op.SLOAD(key=0x1), - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.SSTORE(key=0x64, value=Op.GAS) - + Op.STOP, - nonce=0, - address=Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B), # noqa: E501 - ) - - tx = Transaction( - sender=sender, - to=contract_0, - data=Bytes(""), - gas_limit=600000, - ) - - post = { - contract_0: Account( - storage={ - 0: 0x8D5B6, - 1: compute_create_address(address=contract_0, nonce=0), - 2: 0x7ABF8, - 3: 1, - 100: 0x6FE6B, - }, - ), - compute_create_address(address=contract_0, nonce=0): Account(nonce=1), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stCreateTest/test_create_empty_contract_and_call_it_1wei.py b/tests/ported_static/stCreateTest/test_create_empty_contract_and_call_it_1wei.py deleted file mode 100644 index 1b35d16c1b5..00000000000 --- a/tests/ported_static/stCreateTest/test_create_empty_contract_and_call_it_1wei.py +++ /dev/null @@ -1,95 +0,0 @@ -""" -Test_create_empty_contract_and_call_it_1wei. - -Ported from: -state_tests/stCreateTest/CREATE_EmptyContractAndCallIt_1weiFiller.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, - compute_create_address, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - ["state_tests/stCreateTest/CREATE_EmptyContractAndCallIt_1weiFiller.json"], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_create_empty_contract_and_call_it_1wei( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_create_empty_contract_and_call_it_1wei.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - # Source: lll - # { [[0]](GAS) [[1]] (CREATE 0 0 32) [[2]](GAS) [[3]](CALL 60000 (SLOAD 1) 1 0 0 0 0) [[100]] (GAS) } # noqa: E501 - contract_0 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=Op.GAS) - + Op.SSTORE(key=0x1, value=Op.CREATE(value=0x0, offset=0x0, size=0x20)) - + Op.SSTORE(key=0x2, value=Op.GAS) - + Op.SSTORE( - key=0x3, - value=Op.CALL( - gas=0xEA60, - address=Op.SLOAD(key=0x1), - value=0x1, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.SSTORE(key=0x64, value=Op.GAS) - + Op.STOP, - balance=1, - nonce=0, - address=Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B), # noqa: E501 - ) - - tx = Transaction( - sender=sender, - to=contract_0, - data=Bytes(""), - gas_limit=600000, - ) - - post = { - contract_0: Account( - storage={ - 0: 0x8D5B6, - 1: compute_create_address(address=contract_0, nonce=0), - 2: 0x7ABF8, - 3: 1, - 100: 0x6E43F, - }, - ), - compute_create_address(address=contract_0, nonce=0): Account( - balance=1, nonce=1 - ), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stCreateTest/test_create_empty_contract_with_balance.py b/tests/ported_static/stCreateTest/test_create_empty_contract_with_balance.py deleted file mode 100644 index 60862e9022b..00000000000 --- a/tests/ported_static/stCreateTest/test_create_empty_contract_with_balance.py +++ /dev/null @@ -1,79 +0,0 @@ -""" -Test_create_empty_contract_with_balance. - -Ported from: -state_tests/stCreateTest/CREATE_EmptyContractWithBalanceFiller.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, - compute_create_address, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - ["state_tests/stCreateTest/CREATE_EmptyContractWithBalanceFiller.json"], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_create_empty_contract_with_balance( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_create_empty_contract_with_balance.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - # Source: lll - # { [[0]](GAS) [[1]] (CREATE 1 0 32) [[100]] (GAS) } - contract_0 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=Op.GAS) - + Op.SSTORE(key=0x1, value=Op.CREATE(value=0x1, offset=0x0, size=0x20)) - + Op.SSTORE(key=0x64, value=Op.GAS) - + Op.STOP, - balance=1, - nonce=0, - ) - - tx = Transaction( - sender=sender, - to=contract_0, - data=Bytes(""), - gas_limit=600000, - ) - - post = { - contract_0: Account( - storage={ - 0: 0x8D5B6, - 1: compute_create_address(address=contract_0, nonce=0), - 100: 0x7ABF8, - }, - ), - compute_create_address(address=contract_0, nonce=0): Account( - balance=1 - ), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stCreateTest/test_create_transaction_call_data.py b/tests/ported_static/stCreateTest/test_create_transaction_call_data.py index 7c5810b6657..3da9a7599e3 100644 --- a/tests/ported_static/stCreateTest/test_create_transaction_call_data.py +++ b/tests/ported_static/stCreateTest/test_create_transaction_call_data.py @@ -1,29 +1,27 @@ """ -Tests if CALLDATALOAD, CALLDATACOPY, CODECOPY and CODESIZE work... - -call data is always empty in initcode context and "code" is initcode. +Verify CALLDATALOAD, CALLDATACOPY, CODECOPY and CODESIZE in the initcode +context of a create transaction: call data is always empty and "code" is the +initcode itself. Ported from: state_tests/stCreateTest/CreateTransactionCallDataFiller.yml -@manually-enhanced: Do not overwrite. tx_gas was raised from 100 000 to -500 000 so the CREATE path can afford its EIP-8037 NEW_ACCOUNT state -gas on Amsterdam (post-state expectations are unchanged on all forks). +@manually-enhanced: Do not overwrite. The post-state now genuinely verifies +each case (observable +1 reads prove empty call data is zero, a slot-2 canary +guards against silent creation failure, and the CODECOPY case asserts +`code=initcode`), and gas/fork boilerplate was removed in favor of maxing out +the transaction gas. """ import pytest from execution_testing import ( Account, Alloc, - Environment, + Bytecode, StateTestFiller, Transaction, compute_create_address, ) -from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( - resolve_expect_post, -) from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -35,99 +33,70 @@ ) @pytest.mark.valid_from("Cancun") @pytest.mark.parametrize( - "d, g, v", - [ - pytest.param( - 0, - 0, - 0, - id="calldataload", - ), - pytest.param( - 1, - 0, - 0, - id="calldatacopy", - ), - pytest.param( - 2, - 0, - 0, - id="codecopy", - ), - ], + "opcode", + ["calldataload", "calldatacopy", "codecopy"], ) @pytest.mark.pre_alloc_mutable def test_create_transaction_call_data( state_test: StateTestFiller, pre: Alloc, - fork: Fork, - d: int, - g: int, - v: int, + opcode: str, ) -> None: """Tests if CALLDATALOAD, CALLDATACOPY, CODECOPY and CODESIZE work...""" - sender = pre.fund_eoa(amount=0x5AF3107A4000) - - env = Environment( - fee_recipient=sender, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=1000000, - ) + sender = pre.fund_eoa() - expect_entries_: list[dict] = [ - { - "indexes": {"data": [0, 1], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": { - compute_create_address(address=sender, nonce=0): Account( - storage={}, code=b"", nonce=1 - ), - }, - }, - { - "indexes": {"data": [2], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": { - compute_create_address(address=sender, nonce=0): Account( - storage={}, - code=bytes.fromhex("3860008039386000f3"), - nonce=1, - ), - }, - }, - ] + created_contract = compute_create_address(address=sender, nonce=0) - post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork) + # Sentinel written to storage as the final init-code step. If creation + # reverts or the init code does not run to completion, this slot stays + # zero and the test fails instead of silently passing on an account that + # happens to match the expected (small) values. + canary = 0xC0DE - tx_data = [ - Op.SSTORE(key=0x0, value=Op.CALLDATALOAD(offset=0x0)) - + Op.SSTORE(key=0x1, value=Op.CALLDATALOAD(offset=0x21)) - + Op.STOP, - Op.CALLDATACOPY(dest_offset=Op.DUP1, offset=0x0, size=0x1) - + Op.SSTORE(key=0x0, value=Op.MLOAD(offset=0x0)) - + Op.CALLDATACOPY(dest_offset=0x0, offset=0x1, size=0x20) - + Op.SSTORE(key=0x1, value=Op.MLOAD(offset=0x0)) - + Op.STOP, - Op.CODECOPY(dest_offset=Op.DUP1, offset=0x0, size=Op.CODESIZE) - + Op.RETURN(offset=0x0, size=Op.CODESIZE), - ] - # EIP-8037 NEW_ACCOUNT + per-byte state-gas spill on Amsterdam; - # pre-EIP-8037 keeps the original 100 000 budget. - outer_tx_gas = 100_000 - if fork.is_eip_enabled(8037): - outer_tx_gas = 500_000 - tx_gas = [outer_tx_gas] + # Each case sets the init code to run and the post-state it produces. + # Call data is always empty in init code context, so the calldata reads + # resolve to zero; the only thing that varies is the opcode under test. + initcode: Bytecode + post: dict + if opcode == "calldataload": # empty data reads 0; +1 makes it visible + initcode = ( + Op.SSTORE(key=0x0, value=Op.ADD(Op.CALLDATALOAD(offset=0x0), 1)) + + Op.SSTORE(key=0x1, value=Op.ADD(Op.CALLDATALOAD(offset=0x21), 1)) + + Op.SSTORE(key=0x2, value=canary) + + Op.STOP + ) + post = { + created_contract: Account( + storage={0: 1, 1: 1, 2: canary}, code=b"", nonce=1 + ) + } + elif opcode == "calldatacopy": # empty data reads 0; +1 makes it visible + initcode = ( + Op.CALLDATACOPY(dest_offset=Op.DUP1, offset=0x0, size=0x1) + + Op.SSTORE(key=0x0, value=Op.ADD(Op.MLOAD(offset=0x0), 1)) + + Op.CALLDATACOPY(dest_offset=0x0, offset=0x1, size=0x20) + + Op.SSTORE(key=0x1, value=Op.ADD(Op.MLOAD(offset=0x0), 1)) + + Op.SSTORE(key=0x2, value=canary) + + Op.STOP + ) + post = { + created_contract: Account( + storage={0: 1, 1: 1, 2: canary}, code=b"", nonce=1 + ) + } + else: # "codecopy": CODECOPY/CODESIZE return the init code as the code + initcode = Op.CODECOPY( + dest_offset=Op.DUP1, offset=0x0, size=Op.CODESIZE + ) + Op.RETURN(offset=0x0, size=Op.CODESIZE) + # The init code returns its own bytes, so the deployed code is the + # init code itself; assert against it directly rather than a + # hand-copied hex string. + post = {created_contract: Account(storage={}, code=initcode, nonce=1)} tx = Transaction( sender=sender, to=None, - data=tx_data[d], - gas_limit=tx_gas[g], - error=_exc, + data=initcode, ) - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stDelegatecallTestHomestead/test_deleagate_call_after_value_transfer.py b/tests/ported_static/stDelegatecallTestHomestead/test_deleagate_call_after_value_transfer.py index a17a34fd41e..72955299b76 100644 --- a/tests/ported_static/stDelegatecallTestHomestead/test_deleagate_call_after_value_transfer.py +++ b/tests/ported_static/stDelegatecallTestHomestead/test_deleagate_call_after_value_transfer.py @@ -1,17 +1,22 @@ """ -Test_deleagate_call_after_value_transfer. +Verify DELEGATECALL propagates the caller frame's context (CALLVALUE, CALLER, +CALLDATA) into the delegate, after a value-bearing transaction. Ported from: state_tests/stDelegatecallTestHomestead/deleagateCallAfterValueTransferFiller.json + +@manually-enhanced: Do not overwrite. DELEGATECALL context propagation +(CALLVALUE/CALLER/CALLDATA) run in the caller's storage; the ported test +transferred zero value (so "after value transfer" was vacuous) -> a non-zero +tx value is now sent so the callee observes it via CALLVALUE. Dynamic +addresses, gas forwarded via the default Op.GAS. """ import pytest from execution_testing import ( Account, - Address, Alloc, - Bytes, - Environment, + Fork, StateTestFiller, Transaction, ) @@ -20,67 +25,59 @@ REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +TRANSFERRED_VALUE = 0xA + @pytest.mark.ported_from( [ "state_tests/stDelegatecallTestHomestead/deleagateCallAfterValueTransferFiller.json" # noqa: E501 ], ) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable +@pytest.mark.valid_from("TangerineWhistle") def test_deleagate_call_after_value_transfer( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: - """Test_deleagate_call_after_value_transfer.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0x2386F26FC10000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=1000000, - ) - - # Source: lll - # { (SSTORE 0 (CALLVALUE)) (SSTORE 1 (CALLER)) (SSTORE 2 (CALLDATALOAD 0)) } # noqa: E501 - addr = pre.deploy_contract( # noqa: F841 + """DELEGATECALL runs the callee's code in the caller's context.""" + # Delegated code records the environment it observes: it must see the + # enclosing frame's CALLVALUE (the transferred value), the original CALLER + # (the sender), and the delegate-call args as its calldata (0x1). + delegate = pre.deploy_contract( code=Op.SSTORE(key=0x0, value=Op.CALLVALUE) + Op.SSTORE(key=0x1, value=Op.CALLER) + Op.SSTORE(key=0x2, value=Op.CALLDATALOAD(offset=0x0)) + Op.STOP, - nonce=0, ) - # Source: lll - # { (MSTORE 0 0x01) (DELEGATECALL 100000 <contract:0x1000000000000000000000000000000000000001> 0 64 0 64) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 + caller = pre.deploy_contract( code=Op.MSTORE(offset=0x0, value=0x1) + Op.DELEGATECALL( - gas=0x186A0, - address=addr, + address=delegate, args_offset=0x0, args_size=0x40, ret_offset=0x0, ret_size=0x40, ) + Op.STOP, - balance=0x10C8E0, - nonce=0, ) + sender = pre.fund_eoa() tx = Transaction( sender=sender, - to=target, - data=Bytes(""), - gas_limit=453081, + to=caller, + value=TRANSFERRED_VALUE, + protected=fork.supports_protected_txs(), ) post = { - target: Account(storage={0: 0, 1: sender, 2: 1}), - addr: Account(storage={0: 0, 1: 0, 2: 0}), + # DELEGATECALL preserves the enclosing frame's value, so the callee + # sees CALLVALUE == the transferred value; its writes land in the + # caller's storage, not the callee's. + caller: Account( + balance=TRANSFERRED_VALUE, + storage={0: TRANSFERRED_VALUE, 1: sender, 2: 1}, + ), + delegate: Account(storage={0: 0, 1: 0, 2: 0}), } - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stDelegatecallTestHomestead/test_delegatecall_emptycontract.py b/tests/ported_static/stDelegatecallTestHomestead/test_delegatecall_emptycontract.py index 60b0410f6c4..90c1b8b72bb 100644 --- a/tests/ported_static/stDelegatecallTestHomestead/test_delegatecall_emptycontract.py +++ b/tests/ported_static/stDelegatecallTestHomestead/test_delegatecall_emptycontract.py @@ -1,17 +1,19 @@ """ -Test_delegatecall_emptycontract. +Verify a DELEGATECALL to a codeless, nonexistent account succeeds without +creating or touching the target. Ported from: state_tests/stDelegatecallTestHomestead/delegatecallEmptycontractFiller.json + +@manually-enhanced: Do not overwrite. DELEGATECALL to a codeless account +returns success; dynamic addresses, gas maxed out. """ import pytest from execution_testing import ( Account, - Address, Alloc, - Bytes, - Environment, + Fork, StateTestFiller, Transaction, ) @@ -26,33 +28,20 @@ "state_tests/stDelegatecallTestHomestead/delegatecallEmptycontractFiller.json" # noqa: E501 ], ) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable +@pytest.mark.valid_from("TangerineWhistle") def test_delegatecall_emptycontract( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: - """Test_delegatecall_emptycontract.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0x10C8E0) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=1000000, - ) - - # Source: lll - # { [[ 0 ]] (DELEGATECALL 50000 0x945304eb96065b2a98b57a48a06ae28d285a71b5 0 64 0 64 )} # noqa: E501 - target = pre.deploy_contract( # noqa: F841 + """DELEGATECALL to a codeless account succeeds (returns 1).""" + # A DELEGATECALL to an account with no code runs nothing and returns 1. + empty = pre.nonexistent_account() + caller = pre.deploy_contract( code=Op.SSTORE( key=0x0, value=Op.DELEGATECALL( - gas=0xC350, - address=0x945304EB96065B2A98B57A48A06AE28D285A71B5, + address=empty, args_offset=0x0, args_size=0x40, ret_offset=0x0, @@ -60,17 +49,21 @@ def test_delegatecall_emptycontract( ), ) + Op.STOP, - balance=1000, - nonce=0, ) + # DELEGATECALL predates EIP-155, so the tx must go unprotected on + # pre-SpuriousDragon forks or it fails signature validation. tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=105044, + sender=pre.fund_eoa(), + to=caller, + protected=fork.supports_protected_txs(), ) - post = {target: Account(storage={0: 1})} + # DELEGATECALL carries no value, so it must not create (or even touch) + # the target account. + post = { + caller: Account(storage={0: 1}), + empty: Account.NONEXISTENT, + } - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_code_gas.py b/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_code_gas.py deleted file mode 100644 index ce267ffe0b9..00000000000 --- a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_code_gas.py +++ /dev/null @@ -1,84 +0,0 @@ -""" -Test_raw_call_code_gas. - -Ported from: -state_tests/stEIP150singleCodeGasPrices/RawCallCodeGasFiller.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - ["state_tests/stEIP150singleCodeGasPrices/RawCallCodeGasFiller.json"], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_raw_call_code_gas( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_raw_call_code_gas.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - # Source: lll - # { [[2]] (GAS) } - addr = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x2, value=Op.GAS) + Op.STOP, - nonce=0, - ) - # Source: lll - # { [0] (GAS) (CALLCODE 30000 <contract:0x094f5374fce5edbc8e2a8697c15331677e6ebf0b> 0 0 0 0 0) [[1]] (SUB @0 (GAS)) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.POP( - Op.CALLCODE( - gas=0x7530, - address=addr, - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ) - ) - + Op.SSTORE(key=0x1, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.STOP, - nonce=0, - ) - - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=500000, - ) - - post = { - addr: Account(storage={}), - target: Account(storage={1: 24739, 2: 29998}), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_code_gas_ask.py b/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_code_gas_ask.py deleted file mode 100644 index 4850de47a79..00000000000 --- a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_code_gas_ask.py +++ /dev/null @@ -1,84 +0,0 @@ -""" -Test_raw_call_code_gas_ask. - -Ported from: -state_tests/stEIP150singleCodeGasPrices/RawCallCodeGasAskFiller.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - ["state_tests/stEIP150singleCodeGasPrices/RawCallCodeGasAskFiller.json"], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_raw_call_code_gas_ask( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_raw_call_code_gas_ask.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - # Source: lll - # { [[2]] (GAS) } - addr = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x2, value=Op.GAS) + Op.STOP, - nonce=0, - ) - # Source: lll - # { [0] (GAS) (CALLCODE 3000000 <contract:0x094f5374fce5edbc8e2a8697c15331677e6ebf0b> 0 0 0 0 0) [[1]] (SUB @0 (GAS)) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.POP( - Op.CALLCODE( - gas=0x2DC6C0, - address=addr, - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ) - ) - + Op.SSTORE(key=0x1, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.STOP, - nonce=0, - ) - - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=500000, - ) - - post = { - addr: Account(storage={}), - target: Account(storage={1: 24739, 2: 0x727BB}), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_code_gas_memory.py b/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_code_gas_memory.py deleted file mode 100644 index d5eecdff81e..00000000000 --- a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_code_gas_memory.py +++ /dev/null @@ -1,86 +0,0 @@ -""" -Test_raw_call_code_gas_memory. - -Ported from: -state_tests/stEIP150singleCodeGasPrices/RawCallCodeGasMemoryFiller.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - [ - "state_tests/stEIP150singleCodeGasPrices/RawCallCodeGasMemoryFiller.json" # noqa: E501 - ], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_raw_call_code_gas_memory( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_raw_call_code_gas_memory.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - # Source: lll - # { [[2]] (GAS) } - addr = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x2, value=Op.GAS) + Op.STOP, - nonce=0, - ) - # Source: lll - # { [0] (GAS) (CALLCODE 30000 <contract:0x094f5374fce5edbc8e2a8697c15331677e6ebf0b> 0 0 8000 0 8000) [[1]] (SUB @0 (GAS)) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.POP( - Op.CALLCODE( - gas=0x7530, - address=addr, - value=0x0, - args_offset=0x0, - args_size=0x1F40, - ret_offset=0x0, - ret_size=0x1F40, - ) - ) - + Op.SSTORE(key=0x1, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.STOP, - nonce=0, - ) - - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=500000, - ) - - post = { - addr: Account(storage={}), - target: Account(storage={1: 25608, 2: 29998}), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_code_gas_memory_ask.py b/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_code_gas_memory_ask.py deleted file mode 100644 index dbed9f97250..00000000000 --- a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_code_gas_memory_ask.py +++ /dev/null @@ -1,86 +0,0 @@ -""" -Test_raw_call_code_gas_memory_ask. - -Ported from: -state_tests/stEIP150singleCodeGasPrices/RawCallCodeGasMemoryAskFiller.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - [ - "state_tests/stEIP150singleCodeGasPrices/RawCallCodeGasMemoryAskFiller.json" # noqa: E501 - ], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_raw_call_code_gas_memory_ask( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_raw_call_code_gas_memory_ask.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - # Source: lll - # { [[2]] (GAS) } - addr = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x2, value=Op.GAS) + Op.STOP, - nonce=0, - ) - # Source: lll - # { [0] (GAS) (CALLCODE 3000000 <contract:0x094f5374fce5edbc8e2a8697c15331677e6ebf0b> 0 0 8000 0 8000) [[1]] (SUB @0 (GAS)) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.POP( - Op.CALLCODE( - gas=0x2DC6C0, - address=addr, - value=0x0, - args_offset=0x0, - args_size=0x1F40, - ret_offset=0x0, - ret_size=0x1F40, - ) - ) - + Op.SSTORE(key=0x1, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.STOP, - nonce=0, - ) - - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=500000, - ) - - post = { - addr: Account(storage={}), - target: Account(storage={1: 25608, 2: 0x72464}), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_code_gas_value_transfer.py b/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_code_gas_value_transfer.py deleted file mode 100644 index 8082a69671c..00000000000 --- a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_code_gas_value_transfer.py +++ /dev/null @@ -1,87 +0,0 @@ -""" -Test_raw_call_code_gas_value_transfer. - -Ported from: -state_tests/stEIP150singleCodeGasPrices/RawCallCodeGasValueTransferFiller.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - [ - "state_tests/stEIP150singleCodeGasPrices/RawCallCodeGasValueTransferFiller.json" # noqa: E501 - ], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_raw_call_code_gas_value_transfer( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_raw_call_code_gas_value_transfer.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - # Source: lll - # { [[2]] (GAS) } - addr = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x2, value=Op.GAS) + Op.STOP, - nonce=0, - ) - # Source: lll - # { [0] (GAS) (CALLCODE 30000 <contract:0x094f5374fce5edbc8e2a8697c15331677e6ebf0b> 10 0 0 0 0) [[1]] (SUB @0 (GAS)) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.POP( - Op.CALLCODE( - gas=0x7530, - address=addr, - value=0xA, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ) - ) - + Op.SSTORE(key=0x1, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.STOP, - nonce=0, - ) - - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=500000, - value=10, - ) - - post = { - addr: Account(storage={}), - target: Account(storage={1: 31439, 2: 32298}), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_code_gas_value_transfer_ask.py b/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_code_gas_value_transfer_ask.py deleted file mode 100644 index d69e9eec044..00000000000 --- a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_code_gas_value_transfer_ask.py +++ /dev/null @@ -1,87 +0,0 @@ -""" -Test_raw_call_code_gas_value_transfer_ask. - -Ported from: -state_tests/stEIP150singleCodeGasPrices/RawCallCodeGasValueTransferAskFiller.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - [ - "state_tests/stEIP150singleCodeGasPrices/RawCallCodeGasValueTransferAskFiller.json" # noqa: E501 - ], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_raw_call_code_gas_value_transfer_ask( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_raw_call_code_gas_value_transfer_ask.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - # Source: lll - # { [[2]] (GAS) } - addr = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x2, value=Op.GAS) + Op.STOP, - nonce=0, - ) - # Source: lll - # { [0] (GAS) (CALLCODE 3000000 <contract:0x094f5374fce5edbc8e2a8697c15331677e6ebf0b> 10 0 0 0 0) [[1]] (SUB @0 (GAS)) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.POP( - Op.CALLCODE( - gas=0x2DC6C0, - address=addr, - value=0xA, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ) - ) - + Op.SSTORE(key=0x1, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.STOP, - nonce=0, - ) - - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=500000, - value=10, - ) - - post = { - addr: Account(storage={}), - target: Account(storage={1: 31439, 2: 0x70E1C}), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_code_gas_value_transfer_memory.py b/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_code_gas_value_transfer_memory.py deleted file mode 100644 index adc2b040768..00000000000 --- a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_code_gas_value_transfer_memory.py +++ /dev/null @@ -1,87 +0,0 @@ -""" -Test_raw_call_code_gas_value_transfer_memory. - -Ported from: -state_tests/stEIP150singleCodeGasPrices/RawCallCodeGasValueTransferMemoryFiller.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - [ - "state_tests/stEIP150singleCodeGasPrices/RawCallCodeGasValueTransferMemoryFiller.json" # noqa: E501 - ], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_raw_call_code_gas_value_transfer_memory( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_raw_call_code_gas_value_transfer_memory.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - # Source: lll - # { [[2]] (GAS) } - addr = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x2, value=Op.GAS) + Op.STOP, - nonce=0, - ) - # Source: lll - # { [0] (GAS) (CALLCODE 30000 <contract:0x094f5374fce5edbc8e2a8697c15331677e6ebf0b> 10 0 8000 0 8000) [[1]] (SUB @0 (GAS)) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.POP( - Op.CALLCODE( - gas=0x7530, - address=addr, - value=0xA, - args_offset=0x0, - args_size=0x1F40, - ret_offset=0x0, - ret_size=0x1F40, - ) - ) - + Op.SSTORE(key=0x1, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.STOP, - nonce=0, - ) - - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=500000, - value=10, - ) - - post = { - addr: Account(storage={}), - target: Account(storage={1: 32308, 2: 32298}), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_code_gas_value_transfer_memory_ask.py b/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_code_gas_value_transfer_memory_ask.py deleted file mode 100644 index db089d6d3fc..00000000000 --- a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_code_gas_value_transfer_memory_ask.py +++ /dev/null @@ -1,87 +0,0 @@ -""" -Test_raw_call_code_gas_value_transfer_memory_ask. - -Ported from: -state_tests/stEIP150singleCodeGasPrices/RawCallCodeGasValueTransferMemoryAskFiller.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - [ - "state_tests/stEIP150singleCodeGasPrices/RawCallCodeGasValueTransferMemoryAskFiller.json" # noqa: E501 - ], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_raw_call_code_gas_value_transfer_memory_ask( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_raw_call_code_gas_value_transfer_memory_ask.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - # Source: lll - # { [[2]] (GAS) } - addr = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x2, value=Op.GAS) + Op.STOP, - nonce=0, - ) - # Source: lll - # { [0] (GAS) (CALLCODE 3000000 <contract:0x094f5374fce5edbc8e2a8697c15331677e6ebf0b> 10 0 8000 0 8000) [[1]] (SUB @0 (GAS)) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.POP( - Op.CALLCODE( - gas=0x2DC6C0, - address=addr, - value=0xA, - args_offset=0x0, - args_size=0x1F40, - ret_offset=0x0, - ret_size=0x1F40, - ) - ) - + Op.SSTORE(key=0x1, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.STOP, - nonce=0, - ) - - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=500000, - value=10, - ) - - post = { - addr: Account(storage={}), - target: Account(storage={1: 32308, 2: 0x70AC4}), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_gas.py b/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_gas.py index 97bc8524d66..6fe0cbca6de 100644 --- a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_gas.py +++ b/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_gas.py @@ -1,17 +1,29 @@ """ -Test_raw_call_gas. +Measure the gas cost of CALL / CALLCODE / DELEGATECALL with CodeGasMeasure, +across value-transfer and memory-expansion variants. The callee records the +gas it was forwarded. Ported from: state_tests/stEIP150singleCodeGasPrices/RawCallGasFiller.json +state_tests/stEIP150singleCodeGasPrices/RawCallGasValueTransferFiller.json +state_tests/stEIP150singleCodeGasPrices/RawCallMemoryGasFiller.json +state_tests/stEIP150singleCodeGasPrices/RawCallGasValueTransferMemoryFiller.json +state_tests/stEIP150singleCodeGasPrices/RawCallCodeGasFiller.json +state_tests/stEIP150singleCodeGasPrices/RawCallCodeGasValueTransferFiller.json +state_tests/stEIP150singleCodeGasPrices/RawCallCodeGasMemoryFiller.json +state_tests/stEIP150singleCodeGasPrices/RawCallCodeGasValueTransferMemoryFiller.json +state_tests/stEIP150singleCodeGasPrices/RawDelegateCallGasFiller.json +state_tests/stEIP150singleCodeGasPrices/RawDelegateCallGasMemoryFiller.json + +@manually-enhanced: Do not overwrite. Nested call gas via CodeGasMeasure. """ import pytest from execution_testing import ( Account, - Address, Alloc, - Bytes, - Environment, + CodeGasMeasure, + Fork, StateTestFiller, Transaction, ) @@ -20,65 +32,143 @@ REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +FORWARD_BUFFER = 100 # margin forwarded beyond the callee's own gas cost +MEMORY_SIZE = 0x1F40 # args/ret buffer size for memory variants +CALL_VALUE = 0xA +CALLER_BALANCE = 100 + @pytest.mark.ported_from( - ["state_tests/stEIP150singleCodeGasPrices/RawCallGasFiller.json"], + [ + "state_tests/stEIP150singleCodeGasPrices/RawCallGasFiller.json", + "state_tests/stEIP150singleCodeGasPrices/RawCallGasValueTransferFiller.json", # noqa: E501 + "state_tests/stEIP150singleCodeGasPrices/RawCallMemoryGasFiller.json", + "state_tests/stEIP150singleCodeGasPrices/RawCallGasValueTransferMemoryFiller.json", # noqa: E501 + "state_tests/stEIP150singleCodeGasPrices/RawCallCodeGasFiller.json", + "state_tests/stEIP150singleCodeGasPrices/RawCallCodeGasValueTransferFiller.json", # noqa: E501 + "state_tests/stEIP150singleCodeGasPrices/RawCallCodeGasMemoryFiller.json", + "state_tests/stEIP150singleCodeGasPrices/RawCallCodeGasValueTransferMemoryFiller.json", # noqa: E501 + "state_tests/stEIP150singleCodeGasPrices/RawDelegateCallGasFiller.json", + "state_tests/stEIP150singleCodeGasPrices/RawDelegateCallGasMemoryFiller.json", # noqa: E501 + ], +) +@pytest.mark.valid_from("Berlin") +@pytest.mark.parametrize( + "opcode, value, memory", + [ + pytest.param(Op.CALL, 0, False, id="raw_call_gas"), + pytest.param( + Op.CALL, CALL_VALUE, False, id="raw_call_gas_value_transfer" + ), + pytest.param(Op.CALL, 0, True, id="raw_call_memory_gas"), + pytest.param( + Op.CALL, CALL_VALUE, True, id="raw_call_gas_value_transfer_memory" + ), + pytest.param(Op.CALLCODE, 0, False, id="raw_call_code_gas"), + pytest.param( + Op.CALLCODE, + CALL_VALUE, + False, + id="raw_call_code_gas_value_transfer", + ), + pytest.param(Op.CALLCODE, 0, True, id="raw_call_code_gas_memory"), + pytest.param( + Op.CALLCODE, + CALL_VALUE, + True, + id="raw_call_code_gas_value_transfer_memory", + ), + pytest.param(Op.DELEGATECALL, 0, False, id="raw_delegate_call_gas"), + pytest.param( + Op.DELEGATECALL, 0, True, id="raw_delegate_call_gas_memory" + ), + ], ) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable def test_raw_call_gas( state_test: StateTestFiller, pre: Alloc, + fork: Fork, + opcode: Op, + value: int, + memory: bool, ) -> None: - """Test_raw_call_gas.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xE8D4A51000) + """Measure call-family gas, with the callee recording forwarded gas.""" + stipend = fork.gas_costs().CALL_STIPEND if value else 0 + mem = MEMORY_SIZE if memory else 0 - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, + # The callee writes a cold (zero->non-zero) slot; SSTORE cost depends only + # on that transition, not the value, so a placeholder new_value suffices to + # size the gas to forward (large under EIP-8037 state gas). + callee_store = Op.SSTORE( + key=0x2, + value=Op.GAS, + key_warm=False, + original_value=0, + new_value=1, ) + forward_gas = callee_store.gas_cost(fork) + FORWARD_BUFFER + callee = pre.deploy_contract(code=callee_store + Op.STOP) - # Source: lll - # { [[2]] (GAS) } - addr = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x2, value=Op.GAS) + Op.STOP, - nonce=0, - ) - # Source: lll - # { [0] (GAS) (CALL 30000 <contract:0x094f5374fce5edbc8e2a8697c15331677e6ebf0b> 0 0 0 0 0) [[1]] (SUB @0 (GAS)) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.POP( - Op.CALL( - gas=0x7530, - address=addr, - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ) + # Callee records the gas it received: forwarded gas plus the value-transfer + # stipend, minus the GAS opcode it executes. + callee_gas_seen = forward_gas + stipend - Op.GAS.gas_cost(fork) + + if opcode == Op.DELEGATECALL: + call_code = Op.DELEGATECALL( + gas=forward_gas, + address=callee, + args_offset=0x0, + args_size=mem, + ret_offset=0x0, + ret_size=mem, + address_warm=False, + new_memory_size=mem, + ) + else: + call_code = opcode( + gas=forward_gas, + address=callee, + value=value, + args_offset=0x0, + args_size=mem, + ret_offset=0x0, + ret_size=mem, + address_warm=False, + value_transfer=value > 0, + account_new=False, + new_memory_size=mem, ) - + Op.SSTORE(key=0x1, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.STOP, - nonce=0, + caller = pre.deploy_contract( + code=CodeGasMeasure( + code=call_code, + extra_stack_items=1, + sstore_key=0x1, + ), + balance=CALLER_BALANCE, ) tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=500000, + sender=pre.fund_eoa(), + to=caller, + state_gas_reservoir=0, ) + # Measured cost = the call's own cost plus the callee's consumption; the + # value-transfer stipend is forwarded free, not charged to the caller. + call_gas = call_code.gas_cost(fork) + callee_store.gas_cost(fork) - stipend + + # CALL runs the callee in its own context (slot 2 in the callee); CALLCODE + # and DELEGATECALL run it in the caller's context (slot 2 in the caller). + if opcode == Op.CALL: + callee_storage = {0x2: callee_gas_seen} + caller_storage = {0x1: call_gas} + else: + callee_storage = {} + caller_storage = {0x1: call_gas, 0x2: callee_gas_seen} + post = { - addr: Account(storage={2: 29998}), - target: Account(storage={1: 24739}), + callee: Account(storage=callee_storage), + caller: Account(storage=caller_storage), } - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_gas_ask.py b/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_gas_ask.py index 6935ff2a0d7..e16c5b50880 100644 --- a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_gas_ask.py +++ b/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_gas_ask.py @@ -1,17 +1,35 @@ """ -Test_raw_call_gas_ask. +Verify the EIP-150 "all but one 64th" rule: a subcall asking for more gas +than is available receives 63/64 of it, across CALL / CALLCODE / DELEGATECALL +and their value-transfer and memory-expansion variants. Ported from: state_tests/stEIP150singleCodeGasPrices/RawCallGasAskFiller.json +state_tests/stEIP150singleCodeGasPrices/RawCallGasValueTransferAskFiller.json +state_tests/stEIP150singleCodeGasPrices/RawCallMemoryGasAskFiller.json +state_tests/stEIP150singleCodeGasPrices/RawCallGasValueTransferMemoryAskFiller.json +state_tests/stEIP150singleCodeGasPrices/RawCallCodeGasAskFiller.json +state_tests/stEIP150singleCodeGasPrices/RawCallCodeGasMemoryAskFiller.json +state_tests/stEIP150singleCodeGasPrices/RawCallCodeGasValueTransferAskFiller.json +state_tests/stEIP150singleCodeGasPrices/RawCallCodeGasValueTransferMemoryAskFiller.json +state_tests/stEIP150singleCodeGasPrices/RawDelegateCallGasAskFiller.json +state_tests/stEIP150singleCodeGasPrices/RawDelegateCallGasMemoryAskFiller.json + +@manually-enhanced: Do not overwrite. The ported fillers pinned the forwarded +gas as an absolute number tied to the tx gas limit (fork-fragile via the +intrinsic). Reframed so an outer call caps the caller frame at a known gas +budget, the callee returns its observed GAS up to the top frame (no lower-frame +SSTORE state-gas trap), and the expected value is derived from the fork: +`all_but_one_64th(caller_gas - call.gas_cost(fork))`. The caller also reports +its remaining gas after the subcall, preserving the ported fillers' second +assertion that unused forwarded gas is credited back. """ import pytest from execution_testing import ( Account, - Address, Alloc, - Bytes, - Environment, + Fork, StateTestFiller, Transaction, ) @@ -20,65 +38,156 @@ REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +CALLER_GAS = 100_000 +CALL_VALUE = 0xA +MEMORY_SIZE = 0x1F40 # 8000-byte args/ret buffer for the memory variants + @pytest.mark.ported_from( - ["state_tests/stEIP150singleCodeGasPrices/RawCallGasAskFiller.json"], + [ + "state_tests/stEIP150singleCodeGasPrices/RawCallGasAskFiller.json", + "state_tests/stEIP150singleCodeGasPrices/RawCallGasValueTransferAskFiller.json", # noqa: E501 + "state_tests/stEIP150singleCodeGasPrices/RawCallMemoryGasAskFiller.json", # noqa: E501 + "state_tests/stEIP150singleCodeGasPrices/RawCallGasValueTransferMemoryAskFiller.json", # noqa: E501 + "state_tests/stEIP150singleCodeGasPrices/RawCallCodeGasAskFiller.json", + "state_tests/stEIP150singleCodeGasPrices/RawCallCodeGasMemoryAskFiller.json", # noqa: E501 + "state_tests/stEIP150singleCodeGasPrices/RawCallCodeGasValueTransferAskFiller.json", # noqa: E501 + "state_tests/stEIP150singleCodeGasPrices/RawCallCodeGasValueTransferMemoryAskFiller.json", # noqa: E501 + "state_tests/stEIP150singleCodeGasPrices/RawDelegateCallGasAskFiller.json", # noqa: E501 + "state_tests/stEIP150singleCodeGasPrices/RawDelegateCallGasMemoryAskFiller.json", # noqa: E501 + ], +) +@pytest.mark.valid_from("Berlin") +@pytest.mark.parametrize( + "opcode, value, memory", + [ + pytest.param(Op.CALL, 0, False, id="raw_call_gas_ask"), + pytest.param( + Op.CALL, CALL_VALUE, False, id="raw_call_gas_value_transfer_ask" + ), + pytest.param(Op.CALL, 0, True, id="raw_call_memory_gas_ask"), + pytest.param( + Op.CALL, + CALL_VALUE, + True, + id="raw_call_gas_value_transfer_memory_ask", + ), + pytest.param(Op.CALLCODE, 0, False, id="raw_call_code_gas_ask"), + pytest.param(Op.CALLCODE, 0, True, id="raw_call_code_gas_memory_ask"), + pytest.param( + Op.CALLCODE, + CALL_VALUE, + False, + id="raw_call_code_gas_value_transfer_ask", + ), + pytest.param( + Op.CALLCODE, + CALL_VALUE, + True, + id="raw_call_code_gas_value_transfer_memory_ask", + ), + pytest.param( + Op.DELEGATECALL, 0, False, id="raw_delegate_call_gas_ask" + ), + pytest.param( + Op.DELEGATECALL, 0, True, id="raw_delegate_call_gas_memory_ask" + ), + ], ) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable def test_raw_call_gas_ask( state_test: StateTestFiller, pre: Alloc, + fork: Fork, + opcode: Op, + value: int, + memory: bool, ) -> None: - """Test_raw_call_gas_ask.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, + """A subcall asking for more gas than available receives 63/64 of it.""" + sender = pre.fund_eoa() + + # Callee returns the gas it observed on entry back to the caller. + gas_return_code = Op.MSTORE(0, Op.GAS, new_memory_size=32) + Op.RETURN( + 0, 32 ) + gas_return_contract = pre.deploy_contract(code=gas_return_code) + + mem = MEMORY_SIZE if memory else 0 + ret_size = MEMORY_SIZE if memory else 32 # must fit the 32-byte GAS return + new_memory_size = MEMORY_SIZE if memory else 32 - # Source: lll - # { [[2]] (GAS) } - addr = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x2, value=Op.GAS) + Op.STOP, - nonce=0, + # The caller asks for "all" gas (the default Op.GAS operand), which exceeds + # what remains after the call's own cost, so the 63/64 cap kicks in. + if opcode == Op.DELEGATECALL: + caller_call_code = Op.DELEGATECALL( + address=gas_return_contract, + args_offset=0, + args_size=mem, + ret_offset=0, + ret_size=ret_size, + address_warm=False, + new_memory_size=new_memory_size, + ) + else: + caller_call_code = opcode( + address=gas_return_contract, + value=value, + args_offset=0, + args_size=mem, + ret_offset=0, + ret_size=ret_size, + address_warm=False, + value_transfer=value > 0, + account_new=False, + new_memory_size=new_memory_size, + ) + # After the subcall returns, the caller appends its own remaining gas to + # the return data, so the top frame can also assert that the unused part + # of the 63/64-forwarded grant was credited back to the caller. + caller = pre.deploy_contract( + code=caller_call_code + Op.MSTORE(32, Op.GAS) + Op.RETURN(0, 64), + balance=value, ) - # Source: lll - # { [0] (GAS) (CALL 3000000 <contract:0x094f5374fce5edbc8e2a8697c15331677e6ebf0b> 0 0 0 0 0) [[1]] (SUB @0 (GAS)) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.POP( - Op.CALL( - gas=0x2DC6C0, - address=addr, - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ) + + # An outer call pins the caller frame's gas to a known budget, so the + # forwarded amount does not depend on the tx gas limit. + entry = pre.deploy_contract( + code=Op.SSTORE(0, 1) + + Op.CALL( + gas=CALLER_GAS, + address=caller, + value=0, + args_offset=0, + args_size=0, + ret_offset=0, + ret_size=64, ) - + Op.SSTORE(key=0x1, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.STOP, - nonce=0, + + Op.SSTORE(1, Op.MLOAD(0)) + + Op.SSTORE(2, Op.MLOAD(32)), ) - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=500000, + # EIP-150 forwards "all but one 64th" of the gas left after the call's own + # cost; a value-bearing call additionally hands the callee the stipend. + stipend = fork.gas_costs().CALL_STIPEND if value else 0 + available = CALLER_GAS - caller_call_code.gas_cost(fork) + assert available > 0, "CALLER_GAS must exceed the call's own cost" + forwarded = available - available // 64 + expected_gas = forwarded + stipend - Op.GAS.gas_cost(fork) + + # The callee's unconsumed gas returns to the caller: what the caller sees + # after the subcall is its budget minus the call's own cost and the + # callee's consumption (the stipend nets out on value-bearing calls). + expected_caller_gas = ( + CALLER_GAS + - caller_call_code.gas_cost(fork) + + stipend + - gas_return_code.gas_cost(fork) + - Op.GAS.gas_cost(fork) ) + tx = Transaction(sender=sender, to=entry) + post = { - addr: Account(storage={2: 0x727BB}), - target: Account(storage={1: 24739}), + entry: Account(storage={0: 1, 1: expected_gas, 2: expected_caller_gas}) } - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_gas_value_transfer.py b/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_gas_value_transfer.py deleted file mode 100644 index c94dd98e65a..00000000000 --- a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_gas_value_transfer.py +++ /dev/null @@ -1,87 +0,0 @@ -""" -Test_raw_call_gas_value_transfer. - -Ported from: -state_tests/stEIP150singleCodeGasPrices/RawCallGasValueTransferFiller.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - [ - "state_tests/stEIP150singleCodeGasPrices/RawCallGasValueTransferFiller.json" # noqa: E501 - ], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_raw_call_gas_value_transfer( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_raw_call_gas_value_transfer.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - # Source: lll - # { [[2]] (GAS) } - addr = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x2, value=Op.GAS) + Op.STOP, - nonce=0, - ) - # Source: lll - # { [0] (GAS) (CALL 30000 <contract:0x094f5374fce5edbc8e2a8697c15331677e6ebf0b> 10 0 0 0 0) [[1]] (SUB @0 (GAS)) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.POP( - Op.CALL( - gas=0x7530, - address=addr, - value=0xA, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ) - ) - + Op.SSTORE(key=0x1, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.STOP, - nonce=0, - ) - - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=500000, - value=10, - ) - - post = { - addr: Account(storage={2: 32298}), - target: Account(storage={1: 31439}), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_gas_value_transfer_ask.py b/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_gas_value_transfer_ask.py deleted file mode 100644 index 46240d39693..00000000000 --- a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_gas_value_transfer_ask.py +++ /dev/null @@ -1,87 +0,0 @@ -""" -Test_raw_call_gas_value_transfer_ask. - -Ported from: -state_tests/stEIP150singleCodeGasPrices/RawCallGasValueTransferAskFiller.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - [ - "state_tests/stEIP150singleCodeGasPrices/RawCallGasValueTransferAskFiller.json" # noqa: E501 - ], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_raw_call_gas_value_transfer_ask( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_raw_call_gas_value_transfer_ask.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - # Source: lll - # { [[2]] (GAS) } - addr = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x2, value=Op.GAS) + Op.STOP, - nonce=0, - ) - # Source: lll - # { [0] (GAS) (CALL 3000000 <contract:0x094f5374fce5edbc8e2a8697c15331677e6ebf0b> 10 0 0 0 0) [[1]] (SUB @0 (GAS)) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.POP( - Op.CALL( - gas=0x2DC6C0, - address=addr, - value=0xA, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ) - ) - + Op.SSTORE(key=0x1, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.STOP, - nonce=0, - ) - - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=500000, - value=10, - ) - - post = { - addr: Account(storage={2: 0x70E1C}), - target: Account(storage={1: 31439}), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_gas_value_transfer_memory.py b/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_gas_value_transfer_memory.py deleted file mode 100644 index cd1f9f68c18..00000000000 --- a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_gas_value_transfer_memory.py +++ /dev/null @@ -1,87 +0,0 @@ -""" -Test_raw_call_gas_value_transfer_memory. - -Ported from: -state_tests/stEIP150singleCodeGasPrices/RawCallGasValueTransferMemoryFiller.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - [ - "state_tests/stEIP150singleCodeGasPrices/RawCallGasValueTransferMemoryFiller.json" # noqa: E501 - ], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_raw_call_gas_value_transfer_memory( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_raw_call_gas_value_transfer_memory.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - # Source: lll - # { [[2]] (GAS) } - addr = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x2, value=Op.GAS) + Op.STOP, - nonce=0, - ) - # Source: lll - # { [0] (GAS) (CALL 30000 <contract:0x094f5374fce5edbc8e2a8697c15331677e6ebf0b> 10 0 8000 0 8000) [[1]] (SUB @0 (GAS)) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.POP( - Op.CALL( - gas=0x7530, - address=addr, - value=0xA, - args_offset=0x0, - args_size=0x1F40, - ret_offset=0x0, - ret_size=0x1F40, - ) - ) - + Op.SSTORE(key=0x1, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.STOP, - nonce=0, - ) - - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=500000, - value=10, - ) - - post = { - addr: Account(storage={2: 32298}), - target: Account(storage={1: 32308}), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_gas_value_transfer_memory_ask.py b/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_gas_value_transfer_memory_ask.py deleted file mode 100644 index 0939cc07c2a..00000000000 --- a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_gas_value_transfer_memory_ask.py +++ /dev/null @@ -1,87 +0,0 @@ -""" -Test_raw_call_gas_value_transfer_memory_ask. - -Ported from: -state_tests/stEIP150singleCodeGasPrices/RawCallGasValueTransferMemoryAskFiller.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - [ - "state_tests/stEIP150singleCodeGasPrices/RawCallGasValueTransferMemoryAskFiller.json" # noqa: E501 - ], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_raw_call_gas_value_transfer_memory_ask( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_raw_call_gas_value_transfer_memory_ask.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - # Source: lll - # { [[2]] (GAS) } - addr = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x2, value=Op.GAS) + Op.STOP, - nonce=0, - ) - # Source: lll - # { [0] (GAS) (CALL 3000000 <contract:0x094f5374fce5edbc8e2a8697c15331677e6ebf0b> 10 0 8000 0 8000) [[1]] (SUB @0 (GAS)) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.POP( - Op.CALL( - gas=0x2DC6C0, - address=addr, - value=0xA, - args_offset=0x0, - args_size=0x1F40, - ret_offset=0x0, - ret_size=0x1F40, - ) - ) - + Op.SSTORE(key=0x1, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.STOP, - nonce=0, - ) - - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=500000, - value=10, - ) - - post = { - addr: Account(storage={2: 0x70AC4}), - target: Account(storage={1: 32308}), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_memory_gas.py b/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_memory_gas.py deleted file mode 100644 index 2626ed1afa2..00000000000 --- a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_memory_gas.py +++ /dev/null @@ -1,84 +0,0 @@ -""" -Test_raw_call_memory_gas. - -Ported from: -state_tests/stEIP150singleCodeGasPrices/RawCallMemoryGasFiller.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - ["state_tests/stEIP150singleCodeGasPrices/RawCallMemoryGasFiller.json"], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_raw_call_memory_gas( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_raw_call_memory_gas.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - # Source: lll - # { [[2]] (GAS) } - addr = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x2, value=Op.GAS) + Op.STOP, - nonce=0, - ) - # Source: lll - # { [0] (GAS) (CALL 30000 <contract:0x094f5374fce5edbc8e2a8697c15331677e6ebf0b> 0 0 8000 0 8000) [[1]] (SUB @0 (GAS)) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.POP( - Op.CALL( - gas=0x7530, - address=addr, - value=0x0, - args_offset=0x0, - args_size=0x1F40, - ret_offset=0x0, - ret_size=0x1F40, - ) - ) - + Op.SSTORE(key=0x1, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.STOP, - nonce=0, - ) - - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=500000, - ) - - post = { - addr: Account(storage={2: 29998}), - target: Account(storage={1: 25608}), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_memory_gas_ask.py b/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_memory_gas_ask.py deleted file mode 100644 index 1ea4c6eab02..00000000000 --- a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_call_memory_gas_ask.py +++ /dev/null @@ -1,84 +0,0 @@ -""" -Test_raw_call_memory_gas_ask. - -Ported from: -state_tests/stEIP150singleCodeGasPrices/RawCallMemoryGasAskFiller.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - ["state_tests/stEIP150singleCodeGasPrices/RawCallMemoryGasAskFiller.json"], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_raw_call_memory_gas_ask( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_raw_call_memory_gas_ask.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - # Source: lll - # { [[2]] (GAS) } - addr = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x2, value=Op.GAS) + Op.STOP, - nonce=0, - ) - # Source: lll - # { [0] (GAS) (CALL 3000000 <contract:0x094f5374fce5edbc8e2a8697c15331677e6ebf0b> 0 0 8000 0 8000) [[1]] (SUB @0 (GAS)) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.POP( - Op.CALL( - gas=0x2DC6C0, - address=addr, - value=0x0, - args_offset=0x0, - args_size=0x1F40, - ret_offset=0x0, - ret_size=0x1F40, - ) - ) - + Op.SSTORE(key=0x1, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.STOP, - nonce=0, - ) - - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=500000, - ) - - post = { - addr: Account(storage={2: 0x72464}), - target: Account(storage={1: 25608}), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_create_fail_gas_value_transfer.py b/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_create_fail_gas_value_transfer.py deleted file mode 100644 index b1926166116..00000000000 --- a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_create_fail_gas_value_transfer.py +++ /dev/null @@ -1,75 +0,0 @@ -""" -Test_raw_create_fail_gas_value_transfer. - -Ported from: -state_tests/stEIP150singleCodeGasPrices/RawCreateFailGasValueTransferFiller.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, - compute_create_address, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - [ - "state_tests/stEIP150singleCodeGasPrices/RawCreateFailGasValueTransferFiller.json" # noqa: E501 - ], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_raw_create_fail_gas_value_transfer( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_raw_create_fail_gas_value_transfer.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - # Source: lll - # { [0] (GAS) (CREATE 11 0 0) [[1]] (SUB @0 (GAS)) } - contract_0 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.POP(Op.CREATE(value=0xB, offset=0x0, size=0x0)) - + Op.SSTORE(key=0x1, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.STOP, - nonce=0, - ) - - tx = Transaction( - sender=sender, - to=contract_0, - data=Bytes(""), - gas_limit=500000, - value=10, - ) - - post = { - contract_0: Account(storage={1: 32022}), - compute_create_address( - address=contract_0, nonce=0 - ): Account.NONEXISTENT, - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_create_fail_gas_value_transfer2.py b/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_create_fail_gas_value_transfer2.py deleted file mode 100644 index cff6a4c875c..00000000000 --- a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_create_fail_gas_value_transfer2.py +++ /dev/null @@ -1,75 +0,0 @@ -""" -Test_raw_create_fail_gas_value_transfer2. - -Ported from: -state_tests/stEIP150singleCodeGasPrices/RawCreateFailGasValueTransfer2Filler.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, - compute_create_address, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - [ - "state_tests/stEIP150singleCodeGasPrices/RawCreateFailGasValueTransfer2Filler.json" # noqa: E501 - ], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_raw_create_fail_gas_value_transfer2( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_raw_create_fail_gas_value_transfer2.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - # Source: lll - # { [0] (GAS) (CREATE 11 0 8000) [[1]] (SUB @0 (GAS)) } - contract_0 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.POP(Op.CREATE(value=0xB, offset=0x0, size=0x1F40)) - + Op.SSTORE(key=0x1, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.STOP, - nonce=0, - ) - - tx = Transaction( - sender=sender, - to=contract_0, - data=Bytes(""), - gas_limit=500000, - value=10, - ) - - post = { - contract_0: Account(storage={1: 33391}), - compute_create_address( - address=contract_0, nonce=0 - ): Account.NONEXISTENT, - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_create_gas.py b/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_create_gas.py index cfcdaf8e86f..e7e0e5962af 100644 --- a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_create_gas.py +++ b/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_create_gas.py @@ -1,17 +1,25 @@ """ -Test_raw_create_gas. +Measure the gas cost of CREATE with CodeGasMeasure, across value-transfer, +memory-expansion, and insufficient-balance (failure) variants. Ported from: state_tests/stEIP150singleCodeGasPrices/RawCreateGasFiller.json +state_tests/stEIP150singleCodeGasPrices/RawCreateGasMemoryFiller.json +state_tests/stEIP150singleCodeGasPrices/RawCreateGasValueTransferFiller.json +state_tests/stEIP150singleCodeGasPrices/RawCreateGasValueTransferMemoryFiller.json +state_tests/stEIP150singleCodeGasPrices/RawCreateFailGasValueTransferFiller.json +state_tests/stEIP150singleCodeGasPrices/RawCreateFailGasValueTransfer2Filler.json + +@manually-enhanced: Do not overwrite. Six RawCreate*Gas fillers folded into one +CodeGasMeasure parametrize; failure path charges regular_cost (no state gas). """ import pytest from execution_testing import ( Account, - Address, Alloc, - Bytes, - Environment, + CodeGasMeasure, + Fork, StateTestFiller, Transaction, compute_create_address, @@ -21,52 +29,85 @@ REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +GAS_SLOT = 0x1 +MEMORY_SIZE = 0x1F40 # 8000-byte init-code window for the memory variants + @pytest.mark.ported_from( - ["state_tests/stEIP150singleCodeGasPrices/RawCreateGasFiller.json"], + [ + "state_tests/stEIP150singleCodeGasPrices/RawCreateGasFiller.json", + "state_tests/stEIP150singleCodeGasPrices/RawCreateGasMemoryFiller.json", + "state_tests/stEIP150singleCodeGasPrices/RawCreateGasValueTransferFiller.json", # noqa: E501 + "state_tests/stEIP150singleCodeGasPrices/RawCreateGasValueTransferMemoryFiller.json", # noqa: E501 + "state_tests/stEIP150singleCodeGasPrices/RawCreateFailGasValueTransferFiller.json", # noqa: E501 + "state_tests/stEIP150singleCodeGasPrices/RawCreateFailGasValueTransfer2Filler.json", # noqa: E501 + ], +) +@pytest.mark.valid_from("SpuriousDragon") +@pytest.mark.parametrize( + "create_value, size, fails", + [ + pytest.param(0x0, 0x0, False, id="raw_create_gas"), + pytest.param(0x0, MEMORY_SIZE, False, id="raw_create_gas_memory"), + pytest.param(0xA, 0x0, False, id="raw_create_gas_value_transfer"), + pytest.param( + 0xA, MEMORY_SIZE, False, id="raw_create_gas_value_transfer_memory" + ), + pytest.param(0xB, 0x0, True, id="raw_create_fail_gas_value_transfer"), + pytest.param( + 0xB, MEMORY_SIZE, True, id="raw_create_fail_gas_value_transfer2" + ), + ], ) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable def test_raw_create_gas( state_test: StateTestFiller, pre: Alloc, + fork: Fork, + create_value: int, + size: int, + fails: bool, ) -> None: - """Test_raw_create_gas.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, + """Measure CREATE gas; a balance-failure path is cheaper (no state gas).""" + # Init code is never written, so it is `size` zero bytes: the created + # contract STOPs immediately and deposits no code. + create_code = Op.CREATE( + value=create_value, + offset=0x0, + size=size, + new_memory_size=size, + init_code_size=size, ) - - # Source: lll - # { [0] (GAS) (CREATE 0 0 0) [[1]] (SUB @0 (GAS)) } - contract_0 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.POP(Op.CREATE(value=0x0, offset=0x0, size=0x0)) - + Op.SSTORE(key=0x1, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.STOP, - nonce=0, + # Fund the creator one wei short of `create_value` on the failure cases so + # the CREATE aborts on the balance check; otherwise give it exactly enough. + balance = create_value - 1 if fails else create_value + contract = pre.deploy_contract( + code=CodeGasMeasure( + code=create_code, + extra_stack_items=1, + sstore_key=GAS_SLOT, + ), + balance=balance, ) tx = Transaction( - sender=sender, - to=contract_0, - data=Bytes(""), - gas_limit=500000, + sender=pre.fund_eoa(), + to=contract, + state_gas_reservoir=0, ) + created = compute_create_address(address=contract, nonce=1) + if fails: + # A balance-check failure runs no init code and creates no account, so + # only the regular (execution) gas is charged, never state gas. + expected_gas = create_code.regular_cost(fork) + created_account = Account.NONEXISTENT + else: + expected_gas = create_code.gas_cost(fork) + created_account = Account(balance=create_value) + post = { - contract_0: Account(storage={1: 32022}), - compute_create_address(address=contract_0, nonce=0): Account( - balance=0 - ), + contract: Account(storage={GAS_SLOT: expected_gas}), + created: created_account, } - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_create_gas_memory.py b/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_create_gas_memory.py deleted file mode 100644 index 78b9d6682e6..00000000000 --- a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_create_gas_memory.py +++ /dev/null @@ -1,72 +0,0 @@ -""" -Test_raw_create_gas_memory. - -Ported from: -state_tests/stEIP150singleCodeGasPrices/RawCreateGasMemoryFiller.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, - compute_create_address, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - ["state_tests/stEIP150singleCodeGasPrices/RawCreateGasMemoryFiller.json"], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_raw_create_gas_memory( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_raw_create_gas_memory.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - # Source: lll - # { [0] (GAS) (CREATE 0 0 8000) [[1]] (SUB @0 (GAS)) } - contract_0 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.POP(Op.CREATE(value=0x0, offset=0x0, size=0x1F40)) - + Op.SSTORE(key=0x1, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.STOP, - nonce=0, - ) - - tx = Transaction( - sender=sender, - to=contract_0, - data=Bytes(""), - gas_limit=500000, - ) - - post = { - contract_0: Account(storage={1: 33391}), - compute_create_address(address=contract_0, nonce=0): Account( - balance=0 - ), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_create_gas_value_transfer.py b/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_create_gas_value_transfer.py deleted file mode 100644 index 355d74d1dfd..00000000000 --- a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_create_gas_value_transfer.py +++ /dev/null @@ -1,75 +0,0 @@ -""" -Test_raw_create_gas_value_transfer. - -Ported from: -state_tests/stEIP150singleCodeGasPrices/RawCreateGasValueTransferFiller.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, - compute_create_address, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - [ - "state_tests/stEIP150singleCodeGasPrices/RawCreateGasValueTransferFiller.json" # noqa: E501 - ], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_raw_create_gas_value_transfer( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_raw_create_gas_value_transfer.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - # Source: lll - # { [0] (GAS) (CREATE 10 0 0) [[1]] (SUB @0 (GAS)) } - contract_0 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.POP(Op.CREATE(value=0xA, offset=0x0, size=0x0)) - + Op.SSTORE(key=0x1, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.STOP, - nonce=0, - ) - - tx = Transaction( - sender=sender, - to=contract_0, - data=Bytes(""), - gas_limit=500000, - value=10, - ) - - post = { - contract_0: Account(storage={1: 32022}), - compute_create_address(address=contract_0, nonce=0): Account( - balance=10 - ), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_create_gas_value_transfer_memory.py b/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_create_gas_value_transfer_memory.py deleted file mode 100644 index 52578c5b4b9..00000000000 --- a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_create_gas_value_transfer_memory.py +++ /dev/null @@ -1,75 +0,0 @@ -""" -Test_raw_create_gas_value_transfer_memory. - -Ported from: -state_tests/stEIP150singleCodeGasPrices/RawCreateGasValueTransferMemoryFiller.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, - compute_create_address, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - [ - "state_tests/stEIP150singleCodeGasPrices/RawCreateGasValueTransferMemoryFiller.json" # noqa: E501 - ], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_raw_create_gas_value_transfer_memory( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_raw_create_gas_value_transfer_memory.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - # Source: lll - # { [0] (GAS) (CREATE 10 0 8000) [[1]] (SUB @0 (GAS)) } - contract_0 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.POP(Op.CREATE(value=0xA, offset=0x0, size=0x1F40)) - + Op.SSTORE(key=0x1, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.STOP, - nonce=0, - ) - - tx = Transaction( - sender=sender, - to=contract_0, - data=Bytes(""), - gas_limit=500000, - value=10, - ) - - post = { - contract_0: Account(storage={1: 33391}), - compute_create_address(address=contract_0, nonce=0): Account( - balance=10 - ), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_delegate_call_gas.py b/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_delegate_call_gas.py deleted file mode 100644 index 9ff87ffdc2a..00000000000 --- a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_delegate_call_gas.py +++ /dev/null @@ -1,83 +0,0 @@ -""" -Test_raw_delegate_call_gas. - -Ported from: -state_tests/stEIP150singleCodeGasPrices/RawDelegateCallGasFiller.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - ["state_tests/stEIP150singleCodeGasPrices/RawDelegateCallGasFiller.json"], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_raw_delegate_call_gas( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_raw_delegate_call_gas.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - # Source: lll - # { [[2]] (GAS) } - addr = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x2, value=Op.GAS) + Op.STOP, - nonce=0, - ) - # Source: lll - # { [0] (GAS) (DELEGATECALL 30000 <contract:0x094f5374fce5edbc8e2a8697c15331677e6ebf0b> 0 0 0 0) [[1]] (SUB @0 (GAS)) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.POP( - Op.DELEGATECALL( - gas=0x7530, - address=addr, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ) - ) - + Op.SSTORE(key=0x1, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.STOP, - nonce=0, - ) - - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=500000, - ) - - post = { - addr: Account(storage={}), - target: Account(storage={1: 24736, 2: 29998}), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_delegate_call_gas_ask.py b/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_delegate_call_gas_ask.py deleted file mode 100644 index 64eeacabb25..00000000000 --- a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_delegate_call_gas_ask.py +++ /dev/null @@ -1,85 +0,0 @@ -""" -Test_raw_delegate_call_gas_ask. - -Ported from: -state_tests/stEIP150singleCodeGasPrices/RawDelegateCallGasAskFiller.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - [ - "state_tests/stEIP150singleCodeGasPrices/RawDelegateCallGasAskFiller.json" # noqa: E501 - ], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_raw_delegate_call_gas_ask( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_raw_delegate_call_gas_ask.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - # Source: lll - # { [[2]] (GAS) } - addr = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x2, value=Op.GAS) + Op.STOP, - nonce=0, - ) - # Source: lll - # { [0] (GAS) (DELEGATECALL 3000000 <contract:0x094f5374fce5edbc8e2a8697c15331677e6ebf0b> 0 0 0 0) [[1]] (SUB @0 (GAS)) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.POP( - Op.DELEGATECALL( - gas=0x2DC6C0, - address=addr, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ) - ) - + Op.SSTORE(key=0x1, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.STOP, - nonce=0, - ) - - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=500000, - ) - - post = { - addr: Account(storage={}), - target: Account(storage={1: 24736, 2: 0x727BE}), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_delegate_call_gas_memory.py b/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_delegate_call_gas_memory.py deleted file mode 100644 index 3db2620bb5a..00000000000 --- a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_delegate_call_gas_memory.py +++ /dev/null @@ -1,85 +0,0 @@ -""" -Test_raw_delegate_call_gas_memory. - -Ported from: -state_tests/stEIP150singleCodeGasPrices/RawDelegateCallGasMemoryFiller.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - [ - "state_tests/stEIP150singleCodeGasPrices/RawDelegateCallGasMemoryFiller.json" # noqa: E501 - ], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_raw_delegate_call_gas_memory( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_raw_delegate_call_gas_memory.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - # Source: lll - # { [[2]] (GAS) } - addr = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x2, value=Op.GAS) + Op.STOP, - nonce=0, - ) - # Source: lll - # { [0] (GAS) (DELEGATECALL 30000 <contract:0x094f5374fce5edbc8e2a8697c15331677e6ebf0b> 0 8000 0 8000) [[1]] (SUB @0 (GAS)) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.POP( - Op.DELEGATECALL( - gas=0x7530, - address=addr, - args_offset=0x0, - args_size=0x1F40, - ret_offset=0x0, - ret_size=0x1F40, - ) - ) - + Op.SSTORE(key=0x1, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.STOP, - nonce=0, - ) - - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=500000, - ) - - post = { - addr: Account(storage={}), - target: Account(storage={1: 25605, 2: 29998}), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_delegate_call_gas_memory_ask.py b/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_delegate_call_gas_memory_ask.py deleted file mode 100644 index 37333476bf2..00000000000 --- a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_delegate_call_gas_memory_ask.py +++ /dev/null @@ -1,85 +0,0 @@ -""" -Test_raw_delegate_call_gas_memory_ask. - -Ported from: -state_tests/stEIP150singleCodeGasPrices/RawDelegateCallGasMemoryAskFiller.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - [ - "state_tests/stEIP150singleCodeGasPrices/RawDelegateCallGasMemoryAskFiller.json" # noqa: E501 - ], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_raw_delegate_call_gas_memory_ask( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_raw_delegate_call_gas_memory_ask.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - # Source: lll - # { [[2]] (GAS) } - addr = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x2, value=Op.GAS) + Op.STOP, - nonce=0, - ) - # Source: lll - # { [0] (GAS) (DELEGATECALL 3000000 <contract:0x094f5374fce5edbc8e2a8697c15331677e6ebf0b> 0 8000 0 8000) [[1]] (SUB @0 (GAS)) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.POP( - Op.DELEGATECALL( - gas=0x2DC6C0, - address=addr, - args_offset=0x0, - args_size=0x1F40, - ret_offset=0x0, - ret_size=0x1F40, - ) - ) - + Op.SSTORE(key=0x1, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.STOP, - nonce=0, - ) - - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=500000, - ) - - post = { - addr: Account(storage={}), - target: Account(storage={1: 25605, 2: 0x72467}), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP1559/test_sender_balance.py b/tests/ported_static/stEIP1559/test_sender_balance.py index c248718b4df..7ea2c909564 100644 --- a/tests/ported_static/stEIP1559/test_sender_balance.py +++ b/tests/ported_static/stEIP1559/test_sender_balance.py @@ -1,22 +1,20 @@ """ -The execution records the EIP-1559 transaction origin balance to make... - -properly computed based on the effective gas price (not the maximum gas price -as in -the transaction validity check). +The origin balance seen during execution of an EIP-1559 transaction is +computed from the effective gas price, not the maximum gas price used in the +transaction validity check. Ported from: state_tests/stEIP1559/senderBalanceFiller.yml + +@manually-enhanced: Do not overwrite. Balance derived from gas/fee inputs. """ import pytest from execution_testing import ( - EOA, Account, - Address, Alloc, - Bytes, Environment, + Fork, StateTestFiller, Transaction, ) @@ -29,49 +27,58 @@ @pytest.mark.ported_from( ["state_tests/stEIP1559/senderBalanceFiller.yml"], ) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable +@pytest.mark.valid_from("London") def test_sender_balance( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: - """The execution records the EIP-1559 transaction origin balance to...""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = EOA( - key=0xE04D1AC7DDDA0C98397D56A0B501E960D4CD325A39286919AC23C1A07009A869 - ) + """Origin balance during execution reflects the effective gas price.""" + base_fee = 11 + priority_fee = 100 + max_fee = 1000 + sender_balance = 0xDE0B6B3A7640000 + + # The effective gas price is base + priority (kept below max_fee, so the + # validity check would reserve more — the point of the test). + effective_gas_price = base_fee + priority_fee + + env = Environment(base_fee_per_gas=base_fee) + sender = pre.fund_eoa(amount=sender_balance) - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=11, - gas_limit=30000000, + # Source: yul: { sstore(0, balance(caller())) } + target_code = ( + Op.SSTORE( + key=0x0, + value=Op.BALANCE(address=Op.CALLER, address_warm=False), + key_warm=False, + original_value=0, + new_value=1, + ) + + Op.STOP ) + target = pre.deploy_contract(code=target_code) - pre[sender] = Account(balance=0xDE0B6B3A7640000) - # Source: yul - # london - # { - # sstore(0, balance(caller())) - # } - target = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=Op.BALANCE(address=Op.CALLER)) + Op.STOP, - nonce=0, - address=Address(0x420132F96200BA8E5C98298A85633C35C4F052EF), # noqa: E501 + # Size the gas limit to the work done, so the upfront charge (and thus the + # observed balance) tracks the fork's costs rather than a magic number. + gas_limit = ( + fork.transaction_intrinsic_cost_calculator()() + + target_code.gas_cost(fork) + + 1000 ) tx = Transaction( sender=sender, to=target, - data=Bytes(""), - gas_limit=60000, - max_fee_per_gas=1000, - max_priority_fee_per_gas=100, - access_list=[], + gas_limit=gas_limit, + max_fee_per_gas=max_fee, + max_priority_fee_per_gas=priority_fee, ) - post = {target: Account(storage={0: 0xDE0B6B3A6FE6060})} + post = { + target: Account( + storage={0: sender_balance - gas_limit * effective_gas_price} + ) + } state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP3855_push0/test_push0_gas.py b/tests/ported_static/stEIP3855_push0/test_push0_gas.py index e9a870b6600..a3314b55996 100644 --- a/tests/ported_static/stEIP3855_push0/test_push0_gas.py +++ b/tests/ported_static/stEIP3855_push0/test_push0_gas.py @@ -1,17 +1,18 @@ """ -Test_push0_gas. +Measure the gas cost of the PUSH0 instruction. Ported from: state_tests/Shanghai/stEIP3855_push0/push0GasFiller.yml + +@manually-enhanced: Do not overwrite. PUSH0 gas via CodeGasMeasure. """ import pytest from execution_testing import ( Account, - Address, Alloc, - Bytes, - Environment, + CodeGasMeasure, + Fork, StateTestFiller, Transaction, ) @@ -24,42 +25,29 @@ @pytest.mark.ported_from( ["state_tests/Shanghai/stEIP3855_push0/push0GasFiller.yml"], ) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable +@pytest.mark.valid_from("Shanghai") def test_push0_gas( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: - """Test_push0_gas.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0x989680) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=89128960, - ) - - # Source: raw - # 0x5a6000555f5a6000540360015500 - target = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=Op.GAS) - + Op.PUSH0 - + Op.SSTORE(key=0x1, value=Op.SUB(Op.SLOAD(key=0x0), Op.GAS)) - + Op.STOP, - nonce=0, + """Measure PUSH0's gas cost against the fork-derived expectation.""" + sender = pre.fund_eoa() + + push0_code = Op.PUSH0 + target = pre.deploy_contract( + code=CodeGasMeasure( + code=push0_code, + extra_stack_items=1, + sstore_key=0x1, + ), ) tx = Transaction( sender=sender, to=target, - data=Bytes(""), - gas_limit=100000, ) - post = {target: Account(storage={0: 0x13496, 1: 22107})} + post = {target: Account(storage={0x1: push0_code.gas_cost(fork)})} - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP3855_push0/test_push0_gas2.py b/tests/ported_static/stEIP3855_push0/test_push0_gas2.py index 7405bce15a3..85e6c5140be 100644 --- a/tests/ported_static/stEIP3855_push0/test_push0_gas2.py +++ b/tests/ported_static/stEIP3855_push0/test_push0_gas2.py @@ -1,24 +1,23 @@ """ -Test_push0_gas2. +Measure the gas cost of PUSH0 and of PUSH1 0x00: each case asserts its own +fork-derived cost, which together demonstrate PUSH0 is the cheaper encoding. Ported from: state_tests/Shanghai/stEIP3855_push0/push0Gas2Filler.yml + +@manually-enhanced: Do not overwrite. Opcode gas via CodeGasMeasure. """ import pytest from execution_testing import ( - EOA, Account, - Address, Alloc, - Environment, + Bytecode, + CodeGasMeasure, + Fork, StateTestFiller, Transaction, ) -from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( - resolve_expect_post, -) from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" @@ -28,138 +27,34 @@ @pytest.mark.ported_from( ["state_tests/Shanghai/stEIP3855_push0/push0Gas2Filler.yml"], ) -@pytest.mark.valid_from("Cancun") +@pytest.mark.valid_from("Shanghai") @pytest.mark.parametrize( - "d, g, v", - [ - pytest.param( - 0, - 0, - 0, - id="use_push0", - ), - pytest.param( - 1, - 0, - 0, - id="use_push1_00", - ), - ], + "opcode", + [Op.PUSH0, Op.PUSH1[0x00]], + ids=["use_push0", "use_push1_00"], ) -@pytest.mark.pre_alloc_mutable def test_push0_gas2( state_test: StateTestFiller, pre: Alloc, fork: Fork, - d: int, - g: int, - v: int, + opcode: Bytecode, ) -> None: - """Test_push0_gas2.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B) - contract_1 = Address(0x0000000000000000000000000000000000001000) - contract_2 = Address(0x0000000000000000000000000000000000000200) - sender = EOA( - key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8 - ) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=89128960, - ) - - pre[sender] = Account(balance=0x989680) - # Source: yul - # berlin - # { - # sstore(0, call(100000, shr(96, calldataload(0)), 0, 0, 0, 0, 0)) - # sstore(1, 1) - # } - contract_0 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE( - key=0x0, - value=Op.CALL( - gas=0x186A0, - address=Op.SHR(0x60, Op.CALLDATALOAD(offset=Op.DUP1)), - value=Op.DUP1, - args_offset=Op.DUP1, - args_size=Op.DUP1, - ret_offset=Op.DUP1, - ret_size=0x0, - ), - ) - + Op.SSTORE(key=Op.DUP1, value=0x1) - + Op.STOP, - nonce=0, - address=Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B), # noqa: E501 - ) - # Source: raw - # 0x5a5f5a9091039055 - contract_1 = pre.deploy_contract( # noqa: F841 - code=Op.GAS - + Op.PUSH0 - + Op.GAS - + Op.SWAP1 - + Op.SWAP2 - + Op.SUB - + Op.SWAP1 - + Op.SSTORE, - nonce=0, - address=Address(0x0000000000000000000000000000000000001000), # noqa: E501 - ) - # Source: raw - # 0x5a60005a9091039055 - contract_2 = pre.deploy_contract( # noqa: F841 - code=Op.GAS - + Op.PUSH1[0x0] - + Op.GAS - + Op.SWAP1 - + Op.SWAP2 - + Op.SUB - + Op.SWAP1 - + Op.SSTORE, - nonce=0, - address=Address(0x0000000000000000000000000000000000000200), # noqa: E501 + """Measure the parametrized push encoding's exact gas cost.""" + sender = pre.fund_eoa() + + measured = pre.deploy_contract( + code=CodeGasMeasure( + code=opcode, + extra_stack_items=1, + sstore_key=0x0, + ), ) - expect_entries_: list[dict] = [ - { - "indexes": {"data": [0], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": { - contract_1: Account(storage={0: 4}, balance=0), - contract_0: Account(storage={0: 1, 1: 1}), - }, - }, - { - "indexes": {"data": [1], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": { - contract_2: Account(storage={0: 5}, balance=0), - contract_0: Account(storage={0: 1, 1: 1}), - }, - }, - ] - - post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork) - - tx_data = [ - contract_1, - contract_2, - ] - tx_gas = [300000] - tx = Transaction( sender=sender, - to=contract_0, - data=tx_data[d], - gas_limit=tx_gas[g], - error=_exc, + to=measured, ) - state_test(env=env, pre=pre, post=post, tx=tx) + post = {measured: Account(storage={0x0: opcode.gas_cost(fork)})} + + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP5656_MCOPY/test_mcopy_copy_cost.py b/tests/ported_static/stEIP5656_MCOPY/test_mcopy_copy_cost.py index 8cd67806eee..12340a9259e 100644 --- a/tests/ported_static/stEIP5656_MCOPY/test_mcopy_copy_cost.py +++ b/tests/ported_static/stEIP5656_MCOPY/test_mcopy_copy_cost.py @@ -3,604 +3,73 @@ Ported from: state_tests/Cancun/stEIP5656_MCOPY/MCOPY_copy_costFiller.yml + +@manually-enhanced: Do not overwrite. The ported filler probed MCOPY cost via a +tight OOG gas boundary (55697); EIP-8037 reprices the instrumentation SSTORE +into state gas, breaking that boundary. Reframed to measure the MCOPY copy cost +directly with CodeGasMeasure over a pre-expanded memory (so no expansion is +charged), asserting the fork-derived `mcopy.gas_cost(fork)`. """ import pytest from execution_testing import ( Account, - Address, Alloc, - Environment, - Hash, + CodeGasMeasure, + Fork, StateTestFiller, Transaction, ) -from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( - resolve_expect_post, -) from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +GAS_SLOT = 0x0 +# MSTORE at this offset grows memory to PREEXPANDED bytes, covering every +# (src, size) copy region below so the measured MCOPY never expands memory. +PREEXPAND_OFFSET = 0xAF00 +PREEXPANDED = PREEXPAND_OFFSET + 0x20 # 44832 bytes = 1401 words + +SRCS = [0x0, 0x1, 0x1F, 0x20] +SIZES = [0x0, 0x1, 0x1F, 0x20, 0x21, 0xAEDF, 0xAEE0, 0xAEE1] + @pytest.mark.ported_from( ["state_tests/Cancun/stEIP5656_MCOPY/MCOPY_copy_costFiller.yml"], ) @pytest.mark.valid_from("Cancun") -@pytest.mark.parametrize( - "d, g, v", - [ - pytest.param( - 0, - 0, - 0, - id="src0_size0-g0", - ), - pytest.param( - 0, - 1, - 0, - id="src0_size0-g1", - ), - pytest.param( - 1, - 0, - 0, - id="src0_size1-g0", - ), - pytest.param( - 1, - 1, - 0, - id="src0_size1-g1", - ), - pytest.param( - 2, - 0, - 0, - id="src0_size31-g0", - ), - pytest.param( - 2, - 1, - 0, - id="src0_size31-g1", - ), - pytest.param( - 3, - 0, - 0, - id="src0_size32-g0", - ), - pytest.param( - 3, - 1, - 0, - id="src0_size32-g1", - ), - pytest.param( - 4, - 0, - 0, - id="src0_size33-g0", - ), - pytest.param( - 4, - 1, - 0, - id="src0_size33-g1", - ), - pytest.param( - 5, - 0, - 0, - id="src0_size44767-g0", - ), - pytest.param( - 5, - 1, - 0, - id="src0_size44767-g1", - ), - pytest.param( - 6, - 0, - 0, - id="src0_size44768-g0", - ), - pytest.param( - 6, - 1, - 0, - id="src0_size44768-g1", - ), - pytest.param( - 7, - 0, - 0, - id="src0_size44769-g0", - ), - pytest.param( - 7, - 1, - 0, - id="src0_size44769-g1", - ), - pytest.param( - 8, - 0, - 0, - id="src1_size0-g0", - ), - pytest.param( - 8, - 1, - 0, - id="src1_size0-g1", - ), - pytest.param( - 9, - 0, - 0, - id="src1_size1-g0", - ), - pytest.param( - 9, - 1, - 0, - id="src1_size1-g1", - ), - pytest.param( - 10, - 0, - 0, - id="src1_size31-g0", - ), - pytest.param( - 10, - 1, - 0, - id="src1_size31-g1", - ), - pytest.param( - 11, - 0, - 0, - id="src1_size32-g0", - ), - pytest.param( - 11, - 1, - 0, - id="src1_size32-g1", - ), - pytest.param( - 12, - 0, - 0, - id="src1_size33-g0", - ), - pytest.param( - 12, - 1, - 0, - id="src1_size33-g1", - ), - pytest.param( - 13, - 0, - 0, - id="src1_size44767-g0", - ), - pytest.param( - 13, - 1, - 0, - id="src1_size44767-g1", - ), - pytest.param( - 14, - 0, - 0, - id="src1_size44768-g0", - ), - pytest.param( - 14, - 1, - 0, - id="src1_size44768-g1", - ), - pytest.param( - 15, - 0, - 0, - id="src1_size44769-g0", - ), - pytest.param( - 15, - 1, - 0, - id="src1_size44769-g1", - ), - pytest.param( - 16, - 0, - 0, - id="src31_size0-g0", - ), - pytest.param( - 16, - 1, - 0, - id="src31_size0-g1", - ), - pytest.param( - 17, - 0, - 0, - id="src31_size1-g0", - ), - pytest.param( - 17, - 1, - 0, - id="src31_size1-g1", - ), - pytest.param( - 18, - 0, - 0, - id="src31_size31-g0", - ), - pytest.param( - 18, - 1, - 0, - id="src31_size31-g1", - ), - pytest.param( - 19, - 0, - 0, - id="src31_size32-g0", - ), - pytest.param( - 19, - 1, - 0, - id="src31_size32-g1", - ), - pytest.param( - 20, - 0, - 0, - id="src31_size33-g0", - ), - pytest.param( - 20, - 1, - 0, - id="src31_size33-g1", - ), - pytest.param( - 21, - 0, - 0, - id="src31_size44767-g0", - ), - pytest.param( - 21, - 1, - 0, - id="src31_size44767-g1", - ), - pytest.param( - 22, - 0, - 0, - id="src31_size44768-g0", - ), - pytest.param( - 22, - 1, - 0, - id="src31_size44768-g1", - ), - pytest.param( - 23, - 0, - 0, - id="src31_size44769-g0", - ), - pytest.param( - 23, - 1, - 0, - id="src31_size44769-g1", - ), - pytest.param( - 24, - 0, - 0, - id="src32_size0-g0", - ), - pytest.param( - 24, - 1, - 0, - id="src32_size0-g1", - ), - pytest.param( - 25, - 0, - 0, - id="src32_size1-g0", - ), - pytest.param( - 25, - 1, - 0, - id="src32_size1-g1", - ), - pytest.param( - 26, - 0, - 0, - id="src32_size31-g0", - ), - pytest.param( - 26, - 1, - 0, - id="src32_size31-g1", - ), - pytest.param( - 27, - 0, - 0, - id="src32_size32-g0", - ), - pytest.param( - 27, - 1, - 0, - id="src32_size32-g1", - ), - pytest.param( - 28, - 0, - 0, - id="src32_size33-g0", - ), - pytest.param( - 28, - 1, - 0, - id="src32_size33-g1", - ), - pytest.param( - 29, - 0, - 0, - id="src32_size44767-g0", - ), - pytest.param( - 29, - 1, - 0, - id="src32_size44767-g1", - ), - pytest.param( - 30, - 0, - 0, - id="src32_size44768-g0", - ), - pytest.param( - 30, - 1, - 0, - id="src32_size44768-g1", - ), - pytest.param( - 31, - 0, - 0, - id="src32_size44769-g0", - ), - pytest.param( - 31, - 1, - 0, - id="src32_size44769-g1", - ), - ], -) +@pytest.mark.parametrize("size", SIZES, ids=lambda s: f"size{s}") +@pytest.mark.parametrize("src", SRCS, ids=lambda s: f"src{s}") def test_mcopy_copy_cost( state_test: StateTestFiller, pre: Alloc, fork: Fork, - d: int, - g: int, - v: int, + src: int, + size: int, ) -> None: - """Test cases for the cost of memory copy in the MCOPY instruction.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0x3B9ACA00) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1687174231, - prev_randao=0x20000, - base_fee_per_gas=10, + """Measure the MCOPY copy cost (linear in size, independent of source).""" + # Memory is pre-expanded past the largest copy region, so the measured + # MCOPY charges only its base + per-word copy cost, never expansion. + mcopy = Op.MCOPY( + dest_offset=0x0, + offset=src, + size=size, + data_size=size, + old_memory_size=PREEXPANDED, + new_memory_size=PREEXPANDED, ) - - # Source: yul - # shanghai optimise { - # function mcopy(dst, src, size) { verbatim_3i_0o(hex"5e", dst, src, size) } # noqa: E501 - # - # // Put a flag in storage indicating successful execution (will be reverted in case of OOG). # noqa: E501 - # sstore(0, 1) - # - # // Expand memory to cover memory expansion cost before MCOPY. - # // The test uses up to 1400 memory words. - # mstore(44800, 1) - # - # // MCOPY using src and size from CALLDATA to 0 destination. - # mcopy(0, calldataload(0), calldataload(32)) - # } - target = pre.deploy_contract( # noqa: F841 - code=Op.JUMP(pc=0xC) - + Op.JUMPDEST - + Op.MCOPY(dest_offset=Op.DUP3, offset=Op.DUP3, size=Op.DUP3) - + Op.POP * 3 - + Op.JUMP - + Op.JUMPDEST - + Op.SSTORE(key=Op.PUSH0, value=0x1) - + Op.MSTORE(offset=0xAF00, value=0x1) - + Op.PUSH1[0x22] - + Op.CALLDATALOAD(offset=0x20) - + Op.CALLDATALOAD(offset=Op.PUSH0) - + Op.PUSH0 - + Op.JUMP(pc=0x3) - + Op.JUMPDEST, - nonce=1, + contract = pre.deploy_contract( + code=Op.MSTORE(offset=PREEXPAND_OFFSET, value=0x1) + + CodeGasMeasure( + code=mcopy, + extra_stack_items=0, + sstore_key=GAS_SLOT, + ), ) - expect_entries_: list[dict] = [ - { - "indexes": { - "data": [ - 0, - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25, - 26, - 27, - 28, - 29, - 30, - 31, - ], - "gas": 0, - "value": -1, - }, - "network": [">=Cancun"], - "result": {target: Account(storage={0: 1})}, - }, - { - "indexes": { - "data": [ - 0, - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 16, - 17, - 18, - 19, - 20, - 24, - 25, - 26, - 27, - 28, - ], - "gas": 1, - "value": -1, - }, - "network": [">=Cancun"], - "result": {target: Account(storage={0: 1})}, - }, - { - "indexes": { - "data": [13, 14, 15, 21, 22, 23, 29, 30, 31], - "gas": 1, - "value": -1, - }, - "network": [">=Cancun"], - "result": {target: Account(storage={0: 0})}, - }, - ] - - post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork) + tx = Transaction(sender=pre.fund_eoa(), to=contract) - tx_data = [ - Hash(0x0) + Hash(0x0), - Hash(0x0) + Hash(0x1), - Hash(0x0) + Hash(0x1F), - Hash(0x0) + Hash(0x20), - Hash(0x0) + Hash(0x21), - Hash(0x0) + Hash(0xAEDF), - Hash(0x0) + Hash(0xAEE0), - Hash(0x0) + Hash(0xAEE1), - Hash(0x1) + Hash(0x0), - Hash(0x1) + Hash(0x1), - Hash(0x1) + Hash(0x1F), - Hash(0x1) + Hash(0x20), - Hash(0x1) + Hash(0x21), - Hash(0x1) + Hash(0xAEDF), - Hash(0x1) + Hash(0xAEE0), - Hash(0x1) + Hash(0xAEE1), - Hash(0x1F) + Hash(0x0), - Hash(0x1F) + Hash(0x1), - Hash(0x1F) + Hash(0x1F), - Hash(0x1F) + Hash(0x20), - Hash(0x1F) + Hash(0x21), - Hash(0x1F) + Hash(0xAEDF), - Hash(0x1F) + Hash(0xAEE0), - Hash(0x1F) + Hash(0xAEE1), - Hash(0x20) + Hash(0x0), - Hash(0x20) + Hash(0x1), - Hash(0x20) + Hash(0x1F), - Hash(0x20) + Hash(0x20), - Hash(0x20) + Hash(0x21), - Hash(0x20) + Hash(0xAEDF), - Hash(0x20) + Hash(0xAEE0), - Hash(0x20) + Hash(0xAEE1), - ] - tx_gas = [100000, 55697] - - tx = Transaction( - sender=sender, - to=target, - data=tx_data[d], - gas_limit=tx_gas[g], - error=_exc, - ) + post = {contract: Account(storage={GAS_SLOT: mcopy.gas_cost(fork)})} - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stMemoryTest/test_call_data_copy_offset.py b/tests/ported_static/stMemoryTest/test_call_data_copy_offset.py deleted file mode 100644 index 2cd7a467e4b..00000000000 --- a/tests/ported_static/stMemoryTest/test_call_data_copy_offset.py +++ /dev/null @@ -1,97 +0,0 @@ -""" -Test_call_data_copy_offset. - -Ported from: -state_tests/stMemoryTest/callDataCopyOffsetFiller.json -""" - -import pytest -from execution_testing import ( - EOA, - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - ["state_tests/stMemoryTest/callDataCopyOffsetFiller.json"], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_call_data_copy_offset( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_call_data_copy_offset.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0xEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE) - contract_1 = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87) - sender = EOA( - key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8 - ) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=1000000, - ) - - pre[sender] = Account(balance=0xDE0B6B3A7640000) - # Source: lll - # { (MSTORE 0x00 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) (CALLDATACOPY 0x00 0xffff 0x10) (SSTORE 0x00 (MLOAD 0x00)) } # noqa: E501 - contract_0 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE( - offset=0x0, - value=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, # noqa: E501 - ) - + Op.CALLDATACOPY(dest_offset=0x0, offset=0xFFFF, size=0x10) - + Op.SSTORE(key=0x0, value=Op.MLOAD(offset=0x0)) - + Op.STOP, - balance=0xDE0B6B3A7640000, - nonce=1, - address=Address(0xEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE), # noqa: E501 - ) - # Source: yul - # berlin { mstore(0, 0x0123456789abcdef) pop(call(0xffff,0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee,0, 0,0x0f, 0,0)) } # noqa: E501 - contract_1 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=0x123456789ABCDEF) - + Op.CALL( - gas=0xFFFF, - address=contract_0, - value=Op.DUP1, - args_offset=Op.DUP2, - args_size=0xF, - ret_offset=Op.DUP1, - ret_size=0x0, - ) - + Op.STOP, - balance=0xDE0B6B3A7640000, - nonce=1, - address=Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87), # noqa: E501 - ) - - tx = Transaction( - sender=sender, - to=contract_1, - data=Bytes(""), - gas_limit=400000, - value=0x186A0, - ) - - post = { - contract_0: Account(storage={0: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF}) - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stMemoryTest/test_code_copy_offset.py b/tests/ported_static/stMemoryTest/test_code_copy_offset.py deleted file mode 100644 index 36b096523aa..00000000000 --- a/tests/ported_static/stMemoryTest/test_code_copy_offset.py +++ /dev/null @@ -1,93 +0,0 @@ -""" -Test_code_copy_offset. - -Ported from: -state_tests/stMemoryTest/codeCopyOffsetFiller.json -""" - -import pytest -from execution_testing import ( - EOA, - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - ["state_tests/stMemoryTest/codeCopyOffsetFiller.json"], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_code_copy_offset( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_code_copy_offset.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = EOA( - key=0xB1F4CBC3A50042184425A6F9E996D0910F7BA879457CE5DAC5C71E498AD3C005 - ) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=1000000, - ) - - pre[sender] = Account(balance=0xDE0B6B3A7640000) - # Source: lll - # { (MSTORE 0x00 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) (CODECOPY 0x00 0xffff 0x10) (SSTORE 0x00 (MLOAD 0x00)) } # noqa: E501 - addr = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE( - offset=0x0, - value=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, # noqa: E501 - ) - + Op.CODECOPY(dest_offset=0x0, offset=0xFFFF, size=0x10) - + Op.SSTORE(key=0x0, value=Op.MLOAD(offset=0x0)) - + Op.STOP, - balance=0xDE0B6B3A7640000, - nonce=1, - address=Address(0x27D16E1D3CC862149F1E7162E612635FCAEF9FF4), # noqa: E501 - ) - # Source: yul - # berlin { mstore(0, 0x0123456789abcdef) pop(call(0xffff, <contract:0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee>, 0, 0, 0x0f, 0, 0)) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=0x123456789ABCDEF) - + Op.CALL( - gas=0xFFFF, - address=addr, - value=Op.DUP1, - args_offset=Op.DUP2, - args_size=0xF, - ret_offset=Op.DUP1, - ret_size=0x0, - ) - + Op.STOP, - balance=0xDE0B6B3A7640000, - nonce=1, - address=Address(0xAF89A7504341A87E1CFDFFD483A00A4688469B3D), # noqa: E501 - ) - - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=400000, - value=0x186A0, - ) - - post = {addr: Account(storage={0: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF})} - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stMemoryTest/test_copy_offset.py b/tests/ported_static/stMemoryTest/test_copy_offset.py new file mode 100644 index 00000000000..26f32b060a9 --- /dev/null +++ b/tests/ported_static/stMemoryTest/test_copy_offset.py @@ -0,0 +1,77 @@ +""" +Test CODECOPY / CALLDATACOPY reading from an out-of-bounds source offset, +which yields zeros. + +Ported from: +state_tests/stMemoryTest/codeCopyOffsetFiller.json +state_tests/stMemoryTest/callDataCopyOffsetFiller.json + +@manually-enhanced: Do not overwrite. CODECOPY/CALLDATACOPY OOB-offset +zero-fill folded into one parametrize; delivery-CALL dropped; dynamic +addresses; nonzero tx calldata so a wrong in-bounds offset is observable. +""" + +import pytest +from execution_testing import ( + Account, + Alloc, + Fork, + StateTestFiller, + Transaction, +) +from execution_testing.vm import Op + +REFERENCE_SPEC_GIT_PATH = "N/A" +REFERENCE_SPEC_VERSION = "N/A" + +# Copy 16 bytes from a source offset far past the end of code/calldata; the +# out-of-bounds region reads as zeros, which overwrite memory bytes 0..15 +# (the most-significant half of the word MLOAD reads back), leaving only the +# low 128 bits of the pre-filled word set to 0xFF. +OOB_OFFSET = 0xFFFF +COPY_SIZE = 0x10 +EXPECTED = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF +# Nonzero calldata makes the CALLDATACOPY arm discriminate a wrong (in-bounds) +# source offset from the correct out-of-bounds zero-fill; with empty calldata +# every offset would read zeros and the assertion would be vacuous. +TX_DATA = bytes(range(1, 33)) + + +@pytest.mark.ported_from( + [ + "state_tests/stMemoryTest/codeCopyOffsetFiller.json", + "state_tests/stMemoryTest/callDataCopyOffsetFiller.json", + ], +) +@pytest.mark.valid_from("Frontier") +@pytest.mark.parametrize( + "copy_op", + [ + pytest.param(Op.CODECOPY, id="code_copy_offset"), + pytest.param(Op.CALLDATACOPY, id="call_data_copy_offset"), + ], +) +def test_copy_offset( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + copy_op: Op, +) -> None: + """Copying from an out-of-bounds source offset yields zeros.""" + contract = pre.deploy_contract( + code=Op.MSTORE(offset=0x0, value=(1 << 256) - 1) + + copy_op(dest_offset=0x0, offset=OOB_OFFSET, size=COPY_SIZE) + + Op.SSTORE(key=0x0, value=Op.MLOAD(offset=0x0)) + + Op.STOP, + ) + + tx = Transaction( + sender=pre.fund_eoa(), + to=contract, + data=TX_DATA, + protected=fork.supports_protected_txs(), + ) + + post = {contract: Account(storage={0: EXPECTED})} + + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stNonZeroCallsTest/test_non_zero_value.py b/tests/ported_static/stNonZeroCallsTest/test_non_zero_value.py new file mode 100644 index 00000000000..27f734dbb65 --- /dev/null +++ b/tests/ported_static/stNonZeroCallsTest/test_non_zero_value.py @@ -0,0 +1,185 @@ +""" +Measure the gas cost of CALL / CALLCODE / DELEGATECALL carrying non-zero +value to targets in various pre-states, using CodeGasMeasure. + +Ported from: +state_tests/stNonZeroCallsTest/NonZeroValue_CALLFiller.json +state_tests/stNonZeroCallsTest/NonZeroValue_CALL_ToEmpty_ParisFiller.json +state_tests/stNonZeroCallsTest/NonZeroValue_CALL_ToOneStorageKey_ParisFiller.json +state_tests/stNonZeroCallsTest/NonZeroValue_CALLCODEFiller.json +state_tests/stNonZeroCallsTest/NonZeroValue_CALLCODE_ToEmpty_ParisFiller.json +state_tests/stNonZeroCallsTest/NonZeroValue_CALLCODE_ToOneStorageKey_ParisFiller.json +state_tests/stNonZeroCallsTest/NonZeroValue_DELEGATECALLFiller.json +state_tests/stNonZeroCallsTest/NonZeroValue_DELEGATECALL_ToEmpty_ParisFiller.json +state_tests/stNonZeroCallsTest/NonZeroValue_DELEGATECALL_ToOneStorageKey_ParisFiller.json +state_tests/stNonZeroCallsTest/NonZeroValue_DELEGATECALL_ToNonNonZeroBalanceFiller.json + +@manually-enhanced: Do not overwrite. Call gas via CodeGasMeasure; the call +success flag is stored inside the measured window (a wrongly failed call is +gas-identical to success against an empty callee, so gas alone cannot +discriminate). +""" + +import pytest +from execution_testing import ( + Account, + Alloc, + CodeGasMeasure, + Fork, + StateTestFiller, + Transaction, +) +from execution_testing.vm import Op + +REFERENCE_SPEC_GIT_PATH = "N/A" +REFERENCE_SPEC_VERSION = "N/A" + +CONTRACT_BALANCE = 100 +CALL_VALUE = 1 +EXISTING_BALANCE = 10 +NONZERO_BALANCE = 100 +FORWARDED_GAS = 0xEA60 +GAS_SLOT = 0x64 +SUCCESS_SLOT = 0x1 + + +@pytest.mark.ported_from( + [ + "state_tests/stNonZeroCallsTest/NonZeroValue_CALLFiller.json", + "state_tests/stNonZeroCallsTest/NonZeroValue_CALL_ToEmpty_ParisFiller.json", # noqa: E501 + "state_tests/stNonZeroCallsTest/NonZeroValue_CALL_ToOneStorageKey_ParisFiller.json", # noqa: E501 + "state_tests/stNonZeroCallsTest/NonZeroValue_CALLCODEFiller.json", + "state_tests/stNonZeroCallsTest/NonZeroValue_CALLCODE_ToEmpty_ParisFiller.json", # noqa: E501 + "state_tests/stNonZeroCallsTest/NonZeroValue_CALLCODE_ToOneStorageKey_ParisFiller.json", # noqa: E501 + "state_tests/stNonZeroCallsTest/NonZeroValue_DELEGATECALLFiller.json", + "state_tests/stNonZeroCallsTest/NonZeroValue_DELEGATECALL_ToEmpty_ParisFiller.json", # noqa: E501 + "state_tests/stNonZeroCallsTest/NonZeroValue_DELEGATECALL_ToOneStorageKey_ParisFiller.json", # noqa: E501 + "state_tests/stNonZeroCallsTest/NonZeroValue_DELEGATECALL_ToNonNonZeroBalanceFiller.json", # noqa: E501 + ], +) +@pytest.mark.valid_from("Berlin") +@pytest.mark.parametrize( + "opcode, target_kind", + [ + pytest.param(Op.CALL, "nonexistent", id="call"), + pytest.param(Op.CALL, "empty", id="call_to_empty"), + pytest.param(Op.CALL, "one_storage_key", id="call_to_one_storage_key"), + pytest.param(Op.CALLCODE, "nonexistent", id="callcode"), + pytest.param(Op.CALLCODE, "empty", id="callcode_to_empty"), + pytest.param( + Op.CALLCODE, "one_storage_key", id="callcode_to_one_storage_key" + ), + pytest.param(Op.DELEGATECALL, "nonexistent", id="delegatecall"), + pytest.param(Op.DELEGATECALL, "empty", id="delegatecall_to_empty"), + pytest.param( + Op.DELEGATECALL, + "one_storage_key", + id="delegatecall_to_one_storage_key", + ), + pytest.param( + Op.DELEGATECALL, + "nonzero_balance", + id="delegatecall_to_nonzero_balance", + ), + ], +) +def test_non_zero_value( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + opcode: Op, + target_kind: str, +) -> None: + """Measure call-family gas to a cold target of each pre-state.""" + transfers_value = opcode != Op.DELEGATECALL + + # Set up the target account in the requested pre-state. + if target_kind == "nonexistent": + call_target = pre.nonexistent_account() + target_balance = 0 + target_storage: dict = {} + elif target_kind == "one_storage_key": + target_balance = EXISTING_BALANCE + target_storage = {0x0: 0x1} + call_target = pre.deploy_contract( + code=b"", balance=target_balance, storage=target_storage + ) + else: + target_balance = ( + NONZERO_BALANCE + if target_kind == "nonzero_balance" + else EXISTING_BALANCE + ) + target_storage = {} + call_target = pre.fund_eoa(amount=target_balance) + + # Only a plain CALL forwards value to the target (and can create it); + # CALLCODE keeps value in the caller's context, DELEGATECALL has no value. + account_new = opcode == Op.CALL and target_kind == "nonexistent" + received = CALL_VALUE if opcode == Op.CALL else 0 + + if opcode == Op.DELEGATECALL: + call_code = Op.DELEGATECALL( + gas=FORWARDED_GAS, + address=call_target, + address_warm=False, + ) + else: + call_code = opcode( + gas=FORWARDED_GAS, + address=call_target, + value=CALL_VALUE, + address_warm=False, + value_transfer=True, + account_new=account_new, + ) + + # Store the call's success flag inside the measured window: a wrongly + # failed call is otherwise indistinguishable from a success into empty + # code (same gas, balances, and storage for CALLCODE/DELEGATECALL). + store_code = Op.SSTORE( + SUCCESS_SLOT, + call_code, + key_warm=False, + original_value=0, + new_value=1, + ) + + contract = pre.deploy_contract( + code=CodeGasMeasure( + code=store_code, + extra_stack_items=0, + sstore_key=GAS_SLOT, + ), + balance=CONTRACT_BALANCE, + ) + + tx = Transaction( + sender=pre.fund_eoa(), + to=contract, + state_gas_reservoir=0, + ) + + # A value-bearing call whose callee consumes nothing returns the stipend. + measured = store_code.gas_cost(fork) + if transfers_value: + measured -= fork.gas_costs().CALL_STIPEND + + if target_kind == "nonexistent": + target_account = ( + Account(balance=CALL_VALUE) if account_new else Account.NONEXISTENT + ) + else: + target_account = Account( + balance=target_balance + received, storage=target_storage + ) + + post = { + contract: Account( + storage={GAS_SLOT: measured, SUCCESS_SLOT: 1}, + balance=CONTRACT_BALANCE - received, + ), + call_target: target_account, + } + + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stNonZeroCallsTest/test_non_zero_value_call.py b/tests/ported_static/stNonZeroCallsTest/test_non_zero_value_call.py deleted file mode 100644 index aae4b4903c5..00000000000 --- a/tests/ported_static/stNonZeroCallsTest/test_non_zero_value_call.py +++ /dev/null @@ -1,88 +0,0 @@ -""" -Test_non_zero_value_call. - -Ported from: -state_tests/stNonZeroCallsTest/NonZeroValue_CALLFiller.json -""" - -import pytest -from execution_testing import ( - EOA, - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - ["state_tests/stNonZeroCallsTest/NonZeroValue_CALLFiller.json"], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_non_zero_value_call( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_non_zero_value_call.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B) - sender = EOA( - key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8 - ) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - pre[sender] = Account(balance=0xE8D4A51000) - # Source: lll - # { [0](GAS) [[1]] (CALL 60000 0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b 1 0 0 0 0) [[100]] (SUB @0 (GAS)) } # noqa: E501 - contract_0 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.SSTORE( - key=0x1, - value=Op.CALL( - gas=0xEA60, - address=0xC94F5374FCE5EDBC8E2A8697C15331677E6EBF0B, - value=0x1, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.SSTORE(key=0x64, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.STOP, - balance=100, - nonce=0, - address=Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B), # noqa: E501 - ) - - tx = Transaction( - sender=sender, - to=contract_0, - data=Bytes(""), - gas_limit=600000, - ) - - post = { - contract_0: Account(storage={1: 1, 100: 56435}, balance=99), - Address(0xC94F5374FCE5EDBC8E2A8697C15331677E6EBF0B): Account( - balance=1 - ), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stNonZeroCallsTest/test_non_zero_value_call_to_empty_paris.py b/tests/ported_static/stNonZeroCallsTest/test_non_zero_value_call_to_empty_paris.py deleted file mode 100644 index fcfb431e5b8..00000000000 --- a/tests/ported_static/stNonZeroCallsTest/test_non_zero_value_call_to_empty_paris.py +++ /dev/null @@ -1,83 +0,0 @@ -""" -Test_non_zero_value_call_to_empty_paris. - -Ported from: -state_tests/stNonZeroCallsTest/NonZeroValue_CALL_ToEmpty_ParisFiller.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - [ - "state_tests/stNonZeroCallsTest/NonZeroValue_CALL_ToEmpty_ParisFiller.json" # noqa: E501 - ], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_non_zero_value_call_to_empty_paris( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_non_zero_value_call_to_empty_paris.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - addr = pre.fund_eoa(amount=10) # noqa: F841 - # Source: lll - # { [0](GAS) [[1]] (CALL 60000 <eoa:0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b> 1 0 0 0 0) [[100]] (SUB @0 (GAS)) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.SSTORE( - key=0x1, - value=Op.CALL( - gas=0xEA60, - address=addr, - value=0x1, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.SSTORE(key=0x64, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.STOP, - balance=1000, - nonce=0, - ) - - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=600000, - ) - - post = { - addr: Account(balance=11), - target: Account(storage={1: 1, 100: 31435}), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stNonZeroCallsTest/test_non_zero_value_call_to_one_storage_key_paris.py b/tests/ported_static/stNonZeroCallsTest/test_non_zero_value_call_to_one_storage_key_paris.py deleted file mode 100644 index 76dd7cdb2ba..00000000000 --- a/tests/ported_static/stNonZeroCallsTest/test_non_zero_value_call_to_one_storage_key_paris.py +++ /dev/null @@ -1,89 +0,0 @@ -""" -Test_non_zero_value_call_to_one_storage_key_paris. - -Ported from: -state_tests/stNonZeroCallsTest/NonZeroValue_CALL_ToOneStorageKey_ParisFiller.json -""" - -import pytest -from execution_testing import ( - EOA, - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - [ - "state_tests/stNonZeroCallsTest/NonZeroValue_CALL_ToOneStorageKey_ParisFiller.json" # noqa: E501 - ], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_non_zero_value_call_to_one_storage_key_paris( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_non_zero_value_call_to_one_storage_key_paris.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - addr = Address(0x4757608F18B70777AE788DD4056EEED52F7AA68F) - sender = EOA( - key=0x4F31B3206FBF0E0E598B9B1A7D8AC86302A0FF1D8930738F1BEBAE9B67173E52 - ) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - pre[sender] = Account(balance=0xE8D4A51000) - pre[addr] = Account(balance=10, storage={0: 1}) - # Source: lll - # { [0](GAS) [[1]] (CALL 60000 <eoa:0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b> 1 0 0 0 0) [[100]] (SUB @0 (GAS)) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.SSTORE( - key=0x1, - value=Op.CALL( - gas=0xEA60, - address=0x4757608F18B70777AE788DD4056EEED52F7AA68F, - value=0x1, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.SSTORE(key=0x64, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.STOP, - balance=1000, - nonce=0, - address=Address(0xF6029618CF51CA5236AFC14EAD1FBE0739573C23), # noqa: E501 - ) - - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=600000, - ) - - post = { - addr: Account(storage={0: 1}, balance=11), - target: Account(storage={1: 1, 100: 31435}), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stNonZeroCallsTest/test_non_zero_value_callcode.py b/tests/ported_static/stNonZeroCallsTest/test_non_zero_value_callcode.py deleted file mode 100644 index 55b2e219e75..00000000000 --- a/tests/ported_static/stNonZeroCallsTest/test_non_zero_value_callcode.py +++ /dev/null @@ -1,88 +0,0 @@ -""" -Test_non_zero_value_callcode. - -Ported from: -state_tests/stNonZeroCallsTest/NonZeroValue_CALLCODEFiller.json -""" - -import pytest -from execution_testing import ( - EOA, - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - ["state_tests/stNonZeroCallsTest/NonZeroValue_CALLCODEFiller.json"], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_non_zero_value_callcode( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_non_zero_value_callcode.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B) - sender = EOA( - key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8 - ) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - pre[sender] = Account(balance=0xE8D4A51000) - # Source: lll - # { [0](GAS) [[1]] (CALLCODE 60000 0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b 1 0 0 0 0) [[100]] (SUB @0 (GAS)) } # noqa: E501 - contract_0 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.SSTORE( - key=0x1, - value=Op.CALLCODE( - gas=0xEA60, - address=0xC94F5374FCE5EDBC8E2A8697C15331677E6EBF0B, - value=0x1, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.SSTORE(key=0x64, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.STOP, - balance=100, - nonce=0, - address=Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B), # noqa: E501 - ) - - tx = Transaction( - sender=sender, - to=contract_0, - data=Bytes(""), - gas_limit=600000, - ) - - post = { - contract_0: Account(storage={1: 1, 100: 31435}), - Address( - 0xC94F5374FCE5EDBC8E2A8697C15331677E6EBF0B - ): Account.NONEXISTENT, - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stNonZeroCallsTest/test_non_zero_value_callcode_to_empty_paris.py b/tests/ported_static/stNonZeroCallsTest/test_non_zero_value_callcode_to_empty_paris.py deleted file mode 100644 index 36f7f5f9c28..00000000000 --- a/tests/ported_static/stNonZeroCallsTest/test_non_zero_value_callcode_to_empty_paris.py +++ /dev/null @@ -1,83 +0,0 @@ -""" -Test_non_zero_value_callcode_to_empty_paris. - -Ported from: -state_tests/stNonZeroCallsTest/NonZeroValue_CALLCODE_ToEmpty_ParisFiller.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - [ - "state_tests/stNonZeroCallsTest/NonZeroValue_CALLCODE_ToEmpty_ParisFiller.json" # noqa: E501 - ], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_non_zero_value_callcode_to_empty_paris( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_non_zero_value_callcode_to_empty_paris.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - addr = pre.fund_eoa(amount=10) # noqa: F841 - # Source: lll - # { [0](GAS) [[1]] (CALLCODE 60000 <eoa:0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b> 1 0 0 0 0) [[100]] (SUB @0 (GAS)) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.SSTORE( - key=0x1, - value=Op.CALLCODE( - gas=0xEA60, - address=addr, - value=0x1, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.SSTORE(key=0x64, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.STOP, - balance=100, - nonce=0, - ) - - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=600000, - ) - - post = { - addr: Account(storage={}, code=b"", balance=10, nonce=0), - target: Account(storage={1: 1, 100: 31435}), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stNonZeroCallsTest/test_non_zero_value_callcode_to_one_storage_key_paris.py b/tests/ported_static/stNonZeroCallsTest/test_non_zero_value_callcode_to_one_storage_key_paris.py deleted file mode 100644 index 24406f1746d..00000000000 --- a/tests/ported_static/stNonZeroCallsTest/test_non_zero_value_callcode_to_one_storage_key_paris.py +++ /dev/null @@ -1,89 +0,0 @@ -""" -Test_non_zero_value_callcode_to_one_storage_key_paris. - -Ported from: -state_tests/stNonZeroCallsTest/NonZeroValue_CALLCODE_ToOneStorageKey_ParisFiller.json -""" - -import pytest -from execution_testing import ( - EOA, - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - [ - "state_tests/stNonZeroCallsTest/NonZeroValue_CALLCODE_ToOneStorageKey_ParisFiller.json" # noqa: E501 - ], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_non_zero_value_callcode_to_one_storage_key_paris( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_non_zero_value_callcode_to_one_storage_key_paris.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - addr = Address(0x4757608F18B70777AE788DD4056EEED52F7AA68F) - sender = EOA( - key=0x4F31B3206FBF0E0E598B9B1A7D8AC86302A0FF1D8930738F1BEBAE9B67173E52 - ) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - pre[sender] = Account(balance=0xE8D4A51000) - pre[addr] = Account(balance=10, storage={0: 1}) - # Source: lll - # { [0](GAS) [[1]] (CALLCODE 60000 <eoa:0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b> 1 0 0 0 0) [[100]] (SUB @0 (GAS)) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.SSTORE( - key=0x1, - value=Op.CALLCODE( - gas=0xEA60, - address=0x4757608F18B70777AE788DD4056EEED52F7AA68F, - value=0x1, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.SSTORE(key=0x64, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.STOP, - balance=1000, - nonce=0, - address=Address(0xB7BB61C75BE691459CEF9A8FD7EC074933FA1D1F), # noqa: E501 - ) - - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=600000, - ) - - post = { - addr: Account(storage={0: 1}, balance=10), - target: Account(storage={1: 1, 100: 31435}), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stNonZeroCallsTest/test_non_zero_value_delegatecall.py b/tests/ported_static/stNonZeroCallsTest/test_non_zero_value_delegatecall.py deleted file mode 100644 index ca0b9e66a79..00000000000 --- a/tests/ported_static/stNonZeroCallsTest/test_non_zero_value_delegatecall.py +++ /dev/null @@ -1,87 +0,0 @@ -""" -Test_non_zero_value_delegatecall. - -Ported from: -state_tests/stNonZeroCallsTest/NonZeroValue_DELEGATECALLFiller.json -""" - -import pytest -from execution_testing import ( - EOA, - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - ["state_tests/stNonZeroCallsTest/NonZeroValue_DELEGATECALLFiller.json"], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_non_zero_value_delegatecall( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_non_zero_value_delegatecall.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B) - sender = EOA( - key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8 - ) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - pre[sender] = Account(balance=0xE8D4A51000) - # Source: lll - # { [0](GAS) [[1]] (DELEGATECALL 60000 0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b 0 0 0 0) [[100]] (SUB @0 (GAS)) } # noqa: E501 - contract_0 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.SSTORE( - key=0x1, - value=Op.DELEGATECALL( - gas=0xEA60, - address=0xC94F5374FCE5EDBC8E2A8697C15331677E6EBF0B, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.SSTORE(key=0x64, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.STOP, - balance=1, - nonce=0, - address=Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B), # noqa: E501 - ) - - tx = Transaction( - sender=sender, - to=contract_0, - data=Bytes(""), - gas_limit=600000, - ) - - post = { - contract_0: Account(storage={1: 1, 100: 24732}), - Address( - 0xC94F5374FCE5EDBC8E2A8697C15331677E6EBF0B - ): Account.NONEXISTENT, - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stNonZeroCallsTest/test_non_zero_value_delegatecall_to_empty_paris.py b/tests/ported_static/stNonZeroCallsTest/test_non_zero_value_delegatecall_to_empty_paris.py deleted file mode 100644 index 1c2a832d499..00000000000 --- a/tests/ported_static/stNonZeroCallsTest/test_non_zero_value_delegatecall_to_empty_paris.py +++ /dev/null @@ -1,81 +0,0 @@ -""" -Test_non_zero_value_delegatecall_to_empty_paris. - -Ported from: -state_tests/stNonZeroCallsTest/NonZeroValue_DELEGATECALL_ToEmpty_ParisFiller.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - [ - "state_tests/stNonZeroCallsTest/NonZeroValue_DELEGATECALL_ToEmpty_ParisFiller.json" # noqa: E501 - ], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_non_zero_value_delegatecall_to_empty_paris( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_non_zero_value_delegatecall_to_empty_paris.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - addr = pre.fund_eoa(amount=10) # noqa: F841 - # Source: lll - # { [0](GAS) [[1]] (DELEGATECALL 60000 <eoa:0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b> 0 0 0 0) [[100]] (SUB @0 (GAS)) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.SSTORE( - key=0x1, - value=Op.DELEGATECALL( - gas=0xEA60, - address=addr, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.SSTORE(key=0x64, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.STOP, - nonce=0, - ) - - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=600000, - ) - - post = { - addr: Account(storage={}, code=b"", balance=10, nonce=0), - target: Account(storage={1: 1, 100: 24732}), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stNonZeroCallsTest/test_non_zero_value_delegatecall_to_non_non_zero_balance.py b/tests/ported_static/stNonZeroCallsTest/test_non_zero_value_delegatecall_to_non_non_zero_balance.py deleted file mode 100644 index f4716bb2707..00000000000 --- a/tests/ported_static/stNonZeroCallsTest/test_non_zero_value_delegatecall_to_non_non_zero_balance.py +++ /dev/null @@ -1,81 +0,0 @@ -""" -Test_non_zero_value_delegatecall_to_non_non_zero_balance. - -Ported from: -state_tests/stNonZeroCallsTest/NonZeroValue_DELEGATECALL_ToNonNonZeroBalanceFiller.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - [ - "state_tests/stNonZeroCallsTest/NonZeroValue_DELEGATECALL_ToNonNonZeroBalanceFiller.json" # noqa: E501 - ], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_non_zero_value_delegatecall_to_non_non_zero_balance( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_non_zero_value_delegatecall_to_non_non_zero_balance.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - addr = pre.fund_eoa(amount=100) # noqa: F841 - # Source: lll - # { [0](GAS) [[1]] (DELEGATECALL 60000 <eoa:0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b> 0 0 0 0) [[100]] (SUB @0 (GAS)) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.SSTORE( - key=0x1, - value=Op.DELEGATECALL( - gas=0xEA60, - address=addr, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.SSTORE(key=0x64, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.STOP, - nonce=0, - ) - - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=600000, - ) - - post = { - addr: Account(balance=100), - target: Account(storage={1: 1, 100: 24732}), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stNonZeroCallsTest/test_non_zero_value_delegatecall_to_one_storage_key_paris.py b/tests/ported_static/stNonZeroCallsTest/test_non_zero_value_delegatecall_to_one_storage_key_paris.py deleted file mode 100644 index 59f545e4ace..00000000000 --- a/tests/ported_static/stNonZeroCallsTest/test_non_zero_value_delegatecall_to_one_storage_key_paris.py +++ /dev/null @@ -1,87 +0,0 @@ -""" -Test_non_zero_value_delegatecall_to_one_storage_key_paris. - -Ported from: -state_tests/stNonZeroCallsTest/NonZeroValue_DELEGATECALL_ToOneStorageKey_ParisFiller.json -""" - -import pytest -from execution_testing import ( - EOA, - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - [ - "state_tests/stNonZeroCallsTest/NonZeroValue_DELEGATECALL_ToOneStorageKey_ParisFiller.json" # noqa: E501 - ], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_non_zero_value_delegatecall_to_one_storage_key_paris( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_non_zero_value_delegatecall_to_one_storage_key_paris.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - addr = Address(0x4757608F18B70777AE788DD4056EEED52F7AA68F) - sender = EOA( - key=0x4F31B3206FBF0E0E598B9B1A7D8AC86302A0FF1D8930738F1BEBAE9B67173E52 - ) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - pre[sender] = Account(balance=0xE8D4A51000) - pre[addr] = Account(balance=10, storage={0: 1}) - # Source: lll - # { [0](GAS) [[1]] (DELEGATECALL 60000 <eoa:0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b> 0 0 0 0) [[100]] (SUB @0 (GAS)) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.SSTORE( - key=0x1, - value=Op.DELEGATECALL( - gas=0xEA60, - address=0x4757608F18B70777AE788DD4056EEED52F7AA68F, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.SSTORE(key=0x64, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.STOP, - nonce=0, - address=Address(0x9C1470E9F035F5D8F34D7C0FF2650F9F89DE43FE), # noqa: E501 - ) - - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=600000, - ) - - post = { - addr: Account(storage={0: 1}, balance=10), - target: Account(storage={1: 1, 100: 24732}), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stSpecialTest/test_make_money.py b/tests/ported_static/stSpecialTest/test_make_money.py index 9f0678d1dc0..26fd61f9de2 100644 --- a/tests/ported_static/stSpecialTest/test_make_money.py +++ b/tests/ported_static/stSpecialTest/test_make_money.py @@ -1,17 +1,21 @@ """ -Test_make_money. +Verify value flows tx -> caller -> callee when the CALL asks for an absurdly +oversized gas amount (near 2^256), which the EIP-150 63/64 cap must clamp. Ported from: state_tests/stSpecialTest/makeMoneyFiller.json + +@manually-enhanced: Do not overwrite. Value flow tx->caller->callee expressed +as a relationship; dynamic addresses. The oversized CALL gas operand is the +original filler's point (clamping, not wrapping, of a near-2^256 ask) and +must stay explicit. """ import pytest from execution_testing import ( Account, - Address, Alloc, - Bytes, - Environment, + Fork, StateTestFiller, Transaction, ) @@ -20,70 +24,52 @@ REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +INITIAL_BALANCE = 0xDE0B6B3A7640000 +TX_VALUE = 10 +CALL_VALUE = 0x17 +# The ported filler asks for nearly 2^256 gas: a client computing e.g. +# `requested + stipend` in wrapping arithmetic would forward almost nothing +# and OOG the callee, so the 63/64 clamp itself is under test. +OVERSIZED_GAS_ASK = 2**256 - 20 + @pytest.mark.ported_from( ["state_tests/stSpecialTest/makeMoneyFiller.json"], ) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable +@pytest.mark.valid_from("TangerineWhistle") def test_make_money( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: - """Test_make_money.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0x3B9ACA00) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=1000000, - ) - - # Source: raw - # 0x600160015532600255 - addr = pre.deploy_contract( # noqa: F841 + """Value forwards tx -> caller -> callee; the callee records ORIGIN.""" + # Callee stores a sentinel and the transaction origin, proving its code + # ran (not merely that value was transferred). + callee = pre.deploy_contract( code=Op.SSTORE(key=0x1, value=0x1) + Op.SSTORE(key=0x2, value=Op.ORIGIN), - balance=0xDE0B6B3A7640000, - nonce=0, + balance=INITIAL_BALANCE, ) - # Source: lll - # { (MSTORE 0 0x601080600c6000396000f20060003554156009570060203560003555) (CALL 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffec <contract:0xaaaaaaaaace5edbc8e2a8697c15331677e6ebf0b> 23 0 0 0 0) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE( - offset=0x0, - value=0x601080600C6000396000F20060003554156009570060203560003555, - ) - + Op.CALL( - gas=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEC, # noqa: E501 - address=addr, - value=0x17, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ) + caller = pre.deploy_contract( + code=Op.CALL(gas=OVERSIZED_GAS_ASK, address=callee, value=CALL_VALUE) + Op.STOP, - balance=0xDE0B6B3A7640000, - nonce=0, + balance=INITIAL_BALANCE, ) + sender = pre.fund_eoa() tx = Transaction( sender=sender, - to=target, - data=Bytes(""), - gas_limit=228500, - value=10, + to=caller, + value=TX_VALUE, + protected=fork.supports_protected_txs(), ) post = { - target: Account(balance=0xDE0B6B3A763FFF3), - sender: Account(balance=0x3B8F6A16), - addr: Account(balance=0xDE0B6B3A7640017), + caller: Account(balance=INITIAL_BALANCE + TX_VALUE - CALL_VALUE), + callee: Account( + balance=INITIAL_BALANCE + CALL_VALUE, + storage={1: 1, 2: sender}, + ), } - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stStaticCall/test_static_call_value_inherit_from_call.py b/tests/ported_static/stStaticCall/test_static_call_value_inherit_from_call.py index 930f7670065..7686c35c607 100644 --- a/tests/ported_static/stStaticCall/test_static_call_value_inherit_from_call.py +++ b/tests/ported_static/stStaticCall/test_static_call_value_inherit_from_call.py @@ -1,17 +1,21 @@ """ -Test_static_call_value_inherit_from_call. +Verify a STATICCALL callee observes CALLVALUE 0, never inheriting the +enclosing frame's non-zero value (delivered here by the transaction). Ported from: state_tests/stStaticCall/static_call_value_inherit_from_callFiller.json + +@manually-enhanced: Do not overwrite. STATICCALL sees CALLVALUE 0 (never +inherited from the enclosing value-bearing frame — the ported filler's +delivery CALL is collapsed into the transaction's own value); dynamic +addresses, gas forwarded via the default Op.GAS. """ import pytest from execution_testing import ( Account, - Address, Alloc, - Bytes, - Environment, + Fork, StateTestFiller, Transaction, ) @@ -20,49 +24,33 @@ REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +CALL_VALUE = 0xA + @pytest.mark.ported_from( [ "state_tests/stStaticCall/static_call_value_inherit_from_callFiller.json" # noqa: E501 ], ) -@pytest.mark.valid_from("Cancun") -@pytest.mark.slow -@pytest.mark.pre_alloc_mutable +@pytest.mark.valid_from("Byzantium") def test_static_call_value_inherit_from_call( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: - """Test_static_call_value_inherit_from_call.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - # Source: lll - # { (MSTORE 0 (CALLVALUE)) (RETURN 0 32) } - addr_2 = pre.deploy_contract( # noqa: F841 + """A STATICCALL callee observes CALLVALUE 0, not the caller's value.""" + # Callee returns whatever CALLVALUE it sees; under STATICCALL that is 0. + callee = pre.deploy_contract( code=Op.MSTORE(offset=0x0, value=Op.CALLVALUE) - + Op.RETURN(offset=0x0, size=0x20) - + Op.STOP, - balance=1, - nonce=0, + + Op.RETURN(offset=0x0, size=0x20), ) - # Source: lll - # { [[0]] (STATICCALL 50000 <contract:0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b> 0 0 0 32) [[1]] (MLOAD 0) } # noqa: E501 - addr = pre.deploy_contract( # noqa: F841 + # The tx delivers CALL_VALUE to this contract, so its own CALLVALUE is + # non-zero; the STATICCALL must still hand the callee a CALLVALUE of 0. + caller = pre.deploy_contract( code=Op.SSTORE( key=0x0, value=Op.STATICCALL( - gas=0xC350, - address=addr_2, + address=callee, args_offset=0x0, args_size=0x0, ret_offset=0x0, @@ -72,33 +60,16 @@ def test_static_call_value_inherit_from_call( + Op.SSTORE(key=0x1, value=Op.MLOAD(offset=0x0)) + Op.STOP, storage={1: 1}, - balance=1, - nonce=0, - ) - # Source: lll - # { (CALL 100000 <contract:0x094f5374fce5edbc8e2a8697c15331677e6ebf0b> 10 0 0 0 0) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.CALL( - gas=0x186A0, - address=addr, - value=0xA, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ) - + Op.STOP, - nonce=0, ) tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=460000, - value=10, + sender=pre.fund_eoa(), + to=caller, + value=CALL_VALUE, + protected=fork.supports_protected_txs(), ) - post = {addr: Account(storage={0: 1, 1: 0})} + # slot 0: STATICCALL succeeded (1). slot 1: the returned CALLVALUE (0). + post = {caller: Account(storage={0: 1, 1: 0})} - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) From d3baec819ff3f83cefc29a69a2fa5b141ea36cb1 Mon Sep 17 00:00:00 2001 From: spencer <spencer.taylor-brown@ethereum.org> Date: Wed, 29 Jul 2026 18:56:58 +0200 Subject: [PATCH 172/233] fix(test-execute): prune fork-less items before evaluating filter_combinations (#3259) --- .../plugins/execute/execute.py | 5 ++ .../plugins/execute/tests/test_execute.py | 53 +++++++++++++++++++ .../pytest_commands/plugins/forks/forks.py | 11 +++- .../forks/tests/test_covariant_markers.py | 19 +++++++ 4 files changed, 87 insertions(+), 1 deletion(-) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/execute.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/execute.py index a10cb1ba5c1..679b05405d0 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/execute.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/execute.py @@ -504,12 +504,17 @@ def pytest_generate_tests(metafunc: pytest.Metafunc) -> None: ) +@pytest.hookimpl(tryfirst=True) def pytest_collection_modifyitems( items: List[pytest.Item], ) -> None: """ Remove transition tests and add the appropriate execute markers to the test. + + Runs tryfirst so that items collected without a fork parametrization + (tests not valid for the session's fork) are removed before other + plugins inspect item params, as in the filler plugin. """ items_for_removal = [] for i, item in enumerate(items): diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/tests/test_execute.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/tests/test_execute.py index 2f606ad21c8..2b6c183747d 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/tests/test_execute.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/tests/test_execute.py @@ -3,6 +3,8 @@ from types import SimpleNamespace from typing import Any +import pytest + from execution_testing.test_types.block_types import EnvironmentDefaults from ..execute import pytest_configure @@ -55,3 +57,54 @@ def test_pytest_configure_applies_explicit_transaction_gas_limit() -> None: assert config.engine_rpc_supported is False EnvironmentDefaults.gas_limit = original_gas_limit + + +EXECUTE_COLLECTION_PLUGINS = [ + "execution_testing.cli.pytest_commands.plugins.shared.execute_fill", + "execution_testing.cli.pytest_commands.plugins.shared.live_client_flags", + "execution_testing.cli.pytest_commands.plugins.execute.execute", + "execution_testing.cli.pytest_commands.plugins.forks.forks", +] + + +def test_forkless_items_pruned_before_filter_combinations( + pytester: pytest.Pytester, +) -> None: + """ + Collect a test that is not valid for the session's fork in execute mode. + + Such a test is collected without a fork parametrization and hence + without its covariant params; it must be pruned before the forks + plugin evaluates filter_combinations predicates, which would + otherwise fail with a TypeError and abort the whole session. + """ + pytester.makepyfile( + """ + import pytest + + @pytest.mark.parametrize("a", [1, 2]) + @pytest.mark.with_all_refund_types() + @pytest.mark.filter_combinations( + lambda refund_type, a, **_: True, + reason="requires the covariant refund_type param", + ) + @pytest.mark.valid_from("Amsterdam") + def test_case(state_test, refund_type, a): + pass + """ + ) + plugin_args = [ + arg for name in EXECUTE_COLLECTION_PLUGINS for arg in ("-p", name) + ] + result = pytester.runpytest( + *plugin_args, + "--fork=Osaka", + "--collect-only", + "-q", + ) + output = "\n".join(result.outlines + result.errlines) + assert "INTERNALERROR" not in output + assert result.ret in ( + pytest.ExitCode.OK, + pytest.ExitCode.NO_TESTS_COLLECTED, + ) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/forks/forks.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/forks/forks.py index d927c6b1637..df04e8d4a14 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/forks/forks.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/forks/forks.py @@ -1584,7 +1584,16 @@ def _combination_filter_reason( f"{predicate!r}", returncode=pytest.ExitCode.USAGE_ERROR, ) - if not predicate(**params): + try: + keep = predicate(**params) + except TypeError as e: + pytest.exit( + f"filter_combinations predicate for " + f"'{item.nodeid}' cannot be called with the " + f"item's params: {e}", + returncode=pytest.ExitCode.USAGE_ERROR, + ) + if not keep: return marker.kwargs.get( "reason", "rejected by filter_combinations" ) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/forks/tests/test_covariant_markers.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/forks/tests/test_covariant_markers.py index 3aa22dd1674..b58e9b5eb1e 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/forks/tests/test_covariant_markers.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/forks/tests/test_covariant_markers.py @@ -617,6 +617,25 @@ def test_case(state_test, a): "filter_combinations deselected all", id="filter_combinations_empty_set_error", ), + pytest.param( + """ + import pytest + + @pytest.mark.parametrize("a", [1, 2]) + @pytest.mark.filter_combinations( + lambda nonexistent_param, **_: True, + reason="predicate names a parameter that does not exist", + ) + @pytest.mark.valid_from("Cancun") + @pytest.mark.valid_until("Cancun") + @pytest.mark.state_test_only + def test_case(state_test, a): + pass + """, + {}, + "cannot be called with the item's params", + id="filter_combinations_bad_predicate_signature_error", + ), ], ) def test_filter_combinations( From 593078295684ff46f686607726d5a839d50b6205 Mon Sep 17 00:00:00 2001 From: spencer <spencer.taylor-brown@ethereum.org> Date: Wed, 29 Jul 2026 19:47:14 +0200 Subject: [PATCH 173/233] refactor(spec-specs,tests): rename EIP-8037 regular gas to execution gas (#3238) --- .claude/commands/implement-eip.md | 4 +- .claude/commands/write-test.md | 2 +- docs/writing_tests/fork_methods.md | 2 +- docs/writing_tests/opcode_metadata.md | 2 +- .../plugins/execute/pre_alloc.py | 65 ++-- .../src/execution_testing/forks/base_fork.py | 18 +- .../forks/forks/eips/amsterdam/eip_2780.py | 14 +- .../forks/forks/eips/amsterdam/eip_8037.py | 38 +-- .../forks/forks/eips/amsterdam/eip_8038.py | 13 +- .../src/execution_testing/forks/gas_costs.py | 2 +- .../src/execution_testing/specs/base.py | 2 +- .../src/execution_testing/specs/blockchain.py | 2 +- .../tools/tests/test_iterating_bytecode.py | 68 ++-- .../tools/tools_code/generators.py | 88 +++--- .../src/execution_testing/vm/bytecode.py | 20 +- src/ethereum/forks/amsterdam/fork.py | 18 +- src/ethereum/forks/amsterdam/fork_types.py | 2 +- src/ethereum/forks/amsterdam/transactions.py | 42 +-- src/ethereum/forks/amsterdam/vm/__init__.py | 3 +- .../forks/amsterdam/vm/eoa_delegation.py | 2 +- src/ethereum/forks/amsterdam/vm/gas.py | 82 ++--- .../amsterdam/vm/instructions/storage.py | 4 +- .../forks/amsterdam/vm/instructions/system.py | 22 +- .../forks/amsterdam/vm/interpreter.py | 2 +- .../helpers.py | 6 +- .../test_authorization_charges.py | 6 +- .../test_authorization_oog.py | 122 +++---- .../test_calldata_floor.py | 2 +- .../test_fork_transition.py | 10 +- .../test_intrinsic_gas_boundary.py | 2 +- .../test_top_frame_charges.py | 86 ++--- .../test_value_moving_transactions.py | 6 +- .../test_value_moving_with_tx_delegation.py | 16 +- .../test_gas_accounting.py | 50 +-- .../test_block_access_lists.py | 2 +- .../test_block_access_lists_cross_index.py | 2 +- .../test_block_access_lists_eip7702.py | 8 +- .../test_block_access_lists_opcodes.py | 4 +- .../test_max_code_size.py | 2 +- .../test_additional_coverage.py | 14 +- .../test_floor_boundary_exact_balance.py | 4 +- .../test_floor_boundary_exact_balance.py | 4 +- .../spec.py | 6 +- .../test_block_2d_gas_accounting.py | 158 +++++----- .../test_state_gas_call.py | 52 +-- .../test_state_gas_calldata_floor.py | 58 ++-- .../test_state_gas_create.py | 268 ++++++++-------- .../test_state_gas_fork_transition.py | 4 +- .../test_state_gas_multi_block.py | 2 +- .../test_state_gas_ordering.py | 28 +- .../test_state_gas_pricing.py | 92 +++--- .../test_state_gas_reservoir.py | 163 +++++----- .../test_state_gas_selfdestruct.py | 58 ++-- .../test_state_gas_set_code.py | 298 +++++++++--------- .../test_state_gas_sstore.py | 96 +++--- .../test_access_list_gas.py | 6 +- .../test_call_gas.py | 54 ++-- .../test_create_gas.py | 70 ++-- .../test_fork_transition.py | 24 +- .../test_selfdestruct_gas.py | 76 ++--- .../test_set_code_auth_gas.py | 58 ++-- .../test_set_code_auth_refunds.py | 18 +- .../test_sstore_gas.py | 32 +- .../test_sstore_refunds.py | 20 +- .../test_transient_storage_regression.py | 2 +- .../compute/instruction/test_system.py | 4 +- tests/benchmark/helper/contract_factory.py | 22 +- .../stateful/bloatnet/test_sstore.py | 3 +- .../test_raw_create_gas.py | 4 +- 69 files changed, 1284 insertions(+), 1255 deletions(-) diff --git a/.claude/commands/implement-eip.md b/.claude/commands/implement-eip.md index 4465317f1e3..f42d9361f25 100644 --- a/.claude/commands/implement-eip.md +++ b/.claude/commands/implement-eip.md @@ -34,11 +34,11 @@ Each fork lives at `src/ethereum/forks/<fork_name>/`. Explore the latest fork di ## Gas Handling -Recent forks meter two gas dimensions: regular gas and state gas (for durable state growth). Key rules: +Recent forks meter two gas dimensions: execution gas and state gas (for durable state growth). Key rules: 1. Gas constants and calculations go in `vm/gas.py`; a frame's mutable gas state lives on `Evm.gas_meter`. 2. Extend the named helper vocabulary (`charge_*`, `credit_*`, `restore_*`, `withhold_*`, ...) instead of doing gas arithmetic by hand at call sites; encode each helper's invariant as an assert. -3. State gas is charged by the frame whose opcode causes the creation, before the child's regular-gas share is withheld; the whole reservoir passes to the child. +3. State gas is charged by the frame whose opcode causes the creation, before the child's execution-gas share is withheld; the whole reservoir passes to the child. 4. A failing frame settles its own meter before returning, so parents incorporate children unconditionally. 5. Opcodes that touch state use labeled stages, with all charging before the operation: `GAS (STATE-INDEPENDENT)` → `STATE ACCESS (STATE-DEPENDENT GAS)` → `STATE GAS` → `CHILD GRANT` → `OPERATION`. Simple opcodes keep the bare `GAS` marker. `generic_call`/`generic_create` contain no pricing; they run the child lifecycle: `PREFLIGHT` → `DESTINATION ACCESS` → `CHILD GRANT` → `DISPATCH` → `OUTCOME`. 6. Avoid "frame" in gas identifiers (a future EIP claims the term); when a name diverges from the spec's variable name, cross-reference the spec name in the docstring. diff --git a/.claude/commands/write-test.md b/.claude/commands/write-test.md index bf6e8eee42a..1cda7e433ec 100644 --- a/.claude/commands/write-test.md +++ b/.claude/commands/write-test.md @@ -50,7 +50,7 @@ Conventions and patterns for writing consensus tests. Run this skill before writ Never hand-reconstruct a gas amount by summing `fork.gas_costs()` constants (`NEW_ACCOUNT`, `CALL_VALUE`, `COLD_STORAGE_WRITE`, `VERY_LOW`, ...). Re-deriving the schedule duplicates the framework's own calculation and silently breaks when a future fork reprices. Instead: -- **Read the cost off the bytecode under test.** Set the relevant opcode metadata (`account_new`, `value_transfer`, `address_warm`, `key_warm`/`original_value`/`current_value`/`new_value`, `init_code_size`, `code_deposit_size`, `new_memory_size`, ...) and use `bytecode.gas_cost(fork)` (regular + state), `.regular_cost(fork)`, `.state_cost(fork)`, or `.refund(fork)`. Link the exact opcode to the behavior — e.g. `Op.SELFDESTRUCT(account_new=True).state_cost(fork)`. +- **Read the cost off the bytecode under test.** Set the relevant opcode metadata (`account_new`, `value_transfer`, `address_warm`, `key_warm`/`original_value`/`current_value`/`new_value`, `init_code_size`, `code_deposit_size`, `new_memory_size`, ...) and use `bytecode.gas_cost(fork)` (execution + state), `.execution_cost(fork)`, `.state_cost(fork)`, or `.refund(fork)`. Link the exact opcode to the behavior — e.g. `Op.SELFDESTRUCT(account_new=True).state_cost(fork)`. - **Transaction-level costs:** `fork.transaction_intrinsic_cost_calculator()`; `fork.transaction_top_frame_state_gas(contract_creation=True)` for the created account's `NEW_ACCOUNT` (under EIP-2780 it is NOT part of the intrinsic — never subtract it from the intrinsic); `fork.transaction_data_floor_cost_calculator()`; `fork.call_value_stipend()`. - **A single bare opcode/schedule cost** (e.g. an account-access constant) comes from a metadata-only opcode: `Op.BALANCE.with_metadata(address_warm=False).gas_cost(fork)`. - **Fork-transition / cross-fork comparisons:** evaluate the same bytecode or intrinsic at each fork (`before = fork.fork_at(timestamp=...)`, `after = ...`) and compare `before` vs `after` costs — do not compare raw schedule constants. diff --git a/docs/writing_tests/fork_methods.md b/docs/writing_tests/fork_methods.md index 6f29d6f3a0c..6edd8abe4a8 100644 --- a/docs/writing_tests/fork_methods.md +++ b/docs/writing_tests/fork_methods.md @@ -117,7 +117,7 @@ fork.transaction_intrinsic_cost_calculator() # Returns a callable ``` !!! warning "Do not reconstruct expected gas from `gas_costs()` constants" - `fork.gas_costs()` exposes the raw schedule for framework internals. When a test needs an *expected* gas amount, derive it from a cost construct that tracks the live schedule (`bytecode.gas_cost(fork)` / `.regular_cost(fork)` / `.state_cost(fork)` / `.refund(fork)`, opcode metadata, the intrinsic/top-frame/data-floor calculators, `fork.call_value_stipend()`) rather than hand-summing constants — hand-built expectations silently break when a fork reprices. See [Opcode Metadata and Gas Calculations](opcode_metadata.md#do-not-hand-reconstruct-gas-from-constants). + `fork.gas_costs()` exposes the raw schedule for framework internals. When a test needs an *expected* gas amount, derive it from a cost construct that tracks the live schedule (`bytecode.gas_cost(fork)` / `.execution_cost(fork)` / `.state_cost(fork)` / `.refund(fork)`, opcode metadata, the intrinsic/top-frame/data-floor calculators, `fork.call_value_stipend()`) rather than hand-summing constants — hand-built expectations silently break when a fork reprices. See [Opcode Metadata and Gas Calculations](opcode_metadata.md#do-not-hand-reconstruct-gas-from-constants). ### Transaction Types diff --git a/docs/writing_tests/opcode_metadata.md b/docs/writing_tests/opcode_metadata.md index 5149fb9ea0b..49753e7eb23 100644 --- a/docs/writing_tests/opcode_metadata.md +++ b/docs/writing_tests/opcode_metadata.md @@ -13,7 +13,7 @@ The execution testing package provides capabilities to calculate gas costs and r Never build an expected gas amount by summing `fork.gas_costs()` constants (`NEW_ACCOUNT`, `CALL_VALUE`, `COLD_STORAGE_WRITE`, `VERY_LOW`, ...). Re-deriving the schedule by hand duplicates the framework's own calculation and silently breaks when a future fork reprices or restructures a cost. Always derive the expectation from a framework construct that tracks the live schedule: -- **The bytecode/opcode under test:** set the relevant metadata (see below) and read `bytecode.gas_cost(fork)` (regular + state), `.regular_cost(fork)`, `.state_cost(fork)`, or `.refund(fork)`. Link the exact opcode to the behavior, e.g. `Op.SELFDESTRUCT(account_new=True).state_cost(fork)`. +- **The bytecode/opcode under test:** set the relevant metadata (see below) and read `bytecode.gas_cost(fork)` (execution + state), `.execution_cost(fork)`, `.state_cost(fork)`, or `.refund(fork)`. Link the exact opcode to the behavior, e.g. `Op.SELFDESTRUCT(account_new=True).state_cost(fork)`. - **A single bare opcode/schedule cost** comes from a metadata-only opcode: `Op.BALANCE.with_metadata(address_warm=False).gas_cost(fork)` yields the cold account-access cost with no operand pushes. - **Transaction-level costs:** `fork.transaction_intrinsic_cost_calculator()`, `fork.transaction_top_frame_state_gas(contract_creation=True)` (the created account's new-account state gas — on recent forks it is charged at the top frame, *not* in the intrinsic, so never subtract it from the intrinsic), `fork.transaction_data_floor_cost_calculator()`, and `fork.call_value_stipend()`. - **Cross-fork / fork-transition comparisons:** evaluate the *same* bytecode or intrinsic at each fork (`before = fork.fork_at(timestamp=...)`, `after = ...`) and compare the resulting costs — do not compare raw schedule constants. diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py index 5f248d6811f..fccb7ca45cd 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py @@ -232,12 +232,12 @@ def _compute_deploy_gas_limit( storage_slots: int = 0, ) -> Tuple[int, int]: """ - Compute the deploy transaction gas limit, returning both the regular - gas portion bound by the EIP 7825 cap and the total regular plus + Compute the deploy transaction gas limit, returning both the execution + gas portion bound by the EIP 7825 cap and the total execution plus state gas used as the transaction gas field. Under EIP 8037 the cap - binds only the regular portion while state gas comes from the block + binds only the execution portion while state gas comes from the block reservoir and may push the total above the cap, and before Amsterdam - the state gas is zero so the total equals the regular gas. The regular + the state gas is zero so the total equals the execution gas. The execution portion is doubled as a safety buffer since gas estimation is approximate while the state portion is exact. """ @@ -247,44 +247,45 @@ def _compute_deploy_gas_limit( sstore = Op.SSTORE(new_value=1) sstore_state_gas = sstore.state_cost(fork) - sstore_regular_gas = sstore.gas_cost(fork) - sstore_state_gas + sstore_execution_gas = sstore.gas_cost(fork) - sstore_state_gas - # The intrinsic cost is now regular-only: the created account's + # The intrinsic cost is now execution-only: the created account's # NEW_ACCOUNT state gas is charged at the top frame, not folded in. - intrinsic_regular_gas = intrinsic_gas_calculator( + intrinsic_execution_gas = intrinsic_gas_calculator( calldata=initcode, contract_creation=True ) - # Regular portion, bound by the gas cap. - regular_gas = intrinsic_regular_gas + # Execution portion, bound by the gas cap. + execution_gas = intrinsic_execution_gas if fork.state_gas_reservoir_enabled(): - regular_gas += gas_costs.OPCODE_KECCAK256_PER_WORD * ( + execution_gas += gas_costs.OPCODE_KECCAK256_PER_WORD * ( (deploy_code_size + 31) // 32 ) else: - regular_gas += deploy_code_size * gas_costs.CODE_DEPOSIT_PER_BYTE - regular_gas += memory_expansion_gas_calculator( + execution_gas += deploy_code_size * gas_costs.CODE_DEPOSIT_PER_BYTE + execution_gas += memory_expansion_gas_calculator( new_bytes=len(bytes(initcode)) ) - regular_gas += storage_slots * sstore_regular_gas + execution_gas += storage_slots * sstore_execution_gas # Double as a safety buffer since gas estimation is approximate. The buffer # must not, by itself, push a contract that genuinely deploys within the - # EIP-7825 regular-gas cap over it: when the unbuffered estimate still fits + # EIP-7825 execution-gas cap over it: when the unbuffered estimate + # still fits # the cap, clamp the limit to the cap instead. The deploy then runs with a - # cap-sized regular limit and consumes only its (smaller) actual gas. + # cap-sized execution limit and consumes only its (smaller) actual gas. # Only a contract whose unbuffered estimate exceeds the cap is truly # undeployable (the caller raises on that). - buffered_regular_gas = regular_gas * 2 + buffered_execution_gas = execution_gas * 2 tx_gas_limit_cap = fork.transaction_gas_limit_cap() if ( tx_gas_limit_cap is not None - and buffered_regular_gas > tx_gas_limit_cap - and regular_gas <= tx_gas_limit_cap + and buffered_execution_gas > tx_gas_limit_cap + and execution_gas <= tx_gas_limit_cap ): - regular_gas = tx_gas_limit_cap + execution_gas = tx_gas_limit_cap else: - regular_gas = buffered_regular_gas + execution_gas = buffered_execution_gas # State portion, from the block reservoir. The created account's # NEW_ACCOUNT is charged at the top frame for create transactions @@ -293,8 +294,8 @@ def _compute_deploy_gas_limit( state_gas += fork.transaction_top_frame_state_gas(contract_creation=True) state_gas += storage_slots * sstore_state_gas - deploy_gas_limit = regular_gas + state_gas - return regular_gas, deploy_gas_limit + deploy_gas_limit = execution_gas + state_gas + return execution_gas, deploy_gas_limit class Alloc(SharedAlloc): @@ -426,18 +427,18 @@ def _deterministic_deploy_contract( raise ValueError( f"initcode too large {len(initcode)} > {max_initcode_size}" ) - regular_gas, deploy_gas_limit = _compute_deploy_gas_limit( + execution_gas, deploy_gas_limit = _compute_deploy_gas_limit( fork, deploy_code_size=len(deploy_code), initcode=initcode, ) # Per EIP-8037, the per-tx 2^24 cap (EIP-7825) binds only the - # regular-gas portion; state gas is drawn from the block reservoir. + # execution-gas portion; state gas is drawn from the block reservoir. tx_gas_limit_cap = fork.transaction_gas_limit_cap() - if tx_gas_limit_cap and regular_gas > tx_gas_limit_cap: + if tx_gas_limit_cap and execution_gas > tx_gas_limit_cap: raise ValueError( - f"deterministic deploy regular gas exceeds the transaction " - f"gas limit cap: {regular_gas} > {tx_gas_limit_cap}" + f"deterministic deploy execution gas exceeds the transaction " + f"gas limit cap: {execution_gas} > {tx_gas_limit_cap}" ) # Defer the on-chain check; the deploy tx (if needed) and the @@ -541,19 +542,19 @@ def _deploy_contract( f"initcode too large {initcode_len} > {max_initcode_size}" ) - regular_gas, deploy_gas_limit = _compute_deploy_gas_limit( + execution_gas, deploy_gas_limit = _compute_deploy_gas_limit( fork, deploy_code_size=len(code), initcode=prepared_initcode, storage_slots=len(storage.root), ) # Per EIP-8037, the per-tx 2^24 cap (EIP-7825) binds only the - # regular-gas portion; state gas is drawn from the block reservoir. + # execution-gas portion; state gas is drawn from the block reservoir. tx_gas_limit_cap = fork.transaction_gas_limit_cap() - if tx_gas_limit_cap and regular_gas > tx_gas_limit_cap: + if tx_gas_limit_cap and execution_gas > tx_gas_limit_cap: raise ValueError( - f"deploy regular gas exceeds the transaction gas limit cap: " - f"{regular_gas} > {tx_gas_limit_cap}" + f"deploy execution gas exceeds the transaction gas limit cap: " + f"{execution_gas} > {tx_gas_limit_cap}" ) deploy_tx = self._add_pending_tx( diff --git a/packages/testing/src/execution_testing/forks/base_fork.py b/packages/testing/src/execution_testing/forks/base_fork.py index f24bc228d77..08305f5eefe 100644 --- a/packages/testing/src/execution_testing/forks/base_fork.py +++ b/packages/testing/src/execution_testing/forks/base_fork.py @@ -177,11 +177,11 @@ class AuthorizationGasInfo(Protocol): class TopFrameGasCalculator(Protocol): """ - A protocol to calculate the additional regular gas charged at the + A protocol to calculate the additional execution gas charged at the top-level transaction frame, after intrinsic gas is deducted but before EVM execution begins. - Returns only the regular-gas portion of the post-intrinsic + Returns only the execution-gas portion of the post-intrinsic state-aware preparation (e.g. the delegated-recipient access charge). The state-gas portion is exposed separately by ``BaseFork.transaction_top_frame_state_gas`` so tests can model the @@ -201,7 +201,7 @@ def __call__( authorizations: Sequence[AuthorizationGasInfo] = (), ) -> int: """ - Return the regular gas consumed by top-frame preparation for a + Return the execution gas consumed by top-frame preparation for a transaction at this fork. Args: @@ -217,9 +217,9 @@ def __call__( target is already warm, charging warm rather than cold access. authorizations: The transaction's EIP-7702 authorizations; - each contributes its top-frame regular gas. + each contributes its top-frame execution gas. - Returns: Regular gas added by top-frame preparation. + Returns: Execution gas added by top-frame preparation. """ pass @@ -782,7 +782,7 @@ def transaction_top_frame_gas_calculator( cls, ) -> TopFrameGasCalculator: """ - Return a callable that calculates the additional regular gas + Return a callable that calculates the additional execution gas charged at the top-level transaction frame, after intrinsic gas is deducted but before EVM execution begins. @@ -818,7 +818,7 @@ def transaction_top_frame_state_gas( frame, after intrinsic gas is deducted but before EVM execution begins. Companion to ``transaction_top_frame_gas_calculator``; tests targeting the spillover boundary feed this through - ``oog_budget_lift`` to get the equivalent regular-gas budget. + ``oog_budget_lift`` to get the equivalent execution-gas budget. Defaults to 0 for forks that do not perform such post-intrinsic preparation. @@ -1012,9 +1012,9 @@ def oog_budget_lift( deploy_code_size: int = 0, ) -> int: """ - Return the extra regular gas an out of gas budget needs to + Return the extra execution gas an out of gas budget needs to stop at the same point on this fork: the state gas EIP-8037 - spills into regular gas for the given SSTOREs, CREATEs, and + spills into execution gas for the given SSTOREs, CREATEs, and deployed bytes. Zero before EIP-8037, so no fork guard needed. """ return ( diff --git a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_2780.py b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_2780.py index c001d74e6e4..13342b15abd 100644 --- a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_2780.py +++ b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_2780.py @@ -48,7 +48,7 @@ def transaction_data_floor_cost_calculator( cls, ) -> TransactionDataFloorCostCalculator: """ - Anchor the calldata floor on the decomposed regular-gas intrinsic + Anchor the calldata floor on the decomposed execution-gas intrinsic base (EIP-2780). The inherited floor base is ``TX_BASE`` alone; add the recipient @@ -71,7 +71,7 @@ def fn( floor = super_fn(data=data, access_list=access_list) is_self_transfer = recipient_type == RecipientType.SELF if contract_creation: - # CREATE_ACCESS regular gas; TX_CREATE folds in the + # CREATE_ACCESS execution gas; TX_CREATE folds in the # NEW_ACCOUNT state gas, which the floor excludes. floor += gas_costs.TX_CREATE - gas_costs.NEW_ACCOUNT elif not is_self_transfer: @@ -170,7 +170,7 @@ def transaction_top_frame_gas_calculator( cls, ) -> TopFrameGasCalculator: """ - Return the additional regular gas charged at the top-level + Return the additional execution gas charged at the top-level transaction frame, after intrinsic gas is deducted but before the EVM dispatches. @@ -197,17 +197,17 @@ def fn( if contract_creation: return 0 - regular = 0 + execution = 0 if recipient_type == RecipientType.DELEGATION_7702: - regular += ( + execution += ( gas_costs.WARM_ACCESS if delegation_warm else gas_costs.COLD_ACCOUNT_ACCESS ) for auth in authorizations: if auth.first_write: - regular += gas_costs.ACCOUNT_WRITE - return regular + execution += gas_costs.ACCOUNT_WRITE + return execution return fn diff --git a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8037.py b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8037.py index f3428f7ec10..030b8dd4db8 100644 --- a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8037.py +++ b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8037.py @@ -118,11 +118,11 @@ def fn(opcode: OpcodeBase) -> int: gas_cost_or_calculator = opcode_gas_map[opcode] if callable(gas_cost_or_calculator): - regular_gas = gas_cost_or_calculator(opcode) + execution_gas = gas_cost_or_calculator(opcode) else: - regular_gas = gas_cost_or_calculator + execution_gas = gas_cost_or_calculator - return regular_gas + opcode_state_calculator(opcode) + return execution_gas + opcode_state_calculator(opcode) return fn @@ -189,11 +189,11 @@ def fn(opcode: OpcodeBase) -> int: refund_or_calculator = opcode_refund_map[opcode] if callable(refund_or_calculator): - regular_refund = refund_or_calculator(opcode) + execution_refund = refund_or_calculator(opcode) else: - regular_refund = refund_or_calculator + execution_refund = refund_or_calculator - return regular_refund + state_refund + return execution_refund + state_refund return fn @@ -324,7 +324,7 @@ def _calculate_return_gas( cls, opcode: OpcodeBase, gas_costs: GasCosts ) -> int: """ - Calculate the regular RETURN gas cost: the code hash gas + Calculate the execution RETURN gas cost: the code hash gas (keccak256 of the deployed bytecode). The per byte code deposit cost moves to state gas, returned by `_calculate_return_state_gas`. """ @@ -361,8 +361,8 @@ def _calculate_create_state_gas( Calculate the CREATE and CREATE2 state gas cost, which is `NEW_ACCOUNT` (if the account did not exist before). Before EIP-8037 this was folded into `OPCODE_CREATE_BASE`. Under - EIP-8037 it is exposed here so that `OPCODE_CREATE_BASE` stays regular - only and matches the spec EVM constant. + EIP-8037 it is exposed here so that `OPCODE_CREATE_BASE` stays + execution-only and matches the spec EVM constant. """ if opcode.metadata["account_new"]: return gas_costs.NEW_ACCOUNT @@ -375,9 +375,9 @@ def _calculate_selfdestruct_state_gas( """ Calculate the SELFDESTRUCT state gas cost: `NEW_ACCOUNT` when a positive balance funds a new account. Before EIP-8037 this was - folded into the regular SELFDESTRUCT cost; under EIP-8037 it is + folded into the execution SELFDESTRUCT cost; under EIP-8037 it is exposed here as state gas (mirroring `_calculate_create_state_gas`) - so the regular cost matches the spec EVM + so the execution cost matches the spec EVM (`OPCODE_SELFDESTRUCT_BASE` + account access + the EIP-8038 `ACCOUNT_WRITE` surcharge). """ @@ -390,14 +390,14 @@ def _calculate_selfdestruct_gas( cls, opcode: OpcodeBase, gas_costs: GasCosts ) -> int: """ - Calculate the regular SELFDESTRUCT gas cost. The Frontier base - calculation folds `NEW_ACCOUNT` into the regular cost when a + Calculate the execution SELFDESTRUCT gas cost. The Frontier base + calculation folds `NEW_ACCOUNT` into the execution cost when a positive balance funds a new account; EIP-8038 (the mixin between the base and EIP-8037 in the MRO) adds only the `ACCOUNT_WRITE` surcharge. EIP-8037 moves that funding cost to the state-gas dimension (see `_calculate_selfdestruct_state_gas`), so this - subtracts the `NEW_ACCOUNT` term back out of the inherited regular - cost; the EIP-8038 `ACCOUNT_WRITE` surcharge stays in regular gas. + subtracts the `NEW_ACCOUNT` term back out of the inherited execution + cost; the EIP-8038 `ACCOUNT_WRITE` surcharge stays in execution gas. """ gas_cost = super()._calculate_selfdestruct_gas(opcode, gas_costs) if opcode.metadata["account_new"]: @@ -411,7 +411,7 @@ def _calculate_call_state_gas( """ Calculate the CALL state gas cost: `NEW_ACCOUNT` when a value transfer funds a new account. Before EIP-8037 this was folded - into the regular CALL cost (EIP-161); under EIP-8037 it is + into the execution CALL cost (EIP-161); under EIP-8037 it is exposed here as state gas, mirroring `_calculate_selfdestruct_state_gas`. """ @@ -426,12 +426,12 @@ def _calculate_call_gas( cls, opcode: OpcodeBase, gas_costs: GasCosts ) -> int: """ - Calculate the regular CALL gas cost. The EIP-161 base - calculation folds `NEW_ACCOUNT` into the regular cost when a + Calculate the execution CALL gas cost. The EIP-161 base + calculation folds `NEW_ACCOUNT` into the execution cost when a value transfer funds a new account; EIP-8037 moves that charge to the state-gas dimension (see `_calculate_call_state_gas`), so this subtracts the `NEW_ACCOUNT` term back out of the - inherited regular cost. + inherited execution cost. """ gas_cost = super()._calculate_call_gas(opcode, gas_costs) metadata = opcode.metadata diff --git a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8038.py b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8038.py index 0e120edbc91..26beca8e120 100644 --- a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8038.py +++ b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8038.py @@ -53,7 +53,7 @@ def gas_costs(cls) -> GasCosts: account_write = 8_000 create_access = 11_000 # ecRecover stays PRECOMPILE_ECRECOVER (3000) until EIP-7904 lands. - regular_per_auth_base_cost = ( + execution_per_auth_base_cost = ( 1_616 + 3_000 + cold_account_access + 2 * warm_access ) @@ -73,8 +73,9 @@ def gas_costs(cls) -> GasCosts: STORAGE_SET=storage_write, OPCODE_CREATE_BASE=create_access, TX_CREATE=create_access, - AUTH_PER_EMPTY_ACCOUNT=account_write + regular_per_auth_base_cost, - REGULAR_PER_AUTH_BASE_COST=regular_per_auth_base_cost, + AUTH_PER_EMPTY_ACCOUNT=account_write + + execution_per_auth_base_cost, + REGULAR_PER_AUTH_BASE_COST=execution_per_auth_base_cost, ) @classmethod @@ -109,7 +110,7 @@ def _calculate_selfdestruct_gas( cls, opcode: OpcodeBase, gas_costs: GasCosts ) -> int: """ - Calculate the regular SELFDESTRUCT gas cost. EIP-8038 adds + Calculate the execution SELFDESTRUCT gas cost. EIP-8038 adds `ACCOUNT_WRITE` when a positive balance is sent to an empty account, on top of the inherited cost (where `NEW_ACCOUNT` holds the EIP-8037 state-gas portion). @@ -126,7 +127,7 @@ def _calculate_sstore_gas( cls, opcode: OpcodeBase, gas_costs: GasCosts ) -> int: """ - Calculate the regular SSTORE gas cost. The state portion is + Calculate the execution SSTORE gas cost. The state portion is returned separately by `_calculate_sstore_state_gas`. Under EIP-8038 the access cost (`COLD_STORAGE_ACCESS` when cold, else `WARM_SLOAD`) is always charged, and a first-time change to the @@ -159,7 +160,7 @@ def _calculate_sstore_refund( cls, opcode: OpcodeBase, gas_costs: GasCosts ) -> int: """ - Calculate the regular SSTORE gas refund. The state portion is + Calculate the execution SSTORE gas refund. The state portion is returned separately by `_calculate_sstore_state_refund`. """ metadata = opcode.metadata diff --git a/packages/testing/src/execution_testing/forks/gas_costs.py b/packages/testing/src/execution_testing/forks/gas_costs.py index 81e660fdaa5..261540cd590 100644 --- a/packages/testing/src/execution_testing/forks/gas_costs.py +++ b/packages/testing/src/execution_testing/forks/gas_costs.py @@ -49,7 +49,7 @@ class GasCosts: # State gas for writing a net-new EIP-7702 delegation indicator; # 0 before the state-creation repricing introduces it. AUTH_BASE: int = 0 - # State-independent regular gas charged per EIP-7702 authorization + # State-independent execution gas charged per EIP-7702 authorization # tuple; 0 before the state-access repricing introduces it. REGULAR_PER_AUTH_BASE_COST: int = 0 diff --git a/packages/testing/src/execution_testing/specs/base.py b/packages/testing/src/execution_testing/specs/base.py index 27278494403..bf0585396f8 100644 --- a/packages/testing/src/execution_testing/specs/base.py +++ b/packages/testing/src/execution_testing/specs/base.py @@ -303,7 +303,7 @@ def validate_benchmark_gas( ) # No single gas dimension may exceed the block gas limit. The # block-header gas is the max across dimensions; the combined - # regular+state gas may exceed the target under EIP-8037, so the + # execution+state gas may exceed the target under EIP-8037, so the # ceiling is checked against the header value when available. block_gas_used = ( benchmark_block_gas_used diff --git a/packages/testing/src/execution_testing/specs/blockchain.py b/packages/testing/src/execution_testing/specs/blockchain.py index 72145a35e4f..ec3bd0d88e2 100644 --- a/packages/testing/src/execution_testing/specs/blockchain.py +++ b/packages/testing/src/execution_testing/specs/blockchain.py @@ -481,7 +481,7 @@ def block_gas_used(self) -> int: Return the block-header gas used. Under EIP-8037 this is the maximum across the independent gas - dimensions (regular vs state), i.e. the value that counts against the + dimensions (execution vs state), i.e. the value that counts against the block gas limit, as opposed to ``cumulative_gas_used`` which is their combined sum. """ diff --git a/packages/testing/src/execution_testing/tools/tests/test_iterating_bytecode.py b/packages/testing/src/execution_testing/tools/tests/test_iterating_bytecode.py index a0d3ac643cd..22e7144db5d 100644 --- a/packages/testing/src/execution_testing/tools/tests/test_iterating_bytecode.py +++ b/packages/testing/src/execution_testing/tools/tests/test_iterating_bytecode.py @@ -99,7 +99,7 @@ def test_iterating_bytecode_gas_cost( iterating_bytecode: IteratingBytecode, iterations: int, expected_cost: int ) -> None: """Test the gas cost calculating function of an iterating bytecode.""" - calculated_cost = iterating_bytecode.regular_gas_cost_by_iteration_count( + calculated_cost = iterating_bytecode.execution_gas_cost_by_iteration_count( fork=Osaka, iteration_count=iterations ) assert calculated_cost == expected_cost, ( @@ -142,15 +142,15 @@ def test_iterating_subcall_reserve_includes_state_gas() -> None: """ The 63/64 reserve covers the subcall's state gas too: once the state reservoir is exhausted, the child pays its state charges (e.g. the - EIP-8037 per-byte code deposit) from forwarded regular gas. + EIP-8037 per-byte code deposit) from forwarded execution gas. """ - # Initcode depositing 2 bytes: tiny regular cost, 2 * 1530 state gas. + # Initcode depositing 2 bytes: tiny execution cost, 2 * 1530 state gas. initcode = Op.RETURN(0, 2, code_deposit_size=2) bytecode = IteratingBytecode( iterating=Op.CREATE2(offset=0, size=2, salt=0), iterating_subcall=initcode, ) - combined = initcode.regular_cost(fork=Amsterdam) + initcode.state_cost( + combined = initcode.execution_cost(fork=Amsterdam) + initcode.state_cost( fork=Amsterdam ) assert initcode.state_cost(fork=Amsterdam) == 2 * 1530 @@ -172,7 +172,7 @@ def test_with_fixed_iteration_count() -> None: assert fixed.iteration_count == 10 assert fixed.gas_cost( Osaka - ) == iterating_bytecode.regular_gas_cost_by_iteration_count( + ) == iterating_bytecode.execution_gas_cost_by_iteration_count( fork=Osaka, iteration_count=10 ) @@ -184,13 +184,13 @@ def test_tx_gas_cost_by_iteration_count() -> None: ) intrinsic_gas_cost_calc = Osaka.transaction_intrinsic_cost_calculator() - tx_gas = bytecode.tx_regular_gas_cost_by_iteration_count( + tx_gas = bytecode.tx_execution_gas_cost_by_iteration_count( fork=Osaka, iteration_count=5, ) expected = ( - bytecode.regular_gas_cost_by_iteration_count( + bytecode.execution_gas_cost_by_iteration_count( fork=Osaka, iteration_count=5 ) + intrinsic_gas_cost_calc() @@ -198,12 +198,12 @@ def test_tx_gas_cost_by_iteration_count() -> None: assert tx_gas == expected # With calldata - tx_gas = bytecode.tx_regular_gas_cost_by_iteration_count( + tx_gas = bytecode.tx_execution_gas_cost_by_iteration_count( fork=Osaka, iteration_count=5, calldata=b"hello", ) - expected = bytecode.regular_gas_cost_by_iteration_count( + expected = bytecode.execution_gas_cost_by_iteration_count( fork=Osaka, iteration_count=5 ) + intrinsic_gas_cost_calc( calldata=b"hello", return_cost_deducted_prior_execution=True @@ -223,13 +223,13 @@ def test_tx_gas_limit_by_iteration_count() -> None: iteration_count=5, include_state_gas_reservoir=True, ) - tx_gas_cost = bytecode.tx_regular_gas_cost_by_iteration_count( + tx_gas_cost = bytecode.tx_execution_gas_cost_by_iteration_count( fork=Osaka, iteration_count=5, ) reserve = bytecode.iterating_subcall_reserve(fork=Osaka) - # Osaka has no state-gas reservoir, so the limit is regular + reserve. + # Osaka has no state-gas reservoir, so the limit is execution + reserve. assert tx_gas_limit == tx_gas_cost + reserve @@ -393,12 +393,12 @@ def test_tx_gas_limit_includes_state_gas_reservoir() -> None: """ Under EIP-8037 ``include_state_gas_reservoir`` adds the per-iteration state gas to the transaction gas limit; otherwise the limit is the - regular gas plus the 63/64 subcall reserve only. + execution gas plus the 63/64 subcall reserve only. """ # SSTORE of a fresh slot from zero charges STORAGE_SET state gas. bytecode = IteratingBytecode(iterating=Op.SSTORE(0, 1)) - regular = bytecode.tx_regular_gas_cost_by_iteration_count( + execution = bytecode.tx_execution_gas_cost_by_iteration_count( fork=Amsterdam, iteration_count=5 ) state = bytecode.state_gas_cost_by_iteration_count( @@ -418,13 +418,13 @@ def test_tx_gas_limit_includes_state_gas_reservoir() -> None: include_state_gas_reservoir=True, ) - assert without_state == regular + reserve - assert with_state == regular + reserve + state + assert without_state == execution + reserve + assert with_state == execution + reserve + state -def test_state_reservoir_lets_tx_gas_exceed_regular_gas_limit_cap() -> None: +def test_state_reservoir_lets_tx_gas_exceed_execution_gas_limit_cap() -> None: """ - Under EIP-8037 the EIP-7825 transaction gas limit cap binds regular gas + Under EIP-8037 the EIP-7825 transaction gas limit cap binds execution gas only. A state-heavy transaction can therefore pack more iterations than that cap alone would allow, because its state gas draws from a separate reservoir and the combined ``tx.gas`` grows past the cap. @@ -433,18 +433,18 @@ def test_state_reservoir_lets_tx_gas_exceed_regular_gas_limit_cap() -> None: fork = CustomAmsterdam.with_tx_gas_limit_cap(cap) bytecode = IteratingBytecode(iterating=Op.SSTORE(0, 1)) - total_iterations = (cap // Op.SSTORE(0, 1).regular_cost(fork=fork)) - 1 + total_iterations = (cap // Op.SSTORE(0, 1).execution_cost(fork=fork)) - 1 counts = list( bytecode.tx_iterations_by_total_iteration_count( fork=fork, total_iterations=total_iterations ) ) - # Regular gas stays under the cap, so all iterations fit in one tx even - # though their combined (regular + state) gas far exceeds the cap. + # Execution gas stays under the cap, so all iterations fit in one tx even + # though their combined (execution + state) gas far exceeds the cap. assert counts == [total_iterations] - regular = bytecode.tx_regular_gas_cost_by_iteration_count( + execution = bytecode.tx_execution_gas_cost_by_iteration_count( fork=fork, iteration_count=total_iterations ) combined = bytecode.tx_gas_limit_by_iteration_count( @@ -452,7 +452,7 @@ def test_state_reservoir_lets_tx_gas_exceed_regular_gas_limit_cap() -> None: iteration_count=total_iterations, include_state_gas_reservoir=True, ) - assert regular <= cap, "regular gas must respect the EIP-7825 cap" + assert execution <= cap, "execution gas must respect the EIP-7825 cap" assert combined > cap, ( "combined tx.gas exceeds the cap via state reservoir" ) @@ -471,12 +471,12 @@ def test_transaction_with_cost_billing_by_outcome( ) -> None: """ Billed gas and block-header contribution follow the expected outcome: - combined regular + state on success, regular only on revert (state gas + combined execution + state on success, execution only on revert (state gas is refunded), and the whole gas limit on an exceptional halt. """ tx = TransactionWithCost( gas_limit=150_000, - regular_cost=60_000, + execution_cost=60_000, state_cost=40_000, outcome=outcome, ) @@ -487,21 +487,21 @@ def test_transaction_with_cost_billing_by_outcome( def test_tx_iterations_by_gas_limit_outcome_packing() -> None: """ The block budget is consumed according to the expected outcome: the - max-dimension gas on success, the regular gas only on revert, and the + max-dimension gas on success, the execution gas only on revert, and the whole gas limit (including the subcall reserve, without any state allowance) on out-of-gas. """ budget = 1_000_000 fork = CustomAmsterdam.with_tx_gas_limit_cap(16_777_216) - # SSTORE of a fresh slot from zero: state gas dominates regular gas. + # SSTORE of a fresh slot from zero: state gas dominates execution gas. bytecode = IteratingBytecode( iterating=Op.SSTORE(0, 1), iterating_subcall=6300 ) reserve = bytecode.iterating_subcall_reserve(fork=fork) assert reserve > 0 - def regular(iterations: int) -> int: - return bytecode.tx_regular_gas_cost_by_iteration_count( + def execution(iterations: int) -> int: + return bytecode.tx_execution_gas_cost_by_iteration_count( fork=fork, iteration_count=iterations ) @@ -525,17 +525,17 @@ def state(iterations: int) -> int: ) # Success packing is bound by the dominant (state) dimension. - assert sum(max(regular(i), state(i)) for i in success) <= budget + assert sum(max(execution(i), state(i)) for i in success) <= budget assert state(sum(success) + 1) > budget, ( "one more iteration should overflow the state dimension" ) - # Revert packing bills regular gas only, so far more iterations fit. + # Revert packing bills execution gas only, so far more iterations fit. assert sum(revert) > sum(success) - assert sum(regular(i) for i in revert) <= budget + assert sum(execution(i) for i in revert) <= budget # Out-of-gas packing counts the whole gas limit, reserve included. - assert sum(regular(i) + reserve for i in out_of_gas) <= budget - assert regular(sum(out_of_gas) + 1) + reserve > budget, ( - "one more iteration should overflow the regular budget" + assert sum(execution(i) + reserve for i in out_of_gas) <= budget + assert execution(sum(out_of_gas) + 1) + reserve > budget, ( + "one more iteration should overflow the execution budget" ) diff --git a/packages/testing/src/execution_testing/tools/tools_code/generators.py b/packages/testing/src/execution_testing/tools/tools_code/generators.py index 75ef1a354b0..0ccad4b9075 100644 --- a/packages/testing/src/execution_testing/tools/tools_code/generators.py +++ b/packages/testing/src/execution_testing/tools/tools_code/generators.py @@ -112,7 +112,7 @@ def __new__( return instance - def execution_gas(self, fork: Type[ForkOpcodeInterface]) -> int: + def evm_gas(self, fork: Type[ForkOpcodeInterface]) -> int: """ Gas cost of executing the initcode, charged before the code deposit fee. @@ -773,9 +773,9 @@ class TxOutcome(Enum): Expected outcome of a generated transaction. Under EIP-8037 the outcome decides how gas is billed: on success the - sender pays regular plus state gas, on revert the runtime state gas is + sender pays execution plus state gas, on revert the runtime state gas is rolled back into the reservoir and refunded, and on an exceptional halt - the whole declared gas limit burns in the regular dimension. + the whole declared gas limit burns in the execution dimension. """ SUCCESS = auto() @@ -786,7 +786,7 @@ class TxOutcome(Enum): class TransactionWithCost(Transaction): """Transaction object that can include the expected gas to be consumed.""" - regular_cost: int = Field(..., exclude=True) + execution_cost: int = Field(..., exclude=True) state_cost: int = Field(..., exclude=True) outcome: TxOutcome = Field(TxOutcome.SUCCESS, exclude=True) @@ -797,8 +797,8 @@ def gas_cost(self) -> int: `cumulativeGasUsed` reflects. Use for `expected_benchmark_gas_used`. - On success this is the combined regular + state gas. On revert only - the regular gas is billed (runtime state gas is refunded; intrinsic + On success this is the combined execution + state gas. On revert only + the execution gas is billed (runtime state gas is refunded; intrinsic state gas, e.g. for authorizations, is not modeled here). On an exceptional halt the whole gas limit burns: the generators size out-of-gas transactions below the EIP-7825 cap, where the state @@ -806,11 +806,11 @@ def gas_cost(self) -> int: """ match self.outcome: case TxOutcome.REVERT: - return self.regular_cost + return self.execution_cost case TxOutcome.OUT_OF_GAS: return int(self.gas_limit) case _: - return self.regular_cost + self.state_cost + return self.execution_cost + self.state_cost @property def block_gas_cost(self) -> int: @@ -818,24 +818,24 @@ def block_gas_cost(self) -> int: Return the gas this transaction contributes to the block-header gas. The block-header gas is the maximum across the independent gas - dimensions (EIP-8037: `max(regular, state)`), not their sum, so this + dimensions (EIP-8037: `max(execution, state)`), not their sum, so this is the right per-transaction quantity for block-fitting decisions (e.g. how many transactions fit under a gas target). On revert only - the regular gas lands; on an exceptional halt the whole gas limit - lands in the regular dimension. + the execution gas lands; on an exceptional halt the whole gas limit + lands in the execution dimension. Summing this over a block is exact only when a single dimension dominates every transaction uniformly (the common benchmark shape); for a mixed block the exact occupancy is - `max(sum(regular_cost), sum(state_cost))`. + `max(sum(execution_cost), sum(state_cost))`. """ match self.outcome: case TxOutcome.REVERT: - return self.regular_cost + return self.execution_cost case TxOutcome.OUT_OF_GAS: return int(self.gas_limit) case _: - return max(self.regular_cost, self.state_cost) + return max(self.execution_cost, self.state_cost) @dataclass(kw_only=True, slots=True) @@ -844,7 +844,7 @@ class GasCaps: Small helper class to represent multidimensional gas caps. """ - regular: int + execution: int state: int | None gas_limit: int | None @@ -953,7 +953,7 @@ def iterating_subcall_gas_cost( """Return the gas cost of the iterating subcall.""" if isinstance(self.iterating_subcall, int): return self.iterating_subcall - return self.iterating_subcall.regular_cost(fork=fork) + return self.iterating_subcall.execution_cost(fork=fork) def iterating_subcall_state_gas_cost( self, *, fork: Type[ForkOpcodeInterface] @@ -980,16 +980,16 @@ def iterating_subcall_reserve( iterating_subcall_gas_cost * 64 // 63 ) - iterating_subcall_gas_cost - def regular_gas_cost_by_iteration_count( + def execution_gas_cost_by_iteration_count( self, *, fork: Type[ForkOpcodeInterface], iteration_count: int ) -> int: """Return the cost of iterating through the bytecode N times.""" loop_gas_cost = 0 if iteration_count > 0: # Cold cost is just charged for the first iteration - loop_gas_cost = self.iterating.regular_cost(fork=fork) + loop_gas_cost = self.iterating.execution_cost(fork=fork) # Warm cost is charged for all iterations except the first - loop_gas_cost += self.warm_iterating.regular_cost(fork=fork) * ( + loop_gas_cost += self.warm_iterating.execution_cost(fork=fork) * ( iteration_count - 1 ) # Subcall cost is charged for all iterations. @@ -997,9 +997,9 @@ def regular_gas_cost_by_iteration_count( self.iterating_subcall_gas_cost(fork=fork) * iteration_count ) return ( - self.setup.regular_cost(fork=fork) + self.setup.execution_cost(fork=fork) + loop_gas_cost - + self.cleanup.regular_cost(fork=fork) + + self.cleanup.execution_cost(fork=fork) ) def state_gas_cost_by_iteration_count( @@ -1043,7 +1043,7 @@ def with_fixed_iteration_count( # Methods to calculate transactions that call a contract containing the # iterating bytecode. - def tx_regular_gas_cost_by_iteration_count( + def tx_execution_gas_cost_by_iteration_count( self, *, fork: Fork, @@ -1088,7 +1088,7 @@ def tx_regular_gas_cost_by_iteration_count( } ) return ( - self.regular_gas_cost_by_iteration_count( + self.execution_gas_cost_by_iteration_count( fork=fork, iteration_count=iteration_count ) + intrinsic_gas_cost_calc(**intrinsic_cost_kwargs) @@ -1111,7 +1111,7 @@ def tx_gas_limit_by_iteration_count( The gas limit is calculated by adding the required extra gas for the last iteration due to the 63/64 rule. """ - tx_gas_limit = self.tx_regular_gas_cost_by_iteration_count( + tx_gas_limit = self.tx_execution_gas_cost_by_iteration_count( fork=fork, iteration_count=iteration_count, start_iteration=start_iteration, @@ -1135,18 +1135,18 @@ def _iteration_count_exceeds_caps( """ Evaluate whether the iteration count exceeds any of the constraints. """ - tx_regular_gas_cost = self.tx_regular_gas_cost_by_iteration_count( + tx_execution_gas_cost = self.tx_execution_gas_cost_by_iteration_count( fork=fork, iteration_count=iteration_count, start_iteration=start_iteration, **intrinsic_cost_kwargs, ) - if tx_regular_gas_cost > caps.regular: + if tx_execution_gas_cost > caps.execution: return True if caps.gas_limit is not None and ( - self.iterating_subcall_reserve(fork=fork) + tx_regular_gas_cost + self.iterating_subcall_reserve(fork=fork) + tx_execution_gas_cost > caps.gas_limit ): return True @@ -1169,7 +1169,7 @@ def _binary_search_iterations( **intrinsic_cost_kwargs: Any, ) -> Tuple[int, int, int]: """ - Binary search for the maximum iterations that fit within the regular + Binary search for the maximum iterations that fit within the execution gas, state gas and gas limit cap constraints. """ if self._iteration_count_exceeds_caps( @@ -1212,8 +1212,8 @@ def _binary_search_iterations( low = mid + 1 best_iterations = low - 1 - best_iterations_regular_gas = ( - self.tx_regular_gas_cost_by_iteration_count( + best_iterations_execution_gas = ( + self.tx_execution_gas_cost_by_iteration_count( fork=fork, iteration_count=best_iterations, start_iteration=start_iteration, @@ -1225,7 +1225,7 @@ def _binary_search_iterations( ) return ( best_iterations, - best_iterations_regular_gas, + best_iterations_execution_gas, best_iterations_state_gas, ) @@ -1252,7 +1252,7 @@ def tx_iterations_by_gas_limit( The gas each transaction counts against the budget follows its expected outcome (see `TransactionWithCost.block_gas_cost`): the - max-dimension gas on success, the regular gas only on revert (state + max-dimension gas on success, the execution gas only on revert (state gas is refunded), and the whole gas limit including the subcall reserve on out-of-gas. """ @@ -1269,7 +1269,7 @@ def tx_iterations_by_gas_limit( def current_caps() -> GasCaps: return GasCaps( - regular=remaining_gas - reserve, + execution=remaining_gas - reserve, # State gas only counts against the block budget when the # transaction succeeds; on revert or halt it is refunded. state=( @@ -1289,7 +1289,7 @@ def current_caps() -> GasCaps: # within remaining_gas ( best_iterations, - best_iterations_regular_gas, + best_iterations_execution_gas, best_iterations_state_gas, ) = self._binary_search_iterations( fork=fork, @@ -1300,12 +1300,12 @@ def current_caps() -> GasCaps: yield best_iterations match outcome: case TxOutcome.REVERT: - remaining_gas -= best_iterations_regular_gas + remaining_gas -= best_iterations_execution_gas case TxOutcome.OUT_OF_GAS: - remaining_gas -= best_iterations_regular_gas + reserve + remaining_gas -= best_iterations_execution_gas + reserve case _: remaining_gas -= max( - best_iterations_regular_gas, + best_iterations_execution_gas, best_iterations_state_gas, ) start_iteration += best_iterations @@ -1351,7 +1351,7 @@ def tx_iterations_by_total_iteration_count( best_iterations, _, _ = self._binary_search_iterations( fork=fork, caps=GasCaps( - regular=gas_limit_cap, + execution=gas_limit_cap, state=None, gas_limit=gas_limit_cap, ), @@ -1399,7 +1399,7 @@ def transactions_by_gas_limit( according to `outcome`. Out-of-gas transactions are sized without the state gas allowance, - so the whole gas limit burns as regular gas and the billed amount is + so the whole gas limit burns as execution gas and the billed amount is exact; the caller must still make the bytecode inexhaustible (e.g. with a negative `tx_gas_limit_delta` or a loop with no exit). """ @@ -1427,7 +1427,7 @@ def transactions_by_gas_limit( ), **intrinsic_cost_kwargs, ) - tx_regular_cost = self.tx_regular_gas_cost_by_iteration_count( + tx_execution_cost = self.tx_execution_gas_cost_by_iteration_count( fork=fork, iteration_count=iteration_count, start_iteration=start_iteration, @@ -1448,7 +1448,7 @@ def transactions_by_gas_limit( to=to, gas_limit=tx_gas_limit + tx_gas_limit_delta, sender=sender, - regular_cost=tx_regular_cost, + execution_cost=tx_execution_cost, state_cost=tx_state_cost, outcome=outcome, **current_tx_kwargs, @@ -1503,7 +1503,7 @@ def transactions_by_total_iteration_count( include_state_gas_reservoir=True, **intrinsic_cost_kwargs, ) - tx_regular_cost = self.tx_regular_gas_cost_by_iteration_count( + tx_execution_cost = self.tx_execution_gas_cost_by_iteration_count( fork=fork, iteration_count=iteration_count, start_iteration=start_iteration, @@ -1524,7 +1524,7 @@ def transactions_by_total_iteration_count( to=to, gas_limit=tx_gas_limit + tx_gas_limit_delta, sender=sender, - regular_cost=tx_regular_cost, + execution_cost=tx_execution_cost, state_cost=tx_state_cost, **current_tx_kwargs, ) @@ -1591,7 +1591,7 @@ def __new__( def gas_cost(self, fork: Type[ForkOpcodeInterface]) -> int: """Return the cost of iterating through the bytecode N times.""" - return self.regular_gas_cost_by_iteration_count( + return self.execution_gas_cost_by_iteration_count( fork=fork, iteration_count=self.iteration_count, ) + self.state_gas_cost_by_iteration_count( diff --git a/packages/testing/src/execution_testing/vm/bytecode.py b/packages/testing/src/execution_testing/vm/bytecode.py index 1a28cd18f09..fdb73d2fa0c 100644 --- a/packages/testing/src/execution_testing/vm/bytecode.py +++ b/packages/testing/src/execution_testing/vm/bytecode.py @@ -38,8 +38,8 @@ class Bytecode: _gas_cost_fork_: Type[ForkOpcodeInterface] | None = None _state_cost_: int | None = None _state_cost_fork_: Type[ForkOpcodeInterface] | None = None - _regular_cost_: int | None = None - _regular_cost_fork_: Type[ForkOpcodeInterface] | None = None + _execution_cost_: int | None = None + _execution_cost_fork_: Type[ForkOpcodeInterface] | None = None _refund_: int | None = None _refund_fork_: Type[ForkOpcodeInterface] | None = None _state_refund_: int | None = None @@ -321,19 +321,19 @@ def state_cost(self, fork: Type[ForkOpcodeInterface]) -> int: self._state_cost_ += opcode_state_calculator(opcode) return self._state_cost_ - def regular_cost(self, fork: Type[ForkOpcodeInterface]) -> int: + def execution_cost(self, fork: Type[ForkOpcodeInterface]) -> int: """ - Use a fork object to calculate the regular gas used by this + Use a fork object to calculate the execution gas used by this bytecode (i.e. excluding the state-gas portion under EIP-8037). - Useful for OOG-boundary tests that need to land at the regular - gas charge of an opcode rather than its combined regular + state + Useful for OOG-boundary tests that need to land at the execution + gas charge of an opcode rather than its combined execution + state cost. """ - if self._regular_cost_ is None or self._regular_cost_fork_ != fork: - self._regular_cost_fork_ = fork - self._regular_cost_ = self.gas_cost(fork) - self.state_cost(fork) - return self._regular_cost_ + if self._execution_cost_ is None or self._execution_cost_fork_ != fork: + self._execution_cost_fork_ = fork + self._execution_cost_ = self.gas_cost(fork) - self.state_cost(fork) + return self._execution_cost_ def refund(self, fork: Type[ForkOpcodeInterface]) -> int: """Use a fork object to calculate the gas refund from this bytecode.""" diff --git a/src/ethereum/forks/amsterdam/fork.py b/src/ethereum/forks/amsterdam/fork.py index d76b92a24cc..9ffddf833ae 100644 --- a/src/ethereum/forks/amsterdam/fork.py +++ b/src/ethereum/forks/amsterdam/fork.py @@ -98,7 +98,7 @@ from .vm.gas import ( GasCosts, StateGasCosts, - allocate_execution_gas, + allocate_evm_gas, calculate_blob_gas_price, calculate_data_fee, calculate_excess_blob_gas, @@ -561,7 +561,7 @@ def check_transaction( is empty. """ - regular_gas_available = ( + execution_gas_available = ( block_env.block_gas_limit - block_output.block_gas_used ) state_gas_available = ( @@ -570,8 +570,8 @@ def check_transaction( blob_gas_available = MAX_BLOB_GAS_PER_BLOCK - block_output.blob_gas_used # EIP-8037 per-dimension inclusion check. - if min(TX_MAX_GAS_LIMIT, tx.gas) > regular_gas_available: - raise GasUsedExceedsLimitError("regular gas used exceeds limit") + if min(TX_MAX_GAS_LIMIT, tx.gas) > execution_gas_available: + raise GasUsedExceedsLimitError("execution gas used exceeds limit") if tx.gas > state_gas_available: raise GasUsedExceedsLimitError("state gas used exceeds limit") @@ -1048,9 +1048,9 @@ def process_transaction( effective_gas_fee = tx.gas * effective_gas_price - # Split execution gas into a regular grant (capped by the remaining - # regular-gas budget) and a state gas reservoir. - allocation = allocate_execution_gas(tx.gas, intrinsic) + # Split the EVM gas into an execution-gas grant (capped by the + # remaining execution-gas budget) and a state gas reservoir. + allocation = allocate_evm_gas(tx.gas, intrinsic) increment_nonce(tx_state, sender) @@ -1077,7 +1077,7 @@ def process_transaction( recipient=tx.to, value=tx.value, gas_price=effective_gas_price, - gas=allocation.regular_gas, + gas=allocation.execution_gas, state_gas_reservoir=allocation.state_gas_reservoir, access_list_addresses=access_list_addresses, access_list_storage_keys=access_list_storage_keys, @@ -1113,7 +1113,7 @@ def process_transaction( # transfer miner fees create_ether(tx_state, block_env.coinbase, U256(transaction_fee)) - block_output.block_gas_used += settlement.regular_gas_used + block_output.block_gas_used += settlement.execution_gas_used block_output.block_state_gas_used += settlement.state_gas_used block_output.blob_gas_used += tx_blob_gas_used diff --git a/src/ethereum/forks/amsterdam/fork_types.py b/src/ethereum/forks/amsterdam/fork_types.py index 52f0c8f8234..a058a019aea 100644 --- a/src/ethereum/forks/amsterdam/fork_types.py +++ b/src/ethereum/forks/amsterdam/fork_types.py @@ -34,7 +34,7 @@ Bloom = Bytes256 -RegularGas = NewType("RegularGas", Uint) +ExecutionGas = NewType("ExecutionGas", Uint) StateGas = NewType("StateGas", Uint) diff --git a/src/ethereum/forks/amsterdam/transactions.py b/src/ethereum/forks/amsterdam/transactions.py index 251c80472e2..d83754306db 100644 --- a/src/ethereum/forks/amsterdam/transactions.py +++ b/src/ethereum/forks/amsterdam/transactions.py @@ -25,7 +25,7 @@ InitCodeTooLargeError, TransactionTypeError, ) -from .fork_types import Authorization, RegularGas, VersionedHash +from .fork_types import Authorization, ExecutionGas, VersionedHash @final @@ -33,10 +33,10 @@ class IntrinsicGasCost: """Intrinsic gas costs for a transaction, split by gas type.""" - regular: RegularGas - """Regular execution gas (calldata, base cost, access list, etc.).""" + execution: ExecutionGas + """Execution gas (calldata, base cost, access list, etc.).""" - calldata_floor: RegularGas + calldata_floor: ExecutionGas """ Minimum gas cost based on calldata size per [EIP-7623]. @@ -597,16 +597,16 @@ def validate_transaction(tx: Transaction, sender: Address) -> IntrinsicGasCost: from .vm.interpreter import MAX_INIT_CODE_SIZE intrinsic = calculate_intrinsic_cost(tx, sender) - intrinsic_gas = Uint(intrinsic.regular) + intrinsic_gas = Uint(intrinsic.execution) if intrinsic_gas > tx.gas: raise InsufficientTransactionGasError("Insufficient intrinsic gas") if intrinsic.calldata_floor > tx.gas: raise InsufficientTransactionGasError("Insufficient calldata floor") if tx.to == Bytes0(b"") and len(tx.data) > MAX_INIT_CODE_SIZE: raise InitCodeTooLargeError("Code size too large") - if intrinsic.regular > TX_MAX_GAS_LIMIT: + if intrinsic.execution > TX_MAX_GAS_LIMIT: raise InsufficientTransactionGasError( - "Intrinsic regular gas exceeds TX_MAX_GAS_LIMIT" + "Intrinsic execution gas exceeds TX_MAX_GAS_LIMIT" ) if intrinsic.calldata_floor > TX_MAX_GAS_LIMIT: raise InsufficientTransactionGasError( @@ -652,9 +652,9 @@ def calculate_intrinsic_cost( charges. This function takes a transaction and its sender as parameters and - returns the intrinsic regular gas cost and the minimum (floor) gas - cost based on the calldata size. The floor is anchored on the - regular-gas portion of items 1 to 3 above rather than `TX_BASE` + returns the intrinsic execution gas cost and the minimum (floor) + gas cost based on the calldata size. The floor is anchored on the + execution-gas portion of items 1 to 3 above rather than `TX_BASE` alone, so it never undercuts the transaction's own intrinsic base. """ from .vm.gas import GasCosts, init_code_cost @@ -666,15 +666,15 @@ def calculate_intrinsic_cost( is_create = tx.to == Bytes0(b"") is_self_transfer = tx.to == sender - recipient_regular_gas = Uint(0) + recipient_execution_gas = Uint(0) init_code_gas = Uint(0) if is_create: - recipient_regular_gas = GasCosts.CREATE_ACCESS + recipient_execution_gas = GasCosts.CREATE_ACCESS init_code_gas = init_code_cost(ulen(tx.data)) elif not is_self_transfer: - recipient_regular_gas = GasCosts.COLD_ACCOUNT_ACCESS + recipient_execution_gas = GasCosts.COLD_ACCOUNT_ACCESS if tx.value > U256(0): - recipient_regular_gas += GasCosts.TX_VALUE_COST + recipient_execution_gas += GasCosts.TX_VALUE_COST access_list_cost = Uint(0) tokens_in_access_list = Uint(0) @@ -704,24 +704,24 @@ def calculate_intrinsic_cost( # Total floor tokens. total_floor_tokens = floor_tokens_in_calldata + tokens_in_access_list - # Decomposed regular-gas intrinsic base (EIP-2780), which also anchors - # the calldata floor. - base_regular_gas = GasCosts.TX_BASE + recipient_regular_gas + # Decomposed execution-gas intrinsic base (EIP-2780), which also + # anchors the calldata floor. + base_execution_gas = GasCosts.TX_BASE + recipient_execution_gas # Floor gas cost (EIP-7623: minimum gas for data-heavy transactions). data_floor_gas_cost = ( - total_floor_tokens * GasCosts.TX_DATA_TOKEN_FLOOR + base_regular_gas + total_floor_tokens * GasCosts.TX_DATA_TOKEN_FLOOR + base_execution_gas ) return IntrinsicGasCost( - regular=RegularGas( - base_regular_gas + execution=ExecutionGas( + base_execution_gas + init_code_gas + data_cost + access_list_cost + auth_cost ), - calldata_floor=RegularGas(data_floor_gas_cost), + calldata_floor=ExecutionGas(data_floor_gas_cost), ) diff --git a/src/ethereum/forks/amsterdam/vm/__init__.py b/src/ethereum/forks/amsterdam/vm/__init__.py index c7563ab5fd2..a2395a96ff9 100644 --- a/src/ethereum/forks/amsterdam/vm/__init__.py +++ b/src/ethereum/forks/amsterdam/vm/__init__.py @@ -70,7 +70,8 @@ class BlockOutput: Contains the following: block_gas_used : `ethereum.base_types.Uint` - Gas used for executing all transactions. + Execution gas used for executing all transactions. EIP-8037 + names this counter `block_execution_gas_used`. block_state_gas_used : `ethereum.base_types.Uint` State gas used for executing all transactions. cumulative_gas_used : `ethereum.base_types.Uint` diff --git a/src/ethereum/forks/amsterdam/vm/eoa_delegation.py b/src/ethereum/forks/amsterdam/vm/eoa_delegation.py index f2c69d5e7d8..28639cb7544 100644 --- a/src/ethereum/forks/amsterdam/vm/eoa_delegation.py +++ b/src/ethereum/forks/amsterdam/vm/eoa_delegation.py @@ -206,7 +206,7 @@ def set_delegation(evm: Evm) -> None: - ``StateGasCosts.NEW_ACCOUNT`` (state) when the authority's account leaf does not yet exist. - - ``GasCosts.ACCOUNT_WRITE`` (regular) when applying the + - ``GasCosts.ACCOUNT_WRITE`` (execution) when applying the authorization is the transaction's first write to the authority's leaf. Writes the transaction already prices elsewhere are exempt: the sender's, covered by ``TX_BASE``, and, for a diff --git a/src/ethereum/forks/amsterdam/vm/gas.py b/src/ethereum/forks/amsterdam/vm/gas.py index d4fd188946f..358418b7545 100644 --- a/src/ethereum/forks/amsterdam/vm/gas.py +++ b/src/ethereum/forks/amsterdam/vm/gas.py @@ -254,8 +254,9 @@ class GasMeter: gas_left: Uint """ - Gas still available from the frame's regular grant. Pays regular - charges, and state charges as [spill] once the reservoir empties. + Gas still available from the frame's execution-gas grant. Pays + execution-gas charges, and state charges as [spill] once the + reservoir empties. [spill]: ref:ethereum.forks.amsterdam.vm.gas.GasMeter.state_gas_spilled """ @@ -280,7 +281,7 @@ class GasMeter: state_gas_spilled: Uint = Uint(0) """ - Regular gas spent covering state charges after the reservoir + Execution gas spent covering state charges after the reservoir emptied. Credited back to `gas_left` first, in LIFO order, on a refund or failure. [EIP-8037] names this quantity `state_gas_from_gas_left`. @@ -358,14 +359,14 @@ def check_gas(evm: "Evm", amount: Uint) -> None: def charge_gas(evm: "Evm", amount: Uint) -> None: """ - Subtracts `amount` from `gas_left` (regular gas). + Subtracts `amount` from `gas_left` (execution gas). Parameters ---------- evm : The current EVM. amount : - The amount of regular gas the current operation requires. + The amount of execution gas the current operation requires. """ evm_trace(evm, GasAndRefund(int(amount))) @@ -561,7 +562,7 @@ def credit_state_gas_refund(gas_meter: GasMeter, amount: StateGas) -> None: def forfeit_remaining_gas(gas_meter: GasMeter) -> None: """ - Consume all remaining regular gas on an exceptional halt. + Consume all remaining execution gas on an exceptional halt. Parameters ---------- @@ -580,7 +581,7 @@ def withhold_create_gas(gas_meter: GasMeter) -> Uint: Withhold and return the gas made available to a `CREATE*` child. Deduct the all-but-one-64th share from the frame's `gas_left` and - return it as the child frame's regular gas grant. + return it as the child frame's execution-gas grant. Parameters ---------- @@ -590,7 +591,7 @@ def withhold_create_gas(gas_meter: GasMeter) -> Uint: Returns ------- child_gas : `ethereum.base_types.Uint` - The regular gas granted to the child frame. + The execution gas granted to the child frame. """ child_gas = max_message_call_gas(gas_meter.gas_left) @@ -629,15 +630,15 @@ def restore_child_gas( Return a child frame's unused gas grant to the parent. Used when the child frame is never entered (for example, a stack - depth or balance check fails): the withheld regular gas and drained - reservoir are returned untouched. + depth or balance check fails): the withheld execution gas and + drained reservoir are returned untouched. Parameters ---------- gas_meter : The parent frame's gas meter. gas : - The regular gas grant to return. + The execution gas grant to return. state_gas_reservoir : The state gas reservoir to return. @@ -910,28 +911,28 @@ def calculate_data_fee(excess_blob_gas: U64, tx: Transaction) -> Uint: @final @dataclass -class ExecutionGasAllocation: +class EvmGasAllocation: """ - Split of a transaction's execution gas across the two dimensions. + Split of a transaction's EVM gas across the two dimensions. """ - regular_gas: Uint - """Regular gas granted to the top frame, capped by the budget.""" + execution_gas: Uint + """Execution gas granted to the top frame, capped by the budget.""" state_gas_reservoir: Uint """State gas set aside for the top frame's reservoir.""" -def allocate_execution_gas( +def allocate_evm_gas( tx_gas: Uint, intrinsic: IntrinsicGasCost -) -> ExecutionGasAllocation: +) -> EvmGasAllocation: """ - Split execution gas into a regular grant and a state reservoir. + Split EVM gas into an execution-gas grant and a state reservoir. - After the intrinsic cost is removed, the remaining execution gas is - divided into regular gas -- capped by the regular-gas budget that - remains below `TX_MAX_GAS_LIMIT` -- and a state gas reservoir that - holds whatever exceeds that cap. + After the intrinsic cost is removed, the remaining EVM gas is + divided into execution gas -- capped by the execution-gas budget + that remains below `TX_MAX_GAS_LIMIT` -- and a state gas reservoir + that holds whatever exceeds that cap. Only valid once `validate_transaction` has confirmed the transaction can afford its intrinsic cost, which guarantees the subtractions @@ -946,15 +947,15 @@ def allocate_execution_gas( Returns ------- - allocation : `ExecutionGasAllocation` - The regular gas grant and state gas reservoir. + allocation : `EvmGasAllocation` + The execution gas grant and state gas reservoir. """ - execution_gas = tx_gas - Uint(intrinsic.regular) - regular_gas_budget = TX_MAX_GAS_LIMIT - intrinsic.regular - regular_gas = min(regular_gas_budget, execution_gas) - state_gas_reservoir = Uint(execution_gas - regular_gas) - return ExecutionGasAllocation(regular_gas, state_gas_reservoir) + evm_gas = tx_gas - Uint(intrinsic.execution) + execution_gas_budget = TX_MAX_GAS_LIMIT - intrinsic.execution + execution_gas = min(execution_gas_budget, evm_gas) + state_gas_reservoir = Uint(evm_gas - execution_gas) + return EvmGasAllocation(execution_gas, state_gas_reservoir) @final @@ -973,8 +974,8 @@ class TransactionGasSettlement: gas_left: Uint """Gas returned to the sender, priced at the effective gas price.""" - regular_gas_used: Uint - """Regular gas the transaction contributes to the block total.""" + execution_gas_used: Uint + """Execution gas the transaction contributes to the block total.""" state_gas_used: Uint """State gas the transaction contributes to the block total.""" @@ -993,16 +994,17 @@ def settle_transaction_gas( Compute, in order: - - the gas used before refunds, from the gas limit less the regular - gas and reservoir the top frame returned; + - the gas used before refunds, from the gas limit less the + execution gas and reservoir the top frame returned; - the refund, capped at one fifth of that pre-refund usage; - the gas used, taken as the larger of the post-refund usage and the calldata floor, so a transaction never pays below the floor; and - the per-dimension block amounts: the state gas used (clamped to - zero, since refunds can drive it negative) and the regular gas - used, which carries the floor because the floor binds the regular - dimension. Unlike the sender-facing `gas_used`, it ignores - refunds: block accounting counts pre-refund gas ([EIP-7778]). + zero, since refunds can drive it negative) and the execution gas + used, which carries the floor because the floor binds the + execution dimension. Unlike the sender-facing `gas_used`, it + ignores refunds: block accounting counts pre-refund gas + ([EIP-7778]). Parameters ---------- @@ -1011,7 +1013,7 @@ def settle_transaction_gas( intrinsic : The transaction's intrinsic gas cost. gas_left : - Regular gas the top frame returned. + Execution gas the top frame returned. state_gas_left : State gas reservoir the top frame returned. refund_counter : @@ -1033,13 +1035,13 @@ def settle_transaction_gas( gas_used = max(gas_used_after_refund, intrinsic.calldata_floor) settled_state_gas_used = Uint(max(0, state_gas_used)) - regular_gas_used = max( + execution_gas_used = max( gas_used_before_refund - settled_state_gas_used, intrinsic.calldata_floor, ) return TransactionGasSettlement( gas_used=gas_used, gas_left=tx_gas - gas_used, - regular_gas_used=regular_gas_used, + execution_gas_used=execution_gas_used, state_gas_used=settled_state_gas_used, ) diff --git a/src/ethereum/forks/amsterdam/vm/instructions/storage.py b/src/ethereum/forks/amsterdam/vm/instructions/storage.py index b63a8ea2a42..b54b3821fd2 100644 --- a/src/ethereum/forks/amsterdam/vm/instructions/storage.py +++ b/src/ethereum/forks/amsterdam/vm/instructions/storage.py @@ -149,8 +149,8 @@ def sstore(evm: Evm) -> None: # Slot set then cleared: refund the state gas charge. credit_state_gas_refund(evm.gas_meter, StateGasCosts.STORAGE_SET) - # Charge regular gas before state gas so that a regular-gas OOG - # does not consume state gas that would inflate the parent's + # Charge execution gas before state gas so that an execution-gas + # OOG does not consume state gas that would inflate the parent's # reservoir on frame failure. charge_gas(evm, gas_cost) charge_state_gas(evm, state_gas) diff --git a/src/ethereum/forks/amsterdam/vm/instructions/system.py b/src/ethereum/forks/amsterdam/vm/instructions/system.py index 7d5a23a0173..3f3afafe53d 100644 --- a/src/ethereum/forks/amsterdam/vm/instructions/system.py +++ b/src/ethereum/forks/amsterdam/vm/instructions/system.py @@ -115,10 +115,10 @@ def generic_create( charge_state_gas(evm, StateGasCosts.NEW_ACCOUNT) # CHILD GRANT - # Withhold all but one 64th of the regular gas. + # Withhold all but one 64th of the execution gas. create_message_gas = withhold_create_gas(evm.gas_meter) - # On a collision the child's regular grant is consumed and no + # On a collision the child's execution-gas grant is consumed and no # account is created; a storage-only collision target is # non-existent: charged above, refilled here. if not account_deployable(tx_state, contract_address): @@ -484,7 +484,7 @@ def call(evm: Evm) -> None: # STATE ACCESS (STATE-DEPENDENT GAS) # Perform the accesses and complete the state-dependent pricing -- - # a delegation adds its access cost -- then charge the regular + # a delegation adds its access cost -- then charge the execution # gas. tx_state = evm.message.tx_env.state if is_cold_access: @@ -610,7 +610,7 @@ def callcode(evm: Evm) -> None: # STATE ACCESS (STATE-DEPENDENT GAS) # Perform the accesses and complete the state-dependent pricing -- - # a delegation adds its access cost; the regular gas is charged + # a delegation adds its access cost; the execution gas is charged # with the child grant. tx_state = evm.message.tx_env.state if is_cold_access: @@ -634,7 +634,7 @@ def callcode(evm: Evm) -> None: code = get_code(tx_state, code_hash) # CHILD GRANT - # Charge the call's cost and withhold the child's regular gas + # Charge the call's cost and withhold the child's execution gas # share in one step. The whole reservoir rides along (no 63/64 # rule for state gas). message_call_gas = calculate_message_call_gas( @@ -724,8 +724,8 @@ def selfdestruct(evm: Evm) -> None: state_gas = StateGasCosts.NEW_ACCOUNT account_write_gas = GasCosts.ACCOUNT_WRITE - # Charge regular gas before state gas so that a regular-gas OOG - # does not consume state gas that would inflate the parent's + # Charge execution gas before state gas so that an execution-gas + # OOG does not consume state gas that would inflate the parent's # reservoir on frame failure. charge_gas(evm, gas_cost + account_write_gas) charge_state_gas(evm, state_gas) @@ -791,7 +791,7 @@ def delegatecall(evm: Evm) -> None: # STATE ACCESS (STATE-DEPENDENT GAS) # Perform the accesses and complete the state-dependent pricing -- - # a delegation adds its access cost; the regular gas is charged + # a delegation adds its access cost; the execution gas is charged # with the child grant. if is_cold_access: evm.accessed_addresses.add(code_address) @@ -815,7 +815,7 @@ def delegatecall(evm: Evm) -> None: code = get_code(tx_state, code_hash) # CHILD GRANT - # Charge the call's cost and withhold the child's regular gas + # Charge the call's cost and withhold the child's execution gas # share in one step. The whole reservoir rides along (no 63/64 # rule for state gas). message_call_gas = calculate_message_call_gas( @@ -894,7 +894,7 @@ def staticcall(evm: Evm) -> None: # STATE ACCESS (STATE-DEPENDENT GAS) # Perform the accesses and complete the state-dependent pricing -- - # a delegation adds its access cost; the regular gas is charged + # a delegation adds its access cost; the execution gas is charged # with the child grant. if is_cold_access: evm.accessed_addresses.add(to) @@ -918,7 +918,7 @@ def staticcall(evm: Evm) -> None: code = get_code(tx_state, code_hash) # CHILD GRANT - # Charge the call's cost and withhold the child's regular gas + # Charge the call's cost and withhold the child's execution gas # share in one step. The whole reservoir rides along (no 63/64 # rule for state gas). message_call_gas = calculate_message_call_gas( diff --git a/src/ethereum/forks/amsterdam/vm/interpreter.py b/src/ethereum/forks/amsterdam/vm/interpreter.py index 8d0a3fe26ce..b1810361f3a 100644 --- a/src/ethereum/forks/amsterdam/vm/interpreter.py +++ b/src/ethereum/forks/amsterdam/vm/interpreter.py @@ -426,7 +426,7 @@ def process_message(message: Message) -> Evm: except ExceptionalHalt as error: evm_trace(evm, OpException(error)) # Frame settlement: refill state gas to the baseline, then - # forfeit -- a halted frame returns no regular gas to its + # forfeit -- a halted frame returns no execution gas to its # parent. After these handlers the meter states exactly what # the frame gives back, so parents absorb unconditionally. restore_state_gas(evm.gas_meter) diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/helpers.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/helpers.py index 6a61ddbb6b4..3b158cfc374 100644 --- a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/helpers.py +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/helpers.py @@ -194,7 +194,7 @@ def authorization_transaction_cost( The recipient is a ``CONTRACT`` that runs no code, so no recipient top-frame charge applies and the cost reduces to the intrinsic plus - the authorizations' own top-frame regular and state charges. Each + the authorizations' own top-frame execution and state charges. Each authorization's charge is driven by its ``creates_account`` / ``writes_delegation`` / ``first_write`` annotations. """ @@ -203,7 +203,7 @@ def authorization_transaction_cost( authorization_list_or_count=authorization_list, return_cost_deducted_prior_execution=True, ) - top_frame_regular = fork.transaction_top_frame_gas_calculator()( + top_frame_execution = fork.transaction_top_frame_gas_calculator()( recipient_type=RecipientType.CONTRACT, authorizations=authorization_list, ) @@ -211,7 +211,7 @@ def authorization_transaction_cost( recipient_type=RecipientType.CONTRACT, authorizations=authorization_list, ) - return intrinsic_gas + top_frame_regular + top_frame_state + return intrinsic_gas + top_frame_execution + top_frame_state def setup_target( diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_authorization_charges.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_authorization_charges.py index 152d1c45d7f..d4a2b496fe3 100644 --- a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_authorization_charges.py +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_authorization_charges.py @@ -201,7 +201,7 @@ def _intrinsic_gas( recipient_type: RecipientType = RecipientType.CONTRACT, sends_value: bool = False, ) -> int: - """Return the regular intrinsic gas deducted before execution.""" + """Return the execution intrinsic gas deducted before execution.""" return fork.transaction_intrinsic_cost_calculator()( recipient_type=recipient_type, sends_value=sends_value, @@ -377,7 +377,7 @@ def test_account_write_authority_is_recipient( # delegation. recipient_type = RecipientType.DELEGATION_7702 authorizations = [authorization] - top_frame_regular = fork.transaction_top_frame_gas_calculator()( + top_frame_execution = fork.transaction_top_frame_gas_calculator()( recipient_type=recipient_type, authorizations=authorizations, ) @@ -392,7 +392,7 @@ def test_account_write_authority_is_recipient( recipient_type=recipient_type, sends_value=bool(value), ) - + top_frame_regular + + top_frame_execution + top_frame_state ) diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_authorization_oog.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_authorization_oog.py index 2b7065f6363..eb9b707b739 100644 --- a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_authorization_oog.py +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_authorization_oog.py @@ -13,7 +13,7 @@ ``NEW_ACCOUNT``, or on the delegation-resolution access. The whole preparation shares one snapshot, so every authorization applied so far is rolled back and the frame halts without dispatching. The - transaction is still included and consumes its full regular budget; + transaction is still included and consumes its full execution budget; a state-gas reservoir, whose charges are refilled with the rollback, is returned to the sender in full. The sender nonce (bumped at inclusion, before the snapshot) is not rolled back. @@ -73,7 +73,7 @@ def _auth_top_frame_charges(fork: Fork, authorizations: list) -> int: """ - Return the top-frame regular + state gas attributable to the + Return the top-frame execution + state gas attributable to the authorizations alone. Computed against a ``CONTRACT`` recipient, which contributes no @@ -82,7 +82,7 @@ def _auth_top_frame_charges(fork: Fork, authorizations: list) -> int: ``AUTH_BASE``. Under the zero state reservoir these all draw from ``gas_left``. """ - regular = fork.transaction_top_frame_gas_calculator()( + execution = fork.transaction_top_frame_gas_calculator()( recipient_type=RecipientType.CONTRACT, authorizations=authorizations, ) @@ -90,17 +90,17 @@ def _auth_top_frame_charges(fork: Fork, authorizations: list) -> int: recipient_type=RecipientType.CONTRACT, authorizations=authorizations, ) - return regular + state + return execution + state -def _intrinsic_regular( +def _intrinsic_execution( fork: Fork, authorization_list: list, *, recipient_type: RecipientType, sends_value: bool = False, ) -> int: - """Return the regular intrinsic gas deducted before execution.""" + """Return the execution intrinsic gas deducted before execution.""" return fork.transaction_intrinsic_cost_calculator()( recipient_type=recipient_type, sends_value=sends_value, @@ -157,7 +157,7 @@ def test_set_delegation_oog_charge_point( - ``new_account``: the second (a creation) runs out at its opening ``NEW_ACCOUNT`` state charge. - ``account_write``: the second covers ``NEW_ACCOUNT`` but runs out - at the following ``ACCOUNT_WRITE`` regular charge. + at the following ``ACCOUNT_WRITE`` execution charge. - ``auth_base``: the second (a delegation on an existing empty EOA) covers its first-write ``ACCOUNT_WRITE`` but runs out at the following ``AUTH_BASE`` state charge. @@ -193,12 +193,12 @@ def test_set_delegation_oog_charge_point( authorization_list = [first.authorization, second.authorization] - intrinsic_regular = _intrinsic_regular( + intrinsic_execution = _intrinsic_execution( fork, authorization_list, recipient_type=RecipientType.CONTRACT ) first_auth_charges = _auth_top_frame_charges(fork, [first.authorization]) - # gas_left entering set_delegation is gas_limit - intrinsic_regular + # gas_left entering set_delegation is gas_limit - intrinsic_execution # (the state reservoir is zero). The first authorization is applied # in full; the second is starved by one gas at the target charge, # after covering any charges that precede it within that same @@ -214,7 +214,7 @@ def test_set_delegation_oog_charge_point( shortfall_charge = gas_costs.AUTH_BASE gas_limit = ( - intrinsic_regular + first_auth_charges + preceding + shortfall_charge + intrinsic_execution + first_auth_charges + preceding + shortfall_charge ) if outcome != "succeeds": gas_limit -= 1 @@ -317,7 +317,7 @@ def test_set_delegation_oog_rolls_back_first_auth( second = build_authorization(pre, AuthorizationAction.CREATES_ACCOUNT) authorization_list = [first.authorization, second.authorization] - intrinsic_regular = _intrinsic_regular( + intrinsic_execution = _intrinsic_execution( fork, authorization_list, recipient_type=RecipientType.CONTRACT ) first_auth_charges = _auth_top_frame_charges(fork, [first.authorization]) @@ -326,7 +326,7 @@ def test_set_delegation_oog_rolls_back_first_auth( # runs out at its opening NEW_ACCOUNT state charge, rolling back the # whole authorization phase. gas_limit = ( - intrinsic_regular + first_auth_charges + gas_costs.NEW_ACCOUNT - 1 + intrinsic_execution + first_auth_charges + gas_costs.NEW_ACCOUNT - 1 ) tx = Transaction( @@ -455,7 +455,7 @@ def test_recipient_charge_oog_rolls_back_delegations( delegated_to: BalAccountExpectation.empty() if succeeds else None } - intrinsic_regular = _intrinsic_regular( + intrinsic_execution = _intrinsic_execution( fork, authorization_list, recipient_type=recipient_type, @@ -466,7 +466,7 @@ def test_recipient_charge_oog_rolls_back_delegations( # is starved by one gas -- or, with ``succeeds``, covered exactly. # The charge shares the preparation snapshot, so its out-of-gas # rolls the applied delegations back. - gas_limit = intrinsic_regular + auth_charges + recipient_charge_gas + gas_limit = intrinsic_execution + auth_charges + recipient_charge_gas if not succeeds: gas_limit -= 1 @@ -538,7 +538,7 @@ def test_reservoir_settlement_by_failure_point( at each point along the top frame, settles four different ways. A non-zero reservoir requires ``gas_limit`` above the EIP-7825 cap, - which also hands the frame the *full* regular budget -- so starving + which also hands the frame the *full* execution budget -- so starving the preparation is only reachable when its demand exceeds the cap plus the reservoir. Account-creating authorizations are the one charge dense enough to get there: each demands ~234,606 gas @@ -564,7 +564,7 @@ def test_reservoir_settlement_by_failure_point( leaves the reservoir empty and the halt burns the rest: ``gas_used == gas_limit``, the full amount. - ``execution_revert``: as above, but ``REVERT`` returns the unused - regular budget: ``gas_used`` is exactly the intrinsic cost plus + execution budget: ``gas_used`` is exactly the intrinsic cost plus every preparation charge plus the reverting code's own gas. Together the four pin that the reservoir's fate follows the state @@ -608,11 +608,11 @@ def creation_authorization(authority: EOA) -> AuthorizationTuple: probe_authority = pre.fund_eoa(amount=0) probe = creation_authorization(probe_authority) - base_intrinsic = _intrinsic_regular( + base_intrinsic = _intrinsic_execution( fork, [], recipient_type=RecipientType.DELEGATION_7702 ) per_auth_intrinsic = ( - _intrinsic_regular( + _intrinsic_execution( fork, [probe], recipient_type=RecipientType.DELEGATION_7702 ) - base_intrinsic @@ -634,7 +634,7 @@ def creation_authorization(authority: EOA) -> AuthorizationTuple: creation_authorization(authority) for authority in authorities[1:] ] - intrinsic_regular = base_intrinsic + auth_count * per_auth_intrinsic + intrinsic_execution = base_intrinsic + auth_count * per_auth_intrinsic auth_charges = auth_count * per_auth_charges dispatch_charge = gas_costs.COLD_ACCOUNT_ACCESS auth_state_total = fork.transaction_top_frame_state_gas( @@ -644,30 +644,32 @@ def creation_authorization(authority: EOA) -> AuthorizationTuple: if failure_point == "set_delegation_oog": # The final authorization's closing AUTH_BASE is starved by one. - gas_limit = intrinsic_regular + auth_charges - 1 + gas_limit = intrinsic_execution + auth_charges - 1 expected_gas_used = cap delegations_persist = False elif failure_point == "dispatch_charge_oog": # All authorizations apply; the recipient's cold # delegation-resolution access is starved by one. - gas_limit = intrinsic_regular + auth_charges + dispatch_charge - 1 + gas_limit = intrinsic_execution + auth_charges + dispatch_charge - 1 expected_gas_used = cap delegations_persist = False elif failure_point == "execution_halt": - gas_limit = intrinsic_regular + auth_charges + dispatch_charge + 10_000 + gas_limit = ( + intrinsic_execution + auth_charges + dispatch_charge + 10_000 + ) expected_gas_used = gas_limit delegations_persist = True else: # execution_revert exec_gas = recipient_code.gas_cost(fork) gas_limit = ( - intrinsic_regular + intrinsic_execution + auth_charges + dispatch_charge + exec_gas + 10_000 ) expected_gas_used = ( - intrinsic_regular + auth_charges + dispatch_charge + exec_gas + intrinsic_execution + auth_charges + dispatch_charge + exec_gas ) delegations_persist = True @@ -828,14 +830,14 @@ def creation_authorization(authority: EOA) -> AuthorizationTuple: probe_authority = pre.fund_eoa(amount=0) probe = creation_authorization(probe_authority) - base_intrinsic = _intrinsic_regular( + base_intrinsic = _intrinsic_execution( fork, [], recipient_type=RecipientType.EMPTY_ACCOUNT, sends_value=True, ) per_auth_intrinsic = ( - _intrinsic_regular( + _intrinsic_execution( fork, [probe], recipient_type=RecipientType.EMPTY_ACCOUNT, @@ -859,7 +861,7 @@ def creation_authorization(authority: EOA) -> AuthorizationTuple: creation_authorization(authority) for authority in authorities[1:] ] - intrinsic_regular = base_intrinsic + auth_count * per_auth_intrinsic + intrinsic_execution = base_intrinsic + auth_count * per_auth_intrinsic auth_charges = auth_count * per_auth_charges auth_state_total = fork.transaction_top_frame_state_gas( recipient_type=RecipientType.CONTRACT, @@ -869,14 +871,14 @@ def creation_authorization(authority: EOA) -> AuthorizationTuple: data = b"" if failure_point == "set_delegation_oog": # The final authorization's closing AUTH_BASE is starved by one. - gas_limit = intrinsic_regular + auth_charges - 1 + gas_limit = intrinsic_execution + auth_charges - 1 expected_gas_used = cap delegations_persist = False elif failure_point == "dispatch_charge_oog": # All authorizations apply; the recipient's NEW_ACCOUNT state # charge is starved by one. gas_limit = ( - intrinsic_regular + auth_charges + gas_costs.NEW_ACCOUNT - 1 + intrinsic_execution + auth_charges + gas_costs.NEW_ACCOUNT - 1 ) expected_gas_used = cap delegations_persist = False @@ -1012,7 +1014,7 @@ def test_delegation_persists_on_execution_oog( authorization_list = [auth_a.authorization, auth_b.authorization] auth_charges = _auth_top_frame_charges(fork, authorization_list) - intrinsic_regular = _intrinsic_regular( + intrinsic_execution = _intrinsic_execution( fork, authorization_list, recipient_type=RecipientType.CONTRACT, @@ -1023,7 +1025,7 @@ def test_delegation_persists_on_execution_oog( # execution and then runs out on the second, consuming all gas. recipient_code = Op.PUSH1(0) + Op.PUSH1(0) one_opcode = Op.PUSH1(0).gas_cost(fork) - gas_limit = intrinsic_regular + auth_charges + one_opcode + gas_limit = intrinsic_execution + auth_charges + one_opcode recipient = pre.deploy_contract(code=recipient_code) @@ -1104,16 +1106,16 @@ def test_auth_state_charges_survive_dispatch_revert( auth = build_authorization(pre, auth_action) authorization_list = [auth.authorization] - intrinsic_regular = _intrinsic_regular( + intrinsic_execution = _intrinsic_execution( fork, authorization_list, recipient_type=RecipientType.CONTRACT ) auth_charges = _auth_top_frame_charges(fork, authorization_list) revert_exec_gas = revert_code.gas_cost(fork) - # The authorization's regular and state charges and the two PUSH + # The authorization's execution and state charges and the two PUSH # opcodes feeding the REVERT stay paid; only the unused execution # budget returns. - gas_used = intrinsic_regular + auth_charges + revert_exec_gas + gas_used = intrinsic_execution + auth_charges + revert_exec_gas tx = Transaction( sender=sender, @@ -1158,10 +1160,10 @@ def test_auth_state_charges_survive_dispatch_halt_with_reservoir( With an ordinary gas limit the reservoir is zero and a halt consumes all of ``gas_left`` anyway, masking any wrongly-refilled state gas. Here the gas limit exceeds the EIP-7825 cap (allowed -- - the cap binds only the regular dimension), so the excess forms a + the cap binds only the execution dimension), so the excess forms a state-gas reservoir that covers the authorization's ``NEW_ACCOUNT`` + ``AUTH_BASE``. The dispatched call hits ``INVALID``, consuming - all regular gas; the *unused* reservoir returns to the sender, but + all execution gas; the *unused* reservoir returns to the sender, but the portion consumed for the persisting delegation must not. A regression that refills the authorization's state gas with the @@ -1192,7 +1194,7 @@ def test_auth_state_charges_survive_dispatch_halt_with_reservoir( # into gas_left. reservoir = auth_state_gas + 50_000 - # The halt consumes the full regular budget (the cap); of the + # The halt consumes the full execution budget (the cap); of the # reservoir, only the authorization's state gas is consumed -- its # delegation persists -- and the unused remainder returns. gas_used = cap + auth_state_gas @@ -1225,9 +1227,9 @@ def test_auth_state_gas_in_header_on_dispatch_revert( The state gas of an applied authorization is counted in the block's state dimension when the dispatched call reverts. - The header ``gas_used`` is ``max(block_regular_gas, + The header ``gas_used`` is ``max(block_execution_gas, block_state_gas)``. The authorization creates and delegates a fresh - authority (218,790 state gas), which dominates the small regular + authority (218,790 state gas), which dominates the small execution side (intrinsic + ``ACCOUNT_WRITE`` + the pre-revert execution), so a correct accounting yields ``gas_used == 218,790`` even though the dispatched call reverts -- the delegation, and the state it grew, @@ -1235,7 +1237,7 @@ def test_auth_state_gas_in_header_on_dispatch_revert( A regression that refills the authorization's state gas on the frame's rollback collapses ``tx_state_gas`` to zero and the header - to the small regular sum, which balance-only state tests cannot + to the small execution sum, which balance-only state tests cannot distinguish from a correctly-split total. """ sender = pre.fund_eoa() @@ -1246,10 +1248,10 @@ def test_auth_state_gas_in_header_on_dispatch_revert( auth = build_authorization(pre, AuthorizationAction.CREATES_ACCOUNT) authorization_list = [auth.authorization] - intrinsic_regular = _intrinsic_regular( + intrinsic_execution = _intrinsic_execution( fork, authorization_list, recipient_type=RecipientType.CONTRACT ) - auth_regular = fork.transaction_top_frame_gas_calculator()( + auth_execution = fork.transaction_top_frame_gas_calculator()( recipient_type=RecipientType.CONTRACT, authorizations=authorization_list, ) @@ -1259,11 +1261,11 @@ def test_auth_state_gas_in_header_on_dispatch_revert( ) revert_exec_gas = revert_code.gas_cost(fork) - regular_total = intrinsic_regular + auth_regular + revert_exec_gas - assert auth_state > regular_total, ( + execution_total = intrinsic_execution + auth_execution + revert_exec_gas + assert auth_state > execution_total, ( "the state dimension must dominate for the header to pin it" ) - expected_gas_used = max(regular_total, auth_state) + expected_gas_used = max(execution_total, auth_state) tx = Transaction( sender=sender, @@ -1314,7 +1316,7 @@ def test_reverted_dispatch_state_gas_counts_toward_block_limit( beyond it (``exceeded``: the per-transaction state check fires and the block is correctly rejected). - The regular dimension is asserted to have room either way, pinning + The execution dimension is asserted to have room either way, pinning the rejection to the state dimension. An implementation that drops a reverted transaction's persisting state gas from the block's state total would accept the ``exceeded`` block and fork. @@ -1330,10 +1332,10 @@ def test_reverted_dispatch_state_gas_counts_toward_block_limit( auth = build_authorization(pre, AuthorizationAction.CREATES_ACCOUNT) authorization_list = [auth.authorization] - intrinsic_regular = _intrinsic_regular( + intrinsic_execution = _intrinsic_execution( fork, authorization_list, recipient_type=RecipientType.CONTRACT ) - auth_regular = fork.transaction_top_frame_gas_calculator()( + auth_execution = fork.transaction_top_frame_gas_calculator()( recipient_type=RecipientType.CONTRACT, authorizations=authorization_list, ) @@ -1343,13 +1345,13 @@ def test_reverted_dispatch_state_gas_counts_toward_block_limit( ) revert_exec_gas = revert_code.gas_cost(fork) - first_tx_regular = intrinsic_regular + auth_regular + revert_exec_gas + first_tx_execution = intrinsic_execution + auth_execution + revert_exec_gas first_tx = Transaction( sender=pre.fund_eoa(), to=recipient, value=0, authorization_list=authorization_list, - gas_limit=first_tx_regular + auth_state, + gas_limit=first_tx_execution + auth_state, ) # The last transaction's worst-case state contribution is its full @@ -1360,10 +1362,10 @@ def test_reverted_dispatch_state_gas_counts_toward_block_limit( last_tx_gas = state_available + delta # Pin the rejection (when delta > 0) to the state check: the - # regular check must not fire. - regular_available = block_gas_limit - first_tx_regular - assert min(cap, last_tx_gas) < regular_available, ( - "the last tx would fail the regular check instead of the state check" + # execution check must not fire. + execution_available = block_gas_limit - first_tx_execution + assert min(cap, last_tx_gas) < execution_available, ( + "the last tx would fail the execution check instead of the state check" ) last_tx_error = ( @@ -1413,7 +1415,7 @@ def test_recipient_new_account_refilled_on_dispatch_halt_with_reservoir( refills. The gas limit exceeds the EIP-7825 cap so the charge draws from a - state-gas reservoir; the halt consumes the full regular budget (the + state-gas reservoir; the halt consumes the full execution budget (the cap) but the *entire* reservoir returns, pinning the refill in the receipt's gas used. This is the counterpart of ``test_auth_state_charges_survive_dispatch_halt_with_reservoir``, @@ -1436,7 +1438,7 @@ def test_recipient_new_account_refilled_on_dispatch_halt_with_reservoir( reservoir = new_account_state_gas + 50_000 - # The halt consumes the full regular budget; the NEW_ACCOUNT drawn + # The halt consumes the full execution budget; the NEW_ACCOUNT drawn # from the reservoir is refilled (the account creation rolled # back), so the whole reservoir returns to the sender. gas_used = cap @@ -1491,11 +1493,11 @@ def test_dispatched_frame_state_gas_still_refills_on_revert( auth = build_authorization(pre, AuthorizationAction.SETS_NEW_DELEGATION) authorization_list = [auth.authorization] - intrinsic_regular = _intrinsic_regular( + intrinsic_execution = _intrinsic_execution( fork, authorization_list, recipient_type=RecipientType.CONTRACT ) auth_charges = _auth_top_frame_charges(fork, authorization_list) - exec_regular = sstore_revert_code.regular_cost(fork) + evm_execution = sstore_revert_code.execution_cost(fork) exec_state = sstore_revert_code.state_cost(fork) assert exec_state > 0, ( "the dispatched SSTORE must carry a state-gas charge" @@ -1503,8 +1505,8 @@ def test_dispatched_frame_state_gas_still_refills_on_revert( # The SSTORE's state gas is charged and then refilled by the # revert (the slot rolls back), so the sender pays only the - # authorization charges and the regular execution gas. - gas_used = intrinsic_regular + auth_charges + exec_regular + # authorization charges and the execution gas. + gas_used = intrinsic_execution + auth_charges + evm_execution tx = Transaction( sender=sender, diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_calldata_floor.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_calldata_floor.py index b0ba400f25e..c1a13931d54 100644 --- a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_calldata_floor.py +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_calldata_floor.py @@ -259,7 +259,7 @@ def test_calldata_floor_contract_creation( empty code, and prices every byte as one floor token. - ``floor_binds``: ``gas_used`` pins to the floor, which anchors - on the creation regular base (``TX_BASE + CREATE_ACCESS``) + on the creation execution base (``TX_BASE + CREATE_ACCESS``) but excludes the created account's ``NEW_ACCOUNT`` *state* charge and the init-code word cost -- both masked by the binding floor -- while the deploy (and any moved wei) still lands. diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_fork_transition.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_fork_transition.py index 4f0cffce3c8..18885366818 100644 --- a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_fork_transition.py +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_fork_transition.py @@ -18,7 +18,7 @@ lowered ``TX_BASE`` with no recipient or value-transfer charge, regardless of value, the largest reduction. - A contract creation splits the flat pre-fork ``TX_CREATE`` into the - ``CREATE_ACCESS`` regular intrinsic and a top-frame ``NEW_ACCOUNT`` + ``CREATE_ACCESS`` execution intrinsic and a top-frame ``NEW_ACCOUNT`` state charge. """ @@ -177,9 +177,9 @@ def test_creation_tx_intrinsic_across_amsterdam_transition( The same creation transaction (``to=None``, ``STOP`` init code that deploys empty code) is sent in a pre-fork block and a post-fork block, each from a fresh sender with the gas limit pinned exactly. - Pre-fork the whole cost is regular intrinsic: ``TX_BASE`` plus the + Pre-fork the whole cost is execution intrinsic: ``TX_BASE`` plus the flat ``TX_CREATE``. Post-fork the intrinsic keeps only the - ``CREATE_ACCESS`` regular portion of ``TX_CREATE``, while the created + ``CREATE_ACCESS`` execution portion of ``TX_CREATE``, while the created account's ``NEW_ACCOUNT`` is charged as *state* gas at the top frame — the sender-facing total is the sum of both. @@ -204,11 +204,11 @@ def test_creation_tx_intrinsic_across_amsterdam_transition( ) init_code_terms = pre_costs.TX_DATA_TOKEN_STANDARD + 2 - # Pre-fork: flat regular intrinsic, no top-frame charge. + # Pre-fork: flat execution intrinsic, no top-frame charge. expected_pre = pre_costs.TX_BASE + pre_costs.TX_CREATE + init_code_terms # Post-fork: EIP-8037 folds ``NEW_ACCOUNT`` into ``TX_CREATE``; # EIP-2780 moves that state portion to the top frame, leaving the - # ``CREATE_ACCESS`` regular remainder in the intrinsic. + # ``CREATE_ACCESS`` execution remainder in the intrinsic. expected_post = ( post_costs.TX_BASE + (post_costs.TX_CREATE - post_costs.NEW_ACCOUNT) diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_intrinsic_gas_boundary.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_intrinsic_gas_boundary.py index 2292edae54e..6ecaf65362a 100644 --- a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_intrinsic_gas_boundary.py +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_intrinsic_gas_boundary.py @@ -95,7 +95,7 @@ def test_intrinsic_gas_floor_boundary_contract_creation( A creation tx's intrinsic includes the ``NEW_ACCOUNT`` state gas, so the pre-execution check rejects against the combined - ``regular + state`` intrinsic. The init code never runs. + ``execution + state`` intrinsic. The init code never runs. """ sender = pre.fund_eoa(10**18) init_code = Op.STOP diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_top_frame_charges.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_top_frame_charges.py index 9d59d9d7be8..ca889f3f525 100644 --- a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_top_frame_charges.py +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_top_frame_charges.py @@ -8,12 +8,12 @@ - ``NEW_ACCOUNT`` (state gas) when the recipient is empty and the transaction transfers value, or when a creation transaction's target leaf did not exist before the transaction. -- ``COLD_ACCOUNT_ACCESS`` (regular gas) when the recipient holds an +- ``COLD_ACCOUNT_ACCESS`` (execution gas) when the recipient holds an EIP-7702 delegation. Each test parametrizes over the interesting outcomes for that charge: running out of gas at the boundary, succeeding through the charge and -into the EVM, and (for the regular charge) succeeding through the +into the EVM, and (for the execution charge) succeeding through the charge but reverting from the delegated code. For creation transactions, the charge keys on the *transaction pre-state* being empty, and — being consumed on any successful halt — survives the @@ -179,14 +179,14 @@ def test_top_frame_new_account_charged_as_state_gas( ) -> None: """ The top-frame ``NEW_ACCOUNT`` charge for a value transfer to an - empty recipient is *state* gas, not regular gas. This pins the + empty recipient is *state* gas, not execution gas. This pins the dimension via the block header ``gas_used``, which the spec - computes as ``max(block_regular_gas, block_state_gas)``. + computes as ``max(block_execution_gas, block_state_gas)``. Correctly attributed, the ``NEW_ACCOUNT`` state gas dominates the - small regular intrinsic, so ``gas_used == NEW_ACCOUNT``. A - regression mis-classifying the charge as regular gas would instead - yield ``intrinsic_regular + NEW_ACCOUNT``. + small execution intrinsic, so ``gas_used == NEW_ACCOUNT``. A + regression mis-classifying the charge as execution gas would instead + yield ``intrinsic_execution + NEW_ACCOUNT``. ``state_test``-based balance assertions (e.g. ``test_top_frame_state_charge``) only observe the *sum* of the two @@ -197,7 +197,7 @@ def test_top_frame_new_account_charged_as_state_gas( target = pre.fund_eoa(amount=0) value = 1 - intrinsic_regular = fork.transaction_intrinsic_cost_calculator()( + intrinsic_execution = fork.transaction_intrinsic_cost_calculator()( sends_value=True, recipient_type=RecipientType.EMPTY_ACCOUNT, return_cost_deducted_prior_execution=True, @@ -206,26 +206,26 @@ def test_top_frame_new_account_charged_as_state_gas( sends_value=True, recipient_type=RecipientType.EMPTY_ACCOUNT, ) - # The state charge must dominate the regular intrinsic for the - # header ``gas_used`` to distinguish a state vs regular + # The state charge must dominate the execution intrinsic for the + # header ``gas_used`` to distinguish a state vs execution # mis-classification. - assert new_account_state_gas > intrinsic_regular, ( + assert new_account_state_gas > intrinsic_execution, ( "test only distinguishes the dimension when NEW_ACCOUNT " - f"({new_account_state_gas}) dominates the regular intrinsic " - f"({intrinsic_regular})" + f"({new_account_state_gas}) dominates the execution intrinsic " + f"({intrinsic_execution})" ) - # No EVM bytecode runs (empty recipient), so the only regular gas + # No EVM bytecode runs (empty recipient), so the only execution gas # is the intrinsic and the only state gas is the top-frame # ``NEW_ACCOUNT`` charge. - expected_gas_used = max(intrinsic_regular, new_account_state_gas) + expected_gas_used = max(intrinsic_execution, new_account_state_gas) gas_price = 1_000_000_000 tx = Transaction( sender=sender, to=target, value=value, - gas_limit=intrinsic_regular + new_account_state_gas + 1000, + gas_limit=intrinsic_execution + new_account_state_gas + 1000, gas_price=gas_price, ) @@ -439,7 +439,7 @@ def test_top_frame_new_account_skipped_for_create_target_funded_same_block( The funding transaction pays its own top-frame ``NEW_ACCOUNT`` for materializing the leaf, which the block header pins as the block's - entire state-gas dimension: ``gas_used = max(regular, state)`` must + entire state-gas dimension: ``gas_used = max(execution, state)`` must equal exactly one ``NEW_ACCOUNT``. """ funder = pre.fund_eoa() @@ -503,11 +503,11 @@ def test_top_frame_new_account_skipped_for_create_target_funded_same_block( ) # Header pin: the block's state dimension is exactly the funding - # transaction's ``NEW_ACCOUNT``; both regular intrinsics sit at or + # transaction's ``NEW_ACCOUNT``; both execution intrinsics sit at or # above their calldata floors, so no floor term enters the block's - # regular dimension either. - block_regular = fund_intrinsic + create_total - assert fund_state_gas > block_regular, ( + # execution dimension either. + block_execution = fund_intrinsic + create_total + assert fund_state_gas > block_execution, ( "the state dimension must dominate for the header to pin it" ) @@ -537,7 +537,7 @@ def test_top_frame_new_account_skipped_for_create_target_funded_same_block( pytest.param(1, id="non-zero_value"), ], ) -def test_top_frame_regular_charge( +def test_top_frame_execution_charge( fork: Fork, pre: Alloc, state_test: StateTestFiller, @@ -546,15 +546,15 @@ def test_top_frame_regular_charge( ) -> None: """ Recipient is an existing EIP-7702 delegation, so the top-frame - fires the ``COLD_ACCOUNT_ACCESS`` regular-gas charge regardless of + fires the ``COLD_ACCOUNT_ACCESS`` execution-gas charge regardless of whether the transaction transfers value. - - ``oog``: gas limit is one short of covering the regular charge + - ``oog``: gas limit is one short of covering the execution charge (plus the value-transfer charge when ``value > 0``). The transaction OOGs at ``charge_gas(COLD_ACCOUNT_ACCESS)`` before the delegated code runs. The sender pays the full ``gas_limit`` and the recipient keeps its pre-tx state. - - ``success``: gas limit covers the regular charge; the delegated + - ``success``: gas limit covers the execution charge; the delegated code is a ``STOP`` and the transaction lands the value transfer. - ``evm_reverts``: the delegated code reverts immediately. The top-frame charge is consumed before dispatch and the two @@ -584,7 +584,7 @@ def test_top_frame_regular_charge( recipient_type=RecipientType.DELEGATION_7702, ) assert top_frame_gas > 0, ( - "top-frame regular gas must be non-zero for this scenario" + "top-frame execution gas must be non-zero for this scenario" ) gas_price = 1_000_000_000 @@ -691,12 +691,12 @@ def test_initcode_selfdestruct_keeps_top_frame_state_charge( beneficiary = pre.nonexistent_account() # Sweeping a non-zero balance into a non-existent leaf creates # the beneficiary, paying NEW_ACCOUNT (state) and ACCOUNT_WRITE - # (regular) at the opcode. + # (execution) at the opcode. init_code = Op.SELFDESTRUCT.with_metadata( address_warm=False, account_new=bool(value) )(beneficiary) - # Combined regular + state execution gas, including any sweep + # Combined execution + state execution gas, including any sweep # charges modeled by the metadata above. exec_gas = init_code.gas_cost(fork) @@ -756,14 +756,14 @@ def test_initcode_selfdestruct_state_gas_in_header( dimensions, so the sibling ``test_initcode_selfdestruct_keeps_top_frame_state_charge`` cannot distinguish which dimension the surviving charge settled into. The - block header can: ``gas_used = max(block_regular, block_state)``, + block header can: ``gas_used = max(block_execution, block_state)``, and with a zero endowment and a self beneficiary the whole created account vanishes while the state side (one ``NEW_ACCOUNT``, - dominating the small regular side) must still show in the header. + dominating the small execution side) must still show in the header. Bug signatures: a refill regression collapses the header to the - small regular sum; a regular-gas mis-classification raises it to - ``regular + NEW_ACCOUNT``. + small execution sum; an execution-gas mis-classification raises it to + ``execution + NEW_ACCOUNT``. """ sender = pre.fund_eoa() created = compute_create_address(address=sender, nonce=sender.nonce) @@ -771,7 +771,7 @@ def test_initcode_selfdestruct_state_gas_in_header( init_code = Op.SELFDESTRUCT.with_metadata( address_warm=True, account_new=False )(Op.ADDRESS) - exec_regular = init_code.regular_cost(fork) + evm_execution = init_code.execution_cost(fork) intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( calldata=init_code, @@ -785,14 +785,14 @@ def test_initcode_selfdestruct_state_gas_in_header( data=init_code, contract_creation=True, ) - # Block accounting carries the calldata floor in the regular + # Block accounting carries the calldata floor in the execution # dimension. - regular_side = max(intrinsic_gas + exec_regular, calldata_floor) - assert state_side > regular_side, ( + execution_side = max(intrinsic_gas + evm_execution, calldata_floor) + assert state_side > execution_side, ( "the state dimension must dominate for the header to pin it" ) - total_gas = intrinsic_gas + state_side + exec_regular + total_gas = intrinsic_gas + state_side + evm_execution tx = Transaction( sender=sender, to=None, @@ -821,7 +821,7 @@ class TopFrameFailureMode(Enum): CREATE_STATE_OOG = auto() NEW_ACCOUNT_STATE_OOG = auto() - DELEGATED_REGULAR_OOG = auto() + DELEGATED_EXECUTION_OOG = auto() @pytest.mark.parametrize( @@ -836,8 +836,8 @@ class TopFrameFailureMode(Enum): id="new_account_state_oog", ), pytest.param( - TopFrameFailureMode.DELEGATED_REGULAR_OOG, - id="delegated_regular_oog", + TopFrameFailureMode.DELEGATED_EXECUTION_OOG, + id="delegated_execution_oog", ), ], ) @@ -873,8 +873,8 @@ def test_receipt_status_top_frame_oog_between_successful_txs( - ``new_account_state_oog``: value transfer to an empty recipient; the ``NEW_ACCOUNT`` state charge fires and the gas limit is one short. - - ``delegated_regular_oog``: recipient holds an EIP-7702 - delegation; the ``COLD_ACCOUNT_ACCESS`` regular charge fires and + - ``delegated_execution_oog``: recipient holds an EIP-7702 + delegation; the ``COLD_ACCOUNT_ACCESS`` execution charge fires and the gas limit is one short. The failing transaction burns its full gas limit, bumps the sender @@ -929,7 +929,7 @@ def test_receipt_status_top_frame_oog_between_successful_txs( # The rolled-back transfer must not bring the recipient into # existence. fail_target_post = None - elif failure_mode is TopFrameFailureMode.DELEGATED_REGULAR_OOG: + elif failure_mode is TopFrameFailureMode.DELEGATED_EXECUTION_OOG: delegated_to = pre.deploy_contract(code=Op.STOP) target_code = Spec7702.delegation_designation(delegated_to) fail_to = pre.deploy_contract(code=target_code) diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_value_moving_transactions.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_value_moving_transactions.py index 42d694f0c2f..05fd5d9aef1 100644 --- a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_value_moving_transactions.py +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_value_moving_transactions.py @@ -91,7 +91,7 @@ def test_value_moving_transactions( recipient_type=recipient_type, ) # Under the default zero state-gas reservoir, top-frame state gas - # spills entirely into regular gas. + # spills entirely into execution gas. total_gas_cost = intrinsic_gas + top_frame_gas + top_frame_state_gas tx_gas_limit = total_gas_cost @@ -159,7 +159,7 @@ def test_value_contract_creation_tx( When the init code reverts, the deploy is rolled back: no code is set, the value transfer is reversed, and the top-frame ``NEW_ACCOUNT`` state-gas charge for the created account is - refilled. The sender therefore pays only the regular intrinsic + refilled. The sender therefore pays only the execution intrinsic plus the few EVM gas units spent before the revert -- the ``NEW_ACCOUNT`` charge does not appear on the receipt. """ @@ -194,7 +194,7 @@ def test_value_contract_creation_tx( # charge is refilled and does not appear on the receipt. gas_used = intrinsic_gas + execution_gas # A tiny init code can leave the decomposed calldata floor above - # the regular gas actually consumed; gas_used then pins to the + # the execution gas actually consumed; gas_used then pins to the # floor, which EIP-2780 anchors on the create intrinsic base. gas_used = max( gas_used, diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_value_moving_with_tx_delegation.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_value_moving_with_tx_delegation.py index 14e9d4a1fee..71b3d790b6a 100644 --- a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_value_moving_with_tx_delegation.py +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_value_moving_with_tx_delegation.py @@ -6,7 +6,7 @@ Each authorization pays, on top of the state-independent ``REGULAR_PER_AUTH_BASE_COST`` charged in the intrinsic: -- ``NEW_ACCOUNT`` (state) + ``ACCOUNT_WRITE`` (regular) when the +- ``NEW_ACCOUNT`` (state) + ``ACCOUNT_WRITE`` (execution) when the authority's account leaf does not yet exist, and - ``AUTH_BASE`` (state) when a net-new delegation indicator is written. @@ -106,7 +106,7 @@ def test_tx_installs_delegation_on_funded_recipient( authorization_list_or_count=authorization_list, return_cost_deducted_prior_execution=True, ) - top_frame_regular = fork.transaction_top_frame_gas_calculator()( + top_frame_execution = fork.transaction_top_frame_gas_calculator()( sends_value=bool(value), recipient_type=RecipientType.DELEGATION_7702, delegation_warm=False, @@ -119,8 +119,8 @@ def test_tx_installs_delegation_on_funded_recipient( ) # Costs are charged exactly (no refund); under the default zero - # state-gas reservoir the state gas spills into regular gas. - total_gas_cost = intrinsic_gas + top_frame_regular + top_frame_state + # state-gas reservoir the state gas spills into execution gas. + total_gas_cost = intrinsic_gas + top_frame_execution + top_frame_state tx_gas_limit = total_gas_cost + 1000 gas_price = 1_000_000_000 @@ -203,7 +203,7 @@ def test_tx_installs_delegation_on_empty_recipient( authorization_list_or_count=authorization_list, return_cost_deducted_prior_execution=True, ) - top_frame_regular = fork.transaction_top_frame_gas_calculator()( + top_frame_execution = fork.transaction_top_frame_gas_calculator()( sends_value=bool(value), recipient_type=RecipientType.DELEGATION_7702, delegation_warm=False, @@ -215,7 +215,7 @@ def test_tx_installs_delegation_on_empty_recipient( authorizations=authorization_list, ) - total_gas_cost = intrinsic_gas + top_frame_regular + top_frame_state + total_gas_cost = intrinsic_gas + top_frame_execution + top_frame_state tx_gas_limit = total_gas_cost + 1000 gas_price = 1_000_000_000 @@ -333,7 +333,7 @@ def test_tx_installs_delegation_on_sender( authorization_list_or_count=authorization_list, return_cost_deducted_prior_execution=True, ) - top_frame_regular = fork.transaction_top_frame_gas_calculator()( + top_frame_execution = fork.transaction_top_frame_gas_calculator()( sends_value=bool(value), recipient_type=top_frame_recipient_type, delegation_warm=False, @@ -345,7 +345,7 @@ def test_tx_installs_delegation_on_sender( authorizations=authorization_list, ) - total_gas_cost = intrinsic_gas + top_frame_regular + top_frame_state + total_gas_cost = intrinsic_gas + top_frame_execution + top_frame_state tx_gas_limit = total_gas_cost + 1000 gas_price = 1_000_000_000 diff --git a/tests/amsterdam/eip7778_block_gas_accounting_without_refunds/test_gas_accounting.py b/tests/amsterdam/eip7778_block_gas_accounting_without_refunds/test_gas_accounting.py index 987ecf51784..21c1c67cbca 100644 --- a/tests/amsterdam/eip7778_block_gas_accounting_without_refunds/test_gas_accounting.py +++ b/tests/amsterdam/eip7778_block_gas_accounting_without_refunds/test_gas_accounting.py @@ -88,14 +88,14 @@ def build_refund_tx( storage=dict.fromkeys(storage_slots, 1), ) - # Combined gas (regular + state) from intrinsic cost calculator + # Combined gas (execution + state) from intrinsic cost calculator combined_gas_used = intrinsic_cost_calc( calldata=call_data, return_cost_deducted_prior_execution=True, authorization_list_or_count=authorization_list, ) + code.gas_cost(fork) - # EIP-8037: block gas_used only counts regular gas + # EIP-8037: block gas_used only counts execution gas gas_used_pre_refund = combined_gas_used # Calculate refund (still applied to user's balance) @@ -108,7 +108,7 @@ def build_refund_tx( remaining_state_gas = 0 # In the spec, the refund cap uses tx_gas_used_before_refund which is - # tx.gas - gas_left - state_gas_left (combined regular + remaining + # tx.gas - gas_left - state_gas_left (combined execution + remaining # state). combined_before_refund = gas_used_pre_refund + remaining_state_gas @@ -123,7 +123,7 @@ def build_refund_tx( gas_used_post_refund = receipt_gas_used refund_tx_gas_used = max(call_data_floor_cost, gas_used_post_refund) - # gas_limit must cover combined gas (regular + state) + # gas_limit must cover combined gas (execution + state) refund_tx_gas_limit = ( max(call_data_floor_cost, combined_gas_used) + refund_tx_extra_gas ) @@ -217,10 +217,10 @@ def test_simple_gas_accounting( refund_tx_reverts=refund_tx_reverts, ) - # EIP-8037: block gas_used = max(block_regular_gas, block_state_gas), - # with the calldata floor binding the regular dimension. - block_regular = max(gas_used_pre_refund, call_data_floor_cost) - refund_tx_block_gas_used = max(block_regular, tx_state_gas) + # EIP-8037: block gas_used = max(block_execution_gas, block_state_gas), + # with the calldata floor binding the execution dimension. + block_execution = max(gas_used_pre_refund, call_data_floor_cost) + refund_tx_block_gas_used = max(block_execution, tx_state_gas) blockchain_test( pre=pre, @@ -318,7 +318,7 @@ def test_multi_transaction_gas_accounting( extra_tx_intrinsic_gas_cost = intrinsic_cost_calc( calldata=extra_tx_calldata ) - # Block regular gas applies the calldata floor to the actual charge. + # Block execution gas applies the calldata floor to the actual charge. extra_tx_block_gas = max( intrinsic_cost_calc( calldata=extra_tx_calldata, @@ -342,13 +342,13 @@ def test_multi_transaction_gas_accounting( ), ) - # EIP-8037: block_gas_used = max(sum_regular, sum_state) + # EIP-8037: block_gas_used = max(sum_execution, sum_state) # Extra tx has no state gas, so its state gas contribution = 0 - block_regular = gas_used_pre_refund + extra_tx_block_gas + block_execution = gas_used_pre_refund + extra_tx_block_gas block_state = tx_state_gas - total_block_gas_used = max(block_regular, block_state) + total_block_gas_used = max(block_execution, block_state) # The block gas_limit must accommodate extra_tx's full gas_limit - # (floor-inclusive, like its block-regular charge). For + # (floor-inclusive, like its block-execution charge). For # exceed_block_gas_limit=True we set the limit below # total_block_gas_used to test that the extra_tx fails. if exceed_block_gas_limit: @@ -513,10 +513,10 @@ def test_varying_calldata_costs( f"Could not find the call_data with {num_iterations} iterations." ) - # EIP-8037: block gas_used = max(block_regular_gas, block_state_gas), - # with the calldata floor binding the regular dimension. - block_regular = max(gas_used_pre_refund, call_data_floor_cost) - refund_tx_block_gas_used = max(block_regular, tx_state_gas) + # EIP-8037: block gas_used = max(block_execution_gas, block_state_gas), + # with the calldata floor binding the execution dimension. + block_execution = max(gas_used_pre_refund, call_data_floor_cost) + refund_tx_block_gas_used = max(block_execution, tx_state_gas) blockchain_test( pre=pre, @@ -566,10 +566,10 @@ def test_multiple_refund_types_in_one_tx( refund_tx_reverts=refund_tx_reverts, ) - # EIP-8037: block gas_used = max(block_regular_gas, block_state_gas), - # with the calldata floor binding the regular dimension. - block_regular = max(gas_used_pre_refund, call_data_floor_cost) - refund_tx_block_gas_used = max(block_regular, tx_state_gas) + # EIP-8037: block gas_used = max(block_execution_gas, block_state_gas), + # with the calldata floor binding the execution dimension. + block_execution = max(gas_used_pre_refund, call_data_floor_cost) + refund_tx_block_gas_used = max(block_execution, tx_state_gas) blockchain_test( pre=pre, @@ -599,7 +599,7 @@ def test_mixed_gas_regimes( tx3: 1000 zero-byte calldata to STOP (floor binds fee and block gas). The floor binds the tx-level fee (tx_gas_used = max(post_refund, - floor)) and the block's regular dimension (max(pre_refund gas minus + floor)) and the block's execution dimension (max(pre_refund gas minus state gas, floor)) alike. Per-tx sender balance is also asserted to lock in that the floor-binding tx pays `floor * gas_price`, not `pre_refund * gas_price`. @@ -615,7 +615,7 @@ def test_mixed_gas_regimes( tx1_target = pre.deploy_contract(code=tx1_code) tx1_sender = pre.fund_eoa(initial_fund) tx1_data = b"" - # Full intrinsic + execution gas (regular + state) sizes the gas limit + # Full intrinsic + execution gas (execution + state) sizes the gas limit # and the balance charged to the sender. tx1_pre_refund = intrinsic_cost_calc( calldata=tx1_data, @@ -624,7 +624,7 @@ def test_mixed_gas_regimes( tx1_floor = data_floor_calc(data=tx1_data) assert tx1_pre_refund > tx1_floor, "tx1: pre_refund must exceed floor" tx1_contribution = max(tx1_pre_refund, tx1_floor) - # EIP-8037: block gas_used counts only regular gas; the SSTORE-set + # EIP-8037: block gas_used counts only execution gas; the SSTORE-set # state gas lives in the separate state dimension, so the block-level # contribution excludes it. tx1_block_contribution = max( @@ -678,7 +678,7 @@ def test_mixed_gas_regimes( tx3_floor = data_floor_calc(data=tx3_data) assert tx3_floor > tx3_pre_refund, "tx3: floor must bind upward" tx3_fee_gas = max(tx3_pre_refund, tx3_floor) - # The floor binds the block's regular dimension as well as the fee. + # The floor binds the block's execution dimension as well as the fee. tx3_block_contribution = max(tx3_pre_refund, tx3_floor) tx3 = Transaction( to=tx3_target, diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py index b59367bfb09..d9b50654535 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py @@ -3060,7 +3060,7 @@ def test_bal_cross_tx_funding_chain( # to recipients that begin empty, so each pays the value-transfer # intrinsic surcharges plus the top-frame ``NEW_ACCOUNT`` state # charge that fires under EIP-2780. With the default zero - # state-gas reservoir the latter spills entirely into regular gas. + # state-gas reservoir the latter spills entirely into execution gas. forwarding_intrinsic = intrinsic_calc( sends_value=True, recipient_type=RecipientType.EMPTY_ACCOUNT, diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_cross_index.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_cross_index.py index d5b6f59bbd4..5ece8d98570 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_cross_index.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_cross_index.py @@ -2,7 +2,7 @@ Tests for EIP-7928 BAL cross-index tracking. Tests that state changes are correctly tracked across different block indices: -- Index 1..N: Regular transactions +- Index 1..N: Execution transactions - Index N+1: Post-execution system operations Includes tests for system contracts (withdrawal/consolidation) cross-index diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip7702.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip7702.py index fd8a11c1fff..cc0f6fa9aaa 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip7702.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip7702.py @@ -581,7 +581,7 @@ def test_bal_7702_recipient_excluded_on_authorization_oog( auth = build_authorization(pre, AuthorizationAction.CREATES_ACCOUNT) authorization_list = [auth.authorization] - intrinsic_regular = fork.transaction_intrinsic_cost_calculator()( + intrinsic_execution = fork.transaction_intrinsic_cost_calculator()( recipient_type=RecipientType.CONTRACT, authorization_list_or_count=authorization_list, return_cost_deducted_prior_execution=True, @@ -592,12 +592,12 @@ def test_bal_7702_recipient_excluded_on_authorization_oog( if outcome == "oog": # The authorization runs out at its opening NEW_ACCOUNT state # charge, drawn from gas_left under the zero state reservoir. - gas_limit = intrinsic_regular + fork.gas_costs().NEW_ACCOUNT - 1 + gas_limit = intrinsic_execution + fork.gas_costs().NEW_ACCOUNT - 1 recipient_expectation = None authority_expectation = BalAccountExpectation.empty() expected_authority = auth.original_account else: - top_frame_regular = fork.transaction_top_frame_gas_calculator()( + top_frame_execution = fork.transaction_top_frame_gas_calculator()( recipient_type=RecipientType.CONTRACT, authorizations=authorization_list, ) @@ -605,7 +605,7 @@ def test_bal_7702_recipient_excluded_on_authorization_oog( recipient_type=RecipientType.CONTRACT, authorizations=authorization_list, ) - gas_limit = intrinsic_regular + top_frame_regular + top_frame_state + gas_limit = intrinsic_execution + top_frame_execution + top_frame_state recipient_expectation = BalAccountExpectation.empty() authority_expectation = BalAccountExpectation( nonce_changes=[BalNonceChange(block_access_index=1, post_nonce=1)], diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py index 3392b80ff0a..e6ca49ddbd1 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py @@ -368,7 +368,7 @@ def test_bal_account_touch_system_address( access_opcode: Callable[[Address], Bytecode], ) -> None: """ - Ensure a regular transaction that explicitly touches SYSTEM_ADDRESS via + Ensure a normal transaction that explicitly touches SYSTEM_ADDRESS via an account-accessing opcode includes SYSTEM_ADDRESS as an account-only BAL entry. @@ -3092,7 +3092,7 @@ def test_bal_transient_storage_not_tracked( """ alice = pre.fund_eoa() - # Contract that uses transient storage then persists to regular storage + # Contract that uses transient storage then persists to execution storage contract_code = ( # TSTORE slot 0x01 with value 0x42 (transient storage) Op.TSTORE(0x01, 0x42) 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 808d911d3cd..d6b52684e67 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 @@ -162,7 +162,7 @@ def test_max_code_size_deposit_gas( gas_limit=( intrinsic_gas + top_frame_state_gas - + initcode.execution_gas(fork) + + initcode.evm_gas(fork) + initcode.deployment_gas(fork) - gas_shortfall ), diff --git a/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_additional_coverage.py b/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_additional_coverage.py index 8a19de64cec..7392728dc57 100644 --- a/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_additional_coverage.py +++ b/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_additional_coverage.py @@ -702,16 +702,16 @@ def test_authorization_list_intrinsic_gas( Verify the authorization-list intrinsic cost under EIP-2780. Each authorization adds exactly ``REGULAR_PER_AUTH_BASE_COST`` to - the (regular) intrinsic; the state-dependent authorization costs + the (execution) intrinsic; the state-dependent authorization costs moved to the top frame. Measured on the *raw* intrinsic (before the EIP-7623 calldata floor is applied) the per-authorization delta is exactly ``num_authorizations * REGULAR_PER_AUTH_BASE_COST`` -- even when the floor would otherwise mask it (e.g. a single authorization whose base cost stays below the floor). Each existing authority then pays the - first-write ``ACCOUNT_WRITE`` (regular) and ``AUTH_BASE`` + first-write ``ACCOUNT_WRITE`` (execution) and ``AUTH_BASE`` (state) at the top frame, so with a STOP recipient the receipt - is ``max(intrinsic_regular + num_authorizations * + is ``max(intrinsic_execution + num_authorizations * (ACCOUNT_WRITE + AUTH_BASE), floor_cost)``. """ gas_costs = fork.gas_costs() @@ -762,17 +762,17 @@ def test_authorization_list_intrinsic_gas( data=calldata ) # Existing authorities pay the first-write ACCOUNT_WRITE - # (regular) and AUTH_BASE (state) each at the top frame; the + # (execution) and AUTH_BASE (state) each at the top frame; the # STOP recipient does no execution, so the receipt is - # max(regular + state, floor). - top_frame_regular = fork.transaction_top_frame_gas_calculator()( + # max(execution + state, floor). + top_frame_execution = fork.transaction_top_frame_gas_calculator()( authorizations=authorization_list, ) top_frame_state = fork.transaction_top_frame_state_gas( authorizations=authorization_list, ) expected_gas = max( - intrinsic_with_auth + top_frame_regular + top_frame_state, + intrinsic_with_auth + top_frame_execution + top_frame_state, floor_cost, ) diff --git a/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_floor_boundary_exact_balance.py b/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_floor_boundary_exact_balance.py index 1dddc735d39..8b2be28ff78 100644 --- a/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_floor_boundary_exact_balance.py +++ b/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_floor_boundary_exact_balance.py @@ -49,7 +49,7 @@ def test_below_amsterdam_floor_with_exact_balance_sender( `test_transaction_validity.py`. """ tx_data = Bytes(b"\x00" * zero_bytes) - intrinsic_regular = fork.transaction_intrinsic_cost_calculator()( + intrinsic_execution = fork.transaction_intrinsic_cost_calculator()( calldata=tx_data, return_cost_deducted_prior_execution=True, ) @@ -62,7 +62,7 @@ def test_below_amsterdam_floor_with_exact_balance_sender( # (zero/nonzero both weighted by 4). prague_floor = 21000 + Spec7623.TX_DATA_TOKEN_FLOOR * zero_bytes gas_limit = (prague_floor + amsterdam_floor) // 2 - assert intrinsic_regular <= gas_limit + assert intrinsic_execution <= gas_limit assert prague_floor <= gas_limit < amsterdam_floor gas_price = 10 diff --git a/tests/amsterdam/eip7981_increase_access_list_cost/test_floor_boundary_exact_balance.py b/tests/amsterdam/eip7981_increase_access_list_cost/test_floor_boundary_exact_balance.py index a6ddbcc7747..60eafcd0efb 100644 --- a/tests/amsterdam/eip7981_increase_access_list_cost/test_floor_boundary_exact_balance.py +++ b/tests/amsterdam/eip7981_increase_access_list_cost/test_floor_boundary_exact_balance.py @@ -57,7 +57,7 @@ def test_below_amsterdam_floor_with_access_list_exact_balance( ) ] tx_data = Bytes(b"\x01" * nonzero_bytes) - intrinsic_regular = fork.transaction_intrinsic_cost_calculator()( + intrinsic_execution = fork.transaction_intrinsic_cost_calculator()( calldata=tx_data, access_list=access_list, return_cost_deducted_prior_execution=True, @@ -68,7 +68,7 @@ def test_below_amsterdam_floor_with_access_list_exact_balance( # Pin gas_limit inside the access-list-byte uplift gap so an # implementation that omits this term from its floor accepts. gas_limit = (amsterdam_floor_no_al + amsterdam_floor) // 2 - assert intrinsic_regular <= gas_limit < amsterdam_floor + assert intrinsic_execution <= gas_limit < amsterdam_floor assert gas_limit >= amsterdam_floor_no_al gas_price = 10 diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/spec.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/spec.py index e807c34ebd4..ced4110a55e 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/spec.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/spec.py @@ -47,10 +47,10 @@ class Spec: STATE_BYTES_PER_STORAGE_SET = 64 STATE_BYTES_PER_AUTH_BASE = 23 - # Regular gas constants. EIP-8037 separated state from regular gas; + # Execution gas constants. EIP-8037 separated state from execution gas; # EIP-8038 then repriced them. - REGULAR_GAS_CREATE = 11000 - # Total regular intrinsic per EIP-7702 authorization: + EXECUTION_GAS_CREATE = 11000 + # Total execution intrinsic per EIP-7702 authorization: # ACCOUNT_WRITE (8000) + REGULAR_PER_AUTH_BASE_COST (7816). PER_AUTH_BASE_COST = 15816 GAS_COLD_STORAGE_WRITE = 13000 diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py index 662af4980f2..c1d358afc6a 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py @@ -2,7 +2,7 @@ Test block-level two-dimensional gas accounting under EIP-8037. Verify that the block header gas_used equals -max(block_regular_gas_used, block_state_gas_used) across +max(block_execution_gas_used, block_state_gas_used) across single-block, multi-block, and mixed-transaction scenarios. Tests for [EIP-8037: State Creation Gas Cost Increase] @@ -39,9 +39,9 @@ def sstore_tx_gas(fork: Fork, num_sstores: int = 1) -> tuple[int, int]: - """Return (regular, state) gas for a tx with N cold SSTOREs.""" + """Return (execution, state) gas for a tx with N cold SSTOREs.""" intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() - evm_total = num_sstores * Op.SSTORE(0, 1).regular_cost(fork) + evm_total = num_sstores * Op.SSTORE(0, 1).execution_cost(fork) state = num_sstores * Op.SSTORE(new_value=1).state_cost(fork) return intrinsic_gas + evm_total, state @@ -112,20 +112,20 @@ def test_block_gas_used_state_dominates( num_sstores: int, ) -> None: """ - Verify block.gas_used = block_state_gas when state > regular. + Verify block.gas_used = block_state_gas when state > execution. Each tx performs zero-to-nonzero SSTOREs. Since state gas per - SSTORE exceeds regular gas, block_state_gas exceeds - block_regular_gas and becomes the header gas_used. + SSTORE exceeds execution gas, block_state_gas exceeds + block_execution_gas and becomes the header gas_used. The spillover variant provides reservoir for only one SSTORE per tx; the remaining state gas spills into gas_left. Block-level accounting must still separate the two dimensions. """ - tx_regular, tx_state = sstore_tx_gas(fork, num_sstores) - block_regular = num_txs * tx_regular + tx_execution, tx_state = sstore_tx_gas(fork, num_sstores) + block_execution = num_txs * tx_execution block_state = num_txs * tx_state - assert block_state > block_regular + assert block_state > block_execution txs, post = sstore_txs( pre, @@ -146,18 +146,18 @@ def test_block_gas_used_state_dominates( @pytest.mark.valid_from("EIP8037") -def test_block_gas_used_regular_dominates( +def test_block_gas_used_execution_dominates( blockchain_test: BlockchainTestFiller, pre: Alloc, fork: Fork, ) -> None: """ - Verify block.gas_used = block_regular_gas when state gas is zero. + Verify block.gas_used = block_execution_gas when state gas is zero. A block containing only STOP transactions to existing contracts produces no state gas. The block header gas_used must equal the - sum of regular gas across all transactions, since - max(regular, 0) = regular. + sum of execution gas across all transactions, since + max(execution, 0) = execution. """ num_txs = 3 intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() @@ -194,16 +194,18 @@ def test_block_gas_used_mixed_txs( """ Verify block.gas_used with mixed STOP and SSTORE transactions. - STOP txs contribute only regular gas; SSTORE txs contribute both. + STOP txs contribute only execution gas; SSTORE txs contribute both. The interleaved variant alternates SSTORE/STOP to test that non-contiguous state gas contributions accumulate correctly. """ intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() - tx_regular_sstore, tx_state_sstore = sstore_tx_gas(fork) + tx_execution_sstore, tx_state_sstore = sstore_tx_gas(fork) - block_regular = num_stop * intrinsic_gas + num_sstore * tx_regular_sstore + block_execution = ( + num_stop * intrinsic_gas + num_sstore * tx_execution_sstore + ) block_state = num_sstore * tx_state_sstore - expected = max(block_regular, block_state) + expected = max(block_execution, block_state) txs_sstore, post = sstore_txs(pre, fork, num_sstore) txs_stop = stop_txs(pre, fork, num_stop) @@ -239,7 +241,7 @@ def test_block_gas_refund_eip7778_no_block_reduction( """ Verify block gas accounting for SSTORE 0→x→0 refund paths. - Regular gas refund via `refund_counter` does NOT reduce block gas + Execution gas refund via `refund_counter` does NOT reduce block gas (EIP-7778). State gas refund goes to the reservoir and DOES reduce `block_state_gas_used` (net zero state growth). """ @@ -254,8 +256,8 @@ def test_block_gas_refund_eip7778_no_block_reduction( current_value=1, new_value=0, )(0, 0) - tx_regular = intrinsic_gas + code.gas_cost(fork) - sstore_state_gas - expected = num_txs * tx_regular + tx_execution = intrinsic_gas + code.gas_cost(fork) - sstore_state_gas + expected = num_txs * tx_execution txs = [] for _ in range(num_txs): contract = pre.deploy_contract(code=code) @@ -297,9 +299,9 @@ def test_block_2d_gas_boundary_exact_fit( num_sstores: int, ) -> None: """ - Verify a block is valid when state gas dominates regular gas. + Verify a block is valid when state gas dominates execution gas. - Clients that sum regular + state will reject this valid block. + Clients that sum execution + state will reject this valid block. """ block_gas_limit = 30_000_000 while True: @@ -311,17 +313,17 @@ def test_block_2d_gas_boundary_exact_fit( env = Environment( gas_limit=block_gas_limit, ) - tx_regular, tx_state = sstore_tx_gas(fork, num_sstores) - intrinsic_regular = fork.transaction_intrinsic_cost_calculator()() + tx_execution, tx_state = sstore_tx_gas(fork, num_sstores) + intrinsic_execution = fork.transaction_intrinsic_cost_calculator()() - tx_limit = tx_regular + tx_state + tx_regular // 10 + tx_limit = tx_execution + tx_state + tx_execution // 10 - # Per-tx worst-case state contribution: tx.gas - intrinsic_regular. + # Per-tx worst-case state contribution: tx.gas - intrinsic_execution. # The block_gas_limit must leave enough state budget for every tx. - worst_state_per_tx = tx_limit - intrinsic_regular + worst_state_per_tx = tx_limit - intrinsic_execution minimum_block_gas_limit = max( - # Regular dimension: last tx must fit. - (num_txs - 1) * tx_regular + tx_limit, + # Execution dimension: last tx must fit. + (num_txs - 1) * tx_execution + tx_limit, # State dimension: cumulative worst-case must fit. num_txs * worst_state_per_tx, ) @@ -329,9 +331,9 @@ def test_block_2d_gas_boundary_exact_fit( break block_gas_limit += 1_000_000 - block_regular = num_txs * tx_regular + block_execution = num_txs * tx_execution block_state = num_txs * tx_state - expected_gas_used = max(block_regular, block_state) + expected_gas_used = max(block_execution, block_state) txs, post = sstore_txs( pre, @@ -416,16 +418,16 @@ def test_block_gas_used_create_tx( create_state_gas = fork.create_state_gas(code_size=0) init_code = bytes(Op.STOP) - create_regular = ( + create_execution = ( intrinsic_calc( calldata=init_code, contract_creation=True, ) - create_state_gas ) - stop_regular = intrinsic_calc() + stop_execution = intrinsic_calc() - expected = max(create_regular + stop_regular, create_state_gas) + expected = max(create_execution + stop_execution, create_state_gas) txs = [ Transaction( @@ -457,13 +459,13 @@ def test_multi_block_dimension_flip( """ Verify gas_used across blocks where dominant dimension flips. - Block 1: STOP txs only (regular dominates). + Block 1: STOP txs only (execution dominates). Block 2: SSTORE txs only (state dominates). Each block independently computes its own 2D max. """ n = 3 intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() - tx_regular, tx_state = sstore_tx_gas(fork) + tx_execution, tx_state = sstore_tx_gas(fork) block_1 = stop_txs(pre, fork, n) block_2, post_2 = sstore_txs(pre, fork, n) @@ -478,7 +480,7 @@ def test_multi_block_dimension_flip( Block( txs=block_2, header_verify=Header( - gas_used=max(n * tx_regular, n * tx_state), + gas_used=max(n * tx_execution, n * tx_state), ), ), ], @@ -538,7 +540,7 @@ def test_tx_gas_limit_block_boundary( Reject tx whose ``gas_limit`` exceeds the block ``gas_limit``. EIP-8037 inclusion rule: ``min(TX_MAX_GAS_LIMIT, tx.gas) <= - regular_gas_available`` and ``tx.gas <= state_gas_available``. + execution_gas_available`` and ``tx.gas <= state_gas_available``. At block start both budgets equal ``block_gas_limit``. """ gas_limit = block_gas_limit + tx_gas_delta @@ -612,16 +614,16 @@ def test_tx_gas_limit_block_boundary( # EIP-8037 novelty. Floor is Osaka only because the gas-cap guard # below relies on EIP-7825's transaction_gas_limit_cap(). @pytest.mark.valid_from("Osaka") -def test_tx_inclusion_at_regular_gas_block_limit_small( +def test_tx_inclusion_at_execution_gas_block_limit_small( blockchain_test: BlockchainTestFiller, pre: Alloc, fork: Fork, delta: int, ) -> None: """ - Probe the regular-gas inclusion boundary with a small-gas tx. + Probe the execution-gas inclusion boundary with a small-gas tx. - The second tx's ``gas_limit`` is the remaining regular budget + The second tx's ``gas_limit`` is the remaining execution budget plus ``delta``. The inclusion check uses strict ``>``, so ``delta=0`` must pass and ``delta=1`` must reject with ``GAS_ALLOWANCE_EXCEEDED``. Catches an off-by-one ``>=`` bug. @@ -683,7 +685,7 @@ def test_tx_inclusion_at_regular_gas_block_limit_small( ], ) @pytest.mark.valid_from("EIP8037") -def test_block_2d_gas_tx_gas_limit_exceeds_regular_remaining( +def test_block_2d_gas_tx_gas_limit_exceeds_execution_remaining( blockchain_test: BlockchainTestFiller, pre: Alloc, fork: Fork, @@ -691,7 +693,7 @@ def test_block_2d_gas_tx_gas_limit_exceeds_regular_remaining( ) -> None: """ Verify a block is valid when a later tx's gas_limit exceeds the - regular budget remaining but its capped regular contribution fits. + execution budget remaining but its capped execution contribution fits. """ gas_limit_cap = fork.transaction_gas_limit_cap() assert gas_limit_cap is not None @@ -714,9 +716,9 @@ def test_block_2d_gas_tx_gas_limit_exceeds_regular_remaining( code=Op.SSTORE(storage.store_next(1), 1), ) - tx1_regular = intrinsic_gas - tx2_regular, tx2_state = sstore_tx_gas(fork) - expected_gas_used = max(tx1_regular + tx2_regular, tx2_state) + tx1_execution = intrinsic_gas + tx2_execution, tx2_state = sstore_tx_gas(fork) + expected_gas_used = max(tx1_execution + tx2_execution, tx2_state) blockchain_test( pre=pre, @@ -751,11 +753,11 @@ def test_receipt_cumulative_differs_from_header_gas_used( Verify receipt cumulative_gas_used can diverge from header gas_used under 2D accounting when state gas dominates. """ - tx_regular, tx_state = sstore_tx_gas(fork) + tx_execution, tx_state = sstore_tx_gas(fork) num_txs = 3 sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) - per_tx_gas_used = tx_regular + tx_state + per_tx_gas_used = tx_execution + tx_state txs: list[Transaction] = [] post: dict = {} @@ -776,11 +778,11 @@ def test_receipt_cumulative_differs_from_header_gas_used( ) post[contract] = Account(storage=storage) - block_regular = num_txs * tx_regular + block_execution = num_txs * tx_execution block_state = num_txs * tx_state - header_gas_used = max(block_regular, block_state) + header_gas_used = max(block_execution, block_state) - assert block_state > block_regular + assert block_state > block_execution assert header_gas_used < num_txs * per_tx_gas_used blockchain_test( @@ -795,7 +797,7 @@ def test_receipt_cumulative_differs_from_header_gas_used( ) -@pytest.mark.parametrize("dominant_dimension", ["state", "regular"]) +@pytest.mark.parametrize("dominant_dimension", ["state", "execution"]) @pytest.mark.parametrize( "single_tx", [ @@ -815,8 +817,8 @@ def test_base_fee_per_gas_follows_dominant_dimension( Verify the child block's base fee follows the bottleneck dimension. Block 1 exceeds the gas target on one dimension only: state, via - SSTORE-set txs that spill, or regular, via STOP/MSTORE txs. Its header - gas_used = max(regular, state) is then set by that dimension alone, + SSTORE-set txs that spill, or execution, via STOP/MSTORE txs. Its header + gas_used = max(execution, state) is then set by that dimension alone, which lifts empty block 2's base fee under the EIP-1559 update. """ genesis_base_fee = 10**9 @@ -831,36 +833,40 @@ def test_base_fee_per_gas_follows_dominant_dimension( if single_tx: num_txs = 1 num_sstores = target // sstore_tx_gas(fork, num_sstores=1)[1] + 1 - tx_regular, tx_state = sstore_tx_gas(fork, num_sstores=num_sstores) + tx_execution, tx_state = sstore_tx_gas( + fork, num_sstores=num_sstores + ) else: num_sstores = 1 - tx_regular, tx_state = sstore_tx_gas(fork, num_sstores=num_sstores) - while tx_regular >= tx_state: + tx_execution, tx_state = sstore_tx_gas( + fork, num_sstores=num_sstores + ) + while tx_execution >= tx_state: num_sstores += 1 - tx_regular, tx_state = sstore_tx_gas( + tx_execution, tx_state = sstore_tx_gas( fork, num_sstores=num_sstores ) num_txs = target // tx_state + 1 - block_regular = num_txs * tx_regular + block_execution = num_txs * tx_execution block_state = num_txs * tx_state - tx_gas_limit = tx_regular + tx_state - assert block_state > target > block_regular + tx_gas_limit = tx_execution + tx_state + assert block_state > target > block_execution else: if single_tx: num_txs = 1 # Just consume all gas - regular_contract = pre.deploy_contract( + execution_contract = pre.deploy_contract( code=Op.MSTORE(offset=2**256 - 1, value=1) + Op.STOP ) tx_gas_limit = target + 1 else: tx_gas_limit = fork.transaction_intrinsic_cost_calculator()() - # Enough STOP txs that regular gas alone clears the target. - regular_contract = pre.deploy_contract(code=Op.STOP) + # Enough STOP txs that execution gas alone clears the target. + execution_contract = pre.deploy_contract(code=Op.STOP) num_txs = target // tx_gas_limit + 1 - block_regular = num_txs * tx_gas_limit + block_execution = num_txs * tx_gas_limit block_state = 0 - assert block_regular > target > block_state + assert block_execution > target > block_state for _ in range(num_txs): if dominant_dimension == "state": @@ -872,7 +878,7 @@ def test_base_fee_per_gas_follows_dominant_dimension( contract = pre.deploy_contract(code=code) post[contract] = Account(storage=storage) else: - contract = regular_contract + contract = execution_contract txs.append( Transaction( to=contract, @@ -883,7 +889,7 @@ def test_base_fee_per_gas_follows_dominant_dimension( ) ) - block_1_gas_used = max(block_regular, block_state) + block_1_gas_used = max(block_execution, block_state) assert block_1_gas_used < gas_limit, ( "test needs update: gas_limit reached by usage, simply raise the " "anchored gas_limit value" @@ -950,7 +956,7 @@ def test_cumulative_block_state_gas_boundary( state gas reaches block_state_gas_used only via spillover, and its gas_limit exactly fills the block. tx2's gas_limit is the remaining state budget plus delta, below both the per-tx cap and the remaining - regular budget, so only the state gate can reject it: delta=0 must + execution budget, so only the state gate can reject it: delta=0 must be accepted (strict >) and delta=1 rejected. test_block_state_gas_limit_boundary covers this gate with a reservoir-funded tx1 and an above-cap tx2. @@ -960,13 +966,13 @@ def test_cumulative_block_state_gas_boundary( sstore_code = ( sum((Op.SSTORE(i, 1) for i in range(n)), Bytecode()) + Op.STOP ) - tx1_regular = intrinsic + sstore_code.regular_cost(fork) + tx1_execution = intrinsic + sstore_code.execution_cost(fork) tx1_state = sstore_code.state_cost(fork) - # tx1 exactly fills the block; the leftover state budget is tx1_regular. - block_gas_limit = tx1_regular + tx1_state - # tx2 stays within the remaining regular budget, so only the state + # tx1 exactly fills the block; the leftover state budget is tx1_execution. + block_gas_limit = tx1_execution + tx1_state + # tx2 stays within the remaining execution budget, so only the state # dimension can reject it. - assert tx1_regular + 1 <= block_gas_limit - tx1_regular + assert tx1_execution + 1 <= block_gas_limit - tx1_execution sstore_contract = pre.deploy_contract(code=sstore_code) stop_contract = pre.deploy_contract(code=Op.STOP) @@ -977,7 +983,7 @@ def test_cumulative_block_state_gas_boundary( ) tx2 = Transaction( to=stop_contract, - gas_limit=tx1_regular + delta, + gas_limit=tx1_execution + delta, sender=pre.fund_eoa(), error=error, ) @@ -987,7 +993,7 @@ def test_cumulative_block_state_gas_boundary( if not delta: post = {sstore_contract: Account(storage=dict.fromkeys(range(n), 1))} header_verify = Header( - gas_used=max(tx1_regular + intrinsic, tx1_state) + gas_used=max(tx1_execution + intrinsic, tx1_state) ) blockchain_test( diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py index 170217fa7cf..79e81ad7deb 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py @@ -7,7 +7,7 @@ that spilled into `gas_left` returns there and the reservoir-funded portion restores the reservoir. An exceptional halt likewise resets the reservoir to its start-of-frame value, but the spilled portion stays -consumed as regular gas with the rest of `gas_left`. +consumed as execution gas with the rest of `gas_left`. All CALL-family opcodes (CALL, DELEGATECALL, STATICCALL) pass the full reservoir to child frames. @@ -171,7 +171,7 @@ def test_reservoir_returned_on_oog( """ Test state gas reservoir is returned to parent on child OOG. - The child runs out of regular gas. The parent recovers the + The child runs out of execution gas. The parent recovers the reservoir and can use it for its own state operations. """ sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) @@ -260,7 +260,7 @@ def test_reservoir_restored_after_child_spill_and_halt( reservoir and spilling into `gas_left`, then hits INVALID causing an exceptional halt. The child's halt resets its frame to (0, R0_child) — only the reservoir-portion is returned to the - parent; the spilled gas stays burned (re-classified as regular). + parent; the spilled gas stays burned (re-classified as execution). The parent does two SSTOREs: the first drains the recovered reservoir, the second spills from the parent's own `gas_left`. """ @@ -665,7 +665,7 @@ def test_gas_opcode_excludes_reservoir( ) # Verify: slot 0 should hold a value <= TX_MAX_GAS_LIMIT - # (gas_left is capped by TX_MAX_GAS_LIMIT - intrinsic.regular) + # (gas_left is capped by TX_MAX_GAS_LIMIT - intrinsic.execution) # We can't check the exact value, but we verify the SSTORE # succeeded and the contract executed correctly post = {contract: Account(storage=storage)} @@ -820,14 +820,14 @@ def test_call_pre_charged_costs_excluded_from_forwarding( child_code = Op.SSTORE(child_storage.store_next(1, "child_ran"), 1) child = pre.deploy_contract(child_code) - child_regular_gas = child_code.regular_cost(fork) + child_execution_gas = child_code.execution_cost(fork) # Memory expansion triggered by ret_size on the wrapper's CALL ret_size = 512 * 32 # 512 words memory_cost = fork.memory_expansion_gas_calculator()(new_bytes=ret_size) # Wrapper: CALL child requesting max gas with memory expansion. The - # memory metadata makes `wrapper_code.regular_cost(fork)` fold the + # memory metadata makes `wrapper_code.execution_cost(fork)` fold the # cold access, the 7 argument pushes and the memory expansion. wrapper_code = Op.CALL( gas=0xFFFFFFFF, @@ -844,9 +844,9 @@ def test_call_pre_charged_costs_excluded_from_forwarding( # After the up-front pre-charge, the wrapper has gas_remaining left. # The 63/64 rule should forward gas_remaining * 63/64 to the child — # just enough for its SSTORE. - gas_remaining = child_regular_gas * 64 // 63 + memory_cost // 2 + gas_remaining = child_execution_gas * 64 // 63 + memory_cost // 2 - wrapper_gas = wrapper_code.regular_cost(fork) + gas_remaining + wrapper_gas = wrapper_code.execution_cost(fork) + gas_remaining caller = pre.deploy_contract( Op.POP(Op.CALL(gas=wrapper_gas, address=wrapper)) @@ -877,7 +877,7 @@ def test_call_new_account_header_gas_used( A contract CALLs a non-existent address with value, charging GAS_NEW_ACCOUNT state gas. The block must be accepted with - correct 2D max(regular, state) accounting in the header. + correct 2D max(execution, state) accounting in the header. """ target = pre.fund_eoa(amount=0) @@ -1207,7 +1207,7 @@ def test_call_value_to_pre_existing_selfdestructed_account( new account creation gate does not fire. Several cold SSTOREs after the CALLs make block state gas - dominate the block regular gas component, so the block header + dominate the block execution gas component, so the block header reflects exactly `num_probes * sstore_state_gas`. A spurious new account charge on the value bearing CALL would push the header up by that charge, breaking the assertion. @@ -1215,7 +1215,7 @@ def test_call_value_to_pre_existing_selfdestructed_account( sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) # Enough probes that the combined probe state gas dominates the - # transaction's regular gas component and the header reflects + # transaction's execution gas component and the header reflects # block state gas alone. num_probes = 6 probe_state_gas = num_probes * sstore_state_gas @@ -1400,7 +1400,7 @@ def test_create_oog_during_state_gas_charge( """ Verify the parent reservoir is refunded when a child's CREATE OOGs while charging account-creation state gas. The grandchild - SSTORE is forwarded only its regular stipend, so it succeeds + SSTORE is forwarded only its execution stipend, so it succeeds only if the refund landed in the reservoir (not in `gas_left`). """ sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) @@ -1426,7 +1426,7 @@ def test_create_oog_during_state_gas_charge( grandchild_code = Op.SSTORE(grandchild_storage.store_next(1, "ran"), 1) grandchild = pre.deploy_contract(code=grandchild_code) - grandchild_stipend = grandchild_code.regular_cost(fork) + grandchild_stipend = grandchild_code.execution_cost(fork) parent = pre.deploy_contract( code=( @@ -1449,14 +1449,14 @@ def test_create_oog_during_state_gas_charge( @pytest.mark.valid_from("EIP8037") -def test_call_new_account_no_regular_account_creation_cost( +def test_call_new_account_no_execution_account_creation_cost( state_test: StateTestFiller, pre: Alloc, fork: Fork, ) -> None: """ Verify CALL with value to a non-existent account does not - charge a regular account-creation cost on top of state gas. + charge an execution-gas account-creation cost on top of state gas. """ target = pre.fund_eoa(amount=0) @@ -1474,8 +1474,8 @@ def test_call_new_account_no_regular_account_creation_cost( ) caller = pre.deploy_contract(code=caller_code, balance=1) - # Tight budget: slack is less than the old pre-Amsterdam regular - # account-creation cost, so any extra regular draw would OOG. + # Tight budget: slack is less than the old pre-Amsterdam execution + # account-creation cost, so any extra execution draw would OOG. intrinsic = fork.transaction_intrinsic_cost_calculator()() tx = Transaction( to=caller, @@ -1563,7 +1563,7 @@ def test_child_failure_refunds_state_gas_to_reservoir_not_gas_left( """ Verify state gas from a failing child is restored to the reservoir, so a sibling probe SSTORE can draw from it under a - tight regular stipend. Covers SSTORE and CALL-value (new + tight execution stipend. Covers SSTORE and CALL-value (new account) state-gas charge paths. """ sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) @@ -1590,7 +1590,7 @@ def test_child_failure_refunds_state_gas_to_reservoir_not_gas_left( child = pre.deploy_contract(code=child_code, balance=child_balance) probe = pre.deploy_contract(probe_code) - probe_stipend = probe_code.regular_cost(fork) + probe_stipend = probe_code.execution_cost(fork) parent = pre.deploy_contract( code=( @@ -1641,7 +1641,7 @@ def test_call_insufficient_balance_refunds_new_account_state_gas( probe_code = Op.SSTORE(probe_storage.store_next(1, "probe_ran"), 1) probe = pre.deploy_contract(probe_code) - probe_stipend = probe_code.regular_cost(fork) + probe_stipend = probe_code.execution_cost(fork) non_existent_account = pre.nonexistent_account() @@ -1690,7 +1690,7 @@ def test_call_value_precompile_halt_refunds_new_account_state_gas( probe_code = Op.SSTORE(probe_storage.store_next(1, "probe_ran"), 1) probe = pre.deploy_contract(probe_code) - probe_stipend = probe_code.regular_cost(fork) + probe_stipend = probe_code.execution_cost(fork) ecpairing = 0x08 @@ -1740,7 +1740,7 @@ def test_call_value_new_account_state_gas_consumed_on_caller_halt( in the child and the charge is refilled to `gas_left` in LIFO order. The caller then hits `INVALID`; the halt burns all of `gas_left`, including the spilled charge, and resets the reservoir to its start-of-frame value. - The sender pays the full regular budget: the whole `gas_limit` in-cap, or + The sender pays the full execution budget: the whole `gas_limit` in-cap, or the EIP-7825 gas cap over-cap (the restored reservoir is refunded). The value transfer is rolled back, leaving `target` absent and the caller balance intact. @@ -1812,7 +1812,7 @@ def test_call_value_new_account_state_gas_returned_on_caller_revert( caller ends with `REVERT`. A revert refills the frame state gas in LIFO order: the spilled portion returns to `gas_left` and the reservoir-funded portion restores the reservoir, both refunded to the sender. The sender - pays only the regular execution gas, the same value in-cap and over-cap, + pays only the execution gas, the same value in-cap and over-cap, and the value transfer is rolled back. """ value = 1 @@ -1827,14 +1827,14 @@ def test_call_value_new_account_state_gas_returned_on_caller_revert( caller = pre.deploy_contract(code=caller_code, balance=value) sender = pre.fund_eoa() - # Only regular execution is billed: the spilled and reservoir-funded + # Only execution gas is billed: the spilled and reservoir-funded # parts of the NEW_ACCOUNT charge are both refunded, so the cost - # matches in-cap and over-cap. `regular_cost` covers the pushes, cold + # matches in-cap and over-cap. `execution_cost` covers the pushes, cold # access and the value transfer (NEW_ACCOUNT lands in the state # dimension); the empty child returns its stipend unused. expected_gas_used = ( fork.transaction_intrinsic_cost_calculator()() - + caller_code.regular_cost(fork) + + caller_code.execution_cost(fork) - fork.call_value_stipend() ) receipt = TransactionReceipt(cumulative_gas_used=expected_gas_used) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_calldata_floor.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_calldata_floor.py index 2a58f8e6d55..08ab7d6cd23 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_calldata_floor.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_calldata_floor.py @@ -1,11 +1,11 @@ """ Test EIP-7623 calldata floor interaction with EIP-8037 state gas. -The calldata floor applies to the regular gas dimension only. It +The calldata floor applies to the execution gas dimension only. It does not affect state gas. Block gas accounting applies the floor to -the regular dimension (``max(pre_refund_gas - state_gas, floor)``), +the execution dimension (``max(pre_refund_gas - state_gas, floor)``), so a transaction contributes at least the floor to the block's -regular gas while state gas is tracked separately. +execution gas while state gas is tracked separately. Tests for [EIP-8037: State Creation Gas Cost Increase] (https://eips.ethereum.org/EIPS/eip-8037). @@ -44,7 +44,7 @@ def test_calldata_floor_with_sstore( Test calldata floor does not affect state gas charging. A transaction with large calldata triggers the calldata floor for - regular gas, but state gas for SSTORE is charged independently. + execution gas, but state gas for SSTORE is charged independently. """ storage = Storage() contract = pre.deploy_contract( @@ -71,7 +71,7 @@ def test_calldata_floor_independent_of_state_gas( pre: Alloc, ) -> None: """ - Test calldata floor applies only to regular gas dimension. + Test calldata floor applies only to execution gas dimension. The calldata floor applies only to the sender's bill and does not affect the state gas dimension. A transaction with high calldata @@ -102,7 +102,7 @@ def test_calldata_floor_higher_than_execution_with_state_ops( """ Test state gas is tracked separately when calldata floor dominates. - Even when calldata floor > actual regular gas used, state gas for + Even when calldata floor > actual execution gas used, state gas for SSTORE is charged normally from the reservoir or gas_left. """ sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) @@ -112,7 +112,7 @@ def test_calldata_floor_higher_than_execution_with_state_ops( code=Op.SSTORE(storage.store_next(1), 1), ) - # Large calldata so floor dominates regular gas + # Large calldata so floor dominates execution gas calldata = b"\x01" * 1024 tx = Transaction( @@ -144,9 +144,9 @@ def test_calldata_floor_exceeding_tx_gas_limit_cap( Reject a transaction whose calldata floor exceeds the cap, isolating the cap check from the sufficiency check. - EIP-8037 caps ``max(intrinsic_regular, calldata_floor)`` at + EIP-8037 caps ``max(intrinsic_execution, calldata_floor)`` at ``TX_MAX_GAS_LIMIT``. When the EIP-7976 calldata floor crosses the cap - the transaction must be rejected even though the regular intrinsic gas + the transaction must be rejected even though the execution intrinsic gas is within the cap. For the rejection case ``gas_limit`` is set above the floor so the sufficiency check ``max(intrinsic_total, floor) <= tx.gas`` passes and the cap is the only reason for rejection — the exact shape a @@ -186,12 +186,12 @@ def floor_fits(num_bytes: int) -> bool: if exceeds_cap: intrinsic = fork.transaction_intrinsic_cost_calculator() - regular = intrinsic( + execution = intrinsic( calldata=calldata, return_cost_deducted_prior_execution=True, ) assert floor > cap, "calldata floor must exceed the cap" - assert regular < cap, "regular intrinsic must stay below the cap" + assert execution < cap, "execution intrinsic must stay below the cap" # Fund the floor in full so the sufficiency check cannot reject the # transaction first; only the cap check can. gas_limit = floor + 1_000_000 @@ -265,28 +265,28 @@ def test_calldata_floor_binds_with_reservoir( Large calldata makes the EIP-7976 floor the sender's bill, while an over-cap `gas_limit` puts the SSTORE-set state charge in the - reservoir. The floor binds the receipt and the block's regular + reservoir. The floor binds the receipt and the block's execution dimension alike, so the header gas_used is the floor (not the state dimension). """ storage = Storage() code = Op.SSTORE(storage.store_next(1), 1, new_value=1) state_cost = code.state_cost(fork) - regular_cost = code.regular_cost(fork) + execution_cost = code.execution_cost(fork) - # Sized so the floor binds while block-regular stays under storage_set. + # Sized so the floor binds while block-execution stays under storage_set. calldata = b"\x00" * 5000 floor = fork.transaction_data_floor_cost_calculator()(data=calldata) intrinsic = fork.transaction_intrinsic_cost_calculator()( calldata=calldata, return_cost_deducted_prior_execution=True, ) - tx_regular = intrinsic + regular_cost - assert floor > tx_regular + state_cost, ( + tx_execution = intrinsic + execution_cost + assert floor > tx_execution + state_cost, ( "calldata floor must exceed the sender's pre-floor bill" ) - assert tx_regular < state_cost, ( - "block-regular must stay under the state dimension" + assert tx_execution < state_cost, ( + "block-execution must stay under the state dimension" ) contract = pre.deploy_contract(code=code) @@ -313,10 +313,10 @@ def test_calldata_floor_counts_toward_block_gas( fork: Fork, ) -> None: """ - Verify the calldata floor is charged to the block's regular gas. + Verify the calldata floor is charged to the block's execution gas. With a STOP callee and large zero-byte calldata the floor exceeds - the actual regular gas charge, so the transaction contributes the + the actual execution gas charge, so the transaction contributes the floor (not the pre-floor charge) to the header gas_used. """ calldata = b"\x00" * 1024 @@ -353,10 +353,10 @@ def test_calldata_floor_not_discounted_by_state_gas( Verify state gas spending does not discount the block-level floor. Calldata is sized so the floor sits between the transaction's - regular-gas portion and its total gas used - (``tx_regular < floor < tx_regular + state``). The sender's bill is - the pre-floor total, yet the block's regular dimension must still - charge the full floor: the floor is compared against the regular + execution-gas portion and its total gas used + (``tx_execution < floor < tx_execution + state``). The sender's bill is + the pre-floor total, yet the block's execution dimension must still + charge the full floor: the floor is compared against the execution portion alone, so state gas cannot absorb it. An implementation that instead floors the transaction total before deducting state gas (or skips the floor entirely) would report the state dimension @@ -365,7 +365,7 @@ def test_calldata_floor_not_discounted_by_state_gas( storage = Storage() code = Op.SSTORE(storage.store_next(1), 1, new_value=1) state_cost = code.state_cost(fork) - regular_cost = code.regular_cost(fork) + execution_cost = code.execution_cost(fork) floor_cost = fork.transaction_data_floor_cost_calculator() # Smallest zero-byte calldata whose floor exceeds the state @@ -380,10 +380,10 @@ def test_calldata_floor_not_discounted_by_state_gas( calldata=calldata, return_cost_deducted_prior_execution=True, ) - tx_regular = intrinsic + regular_cost - tx_total = tx_regular + state_cost - assert tx_regular < floor < tx_total, ( - "floor must bind the regular portion but not the total" + tx_execution = intrinsic + execution_cost + tx_total = tx_execution + state_cost + assert tx_execution < floor < tx_total, ( + "floor must bind the execution portion but not the total" ) contract = pre.deploy_contract(code=code) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py index fe8a624fc16..d403d5f8b4b 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py @@ -2,7 +2,7 @@ Test CREATE and CREATE2 state gas charging under EIP-8037. Contract creation charges state gas for the new account and for -code deposit. Regular gas for CREATE is charged separately. +code deposit. Execution gas for CREATE is charged separately. Tests for [EIP-8037: State Creation Gas Cost Increase] (https://eips.ethereum.org/EIPS/eip-8037). @@ -271,7 +271,7 @@ def test_code_deposit_state_gas_exact_fit_boundary( 0, code_size, code_deposit_size=code_size, new_memory_size=code_size ) - intrinsic_regular = fork.transaction_intrinsic_cost_calculator()( + intrinsic_execution = fork.transaction_intrinsic_cost_calculator()( calldata=bytes(init_code), contract_creation=True, return_cost_deducted_prior_execution=True, @@ -281,7 +281,7 @@ def test_code_deposit_state_gas_exact_fit_boundary( # folds the memory expansion, code-hash keccak and code-deposit state # gas into `init_code`'s own cost. exact_fit_gas = ( - intrinsic_regular + intrinsic_execution + fork.transaction_top_frame_state_gas(contract_creation=True) + init_code.gas_cost(fork) ) @@ -300,7 +300,7 @@ def test_code_deposit_state_gas_exact_fit_boundary( post = {created: Account(code=b"\x00" * code_size)} else: # reservoir: the deposit OOG refills the reservoir, so the sender - # pays the regular cap. spill: the refilled NEW_ACCOUNT lands in + # pays the execution cap. spill: the refilled NEW_ACCOUNT lands in # gas_left and is burned, so the sender pays the full gas_limit. receipt_gas_used = cap if funding == "reservoir" else gas_limit post = {created: Account.NONEXISTENT} @@ -456,7 +456,7 @@ def test_create_insufficient_state_gas( """ Test CREATE OOGs when state gas is insufficient. - Provide enough gas for CREATE's regular gas cost but not enough + Provide enough gas for CREATE's execution gas cost but not enough to cover the new-account state gas. The CREATE should fail, returning 0. """ @@ -478,10 +478,10 @@ def test_create_insufficient_state_gas( ), ) - # Tight gas — enough for intrinsic + CREATE regular gas but not + # Tight gas — enough for intrinsic + CREATE execution gas but not # enough for the new account state gas intrinsic_cost = fork.transaction_intrinsic_cost_calculator() - gas_limit = intrinsic_cost() + create_call.regular_cost(fork) + 10_000 + gas_limit = intrinsic_cost() + create_call.execution_cost(fork) + 10_000 tx = Transaction( to=contract, @@ -564,7 +564,7 @@ def test_create_tx_intrinsic_gas_boundary( Test CREATE tx intrinsic gas boundary includes state component. The intrinsic gas for a contract-creating transaction includes - both regular gas and state gas. A transaction with gas_limit + both execution gas and state gas. A transaction with gas_limit exactly at the boundary succeeds; one gas below is rejected. """ intrinsic_cost = fork.transaction_intrinsic_cost_calculator() @@ -602,11 +602,11 @@ def test_create_tx_below_total_intrinsic( initcode: Bytecode, ) -> None: """ - Reject a creation tx one gas below the (now regular-only) intrinsic. + Reject a creation tx one gas below the (now execution-only) intrinsic. Under EIP-2780 the created account's ``NEW_ACCOUNT`` cost moved out of the transaction intrinsic and into the top frame, so the creation - intrinsic is entirely regular: + intrinsic is entirely execution: ``fork.transaction_intrinsic_cost_calculator()(contract_creation=True, calldata=initcode)``. Pinning ``gas_limit`` at ``intrinsic - 1`` must be rejected as intrinsic-gas-too-low, mirroring the set_code case in @@ -614,7 +614,7 @@ def test_create_tx_below_total_intrinsic( This now overlaps ``test_create_tx_intrinsic_gas_boundary`` (``gas_delta=-1``), but additionally sweeps the initcode so the - per-word init-code cost folded into the regular intrinsic is + per-word init-code cost folded into the execution intrinsic is exercised. """ intrinsic = fork.transaction_intrinsic_cost_calculator()( @@ -661,7 +661,7 @@ def test_code_deposit_oog_preserves_parent_reservoir( size=len(init_code), ) - # Limited regular gas forwarded to the factory. After CREATE + # Limited execution gas forwarded to the factory. After CREATE # takes 63/64, the factory retains ~23 K for its SSTOREs. child_gas = 1_500_000 @@ -759,7 +759,7 @@ def test_parent_state_gas_after_child_failure( factory_storage = Storage() # Split the factory into the CREATE run (memory setup + CREATE, whose # result is left on the stack) and the post-CREATE stores, so each - # step's regular gas is read off `.regular_cost(fork)` rather than + # step's execution gas is read off `.execution_cost(fork)` rather than # rebuilt from constants. factory_create_code = ( Op.MSTORE(0, Op.PUSH32(bytes(initcode)), new_memory_size=32) @@ -791,7 +791,7 @@ def test_parent_state_gas_after_child_failure( if failure_op == Op.INVALID: # Simulate runtime gas for HALT under EIP-8037 LIFO refills: - # 1. Regular pool capped by transaction_gas_limit_cap. The + # 1. Execution pool capped by transaction_gas_limit_cap. The # remainder forms the state reservoir. # 2. CREATE charges new_account state gas, reservoir first # then spilled to gas_left and tracked. @@ -806,13 +806,13 @@ def test_parent_state_gas_after_child_failure( # 7. Factory post-CREATE SSTORE charges sstore_state_gas, # reservoir first then spilled to gas_left. execution_gas = gas_limit - intrinsic_cost - regular_budget = gas_limit_cap - intrinsic_cost - sim_gas_left = min(regular_budget, execution_gas) + execution_budget = gas_limit_cap - intrinsic_cost + sim_gas_left = min(execution_budget, execution_gas) sim_state_gas_left = execution_gas - sim_gas_left - # Memory setup, the CREATE arg pushes and the CREATE regular + # Memory setup, the CREATE arg pushes and the CREATE execution # cost are all consumed before the 63/64 split. - sim_gas_left -= factory_create_code.regular_cost(fork) + sim_gas_left -= factory_create_code.execution_cost(fork) # CREATE new_account state gas: reservoir first, spill tracked. new_account_from_reservoir = min( @@ -836,7 +836,7 @@ def test_parent_state_gas_after_child_failure( sim_gas_left += new_account_spill sim_state_gas_left += new_account_from_reservoir - sim_gas_left -= factory_post_create_code.regular_cost(fork) + sim_gas_left -= factory_post_create_code.execution_cost(fork) # Factory post-CREATE SSTORE: reservoir first, spill otherwise. if sim_state_gas_left >= sstore_state_gas: @@ -852,9 +852,9 @@ def test_parent_state_gas_after_child_failure( # factory's own post-CREATE SSTORE consumes net state gas. expected_cumulative = ( intrinsic_cost - + factory_create_code.regular_cost(fork) - + factory_post_create_code.regular_cost(fork) - + initcode.regular_cost(fork) + + factory_create_code.execution_cost(fork) + + factory_post_create_code.execution_cost(fork) + + initcode.execution_cost(fork) + sstore_state_gas ) @@ -884,7 +884,7 @@ def test_nested_create_code_deposit_cannot_borrow_parent_gas( Test nested CREATE code deposit does not borrow parent gas. Provide just enough gas for CREATE to start (new account state - gas + regular gas) but not enough for the child frame to cover + gas + execution gas) but not enough for the child frame to cover code deposit after init code runs. The CREATE increments the factory nonce but code deposit fails, so no contract is deployed. """ @@ -907,19 +907,19 @@ def test_nested_create_code_deposit_cannot_borrow_parent_gas( # Init code child execution: PUSH1 + PUSH1 + RETURN's mem_exp. # Code deposit (keccak + state) is charged AFTER the child returns. - init_cost = init_code.regular_cost(fork) + init_cost = init_code.execution_cost(fork) # Target child: enough for init, not enough for code deposit state. target_child = (init_cost + code_deposit_state) // 2 # Invert EIP-150 63/64ths rule: ceil(target_child * 64 / 63). factory_remaining = (target_child * 64 + 62) // 63 # NEW_ACCOUNT state gas spills into gas_left (no reservoir at the - # top level), so it must be funded out of the regular budget. + # top level), so it must be funded out of the execution budget. intrinsic_cost = fork.transaction_intrinsic_cost_calculator()() gas_limit = ( intrinsic_cost - + factory_mstore.regular_cost(fork) - + factory_create.regular_cost(fork) + + factory_mstore.execution_cost(fork) + + factory_create.execution_cost(fork) + factory_create.state_cost(fork) + factory_remaining ) @@ -952,16 +952,16 @@ def test_sstore_oog_no_reservoir_inflation( gas_shortfall: int, ) -> None: """ - Verify SSTORE state gas is not charged when regular gas OOGs. + Verify SSTORE state gas is not charged when execution gas OOGs. With zero reservoir, all state gas spills into gas_left. A child frame does CREATE (charging state gas from gas_left) followed by SSTORE. When the factory is 1 gas short, SSTORE OOGs. If state - gas is incorrectly charged before regular gas, the extra state gas + gas is incorrectly charged before execution gas, the extra state gas inflates the parent's reservoir on frame failure, changing the transaction's effective gas consumption. - Regression test for SSTORE gas ordering: regular gas must be + Regression test for SSTORE gas ordering: execution gas must be checked before state gas. """ initcode = Initcode(deploy_code=Op.STOP) @@ -985,15 +985,15 @@ def test_sstore_oog_no_reservoir_inflation( factory = pre.deploy_contract(factory_code) create_address = compute_create_address(address=factory, nonce=1) - # Total gas includes both regular and state components since + # Total gas includes both execution and state components since # reservoir is zero — all state gas comes from gas_left. factory_gas = ( factory_code.gas_cost(fork) - + initcode.execution_gas(fork) + + initcode.evm_gas(fork) + initcode.deployment_gas(fork) ) - # Caller forwards total gas (regular + state) through CALL. + # Caller forwards total gas (execution + state) through CALL. # With zero reservoir, the CALL gas parameter is the only source. caller = pre.deploy_contract( Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) @@ -1046,9 +1046,9 @@ def test_max_initcode_size_gas_metering_via_create( """ Verify 2D gas metering for CREATE with max initcode size. - A caller contract forwards exact regular gas to a factory via CALL. + A caller contract forwards exact execution gas to a factory via CALL. State gas is supplied through the reservoir (tx.gas_limit above the - cap). With short_one_gas, the factory is 1 regular gas short and + cap). With short_one_gas, the factory is 1 execution gas short and all state changes revert. """ initcode = Initcode( @@ -1096,22 +1096,22 @@ def test_max_initcode_size_gas_metering_via_create( opcode=create_opcode, ) - # Split gas into regular and state components. + # Split gas into execution and state components. # CALL gas only feeds gas_left; state gas must come from the reservoir. factory_gas = ( factory_code.gas_cost(fork) - + initcode.execution_gas(fork) + + initcode.evm_gas(fork) + initcode.deployment_gas(fork) ) factory_state_gas = fork.create_state_gas( code_size=len(initcode.deploy_code) ) + Op.SSTORE(new_value=1).state_cost(fork) - factory_regular_gas = factory_gas - factory_state_gas + factory_execution_gas = factory_gas - factory_state_gas caller = pre.deploy_contract( Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) + Op.CALL( - gas=factory_regular_gas - gas_shortfall, + gas=factory_execution_gas - gas_shortfall, address=factory, value=0, args_offset=0, @@ -1149,9 +1149,9 @@ def test_create_no_double_charge_new_account( """ Verify CREATE does not double-charge new-account gas. - CREATE charges REGULAR_GAS_CREATE as regular gas and new-account + CREATE charges EXECUTION_GAS_CREATE as execution gas and new-account state gas separately. Provide exactly enough gas for both — if - GAS_NEW_ACCOUNT were charged twice (once in regular, once in + GAS_NEW_ACCOUNT were charged twice (once in execution, once in state), the CREATE would OOG. """ create_state_gas = fork.create_state_gas(code_size=0) @@ -1163,19 +1163,19 @@ def test_create_no_double_charge_new_account( # Compute exact gas: child bytecode + CREATE child frame. # The child frame is empty (size=0) so only the CREATE opcode - # charges matter: regular (REGULAR_GAS_CREATE) + state (new account). + # charges matter: execution (EXECUTION_GAS_CREATE) + state (new account). child_total = child_code.gas_cost(fork) create_address = compute_create_address(address=child, nonce=1) - # Caller forwards exact regular gas via CALL. State gas for + # Caller forwards exact execution gas via CALL. State gas for # new account comes from the reservoir (gas_limit above the cap). caller_storage = Storage() - regular_gas = child_total - create_state_gas + execution_gas = child_total - create_state_gas caller = pre.deploy_contract( Op.SSTORE( caller_storage.store_next(1, "create_succeeds"), - Op.CALL(gas=regular_gas, address=child), + Op.CALL(gas=execution_gas, address=child), ) ) @@ -1229,7 +1229,7 @@ def test_code_deposit_halt_discards_initcode_state_gas( state changes including the new account. The reverted GAS_NEW_ACCOUNT must NOT count in block_state_gas_used, which determines the block header gas_used via - max(block_regular_gas, block_state_gas). + max(block_execution_gas, block_state_gas). """ subcall_forwarded_value = 1 entry_account_value = 1 @@ -1300,12 +1300,12 @@ def test_create_tx_header_gas_used( actual consumed gas. For a fresh target the top-frame NEW_ACCOUNT state gas is charged and - dominates the regular gas, so gas_used == NEW_ACCOUNT. For a + dominates the execution gas, so gas_used == NEW_ACCOUNT. For a pre-existing balance-only leaf the target is not EMPTY pre-tx, so the top-frame NEW_ACCOUNT is never charged: net state gas is zero and only - the regular dimension remains. The block-level calldata floor tops up - that regular remainder, so the expected value is the greater of the - regular intrinsic and the floor, and fails if a stray NEW_ACCOUNT is + the execution dimension remains. The block-level calldata floor tops up + that execution remainder, so the expected value is the greater of the + execution intrinsic and the floor, and fails if a stray NEW_ACCOUNT is charged. """ initcode = Op.STOP @@ -1328,15 +1328,15 @@ def test_create_tx_header_gas_used( sender=sender, ) - # block_gas_used = max(block_regular, block_state) + # block_gas_used = max(block_execution, block_state) if target == "existing": intrinsic_cost = fork.transaction_intrinsic_cost_calculator() - # Regular-only creation intrinsic; STOP initcode deploys empty + # Execution-only creation intrinsic; STOP initcode deploys empty # code (zero deposit) and the pre-existing target adds no state - # gas. The block-level calldata floor tops up this small regular + # gas. The block-level calldata floor tops up this small execution # remainder and, being the larger of the two, is what the header - # reflects (the floor applies to block-level regular gas). - regular_intrinsic = intrinsic_cost( + # reflects (the floor applies to block-level execution gas). + execution_intrinsic = intrinsic_cost( calldata=bytes(initcode), contract_creation=True, return_cost_deducted_prior_execution=True, @@ -1344,13 +1344,13 @@ def test_create_tx_header_gas_used( floor = fork.transaction_data_floor_cost_calculator()( data=bytes(initcode), contract_creation=True ) - assert floor > regular_intrinsic, ( + assert floor > execution_intrinsic, ( "the floor must bind for this arm to pin floor-in-header" ) - expected_gas_used = max(regular_intrinsic, floor) + expected_gas_used = max(execution_intrinsic, floor) else: # For a minimal CREATE tx deploying Op.STOP (1 byte), - # state gas (new account) dominates regular gas. + # state gas (new account) dominates execution gas. expected_gas_used = fork.transaction_top_frame_state_gas( contract_creation=True ) @@ -1403,12 +1403,12 @@ def test_create_initcode_halt_no_code_deposit_state_gas( ) # On exceptional halt all gas_left is consumed. - # block_gas_used = max(block_regular, block_state) + # block_gas_used = max(block_execution, block_state) # block_state = intrinsic_state_gas (new account only, no deposit) - # block_regular = gas_limit - intrinsic_state_gas (all remaining) - tx_regular = gas_limit - intrinsic_state_gas + # block_execution = gas_limit - intrinsic_state_gas (all remaining) + tx_execution = gas_limit - intrinsic_state_gas tx_state = intrinsic_state_gas - expected_gas_used = max(tx_regular, tx_state) + expected_gas_used = max(tx_execution, tx_state) blockchain_test( pre=pre, @@ -1443,7 +1443,7 @@ def test_state_gas_spill_header_gas_used( intrinsic_gas = intrinsic_cost() sstore_state_gas = sstore_code.state_cost(fork) - evm_regular = sstore_code.regular_cost(fork) + evm_execution = sstore_code.execution_cost(fork) # Reservoir = half the SSTORE state gas, rest spills to gas_left reservoir = sstore_state_gas // 2 @@ -1454,9 +1454,9 @@ def test_state_gas_spill_header_gas_used( sender=pre.fund_eoa(), ) - tx_regular = intrinsic_gas + evm_regular + tx_execution = intrinsic_gas + evm_execution tx_state = sstore_state_gas - expected_gas_used = max(tx_regular, tx_state) + expected_gas_used = max(tx_execution, tx_state) blockchain_test( pre=pre, @@ -1578,10 +1578,10 @@ def test_create_silent_failure_refunds_state_gas( # CREATE's GAS_NEW_ACCOUNT is refunded (silent failure, no child # spawned). SSTORE's state portion is tracked separately in - # tx_state, so only the regular dimension remains here. - tx_regular = intrinsic_cost + factory_code.regular_cost(fork) + # tx_state, so only the execution dimension remains here. + tx_execution = intrinsic_cost + factory_code.execution_cost(fork) tx_state = sstore_state_gas - expected = max(tx_regular, tx_state) + expected = max(tx_execution, tx_state) blockchain_test( pre=pre, blocks=[Block(txs=[tx], header_verify=Header(gas_used=expected))], @@ -1649,16 +1649,16 @@ def test_create_child_revert_refunds_state_gas( ) # CREATE's GAS_NEW_ACCOUNT is refunded on child REVERT. SSTORE's - # state portion is tracked separately. Child REVERT regular + # state portion is tracked separately. Child REVERT execution # (init_code execution) is propagated via # incorporate_child_on_error. - tx_regular = ( + tx_execution = ( intrinsic_cost - + factory_code.regular_cost(fork) + + factory_code.execution_cost(fork) + init_code.gas_cost(fork) ) tx_state = sstore_state_gas - expected = max(tx_regular, tx_state) + expected = max(tx_execution, tx_state) blockchain_test( pre=pre, blocks=[Block(txs=[tx], header_verify=Header(gas_used=expected))], @@ -1686,10 +1686,10 @@ def test_create_child_halt_refunds_state_gas( Verify CREATE/CREATE2 child halt refunds parent's account gas. Exceptional halts (invalid opcode, EIP-3541 invalid prefix) - consume all forwarded regular gas, so block accounting cannot + consume all forwarded execution gas, so block accounting cannot strictly discriminate via header gas. Tight gas tuning via a caller wrapper leaves the factory with just - enough `gas_left` to pay the probe SSTORE's regular portion + enough `gas_left` to pay the probe SSTORE's execution portion but not enough to spill the state portion, so the probe SSTORE can only succeed via the refunded reservoir. """ @@ -1719,19 +1719,19 @@ def test_create_child_halt_refunds_state_gas( ), ) - # Tight gas tuning: child halt consumes all forwarded regular + # Tight gas tuning: child halt consumes all forwarded execution # gas. Factory retains - # ~(forwarded - pre_sstore_regular) / 64 after CREATE. Target - # the discrimination window `(probe_regular, - # probe_regular + sstore_state_gas)` so the probe SSTORE - # regular fits but state gas spillover from `gas_left` under + # ~(forwarded - pre_sstore_execution) / 64 after CREATE. Target + # the discrimination window `(probe_execution, + # probe_execution + sstore_state_gas)` so the probe SSTORE + # execution fits but state gas spillover from `gas_left` under # the old behavior OOGs. pre_sstore_code = Op.MSTORE(0, mstore_value) + Op.POP(create_call) - pre_sstore_regular = pre_sstore_code.regular_cost(fork) + pre_sstore_execution = pre_sstore_code.execution_cost(fork) probe_code = Op.SSTORE(0, 1) - probe_regular = probe_code.regular_cost(fork) - target_gas_left = probe_regular + sstore_state_gas // 2 - forwarded_gas = target_gas_left * 64 + pre_sstore_regular + probe_execution = probe_code.execution_cost(fork) + target_gas_left = probe_execution + sstore_state_gas // 2 + forwarded_gas = target_gas_left * 64 + pre_sstore_execution # Reservoir sized for CREATE charge only — SSTORE must pull # from the refunded reservoir, not from spill. caller = pre.deploy_contract( @@ -1782,12 +1782,12 @@ def call(size: int, salt: int) -> Bytecode: # STOP deploys empty code, so only GAS_NEW_ACCOUNT counts for # the successful CREATE, and the failed CREATE is refunded. block_state = create_account_state_gas - tx_regular = ( + tx_execution = ( intrinsic_gas + factory_code.gas_cost(fork) - 2 * create_account_state_gas ) - expected = max(tx_regular, block_state) + expected = max(tx_execution, block_state) tx = Transaction( to=factory, @@ -1815,7 +1815,7 @@ def test_create_collision_refunds_state_gas( Verify CREATE/CREATE2 address collision refunds account state gas. The collision path increments the factory nonce and burns the - forwarded regular gas (consumed by the never-spawned child), but + forwarded execution gas (consumed by the never-spawned child), but still refunds `GAS_NEW_ACCOUNT` to the reservoir. Tight gas tuning limits the factory's post-collision `gas_left` so the probe SSTORE can only succeed via the refunded reservoir, not @@ -1850,17 +1850,17 @@ def test_create_collision_refunds_state_gas( pre.deploy_contract(code=Op.STOP, address=collision_target) # Tight gas tuning: factory retains - # ~(forwarded - pre_sstore_regular) / 64 after collision burns - # `max_message_call_gas` as regular. Target the discrimination - # window `(probe_regular, probe_regular + sstore_state_gas)` so - # the probe SSTORE regular fits but state gas spillover from + # ~(forwarded - pre_sstore_execution) / 64 after collision burns + # `max_message_call_gas` as execution. Target the discrimination + # window `(probe_execution, probe_execution + sstore_state_gas)` so + # the probe SSTORE execution fits but state gas spillover from # `gas_left` under the old behavior OOGs. pre_sstore_code = Op.MSTORE(0, mstore_value) + Op.POP(create_call) - pre_sstore_regular = pre_sstore_code.regular_cost(fork) + pre_sstore_execution = pre_sstore_code.execution_cost(fork) probe_code = Op.SSTORE(0, 1) - probe_regular = probe_code.regular_cost(fork) - target_gas_left = probe_regular + sstore_state_gas // 2 - forwarded_gas = target_gas_left * 64 + pre_sstore_regular + probe_execution = probe_code.execution_cost(fork) + target_gas_left = probe_execution + sstore_state_gas // 2 + forwarded_gas = target_gas_left * 64 + pre_sstore_execution # Reservoir sized for CREATE charge only — SSTORE must pull from # the refunded reservoir, not from spill. caller = pre.deploy_contract( @@ -1916,15 +1916,15 @@ def test_create_code_deposit_oog_refunds_state_gas( ) # Child halt consumes all forwarded gas; factory retains only - # ~(forwarded - pre_sstore_regular) / 64. Target the - # discrimination window so SSTORE regular fits but state gas + # ~(forwarded - pre_sstore_execution) / 64. Target the + # discrimination window so SSTORE execution fits but state gas # spillover fails. pre_sstore_code = Op.MSTORE(0, mstore_value) + Op.POP(create_call) - pre_sstore_regular = pre_sstore_code.regular_cost(fork) + pre_sstore_execution = pre_sstore_code.execution_cost(fork) probe_code = Op.SSTORE(0, 1) - probe_regular = probe_code.regular_cost(fork) - target_gas_left = probe_regular + sstore_state_gas // 2 - forwarded_gas = target_gas_left * 64 + pre_sstore_regular + probe_execution = probe_code.execution_cost(fork) + target_gas_left = probe_execution + sstore_state_gas // 2 + forwarded_gas = target_gas_left * 64 + pre_sstore_execution caller = pre.deploy_contract( code=Op.CALL(gas=forwarded_gas, address=factory) ) @@ -2035,7 +2035,7 @@ def test_create_account_charge_reduces_child_gas( # Burn the middle of `(reduced_share, full_share]` for robustness. target_burn = (full_share + reduced_share) // 2 - # Init code burns `target_burn` regular gas via one MSTORE memory + # Init code burns `target_burn` execution gas via one MSTORE memory # expansion, then deploys empty code (zero code deposit). Invert # `words * MEMORY_PER_WORD + words ** 2 // 512 = target_mem` to size # the sink offset from gas rather than a magic number. @@ -2084,11 +2084,13 @@ def test_create_account_charge_reduces_child_gas( create_address = compute_create_address(address=factory, nonce=1) pre.fund_address(create_address, amount=1) - # Regular gas the factory spends before the NEW_ACCOUNT charge: the - # initcode setup MSTORE plus the create opcode's regular portion. + # Execution gas the factory spends before the NEW_ACCOUNT charge: the + # initcode setup MSTORE plus the create opcode's execution portion. setup = Op.MSTORE(0, mstore_value) - pre_charge_regular = setup.gas_cost(fork) + create_call.regular_cost(fork) - forwarded_gas = gas_at_charge + pre_charge_regular + pre_charge_execution = setup.gas_cost(fork) + create_call.execution_cost( + fork + ) + forwarded_gas = gas_at_charge + pre_charge_execution caller = pre.deploy_contract( code=Op.CALL(gas=forwarded_gas, address=factory) ) @@ -2133,8 +2135,8 @@ def test_failed_create_tx_refills_top_frame_new_account( * REVERT preserves ``gas_left`` and ``restore_state_gas`` returns the spilled ``NEW_ACCOUNT`` to it, so the state block nets to zero - and only the regular consumption counts as work. The calldata floor - tops up the billed amount and the block-level regular gas alike, so + and only the execution consumption counts as work. The calldata floor + tops up the billed amount and the block-level execution gas alike, so receipt and header agree at the greater of consumption and floor: the memory expansion keeps ``revert`` above the floor, while the bare ``revert_floor_bound`` pins the floor in both. @@ -2143,17 +2145,17 @@ def test_failed_create_tx_refills_top_frame_new_account( """ intrinsic_calc = fork.transaction_intrinsic_cost_calculator() - intrinsic_regular = intrinsic_calc( + intrinsic_execution = intrinsic_calc( calldata=bytes(init_code), contract_creation=True, return_cost_deducted_prior_execution=True, ) # gas_limit must cover the top-frame NEW_ACCOUNT and the initcode's own - # regular execution so the initcode runs to completion. + # execution gas so the initcode runs to completion. gas_limit = ( - intrinsic_regular + intrinsic_execution + fork.transaction_top_frame_state_gas(contract_creation=True) - + init_code.regular_cost(fork) + + init_code.execution_cost(fork) + 1000 ) @@ -2163,17 +2165,19 @@ def test_failed_create_tx_refills_top_frame_new_account( expected_gas_used = gas_limit else: # REVERT refills the spilled NEW_ACCOUNT, netting the state block - # to zero, so only the regular consumption counts as work. The + # to zero, so only the execution consumption counts as work. The # calldata floor binds the billed amount and the block-level - # regular gas alike, so receipt and header agree either way. - regular_consumed = intrinsic_regular + init_code.regular_cost(fork) + # execution gas alike, so receipt and header agree either way. + execution_consumed = intrinsic_execution + init_code.execution_cost( + fork + ) floor = fork.transaction_data_floor_cost_calculator()( data=bytes(init_code), contract_creation=True ) - assert (floor > regular_consumed) == floor_binds, ( + assert (floor > execution_consumed) == floor_binds, ( "init code lands on the wrong side of the floor" ) - expected_gas_used = max(regular_consumed, floor) + expected_gas_used = max(execution_consumed, floor) sender = pre.fund_eoa() created = compute_create_address(address=sender, nonce=0) @@ -2210,23 +2214,23 @@ def test_create_tx_collision_no_new_account_charge( charge, but on an address collision the target already exists pre-tx, the create path returns ``AddressCollision`` before the top frame is prepared, and no ``NEW_ACCOUNT`` is ever charged. The full - forwarded gas is burned as regular (no initcode runs) and block + forwarded gas is burned as execution (no initcode runs) and block state-gas is zero, so header ``gas_used`` equals the whole ``gas_limit``. """ intrinsic_calc = fork.transaction_intrinsic_cost_calculator() init_code = Op.STOP - intrinsic_regular = intrinsic_calc( + intrinsic_execution = intrinsic_calc( calldata=bytes(init_code), contract_creation=True ) - gas_limit = intrinsic_regular + 1000 + gas_limit = intrinsic_execution + 1000 sender = pre.fund_eoa() collision_target = compute_create_address(address=sender, nonce=0) pre[collision_target] = Account(nonce=1) - # Collision burns the full forwarded gas as regular; state block is + # Collision burns the full forwarded gas as execution; state block is # zero (no NEW_ACCOUNT charged). expected_gas_used = gas_limit @@ -2261,11 +2265,11 @@ def test_create_tx_collision_refunds_reservoir( Verify the state-gas reservoir is refunded on a depth-0 CREATE-tx address collision when `gas_limit > TX_MAX_GAS_LIMIT`. - EIP-8037 splits `gas_limit` into the capped regular budget and a - state-gas reservoir. On collision the inner regular gas is burnt + EIP-8037 splits `gas_limit` into the capped execution budget and a + state-gas reservoir. On collision the inner execution gas is burnt and `intrinsic_state_gas` is refunded; the reservoir must also be refunded to the sender. `header.gas_used` is fixed at the - regular cap regardless of reservoir handling, so the sender's + execution cap regardless of reservoir handling, so the sender's post-balance is the primary discriminating assertion. """ gas_limit_cap = fork.transaction_gas_limit_cap() @@ -2492,13 +2496,13 @@ def test_selfdestruct_in_create_tx_initcode( create_state_gas = fork.create_state_gas(code_size=0) beneficiary = 0xDEAD - # `account_new` folds the beneficiary's `ACCOUNT_WRITE` regular + # `account_new` folds the beneficiary's `ACCOUNT_WRITE` execution # cost and account-creation state gas into `gas_cost`. initcode = Op.SELFDESTRUCT(beneficiary, account_new=True) sender = pre.fund_eoa() intrinsic_calc = fork.transaction_intrinsic_cost_calculator() - intrinsic_regular = intrinsic_calc( + intrinsic_execution = intrinsic_calc( calldata=bytes(initcode), contract_creation=True, sends_value=True ) @@ -2507,7 +2511,7 @@ def test_selfdestruct_in_create_tx_initcode( expected_state = create_state_gas + initcode.state_cost(fork) initcode_gas = initcode.gas_cost(fork) - gas_limit = intrinsic_regular + create_state_gas + initcode_gas + 1000 + gas_limit = intrinsic_execution + create_state_gas + initcode_gas + 1000 tx = Transaction( sender=sender, @@ -2593,7 +2597,7 @@ def test_inner_create_succeeds_code_deposit_state_gas( ) if outer_outcome == "halts": - initcode_gas = initcode.regular_cost(fork) + initcode_gas = initcode.execution_cost(fork) else: initcode_gas = initcode.gas_cost(fork) # The outer created account's NEW_ACCOUNT is a top-frame state charge @@ -2841,7 +2845,7 @@ def test_inner_create_fail_refunds_in_creation_tx( @pytest.mark.pre_alloc_mutable @pytest.mark.with_all_create_opcodes() @pytest.mark.valid_from("EIP8037") -def test_create_collision_burned_gas_counted_in_block_regular( +def test_create_collision_burned_gas_counted_in_block_execution( blockchain_test: BlockchainTestFiller, pre: Alloc, fork: Fork, @@ -2849,7 +2853,7 @@ def test_create_collision_burned_gas_counted_in_block_regular( ) -> None: """ Verify gas burned by a CREATE/CREATE2 address collision counts - toward block regular gas used in the header. + toward block execution gas used in the header. """ init_code = Op.STOP mstore_value, size = init_code_at_high_bytes(init_code) @@ -2871,7 +2875,7 @@ def test_create_collision_burned_gas_counted_in_block_regular( # CPSB-agnostic baseline: block_state_gas is zero for this tx (the # existent collision target is not charged), so header.gas_used - # equals the regular-gas total. Decompose the parent + inner frame + # equals the execution-gas total. Decompose the parent + inner frame # accounting from fork APIs so the baseline tracks future cost # changes automatically. gas_used_until_collision = ( @@ -2885,7 +2889,7 @@ def test_create_collision_burned_gas_counted_in_block_regular( gas_at_create = gas_limit - gas_used_until_collision # Inner burns 63/64 of the available gas on collision; the parent # retains 1/64. Post-CREATE consumes from the retained pool. A - # mutation that drops the burned forwarded gas from regular + # mutation that drops the burned forwarded gas from execution # accounting would reduce this baseline. retained = gas_at_create // 64 gas_post_create = factory_post_create_code.gas_cost(fork) @@ -2996,7 +3000,7 @@ def test_no_account_charge_on_existing_account( Verify the create opcode is not charged NEW_ACCOUNT when the target account already exists in the trie. - The factory is forwarded exactly the create's regular gas, with no + The factory is forwarded exactly the create's execution gas, with no NEW_ACCOUNT included. Because the target is pre-funded (alive), that budget is sufficient and the create succeeds, deploying empty code (created nonce 1). With one gas less it runs out of gas at the diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_fork_transition.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_fork_transition.py index 2b0c379d36f..255fd52c042 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_fork_transition.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_fork_transition.py @@ -47,7 +47,7 @@ def test_sstore_state_gas_at_transition( Test SSTORE state gas activates at the EIP-8037 fork boundary. Before the fork, an SSTORE zero-to-nonzero succeeds with only - regular gas (no state gas dimension). After the fork, the same + execution gas (no state gas dimension). After the fork, the same operation requires state gas. Both blocks use TX_MAX_GAS_LIMIT which provides enough gas in either regime. """ @@ -59,7 +59,7 @@ def test_sstore_state_gas_at_transition( ) blocks = [ - # Before fork: SSTORE succeeds with regular gas only + # Before fork: SSTORE succeeds with execution gas only Block( timestamp=14_999, txs=[ diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_multi_block.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_multi_block.py index fecc4871775..6b26bd0bb5d 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_multi_block.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_multi_block.py @@ -59,7 +59,7 @@ def test_exact_coinbase_fee_simple_sstore( sstore_contract = pre.deploy_contract(code=sstore_code) # tx 1 gas used: the intrinsic (TX_BASE plus the EIP-2780 - # recipient-access charge) plus the SSTORE code's own regular and + # recipient-access charge) plus the SSTORE code's own execution and # state cost. tx1_gas_used = ( fork.transaction_intrinsic_cost_calculator()() diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_ordering.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_ordering.py index b9b1f8c5074..03fc036f747 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_ordering.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_ordering.py @@ -1,8 +1,8 @@ """ Test state gas consumption ordering under EIP-8037. -When an opcode charges both regular gas and state gas, regular gas MUST -be charged first. If regular gas OOGs, state gas is not consumed. This +When an opcode charges both execution gas and state gas, execution gas MUST +be charged first. If execution gas OOGs, state gas is not consumed. This prevents the parent's reservoir from being inflated on frame failure. Each test gives a child frame exactly 1 gas less than needed, then uses @@ -61,7 +61,7 @@ def test_sstore_oog_reservoir_inflation_detection( that need more total state gas than the correct reservoir but less than the inflated one. - With correct ordering (regular gas first): probe OOGs on 4th SSTORE. + With correct ordering (execution gas first): probe OOGs on 4th SSTORE. With wrong ordering (state gas first): reservoir is inflated, probe succeeds. """ @@ -87,7 +87,7 @@ def test_sstore_oog_reservoir_inflation_detection( factory_gas = ( factory_code.gas_cost(fork) - + initcode.execution_gas(fork) + + initcode.evm_gas(fork) + initcode.deployment_gas(fork) ) @@ -98,15 +98,15 @@ def test_sstore_oog_reservoir_inflation_detection( Op.SSTORE(0, 1) + Op.SSTORE(1, 1) + Op.SSTORE(2, 1) + Op.SSTORE(3, 1) ) - # Compute probe gas: enough for 4 SSTOREs' regular gas + pushes, - # but after 4th regular charge, gas_left < the state gas spill. + # Compute probe gas: enough for 4 SSTOREs' execution gas + pushes, + # but after 4th execution charge, gas_left < the state gas spill. sstore_state = Op.SSTORE(new_value=1).state_cost(fork) - sstore_regular = Op.SSTORE(0, 1).regular_cost(fork) + sstore_execution = Op.SSTORE(0, 1).execution_cost(fork) create_state_gas = fork.create_state_gas( code_size=len(initcode.deploy_code) ) spill = 4 * sstore_state - create_state_gas - probe_gas = 4 * sstore_regular + spill // 2 + probe_gas = 4 * sstore_execution + spill // 2 caller_storage = Storage() caller = pre.deploy_contract( @@ -153,7 +153,7 @@ def test_call_oog_reservoir_inflation_detection( Detect CALL state gas ordering via reservoir inflation. A child does CALL(value=1) to a dead address with gas tuned so - the regular gas charge OOGs by 1. If state gas (new account) is + the execution gas charge OOGs by 1. If state gas (new account) is incorrectly charged first, the parent's reservoir is inflated. A single-SSTORE probe detects the inflation: with correct reservoir @@ -171,7 +171,7 @@ def test_call_oog_reservoir_inflation_detection( value_transfer=True, account_new=True, ) - # One gas short of the CALL's full cost (regular plus the NEW_ACCOUNT + # One gas short of the CALL's full cost (execution plus the NEW_ACCOUNT # state charge), so it OOGs on the account-creation charge. child_gas = child_code.gas_cost(fork) - 1 child = pre.deploy_contract(child_code) @@ -209,14 +209,14 @@ def test_selfdestruct_oog_reservoir_inflation_detection( Detect SELFDESTRUCT state gas ordering via reservoir inflation. A child with non-zero balance does SELFDESTRUCT(dead_beneficiary) - with gas tuned so the regular gas charge OOGs by 1. If state gas + with gas tuned so the execution gas charge OOGs by 1. If state gas is incorrectly charged first, the parent's reservoir is inflated. Single-SSTORE probe detects the inflation. """ dead_beneficiary = 0xBEEF child_code = Op.SELFDESTRUCT(dead_beneficiary, account_new=True) - # One gas short of the SELFDESTRUCT's full cost (regular plus the + # One gas short of the SELFDESTRUCT's full cost (execution plus the # NEW_ACCOUNT state charge), so it OOGs on the account-creation charge. child_gas = child_code.gas_cost(fork) - 1 child = pre.deploy_contract(child_code, balance=1) @@ -289,7 +289,7 @@ def test_create_oog_reservoir_inflation_detection( else: child_code = Op.MSTORE(0, 0, new_memory_size=WORD_SIZE) + create_op - # One gas short of the CREATE's full cost (regular plus the NEW_ACCOUNT + # One gas short of the CREATE's full cost (execution plus the NEW_ACCOUNT # state charge), so it OOGs on the account-creation charge. child_gas = child_code.gas_cost(fork) - 1 child = pre.deploy_contract(child_code) @@ -361,7 +361,7 @@ def test_create_oog_full_burn_no_state_credit( factory_code = Op.MSTORE(0, 0, new_memory_size=WORD_SIZE) + create_op factory = pre.deploy_contract(factory_code) - # One gas short of the CREATE's full cost (regular plus the NEW_ACCOUNT + # One gas short of the CREATE's full cost (execution plus the NEW_ACCOUNT # state charge), so it OOGs on the account-creation charge. body_gas = factory_code.gas_cost(fork) - 1 diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_pricing.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_pricing.py index 99af67b5a2c..df91061a30d 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_pricing.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_pricing.py @@ -99,7 +99,7 @@ def test_charge_draws_entirely_from_reservoir( When the reservoir has enough gas for the SSTORE state cost, gas_left should not be reduced by the state charge. Verify by - performing a regular-gas-heavy computation after the SSTORE. + performing an execution-gas-heavy computation after the SSTORE. """ sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) @@ -108,10 +108,10 @@ def test_charge_draws_entirely_from_reservoir( code=( # SSTORE draws state gas from reservoir Op.SSTORE(storage.store_next(1), 1) - # Remaining gas_left is available for regular ops + # Remaining gas_left is available for execution ops + Op.SSTORE( storage.store_next(1), - Op.ADD(1, 0), # Cheap regular-gas op + Op.ADD(1, 0), # Cheap execution-gas op ) ), ) @@ -183,9 +183,9 @@ def test_charge_spill_boundary( contract = pre.deploy_contract(code=code) intrinsic = fork.transaction_intrinsic_cost_calculator()() - regular = code.regular_cost(fork) + execution = code.execution_cost(fork) sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) - gas_limit = intrinsic + regular + sstore_state_gas + gas_delta + gas_limit = intrinsic + execution + sstore_state_gas + gas_delta tx = Transaction( to=contract, @@ -194,7 +194,7 @@ def test_charge_spill_boundary( ) header = Header( - gas_used=max(intrinsic + regular, sstore_state_gas) + gas_used=max(intrinsic + execution, sstore_state_gas) if gas_delta == 0 else gas_limit ) @@ -217,7 +217,7 @@ def test_refund_cap_includes_state_gas( When state gas is drawn from gas_left (no reservoir), it counts toward tx_gas_used_before_refund. The 1/5 refund cap applies to - the combined total of regular + state gas consumed. This test + the combined total of execution + state gas consumed. This test performs an SSTORE zero-to-nonzero-to-zero sequence to generate a refund and verifies the transaction succeeds. """ @@ -248,7 +248,7 @@ def test_refund_with_reservoir_state_gas( Test refund when state gas is drawn from reservoir. When state gas comes from the reservoir, the refund still applies. - The refund_counter accumulates state + regular gas refunds, and + The refund_counter accumulates state + execution gas refunds, and the 1/5 cap uses tx_gas_used_before_refund which accounts for both dimensions. An SSTORE zero-to-nonzero-to-zero sequence should refund correctly. @@ -270,30 +270,32 @@ def test_refund_with_reservoir_state_gas( state_test(pre=pre, post=post, tx=tx) -def _access_list_over_regular_cap( +def _access_list_over_execution_cap( fork: Fork, cap: int, *, margin_num: int = 1, margin_den: int = 1 ) -> list[AccessList]: """ - Build an access list whose intrinsic *regular* gas exceeds ``cap`` by + Build an access list whose intrinsic *execution* gas exceeds ``cap`` by roughly the factor ``margin_num / margin_den``. - Each access-list address adds a fixed amount to the regular intrinsic + Each access-list address adds a fixed amount to the execution intrinsic (the EIP-2930 address cost plus the EIP-7981 floor-token surcharge) and a much smaller amount to the calldata floor, so the list raises the - regular operand of ``max(intrinsic_regular, calldata_floor)`` over the + execution operand of ``max(intrinsic_execution, calldata_floor)`` over the cap while the floor stays below it. No state gas is incurred. """ intrinsic = fork.transaction_intrinsic_cost_calculator() - base_regular = intrinsic(return_cost_deducted_prior_execution=True) - per_address_regular = ( + base_execution = intrinsic(return_cost_deducted_prior_execution=True) + per_address_execution = ( intrinsic( access_list=[AccessList(address=Address(0x100), storage_keys=[])], return_cost_deducted_prior_execution=True, ) - - base_regular + - base_execution ) - assert per_address_regular > 0 - num_entries = (cap * margin_num) // (per_address_regular * margin_den) + 1 + assert per_address_execution > 0 + num_entries = (cap * margin_num) // ( + per_address_execution * margin_den + ) + 1 return [ AccessList(address=Address(0x10000 + i), storage_keys=[]) for i in range(num_entries) @@ -302,18 +304,18 @@ def _access_list_over_regular_cap( @pytest.mark.exception_test @pytest.mark.valid_from("EIP8037") -def test_intrinsic_regular_gas_exceeds_cap( +def test_intrinsic_execution_gas_exceeds_cap( state_test: StateTestFiller, pre: Alloc, fork: Fork, ) -> None: """ - Reject a transaction whose intrinsic *regular* gas exceeds the cap. + Reject a transaction whose intrinsic *execution* gas exceeds the cap. - EIP-8037 enforces ``max(intrinsic_regular, calldata_floor) <= + EIP-8037 enforces ``max(intrinsic_execution, calldata_floor) <= TX_MAX_GAS_LIMIT`` after the separate sufficiency check ``max(intrinsic_total, calldata_floor) <= tx.gas``. A large access list - raises the regular intrinsic over the cap while adding no state gas and + raises the execution intrinsic over the cap while adding no state gas and keeping the calldata floor below the cap. ``gas_limit`` is set above the total intrinsic so the sufficiency check passes and the cap is the only reason the transaction is rejected; a client that compares the intrinsic @@ -324,16 +326,16 @@ def test_intrinsic_regular_gas_exceeds_cap( floor_cost = fork.transaction_data_floor_cost_calculator() intrinsic = fork.transaction_intrinsic_cost_calculator() - access_list = _access_list_over_regular_cap(fork, cap) - regular = intrinsic( + access_list = _access_list_over_execution_cap(fork, cap) + execution = intrinsic( access_list=access_list, return_cost_deducted_prior_execution=True, ) floor = floor_cost(data=b"", access_list=access_list) - tx_gas = regular + 1_000_000 + tx_gas = execution + 1_000_000 - assert max(regular, floor) > cap, "cap check must fire" - assert regular <= tx_gas, "sufficiency check must not fire" + assert max(execution, floor) > cap, "cap check must fire" + assert execution <= tx_gas, "sufficiency check must not fire" assert floor <= tx_gas tx = Transaction( @@ -349,21 +351,21 @@ def test_intrinsic_regular_gas_exceeds_cap( @pytest.mark.exception_test @pytest.mark.valid_from("EIP8037") -def test_intrinsic_regular_gas_exceeds_cap_with_floor_below_cap( +def test_intrinsic_execution_gas_exceeds_cap_with_floor_below_cap( state_test: StateTestFiller, pre: Alloc, fork: Fork, ) -> None: """ - Reject when intrinsic *regular* gas exceeds the cap while the calldata - floor stays below it, isolating the regular operand of - ``max(intrinsic_regular, calldata_floor)``. + Reject when intrinsic *execution* gas exceeds the cap while the calldata + floor stays below it, isolating the execution operand of + ``max(intrinsic_execution, calldata_floor)``. - A large access list with no calldata pushes the regular intrinsic over + A large access list with no calldata pushes the execution intrinsic over the cap while the floor stays well below it, and ``gas_limit`` covers the total intrinsic so the sufficiency check passes. The explicit ``floor < cap`` assertion guarantees the rejection comes from the - regular operand, so a client that compares only the calldata floor + execution operand, so a client that compares only the calldata floor against the cap would wrongly accept the transaction. """ cap = fork.transaction_gas_limit_cap() @@ -371,19 +373,19 @@ def test_intrinsic_regular_gas_exceeds_cap_with_floor_below_cap( floor_cost = fork.transaction_data_floor_cost_calculator() intrinsic = fork.transaction_intrinsic_cost_calculator() - access_list = _access_list_over_regular_cap( + access_list = _access_list_over_execution_cap( fork, cap, margin_num=5, margin_den=4 ) - regular = intrinsic( + execution = intrinsic( access_list=access_list, return_cost_deducted_prior_execution=True, ) floor = floor_cost(data=b"", access_list=access_list) - tx_gas = regular + 1_000_000 + tx_gas = execution + 1_000_000 - assert regular > cap, "regular operand must exceed the cap" + assert execution > cap, "execution operand must exceed the cap" assert floor < cap, "calldata floor must stay below the cap" - assert regular <= tx_gas, "sufficiency check must not fire" + assert execution <= tx_gas, "sufficiency check must not fire" tx = Transaction( ty=1, @@ -407,7 +409,7 @@ def test_intrinsic_within_cap_gas_limit_above_cap( intrinsic operands stay below it. EIP-8037 relaxes the EIP-7825 cap on ``tx.gas`` itself; only - ``max(intrinsic_regular, calldata_floor)`` is capped. This positive + ``max(intrinsic_execution, calldata_floor)`` is capped. This positive control sets ``gas_limit`` above the cap with a small access list so both operands are far below it, and the transaction must execute. It is the accepting counterpart to the cap-rejection tests above. @@ -421,12 +423,12 @@ def test_intrinsic_within_cap_gas_limit_above_cap( AccessList(address=Address(0x10000 + i), storage_keys=[]) for i in range(16) ] - regular = intrinsic( + execution = intrinsic( access_list=access_list, return_cost_deducted_prior_execution=True, ) floor = floor_cost(data=b"", access_list=access_list) - assert regular <= cap + assert execution <= cap assert floor <= cap storage = Storage() @@ -466,26 +468,26 @@ def test_calldata_floor_enforced_with_state_gas( Test EIP-7623 calldata floor is enforced when EIP-8037 is active. Send 100 non-zero calldata bytes to a call transaction so the - regular intrinsic cost is below the calldata floor. A gas_limit + execution intrinsic cost is below the calldata floor. A gas_limit at the floor succeeds; one below the floor is rejected. """ calldata = b"\x01" * 100 intrinsic_cost = fork.transaction_intrinsic_cost_calculator() floor_cost = fork.transaction_data_floor_cost_calculator() - regular_gas = intrinsic_cost( + execution_gas = intrinsic_cost( calldata=calldata, return_cost_deducted_prior_execution=True, ) floor_gas = floor_cost(data=calldata) - assert floor_gas > regular_gas, "floor must exceed regular for test" + assert floor_gas > execution_gas, "floor must exceed execution for test" if above_floor: gas_limit = floor_gas error = None else: - # Between regular and floor: satisfies regular but not floor - gas_limit = (regular_gas + floor_gas) // 2 + # Between execution and floor: satisfies execution but not floor + gas_limit = (execution_gas + floor_gas) // 2 error = TransactionException.INTRINSIC_GAS_BELOW_FLOOR_GAS_COST tx = Transaction( diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_reservoir.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_reservoir.py index 5ac3ba9226c..a75ac6136e5 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_reservoir.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_reservoir.py @@ -3,11 +3,12 @@ EIP-7825 TX_MAX_GAS_LIMIT cap. EIP-8037 splits execution gas into two pools: -- `gas_left` (regular gas): capped at `TX_MAX_GAS_LIMIT - intrinsic.regular` -- `state_gas_reservoir`: the overflow beyond the regular gas cap +- `gas_left` (execution gas): capped at + `TX_MAX_GAS_LIMIT - intrinsic.execution` +- `state_gas_reservoir`: the overflow beyond the execution gas cap State gas charges draw from the reservoir first, then spill into gas_left. -Regular gas charges draw only from gas_left. +Execution gas charges draw only from gas_left. Tests for [EIP-8037: State Creation Gas Cost Increase] (https://eips.ethereum.org/EIPS/eip-8037). @@ -170,17 +171,17 @@ def test_insufficient_gas_for_sstore_state_cost( """ Test that execution OOGs when gas is insufficient for SSTORE state cost. - Provide just enough gas for intrinsic costs plus the SSTORE regular + Provide just enough gas for intrinsic costs plus the SSTORE execution gas, but not enough to also cover the SSTORE state gas. The SSTORE should OOG, leaving storage slot 0 unchanged at zero. """ contract_code = Op.SSTORE(0, 1) contract = pre.deploy_contract(code=contract_code) - # Enough for intrinsic + warm SSTORE regular gas, but not the + # Enough for intrinsic + warm SSTORE execution gas, but not the # state gas cost for zero-to-nonzero transition intrinsic_cost = fork.transaction_intrinsic_cost_calculator() - gas_limit = intrinsic_cost() + contract_code.regular_cost(fork) + gas_limit = intrinsic_cost() + contract_code.execution_cost(fork) tx = Transaction( to=contract, @@ -201,16 +202,16 @@ def test_insufficient_gas_for_sstore_state_cost( ], ) @pytest.mark.valid_from("EIP8037") -def test_block_regular_gas_limit( +def test_block_execution_gas_limit( blockchain_test: BlockchainTestFiller, pre: Alloc, exceed_block_gas_limit: bool, fork: Fork, ) -> None: """ - Test check_transaction enforcement of regular gas against block limit. + Test check_transaction enforcement of execution gas against block limit. - The regular gas check uses min(TX_MAX_GAS_LIMIT, tx.gas). + The execution gas check uses min(TX_MAX_GAS_LIMIT, tx.gas). Fill the block with transactions at TX_MAX_GAS_LIMIT and verify the last one is accepted or rejected based on remaining capacity. """ @@ -268,7 +269,7 @@ def test_block_state_gas_limit_boundary( (delta=0, accepted because the check is strict `>`) or exceeds it by 1 (delta=1, rejected with `GAS_ALLOWANCE_EXCEEDED`). - The regular check is asserted to pass so rejection on delta=1 is + The execution check is asserted to pass so rejection on delta=1 is pinned to the state dimension. """ gas_limit_cap = fork.transaction_gas_limit_cap() @@ -285,7 +286,7 @@ def test_block_state_gas_limit_boundary( tx1_contract = pre.deploy_contract(code=tx1_code) tx1_state = tx1_code.state_cost(fork) - tx1_regular = intrinsic_cost() + tx1_code.gas_cost(fork) - tx1_state + tx1_execution = intrinsic_cost() + tx1_code.gas_cost(fork) - tx1_state tx1_gas = gas_limit_cap + tx1_state # tx2: worst-case state contribution = tx.gas (strict EIP rule). @@ -294,10 +295,10 @@ def test_block_state_gas_limit_boundary( tx2_gas = state_available + delta # Pin the rejection (when delta > 0) to the state check: the - # regular check must not fire. - regular_available = block_gas_limit - tx1_regular - assert min(gas_limit_cap, tx2_gas) < regular_available, ( - "tx2 would fail the regular check instead of the state check" + # execution check must not fire. + execution_available = block_gas_limit - tx1_execution + assert min(gas_limit_cap, tx2_gas) < execution_available, ( + "tx2 would fail the execution check instead of the state check" ) tx2_error = ( @@ -333,57 +334,57 @@ def test_block_state_gas_limit_boundary( @pytest.mark.exception_test @pytest.mark.valid_from("EIP8037") -def test_creation_tx_regular_check_uses_full_tx_gas( +def test_creation_tx_execution_check_uses_full_tx_gas( blockchain_test: BlockchainTestFiller, pre: Alloc, fork: Fork, ) -> None: """ - Verify the regular check uses the full `tx.gas` (no subtraction). + Verify the execution check uses the full `tx.gas` (no subtraction). - The EIP regular check is `min(TX_MAX, tx.gas) > regular_available`. + The EIP execution check is `min(TX_MAX, tx.gas) > execution_available`. Under EIP-2780 a creation tx has `intrinsic.state == 0` (the created account's `NEW_ACCOUNT` moved to the top frame), so its intrinsic is - regular-only. This test sizes a creation tx whose full `tx.gas` - exceeds the remaining regular budget by one — it must be rejected. A + execution-only. This test sizes a creation tx whose full `tx.gas` + exceeds the remaining execution budget by one — it must be rejected. A formula that instead used the execution gas - (`tx.gas - intrinsic_regular`) would have wrongly accepted. + (`tx.gas - intrinsic_execution`) would have wrongly accepted. """ gas_limit_cap = fork.transaction_gas_limit_cap() assert gas_limit_cap is not None - # The creation intrinsic is regular-only and cpsb-free - # (GAS_TX_BASE + REGULAR_GAS_CREATE + init_code_cost), giving a stable + # The creation intrinsic is execution-only and cpsb-free + # (GAS_TX_BASE + EXECUTION_GAS_CREATE + init_code_cost), giving a stable # `block_gas_limit` independent of cpsb. - intrinsic_regular = fork.transaction_intrinsic_cost_calculator()( + intrinsic_execution = fork.transaction_intrinsic_cost_calculator()( contract_creation=True ) # Tight boundary: after the filler consumes gas_limit_cap, exactly - # `intrinsic_regular + 1` regular gas remains in the block. - block_gas_limit = gas_limit_cap + intrinsic_regular + 1 + # `intrinsic_execution + 1` execution gas remains in the block. + block_gas_limit = gas_limit_cap + intrinsic_execution + 1 - # Ask for one more than the remaining regular budget: min(TX_MAX, - # tx.gas) == tx.gas exceeds `remaining_regular` by one, so the strict + # Ask for one more than the remaining execution budget: min(TX_MAX, + # tx.gas) == tx.gas exceeds `remaining_execution` by one, so the strict # check rejects. The tx still carries more than its own intrinsic, so - # it is a valid creation tx on its own — only the block-level regular + # it is a valid creation tx on its own — only the block-level execution # check fails. - remaining_regular = block_gas_limit - gas_limit_cap - create_tx_gas = remaining_regular + 1 + remaining_execution = block_gas_limit - gas_limit_cap + create_tx_gas = remaining_execution + 1 - # Filler consumes the full regular cap (OOG on INVALID). + # Filler consumes the full execution cap (OOG on INVALID). filler = pre.deploy_contract(code=Op.INVALID) assert create_tx_gas <= gas_limit_cap, ( "min(TX_MAX, tx.gas) must be tx.gas for this boundary" ) - assert create_tx_gas > intrinsic_regular, ( + assert create_tx_gas > intrinsic_execution, ( "tx must carry more than its own intrinsic" ) - assert min(gas_limit_cap, create_tx_gas) > remaining_regular, ( - "strict formula must reject: full tx.gas exceeds remaining regular" + assert min(gas_limit_cap, create_tx_gas) > remaining_execution, ( + "strict formula must reject: full tx.gas exceeds remaining execution" ) - assert create_tx_gas - intrinsic_regular <= remaining_regular, ( + assert create_tx_gas - intrinsic_execution <= remaining_execution, ( "a formula using execution gas would have accepted" ) @@ -468,7 +469,7 @@ def test_creation_tx_state_check_exceeded( A creation tx (`to=None`) goes through the per-dimension inclusion check like any other tx. A filler tx consumes state budget; the creation tx's `tx.gas` then exceeds the remaining state budget by - one while its regular contribution still fits, pinning the + one while its execution contribution still fits, pinning the rejection to the state dimension. """ gas_limit_cap = fork.transaction_gas_limit_cap() @@ -485,16 +486,16 @@ def test_creation_tx_state_check_exceeded( tx1_contract = pre.deploy_contract(code=tx1_code) tx1_state = tx1_code.state_cost(fork) - tx1_regular = intrinsic_cost() + tx1_code.gas_cost(fork) - tx1_state + tx1_execution = intrinsic_cost() + tx1_code.gas_cost(fork) - tx1_state tx1_gas = gas_limit_cap + tx1_state state_available = block_gas_limit - tx1_state # tx2: full tx.gas exceeds state_available by 1, so rejected. tx2_gas = state_available + 1 - # Regular check must pass so rejection is pinned to state. - regular_available = block_gas_limit - tx1_regular - assert min(gas_limit_cap, tx2_gas) < regular_available + # Execution check must pass so rejection is pinned to state. + execution_available = block_gas_limit - tx1_execution + assert min(gas_limit_cap, tx2_gas) < execution_available tx1 = Transaction( to=tx1_contract, @@ -529,10 +530,10 @@ def test_block_gas_used_no_state_ops( fork: Fork, ) -> None: """ - Test block gas_used when regular gas dominates (no state operations). + Test block gas_used when execution gas dominates (no state operations). With no state-creating operations, state gas is 0 and block gas_used - should equal regular gas used. + should equal execution gas used. """ contract = pre.deploy_contract(code=Op.STOP) @@ -576,9 +577,9 @@ def test_block_gas_used_with_state_ops( ) intrinsic_cost = fork.transaction_intrinsic_cost_calculator() - block_regular_gas = intrinsic_cost() + code.regular_cost(fork) + block_execution_gas = intrinsic_cost() + code.execution_cost(fork) block_state_gas = code.state_cost(fork) - assert block_state_gas > block_regular_gas + assert block_state_gas > block_execution_gas blockchain_test( pre=pre, @@ -603,7 +604,7 @@ def test_block_2d_gas_valid_when_cumulative_exceeds_limit( """ Verify block validity under 2D gas when sum(txGasUsed) > gas_limit. - EIP-8037 block validity: max(regular, state) <= gas_limit. + EIP-8037 block validity: max(execution, state) <= gas_limit. Receipt cumulative_gas_used sums both dimensions per-tx, so it can legitimately exceed gas_limit. Clients must not use the 1D cumulative check for block validation. @@ -613,21 +614,21 @@ def test_block_2d_gas_valid_when_cumulative_exceeds_limit( sstore_code = Op.SSTORE(0, 1, new_value=1) sstore_state_gas = sstore_code.state_cost(fork) - tx_regular = ( - sstore_code.regular_cost(fork) + tx_execution = ( + sstore_code.execution_cost(fork) + fork.transaction_intrinsic_cost_calculator()() ) tx_state = sstore_state_gas - tx_gas_used = tx_regular + tx_state + tx_gas_used = tx_execution + tx_state - assert tx_state > tx_regular + assert tx_state > tx_execution block_gas_used = tx_state env = Environment(gas_limit=block_gas_limit) tx_limit = tx_gas_used + 1000 # Strict rule counts full `tx.gas` per dimension; state is the - # binding one (tx_state > tx_regular), so every `tx_limit` must + # binding one (tx_state > tx_execution), so every `tx_limit` must # fit the remaining state gas. num_txs = (block_gas_limit - tx_limit) // tx_state + 1 two_d_bound = num_txs * block_gas_used @@ -796,7 +797,7 @@ def test_top_level_failure_zeros_block_state_gas( With `state_gas_used` zeroed on failure, `block_state_gas_used` excludes any state gas consumed during the failed transaction and - the block header `gas_used` falls back to the regular gas + the block header `gas_used` falls back to the execution gas component alone. """ gas_limit_cap = fork.transaction_gas_limit_cap() @@ -820,19 +821,19 @@ def test_top_level_failure_zeros_block_state_gas( ) if failure_mode == "revert": - expected_block_regular = ( + expected_block_execution = ( intrinsic_cost + code.gas_cost(fork) - sstore_state_gas ) else: # Exceptional halt and out of gas zero gas_left. - expected_block_regular = tx_gas - sstore_state_gas + expected_block_execution = tx_gas - sstore_state_gas blockchain_test( pre=pre, blocks=[ Block( txs=[tx], - header_verify=Header(gas_used=expected_block_regular), + header_verify=Header(gas_used=expected_block_execution), ), ], post={contract: Account(storage={})}, @@ -851,7 +852,7 @@ def test_creation_tx_failure_preserves_intrinsic_state_gas( A creation tx (to=None) whose initcode halts exercises both the intrinsic state gas for the new account and the top level failure refund of execution state gas. The test asserts the block header - `gas_used` equals `max(block_regular, intrinsic_state_gas)`, + `gas_used` equals `max(block_execution, intrinsic_state_gas)`, guarding that the failure path does not raise and that block accounting does not underflow when the refund is applied. """ @@ -871,8 +872,8 @@ def test_creation_tx_failure_preserves_intrinsic_state_gas( sender=pre.fund_eoa(), ) - block_regular = tx_gas - create_intrinsic_state - sstore_state_gas - expected_gas_used = max(block_regular, create_intrinsic_state) + block_execution = tx_gas - create_intrinsic_state - sstore_state_gas + expected_gas_used = max(block_execution, create_intrinsic_state) blockchain_test( pre=pre, @@ -918,7 +919,7 @@ def test_subcall_failure_does_not_zero_top_level_state_gas( sender=pre.fund_eoa(), ) - # Parent's SSTORE state gas dominates tx_regular and surfaces in + # Parent's SSTORE state gas dominates tx_execution and surfaces in # the block header, proving the top level refund is scoped to # top level failures and not child reverts. blockchain_test( @@ -968,7 +969,7 @@ def test_top_level_failure_spilled_state_gas( `gas_left` and only the reservoir-funded portion to the reservoir. - REVERT preserves `gas_left`, so all state gas is refunded and the - sender pays only the regular component. + sender pays only the execution component. - Halt refills LIFO then zeros `gas_left`, so the spill is burned and only the start reservoir survives. """ @@ -1001,7 +1002,7 @@ def test_top_level_failure_spilled_state_gas( if failure_mode == "revert": # gas_left preserved, all state gas refunded, so the sender - # pays only the regular component. + # pays only the execution component. expected_cumulative = ( intrinsic_cost + parent_code.gas_cost(fork) - total_state ) @@ -1231,7 +1232,7 @@ def test_nested_failure_resets_to_tx_reservoir( Refunds are LIFO. On REVERT every state gas charge (body charges, spilled portions, and CREATE pre-charges) is refilled, the spill - landing back in `gas_left`, so the user pays only regular charges + landing back in `gas_left`, so the user pays only execution charges plus intrinsic. On HALT the LIFO refill returns spilled state gas to `gas_left`, which is then zeroed, so only the start reservoir survives and the user pays `tx_gas - reservoir = gas_limit_cap`, @@ -1240,7 +1241,7 @@ def test_nested_failure_resets_to_tx_reservoir( Two assertions cross-check the gas accounting: - `cumulative_gas_used` (receipt) pins `tx.gas - gas_left - state_gas_left`, catching bugs in the leftover split. - - `header.gas_used` pins `max(block_regular, block_state)` via + - `header.gas_used` pins `max(block_execution, block_state)` via the block accumulators. """ gas_limit_cap = fork.transaction_gas_limit_cap() @@ -1270,7 +1271,7 @@ def test_nested_failure_resets_to_tx_reservoir( else: top, frame_codes = _build_create_chain(pre, frame_bodies, terminator) - sum_regular = sum(code.regular_cost(fork) for code in frame_codes) + sum_execution = sum(code.execution_cost(fork) for code in frame_codes) if failure_mode == "halt": # LIFO refill returns spilled state gas (and spilled CREATE # pre-charges) to gas_left, which halt then zeros. Only the @@ -1278,17 +1279,17 @@ def test_nested_failure_resets_to_tx_reservoir( expected_cumulative = tx_gas - reservoir assert expected_cumulative == gas_limit_cap # Header: all gas_left (including the refilled spill) is - # consumed as regular. Block state gas is zero for plain + # consumed as execution. Block state gas is zero for plain # frames. expected_header_gas_used = gas_limit_cap elif failure_mode == "revert": # Revert preserves gas_left, full state gas refund, so the - # user pays only regular costs plus intrinsic. - expected_cumulative = intrinsic_cost + sum_regular - # Header reflects the regular-vs-state attribution directly: + # user pays only execution costs plus intrinsic. + expected_cumulative = intrinsic_cost + sum_execution + # Header reflects the execution-vs-state attribution directly: # state_gas_used is zeroed by the tx error handler, so only - # regular gas usage shows up. - expected_header_gas_used = intrinsic_cost + sum_regular + # execution gas usage shows up. + expected_header_gas_used = intrinsic_cost + sum_execution else: raise ValueError("Invariant, unreachable code.") @@ -1461,7 +1462,7 @@ def test_top_level_opcode_oog_before_frame_end_does_not_refund_state_gas( unsettled state gas. The transaction has enough gas for the SSTORE and all preceding - regular work, but is one gas short of the MCOPY regular cost. The + execution work, but is one gas short of the MCOPY execution cost. The frame halts before frame-end settlement runs, so the earlier SSTORE never contributes execution state gas to refund. """ @@ -1478,7 +1479,7 @@ def test_top_level_opcode_oog_before_frame_end_does_not_refund_state_gas( ) contract = pre.deploy_contract(code=code) - # One gas short of the regular-gas portion of successful execution. + # One gas short of the execution-gas portion of successful execution. tx_gas = intrinsic_cost + code.gas_cost(fork) - sstore_state_gas - 1 tx = Transaction( @@ -1508,14 +1509,14 @@ def test_top_level_opcode_oog_before_frame_end_does_not_refund_state_gas( ], ) @pytest.mark.valid_from("EIP8037") -def test_access_list_gas_is_regular_not_state( +def test_access_list_gas_is_execution_not_state( blockchain_test: BlockchainTestFiller, pre: Alloc, fork: Fork, num_access_list_entries: int, slots_per_entry: int, ) -> None: - """Verify EIP-2930 access list gas counts as regular, not state.""" + """Verify EIP-2930 access list gas counts as execution, not state.""" contract = pre.deploy_contract(code=Op.STOP) access_list = [] @@ -1549,12 +1550,12 @@ def test_access_list_gas_is_regular_not_state( @pytest.mark.valid_from("EIP8037") -def test_access_list_warm_savings_stay_regular( +def test_access_list_warm_savings_stay_execution( blockchain_test: BlockchainTestFiller, pre: Alloc, fork: Fork, ) -> None: - """Verify access-list warm savings stay in regular gas.""" + """Verify access-list warm savings stay in execution gas.""" sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) contract = pre.deploy_contract( @@ -1671,7 +1672,7 @@ def test_subcall_revert_does_not_leak_grandchild_storage_clear_credit( # phantom credit surfaces as residual reservoir at tx end. legit_state_cost = 2 * num_slots * sstore_state_gas - # `bytecode.gas_cost(fork)` sums each opcode's regular and state + # `bytecode.gas_cost(fork)` sums each opcode's execution and state # contributions. Setup/phantom SSTOREs predict +sstore_state_gas # each; inner's clears predict 0 (the negative byte_delta is a # frame-level effect, not per-opcode). The frame-end byte_delta @@ -1825,11 +1826,11 @@ def test_subcall_set_clear_revert_pays_no_state_gas( ) -> None: """ A child frame doing SSTORE 0 to x to 0 then REVERT must bill the - sender only intrinsic + regular costs. + sender only intrinsic + execution costs. Both SSTOREs roll back with the REVERT, so the matching state-gas charge and refund cancel cleanly. The receipt's - `cumulative_gas_used` equals the regular baseline; a leftover + `cumulative_gas_used` equals the execution baseline; a leftover `sstore_state_gas` would surface a double-charge at the failure boundary. @@ -1862,8 +1863,8 @@ def test_subcall_set_clear_revert_pays_no_state_gas( expected_cumulative = ( intrinsic_cost - + top_code.regular_cost(fork) - + inner_code.regular_cost(fork) + + top_code.execution_cost(fork) + + inner_code.execution_cost(fork) ) tx = Transaction( diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_selfdestruct.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_selfdestruct.py index f02eaca1336..7ff6505041e 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_selfdestruct.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_selfdestruct.py @@ -227,7 +227,7 @@ def test_selfdestruct_state_gas_refilled_on_ancestor_revert( The inner frame spills the NEW_ACCOUNT charge and self-destructs successfully, then the caller reverts: the beneficiary creation rolls back and the spilled state charge is refilled. The EIP-8038 - regular account-write charge for the attempted empty-account value + execution account-write charge for the attempted empty-account value transfer remains billed. """ beneficiary = 0xDEAD @@ -236,10 +236,10 @@ def test_selfdestruct_state_gas_refilled_on_ancestor_revert( caller_code = Op.POP(Op.CALL(gas=Op.GAS, address=inner)) + Op.REVERT(0, 0) caller = pre.deploy_contract(code=caller_code) - expected_regular = ( + expected_execution = ( fork.transaction_intrinsic_cost_calculator()() + caller_code.gas_cost(fork) - + inner_code.regular_cost(fork) + + inner_code.execution_cost(fork) ) tx = Transaction(to=caller, sender=pre.fund_eoa()) @@ -247,7 +247,7 @@ def test_selfdestruct_state_gas_refilled_on_ancestor_revert( pre=pre, post={beneficiary: Account.NONEXISTENT, inner: Account(balance=1)}, tx=tx, - blockchain_test_header_verify=Header(gas_used=expected_regular), + blockchain_test_header_verify=Header(gas_used=expected_execution), ) @@ -298,13 +298,13 @@ def test_create_selfdestruct_no_refund_account_and_storage( total_state_gas = factory_code.state_cost(fork) + init_code.state_cost( fork ) - regular_used = ( + execution_used = ( intrinsic_gas + factory_code.gas_cost(fork) + init_code.gas_cost(fork) - total_state_gas ) - expected_gas_used = max(regular_used, total_state_gas) + expected_gas_used = max(execution_used, total_state_gas) tx = Transaction( to=factory, @@ -439,8 +439,8 @@ def test_create_selfdestruct_code_deposit_no_refund_header_check( sender=pre.fund_eoa(), ) - baseline_block_regular = 0x94C8 - expected_gas_used = max(baseline_block_regular, total_state_gas) + baseline_block_execution = 0x94C8 + expected_gas_used = max(baseline_block_execution, total_state_gas) blockchain_test( pre=pre, @@ -493,14 +493,14 @@ def test_create_selfdestruct_sstore_restoration_refund( new_account_state_gas = factory_code.state_cost(fork) state_used = new_account_state_gas - regular_used = ( + execution_used = ( intrinsic_gas + factory_code.gas_cost(fork) + init_code.gas_cost(fork) - new_account_state_gas - sstore_state_gas ) - expected_gas_used = max(regular_used, state_used) + expected_gas_used = max(execution_used, state_used) tx = Transaction( to=factory, @@ -531,7 +531,7 @@ def test_selfdestruct_pre_existing_account_no_refund( state gas back into the reservoir. A contract deployed in `pre` is destroyed by the tx; `accounts_to_delete` contains it but `created_accounts` does not, so no refund is applied. The block - header `gas_used` reflects the full regular-gas tx cost (no + header `gas_used` reflects the full execution-gas tx cost (no state-gas refund offset). """ intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() @@ -545,8 +545,8 @@ def test_selfdestruct_pre_existing_account_no_refund( caller = pre.deploy_contract(code=caller_code) # No refund offset: both caller_code and victim_code are pure - # regular gas (SELFDESTRUCT to self, no value-to-new-account). - tx_regular = ( + # execution gas (SELFDESTRUCT to self, no value-to-new-account). + tx_execution = ( intrinsic_gas + caller_code.gas_cost(fork) + victim_code.gas_cost(fork) ) @@ -560,7 +560,7 @@ def test_selfdestruct_pre_existing_account_no_refund( # does not delete it — the account still exists after the tx. blockchain_test( pre=pre, - blocks=[Block(txs=[tx], header_verify=Header(gas_used=tx_regular))], + blocks=[Block(txs=[tx], header_verify=Header(gas_used=tx_execution))], post={victim: Account(code=victim_code)}, ) @@ -591,9 +591,9 @@ def test_selfdestruct_via_delegatecall_chain_no_refund( # Bottom of the chain does the SELFDESTRUCT; intermediate helpers # just delegate further down. Track each frame's bytecode so we - # can sum its regular gas into `expected_gas_used` below. + # can sum its execution gas into `expected_gas_used` below. sd_code = Op.SELFDESTRUCT.with_metadata(address_warm=True)(Op.ADDRESS) - chain_regular_gas = sd_code.gas_cost(fork) + chain_execution_gas = sd_code.gas_cost(fork) delegate_target = pre.deploy_contract(code=sd_code) for _ in range(num_hops - 1): hop_code = ( @@ -604,7 +604,7 @@ def test_selfdestruct_via_delegatecall_chain_no_refund( ) + Op.STOP ) - chain_regular_gas += hop_code.gas_cost(fork) + chain_execution_gas += hop_code.gas_cost(fork) delegate_target = pre.deploy_contract(code=hop_code) # A's deployed runtime: one delegation into the top of the chain. @@ -666,15 +666,15 @@ def test_selfdestruct_via_delegatecall_chain_no_refund( created_address = compute_create_address(address=factory, nonce=1) total_state_gas = factory_code.state_cost(fork) + initcode.state_cost(fork) - regular_used = ( + execution_used = ( intrinsic_gas + factory_code.gas_cost(fork) + initcode.gas_cost(fork) + deployed_code.gas_cost(fork) - + chain_regular_gas + + chain_execution_gas - total_state_gas ) - expected_gas_used = max(regular_used, total_state_gas) + expected_gas_used = max(execution_used, total_state_gas) tx = Transaction( to=factory, @@ -706,18 +706,18 @@ def test_selfdestruct_new_beneficiary_account_write_cost( ) -> None: """ Verify SELFDESTRUCT to a new beneficiary charges `ACCOUNT_WRITE` - regular gas plus the account-creation state gas, and not the - legacy combined regular account-creation cost. + execution gas plus the account-creation state gas, and not the + legacy combined execution account-creation cost. """ beneficiary = pre.fund_eoa(amount=0) victim_code = Op.SELFDESTRUCT(beneficiary, account_new=True) victim = pre.deploy_contract(code=victim_code, balance=1) - # Tight budget: slack is less than the legacy 25,000 regular - # account-creation cost minus `ACCOUNT_WRITE`, so any regular draw + # Tight budget: slack is less than the legacy 25,000 execution + # account-creation cost minus `ACCOUNT_WRITE`, so any execution draw # beyond `ACCOUNT_WRITE` would OOG. The opcode metadata folds the - # `ACCOUNT_WRITE` regular cost and the account-creation state gas + # `ACCOUNT_WRITE` execution cost and the account-creation state gas # into `gas_cost`. intrinsic = fork.transaction_intrinsic_cost_calculator()() tx = Transaction( @@ -777,20 +777,20 @@ def test_create_tx_selfdestruct_initcode_state_gas( init_code = Op.SELFDESTRUCT.with_metadata( account_new=creates_new_beneficiary )(beneficiary) - intrinsic_regular = intrinsic_calc( + intrinsic_execution = intrinsic_calc( calldata=bytes(init_code), contract_creation=True ) expected_state = fork.transaction_top_frame_state_gas( contract_creation=True ) + init_code.state_cost(fork) - expected_regular = intrinsic_regular + init_code.regular_cost(fork) - expected_gas_used = max(expected_regular, expected_state) + expected_execution = intrinsic_execution + init_code.execution_cost(fork) + expected_gas_used = max(expected_execution, expected_state) tx = Transaction( to=None, data=init_code, - gas_limit=intrinsic_regular + 100_000 + expected_state, + gas_limit=intrinsic_execution + 100_000 + expected_state, sender=sender, value=tx_value, ) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py index c537b42088b..8a84ec8571b 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py @@ -8,7 +8,7 @@ are charged lazily at the top frame in ``set_delegation``, keyed on each authority's pre-transaction state: -* ``NEW_ACCOUNT`` (state) + ``ACCOUNT_WRITE`` (regular) when the +* ``NEW_ACCOUNT`` (state) + ``ACCOUNT_WRITE`` (execution) when the authority's account leaf does not exist pre-tx (it gets created); and * ``AUTH_BASE`` (state) when a net-new delegation indicator is written -- the authority holds no delegation both before the transaction and at @@ -18,12 +18,12 @@ For a value-free type-4 transaction whose recipient runs code ``code``: * the receipt ``cumulative_gas_used`` is the plain sum - ``intrinsic_regular + top_frame_regular + top_frame_state + - execution_regular + execution_state`` (no refund term); and -* the header ``gas_used`` is ``max(block_regular, block_state)`` where - ``block_regular = intrinsic_regular + top_frame_regular + - execution_regular`` and ``block_state = top_frame_state + - execution_state``. + ``intrinsic_execution + top_frame_execution + top_frame_state + + evm_execution + evm_state`` (no refund term); and +* the header ``gas_used`` is ``max(block_execution, block_state)`` where + ``block_execution = intrinsic_execution + top_frame_execution + + evm_execution`` and ``block_state = top_frame_state + + evm_state``. Tests for [EIP-8037: State Creation Gas Cost Increase] (https://eips.ethereum.org/EIPS/eip-8037); the ``valid_from("EIP8037")`` @@ -70,14 +70,14 @@ def _auth_gas( sends_value: bool = False, delegation_warm: bool = False, ) -> tuple[int, int, int]: - """Return (intrinsic_regular, top_frame_regular, top_frame_state).""" - intrinsic_regular = fork.transaction_intrinsic_cost_calculator()( + """Return (intrinsic_execution, top_frame_execution, top_frame_state).""" + intrinsic_execution = fork.transaction_intrinsic_cost_calculator()( authorization_list_or_count=authorization_list, recipient_type=recipient_type, sends_value=sends_value, return_cost_deducted_prior_execution=True, ) - top_frame_regular = fork.transaction_top_frame_gas_calculator()( + top_frame_execution = fork.transaction_top_frame_gas_calculator()( recipient_type=recipient_type, sends_value=sends_value, delegation_warm=delegation_warm, @@ -88,26 +88,26 @@ def _auth_gas( sends_value=sends_value, authorizations=authorization_list, ) - return intrinsic_regular, top_frame_regular, top_frame_state + return intrinsic_execution, top_frame_execution, top_frame_state def _receipt_and_header( - intrinsic_regular: int, - top_frame_regular: int, + intrinsic_execution: int, + top_frame_execution: int, top_frame_state: int, *, - execution_regular: int = 0, - execution_state: int = 0, + evm_execution: int = 0, + evm_state: int = 0, ) -> tuple[int, int]: """ Return the (receipt cumulative_gas_used, header gas_used) for a successful (non-reverting) transaction under the no-refund top-frame model. """ - block_regular = intrinsic_regular + top_frame_regular + execution_regular - block_state = top_frame_state + execution_state - cumulative_gas_used = block_regular + block_state - header_gas_used = max(block_regular, block_state) + block_execution = intrinsic_execution + top_frame_execution + evm_execution + block_state = top_frame_state + evm_state + cumulative_gas_used = block_execution + block_state + header_gas_used = max(block_execution, block_state) return cumulative_gas_used, header_gas_used @@ -131,8 +131,8 @@ def test_authorization_state_gas_scaling( Each authority is an existing funded EOA gaining a fresh delegation, so ``set_delegation`` charges only the top-frame ``AUTH_BASE`` per authorization (no ``NEW_ACCOUNT`` / ``ACCOUNT_WRITE`` and no refund). - The receipt gas is the regular intrinsic plus ``num_auths * - AUTH_BASE`` and the header ``gas_used`` is the max of the regular and + The receipt gas is the execution intrinsic plus ``num_auths * + AUTH_BASE`` and the header ``gas_used`` is the max of the execution and state blocks. """ contract = pre.deploy_contract(code=Op.STOP) @@ -149,11 +149,11 @@ def test_authorization_state_gas_scaling( for signer in signers ] - intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( + intrinsic_execution, top_frame_execution, top_frame_state = _auth_gas( fork, authorization_list ) cumulative_gas_used, header_gas_used = _receipt_and_header( - intrinsic_regular, top_frame_regular, top_frame_state + intrinsic_execution, top_frame_execution, top_frame_state ) tx = Transaction( @@ -194,9 +194,9 @@ def test_set_code_tx_below_total_intrinsic( num_auths: int, ) -> None: """ - Reject a set_code tx one gas below the (now regular-only) intrinsic. + Reject a set_code tx one gas below the (now execution-only) intrinsic. - Under EIP-2780 the authorization intrinsic is entirely regular (the + Under EIP-2780 the authorization intrinsic is entirely execution (the state-dependent costs moved to the top frame), so the intrinsic gas the transaction must cover is exactly ``fork.transaction_intrinsic_cost_calculator()(auth_list)``. Sweeping @@ -245,7 +245,7 @@ def test_existing_account_no_refund( Its leaf exists, so ``set_delegation`` charges neither ``NEW_ACCOUNT`` nor ``ACCOUNT_WRITE`` (and, unlike the superseded EIP-8037 behaviour, refunds neither); it charges only the top-frame ``AUTH_BASE``. The - receipt gas is therefore exactly the regular intrinsic plus + receipt gas is therefore exactly the execution intrinsic plus ``AUTH_BASE``. """ contract = pre.deploy_contract(code=Op.STOP) @@ -261,11 +261,11 @@ def test_existing_account_no_refund( ), ] - intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( + intrinsic_execution, top_frame_execution, top_frame_state = _auth_gas( fork, authorization_list ) cumulative_gas_used, header_gas_used = _receipt_and_header( - intrinsic_regular, top_frame_regular, top_frame_state + intrinsic_execution, top_frame_execution, top_frame_state ) tx = Transaction( @@ -322,11 +322,11 @@ def test_mixed_new_and_existing_auths( ), ] - intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( + intrinsic_execution, top_frame_execution, top_frame_state = _auth_gas( fork, authorization_list ) cumulative_gas_used, header_gas_used = _receipt_and_header( - intrinsic_regular, top_frame_regular, top_frame_state + intrinsic_execution, top_frame_execution, top_frame_state ) tx = Transaction( @@ -367,15 +367,15 @@ def test_authorization_with_sstore( The authority (an existing EOA) gains a fresh delegation, charged the top-frame ``AUTH_BASE``; the called recipient then performs an SSTORE - whose regular and state costs are charged during execution. The header - ``gas_used`` is the max of the regular block and the (``AUTH_BASE`` + + whose execution and state costs are charged during execution. The header + ``gas_used`` is the max of the execution block and the (``AUTH_BASE`` + SSTORE) state block. """ storage = Storage() code = Op.SSTORE(storage.store_next(1), 1) contract = pre.deploy_contract(code=code) - execution_regular = code.regular_cost(fork) - execution_state = code.state_cost(fork) + evm_execution = code.execution_cost(fork) + evm_state = code.state_cost(fork) signer = pre.fund_eoa() authorization_list = [ @@ -388,15 +388,15 @@ def test_authorization_with_sstore( ), ] - intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( + intrinsic_execution, top_frame_execution, top_frame_state = _auth_gas( fork, authorization_list ) _, header_gas_used = _receipt_and_header( - intrinsic_regular, - top_frame_regular, + intrinsic_execution, + top_frame_execution, top_frame_state, - execution_regular=execution_regular, - execution_state=execution_state, + evm_execution=evm_execution, + evm_state=evm_state, ) tx = Transaction( @@ -429,15 +429,15 @@ def test_existing_account_no_refund_with_sstore( The existing authority pays only the top-frame ``AUTH_BASE`` (no ``NEW_ACCOUNT`` / ``ACCOUNT_WRITE`` and no refund), and the recipient's - SSTORE pays its own regular + state costs. The receipt gas is the + SSTORE pays its own execution + state costs. The receipt gas is the exact sum of the intrinsic, the ``AUTH_BASE`` and the SSTORE cost; there is no reservoir refund to draw on. """ storage = Storage() code = Op.SSTORE(storage.store_next(1), 1) contract = pre.deploy_contract(code=code) - execution_regular = code.regular_cost(fork) - execution_state = code.state_cost(fork) + evm_execution = code.execution_cost(fork) + evm_state = code.state_cost(fork) signer = pre.fund_eoa() authorization_list = [ @@ -450,15 +450,15 @@ def test_existing_account_no_refund_with_sstore( ), ] - intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( + intrinsic_execution, top_frame_execution, top_frame_state = _auth_gas( fork, authorization_list ) cumulative_gas_used, header_gas_used = _receipt_and_header( - intrinsic_regular, - top_frame_regular, + intrinsic_execution, + top_frame_execution, top_frame_state, - execution_regular=execution_regular, - execution_state=execution_state, + evm_execution=evm_execution, + evm_state=evm_state, ) tx = Transaction( @@ -569,11 +569,11 @@ def test_auth_block_gas_accounting( ), ] - intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( + intrinsic_execution, top_frame_execution, top_frame_state = _auth_gas( fork, authorization_list ) cumulative_gas_used, header_gas_used = _receipt_and_header( - intrinsic_regular, top_frame_regular, top_frame_state + intrinsic_execution, top_frame_execution, top_frame_state ) post_code = ( @@ -632,13 +632,13 @@ def test_invalid_nonce_auth_still_charges_intrinsic( ), ] - intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( + intrinsic_execution, top_frame_execution, top_frame_state = _auth_gas( fork, authorization_list ) - assert top_frame_regular == 0 + assert top_frame_execution == 0 assert top_frame_state == 0 cumulative_gas_used, header_gas_used = _receipt_and_header( - intrinsic_regular, top_frame_regular, top_frame_state + intrinsic_execution, top_frame_execution, top_frame_state ) tx = Transaction( @@ -688,13 +688,13 @@ def test_invalid_chain_id_auth_still_charges_intrinsic( ), ] - intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( + intrinsic_execution, top_frame_execution, top_frame_state = _auth_gas( fork, authorization_list ) - assert top_frame_regular == 0 + assert top_frame_execution == 0 assert top_frame_state == 0 cumulative_gas_used, header_gas_used = _receipt_and_header( - intrinsic_regular, top_frame_regular, top_frame_state + intrinsic_execution, top_frame_execution, top_frame_state ) tx = Transaction( @@ -752,11 +752,11 @@ def test_self_sponsored_authorization( ), ] - intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( + intrinsic_execution, top_frame_execution, top_frame_state = _auth_gas( fork, authorization_list ) cumulative_gas_used, header_gas_used = _receipt_and_header( - intrinsic_regular, top_frame_regular, top_frame_state + intrinsic_execution, top_frame_execution, top_frame_state ) tx = Transaction( @@ -821,11 +821,11 @@ def test_duplicate_signer_authorizations( ), ] - intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( + intrinsic_execution, top_frame_execution, top_frame_state = _auth_gas( fork, authorization_list ) cumulative_gas_used, header_gas_used = _receipt_and_header( - intrinsic_regular, top_frame_regular, top_frame_state + intrinsic_execution, top_frame_execution, top_frame_state ) tx = Transaction( @@ -860,17 +860,17 @@ def test_auth_with_calldata_and_access_list( """ Test authorization combined with calldata and an access list. - The regular intrinsic folds in the calldata and access-list costs; on + The execution intrinsic folds in the calldata and access-list costs; on top of it the existing authority pays the top-frame ``AUTH_BASE`` and - the recipient's SSTORE pays its execution regular + state costs. The + the recipient's SSTORE pays its execution + state costs. The receipt gas is the exact sum, with no refund term. Access lists do not warm the authority under EIP-2780, so the auth charge is unaffected. """ storage = Storage() code = Op.SSTORE(storage.store_next(0x42), Op.CALLDATALOAD(0)) contract = pre.deploy_contract(code=code) - execution_regular = code.regular_cost(fork) - execution_state = code.state_cost(fork) + evm_execution = code.execution_cost(fork) + evm_state = code.state_cost(fork) signer = pre.fund_eoa() authorization_list = [ @@ -886,19 +886,21 @@ def test_auth_with_calldata_and_access_list( data = b"\x00" * 31 + b"\x42" access_list = [AccessList(address=contract, storage_keys=[])] - intrinsic_regular = fork.transaction_intrinsic_cost_calculator()( + intrinsic_execution = fork.transaction_intrinsic_cost_calculator()( authorization_list_or_count=authorization_list, calldata=data, access_list=access_list, return_cost_deducted_prior_execution=True, ) - _, top_frame_regular, top_frame_state = _auth_gas(fork, authorization_list) + _, top_frame_execution, top_frame_state = _auth_gas( + fork, authorization_list + ) cumulative_gas_used, header_gas_used = _receipt_and_header( - intrinsic_regular, - top_frame_regular, + intrinsic_execution, + top_frame_execution, top_frame_state, - execution_regular=execution_regular, - execution_state=execution_state, + evm_execution=evm_execution, + evm_state=evm_state, ) tx = Transaction( @@ -948,7 +950,7 @@ def test_mixed_valid_and_invalid_auths( ``set_delegation`` and each writes a net-new delegation on an existing authority, paying the first-write ``ACCOUNT_WRITE`` and the top-frame ``AUTH_BASE``; the invalid (wrong nonce) tuples are skipped and pay - no top-frame charge. The receipt gas is ``intrinsic_regular + + no top-frame charge. The receipt gas is ``intrinsic_execution + num_valid * (ACCOUNT_WRITE + AUTH_BASE)``. """ contract = pre.deploy_contract(code=Op.STOP) @@ -977,11 +979,11 @@ def test_mixed_valid_and_invalid_auths( for signer in invalid_signers ] - intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( + intrinsic_execution, top_frame_execution, top_frame_state = _auth_gas( fork, authorization_list ) cumulative_gas_used, header_gas_used = _receipt_and_header( - intrinsic_regular, top_frame_regular, top_frame_state + intrinsic_execution, top_frame_execution, top_frame_state ) tx = Transaction( @@ -1036,11 +1038,11 @@ def test_many_authorizations( for signer in signers ] - intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( + intrinsic_execution, top_frame_execution, top_frame_state = _auth_gas( fork, authorization_list ) cumulative_gas_used, header_gas_used = _receipt_and_header( - intrinsic_regular, top_frame_regular, top_frame_state + intrinsic_execution, top_frame_execution, top_frame_state ) tx = Transaction( @@ -1075,7 +1077,7 @@ def test_auth_with_multiple_sstores( The existing authority pays the top-frame ``AUTH_BASE`` and the recipient performs five distinct zero-to-nonzero SSTOREs, each paying - its own regular + state cost during execution. Verifies combined + its own execution + state cost during execution. Verifies combined accounting across the top-frame and execution state charges, all drawn from ``gas_left`` with no refund. """ @@ -1085,8 +1087,8 @@ def test_auth_with_multiple_sstores( for _ in range(num_sstores): code += Op.SSTORE(storage.store_next(1), 1) contract = pre.deploy_contract(code=code) - execution_regular = code.regular_cost(fork) - execution_state = code.state_cost(fork) + evm_execution = code.execution_cost(fork) + evm_state = code.state_cost(fork) signer = pre.fund_eoa() authorization_list = [ @@ -1099,15 +1101,15 @@ def test_auth_with_multiple_sstores( ), ] - intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( + intrinsic_execution, top_frame_execution, top_frame_state = _auth_gas( fork, authorization_list ) _, header_gas_used = _receipt_and_header( - intrinsic_regular, - top_frame_regular, + intrinsic_execution, + top_frame_execution, top_frame_state, - execution_regular=execution_regular, - execution_state=execution_state, + evm_execution=evm_execution, + evm_state=evm_state, ) tx = Transaction( @@ -1149,7 +1151,7 @@ def test_authorization_exact_state_gas_boundary( """ Test the intrinsic-gas boundary and the top-frame OOG behaviour. - Under EIP-2780 the intrinsic is regular-only, so the boundary keys off + Under EIP-2780 the intrinsic is execution-only, so the boundary keys off ``fork.transaction_intrinsic_cost_calculator()(auth_list)``. With ``gas_delta=-1`` the transaction is one gas below the intrinsic and is rejected as intrinsic-gas-too-low. With ``gas_delta=0`` the gas limit @@ -1265,8 +1267,8 @@ def test_multi_tx_block_auth_and_sstore( 1. a SetCode tx delegating an existing authority (top-frame ``AUTH_BASE``, no refund); and - 2. a regular tx performing a zero-to-nonzero SSTORE (execution regular - + state). + 2. a normal tx performing a zero-to-nonzero SSTORE (execution + + state gas). The per-transaction receipt ``cumulative_gas_used`` accumulates across the block, so tx1's receipt is its own cost and tx2's is the running @@ -1285,11 +1287,11 @@ def test_multi_tx_block_auth_and_sstore( writes_delegation=True, ), ] - intrinsic_regular_1, top_frame_regular_1, top_frame_state_1 = _auth_gas( - fork, authorization_list + intrinsic_execution_1, top_frame_execution_1, top_frame_state_1 = ( + _auth_gas(fork, authorization_list) ) tx1_gas, _ = _receipt_and_header( - intrinsic_regular_1, top_frame_regular_1, top_frame_state_1 + intrinsic_execution_1, top_frame_execution_1, top_frame_state_1 ) tx_1 = Transaction( to=contract, @@ -1302,13 +1304,13 @@ def test_multi_tx_block_auth_and_sstore( storage = Storage() sstore_code = Op.SSTORE(storage.store_next(1), 1) sstore_contract = pre.deploy_contract(code=sstore_code) - intrinsic_regular_2 = fork.transaction_intrinsic_cost_calculator()( + intrinsic_execution_2 = fork.transaction_intrinsic_cost_calculator()( recipient_type=RecipientType.CONTRACT, return_cost_deducted_prior_execution=True, ) tx2_gas = ( - intrinsic_regular_2 - + sstore_code.regular_cost(fork) + intrinsic_execution_2 + + sstore_code.execution_cost(fork) + sstore_code.state_cost(fork) ) tx_2 = Transaction( @@ -1352,8 +1354,8 @@ def test_fresh_authority_and_sstores_full_state( for _ in range(num_sstores): code += Op.SSTORE(storage.store_next(1), 1) contract = pre.deploy_contract(code=code) - execution_regular = code.regular_cost(fork) - execution_state = code.state_cost(fork) + evm_execution = code.execution_cost(fork) + evm_state = code.state_cost(fork) signer = pre.fund_eoa(amount=0) authorization_list = [ @@ -1366,15 +1368,15 @@ def test_fresh_authority_and_sstores_full_state( ), ] - intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( + intrinsic_execution, top_frame_execution, top_frame_state = _auth_gas( fork, authorization_list ) cumulative_gas_used, header_gas_used = _receipt_and_header( - intrinsic_regular, - top_frame_regular, + intrinsic_execution, + top_frame_execution, top_frame_state, - execution_regular=execution_regular, - execution_state=execution_state, + evm_execution=evm_execution, + evm_state=evm_state, ) tx = Transaction( @@ -1422,7 +1424,7 @@ def test_existing_account_auth_header_gas_used( Every authority is an existing account gaining a fresh delegation, so each pays only the top-frame ``AUTH_BASE`` (no ``NEW_ACCOUNT`` / ``ACCOUNT_WRITE`` and no refund). With STOP execution the header - ``gas_used`` is ``max(intrinsic_regular, num_auths * AUTH_BASE)``. + ``gas_used`` is ``max(intrinsic_execution, num_auths * AUTH_BASE)``. """ contract = pre.deploy_contract(code=Op.STOP) @@ -1438,11 +1440,11 @@ def test_existing_account_auth_header_gas_used( for signer in signers ] - intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( + intrinsic_execution, top_frame_execution, top_frame_state = _auth_gas( fork, authorization_list ) _, header_gas_used = _receipt_and_header( - intrinsic_regular, top_frame_regular, top_frame_state + intrinsic_execution, top_frame_execution, top_frame_state ) tx = Transaction( @@ -1484,8 +1486,8 @@ def test_mixed_auths_header_gas_used( Existing authorities pay only ``AUTH_BASE``; new (nonexistent) authorities additionally pay ``NEW_ACCOUNT`` (state) + ``ACCOUNT_WRITE`` - (regular) for the created leaf. The header ``gas_used`` is - ``max(block_regular, block_state)`` over the summed top-frame charges, + (execution) for the created leaf. The header ``gas_used`` is + ``max(block_execution, block_state)`` over the summed top-frame charges, with no refund term. """ contract = pre.deploy_contract(code=Op.STOP) @@ -1513,11 +1515,11 @@ def test_mixed_auths_header_gas_used( for signer in new_signers ] - intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( + intrinsic_execution, top_frame_execution, top_frame_state = _auth_gas( fork, authorization_list ) _, header_gas_used = _receipt_and_header( - intrinsic_regular, top_frame_regular, top_frame_state + intrinsic_execution, top_frame_execution, top_frame_state ) tx = Transaction( @@ -1562,13 +1564,13 @@ def test_auth_state_gas_persists_on_top_level_revert( folded out of the frame's refillable pools. The recipient writes an SSTORE then REVERTs: the slot rolls back with the frame, so the SSTORE's ``STORAGE_SET`` state gas *is* refilled. The receipt is - therefore the intrinsic and top-frame charges (regular and state) - plus the regular execution gas, with only the authorization's state + therefore the intrinsic and top-frame charges (execution and state) + plus the execution gas, with only the authorization's state portion in the block's state component. """ code = Op.SSTORE(0, 1) + Op.REVERT(0, 0) contract = pre.deploy_contract(code=code) - execution_regular = code.regular_cost(fork) + evm_execution = code.execution_cost(fork) signer = pre.fund_eoa() authorization_list = [ @@ -1581,16 +1583,16 @@ def test_auth_state_gas_persists_on_top_level_revert( ), ] - intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( + intrinsic_execution, top_frame_execution, top_frame_state = _auth_gas( fork, authorization_list ) # The SSTORE's state gas is refilled by the REVERT (the slot rolls # back); the authorization's state gas persists with its delegation. cumulative_gas_used, header_gas_used = _receipt_and_header( - intrinsic_regular, - top_frame_regular, + intrinsic_execution, + top_frame_execution, top_frame_state, - execution_regular=execution_regular, + evm_execution=evm_execution, ) tx = Transaction( @@ -1646,15 +1648,15 @@ def test_auth_state_gas_in_header_after_failure( and so does the state gas that paid for it (``NEW_ACCOUNT`` + ``AUTH_BASE`` for a fresh authority, ``AUTH_BASE`` for an existing one), which is folded out of the frame's refillable pools. The - header is ``max(block_regular, block_state)``: + header is ``max(block_execution, block_state)``: - * REVERT -- the unused execution budget returns, so the regular - component is ``intrinsic_regular + top_frame_regular + - execution_regular`` and the state component is the persisting + * REVERT -- the unused execution budget returns, so the execution + component is ``intrinsic_execution + top_frame_execution + + evm_execution`` and the state component is the persisting authorization state gas. * HALT / OOG -- the frame consumes its whole gas limit; the authorization state gas within it is accounted on the state - component, and the remainder on the regular component. + component, and the remainder on the execution component. """ gas_limit = 500_000 @@ -1687,22 +1689,22 @@ def test_auth_state_gas_in_header_after_failure( ), ] - intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( + intrinsic_execution, top_frame_execution, top_frame_state = _auth_gas( fork, authorization_list ) if failure_mode == "revert": # The authorization's state gas persists with its delegation. _, expected_gas_used = _receipt_and_header( - intrinsic_regular, - top_frame_regular, + intrinsic_execution, + top_frame_execution, top_frame_state, - execution_regular=revert_code.regular_cost(fork), + evm_execution=revert_code.execution_cost(fork), ) else: # HALT / OOG consume the whole gas limit, of which the # persisting authorization state gas is accounted on the state - # component and the remainder on the regular component. + # component and the remainder on the execution component. expected_gas_used = max(gas_limit - top_frame_state, top_frame_state) tx = Transaction( @@ -1743,8 +1745,8 @@ def test_auth_sender_billing_after_failure( top-level REVERT. The delegation persists through the REVERT, so the state gas that - paid for it stays billed alongside the regular gas: the sender pays - ``intrinsic_regular + top_frame_regular + revert_regular`` plus the + paid for it stays billed alongside the execution gas: the sender pays + ``intrinsic_execution + top_frame_execution + revert_execution`` plus the authorization's state charges. Both authorities pay the first-write ``ACCOUNT_WRITE`` and the ``AUTH_BASE``; a new authority additionally pays ``NEW_ACCOUNT`` for the created leaf, so its @@ -1772,16 +1774,16 @@ def test_auth_sender_billing_after_failure( ), ] - intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( + intrinsic_execution, top_frame_execution, top_frame_state = _auth_gas( fork, authorization_list ) # The authorization's state gas persists with its delegation across # the REVERT and stays billed to the sender. expected_cumulative, header_gas_used = _receipt_and_header( - intrinsic_regular, - top_frame_regular, + intrinsic_execution, + top_frame_execution, top_frame_state, - execution_regular=revert_code.regular_cost(fork), + evm_execution=revert_code.execution_cost(fork), ) tx = Transaction( @@ -1834,8 +1836,8 @@ def test_auth_and_execution_state_oog_boundary( storage = Storage() target_code = Op.SSTORE(storage.store_next(1), 1) target = pre.deploy_contract(code=target_code) - execution_regular = target_code.regular_cost(fork) - execution_state = target_code.state_cost(fork) + evm_execution = target_code.execution_cost(fork) + evm_state = target_code.state_cost(fork) authority = pre.fund_eoa() authorization_list = [ @@ -1848,15 +1850,15 @@ def test_auth_and_execution_state_oog_boundary( ), ] - intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( + intrinsic_execution, top_frame_execution, top_frame_state = _auth_gas( fork, authorization_list ) full_cost = ( - intrinsic_regular - + top_frame_regular + intrinsic_execution + + top_frame_execution + top_frame_state - + execution_regular - + execution_state + + evm_execution + + evm_state ) gas_limit = full_cost + gas_delta gas_limit_cap = fork.transaction_gas_limit_cap() @@ -1866,11 +1868,11 @@ def test_auth_and_execution_state_oog_boundary( fits = gas_delta >= 0 if fits: _, header_gas_used = _receipt_and_header( - intrinsic_regular, - top_frame_regular, + intrinsic_execution, + top_frame_execution, top_frame_state, - execution_regular=execution_regular, - execution_state=execution_state, + evm_execution=evm_execution, + evm_state=evm_state, ) else: # One gas short: execution OOGs at the top frame, consuming the @@ -1958,13 +1960,13 @@ def test_invalid_auth_no_top_frame_charge( else: raise ValueError(f"unknown invalidity: {invalidity!r}") - intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( + intrinsic_execution, top_frame_execution, top_frame_state = _auth_gas( fork, [auth] ) - assert top_frame_regular == 0 + assert top_frame_execution == 0 assert top_frame_state == 0 cumulative_gas_used, header_gas_used = _receipt_and_header( - intrinsic_regular, top_frame_regular, top_frame_state + intrinsic_execution, top_frame_execution, top_frame_state ) tx = Transaction( @@ -2023,11 +2025,11 @@ def test_same_tx_create_then_clear( ), ] - intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( + intrinsic_execution, top_frame_execution, top_frame_state = _auth_gas( fork, authorization_list ) cumulative_gas_used, header_gas_used = _receipt_and_header( - intrinsic_regular, top_frame_regular, top_frame_state + intrinsic_execution, top_frame_execution, top_frame_state ) tx = Transaction( @@ -2089,12 +2091,12 @@ def test_same_tx_clear_then_reset_pre_delegated( ), ] - intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( + intrinsic_execution, top_frame_execution, top_frame_state = _auth_gas( fork, authorization_list ) assert top_frame_state == 0 cumulative_gas_used, header_gas_used = _receipt_and_header( - intrinsic_regular, top_frame_regular, top_frame_state + intrinsic_execution, top_frame_execution, top_frame_state ) tx = Transaction( @@ -2154,11 +2156,11 @@ def test_same_authority_increasing_nonce_net_once( for i in range(num_auths) ] - intrinsic_regular, top_frame_regular, top_frame_state = _auth_gas( + intrinsic_execution, top_frame_execution, top_frame_state = _auth_gas( fork, authorization_list ) cumulative_gas_used, header_gas_used = _receipt_and_header( - intrinsic_regular, top_frame_regular, top_frame_state + intrinsic_execution, top_frame_execution, top_frame_state ) tx = Transaction( diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_sstore.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_sstore.py index 71dcef99b11..d04a553e39b 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_sstore.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_sstore.py @@ -5,7 +5,7 @@ `STATE_BYTES_PER_STORAGE_SET * cost_per_state_byte` of state gas. Nonzero-to-nonzero writes charge no state gas. 0 to x to 0 restoration in the same tx refunds state gas directly to `state_gas_reservoir` -(inline at x to 0) and the regular write-cost portion to +(inline at x to 0) and the execution write-cost portion to `refund_counter`. Tests for [EIP-8037: State Creation Gas Cost Increase] @@ -46,7 +46,7 @@ def test_sstore_zero_to_nonzero( Writing a nonzero value to a previously-zero slot charges STATE_BYTES_PER_STORAGE_SET * cost_per_state_byte of state gas - in addition to regular gas. + in addition to execution gas. """ storage = Storage() contract = pre.deploy_contract( @@ -99,7 +99,7 @@ def test_sstore_nonzero_to_zero( Test SSTORE nonzero-to-zero charges no state gas. Clearing a storage slot (setting to zero) does not grow state and - earns a regular gas refund (GAS_STORAGE_CLEAR_REFUND). + earns an execution gas refund (GAS_STORAGE_CLEAR_REFUND). """ storage = Storage() contract = pre.deploy_contract( @@ -126,7 +126,7 @@ def test_sstore_zero_to_zero( Test SSTORE zero-to-zero charges no state gas. Writing zero to an already-zero slot creates no new state. Only - the warm access regular gas cost is charged. + the warm access execution gas cost is charged. """ storage = Storage() contract = pre.deploy_contract( @@ -182,7 +182,7 @@ def test_sstore_restoration_refund_credits_local_reservoir( # Sentinel written only if the CREATE returned (frame did not OOG). sentinel_slot = 2 # refund: clear (1→0, restoration refund). no refund: modify - # (1→2, no state growth, no refund) — same regular shape. + # (1→2, no state growth, no refund) — same execution shape. cleared_value = 0 if refund_sufficient else 2 clearing = pre.deploy_contract( code=( @@ -209,11 +209,13 @@ def test_sstore_restoration_refund_credits_local_reservoir( # The two parent `0→1` sets spill their state gas into `gas_left` # (tx is far below the per-tx cap, so no state-gas reservoir). - # Budget regular headroom for the call chain plus that spill, then + # Budget execution headroom for the call chain plus that spill, then # sit mid-window: short of also spill-funding `create_state_gas`, # so only a refund-credited reservoir can cover the CREATE. - regular_headroom = 200_000 - gas_limit = regular_headroom + 2 * sstore_state_gas + create_state_gas // 2 + execution_headroom = 200_000 + gas_limit = ( + execution_headroom + 2 * sstore_state_gas + create_state_gas // 2 + ) if refund_sufficient: post = {parent: Account(storage={0: 0, 1: 0, sentinel_slot: 1})} @@ -244,7 +246,7 @@ def test_sstore_restoration_refund( When a slot is written from zero to nonzero and then restored to zero in the same transaction, the state gas charge (STATE_BYTES_PER_STORAGE_SET * cost_per_state_byte) is refunded - via refund_counter along with the regular gas write cost. + via refund_counter along with the execution gas write cost. """ contract = pre.deploy_contract( code=(Op.SSTORE(0, 1) + Op.SSTORE(0, 0)), @@ -271,7 +273,7 @@ def test_sstore_restoration_nonzero_no_state_refund( When a slot holds a nonzero original value, changing it and restoring it never involves state gas (no state growth occurred), - so only regular gas refunds apply. + so only execution gas refunds apply. """ contract = pre.deploy_contract( code=(Op.SSTORE(0, 2) + Op.SSTORE(0, 1)), @@ -434,7 +436,7 @@ def test_sstore_stipend_check_excludes_reservoir( excluded either way, which is what this test pins down. With below_stipend: SSTORE fails (gas_left too low, reservoir ignored). - With at_stipend: SSTORE has full regular gas and proceeds. + With at_stipend: SSTORE has full execution gas and proceeds. """ stipend = fork.call_value_stipend() + 1 sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) @@ -443,19 +445,19 @@ def test_sstore_stipend_check_excludes_reservoir( child_code = Op.SSTORE(0, 1) child = pre.deploy_contract(child_code) - # Full regular gas for the child (pushes + SSTORE regular cost). + # Full execution gas for the child (pushes + SSTORE execution cost). # State gas comes from the reservoir so it doesn't affect gas_left. - child_full_regular = child_code.regular_cost(fork) + child_full_execution = child_code.execution_cost(fork) # below_stipend: give 1 less than stipend after pushes, fails check. - # at_stipend: give full regular gas, passes check and completes. + # at_stipend: give full execution gas, passes check and completes. if gas_above_stipend < 0: - push_gas = 2 * Op.PUSH1(0).regular_cost(fork) + push_gas = 2 * Op.PUSH1(0).execution_cost(fork) child_gas = push_gas + stipend - 1 else: - child_gas = child_full_regular + child_gas = child_full_execution - # Caller forwards limited regular gas via CALL. State gas comes + # Caller forwards limited execution gas via CALL. State gas comes # from the reservoir (gas_limit above the cap). caller_storage = Storage() sstore_succeeds = gas_above_stipend >= 0 @@ -513,7 +515,7 @@ def test_sstore_restoration_block_state_gas_zero( current_value=1, new_value=0, )(i, 0) - tx_regular = ( + tx_execution = ( intrinsic_gas + code.gas_cost(fork) - num_cycles * sstore_state_gas ) @@ -526,7 +528,7 @@ def test_sstore_restoration_block_state_gas_zero( blockchain_test( pre=pre, - blocks=[Block(txs=[tx], header_verify=Header(gas_used=tx_regular))], + blocks=[Block(txs=[tx], header_verify=Header(gas_used=tx_execution))], post={contract: Account(storage=dict.fromkeys(range(num_cycles), 0))}, ) @@ -566,10 +568,10 @@ def test_sstore_restoration_mixed_with_genuine_sstore( code += Op.SSTORE(99, 1) num_0_to_1 = num_cycles + 1 - tx_regular = ( + tx_execution = ( intrinsic_gas + code.gas_cost(fork) - num_0_to_1 * sstore_state_gas ) - expected = max(tx_regular, sstore_state_gas) + expected = max(tx_execution, sstore_state_gas) contract = pre.deploy_contract(code=code) tx = Transaction( @@ -619,7 +621,7 @@ def test_sstore_restoration_intermediate_values( new_value=0, )(0, 0) ) - tx_regular = intrinsic_gas + code.gas_cost(fork) - sstore_state_gas + tx_execution = intrinsic_gas + code.gas_cost(fork) - sstore_state_gas contract = pre.deploy_contract(code=code) tx = Transaction( @@ -630,7 +632,7 @@ def test_sstore_restoration_intermediate_values( blockchain_test( pre=pre, - blocks=[Block(txs=[tx], header_verify=Header(gas_used=tx_regular))], + blocks=[Block(txs=[tx], header_verify=Header(gas_used=tx_execution))], post={contract: Account(storage={0: 0})}, ) @@ -666,8 +668,8 @@ def test_sstore_restoration_then_reset( new_value=1, )(0, 1) ) - tx_regular = intrinsic_gas + code.gas_cost(fork) - 2 * sstore_state_gas - expected = max(tx_regular, sstore_state_gas) + tx_execution = intrinsic_gas + code.gas_cost(fork) - 2 * sstore_state_gas + expected = max(tx_execution, sstore_state_gas) contract = pre.deploy_contract(code=code) tx = Transaction( @@ -709,8 +711,8 @@ def test_sstore_restoration_reservoir_replenished_inline( )(0, 0) + Op.SSTORE(1, 1) ) - tx_regular = intrinsic_gas + code.gas_cost(fork) - 2 * sstore_state_gas - expected = max(tx_regular, sstore_state_gas) + tx_execution = intrinsic_gas + code.gas_cost(fork) - 2 * sstore_state_gas + expected = max(tx_execution, sstore_state_gas) contract = pre.deploy_contract(code=code) tx = Transaction( @@ -758,14 +760,14 @@ def test_sstore_restoration_cross_frame( )(0, 0) + Op.STOP ) - # Callee's regular gas excludes the state gas (refunded at x to 0). - child_regular = child_code.gas_cost(fork) - sstore_state_gas + # Callee's execution gas excludes the state gas (refunded at x to 0). + child_execution = child_code.gas_cost(fork) - sstore_state_gas child = pre.deploy_contract(code=child_code) - parent_code = Op.POP(call_opcode(gas=child_regular, address=child)) + parent_code = Op.POP(call_opcode(gas=child_execution, address=child)) parent = pre.deploy_contract(code=parent_code) - tx_regular = intrinsic_gas + parent_code.gas_cost(fork) + child_regular + tx_execution = intrinsic_gas + parent_code.gas_cost(fork) + child_execution tx = Transaction( to=parent, @@ -777,7 +779,7 @@ def test_sstore_restoration_cross_frame( slot_owner = child if call_opcode == Op.CALL else parent blockchain_test( pre=pre, - blocks=[Block(txs=[tx], header_verify=Header(gas_used=tx_regular))], + blocks=[Block(txs=[tx], header_verify=Header(gas_used=tx_execution))], post={slot_owner: Account(storage={0: 0})}, ) @@ -956,7 +958,7 @@ def test_sstore_restoration_ancestor_revert( caller_storage = Storage() # The probe OOGs and returns 0, so the caller's outer SSTORE is a # cold no-op (0 to 0) on a fresh slot, charging only - # COLD_STORAGE_ACCESS rather than the cold set `regular_cost` + # COLD_STORAGE_ACCESS rather than the cold set `execution_cost` # assumes by default. caller_code = Op.POP( call_opcode(gas=Op.GAS, address=middle) @@ -974,14 +976,14 @@ def test_sstore_restoration_ancestor_revert( # No SSTORE-set persists (inner's set+clear cancel, middle reverts, # the probe OOGs and reverts, and the caller's outer SSTORE is a # no-op), so block state gas is zero and header gas_used (the max of - # regular and state) is just the regular total. The probe burns its + # execution and state) is just the execution total. The probe burns its # full forwarded budget on the OOG; its CALL's cold-access surcharge - # is already counted in the caller's regular cost. + # is already counted in the caller's execution cost. expected_gas_used = ( intrinsic_cost - + caller_code.regular_cost(fork) - + middle_code.regular_cost(fork) - + inner_code.regular_cost(fork) + + caller_code.execution_cost(fork) + + middle_code.execution_cost(fork) + + inner_code.execution_cost(fork) + probe_gas ) @@ -1064,16 +1066,16 @@ def test_sstore_restoration_charge_in_ancestor_intermediate_revert( # SSTORE-set + caller's outer SSTORE-set on slot 1. Middle's # own slot-1 set is washed by inner's deferred credit before # middle reverts, so it does not propagate. Header gas_used - # is max(regular, state). - expected_regular = ( + # is max(execution, state). + expected_execution = ( intrinsic_cost - + caller_code.regular_cost(fork) - + middle_code.regular_cost(fork) - + inner_code.regular_cost(fork) - + probe_code.regular_cost(fork) + + caller_code.execution_cost(fork) + + middle_code.execution_cost(fork) + + inner_code.execution_cost(fork) + + probe_code.execution_cost(fork) ) expected_state = 3 * sstore_state_gas - expected_gas_used = max(expected_regular, expected_state) + expected_gas_used = max(expected_execution, expected_state) # Reservoir = 2 * sstore_state_gas covers caller's and middle's # sets; the deferred credit refills middle by sstore_state_gas, @@ -1242,7 +1244,7 @@ def test_sstore_restoration_reservoir_spillover( current_value=1, new_value=0, )(0, 0) - tx_regular = intrinsic_gas + code.gas_cost(fork) - sstore_state_gas + tx_execution = intrinsic_gas + code.gas_cost(fork) - sstore_state_gas contract = pre.deploy_contract(code=code) tx = Transaction( @@ -1253,6 +1255,6 @@ def test_sstore_restoration_reservoir_spillover( blockchain_test( pre=pre, - blocks=[Block(txs=[tx], header_verify=Header(gas_used=tx_regular))], + blocks=[Block(txs=[tx], header_verify=Header(gas_used=tx_execution))], post={contract: Account(storage={0: 0})}, ) diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_access_list_gas.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_access_list_gas.py index d214e83bc21..f744952fe26 100644 --- a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_access_list_gas.py +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_access_list_gas.py @@ -165,7 +165,7 @@ def test_access_list_warms_storage_slot( overwrite of a non-zero original to a new non-zero value pays ``WARM_SLOAD + STORAGE_WRITE``. """ - very_low = Op.PUSH1(0).regular_cost(fork) + very_low = Op.PUSH1(0).execution_cost(fork) slot = 0x42 if op == "SLOAD": @@ -178,7 +178,7 @@ def test_access_list_warms_storage_slot( else: measured_code = Op.SSTORE(slot, 2) # Overhead is the two PUSHes (key, value); the stored value is - # the bare warm SSTORE regular cost (overwrite of a non-zero + # the bare warm SSTORE execution cost (overwrite of a non-zero # original, no state gas). overhead_cost = 2 * very_low extra_stack_items = 0 @@ -188,7 +188,7 @@ def test_access_list_warms_storage_slot( original_value=1, current_value=1, new_value=2, - )(slot, 2).regular_cost(fork) + )(slot, 2).execution_cost(fork) - 2 * very_low ) diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_call_gas.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_call_gas.py index c58fc42934d..946ea7f12db 100644 --- a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_call_gas.py +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_call_gas.py @@ -1,8 +1,8 @@ """ Tests for the EIP-8038 [State Access Gas Cost Increase](https://eips.ethereum.org/EIPS/eip-8038) -``CALL``-family regular-gas dimension. +``CALL``-family execution-gas dimension. -Under EIP-8038 the call opcodes are repriced in their *regular* gas +Under EIP-8038 the call opcodes are repriced in their *execution* gas dimension: - account access costs ``COLD_ACCOUNT_ACCESS`` (3,000) cold or @@ -11,12 +11,12 @@ ``CALL_STIPEND`` = 10,300), charged only by ``CALL``/``CALLCODE``; - a value transfer to a *new* account additionally creates the account, whose ``GAS_NEW_ACCOUNT`` charge is the EIP-8037 *state* dimension and - is asserted via the block header ``max(regular, state)`` accounting, - never as regular gas; + is asserted via the block header ``max(execution, state)`` accounting, + never as execution gas; - an EIP-7702 delegated target is double-accessed (target leaf plus delegation leaf), each access cold or warm independently. -These tests assert the EIP-8038 *regular* dimension; the EIP-8037 +These tests assert the EIP-8038 *execution* dimension; the EIP-8037 *state* dimension for value-to-new-account is covered in ``eip8037_state_creation_gas_cost_increase/test_state_gas_call.py`` and is only re-derived here at the seam to feed header gas accounting. @@ -176,7 +176,7 @@ def test_call_value_alive_target_gas( pre, fork, measured_code, own_cold, balance=1 ) - # CALL gas is wholly regular under EIP-8038 (no state map). + # CALL gas is wholly execution under EIP-8038 (no state map). assert cost_metadata.state_cost(fork) == 0 # Consumed gas: the STOP callee returns the forwarded stipend, so the @@ -211,7 +211,7 @@ def test_callcode_value_to_nonexistent_no_new_account( ``CALLCODE`` runs the callee's code in the caller's own context, so the value never leaves the caller and no beneficiary account is - created. The block ``gas_used`` therefore equals the regular tx + created. The block ``gas_used`` therefore equals the execution tx cost with ``CALL_VALUE`` but with no 183,600 state-gas component. """ intrinsic = fork.transaction_intrinsic_cost_calculator()() @@ -241,7 +241,7 @@ def test_callcode_value_to_nonexistent_no_new_account( callcode_meta = Op.CALLCODE(address_warm=False, value_transfer=True) assert callcode_meta.state_cost(fork) == 0 - # Whole tx is regular gas; no NEW_ACCOUNT state component appears. + # Whole tx is execution gas; no NEW_ACCOUNT state component appears. # The CALLCODE forwards the value-call stipend to the callee, which # (running in the caller's own context with empty code) leaves it # unused and returns it, so consumed gas is the charge minus stipend. @@ -267,12 +267,12 @@ def test_call_value_to_new_account_seam( fork: Fork, ) -> None: """ - Verify the CALL value-to-new-account regular/state seam. + Verify the CALL value-to-new-account execution/state seam. - The EIP-8038 *regular* dimension is ``COLD_ACCOUNT_ACCESS`` + + The EIP-8038 *execution* dimension is ``COLD_ACCOUNT_ACCESS`` + ``CALL_VALUE`` = 13,300; the account creation charge ``GAS_NEW_ACCOUNT`` (183,600) lands in the EIP-8037 *state* - dimension. The block header reflects ``max(regular, state)``, which + dimension. The block header reflects ``max(execution, state)``, which is dominated by the state charge. """ intrinsic = fork.transaction_intrinsic_cost_calculator()() @@ -280,7 +280,7 @@ def test_call_value_to_new_account_seam( # Fresh, value-receiving target (state-empty, will be created). target = pre.fund_eoa(amount=0) - # Metadata-bearing CALL so its cost splits into the regular + # Metadata-bearing CALL so its cost splits into the execution # (access + value transfer) and state (NEW_ACCOUNT) dimensions. call = Op.CALL.with_metadata( address_warm=False, value_transfer=True, account_new=True @@ -298,12 +298,12 @@ def test_call_value_to_new_account_seam( new_account_state_gas = call.state_cost(fork) - # block_gas_used = max(block_regular, block_state). The CALL's - # NEW_ACCOUNT lands on the state axis; the regular axis is the + # block_gas_used = max(block_execution, block_state). The CALL's + # NEW_ACCOUNT lands on the state axis; the execution axis is the # access plus value-transfer cost. - tx_regular = intrinsic + caller_code.regular_cost(fork) + tx_execution = intrinsic + caller_code.execution_cost(fork) tx_state = caller_code.state_cost(fork) - expected_gas_used = max(tx_regular, tx_state) + expected_gas_used = max(tx_execution, tx_state) # State must dominate here, proving NEW_ACCOUNT hit the state axis. assert expected_gas_used == new_account_state_gas @@ -412,7 +412,7 @@ def test_call_exact_gas_oog( inner_code = call_opcode(gas=0, address=target) + Op.STOP inner = pre.deploy_contract(inner_code) - # Exact regular gas for the inner frame: bytecode cost (which folds + # Exact execution gas for the inner frame: bytecode cost (which folds # the cold call cost via the default metadata) under EIP-8038. inner_gas_exact = inner_code.gas_cost(fork) if not sufficient_gas: @@ -477,10 +477,10 @@ def test_call_forwarded_gas_63_64( gas. The spec charges the repriced ``COLD_ACCOUNT_ACCESS`` (3,000) up front and only then forwards ``floor(63/64 * gas_left)`` to the child. The wrapper is handed an exact budget so that, net of the - access charge, ``gas_left`` equals ``child_regular * 64 // 63``; - forwarding then yields exactly the child's regular need - (``child_regular``) and its cold ``SSTORE`` takes effect. With one - gas less the floor drops below ``child_regular`` and the child OOGs, + access charge, ``gas_left`` equals ``child_execution * 64 // 63``; + forwarding then yields exactly the child's execution need + (``child_execution``) and its cold ``SSTORE`` takes effect. With one + gas less the floor drops below ``child_execution`` and the child OOGs, so the slot stays zero. This pins that the floor is taken over ``gas_left`` already net of the post-8038 cold access cost (not before it, and not double-charging it). @@ -488,15 +488,15 @@ def test_call_forwarded_gas_63_64( sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) # Child: a single cold zero-to-nonzero SSTORE as proof of execution. - # Its regular need is the two operand pushes plus the cold storage + # Its execution need is the two operand pushes plus the cold storage # write (the state portion is funded separately via the reservoir, # which is passed to the child in full with no 63/64 rule). child_code = Op.SSTORE(0, 1) child = pre.deploy_contract(child_code) - child_regular = child_code.regular_cost(fork) + child_execution = child_code.execution_cost(fork) - # Smallest budget whose 63/64 floor still reaches `child_regular`. - forward_budget = child_regular * 64 // 63 + # Smallest budget whose 63/64 floor still reaches `child_execution`. + forward_budget = child_execution * 64 // 63 if not sufficient_gas: forward_budget -= 1 @@ -515,9 +515,9 @@ def test_call_forwarded_gas_63_64( wrapper = pre.deploy_contract(wrapper_call) # At the wrapper's CALL the cold access charge is deducted first - # (folded with the operand pushes into its regular cost), leaving + # (folded with the operand pushes into its execution cost), leaving # exactly `forward_budget` as `gas_left` for the 63/64 floor. - wrapper_gas = wrapper_call.regular_cost(fork) + forward_budget + wrapper_gas = wrapper_call.execution_cost(fork) + forward_budget # Outer caller hands the wrapper exactly `wrapper_gas`. caller = pre.deploy_contract( diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_create_gas.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_create_gas.py index f91e6091f87..f981679c30b 100644 --- a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_create_gas.py +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_create_gas.py @@ -1,9 +1,9 @@ """ Tests for the EIP-8038 [State Access Gas Cost Increase](https://eips.ethereum.org/EIPS/eip-8038) -``CREATE``/``CREATE2`` regular-gas dimension. +``CREATE``/``CREATE2`` execution-gas dimension. Under EIP-8038 the contract-creation opcodes are repriced in their -*regular* gas dimension to ``CREATE_ACCESS`` (``ACCOUNT_WRITE`` + +*execution* gas dimension to ``CREATE_ACCESS`` (``ACCOUNT_WRITE`` + ``COLD_STORAGE_ACCESS`` = 11,000), on top of which the EIP-3860 init code word cost (2 per word) and, for ``CREATE2`` only, an additional keccak word cost (6 per word) are charged. The new-account creation @@ -11,9 +11,9 @@ covered in ``eip8037_state_creation_gas_cost_increase/test_state_gas_create.py``. -These tests isolate and assert the EIP-8038 *regular* dimension. At the +These tests isolate and assert the EIP-8038 *execution* dimension. At the contract-creating-transaction boundary the state component is re-derived -only to feed the ``max(regular, state)`` block-header accounting. +only to feed the ``max(execution, state)`` block-header accounting. """ from typing import List @@ -58,7 +58,7 @@ pytest.param(96, id="three_words"), ], ) -def test_create_regular_gas( +def test_create_execution_gas( state_test: StateTestFiller, pre: Alloc, fork: Fork, @@ -66,36 +66,36 @@ def test_create_regular_gas( init_code_size: int, ) -> None: """ - Measure the regular gas of CREATE/CREATE2 and assert the schedule. + Measure the execution gas of CREATE/CREATE2 and assert the schedule. - The EIP-8038 *regular* dimension is ``CREATE_ACCESS`` (11,000) plus + The EIP-8038 *execution* dimension is ``CREATE_ACCESS`` (11,000) plus the EIP-3860 init code word cost (2 per word) plus, for ``CREATE2`` only, an additional keccak word cost (6 per word). The EIP-8037 account-creation state gas is excluded by subtracting ``create_state_gas(0)``. """ - # Isolate the regular dimension: opcode total minus its account + # Isolate the execution dimension: opcode total minus its account # creation state gas (the only state component carried by the CREATE # opcode itself; code deposit is charged on RETURN inside initcode). create_meta = create_opcode(init_code_size=init_code_size) - regular_gas = create_meta.gas_cost(fork) - fork.create_state_gas( + execution_gas = create_meta.gas_cost(fork) - fork.create_state_gas( code_size=0 ) - # Equivalent isolation via the regular_cost helper. - assert regular_gas == create_meta.regular_cost(fork) + # Equivalent isolation via the execution_cost helper. + assert execution_gas == create_meta.execution_cost(fork) # Runtime confirmation via CodeGasMeasure: a factory whose CREATE # deploys empty code, so no code-deposit state gas is charged and the # only state component is the account-creation gas funded from the # reservoir. The initcode is brought into memory BEFORE the measured # window, so the memory-expansion charge is excluded; the measured - # value is the CREATE opcode's regular cost exactly. The overhead + # value is the CREATE opcode's execution cost exactly. The overhead # subtracts the create-call argument pushes (the create leaves one # stack item, its result). # # The initcode is all-zero bytes (`STOP`), so the child frame halts # immediately consuming zero gas and deposits empty code. This keeps - # the measured value the CREATE opcode's own regular cost, with no + # the measured value the CREATE opcode's own execution cost, with no # child-execution gas folded in. `init_code_size` still drives the # opcode's per-init-word charge. padded_init = b"\x00" * init_code_size @@ -105,7 +105,7 @@ def test_create_regular_gas( if create_opcode == Op.CREATE2 else Op.CREATE(value=0, offset=0, size=init_code_size) ) - push_cost = Op.PUSH1(0).regular_cost(fork) + push_cost = Op.PUSH1(0).execution_cost(fork) arg_pushes = (4 if create_opcode == Op.CREATE2 else 3) * push_cost memory_setup = ( @@ -118,7 +118,7 @@ def test_create_regular_gas( code=create_call, overhead_cost=arg_pushes, extra_stack_items=1, - sstore_key=storage.store_next(regular_gas, "create_regular_gas"), + sstore_key=storage.store_next(execution_gas, "create_execution_gas"), ) factory = pre.deploy_contract(code=memory_setup + measure) @@ -155,33 +155,33 @@ def test_create2_keccak_word_delta( ``CREATE2`` hashes the init code to derive the salted address, adding ``OPCODE_KECCAK256_PER_WORD`` (6) per init-code word on top of the - regular cost shared with ``CREATE``. Both opcodes carry the identical + execution cost shared with ``CREATE``. Both opcodes carry the identical EIP-8038 ``CREATE_ACCESS`` base and EIP-3860 word cost. A factory measures a single ``CREATE2`` with ``CodeGasMeasure`` and - stores its absolute regular cost, confirming the opcode's own - ``regular_cost`` (which folds the keccak word surcharge) against the + stores its absolute execution cost, confirming the opcode's own + ``execution_cost`` (which folds the keccak word surcharge) against the runtime charge. """ - create2_regular = Op.CREATE2(init_code_size=init_code_size).regular_cost( - fork - ) + create2_execution = Op.CREATE2( + init_code_size=init_code_size + ).execution_cost(fork) # Init code is all-zero bytes (`STOP`), so the child frame halts # immediately (zero gas) depositing empty code; the CREATE2 charges no # code-deposit state gas and no child execution gas is folded into the - # measurement. The single CREATE2 regular cost is measured via + # measurement. The single CREATE2 execution cost is measured via # CodeGasMeasure with a reservoir sized for its account creation state # gas, keeping the GAS-measured `gas_left` free of state-gas spill. padded = b"\x00" * init_code_size - push4 = 4 * Op.PUSH1(0).regular_cost(fork) + push4 = 4 * Op.PUSH1(0).execution_cost(fork) storage = Storage() measure_create2 = CodeGasMeasure( code=Op.CREATE2(value=0, offset=0, size=init_code_size, salt=0), overhead_cost=push4, extra_stack_items=1, - sstore_key=storage.store_next(create2_regular, "create2_regular"), + sstore_key=storage.store_next(create2_execution, "create2_execution"), ) factory_code = ( Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE, new_memory_size=init_code_size) @@ -245,7 +245,7 @@ def exact_intrinsic_gas( initcode: Initcode, tx_access_list: List[AccessList], ) -> int: - """Return the total (regular + state) intrinsic tx gas cost.""" + """Return the total (execution + state) intrinsic tx gas cost.""" calc = fork.transaction_intrinsic_cost_calculator() return calc( calldata=initcode, @@ -264,18 +264,18 @@ def exact_execution_gas( Under EIP-2780 the created account's ``NEW_ACCOUNT`` state gas moved out of the intrinsic and into the top frame, so it is added - explicitly here (the intrinsic is regular-only). + explicitly here (the intrinsic is execution-only). ``deployment_gas`` is fork-aware: under EIP-8037 it splits the - deposit into the keccak word cost (regular) and the per-byte cost + deposit into the keccak word cost (execution) and the per-byte cost (state), while on a fork without state-byte metering it is the - flat regular per-byte deposit cost. The single call is therefore + flat execution per-byte deposit cost. The single call is therefore correct in either regime. """ execution = exact_intrinsic_gas + fork.transaction_top_frame_state_gas( contract_creation=True ) - execution += initcode.execution_gas(fork) + execution += initcode.evm_gas(fork) execution += initcode.deployment_gas(fork) return execution @@ -339,7 +339,7 @@ def test_create_tx_gas_boundary( sender=sender, ) - # 2D block accounting: gas_used = max(regular, state). Under + # 2D block accounting: gas_used = max(execution, state). Under # EIP-2780 the state axis carries the fresh target's top-frame # NEW_ACCOUNT and (when the deposit succeeds) the per-byte # code-deposit gas. @@ -347,19 +347,19 @@ def test_create_tx_gas_boundary( header_verify = None elif succeeds: # Fresh target: top-frame NEW_ACCOUNT plus the per-byte code - # deposit are the state-gas axis; the rest is regular. + # deposit are the state-gas axis; the rest is execution. state_used = fork.transaction_top_frame_state_gas( contract_creation=True ) state_used += fork.code_deposit_state_gas( code_size=len(initcode.deploy_code) ) - regular_used = gas_limit - state_used - header_verify = Header(gas_used=max(regular_used, state_used)) + execution_used = gas_limit - state_used + header_verify = Header(gas_used=max(execution_used, state_used)) else: # exact_intrinsic / too_little_execution: the top-frame # NEW_ACCOUNT (and any deposit) cannot be covered, the whole - # preparation rolls back, and all gas is burned as regular. + # preparation rolls back, and all gas is burned as execution. header_verify = Header(gas_used=gas_limit) state_test( @@ -464,7 +464,7 @@ def test_create2_to_occupied_address( already-deployed contract, whose ``code_hash`` is non-empty), the creation aborts after the account-access charge: the opcode pushes ``0``, bumps the factory's nonce, charges the message gas to the - regular dimension, and refunds the ``NEW_ACCOUNT`` *state* gas so no + execution dimension, and refunds the ``NEW_ACCOUNT`` *state* gas so no net account-creation charge lands. No child frame runs, so the occupied contract's code and storage are left untouched. """ diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_fork_transition.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_fork_transition.py index cc7f3d21802..08f290fba1e 100644 --- a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_fork_transition.py +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_fork_transition.py @@ -6,12 +6,12 @@ at ``timestamp=14_999`` runs under the pre-fork (parent) schedule; a block at ``timestamp=15_000`` runs under the EIP-8038 schedule. Every before/after magnitude is derived from the opcode's own cost at each -fork (``bytecode.gas_cost`` / ``regular_cost`` / ``refund``) — nothing +fork (``bytecode.gas_cost`` / ``execution_cost`` / ``refund``) — nothing is hardcoded. Two proof styles are used: -* Account-access dimensions that are pure regular gas (``BALANCE`` cold +* Account-access dimensions that are pure execution gas (``BALANCE`` cold access and the ``EXT*`` code-read surcharge) are measured exactly with ``CodeGasMeasure`` in each regime and asserted against the derived cost. @@ -19,7 +19,7 @@ state-gas confounders (``CALL`` with value, ``CREATE``, ``SELFDESTRUCT`` to a fresh beneficiary, ``SSTORE`` first change) are exercised in both blocks to prove the operation still runs in each - regime, with the ``SSTORE`` regular/state split and clear refund + regime, with the ``SSTORE`` execution/state split and clear refund compared across forks via the bytecode's own cost methods. * The authorization intrinsic rise is proven behaviourally: a tx whose ``gas_limit`` equals the old auth intrinsic is valid before the fork @@ -261,7 +261,7 @@ def test_create_base_cost_at_transition( fork: Fork, ) -> None: """ - The ``CREATE`` regular base cost changes across the boundary + The ``CREATE`` execution base cost changes across the boundary (``OPCODE_CREATE_BASE``: 32000 -> 11000 on mainnet, redefined as ``ACCOUNT_WRITE + COLD_STORAGE_ACCESS``). The constant transition is asserted from the derived schedules and a ``CREATE`` is exercised in @@ -352,15 +352,15 @@ def test_sstore_write_cost_at_transition( boundary, and EIP-8038 changes the *model*, not a single number. Before the fork (parent schedule) a zero-to-nonzero ``SSTORE`` is a - flat regular charge (``COLD_STORAGE_ACCESS + STORAGE_SET``) with no - state-gas dimension. After the fork the charge splits: the regular + flat execution charge (``COLD_STORAGE_ACCESS + STORAGE_SET``) with no + state-gas dimension. After the fork the charge splits: the execution portion drops to ``COLD_STORAGE_ACCESS + STORAGE_WRITE`` while the bulk moves into the new state-gas dimension, and the clear refund rises. Every magnitude is derived from the two schedules; nothing is hardcoded. The transition is asserted at the derived-constant level (the - runtime opcode cost cannot isolate the regular portion without the + runtime opcode cost cannot isolate the execution portion without the state-gas confounder) and a zero-to-nonzero ``SSTORE`` is exercised in both blocks to prove it still sets the slot in each regime. """ @@ -370,16 +370,16 @@ def test_sstore_write_cost_at_transition( # First-change (zero -> nonzero, cold) SSTORE in each regime. sstore = Op.SSTORE(new_value=1) - regular_before = sstore.regular_cost(before) - regular_after = sstore.regular_cost(after) + execution_before = sstore.execution_cost(before) + execution_after = sstore.execution_cost(after) state_before = sstore.state_cost(before) state_after = sstore.state_cost(after) total_before = sstore.gas_cost(before) total_after = sstore.gas_cost(after) - # The repricing changes the regular charge, introduces the state + # The repricing changes the execution charge, introduces the state # dimension, and therefore moves the total. - assert regular_after != regular_before + assert execution_after != execution_before assert state_before == 0 assert state_after > 0 assert total_after != total_before @@ -424,7 +424,7 @@ def test_auth_intrinsic_at_transition( The ``7702`` authorization intrinsic *falls* across the boundary. EIP-2780 moves the state-dependent authorization costs (account creation and the delegation-write base) out of the intrinsic and into - the top frame, leaving only the regular ``REGULAR_PER_AUTH_BASE_COST`` + the top frame, leaving only the execution ``REGULAR_PER_AUTH_BASE_COST`` in the intrinsic. The post-fork single-authorization intrinsic is therefore strictly smaller than the pre-fork one, so a tx whose ``gas_limit`` equals the (lower) post-fork intrinsic is rejected with diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_selfdestruct_gas.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_selfdestruct_gas.py index 99d0797731c..b78c595c4b5 100644 --- a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_selfdestruct_gas.py +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_selfdestruct_gas.py @@ -1,8 +1,8 @@ """ Tests for the EIP-8038 [State Access Gas Cost Increase](https://eips.ethereum.org/EIPS/eip-8038) -``SELFDESTRUCT`` regular-gas dimension. +``SELFDESTRUCT`` execution-gas dimension. -Under EIP-8038 ``SELFDESTRUCT`` is charged, in its *regular* gas +Under EIP-8038 ``SELFDESTRUCT`` is charged, in its *execution* gas dimension: - ``OPCODE_SELFDESTRUCT_BASE`` (5,000); @@ -11,9 +11,9 @@ ``WARM_ACCESS`` surcharge); - a net-new ``ACCOUNT_WRITE`` (8,000) when a positive balance is sent to an empty (or non-existent) beneficiary, replacing the legacy combined - 25,000 regular account-creation cost. + 25,000 execution account-creation cost. -So ``regular = 5,000 + (3,000 if cold) + (8,000 if creating)``: 13,000 +So ``execution = 5,000 + (3,000 if cold) + (8,000 if creating)``: 13,000 warm / 16,000 cold when a new beneficiary is created, 5,000 warm / 8,000 cold otherwise. @@ -29,10 +29,10 @@ The framework opcode-gas model splits the two dimensions for ``SELFDESTRUCT`` exactly as the spec does: ``ACCOUNT_WRITE`` is charged -as regular gas and ``GAS_NEW_ACCOUNT`` as state gas, so -``Op.SELFDESTRUCT(account_new=True).regular_cost(fork)`` is the regular +as execution gas and ``GAS_NEW_ACCOUNT`` as state gas, so +``Op.SELFDESTRUCT(account_new=True).execution_cost(fork)`` is the execution charge (16,000 cold / 13,000 warm) and ``.state_cost(fork)`` is -``GAS_NEW_ACCOUNT``. These tests assert the regular dimension and verify +``GAS_NEW_ACCOUNT``. These tests assert the execution dimension and verify account-creation via balances; the state dimension is owned by ``eip8037_state_creation_gas_cost_increase/test_state_gas_selfdestruct.py``. """ @@ -70,7 +70,7 @@ def _destructor_code( beneficiary: Address | Bytecode, *, warm: bool, account_new: bool ) -> Bytecode: """ - Build SELFDESTRUCT bytecode with metadata so ``regular_cost(fork)`` + Build SELFDESTRUCT bytecode with metadata so ``execution_cost(fork)`` folds the beneficiary PUSH and the correct access/account-write charge (account-creation state gas excluded — it is charged separately by the spec). @@ -82,7 +82,7 @@ def _destructor_code( @EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() @pytest.mark.parametrize("warm", [False, True], ids=["cold", "warm"]) -def test_selfdestruct_new_beneficiary_regular_gas( +def test_selfdestruct_new_beneficiary_execution_gas( state_test: StateTestFiller, pre: Alloc, fork: Fork, @@ -94,7 +94,7 @@ def test_selfdestruct_new_beneficiary_regular_gas( The destructor has a non-zero balance and targets an empty, non-existent beneficiary, so the net-new ``ACCOUNT_WRITE`` applies: - ``regular = 5,000 + access + 8,000`` (13,000 warm, 16,000 cold). The + ``execution = 5,000 + access + 8,000`` (13,000 warm, 16,000 cold). The creation gas ``GAS_NEW_ACCOUNT`` is charged on the state axis (the EIP-8037 suite asserts it); here it is funded from the reservoir and the value transfer to the new beneficiary confirms the path. @@ -144,9 +144,9 @@ def test_selfdestruct_alive_beneficiary_no_account_write( """ SELFDESTRUCT to an already-alive beneficiary charges no ACCOUNT_WRITE. - The beneficiary already exists, so no account is created: regular = + The beneficiary already exists, so no account is created: execution = ``5,000 + (3,000 if cold)`` (5,000 warm, 8,000 cold) and no state gas is - charged. The block header reflects the pure regular consumption. + charged. The block header reflects the pure execution consumption. """ beneficiary = pre.fund_eoa(amount=1) # alive @@ -167,12 +167,12 @@ def test_selfdestruct_alive_beneficiary_no_account_write( access_list=access_list ) - # Pure regular: intrinsic + caller frame + destructor frame (whose - # regular_cost folds the SELFDESTRUCT charge and beneficiary PUSH). + # Pure execution: intrinsic + caller frame + destructor frame (whose + # execution_cost folds the SELFDESTRUCT charge and beneficiary PUSH). expected_gas_used = ( intrinsic + caller_code.gas_cost(fork) - + destructor_code.regular_cost(fork) + + destructor_code.execution_cost(fork) ) tx = Transaction( @@ -212,7 +212,7 @@ def test_selfdestruct_codebearing_zero_balance_beneficiary_no_account_write( The beneficiary is alive because it has code, not balance: it holds a zero balance but a non-empty code (``Op.STOP``), so EIP-161 emptiness does not apply and no account is created when a positive balance is - sent to it. Regular = ``5,000 + (3,000 if cold)`` (5,000 warm, 8,000 + sent to it. Execution = ``5,000 + (3,000 if cold)`` (5,000 warm, 8,000 cold) with no ACCOUNT_WRITE and no state gas — distinct from the alive-via-balance case, which exercises the same path through a different liveness source. @@ -237,12 +237,12 @@ def test_selfdestruct_codebearing_zero_balance_beneficiary_no_account_write( access_list=access_list ) - # Pure regular: intrinsic + caller frame + destructor frame (whose - # regular_cost folds the SELFDESTRUCT charge and beneficiary PUSH). + # Pure execution: intrinsic + caller frame + destructor frame (whose + # execution_cost folds the SELFDESTRUCT charge and beneficiary PUSH). expected_gas_used = ( intrinsic + caller_code.gas_cost(fork) - + destructor_code.regular_cost(fork) + + destructor_code.execution_cost(fork) ) tx = Transaction( @@ -280,7 +280,7 @@ def test_selfdestruct_zero_balance_no_account_write( SELFDESTRUCT with a zero-balance destructor charges no ACCOUNT_WRITE. No value is transferred, so even a non-existent beneficiary is not - created: regular = ``5,000 + access`` and no state gas is charged. + created: execution = ``5,000 + access`` and no state gas is charged. """ beneficiary = Address(0xDEAD) # non-existent, but no value sent @@ -302,7 +302,7 @@ def test_selfdestruct_zero_balance_no_account_write( expected_gas_used = ( intrinsic + caller_code.gas_cost(fork) - + destructor_code.regular_cost(fork) + + destructor_code.execution_cost(fork) ) tx = Transaction( @@ -345,7 +345,7 @@ def test_selfdestruct_self_or_precompile_beneficiary( The executing account is in the accessed set on entry (self), and precompiles are pre-warmed from the start, so neither pays a cold - surcharge: regular = ``5,000`` (warm base, no ``WARM_ACCESS``) with no + surcharge: execution = ``5,000`` (warm base, no ``WARM_ACCESS``) with no state gas. The destructor balance is chosen so no account creation occurs: self @@ -378,7 +378,7 @@ def test_selfdestruct_self_or_precompile_beneficiary( expected_gas_used = ( intrinsic + caller_code.gas_cost(fork) - + destructor_code.regular_cost(fork) + + destructor_code.execution_cost(fork) ) tx = Transaction( @@ -418,10 +418,10 @@ def test_selfdestruct_oog_boundary( gas and one short. The destructor sends value to an empty beneficiary, charging - ``5,000 + COLD_ACCOUNT_ACCESS + ACCOUNT_WRITE`` (16,000) in regular gas + ``5,000 + COLD_ACCOUNT_ACCESS + ACCOUNT_WRITE`` (16,000) in execution gas and ``GAS_NEW_ACCOUNT`` in state gas. The child CALL frame has no state reservoir of its own, so the state gas spills into the forwarded - regular gas and the frame needs its full ``gas_cost`` total. Forwarding + execution gas and the frame needs its full ``gas_cost`` total. Forwarding exactly that total lets the SELFDESTRUCT succeed (CALL returns 1); one gas short OOGs (CALL returns 0) before the value transfer, so the beneficiary is never created. @@ -434,7 +434,7 @@ def test_selfdestruct_oog_boundary( destructor = pre.deploy_contract(code=destructor_code, balance=1) # The child CALL frame gets no state reservoir, so the NEW_ACCOUNT - # state gas spills into the forwarded regular gas: forward the full + # state gas spills into the forwarded execution gas: forward the full # total. One gas short forces an out-of-gas before the value transfer. forwarded = destructor_code.gas_cost(fork) if not sufficient_gas: @@ -483,7 +483,7 @@ def test_same_tx_created_selfdestruct_self_burn( to ITSELF: the originator is created in this transaction so it is deleted, and because a same-tx-created contract holding balance is alive, ``account_new`` is false for the self-beneficiary — - ``regular = 5,000`` (warm self, no ``ACCOUNT_WRITE``) and no + ``execution = 5,000`` (warm self, no ``ACCOUNT_WRITE``) and no SELFDESTRUCT state gas. EIP-8246 removes the SELFDESTRUCT burn, so the self-send is a no-op: @@ -494,7 +494,7 @@ def test_same_tx_created_selfdestruct_self_burn( ``NEW_ACCOUNT`` is a top-frame charge levied only when the target is ``EMPTY`` pre-tx, but the pre-funded created target already has a balance, so it is never charged. The self-burn adds no state gas, so - the block ``gas_used`` is the pure regular consumption regardless of + the block ``gas_used`` is the pure execution consumption regardless of the burn behavior. """ intrinsic_calc = fork.transaction_intrinsic_cost_calculator() @@ -513,17 +513,17 @@ def test_same_tx_created_selfdestruct_self_burn( # Self-beneficiary on a balance-bearing same-tx-created contract is # alive: account_new is false, so only the warm base is charged. - # Creation intrinsic is regular-only under EIP-2780; the pre-existing + # Creation intrinsic is execution-only under EIP-2780; the pre-existing # target adds no top-frame NEW_ACCOUNT and the self-burn adds no state - # gas, so net state gas is zero. The regular consumption exceeds the + # gas, so net state gas is zero. The execution consumption exceeds the # decomposed calldata floor, so the floor never pins the billing. - intrinsic_regular = intrinsic_calc( + intrinsic_execution = intrinsic_calc( calldata=bytes(init_code), contract_creation=True, return_cost_deducted_prior_execution=True, ) - expected_regular = intrinsic_regular + init_code.regular_cost(fork) - expected_gas_used = expected_regular + expected_execution = intrinsic_execution + init_code.execution_cost(fork) + expected_gas_used = expected_execution # EIP-8246 removes the SELFDESTRUCT burn: the self-send is a no-op, # the balance stays in the (otherwise emptied) originator, and no @@ -562,7 +562,7 @@ def test_same_tx_created_selfdestruct_to_fresh_beneficiary( A creation transaction whose initcode SELFDESTRUCTs the new contract to a fresh ``Address(0xDEAD)``: the fresh, non-existent beneficiary receives a positive balance, so ``account_new`` is true — - ``regular = 5,000 + COLD_ACCOUNT_ACCESS + ACCOUNT_WRITE`` (16,000 + ``execution = 5,000 + COLD_ACCOUNT_ACCESS + ACCOUNT_WRITE`` (16,000 cold) plus a beneficiary ``NEW_ACCOUNT`` on the state axis. The beneficiary creation charge keys on the beneficiary, while the originator (created in this transaction) is still deleted: a @@ -594,12 +594,12 @@ def test_same_tx_created_selfdestruct_to_fresh_beneficiary( # beneficiary's NEW_ACCOUNT (the SELFDESTRUCT state cost) persists. new_account_state_gas = init_code.state_cost(fork) - intrinsic_regular = intrinsic_calc( + intrinsic_execution = intrinsic_calc( calldata=bytes(init_code), contract_creation=True ) expected_state = new_account_state_gas - expected_regular = intrinsic_regular + init_code.regular_cost(fork) - expected_gas_used = max(expected_regular, expected_state) + expected_execution = intrinsic_execution + init_code.execution_cost(fork) + expected_gas_used = max(expected_execution, expected_state) tx = Transaction( to=None, @@ -607,7 +607,7 @@ def test_same_tx_created_selfdestruct_to_fresh_beneficiary( sender=sender, # Reservoir holds the beneficiary-creation state gas (above the # creation's intrinsic NEW_ACCOUNT) so it does not spill into - # regular gas. + # execution gas. state_gas_reservoir=new_account_state_gas, expected_receipt=TransactionReceipt( logs=[transfer_log(created, beneficiary, amount)] diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_set_code_auth_gas.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_set_code_auth_gas.py index 1d4a9b0624c..72a0cbae289 100644 --- a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_set_code_auth_gas.py +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_set_code_auth_gas.py @@ -1,17 +1,17 @@ """ -Tests for the EIP-7702 authorization *regular*-gas repricing under +Tests for the EIP-7702 authorization *execution*-gas repricing under [EIP-8038: State Access Gas Cost Increase](https://eips.ethereum.org/EIPS/eip-8038). Under EIP-2780 each EIP-7702 authorization is charged in two parts: a -state-independent *regular* base cost paid in the intrinsic, and +state-independent *execution* base cost paid in the intrinsic, and state-dependent costs (``NEW_ACCOUNT`` / ``ACCOUNT_WRITE`` for a new authority leaf, ``AUTH_BASE`` for a net-new delegation indicator) paid lazily at the top frame in ``set_delegation``. This module pins the -**regular** per-authorization intrinsic magnitude and the repriced +**execution** per-authorization intrinsic magnitude and the repriced cold/warm account-access costs that an authorized delegation incurs when later accessed by a ``CALL``. -The regular per-authorization intrinsic magnitude is the fixed +The execution per-authorization intrinsic magnitude is the fixed per-authorization base cost charged by the intrinsic (on Amsterdam, ``101 * 16`` calldata tokens plus the ``3000`` ecrecover, ``3000`` cold and ``2 * 100`` warm accesses of the EIP-7702 base), isolated here as @@ -54,9 +54,9 @@ pytestmark = pytest.mark.valid_from("Amsterdam") -def _regular_per_auth(fork: Fork) -> int: +def _execution_per_auth(fork: Fork) -> int: """ - Return the *regular* intrinsic gas charged per EIP-7702 + Return the *execution* intrinsic gas charged per EIP-7702 authorization. Under EIP-2780 the intrinsic charges only the state-independent @@ -75,7 +75,7 @@ def _regular_per_auth(fork: Fork) -> int: ) -def _regular_intrinsic( +def _execution_intrinsic( fork: Fork, *, n: int, @@ -110,7 +110,7 @@ def _regular_intrinsic( pytest.param(True, id="access_list_contains_authority"), ], ) -def test_auth_regular_intrinsic_magnitude( +def test_auth_execution_intrinsic_magnitude( state_test: StateTestFiller, env: Environment, pre: Alloc, @@ -120,10 +120,10 @@ def test_auth_regular_intrinsic_magnitude( authority_in_access_list: bool, ) -> None: """ - Assert the EIP-8038 *regular* per-authorization intrinsic magnitude. + Assert the EIP-8038 *execution* per-authorization intrinsic magnitude. - The regular intrinsic above the ``n=0`` base must equal - ``n * regular_per_auth`` plus the access-list delta (derived from + The execution intrinsic above the ``n=0`` base must equal + ``n * execution_per_auth`` plus the access-list delta (derived from the calculator itself so the calldata-floor contribution of the access-list bytes is accounted for). """ @@ -144,17 +144,19 @@ def test_auth_regular_intrinsic_magnitude( AccessList(address=signer, storage_keys=[]) for signer in signers ] - base_regular = _regular_intrinsic(fork, n=0) - regular = _regular_intrinsic(fork, n=n, access_list=access_list) + base_execution = _execution_intrinsic(fork, n=0) + execution = _execution_intrinsic(fork, n=n, access_list=access_list) # Access-list delta is derived from the calculator (it folds in the # calldata-floor cost of the access-list bytes), never hardcoded. - access_list_delta = _regular_intrinsic( + access_list_delta = _execution_intrinsic( fork, n=0, access_list=access_list - ) - _regular_intrinsic(fork, n=0) + ) - _execution_intrinsic(fork, n=0) - expected_per_auth = _regular_per_auth(fork) - assert regular - base_regular == n * expected_per_auth + access_list_delta + expected_per_auth = _execution_per_auth(fork) + assert ( + execution - base_execution == n * expected_per_auth + access_list_delta + ) sender = pre.fund_eoa() tx = Transaction( @@ -185,8 +187,8 @@ def test_auth_intrinsic_oog_boundary( Reject a set-code transaction one gas below the full intrinsic. ``gas_limit`` is set to ``full_intrinsic - 1`` (full intrinsic = - regular + auth state gas). Catches an implementation that omits the - repriced regular per-authorization cost from the intrinsic check. + execution + auth state gas). Catches an implementation that omits the + repriced execution per-authorization cost from the intrinsic check. """ contract = pre.deploy_contract(code=Op.STOP) authorization_list = [ @@ -234,7 +236,7 @@ def test_invalid_auth_charged_intrinsic( Each invalidity kind (``INVALID_NONCE``, ``INVALID_CHAIN_ID``, ``REPEATED_NONCE``, ``AUTHORITY_IS_CONTRACT``) makes the authorization invalid during processing, so it is silently skipped, - but its regular + state intrinsic gas is still paid. The transaction + but its execution + state intrinsic gas is still paid. The transaction succeeds. """ contract = pre.deploy_contract(code=Op.STOP) @@ -288,7 +290,7 @@ def test_invalid_auth_charged_intrinsic( else: raise ValueError(f"unknown invalidity: {invalidity!r}") - # The full intrinsic (regular + state) is charged regardless of + # The full intrinsic (execution + state) is charged regardless of # validity. Provide a comfortable gas limit and let the receipt # accounting be verified by the framework; the key assertion is the # untouched-authority post state. @@ -438,18 +440,18 @@ def test_mixed_validity_multi_auth_receipt_gas( # for every tuple; the one valid authorization adds ``AUTH_BASE`` at # the top frame for its net-new delegation indicator. The skipped # tuple and the plain ``STOP`` recipient add nothing. - intrinsic_regular = fork.transaction_intrinsic_cost_calculator()( + intrinsic_execution = fork.transaction_intrinsic_cost_calculator()( authorization_list_or_count=n, return_cost_deducted_prior_execution=True, ) - top_frame_regular = fork.transaction_top_frame_gas_calculator()( + top_frame_execution = fork.transaction_top_frame_gas_calculator()( authorizations=authorization_list, ) top_frame_state = fork.transaction_top_frame_state_gas( authorizations=authorization_list, ) cumulative_gas_used = ( - intrinsic_regular + top_frame_regular + top_frame_state + intrinsic_execution + top_frame_execution + top_frame_state ) tx = Transaction( @@ -540,7 +542,7 @@ def test_auth_account_warming( # Measure the cost of a single CALL to the authority. The CALL # opcode leaves one stack item (success); the overhead is the PUSHes # for its arguments. - overhead_cost = Op.PUSH1(0).regular_cost(fork) * len(Op.CALL.kwargs) + overhead_cost = Op.PUSH1(0).execution_cost(fork) * len(Op.CALL.kwargs) storage = Storage() callee_code = CodeGasMeasure( code=Op.CALL(gas=0, address=authority), @@ -578,7 +580,7 @@ def test_many_auths_block_limit( limit cap and confirm it succeeds. The authorization count is sized from the per-authorization total - intrinsic (regular + state) and the transaction gas-limit cap, so it + intrinsic (execution + state) and the transaction gas-limit cap, so it automatically tracks the repriced cost. """ gas_limit_cap = fork.transaction_gas_limit_cap() @@ -586,7 +588,7 @@ def test_many_auths_block_limit( contract = pre.deploy_contract(code=Op.STOP) - # Per-authorization total for a fresh (empty) authority: the regular + # Per-authorization total for a fresh (empty) authority: the execution # intrinsic base plus the top-frame account-write, account-creation # and delegation-write charges, derived from the fork's calculators # so it tracks the repricing. The probe only feeds the gas @@ -601,7 +603,7 @@ def test_many_auths_block_limit( first_write=True, ) per_auth_total = ( - _regular_per_auth(fork) + _execution_per_auth(fork) + fork.transaction_top_frame_gas_calculator()( authorizations=[probe_auth] ) diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_set_code_auth_refunds.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_set_code_auth_refunds.py index d202332d6da..d72e03aca97 100644 --- a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_set_code_auth_refunds.py +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_set_code_auth_refunds.py @@ -4,7 +4,7 @@ EIP-8038 originally over-charged every authorization as if it created a new account and *refunded* the difference (``ACCOUNT_WRITE`` on the -regular channel, ``NEW_ACCOUNT`` -- and ``AUTH_BASE`` on a clear -- on +execution channel, ``NEW_ACCOUNT`` -- and ``AUTH_BASE`` on a clear -- on the state channel) when the authority leaf already existed. Under EIP-2780 that over-charge-then-refund is gone: the @@ -72,7 +72,7 @@ def test_existing_authority_no_new_account_charge( (and, unlike the superseded EIP-8038 behaviour, refunds none); it charges the first-write ``ACCOUNT_WRITE`` and the top-frame ``AUTH_BASE`` for the net-new delegation indicator. The receipt gas - is therefore exactly the regular intrinsic plus + is therefore exactly the execution intrinsic plus ``n * (ACCOUNT_WRITE + AUTH_BASE)``, with no refund term. """ recipient = pre.deploy_contract(code=Op.STOP) @@ -94,18 +94,18 @@ def test_existing_authority_no_new_account_charge( # Existing leaf + net-new delegation: the first-write ACCOUNT_WRITE # and AUTH_BASE at the top frame. NEW_ACCOUNT is neither charged # nor refunded, so the receipt gas is the exact charge. - intrinsic_regular = fork.transaction_intrinsic_cost_calculator()( + intrinsic_execution = fork.transaction_intrinsic_cost_calculator()( authorization_list_or_count=n, return_cost_deducted_prior_execution=True, ) - top_frame_regular = fork.transaction_top_frame_gas_calculator()( + top_frame_execution = fork.transaction_top_frame_gas_calculator()( authorizations=authorization_list, ) top_frame_state = fork.transaction_top_frame_state_gas( authorizations=authorization_list, ) cumulative_gas_used = ( - intrinsic_regular + top_frame_regular + top_frame_state + intrinsic_execution + top_frame_execution + top_frame_state ) tx = Transaction( @@ -145,7 +145,7 @@ def test_clearing_delegation_no_state_charge( still writes the authority's leaf (code emptied, nonce bumped), so the transaction's first-write ``ACCOUNT_WRITE`` applies. Nothing is refunded (the over-charge is gone), so the receipt gas is exactly - the regular intrinsic plus ``n * ACCOUNT_WRITE``. + the execution intrinsic plus ``n * ACCOUNT_WRITE``. """ recipient = pre.deploy_contract(code=Op.STOP) delegated_to = pre.deploy_contract(code=Op.STOP) @@ -169,18 +169,18 @@ def test_clearing_delegation_no_state_charge( # Clearing an existing delegation writes no net-new indicator, so # no top-frame state charge applies and no refund fires; only the # first-write ACCOUNT_WRITE is charged per authority. - intrinsic_regular = fork.transaction_intrinsic_cost_calculator()( + intrinsic_execution = fork.transaction_intrinsic_cost_calculator()( authorization_list_or_count=n, return_cost_deducted_prior_execution=True, ) - top_frame_regular = fork.transaction_top_frame_gas_calculator()( + top_frame_execution = fork.transaction_top_frame_gas_calculator()( authorizations=authorization_list, ) top_frame_state = fork.transaction_top_frame_state_gas( authorizations=authorization_list, ) assert top_frame_state == 0 - cumulative_gas_used = intrinsic_regular + top_frame_regular + cumulative_gas_used = intrinsic_execution + top_frame_execution tx = Transaction( to=recipient, diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_sstore_gas.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_sstore_gas.py index f3cdf3e1669..67be30b8213 100644 --- a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_sstore_gas.py +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_sstore_gas.py @@ -1,12 +1,12 @@ """ Tests for [EIP-8038: State Access Gas Cost Increase](https://eips.ethereum.org/EIPS/eip-8038). -Covers the EIP-8038 ``SSTORE`` *regular* (non-state) gas schedule. The +Covers the EIP-8038 ``SSTORE`` *execution* (non-state) gas schedule. The state-creation charge for a zero-to-nonzero write is owned by EIP-8037 and is asserted separately; here every expectation is taken from the -``regular_cost`` dimension only. +``execution_cost`` dimension only. -The regular ``SSTORE`` cost is the slot-access cost (``COLD_STORAGE_ACCESS`` +The execution ``SSTORE`` cost is the slot-access cost (``COLD_STORAGE_ACCESS`` when the key is cold, else ``WARM_SLOAD``) plus, on the first change of the slot in the transaction (``original == current != new``), the write cost ``STORAGE_WRITE`` (modeled as ``COLD_STORAGE_WRITE - COLD_STORAGE_ACCESS``). @@ -56,7 +56,7 @@ @EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() @pytest.mark.parametrize("key_warm,original,current,new", SSTORE_ROWS) -def test_sstore_regular_gas( +def test_sstore_execution_gas( state_test: StateTestFiller, pre: Alloc, fork: Fork, @@ -66,19 +66,19 @@ def test_sstore_regular_gas( new: int, ) -> None: """ - Measure the regular ``SSTORE`` gas for each EIP-8038 row and assert it. + Measure the execution ``SSTORE`` gas for each EIP-8038 row and assert it. The final (measured) ``SSTORE`` is wrapped in ``CodeGasMeasure`` so the - executed regular cost is stored on-chain and asserted against - ``expected_regular`` (slot access plus write-on-first-change). The same + executed execution cost is stored on-chain and asserted against + ``expected_execution`` (slot access plus write-on-first-change). The same value is cross-checked against the framework opcode model's - ``regular_cost`` as a secondary guard. The state-gas dimension is owned + ``execution_cost`` as a secondary guard. The state-gas dimension is owned by EIP-8037 and funded from the reservoir, so it is excluded here. """ # Move the data off slot 0 so ``CodeGasMeasure`` can store the measured # cost in slot 0. The bare (operand-free) opcode carries the metadata so # the measure overhead resolves to just the two operand PUSHes, and - # ``regular_cost``/``gas_cost`` are exact. + # ``execution_cost``/``gas_cost`` are exact. data_slot = 0x42 result_slot = 0 measured_bare = Op.SSTORE.with_metadata( @@ -90,7 +90,7 @@ def test_sstore_regular_gas( measured = measured_bare(data_slot, new) # Cross-check the oracle agrees with the hand-derived formula. - expected_regular = measured_bare.regular_cost(fork) + expected_execution = measured_bare.execution_cost(fork) # Reach ``current`` from ``original`` with an unmeasured prep SSTORE when # they differ, then measure the write to ``new``. The slot is warmed for @@ -122,9 +122,9 @@ def test_sstore_regular_gas( ) # State gas (owned by EIP-8037) is funded from the reservoir so it never - # disturbs the regular gas this test isolates. ``gas_limit`` is left + # disturbs the execution gas this test isolates. ``gas_limit`` is left # unset so the reservoir lands above the EIP-7825 cap and ``Op.GAS`` - # measures regular gas only; an explicit gas_limit below the cap would + # measures execution gas only; an explicit gas_limit below the cap would # zero the reservoir and spill state gas into the measurement. single_set_state_gas = Op.SSTORE(new_value=1).state_cost(fork) tx = Transaction( @@ -134,9 +134,9 @@ def test_sstore_regular_gas( state_gas_reservoir=2 * single_set_state_gas, ) - # result_slot holds the measured regular cost; data_slot holds ``new`` + # result_slot holds the measured execution cost; data_slot holds ``new`` # (absent when new == 0, because the slot is cleared). - expected_storage = {result_slot: expected_regular} + expected_storage = {result_slot: expected_execution} if new != 0: expected_storage[data_slot] = new post = {contract: Account(storage=expected_storage)} @@ -183,8 +183,8 @@ def test_sstore_cold_then_warm_same_slot( ) second = second_bare(data_slot, 3) - expected_first = first_bare.regular_cost(fork) - expected_second = second_bare.regular_cost(fork) + expected_first = first_bare.execution_cost(fork) + expected_second = second_bare.execution_cost(fork) # Each measured write stores its own runtime cost; the overhead # subtraction strips the two operand PUSHes so the stored value is the diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_sstore_refunds.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_sstore_refunds.py index 4fbe570673d..7755bdc9240 100644 --- a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_sstore_refunds.py +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_sstore_refunds.py @@ -11,7 +11,7 @@ trip; this reversal is exercised by ``test_sstore_clear_then_reset_nets_zero``. -This module covers the EIP-8038 *regular* ``SSTORE`` refund schedule via +This module covers the EIP-8038 *execution* ``SSTORE`` refund schedule via the transaction receipt's ``cumulative_gas_used``: * Clearing a slot whose original value is non-zero grants @@ -26,7 +26,7 @@ * The applied refund is capped at ``gas_used // 5`` (EIP-3529 quotient). All refunds use a non-zero original so the state-creation refund owned by -EIP-8037 is never involved; only the EIP-8038 regular dimension is +EIP-8037 is never involved; only the EIP-8038 execution dimension is exercised. """ @@ -56,7 +56,7 @@ def _cumulative_gas_used(code: Bytecode, fork: Fork) -> int: Return the receipt ``cumulative_gas_used`` for a single transaction whose execution is exactly ``code``. - Mirrors the spec: gross gas is intrinsic plus the regular and state + Mirrors the spec: gross gas is intrinsic plus the execution and state gas of the code; the applied refund is ``min(gross // 5, refund)`` (EIP-3529 quotient cap); the receipt reports gross minus the applied refund. @@ -64,7 +64,7 @@ def _cumulative_gas_used(code: Bytecode, fork: Fork) -> int: intrinsic = fork.transaction_intrinsic_cost_calculator()( return_cost_deducted_prior_execution=True ) - gross = intrinsic + code.regular_cost(fork) + code.state_cost(fork) + gross = intrinsic + code.execution_cost(fork) + code.state_cost(fork) applied_refund = min(gross // 5, code.refund(fork)) return gross - applied_refund @@ -104,7 +104,7 @@ def test_sstore_clear_grants_refund( intrinsic = fork.transaction_intrinsic_cost_calculator()( return_cost_deducted_prior_execution=True ) - gross = intrinsic + code.regular_cost(fork) + gross = intrinsic + code.execution_cost(fork) assert gross // 5 > refund_clear assert expected_cumulative == gross - refund_clear @@ -200,7 +200,7 @@ def test_sstore_restore_nonzero_refunds_write( intrinsic = fork.transaction_intrinsic_cost_calculator()( return_cost_deducted_prior_execution=True ) - gross = intrinsic + code.regular_cost(fork) + gross = intrinsic + code.execution_cost(fork) assert gross // 5 > storage_write assert expected_cumulative == gross - storage_write @@ -253,7 +253,7 @@ def test_sstore_refund_quotient_cap( intrinsic = fork.transaction_intrinsic_cost_calculator()( return_cost_deducted_prior_execution=True ) - gross = intrinsic + code.regular_cost(fork) + gross = intrinsic + code.execution_cost(fork) # The cap binds for every parametrization (single-clear gross is far # below 5x a clear refund). cap = gross // 5 @@ -306,10 +306,10 @@ def test_sstore_refund_cap_exact_equality( # Target the exact boundary: gross == quotient * accrued, so that # gross // quotient == accrued with no slack. Solve for the JUMPDEST # count from the remaining gas after intrinsic and the clear's - # regular cost; each JUMPDEST costs exactly 1 gas. + # execution cost; each JUMPDEST costs exactly 1 gas. jumpdest_gas = Op.JUMPDEST.gas_cost(fork) target_gross = quotient * accrued - base_gross = intrinsic + clear.regular_cost(fork) + base_gross = intrinsic + clear.execution_cost(fork) burn_gas = target_gross - base_gross num_jumpdest, remainder = divmod(burn_gas, jumpdest_gas) # An exact integer JUMPDEST count must reach the boundary; otherwise @@ -320,7 +320,7 @@ def test_sstore_refund_cap_exact_equality( code = clear + Op.JUMPDEST * num_jumpdest contract = pre.deploy_contract(code=code, storage={0: 1}) - gross = intrinsic + code.regular_cost(fork) + code.state_cost(fork) + gross = intrinsic + code.execution_cost(fork) + code.state_cost(fork) # Exact equality: the cap is neither under nor over the accrued refund. assert gross == target_gross assert gross // quotient == accrued diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_transient_storage_regression.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_transient_storage_regression.py index 5716c9dc277..afac3738334 100644 --- a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_transient_storage_regression.py +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_transient_storage_regression.py @@ -53,7 +53,7 @@ def test_transient_storage_gas_unchanged( # Measure TSTORE then TLOAD of the same transient slot in one frame, # subtracting the PUSH wrapper so the stored value is the bare opcode # cost. - push_cost = Op.PUSH1(0).regular_cost(fork) + push_cost = Op.PUSH1(0).execution_cost(fork) tstore_code = CodeGasMeasure( code=Op.TSTORE(0, 1), overhead_cost=2 * push_cost, diff --git a/tests/benchmark/compute/instruction/test_system.py b/tests/benchmark/compute/instruction/test_system.py index 0bc561435ee..02cbbbfcd83 100644 --- a/tests/benchmark/compute/instruction/test_system.py +++ b/tests/benchmark/compute/instruction/test_system.py @@ -407,7 +407,7 @@ def test_creates_collisions( ) proxy_contract = pre.deploy_contract(code=proxy_contract_code) - min_gas_required = proxy_contract_code.regular_cost( + min_gas_required = proxy_contract_code.execution_cost( fork ) + proxy_contract_code.state_cost(fork) setup = Op.PUSH20(proxy_contract) + Op.PUSH3(min_gas_required) @@ -425,7 +425,7 @@ def test_creates_collisions( ) pre.deploy_contract(address=addr, code=Op.INVALID) else: - creation_cost = proxy_contract_code.regular_cost(fork) + creation_cost = proxy_contract_code.execution_cost(fork) max_contract_count = ( 2 * gas_benchmark_value // creation_cost if fixed_opcode_count is None diff --git a/tests/benchmark/helper/contract_factory.py b/tests/benchmark/helper/contract_factory.py index 27176c325cf..6b06ef30c77 100644 --- a/tests/benchmark/helper/contract_factory.py +++ b/tests/benchmark/helper/contract_factory.py @@ -247,9 +247,9 @@ def transactions_by_total_contract_count( """ Create a list of transactions calling the factory to create the given number of contracts, each transaction capped by the fork's - regular-gas limit cap (EIP-7825). Under EIP-8037 the per-byte code + execution-gas limit cap (EIP-7825). Under EIP-8037 the per-byte code deposit is state gas drawn from a separate reservoir, so the split - bounds regular gas only and lets the combined gas exceed the cap. + bounds execution gas only and lets the combined gas exceed the cap. """ to = self.address() @@ -265,7 +265,7 @@ def calldata(iteration_count: int, start_iteration: int) -> bytes: start_iteration: int = contract_start_index tx_gas_limit: int | None = None - tx_regular_cost: int | None = None + tx_execution_cost: int | None = None tx_state_cost: int | None = None last_iteration_count: int = 0 @@ -277,7 +277,7 @@ def calldata(iteration_count: int, start_iteration: int) -> bytes: ): if ( tx_gas_limit is None - or tx_regular_cost is None + or tx_execution_cost is None or tx_state_cost is None or iteration_count != last_iteration_count ): @@ -288,11 +288,13 @@ def calldata(iteration_count: int, start_iteration: int) -> bytes: include_state_gas_reservoir=True, calldata=calldata_max, ) - tx_regular_cost = self.tx_regular_gas_cost_by_iteration_count( - fork=fork, - iteration_count=iteration_count, - start_iteration=start_iteration, - calldata=calldata_max, + tx_execution_cost = ( + self.tx_execution_gas_cost_by_iteration_count( + fork=fork, + iteration_count=iteration_count, + start_iteration=start_iteration, + calldata=calldata_max, + ) ) tx_state_cost = self.state_gas_cost_by_iteration_count( fork=fork, @@ -310,7 +312,7 @@ def calldata(iteration_count: int, start_iteration: int) -> bytes: to=to, gas_limit=tx_gas_limit, sender=sender, - regular_cost=tx_regular_cost, + execution_cost=tx_execution_cost, state_cost=tx_state_cost, data=calldata(iteration_count, start_iteration), deployed_contracts=deployed_contracts, diff --git a/tests/benchmark/stateful/bloatnet/test_sstore.py b/tests/benchmark/stateful/bloatnet/test_sstore.py index 5e612d5c118..ea958753444 100644 --- a/tests/benchmark/stateful/bloatnet/test_sstore.py +++ b/tests/benchmark/stateful/bloatnet/test_sstore.py @@ -463,7 +463,8 @@ def test_sstore_variants( [1, 0, 1, 0], id="oscillation_4x_from_zero", marks=pytest.mark.skip( - reason="net-zero state gas; degenerates to a regular-gas loop" + reason="net-zero state gas; degenerates to an " + "execution-gas loop" ), ), pytest.param( diff --git a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_create_gas.py b/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_create_gas.py index e7e0e5962af..a7e5900a463 100644 --- a/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_create_gas.py +++ b/tests/ported_static/stEIP150singleCodeGasPrices/test_raw_create_gas.py @@ -11,7 +11,7 @@ state_tests/stEIP150singleCodeGasPrices/RawCreateFailGasValueTransfer2Filler.json @manually-enhanced: Do not overwrite. Six RawCreate*Gas fillers folded into one -CodeGasMeasure parametrize; failure path charges regular_cost (no state gas). +CodeGasMeasure parametrize; failure path charges execution_cost (no state gas). """ import pytest @@ -99,7 +99,7 @@ def test_raw_create_gas( if fails: # A balance-check failure runs no init code and creates no account, so # only the regular (execution) gas is charged, never state gas. - expected_gas = create_code.regular_cost(fork) + expected_gas = create_code.execution_cost(fork) created_account = Account.NONEXISTENT else: expected_gas = create_code.gas_cost(fork) From 3ae3d66cc2b3fd448a9578e58fb49bbae30752eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=94=A1=E4=BD=B3=E8=AA=A0=20Louis=20Tsai?= <72684086+LouisTsai-Csie@users.noreply.github.com> Date: Thu, 30 Jul 2026 01:48:44 +0800 Subject: [PATCH 174/233] feat(tests): implement eip-8070 sparse blob pool tests (#2948) Co-authored-by: spencer-tb <spencer.tb@ethereum.org> --- .../execution/blob_transaction.py | 218 ++++++++++++- .../forks/forks/eips/amsterdam/eip_8070.py | 16 + .../src/execution_testing/rpc/__init__.py | 2 + .../testing/src/execution_testing/rpc/rpc.py | 28 +- .../src/execution_testing/rpc/rpc_types.py | 23 ++ .../src/execution_testing/specs/blobs.py | 8 + .../eip8070_sparse_blobpool/__init__.py | 3 + .../eip8070_sparse_blobpool/conftest.py | 148 +++++++++ .../amsterdam/eip8070_sparse_blobpool/spec.py | 42 +++ .../test_custody_columns.py | 108 +++++++ .../eip8070_sparse_blobpool/test_get_cells.py | 291 ++++++++++++++++++ 11 files changed, 878 insertions(+), 9 deletions(-) create mode 100644 packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8070.py create mode 100644 tests/amsterdam/eip8070_sparse_blobpool/__init__.py create mode 100644 tests/amsterdam/eip8070_sparse_blobpool/conftest.py create mode 100644 tests/amsterdam/eip8070_sparse_blobpool/spec.py create mode 100644 tests/amsterdam/eip8070_sparse_blobpool/test_custody_columns.py create mode 100644 tests/amsterdam/eip8070_sparse_blobpool/test_get_cells.py diff --git a/packages/testing/src/execution_testing/execution/blob_transaction.py b/packages/testing/src/execution_testing/execution/blob_transaction.py index 796f2c455ac..e120a91c79c 100644 --- a/packages/testing/src/execution_testing/execution/blob_transaction.py +++ b/packages/testing/src/execution_testing/execution/blob_transaction.py @@ -14,11 +14,19 @@ from execution_testing.rpc import ( BlobAndProofV1, BlobAndProofV2, + BlobCellsAndProofsV1, EngineRPC, EthRPC, ) -from execution_testing.rpc.rpc_types import GetBlobsResponse +from execution_testing.rpc.rpc_types import ( + ForkchoiceState, + GetBlobsResponse, + GetBlobsV4Response, + JSONRPCError, + PayloadStatusEnum, +) from execution_testing.test_types import ( + Blob, Environment, NetworkWrappedTransaction, Transaction, @@ -31,6 +39,20 @@ logger = get_logger(__name__) +CUSTODY_COLUMNS_BYTE_LENGTH = 16 +"""Byte length of a well-formed `custodyColumns` bitmap (EIP-8070).""" + + +def _interleave_hashes(a: List[Hash], b: List[Hash]) -> List[Hash]: + """Interleave two hash lists, starting with `a`, appending leftovers.""" + interleaved: List[Hash] = [] + for x, y in zip(a, b, strict=False): + interleaved.extend((x, y)) + shorter_length = min(len(a), len(b)) + interleaved.extend(a[shorter_length:]) + interleaved.extend(b[shorter_length:]) + return interleaved + def _validate_blob_and_proof( expected_blob: BlobAndProofV1 | BlobAndProofV2 | None, @@ -107,6 +129,81 @@ def _validate_blob_and_proof( ) +def _validate_cells_and_proofs( + expected_blob: Blob | None, + received: BlobCellsAndProofsV1 | None, + cell_mask: int, + index: int, +) -> None: + """ + Validate a received `engine_getBlobsV4` cell matrix against a local blob. + + The response is a compact matrix: for each existing blob the client + returns only the cells selected by `cell_mask`, ordered by ascending + cell index, so `blob_cells[k]` is the k-th requested cell. When + `expected_blob` is `None` (a non-existing hash), the whole entry must be + `null`. + + Per execution-apis `engine_getBlobsV4`, `cell_mask` is a little-endian + 16-byte bitmap where bit `i` selects cell `i` (see `EngineRPC.get_blobs`). + Network-wrapped txs deliver the full blob, so the client holds every + requested cell; a returned `null` means an unavailable cell and fails. + """ + if expected_blob is None: + if received is None: + logger.info( + f"Blob at index {index} correctly returned null " + "(non-existing blob hash)" + ) + return + raise ValueError( + f"Blob at index {index} should be null (non-existing hash), " + f"but client returned a cell matrix." + ) + if received is None: + raise ValueError(f"Received cell matrix at index {index} is empty.") + + assert expected_blob.cells is not None, ( + "Local blob has no cells; getBlobsV4 requires a fork with cell proofs." + ) + assert isinstance(expected_blob.proof, list), ( + "Local blob proof is not a cell-proof list." + ) + # Compact matrix: the client returns only the requested cells, in + # ascending cell-index order (bit `i` of the mask selects cell `i`). + requested_indices = [ + i for i in range(len(expected_blob.cells)) if (cell_mask >> i) & 1 + ] + if len(received.blob_cells) != len(requested_indices): + raise ValueError( + f"Cell matrix at index {index} has {len(received.blob_cells)} " + f"cells, expected {len(requested_indices)}." + ) + if len(received.proofs) != len(requested_indices): + raise ValueError( + f"Proof matrix at index {index} has {len(received.proofs)} " + f"proofs, expected {len(requested_indices)}." + ) + + for pos, cell_index in enumerate(requested_indices): + recv_cell = received.blob_cells[pos] + recv_proof = received.proofs[pos] + if recv_cell is None or recv_proof is None: + raise ValueError( + f"Requested cell {cell_index} at blob index {index} was " + "returned as null." + ) + if recv_cell != expected_blob.cells[cell_index]: + raise ValueError( + f"Cell mismatch at blob index {index}, cell {cell_index}." + ) + if recv_proof != expected_blob.proof[cell_index]: + raise ValueError( + f"Cell proof mismatch at blob index {index}, " + f"cell {cell_index}." + ) + + def versioned_hashes_with_blobs_and_proofs( tx: NetworkWrappedTransaction, ) -> Dict[Hash, BlobAndProofV1 | BlobAndProofV2]: @@ -148,7 +245,10 @@ class BlobTransaction(BaseExecute): txs: List[NetworkWrappedTransaction | Transaction] nonexisting_blob_hashes: List[Hash] | None = None + interleave_nonexisting_blob_hashes: bool = False get_blobs_version: int | None = None + cell_mask: int | None = None + custody_columns: bytes | None = None def prepare_transactions( self, @@ -199,6 +299,64 @@ def get_required_sender_balances( balances[sender] += tx.signer_minimum_balance(fork=fork) return balances + def _update_custody_columns( + self, + fork: Fork, + eth_rpc: EthRPC, + engine_rpc: EngineRPC, + ) -> None: + """ + Send a forkchoice update carrying the `custodyColumns` bitmap. + + A 16-byte bitmap must be accepted with a VALID payload status + (custody set update errors must not affect the forkchoice flow, + per `engine_forkchoiceUpdatedV4`); any other length must be + rejected with `-32602: Invalid params`. + """ + assert self.custody_columns is not None + fcu_version = fork.engine_forkchoice_updated_version() + assert fcu_version is not None and fcu_version >= 4, ( + "custodyColumns requires engine_forkchoiceUpdatedV4." + ) + latest_block = eth_rpc.get_block_by_number("latest") + assert latest_block is not None, "Failed to fetch the latest block." + forkchoice_state = ForkchoiceState( + head_block_hash=Hash(latest_block["hash"]), + ) + valid_length = len(self.custody_columns) == CUSTODY_COLUMNS_BYTE_LENGTH + try: + response = engine_rpc.forkchoice_updated( + forkchoice_state, + None, + version=fcu_version, + custody_columns=self.custody_columns, + ) + except JSONRPCError as e: + if valid_length: + raise + if e.code != -32602: + raise ValueError( + f"Expected error -32602 (Invalid params) for a " + f"{len(self.custody_columns)}-byte custodyColumns, " + f"got {e.code}: {e.message}" + ) from e + logger.info( + f"Client correctly rejected a " + f"{len(self.custody_columns)}-byte custodyColumns bitmap." + ) + return + if not valid_length: + raise ValueError( + f"Client accepted a {len(self.custody_columns)}-byte " + "custodyColumns bitmap; expected -32602 (Invalid params)." + ) + status = response.payload_status.status + if status != PayloadStatusEnum.VALID: + raise ValueError( + f"forkchoiceUpdatedV{fcu_version} with custodyColumns " + f"returned payload status {status}, expected VALID." + ) + def execute( self, fork: Fork, @@ -208,6 +366,7 @@ def execute( ) -> ExecuteResult: """Execute the format.""" versioned_hashes: Dict[Hash, BlobAndProofV1 | BlobAndProofV2] = {} + blobs_by_hash: Dict[Hash, Blob] = {} sent_txs: List[Transaction] = [] for tx_index, tx in enumerate(self.txs): tx = tx.with_signature_and_sender() @@ -218,6 +377,8 @@ def execute( versioned_hashes.update( versioned_hashes_with_blobs_and_proofs(tx) ) + for blob in tx.blob_objects: + blobs_by_hash[blob.versioned_hash] = blob else: sent_txs.append(tx) label = ( @@ -257,10 +418,27 @@ def execute( list_versioned_hashes = list(versioned_hashes.keys()) if self.nonexisting_blob_hashes is not None: - list_versioned_hashes.extend(self.nonexisting_blob_hashes) + if self.interleave_nonexisting_blob_hashes: + assert version >= 4, ( + "interleave_nonexisting_blob_hashes is only supported " + "with getBlobsV4." + ) + list_versioned_hashes = _interleave_hashes( + self.nonexisting_blob_hashes, list_versioned_hashes + ) + else: + list_versioned_hashes.extend(self.nonexisting_blob_hashes) - blob_response: GetBlobsResponse | None = engine_rpc.get_blobs( - list_versioned_hashes, version=version + if self.custody_columns is not None: + self._update_custody_columns(fork, eth_rpc, engine_rpc) + + indices_bitarray = self.cell_mask if version >= 4 else None + blob_response: GetBlobsResponse | GetBlobsV4Response | None = ( + engine_rpc.get_blobs( + list_versioned_hashes, + version=version, + indices_bitarray=indices_bitarray, + ) ) if version <= 2: @@ -284,6 +462,7 @@ def execute( f"getBlobsV{version} returned 'null' but all " "requested blobs should exist." ) + assert isinstance(blob_response, GetBlobsResponse) local_blobs_and_proofs = list(versioned_hashes.values()) assert len(blob_response) == len(local_blobs_and_proofs), ( f"Expected {len(local_blobs_and_proofs)} blobs and " @@ -305,6 +484,7 @@ def execute( "response, but V3 should always return an array " "(with null entries for missing blobs)." ) + assert isinstance(blob_response, GetBlobsResponse) expected_blobs_and_proofs: List[ BlobAndProofV1 | BlobAndProofV2 | None ] = list(versioned_hashes.values()) @@ -334,10 +514,38 @@ def execute( f"blobs and {nonexisting_count} null entries for " "missing blobs" ) + elif version == 4: + # V4 (EIP-8070): partial cell matrix, selected by cell_mask + assert self.cell_mask is not None, ( + f"getBlobsV{version} requires a cell_mask." + ) + if blob_response is None: + raise ValueError( + f"getBlobsV{version} returned 'null' for the entire " + "response, but V4 should always return an array " + "(with null entries for missing blobs)." + ) + assert isinstance(blob_response, GetBlobsV4Response) + # `blobs_by_hash` only holds existing blobs, so non-existing + # hashes map to `None` at their exact request positions. + expected_blobs: List[Blob | None] = [ + blobs_by_hash.get(vh) for vh in list_versioned_hashes + ] + if len(blob_response) != len(expected_blobs): + raise ValueError( + f"Expected {len(expected_blobs)} blob responses, " + f"got {len(blob_response)}." + ) + for i, (expected_cells, received_cells) in enumerate( + zip(expected_blobs, blob_response.root, strict=True) + ): + _validate_cells_and_proofs( + expected_cells, received_cells, self.cell_mask, i + ) else: raise NotImplementedError( f"getBlobsV{version} is not supported. " - "Supported versions: V1, V2, V3." + "Supported versions: V1, V2, V3, V4." ) eth_rpc.wait_for_transactions(sent_txs) diff --git a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8070.py b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8070.py new file mode 100644 index 00000000000..2b43e4a68ec --- /dev/null +++ b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8070.py @@ -0,0 +1,16 @@ +""" +EIP-8070: eth/72 - Sparse Blobpool. + +Custody-aligned sampling of the blobpool, adding the `engine_getBlobsV4` +endpoint to retrieve a partial cell matrix of a blob. + +https://eips.ethereum.org/EIPS/eip-8070 +""" + +from ....base_fork import BaseFork + + +class EIP8070(BaseFork): + """EIP-8070 class.""" + + pass diff --git a/packages/testing/src/execution_testing/rpc/__init__.py b/packages/testing/src/execution_testing/rpc/__init__.py index d62b65bcd7a..1812a5fa98a 100644 --- a/packages/testing/src/execution_testing/rpc/__init__.py +++ b/packages/testing/src/execution_testing/rpc/__init__.py @@ -22,6 +22,7 @@ from .rpc_types import ( BlobAndProofV1, BlobAndProofV2, + BlobCellsAndProofsV1, EthConfigResponse, ForkConfig, ForkConfigBlobSchedule, @@ -35,6 +36,7 @@ "AdminRPC", "BlobAndProofV1", "BlobAndProofV2", + "BlobCellsAndProofsV1", "BlockNotAvailableError", "BlockNumberType", "DebugRPC", diff --git a/packages/testing/src/execution_testing/rpc/rpc.py b/packages/testing/src/execution_testing/rpc/rpc.py index cee13b98e30..739c2c26962 100644 --- a/packages/testing/src/execution_testing/rpc/rpc.py +++ b/packages/testing/src/execution_testing/rpc/rpc.py @@ -51,6 +51,7 @@ ForkchoiceState, ForkchoiceUpdateResponse, GetBlobsResponse, + GetBlobsV4Response, GetPayloadResponse, JSONRPCError, JSONRPCRequest, @@ -1444,6 +1445,7 @@ def forkchoice_updated( payload_attributes: PayloadAttributes | None = None, *, version: int, + custody_columns: bytes | None = None, ) -> ForkchoiceUpdateResponse: """ `engine_forkchoiceUpdatedVX`: Updates the forkchoice state of the @@ -1451,10 +1453,16 @@ def forkchoice_updated( """ method = f"forkchoiceUpdatedV{version}" + params: List[Any] if payload_attributes is None: params = [to_json(forkchoice_state), None] else: params = [to_json(forkchoice_state), to_json(payload_attributes)] + if custody_columns is not None: + # Third parameter of `engine_forkchoiceUpdatedV4` (EIP-8070): + # a bitmap of the blob columns custodied by the node. + assert version >= 4, "custodyColumns requires forkchoiceUpdatedV4." + params.append(f"0x{custody_columns.hex()}") return ForkchoiceUpdateResponse.model_validate( self.post_request( @@ -1487,21 +1495,33 @@ def get_blobs( versioned_hashes: List[Hash], *, version: int, - ) -> GetBlobsResponse | None: + indices_bitarray: int | None = None, + ) -> GetBlobsResponse | GetBlobsV4Response | None: """ `engine_getBlobsVX`: Retrieves blobs from an execution layers tx pool. """ method = f"getBlobsV{version}" - params = [f"{h}" for h in versioned_hashes] + params: List[Any] = [[f"{h}" for h in versioned_hashes]] + + if version >= 4: + assert indices_bitarray is not None, ( + f"getBlobsV{version} requires an indices_bitarray cell mask." + ) + # `indices_bitarray` is a little-endian 16-byte bitmap where bit + # `i` selects cell `i` (execution-apis `engine_getBlobsV4`). + params.append(f"0x{indices_bitarray.to_bytes(16, 'little').hex()}") response = self.post_request( - request=RPCCall(method=method, params=[params]), + request=RPCCall(method=method, params=params), ).result_or_raise() if response is None: # for tests that request non-existing blobs logger.debug("get_blobs response received but it has value: None") return None - return GetBlobsResponse.model_validate( + response_model = ( + GetBlobsV4Response if version >= 4 else GetBlobsResponse + ) + return response_model.model_validate( response, context=self.response_validation_context, ) diff --git a/packages/testing/src/execution_testing/rpc/rpc_types.py b/packages/testing/src/execution_testing/rpc/rpc_types.py index d4e7784a6c0..f58bd345378 100644 --- a/packages/testing/src/execution_testing/rpc/rpc_types.py +++ b/packages/testing/src/execution_testing/rpc/rpc_types.py @@ -313,6 +313,13 @@ class BlobAndProofV2(CamelModel): proofs: List[Bytes] +class BlobCellsAndProofsV1(CamelModel): + """Represents a partial cell and cell-proof structure (>= Amsterdam).""" + + blob_cells: List[Bytes | None] + proofs: List[Bytes | None] + + class GetPayloadResponse(CamelModel): """Represents the response of a get payload request.""" @@ -341,6 +348,22 @@ def __getitem__( return self.root[index] +class GetBlobsV4Response( + EthereumTestRootModel[List[BlobCellsAndProofsV1 | None]] +): + """Represents the response of an `engine_getBlobsV4` request.""" + + root: List[BlobCellsAndProofsV1 | None] + + def __len__(self) -> int: + """Return the number of blob entries in the response.""" + return len(self.root) + + def __getitem__(self, index: int) -> BlobCellsAndProofsV1 | None: + """Return the blob cell matrix at the given index.""" + return self.root[index] + + class ForkConfigBlobSchedule(CamelModel): """Representation of the blob schedule of a given fork.""" diff --git a/packages/testing/src/execution_testing/specs/blobs.py b/packages/testing/src/execution_testing/specs/blobs.py index 7eb8c8dc20e..630f6765dc6 100644 --- a/packages/testing/src/execution_testing/specs/blobs.py +++ b/packages/testing/src/execution_testing/specs/blobs.py @@ -23,7 +23,10 @@ class BlobsTest(BaseTest): pre: Alloc txs: List[NetworkWrappedTransaction | Transaction] nonexisting_blob_hashes: List[Hash] | None = None + interleave_nonexisting_blob_hashes: bool = False get_blobs_version: int | None = None + cell_mask: int | None = None + custody_columns: bytes | None = None supported_execute_formats: ClassVar[Sequence[LabeledExecuteFormat]] = [ LabeledExecuteFormat( @@ -53,7 +56,12 @@ def execute( return BlobTransaction( txs=self.txs, nonexisting_blob_hashes=self.nonexisting_blob_hashes, + interleave_nonexisting_blob_hashes=( + self.interleave_nonexisting_blob_hashes + ), get_blobs_version=self.get_blobs_version, + cell_mask=self.cell_mask, + custody_columns=self.custody_columns, ) raise Exception(f"Unsupported execute format: {execute_format}") diff --git a/tests/amsterdam/eip8070_sparse_blobpool/__init__.py b/tests/amsterdam/eip8070_sparse_blobpool/__init__.py new file mode 100644 index 00000000000..19ebd3f9418 --- /dev/null +++ b/tests/amsterdam/eip8070_sparse_blobpool/__init__.py @@ -0,0 +1,3 @@ +""" +Test suite for [EIP-8070: eth/72 - Sparse Blobpool](https://eips.ethereum.org/EIPS/eip-8070). +""" diff --git a/tests/amsterdam/eip8070_sparse_blobpool/conftest.py b/tests/amsterdam/eip8070_sparse_blobpool/conftest.py new file mode 100644 index 00000000000..3952b6363cf --- /dev/null +++ b/tests/amsterdam/eip8070_sparse_blobpool/conftest.py @@ -0,0 +1,148 @@ +"""Shared fixtures for building blob transactions in EIP-8070 tests.""" + +from typing import List, Optional + +import pytest +from execution_testing import ( + Address, + Alloc, + Blob, + Fork, + NetworkWrappedTransaction, + Transaction, + TransactionException, +) + + +@pytest.fixture +def destination_account(pre: Alloc) -> Address: + """Destination account for the blob transactions.""" + return pre.fund_eoa(amount=0) + + +@pytest.fixture +def tx_value() -> int: + """Value contained by the transactions sent during test.""" + return 1 + + +@pytest.fixture +def tx_gas(fork: Fork) -> int: + """Gas allocated to transactions sent during test.""" + return fork.transaction_intrinsic_cost_calculator()() + + +@pytest.fixture +def block_base_fee_per_gas() -> int: + """Return default max fee per gas for transactions sent during test.""" + return 7 + + +@pytest.fixture +def tx_calldata() -> bytes: + """Calldata in transactions sent during test.""" + return b"" + + +@pytest.fixture(autouse=True) +def parent_excess_blobs() -> int: + """Excess blobs of the parent block (defaults to a blob gas price of 1).""" + return 10 + + +@pytest.fixture(autouse=True) +def parent_blobs() -> int: + """Blobs of the parent block.""" + return 0 + + +@pytest.fixture +def excess_blob_gas( + fork: Fork, + parent_excess_blobs: int | None, + parent_blobs: int | None, + block_base_fee_per_gas: int, +) -> int | None: + """Calculate the excess blob gas of the block under test.""" + if parent_excess_blobs is None or parent_blobs is None: + return None + excess_blob_gas = fork.excess_blob_gas_calculator() + return excess_blob_gas( + parent_excess_blobs=parent_excess_blobs, + parent_blob_count=parent_blobs, + parent_base_fee_per_gas=block_base_fee_per_gas, + ) + + +@pytest.fixture +def blob_gas_price( + fork: Fork, + excess_blob_gas: int | None, +) -> int | None: + """Return blob gas price for the block of the test.""" + if excess_blob_gas is None: + return None + get_blob_gas_price = fork.blob_gas_price_calculator() + return get_blob_gas_price(excess_blob_gas=excess_blob_gas) + + +@pytest.fixture +def txs_versioned_hashes(txs_blobs: List[List[Blob]]) -> List[List[bytes]]: + """List of blob versioned hashes derived from the blobs.""" + return [[blob.versioned_hash for blob in blob_tx] for blob_tx in txs_blobs] + + +@pytest.fixture +def tx_max_fee_per_blob_gas(fork: Fork, blob_gas_price: Optional[int]) -> int: + """Max fee per blob gas for transactions sent during test.""" + if blob_gas_price is None: + return fork.min_base_fee_per_blob_gas() + return blob_gas_price + + +@pytest.fixture +def tx_error() -> Optional[TransactionException]: + """No transaction is expected to be rejected by the transition tool.""" + return None + + +@pytest.fixture(autouse=True) +def txs( + pre: Alloc, + destination_account: Optional[Address], + tx_gas: int, + tx_value: int, + tx_calldata: bytes, + tx_max_fee_per_blob_gas: int, + txs_versioned_hashes: List[List[bytes]], + tx_error: Optional[TransactionException], + txs_blobs: List[List[Blob]], + fork: Fork, +) -> List[NetworkWrappedTransaction | Transaction]: + """Prepare the list of transactions that are sent during the test.""" + if len(txs_blobs) != len(txs_versioned_hashes): + raise ValueError( + "txs_blobs and txs_versioned_hashes should have the same length" + ) + txs: List[NetworkWrappedTransaction | Transaction] = [] + for tx_blobs, tx_versioned_hashes in zip( + txs_blobs, txs_versioned_hashes, strict=False + ): + tx = Transaction( + sender=pre.fund_eoa(), + to=destination_account, + value=tx_value, + gas_limit=tx_gas, + data=tx_calldata, + max_fee_per_blob_gas=tx_max_fee_per_blob_gas, + access_list=[], + blob_versioned_hashes=tx_versioned_hashes, + error=tx_error, + ) + network_wrapped_tx = NetworkWrappedTransaction( + tx=tx, + blob_objects=tx_blobs, + wrapper_version=fork.full_blob_tx_wrapper_version(), + ) + txs.append(network_wrapped_tx) + return txs diff --git a/tests/amsterdam/eip8070_sparse_blobpool/spec.py b/tests/amsterdam/eip8070_sparse_blobpool/spec.py new file mode 100644 index 00000000000..d6ec4c58162 --- /dev/null +++ b/tests/amsterdam/eip8070_sparse_blobpool/spec.py @@ -0,0 +1,42 @@ +"""Defines EIP-8070 specification constants and functions.""" + +from dataclasses import dataclass + + +@dataclass(frozen=True) +class ReferenceSpec: + """Defines the reference spec version and git path.""" + + git_path: str + version: str + + +ref_spec_8070 = ReferenceSpec( + "EIPS/eip-8070.md", "64d1b463e1c75884c995f81d8ffab40401acbcaa" +) + + +@dataclass(frozen=True) +class Spec: + """ + Parameters from the EIP-8070 specification as defined at + https://eips.ethereum.org/EIPS/eip-8070. + """ + + CELLS_PER_EXT_BLOB = 128 + """Number of cells an extended blob is split into for `getBlobsV4`.""" + + RECONSTRUCTION_THRESHOLD = 64 + """Number of cells required for Reed-Solomon reconstruction of a blob.""" + + SAMPLES_PER_SLOT = 8 + """Minimum number of blob columns a node must custody.""" + + CUSTODY_BITMAP_BYTES = 16 + """Byte length of the `custodyColumns` and cell mask bitmaps.""" + + MIN_SUPPORTED_REQUEST_SIZE = 128 + """ + Minimum `getBlobsV4` request size (in versioned hashes) that clients + must support, per the execution-apis `engine_getBlobsV4` definition. + """ diff --git a/tests/amsterdam/eip8070_sparse_blobpool/test_custody_columns.py b/tests/amsterdam/eip8070_sparse_blobpool/test_custody_columns.py new file mode 100644 index 00000000000..52c6fcd78e8 --- /dev/null +++ b/tests/amsterdam/eip8070_sparse_blobpool/test_custody_columns.py @@ -0,0 +1,108 @@ +""" +Custody columns forkchoice tests. + +Tests for the `custodyColumns` parameter of `engine_forkchoiceUpdatedV4` +in [EIP-8070: eth/72 - Sparse Blobpool]( +https://eips.ethereum.org/EIPS/eip-8070). + +`custodyColumns` is an optional 16-byte bitmap informing the execution +client of the blob columns it must custody. A well-formed bitmap must be +accepted (custody set update errors must not affect the forkchoice flow); +a bitmap of any other length must be rejected with `-32602: Invalid +params`. Blob serving via `engine_getBlobsV4` must be unaffected either +way, since the client holds the full blobs. +""" + +from typing import List + +import pytest +from execution_testing import ( + Alloc, + Blob, + BlobsTestFiller, + Fork, + NetworkWrappedTransaction, + Transaction, +) + +from .spec import Spec, ref_spec_8070 + +REFERENCE_SPEC_GIT_PATH = ref_spec_8070.git_path +REFERENCE_SPEC_VERSION = ref_spec_8070.version + +pytestmark = pytest.mark.valid_from("EIP8070") + +CELLS = Spec.CELLS_PER_EXT_BLOB +ALL_CELLS_MASK = (1 << CELLS) - 1 +BITMAP_BYTES = Spec.CUSTODY_BITMAP_BYTES + + +def generate_single_blob_layout(fork: Fork) -> List: + """Return a single-blob transaction layout.""" + return [ + pytest.param([[Blob.from_fork(fork)]], id="single_blob_transaction") + ] + + +@pytest.mark.parametrize( + "custody_columns", + [ + pytest.param(b"\xff" * BITMAP_BYTES, id="all_columns"), + pytest.param( + ((1 << Spec.SAMPLES_PER_SLOT) - 1).to_bytes( + BITMAP_BYTES, "little" + ), + id="custody_aligned_8", + ), + pytest.param(b"\x00" * BITMAP_BYTES, id="no_columns"), + ], +) +@pytest.mark.parametrize_by_fork("txs_blobs", generate_single_blob_layout) +@pytest.mark.exception_test +def test_fcu_custody_columns( + blobs_test: BlobsTestFiller, + pre: Alloc, + txs: List[NetworkWrappedTransaction | Transaction], + custody_columns: bytes, +) -> None: + """ + Test that `engine_forkchoiceUpdatedV4` accepts a 16-byte + `custodyColumns` bitmap with a VALID payload status and that blob + serving via `getBlobsV4` is unaffected by the custody update. + """ + blobs_test( + pre=pre, + txs=txs, + get_blobs_version=4, + cell_mask=ALL_CELLS_MASK, + custody_columns=custody_columns, + ) + + +@pytest.mark.parametrize( + "custody_columns", + [ + pytest.param(b"\xff" * (BITMAP_BYTES - 1), id="fifteen_bytes"), + pytest.param(b"\xff" * (BITMAP_BYTES + 1), id="seventeen_bytes"), + pytest.param(b"", id="empty"), + ], +) +@pytest.mark.parametrize_by_fork("txs_blobs", generate_single_blob_layout) +@pytest.mark.exception_test +def test_fcu_custody_columns_invalid_length( + blobs_test: BlobsTestFiller, + pre: Alloc, + txs: List[NetworkWrappedTransaction | Transaction], + custody_columns: bytes, +) -> None: + """ + Test that a malformed-length `custodyColumns` bitmap is rejected with + `-32602: Invalid params` and does not affect subsequent blob serving. + """ + blobs_test( + pre=pre, + txs=txs, + get_blobs_version=4, + cell_mask=ALL_CELLS_MASK, + custody_columns=custody_columns, + ) diff --git a/tests/amsterdam/eip8070_sparse_blobpool/test_get_cells.py b/tests/amsterdam/eip8070_sparse_blobpool/test_get_cells.py new file mode 100644 index 00000000000..486fa333f50 --- /dev/null +++ b/tests/amsterdam/eip8070_sparse_blobpool/test_get_cells.py @@ -0,0 +1,291 @@ +""" +Get cells engine endpoint tests. + +Tests for the `engine_getBlobsV4` endpoint in [EIP-8070: eth/72 - Sparse +Blobpool](https://eips.ethereum.org/EIPS/eip-8070). + +`engine_getBlobsV4` retrieves a custody-aligned subset of a blob's cells, +selected by a `uint128` `indices_bitarray` cell mask, and returns a partial +cell matrix with `null` entries for cells that were not requested or are not +held by the client. +""" + +from hashlib import sha256 +from typing import List + +import pytest +from execution_testing import ( + Alloc, + Blob, + BlobsTestFiller, + Fork, + Hash, + NetworkWrappedTransaction, + Transaction, +) + +from .spec import Spec, ref_spec_8070 + +REFERENCE_SPEC_GIT_PATH = ref_spec_8070.git_path +REFERENCE_SPEC_VERSION = ref_spec_8070.version + +pytestmark = pytest.mark.valid_from("EIP8070") + +CELLS = Spec.CELLS_PER_EXT_BLOB +ALL_CELLS_MASK = (1 << CELLS) - 1 + + +def generate_blob_layouts(fork: Fork) -> List: + """Return blob transaction layouts to exercise `getBlobsV4`.""" + max_blobs_per_block = fork.max_blobs_per_block() + max_blobs_per_tx = fork.max_blobs_per_tx() + target_blobs_per_block = fork.target_blobs_per_block() + + # Ascending pattern (1, 2, 3... blobs per tx) capped at the target + ascending_txs = [] + total_blobs = 0 + blob_offset = 0 + for tx_size in range(1, max_blobs_per_tx + 1): + if total_blobs + tx_size > target_blobs_per_block: + break + ascending_txs.append( + [Blob.from_fork(fork, blob_offset + j) for j in range(tx_size)] + ) + total_blobs += tx_size + blob_offset += tx_size + + two_tx_blobs = min(target_blobs_per_block // 2, max_blobs_per_tx) + three_tx_blobs = min(target_blobs_per_block // 3, max_blobs_per_tx) + + return [ + pytest.param( + [[Blob.from_fork(fork)]], + id="single_blob_transaction", + ), + pytest.param( + [[Blob.from_fork(fork, s) for s in range(max_blobs_per_tx)]], + id="max_blobs_per_tx", + ), + pytest.param( + [[Blob.from_fork(fork, s)] for s in range(max_blobs_per_block)], + id="max_blobs_per_block", + ), + pytest.param( + [[Blob.from_fork(fork, s)] for s in range(target_blobs_per_block)], + id="target_blobs_per_block", + ), + pytest.param( + [ + [Blob.from_fork(fork, s) for s in range(two_tx_blobs)], + [ + Blob.from_fork(fork, s + two_tx_blobs) + for s in range(two_tx_blobs) + ], + ], + id="two_tx_equal_blobs", + ), + pytest.param( + [ + [ + Blob.from_fork(fork, s + i * three_tx_blobs) + for s in range(three_tx_blobs) + ] + for i in range(3) + ], + id="three_tx_equal_blobs", + ), + pytest.param( + [[Blob.from_fork(fork, s) for s in range(max_blobs_per_tx)]] + + [ + [Blob.from_fork(fork, max_blobs_per_tx + s)] + for s in range(max_blobs_per_block - max_blobs_per_tx) + ], + id="mixed_max_tx_plus_singles", + ), + pytest.param( + ascending_txs, + id="ascending_blob_pattern", + ), + ] + + +def generate_single_blob_layout(fork: Fork) -> List: + """Return a single-blob transaction layout.""" + return [ + pytest.param([[Blob.from_fork(fork)]], id="single_blob_transaction") + ] + + +def generate_single_blob_txs_layout(fork: Fork) -> List: + """Return a layout of three single-blob transactions.""" + return [ + pytest.param( + [[Blob.from_fork(fork, s)] for s in range(3)], + id="three_single_blob_txs", + ) + ] + + +def generate_cell_masks() -> List: + """Return cell masks to exercise `getBlobsV4`.""" + return [ + pytest.param(ALL_CELLS_MASK, id="all_cells"), + pytest.param((1 << Spec.RECONSTRUCTION_THRESHOLD) - 1, id="first_64"), + pytest.param( + ALL_CELLS_MASK ^ ((1 << Spec.RECONSTRUCTION_THRESHOLD) - 1), + id="top_64", + ), + pytest.param(0xFF, id="custody_aligned_8"), + pytest.param(1, id="single_cell"), + pytest.param(1 << (CELLS - 1), id="last_cell"), + pytest.param( + sum(1 << i for i in range(0, CELLS, 2)), id="alternating_cells" + ), + pytest.param(0, id="no_cells"), + ] + + +@pytest.mark.parametrize( + "cell_mask", + generate_cell_masks(), +) +@pytest.mark.parametrize_by_fork("txs_blobs", generate_blob_layouts) +@pytest.mark.exception_test +def test_get_cells( + blobs_test: BlobsTestFiller, + pre: Alloc, + txs: List[NetworkWrappedTransaction | Transaction], + cell_mask: int, +) -> None: + """ + Test that `getBlobsV4` returns exactly the cells selected by the mask. + + Requested cells (and their proofs) must match the locally computed values; + non-requested cell indices must be `null` in the partial matrix. + """ + blobs_test( + pre=pre, + txs=txs, + get_blobs_version=4, + cell_mask=cell_mask, + ) + + +@pytest.mark.parametrize( + "cell_mask", + generate_cell_masks(), +) +@pytest.mark.parametrize_by_fork("txs_blobs", generate_blob_layouts) +@pytest.mark.exception_test +def test_get_cells_partial_and_missing( + blobs_test: BlobsTestFiller, + pre: Alloc, + txs: List[NetworkWrappedTransaction | Transaction], + cell_mask: int, +) -> None: + """ + Test that `getBlobsV4` returns a partial response: existing blobs yield a + cell matrix while non-existing versioned hashes yield `null` entries. + """ + nonexisting_blob_hashes = [ + Hash(sha256(str(i).encode()).digest()) for i in range(5) + ] + blobs_test( + pre=pre, + txs=txs, + get_blobs_version=4, + cell_mask=cell_mask, + nonexisting_blob_hashes=nonexisting_blob_hashes, + ) + + +@pytest.mark.parametrize( + "cell_mask", + generate_cell_masks(), +) +@pytest.mark.parametrize("txs_blobs", [[]], ids=["no_blobs"]) +@pytest.mark.exception_test +def test_get_cells_only_nonexisting( + blobs_test: BlobsTestFiller, + pre: Alloc, + cell_mask: int, +) -> None: + """ + Test that `getBlobsV4` returns an array of `null` entries (one per + requested hash) when all requested blobs are non-existing. + """ + nonexisting_blob_hashes = [ + Hash(sha256(str(i).encode()).digest()) for i in range(5) + ] + blobs_test( + pre=pre, + txs=[], + get_blobs_version=4, + cell_mask=cell_mask, + nonexisting_blob_hashes=nonexisting_blob_hashes, + ) + + +@pytest.mark.parametrize( + "cell_mask", + [pytest.param(0xFF, id="custody_aligned_8")], +) +@pytest.mark.parametrize_by_fork("txs_blobs", generate_single_blob_layout) +@pytest.mark.exception_test +def test_get_cells_min_request_size( + blobs_test: BlobsTestFiller, + pre: Alloc, + txs: List[NetworkWrappedTransaction | Transaction], + cell_mask: int, +) -> None: + """ + Test a request of 128 versioned hashes, the minimum request size a + client must support for `getBlobsV4`. + + The response must hold one entry per requested hash: a cell matrix for + the existing blob and `null` for each non-existing hash. + """ + nonexisting_blob_hashes = [ + Hash(sha256(str(i).encode()).digest()) + for i in range(Spec.MIN_SUPPORTED_REQUEST_SIZE - 1) + ] + blobs_test( + pre=pre, + txs=txs, + get_blobs_version=4, + cell_mask=cell_mask, + nonexisting_blob_hashes=nonexisting_blob_hashes, + ) + + +@pytest.mark.parametrize( + "cell_mask", + [ + pytest.param(ALL_CELLS_MASK, id="all_cells"), + pytest.param(0xFF, id="custody_aligned_8"), + ], +) +@pytest.mark.parametrize_by_fork("txs_blobs", generate_single_blob_txs_layout) +@pytest.mark.exception_test +def test_get_cells_interleaved_missing( + blobs_test: BlobsTestFiller, + pre: Alloc, + txs: List[NetworkWrappedTransaction | Transaction], + cell_mask: int, +) -> None: + """ + Test that `null` entries appear at the exact request positions when + non-existing hashes are interleaved with existing ones (leading, + middle, and trailing positions of the request). + """ + nonexisting_blob_hashes = [ + Hash(sha256(str(i).encode()).digest()) for i in range(5) + ] + blobs_test( + pre=pre, + txs=txs, + get_blobs_version=4, + cell_mask=cell_mask, + nonexisting_blob_hashes=nonexisting_blob_hashes, + interleave_nonexisting_blob_hashes=True, + ) From af137475d7b15842438cfcb30c01d8903bf57b3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=94=A1=E4=BD=B3=E8=AA=A0=20Louis=20Tsai?= <72684086+LouisTsai-Csie@users.noreply.github.com> Date: Thu, 30 Jul 2026 05:59:18 +0800 Subject: [PATCH 175/233] feat(test-benchmark): on-chain account verification (#3197) * feat: implement deployed account verification * refactor: add account verification to tests * refactor: update alloc to include the pre method * refactor: verify account flag name * refactor: clean up docstring * refactor: chunk-wise early raise in deployed accounts verification * refactor: move account verification to a helper, session-scope dedup state * refactor(test-execute): verify_full_accounts internal of pre --------- Co-authored-by: marioevz <marioevz@gmail.com> --- .../plugins/execute/pre_alloc.py | 168 ++++++++++++++++++ .../plugins/fill_stateful/fill_stateful.py | 11 ++ .../src/execution_testing/specs/blockchain.py | 4 + .../test_types/account_types.py | 25 +++ tests/benchmark/conftest.py | 7 + tests/benchmark/helper/account_creator.py | 91 +++++++++- .../helper/account_sender_receiver.py | 59 ++++++ .../benchmark/helper/account_verification.py | 96 ++++++++++ .../stateful/bloatnet/test_account_query.py | 12 ++ .../bloatnet/test_transaction_types.py | 73 +++++++- 10 files changed, 533 insertions(+), 13 deletions(-) create mode 100644 tests/benchmark/helper/account_verification.py diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py index fccb7ca45cd..4b80161a9b4 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py @@ -1,5 +1,6 @@ """Pre-allocation fixtures used for test filling.""" +from collections.abc import Sequence from dataclasses import dataclass from itertools import count from pathlib import Path @@ -224,6 +225,67 @@ class _DeferredFundAddress: minimum_balance: bool +@dataclass +class _DeferredAccountAssertion: + """ + Deferred assertion on a predeployed account. + + Verified at start_block before the benchmark runs. + Uses primitives only to stay independent of test expectations. + """ + + address: Address + is_existing_account: bool + is_contract: bool + min_balance: int | None + code_prefix: bytes | None + label: str | None + + +class DeployedAccountVerificationError(AssertionError): + """Raised when predeployed benchmark targets fail verification.""" + + +def _check_account_assertion( + d: _DeferredAccountAssertion, + account: Account | None, + code: Bytes | None, +) -> list[str]: + """Return human-readable failures for one account assertion (may be [].""" + who = f"{d.label or '<target>'} at {d.address}" + if account is None: + return [f"{who}: no account data returned from the client"] + balance = int(account.balance) + nonce = int(account.nonce) + errors: list[str] = [] + if not d.is_existing_account: + if balance != 0 or nonce != 0: + errors.append( + f"{who}: expected NON-existent, got balance={balance} " + f"nonce={nonce}" + ) + return errors + if d.is_contract and nonce < 1: + errors.append( + f"{who}: expected a deployed contract (nonce>=1) but got " + f"nonce={nonce}, balance={balance} — likely NOT deployed on the " + "snapshot; the benchmark would silently hit an empty account" + ) + if d.min_balance is not None and balance < d.min_balance: + errors.append( + f"{who}: expected balance>={d.min_balance} but got {balance}" + ) + if d.code_prefix is not None: + actual = bytes(code) if code is not None else b"" + if not actual.startswith(d.code_prefix): + errors.append( + f"{who}: expected code to start with " + f"0x{d.code_prefix.hex()} (e.g. a delegated account) but " + f"got 0x{actual.hex()}" + ) + return errors + + def _compute_deploy_gas_limit( fork: Fork, *, @@ -320,8 +382,12 @@ class Alloc(SharedAlloc): _deferred_fund_addresses: List[_DeferredFundAddress] = PrivateAttr( default_factory=list ) + _deferred_account_assertions: List[_DeferredAccountAssertion] = ( + PrivateAttr(default_factory=list) + ) _block_number: int = PrivateAttr() _timestamp: int = PrivateAttr() + _verify_full: bool = PrivateAttr(default=False) def __init__( self, @@ -335,6 +401,7 @@ def __init__( block_number: int = 0, timestamp: int = 0, funding_gas_limit: int = 200_000, + verify_full: bool = False, **kwargs: Any, ) -> None: """Initialize the pre-alloc with the given parameters.""" @@ -348,6 +415,7 @@ def __init__( self._block_number = block_number self._timestamp = timestamp self._funding_gas_limit = funding_gas_limit + self._verify_full = verify_full def code_pre_processor(self, code: Bytecode) -> Bytecode: """Pre-processes the code before setting it.""" @@ -826,6 +894,103 @@ def _nonexistent_account(self) -> Address: logger.debug(f"Returning unused address {eoa} (nonexistent account)") return Address(eoa) + def expect_account_state( + self, + addresses: Address | Sequence[Address], + *, + is_existing_account: bool = True, + is_contract: bool = False, + min_balance: int | None = None, + code_prefix: bytes | None = None, + ) -> None: + """ + Register deferred assertion(s) on predeployed account(s). + + Verified at start_block (fill-stateful only). For a range, only the + first and last are checked unless ``--verify-full-accounts`` is set; + each assertion's label is taken from the address itself. + """ + if isinstance(addresses, Address): + targets: Sequence[Address] = (addresses,) + elif self._verify_full or len(addresses) <= 2: + targets = addresses + else: + targets = (addresses[0], addresses[-1]) + for address in targets: + self._deferred_account_assertions.append( + _DeferredAccountAssertion( + address=address, + is_existing_account=is_existing_account, + is_contract=is_contract, + min_balance=min_balance, + code_prefix=code_prefix, + label=address.label, + ) + ) + + def verify_deployed_accounts(self, block_number: int) -> None: + """ + Verify registered predeployed-account assertions at block_number. + + Batches eth_getBalance and eth_getTransactionCount queries. + Fetches code only for assertions with code_prefix (e.g., EIP-7702 + designation). Collects all failures before raising. + """ + deferred = self._deferred_account_assertions + self._deferred_account_assertions = [] + if not deferred: + return + + chunk, max_reported, verified = 2000, 20, 0 + for i in range(0, len(deferred), chunk): + batch = deferred[i : i + chunk] + + query = BaseAlloc(root={d.address: Account() for d in batch}) + accounts = self._eth_rpc.get_alloc( + query, block_number=block_number, skip_code=True + ).root + + code_targets = [ + d.address for d in batch if d.code_prefix is not None + ] + codes: dict[Address, Bytes] = dict( + zip( + code_targets, + self._eth_rpc.get_codes( + code_targets, block_number=block_number + ), + strict=True, + ) + ) + + errors: list[str] = [] + failed = 0 + for d in batch: + errs = _check_account_assertion( + d, accounts.get(d.address), codes.get(d.address) + ) + if errs: + failed += 1 + errors.extend(errs) + + if errors: + shown = errors[:max_reported] + omitted = len(errors) - len(shown) + suffix = f"\n ... and {omitted} more" if omitted else "" + raise DeployedAccountVerificationError( + f"{failed} predeployed benchmark target(s) failed " + f"verification at start_block (after checking " + f"{verified + len(batch)}):\n " + + "\n ".join(shown) + + suffix + ) + verified += len(batch) + + logger.info( + f"Verified {verified} predeployed benchmark target(s) at " + f"block {block_number}" + ) + def resolve_deferred_checks(self) -> None: """ Resolve all deferred on-chain checks using batched RPC calls. @@ -1156,6 +1321,9 @@ def pre( node_id=request.node.nodeid, address_stubs=address_stubs, funding_gas_limit=sender_fund_refund_gas_limit, + verify_full=getattr( + request.config.option, "verify_full_accounts", False + ), ) # Yield the pre-alloc for usage during the test diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/fill_stateful/fill_stateful.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/fill_stateful/fill_stateful.py index c98280b986f..e98f15a6573 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/fill_stateful/fill_stateful.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/fill_stateful/fill_stateful.py @@ -147,6 +147,17 @@ def pytest_addoption(parser: pytest.Parser) -> None: "opt-in." ), ) + group.addoption( + "--verify-full-accounts", + action="store_true", + dest="verify_full_accounts", + default=False, + help=( + "Verify all predeployed targets instead of sampling. " + "By default, only first and last accounts per range are checked. " + "This flag checks every account at start_block.)" + ), + ) def _resolve_session_fork( diff --git a/packages/testing/src/execution_testing/specs/blockchain.py b/packages/testing/src/execution_testing/specs/blockchain.py index ec3bd0d88e2..a96b02df608 100644 --- a/packages/testing/src/execution_testing/specs/blockchain.py +++ b/packages/testing/src/execution_testing/specs/blockchain.py @@ -1468,6 +1468,10 @@ def make_stateful_fixture( max_fee_per_blob_gas=max_fee_per_blob_gas, ) + self.pre.verify_deployed_accounts( + int(HexNumber(start_block["number"])) + ) + # Materialise queued pre-alloc txs into a synthetic setup block. blocks_to_process: List[Block] = [] if callable(pending_getter): diff --git a/packages/testing/src/execution_testing/test_types/account_types.py b/packages/testing/src/execution_testing/test_types/account_types.py index e5e03717f28..e9f498b6fe3 100644 --- a/packages/testing/src/execution_testing/test_types/account_types.py +++ b/packages/testing/src/execution_testing/test_types/account_types.py @@ -1,6 +1,7 @@ """Account-related types for Ethereum tests.""" import json +from collections.abc import Sequence from dataclasses import dataclass from enum import Enum, auto from typing import ( @@ -671,3 +672,27 @@ def nonexistent_account(self) -> Address: raise NotImplementedError( "nonexistent_account is not implemented in the base class" ) + + def expect_account_state( + self, + addresses: Address | Sequence[Address], + *, + is_existing_account: bool = True, + is_contract: bool = False, + min_balance: int | None = None, + code_prefix: bytes | None = None, + ) -> None: + """ + Register start-block expectation(s) for predeployed account(s). + + Accepts a single address or a range; labels ride on the addresses + themselves. Used only by fill-stateful; ignored by other + allocations. + """ + + def verify_deployed_accounts(self, block_number: int) -> None: + """ + Verify predeployed-account expectations at block_number. + + No-op unless fill-stateful allocation. + """ diff --git a/tests/benchmark/conftest.py b/tests/benchmark/conftest.py index 5a4643e2e38..616b4fdc692 100755 --- a/tests/benchmark/conftest.py +++ b/tests/benchmark/conftest.py @@ -1,5 +1,6 @@ """Pytest configuration for benchmark tests.""" +from collections.abc import Hashable from pathlib import Path from typing import Any @@ -82,3 +83,9 @@ def pytest_ignore_collect(collection_path: Path, config: Any) -> bool | None: def tx_gas_limit(fork: Fork, gas_benchmark_value: int) -> int: """Return the transaction gas limit cap.""" return fork.transaction_gas_limit_cap() or gas_benchmark_value + + +@pytest.fixture(scope="session") +def verified_accounts() -> dict[Hashable, int]: + """Session high-water-mark per target family, so each is verified once.""" + return {} diff --git a/tests/benchmark/helper/account_creator.py b/tests/benchmark/helper/account_creator.py index 2bb72d09cc6..464d04939f1 100644 --- a/tests/benchmark/helper/account_creator.py +++ b/tests/benchmark/helper/account_creator.py @@ -1,23 +1,40 @@ """Benchmark target accounts of various kinds for creation and location..""" from abc import ABC, abstractmethod +from collections.abc import Callable, Hashable from dataclasses import dataclass from enum import Enum, auto from typing import ClassVar, Self from execution_testing import ( DETERMINISTIC_FACTORY_ADDRESS, + Address, + Alloc, Bytecode, Create2PreimageLayout, Hash, Op, SequentialAddressLayout, + compute_create2_address, keccak256, ) from execution_testing.forks import Osaka +from tests.benchmark.helper.account_verification import ( + AccountExpectation, + register_target_range, +) + DEFAULT_CODE_SIZE = Osaka.max_code_size() +ADDRESS_MASK = (1 << 160) - 1 + +# Spamoor EOA creator starts created accounts at 0x1000 +# (https://github.com/CPerezz/spamoor/pull/12). +EXISTING_EOA_BASE = 0x1000 +# An address range that is never funded. +NON_EXISTING_BASE = keccak256(b"random") + class AccountMode(Enum): """Benchmark target account variant.""" @@ -345,12 +362,76 @@ def address_source(self, index_op: Bytecode) -> AddressSource: ) match self.mode: case AccountMode.EXISTING_EOA: - # Spamoor EOA creator starts created accounts at 0x1000. - # https://github.com/CPerezz/spamoor/pull/12 - base_addr = Hash(0x1000) + base_addr = Hash(EXISTING_EOA_BASE) case AccountMode.NON_EXISTING_ACCOUNT: - # An address range that is never funded. - base_addr = keccak256(b"random") + base_addr = NON_EXISTING_BASE case _: raise ValueError(f"{self.mode.name} has no address source") return SequentialAddressSource(base_addr=base_addr, index_op=index_op) + + def expected_account(self) -> AccountExpectation: + """Return the expected on-chain shape for this mode at start_block.""" + if self.derives_address_via_create2: + # CREATE2 address binds code; check presence only. + return AccountExpectation(is_contract=True) + match self.mode: + case AccountMode.EXISTING_EOA: + return AccountExpectation(min_balance=1) + case AccountMode.NON_EXISTING_ACCOUNT: + return AccountExpectation(is_existing_account=False) + case _: + raise ValueError(f"{self.mode.name} has no expected account") + + def target_address_of( + self, label: str | None = None + ) -> Callable[[int], Address]: + """ + Return an ``index -> target Address`` map mirroring address_source. + + CREATE2 initcode is assembled once (salt varies); ``label`` is + attached to every derived address. + """ + if self.derives_address_via_create2: + initcode = self.initcode + + def create2_address(index: int) -> Address: + return Address( + compute_create2_address( + address=DETERMINISTIC_FACTORY_ADDRESS, + salt=index, + initcode=initcode, + ), + label=label, + ) + + return create2_address + match self.mode: + case AccountMode.EXISTING_EOA: + base = EXISTING_EOA_BASE + case AccountMode.NON_EXISTING_ACCOUNT: + base = int.from_bytes(NON_EXISTING_BASE, "big") + case _: + raise ValueError(f"{self.mode.name} has no address source") + + def sequential_address(index: int) -> Address: + return Address((base + index) & ADDRESS_MASK, label=label) + + return sequential_address + + def register_targets( + self, + pre: Alloc, + count: int, + *, + verified_accounts: dict[Hashable, int], + label: str | None = None, + ) -> None: + """Register ``[0, count)`` of this mode's targets for verification.""" + register_target_range( + pre, + key=(self.mode, self.code_size), + count=count, + expectation=self.expected_account(), + address_of=self.target_address_of(label or self.mode.name), + verified_accounts=verified_accounts, + ) diff --git a/tests/benchmark/helper/account_sender_receiver.py b/tests/benchmark/helper/account_sender_receiver.py index 7a860607935..de75af8de49 100644 --- a/tests/benchmark/helper/account_sender_receiver.py +++ b/tests/benchmark/helper/account_sender_receiver.py @@ -1,17 +1,25 @@ """Deterministic benchmark sender and receiver accounts.""" import itertools +from collections.abc import Hashable from typing import Generator from execution_testing import ( DETERMINISTIC_FACTORY_ADDRESS, EOA, Address, + Alloc, compute_create2_address, compute_create_address, keccak256, ) +from tests.benchmark.helper.account_verification import ( + AccountExpectation, + register_target_range, +) +from tests.prague.eip7702_set_code_tx.spec import Spec + # Deterministic sender pool, pre-funded via system-contract withdrawals # (funding.txt) during payload generation. Kept out of the pre-allocation so # the accounts stay uncached. @@ -78,3 +86,54 @@ def yield_distinct_delegate_receiver() -> Generator[Address, None, None]: """Yield EOA delegating to a distinct EXISTING_CONTRACT_DIFF_MAX.""" for i in itertools.count(0): yield EOA(key=DELEGATE_BASE_KEY + i) + + +def expected_delegation() -> AccountExpectation: + """ + Expected shape of a 7702-delegated authority. + + Only asserts the account carries a delegation designator; the delegate + target it points to is not checked. + """ + return AccountExpectation(code_prefix=bytes(Spec.DELEGATION_DESIGNATION)) + + +def register_bittrex_targets( + pre: Alloc, + count: int, + *, + verified_accounts: dict[Hashable, int], +) -> None: + """Register the first *count* Bittrex CREATE contract receivers.""" + register_target_range( + pre, + key="bittrex_contract", + count=count, + expectation=AccountExpectation(is_contract=True), + address_of=lambda index: Address( + compute_create_address( + address=BITTREX_CONTROLLER_ADDRESS, nonce=2 + index + ), + label="diff_to_contract", + ), + verified_accounts=verified_accounts, + ) + + +def register_delegate_targets( + pre: Alloc, + count: int, + *, + verified_accounts: dict[Hashable, int], +) -> None: + """Register the first *count* delegated authorities (7702 designator).""" + register_target_range( + pre, + key="delegate_authority", + count=count, + expectation=expected_delegation(), + address_of=lambda index: Address( + EOA(key=DELEGATE_BASE_KEY + index), label="delegate_authority" + ), + verified_accounts=verified_accounts, + ) diff --git a/tests/benchmark/helper/account_verification.py b/tests/benchmark/helper/account_verification.py new file mode 100644 index 00000000000..0666b12b99a --- /dev/null +++ b/tests/benchmark/helper/account_verification.py @@ -0,0 +1,96 @@ +"""Verification of snapshot-predeployed benchmark target accounts.""" + +from collections.abc import Callable, Hashable, Sequence +from dataclasses import dataclass +from typing import overload + +from execution_testing import Address, Alloc + + +class AddressRange(Sequence[Address]): + """ + Lazily-indexed range of target addresses. + + Backed by an index -> Address map, so sampling the endpoints costs + O(1): checking only the first and last of a huge range never derives + the addresses in between. + """ + + def __init__( + self, start: int, stop: int, address_of: Callable[[int], Address] + ) -> None: + """Cover indices ``[start, stop)`` via ``address_of``.""" + self._start = start + self._stop = stop + self._address_of = address_of + + def __len__(self) -> int: + """Return the number of addresses in the range.""" + return self._stop - self._start + + @overload + def __getitem__(self, index: int) -> Address: ... + + @overload + def __getitem__(self, index: slice) -> Sequence[Address]: ... + + def __getitem__(self, index: int | slice) -> Address | Sequence[Address]: + """Derive the address at ``index`` (negatives and slices allowed).""" + if isinstance(index, slice): + return [self[i] for i in range(*index.indices(len(self)))] + if index < 0: + index += len(self) + if not 0 <= index < len(self): + raise IndexError(index) + return self._address_of(self._start + index) + + +@dataclass(frozen=True) +class AccountExpectation: + """ + Expected on-chain shape of a snapshot-predeployed target. + + Verified at `start_block`. Defaults skipped. + `is_contract`: nonce >= 1. CREATE2: address binds code. + `code_prefix`: on-chain code must start with given bytes. + """ + + is_existing_account: bool = True + is_contract: bool = False + min_balance: int | None = None + code_prefix: bytes | None = None + + def register( + self, pre: Alloc, addresses: Address | Sequence[Address] + ) -> None: + """Register this expectation for one address or a range.""" + pre.expect_account_state( + addresses, + is_existing_account=self.is_existing_account, + is_contract=self.is_contract, + min_balance=self.min_balance, + code_prefix=self.code_prefix, + ) + + +def register_target_range( + pre: Alloc, + *, + key: Hashable, + count: int, + expectation: AccountExpectation, + address_of: Callable[[int], Address], + verified_accounts: dict[Hashable, int], +) -> None: + """ + Register targets ``[0, count)`` for verification, deduped per family. + + Only the newly-seen tail ``[high-water, count)`` is handed to the + allocation; whether it samples the endpoints or checks every account + is decided there, from ``--verify-full-accounts``. + """ + start = verified_accounts.get(key, 0) + if count <= start: + return + expectation.register(pre, AddressRange(start, count, address_of)) + verified_accounts[key] = count diff --git a/tests/benchmark/stateful/bloatnet/test_account_query.py b/tests/benchmark/stateful/bloatnet/test_account_query.py index fd44cabce5d..bec412631d8 100644 --- a/tests/benchmark/stateful/bloatnet/test_account_query.py +++ b/tests/benchmark/stateful/bloatnet/test_account_query.py @@ -162,6 +162,7 @@ def test_account_access( account_mode: AccountMode, overhead_baseline: bool, cache_strategy: CacheStrategy, + verified_accounts: dict, ) -> None: """Benchmark account access with caching strategies.""" account_creator = AccountCreator(account_mode) @@ -313,6 +314,17 @@ def calldata(iteration_count: int, start_iteration: int) -> bytes: ) ) + if not overhead_baseline and attack_txs: + count = 1 + max( + int.from_bytes(bytes(tx.data)[32:64], "big") for tx in attack_txs + ) + account_creator.register_targets( + pre, + count, + verified_accounts=verified_accounts, + label=account_mode.name, + ) + if cache_strategy == CacheStrategy.CACHE_PREVIOUS_BLOCK: with TestPhaseManager.setup(): cache_sender = pre.fund_eoa() diff --git a/tests/benchmark/stateful/bloatnet/test_transaction_types.py b/tests/benchmark/stateful/bloatnet/test_transaction_types.py index 8094b0869a7..77f08358e04 100644 --- a/tests/benchmark/stateful/bloatnet/test_transaction_types.py +++ b/tests/benchmark/stateful/bloatnet/test_transaction_types.py @@ -1,6 +1,7 @@ """Benchmark ether transfers to receivers that exist on-chain.""" -from typing import Generator +from functools import partial +from typing import Callable, Generator import pytest from execution_testing import ( @@ -19,6 +20,8 @@ AccountMode, ) from tests.benchmark.helper.account_sender_receiver import ( + register_bittrex_targets, + register_delegate_targets, yield_distinct_contract_receiver, yield_distinct_create2_receiver, yield_distinct_delegate_receiver, @@ -51,12 +54,15 @@ def test_ether_transfers_onchain_receivers( transfer_amount: int, fork: Fork, gas_benchmark_value: int, + verified_accounts: dict, ) -> None: """Benchmark ether transfers across different receiver account types.""" senders = yield_distinct_sender() receiver_execution_gas = 0 recipient_type = RecipientType.CONTRACT receivers: Generator[Address, None, None] + + register_targets: Callable[[int], None] | None = None match case_id: case "diff_to_self": receivers = senders @@ -64,9 +70,23 @@ def test_ether_transfers_onchain_receivers( case "diff_to_nonexistent": receivers = yield_distinct_nonexistent_receiver() recipient_type = RecipientType.EMPTY_ACCOUNT + creator = AccountCreator(AccountMode.NON_EXISTING_ACCOUNT) + register_targets = partial( + creator.register_targets, + pre, + verified_accounts=verified_accounts, + label=case_id, + ) case "diff_to_existent": receivers = yield_distinct_existent_receiver() recipient_type = RecipientType.EOA + creator = AccountCreator(AccountMode.EXISTING_EOA) + register_targets = partial( + creator.register_targets, + pre, + verified_accounts=verified_accounts, + label=case_id, + ) case "diff_to_contract": receivers = yield_distinct_contract_receiver() # Runtime code is the same across all the receivers @@ -79,25 +99,58 @@ def test_ether_transfers_onchain_receivers( + Op.JUMPDEST ) receiver_execution_gas = executed_code.gas_cost(fork) + # Bittrex CREATE contracts: address does not bind code, so only + # presence (nonce>=1) is checked. + register_targets = partial( + register_bittrex_targets, + pre, + verified_accounts=verified_accounts, + ) case "diff_to_unique_code_jumpdest_contract": creator = AccountCreator(AccountMode.EXISTING_CONTRACT_JUMPDEST) receivers = yield_distinct_create2_receiver(creator.initcode) receiver_execution_gas = creator.execution_code.gas_cost(fork) + register_targets = partial( + creator.register_targets, + pre, + verified_accounts=verified_accounts, + label=case_id, + ) case "diff_to_contract_minimal": - receivers = yield_distinct_create2_receiver( - AccountCreator(AccountMode.EXISTING_CONTRACT_MINIMAL).initcode + creator = AccountCreator(AccountMode.EXISTING_CONTRACT_MINIMAL) + receivers = yield_distinct_create2_receiver(creator.initcode) + register_targets = partial( + creator.register_targets, + pre, + verified_accounts=verified_accounts, + label=case_id, ) case "diff_to_contract_same_max": - receivers = yield_distinct_create2_receiver( - AccountCreator(AccountMode.EXISTING_CONTRACT_SAME_MAX).initcode + creator = AccountCreator(AccountMode.EXISTING_CONTRACT_SAME_MAX) + receivers = yield_distinct_create2_receiver(creator.initcode) + register_targets = partial( + creator.register_targets, + pre, + verified_accounts=verified_accounts, + label=case_id, ) case "diff_to_contract_diff_max": - receivers = yield_distinct_create2_receiver( - AccountCreator(AccountMode.EXISTING_CONTRACT_DIFF_MAX).initcode + creator = AccountCreator(AccountMode.EXISTING_CONTRACT_DIFF_MAX) + receivers = yield_distinct_create2_receiver(creator.initcode) + register_targets = partial( + creator.register_targets, + pre, + verified_accounts=verified_accounts, + label=case_id, ) case "diff_to_delegated_contract_diff": receivers = yield_distinct_delegate_receiver() recipient_type = RecipientType.DELEGATION_7702 + register_targets = partial( + register_delegate_targets, + pre, + verified_accounts=verified_accounts, + ) case _: raise ValueError(f"Unknown case: {case_id}") @@ -122,15 +175,19 @@ def test_ether_transfers_onchain_receivers( txs = [] for _ in range(iteration_count): sender = next(senders) + to = sender if case_id == "diff_to_self" else next(receivers) txs.append( Transaction( - to=sender if case_id == "diff_to_self" else next(receivers), + to=to, value=transfer_amount, gas_limit=iteration_cost, sender=sender, ) ) + if register_targets is not None: + register_targets(iteration_count) + benchmark_test( pre=pre, post={}, From d3a58bbcd7443755763a6199554b35862f07a94e Mon Sep 17 00:00:00 2001 From: spencer <spencer.taylor-brown@ethereum.org> Date: Thu, 30 Jul 2026 11:45:35 +0200 Subject: [PATCH 176/233] refactor(spec-specs,tests): rename REGULAR_PER_AUTH_BASE_COST to EXECUTION_PER_AUTH_BASE_COST (#3263) --- .../forks/forks/eips/amsterdam/eip_2780.py | 2 +- .../forks/forks/eips/amsterdam/eip_8038.py | 2 +- .../testing/src/execution_testing/forks/gas_costs.py | 2 +- src/ethereum/forks/amsterdam/transactions.py | 4 ++-- src/ethereum/forks/amsterdam/vm/eoa_delegation.py | 2 +- src/ethereum/forks/amsterdam/vm/gas.py | 2 +- .../test_intrinsic_gas_boundary.py | 2 +- .../test_value_moving_with_tx_delegation.py | 2 +- .../test_additional_coverage.py | 8 ++++---- .../eip8037_state_creation_gas_cost_increase/spec.py | 2 +- .../test_state_gas_set_code.py | 10 +++++----- .../test_fork_transition.py | 4 ++-- .../test_set_code_auth_gas.py | 2 +- .../test_set_code_auth_refunds.py | 2 +- 14 files changed, 23 insertions(+), 23 deletions(-) diff --git a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_2780.py b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_2780.py index 13342b15abd..14e6431c9a1 100644 --- a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_2780.py +++ b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_2780.py @@ -129,7 +129,7 @@ def fn( return_cost_deducted_prior_execution=True, ) intrinsic_cost += ( - authorization_count * gas_costs.REGULAR_PER_AUTH_BASE_COST + authorization_count * gas_costs.EXECUTION_PER_AUTH_BASE_COST ) is_self_transfer = recipient_type == RecipientType.SELF diff --git a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8038.py b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8038.py index 26beca8e120..583ca719662 100644 --- a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8038.py +++ b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8038.py @@ -75,7 +75,7 @@ def gas_costs(cls) -> GasCosts: TX_CREATE=create_access, AUTH_PER_EMPTY_ACCOUNT=account_write + execution_per_auth_base_cost, - REGULAR_PER_AUTH_BASE_COST=execution_per_auth_base_cost, + EXECUTION_PER_AUTH_BASE_COST=execution_per_auth_base_cost, ) @classmethod diff --git a/packages/testing/src/execution_testing/forks/gas_costs.py b/packages/testing/src/execution_testing/forks/gas_costs.py index 261540cd590..883cfd8953b 100644 --- a/packages/testing/src/execution_testing/forks/gas_costs.py +++ b/packages/testing/src/execution_testing/forks/gas_costs.py @@ -51,7 +51,7 @@ class GasCosts: AUTH_BASE: int = 0 # State-independent execution gas charged per EIP-7702 authorization # tuple; 0 before the state-access repricing introduces it. - REGULAR_PER_AUTH_BASE_COST: int = 0 + EXECUTION_PER_AUTH_BASE_COST: int = 0 # Utility MEMORY_PER_WORD: int diff --git a/src/ethereum/forks/amsterdam/transactions.py b/src/ethereum/forks/amsterdam/transactions.py index d83754306db..e1293c56feb 100644 --- a/src/ethereum/forks/amsterdam/transactions.py +++ b/src/ethereum/forks/amsterdam/transactions.py @@ -644,7 +644,7 @@ def calculate_intrinsic_cost( 4. Calldata cost (zero and non-zero bytes). 5. Access list entries (if applicable). 6. Authorizations (if applicable): only the state-independent base - cost (`REGULAR_PER_AUTH_BASE_COST`) per tuple. The + cost (`EXECUTION_PER_AUTH_BASE_COST`) per tuple. The state-dependent account-creation and delegation-write costs are charged at the top frame by `set_delegation`. @@ -694,7 +694,7 @@ def calculate_intrinsic_cost( auth_cost = Uint(0) if isinstance(tx, SetCodeTransaction): - auth_cost = GasCosts.REGULAR_PER_AUTH_BASE_COST * ulen( + auth_cost = GasCosts.EXECUTION_PER_AUTH_BASE_COST * ulen( tx.authorizations ) diff --git a/src/ethereum/forks/amsterdam/vm/eoa_delegation.py b/src/ethereum/forks/amsterdam/vm/eoa_delegation.py index 28639cb7544..83cc8e080f3 100644 --- a/src/ethereum/forks/amsterdam/vm/eoa_delegation.py +++ b/src/ethereum/forks/amsterdam/vm/eoa_delegation.py @@ -201,7 +201,7 @@ def set_delegation(evm: Evm) -> None: costs at the top frame. Each valid authorization is charged, on top of the - state-independent ``GasCosts.REGULAR_PER_AUTH_BASE_COST`` already + state-independent ``GasCosts.EXECUTION_PER_AUTH_BASE_COST`` already paid in the intrinsic cost: - ``StateGasCosts.NEW_ACCOUNT`` (state) when the authority's diff --git a/src/ethereum/forks/amsterdam/vm/gas.py b/src/ethereum/forks/amsterdam/vm/gas.py index 358418b7545..5ff707b18c7 100644 --- a/src/ethereum/forks/amsterdam/vm/gas.py +++ b/src/ethereum/forks/amsterdam/vm/gas.py @@ -145,7 +145,7 @@ class GasCosts: # Authorization AUTH_TUPLE_BYTES: Final[Uint] = Uint(101) - REGULAR_PER_AUTH_BASE_COST: Final[Uint] = ( + EXECUTION_PER_AUTH_BASE_COST: Final[Uint] = ( AUTH_TUPLE_BYTES * TX_DATA_TOKEN_FLOOR + PRECOMPILE_ECRECOVER + COLD_ACCOUNT_ACCESS diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_intrinsic_gas_boundary.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_intrinsic_gas_boundary.py index 6ecaf65362a..d8bad2b8ea1 100644 --- a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_intrinsic_gas_boundary.py +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_intrinsic_gas_boundary.py @@ -136,7 +136,7 @@ def test_intrinsic_gas_floor_boundary_with_authorizations( ) -> None: """ Reject a type-4 transaction when ``gas_limit = intrinsic_gas - 1``, - where the intrinsic includes ``REGULAR_PER_AUTH_BASE_COST`` per + where the intrinsic includes ``EXECUTION_PER_AUTH_BASE_COST`` per authorization. EIP-2780 keeps only the state-independent per-authorization base diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_value_moving_with_tx_delegation.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_value_moving_with_tx_delegation.py index 71b3d790b6a..0c0eb333090 100644 --- a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_value_moving_with_tx_delegation.py +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_value_moving_with_tx_delegation.py @@ -4,7 +4,7 @@ A type-4 transaction's authorizations are processed at the top frame (in ``set_delegation``), where their state-dependent costs are charged. Each authorization pays, on top of the state-independent -``REGULAR_PER_AUTH_BASE_COST`` charged in the intrinsic: +``EXECUTION_PER_AUTH_BASE_COST`` charged in the intrinsic: - ``NEW_ACCOUNT`` (state) + ``ACCOUNT_WRITE`` (execution) when the authority's account leaf does not yet exist, and diff --git a/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_additional_coverage.py b/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_additional_coverage.py index 7392728dc57..eda2d3d7880 100644 --- a/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_additional_coverage.py +++ b/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_additional_coverage.py @@ -701,12 +701,12 @@ def test_authorization_list_intrinsic_gas( """ Verify the authorization-list intrinsic cost under EIP-2780. - Each authorization adds exactly ``REGULAR_PER_AUTH_BASE_COST`` to + Each authorization adds exactly ``EXECUTION_PER_AUTH_BASE_COST`` to the (execution) intrinsic; the state-dependent authorization costs moved to the top frame. Measured on the *raw* intrinsic (before the EIP-7623 calldata floor is applied) the per-authorization delta is exactly ``num_authorizations * - REGULAR_PER_AUTH_BASE_COST`` -- even when the floor would + EXECUTION_PER_AUTH_BASE_COST`` -- even when the floor would otherwise mask it (e.g. a single authorization whose base cost stays below the floor). Each existing authority then pays the first-write ``ACCOUNT_WRITE`` (execution) and ``AUTH_BASE`` @@ -752,9 +752,9 @@ def test_authorization_list_intrinsic_gas( actual_auth_cost = intrinsic_with_auth - intrinsic_without_auth assert actual_auth_cost == ( - num_authorizations * gas_costs.REGULAR_PER_AUTH_BASE_COST + num_authorizations * gas_costs.EXECUTION_PER_AUTH_BASE_COST ), ( - "auth intrinsic must be n * REGULAR_PER_AUTH_BASE_COST, got: " + "auth intrinsic must be n * EXECUTION_PER_AUTH_BASE_COST, got: " f"{actual_auth_cost}" ) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/spec.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/spec.py index ced4110a55e..0477ea2159a 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/spec.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/spec.py @@ -51,6 +51,6 @@ class Spec: # EIP-8038 then repriced them. EXECUTION_GAS_CREATE = 11000 # Total execution intrinsic per EIP-7702 authorization: - # ACCOUNT_WRITE (8000) + REGULAR_PER_AUTH_BASE_COST (7816). + # ACCOUNT_WRITE (8000) + EXECUTION_PER_AUTH_BASE_COST (7816). PER_AUTH_BASE_COST = 15816 GAS_COLD_STORAGE_WRITE = 13000 diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py index 8a84ec8571b..6ce3b83b6af 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py @@ -3,7 +3,7 @@ top-frame charge model. Under EIP-2780 (Amsterdam) an authorization's intrinsic cost is only the -state-independent ``REGULAR_PER_AUTH_BASE_COST``; there is no intrinsic +state-independent ``EXECUTION_PER_AUTH_BASE_COST``; there is no intrinsic auth state gas and there are no auth refunds. The state-dependent costs are charged lazily at the top frame in ``set_delegation``, keyed on each authority's pre-transaction state: @@ -615,7 +615,7 @@ def test_invalid_nonce_auth_still_charges_intrinsic( An authorization with a wrong nonce is skipped during ``set_delegation``, so it writes no delegation indicator and incurs no top-frame charge. Its state-independent - ``REGULAR_PER_AUTH_BASE_COST`` is still charged in the intrinsic, and + ``EXECUTION_PER_AUTH_BASE_COST`` is still charged in the intrinsic, and the authority is left untouched. """ contract = pre.deploy_contract(code=Op.STOP) @@ -670,7 +670,7 @@ def test_invalid_chain_id_auth_still_charges_intrinsic( An authorization with a mismatched chain ID is skipped during ``set_delegation`` and incurs no top-frame charge, but its - ``REGULAR_PER_AUTH_BASE_COST`` is still charged in the intrinsic and + ``EXECUTION_PER_AUTH_BASE_COST`` is still charged in the intrinsic and the authority is left untouched. """ contract = pre.deploy_contract(code=Op.STOP) @@ -946,7 +946,7 @@ def test_mixed_valid_and_invalid_auths( Test mixed valid and invalid authorizations under the top-frame model. Every tuple (valid or invalid) pays the intrinsic - ``REGULAR_PER_AUTH_BASE_COST``. Only the valid authorizations reach + ``EXECUTION_PER_AUTH_BASE_COST``. Only the valid authorizations reach ``set_delegation`` and each writes a net-new delegation on an existing authority, paying the first-write ``ACCOUNT_WRITE`` and the top-frame ``AUTH_BASE``; the invalid (wrong nonce) tuples are skipped and pay @@ -1923,7 +1923,7 @@ def test_invalid_auth_no_top_frame_charge( neither ``NEW_ACCOUNT`` / ``ACCOUNT_WRITE`` nor ``AUTH_BASE`` at the top frame (and, unlike the superseded EIP-8037 model, nothing is refilled because nothing was charged). Only the intrinsic - ``REGULAR_PER_AUTH_BASE_COST`` is paid and the authority is never + ``EXECUTION_PER_AUTH_BASE_COST`` is paid and the authority is never created. Swept over the reasons an authorization is rejected. """ target = pre.deploy_contract(code=Op.STOP) diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_fork_transition.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_fork_transition.py index 08f290fba1e..710fb6634da 100644 --- a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_fork_transition.py +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_fork_transition.py @@ -424,8 +424,8 @@ def test_auth_intrinsic_at_transition( The ``7702`` authorization intrinsic *falls* across the boundary. EIP-2780 moves the state-dependent authorization costs (account creation and the delegation-write base) out of the intrinsic and into - the top frame, leaving only the execution ``REGULAR_PER_AUTH_BASE_COST`` - in the intrinsic. The post-fork single-authorization intrinsic is + the top frame, leaving only ``EXECUTION_PER_AUTH_BASE_COST`` in the + intrinsic. The post-fork single-authorization intrinsic is therefore strictly smaller than the pre-fork one, so a tx whose ``gas_limit`` equals the (lower) post-fork intrinsic is rejected with ``INTRINSIC_GAS_TOO_LOW`` before the fork but valid after. diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_set_code_auth_gas.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_set_code_auth_gas.py index 72a0cbae289..fc7b3a6ab9f 100644 --- a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_set_code_auth_gas.py +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_set_code_auth_gas.py @@ -332,7 +332,7 @@ def test_mixed_validity_multi_auth_receipt_gas( Pin the exact receipt gas of a transaction carrying one valid and one invalid authorization under the EIP-2780 top-frame charge model. - Both tuples pay the state-independent ``REGULAR_PER_AUTH_BASE_COST`` + Both tuples pay the state-independent ``EXECUTION_PER_AUTH_BASE_COST`` in the intrinsic. The single valid authorization's authority leaf already exists (a funded EOA) and gains a net-new delegation indicator, so at the top frame it pays the first-write diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_set_code_auth_refunds.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_set_code_auth_refunds.py index d72e03aca97..8496379b96f 100644 --- a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_set_code_auth_refunds.py +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_set_code_auth_refunds.py @@ -17,7 +17,7 @@ reduced, refund-free charge via the exact receipt gas: * a non-clearing delegation on an existing empty-code leaf pays the - intrinsic ``REGULAR_PER_AUTH_BASE_COST`` plus the top-frame + intrinsic ``EXECUTION_PER_AUTH_BASE_COST`` plus the top-frame ``ACCOUNT_WRITE`` (first leaf write) and ``AUTH_BASE`` (the net-new delegation indicator); and * a *clearing* re-authorization of an existing-delegation authority From 6fe02904f959b72658d8c83e0c653ede14966dba Mon Sep 17 00:00:00 2001 From: Aliaksei Osipau <me@flcl.me> Date: Thu, 30 Jul 2026 13:50:50 +0300 Subject: [PATCH 177/233] feat(tests): cover noncanonical deposit ABI offsets in eip6110 (#3240) --- .../test_modified_contract.py | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/tests/prague/eip6110_deposits/test_modified_contract.py b/tests/prague/eip6110_deposits/test_modified_contract.py index 9d577706dd5..e00739986f1 100644 --- a/tests/prague/eip6110_deposits/test_modified_contract.py +++ b/tests/prague/eip6110_deposits/test_modified_contract.py @@ -232,6 +232,109 @@ def test_invalid_layout( ) +@pytest.mark.exception_test +@pytest.mark.eels_base_coverage +def test_invalid_layout_with_swapped_decodable_offsets( + blockchain_test: BlockchainTestFiller, pre: Alloc +) -> None: + """ + Test a deposit log whose ABI offsets are noncanonical but still decodable. + """ + changed_log = create_deposit_log_bytes_with_swapped_amount_and_signature() + + bytecode = Om.MSTORE(changed_log) + Op.LOG1( + 0, + len(changed_log), + Spec.DEPOSIT_EVENT_SIGNATURE_HASH, + ) + bytecode += Op.STOP + + pre[Spec.DEPOSIT_CONTRACT_ADDRESS] = Account( + code=bytecode, + nonce=1, + balance=0, + ) + sender = pre.fund_eoa() + + tx = Transaction( + to=Spec.DEPOSIT_CONTRACT_ADDRESS, + sender=sender, + gas_limit=100_000, + ) + + blockchain_test( + pre=pre, + blocks=[ + Block( + txs=[tx], + exception=[ + BlockException.INVALID_DEPOSIT_EVENT_LAYOUT, + ], + ), + ], + post={}, + ) + + +def create_deposit_log_bytes_with_swapped_amount_and_signature() -> bytes: + """ + Create deposit log bytes with amount and signature dynamic slots swapped. + """ + result = bytearray(576) + + write_uint256(result, 0, 160) + write_uint256(result, 32, 256) + write_uint256(result, 64, 448) + write_uint256(result, 96, 320) + write_uint256(result, 128, 512) + + write_bytes_field( + result, + 160, + 48, + DEFAULT_DEPOSIT_REQUEST_LOG_DATA_DICT["pubkey_data"], + ) + write_bytes_field( + result, + 256, + 32, + DEFAULT_DEPOSIT_REQUEST_LOG_DATA_DICT["withdrawal_credentials_data"], + ) + write_bytes_field( + result, + 320, + 96, + DEFAULT_DEPOSIT_REQUEST_LOG_DATA_DICT["signature_data"], + ) + write_bytes_field( + result, + 448, + 8, + DEFAULT_DEPOSIT_REQUEST_LOG_DATA_DICT["amount_data"], + ) + write_bytes_field( + result, + 512, + 8, + DEFAULT_DEPOSIT_REQUEST_LOG_DATA_DICT["index_data"], + ) + + return bytes(result) + + +def write_uint256(data: bytearray, offset: int, value: int) -> None: + """Write an ABI uint256 word.""" + data[offset : offset + 32] = value.to_bytes(32, byteorder="big") + + +def write_bytes_field( + data: bytearray, offset: int, size: int, value: bytes +) -> None: + """Write an ABI dynamic bytes field at its data offset.""" + write_uint256(data, offset, size) + data[offset + 32 : offset + 32 + len(value)] = value + + @pytest.mark.parametrize("slice_bytes", [True, False]) @pytest.mark.exception_test @pytest.mark.eels_base_coverage From 05ec9375ae321da6f003aa9fbd7e9c3a3ee6c55d Mon Sep 17 00:00:00 2001 From: Aliaksei Osipau <me@flcl.me> Date: Thu, 30 Jul 2026 13:56:45 +0300 Subject: [PATCH 178/233] feat(tests): pin flat 2D inclusion gate semantics (no intrinsic subtraction) (#3245) Co-authored-by: LouisTsai <q1030176@gmail.com> Co-authored-by: spencer-tb <spencer.tb@ethereum.org> --- .../test_block_2d_gas_accounting.py | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py index c1d358afc6a..5726cd0d58c 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py @@ -29,6 +29,7 @@ TransactionException, TransactionReceipt, add_kzg_version, + compute_create_address, ) from ...cancun.eip4844_blobs.spec import Spec as EIP4844_Spec @@ -1008,3 +1009,112 @@ def test_cumulative_block_state_gas_boundary( ], post=post, ) + + +@pytest.mark.parametrize( + "over_by", + [ + pytest.param(0, id="exact_fit"), + pytest.param(1, id="one_above", marks=pytest.mark.exception_test), + pytest.param( + None, + id="state_charge_above", + marks=pytest.mark.exception_test, + ), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_block_2d_inclusion_execution_gate_full_gas_reservation( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, + over_by: int | None, +) -> None: + """ + Pin the flat execution-gas inclusion gate on a contract creation. + + EIP-8037 measures ``min(TX_MAX_GAS_LIMIT, tx.gas)`` against + ``execution_gas_available`` in full, with no credit for the + ``NEW_ACCOUNT`` state gas the creation charges at its top frame. + Filler STOP txs spend execution gas only, so the state budget + stays full and only the execution gate can reject the creation. + + Its ``gas_limit`` is the leftover execution budget plus + ``over_by``, where ``None`` means the state charge itself. A + client crediting that charge would accept ``over_by=1`` with + slack and ``over_by=None`` exactly under strict ``>``; the flat + gate rejects both. ``over_by=0`` is the control, valid either way. + """ + init_code = bytes(Op.STOP) + intrinsic_calc = fork.transaction_intrinsic_cost_calculator() + intrinsic_gas = intrinsic_calc() + + create_execution = intrinsic_calc( + calldata=init_code, + contract_creation=True, + ) + create_state_charge = fork.transaction_top_frame_state_gas( + contract_creation=True + ) + + gas_limit_cap = fork.transaction_gas_limit_cap() + assert gas_limit_cap is not None + + delta = create_state_charge if over_by is None else over_by + + # The CREATE tx draws its state charge from gas_left, so it needs + # both dimensions' gas within the leftover execution budget. + create_gas = create_execution + create_state_charge + num_fillers = create_state_charge // intrinsic_gas + 1 + filler_execution = num_fillers * intrinsic_gas + block_gas_limit = max( + filler_execution + create_gas, + fork.minimum_block_gas_limit(), + ) + execution_available = block_gas_limit - filler_execution + tx_gas_limit = execution_available + delta + + assert execution_available >= create_gas + assert tx_gas_limit <= gas_limit_cap and tx_gas_limit <= block_gas_limit + + assert tx_gas_limit - create_state_charge <= execution_available + assert (tx_gas_limit > execution_available) == (delta > 0) + + error = TransactionException.GAS_ALLOWANCE_EXCEEDED if delta else None + create_sender = pre.fund_eoa() + create_tx = Transaction( + to=None, + data=init_code, + gas_limit=tx_gas_limit, + sender=create_sender, + error=error, + ) + + post: dict = {} + header_verify: Header | None = None + if not delta: + post = { + compute_create_address(address=create_sender, nonce=0): Account( + nonce=1 + ), + } + header_verify = Header( + gas_used=max( + filler_execution + create_execution, + create_state_charge, + ), + ) + + blockchain_test( + genesis_environment=Environment(gas_limit=block_gas_limit), + pre=pre, + blocks=[ + Block( + txs=stop_txs(pre, fork, num_fillers) + [create_tx], + gas_limit=block_gas_limit, + exception=error, + header_verify=header_verify, + ) + ], + post=post, + ) From 946f45c779400e1891f62a072a405abd9d5474cb Mon Sep 17 00:00:00 2001 From: danceratopz <danceratopz@gmail.com> Date: Thu, 30 Jul 2026 13:50:15 +0200 Subject: [PATCH 179/233] feat(tests): EIP-7778 admission gate uses pre-refund gas (#2932) Co-authored-by: spencer-tb <spencer.tb@ethereum.org> --- .../test_gas_accounting.py | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/tests/amsterdam/eip7778_block_gas_accounting_without_refunds/test_gas_accounting.py b/tests/amsterdam/eip7778_block_gas_accounting_without_refunds/test_gas_accounting.py index 21c1c67cbca..38bf9296958 100644 --- a/tests/amsterdam/eip7778_block_gas_accounting_without_refunds/test_gas_accounting.py +++ b/tests/amsterdam/eip7778_block_gas_accounting_without_refunds/test_gas_accounting.py @@ -530,6 +530,99 @@ def test_varying_calldata_costs( ) +@pytest.mark.parametrize( + "refund_tx_reverts", + [ + pytest.param(True, id="refund_tx_reverts"), + pytest.param(False, id=""), + ], +) +@pytest.mark.with_all_refund_types() +@pytest.mark.filter_combinations( + lambda refund_type, refund_tx_reverts, **_: not ( + refund_type == RefundTypes.STORAGE_CLEAR and refund_tx_reverts + ), + reason=( + "STORAGE_CLEAR refund is zero on revert, so post_refund == " + "pre_refund and the admission bypass cannot manifest" + ), +) +@pytest.mark.exception_test +@pytest.mark.execute(pytest.mark.skip(reason="Requires specific gas price")) +@pytest.mark.valid_from("EIP7778") +def test_extra_tx_admission_uses_pre_refund_gas( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, + refund_type: RefundTypes, + refund_tx_reverts: bool, +) -> None: + """ + Test that the admission gate uses the pre-refund accumulator when + the trailing tx's gas_limit exceeds its actual usage. + + Without this slack a post-refund gate is masked: the block is + still rejected by the gas_used > gas_limit check. With it, a buggy + implementation admits the extra tx yet stays within the block gas + limit, diverging from the expected-invalid fixture. + """ + intrinsic_cost_calc = fork.transaction_intrinsic_cost_calculator() + + refunds_count = 10 + stop_address = pre.deterministic_deploy_contract(deploy_code=Op.STOP) + + post = Alloc() + ( + gas_used_post_refund, + gas_used_pre_refund, + _, + call_data_floor_cost, + refund_tx, + ) = build_refund_tx( + fork=fork, + pre=pre, + post=post, + refund_types={refund_type}, + refunds_count=refunds_count, + refund_tx_reverts=refund_tx_reverts, + exceed_block_gas_limit=True, + ) + + assert gas_used_pre_refund > gas_used_post_refund, ( + "Parametrization must produce a refund; without one the admission " + "bypass cannot occur" + ) + + refund_tx_block_gas_used = max(gas_used_pre_refund, call_data_floor_cost) + + extra_tx_sender = pre.fund_eoa() + extra_tx_intrinsic = intrinsic_cost_calc(calldata=b"") + + # Slack so a buggy admit stays within the block gas limit. + extra_tx_gas_limit = 2 * extra_tx_intrinsic + extra_tx = Transaction( + to=stop_address, + gas_limit=extra_tx_gas_limit, + sender=extra_tx_sender, + error=TransactionException.GAS_ALLOWANCE_EXCEEDED, + ) + + environment_gas_limit = refund_tx_block_gas_used + extra_tx_gas_limit - 1 + + blockchain_test( + pre=pre, + blocks=[ + Block( + txs=[refund_tx, extra_tx], + exception=BlockException.GAS_USED_OVERFLOW, + gas_limit=environment_gas_limit, + ) + ], + post=post, + genesis_environment=Environment(gas_limit=environment_gas_limit), + ) + + @pytest.mark.parametrize( "refund_tx_reverts", [ From 2c4a5911f514a292f0234fbd11e2ec9f96513677 Mon Sep 17 00:00:00 2001 From: Novikov Kirill <75641500+knQzx@users.noreply.github.com> Date: Thu, 30 Jul 2026 14:26:42 +0200 Subject: [PATCH 180/233] chore(ci): deduplicate temp-dir creation across Justfile recipes (#3191) Co-authored-by: spencer-tb <spencer.tb@ethereum.org> --- Justfile | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/Justfile b/Justfile index 377a4520762..2efd7b3614d 100644 --- a/Justfile +++ b/Justfile @@ -21,6 +21,18 @@ latest_fork := "Amsterdam" # Use the faster sys.monitoring coverage core (default on 3.14, opt-in below). export COVERAGE_CORE := "sysmon" +# --- Helpers --- + +# Create a recipe's --basetemp scratch directory +[private] +_tmp name: + @mkdir -p "{{ output_dir }}/{{ name }}/tmp" + +# Create a recipe's --basetemp and --log-to directories +[private] +_tmp-logs name: (_tmp name) + @mkdir -p "{{ output_dir }}/{{ name }}/logs" + # --- Static Analysis --- # Auto-fix formatting and lint issues @@ -111,8 +123,7 @@ checklist *args: # Fill the consensus tests using EELS (with Python) [group('consensus tests')] -fill *args: - @mkdir -p "{{ output_dir }}/fill/tmp" "{{ output_dir }}/fill/logs" +fill *args: (_tmp-logs "fill") uv run fill \ -m "not slow" \ -n {{ xdist_workers }} --dist=loadgroup \ @@ -148,8 +159,7 @@ fill-release *args: # Fill the base coverage consensus tests using EELS with PyPy [group('integration tests')] -fill-pypy *args: - @mkdir -p "{{ output_dir }}/fill-pypy/tmp" "{{ output_dir }}/fill-pypy/logs" +fill-pypy *args: (_tmp-logs "fill-pypy") uv run --python pypy3.11 --no-dev --group test fill \ --skip-index \ --output="{{ output_dir }}/fill-pypy/fixtures" \ @@ -171,8 +181,7 @@ fill-pypy *args: # Fill the base coverage consensus tests and run EELS against the fixtures [group('integration tests')] -json-loader *args: - @mkdir -p "{{ output_dir }}/json-loader/tmp" +json-loader *args: (_tmp "json-loader") uv run fill \ -m "eels_base_coverage and not derived_test" \ --until "{{ latest_fork }}" \ @@ -202,8 +211,7 @@ json-loader *args: # Run the spec-tools tests (lint and new-fork tooling) [group('integration tests')] -spec-tools *args: - @mkdir -p "{{ output_dir }}/spec-tools/tmp" +spec-tools *args: (_tmp "spec-tools") uv run pytest \ -n {{ xdist_workers }} \ --basetemp="{{ output_dir }}/spec-tools/tmp" \ @@ -215,8 +223,7 @@ spec-tools *args: # Run the testing package unit tests (with Python) [group('unit tests')] -test-tests *args: - @mkdir -p "{{ output_dir }}/test-tests/tmp" +test-tests *args: (_tmp "test-tests") cd packages/testing && uv run pytest \ -n {{ xdist_workers }} \ --basetemp="{{ output_dir }}/test-tests/tmp" \ @@ -225,8 +232,7 @@ test-tests *args: # Run the testing package unit tests (with PyPy) [group('unit tests')] -test-tests-pypy *args: - @mkdir -p "{{ output_dir }}/test-tests-pypy/tmp" +test-tests-pypy *args: (_tmp "test-tests-pypy") cd packages/testing && uv run --python pypy3.11 --no-dev --group test pytest \ -n auto --maxprocesses 6 \ --basetemp="{{ output_dir }}/test-tests-pypy/tmp" \ @@ -243,8 +249,7 @@ test-ci-scripts *args: # Smoke-test benchmark tests: fill blockchain_test fixtures, then verify against EELS. [group('benchmark tests')] -bench-gas *args: - @mkdir -p "{{ output_dir }}/bench-gas/tmp" "{{ output_dir }}/bench-gas/logs" +bench-gas *args: (_tmp-logs "bench-gas") @echo "==> Step 1/3: Generating pre-alloc groups (smoke-tests the BlockchainEngineX path)" uv run fill \ --generate-pre-alloc-groups \ @@ -286,8 +291,7 @@ bench-gas *args: # Fill benchmark tests with --fixed-opcode-count 1 [group('benchmark tests')] -bench-opcode *args: - @mkdir -p "{{ output_dir }}/bench-opcode/tmp" "{{ output_dir }}/bench-opcode/logs" +bench-opcode *args: (_tmp-logs "bench-opcode") uv run fill \ --evm-bin="{{ evm_bin }}" \ --fixed-opcode-count 1 \ @@ -304,8 +308,7 @@ bench-opcode *args: # Run benchmark_parser, then fill benchmark tests using its config [group('benchmark tests')] -bench-opcode-config *args: - @mkdir -p "{{ output_dir }}/bench-opcode-config/tmp" "{{ output_dir }}/bench-opcode-config/logs" +bench-opcode-config *args: (_tmp-logs "bench-opcode-config") uv run benchmark_parser uv run fill \ --evm-bin="{{ evm_bin }}" \ From 3e27f98bf6dedbd40bf0e45eb95860a7872b23b5 Mon Sep 17 00:00:00 2001 From: Kumarutkarsh9470 <kutkarsh517@gmail.com> Date: Thu, 30 Jul 2026 17:57:52 +0530 Subject: [PATCH 181/233] test(spec-tests): add RLP canonical `Uint` decoding test (#3210) Co-authored-by: spencer-tb <spencer.tb@ethereum.org> --- tests/json_loader/test_rlp.py | 50 +++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 tests/json_loader/test_rlp.py diff --git a/tests/json_loader/test_rlp.py b/tests/json_loader/test_rlp.py new file mode 100644 index 00000000000..a7737a4b550 --- /dev/null +++ b/tests/json_loader/test_rlp.py @@ -0,0 +1,50 @@ +""" +Test that RLP decoding rejects non-canonical integer encodings. + +RLP encodes integers big-endian without leading zero bytes; zero is +the empty byte string, not `0x00`. Execution clients enforce this, so +the `ethereum-rlp` dependency must too. +""" + +from typing import Union + +import pytest +from ethereum_rlp import rlp +from ethereum_rlp.exceptions import DecodingError +from ethereum_types.numeric import U64, U256, Uint + + +@pytest.mark.parametrize( + "encoded, expected", + [ + pytest.param(b"\x80", 0, id="zero-empty-string"), + pytest.param(b"\x01", 1, id="one"), + pytest.param(b"\x7f", 0x7F, id="single-byte-max"), + pytest.param(b"\x81\x80", 0x80, id="smallest-prefixed"), + pytest.param(b"\x82\x01\x00", 0x0100, id="two-bytes"), + ], +) +def test_decode_to_uint_accepts_canonical( + encoded: bytes, expected: int +) -> None: + """Decode canonical integer encodings to the expected value.""" + assert rlp.decode_to(Uint, encoded) == Uint(expected) + + +@pytest.mark.parametrize( + "integer_type", [Uint, U64, U256], ids=["Uint", "U64", "U256"] +) +@pytest.mark.parametrize( + "encoded", + [ + pytest.param(b"\x00", id="single-zero-byte"), + pytest.param(b"\x82\x00\x01", id="leading-zero-two-bytes"), + pytest.param(b"\x83\x00\x00\x01", id="leading-zeros-three-bytes"), + ], +) +def test_decode_to_integer_rejects_leading_zeros( + integer_type: type[Union[Uint, U64, U256]], encoded: bytes +) -> None: + """Reject integer encodings with leading zero bytes.""" + with pytest.raises(DecodingError, match="non-canonical"): + rlp.decode_to(integer_type, encoded) From ad970077a4d866b1d15cd171fa708de49760a4f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= <pawel@hepcolgum.band> Date: Thu, 30 Jul 2026 16:45:23 +0200 Subject: [PATCH 182/233] feat(tests): add dynamic-destination JUMP/JUMPI invalid-target tests (#3153) Co-authored-by: spencer-tb <spencer.tb@ethereum.org> --- tests/frontier/opcodes/test_dynamic_jump.py | 200 ++++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 tests/frontier/opcodes/test_dynamic_jump.py diff --git a/tests/frontier/opcodes/test_dynamic_jump.py b/tests/frontier/opcodes/test_dynamic_jump.py new file mode 100644 index 00000000000..20a5f2bcdaf --- /dev/null +++ b/tests/frontier/opcodes/test_dynamic_jump.py @@ -0,0 +1,200 @@ +""" +Tests for JUMP and JUMPI with runtime-computed destinations or conditions. +""" + +from typing import Tuple + +import pytest +from execution_testing import ( + Account, + Alloc, + Environment, + Fork, + Hash, + Op, + StateTestFiller, + Storage, + Transaction, + TransactionReceipt, +) + +REFERENCE_SPEC_GIT_PATH = "N/A" +REFERENCE_SPEC_VERSION = "N/A" + +GAS_LIMIT = 100_000 + +DESTINATION_SLOT = 0xDE + +DESTINATION_KINDS = [ + "push_data_jumpdest", + "push_data_non_jumpdest", + "one_past_code", + "jumpdest_alias_2_64", + "max_u256", +] + +LEGACY_VM_TESTS = ( + "https://github.com/ethereum/legacytests/blob/master/" + "src/LegacyTests/Constantinople/VMTestsFiller/vmIOandFlowOperations" +) + + +@pytest.mark.ported_from( + [ + f"{LEGACY_VM_TESTS}/DynamicJumpInsidePushWithJumpDestFiller.json", + f"{LEGACY_VM_TESTS}/DynamicJumpInsidePushWithoutJumpDestFiller.json", + f"{LEGACY_VM_TESTS}/DynamicJumpifInsidePushWithJumpDestFiller.json", + f"{LEGACY_VM_TESTS}/DynamicJumpifInsidePushWithoutJumpDestFiller.json", + f"{LEGACY_VM_TESTS}/DynamicJumpiOutsideBoundaryFiller.json", + f"{LEGACY_VM_TESTS}/DynamicJumpJD_DependsOnJumps0Filler.json", + f"{LEGACY_VM_TESTS}/DynamicJumpPathologicalTest1Filler.json", + f"{LEGACY_VM_TESTS}/DynamicJumpPathologicalTest2Filler.json", + f"{LEGACY_VM_TESTS}/DynamicJumpPathologicalTest3Filler.json", + f"{LEGACY_VM_TESTS}/BlockNumberDynamicJumpInsidePushWithJumpDestFiller.json", + f"{LEGACY_VM_TESTS}/BlockNumberDynamicJumpInsidePushWithoutJumpDestFiller.json", + f"{LEGACY_VM_TESTS}/BlockNumberDynamicJumpifInsidePushWithJumpDestFiller.json", + f"{LEGACY_VM_TESTS}/BlockNumberDynamicJumpifInsidePushWithoutJumpDestFiller.json", + f"{LEGACY_VM_TESTS}/BlockNumberDynamicJumpiOutsideBoundaryFiller.json", + f"{LEGACY_VM_TESTS}/JDfromStorageDynamicJumpInsidePushWithJumpDestFiller.json", + f"{LEGACY_VM_TESTS}/JDfromStorageDynamicJumpInsidePushWithoutJumpDestFiller.json", + f"{LEGACY_VM_TESTS}/JDfromStorageDynamicJumpifInsidePushWithJumpDestFiller.json", + f"{LEGACY_VM_TESTS}/JDfromStorageDynamicJumpifInsidePushWithoutJumpDestFiller.json", + f"{LEGACY_VM_TESTS}/JDfromStorageDynamicJumpiOutsideBoundaryFiller.json", + f"{LEGACY_VM_TESTS}/bad_indirect_jump2Filler.json", + ], +) +@pytest.mark.valid_from("Frontier") +@pytest.mark.parametrize( + "opcode,condition", + [ + pytest.param(Op.JUMP, (), id="jump"), + pytest.param(Op.JUMPI, (1,), id="jumpi"), + ], +) +@pytest.mark.parametrize( + "dest_source,dest_kind", + [ + (source, kind) + for source in ("calldata", "storage", "number") + for kind in DESTINATION_KINDS + ], +) +def test_dynamic_jump_invalid_destination( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + opcode: Op, + condition: Tuple[int, ...], + dest_source: str, + dest_kind: str, +) -> None: + """ + Jump to an invalid destination computed at run time and verify the + exceptional halt: storage stays empty and all gas is consumed. + + The destination is never a static PUSH, so implementations that + pre-analyze PUSH+JUMP pairs must still validate it at execution time. + It is read from calldata, read from storage, or derived from the + block number, and it lands inside PUSH immediate data (on a 0x5B byte + and on a non-0x5B byte), one byte past the code, at an offset whose + truncation to 64 bits is a valid JUMPDEST, or at the maximum word. + """ + env = Environment() + storage = Storage() + dest_expression = { + "calldata": Op.CALLDATALOAD(0), + "storage": Op.SLOAD(DESTINATION_SLOT), + "number": Op.ADD(Op.NUMBER, Op.CALLDATALOAD(0)), + }[dest_source] + dispatch = opcode(dest_expression, *condition) + fallthrough = ( + Op.SSTORE(storage.store_next(0, "jump not halted"), 1) + Op.STOP + ) + island = Op.PUSH2(0x5B00) + Op.POP + code = ( + dispatch + + fallthrough + + island + + Op.JUMPDEST + + Op.SSTORE(storage.store_next(0, "invalid destination taken"), 2) + + Op.STOP + ) + push_data = len(dispatch + fallthrough) + 1 + jumpdest = len(dispatch + fallthrough + island) + assert bytes(code)[push_data] == Op.JUMPDEST.int() + assert bytes(code)[push_data + 1] != Op.JUMPDEST.int() + assert bytes(code)[jumpdest] == Op.JUMPDEST.int() + dest = { + "push_data_jumpdest": push_data, + "push_data_non_jumpdest": push_data + 1, + "one_past_code": len(code), + "jumpdest_alias_2_64": 2**64 + jumpdest, + "max_u256": 2**256 - 1, + }[dest_kind] + + initial_storage = Storage() + if dest_source == "storage": + initial_storage[DESTINATION_SLOT] = dest + storage[DESTINATION_SLOT] = dest + contract = pre.deploy_contract(code, storage=initial_storage) + tx = Transaction( + sender=pre.fund_eoa(), + to=contract, + data={ + "calldata": Hash(dest), + "storage": b"", + "number": Hash(dest - env.number), + }[dest_source], + gas_limit=GAS_LIMIT, + expected_receipt=TransactionReceipt(cumulative_gas_used=GAS_LIMIT), + protected=fork.supports_protected_txs(), + ) + + state_test( + env=env, pre=pre, post={contract: Account(storage=storage)}, tx=tx + ) + + +@pytest.mark.ported_from( + [ + f"{LEGACY_VM_TESTS}/jumpi1Filler.json", + ], +) +@pytest.mark.valid_from("Frontier") +@pytest.mark.parametrize( + "dest", + [pytest.param(1, id="push_data"), pytest.param(2**256 - 1, id="max_u256")], +) +def test_jumpi_not_taken_invalid_destination( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + dest: int, +) -> None: + """ + Verify that a false-condition JUMPI whose destination is invalid does + not fail and falls through. + + The condition comes from calldata, so implementations that eagerly + validate a static JUMPI destination must still skip the validation + when the branch is not taken. The code holds no JUMPDEST at all, so + both an in-code destination inside PUSH immediate data and one past + the maximum code size are invalid. + """ + storage = Storage() + code = ( + Op.JUMPI(dest, Op.CALLDATALOAD(0)) + + Op.SSTORE(storage.store_next(1, "fell through"), 1) + + Op.STOP + ) + assert Op.JUMPDEST.int() not in bytes(code) + + contract = pre.deploy_contract(code) + tx = Transaction( + sender=pre.fund_eoa(), + to=contract, + data=Hash(0), + protected=fork.supports_protected_txs(), + ) + + state_test(pre=pre, post={contract: Account(storage=storage)}, tx=tx) From 610cd779baff95f99c85442dd2fa8cc8146260b2 Mon Sep 17 00:00:00 2001 From: Marc <Marchhill@users.noreply.github.com> Date: Thu, 30 Jul 2026 15:50:30 +0100 Subject: [PATCH 183/233] feat(tests): add EIP-8037 spill-refund accounting coverage (#3158) Co-authored-by: spencer-tb <spencer.tb@ethereum.org> --- .../test_state_gas_call.py | 141 +++++++++++++++ .../test_state_gas_set_code.py | 76 ++++++++ .../test_state_gas_sstore.py | 165 ++++++++++++++++++ 3 files changed, 382 insertions(+) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py index 79e81ad7deb..75094848963 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_call.py @@ -1859,3 +1859,144 @@ def test_call_value_new_account_state_gas_returned_on_caller_revert( target: Account.NONEXISTENT, } state_test(pre=pre, post=post, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_reverted_grandchild_spill_through_child_halt( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify a grandchild's reverted spill does not ride through the + child's exceptional halt into the caller's accounting: the receipt + pins that the sender pays the halted child's forwarded budget + exactly once, not the grandchild's refilled spill on top. + + The tx gas limit sits below the EIP-7825 cap, so the reservoir is + empty and the grandchild's set spills from `gas_left`. + """ + intrinsic_cost = fork.transaction_intrinsic_cost_calculator()() + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + child_budget = 400_000 + + grandchild = pre.deploy_contract(code=Op.SSTORE(0, 1) + Op.REVERT(0, 0)) + child = pre.deploy_contract( + code=Op.POP(Op.CALL(gas=Op.GAS, address=grandchild)) + Op.INVALID, + ) + + storage = Storage() + # The child call halts and returns 0, so the caller's first SSTORE + # is a cold no-op (0 to 0) on a fresh slot rather than the cold set + # `execution_cost` assumes by default. + caller_code = Op.SSTORE.with_metadata( + key_warm=False, + original_value=0, + current_value=0, + new_value=0, + )( + storage.store_next(0, "child_halted"), + Op.CALL(gas=child_budget, address=child), + ) + Op.SSTORE(storage.store_next(1, "caller_completed"), 1) + caller = pre.deploy_contract(code=caller_code) + + # The halted child consumes its whole forwarded budget as execution + # gas; the caller's slot-1 set is the only surviving state charge. + expected_cumulative = ( + intrinsic_cost + + caller_code.execution_cost(fork) + + child_budget + + sstore_state_gas + ) + + tx = Transaction( + to=caller, + gas_limit=1_000_000, + sender=pre.fund_eoa(), + expected_receipt=TransactionReceipt( + cumulative_gas_used=expected_cumulative, + ), + ) + + post = { + caller: Account(storage=storage), + grandchild: Account(storage={0: 0}), + } + state_test(pre=pre, post=post, tx=tx) + + +@pytest.mark.valid_from("EIP8037") +def test_soft_failed_value_call_refund_through_child_halt( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify a same-frame NEW_ACCOUNT charge-and-refund (a value CALL + soft-failing the balance check) performed after a reverted child + call, merged into a frame with its own spilled set that then + exceptionally halts, charges the sender the halted frame's budget + exactly once — pinned by the receipt. + """ + intrinsic_cost = fork.transaction_intrinsic_cost_calculator()() + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + child_budget = 600_000 + + grandchild = pre.deploy_contract(code=Op.SSTORE(0, 1) + Op.REVERT(0, 0)) + fresh = pre.nonexistent_account() + # Zero balance: the value CALL soft-fails its balance check after + # the up-front NEW_ACCOUNT state charge, refunded in-frame. + middle = pre.deploy_contract( + code=( + Op.POP(Op.CALL(gas=Op.GAS, address=grandchild)) + + Op.POP(Op.CALL(gas=Op.GAS, address=fresh, value=1)) + + Op.STOP + ), + ) + child = pre.deploy_contract( + code=( + Op.SSTORE(0, 1) + + Op.POP(Op.CALL(gas=Op.GAS, address=middle)) + + Op.INVALID + ), + ) + + storage = Storage() + # The child call halts and returns 0, so the caller's first SSTORE + # is a cold no-op (0 to 0) on a fresh slot rather than the cold set + # `execution_cost` assumes by default. + caller_code = Op.SSTORE.with_metadata( + key_warm=False, + original_value=0, + current_value=0, + new_value=0, + )( + storage.store_next(0, "child_halted"), + Op.CALL(gas=child_budget, address=child), + ) + Op.SSTORE(storage.store_next(1, "caller_completed"), 1) + caller = pre.deploy_contract(code=caller_code) + + # The halted child consumes its whole forwarded budget as execution + # gas; the caller's slot-1 set is the only surviving state charge. + expected_cumulative = ( + intrinsic_cost + + caller_code.execution_cost(fork) + + child_budget + + sstore_state_gas + ) + + tx = Transaction( + to=caller, + gas_limit=1_000_000, + sender=pre.fund_eoa(), + expected_receipt=TransactionReceipt( + cumulative_gas_used=expected_cumulative, + ), + ) + + post = { + caller: Account(storage=storage), + child: Account(storage={0: 0}), + fresh: Account.NONEXISTENT, + } + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py index 6ce3b83b6af..0d846bbfb90 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py @@ -57,6 +57,7 @@ from tests.prague.eip7702_set_code_tx.spec import Spec as Spec7702 from .spec import ref_spec_8037 +from .test_state_gas_sstore import revoked_advance_call_tree REFERENCE_SPEC_GIT_PATH = ref_spec_8037.git_path REFERENCE_SPEC_VERSION = ref_spec_8037.version @@ -1807,6 +1808,81 @@ def test_auth_sender_billing_after_failure( ) +@pytest.mark.parametrize( + "inner_shape", + [ + pytest.param("burned_child_spill", id="burned_child_spill"), + pytest.param("revoked_advance", id="revoked_advance"), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_top_level_halt_keeps_intrinsic_auth_state_gas( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + inner_shape: str, +) -> None: + """ + Verify a top-level exceptional halt keeps the full authorization + state gas in the state dimension while the burned child spill stays + in the execution dimension: the header reports + ``max(gas_limit - auth_state, auth_state)`` regardless of the + spill shape burned inside the halted frame. + + The tx gas limit sits below the EIP-7825 cap, so the reservoir is + empty and every state charge inside the halted frame spills from + `gas_left`. + """ + gas_limit = 1_000_000 + + if inner_shape == "burned_child_spill": + inner = pre.deploy_contract(code=Op.SSTORE(0, 1) + Op.INVALID) + else: + inner = revoked_advance_call_tree(pre) + + recipient = pre.deploy_contract( + code=Op.POP(Op.CALL(gas=Op.GAS, address=inner)) + Op.INVALID, + ) + + delegate = pre.deploy_contract(code=Op.STOP) + signer = pre.fund_eoa(amount=0) + authorization_list = [ + AuthorizationTuple( + address=delegate, + nonce=0, + signer=signer, + creates_account=True, + writes_delegation=True, + ), + ] + _, _, auth_state_gas = _auth_gas(fork, authorization_list) + + # The halt consumes the whole limit: the execution and state + # dimensions sum to `gas_limit` however the split falls. + tx = Transaction( + ty=4, + to=recipient, + gas_limit=gas_limit, + authorization_list=authorization_list, + sender=pre.fund_eoa(), + expected_receipt=TransactionReceipt( + cumulative_gas_used=gas_limit, + ), + ) + + post = { + signer: Account(code=Spec7702.delegation_designation(delegate)), + } + state_test( + pre=pre, + post=post, + tx=tx, + blockchain_test_header_verify=Header( + gas_used=max(gas_limit - auth_state_gas, auth_state_gas) + ), + ) + + @pytest.mark.parametrize( "gas_delta", [ diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_sstore.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_sstore.py index d04a553e39b..59f8422bce2 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_sstore.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_sstore.py @@ -20,12 +20,14 @@ Block, BlockchainTestFiller, Bytecode, + Conditional, Fork, Header, Op, StateTestFiller, Storage, Transaction, + TransactionReceipt, ) from execution_testing.checklists import EIPChecklist @@ -1094,6 +1096,169 @@ def test_sstore_restoration_charge_in_ancestor_intermediate_revert( ) +@pytest.mark.parametrize( + "ending", + [ + pytest.param("success", id="all_frames_succeed"), + pytest.param("top_revert", id="top_level_reverts"), + pytest.param("middle_revert", id="middle_reverts_after_clear"), + ], +) +@pytest.mark.valid_from("EIP8037") +def test_cross_frame_refund_advance( + state_test: StateTestFiller, + pre: Alloc, + ending: str, +) -> None: + """ + Verify a restoration refund credited in a re-entered frame (an + advance against the entry frame's spilled sets) discharges through + the middle frame's merge on success, is void when the top level + reverts, and is revoked when the middle frame reverts after the + clear — leaving both slots set and the sender paying their full + state gas. + + The tx gas limit sits below the EIP-7825 cap, so the reservoir is + empty and the entry frame's sets spill from `gas_left`. + """ + middle_ending = Op.REVERT(0, 0) if ending == "middle_revert" else Op.STOP + middle = pre.deploy_contract( + code=( + Op.POP(Op.CALL(gas=Op.GAS, address=Op.CALLER, args_size=1)) + + middle_ending + ), + ) + + entry_ending = Op.REVERT(0, 0) if ending == "top_revert" else Op.STOP + entry = pre.deploy_contract( + code=Conditional( + condition=Op.CALLDATASIZE, + # Re-entered: clear both slots; each refund is credited + # here as an advance. + if_true=Op.SSTORE(0, 0) + Op.SSTORE(1, 0) + Op.STOP, + if_false=( + Op.SSTORE(0, 1) + + Op.SSTORE(1, 1) + + Op.SSTORE(2, Op.CALL(gas=Op.GAS, address=middle)) + + entry_ending + ), + ), + ) + + tx = Transaction( + to=entry, + gas_limit=1_000_000, + sender=pre.fund_eoa(), + ) + + if ending == "success": + storage = {0: 0, 1: 0, 2: 1} + elif ending == "top_revert": + storage = {} + else: + storage = {0: 1, 1: 1, 2: 0} + + post = {entry: Account(storage=storage)} + state_test(pre=pre, post=post, tx=tx) + + +def revoked_advance_call_tree(pre: Alloc) -> Address: + """ + Deploy a call tree whose entry sets two slots (both spilled), and + whose middle frame — holding one spilled set of its own — value + calls back into the entry, which imports a reverted child call and + then clears both slots. The two-clear advance is only partially + dischargeable against the middle frame's usage; the entry then + exceptionally halts, revoking the rest. + + Returns the entry contract's address. + """ + reverting = pre.deploy_contract(code=Op.SSTORE(0, 1) + Op.REVERT(0, 0)) + middle = pre.deploy_contract( + code=( + Op.SSTORE(0, 1) + + Op.POP(Op.CALL(gas=Op.GAS, address=Op.CALLER, value=1)) + + Op.STOP + ), + balance=1, + ) + return pre.deploy_contract( + code=Conditional( + condition=Op.CALLVALUE, + if_true=( + Op.POP(Op.CALL(gas=Op.GAS, address=reverting)) + + Op.SSTORE(0, 0) + + Op.SSTORE(1, 0) + + Op.STOP + ), + if_false=( + Op.SSTORE(0, 1) + + Op.SSTORE(1, 1) + + Op.POP(Op.CALL(gas=Op.GAS, address=middle)) + + Op.INVALID + ), + ), + ) + + +@pytest.mark.valid_from("EIP8037") +def test_partially_discharged_advance_revoked_by_halt( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """ + Verify an advance only partially dischargeable in the middle frame + (two clears against one middle set) is fully revoked when the entry + frame exceptionally halts, so the sender pays the entry's whole + forwarded budget and the caller's accounting is undisturbed. The + receipt pins the billing: the halted entry consumes its whole + forwarded budget as execution gas and the caller's slot-1 set is + the only surviving state charge. + """ + intrinsic_cost = fork.transaction_intrinsic_cost_calculator()() + sstore_state_gas = Op.SSTORE(new_value=1).state_cost(fork) + entry_budget = 600_000 + entry = revoked_advance_call_tree(pre) + + storage = Storage() + # The entry call OOGs and returns 0, so the caller's first SSTORE + # is a cold no-op (0 to 0) on a fresh slot rather than the cold set + # `execution_cost` assumes by default. + caller_code = Op.SSTORE.with_metadata( + key_warm=False, + original_value=0, + current_value=0, + new_value=0, + )( + storage.store_next(0, "entry_halted"), + Op.CALL(gas=entry_budget, address=entry), + ) + Op.SSTORE(storage.store_next(1, "caller_completed"), 1) + caller = pre.deploy_contract(code=caller_code) + + expected_cumulative = ( + intrinsic_cost + + caller_code.execution_cost(fork) + + entry_budget + + sstore_state_gas + ) + + tx = Transaction( + to=caller, + gas_limit=1_000_000, + sender=pre.fund_eoa(), + expected_receipt=TransactionReceipt( + cumulative_gas_used=expected_cumulative, + ), + ) + + post = { + caller: Account(storage=storage), + entry: Account(storage={0: 0, 1: 0}), + } + state_test(pre=pre, post=post, tx=tx) + + @pytest.mark.with_all_create_opcodes @pytest.mark.valid_from("EIP8037") def test_sstore_restoration_create_init_revert( From 7bc167de29f47eeeabee146991fb5c19240e01da Mon Sep 17 00:00:00 2001 From: Edgar <git@edgl.dev> Date: Thu, 30 Jul 2026 17:18:23 +0200 Subject: [PATCH 184/233] feat(tests): cover invalid-BAL content canonicality (no-op / missing / phantom-read entries) (#3170) --- .../test_types/block_access_list/modifiers.py | 63 +++ .../test_block_access_lists_invalid.py | 385 ++++++++++++++++++ .../test_cases.md | 6 + 3 files changed, 454 insertions(+) diff --git a/packages/testing/src/execution_testing/test_types/block_access_list/modifiers.py b/packages/testing/src/execution_testing/test_types/block_access_list/modifiers.py index 2777f5c5e27..35a03d7f40b 100644 --- a/packages/testing/src/execution_testing/test_types/block_access_list/modifiers.py +++ b/packages/testing/src/execution_testing/test_types/block_access_list/modifiers.py @@ -736,6 +736,68 @@ def transform(bal: BlockAccessList) -> BlockAccessList: return transform +def remove_slot_change( + address: Address, slot: int, block_access_index: int +) -> Callable[[BlockAccessList], BlockAccessList]: + """ + Remove a single slot change entry at a given block access index, while + keeping any other slot_changes entries for that same slot intact. + + Unlike `remove_storage`, which drops all storage_changes for an + account, this targets one entry within one slot's slot_changes list. + Useful for testing that a slot's earliest recorded change must match + the transaction that actually performed it. + + Removing a slot's only change leaves an empty slot_changes list, + which is a different corruption (see `append_empty_slot`); use + `remove_storage` to drop a slot entirely. + """ + + def transform(bal: BlockAccessList) -> BlockAccessList: + found_address = False + found_slot = False + found_index = False + new_root = [] + for account_change in bal.root: + if account_change.address == address: + found_address = True + new_account = account_change.model_copy(deep=True) + for storage_slot in new_account.storage_changes: + if storage_slot.slot != slot: + continue + found_slot = True + remaining = [ + change + for change in storage_slot.slot_changes + if change.block_access_index != block_access_index + ] + if len(remaining) != len(storage_slot.slot_changes): + found_index = True + storage_slot.slot_changes = remaining + new_root.append(new_account) + else: + new_root.append(account_change) + + if not found_address: + raise ValueError( + f"Address {address} not found in BAL to remove slot change" + ) + if not found_slot: + raise ValueError( + f"Storage slot {slot} not found in storage_changes of " + f"account {address}" + ) + if not found_index: + raise ValueError( + f"Block access index {block_access_index} not found in " + f"storage slot {slot} of account {address}" + ) + + return BlockAccessList(root=new_root) + + return transform + + def reverse_accounts() -> Callable[[BlockAccessList], BlockAccessList]: """Reverse the order of accounts in the BAL.""" @@ -831,6 +893,7 @@ def transform(bal: BlockAccessList) -> BlockAccessList: "modify_code", # Block access index modifiers "swap_bal_indices", + "remove_slot_change", # Duplicate entry modifiers (uniqueness constraint testing) "duplicate_nonce_change", "duplicate_balance_change", diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_invalid.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_invalid.py index 4a06f085c70..abdd2a5bcc9 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_invalid.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_invalid.py @@ -57,6 +57,7 @@ remove_balances, remove_code, remove_nonces, + remove_slot_change, remove_storage, remove_storage_reads, reverse_accounts, @@ -1730,3 +1731,387 @@ def test_bal_invalid_engine_payload_encoding( ) ], ) + + +@pytest.mark.valid_from("Amsterdam") +@pytest.mark.exception_test +def test_bal_invalid_noop_storage_change( + blockchain_test: BlockchainTestFiller, + pre: Alloc, +) -> None: + """ + Test that clients reject a BAL storage change whose post-value equals + the value already present at the start of the transaction. + + Oracle performs a round-trip write: SSTORE(0, 0x42) with slot 0 + already at 0x42. The canonical BAL demotes such a no-op write to a + storage_reads entry. The BAL is corrupted into exactly the shape a + builder without no-op demotion would emit: the raw write recorded as + a storage_changes entry (post_value == pre-tx value) and the read + dropped. + """ + alice = pre.fund_eoa() + oracle = pre.deploy_contract(code=Op.SSTORE(0, 0x42), storage={0: 0x42}) + + tx = Transaction(sender=alice, to=oracle, gas_limit=1_000_000) + + blockchain_test( + pre=pre, + # The block reverts and the post state remains unchanged. + post=pre, + blocks=[ + Block( + txs=[tx], + exception=BlockException.INVALID_BLOCK_ACCESS_LIST, + expected_block_access_list=BlockAccessListExpectation( + account_expectations={ + alice: BalAccountExpectation( + nonce_changes=[ + BalNonceChange( + block_access_index=1, post_nonce=1 + ) + ], + ), + oracle: BalAccountExpectation( + storage_reads=[0], + ), + } + ).modify( + remove_storage_reads(oracle), + append_storage( + address=oracle, + slot=0, + change=BalStorageChange( + block_access_index=1, post_value=0x42 + ), + ), + ), + ) + ], + ) + + +@pytest.mark.valid_from("Amsterdam") +@pytest.mark.exception_test +@pytest.mark.parametrize( + "modifier", + [ + pytest.param( + lambda oracle, code: append_change( # noqa: ARG005 + account=oracle, + change=BalBalanceChange(block_access_index=1, post_balance=0), + ), + id="noop_balance_change", + ), + pytest.param( + lambda oracle, code: append_change( # noqa: ARG005 + account=oracle, + change=BalNonceChange(block_access_index=1, post_nonce=1), + ), + id="noop_nonce_change", + ), + pytest.param( + lambda oracle, code: append_change( + account=oracle, + change=BalCodeChange(block_access_index=1, new_code=code), + ), + id="noop_code_change", + ), + ], +) +def test_bal_invalid_noop_value_change( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + modifier: Callable, +) -> None: + """ + Test that clients reject a BAL balance/nonce/code change entry whose + post-value equals the account's real, unchanged value. + + Oracle legitimately appears in the BAL only via a storage read (slot + 0 is read, not written). Its balance (0), nonce (1), and code are + never touched by the transaction. The BAL is corrupted by appending + a change entry for one of these fields whose post-value equals the + account's actual unchanged value -- distinct from the wrong-value + corruption covered elsewhere, since here post == pre exactly. + """ + alice = pre.fund_eoa() + code = Op.SLOAD(0) + oracle = pre.deploy_contract(code=code, storage={0: 0x42}) + + tx = Transaction(sender=alice, to=oracle) + + blockchain_test( + pre=pre, + # The block reverts and the post state remains unchanged. + post=pre, + blocks=[ + Block( + txs=[tx], + exception=BlockException.INVALID_BLOCK_ACCESS_LIST, + expected_block_access_list=BlockAccessListExpectation( + account_expectations={ + alice: BalAccountExpectation( + nonce_changes=[ + BalNonceChange( + block_access_index=1, post_nonce=1 + ) + ], + ), + oracle: BalAccountExpectation( + storage_reads=[0], + ), + } + ).modify(modifier(oracle=oracle, code=code)), + ) + ], + ) + + +@pytest.mark.valid_from("Amsterdam") +@pytest.mark.exception_test +def test_bal_invalid_missing_storage_write( + blockchain_test: BlockchainTestFiller, + pre: Alloc, +) -> None: + """ + Test that clients reject a BAL that omits a storage write that was + actually performed. + + Writer's storage slot 0 goes from 0 (default) to 1. The BAL is + corrupted by removing the account's storage_changes entirely. + Unlike `test_bal_invalid_field_entries[missing_storage_change]`, + the account has no other changes, so the corrupted entry degrades + to an access-only (empty) entry -- a shape that is legitimate for + merely-touched accounts. + """ + alice = pre.fund_eoa() + writer = pre.deploy_contract(code=Op.SSTORE(0, 1)) + + tx = Transaction(sender=alice, to=writer) + + blockchain_test( + pre=pre, + # The block reverts and the post state remains unchanged. + post=pre, + blocks=[ + Block( + txs=[tx], + exception=BlockException.INVALID_BLOCK_ACCESS_LIST, + expected_block_access_list=BlockAccessListExpectation( + account_expectations={ + alice: BalAccountExpectation( + nonce_changes=[ + BalNonceChange( + block_access_index=1, post_nonce=1 + ) + ], + ), + writer: BalAccountExpectation( + storage_changes=[ + BalStorageSlot( + slot=0, + slot_changes=[ + BalStorageChange( + block_access_index=1, + post_value=1, + ) + ], + ), + ], + ), + } + ).modify(remove_storage(writer)), + ) + ], + ) + + +@pytest.mark.valid_from("Amsterdam") +@pytest.mark.exception_test +def test_bal_invalid_missing_created_code( + blockchain_test: BlockchainTestFiller, + pre: Alloc, +) -> None: + """ + Test that clients reject a BAL that omits the deployed code of a + contract created via a contract-creation transaction. + + Complements `test_bal_invalid_field_entries[missing_code_change]`, + which covers the CREATE-opcode path. + """ + alice = pre.fund_eoa() + runtime_code = Op.STOP + initcode = Initcode(deploy_code=runtime_code) + created = compute_create_address(address=alice) + + tx = Transaction(sender=alice, to=None, data=initcode) + + blockchain_test( + pre=pre, + # The block reverts and the post state remains unchanged. + post=pre, + blocks=[ + Block( + txs=[tx], + exception=BlockException.INVALID_BLOCK_ACCESS_LIST, + expected_block_access_list=BlockAccessListExpectation( + account_expectations={ + alice: BalAccountExpectation( + nonce_changes=[ + BalNonceChange( + block_access_index=1, post_nonce=1 + ) + ], + ), + created: BalAccountExpectation( + code_changes=[ + BalCodeChange( + block_access_index=1, + new_code=runtime_code, + ) + ], + ), + } + ).modify(remove_code(created)), + ) + ], + ) + + +@pytest.mark.valid_from("Amsterdam") +@pytest.mark.exception_test +def test_bal_invalid_omitted_slot_change_at_index( + blockchain_test: BlockchainTestFiller, + pre: Alloc, +) -> None: + """ + Test that clients reject a BAL that drops a slot's earlier change + while keeping its later one, misattributing the slot's first + recorded change to a later transaction than the one that made it. + + Two transactions each write storage slot 0 of the same contract via + the transaction's call value. The BAL is corrupted by removing only + the first transaction's slot_changes entry for slot 0, leaving the + second transaction's entry as the slot's only recorded change. + """ + alice = pre.fund_eoa() + writer = pre.deploy_contract(code=Op.SSTORE(0, Op.CALLVALUE)) + + tx1 = Transaction(sender=alice, to=writer, value=1) + tx2 = Transaction(sender=alice, to=writer, value=2) + + blockchain_test( + pre=pre, + # The block reverts and the post state remains unchanged. + post=pre, + blocks=[ + Block( + txs=[tx1, tx2], + exception=BlockException.INVALID_BLOCK_ACCESS_LIST, + expected_block_access_list=BlockAccessListExpectation( + account_expectations={ + alice: BalAccountExpectation( + nonce_changes=[ + BalNonceChange( + block_access_index=1, post_nonce=1 + ), + BalNonceChange( + block_access_index=2, post_nonce=2 + ), + ], + ), + writer: BalAccountExpectation( + balance_changes=[ + BalBalanceChange( + block_access_index=1, post_balance=1 + ), + BalBalanceChange( + block_access_index=2, post_balance=3 + ), + ], + storage_changes=[ + BalStorageSlot( + slot=0, + slot_changes=[ + BalStorageChange( + block_access_index=1, + post_value=1, + ), + BalStorageChange( + block_access_index=2, + post_value=2, + ), + ], + ), + ], + ), + } + ).modify( + remove_slot_change(writer, slot=0, block_access_index=1) + ), + ) + ], + ) + + +@pytest.mark.valid_from("Amsterdam") +@pytest.mark.exception_test +def test_bal_invalid_phantom_read_on_selfdestruct( + blockchain_test: BlockchainTestFiller, + pre: Alloc, +) -> None: + """ + Test that clients reject a BAL with a phantom storage read for an + account created and destroyed within the same transaction. + + A contract-creation transaction's init code immediately + SELFDESTRUCTs, sending its endowment to beneficiary, without ever + returning runtime code. Per EIP-6780 the created account is created + and destroyed within the same transaction, so it has zero net BAL + changes (its balance and code never persist), but the account still + legitimately appears in the BAL as an entry with empty changes. The + BAL is corrupted by injecting a phantom storage read for a slot the + account never touched. + """ + alice = pre.fund_eoa() + beneficiary = pre.fund_eoa(amount=0) + endowment = 100 + phantom_slot = 0x07 + + initcode = Op.SELFDESTRUCT(beneficiary) + created = compute_create_address(address=alice) + + tx = Transaction(sender=alice, to=None, data=initcode, value=endowment) + + blockchain_test( + pre=pre, + # The block reverts and the post state remains unchanged. + post=pre, + blocks=[ + Block( + txs=[tx], + exception=BlockException.INVALID_BLOCK_ACCESS_LIST, + expected_block_access_list=BlockAccessListExpectation( + account_expectations={ + alice: BalAccountExpectation( + nonce_changes=[ + BalNonceChange( + block_access_index=1, post_nonce=1 + ) + ], + ), + created: BalAccountExpectation.empty(), + beneficiary: BalAccountExpectation( + balance_changes=[ + BalBalanceChange( + block_access_index=1, + post_balance=endowment, + ) + ], + ), + } + ).modify(insert_storage_read(created, phantom_slot)), + ) + ], + ) diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_cases.md b/tests/amsterdam/eip7928_block_level_access_lists/test_cases.md index 6bcc98b121a..6990ea51c8b 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_cases.md +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_cases.md @@ -170,6 +170,12 @@ | `test_bal_invalid_coinbase_balance_value` | Verify clients reject blocks where BAL has an incorrect balance for the coinbase/fee recipient | Same setup as test_bal_invalid_missing_coinbase. BAL modifier changes charlie's post-balance from the actual tip to 999. | Block **MUST** be rejected with `INVALID_BLOCK_ACCESS_LIST` exception. Clients **MUST** validate coinbase balance values match actual fee accounting (priority fee x gas used). | ✅ Completed | | `test_bal_invalid_extraneous_coinbase` | Verify clients reject blocks with a spurious coinbase entry when coinbase received no fees | Parameterized: (1) empty_block: no txs, no withdrawals — only system contracts in valid BAL, (2) withdrawal_only: no txs, one withdrawal to a different address — withdrawals don't pay fees so coinbase is still untouched. BAL modifier appends spurious coinbase entry with empty changes. | Block **MUST** be rejected with `INVALID_BLOCK_ACCESS_LIST` exception. Coinbase **MUST NOT** appear in BAL when it receives no transaction tips, even if the block has other state-modifying activity (withdrawals). | ✅ Completed | | `test_bal_invalid_surplus_system_address_from_system_call` | Verify clients reject a BAL containing `SYSTEM_ADDRESS` solely due to a system operation caller | Empty block with an EIP-4788 pre-execution system call to `BEACON_ROOTS_ADDRESS`. Helper `beacon_root_system_call_expectations` builds the valid baseline BAL: `BEACON_ROOTS_ADDRESS` has timestamp/root storage changes at `block_access_index=0`, while `SYSTEM_ADDRESS` is marked absent (`None`). The fixture BAL is then corrupted by appending an empty `SYSTEM_ADDRESS` entry. | Block **MUST** be rejected with `INVALID_BLOCK_ACCESS_LIST`. The synthetic system caller address **MUST NOT** be accepted unless it experienced an actual state access. | ✅ Completed | +| `test_bal_invalid_noop_storage_change` | Verify clients reject a storage change entry whose post-value equals the value already present at the start of the transaction | Oracle performs a round-trip write: `SSTORE(0, 0x42)` with slot 0 already at `0x42`. The canonical BAL demotes the no-op write to a `storage_reads` entry. BAL is corrupted into the shape a builder without no-op demotion would emit: the read is removed and the raw write appears as a `storage_changes` entry with `post_value=0x42`, equal to the pre-tx value. | Block **MUST** be rejected with `INVALID_BLOCK_ACCESS_LIST` exception. Clients **MUST** reject a storage change entry whose post-value equals the pre-transaction value, not just wrong-value entries. | ✅ Completed | +| `test_bal_invalid_noop_value_change` | Verify clients reject a spurious balance/nonce/code change entry whose post-value equals the account's real, unchanged value | Oracle legitimately appears in the BAL only via a storage read; its balance (0), nonce (1), and code are never touched by the transaction. Parametrized: (1) `noop_balance_change`: appends a `balance_changes` entry with `post_balance` equal to Oracle's real unchanged balance. (2) `noop_nonce_change`: appends a `nonce_changes` entry with `post_nonce` equal to Oracle's real unchanged nonce. (3) `noop_code_change`: appends a `code_changes` entry with `new_code` equal to Oracle's real unchanged code. | Block **MUST** be rejected with `INVALID_BLOCK_ACCESS_LIST` exception. Clients **MUST** reject a change entry whose post-value exactly matches the account's actual unchanged value, distinct from the wrong-value corruption covered by `test_bal_invalid_balance_value`/`test_bal_invalid_nonce_value`. | ✅ Completed | +| `test_bal_invalid_missing_storage_write` | Verify clients reject a BAL that omits a storage write that was actually performed | Writer's storage slot 0 goes from 0 (default) to 1 via `SSTORE`. BAL modifier removes the account's `storage_changes` entirely. | Block **MUST** be rejected with `INVALID_BLOCK_ACCESS_LIST` exception. Clients **MUST** detect an account whose real storage write has no corresponding BAL entry. | ✅ Completed | +| `test_bal_invalid_missing_created_code` | Verify clients reject a BAL that omits the deployed code of a contract created via a contract-creation transaction | Alice sends a top-level `CREATE` transaction (`to=None`) whose init code deploys a small runtime. BAL modifier removes the created contract's `code_changes` entirely. | Block **MUST** be rejected with `INVALID_BLOCK_ACCESS_LIST` exception. Clients **MUST** detect a newly created contract whose deployed code has no corresponding BAL entry. | ✅ Completed | +| `test_bal_invalid_omitted_slot_change_at_index` | Verify clients reject a BAL that drops a slot's earlier change while keeping its later one, misattributing the slot's first recorded change to a later transaction | Two transactions each write storage slot 0 of the same contract via the transaction's call value (slot 0: 0→1 at tx1, 1→2 at tx2). BAL modifier removes only tx1's `slot_changes` entry for slot 0 (new `remove_slot_change` modifier), leaving tx2's entry as the slot's only recorded change. | Block **MUST** be rejected with `INVALID_BLOCK_ACCESS_LIST` exception. Clients **MUST** validate that a slot's first BAL-recorded change matches the transaction that actually performed it, not merely that some change with the correct final value exists. | ✅ Completed | +| `test_bal_invalid_phantom_read_on_selfdestruct` | Verify clients reject a BAL with a phantom storage read for an account created and destroyed within the same transaction | A contract-creation transaction's init code immediately `SELFDESTRUCT`s, sending its endowment to beneficiary, without ever returning runtime code. Per EIP-6780 the created account has zero net BAL changes (`BalAccountExpectation.empty()`) but still legitimately appears in the BAL as an entry with empty changes. BAL modifier injects a phantom `storage_reads` entry for a slot the account never touched. | Block **MUST** be rejected with `INVALID_BLOCK_ACCESS_LIST` exception. Clients **MUST** reject a storage read recorded against an account that performed no storage access at all, even when the account otherwise legitimately appears in the BAL. | ✅ Completed | | `test_bal_2935_simple` | Ensure BAL captures EIP-2935 history storage writes during pre-execution system call alongside normal transactions | Block with 2 normal user transactions: Alice sends 10 wei to Charlie, Bob sends 10 wei to Charlie. At block start (pre-execution), `SYSTEM_ADDRESS` calls `HISTORY_STORAGE_ADDRESS` to store parent block hash. | BAL **MUST** include `HISTORY_STORAGE_ADDRESS` with `storage_changes` (ring buffer slot 0, empty `slot_changes` since parent hash is framework-computed); `SYSTEM_ADDRESS` **MUST NOT** be included in BAL. At `block_access_index=1`: Alice with `nonce_changes`, Charlie with `balance_changes` (10 wei). At `block_access_index=2`: Bob with `nonce_changes`, Charlie with `balance_changes` (20 wei total). | ✅ Completed | | `test_bal_2935_empty_block` | Ensure BAL captures EIP-2935 history storage writes in empty block | Block with no transactions. At block start (pre-execution), `SYSTEM_ADDRESS` calls `HISTORY_STORAGE_ADDRESS` to store parent block hash. | BAL **MUST** include `HISTORY_STORAGE_ADDRESS` with `storage_changes` (ring buffer slot 0, empty `slot_changes`); `SYSTEM_ADDRESS` **MUST NOT** be included in BAL. No transaction-related BAL entries. | ✅ Completed | | `test_bal_2935_query` | Ensure BAL captures storage reads when querying EIP-2935 historical block hashes (valid and invalid queries) with optional value transfer | Parameterized test: Block 1 (empty, stores genesis hash via system call). Block 2: Oracle contract queries `HISTORY_STORAGE_ADDRESS` with block number. Two block number scenarios (valid=0 genesis hash, invalid=1042 out of range) and value (0 or 100 wei). Valid query (block_number=0): reads genesis hash slot, oracle writes returned value. If value > 0, history storage contract receives balance. Invalid query (block_number=1042, out of range): reverts before storage access, oracle has implicit SLOAD recorded, value stays in oracle (not transferred to history storage). | Block 2 BAL **MUST** include: Valid case at `block_access_index=1`: `HISTORY_STORAGE_ADDRESS` with `storage_reads` [slot 0] and `balance_changes` if value > 0, oracle with `storage_changes` (empty `slot_changes`). Invalid case at `block_access_index=1`: `HISTORY_STORAGE_ADDRESS` with NO `storage_reads` (reverts before access) and NO `balance_changes`, oracle with `storage_reads` [0], NO `storage_changes`, and `balance_changes` if value > 0 (value stays in oracle). Alice with `nonce_changes` at `block_access_index=1`. | ✅ Completed | From 2384e39d17a226106a88f6ab34aad4461550ea63 Mon Sep 17 00:00:00 2001 From: spencer <spencer.taylor-brown@ethereum.org> Date: Thu, 30 Jul 2026 17:27:43 +0200 Subject: [PATCH 185/233] chore(tests): improve EIP-8024 coverage, checklist, and ref-spec pin (#3224) Co-authored-by: LouisTsai <q1030176@gmail.com> Co-authored-by: Mario Vega <marioevz@gmail.com> --- .../eip_checklist_external_coverage.txt | 3 + .../eip_checklist_not_applicable.txt | 25 ++ .../eip8024_dupn_swapn_exchange/spec.py | 35 ++- .../eip8024_dupn_swapn_exchange/test_dupn.py | 121 ++++++++- .../test_eip_mainnet.py | 88 +++++++ .../test_eip_vectors.py | 46 ++++ .../test_endofcode_underflow.py | 2 + .../test_exchange.py | 97 ++++++- .../test_execution_contexts.py | 238 ++++++++++++++++++ .../test_fork_transition.py | 146 +++++++++++ .../eip8024_dupn_swapn_exchange/test_swapn.py | 91 ++++++- 11 files changed, 871 insertions(+), 21 deletions(-) create mode 100644 tests/amsterdam/eip8024_dupn_swapn_exchange/eip_checklist_external_coverage.txt create mode 100644 tests/amsterdam/eip8024_dupn_swapn_exchange/eip_checklist_not_applicable.txt create mode 100644 tests/amsterdam/eip8024_dupn_swapn_exchange/test_eip_mainnet.py create mode 100644 tests/amsterdam/eip8024_dupn_swapn_exchange/test_execution_contexts.py create mode 100644 tests/amsterdam/eip8024_dupn_swapn_exchange/test_fork_transition.py diff --git a/tests/amsterdam/eip8024_dupn_swapn_exchange/eip_checklist_external_coverage.txt b/tests/amsterdam/eip8024_dupn_swapn_exchange/eip_checklist_external_coverage.txt new file mode 100644 index 00000000000..93aff2a8aee --- /dev/null +++ b/tests/amsterdam/eip8024_dupn_swapn_exchange/eip_checklist_external_coverage.txt @@ -0,0 +1,3 @@ +general/code_coverage/eels = EIP-8024 adds the dupn/swapn/exchange instructions plus the decode_single/decode_pair helpers (vm/stack.py, vm/instructions/stack.py) and three gas constants; every branch including both forbidden-immediate ranges and both decode_pair arms is executed when filling this suite through the EELS t8n +general/code_coverage/test_coverage = suite logic is exercised end-to-end by filling tests/amsterdam/eip8024_dupn_swapn_exchange with the EELS filler; every parametrized arm produces a fixture with a discriminating post-state +general/code_coverage/missed_lines = no missed lines; the valid, forbidden-immediate, underflow, overflow and end-of-code sweeps cover every instruction branch diff --git a/tests/amsterdam/eip8024_dupn_swapn_exchange/eip_checklist_not_applicable.txt b/tests/amsterdam/eip8024_dupn_swapn_exchange/eip_checklist_not_applicable.txt new file mode 100644 index 00000000000..b2bb19ccc20 --- /dev/null +++ b/tests/amsterdam/eip8024_dupn_swapn_exchange/eip_checklist_not_applicable.txt @@ -0,0 +1,25 @@ +precompile = EIP-8024 does not introduce a new precompile +removed_precompile = EIP-8024 does not remove a precompile +system_contract = EIP-8024 does not introduce a new system contract +transaction_type = EIP-8024 does not introduce a new transaction type +block_header_field = EIP-8024 does not add a new block header field +block_body_field = EIP-8024 does not add a new block body field +block_level_constraint = EIP-8024 does not introduce a new block-level constraint +gas_cost_changes = EIP-8024 does not modify existing gas costs; it only introduces new opcodes with fixed costs +gas_refunds_changes = EIP-8024 does not change gas refunds +blob_count_changes = EIP-8024 does not change blob counts +execution_layer_request = EIP-8024 does not introduce an execution layer request +new_transaction_validity_constraint = EIP-8024 does not introduce a new transaction validity constraint +modified_transaction_validity_constraint = EIP-8024 does not modify transaction validity constraints +opcode/test/mem_exp = DUPN, SWAPN and EXCHANGE do not read or write memory +opcode/test/contract_creation = DUPN, SWAPN and EXCHANGE do not create contracts +opcode/test/terminating = DUPN, SWAPN and EXCHANGE are not terminating opcodes +opcode/test/return_data = DUPN, SWAPN and EXCHANGE do not interact with the return data buffer +opcode/test/out_of_bounds = the single immediate byte is fully partitioned into valid and forbidden values; the forbidden ranges are exhaustively swept and the valid ranges boundary-sampled by the immediate tests +opcode/test/gas_usage/memory_expansion = DUPN, SWAPN and EXCHANGE do not access memory +opcode/test/gas_usage/out_of_gas_memory = DUPN, SWAPN and EXCHANGE do not access memory +opcode/test/gas_usage/order_of_operations = single fixed fee; the gas-first step order is unobservable because every failure mode is an exceptional halt consuming all frame gas +opcode/test/execution_context/tx_context = stack manipulation does not depend on transaction properties +opcode/test/execution_context/block_context = stack manipulation does not depend on block properties +opcode/test/execution_context/initcode/reentry = DUPN, SWAPN and EXCHANGE are not stateful +general/code_coverage/second_client = Optional diff --git a/tests/amsterdam/eip8024_dupn_swapn_exchange/spec.py b/tests/amsterdam/eip8024_dupn_swapn_exchange/spec.py index 8a1c911ee4c..9829f8c779a 100644 --- a/tests/amsterdam/eip8024_dupn_swapn_exchange/spec.py +++ b/tests/amsterdam/eip8024_dupn_swapn_exchange/spec.py @@ -3,11 +3,6 @@ from dataclasses import dataclass from typing import Tuple -from ethereum_types.numeric import U8 - -from ethereum.forks.amsterdam.vm.stack import decode_pair as _decode_pair -from ethereum.forks.amsterdam.vm.stack import decode_single as _decode_single - @dataclass(frozen=True) class ReferenceSpec: @@ -19,7 +14,7 @@ class ReferenceSpec: ref_spec_8024 = ReferenceSpec( git_path="EIPS/eip-8024.md", - version="380cb02832a6ed5310bfde51591e580ca6d1f3cd", + version="34b49095ca5f7343045da279f04e7ecd1e451393", ) @@ -40,12 +35,26 @@ class Spec: EXCHANGE_MAX_SUM: int = 30 -def decode_pair(x: int) -> Tuple[int, int]: - """Decode a pair with proper typing for tests.""" - n, m = _decode_pair(U8(x)) - return int(n), int(m) +def decode_single(x: int) -> int: + """ + Decode the DUPN/SWAPN immediate byte per the EIP-8024 reference code. + Return n with 17 <= n <= 235. + """ + assert 0 <= x <= 90 or 128 <= x <= 255 + return (x + 145) % 256 -def decode_single(x: int) -> int: - """Decode single with proper typing for tests.""" - return int(_decode_single(U8(x))) + +def decode_pair(x: int) -> Tuple[int, int]: + """ + Decode the EXCHANGE immediate byte per the EIP-8024 reference code. + + Return (n, m) with 1 <= n <= 14 and n < m <= 30 - n. + """ + assert 0 <= x <= 81 or 128 <= x <= 255 + k = x ^ 143 + q, r = divmod(k, 16) + if q < r: + return q + 1, r + 1 + else: + return r + 1, 29 - q diff --git a/tests/amsterdam/eip8024_dupn_swapn_exchange/test_dupn.py b/tests/amsterdam/eip8024_dupn_swapn_exchange/test_dupn.py index 3339b343409..f5e4a5c575f 100644 --- a/tests/amsterdam/eip8024_dupn_swapn_exchange/test_dupn.py +++ b/tests/amsterdam/eip8024_dupn_swapn_exchange/test_dupn.py @@ -25,6 +25,9 @@ pytestmark = pytest.mark.valid_from("EIP8024") +@EIPChecklist.Opcode.Test.StackComplexOperations.StackHeights.Odd() +@EIPChecklist.Opcode.Test.StackComplexOperations.StackHeights.Even() +@EIPChecklist.Opcode.Test.StackComplexOperations.DataPortionVariables.Bottom() @pytest.mark.parametrize( "stack_index", [17, 18, 32, 64, 107, 108, 200, 235], @@ -68,6 +71,8 @@ def test_dupn_basic( state_test(pre=pre, post=post, tx=tx) +@EIPChecklist.Opcode.Test.DataPortion.AllZeros() +@EIPChecklist.Opcode.Test.DataPortion.MaxValue() @pytest.mark.parametrize( "immediate", [0, 45, 90, 128, 200, 255], @@ -108,6 +113,7 @@ def test_dupn_valid_immediates( state_test(pre=pre, post=post, tx=tx) +@EIPChecklist.Opcode.Test.StackUnderflow() @pytest.mark.parametrize( "immediate", [0, 45, 90, 128, 200, 255], @@ -234,6 +240,7 @@ def test_endofcode_behavior( state_test(pre=pre, post=post, tx=tx) +@EIPChecklist.Opcode.Test.ExceptionalAbort() @pytest.mark.parametrize( "invalid_immediate", list(range(91, 128)), # 0x5b to 0x7f (JUMPDEST and PUSH opcodes) @@ -277,6 +284,7 @@ def test_dupn_invalid_immediate_aborts( state_test(pre=pre, post=post, tx=tx) +@EIPChecklist.Opcode.Test.DataPortion.Jump() def test_dupn_jump_to_immediate_byte_0x5b_succeeds( pre: Alloc, state_test: StateTestFiller, @@ -313,6 +321,7 @@ def test_dupn_jump_to_immediate_byte_0x5b_succeeds( state_test(pre=pre, post=post, tx=tx) +@EIPChecklist.Opcode.Test.DataPortion.Jump() def test_dupn_jump_to_valid_immediate_fails( pre: Alloc, state_test: StateTestFiller, @@ -323,7 +332,8 @@ def test_dupn_jump_to_valid_immediate_fails( Bytecode: PUSH1(4) JUMP DUPN[0x00] Hex: 6004 56 e6 00 Position 4 contains 0x00 which is a VALID immediate for DUPN. - Valid immediates are skipped in JUMPDEST analysis, so jump fails. + JUMPDEST analysis is unchanged by EIP-8024: position 4 holds 0x00, + not 0x5b, so it is not a valid jump target and the jump fails. """ sender = pre.fund_eoa() @@ -398,3 +408,112 @@ def test_dupn_with_dup1_sequence( post = {contract_address: Account(storage=expected_storage)} state_test(pre=pre, post=post, tx=tx) + + +@EIPChecklist.Opcode.Test.StackOverflow() +@pytest.mark.parametrize( + "stack_delta,call_succeeds", + [ + pytest.param(-1, True, id="dupn_fills_stack_to_limit"), + pytest.param(0, False, id="dupn_stack_overflow"), + ], +) +def test_dupn_stack_overflow( + stack_delta: int, + call_succeeds: bool, + pre: Alloc, + fork: Fork, + state_test: StateTestFiller, +) -> None: + """ + Test that DUPN aborts when pushing past the stack limit. + + The callee pushes `max_stack_height + stack_delta` items and + executes DUPN: from one below the limit the duplicate fills the + stack exactly and succeeds, while from a full stack the push + overflows and aborts the frame. The caller stores the call's + success flag over a nonzero canary. + """ + stack_height = fork.max_stack_height() + stack_delta + callee_code = ( + Op.PUSH0 * stack_height + Op.DUPN[Spec.MIN_STACK_INDEX] + Op.STOP + ) + callee_address = pre.deploy_contract(callee_code) + + caller_code = Op.SSTORE(0, Op.CALL(gas=Op.GAS, address=callee_address)) + caller_address = pre.deploy_contract(caller_code, storage={0: 0xBA5E}) + + tx = Transaction(to=caller_address, sender=pre.fund_eoa()) + + post = { + caller_address: Account( + storage={0: 1 if call_succeeds else 0}, + ), + } + + state_test(pre=pre, post=post, tx=tx) + + +@EIPChecklist.Opcode.Test.StackComplexOperations.StackHeights.Zero() +@EIPChecklist.Opcode.Test.StackUnderflow() +def test_dupn_empty_stack( + pre: Alloc, + state_test: StateTestFiller, +) -> None: + """ + Test DUPN on an empty stack aborts with a stack underflow. + """ + sender = pre.fund_eoa() + + code = Op.SSTORE(0, 1) # leaves the stack empty + code += Op.DUPN[Spec.MIN_STACK_INDEX] + code += Op.STOP + + contract_address = pre.deploy_contract(code=code) + + tx = Transaction(to=contract_address, sender=sender) + + # Transaction should fail, contract storage unchanged. + post = {contract_address: Account(storage={0: 0})} + + state_test(pre=pre, post=post, tx=tx) + + +@EIPChecklist.Opcode.Test.DataPortion.Jump() +def test_dupn_jump_into_immediate_then_execute( + pre: Alloc, + state_test: StateTestFiller, +) -> None: + """ + Test jumping into a DUPN immediate, then executing a second DUPN. + + The jump lands on the first DUPN's 0x5b immediate, a valid JUMPDEST + because analysis is unchanged by EIP-8024, and a second DUPN then + duplicates the deepest pushed value and stores it over the canary. + """ + sender = pre.fund_eoa() + + setup = sum( + (Op.PUSH1[i] for i in range(Spec.MIN_STACK_INDEX, 0, -1)), + Bytecode(), + ) + # The 0x5b landing pad sits 4 bytes past the setup: PUSH1, + # target, JUMP, then the DUPN opcode byte itself. + landing_pad = len(setup) + 4 + + code = setup + code += Op.PUSH1(landing_pad) + Op.JUMP + code += Op.DUPN[b"\x5b"] # Jumped into, never executed. + code += Op.SSTORE( + 0, + Op.DUPN[Spec.MIN_STACK_INDEX], # Executed after landing. + ) + code += Op.STOP + + contract_address = pre.deploy_contract(code=code, storage={0: 0xBA5E}) + + tx = Transaction(to=contract_address, sender=sender) + + post = {contract_address: Account(storage={0: Spec.MIN_STACK_INDEX})} + + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/amsterdam/eip8024_dupn_swapn_exchange/test_eip_mainnet.py b/tests/amsterdam/eip8024_dupn_swapn_exchange/test_eip_mainnet.py new file mode 100644 index 00000000000..fbb5e8aee96 --- /dev/null +++ b/tests/amsterdam/eip8024_dupn_swapn_exchange/test_eip_mainnet.py @@ -0,0 +1,88 @@ +""" +Mainnet marked execute checklist tests for +[EIP-8024: Stack Access Instructions](https://eips.ethereum.org/EIPS/eip-8024). +""" + +import pytest +from execution_testing import ( + Account, + Alloc, + Environment, + Op, + StateTestFiller, + Transaction, +) + +from .spec import ref_spec_8024 + +REFERENCE_SPEC_GIT_PATH = ref_spec_8024.git_path +REFERENCE_SPEC_VERSION = ref_spec_8024.version + +pytestmark = [pytest.mark.valid_at("EIP8024"), pytest.mark.mainnet] + + +def test_stack_access_opcodes_mainnet( + state_test: StateTestFiller, + pre: Alloc, +) -> None: + """ + Test that DUPN, SWAPN and EXCHANGE execute with correct results. + + Each opcode moves a distinct planted marker to the top of the stack, + which is then stored. The opcodes do not depend on any environment + value, so the full post-state assertion holds when `execute`-ed on a + live network. Storage keys start at nonzero canaries so a failed + transaction is distinguishable from a successful one. + """ + dupn_marker = 0xA1 + swapn_marker = 0xB2 + exchange_marker = 0xC3 + + code = ( + # DUPN: duplicate the marker planted at depth 17. + Op.PUSH1(dupn_marker) + + Op.PUSH0 * 16 + + Op.DUPN[17] + + Op.PUSH1(0) + + Op.SSTORE + # SWAPN: swap the top with the marker planted at depth 18. + + Op.PUSH1(swapn_marker) + + Op.PUSH0 * 17 + + Op.SWAPN[17] + + Op.PUSH1(1) + + Op.SSTORE + # EXCHANGE: move the marker from depth 3 to depth 2, then POP. + + Op.PUSH1(exchange_marker) + + Op.PUSH0 * 2 + + Op.EXCHANGE[1, 2] + + Op.POP + + Op.PUSH1(2) + + Op.SSTORE + + Op.STOP + ) + contract = pre.deploy_contract( + code=code, + storage={0: 0xBA5E, 1: 0xBA5E, 2: 0xBA5E}, + ) + tx = Transaction( + ty=0x02, + to=contract, + sender=pre.fund_eoa(), + gas_limit=200_000, + ) + post = { + contract: Account( + storage={ + 0: dupn_marker, + 1: swapn_marker, + 2: exchange_marker, + }, + ), + } + + state_test( + env=Environment(), + pre=pre, + tx=tx, + post=post, + ) diff --git a/tests/amsterdam/eip8024_dupn_swapn_exchange/test_eip_vectors.py b/tests/amsterdam/eip8024_dupn_swapn_exchange/test_eip_vectors.py index 7ee9c1bf4d3..a6be2f763b8 100644 --- a/tests/amsterdam/eip8024_dupn_swapn_exchange/test_eip_vectors.py +++ b/tests/amsterdam/eip8024_dupn_swapn_exchange/test_eip_vectors.py @@ -786,6 +786,52 @@ def test_vector_exchange_invalid_0x52( state_test(pre=pre, post=post, tx=tx) +@pytest.mark.parametrize( + "opcode", + [Op.DUPN, Op.SWAPN, Op.EXCHANGE], +) +def test_vector_push_in_immediate_masks_jumpdest( + pre: Alloc, + state_test: StateTestFiller, + opcode: Op, +) -> None: + """ + Test that a PUSH1 in the would-be immediate masks a following 0x5b. + + Executable form of the `e6605b` assembly vector ([INVALID_DUPN, + PUSH1 0x5b]), parametrized over all three opcodes. JUMPDEST analysis + is unchanged by EIP-8024: the byte after the opcode is analyzed as + PUSH1, whose data portion masks the 0x5b, so the jump to it must + fail. A client that instead masks the opcode's immediate would leave + the 0x5b at an instruction boundary, accept the jump, and succeed — + diverging on bytecode that is valid before and after the fork. + """ + sender = pre.fund_eoa() + + # 00 PUSH1 0x05 + # 02 JUMP + # 03 <opcode> + # 04 PUSH1 0x5b + code = Op.PUSH1(5) + Op.JUMP + opcode[bytes(Op.PUSH1)] + Op.JUMPDEST + expected_bytes = ( + bytes.fromhex("600556") + bytes(opcode) + bytes.fromhex("605b") + ) + assert bytes(code) == expected_bytes + + # This should never execute: position 5 is PUSH1 data, not a + # JUMPDEST, so the jump above aborts. + code += Op.SSTORE(0, 0x42) + code += Op.STOP + + contract_address = pre.deploy_contract(code=code, storage={0: 0xBA5E}) + tx = Transaction(to=contract_address, sender=sender) + + # Transaction fails on the invalid jump; the canary is untouched. + post = {contract_address: Account(storage={0: 0xBA5E})} + + state_test(pre=pre, post=post, tx=tx) + + @pytest.mark.parametrize( "eip8024_opcode,stack_items", [ diff --git a/tests/amsterdam/eip8024_dupn_swapn_exchange/test_endofcode_underflow.py b/tests/amsterdam/eip8024_dupn_swapn_exchange/test_endofcode_underflow.py index cfb88b2054b..d6d4592871c 100644 --- a/tests/amsterdam/eip8024_dupn_swapn_exchange/test_endofcode_underflow.py +++ b/tests/amsterdam/eip8024_dupn_swapn_exchange/test_endofcode_underflow.py @@ -17,6 +17,7 @@ from execution_testing import ( Account, Alloc, + EIPChecklist, Op, StateTestFiller, Transaction, @@ -30,6 +31,7 @@ pytestmark = pytest.mark.valid_from("EIP8024") +@EIPChecklist.Opcode.Test.StackUnderflow() @pytest.mark.parametrize( "eip8024_opcode,pushed_items", [ diff --git a/tests/amsterdam/eip8024_dupn_swapn_exchange/test_exchange.py b/tests/amsterdam/eip8024_dupn_swapn_exchange/test_exchange.py index ae63cbfb4f9..a8241915737 100644 --- a/tests/amsterdam/eip8024_dupn_swapn_exchange/test_exchange.py +++ b/tests/amsterdam/eip8024_dupn_swapn_exchange/test_exchange.py @@ -25,6 +25,8 @@ pytestmark = pytest.mark.valid_from("EIP8024") +@EIPChecklist.Opcode.Test.StackComplexOperations.StackHeights.Odd() +@EIPChecklist.Opcode.Test.StackComplexOperations.StackHeights.Even() @pytest.mark.parametrize( "n,m", [ @@ -95,6 +97,8 @@ def test_exchange_basic( state_test(pre=pre, post=post, tx=tx) +@EIPChecklist.Opcode.Test.DataPortion.AllZeros() +@EIPChecklist.Opcode.Test.DataPortion.MaxValue() @pytest.mark.parametrize( "immediate", [0, 1, 15, 78, 79, 80, 81, 128, 129, 200, 255], @@ -209,6 +213,7 @@ def test_exchange_preserves_other_items( state_test(pre=pre, post=post, tx=tx) +@EIPChecklist.Opcode.Test.StackUnderflow() @pytest.mark.parametrize( "immediate", # Boundaries of both valid ranges (0x00, 0x51, 0x80, 0xFF) @@ -246,6 +251,31 @@ def test_exchange_stack_underflow( state_test(pre=pre, post=post, tx=tx) +@EIPChecklist.Opcode.Test.StackComplexOperations.StackHeights.Zero() +@EIPChecklist.Opcode.Test.StackUnderflow() +def test_exchange_empty_stack( + pre: Alloc, + state_test: StateTestFiller, +) -> None: + """ + Test EXCHANGE on an empty stack aborts with a stack underflow. + """ + sender = pre.fund_eoa() + + code = Op.SSTORE(0, 1) # leaves the stack empty + code += Op.EXCHANGE[Spec.EXCHANGE_MIN_N, Spec.EXCHANGE_MIN_N + 1] + code += Op.STOP + + contract_address = pre.deploy_contract(code=code) + + tx = Transaction(to=contract_address, sender=sender) + + # Transaction should fail, contract storage unchanged. + post = {contract_address: Account(storage={0: 0})} + + state_test(pre=pre, post=post, tx=tx) + + @EIPChecklist.Opcode.Test.GasUsage.Normal() @EIPChecklist.Opcode.Test.GasUsage.OutOfGasExecution() @EIPChecklist.Opcode.Test.GasUsage.ExtraGas() @@ -341,17 +371,18 @@ def test_endofcode_behavior( state_test(pre=pre, post=post, tx=tx) +@EIPChecklist.Opcode.Test.DataPortion.Jump() @pytest.mark.parametrize( "immediate", [ - # valid immediates (0-81 / 128-255): skipped during JUMPDEST - # analysis, not reachable as jump targets + # valid immediates (0-81 / 128-255): none is 0x5b, so none is a + # jump target (JUMPDEST analysis is unchanged by EIP-8024) 0x00, 0x4F, # 79 0x50, # 80 — POP (valid for EXCHANGE) 0x51, # 81 — MLOAD (valid for EXCHANGE) - # invalid immediates (82-127): not skipped during JUMPDEST - # analysis, only 0x5B (91) is a JUMPDEST + # forbidden immediates (82-127): of these only 0x5B (91) is a + # JUMPDEST byte and hence a valid jump target 0x52, # 82 — MSTORE (first invalid immediate) 0x5A, # 90 — GAS (invalid immediate) 0x5B, # 91 — JUMPDEST, only case where jump succeeds @@ -372,8 +403,9 @@ def test_exchange_jump_to_immediate_byte( """ Test jumping to EXCHANGE immediate byte position. - Valid immediates are skipped (can't jump to them). - Invalid immediates are not skipped - only 0x5B (JUMPDEST) allows jumping. + JUMPDEST analysis is unchanged by EIP-8024, so the immediate byte is + a valid jump target exactly when it is 0x5B, regardless of whether + it is a valid EXCHANGE immediate. """ sender = pre.fund_eoa() @@ -451,6 +483,59 @@ def test_exchange_with_push_sequence( state_test(pre=pre, post=post, tx=tx) +@pytest.mark.parametrize( + "n,m", + [ + pytest.param(1, 2, id="adjacent"), + pytest.param(14, 16, id="max_n"), + pytest.param(1, 29, id="deepest"), + ], +) +def test_exchange_full_stack( + n: int, + m: int, + pre: Alloc, + fork: Fork, + state_test: StateTestFiller, +) -> None: + """ + Test EXCHANGE succeeds on a completely full stack. + + EXCHANGE swaps in place without pushing, so it must work at the + stack limit. The top 30 items hold their 1-indexed position from + the top; EXCHANGE[n, m] swaps positions n + 1 and m + 1, then every + window item is stored and checked. The topmost item is popped + unstored to make room for the SSTORE keys: no valid pair can touch + position 1, and a misplaced swap there still corrupts a checked + slot. + """ + window = Spec.EXCHANGE_MAX_M + 1 # deepest reachable position + + code = Op.PUSH0 * (fork.max_stack_height() - window) + for depth in range(window, 0, -1): + code += Op.PUSH1(depth) + + code += Op.EXCHANGE[n, m] + + # The stack is exactly full: pop position 1 so each SSTORE key + # can be pushed without overflowing. + code += Op.POP + for slot in range(1, window): + code += Op.PUSH1(slot) + Op.SSTORE + code += Op.STOP + + expected = {slot: slot + 1 for slot in range(1, window)} + expected[n], expected[m] = expected[m], expected[n] + + contract_address = pre.deploy_contract(code=code) + tx = Transaction(to=contract_address, sender=pre.fund_eoa()) + + post = {contract_address: Account(storage=expected)} + + state_test(pre=pre, post=post, tx=tx) + + +@EIPChecklist.Opcode.Test.ExceptionalAbort() @pytest.mark.parametrize( "immediate", range(82, 128), # Forbidden range: 0x52-0x7F diff --git a/tests/amsterdam/eip8024_dupn_swapn_exchange/test_execution_contexts.py b/tests/amsterdam/eip8024_dupn_swapn_exchange/test_execution_contexts.py new file mode 100644 index 00000000000..fae04ee996c --- /dev/null +++ b/tests/amsterdam/eip8024_dupn_swapn_exchange/test_execution_contexts.py @@ -0,0 +1,238 @@ +""" +Execution-context tests for EIP-8024 (DUPN, SWAPN, EXCHANGE). + +Each context executes all three opcodes, each moving a distinct planted +marker to the top of the stack. Every snippet plants its marker relative +to the current stack top, so the snippets compose regardless of items +left behind by earlier ones. +""" + +import pytest +from execution_testing import ( + Account, + Alloc, + AuthorizationTuple, + Bytecode, + EIPChecklist, + Op, + StateTestFiller, + Transaction, + compute_create_address, +) + +from ...prague.eip7702_set_code_tx.spec import Spec as Spec7702 +from .spec import ref_spec_8024 + +REFERENCE_SPEC_GIT_PATH = ref_spec_8024.git_path +REFERENCE_SPEC_VERSION = ref_spec_8024.version + +pytestmark = pytest.mark.valid_from("EIP8024") + +DUPN_MARKER = 0xA1 +SWAPN_MARKER = 0xB2 +EXCHANGE_MARKER = 0xC3 + +EXPECTED_STORAGE = { + 0: DUPN_MARKER, + 1: SWAPN_MARKER, + 2: EXCHANGE_MARKER, +} + + +def stack_access_storage_code() -> Bytecode: + """Store each opcode's moved marker at storage keys 0, 1 and 2.""" + return ( + Op.PUSH1(DUPN_MARKER) + + Op.PUSH0 * 16 + + Op.DUPN[17] + + Op.PUSH1(0) + + Op.SSTORE + + Op.PUSH1(SWAPN_MARKER) + + Op.PUSH0 * 17 + + Op.SWAPN[17] + + Op.PUSH1(1) + + Op.SSTORE + + Op.PUSH1(EXCHANGE_MARKER) + + Op.PUSH0 * 2 + + Op.EXCHANGE[1, 2] + + Op.POP + + Op.PUSH1(2) + + Op.SSTORE + + Op.STOP + ) + + +def stack_access_memory_code() -> Bytecode: + """ + Write each opcode's moved marker to memory and return 96 bytes. + + Storage-free, so the code also runs inside STATICCALL frames. + """ + return ( + Op.PUSH1(DUPN_MARKER) + + Op.PUSH0 * 16 + + Op.DUPN[17] + + Op.PUSH1(0) + + Op.MSTORE + + Op.PUSH1(SWAPN_MARKER) + + Op.PUSH0 * 17 + + Op.SWAPN[17] + + Op.PUSH1(32) + + Op.MSTORE + + Op.PUSH1(EXCHANGE_MARKER) + + Op.PUSH0 * 2 + + Op.EXCHANGE[1, 2] + + Op.POP + + Op.PUSH1(64) + + Op.MSTORE + + Op.RETURN(0, 96) + ) + + +@EIPChecklist.Opcode.Test.ExecutionContext.Call() +@EIPChecklist.Opcode.Test.ExecutionContext.Callcode() +@EIPChecklist.Opcode.Test.ExecutionContext.Delegatecall() +@EIPChecklist.Opcode.Test.ExecutionContext.Staticcall() +@pytest.mark.with_all_call_opcodes +def test_stack_access_call_contexts( + state_test: StateTestFiller, + pre: Alloc, + call_opcode: Op, +) -> None: + """ + Test DUPN, SWAPN and EXCHANGE in every call frame type. + + The callee returns each opcode's result through memory, so the check + also holds inside STATICCALL frames where storage writes are banned. + The caller stores the call's success flag and the returned markers. + """ + callee_address = pre.deploy_contract(stack_access_memory_code()) + + caller_code = ( + Op.SSTORE( + 0, + call_opcode(address=callee_address, ret_offset=0, ret_size=96), + ) + + Op.SSTORE(1, Op.MLOAD(0)) + + Op.SSTORE(2, Op.MLOAD(32)) + + Op.SSTORE(3, Op.MLOAD(64)) + ) + caller_address = pre.deploy_contract(caller_code) + + tx = Transaction( + sender=pre.fund_eoa(), + to=caller_address, + ) + + post = { + caller_address: Account( + storage={ + 0: 1, + 1: DUPN_MARKER, + 2: SWAPN_MARKER, + 3: EXCHANGE_MARKER, + }, + ), + } + + state_test(pre=pre, tx=tx, post=post) + + +@EIPChecklist.Opcode.Test.ExecutionContext.SetCode() +def test_stack_access_set_code( + state_test: StateTestFiller, + pre: Alloc, +) -> None: + """ + Test DUPN, SWAPN and EXCHANGE inside a set-code delegated account + (EIP-7702). + """ + auth_signer = pre.fund_eoa(amount=0) + set_code_to_address = pre.deploy_contract(stack_access_storage_code()) + + tx = Transaction( + to=auth_signer, + authorization_list=[ + AuthorizationTuple( + address=set_code_to_address, + nonce=0, + signer=auth_signer, + ), + ], + sender=pre.fund_eoa(), + ) + + post = { + set_code_to_address: Account(storage={}), + auth_signer: Account( + nonce=1, + code=Spec7702.delegation_designation(set_code_to_address), + storage=EXPECTED_STORAGE, + ), + } + + state_test(pre=pre, tx=tx, post=post) + + +@EIPChecklist.Opcode.Test.ExecutionContext.Initcode.Behavior() +@EIPChecklist.Opcode.Test.ExecutionContext.Initcode.Behavior.Tx() +def test_stack_access_initcode_tx( + state_test: StateTestFiller, + pre: Alloc, +) -> None: + """ + Test DUPN, SWAPN and EXCHANGE inside the initcode of a + contract-creating transaction. + """ + init_code = stack_access_storage_code() + sender = pre.fund_eoa() + contract_address = compute_create_address(address=sender, nonce=0) + + tx = Transaction(to=None, data=init_code, sender=sender) + + post = { + contract_address: Account(storage=EXPECTED_STORAGE), + } + + state_test(pre=pre, tx=tx, post=post) + + +@EIPChecklist.Opcode.Test.ExecutionContext.Initcode.Behavior() +@EIPChecklist.Opcode.Test.ExecutionContext.Initcode.Behavior.Opcode() +@pytest.mark.parametrize("opcode", [Op.CREATE, Op.CREATE2]) +def test_stack_access_initcode_create( + state_test: StateTestFiller, + pre: Alloc, + opcode: Op, +) -> None: + """ + Test DUPN, SWAPN and EXCHANGE inside initcode executed via CREATE + and CREATE2. + """ + init_code = stack_access_storage_code() + + factory_code = ( + Op.CALLDATACOPY(offset=0, size=len(init_code)) + + opcode(offset=0, size=len(init_code)) + + Op.STOP + ) + factory_address = pre.deploy_contract(factory_code) + + created_contract_address = compute_create_address( + address=factory_address, + nonce=1, + initcode=init_code, + opcode=opcode, + ) + + tx = Transaction( + to=factory_address, + data=init_code, + sender=pre.fund_eoa(), + ) + + post = { + created_contract_address: Account(storage=EXPECTED_STORAGE), + } + + state_test(pre=pre, tx=tx, post=post) diff --git a/tests/amsterdam/eip8024_dupn_swapn_exchange/test_fork_transition.py b/tests/amsterdam/eip8024_dupn_swapn_exchange/test_fork_transition.py new file mode 100644 index 00000000000..368a26e47de --- /dev/null +++ b/tests/amsterdam/eip8024_dupn_swapn_exchange/test_fork_transition.py @@ -0,0 +1,146 @@ +"""Fork-transition tests for EIP-8024 (DUPN, SWAPN, EXCHANGE).""" + +import pytest +from execution_testing import ( + Account, + Alloc, + Block, + BlockchainTestFiller, + Bytecode, + EIPChecklist, + Op, + Transaction, +) + +from .spec import ref_spec_8024 + +REFERENCE_SPEC_GIT_PATH = ref_spec_8024.git_path +REFERENCE_SPEC_VERSION = ref_spec_8024.version + +FORK_TIMESTAMP = 15_000 + + +def marker_storing_code(opcode: Op) -> tuple[Bytecode, int]: + """ + Build code that stores an opcode-specific marker at storage key NUMBER. + + Each snippet plants the marker at the exact stack depth the opcode + accesses, executes the opcode, and stores the resulting stack top so + the write only happens if the opcode moved the marker as specified. + """ + if opcode == Op.DUPN: + marker = 0xA1 + code = Op.PUSH1(marker) + Op.PUSH0 * 16 + Op.DUPN[17] + elif opcode == Op.SWAPN: + marker = 0xB2 + code = Op.PUSH1(marker) + Op.PUSH0 * 17 + Op.SWAPN[17] + else: + marker = 0xC3 + code = Op.PUSH1(marker) + Op.PUSH0 * 2 + Op.EXCHANGE[1, 2] + Op.POP + return code + Op.NUMBER + Op.SSTORE + Op.STOP, marker + + +@EIPChecklist.Opcode.Test.ForkTransition.Invalid() +@EIPChecklist.Opcode.Test.ForkTransition.At() +@pytest.mark.valid_at_transition_to("EIP8024") +@pytest.mark.parametrize("opcode", [Op.DUPN, Op.SWAPN, Op.EXCHANGE]) +def test_opcode_at_fork_transition( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + opcode: Op, +) -> None: + """ + Test DUPN/SWAPN/EXCHANGE behavior across the EIP-8024 fork transition. + + Before the fork, opcodes 0xE6-0xE8 are undefined: execution halts + with an invalid-opcode exception and no storage write happens. + + From the fork onward, the opcode executes and stores its marker. + Storage is keyed by block NUMBER so each block's outcome is + independently visible in the final post-state: + + * block 1 (pre-fork): slot 1 stays 0 — execution halted. + * block 2 (transition): slot 2 == marker. + * block 3 (post-fork): slot 3 == marker. + """ + sender = pre.fund_eoa() + code, marker = marker_storing_code(opcode) + contract = pre.deploy_contract(code) + + blocks = [ + Block( + timestamp=ts, + txs=[Transaction(sender=sender, to=contract)], + ) + for ts in ( + FORK_TIMESTAMP - 1, + FORK_TIMESTAMP, + FORK_TIMESTAMP + 1, + ) + ] + + post = { + contract: Account( + storage={ + 1: 0, + 2: marker, + 3: marker, + }, + ), + } + + blockchain_test(pre=pre, blocks=blocks, post=post) + + +@EIPChecklist.Opcode.Test.ForkTransition.At() +@EIPChecklist.Opcode.Test.DataPortion.Jump() +@pytest.mark.valid_at_transition_to("EIP8024") +@pytest.mark.parametrize("opcode", [Op.DUPN, Op.SWAPN, Op.EXCHANGE]) +def test_jumpdest_in_immediate_at_fork_transition( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + opcode: Op, +) -> None: + """ + Test a JUMPDEST inside an EIP-8024 immediate across the transition. + + JUMPDEST analysis is unchanged by EIP-8024, so the 0x5b byte in the + opcode's would-be immediate is a valid jump destination both before + and after the fork. The jump skips the opcode byte itself, so every + block stores the marker at its NUMBER-keyed slot. + """ + sender = pre.fund_eoa() + marker = 0xD4 + + # 00 PUSH1 0x04 + # 02 JUMP + # 03 <opcode> + # 04 JUMPDEST (the would-be immediate) + code = Op.PUSH1(4) + Op.JUMP + opcode[b"\x5b"] + code += Op.PUSH1(marker) + Op.NUMBER + Op.SSTORE + Op.STOP + + contract = pre.deploy_contract(code) + + blocks = [ + Block( + timestamp=ts, + txs=[Transaction(sender=sender, to=contract)], + ) + for ts in ( + FORK_TIMESTAMP - 1, + FORK_TIMESTAMP, + FORK_TIMESTAMP + 1, + ) + ] + + post = { + contract: Account( + storage={ + 1: marker, + 2: marker, + 3: marker, + }, + ), + } + + blockchain_test(pre=pre, blocks=blocks, post=post) diff --git a/tests/amsterdam/eip8024_dupn_swapn_exchange/test_swapn.py b/tests/amsterdam/eip8024_dupn_swapn_exchange/test_swapn.py index adef348ccf7..4ba5404c66f 100644 --- a/tests/amsterdam/eip8024_dupn_swapn_exchange/test_swapn.py +++ b/tests/amsterdam/eip8024_dupn_swapn_exchange/test_swapn.py @@ -25,6 +25,9 @@ pytestmark = pytest.mark.valid_from("EIP8024") +@EIPChecklist.Opcode.Test.StackComplexOperations.StackHeights.Odd() +@EIPChecklist.Opcode.Test.StackComplexOperations.StackHeights.Even() +@EIPChecklist.Opcode.Test.StackComplexOperations.DataPortionVariables.Bottom() @pytest.mark.parametrize( "stack_index", [17, 18, 32, 64, 107, 108, 200, 235], @@ -86,6 +89,8 @@ def test_swapn_basic( state_test(pre=pre, post=post, tx=tx) +@EIPChecklist.Opcode.Test.DataPortion.AllZeros() +@EIPChecklist.Opcode.Test.DataPortion.MaxValue() @pytest.mark.parametrize( "immediate", [0, 45, 90, 128, 200, 255], @@ -189,6 +194,7 @@ def test_swapn_preserves_other_stack_items( state_test(pre=pre, post=post, tx=tx) +@EIPChecklist.Opcode.Test.StackUnderflow() def test_swapn_stack_underflow( pre: Alloc, state_test: StateTestFiller, @@ -216,6 +222,31 @@ def test_swapn_stack_underflow( state_test(pre=pre, post=post, tx=tx) +@EIPChecklist.Opcode.Test.StackComplexOperations.StackHeights.Zero() +@EIPChecklist.Opcode.Test.StackUnderflow() +def test_swapn_empty_stack( + pre: Alloc, + state_test: StateTestFiller, +) -> None: + """ + Test SWAPN on an empty stack aborts with a stack underflow. + """ + sender = pre.fund_eoa() + + code = Op.SSTORE(0, 1) # leaves the stack empty + code += Op.SWAPN[Spec.MIN_STACK_INDEX] + code += Op.STOP + + contract_address = pre.deploy_contract(code=code) + + tx = Transaction(to=contract_address, sender=sender) + + # Transaction should fail, contract storage unchanged. + post = {contract_address: Account(storage={0: 0})} + + state_test(pre=pre, post=post, tx=tx) + + @EIPChecklist.Opcode.Test.GasUsage.Normal() @EIPChecklist.Opcode.Test.GasUsage.OutOfGasExecution() @EIPChecklist.Opcode.Test.GasUsage.ExtraGas() @@ -264,6 +295,7 @@ def test_swapn_gas_cost_boundary( state_test(pre=pre, post=post, tx=tx) +@EIPChecklist.Opcode.Test.ExceptionalAbort() @pytest.mark.parametrize( "invalid_immediate", list(range(91, 128)), # 0x5b to 0x7f (JUMPDEST and PUSH opcodes) @@ -353,6 +385,7 @@ def test_endofcode_behavior( state_test(pre=pre, post=post, tx=tx) +@EIPChecklist.Opcode.Test.DataPortion.Jump() def test_swapn_jump_to_immediate_byte_0x5b_succeeds( pre: Alloc, state_test: StateTestFiller, @@ -389,6 +422,7 @@ def test_swapn_jump_to_immediate_byte_0x5b_succeeds( state_test(pre=pre, post=post, tx=tx) +@EIPChecklist.Opcode.Test.DataPortion.Jump() def test_swapn_jump_to_valid_immediate_fails( pre: Alloc, state_test: StateTestFiller, @@ -399,7 +433,8 @@ def test_swapn_jump_to_valid_immediate_fails( Bytecode: PUSH1(4) JUMP SWAPN[0x00] Hex: 6004 56 e7 00 Position 4 contains 0x00 which is a VALID immediate for SWAPN. - Valid immediates are skipped in JUMPDEST analysis, so jump fails. + JUMPDEST analysis is unchanged by EIP-8024: position 4 holds 0x00, + not 0x5b, so it is not a valid jump target and the jump fails. """ sender = pre.fund_eoa() @@ -475,3 +510,57 @@ def test_swapn_with_dup1_and_push( post = {contract_address: Account(storage=expected_storage)} state_test(pre=pre, post=post, tx=tx) + + +@EIPChecklist.Opcode.Test.StackComplexOperations.DataPortionVariables.Top() +@EIPChecklist.Opcode.Test.StackComplexOperations.DataPortionVariables.Middle() +@pytest.mark.parametrize( + "stack_index", + [17, 126, 235], + ids=lambda x: f"swapn_full_stack_{x}", +) +def test_swapn_full_stack( + stack_index: int, + pre: Alloc, + fork: Fork, + state_test: StateTestFiller, +) -> None: + """ + Test SWAPN succeeds on a completely full stack. + + SWAPN swaps in place without pushing, so it must work at the stack + limit. The top marker is swapped down to position `stack_index + 1`; + popping `stack_index` items then exposes it. If a faulty + implementation had not swapped, the popped-to item would hold the + planted deep marker instead, so either direction of failure is + visible in storage. + """ + sender = pre.fund_eoa() + + top_marker = 0xAAAA + deep_marker = 0xBBBB + + # Full stack, top-down: the top marker at position 1, the deep + # marker at the swap target, position stack_index + 1. + stack = [0] * fork.max_stack_height() + stack[0] = top_marker + stack[stack_index] = deep_marker + + code = Bytecode() + for value in reversed(stack): + code += Op.PUSH2(value) if value else Op.PUSH0 + + code += Op.SWAPN[stack_index] + + # Pop down to the swap target and store the item now there. + code += Op.POP * stack_index + code += Op.PUSH1(0) + Op.SSTORE + code += Op.STOP + + contract_address = pre.deploy_contract(code=code, storage={0: 0xBA5E}) + + tx = Transaction(to=contract_address, sender=sender) + + post = {contract_address: Account(storage={0: top_marker})} + + state_test(pre=pre, post=post, tx=tx) From dfb15766f3b1eba928973e7f32df1f9a5cae87f9 Mon Sep 17 00:00:00 2001 From: Mario Vega <marioevz@gmail.com> Date: Thu, 30 Jul 2026 13:59:25 -0600 Subject: [PATCH 186/233] feat(ci): Backport CI Workflow (#3262) * feat(ci): backport bot * fix(ci): Review comments * fix(ci): Review comments --- .github/workflows/backport.yaml | 64 +++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 .github/workflows/backport.yaml diff --git a/.github/workflows/backport.yaml b/.github/workflows/backport.yaml new file mode 100644 index 00000000000..b1d5de0ccea --- /dev/null +++ b/.github/workflows/backport.yaml @@ -0,0 +1,64 @@ +name: Backport merged PR + +# Cherry-picks a merged PR onto another long-lived branch when it carries a +# `backport <branch>` label (e.g. `backport benchmarks/amsterdam`). On a clean +# cherry-pick it opens a PR against the target branch; on conflict it aborts +# and comments on the source PR with manual steps — it never force-pushes nor +# leaves a partially-applied branch behind. +# +# The label may be added before or after merge; backporting only happens once +# the PR is actually merged. + +on: + pull_request_target: + types: [closed, labeled] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number }} + cancel-in-progress: false + +permissions: + contents: write + pull-requests: write + +jobs: + backport: + name: Backport + # Early-exit before the (full-history) checkout unless the PR is merged + # AND carries a `backport …` label; otherwise this ran on every merged PR. + # Matches the guard recommended in the action's README — the leading quote + # in '"backport ' anchors the match to a label-name prefix. + if: >- + github.event.pull_request.merged == true && + contains(toJSON(github.event.pull_request.labels.*.name), '"backport ') + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + token: ${{ secrets.BACKPORT_TOKEN || secrets.GITHUB_TOKEN }} + + - name: Create backport PR + uses: korthout/backport-action@2e830a1d0b8269505846ddd407a70876913ad1f8 # v4.6.0 + with: + # `backport <target-branch>`, e.g. `backport benchmarks/amsterdam`. + label_pattern: '^backport ([^ ]+)$' + # Only single-commit (squash-merged) PRs cherry-pick cleanly; skip + # PRs that landed as merge commits rather than failing the run. + merge_commits: skip + pull_title: '${pull_title} [backport ${target_branch}]' + pull_description: | + Automated backport of #${pull_number} to `${target_branch}`. + # Assign the original author so they shepherd the backport (best + # effort — a non-assignable external author is simply skipped). + add_author_as_assignee: true + # Carry the repo's `A-<area>`/`C-<type>` labels over to the backport + # PR. The action excludes labels matching `label_pattern`, so the + # `backport …` trigger label is never copied (no re-backport loop). + copy_labels_pattern: '^[AC]-' + # A PAT or GitHub App token stored as the `BACKPORT_TOKEN` secret + # lets CI run on the backport PR; the default GITHUB_TOKEN cannot + # trigger downstream workflows. Falls back to GITHUB_TOKEN when the + # secret is absent (PR is still created, CI must be re-triggered). + github_token: ${{ secrets.BACKPORT_TOKEN || secrets.GITHUB_TOKEN }} From 0da438753ef48d4f9722ff24b81e2b0ff501d9d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=94=A1=E4=BD=B3=E8=AA=A0=20Louis=20Tsai?= <72684086+LouisTsai-Csie@users.noreply.github.com> Date: Fri, 31 Jul 2026 05:26:04 +0800 Subject: [PATCH 187/233] feat(test-benchmark): add missing glam eip to benchmark (#3267) * feat: add missing glam eip to benchmark * feat(tests): Add initcode jumpdest analysis in swapn, dupn, exchange --------- Co-authored-by: marioevz <marioevz@gmail.com> --- .../compute/instruction/test_block_context.py | 11 +++ .../compute/instruction/test_stack.py | 71 +++++++++++++++++++ .../compute/scenario/test_mix_operations.py | 3 + 3 files changed, 85 insertions(+) diff --git a/tests/benchmark/compute/instruction/test_block_context.py b/tests/benchmark/compute/instruction/test_block_context.py index d31dba05c0a..2c7aedc7ad4 100644 --- a/tests/benchmark/compute/instruction/test_block_context.py +++ b/tests/benchmark/compute/instruction/test_block_context.py @@ -11,6 +11,7 @@ - CHAINID - BASEFEE - BLOBBASEFEE +- SLOTNUM """ import pytest @@ -79,3 +80,13 @@ def test_blockhash( attack_block=Op.BLOCKHASH(block_number) ), ) + + +@pytest.mark.repricing +@pytest.mark.valid_from("Amsterdam") +def test_slotnum(benchmark_test: BenchmarkTestFiller) -> None: + """Benchmark SLOTNUM instruction.""" + benchmark_test( + target_opcode=Op.SLOTNUM, + code_generator=ExtCallGenerator(attack_block=Op.SLOTNUM), + ) diff --git a/tests/benchmark/compute/instruction/test_stack.py b/tests/benchmark/compute/instruction/test_stack.py index 5744b198fec..bab2e57e257 100644 --- a/tests/benchmark/compute/instruction/test_stack.py +++ b/tests/benchmark/compute/instruction/test_stack.py @@ -6,6 +6,9 @@ - PUSHx - DUPx - SWAPx +- DUPN +- SWAPN +- EXCHANGE """ import pytest @@ -139,3 +142,71 @@ def test_push( attack_block=opcode[1] if opcode.has_data_portion() else opcode ), ) + + +@pytest.mark.repricing +@pytest.mark.valid_from("Amsterdam") +@pytest.mark.parametrize( + "stack_index", + [17, 107, 235], + ids=lambda x: f"stack_{x}", +) +def test_dupn( + benchmark_test: BenchmarkTestFiller, + stack_index: int, +) -> None: + """Benchmark DUPN instruction.""" + opcode = Op.DUPN[stack_index] + benchmark_test( + target_opcode=Op.DUPN, + code_generator=ExtCallGenerator( + setup=Op.PUSH0 * opcode.min_stack_height, + attack_block=opcode, + ), + ) + + +@pytest.mark.repricing +@pytest.mark.valid_from("Amsterdam") +@pytest.mark.parametrize( + "stack_index", + [17, 107, 235], + ids=lambda x: f"stack_{x}", +) +def test_swapn( + benchmark_test: BenchmarkTestFiller, + stack_index: int, +) -> None: + """Benchmark SWAPN instruction.""" + opcode = Op.SWAPN[stack_index] + benchmark_test( + target_opcode=Op.SWAPN, + code_generator=JumpLoopGenerator( + attack_block=opcode, setup=Op.PUSH0 * opcode.min_stack_height + ), + ) + + +@pytest.mark.repricing +@pytest.mark.valid_from("Amsterdam") +@pytest.mark.parametrize( + "n,m", + [ + pytest.param(1, 2, id="n_1_m_2"), + pytest.param(1, 29, id="n_1_m_29"), + pytest.param(14, 16, id="n_14_m_16"), + ], +) +def test_exchange( + benchmark_test: BenchmarkTestFiller, + n: int, + m: int, +) -> None: + """Benchmark EXCHANGE instruction.""" + opcode = Op.EXCHANGE[n, m] + benchmark_test( + target_opcode=Op.EXCHANGE, + code_generator=JumpLoopGenerator( + attack_block=opcode, setup=Op.PUSH0 * opcode.min_stack_height + ), + ) diff --git a/tests/benchmark/compute/scenario/test_mix_operations.py b/tests/benchmark/compute/scenario/test_mix_operations.py index bd534a0d356..aa3db4502db 100644 --- a/tests/benchmark/compute/scenario/test_mix_operations.py +++ b/tests/benchmark/compute/scenario/test_mix_operations.py @@ -19,6 +19,9 @@ Op.PUSH2[bytes(Op.JUMPDEST + Op.JUMPDEST)], Op.PUSH1[bytes(Op.JUMPDEST)] + Op.JUMPDEST, Op.PUSH2[bytes(Op.JUMPDEST + Op.JUMPDEST)] + Op.JUMPDEST, + Op.SWAPN[bytes(Op.JUMPDEST)], + Op.DUPN[bytes(Op.JUMPDEST)], + Op.EXCHANGE[bytes(Op.JUMPDEST)], ], ids=lambda x: x.hex(), ) From a4e9212e4abe568cd32507191689c054e6e41331 Mon Sep 17 00:00:00 2001 From: Mario Vega <marioevz@gmail.com> Date: Fri, 31 Jul 2026 01:43:53 -0600 Subject: [PATCH 188/233] fix(ci): Rename backports branches (#3268) --- .github/workflows/backport.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/backport.yaml b/.github/workflows/backport.yaml index b1d5de0ccea..66a465ca22a 100644 --- a/.github/workflows/backport.yaml +++ b/.github/workflows/backport.yaml @@ -11,7 +11,7 @@ name: Backport merged PR on: pull_request_target: - types: [closed, labeled] + types: [ closed, labeled ] concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number }} @@ -47,6 +47,9 @@ jobs: # Only single-commit (squash-merged) PRs cherry-pick cleanly; skip # PRs that landed as merge commits rather than failing the run. merge_commits: skip + # Create the working branch under `backports/**`: it is exempt from + # the `branch-only-forks` creation rule. + branch_name: 'backports/${pull_number}-to-${target_branch}' pull_title: '${pull_title} [backport ${target_branch}]' pull_description: | Automated backport of #${pull_number} to `${target_branch}`. From a2571e49287644debf6bcaaad35aa8bb0f084de9 Mon Sep 17 00:00:00 2001 From: spencer <spencer.taylor-brown@ethereum.org> Date: Fri, 31 Jul 2026 11:52:40 +0200 Subject: [PATCH 189/233] fix(spec-specs,tests): reprice EIP-8038 access-list costs to cold minus warm (#3271) --- .../execution_testing/forks/forks/eips/amsterdam/eip_8038.py | 4 ++-- src/ethereum/forks/amsterdam/vm/gas.py | 4 ++-- .../test_access_list_gas.py | 4 ++-- .../test_exact_balance_no_fallback.py | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8038.py b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8038.py index 583ca719662..6129ea4d768 100644 --- a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8038.py +++ b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8038.py @@ -67,8 +67,8 @@ def gas_costs(cls) -> GasCosts: ACCOUNT_WRITE=account_write, CALL_VALUE=account_write + 2_300, # ACCOUNT_WRITE + CALL_STIPEND REFUND_STORAGE_CLEAR=12_480, - TX_ACCESS_LIST_ADDRESS=3_000, - TX_ACCESS_LIST_STORAGE_KEY=3_000, + TX_ACCESS_LIST_ADDRESS=cold_account_access - warm_access, + TX_ACCESS_LIST_STORAGE_KEY=cold_storage_access - warm_access, BLOCK_ACCESS_LIST_ITEM=2000, STORAGE_SET=storage_write, OPCODE_CREATE_BASE=create_access, diff --git a/src/ethereum/forks/amsterdam/vm/gas.py b/src/ethereum/forks/amsterdam/vm/gas.py index 5ff707b18c7..4feb2d11509 100644 --- a/src/ethereum/forks/amsterdam/vm/gas.py +++ b/src/ethereum/forks/amsterdam/vm/gas.py @@ -140,8 +140,8 @@ class GasCosts: TX_VALUE_COST: Final[Uint] = Uint(6000) TX_DATA_TOKEN_STANDARD: Final[Uint] = Uint(4) TX_DATA_TOKEN_FLOOR: Final[Uint] = Uint(16) - TX_ACCESS_LIST_ADDRESS: Final[Uint] = COLD_ACCOUNT_ACCESS - TX_ACCESS_LIST_STORAGE_KEY: Final[Uint] = COLD_STORAGE_ACCESS + TX_ACCESS_LIST_ADDRESS: Final[Uint] = COLD_ACCOUNT_ACCESS - WARM_ACCESS + TX_ACCESS_LIST_STORAGE_KEY: Final[Uint] = COLD_STORAGE_ACCESS - WARM_ACCESS # Authorization AUTH_TUPLE_BYTES: Final[Uint] = Uint(101) diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_access_list_gas.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_access_list_gas.py index f744952fe26..06eebb69b49 100644 --- a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_access_list_gas.py +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_access_list_gas.py @@ -4,8 +4,8 @@ Covers the EIP-8038 access-list repricing: * The intrinsic surcharge per access-list entry is - ``TX_ACCESS_LIST_ADDRESS`` (3000) per address and - ``TX_ACCESS_LIST_STORAGE_KEY`` (3000) per storage key, isolated from + ``TX_ACCESS_LIST_ADDRESS`` per address and + ``TX_ACCESS_LIST_STORAGE_KEY`` per storage key, isolated from the EIP-7981 calldata-floor tokens that the Amsterdam intrinsic calculator also charges on access-list bytes. * A storage slot named in the access list is *warm* on its first runtime diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_exact_balance_no_fallback.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_exact_balance_no_fallback.py index 0ef7c1f9459..5b92e45a96c 100644 --- a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_exact_balance_no_fallback.py +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_exact_balance_no_fallback.py @@ -82,8 +82,8 @@ def test_access_list_no_fallback( Reject an access-list transaction whose ``gas_limit`` is one gas below the Amsterdam intrinsic. - EIP-8038 raises ``TX_ACCESS_LIST_ADDRESS`` (2400 -> 3000) and - ``TX_ACCESS_LIST_STORAGE_KEY`` (1900 -> 3000). A client reusing the + EIP-8038 raises ``TX_ACCESS_LIST_ADDRESS`` (2400 -> 2900) and + ``TX_ACCESS_LIST_STORAGE_KEY`` (1900 -> 2900). A client reusing the old per-address/per-key constants would compute an intrinsic smaller by ``num_addresses * addr_delta + num_keys * key_delta``; with the sender funded to the wei, that fallback must not slip through. From 6074ece9dc66110e757f1bcc3fe714ce63b075ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=94=A1=E4=BD=B3=E8=AA=A0=20Louis=20Tsai?= <72684086+LouisTsai-Csie@users.noreply.github.com> Date: Sat, 1 Aug 2026 01:15:07 +0800 Subject: [PATCH 190/233] feat(test-benchmark): enhance worst case coverage (#3274) Co-authored-by: Mario Vega <marioevz@gmail.com> (cherry picked from commit c5d5c04884e40f20e5acb60b8a9e314a227097bb) --- .../compute/instruction/test_bitwise.py | 34 ++++++ .../compute/instruction/test_stack.py | 43 +++++++ .../compute/instruction/test_system.py | 105 +++++++++++++++++- .../compute/precompile/test_modexp.py | 45 ++++++++ 4 files changed, 224 insertions(+), 3 deletions(-) diff --git a/tests/benchmark/compute/instruction/test_bitwise.py b/tests/benchmark/compute/instruction/test_bitwise.py index 044613d5f42..c75a689c87d 100644 --- a/tests/benchmark/compute/instruction/test_bitwise.py +++ b/tests/benchmark/compute/instruction/test_bitwise.py @@ -200,6 +200,40 @@ def select_shift_amount( ) +@pytest.mark.parametrize( + "opcode,initial_value", + [ + pytest.param(Op.SHL, 2**256 - 1), + pytest.param(Op.SHR, 2**256 - 1), + pytest.param(Op.SAR, 2**255 - 1), + pytest.param(Op.SAR, 2**256 - 1), + ], +) +@pytest.mark.parametrize( + "shift", + [ + pytest.param(256, id="word size"), + pytest.param(2**255, id="unrepresentable as a bit index"), + ], +) +def test_shifts_beyond_word_size( + benchmark_test: BenchmarkTestFiller, + opcode: Op, + shift: int, + initial_value: int, +) -> None: + """ + Benchmark shifts by at least the 256-bit word size. + """ + benchmark_test( + target_opcode=opcode, + code_generator=JumpLoopGenerator( + setup=Op.PUSH32[initial_value], + attack_block=Op.PUSH32[shift] + opcode, + ), + ) + + @pytest.mark.repricing @pytest.mark.valid_from("Osaka") def test_clz_same(benchmark_test: BenchmarkTestFiller) -> None: diff --git a/tests/benchmark/compute/instruction/test_stack.py b/tests/benchmark/compute/instruction/test_stack.py index bab2e57e257..470a83d008b 100644 --- a/tests/benchmark/compute/instruction/test_stack.py +++ b/tests/benchmark/compute/instruction/test_stack.py @@ -13,10 +13,12 @@ import pytest from execution_testing import ( + Alloc, BenchmarkTestFiller, ExtCallGenerator, JumpLoopGenerator, Op, + OpcodeTarget, ) @@ -144,6 +146,47 @@ def test_push( ) +@pytest.mark.parametrize( + "opcode,present_data_bytes", + [ + pytest.param(Op.PUSH1, 0, id="PUSH1 with no data"), + pytest.param(Op.PUSH2, 0, id="PUSH2 with no data"), + pytest.param(Op.PUSH2, 1, id="PUSH2 with half its data"), + pytest.param(Op.PUSH32, 0, id="PUSH32 with no data"), + pytest.param(Op.PUSH32, 16, id="PUSH32 with half its data"), + pytest.param(Op.PUSH32, 31, id="PUSH32 one byte short"), + ], +) +def test_push_truncated_data( + benchmark_test: BenchmarkTestFiller, + pre: Alloc, + opcode: Op, + present_data_bytes: int, +) -> None: + """ + Benchmark a PUSH whose data portion runs past the end of the code. + """ + target_contract = pre.deploy_contract( + code=bytes([opcode.int()]) + bytes(present_data_bytes) + ) + + benchmark_test( + target_opcode=OpcodeTarget(f"{opcode} truncated", Op.STATICCALL), + code_generator=JumpLoopGenerator( + attack_block=Op.POP( + Op.STATICCALL( + gas=Op.GAS, + address=target_contract, + args_offset=Op.PUSH0, + args_size=Op.PUSH0, + ret_offset=Op.PUSH0, + ret_size=Op.PUSH0, + ) + ) + ), + ) + + @pytest.mark.repricing @pytest.mark.valid_from("Amsterdam") @pytest.mark.parametrize( diff --git a/tests/benchmark/compute/instruction/test_system.py b/tests/benchmark/compute/instruction/test_system.py index 02cbbbfcd83..0d52c2783d5 100644 --- a/tests/benchmark/compute/instruction/test_system.py +++ b/tests/benchmark/compute/instruction/test_system.py @@ -37,6 +37,8 @@ compute_create_address, ) +from tests.frontier.identity_precompile.spec import Spec as IdentitySpec + @pytest.mark.parametrize("transfer_amount", [0, 1]) @pytest.mark.parametrize("opcode", [Op.CALL, Op.CALLCODE]) @@ -162,6 +164,48 @@ def access_list( ) +@pytest.mark.parametrize( + "opcode,value", + [ + pytest.param(Op.CALL, 0, id="CALL"), + pytest.param(Op.CALL, 1, id="CALL with value"), + pytest.param(Op.CALLCODE, 0, id="CALLCODE"), + pytest.param(Op.CALLCODE, 1, id="CALLCODE with value"), + pytest.param(Op.DELEGATECALL, None, id="DELEGATECALL"), + pytest.param(Op.STATICCALL, None, id="STATICCALL"), + ], +) +def test_call_opcodes_to_precompile( + benchmark_test: BenchmarkTestFiller, + opcode: Op, + value: int | None, +) -> None: + """Benchmark every call opcode dispatching to a precompile.""" + value_kwarg: dict[str, Any] = {} + if value is not None: + value_kwarg = {"value": value} + + attack_block = Op.POP( + opcode( + gas=Op.GAS, + address=IdentitySpec.IDENTITY, + args_offset=Op.PUSH0, + args_size=Op.PUSH0, + ret_offset=Op.PUSH0, + ret_size=Op.PUSH0, + **value_kwarg, + ) + ) + + benchmark_test( + target_opcode=opcode, + code_generator=JumpLoopGenerator( + attack_block=attack_block, + contract_balance=10**9 if value else 0, + ), + ) + + @pytest.mark.repricing(max_code_size_ratio=0) @pytest.mark.parametrize( "opcode", @@ -443,6 +487,51 @@ def test_creates_collisions( ) +@pytest.mark.parametrize( + "opcode", + [ + Op.CREATE, + Op.CREATE2, + ], +) +@pytest.mark.parametrize( + "revert_size", + [ + pytest.param(0, id="empty revert data"), + pytest.param(32, id="32 bytes of revert data"), + pytest.param(1024, id="1KiB of revert data"), + ], +) +def test_creates_reverting_initcode( + benchmark_test: BenchmarkTestFiller, + opcode: Op, + revert_size: int, +) -> None: + """Benchmark CREATE and CREATE2 whose initcode reverts.""" + initcode = Op.REVERT(0, revert_size) + + salt_kwarg: dict[str, Any] = {} + if opcode == Op.CREATE2: + salt_kwarg = {"salt": 0} + + attack_block = Op.POP( + opcode( + value=0, + offset=32 - len(initcode), + size=len(initcode), + **salt_kwarg, + ) + ) + + benchmark_test( + target_opcode=opcode, + code_generator=JumpLoopGenerator( + setup=Op.MSTORE(0, initcode.hex()), + attack_block=attack_block, + ), + ) + + @pytest.mark.parametrize( "opcode", [Op.RETURN, Op.REVERT], @@ -761,16 +850,25 @@ def calldata(iteration_count: int, start_iteration: int) -> bytes: ) -@pytest.mark.parametrize("value_bearing", [True, False]) +@pytest.mark.parametrize( + "value_bearing,beneficiary_is_self", + [ + pytest.param(False, False, id="without value"), + pytest.param(True, False, id="with value moved to the creator"), + pytest.param(True, True, id="with value burnt to self"), + ], +) def test_selfdestruct_initcode( benchmark_test: BenchmarkTestFiller, pre: Alloc, value_bearing: bool, + beneficiary_is_self: bool, fork: Fork, gas_benchmark_value: int, ) -> None: """Benchmark SELFDESTRUCT instruction executed in initcode.""" - initcode = Op.SELFDESTRUCT(Op.CALLER, address_warm=True) + beneficiary = Op.ADDRESS if beneficiary_is_self else Op.CALLER + initcode = Op.SELFDESTRUCT(beneficiary, address_warm=True) # CALLDATA[0:32] = iteration_count setup = ( @@ -836,9 +934,10 @@ def calldata(iteration_count: int, start_iteration: int) -> bytes: total_gas_cost = sum(tx.gas_cost for tx in exec_txs) + returned_to_creator = value_bearing and not beneficiary_is_self post = { attack_code_address: Account( - balance=num_iterations if value_bearing else 0 + balance=num_iterations if returned_to_creator else 0 ) } diff --git a/tests/benchmark/compute/precompile/test_modexp.py b/tests/benchmark/compute/precompile/test_modexp.py index 384f25450f1..d6074157dba 100644 --- a/tests/benchmark/compute/precompile/test_modexp.py +++ b/tests/benchmark/compute/precompile/test_modexp.py @@ -514,6 +514,51 @@ def test_modexp( ) +@pytest.mark.valid_from("Osaka") +@pytest.mark.parametrize( + "base_length,exponent_length,modulus_length", + [ + pytest.param(Spec.MAX_LENGTH_BYTES + 1, 1, 1, id="oversized base"), + pytest.param(1, Spec.MAX_LENGTH_BYTES + 1, 1, id="oversized exponent"), + pytest.param(1, 1, Spec.MAX_LENGTH_BYTES + 1, id="oversized modulus"), + ], +) +def test_modexp_length_above_upper_bound( + benchmark_test: BenchmarkTestFiller, + base_length: int, + exponent_length: int, + modulus_length: int, +) -> None: + """ + Benchmark MODEXP rejecting a length above its EIP-7823 upper bound. + """ + mod_exp_input = ModExpInput( + base=b"\x01", + exponent=b"\x01", + modulus=b"\x01", + declared_base_length=base_length, + declared_exponent_length=exponent_length, + declared_modulus_length=modulus_length, + ) + + attack_block = Op.POP( + Op.STATICCALL( + gas=Spec7883.calculate_gas_cost(mod_exp_input), + address=Spec.MODEXP_ADDRESS, + args_size=Op.CALLDATASIZE, + ), + ) + + benchmark_test( + target_opcode=Precompile.MODEXP, + code_generator=JumpLoopGenerator( + setup=Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE), + attack_block=attack_block, + tx_kwargs={"data": bytes(mod_exp_input)}, + ), + ) + + @pytest.mark.repricing @pytest.mark.parametrize( "mod_exp_input", From c17999c02b7258c5c731688455dce914275423c4 Mon Sep 17 00:00:00 2001 From: spencer <spencer.taylor-brown@ethereum.org> Date: Fri, 31 Jul 2026 19:36:58 +0200 Subject: [PATCH 191/233] feat(ci): fill benchmark tests at 1M gas on every PR (#3273) * fix(tooling): accumulate t8n opcode counts across all transactions in the run * feat(ci): fill benchmark tests at 1M gas on every PR --- .github/workflows/test.yaml | 13 ++++++++++++ Justfile | 21 +++++++++++++++++++ docs/getting_started/verifying_changes.md | 1 + .../evm_tools/t8n/evm_trace/count.py | 15 ++++--------- 4 files changed, 39 insertions(+), 11 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 9060ce9c2ea..e26aeddaf57 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -110,6 +110,19 @@ jobs: flags: unittests token: ${{ secrets.CODECOV_TOKEN }} + fill-benchmark: + runs-on: [self-hosted-ghr, size-xl-x64] + needs: static + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: ./.github/actions/setup-uv + with: + python-version: "3.14" + - name: Run fill-benchmark + run: just fill-benchmark + env: + PYTEST_XDIST_AUTO_NUM_WORKERS: auto + fill-pypy: runs-on: [self-hosted-ghr, size-xl-x64] needs: static diff --git a/Justfile b/Justfile index 2efd7b3614d..01467ef46fe 100644 --- a/Justfile +++ b/Justfile @@ -247,6 +247,27 @@ test-ci-scripts *args: # --- Benchmarks --- +# test_return_revert is excluded: its max-size INVALID-padded callees make +# EELS re-scan jumpdests on every call (100-270s per test, ~60% of the +# suite's runtime); the geth-backed benchmarks/** CI still fills it. +# Fill benchmark tests at 1M gas with the in-repo EELS t8n +[group('benchmark tests')] +fill-benchmark *args: (_tmp-logs "fill-benchmark") + uv run fill \ + --gas-benchmark-values 1 \ + --fork "{{ latest_fork }}" \ + -m "not slow and not derived_test" \ + -k "not test_return_revert" \ + -n {{ xdist_workers }} --dist=loadgroup \ + --skip-index \ + --output="{{ output_dir }}/fill-benchmark/fixtures" \ + --basetemp="{{ output_dir }}/fill-benchmark/tmp" \ + --log-to "{{ output_dir }}/fill-benchmark/logs" \ + --clean \ + --durations=20 \ + "$@" \ + tests/benchmark/compute + # Smoke-test benchmark tests: fill blockchain_test fixtures, then verify against EELS. [group('benchmark tests')] bench-gas *args: (_tmp-logs "bench-gas") diff --git a/docs/getting_started/verifying_changes.md b/docs/getting_started/verifying_changes.md index 660c8d5ebc6..e0d453a82e4 100644 --- a/docs/getting_started/verifying_changes.md +++ b/docs/getting_started/verifying_changes.md @@ -13,6 +13,7 @@ Some CI jobs are slow. Only run the checks relevant to your change. | Any PR (baseline) | `just static` | Lint, format, mypy, spellcheck, import isolation, workflow lint. | | Added or modified tests | `just fill tests/path/to/new/tests` | See [Filling Tests](../filling_tests/index.md). | | Framework changes (`packages/testing/`) | `just test-tests` | Framework unit tests. Mirrors the `test-tests` CI job. | +| Benchmark test changes (`tests/benchmark/`) | `just fill-benchmark` | Fills `tests/benchmark/compute` at 1M gas with EELS. Mirrors the `fill-benchmark` CI job. | | Benchmark framework changes | `just test-tests`, `just bench-gas`, `just bench-opcode`, `just bench-opcode-config` | Benchmark plugin unit tests now run within `test-tests`; the `bench-*` recipes fill/verify the suite (geth-backed on `benchmarks/**`). | | Markdown touched | `just lint-md` | Requires `markdownlint-cli2`; see [Linting Markdown](#linting-markdown). | | Docs touched | `just docs` or `just docs-fast` | `docs-fast` skips the Test Case Reference section for faster iteration. | diff --git a/src/ethereum_spec_tools/evm_tools/t8n/evm_trace/count.py b/src/ethereum_spec_tools/evm_tools/t8n/evm_trace/count.py index d922d788271..368c604c18f 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/evm_trace/count.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/evm_trace/count.py @@ -6,35 +6,29 @@ from ethereum.trace import EvmTracer, OpStart, TraceEvent -from .protocols import Evm - class CountTracer(EvmTracer): """ EVM trace implementation that counts how many times each opcode is executed. + + Counts accumulate over every execution in the run, including system + transactions; consumers create one tracer per t8n run. """ - transaction_environment: object | None active_traces: defaultdict[str, int] def __init__(self) -> None: - self.transaction_environment = None self.active_traces = defaultdict(lambda: 0) def __call__(self, evm: object, event: TraceEvent) -> None: """ Create a trace of the event. """ + del evm # Counting needs only the event, not the EVM state. if not isinstance(event, OpStart): return - assert isinstance(evm, Evm) - - if self.transaction_environment is not evm.message.tx_env: - self.active_traces = defaultdict(lambda: 0) - self.transaction_environment = evm.message.tx_env - self.active_traces[event.op.name] += 1 def results(self) -> dict[str, int]: @@ -43,5 +37,4 @@ def results(self) -> dict[str, int]: """ results = self.active_traces self.active_traces = defaultdict(lambda: 0) - self.transaction_environment = None return results From 9d6e6f8352a0f76e7e8803722d1a2798fa4f0a96 Mon Sep 17 00:00:00 2001 From: spencer <spencer.taylor-brown@ethereum.org> Date: Fri, 31 Jul 2026 22:39:53 +0200 Subject: [PATCH 192/233] feat(tests): cover EIP-8282 builder request dequeues in block access lists (#3270) * feat(tests): cover EIP-8282 builder request dequeues in block access lists * feat(tests): add activation-block BAL tests for builder requests and transfers * refactor(tests): Use `from_index` method * refactor(tests): Fold two test methods into one * fix(tests): Unused import --------- Co-authored-by: marioevz <marioevz@gmail.com> --- .../test_block_access_lists_eip8282.py | 354 ++++++++++++++++++ .../test_fork_transition.py | 263 +++++++++++++ 2 files changed, 617 insertions(+) create mode 100644 tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip8282.py diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip8282.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip8282.py new file mode 100644 index 00000000000..42f9657454a --- /dev/null +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip8282.py @@ -0,0 +1,354 @@ +""" +Tests for the effects of EIP-8282 builder requests on EIP-7928. + +Pin the block access list produced by the builder deposit and exit +predeploys: the enqueuing transactions grow the count and queue tail +slots, and the post-execution system call dequeues the records, resetting +or advancing the bus slots depending on whether the sweep is clean or +partial. +""" + +from typing import Dict, List, Tuple, Type + +import pytest +from execution_testing import ( + Alloc, + BalAccountExpectation, + BalNonceChange, + BalStorageChange, + BalStorageSlot, + Block, + BlockAccessListExpectation, + BlockchainTestFiller, + FeeSystemContractRequest, + SystemContractInteractionBase, + SystemContractInteractionContract, + SystemContractInteractionTransaction, +) + +from ..eip8282_builder_execution_requests.helpers import ( + BuilderDepositRequest, + BuilderExitRequest, +) +from ..eip8282_builder_execution_requests.spec import Spec as Spec8282 +from .spec import ref_spec_7928 + +REFERENCE_SPEC_GIT_PATH = ref_spec_7928.git_path +REFERENCE_SPEC_VERSION = ref_spec_7928.version + +pytestmark = pytest.mark.valid_from("Amsterdam") + + +def _fees( + request_class: Type[FeeSystemContractRequest], count: int +) -> List[int]: + """ + Return the fee each of `count` requests enqueued in a single block must + pay, matching the predeploy's in-call excess computation: requests + already queued this block beyond the target raise the fee for the next + one (the stored excess starts at zero). + """ + return [ + request_class.get_fee(max(i - request_class.target_per_block, 0)) + for i in range(count) + ] + + +def _request( + request_class: Type[FeeSystemContractRequest], index: int, fee: int +) -> FeeSystemContractRequest: + """Build a request from a sequential index, paying `fee` to enqueue.""" + return request_class.from_index(index).copy(fee=fee) + + +def _request_bus_expectation( + request_class: Type[FeeSystemContractRequest], + enqueues: List[Tuple[int, int]], + system_call_index: int, +) -> BalAccountExpectation: + """ + Build the BAL expectation for a request-bus predeploy. + + `enqueues` lists `(block_access_index, cumulative_count)` for each + transaction that enqueues into this predeploy. The count and queue tail + grow with each of them; the system call at `system_call_index` then + resets the count and either resets the tail (clean sweep) or advances + the head to `max_per_block` (partial sweep), writing the new excess if + the enqueued total exceeded the target. + """ + total = enqueues[-1][1] if enqueues else 0 + new_excess = max(total - request_class.target_per_block, 0) + partial_sweep = total > request_class.max_per_block + + count_changes = [ + BalStorageChange(block_access_index=index, post_value=cumulative) + for index, cumulative in enqueues + ] + [BalStorageChange(block_access_index=system_call_index, post_value=0)] + + tail_changes = [ + BalStorageChange(block_access_index=index, post_value=cumulative) + for index, cumulative in enqueues + ] + head_changes = [] + if partial_sweep: + # Partial sweep: the head advances past the dequeued records and + # the tail keeps the queue's end. + head_changes.append( + BalStorageChange( + block_access_index=system_call_index, + post_value=request_class.max_per_block, + ) + ) + else: + # Clean sweep: the head stays at zero (a read) and the tail resets. + tail_changes.append( + BalStorageChange( + block_access_index=system_call_index, post_value=0 + ) + ) + + storage_changes = [] + if new_excess: + storage_changes.append( + BalStorageSlot( + slot=Spec8282.EXCESS_STORAGE_SLOT, + slot_changes=[ + BalStorageChange( + block_access_index=system_call_index, + post_value=new_excess, + ) + ], + ) + ) + storage_changes.append( + BalStorageSlot( + slot=Spec8282.COUNT_STORAGE_SLOT, slot_changes=count_changes + ) + ) + if head_changes: + storage_changes.append( + BalStorageSlot( + slot=Spec8282.QUEUE_HEAD_STORAGE_SLOT, + slot_changes=head_changes, + ) + ) + storage_changes.append( + BalStorageSlot( + slot=Spec8282.QUEUE_TAIL_STORAGE_SLOT, slot_changes=tail_changes + ) + ) + + storage_reads = [] + if not new_excess: + storage_reads.append(Spec8282.EXCESS_STORAGE_SLOT) + if not partial_sweep: + storage_reads.append(Spec8282.QUEUE_HEAD_STORAGE_SLOT) + + kwargs: Dict = {"storage_changes": storage_changes} + if storage_reads: + kwargs["storage_reads"] = storage_reads + return BalAccountExpectation(**kwargs) + + +@pytest.mark.parametrize( + "request_class", + [BuilderDepositRequest, BuilderExitRequest], + ids=["deposit", "exit"], +) +@pytest.mark.parametrize( + "scenario,via_contract", + [ + pytest.param("single", False, id="single_from_eoa"), + pytest.param("target_exceeded", False, id="target_exceeded_from_eoa"), + pytest.param("carry_over", True, id="carry_over_from_contract"), + ], +) +def test_bal_builder_request_dequeue( + pre: Alloc, + blockchain_test: BlockchainTestFiller, + request_class: Type[FeeSystemContractRequest], + scenario: str, + via_contract: bool, +) -> None: + """ + Ensure BAL tracks a builder request predeploy across a clean sweep, a + target-exceeding sweep that writes the excess, and a partial sweep that + advances the queue head. + """ + if scenario == "single": + num_requests = 1 + elif scenario == "target_exceeded": + num_requests = request_class.target_per_block + 1 + else: # carry_over: exceed the per-block dequeue cap + num_requests = request_class.max_per_block + 1 + requests = [ + _request(request_class, i, fee) + for i, fee in enumerate(_fees(request_class, num_requests)) + ] + interaction: SystemContractInteractionBase + if via_contract: + interaction = SystemContractInteractionContract(requests=requests) + else: + interaction = SystemContractInteractionTransaction(requests=requests) + prepared = interaction.update_pre(pre) + txs = prepared.transactions() + system_call_index = len(txs) + 1 + + if via_contract: + enqueues = [(1, num_requests)] + else: + enqueues = [(i + 1, i + 1) for i in range(num_requests)] + + sender = prepared.sender_account + assert sender is not None + + block = Block( + txs=txs, + expected_block_access_list=BlockAccessListExpectation( + account_expectations={ + sender: BalAccountExpectation( + nonce_changes=[ + BalNonceChange( + block_access_index=i + 1, post_nonce=i + 1 + ) + for i in range(len(txs)) + ], + ), + request_class.interaction_contract_address: ( + _request_bus_expectation( + request_class, enqueues, system_call_index + ) + ), + } + ), + ) + + blockchain_test(pre=pre, blocks=[block], post={}) + + +def test_bal_builder_deposits_and_exits_same_block( + pre: Alloc, + blockchain_test: BlockchainTestFiller, +) -> None: + """ + Ensure BAL tracks both builder predeploys when a single block + interleaves deposit and exit requests, pinning which transaction + indices each contract's bus-slot changes carry. + """ + deposit_fees = _fees(BuilderDepositRequest, 2) + exit_fees = _fees(BuilderExitRequest, 2) + interactions = [ + SystemContractInteractionTransaction( + requests=[_request(BuilderDepositRequest, 0, deposit_fees[0])] + ), + SystemContractInteractionTransaction( + requests=[_request(BuilderExitRequest, 0, exit_fees[0])] + ), + SystemContractInteractionTransaction( + requests=[_request(BuilderDepositRequest, 1, deposit_fees[1])] + ), + SystemContractInteractionTransaction( + requests=[_request(BuilderExitRequest, 1, exit_fees[1])] + ), + ] + + txs = [] + senders = [] + for interaction in interactions: + prepared = interaction.update_pre(pre) + txs += prepared.transactions() + assert prepared.sender_account is not None + senders.append(prepared.sender_account) + system_call_index = len(txs) + 1 + + account_expectations: Dict = { + sender: BalAccountExpectation( + nonce_changes=[ + BalNonceChange(block_access_index=i + 1, post_nonce=1) + ], + ) + for i, sender in enumerate(senders) + } + # Deposits are enqueued by transactions 1 and 3, exits by 2 and 4. + account_expectations[ + BuilderDepositRequest.interaction_contract_address + ] = _request_bus_expectation( + BuilderDepositRequest, [(1, 1), (3, 2)], system_call_index + ) + account_expectations[BuilderExitRequest.interaction_contract_address] = ( + _request_bus_expectation( + BuilderExitRequest, [(2, 1), (4, 2)], system_call_index + ) + ) + + block = Block( + txs=txs, + expected_block_access_list=BlockAccessListExpectation( + account_expectations=account_expectations + ), + ) + + blockchain_test(pre=pre, blocks=[block], post={}) + + +@pytest.mark.parametrize( + "request_obj", + [ + pytest.param( + BuilderDepositRequest( + pubkey=1, + withdrawal_credentials=2, + amount=Spec8282.BUILDER_MIN_DEPOSIT // 10**9, + signature=3, + fee=0, + valid=False, + ), + id="deposit_insufficient_fee", + ), + pytest.param( + BuilderExitRequest(pubkey=1, fee=0, valid=False), + id="exit_insufficient_fee", + ), + ], +) +def test_bal_builder_request_invalid( + pre: Alloc, + blockchain_test: BlockchainTestFiller, + request_obj: FeeSystemContractRequest, +) -> None: + """ + Ensure BAL records only reads on a builder predeploy when the request + call reverts for an insufficient fee: the reverted enqueue leaves no + storage change and the system-call dequeue finds an empty queue. + """ + interaction = SystemContractInteractionTransaction(requests=[request_obj]) + prepared = interaction.update_pre(pre) + txs = prepared.transactions() + sender = prepared.sender_account + assert sender is not None + + contract = request_obj.interaction_contract_address + + block = Block( + txs=txs, + expected_block_access_list=BlockAccessListExpectation( + account_expectations={ + sender: BalAccountExpectation( + nonce_changes=[ + BalNonceChange(block_access_index=1, post_nonce=1) + ], + ), + contract: BalAccountExpectation( + storage_reads=[ + Spec8282.EXCESS_STORAGE_SLOT, + Spec8282.COUNT_STORAGE_SLOT, + Spec8282.QUEUE_HEAD_STORAGE_SLOT, + Spec8282.QUEUE_TAIL_STORAGE_SLOT, + ], + storage_changes=[], + ), + } + ), + ) + + blockchain_test(pre=pre, blocks=[block], post={}) diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_fork_transition.py b/tests/amsterdam/eip7928_block_level_access_lists/test_fork_transition.py index e138d75d2da..66e95787aba 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_fork_transition.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_fork_transition.py @@ -3,10 +3,13 @@ import pytest from execution_testing import ( Account, + Address, Alloc, BalAccountExpectation, BalBalanceChange, BalNonceChange, + BalStorageChange, + BalStorageSlot, Block, BlockAccessListExpectation, BlockchainTestFiller, @@ -17,10 +20,19 @@ Environment, Hash, Header, + Op, + SystemContractInteractionTransaction, Transaction, + TransactionReceipt, TransitionFork, ) +from ..eip7708_eth_transfer_logs.spec import transfer_log +from ..eip8282_builder_execution_requests.helpers import ( + BuilderDepositRequest, + BuilderExitRequest, +) +from ..eip8282_builder_execution_requests.spec import Spec as Spec8282 from .spec import ref_spec_7928 REFERENCE_SPEC_GIT_PATH = ref_spec_7928.git_path @@ -250,3 +262,254 @@ def test_fork_transition_bal_size_constraint( blocks=[pre_fork_block, at_fork_block], genesis_environment=Environment(gas_limit=block_gas_limit), ) + + +def _single_request_bus_expectation( + enqueue_index: int, system_call_index: int, post_balance: int +) -> BalAccountExpectation: + """ + Build the BAL expectation for a builder predeploy dequeuing a single + request in a clean sweep: count and queue tail rise to one and reset, + while the excess and head slots stay read-only. + """ + + def bus_slot_changes() -> list: + return [ + BalStorageChange(block_access_index=enqueue_index, post_value=1), + BalStorageChange( + block_access_index=system_call_index, post_value=0 + ), + ] + + return BalAccountExpectation( + balance_changes=[ + BalBalanceChange( + block_access_index=enqueue_index, post_balance=post_balance + ) + ], + storage_changes=[ + BalStorageSlot( + slot=Spec8282.COUNT_STORAGE_SLOT, + slot_changes=bus_slot_changes(), + ), + BalStorageSlot( + slot=Spec8282.QUEUE_TAIL_STORAGE_SLOT, + slot_changes=bus_slot_changes(), + ), + ], + storage_reads=[ + Spec8282.EXCESS_STORAGE_SLOT, + Spec8282.QUEUE_HEAD_STORAGE_SLOT, + ], + ) + + +@pytest.mark.valid_at_transition_to("Amsterdam") +def test_bal_fork_transition_builder_requests( + blockchain_test: BlockchainTestFiller, + pre: Alloc, +) -> None: + """ + Verify the BAL of an activation block that dequeues EIP-8282 builder + requests. + + The first Amsterdam block carries the chain's first builder deposit + and exit requests: the BAL must record both predeploys' request-bus + slots, the enqueuing transaction indices, and the clean-sweep dequeue + by the post-execution system calls. + """ + alice = pre.fund_eoa() + bob = pre.fund_eoa(amount=0) + + deposit = BuilderDepositRequest( + pubkey=1, + withdrawal_credentials=2, + amount=Spec8282.BUILDER_MIN_DEPOSIT // 10**9, + signature=3, + fee=BuilderDepositRequest.get_fee(0), + ) + builder_exit = BuilderExitRequest( + pubkey=1, fee=BuilderExitRequest.get_fee(0) + ) + + deposit_interaction = SystemContractInteractionTransaction( + requests=[deposit] + ).update_pre(pre) + exit_interaction = SystemContractInteractionTransaction( + requests=[builder_exit] + ).update_pre(pre) + txs = deposit_interaction.transactions() + exit_interaction.transactions() + system_call_index = len(txs) + 1 + + deposit_sender = deposit_interaction.sender_account + exit_sender = exit_interaction.sender_account + assert deposit_sender is not None and exit_sender is not None + + blocks = [ + Block( + timestamp=FORK_TIMESTAMP - 1, + txs=[Transaction(sender=alice, to=bob, value=100, gas_price=10)], + header_verify=Header( + block_access_list_hash=Header.EMPTY_FIELD, + ), + ), + Block( + timestamp=FORK_TIMESTAMP, + txs=txs, + expected_block_access_list=BlockAccessListExpectation( + account_expectations={ + deposit_sender: BalAccountExpectation( + nonce_changes=[ + BalNonceChange(block_access_index=1, post_nonce=1) + ], + ), + exit_sender: BalAccountExpectation( + nonce_changes=[ + BalNonceChange(block_access_index=2, post_nonce=1) + ], + ), + Address( + Spec8282.BUILDER_DEPOSIT_CONTRACT_ADDRESS + ): _single_request_bus_expectation( + 1, system_call_index, deposit.value + ), + Address( + Spec8282.BUILDER_EXIT_CONTRACT_ADDRESS + ): _single_request_bus_expectation( + 2, system_call_index, builder_exit.value + ), + } + ), + ), + ] + + blockchain_test( + pre=pre, + blocks=blocks, + post={ + Address(Spec8282.BUILDER_DEPOSIT_CONTRACT_ADDRESS): Account( + balance=deposit.value + ), + Address(Spec8282.BUILDER_EXIT_CONTRACT_ADDRESS): Account( + balance=builder_exit.value + ), + }, + ) + + +@pytest.mark.valid_at_transition_to("Amsterdam") +def test_bal_fork_transition_transfers_and_storage( + blockchain_test: BlockchainTestFiller, + pre: Alloc, +) -> None: + """ + Verify the BAL of an activation block with ordinary transfers, + storage writes and Transfer logs. + + The first Amsterdam block mixes a plain EOA transfer with a contract + call that writes storage and forwards value: the BAL must carry the + balance and storage changes while the receipts carry the EIP-7708 + Transfer logs. + """ + transfer_value = 100 + forward_value = 500 + + alice = pre.fund_eoa() + bob = pre.fund_eoa(amount=0) + carol = pre.fund_eoa() + dave = pre.fund_eoa(amount=0) + relay = pre.deploy_contract( + code=Op.SSTORE(0, 1) + + Op.POP(Op.CALL(Op.GAS, dave, forward_value, 0, 0, 0, 0)), + balance=forward_value, + ) + + pre_fork_tx = Transaction( + sender=alice, to=bob, value=transfer_value, gas_price=10 + ) + tx_transfer = Transaction( + sender=alice, + to=bob, + value=transfer_value, + expected_receipt=TransactionReceipt( + logs=[transfer_log(alice, bob, transfer_value)] + ), + ) + tx_relay = Transaction( + sender=carol, + to=relay, + expected_receipt=TransactionReceipt( + logs=[transfer_log(relay, dave, forward_value)] + ), + ) + + blocks = [ + Block( + timestamp=FORK_TIMESTAMP - 1, + txs=[pre_fork_tx], + header_verify=Header( + block_access_list_hash=Header.EMPTY_FIELD, + ), + ), + Block( + timestamp=FORK_TIMESTAMP, + txs=[tx_transfer, tx_relay], + expected_block_access_list=BlockAccessListExpectation( + account_expectations={ + alice: BalAccountExpectation( + nonce_changes=[ + BalNonceChange(block_access_index=1, post_nonce=2) + ], + ), + bob: BalAccountExpectation( + balance_changes=[ + BalBalanceChange( + block_access_index=1, + post_balance=transfer_value * 2, + ) + ], + ), + carol: BalAccountExpectation( + nonce_changes=[ + BalNonceChange(block_access_index=2, post_nonce=1) + ], + ), + relay: BalAccountExpectation( + balance_changes=[ + BalBalanceChange( + block_access_index=2, post_balance=0 + ) + ], + storage_changes=[ + BalStorageSlot( + slot=0, + slot_changes=[ + BalStorageChange( + block_access_index=2, post_value=1 + ) + ], + ) + ], + ), + dave: BalAccountExpectation( + balance_changes=[ + BalBalanceChange( + block_access_index=2, + post_balance=forward_value, + ) + ], + ), + } + ), + ), + ] + + blockchain_test( + pre=pre, + blocks=blocks, + post={ + bob: Account(balance=transfer_value * 2), + dave: Account(balance=forward_value), + relay: Account(balance=0, storage={0: 1}), + }, + ) From 42f26f124c811b89154a0fe86daf09af069ca4d3 Mon Sep 17 00:00:00 2001 From: Jevin Jojo <jevinjojo1@gmail.com> Date: Mon, 3 Aug 2026 17:37:41 +0530 Subject: [PATCH 193/233] refactor(spec-specs): move priority-fee check to `validate_transaction` (#3056) Co-authored-by: spencer-tb <spencer.tb@ethereum.org> --- src/ethereum/forks/amsterdam/fork.py | 7 ------- src/ethereum/forks/amsterdam/transactions.py | 10 +++++++++- src/ethereum/forks/arrow_glacier/fork.py | 7 ------- src/ethereum/forks/arrow_glacier/transactions.py | 14 ++++++++++++-- src/ethereum/forks/bpo1/fork.py | 7 ------- src/ethereum/forks/bpo1/transactions.py | 10 +++++++++- src/ethereum/forks/bpo2/fork.py | 7 ------- src/ethereum/forks/bpo2/transactions.py | 10 +++++++++- src/ethereum/forks/bpo3/fork.py | 7 ------- src/ethereum/forks/bpo3/transactions.py | 10 +++++++++- src/ethereum/forks/bpo4/fork.py | 7 ------- src/ethereum/forks/bpo4/transactions.py | 10 +++++++++- src/ethereum/forks/bpo5/fork.py | 7 ------- src/ethereum/forks/bpo5/transactions.py | 10 +++++++++- src/ethereum/forks/cancun/fork.py | 7 ------- src/ethereum/forks/cancun/transactions.py | 15 +++++++++++++-- src/ethereum/forks/gray_glacier/fork.py | 7 ------- src/ethereum/forks/gray_glacier/transactions.py | 14 ++++++++++++-- src/ethereum/forks/london/fork.py | 7 ------- src/ethereum/forks/london/transactions.py | 14 ++++++++++++-- src/ethereum/forks/osaka/fork.py | 7 ------- src/ethereum/forks/osaka/transactions.py | 10 +++++++++- src/ethereum/forks/paris/fork.py | 7 ------- src/ethereum/forks/paris/transactions.py | 14 ++++++++++++-- src/ethereum/forks/prague/fork.py | 7 ------- src/ethereum/forks/prague/transactions.py | 15 +++++++++++++-- src/ethereum/forks/shanghai/fork.py | 7 ------- src/ethereum/forks/shanghai/transactions.py | 15 +++++++++++++-- 28 files changed, 150 insertions(+), 119 deletions(-) diff --git a/src/ethereum/forks/amsterdam/fork.py b/src/ethereum/forks/amsterdam/fork.py index 9ffddf833ae..1bcbc252477 100644 --- a/src/ethereum/forks/amsterdam/fork.py +++ b/src/ethereum/forks/amsterdam/fork.py @@ -50,7 +50,6 @@ InsufficientMaxFeePerGasError, InvalidBlobVersionedHashError, NoBlobDataError, - PriorityFeeGreaterThanMaxFeeError, TransactionTypeContractCreationError, WrongChainIdError, ) @@ -538,8 +537,6 @@ def check_transaction( If the sender's balance is not enough to pay for the transaction. InvalidSenderError : If the transaction is from an address that does not exist anymore. - PriorityFeeGreaterThanMaxFeeError : - If the priority fee is greater than the maximum fee per gas. InsufficientMaxFeePerGasError : If the maximum fee per gas is insufficient for the transaction. InsufficientMaxFeePerBlobGasError : @@ -583,10 +580,6 @@ def check_transaction( sender_account = get_account(tx_state, sender) if isinstance(tx, FeeMarketCapableTransaction): - if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: - raise PriorityFeeGreaterThanMaxFeeError( - "priority fee greater than max fee" - ) if tx.max_fee_per_gas < block_env.base_fee_per_gas: raise InsufficientMaxFeePerGasError( tx.max_fee_per_gas, block_env.base_fee_per_gas diff --git a/src/ethereum/forks/amsterdam/transactions.py b/src/ethereum/forks/amsterdam/transactions.py index e1293c56feb..fa51356d6ed 100644 --- a/src/ethereum/forks/amsterdam/transactions.py +++ b/src/ethereum/forks/amsterdam/transactions.py @@ -23,6 +23,7 @@ from .exceptions import ( InitCodeTooLargeError, + PriorityFeeGreaterThanMaxFeeError, TransactionTypeError, ) from .fork_types import Authorization, ExecutionGas, VersionedHash @@ -589,7 +590,9 @@ def validate_transaction(tx: Transaction, sender: Address) -> IntrinsicGasCost: and a `NonceOverflowError` exception if the nonce overflows. It also raises an `InitCodeTooLargeError` if the code size of a contract creation transaction exceeds the maximum allowed - size. + size, and a `PriorityFeeGreaterThanMaxFeeError` if the maximum + priority fee per gas of a fee market transaction exceeds its maximum + fee per gas. [EIP-2681]: https://eips.ethereum.org/EIPS/eip-2681 [EIP-7623]: https://eips.ethereum.org/EIPS/eip-7623 @@ -614,6 +617,11 @@ def validate_transaction(tx: Transaction, sender: Address) -> IntrinsicGasCost: ) if U256(tx.nonce) >= U256(U64.MAX_VALUE): raise NonceOverflowError("Nonce too high") + if isinstance(tx, FeeMarketCapableTransaction): + if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: + raise PriorityFeeGreaterThanMaxFeeError( + "priority fee greater than max fee" + ) return intrinsic diff --git a/src/ethereum/forks/arrow_glacier/fork.py b/src/ethereum/forks/arrow_glacier/fork.py index 5a37ddabcec..fd4cd99a3c5 100644 --- a/src/ethereum/forks/arrow_glacier/fork.py +++ b/src/ethereum/forks/arrow_glacier/fork.py @@ -37,7 +37,6 @@ from .bloom import logs_bloom from .exceptions import ( InsufficientMaxFeePerGasError, - PriorityFeeGreaterThanMaxFeeError, WrongChainIdError, ) from .state_tracker import ( @@ -475,8 +474,6 @@ def check_transaction( If the sender's balance is not enough to pay for the transaction. InvalidSenderError : If the transaction is from an address that does not exist anymore. - PriorityFeeGreaterThanMaxFeeError : - If the priority fee is greater than the maximum fee per gas. InsufficientMaxFeePerGasError : If the maximum fee per gas is insufficient for the transaction. @@ -495,10 +492,6 @@ def check_transaction( sender_account = get_account(tx_state, sender_address) if isinstance(tx, FeeMarketTransaction): - if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: - raise PriorityFeeGreaterThanMaxFeeError( - "priority fee greater than max fee" - ) if tx.max_fee_per_gas < block_env.base_fee_per_gas: raise InsufficientMaxFeePerGasError( tx.max_fee_per_gas, block_env.base_fee_per_gas diff --git a/src/ethereum/forks/arrow_glacier/transactions.py b/src/ethereum/forks/arrow_glacier/transactions.py index b0b5bc9fa04..937641f9950 100644 --- a/src/ethereum/forks/arrow_glacier/transactions.py +++ b/src/ethereum/forks/arrow_glacier/transactions.py @@ -21,7 +21,10 @@ ) from ethereum.state import Address -from .exceptions import TransactionTypeError +from .exceptions import ( + PriorityFeeGreaterThanMaxFeeError, + TransactionTypeError, +) @final @@ -317,7 +320,9 @@ def validate_transaction(tx: Transaction) -> Uint: gas cost of the transaction after validation. It throws an `InsufficientTransactionGasError` exception if the transaction does not provide enough gas to cover the intrinsic cost, and a `NonceOverflowError` - exception if the nonce is greater than `2**64 - 2`. + exception if the nonce is greater than `2**64 - 2`. It also raises a + `PriorityFeeGreaterThanMaxFeeError` if the maximum priority fee per gas + of a fee market transaction exceeds its maximum fee per gas. [EIP-2681]: https://eips.ethereum.org/EIPS/eip-2681 """ @@ -326,6 +331,11 @@ def validate_transaction(tx: Transaction) -> Uint: raise InsufficientTransactionGasError("Insufficient gas") if U256(tx.nonce) >= U256(U64.MAX_VALUE): raise NonceOverflowError("Nonce too high") + if isinstance(tx, FeeMarketTransaction): + if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: + raise PriorityFeeGreaterThanMaxFeeError( + "priority fee greater than max fee" + ) return intrinsic_gas diff --git a/src/ethereum/forks/bpo1/fork.py b/src/ethereum/forks/bpo1/fork.py index 71cca148fac..f3eb249dfaf 100644 --- a/src/ethereum/forks/bpo1/fork.py +++ b/src/ethereum/forks/bpo1/fork.py @@ -42,7 +42,6 @@ InsufficientMaxFeePerGasError, InvalidBlobVersionedHashError, NoBlobDataError, - PriorityFeeGreaterThanMaxFeeError, TransactionTypeContractCreationError, WrongChainIdError, ) @@ -446,8 +445,6 @@ def check_transaction( If the sender's balance is not enough to pay for the transaction. InvalidSenderError : If the transaction is from an address that does not exist anymore. - PriorityFeeGreaterThanMaxFeeError : - If the priority fee is greater than the maximum fee per gas. InsufficientMaxFeePerGasError : If the maximum fee per gas is insufficient for the transaction. InsufficientMaxFeePerBlobGasError : @@ -490,10 +487,6 @@ def check_transaction( sender_account = get_account(tx_state, sender_address) if isinstance(tx, FeeMarketCapableTransaction): - if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: - raise PriorityFeeGreaterThanMaxFeeError( - "priority fee greater than max fee" - ) if tx.max_fee_per_gas < block_env.base_fee_per_gas: raise InsufficientMaxFeePerGasError( tx.max_fee_per_gas, block_env.base_fee_per_gas diff --git a/src/ethereum/forks/bpo1/transactions.py b/src/ethereum/forks/bpo1/transactions.py index 569f1c1ffc9..227c886fbf5 100644 --- a/src/ethereum/forks/bpo1/transactions.py +++ b/src/ethereum/forks/bpo1/transactions.py @@ -23,6 +23,7 @@ from .exceptions import ( InitCodeTooLargeError, + PriorityFeeGreaterThanMaxFeeError, TransactionGasLimitExceededError, TransactionTypeError, ) @@ -562,7 +563,9 @@ def validate_transaction(tx: Transaction) -> IntrinsicGasCost: the transaction does not provide enough gas to cover the intrinsic cost, and a `NonceOverflowError` exception if the nonce is greater than `2**64 - 2`. It also raises an `InitCodeTooLargeError` if the code size of - a contract creation transaction exceeds the maximum allowed size. + a contract creation transaction exceeds the maximum allowed size, and a + `PriorityFeeGreaterThanMaxFeeError` if the maximum priority fee per gas + of a fee market transaction exceeds its maximum fee per gas. [EIP-2681]: https://eips.ethereum.org/EIPS/eip-2681 [EIP-7623]: https://eips.ethereum.org/EIPS/eip-7623 @@ -578,6 +581,11 @@ def validate_transaction(tx: Transaction) -> IntrinsicGasCost: raise TransactionGasLimitExceededError("Gas limit too high") if U256(tx.nonce) >= U256(U64.MAX_VALUE): raise NonceOverflowError("Nonce too high") + if isinstance(tx, FeeMarketCapableTransaction): + if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: + raise PriorityFeeGreaterThanMaxFeeError( + "priority fee greater than max fee" + ) return intrinsic diff --git a/src/ethereum/forks/bpo2/fork.py b/src/ethereum/forks/bpo2/fork.py index 71cca148fac..f3eb249dfaf 100644 --- a/src/ethereum/forks/bpo2/fork.py +++ b/src/ethereum/forks/bpo2/fork.py @@ -42,7 +42,6 @@ InsufficientMaxFeePerGasError, InvalidBlobVersionedHashError, NoBlobDataError, - PriorityFeeGreaterThanMaxFeeError, TransactionTypeContractCreationError, WrongChainIdError, ) @@ -446,8 +445,6 @@ def check_transaction( If the sender's balance is not enough to pay for the transaction. InvalidSenderError : If the transaction is from an address that does not exist anymore. - PriorityFeeGreaterThanMaxFeeError : - If the priority fee is greater than the maximum fee per gas. InsufficientMaxFeePerGasError : If the maximum fee per gas is insufficient for the transaction. InsufficientMaxFeePerBlobGasError : @@ -490,10 +487,6 @@ def check_transaction( sender_account = get_account(tx_state, sender_address) if isinstance(tx, FeeMarketCapableTransaction): - if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: - raise PriorityFeeGreaterThanMaxFeeError( - "priority fee greater than max fee" - ) if tx.max_fee_per_gas < block_env.base_fee_per_gas: raise InsufficientMaxFeePerGasError( tx.max_fee_per_gas, block_env.base_fee_per_gas diff --git a/src/ethereum/forks/bpo2/transactions.py b/src/ethereum/forks/bpo2/transactions.py index 2232f4e2976..f9d1229aed5 100644 --- a/src/ethereum/forks/bpo2/transactions.py +++ b/src/ethereum/forks/bpo2/transactions.py @@ -23,6 +23,7 @@ from .exceptions import ( InitCodeTooLargeError, + PriorityFeeGreaterThanMaxFeeError, TransactionGasLimitExceededError, TransactionTypeError, ) @@ -562,7 +563,9 @@ def validate_transaction(tx: Transaction) -> IntrinsicGasCost: the transaction does not provide enough gas to cover the intrinsic cost, and a `NonceOverflowError` exception if the nonce is greater than `2**64 - 2`. It also raises an `InitCodeTooLargeError` if the code size of - a contract creation transaction exceeds the maximum allowed size. + a contract creation transaction exceeds the maximum allowed size, and a + `PriorityFeeGreaterThanMaxFeeError` if the maximum priority fee per gas + of a fee market transaction exceeds its maximum fee per gas. [EIP-2681]: https://eips.ethereum.org/EIPS/eip-2681 [EIP-7623]: https://eips.ethereum.org/EIPS/eip-7623 @@ -578,6 +581,11 @@ def validate_transaction(tx: Transaction) -> IntrinsicGasCost: raise TransactionGasLimitExceededError("Gas limit too high") if U256(tx.nonce) >= U256(U64.MAX_VALUE): raise NonceOverflowError("Nonce too high") + if isinstance(tx, FeeMarketCapableTransaction): + if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: + raise PriorityFeeGreaterThanMaxFeeError( + "priority fee greater than max fee" + ) return intrinsic diff --git a/src/ethereum/forks/bpo3/fork.py b/src/ethereum/forks/bpo3/fork.py index 71cca148fac..f3eb249dfaf 100644 --- a/src/ethereum/forks/bpo3/fork.py +++ b/src/ethereum/forks/bpo3/fork.py @@ -42,7 +42,6 @@ InsufficientMaxFeePerGasError, InvalidBlobVersionedHashError, NoBlobDataError, - PriorityFeeGreaterThanMaxFeeError, TransactionTypeContractCreationError, WrongChainIdError, ) @@ -446,8 +445,6 @@ def check_transaction( If the sender's balance is not enough to pay for the transaction. InvalidSenderError : If the transaction is from an address that does not exist anymore. - PriorityFeeGreaterThanMaxFeeError : - If the priority fee is greater than the maximum fee per gas. InsufficientMaxFeePerGasError : If the maximum fee per gas is insufficient for the transaction. InsufficientMaxFeePerBlobGasError : @@ -490,10 +487,6 @@ def check_transaction( sender_account = get_account(tx_state, sender_address) if isinstance(tx, FeeMarketCapableTransaction): - if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: - raise PriorityFeeGreaterThanMaxFeeError( - "priority fee greater than max fee" - ) if tx.max_fee_per_gas < block_env.base_fee_per_gas: raise InsufficientMaxFeePerGasError( tx.max_fee_per_gas, block_env.base_fee_per_gas diff --git a/src/ethereum/forks/bpo3/transactions.py b/src/ethereum/forks/bpo3/transactions.py index a06202c81ad..92f8ee9ff8f 100644 --- a/src/ethereum/forks/bpo3/transactions.py +++ b/src/ethereum/forks/bpo3/transactions.py @@ -23,6 +23,7 @@ from .exceptions import ( InitCodeTooLargeError, + PriorityFeeGreaterThanMaxFeeError, TransactionGasLimitExceededError, TransactionTypeError, ) @@ -562,7 +563,9 @@ def validate_transaction(tx: Transaction) -> IntrinsicGasCost: the transaction does not provide enough gas to cover the intrinsic cost, and a `NonceOverflowError` exception if the nonce is greater than `2**64 - 2`. It also raises an `InitCodeTooLargeError` if the code size of - a contract creation transaction exceeds the maximum allowed size. + a contract creation transaction exceeds the maximum allowed size, and a + `PriorityFeeGreaterThanMaxFeeError` if the maximum priority fee per gas + of a fee market transaction exceeds its maximum fee per gas. [EIP-2681]: https://eips.ethereum.org/EIPS/eip-2681 [EIP-7623]: https://eips.ethereum.org/EIPS/eip-7623 @@ -578,6 +581,11 @@ def validate_transaction(tx: Transaction) -> IntrinsicGasCost: raise TransactionGasLimitExceededError("Gas limit too high") if U256(tx.nonce) >= U256(U64.MAX_VALUE): raise NonceOverflowError("Nonce too high") + if isinstance(tx, FeeMarketCapableTransaction): + if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: + raise PriorityFeeGreaterThanMaxFeeError( + "priority fee greater than max fee" + ) return intrinsic diff --git a/src/ethereum/forks/bpo4/fork.py b/src/ethereum/forks/bpo4/fork.py index 71cca148fac..f3eb249dfaf 100644 --- a/src/ethereum/forks/bpo4/fork.py +++ b/src/ethereum/forks/bpo4/fork.py @@ -42,7 +42,6 @@ InsufficientMaxFeePerGasError, InvalidBlobVersionedHashError, NoBlobDataError, - PriorityFeeGreaterThanMaxFeeError, TransactionTypeContractCreationError, WrongChainIdError, ) @@ -446,8 +445,6 @@ def check_transaction( If the sender's balance is not enough to pay for the transaction. InvalidSenderError : If the transaction is from an address that does not exist anymore. - PriorityFeeGreaterThanMaxFeeError : - If the priority fee is greater than the maximum fee per gas. InsufficientMaxFeePerGasError : If the maximum fee per gas is insufficient for the transaction. InsufficientMaxFeePerBlobGasError : @@ -490,10 +487,6 @@ def check_transaction( sender_account = get_account(tx_state, sender_address) if isinstance(tx, FeeMarketCapableTransaction): - if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: - raise PriorityFeeGreaterThanMaxFeeError( - "priority fee greater than max fee" - ) if tx.max_fee_per_gas < block_env.base_fee_per_gas: raise InsufficientMaxFeePerGasError( tx.max_fee_per_gas, block_env.base_fee_per_gas diff --git a/src/ethereum/forks/bpo4/transactions.py b/src/ethereum/forks/bpo4/transactions.py index 8a9080ba356..90256ef5a13 100644 --- a/src/ethereum/forks/bpo4/transactions.py +++ b/src/ethereum/forks/bpo4/transactions.py @@ -23,6 +23,7 @@ from .exceptions import ( InitCodeTooLargeError, + PriorityFeeGreaterThanMaxFeeError, TransactionGasLimitExceededError, TransactionTypeError, ) @@ -562,7 +563,9 @@ def validate_transaction(tx: Transaction) -> IntrinsicGasCost: the transaction does not provide enough gas to cover the intrinsic cost, and a `NonceOverflowError` exception if the nonce is greater than `2**64 - 2`. It also raises an `InitCodeTooLargeError` if the code size of - a contract creation transaction exceeds the maximum allowed size. + a contract creation transaction exceeds the maximum allowed size, and a + `PriorityFeeGreaterThanMaxFeeError` if the maximum priority fee per gas + of a fee market transaction exceeds its maximum fee per gas. [EIP-2681]: https://eips.ethereum.org/EIPS/eip-2681 [EIP-7623]: https://eips.ethereum.org/EIPS/eip-7623 @@ -578,6 +581,11 @@ def validate_transaction(tx: Transaction) -> IntrinsicGasCost: raise TransactionGasLimitExceededError("Gas limit too high") if U256(tx.nonce) >= U256(U64.MAX_VALUE): raise NonceOverflowError("Nonce too high") + if isinstance(tx, FeeMarketCapableTransaction): + if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: + raise PriorityFeeGreaterThanMaxFeeError( + "priority fee greater than max fee" + ) return intrinsic diff --git a/src/ethereum/forks/bpo5/fork.py b/src/ethereum/forks/bpo5/fork.py index 71cca148fac..f3eb249dfaf 100644 --- a/src/ethereum/forks/bpo5/fork.py +++ b/src/ethereum/forks/bpo5/fork.py @@ -42,7 +42,6 @@ InsufficientMaxFeePerGasError, InvalidBlobVersionedHashError, NoBlobDataError, - PriorityFeeGreaterThanMaxFeeError, TransactionTypeContractCreationError, WrongChainIdError, ) @@ -446,8 +445,6 @@ def check_transaction( If the sender's balance is not enough to pay for the transaction. InvalidSenderError : If the transaction is from an address that does not exist anymore. - PriorityFeeGreaterThanMaxFeeError : - If the priority fee is greater than the maximum fee per gas. InsufficientMaxFeePerGasError : If the maximum fee per gas is insufficient for the transaction. InsufficientMaxFeePerBlobGasError : @@ -490,10 +487,6 @@ def check_transaction( sender_account = get_account(tx_state, sender_address) if isinstance(tx, FeeMarketCapableTransaction): - if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: - raise PriorityFeeGreaterThanMaxFeeError( - "priority fee greater than max fee" - ) if tx.max_fee_per_gas < block_env.base_fee_per_gas: raise InsufficientMaxFeePerGasError( tx.max_fee_per_gas, block_env.base_fee_per_gas diff --git a/src/ethereum/forks/bpo5/transactions.py b/src/ethereum/forks/bpo5/transactions.py index 136ef6a1475..d2cd6eddae8 100644 --- a/src/ethereum/forks/bpo5/transactions.py +++ b/src/ethereum/forks/bpo5/transactions.py @@ -23,6 +23,7 @@ from .exceptions import ( InitCodeTooLargeError, + PriorityFeeGreaterThanMaxFeeError, TransactionGasLimitExceededError, TransactionTypeError, ) @@ -562,7 +563,9 @@ def validate_transaction(tx: Transaction) -> IntrinsicGasCost: the transaction does not provide enough gas to cover the intrinsic cost, and a `NonceOverflowError` exception if the nonce is greater than `2**64 - 2`. It also raises an `InitCodeTooLargeError` if the code size of - a contract creation transaction exceeds the maximum allowed size. + a contract creation transaction exceeds the maximum allowed size, and a + `PriorityFeeGreaterThanMaxFeeError` if the maximum priority fee per gas + of a fee market transaction exceeds its maximum fee per gas. [EIP-2681]: https://eips.ethereum.org/EIPS/eip-2681 [EIP-7623]: https://eips.ethereum.org/EIPS/eip-7623 @@ -578,6 +581,11 @@ def validate_transaction(tx: Transaction) -> IntrinsicGasCost: raise TransactionGasLimitExceededError("Gas limit too high") if U256(tx.nonce) >= U256(U64.MAX_VALUE): raise NonceOverflowError("Nonce too high") + if isinstance(tx, FeeMarketCapableTransaction): + if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: + raise PriorityFeeGreaterThanMaxFeeError( + "priority fee greater than max fee" + ) return intrinsic diff --git a/src/ethereum/forks/cancun/fork.py b/src/ethereum/forks/cancun/fork.py index 5d3e2c56040..c3392030944 100644 --- a/src/ethereum/forks/cancun/fork.py +++ b/src/ethereum/forks/cancun/fork.py @@ -40,7 +40,6 @@ InsufficientMaxFeePerGasError, InvalidBlobVersionedHashError, NoBlobDataError, - PriorityFeeGreaterThanMaxFeeError, TransactionTypeContractCreationError, WrongChainIdError, ) @@ -414,8 +413,6 @@ def check_transaction( If the sender's balance is not enough to pay for the transaction. InvalidSenderError : If the transaction is from an address that does not exist anymore. - PriorityFeeGreaterThanMaxFeeError : - If the priority fee is greater than the maximum fee per gas. InsufficientMaxFeePerGasError : If the maximum fee per gas is insufficient for the transaction. InsufficientMaxFeePerBlobGasError : @@ -453,10 +450,6 @@ def check_transaction( sender_account = get_account(tx_state, sender_address) if isinstance(tx, FeeMarketCapableTransaction): - if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: - raise PriorityFeeGreaterThanMaxFeeError( - "priority fee greater than max fee" - ) if tx.max_fee_per_gas < block_env.base_fee_per_gas: raise InsufficientMaxFeePerGasError( tx.max_fee_per_gas, block_env.base_fee_per_gas diff --git a/src/ethereum/forks/cancun/transactions.py b/src/ethereum/forks/cancun/transactions.py index f9d1cac4026..291bf256c63 100644 --- a/src/ethereum/forks/cancun/transactions.py +++ b/src/ethereum/forks/cancun/transactions.py @@ -21,7 +21,11 @@ ) from ethereum.state import Address -from .exceptions import InitCodeTooLargeError, TransactionTypeError +from .exceptions import ( + InitCodeTooLargeError, + PriorityFeeGreaterThanMaxFeeError, + TransactionTypeError, +) from .fork_types import VersionedHash @@ -434,7 +438,9 @@ def validate_transaction(tx: Transaction) -> Uint: provide enough gas to cover the intrinsic cost, and a `NonceOverflowError` exception if the nonce is greater than `2**64 - 2`. It also raises an `InitCodeTooLargeError` if the code size of a contract creation transaction - exceeds the maximum allowed size. + exceeds the maximum allowed size, and a `PriorityFeeGreaterThanMaxFeeError` + if the maximum priority fee per gas of a fee market transaction exceeds + its maximum fee per gas. [EIP-2681]: https://eips.ethereum.org/EIPS/eip-2681 """ @@ -447,6 +453,11 @@ def validate_transaction(tx: Transaction) -> Uint: raise InitCodeTooLargeError("Code size too large") if U256(tx.nonce) >= U256(U64.MAX_VALUE): raise NonceOverflowError("Nonce too high") + if isinstance(tx, FeeMarketCapableTransaction): + if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: + raise PriorityFeeGreaterThanMaxFeeError( + "priority fee greater than max fee" + ) return intrinsic_gas diff --git a/src/ethereum/forks/gray_glacier/fork.py b/src/ethereum/forks/gray_glacier/fork.py index 921551c7b05..4a1fcd6491e 100644 --- a/src/ethereum/forks/gray_glacier/fork.py +++ b/src/ethereum/forks/gray_glacier/fork.py @@ -37,7 +37,6 @@ from .bloom import logs_bloom from .exceptions import ( InsufficientMaxFeePerGasError, - PriorityFeeGreaterThanMaxFeeError, WrongChainIdError, ) from .state_tracker import ( @@ -475,8 +474,6 @@ def check_transaction( If the sender's balance is not enough to pay for the transaction. InvalidSenderError : If the transaction is from an address that does not exist anymore. - PriorityFeeGreaterThanMaxFeeError: - If the priority fee is greater than the maximum fee per gas. InsufficientMaxFeePerGasError : If the maximum fee per gas is insufficient for the transaction. @@ -495,10 +492,6 @@ def check_transaction( sender_account = get_account(tx_state, sender_address) if isinstance(tx, FeeMarketTransaction): - if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: - raise PriorityFeeGreaterThanMaxFeeError( - "priority fee greater than max fee" - ) if tx.max_fee_per_gas < block_env.base_fee_per_gas: raise InsufficientMaxFeePerGasError( tx.max_fee_per_gas, block_env.base_fee_per_gas diff --git a/src/ethereum/forks/gray_glacier/transactions.py b/src/ethereum/forks/gray_glacier/transactions.py index b0b5bc9fa04..937641f9950 100644 --- a/src/ethereum/forks/gray_glacier/transactions.py +++ b/src/ethereum/forks/gray_glacier/transactions.py @@ -21,7 +21,10 @@ ) from ethereum.state import Address -from .exceptions import TransactionTypeError +from .exceptions import ( + PriorityFeeGreaterThanMaxFeeError, + TransactionTypeError, +) @final @@ -317,7 +320,9 @@ def validate_transaction(tx: Transaction) -> Uint: gas cost of the transaction after validation. It throws an `InsufficientTransactionGasError` exception if the transaction does not provide enough gas to cover the intrinsic cost, and a `NonceOverflowError` - exception if the nonce is greater than `2**64 - 2`. + exception if the nonce is greater than `2**64 - 2`. It also raises a + `PriorityFeeGreaterThanMaxFeeError` if the maximum priority fee per gas + of a fee market transaction exceeds its maximum fee per gas. [EIP-2681]: https://eips.ethereum.org/EIPS/eip-2681 """ @@ -326,6 +331,11 @@ def validate_transaction(tx: Transaction) -> Uint: raise InsufficientTransactionGasError("Insufficient gas") if U256(tx.nonce) >= U256(U64.MAX_VALUE): raise NonceOverflowError("Nonce too high") + if isinstance(tx, FeeMarketTransaction): + if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: + raise PriorityFeeGreaterThanMaxFeeError( + "priority fee greater than max fee" + ) return intrinsic_gas diff --git a/src/ethereum/forks/london/fork.py b/src/ethereum/forks/london/fork.py index fd5155d1c82..cc40a2237cf 100644 --- a/src/ethereum/forks/london/fork.py +++ b/src/ethereum/forks/london/fork.py @@ -38,7 +38,6 @@ from .bloom import logs_bloom from .exceptions import ( InsufficientMaxFeePerGasError, - PriorityFeeGreaterThanMaxFeeError, WrongChainIdError, ) from .state_tracker import ( @@ -484,8 +483,6 @@ def check_transaction( If the sender's balance is not enough to pay for the transaction. InvalidSenderError : If the transaction is from an address that does not exist anymore. - PriorityFeeGreaterThanMaxFeeError: - If the priority fee is greater than the maximum fee per gas. InsufficientMaxFeePerGasError : If the maximum fee per gas is insufficient for the transaction. @@ -504,10 +501,6 @@ def check_transaction( sender_account = get_account(tx_state, sender_address) if isinstance(tx, FeeMarketTransaction): - if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: - raise PriorityFeeGreaterThanMaxFeeError( - "priority fee greater than max fee" - ) if tx.max_fee_per_gas < block_env.base_fee_per_gas: raise InsufficientMaxFeePerGasError( tx.max_fee_per_gas, block_env.base_fee_per_gas diff --git a/src/ethereum/forks/london/transactions.py b/src/ethereum/forks/london/transactions.py index b0b5bc9fa04..937641f9950 100644 --- a/src/ethereum/forks/london/transactions.py +++ b/src/ethereum/forks/london/transactions.py @@ -21,7 +21,10 @@ ) from ethereum.state import Address -from .exceptions import TransactionTypeError +from .exceptions import ( + PriorityFeeGreaterThanMaxFeeError, + TransactionTypeError, +) @final @@ -317,7 +320,9 @@ def validate_transaction(tx: Transaction) -> Uint: gas cost of the transaction after validation. It throws an `InsufficientTransactionGasError` exception if the transaction does not provide enough gas to cover the intrinsic cost, and a `NonceOverflowError` - exception if the nonce is greater than `2**64 - 2`. + exception if the nonce is greater than `2**64 - 2`. It also raises a + `PriorityFeeGreaterThanMaxFeeError` if the maximum priority fee per gas + of a fee market transaction exceeds its maximum fee per gas. [EIP-2681]: https://eips.ethereum.org/EIPS/eip-2681 """ @@ -326,6 +331,11 @@ def validate_transaction(tx: Transaction) -> Uint: raise InsufficientTransactionGasError("Insufficient gas") if U256(tx.nonce) >= U256(U64.MAX_VALUE): raise NonceOverflowError("Nonce too high") + if isinstance(tx, FeeMarketTransaction): + if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: + raise PriorityFeeGreaterThanMaxFeeError( + "priority fee greater than max fee" + ) return intrinsic_gas diff --git a/src/ethereum/forks/osaka/fork.py b/src/ethereum/forks/osaka/fork.py index 71cca148fac..f3eb249dfaf 100644 --- a/src/ethereum/forks/osaka/fork.py +++ b/src/ethereum/forks/osaka/fork.py @@ -42,7 +42,6 @@ InsufficientMaxFeePerGasError, InvalidBlobVersionedHashError, NoBlobDataError, - PriorityFeeGreaterThanMaxFeeError, TransactionTypeContractCreationError, WrongChainIdError, ) @@ -446,8 +445,6 @@ def check_transaction( If the sender's balance is not enough to pay for the transaction. InvalidSenderError : If the transaction is from an address that does not exist anymore. - PriorityFeeGreaterThanMaxFeeError : - If the priority fee is greater than the maximum fee per gas. InsufficientMaxFeePerGasError : If the maximum fee per gas is insufficient for the transaction. InsufficientMaxFeePerBlobGasError : @@ -490,10 +487,6 @@ def check_transaction( sender_account = get_account(tx_state, sender_address) if isinstance(tx, FeeMarketCapableTransaction): - if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: - raise PriorityFeeGreaterThanMaxFeeError( - "priority fee greater than max fee" - ) if tx.max_fee_per_gas < block_env.base_fee_per_gas: raise InsufficientMaxFeePerGasError( tx.max_fee_per_gas, block_env.base_fee_per_gas diff --git a/src/ethereum/forks/osaka/transactions.py b/src/ethereum/forks/osaka/transactions.py index ae087f98730..4d64115c46d 100644 --- a/src/ethereum/forks/osaka/transactions.py +++ b/src/ethereum/forks/osaka/transactions.py @@ -23,6 +23,7 @@ from .exceptions import ( InitCodeTooLargeError, + PriorityFeeGreaterThanMaxFeeError, TransactionGasLimitExceededError, TransactionTypeError, ) @@ -566,7 +567,9 @@ def validate_transaction(tx: Transaction) -> IntrinsicGasCost: the transaction does not provide enough gas to cover the intrinsic cost, and a `NonceOverflowError` exception if the nonce is greater than `2**64 - 2`. It also raises an `InitCodeTooLargeError` if the code size of - a contract creation transaction exceeds the maximum allowed size. + a contract creation transaction exceeds the maximum allowed size, and a + `PriorityFeeGreaterThanMaxFeeError` if the maximum priority fee per gas + of a fee market transaction exceeds its maximum fee per gas. [EIP-2681]: https://eips.ethereum.org/EIPS/eip-2681 [EIP-7623]: https://eips.ethereum.org/EIPS/eip-7623 @@ -582,6 +585,11 @@ def validate_transaction(tx: Transaction) -> IntrinsicGasCost: raise TransactionGasLimitExceededError("Gas limit too high") if U256(tx.nonce) >= U256(U64.MAX_VALUE): raise NonceOverflowError("Nonce too high") + if isinstance(tx, FeeMarketCapableTransaction): + if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: + raise PriorityFeeGreaterThanMaxFeeError( + "priority fee greater than max fee" + ) return intrinsic diff --git a/src/ethereum/forks/paris/fork.py b/src/ethereum/forks/paris/fork.py index cfe47564565..ba727222f3e 100644 --- a/src/ethereum/forks/paris/fork.py +++ b/src/ethereum/forks/paris/fork.py @@ -36,7 +36,6 @@ from .bloom import logs_bloom from .exceptions import ( InsufficientMaxFeePerGasError, - PriorityFeeGreaterThanMaxFeeError, WrongChainIdError, ) from .state_tracker import ( @@ -374,8 +373,6 @@ def check_transaction( If the sender's balance is not enough to pay for the transaction. InvalidSenderError : If the transaction is from an address that does not exist anymore. - PriorityFeeGreaterThanMaxFeeError : - If the priority fee is greater than the maximum fee per gas. InsufficientMaxFeePerGasError : If the maximum fee per gas is insufficient for the transaction. @@ -394,10 +391,6 @@ def check_transaction( sender_account = get_account(tx_state, sender_address) if isinstance(tx, FeeMarketTransaction): - if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: - raise PriorityFeeGreaterThanMaxFeeError( - "priority fee greater than max fee" - ) if tx.max_fee_per_gas < block_env.base_fee_per_gas: raise InsufficientMaxFeePerGasError( tx.max_fee_per_gas, block_env.base_fee_per_gas diff --git a/src/ethereum/forks/paris/transactions.py b/src/ethereum/forks/paris/transactions.py index b0b5bc9fa04..937641f9950 100644 --- a/src/ethereum/forks/paris/transactions.py +++ b/src/ethereum/forks/paris/transactions.py @@ -21,7 +21,10 @@ ) from ethereum.state import Address -from .exceptions import TransactionTypeError +from .exceptions import ( + PriorityFeeGreaterThanMaxFeeError, + TransactionTypeError, +) @final @@ -317,7 +320,9 @@ def validate_transaction(tx: Transaction) -> Uint: gas cost of the transaction after validation. It throws an `InsufficientTransactionGasError` exception if the transaction does not provide enough gas to cover the intrinsic cost, and a `NonceOverflowError` - exception if the nonce is greater than `2**64 - 2`. + exception if the nonce is greater than `2**64 - 2`. It also raises a + `PriorityFeeGreaterThanMaxFeeError` if the maximum priority fee per gas + of a fee market transaction exceeds its maximum fee per gas. [EIP-2681]: https://eips.ethereum.org/EIPS/eip-2681 """ @@ -326,6 +331,11 @@ def validate_transaction(tx: Transaction) -> Uint: raise InsufficientTransactionGasError("Insufficient gas") if U256(tx.nonce) >= U256(U64.MAX_VALUE): raise NonceOverflowError("Nonce too high") + if isinstance(tx, FeeMarketTransaction): + if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: + raise PriorityFeeGreaterThanMaxFeeError( + "priority fee greater than max fee" + ) return intrinsic_gas diff --git a/src/ethereum/forks/prague/fork.py b/src/ethereum/forks/prague/fork.py index 9a322c36626..76805746c13 100644 --- a/src/ethereum/forks/prague/fork.py +++ b/src/ethereum/forks/prague/fork.py @@ -41,7 +41,6 @@ InsufficientMaxFeePerGasError, InvalidBlobVersionedHashError, NoBlobDataError, - PriorityFeeGreaterThanMaxFeeError, TransactionTypeContractCreationError, WrongChainIdError, ) @@ -436,8 +435,6 @@ def check_transaction( If the sender's balance is not enough to pay for the transaction. InvalidSenderError : If the transaction is from an address that does not exist anymore. - PriorityFeeGreaterThanMaxFeeError : - If the priority fee is greater than the maximum fee per gas. InsufficientMaxFeePerGasError : If the maximum fee per gas is insufficient for the transaction. InsufficientMaxFeePerBlobGasError : @@ -478,10 +475,6 @@ def check_transaction( sender_account = get_account(tx_state, sender_address) if isinstance(tx, FeeMarketCapableTransaction): - if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: - raise PriorityFeeGreaterThanMaxFeeError( - "priority fee greater than max fee" - ) if tx.max_fee_per_gas < block_env.base_fee_per_gas: raise InsufficientMaxFeePerGasError( tx.max_fee_per_gas, block_env.base_fee_per_gas diff --git a/src/ethereum/forks/prague/transactions.py b/src/ethereum/forks/prague/transactions.py index f28a2a636aa..e4ee0268207 100644 --- a/src/ethereum/forks/prague/transactions.py +++ b/src/ethereum/forks/prague/transactions.py @@ -21,7 +21,11 @@ ) from ethereum.state import Address -from .exceptions import InitCodeTooLargeError, TransactionTypeError +from .exceptions import ( + InitCodeTooLargeError, + PriorityFeeGreaterThanMaxFeeError, + TransactionTypeError, +) from .fork_types import Authorization, VersionedHash @@ -559,7 +563,9 @@ def validate_transaction(tx: Transaction) -> IntrinsicGasCost: the transaction does not provide enough gas to cover the intrinsic cost, and a `NonceOverflowError` exception if the nonce is greater than `2**64 - 2`. It also raises an `InitCodeTooLargeError` if the code size of - a contract creation transaction exceeds the maximum allowed size. + a contract creation transaction exceeds the maximum allowed size, and a + `PriorityFeeGreaterThanMaxFeeError` if the maximum priority fee per gas + of a fee market transaction exceeds its maximum fee per gas. [EIP-2681]: https://eips.ethereum.org/EIPS/eip-2681 [EIP-7623]: https://eips.ethereum.org/EIPS/eip-7623 @@ -573,6 +579,11 @@ def validate_transaction(tx: Transaction) -> IntrinsicGasCost: raise InitCodeTooLargeError("Code size too large") if U256(tx.nonce) >= U256(U64.MAX_VALUE): raise NonceOverflowError("Nonce too high") + if isinstance(tx, FeeMarketCapableTransaction): + if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: + raise PriorityFeeGreaterThanMaxFeeError( + "priority fee greater than max fee" + ) return intrinsic diff --git a/src/ethereum/forks/shanghai/fork.py b/src/ethereum/forks/shanghai/fork.py index 16c4207aca4..1f23255fa6f 100644 --- a/src/ethereum/forks/shanghai/fork.py +++ b/src/ethereum/forks/shanghai/fork.py @@ -36,7 +36,6 @@ from .bloom import logs_bloom from .exceptions import ( InsufficientMaxFeePerGasError, - PriorityFeeGreaterThanMaxFeeError, WrongChainIdError, ) from .state_tracker import ( @@ -378,8 +377,6 @@ def check_transaction( If the sender's balance is not enough to pay for the transaction. InvalidSenderError : If the transaction is from an address that does not exist anymore. - PriorityFeeGreaterThanMaxFeeError : - If the priority fee is greater than the maximum fee per gas. InsufficientMaxFeePerGasError : If the maximum fee per gas is insufficient for the transaction. @@ -398,10 +395,6 @@ def check_transaction( sender_account = get_account(tx_state, sender_address) if isinstance(tx, FeeMarketTransaction): - if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: - raise PriorityFeeGreaterThanMaxFeeError( - "priority fee greater than max fee" - ) if tx.max_fee_per_gas < block_env.base_fee_per_gas: raise InsufficientMaxFeePerGasError( tx.max_fee_per_gas, block_env.base_fee_per_gas diff --git a/src/ethereum/forks/shanghai/transactions.py b/src/ethereum/forks/shanghai/transactions.py index 59239f4a136..0a88f6549f5 100644 --- a/src/ethereum/forks/shanghai/transactions.py +++ b/src/ethereum/forks/shanghai/transactions.py @@ -21,7 +21,11 @@ ) from ethereum.state import Address -from .exceptions import InitCodeTooLargeError, TransactionTypeError +from .exceptions import ( + InitCodeTooLargeError, + PriorityFeeGreaterThanMaxFeeError, + TransactionTypeError, +) @final @@ -322,7 +326,9 @@ def validate_transaction(tx: Transaction) -> Uint: provide enough gas to cover the intrinsic cost, and a `NonceOverflowError` exception if the nonce is greater than `2**64 - 2`. It also raises an `InitCodeTooLargeError` if the code size of a contract creation transaction - exceeds the maximum allowed size. + exceeds the maximum allowed size, and a `PriorityFeeGreaterThanMaxFeeError` + if the maximum priority fee per gas of a fee market transaction exceeds + its maximum fee per gas. [EIP-2681]: https://eips.ethereum.org/EIPS/eip-2681 """ @@ -335,6 +341,11 @@ def validate_transaction(tx: Transaction) -> Uint: raise InitCodeTooLargeError("Code size too large") if U256(tx.nonce) >= U256(U64.MAX_VALUE): raise NonceOverflowError("Nonce too high") + if isinstance(tx, FeeMarketTransaction): + if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: + raise PriorityFeeGreaterThanMaxFeeError( + "priority fee greater than max fee" + ) return intrinsic_gas From f8733cda915237d6f38ef3ec9e3027afc33c2663 Mon Sep 17 00:00:00 2001 From: Mario Vega <marioevz@gmail.com> Date: Tue, 4 Aug 2026 01:23:05 -0600 Subject: [PATCH 194/233] feat(test-types): Fork-based State Commitment Property in Alloc (#3279) * feat(test-types): Fork-based state commitment in alloc * fix(test-types): Comments * fix(test-pytest): Remove `| None` from fork * fix(test-types): Remove explicit PBT references * fix(test-plugins): `Genesis` type state commitment * fix(test-types): Fix _materialize_state * refactor(test-types): Add state-commitment `None` guard --- .../execution_testing/base_types/__init__.py | 2 + .../base_types/composite_types.py | 10 +++ .../execute/eth_config/execute_types.py | 9 +++ .../plugins/execute/pre_alloc.py | 10 +-- .../plugins/execute/rpc/hive.py | 3 + .../execute/tests/test_execute_remote.py | 1 + .../plugins/filler/pre_alloc.py | 20 +---- .../plugins/shared/pre_alloc.py | 3 +- .../client_clis/tests/test_execution_specs.py | 6 +- .../client_clis/tests/test_transition_tool.py | 3 + .../fixtures/pre_alloc_groups.py | 10 +++ .../src/execution_testing/forks/base_fork.py | 6 ++ .../src/execution_testing/specs/blockchain.py | 4 + .../test_types/account_types.py | 80 +++++++++++++++---- .../test_types/tests/test_alloc_prestate.py | 7 +- .../evm_tools/t8n/__init__.py | 1 + 16 files changed, 129 insertions(+), 46 deletions(-) diff --git a/packages/testing/src/execution_testing/base_types/__init__.py b/packages/testing/src/execution_testing/base_types/__init__.py index 7221fc17992..9203aae5d0f 100644 --- a/packages/testing/src/execution_testing/base_types/__init__.py +++ b/packages/testing/src/execution_testing/base_types/__init__.py @@ -27,6 +27,7 @@ Alloc, BlobSchedule, ForkBlobSchedule, + StateCommitment, Storage, StorageRootType, ) @@ -77,6 +78,7 @@ "ReferenceSpec", "RLPSerializable", "SignableRLPSerializable", + "StateCommitment", "Storage", "StorageKey", "StorageRootType", diff --git a/packages/testing/src/execution_testing/base_types/composite_types.py b/packages/testing/src/execution_testing/base_types/composite_types.py index 05c05889b2d..b25ff1dcec1 100644 --- a/packages/testing/src/execution_testing/base_types/composite_types.py +++ b/packages/testing/src/execution_testing/base_types/composite_types.py @@ -3,6 +3,7 @@ import hashlib import json from dataclasses import dataclass +from enum import Enum, auto from typing import ( Any, ClassVar, @@ -569,6 +570,15 @@ class Alloc(EthereumTestRootModel[Dict[Address, Account | None]]): ) +class StateCommitment(Enum): + """ + The state-commitment scheme used to compute an allocation's state root. + """ + + MPT = auto() + """Merkle-Patricia trie.""" + + class AccessList(CamelModel, RLPSerializable): """Access List for transactions.""" diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/eth_config/execute_types.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/eth_config/execute_types.py index e82a61d31f5..73615898ccb 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/eth_config/execute_types.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/eth_config/execute_types.py @@ -358,6 +358,15 @@ class Genesis(CamelModel): base_fee_per_gas: HexNumber = HexNumber(10**9) number: HexNumber = HexNumber(0) + def model_post_init(self, __context: Any) -> None: + """ + Seed the alloc's commitment scheme from the genesis fork. + """ + super().model_post_init(__context) + self.alloc.migrate_state_commitment( + self.config.fork().state_commitment() + ) + @cached_property def hash(self) -> Hash: """Calculate the genesis hash.""" diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py index 4b80161a9b4..45ab14ec864 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/pre_alloc.py @@ -1299,19 +1299,13 @@ def pre( request: pytest.FixtureRequest, ) -> Generator[Alloc, None, None]: """Return default pre allocation for all tests (Empty alloc).""" - # FIXME: Static tests don't have a fork so we need to get it from the node. - actual_fork = fork - if actual_fork is None: - assert hasattr(request.node, "fork") - actual_fork = request.node.fork - # Prepare the pre-alloc logger.debug( f"Initializing pre-alloc for test {request.node.nodeid} " - f"(fork={actual_fork}, chain_id={chain_config.chain_id})" + f"(fork={fork}, chain_id={chain_config.chain_id})" ) pre = Alloc( - fork=actual_fork, + fork=fork, flags=alloc_flags, stub_eoas=stub_eoas, sender=worker_key, diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/rpc/hive.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/rpc/hive.py index 6779a0eb903..608d3d43d8d 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/rpc/hive.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/rpc/hive.py @@ -150,6 +150,9 @@ def build_genesis_header( pre_alloc = Alloc.merge(pre_alloc, base_pre) if empty_accounts := pre_alloc.empty_accounts(): raise Exception(f"Empty accounts in pre state: {empty_accounts}") + pre_alloc.migrate_state_commitment( + session_fork.transitions_from().state_commitment() + ) state_root = pre_alloc.state_root() genesis = FixtureHeader( parent_hash=0, diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/tests/test_execute_remote.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/tests/test_execute_remote.py index 562abb90a59..471ad8244af 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/tests/test_execute_remote.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/tests/test_execute_remote.py @@ -107,6 +107,7 @@ def _build_client_genesis(seed_keys: List[EOA]) -> dict: genesis_alloc = Alloc.merge( Alloc.model_validate(TEST_FORK.pre_allocation_blockchain()), Alloc(alloc_dict), + state_commitment=TEST_FORK.state_commitment(), ) if empty_accounts := genesis_alloc.empty_accounts(): raise Exception(f"Empty accounts in pre state: {empty_accounts}") diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py index b7d96d3beea..7385b825c8b 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py @@ -73,7 +73,7 @@ class Alloc(SharedAlloc): def __init__( self, *args: Any, - fork: Fork, + fork: Fork | TransitionFork, flags: AllocFlags, stub_accounts: Dict[str, Account] | None = None, stub_eoas: Dict[str, EOA] | None = None, @@ -435,7 +435,7 @@ def sha256_from_string(s: str) -> int: @pytest.fixture(scope="function") def node_id_for_entropy( - request: pytest.FixtureRequest, fork: Fork | None + request: pytest.FixtureRequest, fork: Fork | TransitionFork ) -> str: """ Return the node id with the fixture format name and fork name stripped. @@ -453,11 +453,6 @@ def node_id_for_entropy( # deterministic regardless of whether xdist is active. if "@" in node_id: node_id = node_id.rsplit("@", 1)[0] - if fork is None: - # FIXME: Static tests don't have a fork, so we need to get it from the - # node. - assert hasattr(request.node, "fork") - fork = request.node.fork for fixture_format_name in ALL_FIXTURE_FORMAT_NAMES: if fixture_format_name in node_id: parts = node_id.split("::") @@ -495,21 +490,14 @@ def stub_eoas( @pytest.fixture(scope="function") def pre( alloc_flags: AllocFlags, - fork: Fork | None, - request: pytest.FixtureRequest, + fork: Fork | TransitionFork, stub_accounts: Dict[str, Account], stub_eoas: Dict[str, EOA], ) -> Alloc: """Return default pre allocation for all tests (Empty alloc).""" - # FIXME: Static tests don't have a fork so we need to get it from the node. - actual_fork = fork - if actual_fork is None: - assert hasattr(request.node, "fork") - actual_fork = request.node.fork - return Alloc( flags=alloc_flags, - fork=actual_fork, + fork=fork, stub_accounts=stub_accounts, stub_eoas=stub_eoas, ) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/pre_alloc.py index 5076bf2fa52..342a49febda 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/pre_alloc.py @@ -61,7 +61,7 @@ def assert_mutable(self) -> None: def __init__( self, *args: Any, - fork: Fork, + fork: Fork | TransitionFork, flags: AllocFlags, stub_eoas: Dict[str, EOA] | None = None, **kwargs: Any, @@ -70,6 +70,7 @@ def __init__( super().__init__(*args, **kwargs) self._fork = fork self._flags = flags + self._state_commitment = fork.transitions_from().state_commitment() if stub_eoas is not None: self._stub_eoas = stub_eoas diff --git a/packages/testing/src/execution_testing/client_clis/tests/test_execution_specs.py b/packages/testing/src/execution_testing/client_clis/tests/test_execution_specs.py index 2d88da5e8c1..c8114b3a3bc 100644 --- a/packages/testing/src/execution_testing/client_clis/tests/test_execution_specs.py +++ b/packages/testing/src/execution_testing/client_clis/tests/test_execution_specs.py @@ -11,7 +11,7 @@ import pytest from pydantic import TypeAdapter -from execution_testing.base_types import to_json +from execution_testing.base_types import StateCommitment, to_json from execution_testing.client_clis import ( ExecutionSpecsTransitionTool, TransitionTool, @@ -93,7 +93,9 @@ def test_calc_state_root( expected_hash: bytes, ) -> None: """Test calculation of the state root against expected hash.""" - assert Alloc(alloc).state_root().startswith(expected_hash) + test_alloc = Alloc(alloc) + test_alloc.migrate_state_commitment(StateCommitment.MPT) + assert test_alloc.state_root().startswith(expected_hash) @pytest.mark.parametrize("evm_tool", [ExecutionSpecsTransitionTool]) diff --git a/packages/testing/src/execution_testing/client_clis/tests/test_transition_tool.py b/packages/testing/src/execution_testing/client_clis/tests/test_transition_tool.py index 4e7a7e82ff3..97ff0ee83cc 100644 --- a/packages/testing/src/execution_testing/client_clis/tests/test_transition_tool.py +++ b/packages/testing/src/execution_testing/client_clis/tests/test_transition_tool.py @@ -9,6 +9,7 @@ import ijson # type: ignore[import-untyped] import pytest +from execution_testing.base_types import StateCommitment from execution_testing.client_clis import ( CLINotFoundInPathError, EvmOneTransitionTool, @@ -116,6 +117,7 @@ def test_unknown_binary_path() -> None: TEST_ALLOC = Alloc.model_validate( {0xA: {"balance": 1, "nonce": 2, "code": "0x00"}} ) +TEST_ALLOC.migrate_state_commitment(StateCommitment.MPT) TEST_ALLOC_STATE_ROOT = TEST_ALLOC.state_root() @@ -165,6 +167,7 @@ def test_lazy_alloc_file_handles_mixed_entries(tmp_path: Path) -> None: 0xC: {"balance": "0xff", "nonce": 0, "code": "0x"}, } ) + alloc.migrate_state_commitment(StateCommitment.MPT) state_root = alloc.state_root() alloc_path = tmp_path / "alloc.json" alloc_path.write_text(alloc.model_dump_json()) diff --git a/packages/testing/src/execution_testing/fixtures/pre_alloc_groups.py b/packages/testing/src/execution_testing/fixtures/pre_alloc_groups.py index 1c854e45d6d..44a2f5f8999 100644 --- a/packages/testing/src/execution_testing/fixtures/pre_alloc_groups.py +++ b/packages/testing/src/execution_testing/fixtures/pre_alloc_groups.py @@ -53,6 +53,15 @@ class PreAllocGroupBuilder(CamelModel): ) pre: Alloc + def model_post_init(self, __context: Any) -> None: + """ + Seed the pre-alloc's commitment scheme from its genesis fork. + """ + super().model_post_init(__context) + self.pre.migrate_state_commitment( + self.fork.transitions_from().state_commitment() + ) + def get_pre_account_count(self) -> int: """Return the amount of accounts the pre-allocation group holds.""" return len(self.pre.root) @@ -71,6 +80,7 @@ def calculate_genesis(self) -> FixtureHeader: def add_test_alloc(self, test_id: str, new_pre: Alloc) -> None: """Adds a pre to this builder's pre.""" + assert self.pre.state_commitment() == new_pre.state_commitment() self.pre = Alloc.merge( self.pre, new_pre, diff --git a/packages/testing/src/execution_testing/forks/base_fork.py b/packages/testing/src/execution_testing/forks/base_fork.py index 08305f5eefe..abeea03769a 100644 --- a/packages/testing/src/execution_testing/forks/base_fork.py +++ b/packages/testing/src/execution_testing/forks/base_fork.py @@ -25,6 +25,7 @@ AccessList, Address, BlobSchedule, + StateCommitment, ) from execution_testing.base_types.conversions import BytesConvertible from execution_testing.vm import ( @@ -458,6 +459,11 @@ def __init_subclass__( if base_fork_class is not BaseFork: cls._deployed = base_fork_class._deployed + @classmethod + def state_commitment(cls) -> StateCommitment: + """Return the state-commitment scheme for the state root.""" + return StateCommitment.MPT + # Header information abstract methods @classmethod @abstractmethod diff --git a/packages/testing/src/execution_testing/specs/blockchain.py b/packages/testing/src/execution_testing/specs/blockchain.py index a96b02df608..519944255a4 100644 --- a/packages/testing/src/execution_testing/specs/blockchain.py +++ b/packages/testing/src/execution_testing/specs/blockchain.py @@ -819,6 +819,10 @@ def make_genesis( ) if empty_accounts := pre_alloc.empty_accounts(): raise Exception(f"Empty accounts in pre state: {empty_accounts}") + if pre_alloc.state_commitment() is None: + pre_alloc.migrate_state_commitment( + self.fork.transitions_from().state_commitment() + ) state_root = pre_alloc.state_root() genesis = FixtureHeader.genesis( self.fork.transitions_from(), env, state_root diff --git a/packages/testing/src/execution_testing/test_types/account_types.py b/packages/testing/src/execution_testing/test_types/account_types.py index e9f498b6fe3..29454e84db9 100644 --- a/packages/testing/src/execution_testing/test_types/account_types.py +++ b/packages/testing/src/execution_testing/test_types/account_types.py @@ -4,6 +4,7 @@ from collections.abc import Sequence from dataclasses import dataclass from enum import Enum, auto +from types import ModuleType from typing import ( Any, Dict, @@ -30,6 +31,7 @@ Hash, HashInt, Number, + StateCommitment, Storage, StorageRootType, ) @@ -121,6 +123,13 @@ class Alloc(BaseAlloc): _phase: _Phase = PrivateAttr(default=_Phase.CONSTRUCTION) _code_store: Dict[Hash32, Bytes] = PrivateAttr(default_factory=dict) + _state_commitment: StateCommitment | None = PrivateAttr(default=None) + """ + Commitment scheme this allocation's state root is computed under. + + Unset by default: it must be seeded from the accompanying fork before any + state-root computation. + """ @dataclass(kw_only=True) class UnexpectedAccountError(Exception): @@ -199,6 +208,7 @@ def merge( alloc_1: "Alloc", alloc_2: "Alloc", key_collision_mode: KeyCollisionMode = KeyCollisionMode.OVERWRITE, + state_commitment: StateCommitment | None = None, ) -> "Alloc": """Return merged allocation of two sources.""" overlapping_keys = alloc_1.root.keys() & alloc_2.root.keys() @@ -222,18 +232,21 @@ def merge( account_1=account_1, account_2=account_2, ) - merged = alloc_1.model_dump() + merged = alloc_1.model_copy(deep=True) for address, other_account in alloc_2.root.items(): - merged_account = Account.merge( - merged.get(address, None), other_account - ) + merged_account = Account.merge(merged.get(address), other_account) if merged_account: merged[address] = merged_account elif address in merged: - merged.pop(address, None) + merged.root.pop(address, None) - return Alloc(merged) + if state_commitment is not None: + merged.migrate_state_commitment(state_commitment) + else: + # By default, state commitment of the second alloc takes precedence + merged.migrate_state_commitment(alloc_2.state_commitment()) + return merged def __iter__(self) -> Iterator[Address]: # type: ignore [override] """Return iterator over the allocation.""" @@ -300,7 +313,7 @@ def empty_accounts(self) -> List[Address]: def state_root(self) -> Hash: """Return state root of the allocation.""" - return Hash(spec_state_mpt.state_root(self._materialize_state())) + return Hash(self._state_module().state_root(self._materialize_state())) def verify_post_alloc(self, got_alloc: "Alloc") -> None: """ @@ -357,25 +370,40 @@ def _ensure_live(self) -> None: self._build_cache() self._phase = _Phase.LIVE - def _materialize_state(self) -> spec_state_mpt.State: + def _state_module(self) -> ModuleType: + """ + Return the spec state module implementing `self._state_commitment`. + """ + if self._state_commitment is None: + raise ValueError( + "Alloc state commitment is unset; seed it from the " + "accompanying fork." + ) + if self._state_commitment is StateCommitment.MPT: + return spec_state_mpt + raise NotImplementedError("State commitment type not yet implemented.") + + def _materialize_state(self) -> spec_state.PreState: """ - Build an in-memory `ethereum.state_mpt.State` mirror of - `self.root`. + Build a spec-side `PreState` mirror of `self.root` using the + implementation module for this allocation's commitment scheme. Used as the trie-backed delegate for `compute_state_root` (a cold, once-per-block call). The materialized state is not retained. """ - state = spec_state_mpt.State() + mod = self._state_module() + state: spec_state.PreState = mod.State() for address, account in self.root.items(): if account is None: continue addr = Bytes20(address) code = bytes(account.code) if account.code else b"" - code_hash = ( - spec_keccak256(code) if code else spec_state.EMPTY_CODE_HASH - ) - spec_state_mpt.set_account( + if code: + code_hash = mod.store_code(state, code) + else: + code_hash = spec_state.EMPTY_CODE_HASH + mod.set_account( state, addr, spec_state.Account( @@ -388,13 +416,12 @@ def _materialize_state(self) -> spec_state_mpt.State: value_int = int(value_hi) if value_int == 0: continue - spec_state_mpt.set_storage( + mod.set_storage( state, addr, Bytes32(int(key_hi).to_bytes(32, "big")), U256(value_int), ) - state._code_store.update(self._code_store) return state def get_account_optional( @@ -563,6 +590,25 @@ def freeze(self) -> None: """Lock the allocation: no further mutations allowed.""" self._phase = _Phase.FROZEN + def state_commitment(self) -> StateCommitment | None: + """ + Return the commitment scheme this allocation is committed under, or + `None` if it has not been seeded from a fork yet. + """ + return self._state_commitment + + def migrate_state_commitment( + self, commitment: StateCommitment | None + ) -> None: + """ + Switch the commitment scheme used to compute the state root. + """ + if self._phase is _Phase.FROZEN: + raise RuntimeError( + "migrate_state_commitment not allowed: Alloc is FROZEN" + ) + self._state_commitment = commitment + def deterministic_deploy_contract( self, *, diff --git a/packages/testing/src/execution_testing/test_types/tests/test_alloc_prestate.py b/packages/testing/src/execution_testing/test_types/tests/test_alloc_prestate.py index cc7798af169..57b558882f3 100644 --- a/packages/testing/src/execution_testing/test_types/tests/test_alloc_prestate.py +++ b/packages/testing/src/execution_testing/test_types/tests/test_alloc_prestate.py @@ -22,7 +22,7 @@ from ethereum_types.bytes import Bytes20, Bytes32 from ethereum_types.numeric import U256, Uint -from execution_testing.base_types import Account +from execution_testing.base_types import Account, StateCommitment from execution_testing.test_types import Alloc from execution_testing.test_types.account_types import _Phase @@ -45,7 +45,7 @@ def _b20(hex_str: str) -> Bytes20: def _fixture_alloc() -> Alloc: """Build a small alloc with one EOA, one contract, and one empty acct.""" - return Alloc.model_validate( + alloc = Alloc.model_validate( { ADDR_A: {"balance": 100, "nonce": 1}, ADDR_B: { @@ -57,6 +57,8 @@ def _fixture_alloc() -> Alloc: ADDR_C: {"balance": 0, "nonce": 0}, } ) + alloc.migrate_state_commitment(StateCommitment.MPT) + return alloc def _state_from_alloc(alloc: Alloc) -> spec_state_mpt.State: @@ -235,6 +237,7 @@ def test_apply_diff_round_trip_matches_independent_post_state() -> None: }, } ) + alloc_post_expected.migrate_state_commitment(StateCommitment.MPT) # Build the diff that, applied to alloc_pre, should produce # alloc_post_expected. diff --git a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py b/src/ethereum_spec_tools/evm_tools/t8n/__init__.py index 5d263f3fc34..a6f6fe42c3e 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/__init__.py @@ -224,6 +224,7 @@ def __init__( if isinstance(input_alloc, LazyAlloc): input_alloc = input_alloc.materialize() self.alloc = input_alloc.model_copy(deep=True) + self.alloc.migrate_state_commitment(t8n_data.fork.state_commitment()) self.env = t8n_data.env self.txs = list(t8n_data.txs) self.ommers = list(ommers) From 9863a6ddcddef83118104941c5f4151783b11245 Mon Sep 17 00:00:00 2001 From: Guruprasad Kamath <48196632+gurukamath@users.noreply.github.com> Date: Tue, 4 Aug 2026 15:37:08 +0200 Subject: [PATCH 195/233] refactor(specs): remove Message dataclass and update evm function flow (#3192) --- src/ethereum/forks/amsterdam/fork.py | 440 ++++++++---------- src/ethereum/forks/amsterdam/transactions.py | 110 ++++- src/ethereum/forks/amsterdam/utils/message.py | 92 ---- src/ethereum/forks/amsterdam/vm/__init__.py | 58 +-- .../forks/amsterdam/vm/eoa_delegation.py | 100 ++-- src/ethereum/forks/amsterdam/vm/gas.py | 169 ++++++- .../forks/amsterdam/vm/instructions/block.py | 18 +- .../amsterdam/vm/instructions/environment.py | 38 +- .../forks/amsterdam/vm/instructions/log.py | 4 +- .../amsterdam/vm/instructions/storage.py | 34 +- .../forks/amsterdam/vm/instructions/system.py | 173 ++++--- .../forks/amsterdam/vm/interpreter.py | 426 +++++++++-------- .../vm/precompiled_contracts/alt_bn128.py | 6 +- .../vm/precompiled_contracts/blake2f.py | 2 +- .../bls12_381/bls12_381_g1.py | 6 +- .../bls12_381/bls12_381_g2.py | 6 +- .../bls12_381/bls12_381_pairing.py | 2 +- .../vm/precompiled_contracts/ecrecover.py | 2 +- .../vm/precompiled_contracts/identity.py | 2 +- .../vm/precompiled_contracts/modexp.py | 2 +- .../vm/precompiled_contracts/p256verify.py | 2 +- .../precompiled_contracts/point_evaluation.py | 2 +- .../vm/precompiled_contracts/ripemd160.py | 2 +- .../vm/precompiled_contracts/sha256.py | 2 +- .../evm_tools/t8n/evm_trace/eip3155.py | 30 +- .../evm_tools/t8n/evm_trace/protocols.py | 12 +- src/ethereum_spec_tools/new_fork/builder.py | 55 ++- .../new_fork/codemod/constant.py | 15 + tests/evm_tools/test_new_fork.py | 10 +- .../eip7594_peerdas/test_max_blob_per_tx.py | 7 +- vulture_whitelist.py | 1 + 31 files changed, 1053 insertions(+), 775 deletions(-) delete mode 100644 src/ethereum/forks/amsterdam/utils/message.py diff --git a/src/ethereum/forks/amsterdam/fork.py b/src/ethereum/forks/amsterdam/fork.py index 1bcbc252477..da6a5929f0b 100644 --- a/src/ethereum/forks/amsterdam/fork.py +++ b/src/ethereum/forks/amsterdam/fork.py @@ -12,21 +12,19 @@ """ from dataclasses import dataclass -from typing import Final, List, Optional, Tuple, final +from typing import List, Optional, Tuple, final from ethereum_rlp import rlp -from ethereum_types.bytes import Bytes +from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U64, U256, Uint, ulen from ethereum.crypto.hash import Hash32, keccak256 from ethereum.exceptions import ( EthereumException, - GasUsedExceedsLimitError, InsufficientBalanceError, InvalidBlock, InvalidSenderError, - NonceMismatchError, ) from ethereum.forks.bpo5.blocks import Header as PreviousHeader from ethereum.merkle_patricia_trie import root, trie_set @@ -42,18 +40,8 @@ ) from .blocks import Block, Header, Log, Receipt, Withdrawal, encode_receipt from .bloom import logs_bloom -from .exceptions import ( - BlobCountExceededError, - BlobGasLimitExceededError, - EmptyAuthorizationListError, - InsufficientMaxFeePerBlobGasError, - InsufficientMaxFeePerGasError, - InvalidBlobVersionedHashError, - NoBlobDataError, - TransactionTypeContractCreationError, - WrongChainIdError, -) -from .fork_types import Authorization, BlockAccessIndex, VersionedHash +from .exceptions import WrongChainIdError +from .fork_types import Authorization, BlockAccessIndex from .requests import ( BUILDER_DEPOSIT_REQUEST_TYPE, BUILDER_EXIT_REQUEST_TYPE, @@ -76,13 +64,14 @@ set_account_balance, ) from .transactions import ( - TX_MAX_GAS_LIMIT, BlobTransaction, - FeeMarketCapableTransaction, LegacyTransaction, SetCodeTransaction, Transaction, + calculate_effective_gas_price, + calculate_max_gas_fee, chain_id, + check_nonce, decode_transaction, encode_transaction, get_transaction_hash, @@ -90,21 +79,25 @@ recover_sender, validate_transaction, ) +from .utils.address import compute_contract_address from .utils.hexadecimal import hex_to_address -from .utils.message import prepare_message -from .vm import Message from .vm.eoa_delegation import is_valid_delegation +from .vm.gas import ( + MAX_BLOB_GAS_PER_BLOCK as MAX_BLOB_GAS_PER_BLOCK, +) from .vm.gas import ( GasCosts, StateGasCosts, + TransactionGasSettlement, allocate_evm_gas, - calculate_blob_gas_price, calculate_data_fee, calculate_excess_blob_gas, calculate_total_blob_gas, + check_block_gas_capacity, + check_max_fee_per_blob_gas, settle_transaction_gas, ) -from .vm.interpreter import MessageCallOutput, process_message_call +from .vm.interpreter import TransactionOutput, process_top_level BASE_FEE_MAX_CHANGE_DENOMINATOR = Uint(8) ELASTICITY_MULTIPLIER = Uint(2) @@ -119,10 +112,6 @@ Upper bound on the number of new storage slots a single system call is expected to write. """ -MAX_BLOB_GAS_PER_BLOCK: Final[U64] = ( - GasCosts.BLOB_SCHEDULE_MAX * GasCosts.PER_BLOB -) -VERSIONED_HASH_VERSION_KZG = b"\x01" GWEI_TO_WEI = U256(10**9) WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS = hex_to_address( @@ -143,7 +132,6 @@ MAX_BLOCK_SIZE = 10_485_760 SAFETY_MARGIN = 2_097_152 MAX_RLP_BLOCK_SIZE = MAX_BLOCK_SIZE - SAFETY_MARGIN -BLOB_COUNT_LIMIT = 6 @final @@ -497,11 +485,14 @@ def check_transaction( block_env: vm.BlockEnvironment, block_output: vm.BlockOutput, tx: Transaction, - sender: Address, - tx_state: TransactionState, -) -> Tuple[Uint, Tuple[VersionedHash, ...], U64]: + index: Uint, +) -> vm.TransactionEnvironment: """ - Check if the transaction is includable in the block. + Admit a raw transaction and build its execution environment. + + Recover the sender, statically validate the transaction, and check + that it is includable in the block, in that order, so that a + transaction invalid in several ways reports the earliest failure. Parameters ---------- @@ -511,24 +502,23 @@ def check_transaction( The block output for the current block. tx : The transaction. - sender : - The recovered sender address of the transaction. - tx_state : - The transaction state tracker. + index : + The index of the current transaction. Returns ------- - effective_gas_price : - The price to charge for gas when the transaction is executed. - blob_versioned_hashes : - The blob versioned hashes of the transaction. - tx_blob_gas_used: - The blob gas used by the transaction. + tx_env : + The environment for executing the transaction. Raises ------ InvalidBlock : If the transaction is not includable. + InvalidSignatureError : + If the transaction's signature is invalid. + InsufficientTransactionGasError : + If the transaction does not provide enough gas to cover its + intrinsic cost. GasUsedExceedsLimitError : If the gas used by the transaction exceeds the block's gas limit. NonceMismatchError : @@ -544,78 +534,29 @@ def check_transaction( BlobGasLimitExceededError : If the blob gas used by the transaction exceeds the block's blob gas limit. - InvalidBlobVersionedHashError : - If the transaction contains a blob versioned hash with an invalid - version. - NoBlobDataError : - If the transaction is a type 3 but has no blobs. - BlobCountExceededError : - If the transaction is a type 3 and has more blobs than the limit. - TransactionTypeContractCreationError: - If the transaction type is not allowed to create contracts. - EmptyAuthorizationListError : - If the transaction is a SetCodeTransaction and the authorization list - is empty. """ - execution_gas_available = ( - block_env.block_gas_limit - block_output.block_gas_used - ) - state_gas_available = ( - block_env.block_gas_limit - block_output.block_state_gas_used - ) - blob_gas_available = MAX_BLOB_GAS_PER_BLOCK - block_output.blob_gas_used - - # EIP-8037 per-dimension inclusion check. - if min(TX_MAX_GAS_LIMIT, tx.gas) > execution_gas_available: - raise GasUsedExceedsLimitError("execution gas used exceeds limit") - - if tx.gas > state_gas_available: - raise GasUsedExceedsLimitError("state gas used exceeds limit") + sender = recover_sender(tx) + intrinsic = validate_transaction(tx, sender) + tx_state = TransactionState(parent=block_env.state) - tx_blob_gas_used = calculate_total_blob_gas(tx) - if tx_blob_gas_used > blob_gas_available: - raise BlobGasLimitExceededError("blob gas limit exceeded") + check_block_gas_capacity( + block_env, block_output, tx.gas, calculate_total_blob_gas(tx) + ) sender_account = get_account(tx_state, sender) - if isinstance(tx, FeeMarketCapableTransaction): - if tx.max_fee_per_gas < block_env.base_fee_per_gas: - raise InsufficientMaxFeePerGasError( - tx.max_fee_per_gas, block_env.base_fee_per_gas - ) - - priority_fee_per_gas = min( - tx.max_priority_fee_per_gas, - tx.max_fee_per_gas - block_env.base_fee_per_gas, - ) - effective_gas_price = priority_fee_per_gas + block_env.base_fee_per_gas - max_gas_fee = tx.gas * tx.max_fee_per_gas - else: - if tx.gas_price < block_env.base_fee_per_gas: - raise InvalidBlock - effective_gas_price = tx.gas_price - max_gas_fee = tx.gas * tx.gas_price + effective_gas_price = calculate_effective_gas_price( + tx, block_env.base_fee_per_gas + ) + max_gas_fee = calculate_max_gas_fee(tx, tx.gas) if isinstance(tx, BlobTransaction): - blob_count = len(tx.blob_versioned_hashes) - if blob_count == 0: - raise NoBlobDataError("no blob data in transaction") - if blob_count > BLOB_COUNT_LIMIT: - raise BlobCountExceededError( - f"Tx has {blob_count} blobs. Max allowed: {BLOB_COUNT_LIMIT}" - ) - for blob_versioned_hash in tx.blob_versioned_hashes: - if blob_versioned_hash[0:1] != VERSIONED_HASH_VERSION_KZG: - raise InvalidBlobVersionedHashError( - "invalid blob versioned hash" - ) - - blob_gas_price = calculate_blob_gas_price(block_env.excess_blob_gas) - if Uint(tx.max_fee_per_blob_gas) < blob_gas_price: - raise InsufficientMaxFeePerBlobGasError( - "insufficient max fee per blob gas" - ) + check_max_fee_per_blob_gas( + tx.blob_versioned_hashes, + tx.max_fee_per_blob_gas, + block_env.excess_blob_gas, + ) max_gas_fee += Uint(calculate_total_blob_gas(tx)) * Uint( tx.max_fee_per_blob_gas @@ -624,18 +565,7 @@ def check_transaction( else: blob_versioned_hashes = () - if isinstance(tx, (BlobTransaction, SetCodeTransaction)): - if not isinstance(tx.to, Address): - raise TransactionTypeContractCreationError(tx) - - if isinstance(tx, SetCodeTransaction): - if not any(tx.authorizations): - raise EmptyAuthorizationListError("empty authorization list") - - if sender_account.nonce > Uint(tx.nonce): - raise NonceMismatchError("nonce too low") - elif sender_account.nonce < Uint(tx.nonce): - raise NonceMismatchError("nonce too high") + check_nonce(tx, sender_account.nonce) if Uint(sender_account.balance) < max_gas_fee + Uint(tx.value): raise InsufficientBalanceError("insufficient sender balance") @@ -645,10 +575,54 @@ def check_transaction( ): raise InvalidSenderError("not EOA") - return ( - effective_gas_price, - blob_versioned_hashes, - tx_blob_gas_used, + # Split the EVM gas into an execution-gas grant (capped by the + # remaining execution-gas budget) and a state gas reservoir. + allocation = allocate_evm_gas(tx.gas, intrinsic) + + access_list_addresses = set() + access_list_storage_keys = set() + if has_access_list(tx): + for access in tx.access_list: + access_list_addresses.add(access.account) + for slot in access.slots: + access_list_storage_keys.add((access.account, slot)) + + authorizations: Tuple[Authorization, ...] = () + if isinstance(tx, SetCodeTransaction): + authorizations = tx.authorizations + + if isinstance(tx.to, Bytes0): + is_create = True + # A creation's frame runs at the address the contract + # deploys to. + recipient = compute_contract_address(sender, sender_account.nonce) + else: + is_create = False + recipient = tx.to + + accounts_with_paid_writes = {sender} + if is_create or tx.value > U256(0): + accounts_with_paid_writes.add(recipient) + + return vm.TransactionEnvironment( + origin=sender, + recipient=recipient, + is_create=is_create, + data=tx.data, + value=tx.value, + gas_limit=tx.gas, + effective_gas_price=effective_gas_price, + execution_gas_grant=allocation.execution_gas, + state_gas_reservoir=allocation.state_gas_reservoir, + calldata_floor=intrinsic.calldata_floor, + access_list_addresses=access_list_addresses, + access_list_storage_keys=access_list_storage_keys, + accounts_with_paid_writes=accounts_with_paid_writes, + state=tx_state, + blob_versioned_hashes=blob_versioned_hashes, + authorizations=authorizations, + index_in_block=index, + tx_hash=get_transaction_hash(encode_transaction(tx)), ) @@ -693,7 +667,7 @@ def process_checked_system_transaction( block_env: vm.BlockEnvironment, target_address: Address, data: Bytes, -) -> MessageCallOutput: +) -> TransactionOutput: """ Process a system transaction and raise an error if the contract does not contain code or if the transaction fails. @@ -709,8 +683,8 @@ def process_checked_system_transaction( Returns ------- - system_tx_output : `MessageCallOutput` - Output of processing the system transaction. + system_tx_output : `TransactionOutput` + The settled output of the system transaction. """ # Pre-check that the system contract has code. We use a throwaway @@ -752,7 +726,7 @@ def process_unchecked_system_transaction( block_env: vm.BlockEnvironment, target_address: Address, data: Bytes, -) -> MessageCallOutput: +) -> TransactionOutput: """ Process a system transaction without checking if the contract contains code or if the transaction fails. @@ -768,27 +742,29 @@ def process_unchecked_system_transaction( Returns ------- - system_tx_output : `MessageCallOutput` - Output of processing the system transaction. + system_tx_output : `TransactionOutput` + The settled output of the system transaction. """ system_tx_state = TransactionState(parent=block_env.state) - system_contract_code = get_code( - system_tx_state, - get_account(system_tx_state, target_address).code_hash, - ) tx_env = vm.TransactionEnvironment( origin=SYSTEM_ADDRESS, recipient=target_address, + is_create=False, + data=data, value=U256(0), - gas_price=block_env.base_fee_per_gas, - gas=SYSTEM_TRANSACTION_GAS, + gas_limit=SYSTEM_TRANSACTION_GAS, + effective_gas_price=block_env.base_fee_per_gas, + execution_gas_grant=SYSTEM_TRANSACTION_GAS, state_gas_reservoir=( StateGasCosts.STORAGE_SET * SYSTEM_MAX_SSTORES_PER_CALL ), + calldata_floor=Uint(0), access_list_addresses=set(), access_list_storage_keys=set(), + # A system transaction charges no gas, so no write is paid for. + accounts_with_paid_writes=set(), state=system_tx_state, blob_versioned_hashes=(), authorizations=(), @@ -796,30 +772,7 @@ def process_unchecked_system_transaction( tx_hash=None, ) - system_tx_message = Message( - block_env=block_env, - tx_env=tx_env, - caller=SYSTEM_ADDRESS, - target=target_address, - gas=SYSTEM_TRANSACTION_GAS, - state_gas_reservoir=( - StateGasCosts.STORAGE_SET * SYSTEM_MAX_SSTORES_PER_CALL - ), - value=U256(0), - data=data, - code=system_contract_code, - depth=Uint(0), - current_target=target_address, - code_address=target_address, - should_transfer_value=False, - is_static=False, - accessed_addresses=set(), - accessed_storage_keys=set(), - disable_precompiles=False, - parent_evm=None, - ) - - system_tx_output = process_message_call(system_tx_message) + system_tx_output = process_top_level(block_env, tx_env) incorporate_tx_into_block( system_tx_state, block_env.block_access_list_builder @@ -969,6 +922,86 @@ def process_general_purpose_requests( ) +def update_sender_state( + block_env: vm.BlockEnvironment, + tx_env: vm.TransactionEnvironment, + tx: Transaction, +) -> None: + """ + Debit the sender for the transaction's maximum possible gas fee. + + Increment the sender's nonce and deduct the largest fee the + transaction could incur -- its gas limit priced at the effective gas + price, plus the blob fee resolved at inclusion -- up front. + Execution later refunds whatever execution gas was not spent. + + Parameters + ---------- + block_env : + The block's execution environment. + tx_env : + The transaction's execution environment. + tx : + The transaction being charged. + + """ + tx_state = tx_env.state + sender = tx_env.origin + sender_account = get_account(tx_state, sender) + + effective_gas_fee = tx_env.gas_limit * tx_env.effective_gas_price + if isinstance(tx, BlobTransaction): + blob_gas_fee = calculate_data_fee(block_env.excess_blob_gas, tx) + else: + blob_gas_fee = Uint(0) + + increment_nonce(tx_state, sender) + + sender_balance_after_gas_fee = ( + Uint(sender_account.balance) - effective_gas_fee - blob_gas_fee + ) + set_account_balance(tx_state, sender, U256(sender_balance_after_gas_fee)) + + +def disburse_gas_fees( + block_env: vm.BlockEnvironment, + tx_env: vm.TransactionEnvironment, + settlement: TransactionGasSettlement, + payer: Address, +) -> None: + """ + Refund the payer's unspent gas and pay the priority fee. + + Return the gas the transaction did not use to the ``payer`` that + fronted the maximum fee at inclusion, priced at the effective gas + price, and credit the coinbase with the priority fee on the gas that + was used. + + Parameters + ---------- + block_env : + The block scoped environment. + tx_env : + The transaction's execution environment. + settlement : + The settled gas amounts. + payer : + The account that fronted the maximum gas fee and receives the + refund. + + """ + tx_state = tx_env.state + gas_refund_amount = settlement.gas_left * tx_env.effective_gas_price + + priority_fee_per_gas = ( + tx_env.effective_gas_price - block_env.base_fee_per_gas + ) + transaction_fee = settlement.gas_used * priority_fee_per_gas + + create_ether(tx_state, payer, U256(gas_refund_amount)) + create_ether(tx_state, block_env.coinbase, U256(transaction_fee)) + + def process_transaction( block_env: vm.BlockEnvironment, block_output: vm.BlockOutput, @@ -1002,7 +1035,6 @@ def process_transaction( block_env.block_access_list_builder.block_access_index = BlockAccessIndex( index + Uint(1) ) - tx_state = TransactionState(parent=block_env.state) trie_set( block_output.transactions_trie, @@ -1017,98 +1049,26 @@ def process_transaction( actual=tx_chain_id, ) - sender = recover_sender(tx) - intrinsic = validate_transaction(tx, sender) - - ( - effective_gas_price, - blob_versioned_hashes, - tx_blob_gas_used, - ) = check_transaction( - block_env=block_env, - block_output=block_output, - tx=tx, - sender=sender, - tx_state=tx_state, - ) - - sender_account = get_account(tx_state, sender) - - if isinstance(tx, BlobTransaction): - blob_gas_fee = calculate_data_fee(block_env.excess_blob_gas, tx) - else: - blob_gas_fee = Uint(0) - - effective_gas_fee = tx.gas * effective_gas_price - - # Split the EVM gas into an execution-gas grant (capped by the - # remaining execution-gas budget) and a state gas reservoir. - allocation = allocate_evm_gas(tx.gas, intrinsic) - - increment_nonce(tx_state, sender) + tx_env = check_transaction(block_env, block_output, tx, index) - sender_balance_after_gas_fee = ( - Uint(sender_account.balance) - effective_gas_fee - blob_gas_fee - ) - set_account_balance(tx_state, sender, U256(sender_balance_after_gas_fee)) + update_sender_state(block_env, tx_env, tx) - access_list_addresses = set() - access_list_storage_keys = set() - access_list_addresses.add(block_env.coinbase) - if has_access_list(tx): - for access in tx.access_list: - access_list_addresses.add(access.account) - for slot in access.slots: - access_list_storage_keys.add((access.account, slot)) - - authorizations: Tuple[Authorization, ...] = () - if isinstance(tx, SetCodeTransaction): - authorizations = tx.authorizations - - tx_env = vm.TransactionEnvironment( - origin=sender, - recipient=tx.to, - value=tx.value, - gas_price=effective_gas_price, - gas=allocation.execution_gas, - state_gas_reservoir=allocation.state_gas_reservoir, - access_list_addresses=access_list_addresses, - access_list_storage_keys=access_list_storage_keys, - state=tx_state, - blob_versioned_hashes=blob_versioned_hashes, - authorizations=authorizations, - index_in_block=index, - tx_hash=get_transaction_hash(encode_transaction(tx)), - ) - - message = prepare_message(block_env, tx_env, tx) - - tx_output = process_message_call(message) + tx_output = process_top_level(block_env, tx_env) settlement = settle_transaction_gas( - tx.gas, - intrinsic, + tx_env.gas_limit, + tx_env.calldata_floor, tx_output.gas_left, tx_output.state_gas_left, tx_output.refund_counter, tx_output.state_gas_used, ) - gas_refund_amount = settlement.gas_left * effective_gas_price - - # For non-1559 transactions effective_gas_price == tx.gas_price - priority_fee_per_gas = effective_gas_price - block_env.base_fee_per_gas - transaction_fee = settlement.gas_used * priority_fee_per_gas - - # refund gas - create_ether(tx_state, sender, U256(gas_refund_amount)) - - # transfer miner fees - create_ether(tx_state, block_env.coinbase, U256(transaction_fee)) + disburse_gas_fees(block_env, tx_env, settlement, tx_env.origin) block_output.block_gas_used += settlement.execution_gas_used block_output.block_state_gas_used += settlement.state_gas_used - block_output.blob_gas_used += tx_blob_gas_used + block_output.blob_gas_used += calculate_total_blob_gas(tx) block_output.cumulative_gas_used += settlement.gas_used receipt = make_receipt( @@ -1127,9 +1087,11 @@ def process_transaction( block_output.block_logs += tx_output.logs for address in tx_output.accounts_to_delete: - clear_account_preserving_balance(tx_state, address) + clear_account_preserving_balance(tx_env.state, address) - incorporate_tx_into_block(tx_state, block_env.block_access_list_builder) + incorporate_tx_into_block( + tx_env.state, block_env.block_access_list_builder + ) def process_withdrawals( diff --git a/src/ethereum/forks/amsterdam/transactions.py b/src/ethereum/forks/amsterdam/transactions.py index fa51356d6ed..693064688da 100644 --- a/src/ethereum/forks/amsterdam/transactions.py +++ b/src/ethereum/forks/amsterdam/transactions.py @@ -16,14 +16,22 @@ from ethereum.crypto.hash import Hash32, keccak256 from ethereum.exceptions import ( InsufficientTransactionGasError, + InvalidBlock, InvalidSignatureError, + NonceMismatchError, NonceOverflowError, ) from ethereum.state import Address from .exceptions import ( + BlobCountExceededError, + EmptyAuthorizationListError, InitCodeTooLargeError, + InsufficientMaxFeePerGasError, + InvalidBlobVersionedHashError, + NoBlobDataError, PriorityFeeGreaterThanMaxFeeError, + TransactionTypeContractCreationError, TransactionTypeError, ) from .fork_types import Authorization, ExecutionGas, VersionedHash @@ -47,6 +55,16 @@ class IntrinsicGasCost: TX_MAX_GAS_LIMIT = Uint(16_777_216) +BLOB_COUNT_LIMIT = 6 +""" +Maximum number of blobs a single transaction may carry. +""" + +VERSIONED_HASH_VERSION_KZG = b"\x01" +""" +Version byte that every blob versioned hash must start with. +""" + ACCESS_LIST_ADDRESS_FLOOR_TOKENS = Uint(80) """ Floor data tokens contributed by a single access list address per @@ -599,14 +617,46 @@ def validate_transaction(tx: Transaction, sender: Address) -> IntrinsicGasCost: """ from .vm.interpreter import MAX_INIT_CODE_SIZE + if U256(tx.nonce) >= U256(U64.MAX_VALUE): + raise NonceOverflowError("Nonce too high") + + if tx.to == Bytes0(b"") and len(tx.data) > MAX_INIT_CODE_SIZE: + raise InitCodeTooLargeError("Code size too large") + + if isinstance(tx, FeeMarketCapableTransaction): + if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: + raise PriorityFeeGreaterThanMaxFeeError( + "priority fee greater than max fee" + ) + + if isinstance(tx, BlobTransaction): + blob_count = len(tx.blob_versioned_hashes) + if blob_count == 0: + raise NoBlobDataError("no blob data in transaction") + if blob_count > BLOB_COUNT_LIMIT: + raise BlobCountExceededError( + f"Tx has {blob_count} blobs. Max allowed: {BLOB_COUNT_LIMIT}" + ) + for blob_versioned_hash in tx.blob_versioned_hashes: + if blob_versioned_hash[0:1] != VERSIONED_HASH_VERSION_KZG: + raise InvalidBlobVersionedHashError( + "invalid blob versioned hash" + ) + + if isinstance(tx, (BlobTransaction, SetCodeTransaction)): + if not isinstance(tx.to, Address): + raise TransactionTypeContractCreationError(tx) + + if isinstance(tx, SetCodeTransaction): + if not any(tx.authorizations): + raise EmptyAuthorizationListError("empty authorization list") + intrinsic = calculate_intrinsic_cost(tx, sender) intrinsic_gas = Uint(intrinsic.execution) if intrinsic_gas > tx.gas: raise InsufficientTransactionGasError("Insufficient intrinsic gas") if intrinsic.calldata_floor > tx.gas: raise InsufficientTransactionGasError("Insufficient calldata floor") - if tx.to == Bytes0(b"") and len(tx.data) > MAX_INIT_CODE_SIZE: - raise InitCodeTooLargeError("Code size too large") if intrinsic.execution > TX_MAX_GAS_LIMIT: raise InsufficientTransactionGasError( "Intrinsic execution gas exceeds TX_MAX_GAS_LIMIT" @@ -615,13 +665,6 @@ def validate_transaction(tx: Transaction, sender: Address) -> IntrinsicGasCost: raise InsufficientTransactionGasError( "Intrinsic calldata floor exceeds TX_MAX_GAS_LIMIT" ) - if U256(tx.nonce) >= U256(U64.MAX_VALUE): - raise NonceOverflowError("Nonce too high") - if isinstance(tx, FeeMarketCapableTransaction): - if tx.max_fee_per_gas < tx.max_priority_fee_per_gas: - raise PriorityFeeGreaterThanMaxFeeError( - "priority fee greater than max fee" - ) return intrinsic @@ -745,6 +788,55 @@ def count_tokens_in_data(data: bytes) -> Uint: return num_zeros + num_non_zeros * Uint(4) +def calculate_effective_gas_price( + tx: Transaction, base_fee_per_gas: Uint +) -> Uint: + """ + Calculate the price per unit of gas the transaction actually pays. + + A fee-market transaction pays the base fee plus a priority fee + capped by both of its fee caps; its maximum fee must cover the base + fee, or an `InsufficientMaxFeePerGasError` is raised. A transaction + priced with a plain gas price pays that price outright, which must + likewise cover the base fee. + """ + if isinstance(tx, FeeMarketCapableTransaction): + if tx.max_fee_per_gas < base_fee_per_gas: + raise InsufficientMaxFeePerGasError( + tx.max_fee_per_gas, base_fee_per_gas + ) + + priority_fee_per_gas = min( + tx.max_priority_fee_per_gas, + tx.max_fee_per_gas - base_fee_per_gas, + ) + return priority_fee_per_gas + base_fee_per_gas + + if tx.gas_price < base_fee_per_gas: + raise InvalidBlock + return tx.gas_price + + +def calculate_max_gas_fee(tx: Transaction, gas_limit: Uint) -> Uint: + """ + Calculate the largest execution-gas fee the transaction can incur: + `gas_limit` priced at the transaction's fee cap. + """ + if isinstance(tx, FeeMarketCapableTransaction): + return gas_limit * tx.max_fee_per_gas + return gas_limit * tx.gas_price + + +def check_nonce(tx: Transaction, sender_nonce: Uint) -> None: + """ + Check that the transaction's nonce equals the sender's next nonce. + """ + if sender_nonce > Uint(tx.nonce): + raise NonceMismatchError("nonce too low") + elif sender_nonce < Uint(tx.nonce): + raise NonceMismatchError("nonce too high") + + def chain_id(tx: Transaction) -> None | U64: """ Extract the chain identifier from a transaction. See [EIP-155]. diff --git a/src/ethereum/forks/amsterdam/utils/message.py b/src/ethereum/forks/amsterdam/utils/message.py deleted file mode 100644 index a0387240d0b..00000000000 --- a/src/ethereum/forks/amsterdam/utils/message.py +++ /dev/null @@ -1,92 +0,0 @@ -""" -Hardfork Utility Functions For The Message Data-structure. - -.. contents:: Table of Contents - :backlinks: none - :local: - -Introduction ------------- - -Message specific functions used in this amsterdam version of -specification. -""" - -from ethereum_types.bytes import Bytes, Bytes0 -from ethereum_types.numeric import Uint - -from ethereum.state import Address - -from ..state_tracker import get_account -from ..transactions import Transaction -from ..vm import BlockEnvironment, Message, TransactionEnvironment -from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS -from .address import compute_contract_address - - -def prepare_message( - block_env: BlockEnvironment, - tx_env: TransactionEnvironment, - tx: Transaction, -) -> Message: - """ - Execute a transaction against the provided environment. - - Parameters - ---------- - block_env : - Environment for the Ethereum Virtual Machine. - tx_env : - Environment for the transaction. - tx : - Transaction to be executed. - - Returns - ------- - message: `ethereum.forks.amsterdam.vm.Message` - Items containing contract creation or message call specific data. - - """ - accessed_addresses = set() - accessed_addresses.add(tx_env.origin) - accessed_addresses.update(PRE_COMPILED_CONTRACTS.keys()) - accessed_addresses.update(tx_env.access_list_addresses) - - if isinstance(tx.to, Bytes0): - current_target = compute_contract_address( - tx_env.origin, - get_account(tx_env.state, tx_env.origin).nonce - Uint(1), - ) - msg_data = Bytes(b"") - code = tx.data - code_address = None - elif isinstance(tx.to, Address): - current_target = tx.to - msg_data = tx.data - code = None - code_address = tx.to - else: - raise AssertionError("Target must be address or empty bytes") - - accessed_addresses.add(current_target) - - return Message( - block_env=block_env, - tx_env=tx_env, - caller=tx_env.origin, - target=tx.to, - gas=tx_env.gas, - state_gas_reservoir=tx_env.state_gas_reservoir, - value=tx.value, - data=msg_data, - code=code, - depth=Uint(0), - current_target=current_target, - code_address=code_address, - should_transfer_value=True, - is_static=False, - accessed_addresses=accessed_addresses, - accessed_storage_keys=set(tx_env.access_list_storage_keys), - disable_precompiles=False, - parent_evm=None, - ) diff --git a/src/ethereum/forks/amsterdam/vm/__init__.py b/src/ethereum/forks/amsterdam/vm/__init__.py index a2395a96ff9..4ebc1ba2640 100644 --- a/src/ethereum/forks/amsterdam/vm/__init__.py +++ b/src/ethereum/forks/amsterdam/vm/__init__.py @@ -15,7 +15,7 @@ from dataclasses import dataclass, field from typing import List, Optional, Set, Tuple, final -from ethereum_types.bytes import Bytes, Bytes0, Bytes32 +from ethereum_types.bytes import Bytes, Bytes32 from ethereum_types.numeric import U64, U256, Uint from ethereum.crypto.hash import Hash32, keccak256 @@ -31,7 +31,7 @@ from ..transactions import LegacyTransaction from .gas import GasMeter -__all__ = ("Environment", "Evm", "Message") +__all__ = ("Environment", "Evm") TRANSFER_TOPIC = keccak256(b"Transfer(address,address,uint256)") SYSTEM_ADDRESS = Address( bytes.fromhex("fffffffffffffffffffffffffffffffffffffffe") @@ -122,13 +122,19 @@ class TransactionEnvironment: """ origin: Address - recipient: Bytes0 | Address + # For a creation, the address the contract deploys to. + recipient: Address + is_create: bool + data: Bytes value: U256 - gas_price: Uint - gas: Uint + gas_limit: Uint + effective_gas_price: Uint + execution_gas_grant: Uint state_gas_reservoir: Uint + calldata_floor: Uint access_list_addresses: Set[Address] access_list_storage_keys: Set[Tuple[Address, Bytes32]] + accounts_with_paid_writes: Set[Address] state: TransactionState blob_versioned_hashes: Tuple[VersionedHash, ...] authorizations: Tuple[Authorization, ...] @@ -138,45 +144,39 @@ class TransactionEnvironment: @final @dataclass -class Message: +class Evm: """ - Items that are used by contract creation or message call. + A single call frame: its parameters, gas meter, machine state, and + accrued effects. + + A call spawns a child frame and each top-level call is a frame at + depth zero, so one dataclass describes them all. """ + pc: Uint + stack: List[U256] + memory: bytearray + # Init code for a creation; the resolved code for a call. + code: Bytes + gas_meter: GasMeter + valid_jump_destinations: Set[Uint] + logs: Tuple[Log, ...] + running: bool + + # The call's parameters, fixed at frame creation. block_env: BlockEnvironment tx_env: TransactionEnvironment caller: Address - target: Bytes0 | Address current_target: Address - gas: Uint - state_gas_reservoir: Uint value: U256 - data: Bytes + call_data: Bytes code_address: Optional[Address] - code: Optional[Bytes] depth: Uint should_transfer_value: bool is_static: bool - accessed_addresses: Set[Address] - accessed_storage_keys: Set[Tuple[Address, Bytes32]] disable_precompiles: bool parent_evm: Optional["Evm"] - -@final -@dataclass -class Evm: - """The internal state of the virtual machine.""" - - pc: Uint - stack: List[U256] - memory: bytearray - code: Bytes - gas_meter: GasMeter - valid_jump_destinations: Set[Uint] - logs: Tuple[Log, ...] - running: bool - message: Message output: Bytes accounts_to_delete: Set[Address] return_data: Bytes diff --git a/src/ethereum/forks/amsterdam/vm/eoa_delegation.py b/src/ethereum/forks/amsterdam/vm/eoa_delegation.py index 83cc8e080f3..b25f4e9fcfb 100644 --- a/src/ethereum/forks/amsterdam/vm/eoa_delegation.py +++ b/src/ethereum/forks/amsterdam/vm/eoa_delegation.py @@ -14,6 +14,7 @@ from ..fork_types import Authorization from ..state_tracker import ( + TransactionState, account_exists, get_account, get_code, @@ -24,11 +25,12 @@ from ..utils.hexadecimal import hex_to_address from ..vm.gas import ( GasCosts, + GasMeter, StateGasCosts, - charge_gas, - charge_state_gas, + charge_gas_from_meter, + charge_state_gas_from_meter, ) -from . import Evm, Message +from . import BlockEnvironment, Evm, TransactionEnvironment SET_CODE_TX_MAGIC = b"\x05" EOA_DELEGATION_MARKER = b"\xef\x01\x00" @@ -79,6 +81,34 @@ def get_delegated_code_address(code: bytes) -> Optional[Address]: return None +def resolve_delegated_code_address( + state: TransactionState, + gas_meter: GasMeter, + accessed_addresses: Set[Address], + target_address: Address, +) -> Tuple[Address, bool]: + """ + Resolve the address of the code a call target executes. + + If `target_address` carries a delegation designation, charge the + warm or cold access for the delegated address, warm it, and return + it with precompiles disabled; otherwise return `target_address` + unchanged. + """ + code = get_code(state, get_account(state, target_address).code_hash) + delegated_address = get_delegated_code_address(code) + if delegated_address is None: + return target_address, False + + if delegated_address in accessed_addresses: + charge_gas_from_meter(gas_meter, GasCosts.WARM_ACCESS) + else: + charge_gas_from_meter(gas_meter, GasCosts.COLD_ACCOUNT_ACCESS) + accessed_addresses.add(delegated_address) + + return delegated_address, True + + def recover_authority(authorization: Authorization) -> Address: """ Recover the authority address from the authorization. @@ -141,7 +171,7 @@ def calculate_delegation_cost( The delegation address and access gas cost. """ - tx_state = evm.message.tx_env.state + tx_state = evm.tx_env.state code = get_code(tx_state, get_account(tx_state, address).code_hash) @@ -159,7 +189,10 @@ def calculate_delegation_cost( def validate_authorization( - message: Message, auth: Authorization + block_env: BlockEnvironment, + tx_env: TransactionEnvironment, + accessed_authorities: Set[Address], + auth: Authorization, ) -> Optional[Address]: """ Check if the given `Authorization` is valid against the current state. @@ -167,9 +200,9 @@ def validate_authorization( Returns the `authority` address, or `None` if the validation was unsuccessful. """ - tx_state = message.tx_env.state + tx_state = tx_env.state - if auth.chain_id not in (message.block_env.chain_id, U256(0)): + if auth.chain_id not in (block_env.chain_id, U256(0)): return None if auth.nonce >= U64.MAX_VALUE: @@ -180,7 +213,7 @@ def validate_authorization( except InvalidSignatureError: return None - message.accessed_addresses.add(authority) + accessed_authorities.add(authority) authority_account = get_account(tx_state, authority) authority_code = get_code(tx_state, authority_account.code_hash) @@ -195,7 +228,11 @@ def validate_authorization( return authority -def set_delegation(evm: Evm) -> None: +def set_delegation( + block_env: BlockEnvironment, + tx_env: TransactionEnvironment, + gas_meter: GasMeter, +) -> Set[Address]: """ Apply the EIP-7702 authorizations and charge their state-dependent costs at the top frame. @@ -227,35 +264,40 @@ def set_delegation(evm: Evm) -> None: Parameters ---------- - evm : - The top-level transaction frame. + block_env : + Environment for the Ethereum Virtual Machine. + tx_env : + Environment for the transaction. + gas_meter : + Gas meter of the top-level frame. + + Returns + ------- + accessed_authorities : `Set[Address]` + Authorities recovered from the authorizations, warmed for the + transaction. """ - message = evm.message - tx_state = message.tx_env.state - # Accounts whose write the transaction has already priced: the - # sender's leaf was written at inclusion (nonce bump and fee - # deduction), and a value-bearing transaction prepays the - # recipient's balance write -- the transfer itself only happens at - # frame entry, after these charges. - written_accounts: Set[Address] = {message.tx_env.origin} - if evm.message.tx_env.value > U256(0): - written_accounts.add(evm.message.current_target) + assert not tx_env.is_create + tx_state = tx_env.state # Authorities a delegation was set for earlier in this transaction. + accessed_authorities: Set[Address] = set() delegation_set_for: Set[Address] = set() - for auth in message.tx_env.authorizations: - match validate_authorization(message, auth): + for auth in tx_env.authorizations: + match validate_authorization( + block_env, tx_env, accessed_authorities, auth + ): case None: continue case authority: pass if not account_exists(tx_state, authority): - charge_state_gas(evm, StateGasCosts.NEW_ACCOUNT) + charge_state_gas_from_meter(gas_meter, StateGasCosts.NEW_ACCOUNT) - if authority not in written_accounts: - charge_gas(evm, GasCosts.ACCOUNT_WRITE) - written_accounts.add(authority) + if authority not in tx_env.accounts_with_paid_writes: + charge_gas_from_meter(gas_meter, GasCosts.ACCOUNT_WRITE) + tx_env.accounts_with_paid_writes.add(authority) pre_state_authority_account = get_pre_state_account( tx_state, authority @@ -269,9 +311,11 @@ def set_delegation(evm: Evm) -> None: code_to_set = b"" else: if not delegated_before_tx and authority not in delegation_set_for: - charge_state_gas(evm, StateGasCosts.AUTH_BASE) + charge_state_gas_from_meter(gas_meter, StateGasCosts.AUTH_BASE) delegation_set_for.add(authority) code_to_set = EOA_DELEGATION_MARKER + auth.address set_code(tx_state, authority, code_to_set) increment_nonce(tx_state, authority) + + return accessed_authorities diff --git a/src/ethereum/forks/amsterdam/vm/gas.py b/src/ethereum/forks/amsterdam/vm/gas.py index 4feb2d11509..6a07ef0e361 100644 --- a/src/ethereum/forks/amsterdam/vm/gas.py +++ b/src/ethereum/forks/amsterdam/vm/gas.py @@ -16,12 +16,17 @@ from ethereum_types.numeric import U64, U256, Uint, ulen +from ethereum.exceptions import GasUsedExceedsLimitError from ethereum.forks.bpo5.blocks import Header as PreviousHeader from ethereum.trace import GasAndRefund, StateGasAndRefund, evm_trace from ethereum.utils.numeric import ceil32, taylor_exponential from ..blocks import Header -from ..fork_types import StateGas, StateGasPerByte +from ..exceptions import ( + BlobGasLimitExceededError, + InsufficientMaxFeePerBlobGasError, +) +from ..fork_types import StateGas, StateGasPerByte, VersionedHash from ..transactions import ( TX_MAX_GAS_LIMIT, BlobTransaction, @@ -31,7 +36,7 @@ from .exceptions import OutOfGasError if TYPE_CHECKING: - from . import Evm + from . import BlockEnvironment, BlockOutput, Evm # These may be patched at runtime by a future gas repricing utility to @@ -239,6 +244,11 @@ class GasCosts: OPCODE_SELFDESTRUCT_BASE: Final[Uint] = Uint(5000) +MAX_BLOB_GAS_PER_BLOCK: Final[U64] = ( + GasCosts.BLOB_SCHEDULE_MAX * GasCosts.PER_BLOB +) + + @final @dataclass class GasMeter: @@ -357,6 +367,23 @@ def check_gas(evm: "Evm", amount: Uint) -> None: raise OutOfGasError +def charge_gas_from_meter(gas_meter: GasMeter, amount: Uint) -> None: + """ + Subtracts `amount` from `gas_left` (execution gas). + + Parameters + ---------- + gas_meter : + The gas meter. + amount : + The amount of execution gas the current operation requires. + + """ + if gas_meter.gas_left < amount: + raise OutOfGasError + gas_meter.gas_left -= amount + + def charge_gas(evm: "Evm", amount: Uint) -> None: """ Subtracts `amount` from `gas_left` (execution gas). @@ -371,30 +398,24 @@ def charge_gas(evm: "Evm", amount: Uint) -> None: """ evm_trace(evm, GasAndRefund(int(amount))) - gas_meter = evm.gas_meter - if gas_meter.gas_left < amount: - raise OutOfGasError - gas_meter.gas_left -= amount + charge_gas_from_meter(evm.gas_meter, amount) -def charge_state_gas(evm: "Evm", amount: StateGas) -> None: +def charge_state_gas_from_meter(gas_meter: GasMeter, amount: StateGas) -> None: """ Subtracts `amount` from the state gas reservoir, then from `gas_left` when the reservoir is empty, tracking any [spill]. Parameters ---------- - evm : - The current EVM. + gas_meter : + The gas meter. amount : The amount of state gas the current operation requires. [spill]: ref:ethereum.forks.amsterdam.vm.gas.GasMeter.state_gas_spilled """ - evm_trace(evm, StateGasAndRefund(int(amount))) - - gas_meter = evm.gas_meter if gas_meter.state_gas_left >= amount: gas_meter.state_gas_left -= amount elif gas_meter.state_gas_left + gas_meter.gas_left >= amount: @@ -406,6 +427,26 @@ def charge_state_gas(evm: "Evm", amount: StateGas) -> None: raise OutOfGasError +def charge_state_gas(evm: "Evm", amount: StateGas) -> None: + """ + Subtracts `amount` from the state gas reservoir, then from + `gas_left` when the reservoir is empty, tracking any [spill]. + + Parameters + ---------- + evm : + The current EVM. + amount : + The amount of state gas the current operation requires. + + [spill]: ref:ethereum.forks.amsterdam.vm.gas.GasMeter.state_gas_spilled + + """ + evm_trace(evm, StateGasAndRefund(int(amount))) + + charge_state_gas_from_meter(evm.gas_meter, amount) + + def commit_state_gas(gas_meter: GasMeter) -> None: """ Mark the state gas spent so far as non-refillable. @@ -484,9 +525,9 @@ def restore_state_gas_to_entry( The frame's immutable state gas grant. [commit]: ref:ethereum.forks.amsterdam.vm.gas.commit_state_gas - [grant]: ref:ethereum.forks.amsterdam.vm.Message.state_gas_reservoir + [grant]: ref:ethereum.forks.amsterdam.vm.TransactionEnvironment.state_gas_reservoir - """ + """ # noqa: E501 # The baseline starts at the grant and only ever moves down. assert gas_meter.state_gas_baseline <= state_gas_reservoir # Only pre-dispatch failures roll back to entry, and no refund @@ -909,6 +950,96 @@ def calculate_data_fee(excess_blob_gas: U64, tx: Transaction) -> Uint: ) +def check_max_fee_per_blob_gas( + blob_versioned_hashes: Tuple[VersionedHash, ...], + max_fee_per_blob_gas: U256, + excess_blob_gas: U64, +) -> None: + """ + Check that a transaction carrying blobs pays at least the blob gas + price. + + A transaction without blobs pays no blob fee, so its fee cap is not + checked. + + Parameters + ---------- + blob_versioned_hashes : + The transaction's blob versioned hashes. + max_fee_per_blob_gas : + The transaction's fee cap per unit of blob gas. + excess_blob_gas : + The block's excess blob gas. + + Raises + ------ + InsufficientMaxFeePerBlobGasError : + If the fee cap does not cover the blob gas price. + + """ + if not blob_versioned_hashes: + return + + blob_gas_price = calculate_blob_gas_price(excess_blob_gas) + if Uint(max_fee_per_blob_gas) < blob_gas_price: + raise InsufficientMaxFeePerBlobGasError( + "insufficient max fee per blob gas" + ) + + +def check_block_gas_capacity( + block_env: "BlockEnvironment", + block_output: "BlockOutput", + tx_gas: Uint, + tx_blob_gas: U64, +) -> None: + """ + Check that the transaction fits the block's remaining gas capacity. + + Each dimension is checked against its own remaining budget: + execution gas, where a single transaction can consume at most + [`TX_MAX_GAS_LIMIT`]; state gas; and blob gas. + + Parameters + ---------- + block_env : + The block scoped environment. + block_output : + The block output for the current block. + tx_gas : + The transaction's gas limit. + tx_blob_gas : + The blob gas used by the transaction. + + Raises + ------ + GasUsedExceedsLimitError : + If the transaction exceeds the block's remaining execution or + state gas. + BlobGasLimitExceededError : + If the transaction exceeds the block's remaining blob gas. + + [`TX_MAX_GAS_LIMIT`]: ref:ethereum.forks.amsterdam.transactions.TX_MAX_GAS_LIMIT + + """ # noqa: E501 + execution_gas_available = ( + block_env.block_gas_limit - block_output.block_gas_used + ) + state_gas_available = ( + block_env.block_gas_limit - block_output.block_state_gas_used + ) + blob_gas_available = MAX_BLOB_GAS_PER_BLOCK - block_output.blob_gas_used + + if min(TX_MAX_GAS_LIMIT, tx_gas) > execution_gas_available: + raise GasUsedExceedsLimitError("execution gas used exceeds limit") + + if tx_gas > state_gas_available: + raise GasUsedExceedsLimitError("state gas used exceeds limit") + + if tx_blob_gas > blob_gas_available: + raise BlobGasLimitExceededError("blob gas limit exceeded") + + @final @dataclass class EvmGasAllocation: @@ -983,7 +1114,7 @@ class TransactionGasSettlement: def settle_transaction_gas( tx_gas: Uint, - intrinsic: IntrinsicGasCost, + calldata_floor: Uint, gas_left: Uint, state_gas_left: Uint, refund_counter: U256, @@ -1010,8 +1141,8 @@ def settle_transaction_gas( ---------- tx_gas : The transaction's gas limit. - intrinsic : - The transaction's intrinsic gas cost. + calldata_floor : + The transaction's calldata floor gas. gas_left : Execution gas the top frame returned. state_gas_left : @@ -1032,12 +1163,12 @@ def settle_transaction_gas( gas_used_before_refund = tx_gas - gas_left - state_gas_left gas_refund = min(gas_used_before_refund // Uint(5), Uint(refund_counter)) gas_used_after_refund = gas_used_before_refund - gas_refund - gas_used = max(gas_used_after_refund, intrinsic.calldata_floor) + gas_used = max(gas_used_after_refund, calldata_floor) settled_state_gas_used = Uint(max(0, state_gas_used)) execution_gas_used = max( gas_used_before_refund - settled_state_gas_used, - intrinsic.calldata_floor, + calldata_floor, ) return TransactionGasSettlement( gas_used=gas_used, diff --git a/src/ethereum/forks/amsterdam/vm/instructions/block.py b/src/ethereum/forks/amsterdam/vm/instructions/block.py index 24c524673eb..fa286c439cc 100644 --- a/src/ethereum/forks/amsterdam/vm/instructions/block.py +++ b/src/ethereum/forks/amsterdam/vm/instructions/block.py @@ -44,7 +44,7 @@ def block_hash(evm: Evm) -> None: # OPERATION max_block_number = block_number + Uint(256) - current_block_number = evm.message.block_env.number + current_block_number = evm.block_env.number if ( current_block_number <= block_number or current_block_number > max_block_number @@ -54,7 +54,7 @@ def block_hash(evm: Evm) -> None: # or if the block's age is more than 256. current_block_hash = b"\x00" else: - current_block_hash = evm.message.block_env.block_hashes[ + current_block_hash = evm.block_env.block_hashes[ -(current_block_number - block_number) ] @@ -92,7 +92,7 @@ def coinbase(evm: Evm) -> None: charge_gas(evm, GasCosts.OPCODE_COINBASE) # OPERATION - push(evm.stack, U256.from_be_bytes(evm.message.block_env.coinbase)) + push(evm.stack, U256.from_be_bytes(evm.block_env.coinbase)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -126,7 +126,7 @@ def timestamp(evm: Evm) -> None: charge_gas(evm, GasCosts.OPCODE_TIMESTAMP) # OPERATION - push(evm.stack, evm.message.block_env.time) + push(evm.stack, evm.block_env.time) # PROGRAM COUNTER evm.pc += Uint(1) @@ -159,7 +159,7 @@ def number(evm: Evm) -> None: charge_gas(evm, GasCosts.OPCODE_NUMBER) # OPERATION - push(evm.stack, U256(evm.message.block_env.number)) + push(evm.stack, U256(evm.block_env.number)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -192,7 +192,7 @@ def prev_randao(evm: Evm) -> None: charge_gas(evm, GasCosts.OPCODE_PREVRANDAO) # OPERATION - push(evm.stack, U256.from_be_bytes(evm.message.block_env.prev_randao)) + push(evm.stack, U256.from_be_bytes(evm.block_env.prev_randao)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -225,7 +225,7 @@ def gas_limit(evm: Evm) -> None: charge_gas(evm, GasCosts.OPCODE_GASLIMIT) # OPERATION - push(evm.stack, U256(evm.message.block_env.block_gas_limit)) + push(evm.stack, U256(evm.block_env.block_gas_limit)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -255,7 +255,7 @@ def chain_id(evm: Evm) -> None: charge_gas(evm, GasCosts.OPCODE_CHAINID) # OPERATION - push(evm.stack, U256(evm.message.block_env.chain_id)) + push(evm.stack, U256(evm.block_env.chain_id)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -288,7 +288,7 @@ def slot_number(evm: Evm) -> None: charge_gas(evm, GasCosts.OPCODE_SLOTNUM) # OPERATION - push(evm.stack, U256(evm.message.block_env.slot_number)) + push(evm.stack, U256(evm.block_env.slot_number)) # PROGRAM COUNTER evm.pc += Uint(1) diff --git a/src/ethereum/forks/amsterdam/vm/instructions/environment.py b/src/ethereum/forks/amsterdam/vm/instructions/environment.py index 8a7e9ec1486..443554dbc8f 100644 --- a/src/ethereum/forks/amsterdam/vm/instructions/environment.py +++ b/src/ethereum/forks/amsterdam/vm/instructions/environment.py @@ -48,7 +48,7 @@ def address(evm: Evm) -> None: charge_gas(evm, GasCosts.OPCODE_ADDRESS) # OPERATION - push(evm.stack, U256.from_be_bytes(evm.message.current_target)) + push(evm.stack, U256.from_be_bytes(evm.current_target)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -76,7 +76,7 @@ def balance(evm: Evm) -> None: # OPERATION # Non-existent accounts default to EMPTY_ACCOUNT, which has balance 0. - tx_state = evm.message.tx_env.state + tx_state = evm.tx_env.state balance = get_account(tx_state, address).balance push(evm.stack, balance) @@ -103,7 +103,7 @@ def origin(evm: Evm) -> None: charge_gas(evm, GasCosts.OPCODE_ORIGIN) # OPERATION - push(evm.stack, U256.from_be_bytes(evm.message.tx_env.origin)) + push(evm.stack, U256.from_be_bytes(evm.tx_env.origin)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -126,7 +126,7 @@ def caller(evm: Evm) -> None: charge_gas(evm, GasCosts.OPCODE_CALLER) # OPERATION - push(evm.stack, U256.from_be_bytes(evm.message.caller)) + push(evm.stack, U256.from_be_bytes(evm.caller)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -149,7 +149,7 @@ def callvalue(evm: Evm) -> None: charge_gas(evm, GasCosts.OPCODE_CALLVALUE) # OPERATION - push(evm.stack, evm.message.value) + push(evm.stack, evm.value) # PROGRAM COUNTER evm.pc += Uint(1) @@ -173,7 +173,7 @@ def calldataload(evm: Evm) -> None: charge_gas(evm, GasCosts.OPCODE_CALLDATALOAD) # OPERATION - value = buffer_read(evm.message.data, start_index, U256(32)) + value = buffer_read(evm.call_data, start_index, U256(32)) push(evm.stack, U256.from_be_bytes(value)) @@ -198,7 +198,7 @@ def calldatasize(evm: Evm) -> None: charge_gas(evm, GasCosts.OPCODE_CALLDATASIZE) # OPERATION - push(evm.stack, U256(len(evm.message.data))) + push(evm.stack, U256(len(evm.call_data))) # PROGRAM COUNTER evm.pc += Uint(1) @@ -235,7 +235,7 @@ def calldatacopy(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - value = buffer_read(evm.message.data, data_start_index, size) + value = buffer_read(evm.call_data, data_start_index, size) memory_write(evm.memory, memory_start_index, value) # PROGRAM COUNTER @@ -320,7 +320,7 @@ def gasprice(evm: Evm) -> None: charge_gas(evm, GasCosts.OPCODE_GASPRICE) # OPERATION - push(evm.stack, U256(evm.message.tx_env.gas_price)) + push(evm.stack, U256(evm.tx_env.effective_gas_price)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -349,7 +349,7 @@ def extcodesize(evm: Evm) -> None: charge_gas(evm, access_gas_cost) # OPERATION - tx_state = evm.message.tx_env.state + tx_state = evm.tx_env.state code_hash = get_account(tx_state, address).code_hash code = get_code(tx_state, code_hash) @@ -396,7 +396,7 @@ def extcodecopy(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - tx_state = evm.message.tx_env.state + tx_state = evm.tx_env.state code_hash = get_account(tx_state, address).code_hash code = get_code(tx_state, code_hash) @@ -493,7 +493,7 @@ def extcodehash(evm: Evm) -> None: charge_gas(evm, access_gas_cost) # OPERATION - tx_state = evm.message.tx_env.state + tx_state = evm.tx_env.state account = get_account(tx_state, address) if account == EMPTY_ACCOUNT: @@ -525,9 +525,7 @@ def self_balance(evm: Evm) -> None: # OPERATION # Non-existent accounts default to EMPTY_ACCOUNT, which has balance 0. - balance = get_account( - evm.message.tx_env.state, evm.message.current_target - ).balance + balance = get_account(evm.tx_env.state, evm.current_target).balance push(evm.stack, balance) @@ -552,7 +550,7 @@ def base_fee(evm: Evm) -> None: charge_gas(evm, GasCosts.OPCODE_BASEFEE) # OPERATION - push(evm.stack, U256(evm.message.block_env.base_fee_per_gas)) + push(evm.stack, U256(evm.block_env.base_fee_per_gas)) # PROGRAM COUNTER evm.pc += Uint(1) @@ -575,8 +573,8 @@ def blob_hash(evm: Evm) -> None: charge_gas(evm, GasCosts.OPCODE_BLOBHASH) # OPERATION - if int(index) < len(evm.message.tx_env.blob_versioned_hashes): - blob_hash = evm.message.tx_env.blob_versioned_hashes[index] + if int(index) < len(evm.tx_env.blob_versioned_hashes): + blob_hash = evm.tx_env.blob_versioned_hashes[index] else: blob_hash = Bytes32(b"\x00" * 32) push(evm.stack, U256.from_be_bytes(blob_hash)) @@ -602,9 +600,7 @@ def blob_base_fee(evm: Evm) -> None: charge_gas(evm, GasCosts.OPCODE_BLOBBASEFEE) # OPERATION - blob_base_fee = calculate_blob_gas_price( - evm.message.block_env.excess_blob_gas - ) + blob_base_fee = calculate_blob_gas_price(evm.block_env.excess_blob_gas) push(evm.stack, U256(blob_base_fee)) # PROGRAM COUNTER diff --git a/src/ethereum/forks/amsterdam/vm/instructions/log.py b/src/ethereum/forks/amsterdam/vm/instructions/log.py index 695f4de735c..d5f016817e8 100644 --- a/src/ethereum/forks/amsterdam/vm/instructions/log.py +++ b/src/ethereum/forks/amsterdam/vm/instructions/log.py @@ -66,10 +66,10 @@ def log_n(evm: Evm, num_topics: int) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - if evm.message.is_static: + if evm.is_static: raise WriteInStaticContext log_entry = Log( - address=evm.message.current_target, + address=evm.current_target, topics=tuple(topics), data=memory_read_bytes(evm.memory, memory_start_index, size), ) diff --git a/src/ethereum/forks/amsterdam/vm/instructions/storage.py b/src/ethereum/forks/amsterdam/vm/instructions/storage.py index b54b3821fd2..cebee0a7da7 100644 --- a/src/ethereum/forks/amsterdam/vm/instructions/storage.py +++ b/src/ethereum/forks/amsterdam/vm/instructions/storage.py @@ -49,15 +49,15 @@ def sload(evm: Evm) -> None: key = pop(evm.stack).to_be_bytes32() # GAS - if (evm.message.current_target, key) in evm.accessed_storage_keys: + if (evm.current_target, key) in evm.accessed_storage_keys: charge_gas(evm, GasCosts.WARM_ACCESS) else: - evm.accessed_storage_keys.add((evm.message.current_target, key)) + evm.accessed_storage_keys.add((evm.current_target, key)) charge_gas(evm, GasCosts.COLD_STORAGE_ACCESS) # OPERATION - tx_state = evm.message.tx_env.state - value = get_storage(tx_state, evm.message.current_target, key) + tx_state = evm.tx_env.state + value = get_storage(tx_state, evm.current_target, key) push(evm.stack, value) @@ -75,7 +75,7 @@ def sstore(evm: Evm) -> None: The current EVM frame. """ - if evm.message.is_static: + if evm.is_static: raise WriteInStaticContext # STACK @@ -89,7 +89,7 @@ def sstore(evm: Evm) -> None: # Access cost: cold or warm, always charged. is_cold_access = ( - evm.message.current_target, + evm.current_target, key, ) not in evm.accessed_storage_keys if is_cold_access: @@ -108,13 +108,11 @@ def sstore(evm: Evm) -> None: # the slot's original and current values, adjusting the # transaction's refunds. if is_cold_access: - evm.accessed_storage_keys.add((evm.message.current_target, key)) + evm.accessed_storage_keys.add((evm.current_target, key)) - tx_state = evm.message.tx_env.state - original_value = get_storage_original( - tx_state, evm.message.current_target, key - ) - current_value = get_storage(tx_state, evm.message.current_target, key) + tx_state = evm.tx_env.state + original_value = get_storage_original(tx_state, evm.current_target, key) + current_value = get_storage(tx_state, evm.current_target, key) state_gas = StateGas(Uint(0)) @@ -154,7 +152,7 @@ def sstore(evm: Evm) -> None: # reservoir on frame failure. charge_gas(evm, gas_cost) charge_state_gas(evm, state_gas) - set_storage(tx_state, evm.message.current_target, key, new_value) + set_storage(tx_state, evm.current_target, key, new_value) # PROGRAM COUNTER evm.pc += Uint(1) @@ -178,9 +176,7 @@ def tload(evm: Evm) -> None: charge_gas(evm, GasCosts.OPCODE_TLOAD) # OPERATION - value = get_transient_storage( - evm.message.tx_env.state, evm.message.current_target, key - ) + value = get_transient_storage(evm.tx_env.state, evm.current_target, key) push(evm.stack, value) # PROGRAM COUNTER @@ -197,7 +193,7 @@ def tstore(evm: Evm) -> None: The current EVM frame. """ - if evm.message.is_static: + if evm.is_static: raise WriteInStaticContext # STACK @@ -207,8 +203,8 @@ def tstore(evm: Evm) -> None: # GAS charge_gas(evm, GasCosts.OPCODE_TSTORE) set_transient_storage( - evm.message.tx_env.state, - evm.message.current_target, + evm.tx_env.state, + evm.current_target, key, new_value, ) diff --git a/src/ethereum/forks/amsterdam/vm/instructions/system.py b/src/ethereum/forks/amsterdam/vm/instructions/system.py index 3f3afafe53d..082fe35d417 100644 --- a/src/ethereum/forks/amsterdam/vm/instructions/system.py +++ b/src/ethereum/forks/amsterdam/vm/instructions/system.py @@ -14,7 +14,7 @@ from dataclasses import dataclass from typing import final -from ethereum_types.bytes import Bytes, Bytes0 +from ethereum_types.bytes import Bytes from ethereum_types.numeric import U256, Uint from ethereum.state import Address @@ -40,13 +40,13 @@ from .. import ( CALL_SUCCESS, Evm, - Message, emit_transfer_log, incorporate_child, ) from ..exceptions import OutOfGasError, Revert, WriteInStaticContext from ..gas import ( GasCosts, + GasMeter, StateGasCosts, calculate_gas_extend_memory, calculate_message_call_gas, @@ -79,13 +79,14 @@ def generic_create( collision check, the child's gas grant, the child frame itself, and the resolution of its outcome back into the creating frame. """ - # This import causes a circular import error - # if it's not moved inside this method - from ...vm.interpreter import STACK_DEPTH_LIMIT, process_create_message + # These imports cause a circular import error + # if they're not moved inside this method + from ...vm.interpreter import STACK_DEPTH_LIMIT, process_create + from ...vm.runtime import get_valid_jump_destinations - tx_state = evm.message.tx_env.state + tx_state = evm.tx_env.state - call_data = memory_read_bytes( + init_code = memory_read_bytes( evm.memory, memory_start_position, memory_size ) @@ -94,13 +95,13 @@ def generic_create( # PREFLIGHT # Abort without spawning the child: nothing has been charged or # withheld for it yet. - sender_address = evm.message.current_target + sender_address = evm.current_target sender = get_account(tx_state, sender_address) if ( sender.balance < endowment or sender.nonce == Uint(2**64 - 1) - or evm.message.depth + Uint(1) > STACK_DEPTH_LIMIT + or evm.depth + Uint(1) > STACK_DEPTH_LIMIT ): push(evm.stack, U256(0)) return @@ -138,27 +139,45 @@ def generic_create( # DISPATCH - child_message = Message( - block_env=evm.message.block_env, - tx_env=evm.message.tx_env, - caller=evm.message.current_target, - target=Bytes0(), - gas=create_message_gas, - state_gas_reservoir=create_message_state_gas_reservoir, - value=endowment, - data=b"", - code=call_data, + child_evm = Evm( + # Context + block_env=evm.block_env, + tx_env=evm.tx_env, + parent_evm=evm, + depth=evm.depth + Uint(1), + # Call Parameters + caller=evm.current_target, current_target=contract_address, - depth=evm.message.depth + Uint(1), - code_address=None, + value=endowment, + call_data=b"", should_transfer_value=True, is_static=False, + disable_precompiles=False, + # Code + code_address=None, + code=init_code, + valid_jump_destinations=get_valid_jump_destinations(init_code), + # Machine State + gas_meter=GasMeter( + gas_left=create_message_gas, + state_gas_left=create_message_state_gas_reservoir, + state_gas_baseline=create_message_state_gas_reservoir, + ), + pc=Uint(0), + stack=[], + memory=bytearray(), + return_data=b"", + # Accrued Effects + logs=(), + accounts_to_delete=set(), accessed_addresses=evm.accessed_addresses.copy(), accessed_storage_keys=evm.accessed_storage_keys.copy(), - disable_precompiles=False, - parent_evm=evm, + # Outcome + running=True, + output=b"", + error=None, ) - child_evm = process_create_message(child_message) + child_evm = process_create(child_evm) # OUTCOME # The child settled its own gas; absorb it and resolve the @@ -172,7 +191,7 @@ def generic_create( push(evm.stack, U256(0)) else: evm.return_data = b"" - push(evm.stack, U256.from_be_bytes(child_evm.message.current_target)) + push(evm.stack, U256.from_be_bytes(child_evm.current_target)) def create(evm: Evm) -> None: @@ -189,7 +208,7 @@ def create(evm: Evm) -> None: # if it's not moved inside this method from ...vm.interpreter import MAX_INIT_CODE_SIZE - if evm.message.is_static: + if evm.is_static: raise WriteInStaticContext # STACK @@ -213,10 +232,8 @@ def create(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by contract_address = compute_contract_address( - evm.message.current_target, - get_account( - evm.message.tx_env.state, evm.message.current_target - ).nonce, + evm.current_target, + get_account(evm.tx_env.state, evm.current_target).nonce, ) generic_create( @@ -248,7 +265,7 @@ def create2(evm: Evm) -> None: # if it's not moved inside this method from ...vm.interpreter import MAX_INIT_CODE_SIZE - if evm.message.is_static: + if evm.is_static: raise WriteInStaticContext # STACK @@ -277,7 +294,7 @@ def create2(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by contract_address = compute_create2_contract_address( - evm.message.current_target, + evm.current_target, salt, memory_read_bytes(evm.memory, memory_start_position, memory_size), ) @@ -365,17 +382,15 @@ def generic_call(evm: Evm, params: GenericCall) -> None: that abort without spawning, the child frame itself, and the resolution of its outcome back into the calling frame. """ - from ...vm.interpreter import STACK_DEPTH_LIMIT, process_message + from ...vm.interpreter import STACK_DEPTH_LIMIT, process_call + from ...vm.runtime import get_valid_jump_destinations evm.return_data = b"" # PREFLIGHT # Abort without spawning the child: both grants return untouched # and any account-creation charge refills. - if ( - evm.message.depth + Uint(1) > STACK_DEPTH_LIMIT - or params.insufficient_balance - ): + if evm.depth + Uint(1) > STACK_DEPTH_LIMIT or params.insufficient_balance: restore_child_gas( evm.gas_meter, params.gas, params.state_gas_reservoir ) @@ -391,28 +406,46 @@ def generic_call(evm: Evm, params: GenericCall) -> None: params.memory_input_size, ) - child_message = Message( - block_env=evm.message.block_env, - tx_env=evm.message.tx_env, + child_evm = Evm( + # Context + block_env=evm.block_env, + tx_env=evm.tx_env, + parent_evm=evm, + depth=evm.depth + Uint(1), + # Call Parameters caller=params.caller, - target=params.to, - gas=params.gas, - state_gas_reservoir=params.state_gas_reservoir, - value=params.value, - data=call_data, - code=params.code, current_target=params.to, - depth=evm.message.depth + Uint(1), - code_address=params.code_address, + value=params.value, + call_data=call_data, should_transfer_value=params.should_transfer_value, - is_static=params.is_staticcall or evm.message.is_static, + is_static=params.is_staticcall or evm.is_static, + disable_precompiles=params.disable_precompiles, + # Code + code_address=params.code_address, + code=params.code, + valid_jump_destinations=get_valid_jump_destinations(params.code), + # Machine State + gas_meter=GasMeter( + gas_left=params.gas, + state_gas_left=params.state_gas_reservoir, + state_gas_baseline=params.state_gas_reservoir, + ), + pc=Uint(0), + stack=[], + memory=bytearray(), + return_data=b"", + # Accrued Effects + logs=(), + accounts_to_delete=set(), accessed_addresses=evm.accessed_addresses.copy(), accessed_storage_keys=evm.accessed_storage_keys.copy(), - disable_precompiles=params.disable_precompiles, - parent_evm=evm, + # Outcome + running=True, + output=b"", + error=None, ) - child_evm = process_message(child_message) + child_evm = process_call(child_evm) # OUTCOME # The child settled its own gas; absorb it and resolve the @@ -455,7 +488,7 @@ def call(evm: Evm) -> None: memory_output_start_position = pop(evm.stack) memory_output_size = pop(evm.stack) - if evm.message.is_static and value != U256(0): + if evm.is_static and value != U256(0): raise WriteInStaticContext # GAS (STATE-INDEPENDENT) @@ -486,7 +519,7 @@ def call(evm: Evm) -> None: # Perform the accesses and complete the state-dependent pricing -- # a delegation adds its access cost -- then charge the execution # gas. - tx_state = evm.message.tx_env.state + tx_state = evm.tx_env.state if is_cold_access: evm.accessed_addresses.add(to) @@ -535,7 +568,7 @@ def call(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - sender_balance = get_account(tx_state, evm.message.current_target).balance + sender_balance = get_account(tx_state, evm.current_target).balance generic_call( evm, @@ -543,7 +576,7 @@ def call(evm: Evm) -> None: gas=message_call_gas.sub_call, state_gas_reservoir=call_state_gas_reservoir, value=value, - caller=evm.message.current_target, + caller=evm.current_target, to=to, code_address=code_address, should_transfer_value=True, @@ -585,7 +618,7 @@ def callcode(evm: Evm) -> None: # GAS (STATE-INDEPENDENT) # Price what is computable without touching state, and check it is # affordable before any state access is performed. - to = evm.message.current_target + to = evm.current_target extend_memory = calculate_gas_extend_memory( evm.memory, @@ -612,7 +645,7 @@ def callcode(evm: Evm) -> None: # Perform the accesses and complete the state-dependent pricing -- # a delegation adds its access cost; the execution gas is charged # with the child grant. - tx_state = evm.message.tx_env.state + tx_state = evm.tx_env.state if is_cold_access: evm.accessed_addresses.add(code_address) @@ -650,7 +683,7 @@ def callcode(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - sender_balance = get_account(tx_state, evm.message.current_target).balance + sender_balance = get_account(tx_state, evm.current_target).balance generic_call( evm, @@ -658,7 +691,7 @@ def callcode(evm: Evm) -> None: gas=message_call_gas.sub_call, state_gas_reservoir=call_state_gas_reservoir, value=value, - caller=evm.message.current_target, + caller=evm.current_target, to=to, code_address=code_address, should_transfer_value=True, @@ -687,7 +720,7 @@ def selfdestruct(evm: Evm) -> None: The current EVM frame. """ - if evm.message.is_static: + if evm.is_static: raise WriteInStaticContext # STACK @@ -707,7 +740,7 @@ def selfdestruct(evm: Evm) -> None: # STATE ACCESS (STATE-DEPENDENT GAS) # Perform the access; the pricing completes with the state gas # below. - tx_state = evm.message.tx_env.state + tx_state = evm.tx_env.state if is_cold_access: evm.accessed_addresses.add(beneficiary) @@ -719,7 +752,7 @@ def selfdestruct(evm: Evm) -> None: account_write_gas = Uint(0) if ( not is_account_alive(tx_state, beneficiary) - and get_account(tx_state, evm.message.current_target).balance != 0 + and get_account(tx_state, evm.current_target).balance != 0 ): state_gas = StateGasCosts.NEW_ACCOUNT account_write_gas = GasCosts.ACCOUNT_WRITE @@ -731,7 +764,7 @@ def selfdestruct(evm: Evm) -> None: charge_state_gas(evm, state_gas) # OPERATION - originator = evm.message.current_target + originator = evm.current_target originator_balance = get_account(tx_state, originator).balance # Transfer balance @@ -810,7 +843,7 @@ def delegatecall(evm: Evm) -> None: if code_address not in evm.accessed_addresses: evm.accessed_addresses.add(code_address) - tx_state = evm.message.tx_env.state + tx_state = evm.tx_env.state code_hash = get_account(tx_state, code_address).code_hash code = get_code(tx_state, code_hash) @@ -836,9 +869,9 @@ def delegatecall(evm: Evm) -> None: GenericCall( gas=message_call_gas.sub_call, state_gas_reservoir=call_state_gas_reservoir, - value=evm.message.value, - caller=evm.message.caller, - to=evm.message.current_target, + value=evm.value, + caller=evm.caller, + to=evm.current_target, code_address=code_address, should_transfer_value=False, is_staticcall=False, @@ -913,7 +946,7 @@ def staticcall(evm: Evm) -> None: if code_address not in evm.accessed_addresses: evm.accessed_addresses.add(code_address) - tx_state = evm.message.tx_env.state + tx_state = evm.tx_env.state code_hash = get_account(tx_state, code_address).code_hash code = get_code(tx_state, code_hash) @@ -940,7 +973,7 @@ def staticcall(evm: Evm) -> None: gas=message_call_gas.sub_call, state_gas_reservoir=call_state_gas_reservoir, value=U256(0), - caller=evm.message.current_target, + caller=evm.current_target, to=to, code_address=code_address, should_transfer_value=True, diff --git a/src/ethereum/forks/amsterdam/vm/interpreter.py b/src/ethereum/forks/amsterdam/vm/interpreter.py index b1810361f3a..4275887632b 100644 --- a/src/ethereum/forks/amsterdam/vm/interpreter.py +++ b/src/ethereum/forks/amsterdam/vm/interpreter.py @@ -14,7 +14,7 @@ from dataclasses import dataclass from typing import Optional, Set, Tuple, final -from ethereum_types.bytes import Bytes, Bytes0 +from ethereum_types.bytes import Bytes from ethereum_types.numeric import U256, Uint, ulen from ethereum.exceptions import EthereumException @@ -33,6 +33,7 @@ from ..blocks import Log from ..state_tracker import ( + TransactionState, account_deployable, copy_tx_state, destroy_storage, @@ -46,14 +47,13 @@ restore_tx_state, set_code, ) -from ..vm import Message -from ..vm.eoa_delegation import get_delegated_code_address, set_delegation from ..vm.gas import ( GasCosts, GasMeter, StateGasCosts, charge_gas, charge_state_gas, + charge_state_gas_from_meter, commit_state_gas, forfeit_remaining_gas, restore_state_gas, @@ -62,9 +62,12 @@ ) from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS from . import ( + BlockEnvironment, Evm, + TransactionEnvironment, emit_transfer_log, ) +from .eoa_delegation import resolve_delegated_code_address, set_delegation from .exceptions import ( AddressCollision, ExceptionalHalt, @@ -84,106 +87,252 @@ @final @dataclass -class MessageCallOutput: +class TransactionOutput: """ - Output of a particular message call. - - Contains the following: - - 1. `gas_left`: remaining gas after execution. - 2. `refund_counter`: gas to refund after execution. - 3. `logs`: list of `Log` generated during execution. - 4. `accounts_to_delete`: Contracts which have self-destructed. - 5. `error`: The error from the execution if any. - 6. `return_data`: The output of the execution. - 7. `state_gas_left`: remaining state gas after execution. - 8. `state_gas_used`: State gas used during execution. + Settled output of a transaction's top-level call. + + Carry the figures fee settlement and the receipt need, so the + frame itself never leaves the interpreter. """ gas_left: Uint + """Execution gas remaining after execution.""" + refund_counter: U256 + """Gas eligible for refund at the end of the transaction.""" + logs: Tuple[Log, ...] + """Logs emitted during execution; empty when it failed.""" + accounts_to_delete: Set[Address] + """Accounts self-destructed during execution; empty when it failed.""" + error: Optional[EthereumException] + """The error the execution halted with, if any.""" + return_data: Bytes + """The output of the execution.""" + state_gas_left: Uint + """State gas remaining in the reservoir after execution.""" + state_gas_used: int + """Net state gas consumed; negative when refunds exceed charges.""" + + +def charge_value_transfer_to_non_alive_account( + state: TransactionState, + gas_meter: GasMeter, + recipient: Address, + value: U256, +) -> None: + """ + Charge the state gas for creating `recipient` when a value + transfer revives an account that is not alive. + """ + if value > U256(0) and not is_account_alive(state, recipient): + charge_state_gas_from_meter(gas_meter, StateGasCosts.NEW_ACCOUNT) + + +def create_evm( + block_env: BlockEnvironment, + tx_env: TransactionEnvironment, + gas_meter: GasMeter, +) -> Evm: + """ + Build the transaction's top-level frame. + + Apply the EIP-7702 authorizations, charge the state-dependent + dispatch costs to `gas_meter`, and resolve the code the frame + runs. A preparation failure -- a creation-address collision or + insufficient gas -- raises instead of building a frame, leaving + the caller to roll back the state and gas the preparation charged + and settle the transaction without dispatching. + """ + current_target = tx_env.recipient + if tx_env.is_create: + call_data = Bytes(b"") + else: + call_data = tx_env.data + + code_address: Optional[Address] = None + disable_precompiles = False + accessed_addresses: Set[Address] = set() + accessed_storage_keys = set(tx_env.access_list_storage_keys) + + ## Apply the 7702 delegations + if tx_env.authorizations != (): + accessed_authorities = set_delegation(block_env, tx_env, gas_meter) + accessed_addresses.update(accessed_authorities) + commit_state_gas(gas_meter) + + ## Warm up the access sets + accessed_addresses.add(block_env.coinbase) + accessed_addresses.update(PRE_COMPILED_CONTRACTS.keys()) + accessed_addresses.add(tx_env.origin) + accessed_addresses.update(tx_env.access_list_addresses) + accessed_addresses.add(current_target) + + ## Resolve dispatch and charge its state-dependent costs + if tx_env.is_create: + if not account_deployable(tx_env.state, current_target): + raise AddressCollision() + + if ( + get_pre_state_account(tx_env.state, current_target) + == EMPTY_ACCOUNT + ): + charge_state_gas_from_meter(gas_meter, StateGasCosts.NEW_ACCOUNT) + + code = tx_env.data + else: + charge_value_transfer_to_non_alive_account( + tx_env.state, gas_meter, current_target, tx_env.value + ) + + code_address, disable_precompiles = resolve_delegated_code_address( + tx_env.state, gas_meter, accessed_addresses, tx_env.recipient + ) + + code = get_code( + tx_env.state, + get_account(tx_env.state, code_address).code_hash, + ) + + ## Build the frame + return Evm( + # Context + block_env=block_env, + tx_env=tx_env, + parent_evm=None, + depth=Uint(0), + # Call Parameters + caller=tx_env.origin, + current_target=current_target, + value=tx_env.value, + call_data=call_data, + should_transfer_value=True, + is_static=False, + disable_precompiles=disable_precompiles, + # Code + code_address=code_address, + code=code, + valid_jump_destinations=get_valid_jump_destinations(code), + # Machine State + gas_meter=gas_meter, + pc=Uint(0), + stack=[], + memory=bytearray(), + return_data=b"", + # Accrued Effects + logs=(), + accounts_to_delete=set(), + accessed_addresses=accessed_addresses, + accessed_storage_keys=accessed_storage_keys, + # Outcome + running=True, + output=b"", + error=None, + ) -def process_message_call(message: Message) -> MessageCallOutput: +def process_top_level( + block_env: BlockEnvironment, + tx_env: TransactionEnvironment, +) -> TransactionOutput: """ - If `message.target` is empty then it creates a smart contract - else it executes a call from the `message.caller` to the `message.target`. + Execute the top level of a transaction. + + Prepare the transaction's top-level EVM frame and dispatch it: a + contract creation or a call, per the transaction environment. A + preparation failure rolls back everything the preparation changed + and never dispatches; the transaction then settles as if + execution halted at entry, forfeiting its entire gas grant. Parameters ---------- - message : - Transaction specific items. + block_env : + Environment for the Ethereum Virtual Machine. + tx_env : + Environment for the transaction. Returns ------- - output : `MessageCallOutput` - Output of the message call + tx_output : `TransactionOutput` + The settled output of the top-level execution. """ - tx_state = message.tx_env.state - if message.target == Bytes0(b""): - if account_deployable(tx_state, message.current_target): - evm = process_create_message(message) - else: - return MessageCallOutput( - gas_left=Uint(0), - refund_counter=U256(0), - logs=tuple(), - accounts_to_delete=set(), - error=AddressCollision(), - return_data=Bytes(b""), - state_gas_left=message.state_gas_reservoir, - state_gas_used=0, - ) + gas_meter = GasMeter( + gas_left=tx_env.execution_gas_grant, + state_gas_left=tx_env.state_gas_reservoir, + state_gas_baseline=tx_env.state_gas_reservoir, + ) + + prep_snapshot = copy_tx_state(tx_env.state) + try: + evm = create_evm(block_env, tx_env, gas_meter) + except ExceptionalHalt as halt: + # The rollback also reverts any applied delegations, so their + # state gas commit is undone with it: roll state gas back to + # frame entry, refilling every state charge. + restore_tx_state(tx_env.state, prep_snapshot) + restore_state_gas_to_entry(gas_meter, tx_env.state_gas_reservoir) + forfeit_remaining_gas(gas_meter) + return TransactionOutput( + gas_left=gas_meter.gas_left, + refund_counter=U256(gas_meter.refund_counter), + logs=(), + accounts_to_delete=set(), + error=halt, + return_data=Bytes(b""), + state_gas_left=gas_meter.state_gas_left, + state_gas_used=tx_state_gas_used( + gas_meter, tx_env.state_gas_reservoir + ), + ) + + if tx_env.is_create: + process_create(evm) else: - # Authorizations and delegation resolution are handled at the - # top frame inside ``process_message`` (depth 0), so their - # state-dependent gas charges go through the EVM gas pools and - # an out-of-gas there halts the frame cleanly. - evm = process_message(message) + process_call(evm) + # A failed execution contributes no logs or self-destructs. if evm.error: logs: Tuple[Log, ...] = () - accounts_to_delete = set() + accounts_to_delete: Set[Address] = set() else: logs = evm.logs accounts_to_delete = evm.accounts_to_delete tx_end = TransactionEnd( - int(message.gas) - int(evm.gas_meter.gas_left), evm.output, evm.error + int(tx_env.execution_gas_grant) - int(gas_meter.gas_left), + evm.output, + evm.error, ) evm_trace(evm, tx_end) - # A failed frame settles its meter with a zero refund counter, so - # the refunds can be read unconditionally. - return MessageCallOutput( - gas_left=evm.gas_meter.gas_left, - refund_counter=U256(evm.gas_meter.refund_counter), + return TransactionOutput( + gas_left=gas_meter.gas_left, + refund_counter=U256(gas_meter.refund_counter), logs=logs, accounts_to_delete=accounts_to_delete, error=evm.error, return_data=evm.output, - state_gas_left=evm.gas_meter.state_gas_left, + state_gas_left=gas_meter.state_gas_left, state_gas_used=tx_state_gas_used( - evm.gas_meter, message.state_gas_reservoir + gas_meter, tx_env.state_gas_reservoir ), ) -def process_create_message(message: Message) -> Evm: +def process_create(evm: Evm) -> Evm: """ Executes a call to create a smart contract. Parameters ---------- - message : - Transaction specific items. + evm : + Currently running evm. Returns ------- @@ -191,7 +340,7 @@ def process_create_message(message: Message) -> Evm: Items containing execution specific objects. """ - tx_state = message.tx_env.state + tx_state = evm.tx_env.state # take snapshot of state before processing the message snapshot = copy_tx_state(tx_state) @@ -202,17 +351,17 @@ def process_create_message(message: Message) -> Evm: # `CREATE` or `CREATE2` call. # * The first `CREATE` happened before Spurious Dragon and left empty # code. - destroy_storage(tx_state, message.current_target) + destroy_storage(tx_state, evm.current_target) # In the previously mentioned edge case the preexisting storage is ignored # for gas refund purposes. In order to do this we must track created # accounts. This tracking is also needed to respect the constraints # added to SELFDESTRUCT by EIP-6780. - mark_account_created(tx_state, message.current_target) + mark_account_created(tx_state, evm.current_target) - increment_nonce(tx_state, message.current_target) + increment_nonce(tx_state, evm.current_target) - evm = process_message(message) + evm = process_call(evm) if not evm.error: contract_code = evm.output try: @@ -241,91 +390,20 @@ def process_create_message(message: Message) -> Evm: evm.output = b"" evm.error = error else: - set_code(tx_state, message.current_target, contract_code) + set_code(tx_state, evm.current_target, contract_code) else: restore_tx_state(tx_state, snapshot) return evm -def prepare_dispatch(evm: Evm) -> None: - """ - Charge the state-dependent dispatch costs and resolve the code the - top frame will run. - - Runs at the top frame (depth 0), after any EIP-7702 authorizations - have been applied by ``set_delegation`` and before the call is - dispatched: - - - charges the ``NEW_ACCOUNT`` state gas for a contract creation - whose target leaf does not yet exist, or for a value transfer to - a recipient that is not yet alive; and - - resolves a delegation on the recipient, charging the warm or - cold account access and pointing the frame at the delegated - code. - - The creation target is checked against the transaction pre-state: - ``process_create_message`` has already bumped the target's nonce - by the time this runs, so a live check would always see the - account. The recipient check is live, so an authority - materialized earlier in the transaction is not charged - ``NEW_ACCOUNT`` again. - - This function must not mutate the transaction state. Every charge - here pays for state that only materializes inside the dispatched - frame and rolls back with it, so these charges stay refillable -- - unlike the ``set_delegation`` charges, whose state outlives a - dispatch failure and whose gas the caller folds into the frame - baseline. The no-mutation rule is also what keeps the caller's - execution snapshot equal to the state at that fold. - - Insufficient gas raises an ``ExceptionalHalt``; the caller rolls - back the whole preparation -- including the applied authorizations - -- and halts the frame without dispatching. - """ - message = evm.message - tx_state = message.tx_env.state - - if message.target == Bytes0(b""): - if ( - get_pre_state_account(tx_state, message.current_target) - == EMPTY_ACCOUNT - ): - charge_state_gas(evm, StateGasCosts.NEW_ACCOUNT) - else: - recipient = message.current_target - if message.value > U256(0) and not is_account_alive( - tx_state, recipient - ): - charge_state_gas(evm, StateGasCosts.NEW_ACCOUNT) - recipient_code = get_code( - tx_state, get_account(tx_state, recipient).code_hash - ) - delegated_address = get_delegated_code_address(recipient_code) - if delegated_address is not None: - if delegated_address in evm.accessed_addresses: - charge_gas(evm, GasCosts.WARM_ACCESS) - else: - charge_gas(evm, GasCosts.COLD_ACCOUNT_ACCESS) - evm.accessed_addresses.add(delegated_address) - - message.disable_precompiles = True - message.code_address = delegated_address - message.code = get_code( - tx_state, - get_account(tx_state, delegated_address).code_hash, - ) - else: - message.code = recipient_code - - -def process_message(message: Message) -> Evm: +def process_call(evm: Evm) -> Evm: """ Move ether and execute the relevant code. Parameters ---------- - message : - Transaction specific items. + evm : + The EVM frame to execute. Returns ------- @@ -333,82 +411,32 @@ def process_message(message: Message) -> Evm: Items containing execution specific objects """ - tx_state = message.tx_env.state - if message.depth > STACK_DEPTH_LIMIT: + tx_state = evm.tx_env.state + if evm.depth > STACK_DEPTH_LIMIT: raise StackDepthLimitError("Stack depth limit reached") - evm = Evm( - pc=Uint(0), - stack=[], - memory=bytearray(), - code=Bytes(b""), - gas_meter=GasMeter( - gas_left=message.gas, - state_gas_left=message.state_gas_reservoir, - state_gas_baseline=message.state_gas_reservoir, - ), - valid_jump_destinations=set(), - logs=(), - running=True, - message=message, - output=b"", - accounts_to_delete=set(), - return_data=b"", - error=None, - accessed_addresses=message.accessed_addresses, - accessed_storage_keys=message.accessed_storage_keys, - ) - - if message.depth == Uint(0): - prep_snapshot = copy_tx_state(tx_state) - try: - if message.tx_env.authorizations != (): - set_delegation(evm) - # The applied delegations outlive a failure of the - # dispatched code, so their state gas is committed as - # non-refillable; a later failure restores only to the - # post-commit baseline. - commit_state_gas(evm.gas_meter) - prepare_dispatch(evm) - except ExceptionalHalt as error: - evm_trace(evm, OpException(error)) - restore_tx_state(tx_state, prep_snapshot) - # The rollback reverts any applied delegations, so the - # commit above is undone with it: roll state gas back to - # frame entry, refilling every state charge. - restore_state_gas_to_entry( - evm.gas_meter, message.state_gas_reservoir - ) - forfeit_remaining_gas(evm.gas_meter) - evm.error = error - return evm - - assert message.code is not None - evm.code = message.code - evm.valid_jump_destinations = get_valid_jump_destinations(message.code) - snapshot = copy_tx_state(tx_state) # Execute message code and handle errors try: - if message.should_transfer_value and message.value != 0: + if evm.should_transfer_value and evm.value != 0: move_ether( tx_state, - message.caller, - message.current_target, - message.value, + evm.caller, + evm.current_target, + evm.value, ) - if message.caller != message.current_target: + if evm.caller != evm.current_target: emit_transfer_log( evm, - message.caller, - message.current_target, - message.value, + evm.caller, + evm.current_target, + evm.value, ) - if evm.message.code_address in PRE_COMPILED_CONTRACTS: - if not message.disable_precompiles: - evm_trace(evm, PrecompileStart(evm.message.code_address)) - PRE_COMPILED_CONTRACTS[evm.message.code_address](evm) + if evm.code_address in PRE_COMPILED_CONTRACTS: + if not evm.disable_precompiles: + evm_trace(evm, PrecompileStart(evm.code_address)) + PRE_COMPILED_CONTRACTS[evm.code_address](evm) evm_trace(evm, PrecompileEnd()) else: while evm.running and evm.pc < ulen(evm.code): diff --git a/src/ethereum/forks/amsterdam/vm/precompiled_contracts/alt_bn128.py b/src/ethereum/forks/amsterdam/vm/precompiled_contracts/alt_bn128.py index 862506c54c3..e76bbcee100 100644 --- a/src/ethereum/forks/amsterdam/vm/precompiled_contracts/alt_bn128.py +++ b/src/ethereum/forks/amsterdam/vm/precompiled_contracts/alt_bn128.py @@ -146,7 +146,7 @@ def alt_bn128_add(evm: Evm) -> None: The current EVM frame. """ - data = evm.message.data + data = evm.call_data # GAS charge_gas(evm, GasCosts.PRECOMPILE_ECADD) @@ -174,7 +174,7 @@ def alt_bn128_mul(evm: Evm) -> None: The current EVM frame. """ - data = evm.message.data + data = evm.call_data # GAS charge_gas(evm, GasCosts.PRECOMPILE_ECMUL) @@ -202,7 +202,7 @@ def alt_bn128_pairing_check(evm: Evm) -> None: The current EVM frame. """ - data = evm.message.data + data = evm.call_data # GAS charge_gas( diff --git a/src/ethereum/forks/amsterdam/vm/precompiled_contracts/blake2f.py b/src/ethereum/forks/amsterdam/vm/precompiled_contracts/blake2f.py index ae53b1ab4b5..d9990149189 100644 --- a/src/ethereum/forks/amsterdam/vm/precompiled_contracts/blake2f.py +++ b/src/ethereum/forks/amsterdam/vm/precompiled_contracts/blake2f.py @@ -28,7 +28,7 @@ def blake2f(evm: Evm) -> None: The current EVM frame. """ - data = evm.message.data + data = evm.call_data if len(data) != 213: raise InvalidParameter diff --git a/src/ethereum/forks/amsterdam/vm/precompiled_contracts/bls12_381/bls12_381_g1.py b/src/ethereum/forks/amsterdam/vm/precompiled_contracts/bls12_381/bls12_381_g1.py index d1f63224a0c..cb453f19ee0 100644 --- a/src/ethereum/forks/amsterdam/vm/precompiled_contracts/bls12_381/bls12_381_g1.py +++ b/src/ethereum/forks/amsterdam/vm/precompiled_contracts/bls12_381/bls12_381_g1.py @@ -53,7 +53,7 @@ def bls12_g1_add(evm: Evm) -> None: If the input length is invalid. """ - data = evm.message.data + data = evm.call_data if len(data) != 256: raise InvalidParameter("Invalid Input Length") @@ -88,7 +88,7 @@ def bls12_g1_msm(evm: Evm) -> None: If the input length is invalid. """ - data = evm.message.data + data = evm.call_data if len(data) == 0 or len(data) % LENGTH_PER_PAIR != 0: raise InvalidParameter("Invalid Input Length") @@ -133,7 +133,7 @@ def bls12_map_fp_to_g1(evm: Evm) -> None: If the input length is invalid. """ - data = evm.message.data + data = evm.call_data if len(data) != 64: raise InvalidParameter("Invalid Input Length") diff --git a/src/ethereum/forks/amsterdam/vm/precompiled_contracts/bls12_381/bls12_381_g2.py b/src/ethereum/forks/amsterdam/vm/precompiled_contracts/bls12_381/bls12_381_g2.py index 2fd32313f89..7be6695fc2a 100644 --- a/src/ethereum/forks/amsterdam/vm/precompiled_contracts/bls12_381/bls12_381_g2.py +++ b/src/ethereum/forks/amsterdam/vm/precompiled_contracts/bls12_381/bls12_381_g2.py @@ -54,7 +54,7 @@ def bls12_g2_add(evm: Evm) -> None: If the input length is invalid. """ - data = evm.message.data + data = evm.call_data if len(data) != 512: raise InvalidParameter("Invalid Input Length") @@ -89,7 +89,7 @@ def bls12_g2_msm(evm: Evm) -> None: If the input length is invalid. """ - data = evm.message.data + data = evm.call_data if len(data) == 0 or len(data) % LENGTH_PER_PAIR != 0: raise InvalidParameter("Invalid Input Length") @@ -134,7 +134,7 @@ def bls12_map_fp2_to_g2(evm: Evm) -> None: If the input length is invalid. """ - data = evm.message.data + data = evm.call_data if len(data) != 128: raise InvalidParameter("Invalid Input Length") diff --git a/src/ethereum/forks/amsterdam/vm/precompiled_contracts/bls12_381/bls12_381_pairing.py b/src/ethereum/forks/amsterdam/vm/precompiled_contracts/bls12_381/bls12_381_pairing.py index c7a62cb49c0..2723e11854e 100644 --- a/src/ethereum/forks/amsterdam/vm/precompiled_contracts/bls12_381/bls12_381_pairing.py +++ b/src/ethereum/forks/amsterdam/vm/precompiled_contracts/bls12_381/bls12_381_pairing.py @@ -36,7 +36,7 @@ def bls12_pairing(evm: Evm) -> None: If the input length is invalid or if the subgroup check fails. """ - data = evm.message.data + data = evm.call_data if len(data) == 0 or len(data) % 384 != 0: raise InvalidParameter("Invalid Input Length") diff --git a/src/ethereum/forks/amsterdam/vm/precompiled_contracts/ecrecover.py b/src/ethereum/forks/amsterdam/vm/precompiled_contracts/ecrecover.py index 17a0174f6ed..07eab99e319 100644 --- a/src/ethereum/forks/amsterdam/vm/precompiled_contracts/ecrecover.py +++ b/src/ethereum/forks/amsterdam/vm/precompiled_contracts/ecrecover.py @@ -34,7 +34,7 @@ def ecrecover(evm: Evm) -> None: The current EVM frame. """ - data = evm.message.data + data = evm.call_data # GAS charge_gas(evm, GasCosts.PRECOMPILE_ECRECOVER) diff --git a/src/ethereum/forks/amsterdam/vm/precompiled_contracts/identity.py b/src/ethereum/forks/amsterdam/vm/precompiled_contracts/identity.py index b7631736074..448aa8e25a2 100644 --- a/src/ethereum/forks/amsterdam/vm/precompiled_contracts/identity.py +++ b/src/ethereum/forks/amsterdam/vm/precompiled_contracts/identity.py @@ -32,7 +32,7 @@ def identity(evm: Evm) -> None: The current EVM frame. """ - data = evm.message.data + data = evm.call_data # GAS word_count = ceil32(ulen(data)) // Uint(32) diff --git a/src/ethereum/forks/amsterdam/vm/precompiled_contracts/modexp.py b/src/ethereum/forks/amsterdam/vm/precompiled_contracts/modexp.py index bf828ee8f6e..51b2f886cae 100644 --- a/src/ethereum/forks/amsterdam/vm/precompiled_contracts/modexp.py +++ b/src/ethereum/forks/amsterdam/vm/precompiled_contracts/modexp.py @@ -25,7 +25,7 @@ def modexp(evm: Evm) -> None: Calculates `(base**exp) % modulus` for arbitrary sized `base`, `exp` and `modulus`. The return value is the same length as the modulus. """ - data = evm.message.data + data = evm.call_data # GAS base_length = U256.from_be_bytes(buffer_read(data, U256(0), U256(32))) diff --git a/src/ethereum/forks/amsterdam/vm/precompiled_contracts/p256verify.py b/src/ethereum/forks/amsterdam/vm/precompiled_contracts/p256verify.py index 29c2e91e0f0..e75e104d612 100644 --- a/src/ethereum/forks/amsterdam/vm/precompiled_contracts/p256verify.py +++ b/src/ethereum/forks/amsterdam/vm/precompiled_contracts/p256verify.py @@ -38,7 +38,7 @@ def p256verify(evm: Evm) -> None: The current EVM frame. """ - data = evm.message.data + data = evm.call_data # GAS charge_gas(evm, GasCosts.PRECOMPILE_P256VERIFY) diff --git a/src/ethereum/forks/amsterdam/vm/precompiled_contracts/point_evaluation.py b/src/ethereum/forks/amsterdam/vm/precompiled_contracts/point_evaluation.py index d2d105ba13b..5f18d55f850 100644 --- a/src/ethereum/forks/amsterdam/vm/precompiled_contracts/point_evaluation.py +++ b/src/ethereum/forks/amsterdam/vm/precompiled_contracts/point_evaluation.py @@ -40,7 +40,7 @@ def point_evaluation(evm: Evm) -> None: The current EVM frame. """ - data = evm.message.data + data = evm.call_data if len(data) != 192: raise KZGProofError diff --git a/src/ethereum/forks/amsterdam/vm/precompiled_contracts/ripemd160.py b/src/ethereum/forks/amsterdam/vm/precompiled_contracts/ripemd160.py index c82c9bd534d..57afeff0a72 100644 --- a/src/ethereum/forks/amsterdam/vm/precompiled_contracts/ripemd160.py +++ b/src/ethereum/forks/amsterdam/vm/precompiled_contracts/ripemd160.py @@ -35,7 +35,7 @@ def ripemd160(evm: Evm) -> None: The current EVM frame. """ - data = evm.message.data + data = evm.call_data # GAS word_count = ceil32(ulen(data)) // Uint(32) diff --git a/src/ethereum/forks/amsterdam/vm/precompiled_contracts/sha256.py b/src/ethereum/forks/amsterdam/vm/precompiled_contracts/sha256.py index 9d467d7e951..6db9ec970fb 100644 --- a/src/ethereum/forks/amsterdam/vm/precompiled_contracts/sha256.py +++ b/src/ethereum/forks/amsterdam/vm/precompiled_contracts/sha256.py @@ -34,7 +34,7 @@ def sha256(evm: Evm) -> None: The current EVM frame. """ - data = evm.message.data + data = evm.call_data # GAS word_count = ceil32(ulen(data)) // Uint(32) diff --git a/src/ethereum_spec_tools/evm_tools/t8n/evm_trace/eip3155.py b/src/ethereum_spec_tools/evm_tools/t8n/evm_trace/eip3155.py index b2b26008a8e..97d546ece66 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/evm_trace/eip3155.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/evm_trace/eip3155.py @@ -117,28 +117,34 @@ def __call__(self, evm: Any, event: TraceEvent) -> None: """ Create a trace of the event. """ + # TODO: Rethink the tracer interface so it does not probe + # fork-specific frame layouts. Recent forks merge the message + # fields into the frame itself; older forks keep them on + # `evm.message`. + message = getattr(evm, "message", evm) + # System Transaction do not have a tx_hash or index if ( - evm.message.tx_env.index_in_block is None - or evm.message.tx_env.tx_hash is None + message.tx_env.index_in_block is None + or message.tx_env.tx_hash is None ): return assert isinstance(evm, Evm) - if self.transaction_environment is not evm.message.tx_env: + if self.transaction_environment is not message.tx_env: self.active_traces = [] - self.transaction_environment = evm.message.tx_env + self.transaction_environment = message.tx_env last_trace = None if self.active_traces: last_trace = self.active_traces[-1] refund_counter = evm_refund_counter(evm) - parent_evm = evm.message.parent_evm + parent_evm = message.parent_evm while parent_evm is not None: refund_counter += evm_refund_counter(parent_evm) - parent_evm = parent_evm.message.parent_evm + parent_evm = getattr(parent_evm, "message", parent_evm).parent_evm len_memory = len(evm.memory) @@ -162,8 +168,8 @@ def __call__(self, evm: Any, event: TraceEvent) -> None: output_traces( self.active_traces, - evm.message.tx_env.index_in_block, - evm.message.tx_env.tx_hash, + message.tx_env.index_in_block, + message.tx_env.tx_hash, self.output_basedir, ) elif isinstance(event, PrecompileStart): @@ -176,7 +182,7 @@ def __call__(self, evm: Any, event: TraceEvent) -> None: memSize=len_memory, stack=stack, returnData=return_data, - depth=int(evm.message.depth) + 1, + depth=int(message.depth) + 1, refund=refund_counter, opName="0x" + event.address.hex().lstrip("0"), precompile=True, @@ -208,7 +214,7 @@ def __call__(self, evm: Any, event: TraceEvent) -> None: memSize=len_memory, stack=stack, returnData=return_data, - depth=int(evm.message.depth) + 1, + depth=int(message.depth) + 1, refund=refund_counter, opName=str(event.op).split(".")[-1], stateGas=state_gas, @@ -235,7 +241,7 @@ def __call__(self, evm: Any, event: TraceEvent) -> None: # The first opcode in a child message is an InvalidOpcode. # This case has to be explicitly handled since the first # two conditions do not cover it. - or last_trace.depth == evm.message.depth + or last_trace.depth == message.depth ): if not hasattr(event.error, "code"): name = event.error.__class__.__name__ @@ -253,7 +259,7 @@ def __call__(self, evm: Any, event: TraceEvent) -> None: memSize=len_memory, stack=stack, returnData=return_data, - depth=int(evm.message.depth) + 1, + depth=int(message.depth) + 1, refund=refund_counter, opName="InvalidOpcode", gasCostTraced=True, diff --git a/src/ethereum_spec_tools/evm_tools/t8n/evm_trace/protocols.py b/src/ethereum_spec_tools/evm_tools/t8n/evm_trace/protocols.py index 1b0a2271eff..94a8a479dc4 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/evm_trace/protocols.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/evm_trace/protocols.py @@ -33,14 +33,24 @@ class Message(Protocol): class Evm(Protocol): """ The class describes the EVM interface common to every fork's trace. + + The message-scoped fields (`depth`, `tx_env`, `parent_evm`) are + described by [`Message`][msg]. Older forks carry them on + `evm.message`; forks that merge the message into the frame expose + them on `evm` itself, so `evm` satisfies both protocols. Tracers + resolve the carrier with `getattr(evm, "message", evm)`. + + [msg]: ref:ethereum_spec_tools.evm_tools.t8n.evm_trace.protocols.Message """ + # TODO: Rethink the tracer interface so it does not probe + # fork-specific frame layouts. + pc: Uint stack: list[U256] memory: bytearray code: Bytes running: bool - message: Message @runtime_checkable diff --git a/src/ethereum_spec_tools/new_fork/builder.py b/src/ethereum_spec_tools/new_fork/builder.py index 8883ac4b556..f076d2ba923 100644 --- a/src/ethereum_spec_tools/new_fork/builder.py +++ b/src/ethereum_spec_tools/new_fork/builder.py @@ -4,6 +4,7 @@ """ import json +import re import sys import warnings from abc import ABC, abstractmethod @@ -12,7 +13,7 @@ from pathlib import Path from shutil import copytree, ignore_patterns, rmtree from tempfile import TemporaryDirectory -from typing import Final, NamedTuple +from typing import ClassVar, Final, NamedTuple from ethereum_types.numeric import U64, U256, Uint from libcst.tool import main as libcst_tool @@ -48,6 +49,18 @@ def _source_file_for(fork_root: Path, qualified_name: str) -> Path: return fork_root / "__init__.py" +def _assigns(source_file: Path, name: str) -> bool: + """ + Return whether `source_file` assigns a value to `name`. + + Constants occasionally move between a fork's modules, so a modifier that + accepts more than one template layout uses this to pick the qualified name + that the template actually defines. + """ + pattern = re.compile(rf"^\s*{re.escape(name)}\s*[:=]", re.MULTILINE) + return pattern.search(source_file.read_text()) is not None + + @dataclass class CodemodArgs(ABC): """ @@ -204,6 +217,41 @@ def _replacement( return _Replacement(self.qualified_name, self.value, self.imports) +@dataclass +class SetMaxBlobGasPerBlock(ReplaceValue): + """ + Instruct `libcst.tool:main` to replace the value of + `MAX_BLOB_GAS_PER_BLOCK`, wherever the template fork defines it. + """ + + value: str + + # TODO: Replace this class with a plain `SetConstant` targeting + # `vm.gas.MAX_BLOB_GAS_PER_BLOCK` once every fork usable as a template + # defines the constant there (i.e. once the pre-Amsterdam forks, which + # define it in `fork`, are gone). + candidates: ClassVar[tuple[str, ...]] = ( + "vm.gas.MAX_BLOB_GAS_PER_BLOCK", + "fork.MAX_BLOB_GAS_PER_BLOCK", + ) + + @override + def _replacement( + self, fork_builder: "ForkBuilder", working_directory: Path + ) -> _Replacement: + fork_root = working_directory / "ethereum" / fork_builder.new_fork + + for qualified_name in self.candidates: + source = _source_file_for(fork_root, qualified_name) + if _assigns(source, "MAX_BLOB_GAS_PER_BLOCK"): + return _Replacement(qualified_name, self.value, []) + + raise Exception( + "template fork defines MAX_BLOB_GAS_PER_BLOCK in none of: " + + ", ".join(self.candidates) + ) + + @dataclass class SetForkCriteria(ReplaceValue): """ @@ -549,10 +597,7 @@ def modify_max_blob_gas_per_block( ) -> None: """Append a `CodemodArgs` that sets `MAX_BLOB_GAS_PER_BLOCK`.""" self.modifiers.append( - SetConstant( - "fork.MAX_BLOB_GAS_PER_BLOCK", - repr(max_blob_gas_per_block), - ) + SetMaxBlobGasPerBlock(repr(max_blob_gas_per_block)) ) def modify_blob_schedule_target(self, blob_schedule_target: U64) -> None: diff --git a/src/ethereum_spec_tools/new_fork/codemod/constant.py b/src/ethereum_spec_tools/new_fork/codemod/constant.py index ce3204b90de..8e913450e3a 100644 --- a/src/ethereum_spec_tools/new_fork/codemod/constant.py +++ b/src/ethereum_spec_tools/new_fork/codemod/constant.py @@ -34,6 +34,7 @@ class SetConstantCommand(VisitorBasedCodemodCommand): _in_assign_target: bool _matches: bool + _replaced: bool @staticmethod def add_args(arg_parser: argparse.ArgumentParser) -> None: @@ -78,8 +79,20 @@ def __init__( self.value = cst.parse_expression(value) self._in_assign_target = False self._matches = False + self._replaced = False self.imports = imports or [] + @override + def leave_Module( # noqa: D102 + self, original_node: cst.Module, updated_node: cst.Module + ) -> cst.Module: + if not self._replaced: + raise Exception( + f"`{self.qualified_name}` is not assigned in this module" + ) + + return updated_node + @override def visit_Assign_targets(self, node: cst.Assign) -> None: # noqa: D102 if self._in_assign_target: @@ -108,6 +121,7 @@ def leave_Assign( # noqa: D102 return updated_node self._matches = False + self._replaced = True if len(original_node.targets) != 1: raise NotImplementedError( @@ -152,6 +166,7 @@ def leave_AnnAssign( # noqa: D102 return updated_node self._matches = False + self._replaced = True for module, identifier in self.imports: AddImportsVisitor.add_needed_import( diff --git a/tests/evm_tools/test_new_fork.py b/tests/evm_tools/test_new_fork.py index 45e76591af5..9d519543d00 100644 --- a/tests/evm_tools/test_new_fork.py +++ b/tests/evm_tools/test_new_fork.py @@ -86,8 +86,14 @@ def test_end_to_end(template_fork: str) -> None: for needle in expected: assert needle in source - with (fork_dir / "fork.py").open("r") as f: - assert "MAX_BLOB_GAS_PER_BLOCK: Final[U64] = U64(99)" in f.read() + # TODO: Assert on `vm/gas.py` alone once every fork usable as a + # template defines the constant there (i.e. once the pre-Amsterdam + # forks, which define it in `fork.py`, are gone). + blob_gas_ceiling = "MAX_BLOB_GAS_PER_BLOCK: Final[U64] = U64(99)" + assert any( + blob_gas_ceiling in (fork_dir / relative_path).read_text() + for relative_path in (Path("vm") / "gas.py", Path("fork.py")) + ) # TODO: Remove this condition once trie.py is removed from all # forks (i.e. fork-agnostic Trie is ported to pre-amsterdam forks). diff --git a/tests/osaka/eip7594_peerdas/test_max_blob_per_tx.py b/tests/osaka/eip7594_peerdas/test_max_blob_per_tx.py index 41e06274c4e..317aec9164a 100644 --- a/tests/osaka/eip7594_peerdas/test_max_blob_per_tx.py +++ b/tests/osaka/eip7594_peerdas/test_max_blob_per_tx.py @@ -128,11 +128,16 @@ def test_invalid_max_blobs_per_tx( number of blobs per transaction, even if the total would be within the block limit. """ + # When the blob count also exceeds the block allowance, the reported + # exception depends on the fork's validation order, so accept either. state_test( env=env, pre=pre, tx=tx.with_error( - TransactionException.TYPE_3_TX_MAX_BLOB_GAS_ALLOWANCE_EXCEEDED + [ + TransactionException.TYPE_3_TX_MAX_BLOB_GAS_ALLOWANCE_EXCEEDED, + TransactionException.TYPE_3_TX_BLOB_COUNT_EXCEEDED, + ] if blob_count > fork.max_blobs_per_block() else TransactionException.TYPE_3_TX_BLOB_COUNT_EXCEEDED ), diff --git a/vulture_whitelist.py b/vulture_whitelist.py index e944b4e3f7c..b68fbccedab 100644 --- a/vulture_whitelist.py +++ b/vulture_whitelist.py @@ -190,6 +190,7 @@ SetConstantCommand.visit_AnnAssign_target SetConstantCommand.leave_AnnAssign_target SetConstantCommand.leave_AnnAssign +SetConstantCommand.leave_Module # src/ethereum_spec_tools/new_fork/codemod/remove_docstring.py - codemod class from ethereum_spec_tools.new_fork.codemod.remove_docstring import ( From 9b68a91c974de4d47e2eda14d92f606f057a8e0c Mon Sep 17 00:00:00 2001 From: spencer <spencer.taylor-brown@ethereum.org> Date: Tue, 4 Aug 2026 18:38:00 +0200 Subject: [PATCH 196/233] fix(test-execute): report each test as an individual hive test case (#3287) --- .../plugins/execute/rpc/hive.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/rpc/hive.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/rpc/hive.py index 608d3d43d8d..a5f1aa18859 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/rpc/hive.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/rpc/hive.py @@ -285,6 +285,24 @@ def test_suite_description() -> str: return "Execute EEST tests using hive endpoint." +@pytest.fixture(scope="function") +def test_case_description(request: pytest.FixtureRequest) -> str: + """Return the test docstring as the hive test-case description.""" + description = getattr(request.node.function, "__doc__", None) + return description or "" + + +@pytest.fixture(autouse=True) +def per_test_hive_test(hive_test: HiveTest) -> None: + """ + Report each pytest test as an individual hive test case. + + The client runs under the session-scoped base hive test; this + per-test entry only propagates the individual test result to hive. + """ + del hive_test + + @pytest.fixture(autouse=True, scope="session") def base_hive_test( request: pytest.FixtureRequest, From 7a0430d13d53ebac585e527491081db47c6d2cd9 Mon Sep 17 00:00:00 2001 From: spencer <spencer.taylor-brown@ethereum.org> Date: Tue, 4 Aug 2026 18:57:20 +0200 Subject: [PATCH 197/233] refactor(tests): remove dead static-filler infrastructure (#3283) * refactor(tests): remove dead static-filler infrastructure Remove the static-filler stack, now dead after the legacy ethereum/tests fillers were ported to Python tests under tests/ported_static/: - Fill path: the static_filler.py plugin and the --fill-static-tests flag and plumbing (incl. the unconditional -p static_filler load in pytest-fill.ini and the FixtureCollector.fill_static_tests field). - Parser: specs/base_static.py and the specs/static_state/ package. - Migration tools: scripts/filler_to_python/, cli/fillerconvert/, cli/modify_static_test_gas_limits.py and their entry points. Relocate the two runtime helpers used by 339 ported tests (resolve_expect_post / resolve_expect_post_fork, plus the ForkSet / ForkConstraint / CMP / _match_index closure) into a new self-contained module specs/post_state_resolution.py, and codemod the imports. Also drop the now-dead yul_test and compile_yul_with markers (the yul fixture they keyed off lived only in static_filler.py). Removes the public exports execution_testing.specs.BaseStaticTest and execution_testing.specs.StateStaticTest (no in-repo consumers). To be merged after #2556 (ethereum/tests archival), whose parity tooling overlaps with the migration tools removed here. * refactor(test-plugins): remove static-test fork-is-None fallbacks * refactor(tests): move post_state_resolution into tests/ported_static * refactor(test-tools): remove unused Yul/Solc compiler support --------- Co-authored-by: Leo Lara <leo@leolara.me> Co-authored-by: Mario Vega <marioevz@gmail.com> --- packages/testing/pyproject.toml | 2 - .../cli/fillerconvert/fillerconvert.py | 61 - .../cli/fillerconvert/verify_filled.py | 100 - .../cli/modify_static_test_gas_limits.py | 272 --- .../plugins/execute/execute.py | 3 - .../pytest_commands/plugins/filler/filler.py | 20 - .../plugins/filler/static_filler.py | 466 ---- .../cli/pytest_commands/plugins/help/help.py | 1 - .../plugins/shared/execute_fill.py | 23 - .../pytest_commands/plugins/solc/__init__.py | 1 - .../cli/pytest_commands/plugins/solc/solc.py | 149 -- .../pytest_ini_files/pytest-fill.ini | 1 - .../execution_testing/fixtures/collector.py | 7 - .../fixtures/tests/test_collector.py | 11 - .../src/execution_testing/forks/base_fork.py | 9 - .../execution_testing/forks/forks/forks.py | 17 +- .../src/execution_testing/specs/__init__.py | 4 - .../execution_testing/specs/base_static.py | 188 -- .../specs/static_state/__init__.py | 1 - .../specs/static_state/account.py | 250 --- .../specs/static_state/common/__init__.py | 39 - .../specs/static_state/common/common.py | 418 ---- .../specs/static_state/common/compile_yul.py | 102 - .../specs/static_state/common/tags.py | 227 -- .../specs/static_state/environment.py | 78 - .../specs/static_state/expect_section.py | 490 ----- .../specs/static_state/general_transaction.py | 243 --- .../specs/static_state/state_static.py | 231 -- .../tools/tests/test_code.py | 42 - .../tools/tools_code/__init__.py | 4 - .../execution_testing/tools/tools_code/yul.py | 112 - scripts/filler_to_python/__init__.py | 1 - scripts/filler_to_python/__main__.py | 328 --- scripts/filler_to_python/analyzer.py | 1940 ----------------- scripts/filler_to_python/ir.py | 158 -- scripts/filler_to_python/render.py | 295 --- .../templates/state_test.py.j2 | 412 ---- scripts/verify_dynamic_addresses.sh | 34 - tests/ported_static/__init__.py | 1 + tests/ported_static/post_state_resolution.py | 229 ++ .../test_addmod_non_const.py | 5 +- .../test_and_non_const.py | 5 +- .../test_balance_non_const.py | 5 +- .../test_byte_non_const.py | 5 +- .../test_call_non_const.py | 5 +- .../test_callcode_non_const.py | 5 +- .../test_calldatacopy_non_const.py | 5 +- .../test_calldataload_non_const.py | 5 +- .../test_codecopy_non_const.py | 5 +- .../test_create_non_const.py | 5 +- .../test_delegatecall_non_const.py | 5 +- .../test_div_non_const.py | 5 +- .../stArgsZeroOneBalance/test_eq_non_const.py | 5 +- .../test_exp_non_const.py | 5 +- .../test_extcodecopy_non_const.py | 5 +- .../test_extcodesize_non_const.py | 5 +- .../stArgsZeroOneBalance/test_gt_non_const.py | 5 +- .../test_iszero_non_const.py | 5 +- .../test_jump_non_const.py | 5 +- .../test_jumpi_non_const.py | 5 +- .../test_log0_non_const.py | 5 +- .../test_log1_non_const.py | 5 +- .../test_log2_non_const.py | 5 +- .../test_log3_non_const.py | 5 +- .../stArgsZeroOneBalance/test_lt_non_const.py | 5 +- .../test_mload_non_const.py | 5 +- .../test_mod_non_const.py | 5 +- .../test_mstore8_non_const.py | 5 +- .../test_mstore_non_const.py | 5 +- .../test_mul_non_const.py | 5 +- .../test_mulmod_non_const.py | 5 +- .../test_not_non_const.py | 5 +- .../stArgsZeroOneBalance/test_or_non_const.py | 5 +- .../test_return_non_const.py | 5 +- .../test_sdiv_non_const.py | 5 +- .../test_sgt_non_const.py | 5 +- .../test_sha3_non_const.py | 5 +- .../test_signext_non_const.py | 5 +- .../test_sload_non_const.py | 5 +- .../test_slt_non_const.py | 5 +- .../test_smod_non_const.py | 5 +- .../test_sstore_non_const.py | 5 +- .../test_sub_non_const.py | 5 +- .../test_xor_non_const.py | 5 +- .../stBadOpcode/test_measure_gas.py | 5 +- .../stBadOpcode/test_operation_diff_gas.py | 5 +- .../stCallCodes/test_callcode_dynamic_code.py | 5 +- .../test_callcode_dynamic_code2_self_call.py | 5 +- ..._callcode_in_initcode_to_empty_contract.py | 5 +- ..._exis_contract_with_v_transfer_ne_money.py | 5 +- ...llcode_in_initcode_to_existing_contract.py | 5 +- .../test_call1024_oog.py | 5 +- .../test_call1024_pre_calls.py | 5 +- .../test_call_with_high_value_and_gas_oog.py | 5 +- ...all_with_high_value_and_oo_gat_tx_level.py | 5 +- .../test_callcode1024_oog.py | 5 +- .../test_callcode_lose_gas_oog.py | 5 +- ..._ask_more_gas_then_transaction_provided.py | 5 +- .../test_create_fail_balance_too_low.py | 5 +- .../test_create_init_oo_gfor_create.py | 5 +- ...name_registrator_per_txs_not_enough_gas.py | 5 +- .../test_create2_code_size_limit.py | 5 +- .../test_create_code_size_limit.py | 5 +- .../stCreate2/test_create2_first_byte_loop.py | 5 +- .../test_create2_high_nonce_delegatecall.py | 5 +- .../stCreate2/test_create2_init_codes.py | 5 +- .../test_create2_oo_gafter_init_code.py | 5 +- ...create2_oo_gafter_init_code_returndata2.py | 5 +- .../test_create2_oog_from_call_refunds.py | 5 +- .../stCreate2/test_create2_recursive.py | 5 +- .../stCreate2/test_create2_smart_init_code.py | 5 +- .../stCreate2/test_create2_suicide.py | 5 +- .../stCreate2/test_create2call_precompiles.py | 5 +- .../test_create2check_fields_in_initcode.py | 5 +- .../test_create2collision_balance.py | 5 +- .../stCreate2/test_create2collision_code2.py | 5 +- .../test_create2collision_selfdestructed.py | 5 +- .../test_create2collision_selfdestructed2.py | 5 +- .../stCreate2/test_create2no_cash.py | 5 +- .../stCreate2/test_create_message_reverted.py | 5 +- ...st_create_message_reverted_oog_in_init2.py | 5 +- .../test_returndatacopy_following_create.py | 5 +- .../test_revert_depth_create2_oog.py | 5 +- .../test_revert_depth_create2_oog_berlin.py | 5 +- ...t_revert_depth_create_address_collision.py | 5 +- ...t_depth_create_address_collision_berlin.py | 5 +- .../stCreate2/test_revert_opcode_create.py | 5 +- .../stCreateTest/test_code_in_constructor.py | 5 +- .../test_create_address_warm_after_fail.py | 5 +- .../test_create_collision_to_empty2.py | 5 +- ...tract_create_ne_contract_in_init_oog_tr.py | 5 +- .../stCreateTest/test_create_fail_result.py | 5 +- .../stCreateTest/test_create_large_result.py | 5 +- .../test_create_oo_gafter_init_code.py | 5 +- ..._create_oo_gafter_init_code_returndata2.py | 5 +- ...test_create_oo_gafter_init_code_revert2.py | 5 +- .../test_create_oo_gafter_max_codesize.py | 5 +- .../test_create_oog_from_call_refunds.py | 5 +- .../stCreateTest/test_create_results.py | 5 +- .../test_create_transaction_high_nonce.py | 5 +- .../test_transaction_collision_to_empty2.py | 5 +- ...transaction_collision_to_empty_but_code.py | 5 +- .../test_call1024_oog.py | 5 +- .../test_call1024_pre_calls.py | 5 +- .../test_callcode_lose_gas_oog.py | 5 +- .../test_trans_storage_ok.py | 5 +- .../test_trans_storage_reset.py | 5 +- .../test_eip2929.py | 5 +- .../test_eip2929_minus_ff.py | 5 +- .../test_gas_cost.py | 5 +- .../test_gas_cost_memory.py | 5 +- .../stEIP1559/test_low_gas_limit.py | 5 +- .../stEIP1559/test_low_gas_price_old_types.py | 5 +- .../stEIP1559/test_out_of_funds.py | 5 +- .../stEIP1559/test_out_of_funds_old_types.py | 5 +- .../stEIP1559/test_val_causes_oof.py | 5 +- .../stEIP2930/test_address_opcodes.py | 5 +- .../stEIP2930/test_coinbase_t01.py | 5 +- .../stEIP2930/test_coinbase_t2.py | 5 +- .../stEIP2930/test_manual_create.py | 5 +- .../stEIP2930/test_storage_costs.py | 5 +- .../stEIP2930/test_transaction_costs.py | 5 +- .../stEIP2930/test_varied_context.py | 5 +- ...iding_with_non_empty_account_init_paris.py | 5 +- .../stEIP3855_push0/test_push0.py | 5 +- .../test_create2_init_code_size_limit.py | 5 +- .../test_create_init_code_size_limit.py | 5 +- .../test_creation_tx_init_code_size_limit.py | 3 +- .../stEIP5656_MCOPY/test_mcopy.py | 5 +- .../test_mcopy_memory_expansion_cost.py | 5 +- .../stExample/test_labels_example.py | 5 +- .../stExample/test_ranges_example.py | 5 +- .../test_out_of_gas_contract_creation.py | 5 +- ..._out_of_gas_prefunded_contract_creation.py | 5 +- .../test_oo_gin_return.py | 5 +- .../stMemoryStressTest/test_fill_stack.py | 5 +- .../test_mload32bit_bound.py | 5 +- .../test_mload32bit_bound2.py | 5 +- .../test_mload32bit_bound_msize.py | 5 +- .../test_mstore_bounds2a.py | 5 +- .../stMemoryStressTest/test_return_bounds.py | 5 +- .../stMemoryStressTest/test_sstore_bounds.py | 5 +- .../ported_static/stMemoryTest/test_buffer.py | 5 +- .../stMemoryTest/test_buffer_src_offset.py | 5 +- tests/ported_static/stMemoryTest/test_oog.py | 5 +- .../stPreCompiledContracts/test_modexp.py | 5 +- .../test_modexp_tests.py | 5 +- .../test_precomps_eip2929_cancun.py | 5 +- .../test_call_ecrecover_overflow.py | 5 +- .../test_ecrecover_weird_v.py | 5 +- .../test_modexp_0_0_0_20500.py | 5 +- .../test_call1_mb1024_calldepth.py | 5 +- .../test_call20_kbytes_contract50_1.py | 5 +- .../test_call20_kbytes_contract50_2.py | 5 +- .../test_call20_kbytes_contract50_3.py | 5 +- .../test_call50000.py | 5 +- .../test_call50000_ecrec.py | 5 +- .../test_call50000_identity.py | 5 +- .../test_call50000_identity2.py | 5 +- .../test_call50000_rip160.py | 5 +- .../test_call50000_sha256.py | 5 +- .../test_callcode50000.py | 5 +- .../test_create1000.py | 5 +- .../test_create1000_shnghai.py | 5 +- .../test_return50000.py | 5 +- .../test_return50000_2.py | 5 +- .../test_refund_call_to_suicide_no_storage.py | 5 +- .../test_refund_call_to_suicide_storage.py | 5 +- .../test_refund_call_to_suicide_twice.py | 5 +- .../test_refund_suicide50procent_cap.py | 5 +- .../test_modexp_modsize0_returndatasize.py | 5 +- .../test_too_long_return_data_copy.py | 5 +- .../stRevertTest/test_cost_revert.py | 5 +- ...t_revert_depth_create_address_collision.py | 5 +- .../test_revert_depth_create_oog.py | 5 +- .../stRevertTest/test_revert_opcode.py | 5 +- .../stRevertTest/test_revert_opcode_calls.py | 5 +- .../stRevertTest/test_revert_opcode_create.py | 5 +- .../test_revert_opcode_direct_call.py | 5 +- ...pcode_in_calls_on_non_empty_return_data.py | 5 +- .../test_revert_opcode_multiple_sub_calls.py | 5 +- .../stRevertTest/test_revert_opcode_return.py | 5 +- ...evert_precompiled_touch_exact_oog_paris.py | 5 +- .../test_revert_precompiled_touch_paris.py | 5 +- ..._revert_precompiled_touch_storage_paris.py | 5 +- .../test_revert_sub_call_storage_oog.py | 3 +- .../test_revert_sub_call_storage_oog2.py | 3 +- .../stSStoreTest/test_sstore_0to0.py | 5 +- .../stSStoreTest/test_sstore_0to0to0.py | 5 +- .../stSStoreTest/test_sstore_0to0to_x.py | 5 +- .../stSStoreTest/test_sstore_0to_x.py | 5 +- .../stSStoreTest/test_sstore_0to_xto0.py | 5 +- .../stSStoreTest/test_sstore_0to_xto0to_x.py | 5 +- .../stSStoreTest/test_sstore_0to_xto_x.py | 5 +- .../stSStoreTest/test_sstore_0to_xto_y.py | 5 +- ..._change_from_external_call_in_init_code.py | 5 +- .../stSStoreTest/test_sstore_gas_left.py | 5 +- .../stSStoreTest/test_sstore_xto0.py | 5 +- .../stSStoreTest/test_sstore_xto0to0.py | 5 +- .../stSStoreTest/test_sstore_xto0to_x.py | 5 +- .../stSStoreTest/test_sstore_xto0to_xto0.py | 5 +- .../stSStoreTest/test_sstore_xto0to_y.py | 5 +- .../stSStoreTest/test_sstore_xto_x.py | 5 +- .../stSStoreTest/test_sstore_xto_xto0.py | 5 +- .../stSStoreTest/test_sstore_xto_xto_x.py | 5 +- .../stSStoreTest/test_sstore_xto_xto_y.py | 5 +- .../stSStoreTest/test_sstore_xto_y.py | 5 +- .../stSStoreTest/test_sstore_xto_yto0.py | 5 +- .../stSStoreTest/test_sstore_xto_yto_x.py | 5 +- .../stSStoreTest/test_sstore_xto_yto_y.py | 5 +- .../stSStoreTest/test_sstore_xto_yto_z.py | 5 +- .../test_self_balance_call_types.py | 5 +- .../stSpecialTest/test_eoa_empty_paris.py | 5 +- .../stStackTests/test_underflow_test.py | 5 +- .../stStaticCall/test_static_ab_acalls0.py | 5 +- .../stStaticCall/test_static_ab_acalls1.py | 5 +- .../stStaticCall/test_static_ab_acalls2.py | 5 +- .../stStaticCall/test_static_ab_acalls3.py | 5 +- .../test_static_ab_acalls_suicide0.py | 5 +- .../stStaticCall/test_static_call10.py | 5 +- .../test_static_call1024_balance_too_low.py | 5 +- .../test_static_call1024_balance_too_low2.py | 5 +- .../stStaticCall/test_static_call1024_oog.py | 5 +- .../test_static_call1024_pre_calls.py | 5 +- .../test_static_call1024_pre_calls2.py | 5 +- .../test_static_call1024_pre_calls3.py | 5 +- .../test_static_call1_mb1024_calldepth.py | 5 +- .../stStaticCall/test_static_call50000.py | 5 +- .../test_static_call50000_ecrec.py | 5 +- .../test_static_call50000_identity.py | 5 +- .../test_static_call50000_identity2.py | 5 +- ...test_static_call50000bytes_contract50_1.py | 5 +- ...test_static_call50000bytes_contract50_2.py | 5 +- ...test_static_call50000bytes_contract50_3.py | 5 +- ...e_consume_more_gas_then_transaction_has.py | 5 +- ...more_gas_on_depth2_then_transaction_has.py | 5 +- .../stStaticCall/test_static_call_basic.py | 5 +- ...ract_to_create_contract_and_call_it_oog.py | 5 +- ...ic_call_contract_to_create_contract_oog.py | 5 +- ...ntract_to_create_contract_oog_bonus_gas.py | 5 +- .../stStaticCall/test_static_call_create.py | 5 +- .../stStaticCall/test_static_call_create2.py | 5 +- .../test_static_call_ecrecover0_0input.py | 5 +- ...static_call_with_high_value_and_gas_oog.py | 5 +- .../stStaticCall/test_static_callcall_00.py | 5 +- .../test_static_callcall_00_ooge.py | 5 +- .../test_static_callcall_00_ooge_1.py | 5 +- .../test_static_callcallcall_000.py | 5 +- .../test_static_callcallcall_000_ooge.py | 5 +- .../test_static_callcallcallcode_001.py | 5 +- .../test_static_callcallcallcode_001_2.py | 5 +- .../test_static_callcallcode_01_2.py | 5 +- .../test_static_callcallcodecall_010_2.py | 5 +- .../test_static_callcallcodecallcode_011_2.py | 5 +- ..._static_callcallcodecallcode_011_ooge_2.py | 5 +- ...c_callcallcodecallcode_011_oogm_before2.py | 5 +- ...est_static_callcodecall_10_suicide_end2.py | 5 +- ...tatic_callcodecallcall_100_oogm_after_3.py | 5 +- ...c_callcodecallcallcode_101_oogm_after_3.py | 5 +- ...c_callcodecallcodecall_110_suicide_end2.py | 5 +- .../stStaticCall/test_static_check_opcodes.py | 5 +- .../test_static_check_opcodes2.py | 5 +- .../test_static_check_opcodes3.py | 5 +- .../test_static_check_opcodes4.py | 5 +- .../test_static_check_opcodes5.py | 5 +- ..._ask_more_gas_then_transaction_provided.py | 5 +- ..._that_ask_fore_gas_then_trabsaction_has.py | 5 +- .../test_static_loop_calls_then_revert.py | 5 +- ...est_static_refund_call_to_suicide_twice.py | 5 +- .../test_static_return_bounds_oog.py | 5 +- ...ame_registrator_zeor_size_mem_expansion.py | 5 +- ...e_to_name_registrator_zero_mem_expanion.py | 5 +- .../test_double_selfdestruct_test.py | 5 +- .../test_double_selfdestruct_touch_paris.py | 5 +- .../test_multi_selfdestruct.py | 5 +- .../stTransactionTest/test_no_src_account.py | 5 +- .../test_no_src_account1559.py | 5 +- .../test_no_src_account_create.py | 5 +- .../test_no_src_account_create1559.py | 5 +- .../test_opcodes_transaction_init.py | 5 +- .../test_overflow_gas_require2.py | 3 +- ...ides_and_internal_call_suicides_success.py | 5 +- ...ned_construction_not_enough_gas_partial.py | 3 +- .../test_wallet_construction_oog.py | 3 +- .../stZeroKnowledge/test_pairing_test.py | 5 +- .../vmArithmeticTest/test_add.py | 5 +- .../vmArithmeticTest/test_addmod.py | 5 +- .../vmArithmeticTest/test_div.py | 5 +- .../vmArithmeticTest/test_exp.py | 5 +- .../vmArithmeticTest/test_mod.py | 5 +- .../vmArithmeticTest/test_mul.py | 5 +- .../vmArithmeticTest/test_mulmod.py | 5 +- .../vmArithmeticTest/test_sdiv.py | 5 +- .../vmArithmeticTest/test_signextend.py | 5 +- .../vmArithmeticTest/test_smod.py | 5 +- .../vmArithmeticTest/test_sub.py | 5 +- .../vmBitwiseLogicOperation/test_and.py | 5 +- .../vmBitwiseLogicOperation/test_byte.py | 5 +- .../vmBitwiseLogicOperation/test_eq.py | 5 +- .../vmBitwiseLogicOperation/test_gt.py | 5 +- .../vmBitwiseLogicOperation/test_iszero.py | 5 +- .../vmBitwiseLogicOperation/test_lt.py | 5 +- .../vmBitwiseLogicOperation/test_not.py | 5 +- .../vmBitwiseLogicOperation/test_or.py | 5 +- .../vmBitwiseLogicOperation/test_sgt.py | 5 +- .../vmBitwiseLogicOperation/test_slt.py | 5 +- .../vmBitwiseLogicOperation/test_xor.py | 5 +- .../vmIOandFlowOperations/test_codecopy.py | 5 +- .../vmIOandFlowOperations/test_gas.py | 5 +- .../vmIOandFlowOperations/test_jump.py | 5 +- .../test_jump_to_push.py | 5 +- .../vmIOandFlowOperations/test_jumpi.py | 5 +- .../test_loops_conditionals.py | 5 +- .../vmIOandFlowOperations/test_mload.py | 5 +- .../vmIOandFlowOperations/test_msize.py | 5 +- .../vmIOandFlowOperations/test_mstore.py | 5 +- .../vmIOandFlowOperations/test_mstore8.py | 5 +- .../vmIOandFlowOperations/test_pc.py | 5 +- .../vmIOandFlowOperations/test_pop.py | 5 +- .../vmIOandFlowOperations/test_return.py | 5 +- .../test_sstore_sload.py | 5 +- tests/ported_static/vmLogTest/test_log0.py | 5 +- tests/ported_static/vmLogTest/test_log1.py | 5 +- tests/ported_static/vmLogTest/test_log2.py | 5 +- tests/ported_static/vmLogTest/test_log3.py | 5 +- tests/ported_static/vmLogTest/test_log4.py | 5 +- .../ported_static/vmTests/test_block_info.py | 5 +- tests/ported_static/vmTests/test_env_info.py | 5 +- tests/ported_static/vmTests/test_random.py | 5 +- tests/ported_static/vmTests/test_sha3.py | 5 +- tests/ported_static/vmTests/test_suicide.py | 5 +- 371 files changed, 1218 insertions(+), 7395 deletions(-) delete mode 100644 packages/testing/src/execution_testing/cli/fillerconvert/fillerconvert.py delete mode 100644 packages/testing/src/execution_testing/cli/fillerconvert/verify_filled.py delete mode 100644 packages/testing/src/execution_testing/cli/modify_static_test_gas_limits.py delete mode 100644 packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/static_filler.py delete mode 100644 packages/testing/src/execution_testing/cli/pytest_commands/plugins/solc/__init__.py delete mode 100644 packages/testing/src/execution_testing/cli/pytest_commands/plugins/solc/solc.py delete mode 100644 packages/testing/src/execution_testing/specs/base_static.py delete mode 100644 packages/testing/src/execution_testing/specs/static_state/__init__.py delete mode 100644 packages/testing/src/execution_testing/specs/static_state/account.py delete mode 100644 packages/testing/src/execution_testing/specs/static_state/common/__init__.py delete mode 100644 packages/testing/src/execution_testing/specs/static_state/common/common.py delete mode 100644 packages/testing/src/execution_testing/specs/static_state/common/compile_yul.py delete mode 100644 packages/testing/src/execution_testing/specs/static_state/common/tags.py delete mode 100644 packages/testing/src/execution_testing/specs/static_state/environment.py delete mode 100644 packages/testing/src/execution_testing/specs/static_state/expect_section.py delete mode 100644 packages/testing/src/execution_testing/specs/static_state/general_transaction.py delete mode 100644 packages/testing/src/execution_testing/specs/static_state/state_static.py delete mode 100644 packages/testing/src/execution_testing/tools/tools_code/yul.py delete mode 100644 scripts/filler_to_python/__init__.py delete mode 100644 scripts/filler_to_python/__main__.py delete mode 100644 scripts/filler_to_python/analyzer.py delete mode 100644 scripts/filler_to_python/ir.py delete mode 100644 scripts/filler_to_python/render.py delete mode 100644 scripts/filler_to_python/templates/state_test.py.j2 delete mode 100755 scripts/verify_dynamic_addresses.sh create mode 100644 tests/ported_static/__init__.py create mode 100644 tests/ported_static/post_state_resolution.py diff --git a/packages/testing/pyproject.toml b/packages/testing/pyproject.toml index bca724ebe52..f8d980872ad 100644 --- a/packages/testing/pyproject.toml +++ b/packages/testing/pyproject.toml @@ -97,11 +97,9 @@ order_fixtures = "execution_testing.cli.order_fixtures:order_fixtures" evm_bytes = "execution_testing.cli.evm_bytes:evm_bytes" hasher = "execution_testing.cli.hasher:main" eest = "execution_testing.cli.eest.cli:eest" -fillerconvert = "execution_testing.cli.fillerconvert.fillerconvert:main" groupstats = "execution_testing.cli.show_pre_alloc_group_stats:main" extract_config = "execution_testing.cli.extract_config:extract_config" compare_fixtures = "execution_testing.cli.compare_fixtures:main" -modify_static_test_gas_limits = "execution_testing.cli.modify_static_test_gas_limits:main" benchmark_parser = "execution_testing.cli.benchmark_parser:main" [tool.setuptools.packages.find] diff --git a/packages/testing/src/execution_testing/cli/fillerconvert/fillerconvert.py b/packages/testing/src/execution_testing/cli/fillerconvert/fillerconvert.py deleted file mode 100644 index f0ac78ff6e5..00000000000 --- a/packages/testing/src/execution_testing/cli/fillerconvert/fillerconvert.py +++ /dev/null @@ -1,61 +0,0 @@ -"""Simple CLI tool that reads filler files in the `ethereum/tests` format.""" - -import argparse -from glob import glob -from pathlib import Path - -from .verify_filled import verify_refilled - - -def main() -> None: - """Run the main function.""" - parser = argparse.ArgumentParser(description="Filler parser.") - - parser.add_argument( - "mode", - type=str, - help="The type of filler we are trying to parse: blockchain/state.", - ) - parser.add_argument( - "folder_path", - type=Path, - help="The path to the JSON/YML filler directory", - ) - parser.add_argument( - "legacy_path", type=Path, help="The path to the legacy tests directory" - ) - - args = parser.parse_args() - args.folder_path = Path(str(args.folder_path).split("=")[-1]) - args.mode = str(args.mode).split("=")[-1] - - print("Scanning: " + str(args.folder_path)) - files = glob( - str(args.folder_path / "**" / "*.json"), recursive=True - ) + glob(str(args.folder_path / "**" / "*.yml"), recursive=True) - - if args.mode == "blockchain": - raise NotImplementedError("Blockchain filler not implemented yet.") - - if args.mode == "verify": - verified_vectors = 0 - for file in files: - print("Verify: " + file) - refilled_file = file - relative_file = file.removeprefix(str(args.folder_path))[1:] - original_file = ( - args.legacy_path / "GeneralStateTests" / relative_file - ) - verified_vectors += verify_refilled( - Path(refilled_file), original_file - ) - print(f"Total vectors verified: {verified_vectors}") - - # Solidity skipped tests - # or file.endswith("stExample/solidityExampleFiller.yml") - # or file.endswith("vmPerformance/performanceTesterFiller.yml") - # or file.endswith("vmPerformance/loopExpFiller.yml") - # or file.endswith("vmPerformance/loopMulFiller.yml") - # or - # file.endswith("stRevertTest/RevertRemoteSubCallStorageOOGFiller.yml") - # or file.endswith("stSolidityTest/SelfDestructFiller.yml") diff --git a/packages/testing/src/execution_testing/cli/fillerconvert/verify_filled.py b/packages/testing/src/execution_testing/cli/fillerconvert/verify_filled.py deleted file mode 100644 index 7e0555d3a9a..00000000000 --- a/packages/testing/src/execution_testing/cli/fillerconvert/verify_filled.py +++ /dev/null @@ -1,100 +0,0 @@ -"""Verify refilled test vs original generated test.""" - -import re -from pathlib import Path - -from pydantic import BaseModel, RootModel - - -# Define only relevant data we need to read from the files -class Indexes(BaseModel): - """Post Section Indexes.""" - - data: int - gas: int - value: int - - -class PostRecord(BaseModel): - """Post results record.""" - - hash: str - indexes: Indexes - - -class StateTest(BaseModel): - """StateTest in filled file.""" - - post: dict[str, list[PostRecord]] - - -class FilledStateTest(RootModel[dict[str, StateTest]]): - """State Test Wrapper.""" - - -def verify_refilled(refilled: Path, original: Path) -> int: - """ - Verify the post hash of the refilled test against the original. - - Extract the d,g,v from the refilled test name. Find the post record for - this d,g,v and the fork of the refilled test. Compare the post hash. - """ - verified_vectors = 0 - json_str = refilled.read_text(encoding="utf-8") - refilled_test_wrapper = FilledStateTest.model_validate_json(json_str) - - json_str = original.read_text(encoding="utf-8") - original_test_wrapper = FilledStateTest.model_validate_json(json_str) - - # Each original test has only 1 test with many posts for each fork and many - # txs - original_test_name, test_original = list( - original_test_wrapper.root.items() - )[0] - - for ( - refilled_test_name, - refilled_test, - ) in refilled_test_wrapper.root.items(): - # Each refilled test has only 1 post for 1 fork and 1 transaction - refilled_fork, refilled_result = list(refilled_test.post.items())[0] - pattern = r"v=(\d+)-g=(\d+)-d=(\d+)" - match = re.search(pattern, refilled_test_name) - if match: - v, g, d = match.groups() - v, g, d = int(v), int(g), int(d) - - found = False - original_result = test_original.post[refilled_fork] - for res in original_result: - if ( - res.indexes.data == d - and res.indexes.gas == g - and res.indexes.value == v - ): - print(f"check: {refilled_fork}, d:{d}, g:{g}, v:{v}") - if res.hash != refilled_result[0].hash: - raise Exception( - "\nRefilled test post hash mismatch: \n" - f"test_name: {refilled_test_name}\n" - f"original_name: {original}\n" - f"refilled_hash: {refilled_result[0].hash}\n" - f"original_hash: {res.hash} " - f"f: {refilled_fork}, d: {d}, g: {g}, v: {v}" - ) - found = True - verified_vectors += 1 - break - - if not found: - raise Exception( - "\nRefilled test not found in original: \n" - f"test_name: {refilled_test_name}\n" - f"original_name: {original}\n" - ) - else: - raise Exception( - "Could not regex match d.g.v indexes from refilled test name!" - ) - - return verified_vectors diff --git a/packages/testing/src/execution_testing/cli/modify_static_test_gas_limits.py b/packages/testing/src/execution_testing/cli/modify_static_test_gas_limits.py deleted file mode 100644 index 70b0d1c1ec8..00000000000 --- a/packages/testing/src/execution_testing/cli/modify_static_test_gas_limits.py +++ /dev/null @@ -1,272 +0,0 @@ -""" -Command to scan and overwrite the static tests' gas limits to new optimized -value given in the input file. -""" - -import json -import re -from pathlib import Path -from typing import Dict, List, Set - -import click -import yaml - -from execution_testing.base_types import ( - EthereumTestRootModel, - HexNumber, - ZeroPaddedHexNumber, -) -from execution_testing.cli.pytest_commands.plugins.filler.static_filler import ( # noqa: E501 - NoIntResolver, -) -from execution_testing.specs import StateStaticTest - - -class GasLimitDict(EthereumTestRootModel): - """Formatted JSON file with new gas limits in each test.""" - - root: Dict[str, int | None] - - def unique_files(self) -> Set[Path]: - """Return a list of unique test files.""" - files = set() - for test in self.root: - filename, _ = test.split("::") - files.add(Path(filename)) - return files - - def get_tests_by_file_path(self, file: Path | str) -> Set[str]: - """Return a list of all tests that belong to a given file path.""" - tests = set() - for test in self.root: - current_file, _ = test.split("::") - if current_file == str(file): - tests.add(test) - return tests - - -class StaticTestFile(EthereumTestRootModel): - """A static test file.""" - - root: Dict[str, StateStaticTest] - - -def _check_fixtures( - *, - input_path: Path, - max_gas_limit: int | None, - dry_run: bool, - verbose: bool, -) -> None: - """ - Perform checks on fixtures in the specified directory. - """ - # Load the test dictionary from the input JSON file - test_dict = GasLimitDict.model_validate_json(input_path.read_text()) - - # Iterate through each unique test file that needs modification - for test_file in test_dict.unique_files(): - tests = test_dict.get_tests_by_file_path(test_file) - test_file_contents = test_file.read_text() - - # Parse the test file based on its format (YAML or JSON) - if test_file.suffix == ".yml" or test_file.suffix == ".yaml": - loaded_yaml = yaml.load( - test_file.read_text(), Loader=NoIntResolver - ) - try: - parsed_test_file = StaticTestFile.model_validate(loaded_yaml) - except Exception as e: - yaml_dump = json.dumps(loaded_yaml, indent=2) - raise Exception( - f"Unable to parse file {test_file}: {yaml_dump}" - ) from e - else: - parsed_test_file = StaticTestFile.model_validate_json( - test_file_contents - ) - - # Validate that the file contains exactly one test - assert len(parsed_test_file.root) == 1, ( - f"File {test_file} contains more than one test." - ) - _, parsed_test = parsed_test_file.root.popitem() - - # Skip files with multiple gas limit values - if len(parsed_test.transaction.gas_limit) != 1: - if dry_run or verbose: - print( - f"Test file {test_file} contains more than one test " - "(after parsing), skipping." - ) - continue - - # Get the current gas limit and check if modification is needed - current_gas_limit = int(parsed_test.transaction.gas_limit[0]) - if max_gas_limit is not None and current_gas_limit <= max_gas_limit: - # Nothing to do, finished - for test in tests: - test_dict.root.pop(test) - continue - - # Collect valid gas values for this test file - gas_values: List[int] = [] - for gas_value in [test_dict.root[test] for test in tests]: - if gas_value is None: - if dry_run or verbose: - print( - f"Test file {test_file} contains at least one test " - "that cannot be updated, skipping." - ) - continue - else: - gas_values.append(gas_value) - - # Calculate the new gas limit (rounded up to nearest 100,000) - new_gas_limit = max(gas_values) - modified_new_gas_limit = ((new_gas_limit // 100000) + 1) * 100000 - if verbose: - print( - f"Changing exact new gas limit ({new_gas_limit}) to " - f"rounded ({modified_new_gas_limit})" - ) - new_gas_limit = modified_new_gas_limit - - # Check if the new gas limit exceeds the maximum allowed - if max_gas_limit is not None and new_gas_limit > max_gas_limit: - if dry_run or verbose: - print( - f"New gas limit ({new_gas_limit}) " - f"exceeds max ({max_gas_limit})" - ) - continue - - if dry_run or verbose: - print( - f"Test file {test_file} requires modification " - f"({new_gas_limit})" - ) - - # Find the appropriate pattern to replace the current gas limit - potential_types = [int, HexNumber, ZeroPaddedHexNumber] - substitute_pattern = None - substitute_string = None - - attempted_patterns = [] - - for current_type in potential_types: - potential_substitute_pattern = ( - rf"\b{current_type(current_gas_limit)}\b" - ) - potential_substitute_string = f"{current_type(new_gas_limit)}" - if ( - re.search( - potential_substitute_pattern, - test_file_contents, - flags=re.RegexFlag.MULTILINE, - ) - is not None - ): - substitute_pattern = potential_substitute_pattern - substitute_string = potential_substitute_string - break - - attempted_patterns.append(potential_substitute_pattern) - - # Validate that a replacement pattern was found - assert substitute_pattern is not None, ( - f"Current gas limit ({attempted_patterns}) " - f"not found in {test_file}" - ) - assert substitute_string is not None - - # Perform the replacement in the test file content - new_test_file_contents = re.sub( - substitute_pattern, substitute_string, test_file_contents - ) - - assert test_file_contents != new_test_file_contents, ( - "Could not modify test file" - ) - - # Skip writing changes if this is a dry run - if dry_run: - continue - - # Write the modified content back to the test file - test_file.write_text(new_test_file_contents) - for test in tests: - test_dict.root.pop(test) - - if dry_run: - return - - # Write changes to the input file - input_path.write_text(test_dict.model_dump_json(indent=2)) - - -MAX_GAS_LIMIT = 16_777_216 - - -@click.command() -@click.option( - "--input", - "-i", - "input_str", - type=click.Path( - exists=True, file_okay=True, dir_okay=False, readable=True - ), - required=True, - help=( - "The input json file or directory containing json listing the new " - "gas limits for the static test files." - ), -) -@click.option( - "--max-gas-limit", - default=MAX_GAS_LIMIT, - expose_value=True, - help=( - "Gas limit that triggers a test modification, and also the maximum " - "value that a test should have after modification." - ), -) -@click.option( - "--dry-run", - "-d", - "dry_run", - is_flag=True, - default=False, - expose_value=True, - help="Don't modify any files, simply print operations to be performed.", -) -@click.option( - "--verbose", - "-v", - "verbose", - is_flag=True, - default=False, - expose_value=True, - help="Print extra information.", -) -def main( - input_str: str, max_gas_limit: int | None, dry_run: bool, verbose: bool -) -> None: - """ - Perform checks on fixtures in the specified directory. - """ - input_path = Path(input_str) - if not dry_run: - # Always dry-run first before actually modifying - _check_fixtures( - input_path=input_path, - max_gas_limit=max_gas_limit, - dry_run=True, - verbose=False, - ) - _check_fixtures( - input_path=input_path, - max_gas_limit=max_gas_limit, - dry_run=dry_run, - verbose=verbose, - ) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/execute.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/execute.py index 679b05405d0..b5a8830e4fd 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/execute.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/execute.py @@ -547,8 +547,5 @@ def pytest_collection_modifyitems( ) ) - if "yul" in item.fixturenames: # type: ignore - item.add_marker(pytest.mark.yul_test) - for i in reversed(items_for_removal): items.pop(i) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py index 37184d04e91..7db3702718c 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py @@ -1469,20 +1469,8 @@ def fixture_collector( Return configured fixture collector instance used for all tests in one test module. """ - # Dynamically load the 'static_filler' and 'solc' plugins if needed - if request.config.getoption("fill_static_tests_enabled"): - request.config.pluginmanager.import_plugin( - "execution_testing.cli.pytest_commands.plugins.filler.static_filler" - ) - request.config.pluginmanager.import_plugin( - "execution_testing.cli.pytest_commands.plugins.solc.solc" - ) - fixture_collector = FixtureCollector( output_dir=fixture_output.directory, - fill_static_tests=request.config.getoption( - "fill_static_tests_enabled" - ), single_fixture_per_file=fixture_output.single_fixture_per_file, filler_path=filler_path, base_dump_dir=base_dump_dir, @@ -1614,9 +1602,6 @@ def base_test_parametrizer_func( else: fixture_format = request.param assert issubclass(fixture_format, BaseFixture) - if fork is None: - assert hasattr(request.node, "fork") - fork = request.node.fork class BaseTestWrapper(cls): # type: ignore __is_base_test_wrapper__ = True @@ -1843,9 +1828,6 @@ def pytest_generate_tests(metafunc: pytest.Metafunc) -> None: """ Pytest hook used to dynamically generate test cases for each fixture format a given test spec supports. - - NOTE: The static test filler does NOT use this hook. See - FillerFile.collect() in ./static_filler.py for more details. """ session: FillingSession = metafunc.config.filling_session # type: ignore[attr-defined] markers = list(metafunc.definition.iter_markers()) @@ -1951,8 +1933,6 @@ def pytest_collection_modifyitems( if marker.name == "fill": for mark in marker.args: item.add_marker(mark) - if "yul" in item.fixturenames: # type: ignore - item.add_marker(pytest.mark.yul_test) # Update test ID for state tests that use a transition fork if fork in get_transition_forks(): diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/static_filler.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/static_filler.py deleted file mode 100644 index 08b2195410f..00000000000 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/static_filler.py +++ /dev/null @@ -1,466 +0,0 @@ -""" -Static filler pytest plugin that reads test cases from static files and fills -them into test fixtures. -""" - -import inspect -import itertools -import json -import warnings -from pathlib import Path -from typing import Any, Callable, Dict, Generator, List, Self, Tuple, Type - -import pytest -import yaml -from _pytest.fixtures import TopRequest -from _pytest.mark import ParameterSet -from _pytest.python import Module - -from execution_testing.fixtures import BaseFixture, LabeledFixtureFormat -from execution_testing.forks import Fork, get_closest_fork -from execution_testing.specs import BaseStaticTest, BaseTest -from execution_testing.tools.tools_code.yul import Yul - -from ..forks.forks import ValidityMarker, fork_markers -from ..shared.helpers import labeled_format_parameter_set - - -def get_test_id_from_arg_names_and_values( - arg_names: List[str], arg_values: List[Any] | Tuple[Any, ...] -) -> str: - """Get the test id from argument names and values.""" - return "-".join( - [ - f"{arg_name}={arg_value}" - for arg_name, arg_value in zip(arg_names, arg_values, strict=True) - ] - ) - - -def get_argument_names_and_values_from_parametrize_mark( - mark: pytest.Mark, -) -> Tuple[List[str], List[ParameterSet]]: - """Get the argument names and values from a parametrize mark.""" - if mark.name != "parametrize": - raise Exception("Mark is not a parametrize mark") - kwargs_dict = dict(mark.kwargs) - ids: Callable | List[str] | None = ( - kwargs_dict.pop("ids") if "ids" in kwargs_dict else None - ) - marks: List[pytest.Mark] = ( - kwargs_dict.pop("marks") if "marks" in kwargs_dict else [] - ) - if kwargs_dict: - raise Exception("Mark has kwargs which is not supported") - args = mark.args - if not isinstance(args, tuple): - raise Exception("Args is not a tuple") - if len(args) != 2: - raise Exception("Args does not have 2 elements") - arg_names = args[0] if isinstance(args[0], list) else args[0].split(",") - arg_values = [] - for arg_index, arg_value in enumerate(args[1]): - if not isinstance(arg_value, ParameterSet): - original_arg_value = arg_value - if not isinstance(arg_value, tuple) and not isinstance( - arg_value, list - ): - arg_value = (arg_value,) - test_id: str = get_test_id_from_arg_names_and_values( - arg_names, arg_value - ) - if ids: - if callable(ids): - test_id = ids(original_arg_value) - else: - test_id = ids[arg_index] - arg_values.append(ParameterSet(arg_value, marks, id=test_id)) - else: - arg_values.append(arg_value) - return arg_names, arg_values - - -def get_all_combinations_from_parametrize_marks( - parametrize_marks: List[pytest.Mark], -) -> Tuple[List[str], List[ParameterSet]]: - """Get all combinations of arguments from multiple parametrize marks.""" - assert parametrize_marks, "No parametrize marks found" - list_of_values: List[List[ParameterSet]] = [] - all_argument_names = [] - for mark in parametrize_marks: - arg_names, arg_values = ( - get_argument_names_and_values_from_parametrize_mark(mark) - ) - list_of_values.append(arg_values) - all_argument_names.extend(arg_names) - all_value_combinations: List[ParameterSet] = [] - # use itertools to get all combinations - test_ids = set() - for combination in itertools.product(*list_of_values): - values: List[Any] = [] - marks: List[pytest.Mark | pytest.MarkDecorator] = [] - for param_set in combination: - values.extend(param_set.values) - marks.extend(param_set.marks) - test_id = "-".join([param.id or "" for param in combination]) # type: ignore[misc] - if test_id in test_ids: - current_int = 2 - while f"{test_id}-{current_int}" in test_ids: - current_int += 1 - test_id = f"{test_id}-{current_int}" - all_value_combinations.append( - ParameterSet( - values=values, - marks=marks, - id=test_id, - ) - ) - test_ids.add(test_id) - - return all_argument_names, all_value_combinations - - -def pytest_collect_file( - file_path: Path, parent: Module -) -> pytest.Collector | None: - """ - Pytest hook that collects test cases from static files and fills them into - test fixtures. - """ - fill_static_tests_enabled = parent.config.getoption( - "fill_static_tests_enabled" - ) - if not fill_static_tests_enabled: - return None - if not BaseStaticTest.formats: - # No formats registered, so no need to collect any files. - return None - if file_path.suffix in (".json", ".yml", ".yaml"): - init_file = file_path.parent / "__init__.py" - module = Module.from_parent( - parent=parent, - path=init_file, - nodeid=str(init_file), - ) - return FillerFile.from_parent(module, path=file_path) - return None - - -class NoIntResolver(yaml.SafeLoader): - """Class that tells yaml to not resolve int values.""" - - pass - - -# Remove the implicit resolver for integers -# Because yaml treat unquoted numbers 000001000 as oct numbers -# Treat all numbers as str instead -for ch in list(NoIntResolver.yaml_implicit_resolvers): - resolvers = NoIntResolver.yaml_implicit_resolvers[ch] - NoIntResolver.yaml_implicit_resolvers[ch] = [ - (tag, regexp) - for tag, regexp in resolvers - if tag != "tag:yaml.org,2002:int" - ] - - -class FillerFile(pytest.File): - """ - Filler file that reads test cases from static files and fills them into - test fixtures. - """ - - def collect(self: "FillerFile") -> Generator["FillerTestItem", None, None]: - """Collect test cases from a single static file.""" - if not self.path.stem.endswith("Filler"): - return - with open(self.path, "r") as file: - try: - loaded_file = ( - json.load(file) - if self.path.suffix == ".json" - else yaml.load(file, Loader=NoIntResolver) - ) - for key in loaded_file: - filler = BaseStaticTest.model_validate(loaded_file[key]) - - func = filler.fill_function() - - function_marks: List[pytest.Mark] = [] - if hasattr(func, "pytestmark"): - function_marks = func.pytestmark[:] - parametrize_marks: List[pytest.Mark] = [ - mark - for mark in function_marks - if mark.name == "parametrize" - ] - - func_parameters = inspect.signature(func).parameters - - fixture_formats: List[ - Type[BaseFixture] | LabeledFixtureFormat - ] = [] - spec_parameter_name = "" - for test_type in BaseTest.spec_types.values(): - if ( - test_type.pytest_parameter_name() - in func_parameters - ): - assert not spec_parameter_name, ( - "Multiple spec parameters found" - ) - spec_parameter_name = ( - test_type.pytest_parameter_name() - ) - session = self.config.filling_session # type: ignore[attr-defined] - supported = test_type.supported_fixture_formats - fixture_formats.extend( - fmt - for fmt in supported - if session.should_generate_format(fmt) - ) - - test_fork_set = ( - ValidityMarker.get_test_fork_set_from_markers( - iter(function_marks) - ) - ) - if not test_fork_set: - pytest.fail( - "The test function's " - f"'{key}' fork validity markers generate " - "an empty fork range. Please check the arguments " - "to its markers: @pytest.mark.valid_from and " - "@pytest.mark.valid_until." - ) - intersection_set = ( - test_fork_set & self.config.selected_fork_set # type: ignore - ) - - extra_function_marks: List[pytest.Mark] = [ - mark - for mark in function_marks - if mark.name != "parametrize" - and not ValidityMarker.is_validity_or_filter_marker( - mark.name - ) - ] - - for format_with_or_without_label in fixture_formats: - fixture_format_parameter_set = ( - labeled_format_parameter_set( - format_with_or_without_label - ) - ) - fixture_format = ( - format_with_or_without_label.format - if isinstance( - format_with_or_without_label, - LabeledFixtureFormat, - ) - else format_with_or_without_label - ) - for fork in sorted(intersection_set): - params: Dict[str, Any] = { - spec_parameter_name: fixture_format - } - fixturenames = [ - spec_parameter_name, - ] - marks: List[pytest.Mark | pytest.MarkDecorator] = [ - mark - for mark in fixture_format_parameter_set.marks - if mark.name != "parametrize" - ] - ps_id = fixture_format_parameter_set.id - test_id = f"fork_{fork.name()}-{ps_id}" - if "fork" in func_parameters: - params["fork"] = fork - if "pre" in func_parameters: - fixturenames.append("pre") - if "request" in func_parameters: - fixturenames.append("request") - - if parametrize_marks: - parameter_names, parameter_set_list = ( - get_all_combinations_from_parametrize_marks( - parametrize_marks - ) - ) - for parameter_set in parameter_set_list: - # Copy and extend the params with the - # parameter set - case_marks = ( - marks[:] - + [ - mark - for mark in parameter_set.marks - if mark.name != "parametrize" - ] - + extra_function_marks - + fork_markers(fork=fork) - ) - case_params = params.copy() | dict( - zip( - parameter_names, - parameter_set.values, - strict=True, - ) - ) - - yield FillerTestItem.from_parent( - self, - original_name=key, - func=func, - params=case_params, - fixturenames=fixturenames, - name=f"{key}[{test_id}-{parameter_set.id}]", - fork=fork, - fixture_format=fixture_format, - marks=case_marks, - ) - else: - case_marks = marks[:] + fork_markers(fork=fork) - yield FillerTestItem.from_parent( - self, - original_name=key, - func=func, - params=params, - fixturenames=fixturenames, - name=f"{key}[{test_id}]", - fork=fork, - fixture_format=fixture_format, - marks=case_marks, - ) - except Exception as e: - pytest.fail(f"Error loading file {self.path} as a test: {e}") - warnings.warn( - f"Error loading file {self.path} as a test: {e}", - stacklevel=1, - ) - return - - -class FillerTestItem(pytest.Item): - """Filler test item produced from a single test from a static file.""" - - originalname: str - func: Callable - params: Dict[str, Any] - fixturenames: List[str] - github_url: str = "" - fork: Fork - fixture_format: Type[BaseFixture] - - def __init__( - self, - *args: Any, - original_name: str, - func: Callable, - params: Dict[str, Any], - fixturenames: List[str], - fork: Fork, - fixture_format: Type[BaseFixture], - marks: List[pytest.Mark], - **kwargs: Any, - ) -> None: - """Initialize the filler test item.""" - super().__init__(*args, **kwargs) - self.originalname = original_name - self.func = func - self.params = params - self.fixturenames = fixturenames - self.fork = fork - self.fixture_format = fixture_format - for marker in marks: - if type(marker) is pytest.Mark: - self.own_markers.append(marker) - else: - self.add_marker(marker) - - def setup(self) -> None: - """Resolve and apply fixtures before test execution.""" - self._fixtureinfo = self.session._fixturemanager.getfixtureinfo( - self, - None, - None, - ) - request = TopRequest( - self, # type: ignore[arg-type] - _ispytest=True, - ) - for fixture_name in self.fixturenames: - if fixture_name == "request": - self.params[fixture_name] = request - else: - self.params[fixture_name] = request.getfixturevalue( - fixture_name - ) - - def runtest(self) -> None: - """Execute the test logic for this specific static test.""" - self.func(**self.params) - - def reportinfo(self) -> Tuple[Path, int, str]: - """Provide information for test reporting.""" - return self.fspath, 0, f"Static file test: {self.name}" - - -@pytest.fixture -def yul(fork: Fork, request: pytest.FixtureRequest) -> Type[Yul]: - """ - Fixture that allows contract code to be defined with Yul code. - - This fixture defines a class that wraps the - ::execution_testing.tools.Yul class so that upon instantiation within - the test case, it provides the test case's current fork parameter. - The fork is then available for use in solc's arguments for the Yul - code compilation. - - Test cases can override the default value by specifying a fixed version - with the @pytest.mark.compile_yul_with(FORK) marker. - """ - solc_target_fork: Fork | None - marker = request.node.get_closest_marker("compile_yul_with") - assert hasattr(request.config, "solc_version"), ( - "solc_version not set in pytest config." - ) - if marker: - if not marker.args[0]: - node_name = request.node.name - pytest.fail( - f"{node_name}: Expected one argument in " - "'compile_yul_with' marker." - ) - for fork in request.config.all_forks: # type: ignore - if fork.name() == marker.args[0]: - solc_target_fork = fork - break - else: - node_name = request.node.name - fork_arg = marker.args[0] - pytest.fail( - f"{node_name}: Fork {fork_arg} not found in forks list." - ) - else: - solc_target_fork = get_closest_fork(fork) - assert solc_target_fork is not None, ( - "No fork supports provided solc version." - ) - if ( - solc_target_fork != fork - and request.config.getoption("verbose") >= 1 - ): - solc_name = solc_target_fork.name() - fork_name = fork.name() - warnings.warn( - f"Compiling Yul for {solc_name}, not {fork_name}.", - stacklevel=2, - ) - - class YulWrapper(Yul): - def __new__(cls, *args: Any, **kwargs: Any) -> Self: - kwargs["fork"] = solc_target_fork - return super(YulWrapper, cls).__new__(cls, *args, **kwargs) - - return YulWrapper diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/help/help.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/help/help.py index 00bba7bd288..0b501956faa 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/help/help.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/help/help.py @@ -108,7 +108,6 @@ def pytest_configure(config: pytest.Config) -> None: "pytest-fill.ini", [ "evm", - "solc", "fork range", "filler location", "defining debug", diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/execute_fill.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/execute_fill.py index db4367f6744..4c82a3adbca 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/execute_fill.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/execute_fill.py @@ -167,15 +167,6 @@ def pytest_configure(config: pytest.Config) -> None: if not hasattr(config, "op_mode"): config.op_mode = OpMode.CONSENSUS # type: ignore[attr-defined] - config.addinivalue_line( - "markers", - "yul_test: a test case that compiles Yul code.", - ) - config.addinivalue_line( - "markers", - "compile_yul_with(fork): Always compile Yul source using the " - "corresponding evm version.", - ) config.addinivalue_line( "markers", "fill: Markers to be added in fill mode only.", @@ -398,17 +389,3 @@ def is_exception_test(request: pytest.FixtureRequest) -> bool: test (invalid block, invalid transaction). """ return request.node.get_closest_marker("exception_test") is not None - - -def pytest_addoption(parser: pytest.Parser) -> None: - """Add command-line options to pytest.""" - static_filler_group = parser.getgroup( - "static", "Arguments defining static filler behavior" - ) - static_filler_group.addoption( - "--fill-static-tests", - action="store_true", - dest="fill_static_tests_enabled", - default=None, - help=("Enable reading and filling from static test files."), - ) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/solc/__init__.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/solc/__init__.py deleted file mode 100644 index 4a054a553a2..00000000000 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/solc/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""A pytest plugin that provides solc functionality to fill/execute tests.""" diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/solc/solc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/solc/solc.py deleted file mode 100644 index 1557d18d63c..00000000000 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/solc/solc.py +++ /dev/null @@ -1,149 +0,0 @@ -"""Pytest plugin for configuring and verifying the solc compiler.""" - -import subprocess -from shutil import which - -import pytest -from pytest_metadata.plugin import metadata_key -from semver import Version - -SOLC_EXPECTED_MIN_VERSION: Version = Version.parse("0.8.24") - - -def pytest_addoption(parser: pytest.Parser) -> None: - """Add command-line options to pytest.""" - solc_group = parser.getgroup( - "solc", "Arguments defining the solc executable" - ) - solc_group.addoption( - "--solc-bin", - action="store", - dest="solc_bin", - type=str, - default=None, - help=( - "Path to a solc executable (for Yul source compilation). " - "Default: solc binary in PATH." - ), - ) - - -@pytest.hookimpl(tryfirst=True) -def pytest_configure(config: pytest.Config) -> None: - """Ensure that solc is available and get its version.""" - solc_bin = config.getoption("solc_bin") - - # Use provided solc binary or find it in PATH - if solc_bin: - if not which(solc_bin): - pytest.exit( - f"Specified solc binary not found: {solc_bin}", - returncode=pytest.ExitCode.USAGE_ERROR, - ) - else: - solc_bin = which("solc") - if not solc_bin: - pytest.exit( - "solc binary not found in PATH. Please install solc and " - "ensure it's in your PATH.", - returncode=pytest.ExitCode.USAGE_ERROR, - ) - - # Get solc version using subprocess - try: - result = subprocess.run( - [solc_bin, "--version"], - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - text=True, - check=True, - ) - except subprocess.CalledProcessError as e: - pytest.exit( - f"Failed to get solc version. Command output: {e.stdout}", - returncode=pytest.ExitCode.USAGE_ERROR, - ) - except subprocess.TimeoutExpired: - pytest.exit( - "Timeout while getting solc version.", - returncode=pytest.ExitCode.USAGE_ERROR, - ) - except Exception as e: - pytest.exit( - f"Unexpected error while getting solc version: {e}", - returncode=pytest.ExitCode.USAGE_ERROR, - ) - - # Parse version from output - version_output = result.stdout - version_line = None - - # Look for version in output (format: "Version: X.Y.Z+commit.hash") - for line in version_output.split("\n"): - if line.startswith("Version:"): - version_line = line - break - - if not version_line: - pytest.exit( - f"Could not parse solc version from output:\n{version_output}", - returncode=pytest.ExitCode.USAGE_ERROR, - ) - - # Extract version number - try: - # --version format is typically something like - # "0.8.24+commit.e11b9ed9.Linux.g++" - version_str = version_line.split()[1].split("+")[0] - solc_version_semver = Version.parse(version_str) - except (IndexError, ValueError) as e: - pytest.exit( - f"Failed to parse solc version from: {version_line}\nError: {e}", - returncode=pytest.ExitCode.USAGE_ERROR, - ) - - # Store version in metadata - if "Tools" not in config.stash[metadata_key]: - config.stash[metadata_key]["Tools"] = { - "solc": str(solc_version_semver), - } - else: - config.stash[metadata_key]["Tools"]["solc"] = str(solc_version_semver) - - # Check minimum version requirement - solc_version_semver = Version.parse( - str(solc_version_semver).split()[0].split("-")[0] - ) - if solc_version_semver < SOLC_EXPECTED_MIN_VERSION: - pytest.exit( - f"Unsupported solc version: {solc_version_semver}. Minimum " - f"required version is {SOLC_EXPECTED_MIN_VERSION}", - returncode=pytest.ExitCode.USAGE_ERROR, - ) - - # Store for later use - config.solc_version = solc_version_semver # type: ignore - config.option.solc_bin = solc_bin # save for fixture - - if config.getoption("verbose") > 0: - print(f"Using solc version {solc_version_semver} from {solc_bin}") - - -@pytest.fixture(autouse=True, scope="session") -def solc_bin(request: pytest.FixtureRequest) -> str | None: - """Return configured solc binary path.""" - return request.config.getoption("solc_bin") or which("solc") - - -@pytest.hookimpl(trylast=True) -def pytest_report_header( - config: pytest.Config, start_path: object -) -> list[str] | None: - """Add lines to pytest's console output header.""" - del start_path - - if config.option.collectonly: - return None - solc_version = config.stash[metadata_key]["Tools"]["solc"] - solc_path = config.option.solc_bin or which("solc") - return [f"solc: {solc_version}", f"solc path: {solc_path}"] diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/pytest_ini_files/pytest-fill.ini b/packages/testing/src/execution_testing/cli/pytest_commands/pytest_ini_files/pytest-fill.ini index 179ac2082f0..e627de46c9a 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/pytest_ini_files/pytest-fill.ini +++ b/packages/testing/src/execution_testing/cli/pytest_commands/pytest_ini_files/pytest-fill.ini @@ -12,7 +12,6 @@ addopts = -p execution_testing.cli.pytest_commands.plugins.concurrency -p execution_testing.cli.pytest_commands.plugins.filler.pre_alloc -p execution_testing.cli.pytest_commands.plugins.filler.ported_tests - -p execution_testing.cli.pytest_commands.plugins.filler.static_filler -p execution_testing.cli.pytest_commands.plugins.shared.benchmarking -p execution_testing.cli.pytest_commands.plugins.shared.transaction_fixtures -p execution_testing.cli.pytest_commands.plugins.help.help diff --git a/packages/testing/src/execution_testing/fixtures/collector.py b/packages/testing/src/execution_testing/fixtures/collector.py index 5865e99e7b3..414f92e0f64 100644 --- a/packages/testing/src/execution_testing/fixtures/collector.py +++ b/packages/testing/src/execution_testing/fixtures/collector.py @@ -210,7 +210,6 @@ class FixtureCollector: """Collects all fixtures generated by the test cases.""" output_dir: Path - fill_static_tests: bool single_fixture_per_file: bool filler_path: Path base_dump_dir: Optional[Path] = None @@ -238,12 +237,6 @@ def get_fixture_basename(self, info: TestInfo) -> Path: self.filler_path ) - # Each legacy test filler has only 1 test per file if it's a !state - # test! So no need to create directory Add11/add11.json it can be plain - # add11.json - if self.fill_static_tests: - return module_relative_output_dir.parent / info.original_name - if self.single_fixture_per_file: return module_relative_output_dir / info.get_single_test_name( mode="test" diff --git a/packages/testing/src/execution_testing/fixtures/tests/test_collector.py b/packages/testing/src/execution_testing/fixtures/tests/test_collector.py index 4b41f1c6d4f..11ef7ba1eed 100644 --- a/packages/testing/src/execution_testing/fixtures/tests/test_collector.py +++ b/packages/testing/src/execution_testing/fixtures/tests/test_collector.py @@ -71,7 +71,6 @@ def test_single_fixture_matches_json_dumps( """Output for a single fixture must match json.dumps(..., indent=4).""" collector = FixtureCollector( output_dir=output_dir, - fill_static_tests=False, single_fixture_per_file=False, filler_path=filler_path, generate_index=False, @@ -101,7 +100,6 @@ def test_multiple_fixtures_match_json_dumps( """ collector = FixtureCollector( output_dir=output_dir, - fill_static_tests=False, single_fixture_per_file=False, filler_path=filler_path, generate_index=False, @@ -136,7 +134,6 @@ def test_multiple_workers_merge_correctly( """ collector1 = FixtureCollector( output_dir=output_dir, - fill_static_tests=False, single_fixture_per_file=False, filler_path=filler_path, generate_index=False, @@ -154,7 +151,6 @@ def test_multiple_workers_merge_correctly( # Worker B writes fixtures 3-5 (separate partial file) collector2 = FixtureCollector( output_dir=output_dir, - fill_static_tests=False, single_fixture_per_file=False, filler_path=filler_path, generate_index=False, @@ -189,7 +185,6 @@ def test_output_is_valid_json( """The written file must be parseable as valid JSON.""" collector = FixtureCollector( output_dir=output_dir, - fill_static_tests=False, single_fixture_per_file=False, filler_path=filler_path, generate_index=False, @@ -214,7 +209,6 @@ def test_fixtures_sorted_by_key( """Fixture entries in the output file must be sorted by key.""" collector = FixtureCollector( output_dir=output_dir, - fill_static_tests=False, single_fixture_per_file=False, filler_path=filler_path, generate_index=False, @@ -241,7 +235,6 @@ def test_partial_files_cleaned_up_after_merge( """Partial JSONL files are deleted after merging.""" collector = FixtureCollector( output_dir=output_dir, - fill_static_tests=False, single_fixture_per_file=False, filler_path=filler_path, generate_index=False, @@ -289,7 +282,6 @@ def test_single_fixture_matches_legacy( new_dir.mkdir() collector = FixtureCollector( output_dir=new_dir, - fill_static_tests=False, single_fixture_per_file=False, filler_path=filler_path, generate_index=False, @@ -328,7 +320,6 @@ def test_multiple_fixtures_match_legacy( new_dir.mkdir() collector = FixtureCollector( output_dir=new_dir, - fill_static_tests=False, single_fixture_per_file=False, filler_path=filler_path, generate_index=False, @@ -372,7 +363,6 @@ def test_multiple_workers_match_legacy( for worker_idx in range(3): collector = FixtureCollector( output_dir=new_dir, - fill_static_tests=False, single_fixture_per_file=False, filler_path=filler_path, generate_index=False, @@ -422,7 +412,6 @@ def test_special_characters_in_keys_match_legacy( new_dir.mkdir() collector = FixtureCollector( output_dir=new_dir, - fill_static_tests=False, single_fixture_per_file=False, filler_path=filler_path, generate_index=False, diff --git a/packages/testing/src/execution_testing/forks/base_fork.py b/packages/testing/src/execution_testing/forks/base_fork.py index abeea03769a..bf006ce2603 100644 --- a/packages/testing/src/execution_testing/forks/base_fork.py +++ b/packages/testing/src/execution_testing/forks/base_fork.py @@ -336,7 +336,6 @@ class BaseFork(ForkOpcodeInterface, metaclass=BaseForkMeta): is_transition_fork: ClassVar[bool] = False _transition_tool_name: ClassVar[Optional[str]] = None - _solc_name: ClassVar[Optional[str]] = None _ignore: ClassVar[bool] = False _bpo_fork: ClassVar[bool] = False _children: ClassVar[Set[Type["BaseFork"]]] = set() @@ -357,7 +356,6 @@ def __init_subclass__( cls, *, transition_tool_name: Optional[str] = None, - solc_name: Optional[str] = None, ignore: bool = False, bpo_fork: bool = False, ruleset_name: Optional[str] = None, @@ -374,7 +372,6 @@ def __init_subclass__( forks. """ cls._transition_tool_name = transition_tool_name - cls._solc_name = solc_name cls._ignore = ignore cls._bpo_fork = bpo_fork cls._ruleset_name = ruleset_name @@ -1312,12 +1309,6 @@ def transition_tool_name(cls) -> str: """ pass - @classmethod - @abstractmethod - def solc_name(cls) -> str: - """Return fork name as it's meant to be passed to the solc compiler.""" - pass - @classmethod def is_deployed(cls) -> bool: """ diff --git a/packages/testing/src/execution_testing/forks/forks/forks.py b/packages/testing/src/execution_testing/forks/forks/forks.py index 8d1bb82e5d0..29ffc2b5eda 100644 --- a/packages/testing/src/execution_testing/forks/forks/forks.py +++ b/packages/testing/src/execution_testing/forks/forks/forks.py @@ -41,10 +41,7 @@ # All forks must be listed here !!! in the order they were introduced !!! -class Frontier( - BaseFork, - solc_name="homestead", -): +class Frontier(BaseFork): """Frontier fork.""" @classmethod @@ -57,13 +54,6 @@ def transition_tool_name(cls) -> str: return cls._transition_tool_name return cls.name() - @classmethod - def solc_name(cls) -> str: - """Return fork name as it's meant to be passed to the solc compiler.""" - if cls._solc_name is not None: - return cls._solc_name - return cls.name().lower() - @classmethod def header_base_fee_required(cls) -> bool: """At genesis, header must not contain base fee.""" @@ -1419,7 +1409,6 @@ class Constantinople( class ConstantinopleFix( Constantinople, - solc_name="constantinople", ruleset_name="PETERSBURG", ): """Constantinople Fix fork.""" @@ -1443,7 +1432,6 @@ class Istanbul( # Glacier forks skipped, unless explicitly specified class MuirGlacier( Istanbul, - solc_name="istanbul", ignore=True, ): """Muir Glacier fork.""" @@ -1474,7 +1462,6 @@ class London( # Glacier forks skipped, unless explicitly specified class ArrowGlacier( London, - solc_name="london", ignore=True, ): """Arrow Glacier fork.""" @@ -1484,7 +1471,6 @@ class ArrowGlacier( class GrayGlacier( ArrowGlacier, - solc_name="london", ignore=True, ): """Gray Glacier fork.""" @@ -1555,7 +1541,6 @@ class Osaka( eips.EIP7951, eips.EIP7883, Prague, - solc_name="cancun", ): """Osaka fork.""" diff --git a/packages/testing/src/execution_testing/specs/__init__.py b/packages/testing/src/execution_testing/specs/__init__.py index e850fe4cb23..a651530a090 100644 --- a/packages/testing/src/execution_testing/specs/__init__.py +++ b/packages/testing/src/execution_testing/specs/__init__.py @@ -1,7 +1,6 @@ """Test spec definitions and utilities.""" from .base import BaseTest, TestSpec -from .base_static import BaseStaticTest from .benchmark import ( BenchmarkTest, BenchmarkTestFiller, @@ -17,7 +16,6 @@ Header, ) from .state import StateTest, StateTestFiller, StateTestSpec -from .static_state.state_static import StateStaticTest from .transaction import ( TransactionTest, TransactionTestFiller, @@ -25,7 +23,6 @@ ) __all__ = ( - "BaseStaticTest", "BaseTest", "BenchmarkTest", "BenchmarkTestFiller", @@ -41,7 +38,6 @@ "Block", "Header", "OpcodeTarget", - "StateStaticTest", "StateTest", "StateTestFiller", "StateTestSpec", diff --git a/packages/testing/src/execution_testing/specs/base_static.py b/packages/testing/src/execution_testing/specs/base_static.py deleted file mode 100644 index decfdcd22e2..00000000000 --- a/packages/testing/src/execution_testing/specs/base_static.py +++ /dev/null @@ -1,188 +0,0 @@ -""" -Base class to parse test cases written in static formats. -""" - -import re -from abc import abstractmethod -from typing import Any, Callable, ClassVar, Dict, List, Tuple, Type, Union - -from pydantic import ( - BaseModel, - TypeAdapter, - ValidatorFunctionWrapHandler, - model_validator, -) - -from execution_testing.base_types import Bytes - - -class BaseStaticTest(BaseModel): - """Represents a base class that reads cases from static files.""" - - formats: ClassVar[List[Type["BaseStaticTest"]]] = [] - formats_type_adapter: ClassVar[TypeAdapter] - - format_name: ClassVar[str] = "" - - @classmethod - def __pydantic_init_subclass__(cls, **kwargs: Any) -> None: - """ - Register all subclasses of BaseStaticTest with a static test format - name set as possible static test format. - """ - if cls.format_name: - # Register the new fixture format - BaseStaticTest.formats.append(cls) - if len(BaseStaticTest.formats) > 1: - BaseStaticTest.formats_type_adapter = TypeAdapter( - Union[tuple(BaseStaticTest.formats)], - ) - else: - BaseStaticTest.formats_type_adapter = TypeAdapter(cls) - - @model_validator(mode="wrap") - @classmethod - def _parse_into_subclass( - cls, v: Any, handler: ValidatorFunctionWrapHandler - ) -> "BaseStaticTest": - """Parse the static test into the correct subclass.""" - if cls is BaseStaticTest: - return BaseStaticTest.formats_type_adapter.validate_python(v) - return handler(v) - - @abstractmethod - def fill_function(self) -> Callable: - """ - Return the test function that can be used to fill the test. - - This method should be implemented by the subclasses. - - The function returned can be optionally decorated with the - `@pytest.mark.parametrize` decorator to parametrize the test with the - number of sub test cases. - - Example: - ``` - @pytest.mark.parametrize("n", [1]) - @pytest.mark.parametrize("m", [1, 2]) - @pytest.mark.valid_from("Homestead") - def test_state_filler( - state_test: StateTestFiller, - fork: Fork, - pre: Alloc, - n: int, - m: int - ): - \"\"\"Generate a test from a static state filler.\"\"\" - assert n == 1 - assert m in [1, 2] - env = Environment(**self.env.model_dump()) - sender = pre.fund_eoa() - tx = Transaction( - ty=0x0, - nonce=0, - to=Address(0x1000), - gas_limit=500000, - protected=False if fork in [Frontier, Homestead] else True, - data="", - sender=sender, - ) - state_test(env=env, pre=pre, post={}, tx=tx) - ``` - - To aid the generation of the test, the function can be defined and then - the decorator be applied after defining the function: - - ``` - def test_state_filler( - state_test: StateTestFiller, - fork: Fork, - pre: Alloc, - n: int, - m: int, - ): - - ... - - test_state_filler = pytest.mark.parametrize("n", - [1])(test_state_filler - ) - test_state_filler = pytest.mark.parametrize("m", - [1, 2])(test_state_filler - ) - - if self.valid_from: - test_state_filler = pytest.mark.valid_from( - self.valid_from - )(test_state_filler) - - if self.valid_until: - test_state_filler = pytest.mark.valid_until( - self.valid_until - )(test_state_filler) - - return test_state_filler - ``` - - The function can contain the following parameters on top of the spec - type parameter (`state_test` in the example above): - `fork`: The fork - for which the test is currently being filled. - `pre`: The pre-state of - the test. - - """ - raise NotImplementedError - - @staticmethod - def remove_comments(data: Dict) -> Dict: - """Remove comments from a dictionary.""" - result = {} - for k, v in data.items(): - if isinstance(k, str) and k.startswith("//"): - continue - if isinstance(v, dict): - v = BaseStaticTest.remove_comments(v) - elif isinstance(v, list): - v = [ - BaseStaticTest.remove_comments(i) - if isinstance(i, dict) - else i - for i in v - ] - result[k] = v - return result - - @model_validator(mode="before") - @classmethod - def remove_comments_from_model(cls, data: Any) -> Any: - """Remove comments from the static file loaded, if any.""" - if isinstance(data, dict): - return BaseStaticTest.remove_comments(data) - return data - - -def remove_comments(v: str) -> str: - """ - Split by line and then remove the comments (starting with #) at the end of - each line if any. - """ - return "\n".join([line.split("#")[0].strip() for line in v.splitlines()]) - - -label_matcher = re.compile(r"^:label\s+(\S+)\s*", re.MULTILINE) -raw_matcher = re.compile(r":raw\s+(.*)", re.MULTILINE) - - -def labeled_bytes_from_string(v: str) -> Tuple[str | None, Bytes]: - """Parse `:label` and `:raw` from a string.""" - v = remove_comments(v) - - label: str | None = None - if m := label_matcher.search(v): - label = m.group(1) - v = label_matcher.sub("", v) - - m = raw_matcher.match(v.replace("\n", " ")) - if not m: - raise Exception(f"Unable to parse container from string: {v}") - strip_string = m.group(1).strip() - return label, Bytes(strip_string) diff --git a/packages/testing/src/execution_testing/specs/static_state/__init__.py b/packages/testing/src/execution_testing/specs/static_state/__init__.py deleted file mode 100644 index 69f8b6f2047..00000000000 --- a/packages/testing/src/execution_testing/specs/static_state/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Ethereum/tests structures.""" diff --git a/packages/testing/src/execution_testing/specs/static_state/account.py b/packages/testing/src/execution_testing/specs/static_state/account.py deleted file mode 100644 index 84e2bbbcb52..00000000000 --- a/packages/testing/src/execution_testing/specs/static_state/account.py +++ /dev/null @@ -1,250 +0,0 @@ -"""Account structure of ethereum/tests fillers.""" - -import hashlib -import json -from typing import Any, Dict, List, Mapping, Set, Tuple - -from pydantic import BaseModel, ConfigDict - -from execution_testing.base_types import ( - Account, - EthereumTestRootModel, - Hash, - HexNumber, -) -from execution_testing.test_types import ( - Alloc, - contract_address_from_hash, - eoa_from_hash, -) - -from .common import ( - AddressOrTagInFiller, - CodeInFiller, - ContractTag, - SenderTag, - Tag, - TagDependentData, - TagDict, - ValueInFiller, - ValueOrTagInFiller, -) - - -class StorageInPre(EthereumTestRootModel): - """Class that represents a storage in pre-state.""" - - root: Dict[ValueInFiller, ValueOrTagInFiller] - - def tag_dependencies(self) -> Mapping[str, Tag]: - """Get tag dependencies.""" - tag_dependencies: Dict[str, Tag] = {} - for k, v in self.root.items(): - if isinstance(k, Tag): - tag_dependencies[k.name] = k - if isinstance(v, Tag): - tag_dependencies[v.name] = v - return tag_dependencies - - def resolve(self, tags: TagDict) -> Dict[ValueInFiller, ValueInFiller]: - """Resolve the storage.""" - resolved_storage: Dict[ValueInFiller, ValueInFiller] = {} - for key, value in self.root.items(): - if isinstance(value, Tag): - resolved_storage[key] = HexNumber( - int.from_bytes(value.resolve(tags), "big") - ) - else: - resolved_storage[key] = value - return resolved_storage - - -class AccountInFiller(BaseModel, TagDependentData): - """Class that represents an account in filler.""" - - balance: ValueInFiller | None = None - code: CodeInFiller | None = None - nonce: ValueInFiller | None = None - storage: StorageInPre | None = None - - model_config = ConfigDict(arbitrary_types_allowed=True, extra="forbid") - - def tag_dependencies(self) -> Mapping[str, Tag]: - """Get tag dependencies.""" - tag_dependencies: Dict[str, Tag] = {} - if self.storage is not None: - tag_dependencies.update(self.storage.tag_dependencies()) - if self.code is not None and isinstance(self.code, CodeInFiller): - tag_dependencies.update(self.code.tag_dependencies()) - return tag_dependencies - - def resolve(self, tags: TagDict) -> Dict[str, Any]: - """Resolve the account.""" - account_properties: Dict[str, Any] = {} - if self.balance is not None: - account_properties["balance"] = self.balance - if self.code is not None: - if compiled_code := self.code.compiled(tags): - account_properties["code"] = compiled_code - if self.nonce is not None: - account_properties["nonce"] = self.nonce - if self.storage is not None: - if resolved_storage := self.storage.resolve(tags): - account_properties["storage"] = resolved_storage - return account_properties - - def hash(self) -> Hash: - """Return a hash of the account as it is in the filler.""" - dumped = self.model_dump(mode="json", exclude_none=True) - return Hash( - hashlib.sha256( - json.dumps( - dumped, - sort_keys=True, - separators=(",", ":"), - ).encode("utf-8") - ).digest() - ) - - -class PreInFiller(EthereumTestRootModel): - """Class that represents a pre-state in filler.""" - - root: Dict[AddressOrTagInFiller, AccountInFiller] - - def _build_dependency_graph( - self, - ) -> Tuple[Dict[str, Set[str]], Dict[str, AddressOrTagInFiller]]: - """Build a dependency graph for all tags.""" - dep_graph: Dict[str, Set[str]] = {} - tag_to_address: Dict[str, AddressOrTagInFiller] = {} - - # First pass: identify all tags and their dependencies - for address_or_tag, account in self.root.items(): - if isinstance(address_or_tag, Tag): - tag_name = address_or_tag.name - tag_to_address[tag_name] = address_or_tag - dep_graph[tag_name] = set() - - # Get dependencies from account properties - dependencies = account.tag_dependencies() - for dep_name in dependencies: - if dep_name != tag_name: # Ignore self-references - dep_graph[tag_name].add(dep_name) - - return dep_graph, tag_to_address - - def _topological_sort(self, dep_graph: Dict[str, Set[str]]) -> List[str]: - """Perform topological sort on dependency graph.""" - # Create a copy to modify - graph = {node: deps.copy() for node, deps in dep_graph.items()} - - # Find nodes with no dependencies - no_deps = [node for node, deps in graph.items() if not deps] - sorted_nodes = [] - - while no_deps: - # Process a node with no dependencies - node = no_deps.pop() - sorted_nodes.append(node) - - # Remove this node from other nodes' dependencies - for other_node, deps in graph.items(): - if node in deps: - deps.remove(node) - if not deps and other_node not in sorted_nodes: - no_deps.append(other_node) - - # Check for cycles - remaining = [node for node in graph if node not in sorted_nodes] - if remaining: - # Handle cycles by processing remaining nodes in any order - # This works because self-references are allowed - sorted_nodes.extend(remaining) - - return sorted_nodes - - def setup(self, pre: Alloc, all_dependencies: Dict[str, Tag]) -> TagDict: - """Resolve the pre-state with improved tag resolution.""" - resolved_accounts: TagDict = {} - - # Separate tagged and non-tagged accounts - tagged_accounts = {} - non_tagged_accounts = {} - - for address_or_tag, account in self.root.items(): - if isinstance(address_or_tag, Tag): - tagged_accounts[address_or_tag] = account - else: - non_tagged_accounts[address_or_tag] = account - - # Step 1: Process non-tagged accounts but don't compile code yet - # We'll compile code later after all tags are resolved - non_tagged_to_process = [] - for address, account in non_tagged_accounts.items(): - non_tagged_to_process.append((address, account)) - resolved_accounts[address.hex()] = address - - # Step 2: Build dependency graph for tagged accounts - dep_graph, tag_to_address = self._build_dependency_graph() - - # Step 3: Get topological order - resolution_order = self._topological_sort(dep_graph) - - # Step 4: Pre-deploy all contract tags and pre-fund EOAs to get - # addresses - account_salts: Dict[Hash, int] = {} - for tag_name in resolution_order: - if tag_name in tag_to_address: - tag = tag_to_address[tag_name] - account_hash = self.root[tag].hash() - salt = account_salts.get(account_hash, 0) - account_salts[account_hash] = salt + 1 - if isinstance(tag, ContractTag): - # Get a placeholder address - resolved_accounts[tag_name] = contract_address_from_hash( - account_hash, salt - ) - elif isinstance(tag, SenderTag): - # Create a placeholder EOA - eoa = eoa_from_hash(account_hash, salt) - # Store the EOA object for SenderKeyTag resolution - resolved_accounts[tag_name] = eoa - - # Step 5: Now resolve all properties with all addresses available - for tag_name in resolution_order: - if tag_name in tag_to_address: - tag = tag_to_address[tag_name] - assert isinstance(tag, (ContractTag, SenderTag)), ( - f"Tag {tag_name} is not a contract or sender" - ) - account = tagged_accounts[tag] - - # All addresses are now available, so resolve properties - account_properties = account.resolve(resolved_accounts) - - if isinstance(tag, (ContractTag, SenderTag)): - deployed_address = resolved_accounts[tag_name] - pre[deployed_address] = Account(**account_properties) - - # Step 6: Now process non-tagged accounts (including code compilation) - for address, account_in_filler in non_tagged_to_process: - pre[address] = Account( - **account_in_filler.resolve(resolved_accounts) - ) - - # Step 7: Handle any extra dependencies not in pre - for extra_dependency in all_dependencies: - if extra_dependency not in resolved_accounts: - if all_dependencies[extra_dependency].type != "eoa": - raise ValueError( - f"Contract dependency {extra_dependency} " - "not found in pre" - ) - - # Create new EOA - this will have a dynamically generated key - # and address - eoa = pre.fund_eoa(amount=0, label=extra_dependency) - resolved_accounts[extra_dependency] = eoa - - return resolved_accounts diff --git a/packages/testing/src/execution_testing/specs/static_state/common/__init__.py b/packages/testing/src/execution_testing/specs/static_state/common/__init__.py deleted file mode 100644 index 040841d7ee6..00000000000 --- a/packages/testing/src/execution_testing/specs/static_state/common/__init__.py +++ /dev/null @@ -1,39 +0,0 @@ -"""Ethereum/tests structures.""" - -from .common import ( - AccessListInFiller, - AddressInFiller, - AddressOrCreateTagInFiller, - AddressOrTagInFiller, - AddressTag, - CodeInFiller, - ContractTag, - HashOrTagInFiller, - SenderTag, - Tag, - TagDependentData, - TagDict, - ValueInFiller, - ValueOrCreateTagInFiller, - ValueOrTagInFiller, - parse_address_or_tag, -) - -__all__ = [ - "AccessListInFiller", - "AddressInFiller", - "AddressOrCreateTagInFiller", - "AddressOrTagInFiller", - "AddressTag", - "CodeInFiller", - "ContractTag", - "HashOrTagInFiller", - "Tag", - "TagDict", - "TagDependentData", - "SenderTag", - "ValueInFiller", - "ValueOrCreateTagInFiller", - "ValueOrTagInFiller", - "parse_address_or_tag", -] diff --git a/packages/testing/src/execution_testing/specs/static_state/common/common.py b/packages/testing/src/execution_testing/specs/static_state/common/common.py deleted file mode 100644 index fead4be2e6f..00000000000 --- a/packages/testing/src/execution_testing/specs/static_state/common/common.py +++ /dev/null @@ -1,418 +0,0 @@ -"""Common field types from ethereum/tests.""" - -import re -import subprocess -import tempfile -from typing import Any, Dict, List, Mapping, Tuple, Union - -from eth_abi import encode -from eth_utils import function_signature_to_4byte_selector -from pydantic import ( - BaseModel, - BeforeValidator, - Field, - PrivateAttr, - model_validator, -) -from pydantic_core import core_schema -from typing_extensions import Annotated - -from execution_testing.base_types import ( - AccessList, - Address, - CamelModel, - Hash, - HexNumber, -) - -from .compile_yul import compile_yul -from .tags import ( - ContractTag, - CreateTag, - SenderKeyTag, - SenderTag, - Tag, - TagDependentData, - TagDict, -) - - -def parse_hex_number(i: str | int) -> int: - """Check if the given string is a valid hex number.""" - if i == "" or i == "0x": - return 0 - if isinstance(i, int): - return i - if i.startswith("0x:bigint "): - i = i[10:] - return int(i, 16) - if i.startswith("0x") or any(char in "abcdef" for char in i.lower()): - return int(i, 16) - return int(i, 10) - - -def parse_args_from_string_into_array( - stream: str, pos: int, delim: str = " " -) -> Tuple[List[str], int]: - """Parse YUL options into array.""" - args = [] - arg = "" - # Loop until end of stream or until encountering newline or '{' - while pos < len(stream) and stream[pos] not in ("\n", "{"): - if stream[pos] == delim: - args.append(arg) - arg = "" - else: - arg += stream[pos] - pos += 1 - if arg: - args.append(arg) - return args, pos - - -class CodeInFiller(BaseModel, TagDependentData): - """Not compiled code source in test filler.""" - - label: str | None - source: str - _dependencies: Dict[str, Tag] = PrivateAttr(default_factory=dict) - - @model_validator(mode="before") - @classmethod - def validate_from_string(cls, code: Any) -> Any: - """Validate from string, separating label from code source.""" - if isinstance(code, str): - label_marker = ":label" - # Only look for label at the beginning of the string (possibly - # after whitespace) - stripped_code = code.lstrip() - - # Parse :label into code options - label = None - source = code - - # Check if the code starts with :label - if stripped_code.startswith(label_marker): - # Calculate the position in the original string - label_index = code.find(label_marker) - space_index = code.find( - " ", label_index + len(label_marker) + 1 - ) - if space_index == -1: - label = code[label_index + len(label_marker) + 1 :] - source = "" # No source after label - else: - label = code[ - label_index + len(label_marker) + 1 : space_index - ] - source = code[space_index + 1 :].strip() - - return {"label": label, "source": source} - return code - - def model_post_init(self, context: Any) -> None: - """Initialize StateStaticTest.""" - super().model_post_init(context) - tag_dependencies: Dict[str, Tag] = {} - for tag_type in {ContractTag, SenderTag}: - for m in tag_type.regex_pattern.finditer(self.source): - new_tag = tag_type.model_validate(m.group(0)) - tag_dependencies[new_tag.name] = new_tag - self._dependencies = tag_dependencies - - def compiled(self, tags: TagDict) -> bytes: - """Compile the code from source to bytes.""" - raw_code = self.source - if isinstance(raw_code, int): - # Users pass code as int (very bad) - hex_str = format(raw_code, "02x") - return bytes.fromhex(hex_str) - - if not isinstance(raw_code, str): - raise ValueError( - f"code is of type {type(raw_code)} but expected a string: " - f"{raw_code}" - ) - if len(raw_code) == 0: - return b"" - - compiled_code = "" - - def replace_tags(raw_code: str, keep_prefix: bool) -> str: - for tag in self._dependencies.values(): - if tag.name not in tags: - raise ValueError(f"Tag {tag} not found in tags") - substitution_address = f"{tag.resolve(tags)}" - if not keep_prefix and substitution_address.startswith("0x"): - substitution_address = substitution_address[2:] - # Use the original string if available, otherwise construct a - # pattern - if hasattr(tag, "original_string") and tag.original_string: - raw_code = raw_code.replace( - tag.original_string, substitution_address - ) - else: - raw_code = re.sub( - f"<\\w+:{tag.name}(:0x.+)?>", - substitution_address, - raw_code, - ) - return raw_code - - raw_marker = ":raw 0x" - raw_index = raw_code.find(raw_marker) - if raw_index == -1: - raw_index = replace_tags(raw_code, True).find(raw_marker) - abi_marker = ":abi" - abi_index = raw_code.find(abi_marker) - yul_marker = ":yul" - yul_index = raw_code.find(yul_marker) - - # Parse :raw or 0x - if raw_index != -1 or raw_code.lstrip().startswith("0x"): - raw_code = replace_tags(raw_code, False) - # Parse :raw - if raw_index != -1: - compiled_code = raw_code[raw_index + len(raw_marker) :] - # Parse plain code 0x - elif raw_code.lstrip().startswith("0x"): - compiled_code = raw_code[2:].lower() - else: - raw_code = replace_tags(raw_code, True) - # Parse :yul - if yul_index != -1: - option_start = yul_index + len(yul_marker) - options: list[str] = [] - native_yul_options: str = "" - - if raw_code[option_start:].lstrip().startswith("{"): - # No yul options, proceed to code parsing - source_start = option_start - else: - opt, source_start = parse_args_from_string_into_array( - raw_code, option_start + 1 - ) - for arg in opt: - if arg == "object" or arg == '"C"': - native_yul_options += arg + " " - else: - options.append(arg) - - with tempfile.NamedTemporaryFile( - mode="w+", delete=False, suffix=".yul" - ) as tmp: - tmp.write(native_yul_options + raw_code[source_start:]) - tmp_path = tmp.name - compiled_code = compile_yul( - source_file=tmp_path, - evm_version=options[0] if len(options) >= 1 else None, - optimize=options[1] if len(options) >= 2 else None, - )[2:] - - # Parse :abi - elif abi_index != -1: - abi_encoding = raw_code[abi_index + len(abi_marker) + 1 :] - tokens = abi_encoding.strip().split() - abi = tokens[0] - function_signature = function_signature_to_4byte_selector(abi) - parameter_str = re.sub(r"^\w+", "", abi).strip() - - parameter_types = parameter_str.strip("()").split(",") - if len(tokens) > 1: - function_parameters = encode( - [parameter_str], - [ - [ - # treat big ints as 256bits - int(t.lower(), 0) & ((1 << 256) - 1) - if parameter_types[t_index] == "uint" - # treat positive values as True - else int(t.lower(), 0) > 0 - if parameter_types[t_index] == "bool" - else False - and ValueError("unhandled parameter_types") - for t_index, t in enumerate(tokens[1:]) - ] - ], - ) - return function_signature + function_parameters - return function_signature - - # Parse lllc code - elif ( - raw_code.lstrip().startswith("{") - or raw_code.lstrip().startswith("(asm") - or raw_code.lstrip().startswith(":raw 0x") - ): - with tempfile.NamedTemporaryFile( - mode="w+", delete=False - ) as tmp: - tmp.write(raw_code) - tmp_path = tmp.name - - # - using lllc - result = subprocess.run( - ["lllc", tmp_path], capture_output=True, text=True - ) - - # - using docker: If the running machine does not have lllc - # installed, we can use docker to run lllc, but we need to - # start a container first, and the process is generally slower. - # - # from .docker import get_lllc_container_id - # result = subprocess.run( ["docker", - # "exec", - # get_lllc_container_id(), - # "lllc", - # tmp_path[5:]], - # capture_output=True, - # text=True - # ) - compiled_code = "".join(result.stdout.splitlines()) - - else: - raise Exception(f'Error parsing code: "{raw_code}"') - - try: - return bytes.fromhex(compiled_code) - except ValueError as e: - raise Exception(f'Error parsing compile code: "{raw_code}"') from e - - def tag_dependencies(self) -> Mapping[str, Tag]: - """Get tag dependencies.""" - return self._dependencies - - -class AddressTag: - """ - Represents an address tag like: - - <eoa:sender:0x...>. - - <contract:target:0x...>. - - <coinbase:0x...>. - """ - - def __init__(self, tag_type: str, tag_name: str, original_string: str): - """Initialize address tag.""" - self.tag_type = tag_type # "eoa", "contract", or "coinbase" - # e.g., "sender", "target", or address for 2-part tags - self.tag_name = tag_name - self.original_string = original_string - - def __str__(self) -> str: - """Return original tag string.""" - return self.original_string - - def __repr__(self) -> str: - """Return debug representation.""" - return f"AddressTag(type={self.tag_type}, name={self.tag_name})" - - def __eq__(self, other: object) -> bool: - """Check equality based on original string.""" - if isinstance(other, AddressTag): - return self.original_string == other.original_string - return False - - def __hash__(self) -> int: - """Hash based on original string for use as dict key.""" - return hash(self.original_string) - - @classmethod - def __get_pydantic_core_schema__( - cls, source_type: Any, handler: Any - ) -> core_schema.CoreSchema: - """Pydantic core schema for AddressTag.""" - return core_schema.str_schema() - - -def parse_address_or_tag(value: Any) -> Union[Address, AddressTag]: - """Parse either a regular address or an address tag.""" - if not isinstance(value, str): - # Non-string values should be converted to Address normally - return Address(value, left_padding=True) - - # Check if it matches tag pattern: - # - <eoa:0x...>, <contract:0x...>, <coinbase:0x...> - # - <eoa:name:0x...>, <contract:name:0x...> - - # Try 3-part pattern first (type:name:address) - tag_pattern_3_part = r"^<(eoa|contract|coinbase):([^:]+):(.+)>$" - match = re.match(tag_pattern_3_part, value.strip()) - - if match: - tag_type = match.group(1) - tag_name = match.group(2) - address_part = match.group(3) - # For 3-part tags, the tag_name is the middle part - return AddressTag(tag_type, tag_name, value.strip()) - - # Try 2-part pattern (type:address) - tag_pattern_2_part = r"^<(eoa|contract|coinbase):(.+)>$" - match = re.match(tag_pattern_2_part, value.strip()) - - if match: - tag_type = match.group(1) - address_part = match.group(2) - # For 2-part tags, use the address as the tag_name - return AddressTag(tag_type, address_part, value.strip()) - - # Regular address string - return Address(value, left_padding=True) - - -def parse_address_or_tag_for_access_list(value: Any) -> Union[Address, str]: - """ - Parse either a regular address or an address tag, keeping tags as strings - for later resolution. - """ - if not isinstance(value, str): - # Non-string values should be converted to Address normally - return Address(value, left_padding=True) - - # Check if it matches a tag pattern - tag_pattern = r"^<(eoa|contract|coinbase):.+>$" - if re.match(tag_pattern, value.strip()): - # Return the tag string as-is for later resolution - return value.strip() - else: - # Regular address string - return Address(value, left_padding=True) - - -AddressInFiller = Annotated[ - Address, BeforeValidator(lambda a: Address(a, left_padding=True)) -] -AddressOrTagInFiller = ContractTag | SenderTag | Address -AddressOrCreateTagInFiller = ContractTag | SenderTag | CreateTag | Address -ValueInFiller = Annotated[HexNumber, BeforeValidator(parse_hex_number)] -ValueOrTagInFiller = ContractTag | SenderTag | ValueInFiller -ValueOrCreateTagInFiller = ContractTag | SenderTag | CreateTag | ValueInFiller -HashOrTagInFiller = SenderKeyTag | Hash - - -class AccessListInFiller(CamelModel, TagDependentData): - """ - Access List for transactions in fillers that can contain address tags. - """ - - address: AddressOrTagInFiller - storage_keys: List[Hash] = Field(default_factory=list) - - def tag_dependencies(self) -> Mapping[str, Tag]: - """Get tag dependencies.""" - if isinstance(self.address, Tag): - return { - self.address.name: self.address, - } - return {} - - def resolve(self, tags: TagDict) -> AccessList: - """Resolve the access list.""" - kwargs: Dict[str, Address | List[Hash]] = {} - if isinstance(self.address, Tag): - kwargs["address"] = self.address.resolve(tags) - else: - kwargs["address"] = self.address - kwargs["storageKeys"] = [ - Hash(key, left_padding=True) for key in self.storage_keys - ] - return AccessList(**kwargs) diff --git a/packages/testing/src/execution_testing/specs/static_state/common/compile_yul.py b/packages/testing/src/execution_testing/specs/static_state/common/compile_yul.py deleted file mode 100644 index 4fc4bd84ef6..00000000000 --- a/packages/testing/src/execution_testing/specs/static_state/common/compile_yul.py +++ /dev/null @@ -1,102 +0,0 @@ -"""compile yul with arguments.""" - -import subprocess -from pathlib import Path -from typing import LiteralString - - -def safe_solc_command( - source_file: Path | str, - evm_version: str | None = None, - optimize: str | None = None, -) -> list[str]: - """Safely construct solc command with validated inputs.""" - # Validate source file path - source_path = Path(source_file) - if not source_path.exists(): - raise FileNotFoundError(f"Source file not found: {source_file}") - - cmd: list[str] = ["solc"] - - # Add EVM version if provided (validate against known versions) - if evm_version: - valid_versions = { - "homestead", - "tangerineWhistle", - "spuriousDragon", - "byzantium", - "constantinople", - "petersburg", - "istanbul", - "berlin", - "london", - "paris", - "shanghai", - "cancun", - } - if evm_version not in valid_versions: - raise ValueError(f"Invalid EVM version: {evm_version}") - cmd.extend(["--evm-version", evm_version]) - - # Add compilation flags (using literal strings) - strict_assembly: LiteralString = "--strict-assembly" - cmd.append(strict_assembly) - - if optimize is None: - optimize_flag: LiteralString = "--optimize" - yul_opts: LiteralString = "--yul-optimizations=:" - cmd.extend([optimize_flag, yul_opts]) - - cmd.append(str(source_path)) - return cmd - - -def compile_yul( - source_file: str, - evm_version: str | None = None, - optimize: str | None = None, -) -> str: - """ - Compiles a Yul source file using solc and returns the binary - representation. - - Arguments: - source_file (str): Path to the Yul source file. - evm_version(str, optional): The EVM version to use (e.g., 'istanbul'). - Defaults to None. - optimize (any, optional): If provided (non-None), optimization flags - are not added. If None, additional - optimization flags will be included. - - Returns: str: The binary representation prefixed with "0x". - - Raises: Exception: If the solc output contains an error message. - - """ - cmd = safe_solc_command(source_file, evm_version, optimize) - - # Execute the solc command and capture both stdout and stderr - result = subprocess.run( - cmd, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - text=True, - check=False, - ) - out = result.stdout - - # Check for errors in the output - if "Error" in out: - raise Exception(f"Yul compilation error:\n{out}") - - # Search for the "Binary representation:" line and get the following line - # as the binary - lines = out.splitlines() - binary_line = "" - for i, line in enumerate(lines): - if "Binary representation:" in line: - if i + 1 < len(lines): - binary_line = lines[i + 1].strip() - break - - return f"0x{binary_line}" diff --git a/packages/testing/src/execution_testing/specs/static_state/common/tags.py b/packages/testing/src/execution_testing/specs/static_state/common/tags.py deleted file mode 100644 index e63464635d6..00000000000 --- a/packages/testing/src/execution_testing/specs/static_state/common/tags.py +++ /dev/null @@ -1,227 +0,0 @@ -"""Classes to manage tags in static state tests.""" - -import re -from abc import ABC, abstractmethod -from typing import Any, ClassVar, Dict, Generic, Mapping, TypeVar - -from pydantic import BaseModel, model_validator - -from execution_testing.base_types import Address, Bytes, Hash, HexNumber -from execution_testing.test_types import ( - EOA, - compute_create2_address, - compute_create_address, -) - -TagDict = Dict[str, Address | EOA] - -T = TypeVar("T", bound=Address | Hash) - - -class Tag(BaseModel, Generic[T]): - """Tag.""" - - name: str - type: ClassVar[str] = "" - regex_pattern: ClassVar[re.Pattern] = re.compile(r"<\w+:(\w+)(:[^>]+)?") - # Store the original tag string for replacement - original_string: str | None = None - - def __hash__(self) -> int: - """Hash based on original string for use as dict key.""" - return hash(f"{self.__class__.__name__}:{self.name}") - - @model_validator(mode="before") - @classmethod - def validate_from_string(cls, data: Any) -> Any: - """Validate the generic tag from string: <tag_kind:name:0x...>.""" - if isinstance(data, str): - if m := cls.regex_pattern.match(data): - name = m.group(1) - return {"name": name, "original_string": data} - return data - - def resolve(self, tags: TagDict) -> T: - """Resolve the tag.""" - raise NotImplementedError("Subclasses must implement this method") - - -class TagDependentData(ABC): - """Data for resolving tags.""" - - @abstractmethod - def tag_dependencies(self) -> Mapping[str, Tag]: - """Get tag dependencies.""" - pass - - -class AddressTag(Tag[Address]): - """Address tag.""" - - def resolve(self, tags: TagDict) -> Address: - """Resolve the tag.""" - assert self.name in tags, f"Tag {self.name} not found in tags" - return Address(tags[self.name]) - - -class ContractTag(AddressTag): - """Contract tag.""" - - type: ClassVar[str] = "contract" - regex_pattern: ClassVar[re.Pattern] = re.compile( - r"<contract:([^:>]+)(?::(0x[a-fA-F0-9]+))?>" - ) - # Optional hard-coded address for debugging - debug_address: Address | None = None - - @model_validator(mode="before") - @classmethod - def validate_from_string(cls, data: Any) -> Any: - """ - Validate the contract tag from string: - <contract:name:0x...> - or - <contract:0x...>. - """ - if isinstance(data, str): - if m := cls.regex_pattern.match(data): - name_or_addr = m.group(1) - debug_addr = ( - m.group(2) if m.lastindex and m.lastindex >= 2 else None - ) - - # Check if it's a 2-part format with an address - if name_or_addr.startswith("0x") and len(name_or_addr) == 42: - # For 2-part format, use the full address as the name This - # ensures all references to the same address get the same - # tag name - return { - "name": name_or_addr, - "debug_address": Address(name_or_addr), - "original_string": data, - } - else: - # Normal 3-part format - use the name as-is - result = {"name": name_or_addr, "original_string": data} - if debug_addr: - result["debug_address"] = Address(debug_addr) - return result - return data - - -class CreateTag(AddressTag): - """Contract derived from a another contract via CREATE.""" - - create_type: str - nonce: HexNumber | None = None - salt: HexNumber | None = None - initcode: Bytes | None = None - - type: ClassVar[str] = "contract" - regex_pattern: ClassVar[re.Pattern] = re.compile( - r"<(create|create2):(\w+):(\w+):?(\w+)?>" - ) - - @model_validator(mode="before") - @classmethod - def validate_from_string(cls, data: Any) -> Any: - """Validate the create tag from string: <create:name:nonce>.""" - if isinstance(data, str): - if m := cls.regex_pattern.match(data): - create_type = m.group(1) - name = m.group(2) - kwargs = { - "create_type": create_type, - "name": name, - "original_string": data, - } - if create_type == "create": - kwargs["nonce"] = m.group(3) - elif create_type == "create2": - kwargs["salt"] = m.group(3) - kwargs["initcode"] = m.group(4) - return kwargs - return data - - def resolve(self, tags: TagDict) -> Address: - """Resolve the tag.""" - assert self.name in tags, f"Tag {self.name} not found in tags" - if self.create_type == "create": - assert self.nonce is not None, "Nonce is required for create" - return compute_create_address( - address=tags[self.name], nonce=self.nonce - ) - elif self.create_type == "create2": - assert self.salt is not None, "Salt is required for create2" - assert self.initcode is not None, ( - "Init code is required for create2" - ) - return compute_create2_address( - address=tags[self.name], salt=self.salt, initcode=self.initcode - ) - else: - raise ValueError(f"Invalid create type: {self.create_type}") - - -class SenderTag(AddressTag): - """Sender tag.""" - - type: ClassVar[str] = "eoa" - regex_pattern: ClassVar[re.Pattern] = re.compile( - r"<eoa:(\w+)(?::(0x[a-fA-F0-9]+))?>" - ) - # Optional hard-coded address for debugging - debug_address: Address | None = None - - @model_validator(mode="before") - @classmethod - def validate_from_string(cls, data: Any) -> Any: - """Validate the sender tag from string: <eoa:name:0x...>.""" - if isinstance(data, str): - if m := cls.regex_pattern.match(data): - name = m.group(1) - debug_addr = ( - m.group(2) if m.lastindex and m.lastindex >= 2 else None - ) - - result = {"name": name, "original_string": data} - if debug_addr: - result["debug_address"] = Address(debug_addr) - return result - return data - - -class SenderKeyTag(Tag[EOA]): - """Sender eoa tag.""" - - type: ClassVar[str] = "eoa" - regex_pattern: ClassVar[re.Pattern] = re.compile( - r"<eoa:(\w+)(?::(0x[a-fA-F0-9]+))?>" - ) - debug_key: str | None = None # Optional hard-coded key for debugging - - @model_validator(mode="before") - @classmethod - def validate_from_string(cls, data: Any) -> Any: - """Validate the sender key tag from string: <eoa:name:0xkey...>.""" - if isinstance(data, str): - if m := cls.regex_pattern.match(data): - name = m.group(1) - debug_key = ( - m.group(2) if m.lastindex and m.lastindex >= 2 else None - ) - - result = {"name": name, "original_string": data} - if debug_key: - result["debug_key"] = debug_key - return result - return data - - def resolve(self, tags: TagDict) -> EOA: - """Resolve the tag.""" - assert self.name in tags, f"Tag {self.name} not found in tags" - result = tags[self.name] - assert isinstance(result, EOA), ( - f"Expected EOA but got {type(result)} for tag {self.name}" - ) - return result diff --git a/packages/testing/src/execution_testing/specs/static_state/environment.py b/packages/testing/src/execution_testing/specs/static_state/environment.py deleted file mode 100644 index 89b42cbe45b..00000000000 --- a/packages/testing/src/execution_testing/specs/static_state/environment.py +++ /dev/null @@ -1,78 +0,0 @@ -"""Environment structure of ethereum/tests fillers.""" - -from typing import Any, Dict - -from pydantic import BaseModel, ConfigDict, Field, model_validator - -from execution_testing.base_types import Address -from execution_testing.test_types import Environment - -from .common import AddressOrTagInFiller, Tag, TagDict, ValueInFiller - - -class EnvironmentInStateTestFiller(BaseModel): - """Class that represents an environment filler.""" - - current_coinbase: AddressOrTagInFiller = Field( - ..., alias="currentCoinbase" - ) - current_gas_limit: ValueInFiller = Field(..., alias="currentGasLimit") - current_number: ValueInFiller = Field(..., alias="currentNumber") - current_timestamp: ValueInFiller = Field(..., alias="currentTimestamp") - - current_difficulty: ValueInFiller | None = Field( - ValueInFiller("0x020000"), alias="currentDifficulty" - ) - current_random: ValueInFiller | None = Field( - ValueInFiller("0x020000"), alias="currentRandom" - ) - current_base_fee: ValueInFiller | None = Field( - ValueInFiller("0x0a"), alias="currentBaseFee" - ) - - current_excess_blob_gas: ValueInFiller | None = Field( - None, alias="currentExcessBlobGas" - ) - current_slot_number: ValueInFiller | None = Field(None, alias="slotNumber") - - model_config = ConfigDict(extra="forbid") - - @model_validator(mode="after") - def check_fields(self) -> "EnvironmentInStateTestFiller": - """Validate all fields are set.""" - if self.current_difficulty is None: - if self.current_random is None: - raise ValueError( - "If `currentDifficulty` is not set, " - "`currentRandom` must be set!" - ) - return self - - def get_environment(self, tags: TagDict) -> Environment: - """Get the environment.""" - kwargs: Dict[str, Any] = {} - if isinstance(self.current_coinbase, Tag): - assert self.current_coinbase.name in tags, ( - f"Tag {self.current_coinbase.name} to resolve coinbase " - "not found in tags" - ) - kwargs["fee_recipient"] = self.current_coinbase.resolve(tags) - else: - kwargs["fee_recipient"] = Address(self.current_coinbase) - if self.current_difficulty is not None: - kwargs["difficulty"] = self.current_difficulty - if self.current_random is not None: - kwargs["prev_randao"] = self.current_random - if self.current_gas_limit is not None: - kwargs["gas_limit"] = self.current_gas_limit - if self.current_number is not None: - kwargs["number"] = self.current_number - if self.current_timestamp is not None: - kwargs["timestamp"] = self.current_timestamp - if self.current_base_fee is not None: - kwargs["base_fee_per_gas"] = self.current_base_fee - if self.current_excess_blob_gas is not None: - kwargs["excess_blob_gas"] = self.current_excess_blob_gas - if self.current_slot_number is not None: - kwargs["slot_number"] = self.current_slot_number - return Environment(**kwargs) diff --git a/packages/testing/src/execution_testing/specs/static_state/expect_section.py b/packages/testing/src/execution_testing/specs/static_state/expect_section.py deleted file mode 100644 index 0425b65fb0a..00000000000 --- a/packages/testing/src/execution_testing/specs/static_state/expect_section.py +++ /dev/null @@ -1,490 +0,0 @@ -"""Expect section structure of ethereum/tests fillers.""" - -import re -from enum import StrEnum -from typing import Annotated, Any, Dict, Iterator, List, Mapping, Set, Union - -from pydantic import ( - BaseModel, - BeforeValidator, - Field, - ValidatorFunctionWrapHandler, - field_validator, - model_validator, -) - -from execution_testing.base_types import ( - Account, - Address, - CamelModel, - EthereumTestRootModel, - HexNumber, - Storage, -) -from execution_testing.exceptions import ( - TransactionExceptionInstanceOrList, -) -from execution_testing.forks import Fork, get_forks -from execution_testing.test_types import Alloc - -from .common import ( - AddressOrCreateTagInFiller, - CodeInFiller, - Tag, - TagDependentData, - TagDict, - ValueInFiller, - ValueOrCreateTagInFiller, -) - - -class Indexes(BaseModel): - """Class that represents an index filler.""" - - data: int | List[Union[int, str]] | List[int] | str = Field(-1) - gas: int | List[Union[int, str]] | List[int] | str = Field(-1) - value: int | List[Union[int, str]] | List[int] | str = Field(-1) - - -def validate_any_string_as_none(v: Any) -> Any: - """Validate "ANY" as None.""" - if type(v) is str and v == "ANY": - return None - return v - - -class StorageInExpectSection(EthereumTestRootModel, TagDependentData): - """Class that represents a storage in expect section filler.""" - - root: Dict[ - ValueOrCreateTagInFiller, - Annotated[ - ValueOrCreateTagInFiller | None, - BeforeValidator(validate_any_string_as_none), - ], - ] - - def tag_dependencies(self) -> Mapping[str, Tag]: - """Get storage dependencies.""" - tag_dependencies = {} - for key, value in self.root.items(): - if isinstance(key, Tag): - tag_dependencies[key.name] = key - if isinstance(value, Tag): - tag_dependencies[value.name] = value - return tag_dependencies - - def resolve(self, tags: TagDict) -> Storage: - """Resolve the account with the given tags.""" - storage = Storage() - for key, value in self.root.items(): - resolved_key: HexNumber | Address - if isinstance(key, Tag): - resolved_key = key.resolve(tags) - else: - resolved_key = key - if value is None: - storage.set_expect_any(resolved_key) - elif isinstance(value, Tag): - storage[resolved_key] = value.resolve(tags) - else: - storage[resolved_key] = value - return storage - - def __contains__(self, key: Address) -> bool: - """Check if the storage contains a key.""" - return key in self.root - - def __iter__(self) -> Iterator[ValueOrCreateTagInFiller]: # type: ignore[override] - """Iterate over the storage.""" - return iter(self.root) - - -class AccountInExpectSection(BaseModel, TagDependentData): - """Class that represents an account in expect section filler.""" - - balance: ValueInFiller | None = None - code: CodeInFiller | None = None - nonce: ValueInFiller | None = None - storage: StorageInExpectSection | None = None - - @model_validator(mode="wrap") # type: ignore[misc] - @classmethod - def validate_should_not_exist( - cls, v: Any, handler: ValidatorFunctionWrapHandler - ) -> "AccountInExpectSection | None": - """ - Validate the "shouldnotexist" field, which makes this validator return - `None`. - """ - if isinstance(v, dict): - if "shouldnotexist" in v: - return None - return handler(v) - - def tag_dependencies(self) -> Mapping[str, Tag]: - """Get tag dependencies.""" - tag_dependencies: Dict[str, Tag] = {} - if self.code is not None: - tag_dependencies.update(self.code.tag_dependencies()) - if self.storage is not None: - tag_dependencies.update(self.storage.tag_dependencies()) - return tag_dependencies - - def resolve(self, tags: TagDict) -> Account: - """Resolve the account with the given tags.""" - account_kwargs: Dict[str, Any] = {} - if self.storage is not None: - account_kwargs["storage"] = self.storage.resolve(tags) - if self.code is not None: - account_kwargs["code"] = self.code.compiled(tags) - if self.balance is not None: - account_kwargs["balance"] = self.balance - if self.nonce is not None: - account_kwargs["nonce"] = self.nonce - return Account(**account_kwargs) - - -class CMP(StrEnum): - """Comparison action.""" - - LE = "<=" - GE = ">=" - LT = "<" - GT = ">" - EQ = "=" - - -class ForkConstraint(BaseModel): - """Single fork with an operand.""" - - operand: CMP - fork: Fork - - @field_validator("fork", mode="before") - @classmethod - def parse_fork_synonyms(cls, value: Any) -> Any: - """Resolve fork synonyms.""" - if value == "EIP158": - value = "Byzantium" - return value - - @model_validator(mode="before") - @classmethod - def parse_from_string(cls, data: Any) -> Any: - """Parse a fork with operand from a string.""" - if isinstance(data, str): - for cmp in CMP: - if data.startswith(cmp): - fork = data.removeprefix(cmp) - return { - "operand": cmp, - "fork": fork, - } - return { - "operand": CMP.EQ, - "fork": data, - } - return data - - def match(self, fork: Fork) -> bool: - """Return whether the fork satisfies the operand evaluation.""" - match self.operand: - case CMP.LE: - return fork <= self.fork - case CMP.GE: - return fork >= self.fork - case CMP.LT: - return fork < self.fork - case CMP.GT: - return fork > self.fork - case CMP.EQ: - return fork == self.fork - case _: - raise ValueError(f"Invalid operand: {self.operand}") - - -class ForkSet(EthereumTestRootModel): - """Set of forks.""" - - root: Set[Fork] - - @model_validator(mode="before") - @classmethod - def parse_from_list_or_string(cls, value: Any) -> Set[Fork]: - """Parse fork_with_operand `>=Cancun` into {Cancun, Prague, ...}.""" - fork_set: Set[Fork] = set() - if not isinstance(value, list): - value = [value] - - for fork_with_operand in value: - matches = re.findall(r"(<=|<|>=|>|=)([^<>=]+)", fork_with_operand) - if matches: - all_fork_constraints = [ - ForkConstraint.model_validate(f"{op}{fork.strip()}") - for op, fork in matches - ] - else: - all_fork_constraints = [ - ForkConstraint.model_validate(fork_with_operand.strip()) - ] - - for fork in get_forks(): - for f in all_fork_constraints: - if not f.match(fork): - # If any constraint does not match, skip adding - break - else: - # All constraints match, add the fork to the set - fork_set.add(fork) - - return fork_set - - def __hash__(self) -> int: - """Return the hash of the fork set.""" - h = hash(None) - for fork in sorted([str(f) for f in self]): - h ^= hash(fork) - return h - - def __contains__(self, fork: Fork) -> bool: - """Check if the fork set contains a fork.""" - return fork in self.root - - def __iter__(self) -> Iterator[Fork]: # type: ignore[override] - """Iterate over the fork set.""" - return iter(self.root) - - def __len__(self) -> int: - """Return the length of the fork set.""" - return len(self.root) - - -class ResultInFiller(EthereumTestRootModel, TagDependentData): - """ - Post section in state test filler. - - A value of `None` for an address means that the account should not be in - the state trie at the end of the test. - """ - - root: Dict[AddressOrCreateTagInFiller, AccountInExpectSection | None] - - def tag_dependencies(self) -> Mapping[str, Tag]: - """Return all tags used in the result.""" - tag_dependencies: Dict[str, Tag] = {} - for address, account in self.root.items(): - if isinstance(address, Tag): - tag_dependencies[address.name] = address - - if account is None: - continue - - tag_dependencies.update(account.tag_dependencies()) - - return tag_dependencies - - def resolve(self, tags: TagDict) -> Alloc: - """Resolve the post section.""" - post = Alloc() - for address, account in self.root.items(): - if isinstance(address, Tag): - resolved_address = address.resolve(tags) - else: - resolved_address = Address(address) - - post[resolved_address] = ( - account.resolve(tags) if account is not None else account - ) - return post - - def __contains__(self, address: Address) -> bool: - """Check if the result contains an address.""" - return address in self.root - - def __iter__(self) -> Iterator[AddressOrCreateTagInFiller]: # type: ignore[override] - """Iterate over the result.""" - return iter(self.root) - - def __len__(self) -> int: - """Return the length of the result.""" - return len(self.root) - - -class ExpectException(EthereumTestRootModel): - """Expect exception model.""" - - root: Dict[ForkSet, TransactionExceptionInstanceOrList] - - def __getitem__(self, fork: Fork) -> TransactionExceptionInstanceOrList: - """Get an expectation for a given fork.""" - for k in self.root: - if fork in k: - return self.root[k] - raise KeyError(f"Fork {fork} not found in expectations.") - - def __contains__(self, fork: Fork) -> bool: - """Check if the expect exception contains a fork.""" - return fork in self.root - - def __iter__(self) -> Iterator[ForkSet]: # type: ignore[override] - """Iterate over the expect exception.""" - return iter(self.root) - - def __len__(self) -> int: - """Return the length of the expect exception.""" - return len(self.root) - - -class ExpectSectionInStateTestFiller(CamelModel): - """Expect section in state test filler.""" - - indexes: Indexes = Field(default_factory=Indexes) - network: ForkSet - result: ResultInFiller - expect_exception: ExpectException | None = None - - def model_post_init(self, __context: Any) -> None: - """Validate that the expectation is coherent.""" - if self.expect_exception is None: - return - all_forks: Set[Fork] = set() - for current_fork_set in self.expect_exception: - for fork in current_fork_set: - assert fork not in all_forks - all_forks.add(fork) - - def has_index(self, d: int, g: int, v: int) -> bool: - """Check if there is index set in indexes.""" - d_match: bool = False - g_match: bool = False - v_match: bool = False - - # Check if data index match - if isinstance(self.indexes.data, int): - d_match = ( - True - if self.indexes.data == -1 or self.indexes.data == d - else False - ) - elif isinstance(self.indexes.data, list): - d_match = True if self.indexes.data.count(d) else False - - # Check if gas index match - if isinstance(self.indexes.gas, int): - g_match = ( - True - if self.indexes.gas == -1 or self.indexes.gas == g - else False - ) - elif isinstance(self.indexes.gas, list): - g_match = True if self.indexes.gas.count(g) else False - - # Check if value index match - if isinstance(self.indexes.value, int): - v_match = ( - True - if self.indexes.value == -1 or self.indexes.value == v - else False - ) - elif isinstance(self.indexes.value, list): - v_match = True if self.indexes.value.count(v) else False - - return d_match and g_match and v_match - - -def _match_index(idx: int | list, val: int) -> bool: - """Check if an index specification matches a value.""" - if isinstance(idx, int): - return idx == -1 or idx == val - if isinstance(idx, list): - return val in idx - return False - - -def resolve_expect_post( - expect_entries: list[dict], - d: int, - g: int, - v: int, - fork: Fork, -) -> tuple[dict, TransactionExceptionInstanceOrList | None]: - """ - Resolve expected post-state for given d, g, v and fork. - - Used by generated Python tests at runtime. The expect_entries are - materialized Python dicts with resolved addresses and Account objects. - """ - for entry in expect_entries: - indexes = entry["indexes"] - if not _match_index(indexes.get("data", -1), d): - continue - if not _match_index(indexes.get("gas", -1), g): - continue - if not _match_index(indexes.get("value", -1), v): - continue - - # Match fork against network constraints - network = entry["network"] - fork_set = ForkSet.model_validate(network) - if fork not in fork_set: - continue - - # Found matching entry - result = entry.get("result", {}) - - # Resolve exception - exception: TransactionExceptionInstanceOrList | None = None - expect_exc = entry.get("expect_exception") - if expect_exc: - for constraint_str, exc_value in expect_exc.items(): - exc_fork_set = ForkSet.model_validate( - constraint_str.split(",") - ) - if fork in exc_fork_set: - exception = exc_value - break - - return result, exception - - raise ValueError( - f"No matching expect entry for d={d}, g={g}, v={v}, fork={fork}" - ) - - -def resolve_expect_post_fork( - expect_entries: list[dict], - fork: Fork, -) -> tuple[dict, TransactionExceptionInstanceOrList | None]: - """ - Resolve expected post-state for a given fork only (no d/g/v matching). - - Used by single-case generated Python tests that have fork-dependent - post-state (multiple expect sections with different networks but only - one (d, g, v) combo). - """ - for entry in expect_entries: - # Match fork against network constraints - network = entry["network"] - fork_set = ForkSet.model_validate(network) - if fork not in fork_set: - continue - - # Found matching entry - result = entry.get("result", {}) - - # Resolve exception - exception: TransactionExceptionInstanceOrList | None = None - expect_exc = entry.get("expect_exception") - if expect_exc: - for constraint_str, exc_value in expect_exc.items(): - exc_fork_set = ForkSet.model_validate( - constraint_str.split(",") - ) - if fork in exc_fork_set: - exception = exc_value - break - - return result, exception - - raise ValueError(f"No matching expect entry for fork={fork}") diff --git a/packages/testing/src/execution_testing/specs/static_state/general_transaction.py b/packages/testing/src/execution_testing/specs/static_state/general_transaction.py deleted file mode 100644 index a0878d334ec..00000000000 --- a/packages/testing/src/execution_testing/specs/static_state/general_transaction.py +++ /dev/null @@ -1,243 +0,0 @@ -"""General transaction structure of ethereum/tests fillers.""" - -from typing import Any, Dict, Generator, List, Mapping - -from pydantic import ( - BaseModel, - ConfigDict, - Field, - field_validator, - model_validator, -) - -from execution_testing.base_types import ( - Address, - CamelModel, - EthereumTestRootModel, - Hash, -) -from execution_testing.exceptions import ( - TransactionExceptionInstanceOrList, -) -from execution_testing.test_types import Transaction - -from .common import ( - AccessListInFiller, - AddressOrTagInFiller, - CodeInFiller, - HashOrTagInFiller, - Tag, - TagDependentData, - TagDict, - ValueInFiller, -) - - -class DataWithAccessList(CamelModel, TagDependentData): - """Class that represents data with access list.""" - - data: CodeInFiller - access_list: List[AccessListInFiller] | None = None - - @field_validator("access_list", mode="before") - @classmethod - def convert_keys_to_hash( - cls, access_list: List[Dict[str, Any]] | None - ) -> List[Dict[str, Any]] | None: # noqa: N805 - """Fix keys.""" - if access_list is None: - return None - for entry in access_list: - if "storageKeys" in entry: - entry["storageKeys"] = [ - Hash(key, left_padding=True) - for key in entry["storageKeys"] - ] - return access_list - - def tag_dependencies(self) -> Mapping[str, Tag]: - """Get tag dependencies.""" - tag_dependencies: Dict[str, Tag] = {} - if self.access_list is not None: - for entry in self.access_list: - tag_dependencies.update(entry.tag_dependencies()) - if self.data is not None and isinstance(self.data, CodeInFiller): - tag_dependencies.update(self.data.tag_dependencies()) - return tag_dependencies - - @model_validator(mode="wrap") - @classmethod - def wrap_data_only(cls, data: Any, handler: Any) -> "DataWithAccessList": - """Wrap data only if it is not a dictionary.""" - if not isinstance(data, dict) and not isinstance( - data, DataWithAccessList - ): - data = {"data": data} - return handler(data) - - -class LabeledDataIndex(BaseModel): - """Represents an index with a label if any.""" - - index: int - label: str | None = None - - def __str__(self) -> str: - """Transform into a string that can be part of a test name.""" - if self.label is not None: - return self.label - return f"{self.index}" - - -class LabeledDataList(EthereumTestRootModel): - """Class that represents a list of labeled data.""" - - root: List[DataWithAccessList] - - def __getitem__(self, label_or_index: int | str) -> DataWithAccessList: - """Get an item by label or index.""" - if isinstance(label_or_index, int): - return self.root[label_or_index] - if isinstance(label_or_index, str): - for item in self.root: - if item.data.label == label_or_index: - return item - raise KeyError( - f"Label/index {label_or_index} not found in data indexes" - ) - - def __contains__(self, label_or_index: int | str) -> bool: - """ - Return True if the LabeledDataList contains the given label/index. - """ - if isinstance(label_or_index, int): - return label_or_index < len(self.root) - if isinstance(label_or_index, str): - for item in self.root: - if item.data.label == label_or_index: - return True - return False - - def __len__(self) -> int: - """Return the length of the list.""" - return len(self.root) - - def __iter__(self) -> Generator[LabeledDataIndex, None, None]: # type: ignore - """Return the iterator of the root list.""" - for i, item in enumerate(self.root): - labeled_data_index = LabeledDataIndex(index=i) - if item.data.label is not None: - labeled_data_index.label = item.data.label - yield labeled_data_index - - -class GeneralTransactionInFiller(BaseModel, TagDependentData): - """Class that represents general transaction in filler.""" - - data: LabeledDataList - gas_limit: List[ValueInFiller] = Field(..., alias="gasLimit") - gas_price: ValueInFiller | None = Field(None, alias="gasPrice") - nonce: ValueInFiller | None - to: AddressOrTagInFiller | None - value: List[ValueInFiller] - secret_key: HashOrTagInFiller = Field(..., alias="secretKey") - - max_fee_per_gas: ValueInFiller | None = Field(None, alias="maxFeePerGas") - max_priority_fee_per_gas: ValueInFiller | None = Field( - None, alias="maxPriorityFeePerGas" - ) - - max_fee_per_blob_gas: ValueInFiller | None = Field( - None, alias="maxFeePerBlobGas" - ) - blob_versioned_hashes: List[Hash] | None = Field( - None, alias="blobVersionedHashes" - ) - - model_config = ConfigDict(extra="forbid") - - def tag_dependencies(self) -> Mapping[str, Tag]: - """Get tag dependencies.""" - tag_dependencies: Dict[str, Tag] = {} - if self.data: - for idx in self.data: - data = self.data[idx.index] - tag_dependencies.update(data.tag_dependencies()) - if self.to is not None and isinstance(self.to, Tag): - tag_dependencies[self.to.name] = self.to - if self.secret_key is not None and isinstance(self.secret_key, Tag): - tag_dependencies[self.secret_key.name] = self.secret_key - return tag_dependencies - - @field_validator("to", mode="before") - def check_single_key(cls, to: Any) -> Any: # noqa: N805 - """Creation transaction.""" - if to == "": - to = None - return to - - @model_validator(mode="after") - def check_fields(self) -> "GeneralTransactionInFiller": - """Validate all fields are set.""" - if self.gas_price is None: - if ( - self.max_fee_per_gas is None - or self.max_priority_fee_per_gas is None - ): - raise ValueError( - "If `gasPrice` is not set," - " `maxFeePerGas` and `maxPriorityFeePerGas` must be set!" - ) - return self - - def get_transaction( - self, - tags: TagDict, - d: int, - g: int, - v: int, - exception: TransactionExceptionInstanceOrList | None, - ) -> Transaction: - """Get the transaction.""" - data_box = self.data[d] - kwargs: Dict[str, Any] = {} - if self.to is None: - kwargs["to"] = None - elif isinstance(self.to, Tag): - kwargs["to"] = self.to.resolve(tags) - else: - kwargs["to"] = Address(self.to) - - kwargs["data"] = data_box.data.compiled(tags) - if data_box.access_list is not None: - kwargs["access_list"] = [ - entry.resolve(tags) for entry in data_box.access_list - ] - - kwargs["gas_limit"] = self.gas_limit[g] - - if isinstance(self.secret_key, Tag): - sender = self.secret_key.resolve(tags) - kwargs["secret_key"] = sender.key - else: - kwargs["secret_key"] = self.secret_key - - if self.value[v] > 0: - kwargs["value"] = self.value[v] - if self.gas_price is not None: - kwargs["gas_price"] = self.gas_price - if self.nonce is not None: - kwargs["nonce"] = self.nonce - if self.max_fee_per_gas is not None: - kwargs["max_fee_per_gas"] = self.max_fee_per_gas - if self.max_priority_fee_per_gas is not None: - kwargs["max_priority_fee_per_gas"] = self.max_priority_fee_per_gas - if self.max_fee_per_blob_gas is not None: - kwargs["max_fee_per_blob_gas"] = self.max_fee_per_blob_gas - if self.blob_versioned_hashes is not None: - kwargs["blob_versioned_hashes"] = self.blob_versioned_hashes - - if exception is not None: - kwargs["error"] = exception - - return Transaction(**kwargs) diff --git a/packages/testing/src/execution_testing/specs/static_state/state_static.py b/packages/testing/src/execution_testing/specs/static_state/state_static.py deleted file mode 100644 index 37619835a64..00000000000 --- a/packages/testing/src/execution_testing/specs/static_state/state_static.py +++ /dev/null @@ -1,231 +0,0 @@ -"""Ethereum General State Test filler static test spec parser.""" - -from typing import Any, Callable, ClassVar, List, Self, Set, Union - -import pytest -from _pytest.mark.structures import ParameterSet -from pydantic import BaseModel, ConfigDict, Field, model_validator - -from execution_testing.forks import Fork -from execution_testing.test_types import Alloc - -from ..base_static import BaseStaticTest -from ..state import StateTestFiller -from .account import PreInFiller -from .common import Tag -from .environment import EnvironmentInStateTestFiller -from .expect_section import ExpectSectionInStateTestFiller -from .general_transaction import GeneralTransactionInFiller - - -class Info(BaseModel): - """Class that represents an info filler.""" - - comment: str | None = Field(None) - pytest_marks: List[str] = Field(default_factory=list) - - -class StateStaticTest(BaseStaticTest): - """General State Test static filler from ethereum/tests.""" - - test_name: str = "" - format_name: ClassVar[str] = "state_test" - - info: Info | None = Field(None, alias="_info") - env: EnvironmentInStateTestFiller - pre: PreInFiller - transaction: GeneralTransactionInFiller - expect: List[ExpectSectionInStateTestFiller] - - model_config = ConfigDict(extra="forbid") - - def model_post_init(self, context: Any) -> None: - """Initialize StateStaticTest.""" - super().model_post_init(context) - - @model_validator(mode="after") - def match_labels(self) -> Self: - """Replace labels in expect section with corresponding tx.d indexes.""" - - def parse_string_indexes(indexes: str) -> List[int]: - """Parse index that are string in to list of int.""" - if ":label" in indexes: - # Parse labels in data - indexes = indexes.replace(":label ", "") - tx_matches: List[int] = [] - for idx in self.transaction.data: - if indexes == idx.label: - tx_matches.append(idx.index) - return tx_matches - else: - # Parse ranges in data - start, end = map(int, indexes.lstrip().split("-")) - return list(range(start, end + 1)) - - def parse_indexes( - indexes: Union[ - int, str, list[Union[int, str]], list[str], list[int] - ], - do_hint: bool = False, - ) -> List[int] | int: - """ - Parse indexes and replace all ranges and labels into tx indexes. - """ - result: List[int] | int = [] - - if do_hint: - print("Before: " + str(indexes)) - - if isinstance(indexes, int): - result = indexes - if isinstance(indexes, str): - result = parse_string_indexes(indexes) - if isinstance(indexes, list): - result = [] - for element in indexes: - parsed = parse_indexes(element) - if isinstance(parsed, int): - result.append(parsed) - else: - result.extend(parsed) - result = list(set(result)) - - if do_hint: - print("After: " + str(result)) - return result - - for expect_section in self.expect: - expect_section.indexes.data = parse_indexes( - expect_section.indexes.data - ) - expect_section.indexes.gas = parse_indexes( - expect_section.indexes.gas - ) - expect_section.indexes.value = parse_indexes( - expect_section.indexes.value - ) - - return self - - def fill_function(self) -> Callable: - """Return a StateTest spec from a static file.""" - # Check if this test uses tags - has_tags = False - tx_tag_dependencies = self.transaction.tag_dependencies() - if tx_tag_dependencies: - has_tags = True - else: - # Check expect sections for tags - for expect in self.expect: - result_tag_dependencies = expect.result.tag_dependencies() - if result_tag_dependencies: - has_tags = True - break - - fully_tagged = True - for address in self.pre.root: - if not isinstance(address, Tag): - fully_tagged = False - break - - d_g_v_parameters: List[ParameterSet] = [] - for d in self.transaction.data: - for g in range(len(self.transaction.gas_limit)): - for v in range(len(self.transaction.value)): - exception_test = False - for expect in self.expect: - if ( - expect.has_index(d.index, g, v) - and expect.expect_exception is not None - ): - exception_test = True - # TODO: This does not take into account exceptions that - # only happen on specific forks, but this requires a - # covariant parametrize - marks = ( - [pytest.mark.exception_test] if exception_test else [] - ) - id_label = "" - if len(self.transaction.data) > 1 or d.label is not None: - if d.label is not None: - id_label = f"{d}" - else: - id_label = f"d{d}" - if len(self.transaction.gas_limit) > 1: - id_label += f"-g{g}" - if len(self.transaction.value) > 1: - id_label += f"-v{v}" - d_g_v_parameters.append( - pytest.param(d.index, g, v, marks=marks, id=id_label) - ) - - @pytest.mark.valid_at(*self.get_valid_at_forks()) - @pytest.mark.parametrize("d,g,v", d_g_v_parameters) - def test_state_vectors( - state_test: StateTestFiller, - pre: Alloc, - fork: Fork, - d: int, - g: int, - v: int, - ) -> None: - for expect in self.expect: - if expect.has_index(d, g, v): - if fork in expect.network: - tx_tag_dependencies = ( - self.transaction.tag_dependencies() - ) - result_tag_dependencies = ( - expect.result.tag_dependencies() - ) - all_dependencies = { - **tx_tag_dependencies, - **result_tag_dependencies, - } - tags = self.pre.setup(pre, all_dependencies) - env = self.env.get_environment(tags) - exception = ( - None - if expect.expect_exception is None - else expect.expect_exception[fork] - ) - tx = self.transaction.get_transaction( - tags, d, g, v, exception - ) - post = expect.result.resolve(tags) - state_test( - env=env, - pre=pre, - post=post, - tx=tx, - ) - return - pytest.fail( - f"Expectation not found for d={d}, g={g}, v={v}, fork={fork}" - ) - - if self.info and self.info.pytest_marks: - for mark in self.info.pytest_marks: - apply_mark = getattr(pytest.mark, mark) - test_state_vectors = apply_mark(test_state_vectors) - - if has_tags: - test_state_vectors = pytest.mark.tagged(test_state_vectors) - if fully_tagged: - test_state_vectors = pytest.mark.fully_tagged( - test_state_vectors - ) - else: - test_state_vectors = pytest.mark.untagged(test_state_vectors) - - # All static tests are mutable since we do `pre[0x123...] = Account()` - test_state_vectors = pytest.mark.pre_alloc_mutable(test_state_vectors) - - return test_state_vectors - - def get_valid_at_forks(self) -> List[str]: - """Return list of forks that are valid for this test.""" - fork_set: Set[Fork] = set() - for expect in self.expect: - fork_set.update(expect.network) - return sorted([str(f) for f in fork_set]) diff --git a/packages/testing/src/execution_testing/tools/tests/test_code.py b/packages/testing/src/execution_testing/tools/tests/test_code.py index 370f385488b..2ca28f2221b 100644 --- a/packages/testing/src/execution_testing/tools/tests/test_code.py +++ b/packages/testing/src/execution_testing/tools/tests/test_code.py @@ -1,10 +1,8 @@ """Test suite for `ethereum_test.code` module.""" -from string import Template from typing import Mapping import pytest -from semver import Version from execution_testing.base_types import ( Account, @@ -13,17 +11,10 @@ TestAddress, TestPrivateKey, ) -from execution_testing.cli.pytest_commands.plugins.solc.solc import ( - SOLC_EXPECTED_MIN_VERSION, -) from execution_testing.client_clis import TransitionTool from execution_testing.fixtures import BlockchainFixture from execution_testing.forks import ( Cancun, - Fork, - Homestead, - Shanghai, - get_deployed_forks, ) from execution_testing.specs import StateTest from execution_testing.test_types import Alloc, Environment, Transaction @@ -32,39 +23,6 @@ from ..tools_code import CalldataCase, Case, Conditional, Initcode, Switch -@pytest.fixture(params=get_deployed_forks()) -def fork(request: pytest.FixtureRequest) -> Fork: - """Return the target evm-version (fork) for solc compilation.""" - return request.param - - -@pytest.fixture() -def expected_bytes( - request: pytest.FixtureRequest, solc_version: Version, fork: Fork -) -> bytes: - """Return the expected bytes for the test.""" - expected_bytes = request.param - if isinstance(expected_bytes, Template): - if solc_version < SOLC_EXPECTED_MIN_VERSION or fork <= Homestead: - solc_padding = "" - else: - solc_padding = "00" - return bytes.fromhex( - expected_bytes.substitute(solc_padding=solc_padding) - ) - if isinstance(expected_bytes, bytes): - if fork >= Shanghai: - expected_bytes = b"\x5f" + expected_bytes[2:] - if solc_version < SOLC_EXPECTED_MIN_VERSION or fork <= Homestead: - return expected_bytes - else: - return expected_bytes + b"\x00" - - raise Exception( - "Unsupported expected_bytes type: {}".format(type(expected_bytes)) - ) - - @pytest.mark.parametrize( "initcode,bytecode", [ diff --git a/packages/testing/src/execution_testing/tools/tools_code/__init__.py b/packages/testing/src/execution_testing/tools/tools_code/__init__.py index 3eb13abd3ee..9102cb0f681 100644 --- a/packages/testing/src/execution_testing/tools/tools_code/__init__.py +++ b/packages/testing/src/execution_testing/tools/tools_code/__init__.py @@ -17,7 +17,6 @@ While, WhileGas, ) -from .yul import Solc, Yul, YulCompiler __all__ = ( "CalldataCase", @@ -30,12 +29,9 @@ "Initcode", "IteratingBytecode", "SequentialAddressLayout", - "Solc", "Switch", "TransactionWithCost", "TxOutcome", "While", "WhileGas", - "Yul", - "YulCompiler", ) diff --git a/packages/testing/src/execution_testing/tools/tools_code/yul.py b/packages/testing/src/execution_testing/tools/tools_code/yul.py deleted file mode 100644 index eb3a0ff8df6..00000000000 --- a/packages/testing/src/execution_testing/tools/tools_code/yul.py +++ /dev/null @@ -1,112 +0,0 @@ -"""Yul frontend.""" - -import re -import warnings -from functools import cached_property -from pathlib import Path -from shutil import which -from subprocess import CompletedProcess, run -from typing import Optional, Type - -from semver import Version -from typing_extensions import Self - -from execution_testing.forks import Fork -from execution_testing.vm import Bytecode - -DEFAULT_SOLC_ARGS = ("--assemble", "-") -VERSION_PATTERN = re.compile(r"Version: (.*)") - - -class Solc: - """Solc compiler.""" - - binary: Path - - def __init__( - self, - binary: Optional[Path | str] = None, - ): - """Initialize the solc compiler.""" - if not binary: - which_path = which("solc") - if which_path is not None: - binary = Path(which_path) - if not binary or not Path(binary).exists(): - raise Exception( - """`solc` binary executable not found, please refer to - https://docs.soliditylang.org/en/latest/installing-solidity.html - for help downloading and installing `solc`""" - ) - self.binary = Path(binary) - - def run( - self, *args: str, input_value: str | None = None - ) -> CompletedProcess: - """Run solc with the given arguments.""" - return run( - [self.binary, *args], - capture_output=True, - text=True, - input=input_value, - ) - - @cached_property - def version(self) -> Version: - """Return solc's version.""" - for line in self.run("--version").stdout.splitlines(): - if match := VERSION_PATTERN.search(line): - # Sanitize - solc_version_string = match.group(1).replace("g++", "gpp") - return Version.parse(solc_version_string) - warnings.warn("Unable to determine solc version.", stacklevel=2) - return Version(0) - - -class Yul(Bytecode): - """ - Yul compiler. - Compiles Yul source code into bytecode. - """ - - source: str - evm_version: str | None - - def __new__( - cls, - source: str, - fork: Optional[Fork] = None, - binary: Optional[Path | str] = None, - ) -> Self: - """Compile Yul source code into bytecode.""" - solc = Solc(binary) - evm_version = fork.solc_name() if fork else None - - solc_args = ("--evm-version", evm_version) if evm_version else () - - result = solc.run(*solc_args, *DEFAULT_SOLC_ARGS, input_value=source) - - if result.returncode: - stderr_lines = result.stderr.splitlines() - stderr_message = "\n".join(line.strip() for line in stderr_lines) - raise Exception( - f"failed to compile yul source:\n{stderr_message[7:]}" - ) - - lines = result.stdout.splitlines() - - hex_str = lines[lines.index("Binary representation:") + 1] - - bytecode = bytes.fromhex(hex_str) - instance = super().__new__( - cls, - bytecode, - popped_stack_items=0, - pushed_stack_items=0, - ) - instance.source = source - instance.evm_version = evm_version - return instance - - -YulCompiler = Type[Yul] diff --git a/scripts/filler_to_python/__init__.py b/scripts/filler_to_python/__init__.py deleted file mode 100644 index 359373d411c..00000000000 --- a/scripts/filler_to_python/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Convert static filler YAML/JSON to Python test files.""" diff --git a/scripts/filler_to_python/__main__.py b/scripts/filler_to_python/__main__.py deleted file mode 100644 index 35d7d6c6edb..00000000000 --- a/scripts/filler_to_python/__main__.py +++ /dev/null @@ -1,328 +0,0 @@ -"""CLI entry point: load -> analyze -> render -> format -> write.""" - -from __future__ import annotations - -import argparse -import ast -import logging -import os -import re -import subprocess -import sys -from pathlib import Path - -from .analyzer import analyze, load_filler -from .render import render_test - -logger = logging.getLogger(__name__) - -MANUALLY_ENHANCED_TAG = "@manually-enhanced" - -# Fillers consolidated into hand-written tests outside -# ``tests/ported_static``; never regenerate them. -# sstore_combinations_*: -# tests/istanbul/eip2200_net_gas_metering/test_sstore_combinations.py -CONSOLIDATED_FILLERS = { - "sstore_combinations_initial00_ParisFiller", - "sstore_combinations_initial00_2_ParisFiller", - "sstore_combinations_initial01_ParisFiller", - "sstore_combinations_initial01_2_ParisFiller", - "sstore_combinations_initial10_ParisFiller", - "sstore_combinations_initial10_2_ParisFiller", - "sstore_combinations_initial11_ParisFiller", - "sstore_combinations_initial11_2_ParisFiller", - "sstore_combinations_initial20_ParisFiller", - "sstore_combinations_initial20_2_ParisFiller", - "sstore_combinations_initial21_ParisFiller", - "sstore_combinations_initial21_2_ParisFiller", -} - - -def _has_manually_enhanced_tag(file_path: Path) -> bool: - """Check if a file has @manually-enhanced in its module docstring.""" - try: - source = file_path.read_text() - tree = ast.parse(source) - except (OSError, SyntaxError): - return False - docstring = ast.get_docstring(tree) - if docstring is None: - return False - return MANUALLY_ENHANCED_TAG in docstring - - -def post_format(source: str) -> str: - """Format generated Python source with ruff.""" - # ruff format - try: - result = subprocess.run( - ["ruff", "format", "--stdin-filename", "test.py", "-"], - input=source, - capture_output=True, - text=True, - env={**os.environ, "RUST_MIN_STACK": "8388608"}, - ) - if result.returncode == 0: - source = result.stdout - except FileNotFoundError: - pass # ruff not installed - - # ruff check --fix (accept output even with remaining unfixable issues) - try: - result = subprocess.run( - [ - "ruff", - "check", - "--fix", - "--stdin-filename", - "test.py", - "-", - ], - input=source, - capture_output=True, - text=True, - env={**os.environ, "RUST_MIN_STACK": "8388608"}, - ) - if result.stdout: - source = result.stdout - except FileNotFoundError: - pass - - # Add # noqa for generated code issues that can't be auto-fixed. - # Track docstring boundaries to avoid adding noqa inside docstrings. - lines = source.split("\n") - fixed_lines: list[str] = [] - in_docstring = False - for line in lines: - stripped = line.rstrip() - if '"""' in stripped: - count = stripped.count('"""') - if count == 1: - in_docstring = not in_docstring - # count == 2 means open+close on same line, no state change - if in_docstring: - fixed_lines.append(line) - continue - noqa_parts: list[str] = [] - if len(stripped) > 79: - noqa_parts.append("E501") - # F841: deploy_contract assigns to variables used in expect dicts - if "= pre.deploy_contract(" in stripped: - noqa_parts.append("F841") - if noqa_parts and "# noqa" not in stripped: - codes = ", ".join(noqa_parts) - fixed_lines.append(f"{stripped} # noqa: {codes}") - else: - fixed_lines.append(line) - source = "\n".join(fixed_lines) - - return source - - -def _filler_name_to_filename(stem: str) -> str: - """Convert filler stem to output filename.""" - name = re.sub(r"Filler$", "", stem) - # camel_to_snake - s = re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", name) - s = re.sub(r"([A-Z]+)([A-Z][a-z])", r"\1_\2", s) - result = s.lower() - result = result.replace("+", "_plus_") - result = result.replace("-", "_minus_") - result = re.sub(r"[^a-z0-9_]", "_", result) - result = re.sub(r"_+", "_", result) - return "test_" + result.strip("_") + ".py" - - -def discover_fillers(fillers_dir: Path) -> list[Path]: - """Walk a directory for *Filler.yml and *Filler.json files.""" - found: list[Path] = [] - for root, _dirs, files in os.walk(fillers_dir): - for f in sorted(files): - if f.endswith("Filler.yml") or f.endswith("Filler.json"): - found.append(Path(root) / f) - return found - - -def process_single_filler( - filler_path: Path, - fillers_base: Path, - output_dir: Path, - dry_run: bool = False, -) -> str: - """ - Process one filler file. - - Return "ok", "fail", "warn", or "skip". - """ - try: - if filler_path.stem in CONSOLIDATED_FILLERS: - logger.info( - "SKIP: %s (consolidated into a hand-written test)", - filler_path, - ) - return "skip" - - # Relative path for the generated test's ported_from marker - try: - rel_path = filler_path.relative_to(fillers_base.parent) - except ValueError: - rel_path = filler_path - - # Skip files that have been manually enhanced - category = rel_path.parts[-2] if len(rel_path.parts) >= 2 else "" - out_file = ( - output_dir / category / _filler_name_to_filename(filler_path.stem) - ) - if out_file.exists() and _has_manually_enhanced_tag(out_file): - logger.info( - "SKIP: %s (existing file has %s tag)", - out_file, - MANUALLY_ENHANCED_TAG, - ) - return "skip" - - # Load - test_name, model = load_filler(filler_path) - - # Analyze - ir = analyze(test_name, model, rel_path) - - # Render - source = render_test(ir) - - # Verify syntax - try: - ast.parse(source) - except SyntaxError as e: - logger.error( - "Syntax error in generated code for %s: %s", - filler_path, - e, - ) - return "fail" - - # Format - source = post_format(source) - - if dry_run: - print(f"[DRY-RUN] {filler_path} -> {ir.test_name}") - return "ok" - - # Write - out_file.parent.mkdir(parents=True, exist_ok=True) - - # Write __init__.py if needed - init_file = out_file.parent / "__init__.py" - if not init_file.exists(): - init_file.write_text( - f'"""Ported static tests: {category}.""" # noqa: N999\n' - ) - - out_file.write_text(source) - logger.info("OK: %s -> %s", filler_path.name, out_file) - return "ok" - - except Exception as e: - logger.error("FAIL: %s: %s", filler_path, e) - if logger.isEnabledFor(logging.DEBUG): - logger.debug("Traceback:", exc_info=True) - return "fail" - - -def main() -> None: - """Run the filler-to-python pipeline.""" - parser = argparse.ArgumentParser( - description="Convert static filler YAML/JSON to Python test files." - ) - parser.add_argument( - "--fillers", - type=Path, - required=True, - help="Directory containing *Filler.yml/*.json files.", - ) - parser.add_argument( - "--output", - type=Path, - required=True, - help="Output directory for generated .py test files.", - ) - parser.add_argument( - "--single", - type=Path, - default=None, - help="Process a single filler file instead of the whole directory.", - ) - parser.add_argument( - "--filter", - type=Path, - default=None, - help="Only convert fillers listed in this file (one path per line).", - ) - parser.add_argument( - "--dry-run", - action="store_true", - help="Parse and analyze but don't write files.", - ) - parser.add_argument( - "-v", - "--verbose", - action="store_true", - help="Enable verbose logging.", - ) - - args = parser.parse_args() - - logging.basicConfig( - level=logging.DEBUG if args.verbose else logging.INFO, - format="%(levelname)s: %(message)s", - ) - - if args.single: - filler_paths = [args.single] - else: - if not args.fillers.is_dir(): - logger.error("--fillers must be a directory: %s", args.fillers) - sys.exit(1) - filler_paths = discover_fillers(args.fillers) - - # Apply filter - if args.filter: - allowed = set() - for line in args.filter.read_text().splitlines(): - line = line.strip() - if line and not line.startswith("#"): - allowed.add(line) - filler_paths = [ - p for p in filler_paths if str(p) in allowed or p.name in allowed - ] - - if not filler_paths: - logger.warning("No filler files found.") - sys.exit(0) - - logger.info("Processing %d filler(s)...", len(filler_paths)) - - counts = {"ok": 0, "fail": 0, "warn": 0, "skip": 0} - for filler_path in filler_paths: - status = process_single_filler( - filler_path, - args.fillers, - args.output, - dry_run=args.dry_run, - ) - counts[status] += 1 - - # Summary - total = sum(counts.values()) - skip_msg = f", {counts['skip']} skipped" if counts["skip"] else "" - print( - f"\nDone: {counts['ok']}/{total} OK, " - f"{counts['fail']} failed, {counts['warn']} warnings" - f"{skip_msg}" - ) - if counts["fail"] > 0: - sys.exit(1) - - -if __name__ == "__main__": - main() diff --git a/scripts/filler_to_python/analyzer.py b/scripts/filler_to_python/analyzer.py deleted file mode 100644 index 62a90fcdba1..00000000000 --- a/scripts/filler_to_python/analyzer.py +++ /dev/null @@ -1,1940 +0,0 @@ -"""Analyze a parsed filler model and produce codegen IR.""" - -from __future__ import annotations - -import json -import logging -import re -import warnings -from pathlib import Path -from typing import Any - -import yaml -from execution_testing.base_types import Address -from execution_testing.base_types import Hash as EHash -from execution_testing.cli.evm_bytes import process_evm_bytes_string -from execution_testing.exceptions import TransactionException -from execution_testing.forks import get_forks -from execution_testing.specs import StateStaticTest -from execution_testing.specs.static_state.common import Tag, TagDict -from execution_testing.specs.static_state.common.tags import ( - ContractTag, - SenderKeyTag, - SenderTag, -) -from execution_testing.specs.static_state.expect_section import ( - ForkSet, -) -from execution_testing.specs.static_state.general_transaction import ( - GeneralTransactionInFiller, -) -from execution_testing.test_types import ( - EOA, - Alloc, - compute_create_address, - eoa_from_hash, -) -from execution_testing.vm import Op - -from .ir import ( - AccessListEntryIR, - AccountAssertionIR, - AccountIR, - EnvironmentIR, - ExpectEntryIR, - ImportsIR, - IntermediateTestModel, - ParameterCaseIR, - SenderIR, - TransactionIR, -) - -try: - from execution_testing.cli.pytest_commands.plugins.filler.static_filler import ( # noqa: E501 - NoIntResolver, - ) -except ImportError: - import yaml as _yaml - - class NoIntResolver(_yaml.SafeLoader): # type: ignore[no-redef] - """Fallback NoIntResolver.""" - - pass - - -logger = logging.getLogger(__name__) - -MAX_BYTECODE_OP_SIZE = 24576 -SLOW_CATEGORIES = { - "stQuadraticComplexityTest", - "stStaticCall", - "stTimeConsuming", -} - -# Ported tests (relative to ``tests/ported_static/``) that must keep -# hardcoded addresses. These do not converge under ``exact-no-stack`` -# with dynamic addresses because of patterns the analyzer's heuristics -# cannot cover: -# -# - EIP-2929 warm/cold gas accounting that depends on which addresses -# are warm at call time (baseline-specific layout). -# - CREATE2 collision semantics that depend on specific pre-state -# addresses colliding with computed CREATE2 targets. -# - Keccak-derived storage keys (Solidity mappings) baked into the -# pre-state on specific sender / contract addresses. -# - Structural transaction rejections sensitive to exact pre-state -# collisions (empty-but-code, init-colliding-with-non-empty). -# - Edge cases where dynamic allocation randomly picks an address -# with a leading zero byte, changing PUSH size. -# - Tag resolution mismatches (analyzer resolves <contract:0x…hint> -# to a fresh deterministic address, but baseline used the hint). -# -# Treat this list as an allowlist of "we've accepted the divergence -# here; don't try to make it dynamic". See trace-divergences.md for -# the per-file rationale. -FORCE_HARDCODED_TESTS: set[str] = { - # GAS_ONLY (29) — EIP-2929 warm/cold access cost differences - "stCallCodes/test_callcode_dynamic_code.py", - "stCallCodes/test_callcode_dynamic_code2_self_call.py", - "stCallCreateCallCodeTest/test_call1024_pre_calls.py", - "stCallCreateCallCodeTest/test_contract_creation_make_call_that_ask_more_gas_then_transaction_provided.py", # noqa: E501 - "stCreate2/test_returndatacopy_following_create.py", - "stCreateTest/test_create_collision_to_empty2.py", - "stCreateTest/test_create_transaction_refund_ef.py", - "stDelegatecallTestHomestead/test_call1024_pre_calls.py", - "stDelegatecallTestHomestead/test_delegatecode_dynamic_code2_self_call.py", # noqa: E501 - "stEIP150singleCodeGasPrices/test_eip2929_oog.py", - "stEIP2930/test_manual_create.py", - "stEIP3651_warmcoinbase/test_coinbase_warm_account_call_gas_fail.py", - "stEIP3855_push0/test_push0.py", - "stEIP3855_push0/test_push0_gas2.py", - "stHomesteadSpecific/test_contract_creation_oo_gdont_leave_empty_contract_via_transaction.py", # noqa: E501 - "stRandom/test_random_statetest282.py", - "stRandom/test_random_statetest287.py", - "stRandom/test_random_statetest384.py", - "stRandom2/test_random_statetest401.py", - "stRandom2/test_random_statetest508.py", - "stRevertTest/test_cost_revert.py", - "stRevertTest/test_revert_opcode_in_calls_on_non_empty_return_data.py", - "stRevertTest/test_revert_opcode_multiple_sub_calls.py", - "stRevertTest/test_revert_precompiled_touch_paris.py", - "stStackTests/test_underflow_test.py", - "stSystemOperationsTest/test_suicide_caller_addres_too_big_left.py", - "vmBitwiseLogicOperation/test_byte.py", - "vmIOandFlowOperations/test_jump_to_push.py", - "vmIOandFlowOperations/test_jumpi.py", - # EXECUTION_PATH_DIVERGED — remaining 8 (Categories B, C, D, E) - "stCreate2/test_create2_suicide.py", - "stCreate2/test_create2collision_code2.py", - "stCreate2/test_create2collision_selfdestructed2.py", - "stDelegatecallTestHomestead/test_delegatecall_in_initcode_to_existing_contract_oog.py", # noqa: E501 - "stLogTests/test_log1_non_empty_mem.py", - "stSystemOperationsTest/test_double_selfdestruct_touch_paris.py", - "stWalletTest/test_multi_owned_change_requirement_to1.py", - "stWalletTest/test_multi_owned_revoke_nothing.py", - # EXECUTION_PATH_DIVERGED + GAS (5) - "stCreate2/test_create2collision_code.py", - "stCreate2/test_create2collision_nonce.py", - "stCreate2/test_create2collision_selfdestructed.py", - "stCreate2/test_create2collision_selfdestructed_revert.py", - "stSStoreTest/test_sstore_gas_left.py", - # OUTPUT_DIFFERS — remaining 2 (Categories F, H) - "stEIP3651_warmcoinbase/test_coinbase_warm_account_call_gas.py", - "stWalletTest/test_multi_owned_is_owner_true.py", - # Precompile-as-EOA — tests fund precompile addresses as EOAs, - # then check nonce after calling the precompile. Dynamic EOAs - # land at different addresses than the precompile targets. - # STRUCTURAL — CREATE collision / EIP-3607 rejection behaviour. - # With dynamic addresses the collision doesn't happen, so the tx - # runs instead of being rejected → traces appear where baseline - # had none. - "stCreateTest/test_transaction_collision_to_empty_but_code.py", - "stCreateTest/test_transaction_collision_to_empty_but_nonce.py", - "stEIP3607/test_init_colliding_with_non_empty_account.py", - "stEIP3607/test_transaction_colliding_with_non_empty_account_calls.py", - "stEIP3607/test_transaction_colliding_with_non_empty_account_calls_itself.py", - "stEIP3607/test_transaction_colliding_with_non_empty_account_init_paris.py", - "stEIP3607/test_transaction_colliding_with_non_empty_account_send_paris.py", - # Remaining CI assertion failures — gas measurements, keccak storage, - # collision semantics, address-in-code, precompile interactions, etc. - # that are fundamentally incompatible with dynamic addresses. - "stBadOpcode/test_measure_gas.py", - "stBadOpcode/test_operation_diff_gas.py", - "stCallCodes/test_callcode_in_initcode_to_existing_contract_with_value_transfer.py", - "stCreate2/test_create2collision_balance.py", - "stCreate2/test_revert_depth_create_address_collision.py", - "stCreate2/test_revert_depth_create_address_collision_berlin.py", - "stCreateTest/test_create_empty_contract_with_storage.py", - "stCreateTest/test_transaction_collision_to_empty2.py", - "stDelegatecallTestHomestead/test_delegatecall_in_initcode_to_existing_contract.py", - "stEIP1153_transientStorage/test_trans_storage_ok.py", - "stEIP158Specific/test_call_one_v_call_suicide2.py", - "stInitCodeTest/test_out_of_gas_prefunded_contract_creation.py", - "stNonZeroCallsTest/test_non_zero_value_call_to_one_storage_key_paris.py", - "stNonZeroCallsTest/test_non_zero_value_callcode_to_one_storage_key_paris.py", - "stNonZeroCallsTest/test_non_zero_value_delegatecall_to_one_storage_key_paris.py", - "stNonZeroCallsTest/test_non_zero_value_suicide_to_empty_paris.py", - "stNonZeroCallsTest/test_non_zero_value_suicide_to_non_non_zero_balance.py", - "stNonZeroCallsTest/test_non_zero_value_suicide_to_one_storage_key_paris.py", - "stNonZeroCallsTest/test_non_zero_value_transaction_cal_lwith_data_to_one_storage_key_paris.py", - "stNonZeroCallsTest/test_non_zero_value_transaction_call_to_one_storage_key_paris.py", - "stPreCompiledContracts2/test_call_ecrecover0.py", - "stPreCompiledContracts2/test_call_ecrecover0_complete_return_value.py", - "stPreCompiledContracts2/test_call_ecrecover0_gas3000.py", - "stPreCompiledContracts2/test_call_ecrecover0_overlapping_input_output.py", - "stPreCompiledContracts2/test_call_ecrecover_check_length.py", - "stPreCompiledContracts2/test_call_ecrecover_v_prefixed0.py", - "stPreCompiledContracts2/test_callcode_ecrecover0.py", - "stPreCompiledContracts2/test_callcode_ecrecover0_complete_return_value.py", - "stPreCompiledContracts2/test_callcode_ecrecover0_gas3000.py", - "stPreCompiledContracts2/test_callcode_ecrecover0_overlapping_input_output.py", - "stPreCompiledContracts2/test_callcode_ecrecover_v_prefixed0.py", - "stRandom/test_random_statetest144.py", - "stRandom2/test_random_statetest642.py", - "stRandom2/test_random_statetest645.py", - "stRandom2/test_random_statetest646.py", - "stRevertTest/test_revert_depth_create_address_collision.py", - "stRevertTest/test_revert_in_create_in_init_paris.py", - "stRevertTest/test_revert_prefound.py", - "stRevertTest/test_revert_prefound_empty_paris.py", - "stSpecialTest/test_failed_create_reverts_deletion_paris.py", - "stSystemOperationsTest/test_create_hash_collision.py", - "stSystemOperationsTest/test_test_random_test.py", - "stWalletTest/test_day_limit_construction.py", - "stWalletTest/test_day_limit_construction_partial.py", - "stWalletTest/test_day_limit_reset_spent_today.py", - "stWalletTest/test_day_limit_set_daily_limit.py", - "stWalletTest/test_day_limit_set_daily_limit_no_data.py", - "stWalletTest/test_multi_owned_add_owner_add_myself.py", - "stWalletTest/test_multi_owned_change_owner_from_not_owner.py", - "stWalletTest/test_multi_owned_change_owner_no_argument.py", - "stWalletTest/test_multi_owned_change_owner_to_is_owner.py", - "stWalletTest/test_multi_owned_change_requirement_to0.py", - "stWalletTest/test_multi_owned_change_requirement_to2.py", - "stWalletTest/test_multi_owned_construction_correct.py", - "stWalletTest/test_multi_owned_remove_owner_by_non_owner.py", - "stWalletTest/test_multi_owned_remove_owner_my_self.py", - "stWalletTest/test_multi_owned_remove_owner_owner_is_not_owner.py", - "stWalletTest/test_wallet_change_requirement_remove_pending_transaction.py", - "stWalletTest/test_wallet_construction.py", - "stWalletTest/test_wallet_construction_oog.py", - "stWalletTest/test_wallet_construction_partial.py", - "stWalletTest/test_wallet_kill.py", - "stWalletTest/test_wallet_kill_to_wallet.py", - "stWalletTest/test_wallet_remove_owner_remove_pending_transaction.py", - "stZeroCallsRevert/test_zero_value_call_to_one_storage_key_oog_revert_paris.py", - "stZeroCallsRevert/test_zero_value_callcode_to_one_storage_key_oog_revert_paris.py", - "stZeroCallsRevert/test_zero_value_delegatecall_to_one_storage_key_oog_revert_paris.py", - "stZeroCallsRevert/test_zero_value_suicide_to_one_storage_key_oog_revert_paris.py", - "stZeroCallsTest/test_zero_value_call_to_one_storage_key_paris.py", - "stZeroCallsTest/test_zero_value_callcode_to_one_storage_key_paris.py", - "stZeroCallsTest/test_zero_value_delegatecall_to_one_storage_key_paris.py", - "stZeroCallsTest/test_zero_value_suicide_to_empty_paris.py", - "stZeroCallsTest/test_zero_value_suicide_to_non_zero_balance.py", - "stZeroCallsTest/test_zero_value_suicide_to_one_storage_key_paris.py", - "stZeroCallsTest/test_zero_value_transaction_cal_lwith_data_to_one_storage_key_paris.py", - "stZeroCallsTest/test_zero_value_transaction_call_to_one_storage_key_paris.py", - # Slow-marked tests that fail with dynamic addresses (excluded from - # the main verification by -m "not slow" but exercised on full CI - # runs without that filter — same KV_CALL_FLIP / collision / - # ecrecover patterns as the non-slow allowlisted siblings). - "stQuadraticComplexityTest/test_return50000.py", - "stQuadraticComplexityTest/test_return50000_2.py", - "stStaticCall/test_static_call_ecrecover0.py", - "stStaticCall/test_static_call_ecrecover0_complete_return_value.py", - "stStaticCall/test_static_call_ecrecover0_gas3000.py", - "stStaticCall/test_static_call_ecrecover0_overlapping_input_output.py", - "stStaticCall/test_static_call_ecrecover_check_length.py", - "stStaticCall/test_static_call_ecrecover_v_prefixed0.py", - "stStaticCall/test_static_call_to_call_code_op_code_check.py", - "stStaticCall/test_static_call_to_call_op_code_check.py", - "stStaticCall/test_static_call_to_del_call_op_code_check.py", - "stStaticCall/test_static_call_to_static_op_code_check.py", - "stStaticCall/test_static_check_opcodes.py", - "stStaticCall/test_static_check_opcodes2.py", - "stStaticCall/test_static_check_opcodes3.py", - "stStaticCall/test_static_check_opcodes4.py", - "stStaticCall/test_static_check_opcodes5.py", -} - - -def _ported_rel_path(filler_path: Path) -> str: - """Return the ``<category>/test_<snake>.py`` path for a filler.""" - category = filler_path.parent.name if filler_path.parent.name else "" - py_test_name = _filler_name_to_test_name(filler_path.stem) - return f"{category}/{py_test_name}.py" - - -class _AnalyzerAlloc(Alloc): - """Alloc subclass that supports fund_eoa for analysis.""" - - _eoa_counter: int = 0 - - def fund_eoa( - self, - _amount: Any = None, - _label: Any = None, - **_kwargs: Any, - ) -> EOA: - """Create a deterministic EOA for analysis.""" - self._eoa_counter += 1 - h = EHash(self._eoa_counter.to_bytes(32, "big")) - return eoa_from_hash(h, 0) - - -# --------------------------------------------------------------------------- -# Public API -# --------------------------------------------------------------------------- - - -def load_filler(path: Path) -> tuple[str, StateStaticTest]: - """Load a filler file and return (test_name, validated model).""" - with open(path) as f: - if path.suffix == ".json": - data = json.load(f) - else: - data = yaml.load(f, Loader=NoIntResolver) - - test_name = next(iter(data)) - model = StateStaticTest.model_validate(data[test_name]) - model.test_name = test_name - return test_name, model - - -def analyze( - test_name: str, - model: StateStaticTest, - filler_path: Path, -) -> IntermediateTestModel: - """Analyze a parsed filler model and produce codegen IR.""" - # 1. Gather all tag dependencies - all_deps: dict[str, Tag] = {} - all_deps.update(model.transaction.tag_dependencies()) - for expect in model.expect: - all_deps.update(expect.result.tag_dependencies()) - imports = ImportsIR() - - # 2. Resolve tags via pre-state setup - pre = _AnalyzerAlloc() - tags = model.pre.setup(pre, all_deps) - - # 2b. Honour precompile hint addresses for tagged EOAs. - # The static filler resolves ``<eoa:0x...01>`` through - # ``eoa_from_hash`` (random placeholder address). LLL source code - # however references those addresses literally (e.g. - # ``(call gas 0x01 ...)``), so the bytecode lands at the precompile - # while the funded EOA lands somewhere else. Override the resolved - # address back to the literal hint when it falls in the precompile - # range (0x01-0x10) — that way ``addr_to_var`` registers 0x01 and - # tx-data / post-state resolutions stay consistent with bytecode. - # - # Track the override addresses so that the corresponding EOA can - # be pinned non-dynamic later. Without pinning, the variable in - # the generated test (``addr_5``) would still go through - # ``pre.fund_eoa()`` at runtime and land at a random address, - # while ``tx_data`` would carry that random address — breaking - # any contract that calls the literal precompile (0x01). - pinned_eoa_addrs: set[Address] = set() - for tag in model.pre.root.keys(): - if not isinstance(tag, SenderTag): - continue - name = tag.name - if not ( - isinstance(name, str) and name.startswith("0x") and len(name) == 42 - ): - continue - try: - hint_int = int(name, 16) - except ValueError: - continue - if 1 <= hint_int <= 0x10: - hint_addr = Address(hint_int) - tags[tag.name] = hint_addr - pinned_eoa_addrs.add(hint_addr) - - # 3. Fork range (must sort chronologically, not alphabetically) - all_fork_names = [str(f) for f in sorted(get_forks())] - valid_forks_set = set(model.get_valid_at_forks()) - valid_forks_chrono = [f for f in all_fork_names if f in valid_forks_set] - valid_from = valid_forks_chrono[0] if valid_forks_chrono else "Cancun" - - valid_until: str | None = None - if valid_forks_chrono and valid_forks_chrono[-1] != all_fork_names[-1]: - valid_until = valid_forks_chrono[-1] - - # 4. Category from filler path - category = filler_path.parent.name if filler_path.parent.name else "" - - # 5. Build address -> variable name mapping - addr_to_var = _assign_variable_names(model, tags) - - # 5b. Resolve coinbase address for later use - coinbase_addr: Address | None = None - if isinstance(model.env.current_coinbase, Tag): - tag_name = model.env.current_coinbase.name - if tag_name in tags: - resolved = tags[tag_name] - if isinstance(resolved, Address): - coinbase_addr = resolved - else: - coinbase_addr = Address(int.from_bytes(resolved, "big")) - else: - coinbase_addr = model.env.current_coinbase - - # 6. Identify sender - sender_ir, sender_tag_name = _build_sender_ir(model, tags) - - # 7. Build TX arrays - probably_bytecode = model.transaction.to is None - tx_data, tx_gas, tx_value = _build_tx_arrays( - model.transaction, - tags, - addr_to_var, - probably_bytecode, - imports, - ) - - # 8. Parameter matrix - parameters = _build_parameters(model) - is_multi_case = len(parameters) > 1 - - # Detect fork-dependent single-case tests (multiple expect sections - # with different networks but only one (d, g, v) combo) - is_fork_dependent = not is_multi_case and len(model.expect) > 1 - - # 9. Build accounts - force_hardcoded = _ported_rel_path(filler_path) in FORCE_HARDCODED_TESTS - accounts = _build_accounts( - model, - tags, - addr_to_var, - sender_tag_name, - imports, - force_hardcoded=force_hardcoded, - coinbase_addr=coinbase_addr, - pinned_eoa_addrs=pinned_eoa_addrs, - ) - - # Track if sender is not in the pre-state (for fund_eoa handling). - # When True, the generated test uses pre.fund_eoa(amount=0) instead - # of EOA(key=...), matching the static fill's setup() step 7. - if sender_tag_name and not any(a.is_sender for a in accounts): - sender_ir.not_in_pre = True - - # 10. Build environment - environment_ir = _build_environment(model, tags, addr_to_var) - - # 11. Build expect entries - expect_entries = _build_expect_entries( - model, tags, addr_to_var, all_fork_names, imports - ) - - # 11b. If post-state has unresolvable addresses — either as account - # references (Address(0x...)) or as address-like storage values - # (large ints > 2^32 that weren't resolved to variable names) — - # disable dynamic for ALL accounts (including sender) so every - # address stays fixed and CREATE-derived addresses match baseline. - # Values above 2**32 are likely addresses, not small ints. - addr_like_threshold = 0x100000000 - has_unresolved = any( - "Address(0x" in a.var_ref - for entry in expect_entries - for a in entry.result - ) or any( - isinstance(v, int) and v >= addr_like_threshold - for entry in expect_entries - for a in entry.result - if a.storage is not None - for v in a.storage.values() - ) - if has_unresolved: - for acct in accounts: - acct.use_dynamic = False - - # Sender: dynamic unless unresolvable post-state or hardcoded allowlist. - sender_ir.use_dynamic = not force_hardcoded and not has_unresolved - - # 11c. Forced hardcoded (allowlist) also pins every EOA so coinbase - # rebinds and fund_eoa-generated EOAs don't leak into an otherwise - # hardcoded test. - if force_hardcoded: - for acct in accounts: - acct.use_dynamic = False - - # 12. Build transaction IR - transaction_ir, access_list_entries = _build_transaction_ir( - model, - tags, - addr_to_var, - tx_data, - tx_gas, - tx_value, - is_multi_case, - imports, - ) - - # 13. Address constants (non-tagged, non-sender addresses) - address_constants = _build_address_constants( - model, tags, addr_to_var, sender_tag_name, accounts - ) - - # 14. Import flags - if access_list_entries or any( - model.transaction.data[d.index].access_list is not None - for d in model.transaction.data - ): - imports.needs_access_list = True - - if ( - imports.needs_access_list - or model.transaction.blob_versioned_hashes is not None - ): - imports.needs_hash = True - - if any(p.has_exception for p in parameters): - imports.needs_tx_exception = True - - # 15. Filler comment - filler_comment = "" - if model.info and model.info.comment: - filler_comment = model.info.comment - - # 16. Test name - py_test_name = _filler_name_to_test_name(test_name) - - # 17. Whether the test mutates the pre-allocation. The framework's - # ``assert_mutable()`` is triggered by: - # * ``pre[var] = Account(...)`` (any non-dynamic account) - # * ``EOA(key=...)`` (non-dynamic sender) - # * ``pre.deploy_contract(address=...)`` (non-dynamic contract) - # * ``pre.deploy_contract(..., nonce=0)`` (default emit when the - # filler's account had nonce 0 or unset) - # * ``pre.fund_eoa(nonce=...)`` (dynamic sender with explicit - # nonce — used for high-nonce senders) - # Tests not hitting any of these can run under the ``execute`` plugin. - # The template only emits ``pre.fund_eoa(nonce=...)`` when - # ``sender.nonce`` is truthy, and ``pre.deploy_contract(..., nonce=N)`` - # always emits N (defaulting to 0 when ``account.nonce`` is None). - # Mirror those conditions exactly. - sender_emits_nonce_kwarg = bool(sender_ir.nonce) - contract_nonce_zero = any( - not a.is_eoa and (a.nonce is None or a.nonce == 0) for a in accounts - ) - needs_mutable_pre = ( - not sender_ir.use_dynamic - or sender_emits_nonce_kwarg - or any(not a.use_dynamic for a in accounts) - or contract_nonce_zero - ) - - return IntermediateTestModel( - test_name=py_test_name, - filler_path=str(filler_path), - filler_comment=filler_comment, - category=category, - valid_from=valid_from, - valid_until=valid_until, - is_slow=( - (model.info is not None and "slow" in model.info.pytest_marks) - or category in SLOW_CATEGORIES - ), - is_multi_case=is_multi_case, - is_fork_dependent=is_fork_dependent, - needs_mutable_pre=needs_mutable_pre, - environment=environment_ir, - accounts=accounts, - sender=sender_ir, - parameters=parameters, - transaction=transaction_ir, - expect_entries=expect_entries, - address_constants=address_constants, - tx_data=tx_data, - tx_gas=tx_gas, - tx_value=tx_value, - imports=imports, - ) - - -# --------------------------------------------------------------------------- -# Private helpers -# --------------------------------------------------------------------------- - - -def _camel_to_snake(name: str) -> str: - """Convert CamelCase to snake_case.""" - s = re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", name) - s = re.sub(r"([A-Z]+)([A-Z][a-z])", r"\1_\2", s) - return s.lower() - - -def _filler_name_to_test_name(filler_stem: str) -> str: - """Convert filler stem to Python test function name.""" - name = re.sub(r"Filler$", "", filler_stem) - result = "test_" + _camel_to_snake(name) - result = result.replace("+", "_plus_") - result = result.replace("-", "_minus_") - result = re.sub(r"[^a-z0-9_]", "_", result) - result = re.sub(r"_+", "_", result) - return result.strip("_") - - -def _classify_code_source(source: str) -> str: - """Classify code source and format as a comment block.""" - if not source or source.strip() == "": - return "" - - stripped = source.strip() - - if stripped.startswith(":yul"): - lang = "yul" - body = stripped[4:].strip() - elif stripped.startswith("{") or stripped.startswith("(asm"): - lang = "lll" - body = stripped - elif stripped.startswith(":abi"): - lang = "abi" - body = stripped[4:].strip() - elif stripped.startswith(":raw"): - lang = "raw" - body = stripped[4:].strip() - elif stripped.startswith("0x"): - lang = "hex" - body = stripped - else: - lang = "unknown" - body = stripped - - lines = body.split("\n") - if len(lines) > 30: - lines = lines[:30] + [f"... ({len(lines) - 30} more lines)"] - - comment_lines = [f" # Source: {lang}"] - for line in lines: - comment_lines.append(f" # {line}") - return "\n".join(comment_lines) - - -def _get_int_definitions( - addr_to_var: dict[Address | EOA, str] | None, -) -> dict[int, str]: - """ - Convert variable dictionary to int definitions used by the evm bytecode - parser. - """ - result: dict[int, str] = {} - if not addr_to_var: - return result - for k, v in addr_to_var.items(): - result[int.from_bytes(k, "big")] = v - return result - - -def _bytes_to_op_expr( - code_bytes: bytes, - addr_to_var: dict[Address | EOA, str] | None = None, -) -> str | None: - """Convert compiled bytecode to Op expression string.""" - if not code_bytes or len(code_bytes) > MAX_BYTECODE_OP_SIZE: - return None - - hex_str = code_bytes.hex() - if not hex_str: - return None - - try: - int_definitions = _get_int_definitions(addr_to_var) - op_str = process_evm_bytes_string( - hex_str, - assembly=False, - int_definitions=int_definitions, - ) - # Roundtrip check - compiled = eval( - op_str, {"Op": Op}, {v: k for k, v in int_definitions.items()} - ) # noqa: S307 - if compiled.hex() != hex_str.lower(): - return None - return op_str - except Exception: - return None - - -def _assign_variable_names( - model: StateStaticTest, tags: TagDict -) -> dict[Address | EOA, str]: - """Build address -> variable name mapping.""" - addr_to_var: dict[Address | EOA, str] = {} - contract_counter = 0 - - # Coinbase - coinbase_addr: Address | None = None - if isinstance(model.env.current_coinbase, Tag): - tag_name = model.env.current_coinbase.name - if tag_name in tags: - coinbase_addr = tags[tag_name] - else: - coinbase_addr = model.env.current_coinbase - - if coinbase_addr: - addr_to_var[coinbase_addr] = "coinbase" - - # Sender - sender_addr: Address | EOA | None = None - if isinstance(model.transaction.secret_key, SenderKeyTag): - tag_name = model.transaction.secret_key.name - if tag_name in tags: - sender_addr = tags[tag_name] - else: - # Non-tagged sender: derive address from key - sender_addr = EOA(key=model.transaction.secret_key) - - if sender_addr: - addr_to_var[sender_addr] = "sender" - - # Tagged pre-state accounts - for address_or_tag, _account in model.pre.root.items(): - if isinstance(address_or_tag, Tag): - tag_name = address_or_tag.name - if tag_name in tags: - addr = tags[tag_name] - if addr not in addr_to_var: - var_name = _sanitize_var_name( - tag_name, set(addr_to_var.values()) - ) - addr_to_var[addr] = var_name - - # Non-tagged pre-state accounts - for address_or_tag, _account in model.pre.root.items(): - if not isinstance(address_or_tag, Tag): - if address_or_tag not in addr_to_var: - var_name = f"contract_{contract_counter}" - contract_counter += 1 - addr_to_var[address_or_tag] = var_name - - # Transaction "to" address - if model.transaction.to is not None: - if isinstance(model.transaction.to, Tag): - tag_name = model.transaction.to.name - if tag_name in tags: - to_addr = tags[tag_name] - if to_addr not in addr_to_var: - var_name = _sanitize_var_name( - tag_name, set(addr_to_var.values()) - ) - addr_to_var[to_addr] = var_name - - return addr_to_var - - -def _sanitize_var_name(name: str, used: set[str]) -> str: - """Sanitize a tag name into a valid Python variable name.""" - var = re.sub(r"[^a-zA-Z0-9_]", "_", name) - var = re.sub(r"_+", "_", var).strip("_").lower() - if re.match(r"0x[0-9a-f]{40}", var): - # Some tagged tests use addresses as tags, which is confusing, remove - var = "addr" - if not var or var[0].isdigit(): - var = "addr_" + var - # Avoid Python keywords and builtins - _reserved = { - "type", - "hash", - "id", - "input", - "range", - "list", - "dict", - "return", - "class", - "def", - "for", - "if", - "else", - "elif", - "while", - "break", - "continue", - "pass", - "import", - "from", - "as", - "with", - "try", - "except", - "finally", - "raise", - "yield", - "lambda", - "global", - "nonlocal", - "assert", - "del", - "in", - "is", - "not", - "and", - "or", - "True", - "False", - "None", - "async", - "await", - "print", - "exec", - "eval", - "open", - "map", - "filter", - "set", - "bytes", - "int", - "str", - "float", - "bool", - "object", - "super", - "property", - "staticmethod", - "classmethod", - "abs", - "all", - "any", - "bin", - "hex", - "oct", - "len", - "max", - "min", - "pow", - "sum", - "zip", - } - if var in _reserved: - var = var + "_" - base = var - counter = 2 - while var in used: - var = f"{base}_{counter}" - counter += 1 - return var - - -def _addr_hex(addr: Address | EOA) -> str: - """Normalize an address-like value to hex string.""" - hex_str = str(addr)[2:].lstrip("0") - if hex_str == "": - hex_str = "0" - return f"0x{hex_str}" - - -def _build_sender_ir( - model: StateStaticTest, tags: TagDict -) -> tuple[SenderIR, str | None]: - """Build SenderIR and return (sender_ir, sender_tag_name).""" - if isinstance(model.transaction.secret_key, SenderKeyTag): - tag_name = model.transaction.secret_key.name - # Get the filler-derived key from tags (eoa_from_hash result) - resolved = tags.get(tag_name) - if isinstance(resolved, EOA): - key = resolved.key - assert key is not None - key_int = int.from_bytes(key, "big") - else: - key_int = 0 - # Find sender balance and nonce from pre-state - balance = 0 - nonce: int | None = None - for address_or_tag, account in model.pre.root.items(): - if isinstance(address_or_tag, SenderTag): - if address_or_tag.name == tag_name: - balance = int(account.balance) if account.balance else 0 - if account.nonce is not None: - nonce = int(account.nonce) - break - return ( - SenderIR( - is_tagged=False, key=key_int, balance=balance, nonce=nonce - ), - tag_name, - ) - else: - # Find sender balance and nonce from pre-state - eoa = EOA(key=model.transaction.secret_key) - sender_addr = _addr_hex(eoa) - balance = 0 - nonce = None - for address_or_tag, account in model.pre.root.items(): - if not isinstance(address_or_tag, Tag): - if _addr_hex(address_or_tag) == sender_addr: - balance = int(account.balance) if account.balance else 0 - if account.nonce is not None: - nonce = int(account.nonce) - break - return SenderIR( - is_tagged=False, - key=int.from_bytes(eoa.key, "big"), - balance=balance, - nonce=nonce, - ), None - - -def _build_parameters(model: StateStaticTest) -> list[ParameterCaseIR]: - """Build the (d, g, v) parameter matrix.""" - parameters: list[ParameterCaseIR] = [] - for d in model.transaction.data: - for g in range(len(model.transaction.gas_limit)): - for v in range(len(model.transaction.value)): - has_exc = False - for expect in model.expect: - if ( - expect.has_index(d.index, g, v) - and expect.expect_exception is not None - ): - has_exc = True - - # Build ID label (same logic as fill_function) - id_label = "" - if len(model.transaction.data) > 1 or d.label is not None: - if d.label is not None: - id_label = f"{d}" - else: - id_label = f"d{d}" - if len(model.transaction.gas_limit) > 1: - id_label += f"-g{g}" - if len(model.transaction.value) > 1: - id_label += f"-v{v}" - - marks = "pytest.mark.exception_test" if has_exc else None - - parameters.append( - ParameterCaseIR( - d=d.index, - g=g, - v=v, - has_exception=has_exc, - label=d.label, - id=id_label, - marks=marks, - ) - ) - return parameters - - -def _resolve_storage_values( - storage: dict[int, int], - addr_to_var: dict[Address | EOA, str], - imports: ImportsIR | None = None, -) -> dict[int, int | str]: - """Replace storage values matching known addresses with var names.""" - if not storage or not addr_to_var: - return storage - # Build int -> var_name lookup from addr_to_var - int_to_var: dict[int, str] = {} - for addr, var_name in addr_to_var.items(): - int_to_var[int.from_bytes(addr, "big")] = var_name - # Also build CREATE-derived address lookup - create_to_expr: dict[int, str] = {} - for addr, var_name in addr_to_var.items(): - for nonce in range(256): - created = compute_create_address(address=addr, nonce=nonce) - created_int = int.from_bytes(created, "big") - if created_int not in int_to_var: - create_to_expr[created_int] = ( - f"compute_create_address(address={var_name}," - f" nonce={nonce})" - ) - result: dict[int, int | str] = {} - for k, v in storage.items(): - if v in int_to_var: - result[k] = int_to_var[v] - elif v in create_to_expr: - if imports is not None: - imports.needs_compute_create_address = True - result[k] = create_to_expr[v] - else: - result[k] = v - return result - - -def _find_address_refs_in_bytecode( - code_bytes: bytes, - known_addresses: set[Address], -) -> dict[Address, int]: - """ - Find known addresses referenced in bytecode via PUSH. - - Return a mapping ``address -> minimum PUSH size observed``. - A push size < 20 means the baseline bytecode compiled the - address to fewer bytes (leading zero bytes); the referenced - contract must stay hardcoded so the compiler keeps emitting - the same short PUSH opcode and the trace stays aligned. - """ - refs: dict[Address, int] = {} - # Pre-compute int values for fast comparison - known_ints = {int.from_bytes(a, "big") for a in known_addresses} - i = 0 - while i < len(code_bytes): - opcode = code_bytes[i] - if 0x60 <= opcode <= 0x7F: # PUSH1..PUSH32 - push_size = opcode - 0x5F - push_data = code_bytes[i + 1 : i + 1 + push_size] - if len(push_data) == push_size: - # Addresses with leading zero bytes are compiled to - # a PUSH smaller than PUSH20 (down to PUSH1 for 1-byte - # addresses like 0x01). Match on int value against the - # known-address set — false positives would require a - # PUSHn that happens to push exactly a value already - # registered as a pre-state address, which is rare in - # practice. - val = int.from_bytes(push_data, "big") - if val in known_ints: - addr = Address(val) - if addr not in refs or push_size < refs[addr]: - refs[addr] = push_size - i += 1 + push_size - else: - i += 1 - return refs - - -def _topological_sort_contracts( - contract_addrs: list[Address], - deps: dict[Address, set[Address]], -) -> tuple[list[Address], set[Address]]: - """ - Return (sorted_addresses, cycle_addresses). - - If A's bytecode references B, B must be deployed before A. - """ - addr_set = set(contract_addrs) - # forward[B] = {A} means A depends on B, so B must come first - forward: dict[Address, set[Address]] = {a: set() for a in addr_set} # noqa: C420 - in_deg: dict[Address, int] = dict.fromkeys(addr_set, 0) - for a, dep_set in deps.items(): - if a not in addr_set: - continue - for b in dep_set: - if b in addr_set: - forward[b].add(a) - in_deg[a] += 1 - - # Kahn's algorithm - queue = [a for a in contract_addrs if in_deg[a] == 0] - sorted_addrs: list[Address] = [] - while queue: - node = queue.pop(0) - sorted_addrs.append(node) - for neighbor in forward[node]: - in_deg[neighbor] -= 1 - if in_deg[neighbor] == 0: - queue.append(neighbor) - - cycle_addrs = addr_set - set(sorted_addrs) - return sorted_addrs, cycle_addrs - - -def _build_accounts( - model: StateStaticTest, - tags: TagDict, - addr_to_var: dict[Address | EOA, str], - sender_tag_name: str | None, - imports: ImportsIR, - *, - force_hardcoded: bool = False, - coinbase_addr: Address | None = None, - pinned_eoa_addrs: set[Address] | None = None, -) -> list[AccountIR]: - """Build AccountIR list with dependency-ordered contracts.""" - if pinned_eoa_addrs is None: - pinned_eoa_addrs = set() - # ------------------------------------------------------------------ - # Pass 1: gather account metadata and compile bytecode (no Op yet) - # ------------------------------------------------------------------ - raw_accounts: list[AccountIR] = [] - # Map address -> compiled code_bytes for contracts (for dep analysis) - code_bytes_map: dict[Address, bytes] = {} - - for address_or_tag, account in model.pre.root.items(): - is_tagged = isinstance(address_or_tag, Tag) - # SenderTag type is always EOA, ContractTag is always contract - is_eoa = isinstance(address_or_tag, SenderTag) if is_tagged else False - is_sender = False - - if is_tagged: - tag_name = address_or_tag.name - if sender_tag_name and tag_name == sender_tag_name: - is_sender = True - is_eoa = True - resolved = tags.get(tag_name) - var_name = ( - addr_to_var.get(resolved, tag_name) - if resolved is not None - else tag_name - ) - address = resolved - else: - address_str = str(address_or_tag) - var_name = addr_to_var.get( - address_or_tag, f"addr_{address_str[:10]}" - ) - # Check if this non-tagged address is the sender - if addr_to_var.get(address_or_tag) == "sender": - is_sender = True - is_eoa = True - address = address_or_tag - - assert not var_name.startswith("0x") - - # Determine if non-tagged account has code (is a contract) - has_code = account.code is not None and account.code.source.strip() - if not is_tagged and not is_eoa and not has_code: - # Non-tagged, no code — treat as EOA - is_eoa = True - - # Compile code but defer Op expression conversion - source_comment = "" - code_bytes: bytes = b"" - oversized_code = False - if has_code: - source_comment = _classify_code_source(account.code.source) - try: - code_bytes = account.code.compiled(tags) - if len(code_bytes) > MAX_BYTECODE_OP_SIZE: - oversized_code = True - except Exception as e: - warnings.warn( - f"Code compilation failed for {var_name}: {e}", - stacklevel=2, - ) - - # Storage - storage: dict[int, int | str] = {} - if account.storage and account.storage.root: - resolved_storage = account.storage.resolve(tags) - for k, v in resolved_storage.items(): - storage[int(k)] = int(v) - storage = _resolve_storage_values(storage, addr_to_var, imports) - - # Balance and nonce - balance = int(account.balance) if account.balance is not None else 0 - nonce = int(account.nonce) if account.nonce is not None else None - - acct_ir = AccountIR( - var_name=var_name, - is_tagged=is_tagged, - is_eoa=is_eoa, - is_sender=is_sender, - balance=balance, - nonce=nonce, - address=address, - source_comment=source_comment, - code_expr="", - storage=storage, - oversized_code=oversized_code, - use_dynamic=True, - ) - - # Oversized contracts must keep hardcoded address - if oversized_code: - acct_ir.use_dynamic = False - - # Coinbase account must keep hardcoded address so - # Environment(fee_recipient=coinbase) and the pre-state - # entry refer to the same address. - if ( - coinbase_addr is not None - and address is not None - and int.from_bytes(address, "big") - == int.from_bytes(coinbase_addr, "big") - ): - acct_ir.use_dynamic = False - - raw_accounts.append(acct_ir) - if code_bytes and address is not None: - code_bytes_map[address] = code_bytes - - # ------------------------------------------------------------------ - # Build dependency graph and topological sort for contracts - # ------------------------------------------------------------------ - known_contract_addrs: set[Address] = set() - for acct in raw_accounts: - if not acct.is_eoa and acct.address is not None: - known_contract_addrs.add(acct.address) - - # All known addresses (contracts + EOAs) for bytecode ref scanning - all_known_addrs: set[Address] = set() - for addr_or_eoa in addr_to_var: - if isinstance(addr_or_eoa, Address): - all_known_addrs.add(addr_or_eoa) - else: - all_known_addrs.add(Address(int.from_bytes(addr_or_eoa, "big"))) - - # Pre-state EOA addresses — used to recognise short-PUSH refs that - # point at funded EOAs (e.g. precompile addresses 0x01-0x10 listed - # as ``<eoa:0x...01>`` in the filler) instead of contracts. - known_eoa_addrs: set[Address] = set() - for acct in raw_accounts: - if acct.is_eoa and acct.address is not None: - known_eoa_addrs.add(acct.address) - - deps: dict[Address, set[Address]] = {} - # Contract addresses referenced via PUSH<20 anywhere: baseline - # bytecode compiled them to a short PUSH because of leading zero - # bytes, so they must stay hardcoded to keep the opcode sequence - # aligned. - short_push_refs: set[Address] = set() - # EOA addresses referenced via PUSH<20 — pin those EOAs to their - # literal address so the funded account lands at the precompile - # (e.g. 0x01) instead of a random ``pre.fund_eoa`` address. - short_push_eoa_refs: set[Address] = set() - # True when a short-PUSH ref targets an address that is neither a - # pre-state contract nor a pre-state EOA (e.g. an external tag - # like <contract:0x...dead> only referenced from bytecode). No - # account can be pinned, so fall back to globally disabling - # dynamic addresses for the whole test. - short_push_unpinnable = False - for addr, cb in code_bytes_map.items(): - refs = _find_address_refs_in_bytecode(cb, all_known_addrs) - # Track deps on other contracts. Keep self-references — they - # create self-loops detected as cycles, forcing hardcoded addr. - deps[addr] = set(refs) & known_contract_addrs - for ref_addr, push_size in refs.items(): - if push_size < 20: - if ref_addr in known_contract_addrs: - short_push_refs.add(ref_addr) - elif ref_addr in known_eoa_addrs: - short_push_eoa_refs.add(ref_addr) - else: - short_push_unpinnable = True - - contract_addrs_ordered = [ - acct.address - for acct in raw_accounts - if not acct.is_eoa and acct.address is not None - ] - sorted_addrs, cycle_addrs = _topological_sort_contracts( - contract_addrs_ordered, deps - ) - - # Mark cycle contracts as non-dynamic, then propagate: any contract - # referenced by a non-dynamic contract must also be non-dynamic - # (because the non-dynamic bytecode contains the old address). - non_dynamic_addrs = set(cycle_addrs) - for acct in raw_accounts: - if acct.oversized_code and acct.address is not None: - non_dynamic_addrs.add(acct.address) - # Short-PUSH refs: pin the referenced contract so its address keeps - # the same leading-zero profile as baseline. - non_dynamic_addrs.update(short_push_refs) - - changed = True - while changed: - changed = False - for addr in list(non_dynamic_addrs): - for ref in deps.get(addr, set()): - if ( - ref not in non_dynamic_addrs - and ref in known_contract_addrs - ): - non_dynamic_addrs.add(ref) - changed = True - - for acct in raw_accounts: - if acct.address in non_dynamic_addrs: - acct.use_dynamic = False - - # Pin EOAs whose addresses are referenced via short PUSH so the - # funded account lands at the literal address (e.g. precompile - # 0x01) instead of a random ``pre.fund_eoa`` address. - for acct in raw_accounts: - if acct.address in short_push_eoa_refs: - acct.use_dynamic = False - - # Pin EOAs whose tags were hint-overridden into the precompile - # range. The override registered the literal address in - # ``addr_to_var`` so tx-data and post-state resolutions point at - # ``addr_X`` (a variable). The variable must hold the literal - # precompile address at runtime, not whatever ``pre.fund_eoa`` - # picks. - for acct in raw_accounts: - if acct.address in pinned_eoa_addrs: - acct.use_dynamic = False - - # ------------------------------------------------------------------ - # Pass 2: convert bytecode to Op expressions - # ------------------------------------------------------------------ - # Collect all address variable names for arithmetic detection - addr_var_names = set(addr_to_var.values()) - - for acct in raw_accounts: - cb = code_bytes_map.get(acct.address) if acct.address else None - if not cb: - continue - try: - if acct.use_dynamic: - # Try with addr_to_var for symbolic references - op_expr = _bytes_to_op_expr(cb, addr_to_var) - if op_expr is None: - # Fallback: without addr_to_var (keep dynamic) - op_expr = _bytes_to_op_expr(cb) - else: - op_expr = _bytes_to_op_expr(cb) - - if op_expr: - acct.code_expr = op_expr - imports.needs_op = True - elif cb: - acct.code_expr = f'bytes.fromhex("{cb.hex()}")' - except Exception: - acct.code_expr = 'b""' - - # ------------------------------------------------------------------ - # Check for address variables used in arithmetic operations. - # Pattern: Op.ADD(contract_0, ...) means contracts are at - # sequential addresses and cannot be dynamically assigned. - # If found, disable dynamic for ALL contracts. - # ------------------------------------------------------------------ - arith_ops = {"Op.ADD(", "Op.SUB(", "Op.MUL(", "Op.DIV("} - has_addr_arithmetic = False - for acct in raw_accounts: - if not acct.code_expr: - continue - for var_name in addr_var_names: - for arith_op in arith_ops: - if f"{arith_op}{var_name}" in acct.code_expr: - has_addr_arithmetic = True - break - if has_addr_arithmetic: - break - if has_addr_arithmetic: - break - - # ------------------------------------------------------------------ - # Computed call targets: CALL/STATICCALL/DELEGATECALL/CALLCODE - # receiving `address=` from arithmetic or memory reads. Tests that - # do this usually rely on specific pre-state contract addresses - # (dispatch-by-offset) and won't survive dynamic allocation. - # ------------------------------------------------------------------ - computed_addr_patterns = ( - "address=Op.ADD(", - "address=Op.SUB(", - "address=Op.MUL(", - "address=Op.DIV(", - "address=Op.MOD(", - "address=Op.MLOAD(", - "address=Op.SLOAD(", - "address=Op.CALLDATALOAD(", - ) - has_computed_call_target = False - for acct in raw_accounts: - if not acct.code_expr: - continue - for pat in computed_addr_patterns: - if pat in acct.code_expr: - has_computed_call_target = True - break - if has_computed_call_target: - break - - if ( - has_addr_arithmetic - or short_push_unpinnable - or has_computed_call_target - or force_hardcoded - ): - # Disable dynamic for all contracts and re-generate Op - # expressions without addr_to_var. Triggers: - # * address arithmetic (Op.ADD(var, ...)) assumes sequential - # addresses that dynamic allocation can't preserve. - # * a short-PUSH ref that points outside the pre-state has no - # contract to pin, so the whole test must keep the filler's - # resolved addresses. - # * computed call targets (CALL with address=Op.ADD/MLOAD/ - # CALLDATALOAD/...) dispatch by baseline-relative offsets. - # * the test is on the FORCE_HARDCODED_TESTS allowlist — we've - # accepted that it can't converge under exact-no-stack with - # dynamic addresses (see module docstring on that set). - for acct in raw_accounts: - if not acct.is_eoa: - acct.use_dynamic = False - cb = code_bytes_map.get(acct.address) if acct.address else None - if not cb: - continue - try: - op_expr = _bytes_to_op_expr(cb) - if op_expr: - acct.code_expr = op_expr - elif cb: - acct.code_expr = f'bytes.fromhex("{cb.hex()}")' - except Exception: - acct.code_expr = 'b""' - - # ------------------------------------------------------------------ - # Reorder: EOAs first (filler order), then contracts (topo order) - # ------------------------------------------------------------------ - eoa_accounts = [a for a in raw_accounts if a.is_eoa] - contract_by_addr = {a.address: a for a in raw_accounts if not a.is_eoa} - # Sorted contracts first, then any cycle contracts in filler order - ordered_contracts: list[AccountIR] = [] - for addr in sorted_addrs: - if addr in contract_by_addr: - ordered_contracts.append(contract_by_addr[addr]) - # Append cycle contracts (non-dynamic) in their original filler order - for acct in raw_accounts: - if not acct.is_eoa and acct.address in cycle_addrs: - ordered_contracts.append(acct) - - return eoa_accounts + ordered_contracts - - -def _build_environment( - model: StateStaticTest, - tags: TagDict, - addr_to_var: dict[Address | EOA, str], -) -> EnvironmentIR: - """Build EnvironmentIR.""" - # Resolve coinbase - if isinstance(model.env.current_coinbase, Tag): - tag_name = model.env.current_coinbase.name - resolved = tags.get(tag_name) - coinbase_var = ( - addr_to_var.get(resolved, tag_name) if resolved else tag_name - ) - else: - coinbase_var = addr_to_var.get(model.env.current_coinbase, "coinbase") - - return EnvironmentIR( - coinbase_var=coinbase_var, - number=int(model.env.current_number), - timestamp=int(model.env.current_timestamp), - difficulty=( - int(model.env.current_difficulty) - if model.env.current_difficulty is not None - else None - ), - prev_randao=( - int(model.env.current_random) - if model.env.current_random is not None - else None - ), - base_fee_per_gas=( - int(model.env.current_base_fee) - if model.env.current_base_fee is not None - else None - ), - excess_blob_gas=( - int(model.env.current_excess_blob_gas) - if model.env.current_excess_blob_gas is not None - else None - ), - gas_limit=int(model.env.current_gas_limit), - ) - - -def _fork_set_to_constraints( - fork_set: ForkSet, all_fork_names: list[str] -) -> list[str]: - """Reconstruct constraint strings from an expanded ForkSet.""" - set_fork_names = sorted( - [str(f) for f in fork_set], - key=lambda f: all_fork_names.index(f) if f in all_fork_names else 999, - ) - - if not set_fork_names: - return [] - - if len(set_fork_names) == 1: - return [set_fork_names[0]] - - # Try to detect contiguous ranges - groups: list[list[str]] = [] - group: list[str] = [set_fork_names[0]] - for i in range(1, len(set_fork_names)): - curr_idx = all_fork_names.index(set_fork_names[i]) - prev_idx = all_fork_names.index(set_fork_names[i - 1]) - if curr_idx == prev_idx + 1: - group.append(set_fork_names[i]) - else: - groups.append(group) - group = [set_fork_names[i]] - groups.append(group) - - constraints: list[str] = [] - for g in groups: - if len(g) == 1: - constraints.append(g[0]) - else: - last_idx = all_fork_names.index(g[-1]) - if last_idx == len(all_fork_names) - 1: - constraints.append(f">={g[0]}") - else: - next_fork = all_fork_names[last_idx + 1] - constraints.append(f">={g[0]}<{next_fork}") - return constraints - - -def _format_exception_value( - exc: Any, -) -> str: - """Format a TransactionException value as a Python expression string.""" - if isinstance(exc, list): - parts = [f"TransactionException.{e.name}" for e in exc] - return "[" + ", ".join(parts) + "]" - if isinstance(exc, TransactionException): - return f"TransactionException.{exc.name}" - return str(exc) - - -def _build_expect_entries( - model: StateStaticTest, - tags: TagDict, - addr_to_var: dict[Address | EOA, str], - all_fork_names: list[str], - imports: ImportsIR, -) -> list[ExpectEntryIR]: - """Build ExpectEntryIR list.""" - entries: list[ExpectEntryIR] = [] - - for expect in model.expect: - # Indexes - indexes = { - "data": expect.indexes.data, - "gas": expect.indexes.gas, - "value": expect.indexes.value, - } - - # Network constraints - network = _fork_set_to_constraints(expect.network, all_fork_names) - - # Result: resolve and map to assertions - result_assertions: list[AccountAssertionIR] = [] - for address_or_tag, account_expect in expect.result.root.items(): - if isinstance(address_or_tag, Tag): - # Use resolve() for all tags — handles CreateTag's - # address derivation (compute_create_address etc.) - try: - addr = address_or_tag.resolve(tags) - var_ref = _resolve_address(addr, addr_to_var, imports) - except (KeyError, AssertionError): - tag_name = address_or_tag.name - addr = tags.get(tag_name, tag_name) - assert not isinstance(addr, str) - var_ref = _resolve_address(addr, addr_to_var, imports) - else: - var_ref = _resolve_address( - address_or_tag, addr_to_var, imports - ) - - if account_expect is None: - # shouldnotexist - result_assertions.append( - AccountAssertionIR( - var_ref=var_ref, - should_not_exist=True, - ) - ) - continue - - # Storage (including ANY keys) - storage: dict[int, int | str] | None = None - storage_any_keys: list[int] = [] - if account_expect.storage is not None: - storage = {} - resolved_storage = account_expect.storage.resolve(tags) - for k, v in resolved_storage.items(): - storage[int(k)] = int(v) - storage = _resolve_storage_values( - storage, addr_to_var, imports - ) - # Capture ANY keys from _any_map - if hasattr(resolved_storage, "_any_map"): - for k in resolved_storage._any_map: - storage_any_keys.append(int(k)) - - # Code - code: bytes | None = None - if account_expect.code is not None: - try: - code = account_expect.code.compiled(tags) - except Exception: - pass - - result_assertions.append( - AccountAssertionIR( - var_ref=var_ref, - storage=storage, - storage_any_keys=storage_any_keys, - code=code, - balance=( - int(account_expect.balance) - if account_expect.balance is not None - else None - ), - nonce=( - int(account_expect.nonce) - if account_expect.nonce is not None - else None - ), - ) - ) - - # Exception - expect_exc: dict[str, str] | None = None - if expect.expect_exception is not None: - expect_exc = {} - for fork_set_key in expect.expect_exception: - constraint_strs = _fork_set_to_constraints( - fork_set_key, all_fork_names - ) - constraint_key = ",".join(constraint_strs) - exc_value = expect.expect_exception.root[fork_set_key] - expect_exc[constraint_key] = _format_exception_value(exc_value) - - entries.append( - ExpectEntryIR( - indexes=indexes, - network=network, - result=result_assertions, - expect_exception=expect_exc, - ) - ) - - return entries - - -def _build_transaction_ir( - model: StateStaticTest, - tags: TagDict, - addr_to_var: dict[Address | EOA, str], - tx_data: list[str], - tx_gas: list[int], - tx_value: list[int], - is_multi_case: bool, - imports: ImportsIR, -) -> tuple[TransactionIR, list[AccessListEntryIR]]: - """Build TransactionIR. Return (transaction_ir, access_list_entries).""" - # Resolve "to" - to_var: str | None = None - to_is_none = False - if model.transaction.to is None: - to_is_none = True - elif isinstance(model.transaction.to, Tag): - tag_name = model.transaction.to.name - resolved = tags.get(tag_name) - if resolved: - to_var = addr_to_var.get(resolved, tag_name) - else: - to_var = tag_name - else: - to_var = _resolve_address(model.transaction.to, addr_to_var, imports) - - # Access lists — check if they vary per data entry - access_list_entries: list[AccessListEntryIR] = [] - per_data_access_lists: dict[int, list[AccessListEntryIR]] | None = None - - def _resolve_access_list(data_box_al): - entries = [] - for al_entry in data_box_al: - if isinstance(al_entry.address, Tag): - resolved_al = al_entry.address.resolve(tags) - al_address = Address(resolved_al) - else: - al_address = al_entry.address - # Try to resolve to variable name - var_name = addr_to_var.get(al_address) - if var_name: - al_addr_str = var_name - al_dynamic = True - else: - al_addr_str = str(al_address) - al_dynamic = False - al_keys = [str(k) for k in al_entry.storage_keys] - entries.append( - AccessListEntryIR( - address=al_addr_str, - storage_keys=al_keys, - use_dynamic=al_dynamic, - ) - ) - return entries - - # Check if any data entry has access lists - has_any_al = any( - model.transaction.data[d.index].access_list is not None - for d in model.transaction.data - ) - if has_any_al and is_multi_case: - # Build per-data access list map. - # Include entries where access_list is not None (even if empty []) - # because access_list=[] makes the tx type-2 (EIP-2930), while - # access_list=None keeps it legacy. - per_data_al: dict[int, list[AccessListEntryIR]] = {} - for d in model.transaction.data: - data_box = model.transaction.data[d.index] - if data_box.access_list is not None: - per_data_al[d.index] = _resolve_access_list( - data_box.access_list - ) - if per_data_al: - per_data_access_lists = per_data_al - elif has_any_al: - # Single-case: use first data entry's access list - first_data = model.transaction.data[0] - if first_data.access_list is not None: - access_list_entries = _resolve_access_list(first_data.access_list) - - # Blob versioned hashes - blob_hashes: list[str] | None = None - if model.transaction.blob_versioned_hashes is not None: - blob_hashes = [str(h) for h in model.transaction.blob_versioned_hashes] - - # Single-case inlines - data_inline: str | None = None - gas_limit_single: int | None = None - value_single: int | None = None - if not is_multi_case: - if tx_data and tx_data[0]: - data_inline = tx_data[0] - else: - data_inline = "b''" - gas_limit_single = tx_gas[0] if tx_gas else 21000 - value_single = tx_value[0] if tx_value else 0 - - return ( - TransactionIR( - to_var=to_var, - to_is_none=to_is_none, - gas_price=( - int(model.transaction.gas_price) - if model.transaction.gas_price is not None - else None - ), - max_fee_per_gas=( - int(model.transaction.max_fee_per_gas) - if model.transaction.max_fee_per_gas is not None - else None - ), - max_priority_fee_per_gas=( - int(model.transaction.max_priority_fee_per_gas) - if model.transaction.max_priority_fee_per_gas is not None - else None - ), - max_fee_per_blob_gas=( - int(model.transaction.max_fee_per_blob_gas) - if model.transaction.max_fee_per_blob_gas is not None - else None - ), - blob_versioned_hashes=blob_hashes, - nonce=( - int(model.transaction.nonce) - if model.transaction.nonce is not None - else None - ), - access_list=access_list_entries if has_any_al else None, - per_data_access_lists=per_data_access_lists, - data_inline=data_inline, - gas_limit=gas_limit_single, - value=value_single, - ), - access_list_entries, - ) - - -def _build_address_constants( - model: StateStaticTest, - tags: TagDict, - addr_to_var: dict[Address | EOA, str], - sender_tag_name: str | None, - accounts: list[AccountIR], -) -> list[dict[str, str]]: - """Build list of address constants for the function body.""" - constants: list[dict[str, str]] = [] - seen: set[Address | EOA] = set() - - # Collect addresses of dynamic EOAs — these are handled by - # pre.fund_eoa() in the accounts section, not as constants. - dynamic_eoa_addrs: set[Address | EOA] = set() - for acct in accounts: - if acct.is_eoa and acct.use_dynamic and acct.address is not None: - dynamic_eoa_addrs.add(acct.address) - - # Coinbase (tagged or not) — always keep as hardcoded constant - if isinstance(model.env.current_coinbase, Tag): - tag_name = model.env.current_coinbase.name - resolved = tags.get(tag_name) - if resolved: - var_name = addr_to_var.get(resolved, "coinbase") - if var_name != "sender" and resolved not in seen: - constants.append({"var_name": var_name, "hex": f"{resolved}"}) - seen.add(resolved) - else: - addr = model.env.current_coinbase - var_name = addr_to_var.get(model.env.current_coinbase) - if var_name and var_name != "sender" and addr not in seen: - constants.append({"var_name": var_name, "hex": f"{addr}"}) - seen.add(addr) - - # All non-sender, non-contract pre-state accounts (tagged or not) - for address_or_tag, _acct in model.pre.root.items(): - if isinstance(address_or_tag, Tag): - tag_name = address_or_tag.name - # Skip sender - if sender_tag_name and tag_name == sender_tag_name: - continue - # Skip ContractTag accounts (they get address via deploy_contract) - # SenderTag accounts are EOAs even if they have code - if isinstance(address_or_tag, ContractTag): - continue - resolved = tags.get(tag_name) - if resolved: - # Skip dynamic EOAs (handled by fund_eoa) - if resolved in dynamic_eoa_addrs: - continue - var_name = addr_to_var.get(resolved, tag_name) - if resolved not in seen and var_name != "coinbase": - constants.append( - {"var_name": var_name, "hex": f"{resolved}"} - ) - seen.add(resolved) - else: - # Skip dynamic EOAs (handled by fund_eoa) - if address_or_tag in dynamic_eoa_addrs: - continue - var_name = addr_to_var.get(address_or_tag) - if ( - var_name - and var_name != "sender" - and var_name != "coinbase" - and address_or_tag not in seen - ): - constants.append( - {"var_name": var_name, "hex": f"{address_or_tag}"} - ) - seen.add(address_or_tag) - - return constants - - -def _decode_tx_data_word( - data: bytes, addr_to_var: dict[Address | EOA, str], imports: ImportsIR -) -> str: - """ - Attempt to decode a single word of 32 or 20 bytes from the transaction - data into meaningful information. - """ - addr_var: str | None = None - if len(data.lstrip(b"\x00")) <= 20: - maybe_addr = Address(int.from_bytes(data, "big")) - if maybe_addr in addr_to_var: - addr_var = addr_to_var[maybe_addr] - - if len(data) == 32 or len(data) == 20: - if addr_var: - if len(data) == 20: - return addr_var - else: - imports.needs_hash = True - return f"Hash({addr_var}, left_padding=True)" - else: - if len(data) == 32: - imports.needs_hash = True - hex_type = "Hash" - else: - hex_type = "Address" - hex_string = data.hex().lstrip("0") - if len(hex_string) == 0: - hex_string = "0" - return f"{hex_type}(0x{hex_string})" - else: - imports.needs_bytes = True - hex_string = data.hex() - return f'Bytes("{hex_string}")' - - -def _decode_tx_data( - data: bytes, - addr_to_var: dict[Address | EOA, str], - probably_bytecode: bool, - imports: ImportsIR, -) -> str: - """Attempt to decode meaningful information from the transaction data.""" - if probably_bytecode: - bytecode = _bytes_to_op_expr(data, addr_to_var) - if bytecode: - imports.needs_op = True - return bytecode - decoded_words: list[str] = [] - if len(data) > 0 and len(data) % 32 in (0, 4): - if len(data) % 32 == 4: - decoded_words.append( - _decode_tx_data_word(data[:4], addr_to_var, imports) - ) - offset = 4 if len(data) % 32 == 4 else 0 - for i in range(offset, len(data), 32): - decoded_words.append( - _decode_tx_data_word(data[i : i + 32], addr_to_var, imports) - ) - else: - return _decode_tx_data_word(data, addr_to_var, imports) - return " + ".join(decoded_words) - - -def _build_tx_arrays( - tx: GeneralTransactionInFiller, - tags: TagDict, - addr_to_var: dict[Address | EOA, str], - probably_bytecode: bool, - imports: ImportsIR, -) -> tuple[list[str], list[int], list[int]]: - """Build the list of data that goes in each transaction.""" - tx_data: list[str] = [] - for d_entry in tx.data: - data_box = tx.data[d_entry.index] - compiled = data_box.data.compiled(tags) - tx_data.append( - _decode_tx_data(compiled, addr_to_var, probably_bytecode, imports) - ) - - tx_gas = [int(g) for g in tx.gas_limit] - tx_value = [int(v) for v in tx.value] - return tx_data, tx_gas, tx_value - - -def _resolve_address( - addr: Address, - addr_to_var: dict[Address | EOA, str], - imports: ImportsIR, -) -> str: - """ - Return a variable reference if the address or an address derived from it - is contained in the `addr_to_var` dictionary. - - Fallbacks to returning f"Address({addr})". - """ - for var_addr, var in addr_to_var.items(): - if addr == var_addr: - return var - # Check if the address is the result of contract creation from a known - # address. Use a larger range to cover high-nonce senders. - for var_addr, var in addr_to_var.items(): - for nonce in range(10000): - if addr == compute_create_address(address=var_addr, nonce=nonce): - imports.needs_compute_create_address = True - return f"compute_create_address(address={var}, nonce={nonce})" - - # Nested CREATE: address created by a contract that was itself created - # by a known address (2 levels deep, small nonce range to keep - # generation fast — most contracts CREATE only a few children). - for var_addr, var in addr_to_var.items(): - for n1 in range(16): - child = compute_create_address(address=var_addr, nonce=n1) - for n2 in range(16): - if addr == compute_create_address(address=child, nonce=n2): - imports.needs_compute_create_address = True - return ( - f"compute_create_address(" - f"address=compute_create_address(" - f"address={var}, nonce={n1}), nonce={n2})" - ) - - return f"Address({addr})" diff --git a/scripts/filler_to_python/ir.py b/scripts/filler_to_python/ir.py deleted file mode 100644 index 18214cf9cb1..00000000000 --- a/scripts/filler_to_python/ir.py +++ /dev/null @@ -1,158 +0,0 @@ -"""Intermediate Representation dataclasses for filler-to-python codegen.""" - -from __future__ import annotations - -from dataclasses import dataclass, field - -from execution_testing.base_types import Address - - -@dataclass -class EnvironmentIR: - """IR for the test environment.""" - - coinbase_var: str - number: int - timestamp: int - difficulty: int | None = None - prev_randao: int | None = None - base_fee_per_gas: int | None = None - excess_blob_gas: int | None = None - gas_limit: int = 0 - - -@dataclass -class AccountIR: - """IR for a pre-state account.""" - - var_name: str - is_tagged: bool - is_eoa: bool - is_sender: bool - balance: int = 0 - nonce: int | None = None - address: Address | None = None - source_comment: str = "" - code_expr: str = "" - storage: dict = field(default_factory=dict) - oversized_code: bool = False - use_dynamic: bool = True - - -@dataclass -class AccountAssertionIR: - """IR for a post-state account assertion.""" - - var_ref: str - storage: dict | None = None - storage_any_keys: list = field(default_factory=list) - code: bytes | None = None - balance: int | None = None - nonce: int | None = None - should_not_exist: bool = False - - -@dataclass -class ExpectEntryIR: - """IR for one filler expect section.""" - - indexes: dict = field(default_factory=dict) - network: list = field(default_factory=list) - result: list = field(default_factory=list) - expect_exception: dict | None = None - - -@dataclass -class ParameterCaseIR: - """IR for one (d, g, v) parameter combo.""" - - d: int = 0 - g: int = 0 - v: int = 0 - has_exception: bool = False - label: str | None = None - id: str = "" - marks: str | None = None - - -@dataclass -class AccessListEntryIR: - """IR for a single access list entry.""" - - address: str = "" - storage_keys: list = field(default_factory=list) - use_dynamic: bool = False - - -@dataclass -class TransactionIR: - """IR for the transaction.""" - - to_var: str | None = None - to_is_none: bool = False - gas_price: int | None = None - max_fee_per_gas: int | None = None - max_priority_fee_per_gas: int | None = None - max_fee_per_blob_gas: int | None = None - blob_versioned_hashes: list | None = None - nonce: int | None = None - access_list: list | None = None - per_data_access_lists: dict | None = None - data_inline: str | None = None - gas_limit: int | None = None - value: int | None = None - - -@dataclass -class SenderIR: - """IR for the transaction sender.""" - - is_tagged: bool = False - key: int | None = None - balance: int = 0 - nonce: int | None = None - not_in_pre: bool = False - use_dynamic: bool = True - - -@dataclass -class ImportsIR: - """List of import requirements for the test.""" - - needs_op: bool = False - needs_access_list: bool = False - needs_bytes: bool = False - needs_hash: bool = False - needs_tx_exception: bool = False - needs_compute_create_address: bool = False - - -@dataclass -class IntermediateTestModel: - """Complete IR for one test file.""" - - test_name: str = "" - filler_path: str = "" - filler_comment: str = "" - category: str = "" - valid_from: str = "" - valid_until: str | None = None - is_slow: bool = False - is_multi_case: bool = False - is_fork_dependent: bool = False - needs_mutable_pre: bool = False - environment: EnvironmentIR = field( - default_factory=lambda: EnvironmentIR( - coinbase_var="coinbase", number=0, timestamp=0 - ) - ) - accounts: list = field(default_factory=list) - sender: SenderIR = field(default_factory=SenderIR) - parameters: list = field(default_factory=list) - transaction: TransactionIR = field(default_factory=TransactionIR) - expect_entries: list = field(default_factory=list) - address_constants: list = field(default_factory=list) - tx_data: list = field(default_factory=list) - tx_gas: list = field(default_factory=list) - tx_value: list = field(default_factory=list) - imports: ImportsIR = field(default_factory=ImportsIR) diff --git a/scripts/filler_to_python/render.py b/scripts/filler_to_python/render.py deleted file mode 100644 index 000abe511f5..00000000000 --- a/scripts/filler_to_python/render.py +++ /dev/null @@ -1,295 +0,0 @@ -"""Jinja2 rendering for filler-to-python codegen.""" - -from __future__ import annotations - -from dataclasses import asdict -from pathlib import Path - -import jinja2 - -from .ir import AccountAssertionIR, IntermediateTestModel - -TEMPLATE_DIR = Path(__file__).parent / "templates" - - -# --------------------------------------------------------------------------- -# Custom Jinja2 filters -# --------------------------------------------------------------------------- - - -def format_int(v: int | None) -> str: - """Format an integer as Python literal: hex for large values.""" - if v is None: - return "0" - if isinstance(v, bool): - return str(v) - v = int(v) - if v > 0xFFFF: - return hex(v) - return str(v) - - -def format_hex(v: int | str) -> str: - """Always format as hex.""" - if isinstance(v, str): - return v - return hex(int(v)) - - -def format_storage(d: dict) -> str: - """Format a {slot: value} storage dict as Python literal.""" - if not d: - return "{}" - items = [] - for k in sorted(d.keys()): - v = d[k] - if isinstance(v, str): - items.append(f"{format_int(k)}: {v}") - else: - items.append(f"{format_int(k)}: {format_int(v)}") - single = "{" + ", ".join(items) + "}" - if len(single) <= 50: - return single - formatted = ",\n ".join(items) - return "{\n " + formatted + ",\n }" - - -def format_account(a: AccountAssertionIR) -> str: - """Format an AccountAssertionIR as Account(...) expression.""" - if a.should_not_exist: - return "Account.NONEXISTENT" - - parts: list[str] = [] - if a.storage is not None: - if a.storage_any_keys: - # Need Storage object with set_expect_any calls - storage_str = format_storage(a.storage) - any_keys = a.storage_any_keys - parts.append( - f"storage=_storage_with_any({storage_str}, {any_keys})" - ) - else: - parts.append(f"storage={format_storage(a.storage)}") - if a.code is not None: - if a.code: - parts.append(f'code=bytes.fromhex("{a.code.hex()}")') - else: - parts.append('code=b""') - if a.balance is not None: - parts.append(f"balance={format_int(a.balance)}") - if a.nonce is not None: - parts.append(f"nonce={a.nonce}") - - if not parts: - return "Account()" - single = "Account(" + ", ".join(parts) + ")" - if len(single) <= 60: - return single - inner = ",\n ".join(parts) - return "Account(\n " + inner + ",\n )" - - -def format_post(result: list) -> str: - """Format a list of AccountAssertionIR as a post dict literal.""" - if not result: - return "{}" - - entries: list[str] = [] - for a in result: - entries.append(f"{a.var_ref}: {format_account(a)}") - - if len(entries) == 1: - single = "{" + entries[0] + "}" - if len(single) <= 70: - return single - - inner = ",\n ".join(entries) - return "{\n " + inner + ",\n }" - - -def format_expect_exception(d: dict) -> str: - """Format expect_exception dict with unquoted exception values.""" - items = [] - for k, v in d.items(): - items.append(f'"{k}": {v}') - return "{" + ", ".join(items) + "}" - - -def wrap_op_chain(s: str, indent: int = 8) -> str: - """Split an Op chain at + boundaries to fit 79-char lines.""" - if not s: - return '""' - - prefix = " " * indent - # If it fits on one line, just return it - if len(prefix + s) <= 79: - return s - - # If it's a bytes.fromhex expression, just return it (will get noqa) - if s.startswith("bytes.fromhex("): - return s - - # Split at " + " - parts = s.split(" + ") - if len(parts) <= 1: - return s - - lines: list[str] = [] - current_line = parts[0] - for part in parts[1:]: - candidate = current_line + " + " + part - if len(prefix + candidate) <= 79: - current_line = candidate - else: - lines.append(current_line) - current_line = part - - lines.append(current_line) - - if len(lines) == 1: - return lines[0] - - joiner = "\n" + prefix + "+ " - return lines[0] + joiner + joiner.join(lines[1:]) - - -# --------------------------------------------------------------------------- -# Template rendering -# --------------------------------------------------------------------------- - - -def _build_template_env() -> jinja2.Environment: - """Create and configure the Jinja2 environment.""" - env = jinja2.Environment( - loader=jinja2.FileSystemLoader(str(TEMPLATE_DIR)), - keep_trailing_newline=True, - trim_blocks=True, - lstrip_blocks=True, - ) - env.filters["format_int"] = format_int - env.filters["format_hex"] = format_hex - env.filters["format_storage"] = format_storage - env.filters["format_account"] = format_account - env.filters["format_post"] = format_post - env.filters["format_expect_exception"] = format_expect_exception - env.filters["wrap_op_chain"] = wrap_op_chain - return env - - -_template_env = _build_template_env() - - -def render_test(ir: IntermediateTestModel) -> str: - """Render a Python test file from an IR model.""" - template = _template_env.get_template("state_test.py.j2") - - # Build short docstring (first sentence of filler comment) - short_docstring = ir.filler_comment or ir.test_name - if "." in short_docstring: - short_docstring = short_docstring[: short_docstring.index(".") + 1] - if len(short_docstring) > 70: - # Truncate at word boundary - truncated = short_docstring[:67] - last_space = truncated.rfind(" ") - if last_space > 40: - truncated = truncated[:last_space] - short_docstring = truncated + "..." - # Ensure ends with period (D400/D415) - if not short_docstring.endswith("."): - short_docstring += "." - # Capitalize first letter (D403), avoid "This" (D404) - if short_docstring and short_docstring[0].islower(): - short_docstring = short_docstring[0].upper() + short_docstring[1:] - if short_docstring.startswith("This "): - short_docstring = "Test: t" + short_docstring[2:] - # Escape any quotes - short_docstring = short_docstring.replace('"', '\\"') - - # Build docstring — ensure first line ends with period (D400/D415) - docstring = ir.filler_comment or ir.test_name - first_line = docstring.split("\n")[0] - if not first_line.rstrip().endswith("."): - docstring = ( - first_line.rstrip() + ".\n" + "\n".join(docstring.split("\n")[1:]) - ) - docstring = docstring.rstrip() - # Capitalize first letter (D403) and avoid starting with "This" (D404) - if docstring and docstring[0].islower(): - docstring = docstring[0].upper() + docstring[1:] - if docstring.startswith("This "): - docstring = "Test: " + docstring[0].lower() + docstring[1:] - # Ensure all docstring lines fit 79 chars. - # First line must end with period (D400), so truncate if needed. - import textwrap - - doc_lines = docstring.split("\n") - first = doc_lines[0] - if len(first) > 75: - # Truncate at word boundary, add period - trunc = first[:72] - sp = trunc.rfind(" ") - if sp > 40: - trunc = trunc[:sp] - first = trunc + "..." - if not first.endswith("."): - first += "." - doc_lines[0] = first - # Ensure blank line after first line so D400 only checks line 1 - if len(doc_lines) > 1 and doc_lines[1].strip(): - doc_lines.insert(1, "") - wrapped_lines: list[str] = [doc_lines[0]] - for line in doc_lines[1:]: - if len(line) > 79: - wrapped_lines.extend(textwrap.wrap(line, width=79)) - else: - wrapped_lines.append(line) - docstring = "\n".join(wrapped_lines) - - # Has exceptions? - has_exceptions = any(p.has_exception for p in ir.parameters) - - # Needs _storage_with_any helper? - needs_storage_any = any( - a.storage_any_keys for entry in ir.expect_entries for a in entry.result - ) - - # Single-case post and error - single_post = None - single_error = None - if ir.expect_entries and len(ir.expect_entries) == 1: - entry = ir.expect_entries[0] - if entry.result: - single_post = entry.result - if entry.expect_exception: - exc_values = list(entry.expect_exception.values()) - if exc_values: - single_error = exc_values[0] - - context = { - "docstring": docstring, - "filler_path": ir.filler_path, - "test_name": ir.test_name, - "short_docstring": short_docstring, - "valid_from": ir.valid_from, - "valid_until": ir.valid_until, - "is_slow": ir.is_slow, - "is_multi_case": ir.is_multi_case, - "is_fork_dependent": ir.is_fork_dependent, - "needs_mutable_pre": ir.needs_mutable_pre, - "has_exceptions": has_exceptions, - "env": ir.environment, - "accounts": ir.accounts, - "tx": ir.transaction, - "tx_data": ir.tx_data, - "tx_gas": ir.tx_gas, - "tx_value": ir.tx_value, - "expect_entries": ir.expect_entries, - "parameters": ir.parameters, - "sender": ir.sender, - "address_constants": ir.address_constants, - "needs_storage_any": needs_storage_any, - "single_post": single_post, - "single_error": single_error, - } | asdict(ir.imports) - - return template.render(**context) diff --git a/scripts/filler_to_python/templates/state_test.py.j2 b/scripts/filler_to_python/templates/state_test.py.j2 deleted file mode 100644 index 5f0e7559e1d..00000000000 --- a/scripts/filler_to_python/templates/state_test.py.j2 +++ /dev/null @@ -1,412 +0,0 @@ -{#- - Jinja2 template for generating Python test files from static fillers. - - Parametrize approach: always (d, g, v). - Transaction fields are looked up from module-level arrays: - tx_data[d], tx_gas[g], tx_value[v] - Post-state and exceptions are resolved at runtime: - resolve_expect_post(expect_entries_, d, g, v, fork) - - Context variables (from IntermediateTestModel via render.py): - - docstring str Module docstring (from _info.comment) - filler_path str Relative path to filler file - test_name str Python function name (test_xxx) - short_docstring str One-line docstring for the function - valid_from str Fork name (e.g., "Cancun") - valid_until str|None Fork name or None - is_slow bool Mark @pytest.mark.slow - is_multi_case bool len(parameters) > 1 - has_exceptions bool Any case has an exception - - needs_op bool Import Op - needs_tx_exception bool Import TransactionException - needs_access_list bool Import AccessList - needs_hash bool Import Hash - - env EnvironmentIR - accounts list[AccountIR] - transaction TransactionIR - - -- Module-level arrays (from filler transaction) -- - - tx_data list[str] Compiled data hex per d index - tx_gas list[int] Gas limit per g index - tx_value list[int] Value per v index - - -- Expect entries (from filler expect sections) -- - - expect_entries list[ExpectEntryIR] - Each has: .indexes (dict), .network (list[str]), - .result (dict: var_name -> Account assertion), - .expect_exception (dict | None) - Resolved from expect.result.resolve(tags). - Used at runtime by resolve_expect_post(). - - -- Parametrize (d, g, v) combos -- - - parameters list[ParameterCaseIR] - Each has: .d, .g, .v, .has_exception, .label - Built from data x gasLimit x value matrix. - - -- Single-case data (when not is_multi_case) -- - - single_post str|None Formatted post dict, or None if empty - single_error str|None Formatted error, or None - - -- Sender -- - - sender SenderIR .is_tagged, .key, .balance - address_constants list Non-tagged address variables - [{var_name, hex}] - - Custom filters: - format_int(v) Hex vs decimal heuristic - format_storage(d) Format {slot: value} dict - format_account(a) Format Account(storage=..., code=...) - format_post(post) Format post dict {addr: Account(...)} - wrap_op_chain(s, ind) Split Op chain at + for 79-char lines --#} -""" -{{ docstring }} - -Ported from: -{{ filler_path }} -""" - -import pytest -from execution_testing import ( - EOA, - Account, - Address, - Alloc, -{% if needs_bytes %} - Bytes, -{% endif %} - Environment, - StateTestFiller, - Transaction, -{% if needs_tx_exception %} - TransactionException, -{% endif %} -{% if needs_access_list %} - AccessList, -{% endif %} -{% if needs_hash %} - Hash, -{% endif %} -{% if needs_storage_any %} - Storage, -{% endif %} -{% if needs_compute_create_address %} - compute_create_address, -{% endif %} -) -{% if needs_op %} -from execution_testing.vm import Op -{% endif %} -{% if is_multi_case %} -from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( - resolve_expect_post, -) -{% elif is_fork_dependent %} -from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( - resolve_expect_post_fork, -) -{% endif %} - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - -{% if needs_storage_any %} -def _storage_with_any(base: dict, any_keys: list) -> Storage: - """Create Storage with set_expect_any for specified keys.""" - s = Storage(base) - for k in any_keys: - s.set_expect_any(k) - return s - -{% endif %} - -@pytest.mark.ported_from( - ["{{ filler_path }}"], -) -@pytest.mark.valid_from("{{ valid_from }}") -{% if valid_until %} -@pytest.mark.valid_until("{{ valid_until }}") -{% endif %} -{% if is_slow %} -@pytest.mark.slow -{% endif %} -{% if is_multi_case %} -@pytest.mark.parametrize( - "d, g, v", - [ -{% for case in parameters %} - pytest.param( - {{ case.d }}, {{ case.g }}, {{ case.v }}, - id="{{ case.id }}", -{% if case.marks %} - marks={{ case.marks }}, -{% endif %} - ), -{% endfor %} - ], -) -{% endif %} -{% if has_exceptions and not is_multi_case %} -@pytest.mark.exception_test -{% endif %} -{% if needs_mutable_pre %} -@pytest.mark.pre_alloc_mutable -{% endif %} -def {{ test_name }}( - state_test: StateTestFiller, - pre: Alloc, -{% if is_multi_case %} - fork: Fork, - d: int, - g: int, - v: int, -{% elif is_fork_dependent %} - fork: Fork, -{% endif %} -) -> None: - """{{ short_docstring }}""" -{% for addr in address_constants %} - {{ addr.var_name }} = Address({{ addr.hex }}) -{% endfor %} -{% if sender.use_dynamic %} -{% if sender.nonce %} - sender = pre.fund_eoa(amount={{ sender.balance | format_int }}, nonce={{ sender.nonce }}) -{% else %} - sender = pre.fund_eoa(amount={{ sender.balance | format_int }}) -{% endif %} -{% elif sender.not_in_pre %} - sender = pre.fund_eoa(amount=0) -{% else %} - sender = EOA( - key={{ sender.key | format_int }} - ) -{% endif %} - - env = Environment( - fee_recipient={{ env.coinbase_var }}, - number={{ env.number }}, - timestamp={{ env.timestamp }}, -{% if env.prev_randao is not none %} - prev_randao={{ env.prev_randao | format_int }}, -{% endif %} -{% if env.difficulty is not none and env.prev_randao is none %} - difficulty={{ env.difficulty | format_int }}, -{% endif %} -{% if env.base_fee_per_gas is not none %} - base_fee_per_gas={{ env.base_fee_per_gas }}, -{% endif %} -{% if env.excess_blob_gas is not none %} - excess_blob_gas={{ env.excess_blob_gas | format_int }}, -{% endif %} - gas_limit={{ env.gas_limit }}, - ) - -{# Pre-state account setup #} -{% for account in accounts %} -{% if account.is_sender and sender.use_dynamic %} -{# Sender balance already set via pre.fund_eoa() above #} -{% elif account.is_sender %} - pre[sender] = Account(balance={{ account.balance | format_int }}{{ ", nonce=%d" | format(account.nonce) if account.nonce }}{{ ", storage=%s" | format(account.storage | format_storage) if account.storage }}{{ ", code=%s" | format(account.code_expr | wrap_op_chain(indent=8)) if account.code_expr }}) -{% elif account.is_eoa and account.use_dynamic %} - {{ account.var_name }} = pre.fund_eoa(amount={{ account.balance | format_int }}) # noqa: F841 -{% elif account.is_eoa %} - pre[{{ account.var_name }}] = Account(balance={{ account.balance | format_int }}{{ ", nonce=%d" | format(account.nonce) if account.nonce }}{{ ", storage=%s" | format(account.storage | format_storage) if account.storage }}{{ ", code=%s" | format(account.code_expr | wrap_op_chain(indent=8)) if account.code_expr }}) -{% else %} -{# Contract: source comment + deploy #} -{{ account.source_comment }} -{% if account.oversized_code %} - {{ account.var_name }} = Address({{ account.address }}) # oversized contract - pre[{{ account.var_name }}] = Account( - code={{ account.code_expr | wrap_op_chain(indent=8) }}, -{% if account.storage %} - storage={{ account.storage | format_storage }}, -{% endif %} -{% if account.balance %} - balance={{ account.balance | format_int }}, -{% endif %} -{% if account.nonce is not none %} - nonce={{ account.nonce }}, -{% endif %} - ) -{% else %} - {{ account.var_name }} = pre.deploy_contract( - code={{ account.code_expr | wrap_op_chain(indent=8) }}, -{% if account.storage %} - storage={{ account.storage | format_storage }}, -{% endif %} -{% if account.balance %} - balance={{ account.balance | format_int }}, -{% endif %} -{% if account.nonce is not none %} - nonce={{ account.nonce }}, -{% endif %} -{% if not account.use_dynamic %} - address=Address({{ account.address }}), # noqa: E501 -{% endif %} - ) -{% endif %} -{% endif %} -{% endfor %} - -{# Expect entries (used by resolve_expect_post at runtime) #} -{% if single_post %} -{# Post will be assigned below #} -{% elif is_multi_case or is_fork_dependent %} - expect_entries_: list[dict] = [ -{% for entry in expect_entries %} - { -{% if is_multi_case %} - "indexes": {{ entry.indexes }}, -{% endif %} - "network": {{ entry.network }}, - "result": {{ entry.result | format_post }}, -{% if entry.expect_exception %} - "expect_exception": {{ entry.expect_exception | format_expect_exception }}, -{% endif %} - }, -{% endfor %} - ] - -{% if is_multi_case %} - post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork) -{% else %} - post, _exc = resolve_expect_post_fork(expect_entries_, fork) -{% endif %} -{% endif %} - -{% if is_multi_case %} -{# Case value arrays: tx fields indexed by d, g, v #} - tx_data = [ - {% for d in tx_data %} - {{ d }}, - {% endfor %} - ] - tx_gas = [{{ tx_gas | join(", ") }}] -{% if tx_value != [0] %} - tx_value = [{{ tx_value | join(", ") }}] -{% endif %} -{% if tx.per_data_access_lists %} - tx_access_lists: dict[int, list] = { - {% for d_idx, al_entries in tx.per_data_access_lists.items() %} - {{ d_idx }}: [ - {% for al in al_entries %} - AccessList( - {% if al.use_dynamic %} - address={{ al.address }}, - {% else %} - address=Address({{ al.address }}), - {% endif %} - storage_keys=[ - {% for sk in al.storage_keys %} - Hash("{{ sk }}"), # noqa: E501 - {% endfor %} - ], - ), - {% endfor %} - ], - {% endfor %} - } -{% endif %} - -{% endif %} - -{# Transaction #} - tx = Transaction( - sender=sender, -{% if tx.to_var is not none %} - to={{ tx.to_var }}, -{% elif tx.to_is_none %} - to=None, -{% endif %} -{% if is_multi_case %} - data=tx_data[d], - gas_limit=tx_gas[g], -{% if tx_value != [0] %} - value=tx_value[v], -{% endif %} -{% else %} -{% if tx.data_inline and tx.data_inline != "b''" %} - data={{ tx.data_inline }}, -{% endif %} -{% if tx.gas_limit is not none and tx.gas_limit != 21000 %} - gas_limit={{ tx.gas_limit }}, -{% endif %} -{% if tx.value %} - value={{ tx.value | format_int }}, -{% endif %} -{% endif %} -{% if tx.max_fee_per_gas is not none %} - max_fee_per_gas={{ tx.max_fee_per_gas }}, -{% endif %} -{% if tx.max_priority_fee_per_gas is not none %} - max_priority_fee_per_gas={{ tx.max_priority_fee_per_gas }}, -{% endif %} -{% if tx.nonce is not none and tx.nonce != 0 %} - nonce={{ tx.nonce }}, -{% endif %} -{% if tx.gas_price is not none and tx.gas_price != 10 %} - gas_price={{ tx.gas_price }}, -{% endif %} -{% if tx.max_fee_per_blob_gas is not none %} - max_fee_per_blob_gas={{ tx.max_fee_per_blob_gas | format_int }}, -{% endif %} -{% if tx.blob_versioned_hashes is not none %} - blob_versioned_hashes=[ -{% for h in tx.blob_versioned_hashes %} - Hash( - "{{ h }}" # noqa: E501 - ), -{% endfor %} - ], -{% endif %} -{% if tx.per_data_access_lists and is_multi_case %} - access_list=tx_access_lists.get(d), -{% elif tx.access_list is not none %} - access_list=[ -{% for al in tx.access_list %} - AccessList( -{% if al.use_dynamic %} - address={{ al.address }}, -{% else %} - address=Address({{ al.address }}), -{% endif %} - storage_keys=[ -{% for sk in al.storage_keys %} - Hash( - "{{ sk }}" # noqa: E501 - ), -{% endfor %} - ], - ), -{% endfor %} - ], -{% endif %} -{% if single_error %} - error={{ single_error }}, -{% elif single_post %} - {# Single post without error #} -{% elif is_multi_case or is_fork_dependent %} - error=_exc, -{% endif %} - ) - -{# Post-state #} -{% if single_post %} - post = {{ single_post | format_post }} -{% elif is_multi_case or is_fork_dependent %} -{# Post resolved above via resolve_expect_post / resolve_expect_post_fork #} -{% else %} - post: dict = {} -{% endif %} - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/scripts/verify_dynamic_addresses.sh b/scripts/verify_dynamic_addresses.sh deleted file mode 100755 index 3cbd19becaa..00000000000 --- a/scripts/verify_dynamic_addresses.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/bash -# Verify that filler_to_python with dynamic addresses produces -# trace-equivalent tests. Assumes output/traces_baseline/ already -# exists (generated once before any code changes). -set -euo pipefail - -export TMPDIR=./.tmp -mkdir -p "$TMPDIR" output/traces_new - -if [ ! -d "output/traces_baseline" ]; then - echo "ERROR: output/traces_baseline/ not found." - echo "Generate baseline first (before code changes):" - echo " TMPDIR=./.tmp uv run fill tests/ported_static/ --evm-dump-dir output/traces_baseline -n 10 -m 'not slow'" - exit 1 -fi - -# Step 1: Run filler_to_python (overwrites tests/ported_static/) -echo "=== Step 1: Running filler_to_python ===" -uv run python -m scripts.filler_to_python \ - --fillers tests/static/static/state_tests/ \ - --output tests/ported_static/ - -# Step 2: Fill new tests + verify against baseline -echo "=== Step 2: Filling new tests and verifying traces ===" -uv run fill \ - tests/ported_static/ \ - --evm-dump-dir output/traces_new \ - --verify-traces output/traces_baseline \ - --verify-traces-comparator exact-no-stack \ - -n 10 \ - -m "not slow" - -echo "=== Done. Check output above for trace mismatches ===" -echo "=== Use 'git diff tests/ported_static/' to see code changes ===" diff --git a/tests/ported_static/__init__.py b/tests/ported_static/__init__.py new file mode 100644 index 00000000000..9f69e28f385 --- /dev/null +++ b/tests/ported_static/__init__.py @@ -0,0 +1 @@ +"""Ported static tests from ethereum/tests.""" diff --git a/tests/ported_static/post_state_resolution.py b/tests/ported_static/post_state_resolution.py new file mode 100644 index 00000000000..6a75278a2ae --- /dev/null +++ b/tests/ported_static/post_state_resolution.py @@ -0,0 +1,229 @@ +""" +Runtime post-state resolution for ported static tests. + +Provides resolve_expect_post / resolve_expect_post_fork, used by the tests +under tests/ported_static/ to resolve expected post-state and exceptions for +a given (d, g, v) and fork. Relocated out of the deleted specs/static_state/ +parser. +""" + +import re +from enum import StrEnum +from typing import Any, Iterator, Set + +from execution_testing.base_types import EthereumTestRootModel +from execution_testing.exceptions import TransactionExceptionInstanceOrList +from execution_testing.forks import Fork, get_forks +from pydantic import BaseModel, field_validator, model_validator + + +class CMP(StrEnum): + """Comparison action.""" + + LE = "<=" + GE = ">=" + LT = "<" + GT = ">" + EQ = "=" + + +class ForkConstraint(BaseModel): + """Single fork with an operand.""" + + operand: CMP + fork: Fork + + @field_validator("fork", mode="before") + @classmethod + def parse_fork_synonyms(cls, value: Any) -> Any: + """Resolve fork synonyms.""" + if value == "EIP158": + value = "Byzantium" + return value + + @model_validator(mode="before") + @classmethod + def parse_from_string(cls, data: Any) -> Any: + """Parse a fork with operand from a string.""" + if isinstance(data, str): + for cmp in CMP: + if data.startswith(cmp): + fork = data.removeprefix(cmp) + return { + "operand": cmp, + "fork": fork, + } + return { + "operand": CMP.EQ, + "fork": data, + } + return data + + def match(self, fork: Fork) -> bool: + """Return whether the fork satisfies the operand evaluation.""" + match self.operand: + case CMP.LE: + return fork <= self.fork + case CMP.GE: + return fork >= self.fork + case CMP.LT: + return fork < self.fork + case CMP.GT: + return fork > self.fork + case CMP.EQ: + return fork == self.fork + case _: + raise ValueError(f"Invalid operand: {self.operand}") + + +class ForkSet(EthereumTestRootModel): + """Set of forks.""" + + root: Set[Fork] + + @model_validator(mode="before") + @classmethod + def parse_from_list_or_string(cls, value: Any) -> Set[Fork]: + """Parse fork_with_operand `>=Cancun` into {Cancun, Prague, ...}.""" + fork_set: Set[Fork] = set() + if not isinstance(value, list): + value = [value] + + for fork_with_operand in value: + matches = re.findall(r"(<=|<|>=|>|=)([^<>=]+)", fork_with_operand) + if matches: + all_fork_constraints = [ + ForkConstraint.model_validate(f"{op}{fork.strip()}") + for op, fork in matches + ] + else: + all_fork_constraints = [ + ForkConstraint.model_validate(fork_with_operand.strip()) + ] + + for fork in get_forks(): + for f in all_fork_constraints: + if not f.match(fork): + # If any constraint does not match, skip adding + break + else: + # All constraints match, add the fork to the set + fork_set.add(fork) + + return fork_set + + def __hash__(self) -> int: + """Return the hash of the fork set.""" + h = hash(None) + for fork in sorted([str(f) for f in self]): + h ^= hash(fork) + return h + + def __contains__(self, fork: Fork) -> bool: + """Check if the fork set contains a fork.""" + return fork in self.root + + def __iter__(self) -> Iterator[Fork]: # type: ignore[override] + """Iterate over the fork set.""" + return iter(self.root) + + def __len__(self) -> int: + """Return the length of the fork set.""" + return len(self.root) + + +def _match_index(idx: int | list, val: int) -> bool: + """Check if an index specification matches a value.""" + if isinstance(idx, int): + return idx == -1 or idx == val + if isinstance(idx, list): + return val in idx + return False + + +def resolve_expect_post( + expect_entries: list[dict], + d: int, + g: int, + v: int, + fork: Fork, +) -> tuple[dict, TransactionExceptionInstanceOrList | None]: + """ + Resolve expected post-state for given d, g, v and fork. + + Used by generated Python tests at runtime. The expect_entries are + materialized Python dicts with resolved addresses and Account objects. + """ + for entry in expect_entries: + indexes = entry["indexes"] + if not _match_index(indexes.get("data", -1), d): + continue + if not _match_index(indexes.get("gas", -1), g): + continue + if not _match_index(indexes.get("value", -1), v): + continue + + # Match fork against network constraints + network = entry["network"] + fork_set = ForkSet.model_validate(network) + if fork not in fork_set: + continue + + # Found matching entry + result = entry.get("result", {}) + + # Resolve exception + exception: TransactionExceptionInstanceOrList | None = None + expect_exc = entry.get("expect_exception") + if expect_exc: + for constraint_str, exc_value in expect_exc.items(): + exc_fork_set = ForkSet.model_validate( + constraint_str.split(",") + ) + if fork in exc_fork_set: + exception = exc_value + break + + return result, exception + + raise ValueError( + f"No matching expect entry for d={d}, g={g}, v={v}, fork={fork}" + ) + + +def resolve_expect_post_fork( + expect_entries: list[dict], + fork: Fork, +) -> tuple[dict, TransactionExceptionInstanceOrList | None]: + """ + Resolve expected post-state for a given fork only (no d/g/v matching). + + Used by single-case generated Python tests that have fork-dependent + post-state (multiple expect sections with different networks but only + one (d, g, v) combo). + """ + for entry in expect_entries: + # Match fork against network constraints + network = entry["network"] + fork_set = ForkSet.model_validate(network) + if fork not in fork_set: + continue + + # Found matching entry + result = entry.get("result", {}) + + # Resolve exception + exception: TransactionExceptionInstanceOrList | None = None + expect_exc = entry.get("expect_exception") + if expect_exc: + for constraint_str, exc_value in expect_exc.items(): + exc_fork_set = ForkSet.model_validate( + constraint_str.split(",") + ) + if fork in exc_fork_set: + exception = exc_value + break + + return result, exception + + raise ValueError(f"No matching expect entry for fork={fork}") diff --git a/tests/ported_static/stArgsZeroOneBalance/test_addmod_non_const.py b/tests/ported_static/stArgsZeroOneBalance/test_addmod_non_const.py index fae816dfd1e..7c52305bbc7 100644 --- a/tests/ported_static/stArgsZeroOneBalance/test_addmod_non_const.py +++ b/tests/ported_static/stArgsZeroOneBalance/test_addmod_non_const.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stArgsZeroOneBalance/test_and_non_const.py b/tests/ported_static/stArgsZeroOneBalance/test_and_non_const.py index 8eb6f763863..087d6b2b3a4 100644 --- a/tests/ported_static/stArgsZeroOneBalance/test_and_non_const.py +++ b/tests/ported_static/stArgsZeroOneBalance/test_and_non_const.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stArgsZeroOneBalance/test_balance_non_const.py b/tests/ported_static/stArgsZeroOneBalance/test_balance_non_const.py index ae940bbaa03..d9504c04e00 100644 --- a/tests/ported_static/stArgsZeroOneBalance/test_balance_non_const.py +++ b/tests/ported_static/stArgsZeroOneBalance/test_balance_non_const.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stArgsZeroOneBalance/test_byte_non_const.py b/tests/ported_static/stArgsZeroOneBalance/test_byte_non_const.py index 544001d697d..5ce3cf54b0c 100644 --- a/tests/ported_static/stArgsZeroOneBalance/test_byte_non_const.py +++ b/tests/ported_static/stArgsZeroOneBalance/test_byte_non_const.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stArgsZeroOneBalance/test_call_non_const.py b/tests/ported_static/stArgsZeroOneBalance/test_call_non_const.py index a8e915e499a..43be208bd0c 100644 --- a/tests/ported_static/stArgsZeroOneBalance/test_call_non_const.py +++ b/tests/ported_static/stArgsZeroOneBalance/test_call_non_const.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stArgsZeroOneBalance/test_callcode_non_const.py b/tests/ported_static/stArgsZeroOneBalance/test_callcode_non_const.py index e24b3d5f963..47a3bf59374 100644 --- a/tests/ported_static/stArgsZeroOneBalance/test_callcode_non_const.py +++ b/tests/ported_static/stArgsZeroOneBalance/test_callcode_non_const.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stArgsZeroOneBalance/test_calldatacopy_non_const.py b/tests/ported_static/stArgsZeroOneBalance/test_calldatacopy_non_const.py index 9a0b8f9f9c2..aade8dfd1fd 100644 --- a/tests/ported_static/stArgsZeroOneBalance/test_calldatacopy_non_const.py +++ b/tests/ported_static/stArgsZeroOneBalance/test_calldatacopy_non_const.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stArgsZeroOneBalance/test_calldataload_non_const.py b/tests/ported_static/stArgsZeroOneBalance/test_calldataload_non_const.py index dc0f8b82b2e..c88240be73d 100644 --- a/tests/ported_static/stArgsZeroOneBalance/test_calldataload_non_const.py +++ b/tests/ported_static/stArgsZeroOneBalance/test_calldataload_non_const.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stArgsZeroOneBalance/test_codecopy_non_const.py b/tests/ported_static/stArgsZeroOneBalance/test_codecopy_non_const.py index c097c5f467e..49e0c10ffea 100644 --- a/tests/ported_static/stArgsZeroOneBalance/test_codecopy_non_const.py +++ b/tests/ported_static/stArgsZeroOneBalance/test_codecopy_non_const.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stArgsZeroOneBalance/test_create_non_const.py b/tests/ported_static/stArgsZeroOneBalance/test_create_non_const.py index 5106ec78a51..e2b80fcdcf1 100644 --- a/tests/ported_static/stArgsZeroOneBalance/test_create_non_const.py +++ b/tests/ported_static/stArgsZeroOneBalance/test_create_non_const.py @@ -17,10 +17,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stArgsZeroOneBalance/test_delegatecall_non_const.py b/tests/ported_static/stArgsZeroOneBalance/test_delegatecall_non_const.py index 2fc22d2747d..004f1ea9bb7 100644 --- a/tests/ported_static/stArgsZeroOneBalance/test_delegatecall_non_const.py +++ b/tests/ported_static/stArgsZeroOneBalance/test_delegatecall_non_const.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stArgsZeroOneBalance/test_div_non_const.py b/tests/ported_static/stArgsZeroOneBalance/test_div_non_const.py index 754da2814ab..4dabae16362 100644 --- a/tests/ported_static/stArgsZeroOneBalance/test_div_non_const.py +++ b/tests/ported_static/stArgsZeroOneBalance/test_div_non_const.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stArgsZeroOneBalance/test_eq_non_const.py b/tests/ported_static/stArgsZeroOneBalance/test_eq_non_const.py index 09c30fc9052..8bf13afedf2 100644 --- a/tests/ported_static/stArgsZeroOneBalance/test_eq_non_const.py +++ b/tests/ported_static/stArgsZeroOneBalance/test_eq_non_const.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stArgsZeroOneBalance/test_exp_non_const.py b/tests/ported_static/stArgsZeroOneBalance/test_exp_non_const.py index 72f6aace5a0..c9568af7e93 100644 --- a/tests/ported_static/stArgsZeroOneBalance/test_exp_non_const.py +++ b/tests/ported_static/stArgsZeroOneBalance/test_exp_non_const.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stArgsZeroOneBalance/test_extcodecopy_non_const.py b/tests/ported_static/stArgsZeroOneBalance/test_extcodecopy_non_const.py index 1b0cd7e9c8c..319b42c8c06 100644 --- a/tests/ported_static/stArgsZeroOneBalance/test_extcodecopy_non_const.py +++ b/tests/ported_static/stArgsZeroOneBalance/test_extcodecopy_non_const.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stArgsZeroOneBalance/test_extcodesize_non_const.py b/tests/ported_static/stArgsZeroOneBalance/test_extcodesize_non_const.py index e67e53ea57c..eb16b7a066a 100644 --- a/tests/ported_static/stArgsZeroOneBalance/test_extcodesize_non_const.py +++ b/tests/ported_static/stArgsZeroOneBalance/test_extcodesize_non_const.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stArgsZeroOneBalance/test_gt_non_const.py b/tests/ported_static/stArgsZeroOneBalance/test_gt_non_const.py index d8db68a6f82..b7dd19af224 100644 --- a/tests/ported_static/stArgsZeroOneBalance/test_gt_non_const.py +++ b/tests/ported_static/stArgsZeroOneBalance/test_gt_non_const.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stArgsZeroOneBalance/test_iszero_non_const.py b/tests/ported_static/stArgsZeroOneBalance/test_iszero_non_const.py index dc4585cd190..578ab0f9a38 100644 --- a/tests/ported_static/stArgsZeroOneBalance/test_iszero_non_const.py +++ b/tests/ported_static/stArgsZeroOneBalance/test_iszero_non_const.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stArgsZeroOneBalance/test_jump_non_const.py b/tests/ported_static/stArgsZeroOneBalance/test_jump_non_const.py index a7a6d0ed8b7..8097fd414d4 100644 --- a/tests/ported_static/stArgsZeroOneBalance/test_jump_non_const.py +++ b/tests/ported_static/stArgsZeroOneBalance/test_jump_non_const.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stArgsZeroOneBalance/test_jumpi_non_const.py b/tests/ported_static/stArgsZeroOneBalance/test_jumpi_non_const.py index 8ad626d763f..43c839d67dc 100644 --- a/tests/ported_static/stArgsZeroOneBalance/test_jumpi_non_const.py +++ b/tests/ported_static/stArgsZeroOneBalance/test_jumpi_non_const.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stArgsZeroOneBalance/test_log0_non_const.py b/tests/ported_static/stArgsZeroOneBalance/test_log0_non_const.py index 365c39eaccf..2d60ddd22fe 100644 --- a/tests/ported_static/stArgsZeroOneBalance/test_log0_non_const.py +++ b/tests/ported_static/stArgsZeroOneBalance/test_log0_non_const.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stArgsZeroOneBalance/test_log1_non_const.py b/tests/ported_static/stArgsZeroOneBalance/test_log1_non_const.py index 83826d6f6b3..617228c5bb6 100644 --- a/tests/ported_static/stArgsZeroOneBalance/test_log1_non_const.py +++ b/tests/ported_static/stArgsZeroOneBalance/test_log1_non_const.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stArgsZeroOneBalance/test_log2_non_const.py b/tests/ported_static/stArgsZeroOneBalance/test_log2_non_const.py index 418cb6e8be0..b5af387e24c 100644 --- a/tests/ported_static/stArgsZeroOneBalance/test_log2_non_const.py +++ b/tests/ported_static/stArgsZeroOneBalance/test_log2_non_const.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stArgsZeroOneBalance/test_log3_non_const.py b/tests/ported_static/stArgsZeroOneBalance/test_log3_non_const.py index 4a34ed3a76c..fdfad92cd50 100644 --- a/tests/ported_static/stArgsZeroOneBalance/test_log3_non_const.py +++ b/tests/ported_static/stArgsZeroOneBalance/test_log3_non_const.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stArgsZeroOneBalance/test_lt_non_const.py b/tests/ported_static/stArgsZeroOneBalance/test_lt_non_const.py index 34354330ff4..4156877e37e 100644 --- a/tests/ported_static/stArgsZeroOneBalance/test_lt_non_const.py +++ b/tests/ported_static/stArgsZeroOneBalance/test_lt_non_const.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stArgsZeroOneBalance/test_mload_non_const.py b/tests/ported_static/stArgsZeroOneBalance/test_mload_non_const.py index 244fefd710b..77df600045b 100644 --- a/tests/ported_static/stArgsZeroOneBalance/test_mload_non_const.py +++ b/tests/ported_static/stArgsZeroOneBalance/test_mload_non_const.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stArgsZeroOneBalance/test_mod_non_const.py b/tests/ported_static/stArgsZeroOneBalance/test_mod_non_const.py index bda76416253..61fba638372 100644 --- a/tests/ported_static/stArgsZeroOneBalance/test_mod_non_const.py +++ b/tests/ported_static/stArgsZeroOneBalance/test_mod_non_const.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stArgsZeroOneBalance/test_mstore8_non_const.py b/tests/ported_static/stArgsZeroOneBalance/test_mstore8_non_const.py index 0447a50df4c..c19041d86b0 100644 --- a/tests/ported_static/stArgsZeroOneBalance/test_mstore8_non_const.py +++ b/tests/ported_static/stArgsZeroOneBalance/test_mstore8_non_const.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stArgsZeroOneBalance/test_mstore_non_const.py b/tests/ported_static/stArgsZeroOneBalance/test_mstore_non_const.py index 8b021a9b2cd..69d06b71a93 100644 --- a/tests/ported_static/stArgsZeroOneBalance/test_mstore_non_const.py +++ b/tests/ported_static/stArgsZeroOneBalance/test_mstore_non_const.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stArgsZeroOneBalance/test_mul_non_const.py b/tests/ported_static/stArgsZeroOneBalance/test_mul_non_const.py index 76886038831..32bdaaebee2 100644 --- a/tests/ported_static/stArgsZeroOneBalance/test_mul_non_const.py +++ b/tests/ported_static/stArgsZeroOneBalance/test_mul_non_const.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stArgsZeroOneBalance/test_mulmod_non_const.py b/tests/ported_static/stArgsZeroOneBalance/test_mulmod_non_const.py index cd61591b4c6..3d247a97434 100644 --- a/tests/ported_static/stArgsZeroOneBalance/test_mulmod_non_const.py +++ b/tests/ported_static/stArgsZeroOneBalance/test_mulmod_non_const.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stArgsZeroOneBalance/test_not_non_const.py b/tests/ported_static/stArgsZeroOneBalance/test_not_non_const.py index 5ff4ec0e605..5e46024f1a7 100644 --- a/tests/ported_static/stArgsZeroOneBalance/test_not_non_const.py +++ b/tests/ported_static/stArgsZeroOneBalance/test_not_non_const.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stArgsZeroOneBalance/test_or_non_const.py b/tests/ported_static/stArgsZeroOneBalance/test_or_non_const.py index 343f19c2fec..10e1fb03c39 100644 --- a/tests/ported_static/stArgsZeroOneBalance/test_or_non_const.py +++ b/tests/ported_static/stArgsZeroOneBalance/test_or_non_const.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stArgsZeroOneBalance/test_return_non_const.py b/tests/ported_static/stArgsZeroOneBalance/test_return_non_const.py index 4e790b391e0..f8a60670f33 100644 --- a/tests/ported_static/stArgsZeroOneBalance/test_return_non_const.py +++ b/tests/ported_static/stArgsZeroOneBalance/test_return_non_const.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stArgsZeroOneBalance/test_sdiv_non_const.py b/tests/ported_static/stArgsZeroOneBalance/test_sdiv_non_const.py index fe11f8c49a8..061d1ab6490 100644 --- a/tests/ported_static/stArgsZeroOneBalance/test_sdiv_non_const.py +++ b/tests/ported_static/stArgsZeroOneBalance/test_sdiv_non_const.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stArgsZeroOneBalance/test_sgt_non_const.py b/tests/ported_static/stArgsZeroOneBalance/test_sgt_non_const.py index 8e955bebd63..57273095866 100644 --- a/tests/ported_static/stArgsZeroOneBalance/test_sgt_non_const.py +++ b/tests/ported_static/stArgsZeroOneBalance/test_sgt_non_const.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stArgsZeroOneBalance/test_sha3_non_const.py b/tests/ported_static/stArgsZeroOneBalance/test_sha3_non_const.py index 6efbe2105b6..0c584f02b27 100644 --- a/tests/ported_static/stArgsZeroOneBalance/test_sha3_non_const.py +++ b/tests/ported_static/stArgsZeroOneBalance/test_sha3_non_const.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stArgsZeroOneBalance/test_signext_non_const.py b/tests/ported_static/stArgsZeroOneBalance/test_signext_non_const.py index ca26efca5c8..1e04c27ad5c 100644 --- a/tests/ported_static/stArgsZeroOneBalance/test_signext_non_const.py +++ b/tests/ported_static/stArgsZeroOneBalance/test_signext_non_const.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stArgsZeroOneBalance/test_sload_non_const.py b/tests/ported_static/stArgsZeroOneBalance/test_sload_non_const.py index 253acce3f33..bd3e5ecbace 100644 --- a/tests/ported_static/stArgsZeroOneBalance/test_sload_non_const.py +++ b/tests/ported_static/stArgsZeroOneBalance/test_sload_non_const.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stArgsZeroOneBalance/test_slt_non_const.py b/tests/ported_static/stArgsZeroOneBalance/test_slt_non_const.py index 4785add59ca..e5a85c0e3e8 100644 --- a/tests/ported_static/stArgsZeroOneBalance/test_slt_non_const.py +++ b/tests/ported_static/stArgsZeroOneBalance/test_slt_non_const.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stArgsZeroOneBalance/test_smod_non_const.py b/tests/ported_static/stArgsZeroOneBalance/test_smod_non_const.py index 1408d1dbfb2..554fa2f3b10 100644 --- a/tests/ported_static/stArgsZeroOneBalance/test_smod_non_const.py +++ b/tests/ported_static/stArgsZeroOneBalance/test_smod_non_const.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stArgsZeroOneBalance/test_sstore_non_const.py b/tests/ported_static/stArgsZeroOneBalance/test_sstore_non_const.py index 8f1e6ba7914..9b9f4e7a461 100644 --- a/tests/ported_static/stArgsZeroOneBalance/test_sstore_non_const.py +++ b/tests/ported_static/stArgsZeroOneBalance/test_sstore_non_const.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stArgsZeroOneBalance/test_sub_non_const.py b/tests/ported_static/stArgsZeroOneBalance/test_sub_non_const.py index 311cc71bfa2..812a6ed76b3 100644 --- a/tests/ported_static/stArgsZeroOneBalance/test_sub_non_const.py +++ b/tests/ported_static/stArgsZeroOneBalance/test_sub_non_const.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stArgsZeroOneBalance/test_xor_non_const.py b/tests/ported_static/stArgsZeroOneBalance/test_xor_non_const.py index 1ca082aca84..306f68565fd 100644 --- a/tests/ported_static/stArgsZeroOneBalance/test_xor_non_const.py +++ b/tests/ported_static/stArgsZeroOneBalance/test_xor_non_const.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stBadOpcode/test_measure_gas.py b/tests/ported_static/stBadOpcode/test_measure_gas.py index f100b2b1ea8..5f9d16570a6 100644 --- a/tests/ported_static/stBadOpcode/test_measure_gas.py +++ b/tests/ported_static/stBadOpcode/test_measure_gas.py @@ -27,10 +27,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stBadOpcode/test_operation_diff_gas.py b/tests/ported_static/stBadOpcode/test_operation_diff_gas.py index be3bf8c0559..bb80de6c8d9 100644 --- a/tests/ported_static/stBadOpcode/test_operation_diff_gas.py +++ b/tests/ported_static/stBadOpcode/test_operation_diff_gas.py @@ -28,10 +28,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCallCodes/test_callcode_dynamic_code.py b/tests/ported_static/stCallCodes/test_callcode_dynamic_code.py index 516596f0896..f214b95b35b 100644 --- a/tests/ported_static/stCallCodes/test_callcode_dynamic_code.py +++ b/tests/ported_static/stCallCodes/test_callcode_dynamic_code.py @@ -28,10 +28,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCallCodes/test_callcode_dynamic_code2_self_call.py b/tests/ported_static/stCallCodes/test_callcode_dynamic_code2_self_call.py index 0f5363e7c6f..ea1a67226c2 100644 --- a/tests/ported_static/stCallCodes/test_callcode_dynamic_code2_self_call.py +++ b/tests/ported_static/stCallCodes/test_callcode_dynamic_code2_self_call.py @@ -28,10 +28,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCallCodes/test_callcode_in_initcode_to_empty_contract.py b/tests/ported_static/stCallCodes/test_callcode_in_initcode_to_empty_contract.py index 045972d37f5..e91ddf2c177 100644 --- a/tests/ported_static/stCallCodes/test_callcode_in_initcode_to_empty_contract.py +++ b/tests/ported_static/stCallCodes/test_callcode_in_initcode_to_empty_contract.py @@ -22,10 +22,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCallCodes/test_callcode_in_initcode_to_exis_contract_with_v_transfer_ne_money.py b/tests/ported_static/stCallCodes/test_callcode_in_initcode_to_exis_contract_with_v_transfer_ne_money.py index 12715a68d24..75ece98a9a8 100644 --- a/tests/ported_static/stCallCodes/test_callcode_in_initcode_to_exis_contract_with_v_transfer_ne_money.py +++ b/tests/ported_static/stCallCodes/test_callcode_in_initcode_to_exis_contract_with_v_transfer_ne_money.py @@ -18,10 +18,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCallCodes/test_callcode_in_initcode_to_existing_contract.py b/tests/ported_static/stCallCodes/test_callcode_in_initcode_to_existing_contract.py index 68451f0f3ad..900c2e78bda 100644 --- a/tests/ported_static/stCallCodes/test_callcode_in_initcode_to_existing_contract.py +++ b/tests/ported_static/stCallCodes/test_callcode_in_initcode_to_existing_contract.py @@ -18,10 +18,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCallCreateCallCodeTest/test_call1024_oog.py b/tests/ported_static/stCallCreateCallCodeTest/test_call1024_oog.py index 127b02327fd..16729e411a8 100644 --- a/tests/ported_static/stCallCreateCallCodeTest/test_call1024_oog.py +++ b/tests/ported_static/stCallCreateCallCodeTest/test_call1024_oog.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCallCreateCallCodeTest/test_call1024_pre_calls.py b/tests/ported_static/stCallCreateCallCodeTest/test_call1024_pre_calls.py index bece3316ff2..f6880fd3a04 100644 --- a/tests/ported_static/stCallCreateCallCodeTest/test_call1024_pre_calls.py +++ b/tests/ported_static/stCallCreateCallCodeTest/test_call1024_pre_calls.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCallCreateCallCodeTest/test_call_with_high_value_and_gas_oog.py b/tests/ported_static/stCallCreateCallCodeTest/test_call_with_high_value_and_gas_oog.py index 5240cae80cd..86420f81305 100644 --- a/tests/ported_static/stCallCreateCallCodeTest/test_call_with_high_value_and_gas_oog.py +++ b/tests/ported_static/stCallCreateCallCodeTest/test_call_with_high_value_and_gas_oog.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCallCreateCallCodeTest/test_call_with_high_value_and_oo_gat_tx_level.py b/tests/ported_static/stCallCreateCallCodeTest/test_call_with_high_value_and_oo_gat_tx_level.py index d105d652d73..fba8ac29d26 100644 --- a/tests/ported_static/stCallCreateCallCodeTest/test_call_with_high_value_and_oo_gat_tx_level.py +++ b/tests/ported_static/stCallCreateCallCodeTest/test_call_with_high_value_and_oo_gat_tx_level.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCallCreateCallCodeTest/test_callcode1024_oog.py b/tests/ported_static/stCallCreateCallCodeTest/test_callcode1024_oog.py index 2b2f42bd104..c67dfb77762 100644 --- a/tests/ported_static/stCallCreateCallCodeTest/test_callcode1024_oog.py +++ b/tests/ported_static/stCallCreateCallCodeTest/test_callcode1024_oog.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCallCreateCallCodeTest/test_callcode_lose_gas_oog.py b/tests/ported_static/stCallCreateCallCodeTest/test_callcode_lose_gas_oog.py index a75fb2e2479..9999455ecea 100644 --- a/tests/ported_static/stCallCreateCallCodeTest/test_callcode_lose_gas_oog.py +++ b/tests/ported_static/stCallCreateCallCodeTest/test_callcode_lose_gas_oog.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCallCreateCallCodeTest/test_contract_creation_make_call_that_ask_more_gas_then_transaction_provided.py b/tests/ported_static/stCallCreateCallCodeTest/test_contract_creation_make_call_that_ask_more_gas_then_transaction_provided.py index 8571a26ae45..281a5b080b4 100644 --- a/tests/ported_static/stCallCreateCallCodeTest/test_contract_creation_make_call_that_ask_more_gas_then_transaction_provided.py +++ b/tests/ported_static/stCallCreateCallCodeTest/test_contract_creation_make_call_that_ask_more_gas_then_transaction_provided.py @@ -17,10 +17,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCallCreateCallCodeTest/test_create_fail_balance_too_low.py b/tests/ported_static/stCallCreateCallCodeTest/test_create_fail_balance_too_low.py index 89438cb2af2..d73a689d4c7 100644 --- a/tests/ported_static/stCallCreateCallCodeTest/test_create_fail_balance_too_low.py +++ b/tests/ported_static/stCallCreateCallCodeTest/test_create_fail_balance_too_low.py @@ -22,10 +22,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCallCreateCallCodeTest/test_create_init_oo_gfor_create.py b/tests/ported_static/stCallCreateCallCodeTest/test_create_init_oo_gfor_create.py index 7207b0ccaa1..e5bf328b56d 100644 --- a/tests/ported_static/stCallCreateCallCodeTest/test_create_init_oo_gfor_create.py +++ b/tests/ported_static/stCallCreateCallCodeTest/test_create_init_oo_gfor_create.py @@ -17,10 +17,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCallCreateCallCodeTest/test_create_name_registrator_per_txs_not_enough_gas.py b/tests/ported_static/stCallCreateCallCodeTest/test_create_name_registrator_per_txs_not_enough_gas.py index a6159ad7409..87fb08afa78 100644 --- a/tests/ported_static/stCallCreateCallCodeTest/test_create_name_registrator_per_txs_not_enough_gas.py +++ b/tests/ported_static/stCallCreateCallCodeTest/test_create_name_registrator_per_txs_not_enough_gas.py @@ -16,10 +16,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCodeSizeLimit/test_create2_code_size_limit.py b/tests/ported_static/stCodeSizeLimit/test_create2_code_size_limit.py index 6c865aed2e5..05dca04f085 100644 --- a/tests/ported_static/stCodeSizeLimit/test_create2_code_size_limit.py +++ b/tests/ported_static/stCodeSizeLimit/test_create2_code_size_limit.py @@ -18,10 +18,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCodeSizeLimit/test_create_code_size_limit.py b/tests/ported_static/stCodeSizeLimit/test_create_code_size_limit.py index 82d1b803ba4..fff45614bbb 100644 --- a/tests/ported_static/stCodeSizeLimit/test_create_code_size_limit.py +++ b/tests/ported_static/stCodeSizeLimit/test_create_code_size_limit.py @@ -17,10 +17,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCreate2/test_create2_first_byte_loop.py b/tests/ported_static/stCreate2/test_create2_first_byte_loop.py index cd9a424e273..ad7c99aefec 100644 --- a/tests/ported_static/stCreate2/test_create2_first_byte_loop.py +++ b/tests/ported_static/stCreate2/test_create2_first_byte_loop.py @@ -22,10 +22,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCreate2/test_create2_high_nonce_delegatecall.py b/tests/ported_static/stCreate2/test_create2_high_nonce_delegatecall.py index bccd4bc252a..be2b3ef6ba6 100644 --- a/tests/ported_static/stCreate2/test_create2_high_nonce_delegatecall.py +++ b/tests/ported_static/stCreate2/test_create2_high_nonce_delegatecall.py @@ -18,10 +18,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCreate2/test_create2_init_codes.py b/tests/ported_static/stCreate2/test_create2_init_codes.py index e1ad5fa965b..e575a8ef20d 100644 --- a/tests/ported_static/stCreate2/test_create2_init_codes.py +++ b/tests/ported_static/stCreate2/test_create2_init_codes.py @@ -17,10 +17,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCreate2/test_create2_oo_gafter_init_code.py b/tests/ported_static/stCreate2/test_create2_oo_gafter_init_code.py index 08c8f8e8445..24a1fdd0c09 100644 --- a/tests/ported_static/stCreate2/test_create2_oo_gafter_init_code.py +++ b/tests/ported_static/stCreate2/test_create2_oo_gafter_init_code.py @@ -26,10 +26,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCreate2/test_create2_oo_gafter_init_code_returndata2.py b/tests/ported_static/stCreate2/test_create2_oo_gafter_init_code_returndata2.py index b85e078eaf9..43704a07718 100644 --- a/tests/ported_static/stCreate2/test_create2_oo_gafter_init_code_returndata2.py +++ b/tests/ported_static/stCreate2/test_create2_oo_gafter_init_code_returndata2.py @@ -22,10 +22,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCreate2/test_create2_oog_from_call_refunds.py b/tests/ported_static/stCreate2/test_create2_oog_from_call_refunds.py index 2c5c0b12a72..9efd489bbb9 100644 --- a/tests/ported_static/stCreate2/test_create2_oog_from_call_refunds.py +++ b/tests/ported_static/stCreate2/test_create2_oog_from_call_refunds.py @@ -18,10 +18,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCreate2/test_create2_recursive.py b/tests/ported_static/stCreate2/test_create2_recursive.py index 0f9066fe503..667fb747a88 100644 --- a/tests/ported_static/stCreate2/test_create2_recursive.py +++ b/tests/ported_static/stCreate2/test_create2_recursive.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCreate2/test_create2_smart_init_code.py b/tests/ported_static/stCreate2/test_create2_smart_init_code.py index f63fd21214e..c424fd6c436 100644 --- a/tests/ported_static/stCreate2/test_create2_smart_init_code.py +++ b/tests/ported_static/stCreate2/test_create2_smart_init_code.py @@ -28,10 +28,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCreate2/test_create2_suicide.py b/tests/ported_static/stCreate2/test_create2_suicide.py index c2f522daf2a..e9117e3f42f 100644 --- a/tests/ported_static/stCreate2/test_create2_suicide.py +++ b/tests/ported_static/stCreate2/test_create2_suicide.py @@ -21,10 +21,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCreate2/test_create2call_precompiles.py b/tests/ported_static/stCreate2/test_create2call_precompiles.py index c187bf94f9e..e1f75f55150 100644 --- a/tests/ported_static/stCreate2/test_create2call_precompiles.py +++ b/tests/ported_static/stCreate2/test_create2call_precompiles.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCreate2/test_create2check_fields_in_initcode.py b/tests/ported_static/stCreate2/test_create2check_fields_in_initcode.py index 8ee471f590e..23c56f4ddad 100644 --- a/tests/ported_static/stCreate2/test_create2check_fields_in_initcode.py +++ b/tests/ported_static/stCreate2/test_create2check_fields_in_initcode.py @@ -19,10 +19,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCreate2/test_create2collision_balance.py b/tests/ported_static/stCreate2/test_create2collision_balance.py index b5b06b533e8..621bf693a23 100644 --- a/tests/ported_static/stCreate2/test_create2collision_balance.py +++ b/tests/ported_static/stCreate2/test_create2collision_balance.py @@ -23,10 +23,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCreate2/test_create2collision_code2.py b/tests/ported_static/stCreate2/test_create2collision_code2.py index 6d1bb84bfa3..ccc6ee5b539 100644 --- a/tests/ported_static/stCreate2/test_create2collision_code2.py +++ b/tests/ported_static/stCreate2/test_create2collision_code2.py @@ -23,10 +23,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCreate2/test_create2collision_selfdestructed.py b/tests/ported_static/stCreate2/test_create2collision_selfdestructed.py index 16263ba244b..8b55fb8a5f2 100644 --- a/tests/ported_static/stCreate2/test_create2collision_selfdestructed.py +++ b/tests/ported_static/stCreate2/test_create2collision_selfdestructed.py @@ -24,10 +24,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCreate2/test_create2collision_selfdestructed2.py b/tests/ported_static/stCreate2/test_create2collision_selfdestructed2.py index cc5a26cde9a..5686163f404 100644 --- a/tests/ported_static/stCreate2/test_create2collision_selfdestructed2.py +++ b/tests/ported_static/stCreate2/test_create2collision_selfdestructed2.py @@ -23,10 +23,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCreate2/test_create2no_cash.py b/tests/ported_static/stCreate2/test_create2no_cash.py index 0d5894a3320..587c64889c1 100644 --- a/tests/ported_static/stCreate2/test_create2no_cash.py +++ b/tests/ported_static/stCreate2/test_create2no_cash.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCreate2/test_create_message_reverted.py b/tests/ported_static/stCreate2/test_create_message_reverted.py index 0927af9f824..8d0dce8b33f 100644 --- a/tests/ported_static/stCreate2/test_create_message_reverted.py +++ b/tests/ported_static/stCreate2/test_create_message_reverted.py @@ -22,10 +22,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCreate2/test_create_message_reverted_oog_in_init2.py b/tests/ported_static/stCreate2/test_create_message_reverted_oog_in_init2.py index 927b3fa082e..4e4ef237ca4 100644 --- a/tests/ported_static/stCreate2/test_create_message_reverted_oog_in_init2.py +++ b/tests/ported_static/stCreate2/test_create_message_reverted_oog_in_init2.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCreate2/test_returndatacopy_following_create.py b/tests/ported_static/stCreate2/test_returndatacopy_following_create.py index 323a9dda6c8..35c4d9d9c6b 100644 --- a/tests/ported_static/stCreate2/test_returndatacopy_following_create.py +++ b/tests/ported_static/stCreate2/test_returndatacopy_following_create.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCreate2/test_revert_depth_create2_oog.py b/tests/ported_static/stCreate2/test_revert_depth_create2_oog.py index 860237360e1..00ddb3595a4 100644 --- a/tests/ported_static/stCreate2/test_revert_depth_create2_oog.py +++ b/tests/ported_static/stCreate2/test_revert_depth_create2_oog.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCreate2/test_revert_depth_create2_oog_berlin.py b/tests/ported_static/stCreate2/test_revert_depth_create2_oog_berlin.py index d39042da203..c15b8760d49 100644 --- a/tests/ported_static/stCreate2/test_revert_depth_create2_oog_berlin.py +++ b/tests/ported_static/stCreate2/test_revert_depth_create2_oog_berlin.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCreate2/test_revert_depth_create_address_collision.py b/tests/ported_static/stCreate2/test_revert_depth_create_address_collision.py index 9ecb01ac432..38873c202b1 100644 --- a/tests/ported_static/stCreate2/test_revert_depth_create_address_collision.py +++ b/tests/ported_static/stCreate2/test_revert_depth_create_address_collision.py @@ -23,10 +23,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCreate2/test_revert_depth_create_address_collision_berlin.py b/tests/ported_static/stCreate2/test_revert_depth_create_address_collision_berlin.py index 7e8a5f6ff4b..907358ab624 100644 --- a/tests/ported_static/stCreate2/test_revert_depth_create_address_collision_berlin.py +++ b/tests/ported_static/stCreate2/test_revert_depth_create_address_collision_berlin.py @@ -23,10 +23,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCreate2/test_revert_opcode_create.py b/tests/ported_static/stCreate2/test_revert_opcode_create.py index 75dd02849b3..68bd313e3bd 100644 --- a/tests/ported_static/stCreate2/test_revert_opcode_create.py +++ b/tests/ported_static/stCreate2/test_revert_opcode_create.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCreateTest/test_code_in_constructor.py b/tests/ported_static/stCreateTest/test_code_in_constructor.py index 9227244b39b..9483133d6ff 100644 --- a/tests/ported_static/stCreateTest/test_code_in_constructor.py +++ b/tests/ported_static/stCreateTest/test_code_in_constructor.py @@ -19,10 +19,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCreateTest/test_create_address_warm_after_fail.py b/tests/ported_static/stCreateTest/test_create_address_warm_after_fail.py index 358473d4fb0..dd8381ad14c 100644 --- a/tests/ported_static/stCreateTest/test_create_address_warm_after_fail.py +++ b/tests/ported_static/stCreateTest/test_create_address_warm_after_fail.py @@ -33,10 +33,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCreateTest/test_create_collision_to_empty2.py b/tests/ported_static/stCreateTest/test_create_collision_to_empty2.py index 06f61cbd121..07e3f10ca93 100644 --- a/tests/ported_static/stCreateTest/test_create_collision_to_empty2.py +++ b/tests/ported_static/stCreateTest/test_create_collision_to_empty2.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCreateTest/test_create_e_contract_create_ne_contract_in_init_oog_tr.py b/tests/ported_static/stCreateTest/test_create_e_contract_create_ne_contract_in_init_oog_tr.py index c5b2fbd670f..943824d3990 100644 --- a/tests/ported_static/stCreateTest/test_create_e_contract_create_ne_contract_in_init_oog_tr.py +++ b/tests/ported_static/stCreateTest/test_create_e_contract_create_ne_contract_in_init_oog_tr.py @@ -16,10 +16,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCreateTest/test_create_fail_result.py b/tests/ported_static/stCreateTest/test_create_fail_result.py index c87f3c0ec2d..66311e81aae 100644 --- a/tests/ported_static/stCreateTest/test_create_fail_result.py +++ b/tests/ported_static/stCreateTest/test_create_fail_result.py @@ -19,10 +19,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCreateTest/test_create_large_result.py b/tests/ported_static/stCreateTest/test_create_large_result.py index 936dde89a2a..6f513083ae0 100644 --- a/tests/ported_static/stCreateTest/test_create_large_result.py +++ b/tests/ported_static/stCreateTest/test_create_large_result.py @@ -19,10 +19,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCreateTest/test_create_oo_gafter_init_code.py b/tests/ported_static/stCreateTest/test_create_oo_gafter_init_code.py index 8fcff4f2bbd..df76c260c3e 100644 --- a/tests/ported_static/stCreateTest/test_create_oo_gafter_init_code.py +++ b/tests/ported_static/stCreateTest/test_create_oo_gafter_init_code.py @@ -26,10 +26,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCreateTest/test_create_oo_gafter_init_code_returndata2.py b/tests/ported_static/stCreateTest/test_create_oo_gafter_init_code_returndata2.py index 8b4ef73a768..7f3312ce5bb 100644 --- a/tests/ported_static/stCreateTest/test_create_oo_gafter_init_code_returndata2.py +++ b/tests/ported_static/stCreateTest/test_create_oo_gafter_init_code_returndata2.py @@ -22,10 +22,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCreateTest/test_create_oo_gafter_init_code_revert2.py b/tests/ported_static/stCreateTest/test_create_oo_gafter_init_code_revert2.py index b0099e00d23..d1359fd4695 100644 --- a/tests/ported_static/stCreateTest/test_create_oo_gafter_init_code_revert2.py +++ b/tests/ported_static/stCreateTest/test_create_oo_gafter_init_code_revert2.py @@ -18,10 +18,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCreateTest/test_create_oo_gafter_max_codesize.py b/tests/ported_static/stCreateTest/test_create_oo_gafter_max_codesize.py index 4cd22b226bc..405c05abfd8 100644 --- a/tests/ported_static/stCreateTest/test_create_oo_gafter_max_codesize.py +++ b/tests/ported_static/stCreateTest/test_create_oo_gafter_max_codesize.py @@ -18,10 +18,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCreateTest/test_create_oog_from_call_refunds.py b/tests/ported_static/stCreateTest/test_create_oog_from_call_refunds.py index 665dccfb452..2f7088ae13d 100644 --- a/tests/ported_static/stCreateTest/test_create_oog_from_call_refunds.py +++ b/tests/ported_static/stCreateTest/test_create_oog_from_call_refunds.py @@ -19,10 +19,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCreateTest/test_create_results.py b/tests/ported_static/stCreateTest/test_create_results.py index 3d9a852d13e..a18d11e9cc6 100644 --- a/tests/ported_static/stCreateTest/test_create_results.py +++ b/tests/ported_static/stCreateTest/test_create_results.py @@ -18,10 +18,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCreateTest/test_create_transaction_high_nonce.py b/tests/ported_static/stCreateTest/test_create_transaction_high_nonce.py index da3ad39d2b8..891a350fac2 100644 --- a/tests/ported_static/stCreateTest/test_create_transaction_high_nonce.py +++ b/tests/ported_static/stCreateTest/test_create_transaction_high_nonce.py @@ -22,10 +22,11 @@ TransactionException, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCreateTest/test_transaction_collision_to_empty2.py b/tests/ported_static/stCreateTest/test_transaction_collision_to_empty2.py index 2e85f6b9b3c..e49cd593e8f 100644 --- a/tests/ported_static/stCreateTest/test_transaction_collision_to_empty2.py +++ b/tests/ported_static/stCreateTest/test_transaction_collision_to_empty2.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stCreateTest/test_transaction_collision_to_empty_but_code.py b/tests/ported_static/stCreateTest/test_transaction_collision_to_empty_but_code.py index ab1aedcd0b5..468a026fc98 100644 --- a/tests/ported_static/stCreateTest/test_transaction_collision_to_empty_but_code.py +++ b/tests/ported_static/stCreateTest/test_transaction_collision_to_empty_but_code.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stDelegatecallTestHomestead/test_call1024_oog.py b/tests/ported_static/stDelegatecallTestHomestead/test_call1024_oog.py index 2eb7e09753e..0f6b6855f24 100644 --- a/tests/ported_static/stDelegatecallTestHomestead/test_call1024_oog.py +++ b/tests/ported_static/stDelegatecallTestHomestead/test_call1024_oog.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stDelegatecallTestHomestead/test_call1024_pre_calls.py b/tests/ported_static/stDelegatecallTestHomestead/test_call1024_pre_calls.py index 79dc8d66b56..12a08a520dd 100644 --- a/tests/ported_static/stDelegatecallTestHomestead/test_call1024_pre_calls.py +++ b/tests/ported_static/stDelegatecallTestHomestead/test_call1024_pre_calls.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stDelegatecallTestHomestead/test_callcode_lose_gas_oog.py b/tests/ported_static/stDelegatecallTestHomestead/test_callcode_lose_gas_oog.py index 1327e8243c7..121f84bb6c4 100644 --- a/tests/ported_static/stDelegatecallTestHomestead/test_callcode_lose_gas_oog.py +++ b/tests/ported_static/stDelegatecallTestHomestead/test_callcode_lose_gas_oog.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stEIP1153_transientStorage/test_trans_storage_ok.py b/tests/ported_static/stEIP1153_transientStorage/test_trans_storage_ok.py index 818e6e4d756..bd02b257117 100644 --- a/tests/ported_static/stEIP1153_transientStorage/test_trans_storage_ok.py +++ b/tests/ported_static/stEIP1153_transientStorage/test_trans_storage_ok.py @@ -18,10 +18,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stEIP1153_transientStorage/test_trans_storage_reset.py b/tests/ported_static/stEIP1153_transientStorage/test_trans_storage_reset.py index de1ad618dd6..3c607ae091c 100644 --- a/tests/ported_static/stEIP1153_transientStorage/test_trans_storage_reset.py +++ b/tests/ported_static/stEIP1153_transientStorage/test_trans_storage_reset.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stEIP150singleCodeGasPrices/test_eip2929.py b/tests/ported_static/stEIP150singleCodeGasPrices/test_eip2929.py index 8cc16c0dc46..a15a8101267 100644 --- a/tests/ported_static/stEIP150singleCodeGasPrices/test_eip2929.py +++ b/tests/ported_static/stEIP150singleCodeGasPrices/test_eip2929.py @@ -36,10 +36,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stEIP150singleCodeGasPrices/test_eip2929_minus_ff.py b/tests/ported_static/stEIP150singleCodeGasPrices/test_eip2929_minus_ff.py index fbba23d97e0..c27ec8bd484 100644 --- a/tests/ported_static/stEIP150singleCodeGasPrices/test_eip2929_minus_ff.py +++ b/tests/ported_static/stEIP150singleCodeGasPrices/test_eip2929_minus_ff.py @@ -26,10 +26,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stEIP150singleCodeGasPrices/test_gas_cost.py b/tests/ported_static/stEIP150singleCodeGasPrices/test_gas_cost.py index d539c14f735..d84fd092041 100644 --- a/tests/ported_static/stEIP150singleCodeGasPrices/test_gas_cost.py +++ b/tests/ported_static/stEIP150singleCodeGasPrices/test_gas_cost.py @@ -29,10 +29,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stEIP150singleCodeGasPrices/test_gas_cost_memory.py b/tests/ported_static/stEIP150singleCodeGasPrices/test_gas_cost_memory.py index ba5c6ac4b67..8141758957c 100644 --- a/tests/ported_static/stEIP150singleCodeGasPrices/test_gas_cost_memory.py +++ b/tests/ported_static/stEIP150singleCodeGasPrices/test_gas_cost_memory.py @@ -26,10 +26,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stEIP1559/test_low_gas_limit.py b/tests/ported_static/stEIP1559/test_low_gas_limit.py index 9e42e627208..a95571192ef 100644 --- a/tests/ported_static/stEIP1559/test_low_gas_limit.py +++ b/tests/ported_static/stEIP1559/test_low_gas_limit.py @@ -24,10 +24,11 @@ TransactionException, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stEIP1559/test_low_gas_price_old_types.py b/tests/ported_static/stEIP1559/test_low_gas_price_old_types.py index 48974cd0123..543101b5d94 100644 --- a/tests/ported_static/stEIP1559/test_low_gas_price_old_types.py +++ b/tests/ported_static/stEIP1559/test_low_gas_price_old_types.py @@ -16,10 +16,11 @@ TransactionException, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stEIP1559/test_out_of_funds.py b/tests/ported_static/stEIP1559/test_out_of_funds.py index 8684ccdbf0d..a8739b3ffbd 100644 --- a/tests/ported_static/stEIP1559/test_out_of_funds.py +++ b/tests/ported_static/stEIP1559/test_out_of_funds.py @@ -16,10 +16,11 @@ TransactionException, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stEIP1559/test_out_of_funds_old_types.py b/tests/ported_static/stEIP1559/test_out_of_funds_old_types.py index faa5538f101..21d32ccff61 100644 --- a/tests/ported_static/stEIP1559/test_out_of_funds_old_types.py +++ b/tests/ported_static/stEIP1559/test_out_of_funds_old_types.py @@ -16,10 +16,11 @@ TransactionException, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stEIP1559/test_val_causes_oof.py b/tests/ported_static/stEIP1559/test_val_causes_oof.py index dfbb5a3e162..db21231c6a7 100644 --- a/tests/ported_static/stEIP1559/test_val_causes_oof.py +++ b/tests/ported_static/stEIP1559/test_val_causes_oof.py @@ -17,10 +17,11 @@ TransactionException, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stEIP2930/test_address_opcodes.py b/tests/ported_static/stEIP2930/test_address_opcodes.py index 38279bf7408..0ee98b85c8d 100644 --- a/tests/ported_static/stEIP2930/test_address_opcodes.py +++ b/tests/ported_static/stEIP2930/test_address_opcodes.py @@ -30,10 +30,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op, Opcode + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op, Opcode REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stEIP2930/test_coinbase_t01.py b/tests/ported_static/stEIP2930/test_coinbase_t01.py index 754f52c4628..af24697b599 100644 --- a/tests/ported_static/stEIP2930/test_coinbase_t01.py +++ b/tests/ported_static/stEIP2930/test_coinbase_t01.py @@ -26,10 +26,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stEIP2930/test_coinbase_t2.py b/tests/ported_static/stEIP2930/test_coinbase_t2.py index 5e8924e0b17..4a4bbe1a940 100644 --- a/tests/ported_static/stEIP2930/test_coinbase_t2.py +++ b/tests/ported_static/stEIP2930/test_coinbase_t2.py @@ -26,10 +26,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stEIP2930/test_manual_create.py b/tests/ported_static/stEIP2930/test_manual_create.py index 6bf26e1228a..1218f8a5d48 100644 --- a/tests/ported_static/stEIP2930/test_manual_create.py +++ b/tests/ported_static/stEIP2930/test_manual_create.py @@ -29,10 +29,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stEIP2930/test_storage_costs.py b/tests/ported_static/stEIP2930/test_storage_costs.py index 5e588d94abe..a75eb0ade75 100644 --- a/tests/ported_static/stEIP2930/test_storage_costs.py +++ b/tests/ported_static/stEIP2930/test_storage_costs.py @@ -32,10 +32,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stEIP2930/test_transaction_costs.py b/tests/ported_static/stEIP2930/test_transaction_costs.py index fb03a48592e..95895400ebc 100644 --- a/tests/ported_static/stEIP2930/test_transaction_costs.py +++ b/tests/ported_static/stEIP2930/test_transaction_costs.py @@ -28,10 +28,11 @@ Transaction, ) from execution_testing.forks import Amsterdam, Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stEIP2930/test_varied_context.py b/tests/ported_static/stEIP2930/test_varied_context.py index 705de655138..48aeb6ae16c 100644 --- a/tests/ported_static/stEIP2930/test_varied_context.py +++ b/tests/ported_static/stEIP2930/test_varied_context.py @@ -36,10 +36,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stEIP3607/test_transaction_colliding_with_non_empty_account_init_paris.py b/tests/ported_static/stEIP3607/test_transaction_colliding_with_non_empty_account_init_paris.py index 3682a79177a..0ad9b15a2e6 100644 --- a/tests/ported_static/stEIP3607/test_transaction_colliding_with_non_empty_account_init_paris.py +++ b/tests/ported_static/stEIP3607/test_transaction_colliding_with_non_empty_account_init_paris.py @@ -17,10 +17,11 @@ TransactionException, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stEIP3855_push0/test_push0.py b/tests/ported_static/stEIP3855_push0/test_push0.py index 9bc2c28c7de..ad30c228266 100644 --- a/tests/ported_static/stEIP3855_push0/test_push0.py +++ b/tests/ported_static/stEIP3855_push0/test_push0.py @@ -19,10 +19,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stEIP3860_limitmeterinitcode/test_create2_init_code_size_limit.py b/tests/ported_static/stEIP3860_limitmeterinitcode/test_create2_init_code_size_limit.py index 006e46e0616..3734cef6049 100644 --- a/tests/ported_static/stEIP3860_limitmeterinitcode/test_create2_init_code_size_limit.py +++ b/tests/ported_static/stEIP3860_limitmeterinitcode/test_create2_init_code_size_limit.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stEIP3860_limitmeterinitcode/test_create_init_code_size_limit.py b/tests/ported_static/stEIP3860_limitmeterinitcode/test_create_init_code_size_limit.py index dd3acfc9717..6547fff3b58 100644 --- a/tests/ported_static/stEIP3860_limitmeterinitcode/test_create_init_code_size_limit.py +++ b/tests/ported_static/stEIP3860_limitmeterinitcode/test_create_init_code_size_limit.py @@ -18,10 +18,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stEIP3860_limitmeterinitcode/test_creation_tx_init_code_size_limit.py b/tests/ported_static/stEIP3860_limitmeterinitcode/test_creation_tx_init_code_size_limit.py index 8a935525416..62649b547ec 100644 --- a/tests/ported_static/stEIP3860_limitmeterinitcode/test_creation_tx_init_code_size_limit.py +++ b/tests/ported_static/stEIP3860_limitmeterinitcode/test_creation_tx_init_code_size_limit.py @@ -19,7 +19,8 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) diff --git a/tests/ported_static/stEIP5656_MCOPY/test_mcopy.py b/tests/ported_static/stEIP5656_MCOPY/test_mcopy.py index acae638389f..98b3a140eff 100644 --- a/tests/ported_static/stEIP5656_MCOPY/test_mcopy.py +++ b/tests/ported_static/stEIP5656_MCOPY/test_mcopy.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stEIP5656_MCOPY/test_mcopy_memory_expansion_cost.py b/tests/ported_static/stEIP5656_MCOPY/test_mcopy_memory_expansion_cost.py index c6b1b90837d..9e72f01520f 100644 --- a/tests/ported_static/stEIP5656_MCOPY/test_mcopy_memory_expansion_cost.py +++ b/tests/ported_static/stEIP5656_MCOPY/test_mcopy_memory_expansion_cost.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stExample/test_labels_example.py b/tests/ported_static/stExample/test_labels_example.py index f78d807ae82..aafe824c83e 100644 --- a/tests/ported_static/stExample/test_labels_example.py +++ b/tests/ported_static/stExample/test_labels_example.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stExample/test_ranges_example.py b/tests/ported_static/stExample/test_ranges_example.py index 25cf6aa2ded..6572b4428c3 100644 --- a/tests/ported_static/stExample/test_ranges_example.py +++ b/tests/ported_static/stExample/test_ranges_example.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stInitCodeTest/test_out_of_gas_contract_creation.py b/tests/ported_static/stInitCodeTest/test_out_of_gas_contract_creation.py index 9525495cff9..f346844b949 100644 --- a/tests/ported_static/stInitCodeTest/test_out_of_gas_contract_creation.py +++ b/tests/ported_static/stInitCodeTest/test_out_of_gas_contract_creation.py @@ -16,10 +16,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stInitCodeTest/test_out_of_gas_prefunded_contract_creation.py b/tests/ported_static/stInitCodeTest/test_out_of_gas_prefunded_contract_creation.py index 51dd0b6725e..93f7f151c9e 100644 --- a/tests/ported_static/stInitCodeTest/test_out_of_gas_prefunded_contract_creation.py +++ b/tests/ported_static/stInitCodeTest/test_out_of_gas_prefunded_contract_creation.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stMemExpandingEIP150Calls/test_oo_gin_return.py b/tests/ported_static/stMemExpandingEIP150Calls/test_oo_gin_return.py index 145e2b341e1..e5a4324b10b 100644 --- a/tests/ported_static/stMemExpandingEIP150Calls/test_oo_gin_return.py +++ b/tests/ported_static/stMemExpandingEIP150Calls/test_oo_gin_return.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stMemoryStressTest/test_fill_stack.py b/tests/ported_static/stMemoryStressTest/test_fill_stack.py index ecb5ba40c04..25b22b08d14 100644 --- a/tests/ported_static/stMemoryStressTest/test_fill_stack.py +++ b/tests/ported_static/stMemoryStressTest/test_fill_stack.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stMemoryStressTest/test_mload32bit_bound.py b/tests/ported_static/stMemoryStressTest/test_mload32bit_bound.py index 5763dad5154..fca82bbfc15 100644 --- a/tests/ported_static/stMemoryStressTest/test_mload32bit_bound.py +++ b/tests/ported_static/stMemoryStressTest/test_mload32bit_bound.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stMemoryStressTest/test_mload32bit_bound2.py b/tests/ported_static/stMemoryStressTest/test_mload32bit_bound2.py index 322e11428e8..454e27c5cf6 100644 --- a/tests/ported_static/stMemoryStressTest/test_mload32bit_bound2.py +++ b/tests/ported_static/stMemoryStressTest/test_mload32bit_bound2.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stMemoryStressTest/test_mload32bit_bound_msize.py b/tests/ported_static/stMemoryStressTest/test_mload32bit_bound_msize.py index ccff8e7bdf2..44ee2071b77 100644 --- a/tests/ported_static/stMemoryStressTest/test_mload32bit_bound_msize.py +++ b/tests/ported_static/stMemoryStressTest/test_mload32bit_bound_msize.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stMemoryStressTest/test_mstore_bounds2a.py b/tests/ported_static/stMemoryStressTest/test_mstore_bounds2a.py index 82cbcebb094..6d0cb860812 100644 --- a/tests/ported_static/stMemoryStressTest/test_mstore_bounds2a.py +++ b/tests/ported_static/stMemoryStressTest/test_mstore_bounds2a.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stMemoryStressTest/test_return_bounds.py b/tests/ported_static/stMemoryStressTest/test_return_bounds.py index cd09e0777dc..ee5a154fa90 100644 --- a/tests/ported_static/stMemoryStressTest/test_return_bounds.py +++ b/tests/ported_static/stMemoryStressTest/test_return_bounds.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stMemoryStressTest/test_sstore_bounds.py b/tests/ported_static/stMemoryStressTest/test_sstore_bounds.py index 702dd94aeb5..dc6ec4ee358 100644 --- a/tests/ported_static/stMemoryStressTest/test_sstore_bounds.py +++ b/tests/ported_static/stMemoryStressTest/test_sstore_bounds.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stMemoryTest/test_buffer.py b/tests/ported_static/stMemoryTest/test_buffer.py index e8eb1086eaf..148d479581f 100644 --- a/tests/ported_static/stMemoryTest/test_buffer.py +++ b/tests/ported_static/stMemoryTest/test_buffer.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stMemoryTest/test_buffer_src_offset.py b/tests/ported_static/stMemoryTest/test_buffer_src_offset.py index 1a644f966f1..73ad2073666 100644 --- a/tests/ported_static/stMemoryTest/test_buffer_src_offset.py +++ b/tests/ported_static/stMemoryTest/test_buffer_src_offset.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stMemoryTest/test_oog.py b/tests/ported_static/stMemoryTest/test_oog.py index cd47deb6687..5efece6b89d 100644 --- a/tests/ported_static/stMemoryTest/test_oog.py +++ b/tests/ported_static/stMemoryTest/test_oog.py @@ -26,10 +26,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stPreCompiledContracts/test_modexp.py b/tests/ported_static/stPreCompiledContracts/test_modexp.py index 982a98231e2..b15459209ee 100644 --- a/tests/ported_static/stPreCompiledContracts/test_modexp.py +++ b/tests/ported_static/stPreCompiledContracts/test_modexp.py @@ -18,10 +18,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stPreCompiledContracts/test_modexp_tests.py b/tests/ported_static/stPreCompiledContracts/test_modexp_tests.py index 487eb639575..0ff04d412ad 100644 --- a/tests/ported_static/stPreCompiledContracts/test_modexp_tests.py +++ b/tests/ported_static/stPreCompiledContracts/test_modexp_tests.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stPreCompiledContracts/test_precomps_eip2929_cancun.py b/tests/ported_static/stPreCompiledContracts/test_precomps_eip2929_cancun.py index cbd90e4a2a0..1c4a3be1c29 100644 --- a/tests/ported_static/stPreCompiledContracts/test_precomps_eip2929_cancun.py +++ b/tests/ported_static/stPreCompiledContracts/test_precomps_eip2929_cancun.py @@ -30,10 +30,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stPreCompiledContracts2/test_call_ecrecover_overflow.py b/tests/ported_static/stPreCompiledContracts2/test_call_ecrecover_overflow.py index f88c2d7b053..f8fb7054ac9 100644 --- a/tests/ported_static/stPreCompiledContracts2/test_call_ecrecover_overflow.py +++ b/tests/ported_static/stPreCompiledContracts2/test_call_ecrecover_overflow.py @@ -25,10 +25,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stPreCompiledContracts2/test_ecrecover_weird_v.py b/tests/ported_static/stPreCompiledContracts2/test_ecrecover_weird_v.py index 3a607960508..e3f7d612441 100644 --- a/tests/ported_static/stPreCompiledContracts2/test_ecrecover_weird_v.py +++ b/tests/ported_static/stPreCompiledContracts2/test_ecrecover_weird_v.py @@ -18,10 +18,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stPreCompiledContracts2/test_modexp_0_0_0_20500.py b/tests/ported_static/stPreCompiledContracts2/test_modexp_0_0_0_20500.py index 9d30c32379d..3c068b7a37b 100644 --- a/tests/ported_static/stPreCompiledContracts2/test_modexp_0_0_0_20500.py +++ b/tests/ported_static/stPreCompiledContracts2/test_modexp_0_0_0_20500.py @@ -21,10 +21,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stQuadraticComplexityTest/test_call1_mb1024_calldepth.py b/tests/ported_static/stQuadraticComplexityTest/test_call1_mb1024_calldepth.py index 228828eea8c..8082990bd0e 100644 --- a/tests/ported_static/stQuadraticComplexityTest/test_call1_mb1024_calldepth.py +++ b/tests/ported_static/stQuadraticComplexityTest/test_call1_mb1024_calldepth.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stQuadraticComplexityTest/test_call20_kbytes_contract50_1.py b/tests/ported_static/stQuadraticComplexityTest/test_call20_kbytes_contract50_1.py index 4b1d03309c1..b0effdd208f 100644 --- a/tests/ported_static/stQuadraticComplexityTest/test_call20_kbytes_contract50_1.py +++ b/tests/ported_static/stQuadraticComplexityTest/test_call20_kbytes_contract50_1.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stQuadraticComplexityTest/test_call20_kbytes_contract50_2.py b/tests/ported_static/stQuadraticComplexityTest/test_call20_kbytes_contract50_2.py index 00f12488c0a..0f085c0013e 100644 --- a/tests/ported_static/stQuadraticComplexityTest/test_call20_kbytes_contract50_2.py +++ b/tests/ported_static/stQuadraticComplexityTest/test_call20_kbytes_contract50_2.py @@ -19,10 +19,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stQuadraticComplexityTest/test_call20_kbytes_contract50_3.py b/tests/ported_static/stQuadraticComplexityTest/test_call20_kbytes_contract50_3.py index 172093e2818..114e90f2604 100644 --- a/tests/ported_static/stQuadraticComplexityTest/test_call20_kbytes_contract50_3.py +++ b/tests/ported_static/stQuadraticComplexityTest/test_call20_kbytes_contract50_3.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stQuadraticComplexityTest/test_call50000.py b/tests/ported_static/stQuadraticComplexityTest/test_call50000.py index b250e191052..8c6a05718bd 100644 --- a/tests/ported_static/stQuadraticComplexityTest/test_call50000.py +++ b/tests/ported_static/stQuadraticComplexityTest/test_call50000.py @@ -19,10 +19,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stQuadraticComplexityTest/test_call50000_ecrec.py b/tests/ported_static/stQuadraticComplexityTest/test_call50000_ecrec.py index 5ef0eba7901..c08fe1e1611 100644 --- a/tests/ported_static/stQuadraticComplexityTest/test_call50000_ecrec.py +++ b/tests/ported_static/stQuadraticComplexityTest/test_call50000_ecrec.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stQuadraticComplexityTest/test_call50000_identity.py b/tests/ported_static/stQuadraticComplexityTest/test_call50000_identity.py index 6d52d835c00..37968c5d775 100644 --- a/tests/ported_static/stQuadraticComplexityTest/test_call50000_identity.py +++ b/tests/ported_static/stQuadraticComplexityTest/test_call50000_identity.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stQuadraticComplexityTest/test_call50000_identity2.py b/tests/ported_static/stQuadraticComplexityTest/test_call50000_identity2.py index f2d27a68845..4deaf1c83d5 100644 --- a/tests/ported_static/stQuadraticComplexityTest/test_call50000_identity2.py +++ b/tests/ported_static/stQuadraticComplexityTest/test_call50000_identity2.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stQuadraticComplexityTest/test_call50000_rip160.py b/tests/ported_static/stQuadraticComplexityTest/test_call50000_rip160.py index 143a678a724..4a39b90a516 100644 --- a/tests/ported_static/stQuadraticComplexityTest/test_call50000_rip160.py +++ b/tests/ported_static/stQuadraticComplexityTest/test_call50000_rip160.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stQuadraticComplexityTest/test_call50000_sha256.py b/tests/ported_static/stQuadraticComplexityTest/test_call50000_sha256.py index 21b44470ceb..3972f39f35e 100644 --- a/tests/ported_static/stQuadraticComplexityTest/test_call50000_sha256.py +++ b/tests/ported_static/stQuadraticComplexityTest/test_call50000_sha256.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stQuadraticComplexityTest/test_callcode50000.py b/tests/ported_static/stQuadraticComplexityTest/test_callcode50000.py index ad6b43a3b19..859b4707198 100644 --- a/tests/ported_static/stQuadraticComplexityTest/test_callcode50000.py +++ b/tests/ported_static/stQuadraticComplexityTest/test_callcode50000.py @@ -19,10 +19,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stQuadraticComplexityTest/test_create1000.py b/tests/ported_static/stQuadraticComplexityTest/test_create1000.py index 74c1e0f1112..72d5ec759fc 100644 --- a/tests/ported_static/stQuadraticComplexityTest/test_create1000.py +++ b/tests/ported_static/stQuadraticComplexityTest/test_create1000.py @@ -17,10 +17,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stQuadraticComplexityTest/test_create1000_shnghai.py b/tests/ported_static/stQuadraticComplexityTest/test_create1000_shnghai.py index 81abf9ec742..89cfa22dd4e 100644 --- a/tests/ported_static/stQuadraticComplexityTest/test_create1000_shnghai.py +++ b/tests/ported_static/stQuadraticComplexityTest/test_create1000_shnghai.py @@ -18,10 +18,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stQuadraticComplexityTest/test_return50000.py b/tests/ported_static/stQuadraticComplexityTest/test_return50000.py index b1471276494..e77db67b827 100644 --- a/tests/ported_static/stQuadraticComplexityTest/test_return50000.py +++ b/tests/ported_static/stQuadraticComplexityTest/test_return50000.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stQuadraticComplexityTest/test_return50000_2.py b/tests/ported_static/stQuadraticComplexityTest/test_return50000_2.py index d8f060e96a9..f629088cf53 100644 --- a/tests/ported_static/stQuadraticComplexityTest/test_return50000_2.py +++ b/tests/ported_static/stQuadraticComplexityTest/test_return50000_2.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stRefundTest/test_refund_call_to_suicide_no_storage.py b/tests/ported_static/stRefundTest/test_refund_call_to_suicide_no_storage.py index a374d190c0b..51c1efee15b 100644 --- a/tests/ported_static/stRefundTest/test_refund_call_to_suicide_no_storage.py +++ b/tests/ported_static/stRefundTest/test_refund_call_to_suicide_no_storage.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stRefundTest/test_refund_call_to_suicide_storage.py b/tests/ported_static/stRefundTest/test_refund_call_to_suicide_storage.py index ebc41bce622..9cb8b32913e 100644 --- a/tests/ported_static/stRefundTest/test_refund_call_to_suicide_storage.py +++ b/tests/ported_static/stRefundTest/test_refund_call_to_suicide_storage.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stRefundTest/test_refund_call_to_suicide_twice.py b/tests/ported_static/stRefundTest/test_refund_call_to_suicide_twice.py index bbcce3a6ed0..ee26908b919 100644 --- a/tests/ported_static/stRefundTest/test_refund_call_to_suicide_twice.py +++ b/tests/ported_static/stRefundTest/test_refund_call_to_suicide_twice.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stRefundTest/test_refund_suicide50procent_cap.py b/tests/ported_static/stRefundTest/test_refund_suicide50procent_cap.py index 2fc32dac534..4083b826958 100644 --- a/tests/ported_static/stRefundTest/test_refund_suicide50procent_cap.py +++ b/tests/ported_static/stRefundTest/test_refund_suicide50procent_cap.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stReturnDataTest/test_modexp_modsize0_returndatasize.py b/tests/ported_static/stReturnDataTest/test_modexp_modsize0_returndatasize.py index 28bc4574fc5..f5cc065233d 100644 --- a/tests/ported_static/stReturnDataTest/test_modexp_modsize0_returndatasize.py +++ b/tests/ported_static/stReturnDataTest/test_modexp_modsize0_returndatasize.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stReturnDataTest/test_too_long_return_data_copy.py b/tests/ported_static/stReturnDataTest/test_too_long_return_data_copy.py index 2d9ba21d3a9..8ad886570bc 100644 --- a/tests/ported_static/stReturnDataTest/test_too_long_return_data_copy.py +++ b/tests/ported_static/stReturnDataTest/test_too_long_return_data_copy.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stRevertTest/test_cost_revert.py b/tests/ported_static/stRevertTest/test_cost_revert.py index 7c6fddec42c..16cb55a0b1d 100644 --- a/tests/ported_static/stRevertTest/test_cost_revert.py +++ b/tests/ported_static/stRevertTest/test_cost_revert.py @@ -18,10 +18,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stRevertTest/test_revert_depth_create_address_collision.py b/tests/ported_static/stRevertTest/test_revert_depth_create_address_collision.py index fe255c65d17..11c3aaa568c 100644 --- a/tests/ported_static/stRevertTest/test_revert_depth_create_address_collision.py +++ b/tests/ported_static/stRevertTest/test_revert_depth_create_address_collision.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stRevertTest/test_revert_depth_create_oog.py b/tests/ported_static/stRevertTest/test_revert_depth_create_oog.py index 8f3fa8594eb..3cd7ee838c3 100644 --- a/tests/ported_static/stRevertTest/test_revert_depth_create_oog.py +++ b/tests/ported_static/stRevertTest/test_revert_depth_create_oog.py @@ -17,10 +17,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stRevertTest/test_revert_opcode.py b/tests/ported_static/stRevertTest/test_revert_opcode.py index e76a7ee25f2..a88cd707142 100644 --- a/tests/ported_static/stRevertTest/test_revert_opcode.py +++ b/tests/ported_static/stRevertTest/test_revert_opcode.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stRevertTest/test_revert_opcode_calls.py b/tests/ported_static/stRevertTest/test_revert_opcode_calls.py index e8dd0e77288..9ca4076ee6c 100644 --- a/tests/ported_static/stRevertTest/test_revert_opcode_calls.py +++ b/tests/ported_static/stRevertTest/test_revert_opcode_calls.py @@ -26,10 +26,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stRevertTest/test_revert_opcode_create.py b/tests/ported_static/stRevertTest/test_revert_opcode_create.py index 3ac33f19c11..838ed497f76 100644 --- a/tests/ported_static/stRevertTest/test_revert_opcode_create.py +++ b/tests/ported_static/stRevertTest/test_revert_opcode_create.py @@ -17,10 +17,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stRevertTest/test_revert_opcode_direct_call.py b/tests/ported_static/stRevertTest/test_revert_opcode_direct_call.py index e653efdc770..475270e3c47 100644 --- a/tests/ported_static/stRevertTest/test_revert_opcode_direct_call.py +++ b/tests/ported_static/stRevertTest/test_revert_opcode_direct_call.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stRevertTest/test_revert_opcode_in_calls_on_non_empty_return_data.py b/tests/ported_static/stRevertTest/test_revert_opcode_in_calls_on_non_empty_return_data.py index 6079b5cf452..a49cc7b38bc 100644 --- a/tests/ported_static/stRevertTest/test_revert_opcode_in_calls_on_non_empty_return_data.py +++ b/tests/ported_static/stRevertTest/test_revert_opcode_in_calls_on_non_empty_return_data.py @@ -21,10 +21,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stRevertTest/test_revert_opcode_multiple_sub_calls.py b/tests/ported_static/stRevertTest/test_revert_opcode_multiple_sub_calls.py index b91a3a76eb5..b5e9f1f229e 100644 --- a/tests/ported_static/stRevertTest/test_revert_opcode_multiple_sub_calls.py +++ b/tests/ported_static/stRevertTest/test_revert_opcode_multiple_sub_calls.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stRevertTest/test_revert_opcode_return.py b/tests/ported_static/stRevertTest/test_revert_opcode_return.py index 325f7568164..dfed02c676a 100644 --- a/tests/ported_static/stRevertTest/test_revert_opcode_return.py +++ b/tests/ported_static/stRevertTest/test_revert_opcode_return.py @@ -21,10 +21,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stRevertTest/test_revert_precompiled_touch_exact_oog_paris.py b/tests/ported_static/stRevertTest/test_revert_precompiled_touch_exact_oog_paris.py index 936a3505eb0..3961f91e09b 100644 --- a/tests/ported_static/stRevertTest/test_revert_precompiled_touch_exact_oog_paris.py +++ b/tests/ported_static/stRevertTest/test_revert_precompiled_touch_exact_oog_paris.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork, Prague -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stRevertTest/test_revert_precompiled_touch_paris.py b/tests/ported_static/stRevertTest/test_revert_precompiled_touch_paris.py index 5e3ebc1ede5..4239313b32c 100644 --- a/tests/ported_static/stRevertTest/test_revert_precompiled_touch_paris.py +++ b/tests/ported_static/stRevertTest/test_revert_precompiled_touch_paris.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stRevertTest/test_revert_precompiled_touch_storage_paris.py b/tests/ported_static/stRevertTest/test_revert_precompiled_touch_storage_paris.py index 25d6fe6394e..9bbe720f4e3 100644 --- a/tests/ported_static/stRevertTest/test_revert_precompiled_touch_storage_paris.py +++ b/tests/ported_static/stRevertTest/test_revert_precompiled_touch_storage_paris.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stRevertTest/test_revert_sub_call_storage_oog.py b/tests/ported_static/stRevertTest/test_revert_sub_call_storage_oog.py index 9ba23f0660e..8fa75a6c52d 100644 --- a/tests/ported_static/stRevertTest/test_revert_sub_call_storage_oog.py +++ b/tests/ported_static/stRevertTest/test_revert_sub_call_storage_oog.py @@ -19,7 +19,8 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) diff --git a/tests/ported_static/stRevertTest/test_revert_sub_call_storage_oog2.py b/tests/ported_static/stRevertTest/test_revert_sub_call_storage_oog2.py index dd3958fe322..eb6b90120c4 100644 --- a/tests/ported_static/stRevertTest/test_revert_sub_call_storage_oog2.py +++ b/tests/ported_static/stRevertTest/test_revert_sub_call_storage_oog2.py @@ -19,7 +19,8 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) diff --git a/tests/ported_static/stSStoreTest/test_sstore_0to0.py b/tests/ported_static/stSStoreTest/test_sstore_0to0.py index a600afc7370..bec0a45a0fd 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_0to0.py +++ b/tests/ported_static/stSStoreTest/test_sstore_0to0.py @@ -17,10 +17,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stSStoreTest/test_sstore_0to0to0.py b/tests/ported_static/stSStoreTest/test_sstore_0to0to0.py index e7244014c13..665a363bb26 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_0to0to0.py +++ b/tests/ported_static/stSStoreTest/test_sstore_0to0to0.py @@ -17,10 +17,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stSStoreTest/test_sstore_0to0to_x.py b/tests/ported_static/stSStoreTest/test_sstore_0to0to_x.py index 9d6cf9ea9e6..954cd6141ab 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_0to0to_x.py +++ b/tests/ported_static/stSStoreTest/test_sstore_0to0to_x.py @@ -17,10 +17,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stSStoreTest/test_sstore_0to_x.py b/tests/ported_static/stSStoreTest/test_sstore_0to_x.py index 797483aee1e..2ba2d601af1 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_0to_x.py +++ b/tests/ported_static/stSStoreTest/test_sstore_0to_x.py @@ -17,10 +17,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stSStoreTest/test_sstore_0to_xto0.py b/tests/ported_static/stSStoreTest/test_sstore_0to_xto0.py index aa8728ba931..1e2fd6c528f 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_0to_xto0.py +++ b/tests/ported_static/stSStoreTest/test_sstore_0to_xto0.py @@ -17,10 +17,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stSStoreTest/test_sstore_0to_xto0to_x.py b/tests/ported_static/stSStoreTest/test_sstore_0to_xto0to_x.py index ff9254c1ebf..43cbbf555ef 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_0to_xto0to_x.py +++ b/tests/ported_static/stSStoreTest/test_sstore_0to_xto0to_x.py @@ -17,10 +17,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stSStoreTest/test_sstore_0to_xto_x.py b/tests/ported_static/stSStoreTest/test_sstore_0to_xto_x.py index 0b28bdfd8c8..a316e3dc773 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_0to_xto_x.py +++ b/tests/ported_static/stSStoreTest/test_sstore_0to_xto_x.py @@ -17,10 +17,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stSStoreTest/test_sstore_0to_xto_y.py b/tests/ported_static/stSStoreTest/test_sstore_0to_xto_y.py index 0c06be05a9e..48a17648c37 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_0to_xto_y.py +++ b/tests/ported_static/stSStoreTest/test_sstore_0to_xto_y.py @@ -17,10 +17,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stSStoreTest/test_sstore_change_from_external_call_in_init_code.py b/tests/ported_static/stSStoreTest/test_sstore_change_from_external_call_in_init_code.py index b73e6cd6c83..b20952d9a2b 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_change_from_external_call_in_init_code.py +++ b/tests/ported_static/stSStoreTest/test_sstore_change_from_external_call_in_init_code.py @@ -25,10 +25,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stSStoreTest/test_sstore_gas_left.py b/tests/ported_static/stSStoreTest/test_sstore_gas_left.py index 4c4bf0c54c0..30686397044 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_gas_left.py +++ b/tests/ported_static/stSStoreTest/test_sstore_gas_left.py @@ -24,10 +24,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stSStoreTest/test_sstore_xto0.py b/tests/ported_static/stSStoreTest/test_sstore_xto0.py index 3c1e74378c0..80f7bd1d35b 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_xto0.py +++ b/tests/ported_static/stSStoreTest/test_sstore_xto0.py @@ -17,10 +17,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stSStoreTest/test_sstore_xto0to0.py b/tests/ported_static/stSStoreTest/test_sstore_xto0to0.py index b3857446a9a..dfe4b8c9ce9 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_xto0to0.py +++ b/tests/ported_static/stSStoreTest/test_sstore_xto0to0.py @@ -17,10 +17,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stSStoreTest/test_sstore_xto0to_x.py b/tests/ported_static/stSStoreTest/test_sstore_xto0to_x.py index 6aafce6dbf1..806243f8154 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_xto0to_x.py +++ b/tests/ported_static/stSStoreTest/test_sstore_xto0to_x.py @@ -17,10 +17,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stSStoreTest/test_sstore_xto0to_xto0.py b/tests/ported_static/stSStoreTest/test_sstore_xto0to_xto0.py index f497142216d..44a9c8dafbd 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_xto0to_xto0.py +++ b/tests/ported_static/stSStoreTest/test_sstore_xto0to_xto0.py @@ -17,10 +17,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stSStoreTest/test_sstore_xto0to_y.py b/tests/ported_static/stSStoreTest/test_sstore_xto0to_y.py index 2e64a0bc108..58680f9945b 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_xto0to_y.py +++ b/tests/ported_static/stSStoreTest/test_sstore_xto0to_y.py @@ -17,10 +17,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stSStoreTest/test_sstore_xto_x.py b/tests/ported_static/stSStoreTest/test_sstore_xto_x.py index 5f39f313acb..165e323bc3f 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_xto_x.py +++ b/tests/ported_static/stSStoreTest/test_sstore_xto_x.py @@ -17,10 +17,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stSStoreTest/test_sstore_xto_xto0.py b/tests/ported_static/stSStoreTest/test_sstore_xto_xto0.py index c7d245e5a27..c28c49ed1da 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_xto_xto0.py +++ b/tests/ported_static/stSStoreTest/test_sstore_xto_xto0.py @@ -17,10 +17,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stSStoreTest/test_sstore_xto_xto_x.py b/tests/ported_static/stSStoreTest/test_sstore_xto_xto_x.py index 9bc83904c33..89c5ceb7d3d 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_xto_xto_x.py +++ b/tests/ported_static/stSStoreTest/test_sstore_xto_xto_x.py @@ -17,10 +17,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stSStoreTest/test_sstore_xto_xto_y.py b/tests/ported_static/stSStoreTest/test_sstore_xto_xto_y.py index ea56e7f589e..d99e9976466 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_xto_xto_y.py +++ b/tests/ported_static/stSStoreTest/test_sstore_xto_xto_y.py @@ -17,10 +17,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stSStoreTest/test_sstore_xto_y.py b/tests/ported_static/stSStoreTest/test_sstore_xto_y.py index d65925fd123..1688317a76a 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_xto_y.py +++ b/tests/ported_static/stSStoreTest/test_sstore_xto_y.py @@ -17,10 +17,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stSStoreTest/test_sstore_xto_yto0.py b/tests/ported_static/stSStoreTest/test_sstore_xto_yto0.py index f4e5ace93de..c517fee7d5a 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_xto_yto0.py +++ b/tests/ported_static/stSStoreTest/test_sstore_xto_yto0.py @@ -17,10 +17,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stSStoreTest/test_sstore_xto_yto_x.py b/tests/ported_static/stSStoreTest/test_sstore_xto_yto_x.py index 9b2c31951ed..36588cdc59f 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_xto_yto_x.py +++ b/tests/ported_static/stSStoreTest/test_sstore_xto_yto_x.py @@ -17,10 +17,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stSStoreTest/test_sstore_xto_yto_y.py b/tests/ported_static/stSStoreTest/test_sstore_xto_yto_y.py index b5254ed8235..65c7a29e235 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_xto_yto_y.py +++ b/tests/ported_static/stSStoreTest/test_sstore_xto_yto_y.py @@ -17,10 +17,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stSStoreTest/test_sstore_xto_yto_z.py b/tests/ported_static/stSStoreTest/test_sstore_xto_yto_z.py index b6b7811d822..df7dfa76259 100644 --- a/tests/ported_static/stSStoreTest/test_sstore_xto_yto_z.py +++ b/tests/ported_static/stSStoreTest/test_sstore_xto_yto_z.py @@ -17,10 +17,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stSelfBalance/test_self_balance_call_types.py b/tests/ported_static/stSelfBalance/test_self_balance_call_types.py index 2d12d4e5179..d378cd4a7eb 100644 --- a/tests/ported_static/stSelfBalance/test_self_balance_call_types.py +++ b/tests/ported_static/stSelfBalance/test_self_balance_call_types.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stSpecialTest/test_eoa_empty_paris.py b/tests/ported_static/stSpecialTest/test_eoa_empty_paris.py index b6e28c96397..5f1ab7ba4a3 100644 --- a/tests/ported_static/stSpecialTest/test_eoa_empty_paris.py +++ b/tests/ported_static/stSpecialTest/test_eoa_empty_paris.py @@ -27,10 +27,11 @@ TransactionException, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStackTests/test_underflow_test.py b/tests/ported_static/stStackTests/test_underflow_test.py index 92b79de5edb..71ef5864689 100644 --- a/tests/ported_static/stStackTests/test_underflow_test.py +++ b/tests/ported_static/stStackTests/test_underflow_test.py @@ -18,10 +18,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_ab_acalls0.py b/tests/ported_static/stStaticCall/test_static_ab_acalls0.py index ca1b6965436..fa36efae326 100644 --- a/tests/ported_static/stStaticCall/test_static_ab_acalls0.py +++ b/tests/ported_static/stStaticCall/test_static_ab_acalls0.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_ab_acalls1.py b/tests/ported_static/stStaticCall/test_static_ab_acalls1.py index 7f4ceb8d318..35fbb85041a 100644 --- a/tests/ported_static/stStaticCall/test_static_ab_acalls1.py +++ b/tests/ported_static/stStaticCall/test_static_ab_acalls1.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_ab_acalls2.py b/tests/ported_static/stStaticCall/test_static_ab_acalls2.py index df9f37353a3..3702deec171 100644 --- a/tests/ported_static/stStaticCall/test_static_ab_acalls2.py +++ b/tests/ported_static/stStaticCall/test_static_ab_acalls2.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_ab_acalls3.py b/tests/ported_static/stStaticCall/test_static_ab_acalls3.py index 0a20f9a241f..f4c1cbddf3e 100644 --- a/tests/ported_static/stStaticCall/test_static_ab_acalls3.py +++ b/tests/ported_static/stStaticCall/test_static_ab_acalls3.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_ab_acalls_suicide0.py b/tests/ported_static/stStaticCall/test_static_ab_acalls_suicide0.py index 94fcf44f866..ecf549cd1d0 100644 --- a/tests/ported_static/stStaticCall/test_static_ab_acalls_suicide0.py +++ b/tests/ported_static/stStaticCall/test_static_ab_acalls_suicide0.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_call10.py b/tests/ported_static/stStaticCall/test_static_call10.py index 6329bb04db1..92c1b1aa23f 100644 --- a/tests/ported_static/stStaticCall/test_static_call10.py +++ b/tests/ported_static/stStaticCall/test_static_call10.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_call1024_balance_too_low.py b/tests/ported_static/stStaticCall/test_static_call1024_balance_too_low.py index c35d515be61..fe3fced1b38 100644 --- a/tests/ported_static/stStaticCall/test_static_call1024_balance_too_low.py +++ b/tests/ported_static/stStaticCall/test_static_call1024_balance_too_low.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_call1024_balance_too_low2.py b/tests/ported_static/stStaticCall/test_static_call1024_balance_too_low2.py index 6b2a4856aee..414447d465b 100644 --- a/tests/ported_static/stStaticCall/test_static_call1024_balance_too_low2.py +++ b/tests/ported_static/stStaticCall/test_static_call1024_balance_too_low2.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_call1024_oog.py b/tests/ported_static/stStaticCall/test_static_call1024_oog.py index 160aca4a1a3..4020d1dd8e6 100644 --- a/tests/ported_static/stStaticCall/test_static_call1024_oog.py +++ b/tests/ported_static/stStaticCall/test_static_call1024_oog.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_call1024_pre_calls.py b/tests/ported_static/stStaticCall/test_static_call1024_pre_calls.py index e0d260f263f..2517ab684f7 100644 --- a/tests/ported_static/stStaticCall/test_static_call1024_pre_calls.py +++ b/tests/ported_static/stStaticCall/test_static_call1024_pre_calls.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_call1024_pre_calls2.py b/tests/ported_static/stStaticCall/test_static_call1024_pre_calls2.py index 8cf74830cb1..8f051786201 100644 --- a/tests/ported_static/stStaticCall/test_static_call1024_pre_calls2.py +++ b/tests/ported_static/stStaticCall/test_static_call1024_pre_calls2.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_call1024_pre_calls3.py b/tests/ported_static/stStaticCall/test_static_call1024_pre_calls3.py index 6448a69f8f0..d094584a0fa 100644 --- a/tests/ported_static/stStaticCall/test_static_call1024_pre_calls3.py +++ b/tests/ported_static/stStaticCall/test_static_call1024_pre_calls3.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_call1_mb1024_calldepth.py b/tests/ported_static/stStaticCall/test_static_call1_mb1024_calldepth.py index 09ad53cc867..862ef96cf28 100644 --- a/tests/ported_static/stStaticCall/test_static_call1_mb1024_calldepth.py +++ b/tests/ported_static/stStaticCall/test_static_call1_mb1024_calldepth.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_call50000.py b/tests/ported_static/stStaticCall/test_static_call50000.py index 00bf19407fd..fce0726f317 100644 --- a/tests/ported_static/stStaticCall/test_static_call50000.py +++ b/tests/ported_static/stStaticCall/test_static_call50000.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_call50000_ecrec.py b/tests/ported_static/stStaticCall/test_static_call50000_ecrec.py index 00a665359f4..5f9a20ede36 100644 --- a/tests/ported_static/stStaticCall/test_static_call50000_ecrec.py +++ b/tests/ported_static/stStaticCall/test_static_call50000_ecrec.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_call50000_identity.py b/tests/ported_static/stStaticCall/test_static_call50000_identity.py index 411f8506a38..8b36bc6f0a3 100644 --- a/tests/ported_static/stStaticCall/test_static_call50000_identity.py +++ b/tests/ported_static/stStaticCall/test_static_call50000_identity.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_call50000_identity2.py b/tests/ported_static/stStaticCall/test_static_call50000_identity2.py index cbbe552fd44..0b835e9e48e 100644 --- a/tests/ported_static/stStaticCall/test_static_call50000_identity2.py +++ b/tests/ported_static/stStaticCall/test_static_call50000_identity2.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_call50000bytes_contract50_1.py b/tests/ported_static/stStaticCall/test_static_call50000bytes_contract50_1.py index c1571a59264..b99512eafb3 100644 --- a/tests/ported_static/stStaticCall/test_static_call50000bytes_contract50_1.py +++ b/tests/ported_static/stStaticCall/test_static_call50000bytes_contract50_1.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_call50000bytes_contract50_2.py b/tests/ported_static/stStaticCall/test_static_call50000bytes_contract50_2.py index ad164e9725c..848381685bb 100644 --- a/tests/ported_static/stStaticCall/test_static_call50000bytes_contract50_2.py +++ b/tests/ported_static/stStaticCall/test_static_call50000bytes_contract50_2.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_call50000bytes_contract50_3.py b/tests/ported_static/stStaticCall/test_static_call50000bytes_contract50_3.py index 1041834efd5..bb050f67f29 100644 --- a/tests/ported_static/stStaticCall/test_static_call50000bytes_contract50_3.py +++ b/tests/ported_static/stStaticCall/test_static_call50000bytes_contract50_3.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_call_and_callcode_consume_more_gas_then_transaction_has.py b/tests/ported_static/stStaticCall/test_static_call_and_callcode_consume_more_gas_then_transaction_has.py index 1186ce1cc64..35ea3f92a02 100644 --- a/tests/ported_static/stStaticCall/test_static_call_and_callcode_consume_more_gas_then_transaction_has.py +++ b/tests/ported_static/stStaticCall/test_static_call_and_callcode_consume_more_gas_then_transaction_has.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_call_ask_more_gas_on_depth2_then_transaction_has.py b/tests/ported_static/stStaticCall/test_static_call_ask_more_gas_on_depth2_then_transaction_has.py index d47a87e4f8f..081928e9e52 100644 --- a/tests/ported_static/stStaticCall/test_static_call_ask_more_gas_on_depth2_then_transaction_has.py +++ b/tests/ported_static/stStaticCall/test_static_call_ask_more_gas_on_depth2_then_transaction_has.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_call_basic.py b/tests/ported_static/stStaticCall/test_static_call_basic.py index a3cf98b6409..4e0c3f20efc 100644 --- a/tests/ported_static/stStaticCall/test_static_call_basic.py +++ b/tests/ported_static/stStaticCall/test_static_call_basic.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_call_contract_to_create_contract_and_call_it_oog.py b/tests/ported_static/stStaticCall/test_static_call_contract_to_create_contract_and_call_it_oog.py index e6b7b771be7..889b11fa62e 100644 --- a/tests/ported_static/stStaticCall/test_static_call_contract_to_create_contract_and_call_it_oog.py +++ b/tests/ported_static/stStaticCall/test_static_call_contract_to_create_contract_and_call_it_oog.py @@ -17,10 +17,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_call_contract_to_create_contract_oog.py b/tests/ported_static/stStaticCall/test_static_call_contract_to_create_contract_oog.py index a331700c24f..24b7ed399ba 100644 --- a/tests/ported_static/stStaticCall/test_static_call_contract_to_create_contract_oog.py +++ b/tests/ported_static/stStaticCall/test_static_call_contract_to_create_contract_oog.py @@ -17,10 +17,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_call_contract_to_create_contract_oog_bonus_gas.py b/tests/ported_static/stStaticCall/test_static_call_contract_to_create_contract_oog_bonus_gas.py index 33baff843c7..9b05060ab82 100644 --- a/tests/ported_static/stStaticCall/test_static_call_contract_to_create_contract_oog_bonus_gas.py +++ b/tests/ported_static/stStaticCall/test_static_call_contract_to_create_contract_oog_bonus_gas.py @@ -17,10 +17,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_call_create.py b/tests/ported_static/stStaticCall/test_static_call_create.py index b72fd28baf7..26402d5947e 100644 --- a/tests/ported_static/stStaticCall/test_static_call_create.py +++ b/tests/ported_static/stStaticCall/test_static_call_create.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_call_create2.py b/tests/ported_static/stStaticCall/test_static_call_create2.py index 98394b0847c..fd249786ec7 100644 --- a/tests/ported_static/stStaticCall/test_static_call_create2.py +++ b/tests/ported_static/stStaticCall/test_static_call_create2.py @@ -17,10 +17,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_call_ecrecover0_0input.py b/tests/ported_static/stStaticCall/test_static_call_ecrecover0_0input.py index 3e8f4614d9f..e764df19de3 100644 --- a/tests/ported_static/stStaticCall/test_static_call_ecrecover0_0input.py +++ b/tests/ported_static/stStaticCall/test_static_call_ecrecover0_0input.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_call_with_high_value_and_gas_oog.py b/tests/ported_static/stStaticCall/test_static_call_with_high_value_and_gas_oog.py index ddf1233324b..0d282ee4817 100644 --- a/tests/ported_static/stStaticCall/test_static_call_with_high_value_and_gas_oog.py +++ b/tests/ported_static/stStaticCall/test_static_call_with_high_value_and_gas_oog.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_callcall_00.py b/tests/ported_static/stStaticCall/test_static_callcall_00.py index 1dc2606afd2..a15abd7cae0 100644 --- a/tests/ported_static/stStaticCall/test_static_callcall_00.py +++ b/tests/ported_static/stStaticCall/test_static_callcall_00.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_callcall_00_ooge.py b/tests/ported_static/stStaticCall/test_static_callcall_00_ooge.py index 8ba69156208..ef572202282 100644 --- a/tests/ported_static/stStaticCall/test_static_callcall_00_ooge.py +++ b/tests/ported_static/stStaticCall/test_static_callcall_00_ooge.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_callcall_00_ooge_1.py b/tests/ported_static/stStaticCall/test_static_callcall_00_ooge_1.py index 6a9d35e6368..57318036049 100644 --- a/tests/ported_static/stStaticCall/test_static_callcall_00_ooge_1.py +++ b/tests/ported_static/stStaticCall/test_static_callcall_00_ooge_1.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_callcallcall_000.py b/tests/ported_static/stStaticCall/test_static_callcallcall_000.py index 2388568f0db..8f055b37833 100644 --- a/tests/ported_static/stStaticCall/test_static_callcallcall_000.py +++ b/tests/ported_static/stStaticCall/test_static_callcallcall_000.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_callcallcall_000_ooge.py b/tests/ported_static/stStaticCall/test_static_callcallcall_000_ooge.py index 83d2fc21188..63754169437 100644 --- a/tests/ported_static/stStaticCall/test_static_callcallcall_000_ooge.py +++ b/tests/ported_static/stStaticCall/test_static_callcallcall_000_ooge.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_callcallcallcode_001.py b/tests/ported_static/stStaticCall/test_static_callcallcallcode_001.py index 10695d92061..11423312559 100644 --- a/tests/ported_static/stStaticCall/test_static_callcallcallcode_001.py +++ b/tests/ported_static/stStaticCall/test_static_callcallcallcode_001.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_callcallcallcode_001_2.py b/tests/ported_static/stStaticCall/test_static_callcallcallcode_001_2.py index f76a30dbfa5..c0817bd4c83 100644 --- a/tests/ported_static/stStaticCall/test_static_callcallcallcode_001_2.py +++ b/tests/ported_static/stStaticCall/test_static_callcallcallcode_001_2.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_callcallcode_01_2.py b/tests/ported_static/stStaticCall/test_static_callcallcode_01_2.py index 66ab50c76f3..1475a3f80d5 100644 --- a/tests/ported_static/stStaticCall/test_static_callcallcode_01_2.py +++ b/tests/ported_static/stStaticCall/test_static_callcallcode_01_2.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_callcallcodecall_010_2.py b/tests/ported_static/stStaticCall/test_static_callcallcodecall_010_2.py index fbd6b903580..99df8261178 100644 --- a/tests/ported_static/stStaticCall/test_static_callcallcodecall_010_2.py +++ b/tests/ported_static/stStaticCall/test_static_callcallcodecall_010_2.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_011_2.py b/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_011_2.py index a9751f80768..1a6a47bdd78 100644 --- a/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_011_2.py +++ b/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_011_2.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_011_ooge_2.py b/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_011_ooge_2.py index 26f1bd02f04..9e994b73511 100644 --- a/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_011_ooge_2.py +++ b/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_011_ooge_2.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_011_oogm_before2.py b/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_011_oogm_before2.py index 8d3516646cf..c30719e515c 100644 --- a/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_011_oogm_before2.py +++ b/tests/ported_static/stStaticCall/test_static_callcallcodecallcode_011_oogm_before2.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_callcodecall_10_suicide_end2.py b/tests/ported_static/stStaticCall/test_static_callcodecall_10_suicide_end2.py index 92fe9bf3b88..18d9fc5ff53 100644 --- a/tests/ported_static/stStaticCall/test_static_callcodecall_10_suicide_end2.py +++ b/tests/ported_static/stStaticCall/test_static_callcodecall_10_suicide_end2.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_callcodecallcall_100_oogm_after_3.py b/tests/ported_static/stStaticCall/test_static_callcodecallcall_100_oogm_after_3.py index 23f939bb74d..d8a518ac360 100644 --- a/tests/ported_static/stStaticCall/test_static_callcodecallcall_100_oogm_after_3.py +++ b/tests/ported_static/stStaticCall/test_static_callcodecallcall_100_oogm_after_3.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_callcodecallcallcode_101_oogm_after_3.py b/tests/ported_static/stStaticCall/test_static_callcodecallcallcode_101_oogm_after_3.py index d1005686e33..4d67491fbc0 100644 --- a/tests/ported_static/stStaticCall/test_static_callcodecallcallcode_101_oogm_after_3.py +++ b/tests/ported_static/stStaticCall/test_static_callcodecallcallcode_101_oogm_after_3.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_110_suicide_end2.py b/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_110_suicide_end2.py index a72cbc5d33e..c7add2f4bf2 100644 --- a/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_110_suicide_end2.py +++ b/tests/ported_static/stStaticCall/test_static_callcodecallcodecall_110_suicide_end2.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_check_opcodes.py b/tests/ported_static/stStaticCall/test_static_check_opcodes.py index 58ef0d469dc..6c9dbff6e95 100644 --- a/tests/ported_static/stStaticCall/test_static_check_opcodes.py +++ b/tests/ported_static/stStaticCall/test_static_check_opcodes.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_check_opcodes2.py b/tests/ported_static/stStaticCall/test_static_check_opcodes2.py index 39abab90f7d..a9675d83e1b 100644 --- a/tests/ported_static/stStaticCall/test_static_check_opcodes2.py +++ b/tests/ported_static/stStaticCall/test_static_check_opcodes2.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_check_opcodes3.py b/tests/ported_static/stStaticCall/test_static_check_opcodes3.py index 67c36145697..93a6fc44608 100644 --- a/tests/ported_static/stStaticCall/test_static_check_opcodes3.py +++ b/tests/ported_static/stStaticCall/test_static_check_opcodes3.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_check_opcodes4.py b/tests/ported_static/stStaticCall/test_static_check_opcodes4.py index 8c4a09aef3a..af9dc932f24 100644 --- a/tests/ported_static/stStaticCall/test_static_check_opcodes4.py +++ b/tests/ported_static/stStaticCall/test_static_check_opcodes4.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_check_opcodes5.py b/tests/ported_static/stStaticCall/test_static_check_opcodes5.py index ff557a77c24..684ae695055 100644 --- a/tests/ported_static/stStaticCall/test_static_check_opcodes5.py +++ b/tests/ported_static/stStaticCall/test_static_check_opcodes5.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_contract_creation_make_call_that_ask_more_gas_then_transaction_provided.py b/tests/ported_static/stStaticCall/test_static_contract_creation_make_call_that_ask_more_gas_then_transaction_provided.py index 2ba50e12f09..37bec128a16 100644 --- a/tests/ported_static/stStaticCall/test_static_contract_creation_make_call_that_ask_more_gas_then_transaction_provided.py +++ b/tests/ported_static/stStaticCall/test_static_contract_creation_make_call_that_ask_more_gas_then_transaction_provided.py @@ -16,10 +16,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_execute_call_that_ask_fore_gas_then_trabsaction_has.py b/tests/ported_static/stStaticCall/test_static_execute_call_that_ask_fore_gas_then_trabsaction_has.py index c48d9cfe3d4..b5965a5da21 100644 --- a/tests/ported_static/stStaticCall/test_static_execute_call_that_ask_fore_gas_then_trabsaction_has.py +++ b/tests/ported_static/stStaticCall/test_static_execute_call_that_ask_fore_gas_then_trabsaction_has.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_loop_calls_then_revert.py b/tests/ported_static/stStaticCall/test_static_loop_calls_then_revert.py index 32fdd9495db..ed5bd704245 100644 --- a/tests/ported_static/stStaticCall/test_static_loop_calls_then_revert.py +++ b/tests/ported_static/stStaticCall/test_static_loop_calls_then_revert.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_refund_call_to_suicide_twice.py b/tests/ported_static/stStaticCall/test_static_refund_call_to_suicide_twice.py index 6860878412f..4400d80ed2a 100644 --- a/tests/ported_static/stStaticCall/test_static_refund_call_to_suicide_twice.py +++ b/tests/ported_static/stStaticCall/test_static_refund_call_to_suicide_twice.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stStaticCall/test_static_return_bounds_oog.py b/tests/ported_static/stStaticCall/test_static_return_bounds_oog.py index d3be7f1321f..d6c1592c18e 100644 --- a/tests/ported_static/stStaticCall/test_static_return_bounds_oog.py +++ b/tests/ported_static/stStaticCall/test_static_return_bounds_oog.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stSystemOperationsTest/test_call_to_name_registrator_zeor_size_mem_expansion.py b/tests/ported_static/stSystemOperationsTest/test_call_to_name_registrator_zeor_size_mem_expansion.py index 885556214a2..0ce98e0ea4a 100644 --- a/tests/ported_static/stSystemOperationsTest/test_call_to_name_registrator_zeor_size_mem_expansion.py +++ b/tests/ported_static/stSystemOperationsTest/test_call_to_name_registrator_zeor_size_mem_expansion.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stSystemOperationsTest/test_callcode_to_name_registrator_zero_mem_expanion.py b/tests/ported_static/stSystemOperationsTest/test_callcode_to_name_registrator_zero_mem_expanion.py index 27617fd5e2e..132bcf59e56 100644 --- a/tests/ported_static/stSystemOperationsTest/test_callcode_to_name_registrator_zero_mem_expanion.py +++ b/tests/ported_static/stSystemOperationsTest/test_callcode_to_name_registrator_zero_mem_expanion.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stSystemOperationsTest/test_double_selfdestruct_test.py b/tests/ported_static/stSystemOperationsTest/test_double_selfdestruct_test.py index 39db66c2be4..229fe37a194 100644 --- a/tests/ported_static/stSystemOperationsTest/test_double_selfdestruct_test.py +++ b/tests/ported_static/stSystemOperationsTest/test_double_selfdestruct_test.py @@ -22,10 +22,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stSystemOperationsTest/test_double_selfdestruct_touch_paris.py b/tests/ported_static/stSystemOperationsTest/test_double_selfdestruct_touch_paris.py index 26364936fba..b20492dea03 100644 --- a/tests/ported_static/stSystemOperationsTest/test_double_selfdestruct_touch_paris.py +++ b/tests/ported_static/stSystemOperationsTest/test_double_selfdestruct_touch_paris.py @@ -20,10 +20,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stSystemOperationsTest/test_multi_selfdestruct.py b/tests/ported_static/stSystemOperationsTest/test_multi_selfdestruct.py index 004c4a8b668..5d234fc6921 100644 --- a/tests/ported_static/stSystemOperationsTest/test_multi_selfdestruct.py +++ b/tests/ported_static/stSystemOperationsTest/test_multi_selfdestruct.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stTransactionTest/test_no_src_account.py b/tests/ported_static/stTransactionTest/test_no_src_account.py index 1a5c3d9e625..63e91f3eeda 100644 --- a/tests/ported_static/stTransactionTest/test_no_src_account.py +++ b/tests/ported_static/stTransactionTest/test_no_src_account.py @@ -18,10 +18,11 @@ TransactionException, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stTransactionTest/test_no_src_account1559.py b/tests/ported_static/stTransactionTest/test_no_src_account1559.py index d87e3e0e0be..3388b348377 100644 --- a/tests/ported_static/stTransactionTest/test_no_src_account1559.py +++ b/tests/ported_static/stTransactionTest/test_no_src_account1559.py @@ -18,10 +18,11 @@ TransactionException, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stTransactionTest/test_no_src_account_create.py b/tests/ported_static/stTransactionTest/test_no_src_account_create.py index fd82a83d143..43ff298fbf6 100644 --- a/tests/ported_static/stTransactionTest/test_no_src_account_create.py +++ b/tests/ported_static/stTransactionTest/test_no_src_account_create.py @@ -18,10 +18,11 @@ TransactionException, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stTransactionTest/test_no_src_account_create1559.py b/tests/ported_static/stTransactionTest/test_no_src_account_create1559.py index 46f293d3b49..a3a9bf5c9c7 100644 --- a/tests/ported_static/stTransactionTest/test_no_src_account_create1559.py +++ b/tests/ported_static/stTransactionTest/test_no_src_account_create1559.py @@ -17,10 +17,11 @@ TransactionException, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stTransactionTest/test_opcodes_transaction_init.py b/tests/ported_static/stTransactionTest/test_opcodes_transaction_init.py index f93284cfd53..e14591f4675 100644 --- a/tests/ported_static/stTransactionTest/test_opcodes_transaction_init.py +++ b/tests/ported_static/stTransactionTest/test_opcodes_transaction_init.py @@ -18,10 +18,11 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stTransactionTest/test_overflow_gas_require2.py b/tests/ported_static/stTransactionTest/test_overflow_gas_require2.py index 6b78f079f48..9c157e34007 100644 --- a/tests/ported_static/stTransactionTest/test_overflow_gas_require2.py +++ b/tests/ported_static/stTransactionTest/test_overflow_gas_require2.py @@ -16,7 +16,8 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( + +from tests.ported_static.post_state_resolution import ( resolve_expect_post_fork, ) diff --git a/tests/ported_static/stTransactionTest/test_suicides_and_internal_call_suicides_success.py b/tests/ported_static/stTransactionTest/test_suicides_and_internal_call_suicides_success.py index 661b972ec23..03537745a43 100644 --- a/tests/ported_static/stTransactionTest/test_suicides_and_internal_call_suicides_success.py +++ b/tests/ported_static/stTransactionTest/test_suicides_and_internal_call_suicides_success.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/stWalletTest/test_multi_owned_construction_not_enough_gas_partial.py b/tests/ported_static/stWalletTest/test_multi_owned_construction_not_enough_gas_partial.py index 52ccc3ac3f3..095c422a1eb 100644 --- a/tests/ported_static/stWalletTest/test_multi_owned_construction_not_enough_gas_partial.py +++ b/tests/ported_static/stWalletTest/test_multi_owned_construction_not_enough_gas_partial.py @@ -21,7 +21,8 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) diff --git a/tests/ported_static/stWalletTest/test_wallet_construction_oog.py b/tests/ported_static/stWalletTest/test_wallet_construction_oog.py index 851f26cb823..1db9df00272 100644 --- a/tests/ported_static/stWalletTest/test_wallet_construction_oog.py +++ b/tests/ported_static/stWalletTest/test_wallet_construction_oog.py @@ -22,7 +22,8 @@ compute_create_address, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) diff --git a/tests/ported_static/stZeroKnowledge/test_pairing_test.py b/tests/ported_static/stZeroKnowledge/test_pairing_test.py index bf844166115..26b95d2d71c 100644 --- a/tests/ported_static/stZeroKnowledge/test_pairing_test.py +++ b/tests/ported_static/stZeroKnowledge/test_pairing_test.py @@ -16,10 +16,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/vmArithmeticTest/test_add.py b/tests/ported_static/vmArithmeticTest/test_add.py index 38a910bcf77..9a3b35f3e4a 100644 --- a/tests/ported_static/vmArithmeticTest/test_add.py +++ b/tests/ported_static/vmArithmeticTest/test_add.py @@ -18,10 +18,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/vmArithmeticTest/test_addmod.py b/tests/ported_static/vmArithmeticTest/test_addmod.py index 122864ca167..94e74adc98b 100644 --- a/tests/ported_static/vmArithmeticTest/test_addmod.py +++ b/tests/ported_static/vmArithmeticTest/test_addmod.py @@ -18,10 +18,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/vmArithmeticTest/test_div.py b/tests/ported_static/vmArithmeticTest/test_div.py index 5a13e87254f..d0d5c84fd96 100644 --- a/tests/ported_static/vmArithmeticTest/test_div.py +++ b/tests/ported_static/vmArithmeticTest/test_div.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/vmArithmeticTest/test_exp.py b/tests/ported_static/vmArithmeticTest/test_exp.py index 5514c6d2f91..73f8ae82930 100644 --- a/tests/ported_static/vmArithmeticTest/test_exp.py +++ b/tests/ported_static/vmArithmeticTest/test_exp.py @@ -18,10 +18,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/vmArithmeticTest/test_mod.py b/tests/ported_static/vmArithmeticTest/test_mod.py index 44970acfe0f..9357ec45d0e 100644 --- a/tests/ported_static/vmArithmeticTest/test_mod.py +++ b/tests/ported_static/vmArithmeticTest/test_mod.py @@ -18,10 +18,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/vmArithmeticTest/test_mul.py b/tests/ported_static/vmArithmeticTest/test_mul.py index dd176fbf7cd..dd4c64fb869 100644 --- a/tests/ported_static/vmArithmeticTest/test_mul.py +++ b/tests/ported_static/vmArithmeticTest/test_mul.py @@ -18,10 +18,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/vmArithmeticTest/test_mulmod.py b/tests/ported_static/vmArithmeticTest/test_mulmod.py index 173ad89d1cc..222f0355075 100644 --- a/tests/ported_static/vmArithmeticTest/test_mulmod.py +++ b/tests/ported_static/vmArithmeticTest/test_mulmod.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/vmArithmeticTest/test_sdiv.py b/tests/ported_static/vmArithmeticTest/test_sdiv.py index 7c360e52047..888e7a8f36b 100644 --- a/tests/ported_static/vmArithmeticTest/test_sdiv.py +++ b/tests/ported_static/vmArithmeticTest/test_sdiv.py @@ -18,10 +18,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/vmArithmeticTest/test_signextend.py b/tests/ported_static/vmArithmeticTest/test_signextend.py index d05e6c2c264..03501b4ec63 100644 --- a/tests/ported_static/vmArithmeticTest/test_signextend.py +++ b/tests/ported_static/vmArithmeticTest/test_signextend.py @@ -18,10 +18,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/vmArithmeticTest/test_smod.py b/tests/ported_static/vmArithmeticTest/test_smod.py index 728f89a210b..b18f22c0b5b 100644 --- a/tests/ported_static/vmArithmeticTest/test_smod.py +++ b/tests/ported_static/vmArithmeticTest/test_smod.py @@ -18,10 +18,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/vmArithmeticTest/test_sub.py b/tests/ported_static/vmArithmeticTest/test_sub.py index 2d8d2654766..c23d1f25153 100644 --- a/tests/ported_static/vmArithmeticTest/test_sub.py +++ b/tests/ported_static/vmArithmeticTest/test_sub.py @@ -18,10 +18,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/vmBitwiseLogicOperation/test_and.py b/tests/ported_static/vmBitwiseLogicOperation/test_and.py index 919c2763837..7b6b72381a7 100644 --- a/tests/ported_static/vmBitwiseLogicOperation/test_and.py +++ b/tests/ported_static/vmBitwiseLogicOperation/test_and.py @@ -18,10 +18,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/vmBitwiseLogicOperation/test_byte.py b/tests/ported_static/vmBitwiseLogicOperation/test_byte.py index 56887a82b99..066dad03bab 100644 --- a/tests/ported_static/vmBitwiseLogicOperation/test_byte.py +++ b/tests/ported_static/vmBitwiseLogicOperation/test_byte.py @@ -18,10 +18,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/vmBitwiseLogicOperation/test_eq.py b/tests/ported_static/vmBitwiseLogicOperation/test_eq.py index 3c20ccd8773..cede13e379e 100644 --- a/tests/ported_static/vmBitwiseLogicOperation/test_eq.py +++ b/tests/ported_static/vmBitwiseLogicOperation/test_eq.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/vmBitwiseLogicOperation/test_gt.py b/tests/ported_static/vmBitwiseLogicOperation/test_gt.py index 06e1b39c681..367755777d7 100644 --- a/tests/ported_static/vmBitwiseLogicOperation/test_gt.py +++ b/tests/ported_static/vmBitwiseLogicOperation/test_gt.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/vmBitwiseLogicOperation/test_iszero.py b/tests/ported_static/vmBitwiseLogicOperation/test_iszero.py index e83fe80b0d8..d8a364bd832 100644 --- a/tests/ported_static/vmBitwiseLogicOperation/test_iszero.py +++ b/tests/ported_static/vmBitwiseLogicOperation/test_iszero.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/vmBitwiseLogicOperation/test_lt.py b/tests/ported_static/vmBitwiseLogicOperation/test_lt.py index d2be95c3d03..ecefdd25e34 100644 --- a/tests/ported_static/vmBitwiseLogicOperation/test_lt.py +++ b/tests/ported_static/vmBitwiseLogicOperation/test_lt.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/vmBitwiseLogicOperation/test_not.py b/tests/ported_static/vmBitwiseLogicOperation/test_not.py index 4b4e07d8dd0..07ea2212c74 100644 --- a/tests/ported_static/vmBitwiseLogicOperation/test_not.py +++ b/tests/ported_static/vmBitwiseLogicOperation/test_not.py @@ -18,10 +18,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/vmBitwiseLogicOperation/test_or.py b/tests/ported_static/vmBitwiseLogicOperation/test_or.py index 7fc19e60d81..966f99e9da6 100644 --- a/tests/ported_static/vmBitwiseLogicOperation/test_or.py +++ b/tests/ported_static/vmBitwiseLogicOperation/test_or.py @@ -18,10 +18,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/vmBitwiseLogicOperation/test_sgt.py b/tests/ported_static/vmBitwiseLogicOperation/test_sgt.py index 5164480e359..91d6717e29f 100644 --- a/tests/ported_static/vmBitwiseLogicOperation/test_sgt.py +++ b/tests/ported_static/vmBitwiseLogicOperation/test_sgt.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/vmBitwiseLogicOperation/test_slt.py b/tests/ported_static/vmBitwiseLogicOperation/test_slt.py index fc950a2787d..126544f06da 100644 --- a/tests/ported_static/vmBitwiseLogicOperation/test_slt.py +++ b/tests/ported_static/vmBitwiseLogicOperation/test_slt.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/vmBitwiseLogicOperation/test_xor.py b/tests/ported_static/vmBitwiseLogicOperation/test_xor.py index b5bdb7b26bf..a981838ae36 100644 --- a/tests/ported_static/vmBitwiseLogicOperation/test_xor.py +++ b/tests/ported_static/vmBitwiseLogicOperation/test_xor.py @@ -18,10 +18,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/vmIOandFlowOperations/test_codecopy.py b/tests/ported_static/vmIOandFlowOperations/test_codecopy.py index bfc509a4799..9a6dc7236c5 100644 --- a/tests/ported_static/vmIOandFlowOperations/test_codecopy.py +++ b/tests/ported_static/vmIOandFlowOperations/test_codecopy.py @@ -18,10 +18,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/vmIOandFlowOperations/test_gas.py b/tests/ported_static/vmIOandFlowOperations/test_gas.py index fdfe01710a7..87419192ba9 100644 --- a/tests/ported_static/vmIOandFlowOperations/test_gas.py +++ b/tests/ported_static/vmIOandFlowOperations/test_gas.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/vmIOandFlowOperations/test_jump.py b/tests/ported_static/vmIOandFlowOperations/test_jump.py index ba214f9deca..b03a1c53312 100644 --- a/tests/ported_static/vmIOandFlowOperations/test_jump.py +++ b/tests/ported_static/vmIOandFlowOperations/test_jump.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/vmIOandFlowOperations/test_jump_to_push.py b/tests/ported_static/vmIOandFlowOperations/test_jump_to_push.py index f3168e3b044..9643efc1806 100644 --- a/tests/ported_static/vmIOandFlowOperations/test_jump_to_push.py +++ b/tests/ported_static/vmIOandFlowOperations/test_jump_to_push.py @@ -18,10 +18,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/vmIOandFlowOperations/test_jumpi.py b/tests/ported_static/vmIOandFlowOperations/test_jumpi.py index c2da7dde412..334db56cf54 100644 --- a/tests/ported_static/vmIOandFlowOperations/test_jumpi.py +++ b/tests/ported_static/vmIOandFlowOperations/test_jumpi.py @@ -18,10 +18,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/vmIOandFlowOperations/test_loops_conditionals.py b/tests/ported_static/vmIOandFlowOperations/test_loops_conditionals.py index ec091a54e3d..e92c3ba0052 100644 --- a/tests/ported_static/vmIOandFlowOperations/test_loops_conditionals.py +++ b/tests/ported_static/vmIOandFlowOperations/test_loops_conditionals.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/vmIOandFlowOperations/test_mload.py b/tests/ported_static/vmIOandFlowOperations/test_mload.py index cf11bf25816..680f287fbe4 100644 --- a/tests/ported_static/vmIOandFlowOperations/test_mload.py +++ b/tests/ported_static/vmIOandFlowOperations/test_mload.py @@ -18,10 +18,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/vmIOandFlowOperations/test_msize.py b/tests/ported_static/vmIOandFlowOperations/test_msize.py index 5cc3faad9f2..73ec1f00aef 100644 --- a/tests/ported_static/vmIOandFlowOperations/test_msize.py +++ b/tests/ported_static/vmIOandFlowOperations/test_msize.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/vmIOandFlowOperations/test_mstore.py b/tests/ported_static/vmIOandFlowOperations/test_mstore.py index 8ab043b473a..97b39ac5f6e 100644 --- a/tests/ported_static/vmIOandFlowOperations/test_mstore.py +++ b/tests/ported_static/vmIOandFlowOperations/test_mstore.py @@ -18,10 +18,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/vmIOandFlowOperations/test_mstore8.py b/tests/ported_static/vmIOandFlowOperations/test_mstore8.py index 23cc67ce2c1..8ef65086e44 100644 --- a/tests/ported_static/vmIOandFlowOperations/test_mstore8.py +++ b/tests/ported_static/vmIOandFlowOperations/test_mstore8.py @@ -18,10 +18,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/vmIOandFlowOperations/test_pc.py b/tests/ported_static/vmIOandFlowOperations/test_pc.py index fc3df94d285..26c6263eccc 100644 --- a/tests/ported_static/vmIOandFlowOperations/test_pc.py +++ b/tests/ported_static/vmIOandFlowOperations/test_pc.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/vmIOandFlowOperations/test_pop.py b/tests/ported_static/vmIOandFlowOperations/test_pop.py index 9e49f6e31bc..c1b396fdc50 100644 --- a/tests/ported_static/vmIOandFlowOperations/test_pop.py +++ b/tests/ported_static/vmIOandFlowOperations/test_pop.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/vmIOandFlowOperations/test_return.py b/tests/ported_static/vmIOandFlowOperations/test_return.py index 17e39f195f8..d950ce23967 100644 --- a/tests/ported_static/vmIOandFlowOperations/test_return.py +++ b/tests/ported_static/vmIOandFlowOperations/test_return.py @@ -18,10 +18,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/vmIOandFlowOperations/test_sstore_sload.py b/tests/ported_static/vmIOandFlowOperations/test_sstore_sload.py index 21b82a91fe5..60d4f82598e 100644 --- a/tests/ported_static/vmIOandFlowOperations/test_sstore_sload.py +++ b/tests/ported_static/vmIOandFlowOperations/test_sstore_sload.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/vmLogTest/test_log0.py b/tests/ported_static/vmLogTest/test_log0.py index d743762ab28..10560c1c00c 100644 --- a/tests/ported_static/vmLogTest/test_log0.py +++ b/tests/ported_static/vmLogTest/test_log0.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/vmLogTest/test_log1.py b/tests/ported_static/vmLogTest/test_log1.py index b4309d454e6..babfe21ff78 100644 --- a/tests/ported_static/vmLogTest/test_log1.py +++ b/tests/ported_static/vmLogTest/test_log1.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/vmLogTest/test_log2.py b/tests/ported_static/vmLogTest/test_log2.py index 95fa656fe44..8dfc9f8be7f 100644 --- a/tests/ported_static/vmLogTest/test_log2.py +++ b/tests/ported_static/vmLogTest/test_log2.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/vmLogTest/test_log3.py b/tests/ported_static/vmLogTest/test_log3.py index a763ee5fd27..8273f211d09 100644 --- a/tests/ported_static/vmLogTest/test_log3.py +++ b/tests/ported_static/vmLogTest/test_log3.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/vmLogTest/test_log4.py b/tests/ported_static/vmLogTest/test_log4.py index 3266265017a..dfb7b2846a4 100644 --- a/tests/ported_static/vmLogTest/test_log4.py +++ b/tests/ported_static/vmLogTest/test_log4.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/vmTests/test_block_info.py b/tests/ported_static/vmTests/test_block_info.py index 57fc33cc47d..deb55f2f7c0 100644 --- a/tests/ported_static/vmTests/test_block_info.py +++ b/tests/ported_static/vmTests/test_block_info.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/vmTests/test_env_info.py b/tests/ported_static/vmTests/test_env_info.py index 67c2e62f6d6..b0b7803ae9f 100644 --- a/tests/ported_static/vmTests/test_env_info.py +++ b/tests/ported_static/vmTests/test_env_info.py @@ -18,10 +18,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/vmTests/test_random.py b/tests/ported_static/vmTests/test_random.py index 5a64236ce99..b32eee33608 100644 --- a/tests/ported_static/vmTests/test_random.py +++ b/tests/ported_static/vmTests/test_random.py @@ -17,10 +17,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/vmTests/test_sha3.py b/tests/ported_static/vmTests/test_sha3.py index a3805b31105..66f1bd7c5b7 100644 --- a/tests/ported_static/vmTests/test_sha3.py +++ b/tests/ported_static/vmTests/test_sha3.py @@ -18,10 +18,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" diff --git a/tests/ported_static/vmTests/test_suicide.py b/tests/ported_static/vmTests/test_suicide.py index c562b182d0d..68610592631 100644 --- a/tests/ported_static/vmTests/test_suicide.py +++ b/tests/ported_static/vmTests/test_suicide.py @@ -30,10 +30,11 @@ Transaction, ) from execution_testing.forks import Fork -from execution_testing.specs.static_state.expect_section import ( +from execution_testing.vm import Op + +from tests.ported_static.post_state_resolution import ( resolve_expect_post, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" From 7b6aed29c90bc0e8e99078044cc0a1474adcba76 Mon Sep 17 00:00:00 2001 From: spencer <spencer.taylor-brown@ethereum.org> Date: Wed, 5 Aug 2026 11:10:01 +0200 Subject: [PATCH 198/233] feat(spec-specs,tests): apply revised EIP-8038 gas values (#3293) --- .../forks/forks/eips/amsterdam/eip_8038.py | 8 +-- .../tools/tests/test_iterating_bytecode.py | 15 ++++- src/ethereum/forks/amsterdam/vm/gas.py | 8 +-- .../eip2780_reduce_intrinsic_tx_gas/spec.py | 2 +- .../test_block_access_lists_opcodes.py | 57 +++++++++++-------- .../spec.py | 8 +-- .../spec.py | 2 +- .../test_call_gas.py | 47 +++++++-------- .../test_create_gas.py | 14 ++--- .../test_exact_balance_no_fallback.py | 6 +- .../test_fork_transition.py | 16 +++--- .../test_selfdestruct_gas.py | 55 ++++++++++-------- .../test_sload_gas.py | 12 ++-- .../test_sstore_gas.py | 6 +- .../test_sstore_refunds.py | 8 +-- 15 files changed, 147 insertions(+), 117 deletions(-) diff --git a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8038.py b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8038.py index 6129ea4d768..ca495d88597 100644 --- a/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8038.py +++ b/packages/testing/src/execution_testing/forks/forks/eips/amsterdam/eip_8038.py @@ -42,7 +42,7 @@ def gas_costs(cls) -> GasCosts: warm_access = 100 cold_account_access = 3_000 - cold_storage_access = 3_000 + cold_storage_access = 2_100 storage_write = 10_000 # The framework models the SSTORE write via the compound # COLD_STORAGE_WRITE (access + write), so preserve the invariant @@ -50,8 +50,8 @@ def gas_costs(cls) -> GasCosts: cold_storage_write = cold_storage_access + storage_write # Surcharge for the first write to an account leaf, introduced as a # standalone parameter by this repricing. - account_write = 8_000 - create_access = 11_000 + account_write = 9_000 + create_access = account_write + cold_account_access # ecRecover stays PRECOMPILE_ECRECOVER (3000) until EIP-7904 lands. execution_per_auth_base_cost = ( 1_616 + 3_000 + cold_account_access + 2 * warm_access @@ -66,7 +66,7 @@ def gas_costs(cls) -> GasCosts: COLD_STORAGE_WRITE=cold_storage_write, ACCOUNT_WRITE=account_write, CALL_VALUE=account_write + 2_300, # ACCOUNT_WRITE + CALL_STIPEND - REFUND_STORAGE_CLEAR=12_480, + REFUND_STORAGE_CLEAR=11_616, TX_ACCESS_LIST_ADDRESS=cold_account_access - warm_access, TX_ACCESS_LIST_STORAGE_KEY=cold_storage_access - warm_access, BLOCK_ACCESS_LIST_ITEM=2000, diff --git a/packages/testing/src/execution_testing/tools/tests/test_iterating_bytecode.py b/packages/testing/src/execution_testing/tools/tests/test_iterating_bytecode.py index 22e7144db5d..960df26f668 100644 --- a/packages/testing/src/execution_testing/tools/tests/test_iterating_bytecode.py +++ b/packages/testing/src/execution_testing/tools/tests/test_iterating_bytecode.py @@ -433,7 +433,20 @@ def test_state_reservoir_lets_tx_gas_exceed_execution_gas_limit_cap() -> None: fork = CustomAmsterdam.with_tx_gas_limit_cap(cap) bytecode = IteratingBytecode(iterating=Op.SSTORE(0, 1)) - total_iterations = (cap // Op.SSTORE(0, 1).execution_cost(fork=fork)) - 1 + # Largest iteration count the tx splitter accepts for a single tx: + # execution gas plus the subcall reserve must fit the cap, derived + # from the helper's own (linear) cost model. + cost_one = bytecode.tx_execution_gas_cost_by_iteration_count( + fork=fork, iteration_count=1 + ) + per_iteration = ( + bytecode.tx_execution_gas_cost_by_iteration_count( + fork=fork, iteration_count=2 + ) + - cost_one + ) + reserve = bytecode.iterating_subcall_reserve(fork=fork) + total_iterations = 1 + (cap - reserve - cost_one) // per_iteration counts = list( bytecode.tx_iterations_by_total_iteration_count( fork=fork, total_iterations=total_iterations diff --git a/src/ethereum/forks/amsterdam/vm/gas.py b/src/ethereum/forks/amsterdam/vm/gas.py index 6a07ef0e361..d6b6aa4d755 100644 --- a/src/ethereum/forks/amsterdam/vm/gas.py +++ b/src/ethereum/forks/amsterdam/vm/gas.py @@ -80,20 +80,20 @@ class GasCosts: # Access WARM_ACCESS: Final[Uint] = Uint(100) COLD_ACCOUNT_ACCESS: Final[Uint] = Uint(3000) - COLD_STORAGE_ACCESS: Final[Uint] = Uint(3000) + COLD_STORAGE_ACCESS: Final[Uint] = Uint(2100) # Storage STORAGE_WRITE: Final[Uint] = Uint(10000) # Call - CALL_VALUE: Final[Uint] = Uint(10300) # ACCOUNT_WRITE + CALL_STIPEND + CALL_VALUE: Final[Uint] = Uint(11300) # ACCOUNT_WRITE + CALL_STIPEND CALL_STIPEND: Final[Uint] = Uint(2300) - ACCOUNT_WRITE: Final[Uint] = Uint(8000) + ACCOUNT_WRITE: Final[Uint] = Uint(9000) # Contract Creation CODE_DEPOSIT_PER_BYTE: Final[Uint] = Uint(200) CODE_INIT_PER_WORD: Final[Uint] = Uint(2) - CREATE_ACCESS: Final[Uint] = ACCOUNT_WRITE + COLD_STORAGE_ACCESS + CREATE_ACCESS: Final[Uint] = ACCOUNT_WRITE + COLD_ACCOUNT_ACCESS # Utility ZERO: Final[Uint] = Uint(0) diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/spec.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/spec.py index 33b12ffbd2f..fd388234476 100644 --- a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/spec.py +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/spec.py @@ -13,5 +13,5 @@ class ReferenceSpec: ref_spec_2780 = ReferenceSpec( git_path="EIPS/eip-2780.md", - version="04dd54c2e7ec1f408cf4a150d5c1aa43573bd025", + version="36c409e70e4117fc708f02da7d58cd7e5d075f7c", ) diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py index e6ca49ddbd1..da11a3d19cd 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_opcodes.py @@ -114,18 +114,23 @@ def test_bal_sstore_and_oog( """ Test BAL recording with SSTORE at various OOG boundaries and success. - The slot read is recorded in the BAL only once the cold access cost - is covered. Post-repricing that cost (COLD_STORAGE_ACCESS) exceeds the - EIP-2200 stipend, so clearing the stipend sentry alone no longer - records the read. The stipend + 1 case pins the old sentry boundary - against regressions to sentry-gated recording. - - 1. OOG at the stipend, below the access cost -> no BAL changes - 2. OOG above the stipend but below access cost (probed at - stipend + 1 and access cost - 1) -> no BAL changes - 3. OOG at the access cost, write unaffordable -> storage read in BAL - 4. OOG at exact gas minus 1 -> storage read in BAL - 5. exact gas (success) -> storage write in BAL + ``SSTORE`` clears two gates before the write cost: the EIP-2200 + stipend sentry (``gas_left`` must exceed ``CALL_STIPEND``) and the + cold access charge (``COLD_STORAGE_ACCESS``). The slot read is + recorded in the BAL only once both are cleared, so the recording + gate is the higher of the two — which one dominates depends on the + fork's schedule, and the expectations below are derived from that + relation rather than assuming it. + + 1. OOG at the stipend -> sentry fires, no BAL changes + 2. OOG at stipend + 1 -> sentry cleared by one; the read is + recorded only if this also covers the access cost + 3. OOG at access cost - 1 -> below one of the two gates, no BAL + changes + 4. OOG at the recording gate, write unaffordable -> storage read in + BAL + 5. OOG at exact gas minus 1 -> storage read in BAL + 6. exact gas (success) -> storage write in BAL """ alice = pre.fund_eoa() @@ -145,26 +150,29 @@ def test_bal_sstore_and_oog( push_code = Op.PUSH1(0x42) + Op.PUSH1(0x01) push_cost = push_code.gas_cost(fork) - # CALL_STIPEND is a threshold check, not a gas cost. The cold access - # cost gates the read into the BAL and now exceeds the stipend. + # CALL_STIPEND is a threshold check, not a gas cost. The read is + # recorded once the sentry is cleared and the access cost is + # affordable, so the recording gate is the higher of the two. stipend = fork.gas_costs().CALL_STIPEND cold_access = fork.gas_costs().COLD_STORAGE_ACCESS + read_gate = max(cold_access, stipend + 1) if out_of_gas_at == OutOfGasAt.EIP_2200_STIPEND: - # gas_left == stipend: fails the check, below the access cost. + # gas_left == stipend: fails the sentry check outright. tx_gas_limit = intrinsic_gas_cost + push_cost + stipend elif out_of_gas_at == OutOfGasAt.EIP_2200_STIPEND_PLUS_1: - # gas_left == stipend + 1: clears the stipend sentry by one but - # cannot afford the access, so OOG before the read. + # gas_left == stipend + 1: clears the stipend sentry by one; + # whether the access is then affordable depends on the schedule. tx_gas_limit = intrinsic_gas_cost + push_cost + stipend + 1 elif out_of_gas_at == OutOfGasAt.ABOVE_STIPEND_BELOW_ACCESS: - # gas_left == access cost - 1: clears the stipend sentry but - # cannot afford the access, so OOG before the read. + # gas_left == access cost - 1: cannot afford the access (when + # the stipend dominates, the sentry fires first instead), so + # OOG before the read either way. tx_gas_limit = intrinsic_gas_cost + push_cost + cold_access - 1 elif out_of_gas_at == OutOfGasAt.ACCESS_COVERED_OOG_ON_WRITE: - # gas_left == access cost: access affordable (read recorded), - # then OOG on the write cost. - tx_gas_limit = intrinsic_gas_cost + push_cost + cold_access + # gas_left == read gate: sentry cleared and access affordable + # (read recorded), then OOG on the write cost. + tx_gas_limit = intrinsic_gas_cost + push_cost + read_gate elif out_of_gas_at == OutOfGasAt.EXACT_GAS_MINUS_1: # fail at the final charge at exact gas - 1 (boundary condition). tx_gas_limit = intrinsic_gas_cost + full_cost - 1 @@ -178,11 +186,14 @@ def test_bal_sstore_and_oog( gas_limit=tx_gas_limit, ) - # The read is recorded only once the access cost is covered: the + # The read is recorded only once the recording gate is covered: the # frame reaches the implicit SLOAD before any later OOG. expect_storage_read = out_of_gas_at in ( OutOfGasAt.ACCESS_COVERED_OOG_ON_WRITE, OutOfGasAt.EXACT_GAS_MINUS_1, + ) or ( + out_of_gas_at == OutOfGasAt.EIP_2200_STIPEND_PLUS_1 + and stipend + 1 >= cold_access ) expect_storage_write = out_of_gas_at is None diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/spec.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/spec.py index 0477ea2159a..5df2ff71e75 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/spec.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/spec.py @@ -49,8 +49,8 @@ class Spec: # Execution gas constants. EIP-8037 separated state from execution gas; # EIP-8038 then repriced them. - EXECUTION_GAS_CREATE = 11000 + EXECUTION_GAS_CREATE = 12000 # Total execution intrinsic per EIP-7702 authorization: - # ACCOUNT_WRITE (8000) + EXECUTION_PER_AUTH_BASE_COST (7816). - PER_AUTH_BASE_COST = 15816 - GAS_COLD_STORAGE_WRITE = 13000 + # ACCOUNT_WRITE + EXECUTION_PER_AUTH_BASE_COST. + PER_AUTH_BASE_COST = 16816 + GAS_COLD_STORAGE_WRITE = 12100 diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/spec.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/spec.py index 66a7606fd8a..ca2d8133a21 100644 --- a/tests/amsterdam/eip8038_state_access_gas_cost_increase/spec.py +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/spec.py @@ -12,5 +12,5 @@ class ReferenceSpec: ref_spec_8038 = ReferenceSpec( - "EIPS/eip-8038.md", "a8862ae6653a12a2989b64a50eca5334cfe8b3cb" + "EIPS/eip-8038.md", "fc2322854d047ba1fd6e3ae9e61fb7a915535cb7" ) diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_call_gas.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_call_gas.py index 946ea7f12db..5075c2e26ce 100644 --- a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_call_gas.py +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_call_gas.py @@ -5,10 +5,10 @@ Under EIP-8038 the call opcodes are repriced in their *execution* gas dimension: -- account access costs ``COLD_ACCOUNT_ACCESS`` (3,000) cold or - ``WARM_ACCESS`` (100) warm; +- account access costs ``COLD_ACCOUNT_ACCESS`` cold or + ``WARM_ACCESS`` warm; - a positive value transfer adds ``CALL_VALUE`` (``ACCOUNT_WRITE`` + - ``CALL_STIPEND`` = 10,300), charged only by ``CALL``/``CALLCODE``; + ``CALL_STIPEND``), charged only by ``CALL``/``CALLCODE``; - a value transfer to a *new* account additionally creates the account, whose ``GAS_NEW_ACCOUNT`` charge is the EIP-8037 *state* dimension and is asserted via the block header ``max(execution, state)`` accounting, @@ -86,8 +86,8 @@ def test_call_access_gas( """ Measure the access cost of every call opcode with no value transfer. - EIP-8038 charges ``COLD_ACCOUNT_ACCESS`` (3,000) cold and - ``WARM_ACCESS`` (100) warm for all four call opcodes. + EIP-8038 charges ``COLD_ACCOUNT_ACCESS`` cold and ``WARM_ACCESS`` + warm for all four call opcodes. """ target = pre.deploy_contract(Op.STOP) @@ -127,13 +127,13 @@ def test_call_value_alive_target_gas( """ Measure call cost with value transfer to an already-alive target. - ``CALL``/``CALLCODE`` add ``CALL_VALUE`` (10,300) on top of the + ``CALL``/``CALLCODE`` add ``CALL_VALUE`` on top of the access cost, where ``CALL_VALUE = ACCOUNT_WRITE + CALL_STIPEND``. ``DELEGATECALL``/``STATICCALL`` never transfer value, so they pay only the access cost regardless of any value argument. No new account is created (the target is alive), so no state gas is charged. - The ``CALL_STIPEND`` (2,300) is forwarded to the callee; with a + The ``CALL_STIPEND`` is forwarded to the callee; with a ``STOP`` callee it is unused and returned, so the gas *consumed* by the caller is ``access + ACCOUNT_WRITE`` while the *charged* schedule is ``access + CALL_VALUE``. Both are asserted. @@ -212,7 +212,8 @@ def test_callcode_value_to_nonexistent_no_new_account( ``CALLCODE`` runs the callee's code in the caller's own context, so the value never leaves the caller and no beneficiary account is created. The block ``gas_used`` therefore equals the execution tx - cost with ``CALL_VALUE`` but with no 183,600 state-gas component. + cost with ``CALL_VALUE`` but with no ``GAS_NEW_ACCOUNT`` state-gas + component. """ intrinsic = fork.transaction_intrinsic_cost_calculator()() @@ -270,10 +271,9 @@ def test_call_value_to_new_account_seam( Verify the CALL value-to-new-account execution/state seam. The EIP-8038 *execution* dimension is ``COLD_ACCOUNT_ACCESS`` + - ``CALL_VALUE`` = 13,300; the account creation charge - ``GAS_NEW_ACCOUNT`` (183,600) lands in the EIP-8037 *state* - dimension. The block header reflects ``max(execution, state)``, which - is dominated by the state charge. + ``CALL_VALUE``; the account creation charge ``GAS_NEW_ACCOUNT`` + lands in the EIP-8037 *state* dimension. The block header reflects + ``max(execution, state)``, which is dominated by the state charge. """ intrinsic = fork.transaction_intrinsic_cost_calculator()() @@ -344,8 +344,8 @@ def test_call_to_delegated_target_double_access( The spec applies the delegation surcharge to every call opcode (``CALL``/``CALLCODE``/``DELEGATECALL``/``STATICCALL``), so each reads two account leaves: the target's leaf and the delegation's - leaf. Each is charged independently as ``WARM_ACCESS`` (100) or - ``COLD_ACCOUNT_ACCESS`` (3,000) by warmth. ``DELEGATECALL`` and + leaf. Each is charged independently as ``WARM_ACCESS`` or + ``COLD_ACCOUNT_ACCESS`` by warmth. ``DELEGATECALL`` and ``STATICCALL`` carry no value but still pay the delegation surcharge. """ @@ -440,7 +440,7 @@ def test_call_self_is_warm( Verify a self-call is warm: the executing account is pre-warmed. The current target is in the accessed-addresses set on message - entry, so a call to ``ADDRESS`` pays only ``WARM_ACCESS`` (100). + entry, so a call to ``ADDRESS`` pays only ``WARM_ACCESS``. """ # `Op.ADDRESS` is the call's address argument, embedded inside the # runnable call; the self address is in the accessed set on entry, so @@ -474,7 +474,7 @@ def test_call_forwarded_gas_63_64( cold access charge. A wrapper performs a cold, zero-value ``CALL`` requesting maximum - gas. The spec charges the repriced ``COLD_ACCOUNT_ACCESS`` (3,000) + gas. The spec charges the repriced ``COLD_ACCOUNT_ACCESS`` up front and only then forwards ``floor(63/64 * gas_left)`` to the child. The wrapper is handed an exact budget so that, net of the access charge, ``gas_left`` equals ``child_execution * 64 // 63``; @@ -549,7 +549,7 @@ def test_account_warmth_reverts_on_subcall_revert( ``DELEGATECALL`` (so the warmed address belongs to the shared accessed-addresses set) then ``REVERT``s. Back in the outer frame, that same address's first ``BALANCE`` is cold again and is charged - ``COLD_ACCOUNT_ACCESS`` (3,000), proving the warm-address set is + ``COLD_ACCOUNT_ACCESS``, proving the warm-address set is rolled back on revert (mirrors the ``SLOAD`` warmth-revert case for the account dimension). """ @@ -601,7 +601,7 @@ def test_call_to_double_delegated_target_single_hop( an EOA delegated to ``final`` (C), a code-bearing account. A cold ``CALL`` to ``target`` reads exactly two account leaves -- the target's and its delegation's -- and is charged - ``2 * COLD_ACCOUNT_ACCESS`` (6,000). The chain is not followed a + ``2 * COLD_ACCOUNT_ACCESS``. The chain is not followed a second hop, so ``final``'s leaf is not charged. Both the framework opcode model and a runtime ``CodeGasMeasure`` confirm the value. """ @@ -647,7 +647,7 @@ def test_call_precompile_is_warm( Verify a call to a precompile is warm from the start. Precompiles are part of the accessed-addresses set from the start of - every transaction, so a call to one pays only ``WARM_ACCESS`` (100). + every transaction, so a call to one pays only ``WARM_ACCESS``. The identity precompile (address 4) is used as the target. """ identity_precompile = Address(4) @@ -676,18 +676,19 @@ def test_call_value_stipend_is_usable( value: int, ) -> None: """ - The ``CALL`` value-transfer stipend (``CALL_STIPEND`` = 2,300) is + The ``CALL`` value-transfer stipend ``CALL_STIPEND`` is forwarded to the callee and usable for execution. The caller forwards ``gas=0``, so the callee receives only the stipend - (2,300) when a positive value is sent, and nothing otherwise. The - callee runs a small amount of work (well under 2,300 gas) then stops: + when a positive value is sent, and nothing otherwise. The + callee runs a small amount of work (well under the stipend) then + stops: with the stipend the call succeeds (returns 1); without value (no stipend, zero forwarded gas) the work runs out of gas and the call fails (returns 0). This proves the stipend is not merely returned but is spendable by the callee. """ - # ~250 gas of cheap work: comfortably within the 2,300 stipend, far + # ~250 gas of cheap work: comfortably within the stipend, far # above the zero gas forwarded when no value (so no stipend) is sent. work = (Op.PUSH1(0) + Op.POP) * 50 + Op.STOP callee = pre.deploy_contract(code=work) diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_create_gas.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_create_gas.py index f981679c30b..f03aae62c12 100644 --- a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_create_gas.py +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_create_gas.py @@ -4,9 +4,9 @@ Under EIP-8038 the contract-creation opcodes are repriced in their *execution* gas dimension to ``CREATE_ACCESS`` (``ACCOUNT_WRITE`` + -``COLD_STORAGE_ACCESS`` = 11,000), on top of which the EIP-3860 init -code word cost (2 per word) and, for ``CREATE2`` only, an additional -keccak word cost (6 per word) are charged. The new-account creation +``COLD_ACCOUNT_ACCESS``), on top of which the EIP-3860 init code +word cost and, for ``CREATE2`` only, an additional keccak word cost +are charged. The new-account creation and per-byte code deposit charges are the EIP-8037 *state* dimension, covered in ``eip8037_state_creation_gas_cost_increase/test_state_gas_create.py``. @@ -68,9 +68,9 @@ def test_create_execution_gas( """ Measure the execution gas of CREATE/CREATE2 and assert the schedule. - The EIP-8038 *execution* dimension is ``CREATE_ACCESS`` (11,000) plus - the EIP-3860 init code word cost (2 per word) plus, for ``CREATE2`` - only, an additional keccak word cost (6 per word). The EIP-8037 + The EIP-8038 *execution* dimension is ``CREATE_ACCESS`` plus the + EIP-3860 init code word cost plus, for ``CREATE2`` only, an + additional keccak word cost. The EIP-8037 account-creation state gas is excluded by subtracting ``create_state_gas(0)``. """ @@ -393,7 +393,7 @@ def test_aborted_create_does_not_warm_address( balance for the endowment, or nonce overflow), the would-be address is never added to the accessed-addresses set. A subsequent ``BALANCE`` of that address is therefore charged the full - ``COLD_ACCOUNT_ACCESS`` (3,000), not ``WARM_ACCESS`` (100). + ``COLD_ACCOUNT_ACCESS``, not ``WARM_ACCESS``. """ init_code = Op.STOP init_code_bytes = bytes(init_code) diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_exact_balance_no_fallback.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_exact_balance_no_fallback.py index 5b92e45a96c..dec4044daa8 100644 --- a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_exact_balance_no_fallback.py +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_exact_balance_no_fallback.py @@ -82,8 +82,8 @@ def test_access_list_no_fallback( Reject an access-list transaction whose ``gas_limit`` is one gas below the Amsterdam intrinsic. - EIP-8038 raises ``TX_ACCESS_LIST_ADDRESS`` (2400 -> 2900) and - ``TX_ACCESS_LIST_STORAGE_KEY`` (1900 -> 2900). A client reusing the + EIP-8038 raises ``TX_ACCESS_LIST_ADDRESS`` and + ``TX_ACCESS_LIST_STORAGE_KEY``. A client reusing the old per-address/per-key constants would compute an intrinsic smaller by ``num_addresses * addr_delta + num_keys * key_delta``; with the sender funded to the wei, that fallback must not slip through. @@ -218,7 +218,7 @@ def test_cold_account_access_no_fallback( Under EIP-2780 every non-create, non-self transaction pays one ``COLD_ACCOUNT_ACCESS`` in its intrinsic for touching the recipient; - EIP-8038 raises that constant (2600 -> 3000). A client reusing the + EIP-8038 raises that constant. A client reusing the old ``COLD_ACCOUNT_ACCESS`` would compute an intrinsic smaller by the per-access delta, and with the sender funded to the wei that fallback must not execute. diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_fork_transition.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_fork_transition.py index 710fb6634da..00294a928c5 100644 --- a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_fork_transition.py +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_fork_transition.py @@ -121,7 +121,7 @@ def test_cold_account_access_at_transition( ) -> None: """ ``BALANCE`` of a cold account costs ``COLD_ACCOUNT_ACCESS``, which - rises across the Amsterdam boundary (2600 -> 3000 on mainnet). The + rises across the Amsterdam boundary. The same opcode is measured before and after; each block asserts its regime's derived cost. """ @@ -167,9 +167,9 @@ def test_ext_code_surcharge_at_transition( """ The EIP-8038 ``EXT*`` code-read surcharge appears at the fork. The surcharge equals ``EXTCODESIZE`` minus ``BALANCE`` at equal warmth: - it is zero before the fork and one ``WARM_ACCESS`` (100) after. That + it is zero before the fork and one ``WARM_ACCESS`` after. That comparison is computed from the opcode model. On-chain, each block - measures only a cold ``EXTCODESIZE`` (2600 before, 3100 after): its + measures only a cold ``EXTCODESIZE``: its rise reflects the surcharge on top of the cold-access repricing, and ``BALANCE`` is never executed. """ @@ -218,8 +218,8 @@ def test_call_value_cost_at_transition( fork: Fork, ) -> None: """ - ``CALL_VALUE`` rises across the boundary (9000 -> 10300 on mainnet, - becoming ``ACCOUNT_WRITE + CALL_STIPEND``). The constant transition + ``CALL_VALUE`` rises across the boundary, becoming + ``ACCOUNT_WRITE + CALL_STIPEND``. The constant transition is asserted from the derived schedules while a value-bearing ``CALL`` is exercised in both blocks to prove it still succeeds in each regime. @@ -262,8 +262,8 @@ def test_create_base_cost_at_transition( ) -> None: """ The ``CREATE`` execution base cost changes across the boundary - (``OPCODE_CREATE_BASE``: 32000 -> 11000 on mainnet, redefined as - ``ACCOUNT_WRITE + COLD_STORAGE_ACCESS``). The constant transition is + (``OPCODE_CREATE_BASE`` is redefined as + ``ACCOUNT_WRITE + COLD_ACCOUNT_ACCESS``). The constant transition is asserted from the derived schedules and a ``CREATE`` is exercised in both blocks to prove it still deploys. """ @@ -312,7 +312,7 @@ def test_selfdestruct_account_write_at_transition( """ ``SELFDESTRUCT`` gains an ``ACCOUNT_WRITE`` charge when it sends a positive balance to an empty account, which is a new EIP-8038 - parameter (0 -> 8000 on mainnet). The constant transition is + parameter. The constant transition is asserted from the derived schedules and a value-bearing ``SELFDESTRUCT`` to a fresh beneficiary is exercised in both blocks to prove it still runs. diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_selfdestruct_gas.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_selfdestruct_gas.py index b78c595c4b5..16eaf7dea65 100644 --- a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_selfdestruct_gas.py +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_selfdestruct_gas.py @@ -5,19 +5,18 @@ Under EIP-8038 ``SELFDESTRUCT`` is charged, in its *execution* gas dimension: -- ``OPCODE_SELFDESTRUCT_BASE`` (5,000); -- a ``COLD_ACCOUNT_ACCESS`` (3,000) surcharge when the beneficiary is +- ``OPCODE_SELFDESTRUCT_BASE``; +- a ``COLD_ACCOUNT_ACCESS`` surcharge when the beneficiary is cold (a warm beneficiary adds nothing — SELFDESTRUCT has no ``WARM_ACCESS`` surcharge); -- a net-new ``ACCOUNT_WRITE`` (8,000) when a positive balance is sent to - an empty (or non-existent) beneficiary, replacing the legacy combined - 25,000 execution account-creation cost. +- a net-new ``ACCOUNT_WRITE`` when a positive balance is sent to + an empty (or non-existent) beneficiary, replacing the legacy + combined execution account-creation cost. -So ``execution = 5,000 + (3,000 if cold) + (8,000 if creating)``: 13,000 -warm / 16,000 cold when a new beneficiary is created, 5,000 warm / 8,000 -cold otherwise. +So ``execution = OPCODE_SELFDESTRUCT_BASE + (COLD_ACCOUNT_ACCESS if +cold) + (ACCOUNT_WRITE if creating)``. -The beneficiary account-creation charge ``GAS_NEW_ACCOUNT`` (183,600) is +The beneficiary account-creation charge ``GAS_NEW_ACCOUNT`` is the EIP-8037 *state* dimension (`charge_state_gas` in the spec), covered in ``eip8037_state_creation_gas_cost_increase/test_state_gas_selfdestruct.py``. @@ -31,7 +30,7 @@ ``SELFDESTRUCT`` exactly as the spec does: ``ACCOUNT_WRITE`` is charged as execution gas and ``GAS_NEW_ACCOUNT`` as state gas, so ``Op.SELFDESTRUCT(account_new=True).execution_cost(fork)`` is the execution -charge (16,000 cold / 13,000 warm) and ``.state_cost(fork)`` is +charge and ``.state_cost(fork)`` is ``GAS_NEW_ACCOUNT``. These tests assert the execution dimension and verify account-creation via balances; the state dimension is owned by ``eip8037_state_creation_gas_cost_increase/test_state_gas_selfdestruct.py``. @@ -94,7 +93,7 @@ def test_selfdestruct_new_beneficiary_execution_gas( The destructor has a non-zero balance and targets an empty, non-existent beneficiary, so the net-new ``ACCOUNT_WRITE`` applies: - ``execution = 5,000 + access + 8,000`` (13,000 warm, 16,000 cold). The + ``execution = OPCODE_SELFDESTRUCT_BASE + access + ACCOUNT_WRITE``. The creation gas ``GAS_NEW_ACCOUNT`` is charged on the state axis (the EIP-8037 suite asserts it); here it is funded from the reservoir and the value transfer to the new beneficiary confirms the path. @@ -145,8 +144,9 @@ def test_selfdestruct_alive_beneficiary_no_account_write( SELFDESTRUCT to an already-alive beneficiary charges no ACCOUNT_WRITE. The beneficiary already exists, so no account is created: execution = - ``5,000 + (3,000 if cold)`` (5,000 warm, 8,000 cold) and no state gas is - charged. The block header reflects the pure execution consumption. + ``OPCODE_SELFDESTRUCT_BASE + (COLD_ACCOUNT_ACCESS if cold)`` and no + state gas is charged. The block header reflects the pure execution + consumption. """ beneficiary = pre.fund_eoa(amount=1) # alive @@ -212,8 +212,9 @@ def test_selfdestruct_codebearing_zero_balance_beneficiary_no_account_write( The beneficiary is alive because it has code, not balance: it holds a zero balance but a non-empty code (``Op.STOP``), so EIP-161 emptiness does not apply and no account is created when a positive balance is - sent to it. Execution = ``5,000 + (3,000 if cold)`` (5,000 warm, 8,000 - cold) with no ACCOUNT_WRITE and no state gas — distinct from the + sent to it. Execution = ``OPCODE_SELFDESTRUCT_BASE + + (COLD_ACCOUNT_ACCESS if cold)`` with no ACCOUNT_WRITE and no state + gas — distinct from the alive-via-balance case, which exercises the same path through a different liveness source. """ @@ -280,7 +281,8 @@ def test_selfdestruct_zero_balance_no_account_write( SELFDESTRUCT with a zero-balance destructor charges no ACCOUNT_WRITE. No value is transferred, so even a non-existent beneficiary is not - created: execution = ``5,000 + access`` and no state gas is charged. + created: execution = ``OPCODE_SELFDESTRUCT_BASE + access`` and no + state gas is charged. """ beneficiary = Address(0xDEAD) # non-existent, but no value sent @@ -345,8 +347,8 @@ def test_selfdestruct_self_or_precompile_beneficiary( The executing account is in the accessed set on entry (self), and precompiles are pre-warmed from the start, so neither pays a cold - surcharge: execution = ``5,000`` (warm base, no ``WARM_ACCESS``) with no - state gas. + surcharge: execution = ``OPCODE_SELFDESTRUCT_BASE`` (warm base, no + ``WARM_ACCESS``) with no state gas. The destructor balance is chosen so no account creation occurs: self is alive (sending to itself never creates), and the precompile case @@ -418,10 +420,11 @@ def test_selfdestruct_oog_boundary( gas and one short. The destructor sends value to an empty beneficiary, charging - ``5,000 + COLD_ACCOUNT_ACCESS + ACCOUNT_WRITE`` (16,000) in execution gas - and ``GAS_NEW_ACCOUNT`` in state gas. The child CALL frame has no state - reservoir of its own, so the state gas spills into the forwarded - execution gas and the frame needs its full ``gas_cost`` total. Forwarding + ``OPCODE_SELFDESTRUCT_BASE + COLD_ACCOUNT_ACCESS + ACCOUNT_WRITE`` + in execution gas and ``GAS_NEW_ACCOUNT`` in state gas. The child + CALL frame has no state reservoir of its own, so the state gas + spills into the forwarded execution gas and the frame needs its + full ``gas_cost`` total. Forwarding exactly that total lets the SELFDESTRUCT succeed (CALL returns 1); one gas short OOGs (CALL returns 0) before the value transfer, so the beneficiary is never created. @@ -483,7 +486,8 @@ def test_same_tx_created_selfdestruct_self_burn( to ITSELF: the originator is created in this transaction so it is deleted, and because a same-tx-created contract holding balance is alive, ``account_new`` is false for the self-beneficiary — - ``execution = 5,000`` (warm self, no ``ACCOUNT_WRITE``) and no + ``execution = OPCODE_SELFDESTRUCT_BASE`` (warm self, no + ``ACCOUNT_WRITE``) and no SELFDESTRUCT state gas. EIP-8246 removes the SELFDESTRUCT burn, so the self-send is a no-op: @@ -562,8 +566,9 @@ def test_same_tx_created_selfdestruct_to_fresh_beneficiary( A creation transaction whose initcode SELFDESTRUCTs the new contract to a fresh ``Address(0xDEAD)``: the fresh, non-existent beneficiary receives a positive balance, so ``account_new`` is true — - ``execution = 5,000 + COLD_ACCOUNT_ACCESS + ACCOUNT_WRITE`` (16,000 - cold) plus a beneficiary ``NEW_ACCOUNT`` on the state axis. The + ``execution = OPCODE_SELFDESTRUCT_BASE + COLD_ACCOUNT_ACCESS + + ACCOUNT_WRITE`` plus a beneficiary ``NEW_ACCOUNT`` on the state + axis. The beneficiary creation charge keys on the beneficiary, while the originator (created in this transaction) is still deleted: a ``Transfer`` log is emitted (not a ``Burn``). diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_sload_gas.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_sload_gas.py index 26502836c7b..62aad812e74 100644 --- a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_sload_gas.py +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_sload_gas.py @@ -2,7 +2,7 @@ Tests for [EIP-8038: State Access Gas Cost Increase](https://eips.ethereum.org/EIPS/eip-8038). Covers the EIP-8038 ``SLOAD`` repricing: a cold storage slot read costs -``COLD_STORAGE_ACCESS`` (3000) and a warm read costs ``WARM_SLOAD`` (100). +``COLD_STORAGE_ACCESS`` and a warm read costs ``WARM_SLOAD``. A slot is warmed either by listing it in the transaction access list or by a prior in-frame access; warmth acquired inside a sub-call that REVERTs is discarded, so a subsequent read in the outer frame is cold again. @@ -65,8 +65,8 @@ def test_sload_gas( Measure the gas of a ``SLOAD`` on a slot that is either cold or pre-warmed via the transaction access list. - A cold read must cost ``COLD_STORAGE_ACCESS`` (3000); a warm read - must cost ``WARM_SLOAD`` (100). + A cold read must cost ``COLD_STORAGE_ACCESS``; a warm read must + cost ``WARM_SLOAD``. """ slot = 0x42 expected_gas = Op.SLOAD(key_warm=warm).gas_cost(fork) @@ -103,7 +103,7 @@ def test_sload_warm_after_prior_touch( ) -> None: """ A first ``SLOAD`` on a cold slot warms it; the second in-frame - ``SLOAD`` of the same slot is charged ``WARM_SLOAD`` (100). + ``SLOAD`` of the same slot is charged ``WARM_SLOAD``. Slot 0 records the cold first read and slot 1 the warm second read. """ @@ -154,8 +154,8 @@ def test_sload_warmth_reverts_on_subcall_revert( An inner contract ``SLOAD``s the slot via ``DELEGATECALL`` (so the warmed ``(address, slot)`` pair belongs to the outer account) then ``REVERT``s. Back in the outer frame, that same slot's first - ``SLOAD`` is cold again and is charged ``COLD_STORAGE_ACCESS`` - (3000), proving the warm-slot set is rolled back on revert. + ``SLOAD`` is cold again and is charged ``COLD_STORAGE_ACCESS``, + proving the warm-slot set is rolled back on revert. """ slot = 0x42 cold_gas = Op.SLOAD(key_warm=False).gas_cost(fork) diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_sstore_gas.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_sstore_gas.py index 67be30b8213..a46e5305adc 100644 --- a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_sstore_gas.py +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_sstore_gas.py @@ -151,13 +151,13 @@ def test_sstore_cold_then_warm_same_slot( ) -> None: """ A first ``SSTORE`` on a cold slot warms it; the second in-frame - ``SSTORE`` of the same slot is charged only ``WARM_SLOAD`` (100). + ``SSTORE`` of the same slot is charged only ``WARM_SLOAD``. The slot starts non-zero (original 1) and is left unlisted, so the first write is cold and is its first change (original == current != - new), costing ``COLD_STORAGE_ACCESS + STORAGE_WRITE`` (3000 + 10000). + new), costing ``COLD_STORAGE_ACCESS + STORAGE_WRITE``. That write warms the slot, so the second write -- which moves the slot - again without being a first change -- costs only ``WARM_SLOAD`` (100), + again without being a first change -- costs only ``WARM_SLOAD``, with no further ``STORAGE_WRITE``. Slot 0 records the cold first write and slot 1 the warm second write; the data slot keeps its final value. """ diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_sstore_refunds.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_sstore_refunds.py index 7755bdc9240..35e46fc77a7 100644 --- a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_sstore_refunds.py +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_sstore_refunds.py @@ -15,14 +15,14 @@ the transaction receipt's ``cumulative_gas_used``: * Clearing a slot whose original value is non-zero grants - ``REFUND_STORAGE_CLEAR`` (12480) to ``refund_counter`` (no EIP-8037 + ``REFUND_STORAGE_CLEAR`` to ``refund_counter`` (no EIP-8037 state refund, since no state was created). * Clearing then re-setting the same non-zero-original slot nets a zero refund: the clear grant is reversed (``refund -= REFUND_STORAGE_CLEAR``) exactly when ``original != 0 and current == 0`` and a non-zero value is written back. * Restoring a non-zero-original slot to its original value refunds the - write cost ``STORAGE_WRITE`` (10000). + write cost ``STORAGE_WRITE``. * The applied refund is capped at ``gas_used // 5`` (EIP-3529 quotient). All refunds use a non-zero original so the state-creation refund owned by @@ -80,7 +80,7 @@ def test_sstore_clear_grants_refund( Clearing a non-zero-original slot grants ``REFUND_STORAGE_CLEAR``. Enough unrelated gas is burned so the EIP-3529 quotient cap - (``gas_used // 5``) does not bind, letting the full 12480 refund be + (``gas_used // 5``) does not bind, letting the full clear refund be observed in ``cumulative_gas_used``. The non-zero original means no EIP-8037 state refund participates. """ @@ -174,7 +174,7 @@ def test_sstore_restore_nonzero_refunds_write( Restoring a non-zero-original slot refunds the write cost. The slot is changed (charging ``STORAGE_WRITE``) then restored to its - original non-zero value, refunding ``STORAGE_WRITE`` (10000). Gas is + original non-zero value, refunding ``STORAGE_WRITE``. Gas is burned so the quotient cap does not bind and the full refund is observable. """ From 5bb2a5188d8231eb1d335a749ecd8180bd1d1000 Mon Sep 17 00:00:00 2001 From: skbaek <seulkeebaek@gmail.com> Date: Wed, 5 Aug 2026 18:24:41 +0900 Subject: [PATCH 199/233] refactor(spec-specs): decode withdrawal amount as uint64 per EIP-4895 (#3186) Co-authored-by: spencer-tb <spencer.tb@ethereum.org> --- src/ethereum/forks/amsterdam/blocks.py | 4 +-- src/ethereum/forks/amsterdam/fork.py | 2 +- src/ethereum/forks/bpo1/blocks.py | 4 +-- src/ethereum/forks/bpo1/fork.py | 2 +- src/ethereum/forks/bpo2/blocks.py | 4 +-- src/ethereum/forks/bpo2/fork.py | 2 +- src/ethereum/forks/bpo3/blocks.py | 4 +-- src/ethereum/forks/bpo3/fork.py | 2 +- src/ethereum/forks/bpo4/blocks.py | 4 +-- src/ethereum/forks/bpo4/fork.py | 2 +- src/ethereum/forks/bpo5/blocks.py | 4 +-- src/ethereum/forks/bpo5/fork.py | 2 +- src/ethereum/forks/cancun/blocks.py | 4 +-- src/ethereum/forks/cancun/fork.py | 2 +- src/ethereum/forks/osaka/blocks.py | 4 +-- src/ethereum/forks/osaka/fork.py | 2 +- src/ethereum/forks/prague/blocks.py | 4 +-- src/ethereum/forks/prague/fork.py | 2 +- src/ethereum/forks/shanghai/blocks.py | 4 +-- src/ethereum/forks/shanghai/fork.py | 2 +- .../evm_tools/b11r/b11r_types.py | 4 +-- .../evm_tools/loaders/fixture_loader.py | 2 +- .../evm_tools/t8n/__init__.py | 6 ++-- src/ethereum_spec_tools/sync.py | 2 +- tests/json_loader/test_withdrawal_codec.py | 28 +++++++++++++++++++ 25 files changed, 65 insertions(+), 37 deletions(-) create mode 100644 tests/json_loader/test_withdrawal_codec.py diff --git a/src/ethereum/forks/amsterdam/blocks.py b/src/ethereum/forks/amsterdam/blocks.py index 68732a167d4..17fb23253a8 100644 --- a/src/ethereum/forks/amsterdam/blocks.py +++ b/src/ethereum/forks/amsterdam/blocks.py @@ -59,9 +59,9 @@ class Withdrawal: The execution-layer address receiving the withdrawn ETH. """ - amount: U256 + amount: U64 """ - The amount of ETH being withdrawn. + The amount of ETH being withdrawn, in Gwei. """ diff --git a/src/ethereum/forks/amsterdam/fork.py b/src/ethereum/forks/amsterdam/fork.py index da6a5929f0b..b893fbd20d4 100644 --- a/src/ethereum/forks/amsterdam/fork.py +++ b/src/ethereum/forks/amsterdam/fork.py @@ -1111,7 +1111,7 @@ def process_withdrawals( rlp.encode(wd), ) - create_ether(wd_state, wd.address, wd.amount * GWEI_TO_WEI) + create_ether(wd_state, wd.address, U256(wd.amount) * GWEI_TO_WEI) incorporate_tx_into_block(wd_state, block_env.block_access_list_builder) diff --git a/src/ethereum/forks/bpo1/blocks.py b/src/ethereum/forks/bpo1/blocks.py index f3eb9b40bf4..572ee943712 100644 --- a/src/ethereum/forks/bpo1/blocks.py +++ b/src/ethereum/forks/bpo1/blocks.py @@ -59,9 +59,9 @@ class Withdrawal: The execution-layer address receiving the withdrawn ETH. """ - amount: U256 + amount: U64 """ - The amount of ETH being withdrawn. + The amount of ETH being withdrawn, in Gwei. """ diff --git a/src/ethereum/forks/bpo1/fork.py b/src/ethereum/forks/bpo1/fork.py index f3eb249dfaf..159f7369075 100644 --- a/src/ethereum/forks/bpo1/fork.py +++ b/src/ethereum/forks/bpo1/fork.py @@ -995,7 +995,7 @@ def process_withdrawals( rlp.encode(wd), ) - create_ether(wd_state, wd.address, wd.amount * U256(10**9)) + create_ether(wd_state, wd.address, U256(wd.amount) * U256(10**9)) incorporate_tx_into_block(wd_state) diff --git a/src/ethereum/forks/bpo2/blocks.py b/src/ethereum/forks/bpo2/blocks.py index 2fb877682e1..ef72aad7bdb 100644 --- a/src/ethereum/forks/bpo2/blocks.py +++ b/src/ethereum/forks/bpo2/blocks.py @@ -59,9 +59,9 @@ class Withdrawal: The execution-layer address receiving the withdrawn ETH. """ - amount: U256 + amount: U64 """ - The amount of ETH being withdrawn. + The amount of ETH being withdrawn, in Gwei. """ diff --git a/src/ethereum/forks/bpo2/fork.py b/src/ethereum/forks/bpo2/fork.py index f3eb249dfaf..159f7369075 100644 --- a/src/ethereum/forks/bpo2/fork.py +++ b/src/ethereum/forks/bpo2/fork.py @@ -995,7 +995,7 @@ def process_withdrawals( rlp.encode(wd), ) - create_ether(wd_state, wd.address, wd.amount * U256(10**9)) + create_ether(wd_state, wd.address, U256(wd.amount) * U256(10**9)) incorporate_tx_into_block(wd_state) diff --git a/src/ethereum/forks/bpo3/blocks.py b/src/ethereum/forks/bpo3/blocks.py index e5931b35c35..04cb66d5ae8 100644 --- a/src/ethereum/forks/bpo3/blocks.py +++ b/src/ethereum/forks/bpo3/blocks.py @@ -59,9 +59,9 @@ class Withdrawal: The execution-layer address receiving the withdrawn ETH. """ - amount: U256 + amount: U64 """ - The amount of ETH being withdrawn. + The amount of ETH being withdrawn, in Gwei. """ diff --git a/src/ethereum/forks/bpo3/fork.py b/src/ethereum/forks/bpo3/fork.py index f3eb249dfaf..159f7369075 100644 --- a/src/ethereum/forks/bpo3/fork.py +++ b/src/ethereum/forks/bpo3/fork.py @@ -995,7 +995,7 @@ def process_withdrawals( rlp.encode(wd), ) - create_ether(wd_state, wd.address, wd.amount * U256(10**9)) + create_ether(wd_state, wd.address, U256(wd.amount) * U256(10**9)) incorporate_tx_into_block(wd_state) diff --git a/src/ethereum/forks/bpo4/blocks.py b/src/ethereum/forks/bpo4/blocks.py index c09fd2907e1..dd47a5f33fc 100644 --- a/src/ethereum/forks/bpo4/blocks.py +++ b/src/ethereum/forks/bpo4/blocks.py @@ -59,9 +59,9 @@ class Withdrawal: The execution-layer address receiving the withdrawn ETH. """ - amount: U256 + amount: U64 """ - The amount of ETH being withdrawn. + The amount of ETH being withdrawn, in Gwei. """ diff --git a/src/ethereum/forks/bpo4/fork.py b/src/ethereum/forks/bpo4/fork.py index f3eb249dfaf..159f7369075 100644 --- a/src/ethereum/forks/bpo4/fork.py +++ b/src/ethereum/forks/bpo4/fork.py @@ -995,7 +995,7 @@ def process_withdrawals( rlp.encode(wd), ) - create_ether(wd_state, wd.address, wd.amount * U256(10**9)) + create_ether(wd_state, wd.address, U256(wd.amount) * U256(10**9)) incorporate_tx_into_block(wd_state) diff --git a/src/ethereum/forks/bpo5/blocks.py b/src/ethereum/forks/bpo5/blocks.py index 83e98d6345f..b707e1c0ae5 100644 --- a/src/ethereum/forks/bpo5/blocks.py +++ b/src/ethereum/forks/bpo5/blocks.py @@ -59,9 +59,9 @@ class Withdrawal: The execution-layer address receiving the withdrawn ETH. """ - amount: U256 + amount: U64 """ - The amount of ETH being withdrawn. + The amount of ETH being withdrawn, in Gwei. """ diff --git a/src/ethereum/forks/bpo5/fork.py b/src/ethereum/forks/bpo5/fork.py index f3eb249dfaf..159f7369075 100644 --- a/src/ethereum/forks/bpo5/fork.py +++ b/src/ethereum/forks/bpo5/fork.py @@ -995,7 +995,7 @@ def process_withdrawals( rlp.encode(wd), ) - create_ether(wd_state, wd.address, wd.amount * U256(10**9)) + create_ether(wd_state, wd.address, U256(wd.amount) * U256(10**9)) incorporate_tx_into_block(wd_state) diff --git a/src/ethereum/forks/cancun/blocks.py b/src/ethereum/forks/cancun/blocks.py index d1697870b1e..e1cce59b3aa 100644 --- a/src/ethereum/forks/cancun/blocks.py +++ b/src/ethereum/forks/cancun/blocks.py @@ -58,9 +58,9 @@ class Withdrawal: The execution-layer address receiving the withdrawn ETH. """ - amount: U256 + amount: U64 """ - The amount of ETH being withdrawn. + The amount of ETH being withdrawn, in Gwei. """ diff --git a/src/ethereum/forks/cancun/fork.py b/src/ethereum/forks/cancun/fork.py index c3392030944..9cf2d1860af 100644 --- a/src/ethereum/forks/cancun/fork.py +++ b/src/ethereum/forks/cancun/fork.py @@ -813,7 +813,7 @@ def process_withdrawals( rlp.encode(wd), ) - create_ether(wd_state, wd.address, wd.amount * U256(10**9)) + create_ether(wd_state, wd.address, U256(wd.amount) * U256(10**9)) incorporate_tx_into_block(wd_state) diff --git a/src/ethereum/forks/osaka/blocks.py b/src/ethereum/forks/osaka/blocks.py index 1055ba3407c..19114df55a5 100644 --- a/src/ethereum/forks/osaka/blocks.py +++ b/src/ethereum/forks/osaka/blocks.py @@ -59,9 +59,9 @@ class Withdrawal: The execution-layer address receiving the withdrawn ETH. """ - amount: U256 + amount: U64 """ - The amount of ETH being withdrawn. + The amount of ETH being withdrawn, in Gwei. """ diff --git a/src/ethereum/forks/osaka/fork.py b/src/ethereum/forks/osaka/fork.py index f3eb249dfaf..159f7369075 100644 --- a/src/ethereum/forks/osaka/fork.py +++ b/src/ethereum/forks/osaka/fork.py @@ -995,7 +995,7 @@ def process_withdrawals( rlp.encode(wd), ) - create_ether(wd_state, wd.address, wd.amount * U256(10**9)) + create_ether(wd_state, wd.address, U256(wd.amount) * U256(10**9)) incorporate_tx_into_block(wd_state) diff --git a/src/ethereum/forks/prague/blocks.py b/src/ethereum/forks/prague/blocks.py index 2b2c50cbba9..47f8df932b4 100644 --- a/src/ethereum/forks/prague/blocks.py +++ b/src/ethereum/forks/prague/blocks.py @@ -59,9 +59,9 @@ class Withdrawal: The execution-layer address receiving the withdrawn ETH. """ - amount: U256 + amount: U64 """ - The amount of ETH being withdrawn. + The amount of ETH being withdrawn, in Gwei. """ diff --git a/src/ethereum/forks/prague/fork.py b/src/ethereum/forks/prague/fork.py index 76805746c13..2c6c333a086 100644 --- a/src/ethereum/forks/prague/fork.py +++ b/src/ethereum/forks/prague/fork.py @@ -978,7 +978,7 @@ def process_withdrawals( rlp.encode(wd), ) - create_ether(wd_state, wd.address, wd.amount * U256(10**9)) + create_ether(wd_state, wd.address, U256(wd.amount) * U256(10**9)) incorporate_tx_into_block(wd_state) diff --git a/src/ethereum/forks/shanghai/blocks.py b/src/ethereum/forks/shanghai/blocks.py index ad2b2b01293..504ff2a72db 100644 --- a/src/ethereum/forks/shanghai/blocks.py +++ b/src/ethereum/forks/shanghai/blocks.py @@ -57,9 +57,9 @@ class Withdrawal: The execution-layer address receiving the withdrawn ETH. """ - amount: U256 + amount: U64 """ - The amount of ETH being withdrawn. + The amount of ETH being withdrawn, in Gwei. """ diff --git a/src/ethereum/forks/shanghai/fork.py b/src/ethereum/forks/shanghai/fork.py index 1f23255fa6f..dd899f1ab30 100644 --- a/src/ethereum/forks/shanghai/fork.py +++ b/src/ethereum/forks/shanghai/fork.py @@ -647,7 +647,7 @@ def process_withdrawals( rlp.encode(wd), ) - create_ether(wd_state, wd.address, wd.amount * U256(10**9)) + create_ether(wd_state, wd.address, U256(wd.amount) * U256(10**9)) incorporate_tx_into_block(wd_state) diff --git a/src/ethereum_spec_tools/evm_tools/b11r/b11r_types.py b/src/ethereum_spec_tools/evm_tools/b11r/b11r_types.py index f15a1ce5473..4cc7be9bdc4 100644 --- a/src/ethereum_spec_tools/evm_tools/b11r/b11r_types.py +++ b/src/ethereum_spec_tools/evm_tools/b11r/b11r_types.py @@ -27,7 +27,7 @@ class Body: transactions: rlp.Extended ommers: rlp.Extended - withdrawals: Optional[List[Tuple[U64, U64, Bytes20, Uint]]] + withdrawals: Optional[List[Tuple[U64, U64, Bytes20, U64]]] def __init__(self, options: Any, stdin: Any = None): # Parse transactions @@ -83,7 +83,7 @@ def __init__(self, options: Any, stdin: Any = None): parse_hex_or_int(wd["index"], U64), parse_hex_or_int(wd["validatorIndex"], U64), Bytes20(hex_to_bytes(wd["address"])), - parse_hex_or_int(wd["amount"], Uint), + parse_hex_or_int(wd["amount"], U64), ) ) diff --git a/src/ethereum_spec_tools/evm_tools/loaders/fixture_loader.py b/src/ethereum_spec_tools/evm_tools/loaders/fixture_loader.py index f4bf3aa4e5b..f7462bbccc8 100644 --- a/src/ethereum_spec_tools/evm_tools/loaders/fixture_loader.py +++ b/src/ethereum_spec_tools/evm_tools/loaders/fixture_loader.py @@ -96,7 +96,7 @@ def json_to_withdrawals(self, raw: Any) -> Any: hex_to_u64(raw.get("index")), hex_to_u64(raw.get("validatorIndex")), self.fork.hex_to_address(raw.get("address")), - hex_to_u256(raw.get("amount")), + hex_to_u64(raw.get("amount")), ] return self.fork.Withdrawal(*parameters) diff --git a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py b/src/ethereum_spec_tools/evm_tools/t8n/__init__.py index a6f6fe42c3e..bbf59167d72 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py +++ b/src/ethereum_spec_tools/evm_tools/t8n/__init__.py @@ -402,10 +402,10 @@ def _run_blockchain_test(self, block_env: Any, block_output: Any) -> None: withdrawals = self.env.withdrawals or [] fork_withdrawals = tuple( self.fork.Withdrawal( - Uint(int(w.index)), - Uint(int(w.validator_index)), + U64(int(w.index)), + U64(int(w.validator_index)), self.fork.hex_to_address(w.address.hex()), - U256(int(w.amount)), + U64(int(w.amount)), ) for w in withdrawals ) diff --git a/src/ethereum_spec_tools/sync.py b/src/ethereum_spec_tools/sync.py index cb72a9ef603..4969cd725db 100644 --- a/src/ethereum_spec_tools/sync.py +++ b/src/ethereum_spec_tools/sync.py @@ -616,7 +616,7 @@ def make_block(self, json: Any, ommers: Any) -> Any: self.module("utils.hexadecimal").hex_to_address( j["address"] ), - hex_to_u256(j["amount"]), + hex_to_u64(j["amount"]), ) ) diff --git a/tests/json_loader/test_withdrawal_codec.py b/tests/json_loader/test_withdrawal_codec.py new file mode 100644 index 00000000000..7818c9147a4 --- /dev/null +++ b/tests/json_loader/test_withdrawal_codec.py @@ -0,0 +1,28 @@ +"""Test that withdrawal amounts decode as 64-bit unsigned integers.""" + +import pytest +from ethereum_rlp import rlp +from ethereum_rlp.exceptions import RLPException +from ethereum_types.numeric import U64, U256 + +from ethereum.forks.amsterdam.blocks import Withdrawal +from ethereum.state import Address + + +def test_decode_max_withdrawal_amount() -> None: + """Round-trip a withdrawal with the largest valid amount.""" + withdrawal = Withdrawal( + index=U64(0), + validator_index=U64(0), + address=Address(b"\x00" * 20), + amount=U64(2**64 - 1), + ) + encoded = rlp.encode(withdrawal) + assert rlp.decode_to(Withdrawal, encoded) == withdrawal + + +def test_decode_oversized_withdrawal_amount() -> None: + """Reject a withdrawal whose amount does not fit in 64 bits.""" + encoded = rlp.encode((U64(0), U64(0), Address(b"\x00" * 20), U256(2**64))) + with pytest.raises(RLPException): + rlp.decode_to(Withdrawal, encoded) From ee7a6779017e2dfce53766795327209ff222ae14 Mon Sep 17 00:00:00 2001 From: Mario Vega <marioevz@gmail.com> Date: Wed, 5 Aug 2026 04:13:18 -0600 Subject: [PATCH 200/233] fix(test-execute): fix execute for Amsterdam (#3300) Co-authored-by: spencer-tb <spencer.tb@ethereum.org> --- .../execute/rpc/chain_builder_eth_rpc.py | 140 ++++++++++++++++-- .../plugins/execute/rpc/hive.py | 6 +- .../pytest_commands/plugins/execute/sender.py | 32 +++- .../client_clis/client_backend.py | 8 +- .../execution_testing/fixtures/blockchain.py | 1 + .../src/execution_testing/forks/base_fork.py | 8 + .../execution_testing/forks/forks/forks.py | 15 +- .../src/execution_testing/rpc/rpc_types.py | 15 +- .../src/execution_testing/specs/blockchain.py | 1 + .../test_types/block_types.py | 7 +- 10 files changed, 205 insertions(+), 28 deletions(-) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/rpc/chain_builder_eth_rpc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/rpc/chain_builder_eth_rpc.py index 37b7af9aa93..9097a3857fa 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/rpc/chain_builder_eth_rpc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/rpc/chain_builder_eth_rpc.py @@ -6,18 +6,21 @@ import time from contextlib import AbstractContextManager from pathlib import Path -from typing import Any, List, Sequence, Tuple +from typing import Any, List, Mapping, Sequence, Tuple from urllib.parse import urlparse from filelock import FileLock +from pydantic import ValidationError from execution_testing.base_types import ( Address, Bytes, Hash, HexNumber, + to_json, ) from execution_testing.client_clis.cli_types import EnginePayloadMetadata +from execution_testing.fixtures.blockchain import FixtureHeader from execution_testing.forks import Fork, TransitionFork from execution_testing.rpc import ( DEFAULT_REQUEST_TIMEOUT, @@ -36,6 +39,73 @@ from execution_testing.test_types import Withdrawal +def _genesis_header_differences( + expected: FixtureHeader, + actual_block: Mapping[str, Any], +) -> List[str]: + """ + Return the per-field differences between the expected genesis header and + the genesis block returned by the client, using the header's own field + names. + + Return an empty list when the client's block cannot be parsed as a + header, in which case the caller only reports the block hashes. + """ + try: + actual_header = FixtureHeader.model_validate(actual_block) + except ValidationError: + return [] + expected_json = to_json(expected) + actual_json = to_json(actual_header) + # A field can be missing from either side: the fork may require a header + # field we never set, or the client may not report one we do set. + fields = list(expected_json) + [ + field for field in actual_json if field not in expected_json + ] + differences = [ + f"{field}: expected {expected_json.get(field, '<unset>')}, " + f"got {actual_json.get(field, '<unset>')}" + # The block hash is reported separately by the caller. + for field in fields + if field != "hash" + and expected_json.get(field) != actual_json.get(field) + ] + if actual_header.block_hash != Hash(actual_block["hash"]): + differences.append( + "note: the client's header does not hash to the block hash it " + "reports when re-encoded locally, so the fields listed above may " + "be incomplete" + ) + return differences + + +class GenesisMismatchError(Exception): + """ + The client's genesis block does not match the one built locally. + """ + + def __init__( + self, + expected: FixtureHeader, + actual_block: Mapping[str, Any], + ) -> None: + """Initialize the exception with both genesis headers.""" + self.expected = expected + self.actual_block = actual_block + message = ( + "the client's genesis block does not match the one built " + "locally:" + f"\n expected block hash: {expected.block_hash}" + f"\n client block hash: {Hash(actual_block['hash'])}" + ) + differences = _genesis_header_differences(expected, actual_block) + if differences: + message += "\n differing header fields:" + for difference in differences: + message += f"\n {difference}" + super().__init__(message) + + class ChainBuilderEthRPC(BaseEthRPC, namespace="eth"): """ Special type of Ethereum RPC client that also has access to the Engine API @@ -62,6 +132,7 @@ def __init__( max_transactions_per_batch: int | None = None, request_timeout: TimeoutType = DEFAULT_REQUEST_TIMEOUT, testing_rpc: TestingRPC | None = None, + expected_genesis_header: FixtureHeader | None = None, ): """Initialize the Ethereum RPC client for the hive simulator.""" super().__init__( @@ -90,6 +161,8 @@ def __init__( "Error occurred during initial forkchoice_updated" ) if not base_file.exists(): + if expected_genesis_header is not None: + self.verify_genesis_block(expected_genesis_header) base_error_file.touch() # Assume error # Get the head block hash head_block = self.get_block_by_number("latest") @@ -126,6 +199,17 @@ def __init__( base_error_file.unlink() # Success base_file.touch() + def verify_genesis_block(self, expected: FixtureHeader) -> None: + """ + Verify the client's genesis block matches the one built locally. + """ + genesis_block = self.get_block_by_number(0) + assert genesis_block is not None, ( + "client did not return its genesis block" + ) + if Hash(genesis_block["hash"]) != expected.block_hash: + raise GenesisMismatchError(expected, genesis_block) + @property def transaction_polling_context(self) -> AbstractContextManager: """ @@ -137,17 +221,49 @@ def transaction_polling_context(self) -> AbstractContextManager: """ return self.block_building_lock + def _next_timestamp(self, head_block: Mapping[str, Any]) -> int: + """Return the timestamp of the block following ``head_block``.""" + return int(HexNumber(head_block["timestamp"]) + 1) + + def _next_fork(self, head_block: Mapping[str, Any]) -> Fork: + """Return the fork of the block following ``head_block``.""" + return self.fork.fork_at( + block_number=0, timestamp=self._next_timestamp(head_block) + ) + + def _head_fork(self, head_block: Mapping[str, Any]) -> Fork: + """Return the fork of ``head_block`` itself.""" + return self.fork.fork_at( + block_number=int(HexNumber(head_block["number"])), + timestamp=int(HexNumber(head_block["timestamp"])), + ) + def _payload_attributes( self, + head_block: Mapping[str, Any], *, - next_timestamp: int, withdrawals: List[Withdrawal] | None = None, ) -> PayloadAttributes: - """Build payload attributes for a block at ``next_timestamp``.""" - next_fork = self.fork.fork_at(block_number=0, timestamp=next_timestamp) + """ + Build the payload attributes for the block following ``head_block``. + """ + next_timestamp = self._next_timestamp(head_block) + next_fork = self._next_fork(head_block) + next_slot_number: int | None = None + if next_fork.engine_payload_attribute_slot_number(): + if not self._head_fork(head_block).header_slot_number_required(): + next_slot_number = 1 + else: + assert "slotNumber" in head_block, ( + "fork requires a slot number in the block header but the " + "client does not report one for its head block" + ) + next_slot_number = int(HexNumber(head_block["slotNumber"]) + 1) return PayloadAttributes.for_fork( next_fork, timestamp=next_timestamp, + target_gas_limit=int(HexNumber(head_block["gasLimit"])), + slot_number=next_slot_number, withdrawals=withdrawals, ) @@ -216,11 +332,8 @@ def generate_block(self: "ChainBuilderEthRPC") -> None: forkchoice_state = ForkchoiceState( head_block_hash=head_block["hash"], ) - next_timestamp = int(HexNumber(head_block["timestamp"]) + 1) - next_fork = self.fork.fork_at(block_number=0, timestamp=next_timestamp) - payload_attributes = self._payload_attributes( - next_timestamp=next_timestamp - ) + next_fork = self._next_fork(head_block) + payload_attributes = self._payload_attributes(head_block) forkchoice_updated_version = ( next_fork.engine_forkchoice_updated_version() ) @@ -292,10 +405,7 @@ def build_block_with_transactions( with self.block_building_lock: head_block = self.get_block_by_number("latest") assert head_block is not None - next_timestamp = int(HexNumber(head_block["timestamp"]) + 1) - payload_attributes = self._payload_attributes( - next_timestamp=next_timestamp, - ) + payload_attributes = self._payload_attributes(head_block) new_payload = self.testing_rpc.build_block( parent_block_hash=Hash(head_block["hash"]), payload_attributes=payload_attributes, @@ -336,10 +446,8 @@ def fund_via_withdrawals( with self.block_building_lock: head_block = self.get_block_by_number("latest") assert head_block is not None - next_timestamp = int(HexNumber(head_block["timestamp"]) + 1) payload_attributes = self._payload_attributes( - next_timestamp=next_timestamp, - withdrawals=withdrawals, + head_block, withdrawals=withdrawals ) # Explicit empty list, not ``None``: per spec, ``null`` lets # the client pull from its mempool, but we want a diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/rpc/hive.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/rpc/hive.py index a5f1aa18859..cc06ac10e3c 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/rpc/hive.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/rpc/hive.py @@ -28,6 +28,7 @@ DETERMINISTIC_FACTORY_BYTECODE, EOA, Alloc, + BlockAccessList, ChainConfig, Environment, Requests, @@ -182,9 +183,10 @@ def build_genesis_header( requests_hash=Requests() if genesis_fork.header_requests_required() else None, - block_access_list_hash=Hash(EmptyTrieRoot) + block_access_list_hash=BlockAccessList().rlp_hash if genesis_fork.header_bal_hash_required() else None, + slot_number=0 if genesis_fork.header_slot_number_required() else None, ) return (pre_alloc, genesis) @@ -458,6 +460,7 @@ def eth_rpc( session_temp_folder: Path, max_transactions_per_batch: int | None, use_testing_build_block: bool, + base_pre_genesis: Tuple[Alloc, FixtureHeader], ) -> EthRPC: """Initialize ethereum RPC client for the execution client under test.""" get_payload_wait_time = request.config.getoption("get_payload_wait_time") @@ -474,4 +477,5 @@ def eth_rpc( transaction_wait_timeout=tx_wait_timeout, max_transactions_per_batch=max_transactions_per_batch, testing_rpc=testing_rpc, + expected_genesis_header=base_pre_genesis[1], ) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/sender.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/sender.py index 40db82a1eb2..3bfef2665f8 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/sender.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/sender.py @@ -10,7 +10,9 @@ from pytest_metadata.plugin import metadata_key from execution_testing.base_types import Account, Address, Number, Wei +from execution_testing.forks import Fork, TransitionFork from execution_testing.logging import get_logger +from execution_testing.recipient_type import RecipientType from execution_testing.rpc import EthRPC from execution_testing.rpc.rpc_types import JSONRPCError from execution_testing.test_types import ( @@ -56,9 +58,11 @@ def pytest_addoption(parser: pytest.Parser) -> None: action="store", dest="sender_fund_refund_gas_limit", type=Wei, - default=200_000, + default=None, help=( - "Gas limit set for the funding transactions of each worker's sender key." # noqa: E501 + "Gas limit set for the funding transactions of each worker's " + "sender key. Default=None (derived from the fork's cost of a " + "value transfer that creates the recipient account)." ), ) @@ -97,9 +101,27 @@ def sender_funding_transactions_gas_price( @pytest.fixture(scope="session") -def sender_fund_refund_gas_limit(request: pytest.FixtureRequest) -> int: - """Get the gas limit of the funding transactions.""" - gas_limit = request.config.option.sender_fund_refund_gas_limit +def sender_fund_refund_gas_limit( + request: pytest.FixtureRequest, + session_fork: Fork | TransitionFork, +) -> int: + """ + Get the gas limit of the funding and refund transactions. + + A funding transaction creates the recipient account, which is charged + account-creation state gas on top of the intrinsic cost, so the default + is derived from the fork instead of being a fixed value. + """ + gas_limit: int | None = request.config.option.sender_fund_refund_gas_limit + if gas_limit is None: + fork = session_fork.transitions_to() + gas_limit = fork.transaction_intrinsic_cost_calculator()( + sends_value=True, + recipient_type=RecipientType.EMPTY_ACCOUNT, + ) + fork.transaction_top_frame_state_gas( + sends_value=True, + recipient_type=RecipientType.EMPTY_ACCOUNT, + ) logger.info(f"Using gas limit for funding transactions: {gas_limit}") return gas_limit diff --git a/packages/testing/src/execution_testing/client_clis/client_backend.py b/packages/testing/src/execution_testing/client_clis/client_backend.py index 2c2231b9d42..ff1c5c4d003 100644 --- a/packages/testing/src/execution_testing/client_clis/client_backend.py +++ b/packages/testing/src/execution_testing/client_clis/client_backend.py @@ -36,6 +36,7 @@ ) from execution_testing.test_types import ( Alloc, + Environment, Requests, Transaction, Withdrawal, @@ -360,7 +361,7 @@ def _trace_block( def _payload_attributes( self, - env: Any, + env: Environment, block_fork: Fork, ) -> PayloadAttributes: """Build ``PayloadAttributes`` from the test's environment.""" @@ -370,13 +371,18 @@ def _payload_attributes( parent_beacon_block_root: Hash | None = None if block_fork.header_beacon_root_required(): parent_beacon_block_root = Hash(env.parent_beacon_block_root or 0) + slot_number: int | None = None + if env.slot_number is not None: + slot_number = int(env.slot_number) return PayloadAttributes.for_fork( block_fork, timestamp=int(env.timestamp), + target_gas_limit=int(env.gas_limit), prev_randao=Hash(env.prev_randao or 0), suggested_fee_recipient=env.fee_recipient, withdrawals=withdrawals, parent_beacon_block_root=parent_beacon_block_root, + slot_number=slot_number, ) def _finalize( diff --git a/packages/testing/src/execution_testing/fixtures/blockchain.py b/packages/testing/src/execution_testing/fixtures/blockchain.py index 3a840769c06..5ad3d932fa5 100644 --- a/packages/testing/src/execution_testing/fixtures/blockchain.py +++ b/packages/testing/src/execution_testing/fixtures/blockchain.py @@ -561,6 +561,7 @@ def get_payload_attributes(self) -> "PayloadAttributes": withdrawals=execution_payload.withdrawals, parent_beacon_block_root=parent_beacon_block_root, slot_number=execution_payload.slot_number, + target_gas_limit=execution_payload.gas_limit, ) @staticmethod diff --git a/packages/testing/src/execution_testing/forks/base_fork.py b/packages/testing/src/execution_testing/forks/base_fork.py index bf006ce2603..b1a5026acfb 100644 --- a/packages/testing/src/execution_testing/forks/base_fork.py +++ b/packages/testing/src/execution_testing/forks/base_fork.py @@ -1152,6 +1152,14 @@ def engine_payload_attribute_slot_number(cls) -> bool: """ pass + @classmethod + @abstractmethod + def engine_payload_attribute_target_gas_limit(cls) -> bool: + """ + Return true if the payload attributes include the target gas limit. + """ + pass + # Engine API method versions @classmethod def engine_new_payload_version(cls) -> Optional[int]: diff --git a/packages/testing/src/execution_testing/forks/forks/forks.py b/packages/testing/src/execution_testing/forks/forks/forks.py index 29ffc2b5eda..b4f8492993d 100644 --- a/packages/testing/src/execution_testing/forks/forks/forks.py +++ b/packages/testing/src/execution_testing/forks/forks/forks.py @@ -1011,6 +1011,13 @@ def engine_payload_attribute_slot_number(cls) -> bool: """ return False + @classmethod + def engine_payload_attribute_target_gas_limit(cls) -> bool: + """ + At genesis, payload attributes do not include the target gas limit. + """ + return False + @classmethod def get_reward(cls) -> int: """ @@ -1633,4 +1640,10 @@ class Amsterdam( # related Amsterdam specs change over time, and before Amsterdam is # live on mainnet. - pass + @classmethod + def engine_payload_attribute_target_gas_limit(cls) -> bool: + """ + Starting from Amsterdam, payload attributes now include the target gas + limit. + """ + return True diff --git a/packages/testing/src/execution_testing/rpc/rpc_types.py b/packages/testing/src/execution_testing/rpc/rpc_types.py index f58bd345378..271a268c04f 100644 --- a/packages/testing/src/execution_testing/rpc/rpc_types.py +++ b/packages/testing/src/execution_testing/rpc/rpc_types.py @@ -224,6 +224,7 @@ class PayloadAttributes(CamelModel): target_blobs_per_block: HexNumber | None = None max_blobs_per_block: HexNumber | None = None slot_number: HexNumber | None = None + target_gas_limit: HexNumber | None = None @classmethod def for_fork( @@ -231,6 +232,8 @@ def for_fork( fork: Fork, *, timestamp: int, + target_gas_limit: int, + slot_number: int | None, prev_randao: Hash | None = None, suggested_fee_recipient: Address | None = None, withdrawals: List[Withdrawal] | None = None, @@ -250,6 +253,11 @@ def for_fork( and fork.header_beacon_root_required() ): parent_beacon_block_root = Hash(0) + attributes_slot_number: HexNumber | None = None + if fork.engine_payload_attribute_slot_number(): + attributes_slot_number = HexNumber( + 1 if slot_number is None else slot_number + ) return cls( timestamp=HexNumber(timestamp), prev_randao=prev_randao if prev_randao is not None else Hash(0), @@ -270,9 +278,10 @@ def for_fork( if fork.engine_payload_attribute_max_blobs_per_block() else None ), - slot_number=( - HexNumber(0) - if fork.engine_payload_attribute_slot_number() + slot_number=attributes_slot_number, + target_gas_limit=( + HexNumber(target_gas_limit) + if fork.engine_payload_attribute_target_gas_limit() else None ), ) diff --git a/packages/testing/src/execution_testing/specs/blockchain.py b/packages/testing/src/execution_testing/specs/blockchain.py index 519944255a4..ba0f1f191fd 100644 --- a/packages/testing/src/execution_testing/specs/blockchain.py +++ b/packages/testing/src/execution_testing/specs/blockchain.py @@ -132,6 +132,7 @@ def apply_new_parent( updated["parent_gas_used"] = new_parent.gas_used updated["parent_gas_limit"] = new_parent.gas_limit updated["parent_ommers_hash"] = new_parent.ommers_hash + updated["parent_slot_number"] = new_parent.slot_number block_hashes = env.block_hashes.copy() block_hashes[new_parent.number] = new_parent.block_hash updated["block_hashes"] = block_hashes diff --git a/packages/testing/src/execution_testing/test_types/block_types.py b/packages/testing/src/execution_testing/test_types/block_types.py index 56d367e2e68..89e1ce88eca 100644 --- a/packages/testing/src/execution_testing/test_types/block_types.py +++ b/packages/testing/src/execution_testing/test_types/block_types.py @@ -135,6 +135,7 @@ def strip_computed_fields(cls, data: Any) -> Any: ) parent_blob_gas_used: ZeroPaddedHexNumber | None = Field(None) parent_excess_blob_gas: ZeroPaddedHexNumber | None = Field(None) + parent_slot_number: ZeroPaddedHexNumber | None = Field(None) parent_beacon_block_root: Hash | None = Field(None) block_hashes: Dict[ZeroPaddedHexNumber, Hash] = Field(default_factory=dict) @@ -202,7 +203,11 @@ def set_fork_requirements(self, fork: Fork) -> "Environment": updated_values["parent_beacon_block_root"] = 0 if fork.header_slot_number_required() and self.slot_number is None: - updated_values["slot_number"] = 0 + updated_values["slot_number"] = ( + int(self.parent_slot_number) + 1 + if self.parent_slot_number is not None + else 0 + ) return self.copy(**updated_values) From bad053c5e6cfc66258cab5047485e184a1b9cf56 Mon Sep 17 00:00:00 2001 From: danceratopz <danceratopz@gmail.com> Date: Wed, 5 Aug 2026 12:59:51 +0200 Subject: [PATCH 201/233] perf(ci): run the PR docker-image cache gate on a GitHub-hosted runner (#3185) --- .github/workflows/hive-consume.yaml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/hive-consume.yaml b/.github/workflows/hive-consume.yaml index a87c39fac8e..7af5b3f3af2 100644 --- a/.github/workflows/hive-consume.yaml +++ b/.github/workflows/hive-consume.yaml @@ -69,7 +69,14 @@ env: jobs: cache-docker-images: name: Cache Docker Images - runs-on: [self-hosted-ghr, size-l-x64] + # On PRs this job is normally a pure cache restore (~12s), so run it on a + # GitHub-hosted runner instead of paying the ~2.5 min self-hosted + # provisioning wait, which would gate every test-hive job. Push and + # dispatch runs stay on self-hosted so the weekly cache is populated from + # GHR egress IPs, avoiding Docker Hub's per-IP rate limits on the shared + # GitHub-hosted IPs. A PR that lands right after the weekly cache-key + # rollover pulls the 3 images unauthenticated; that rare miss is accepted. + runs-on: ${{ fromJSON(github.event_name == 'pull_request' && '["ubuntu-latest"]' || '["self-hosted-ghr", "size-l-x64"]') }} steps: - name: Checkout execution-specs uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 From 4b4415c952b6ac1e341bcc46b0441327675a2f6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=94=A1=E4=BD=B3=E8=AA=A0=20Louis=20Tsai?= <72684086+LouisTsai-Csie@users.noreply.github.com> Date: Wed, 5 Aug 2026 19:05:38 +0800 Subject: [PATCH 202/233] feat(tests): enhance test coverage, add mainnet test for eip-2780 (#3266) Co-authored-by: Mario Vega <marioevz@gmail.com> Co-authored-by: spencer <spencer.tb@ethereum.org> --- .../helpers.py | 13 +- .../test_eip_mainnet.py | 172 ++++++++++++++++++ .../test_value_moving_transactions.py | 143 +++++++++++++++ .../test_warmth_invariants.py | 51 +++++- 4 files changed, 373 insertions(+), 6 deletions(-) create mode 100644 tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_eip_mainnet.py diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/helpers.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/helpers.py index 3b158cfc374..1053ef60e75 100644 --- a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/helpers.py +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/helpers.py @@ -5,6 +5,7 @@ from execution_testing import ( EOA, + AccessList, Account, Address, Alloc, @@ -16,7 +17,7 @@ from ...prague.eip7702_set_code_tx.spec import Spec as Spec7702 -EOA_INITIAL_BALANCE = 100 +EOA_INITIAL_BALANCE = 1 NULL_ADDRESS = Address(0) RECIPIENT_TYPES_NON_CREATE = [ @@ -186,7 +187,10 @@ def build_authorization( def authorization_transaction_cost( - fork: Fork, authorization_list: list[AuthorizationTuple] + fork: Fork, + authorization_list: list[AuthorizationTuple], + *, + access_list: list[AccessList] | None = None, ) -> int: """ Return the exact gas a value-free type-4 transaction to a plain @@ -199,6 +203,7 @@ def authorization_transaction_cost( ``writes_delegation`` / ``first_write`` annotations. """ intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( + access_list=access_list, recipient_type=RecipientType.CONTRACT, authorization_list_or_count=authorization_list, return_cost_deducted_prior_execution=True, @@ -235,8 +240,6 @@ def setup_target( return sender case RecipientType.DELEGATION_7702: delegated_to = pre.deploy_contract(code=Op.STOP) - return pre.deploy_contract( - code=Spec7702.delegation_designation(delegated_to) - ) + return pre.fund_eoa(amount=0, delegation=delegated_to) case _: raise ValueError(f"Unsupported recipient type {recipient_type}") diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_eip_mainnet.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_eip_mainnet.py new file mode 100644 index 00000000000..6fdd5d41170 --- /dev/null +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_eip_mainnet.py @@ -0,0 +1,172 @@ +""" +Mainnet-marked tests for +[EIP-2780: Resource-based intrinsic transaction gas](https://eips.ethereum.org/EIPS/eip-2780). + +One case per row of the EIP's transaction reference table. This EIP only +reprices, so the pinned ``cumulative_gas_used`` is the sole observable +that catches a client still charging the legacy intrinsic. +""" + +import pytest +from execution_testing import ( + Account, + Address, + Alloc, + Fork, + Initcode, + Op, + RecipientType, + StateTestFiller, + Transaction, + TransactionReceipt, + compute_create_address, +) +from execution_testing.checklists import EIPChecklist + +from .helpers import ( + EOA_INITIAL_BALANCE, + RECIPIENT_TYPES_NON_CREATE, + AuthorizationAction, + authorization_transaction_cost, + build_authorization, + setup_target, +) +from .spec import ref_spec_2780 + +REFERENCE_SPEC_GIT_PATH = ref_spec_2780.git_path +REFERENCE_SPEC_VERSION = ref_spec_2780.version + +pytestmark = [pytest.mark.valid_at("EIP2780"), pytest.mark.mainnet] + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +@pytest.mark.parametrize("recipient_type", RECIPIENT_TYPES_NON_CREATE) +@pytest.mark.parametrize( + "value", + [ + pytest.param(0, id="zero_value"), + pytest.param(1, id="non-zero_value"), + ], +) +def test_transaction_gas( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + recipient_type: RecipientType, + value: int, +) -> None: + """Gas for a non-create transaction, per recipient type and value.""" + sender = pre.fund_eoa() + target = setup_target(pre, recipient_type, sender) + + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( + sends_value=bool(value), + recipient_type=recipient_type, + return_cost_deducted_prior_execution=True, + ) + top_frame_gas = fork.transaction_top_frame_gas_calculator()( + sends_value=bool(value), + recipient_type=recipient_type, + ) + top_frame_state_gas = fork.transaction_top_frame_state_gas( + sends_value=bool(value), + recipient_type=recipient_type, + ) + gas_used = intrinsic_gas + top_frame_gas + top_frame_state_gas + + tx = Transaction( + to=target, + value=value, + gas_limit=gas_used, + sender=sender, + expected_receipt=TransactionReceipt(cumulative_gas_used=gas_used), + ) + + post: dict[Address, Account | None] = {sender: Account(nonce=1)} + if recipient_type != RecipientType.SELF: + target_initial_balance = ( + EOA_INITIAL_BALANCE if recipient_type == RecipientType.EOA else 0 + ) + if recipient_type == RecipientType.EMPTY_ACCOUNT and value == 0: + post[target] = None + else: + post[target] = Account(balance=target_initial_balance + value) + + state_test(pre=pre, post=post, tx=tx) + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +@pytest.mark.parametrize( + "value", + [ + pytest.param(0, id="zero_value"), + pytest.param(1, id="non-zero_value"), + ], +) +def test_contract_creation_gas( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + value: int, +) -> None: + """Gas for a contract-creation transaction, per value.""" + sender = pre.fund_eoa() + deploy_code = Op.STOP + init_code = Initcode(deploy_code=deploy_code) + + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( + calldata=init_code, + contract_creation=True, + sends_value=bool(value), + return_cost_deducted_prior_execution=True, + ) + new_account_state_gas = fork.transaction_top_frame_state_gas( + contract_creation=True, + ) + gas_used = intrinsic_gas + new_account_state_gas + init_code.gas_cost(fork) + + tx = Transaction( + to=None, + value=value, + data=init_code, + gas_limit=gas_used, + sender=sender, + expected_receipt=TransactionReceipt(cumulative_gas_used=gas_used), + ) + + created = compute_create_address(address=sender, nonce=0) + post = {created: Account(balance=value, code=deploy_code)} + state_test(pre=pre, post=post, tx=tx) + + +@EIPChecklist.GasCostChanges.Test.GasUpdatesMeasurement() +@pytest.mark.parametrize( + "action", + [ + pytest.param(AuthorizationAction.CREATES_ACCOUNT, id="new_authority"), + pytest.param( + AuthorizationAction.SETS_NEW_DELEGATION, id="existing_authority" + ), + ], +) +def test_authorization_gas( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + action: AuthorizationAction, +) -> None: + """Gas for one EIP-7702 authorization, per authority pre-state.""" + scenario = build_authorization(pre, action) + authorization_list = [scenario.authorization] + gas_used = authorization_transaction_cost(fork, authorization_list) + + tx = Transaction( + to=pre.deploy_contract(code=Op.STOP), + authorization_list=authorization_list, + gas_limit=gas_used, + sender=pre.fund_eoa(), + expected_receipt=TransactionReceipt(cumulative_gas_used=gas_used), + ) + + post = {scenario.authority: scenario.applied_account} + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_value_moving_transactions.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_value_moving_transactions.py index 05fd5d9aef1..dbabbffa5c2 100644 --- a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_value_moving_transactions.py +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_value_moving_transactions.py @@ -13,23 +13,30 @@ import pytest from execution_testing import ( + AccessList, Account, Address, Alloc, Fork, + Hash, Initcode, Op, RecipientType, StateTestFiller, Transaction, TransactionReceipt, + add_kzg_version, compute_create_address, ) +from ...cancun.eip4844_blobs.spec import Spec as EIP4844_Spec +from ...prague.eip7702_set_code_tx.spec import Spec as Spec7702 from ..eip7708_eth_transfer_logs.spec import transfer_log from .helpers import ( EOA_INITIAL_BALANCE, RECIPIENT_TYPES_NON_CREATE, + AuthorizationAction, + build_authorization, setup_target, ) from .spec import ref_spec_2780 @@ -128,6 +135,142 @@ def test_value_moving_transactions( state_test(pre=pre, tx=tx, post=post) +@pytest.mark.parametrize( + "delegation_warm", + [ + pytest.param(False, id="cold_delegation_target"), + pytest.param(True, id="warm_delegation_target"), + ], +) +def test_self_transfer_with_delegated_sender( + fork: Fork, + pre: Alloc, + state_test: StateTestFiller, + delegation_warm: bool, +) -> None: + """ + Gas for a self-transfer whose sender already holds a delegation. + + The EIP's prose skips the delegation-target access for a + self-transfer; its reference-case row charges it. + """ + value = 1 + delegated_to = pre.deploy_contract(code=Op.STOP) + sender = pre.fund_eoa(delegation=delegated_to) + + access_list = ( + [AccessList(address=delegated_to, storage_keys=[])] + if delegation_warm + else [] + ) + + intrinsic_recipient_type = RecipientType.SELF + top_frame_recipient_type = RecipientType.DELEGATION_7702 + + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( + access_list=access_list, + sends_value=True, + recipient_type=intrinsic_recipient_type, + return_cost_deducted_prior_execution=True, + ) + top_frame_gas = fork.transaction_top_frame_gas_calculator()( + sends_value=True, + recipient_type=top_frame_recipient_type, + delegation_warm=delegation_warm, + ) + total_gas_cost = intrinsic_gas + top_frame_gas + + tx = Transaction( + sender=sender, + to=sender, + value=value, + access_list=access_list, + gas_limit=total_gas_cost, + expected_receipt=TransactionReceipt( + cumulative_gas_used=total_gas_cost, logs=[] + ), + ) + + post = { + sender: Account( + nonce=2, code=Spec7702.delegation_designation(delegated_to) + ), + } + + state_test(pre=pre, tx=tx, post=post) + + +@pytest.mark.with_all_tx_types +def test_intrinsic_decomposition_across_tx_types( + fork: Fork, + pre: Alloc, + state_test: StateTestFiller, + tx_type: int, +) -> None: + """ + The decomposed intrinsic keys on the transaction's fields, not its + type: every type pays the same recipient and value primitives, with + type-specific costs riding on top. + """ + value = 1 + sender = pre.fund_eoa() + recipient = pre.fund_eoa(amount=EOA_INITIAL_BALANCE) + + scenario = ( + build_authorization(pre, AuthorizationAction.SETS_NEW_DELEGATION) + if tx_type == 4 + else None + ) + authorizations = [scenario.authorization] if scenario else [] + + blob_versioned_hashes = ( + add_kzg_version([Hash(1)], EIP4844_Spec.BLOB_COMMITMENT_VERSION_KZG) + if tx_type == 3 + else None + ) + + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( + sends_value=True, + recipient_type=RecipientType.EOA, + authorization_list_or_count=authorizations, + return_cost_deducted_prior_execution=True, + ) + top_frame_gas = fork.transaction_top_frame_gas_calculator()( + sends_value=True, + recipient_type=RecipientType.EOA, + authorizations=authorizations, + ) + top_frame_state_gas = fork.transaction_top_frame_state_gas( + sends_value=True, + recipient_type=RecipientType.EOA, + authorizations=authorizations, + ) + total_gas_cost = intrinsic_gas + top_frame_gas + top_frame_state_gas + + tx = Transaction( + ty=tx_type, + sender=sender, + to=recipient, + value=value, + authorization_list=authorizations or None, + blob_versioned_hashes=blob_versioned_hashes, + gas_limit=total_gas_cost, + expected_receipt=TransactionReceipt( + cumulative_gas_used=total_gas_cost, + logs=[transfer_log(sender, recipient, value)], + ), + ) + + post: dict[Address, Account] = { + sender: Account(nonce=1), + recipient: Account(balance=EOA_INITIAL_BALANCE + value), + } + if scenario: + post[scenario.authority] = scenario.applied_account + + state_test(pre=pre, tx=tx, post=post) + + @pytest.mark.parametrize( "value", [ diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_warmth_invariants.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_warmth_invariants.py index 202e8442a43..1ba466c87be 100644 --- a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_warmth_invariants.py +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_warmth_invariants.py @@ -8,7 +8,8 @@ intrinsic phase, without reading state, so it is *always cold*: listing ``tx.to`` in the access list pays the access-list cost but does not waive it, and the protocol-warmed coinbase is still charged - cold when it is the recipient. + cold when it is the recipient. The same holds for the authority + access folded into ``EXECUTION_PER_AUTH_BASE_COST``. - A delegated recipient's delegation-target access is a *top-frame* charge that reads state, so it follows normal warm/cold accounting: ``WARM_ACCESS`` when the target is already warm -- the sender, the @@ -38,6 +39,11 @@ ) from ...prague.eip7702_set_code_tx.spec import Spec as Spec7702 +from .helpers import ( + AuthorizationAction, + authorization_transaction_cost, + build_authorization, +) from .spec import ref_spec_2780 REFERENCE_SPEC_GIT_PATH = ref_spec_2780.git_path @@ -162,6 +168,49 @@ def test_intrinsic_charges_recipient_is_coinbase( state_test(pre=pre, tx=tx, post=post) +def test_intrinsic_charges_authority_in_access_list( + fork: Fork, + pre: Alloc, + state_test: StateTestFiller, +) -> None: + """ + Authority is listed in the access list. The intrinsic charge still + includes the full ``EXECUTION_PER_AUTH_BASE_COST``, whose folded-in + authority access is charged at the cold rate. + """ + sender = pre.fund_eoa() + recipient = pre.deploy_contract(code=Op.STOP) + + scenario = build_authorization( + pre, AuthorizationAction.SETS_NEW_DELEGATION + ) + authorization_list = [scenario.authorization] + access_list = [AccessList(address=scenario.authority, storage_keys=[])] + + total_gas_cost = authorization_transaction_cost( + fork, authorization_list, access_list=access_list + ) + + tx = Transaction( + ty=4, + sender=sender, + to=recipient, + value=0, + access_list=access_list, + authorization_list=authorization_list, + gas_limit=total_gas_cost, + expected_receipt=TransactionReceipt( + cumulative_gas_used=total_gas_cost, + ), + ) + + post = { + scenario.authority: scenario.applied_account, + } + + state_test(pre=pre, tx=tx, post=post) + + @pytest.mark.parametrize("outcome", ["oog", "success"]) @pytest.mark.parametrize( "value", From f1aa97022a123d4a8a69039432404c3eaf72081d Mon Sep 17 00:00:00 2001 From: mandeep <30563736+mandeepmourya007@users.noreply.github.com> Date: Wed, 5 Aug 2026 17:42:11 +0530 Subject: [PATCH 203/233] feat(fork-types): add ExecutionGas and StateGas NewType wrappers to amsterdam EVM (#3121) Co-authored-by: Guruprasad Kamath <48196632+gurukamath@users.noreply.github.com> --- src/ethereum/forks/amsterdam/fork.py | 11 +- src/ethereum/forks/amsterdam/vm/__init__.py | 19 +- .../forks/amsterdam/vm/eoa_delegation.py | 10 +- src/ethereum/forks/amsterdam/vm/gas.py | 404 ++++++++++-------- .../amsterdam/vm/instructions/arithmetic.py | 7 +- .../amsterdam/vm/instructions/environment.py | 11 +- .../forks/amsterdam/vm/instructions/keccak.py | 3 +- .../forks/amsterdam/vm/instructions/log.py | 11 +- .../forks/amsterdam/vm/instructions/memory.py | 3 +- .../amsterdam/vm/instructions/storage.py | 8 +- .../forks/amsterdam/vm/instructions/system.py | 42 +- .../forks/amsterdam/vm/interpreter.py | 7 +- .../vm/precompiled_contracts/alt_bn128.py | 7 +- .../bls12_381/bls12_381_g1.py | 5 +- .../bls12_381/bls12_381_g2.py | 5 +- .../bls12_381/bls12_381_pairing.py | 3 +- .../vm/precompiled_contracts/identity.py | 7 +- .../vm/precompiled_contracts/modexp.py | 7 +- .../vm/precompiled_contracts/ripemd160.py | 7 +- .../vm/precompiled_contracts/sha256.py | 7 +- 20 files changed, 328 insertions(+), 256 deletions(-) diff --git a/src/ethereum/forks/amsterdam/fork.py b/src/ethereum/forks/amsterdam/fork.py index b893fbd20d4..6d536a0efe4 100644 --- a/src/ethereum/forks/amsterdam/fork.py +++ b/src/ethereum/forks/amsterdam/fork.py @@ -41,7 +41,12 @@ from .blocks import Block, Header, Log, Receipt, Withdrawal, encode_receipt from .bloom import logs_bloom from .exceptions import WrongChainIdError -from .fork_types import Authorization, BlockAccessIndex +from .fork_types import ( + Authorization, + BlockAccessIndex, + ExecutionGas, + StateGas, +) from .requests import ( BUILDER_DEPOSIT_REQUEST_TYPE, BUILDER_EXIT_REQUEST_TYPE, @@ -106,7 +111,7 @@ BEACON_ROOTS_ADDRESS = hex_to_address( "0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02" ) -SYSTEM_TRANSACTION_GAS = Uint(30000000) +SYSTEM_TRANSACTION_GAS = ExecutionGas(Uint(30000000)) SYSTEM_MAX_SSTORES_PER_CALL = Uint(16) """ Upper bound on the number of new storage slots a single system call is @@ -757,7 +762,7 @@ def process_unchecked_system_transaction( gas_limit=SYSTEM_TRANSACTION_GAS, effective_gas_price=block_env.base_fee_per_gas, execution_gas_grant=SYSTEM_TRANSACTION_GAS, - state_gas_reservoir=( + state_gas_reservoir=StateGas( StateGasCosts.STORAGE_SET * SYSTEM_MAX_SSTORES_PER_CALL ), calldata_floor=Uint(0), diff --git a/src/ethereum/forks/amsterdam/vm/__init__.py b/src/ethereum/forks/amsterdam/vm/__init__.py index 4ebc1ba2640..3125187719d 100644 --- a/src/ethereum/forks/amsterdam/vm/__init__.py +++ b/src/ethereum/forks/amsterdam/vm/__init__.py @@ -26,7 +26,12 @@ from ..block_access_lists import BlockAccessList, BlockAccessListBuilder from ..blocks import Log, Receipt, Withdrawal -from ..fork_types import Authorization, VersionedHash +from ..fork_types import ( + Authorization, + ExecutionGas, + StateGas, + VersionedHash, +) from ..state_tracker import BlockState, TransactionState from ..transactions import LegacyTransaction from .gas import GasMeter @@ -69,10 +74,10 @@ class BlockOutput: Contains the following: - block_gas_used : `ethereum.base_types.Uint` + block_gas_used : `ExecutionGas` Execution gas used for executing all transactions. EIP-8037 names this counter `block_execution_gas_used`. - block_state_gas_used : `ethereum.base_types.Uint` + block_state_gas_used : `StateGas` State gas used for executing all transactions. cumulative_gas_used : `ethereum.base_types.Uint` Cumulative gas paid by users (post-refund, post-floor). @@ -95,8 +100,8 @@ class BlockOutput: The block access list for the block. """ - block_gas_used: Uint = Uint(0) - block_state_gas_used: Uint = Uint(0) + block_gas_used: ExecutionGas = ExecutionGas(Uint(0)) + block_state_gas_used: StateGas = StateGas(Uint(0)) cumulative_gas_used: Uint = Uint(0) transactions_trie: Trie[Bytes, Optional[Bytes | LegacyTransaction]] = ( field(default_factory=lambda: Trie(secured=False, default=None)) @@ -129,8 +134,8 @@ class TransactionEnvironment: value: U256 gas_limit: Uint effective_gas_price: Uint - execution_gas_grant: Uint - state_gas_reservoir: Uint + execution_gas_grant: ExecutionGas + state_gas_reservoir: StateGas calldata_floor: Uint access_list_addresses: Set[Address] access_list_storage_keys: Set[Tuple[Address, Bytes32]] diff --git a/src/ethereum/forks/amsterdam/vm/eoa_delegation.py b/src/ethereum/forks/amsterdam/vm/eoa_delegation.py index b25f4e9fcfb..8663ca85cb7 100644 --- a/src/ethereum/forks/amsterdam/vm/eoa_delegation.py +++ b/src/ethereum/forks/amsterdam/vm/eoa_delegation.py @@ -5,14 +5,14 @@ from typing import Optional, Set, Tuple from ethereum_rlp import rlp -from ethereum_types.numeric import U64, U256, Uint +from ethereum_types.numeric import U64, U256 from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import keccak256 from ethereum.exceptions import InvalidSignatureError from ethereum.state import Address -from ..fork_types import Authorization +from ..fork_types import Authorization, ExecutionGas from ..state_tracker import ( TransactionState, account_exists, @@ -154,7 +154,7 @@ def recover_authority(authorization: Authorization) -> Address: def calculate_delegation_cost( evm: Evm, address: Address -) -> Tuple[bool, Address, Uint]: +) -> Tuple[bool, Address, ExecutionGas]: """ Get the delegation address and the cost of access from the address. @@ -167,7 +167,7 @@ def calculate_delegation_cost( Returns ------- - delegation : `Tuple[bool, Address, Uint]` + delegation : `Tuple[bool, Address, ExecutionGas]` The delegation address and access gas cost. """ @@ -176,7 +176,7 @@ def calculate_delegation_cost( code = get_code(tx_state, get_account(tx_state, address).code_hash) if not is_valid_delegation(code): - return False, address, Uint(0) + return False, address, GasCosts.ZERO delegated_address = Address(code[EOA_DELEGATION_MARKER_LENGTH:]) diff --git a/src/ethereum/forks/amsterdam/vm/gas.py b/src/ethereum/forks/amsterdam/vm/gas.py index d6b6aa4d755..25aac27d6b8 100644 --- a/src/ethereum/forks/amsterdam/vm/gas.py +++ b/src/ethereum/forks/amsterdam/vm/gas.py @@ -26,7 +26,12 @@ BlobGasLimitExceededError, InsufficientMaxFeePerBlobGasError, ) -from ..fork_types import StateGas, StateGasPerByte, VersionedHash +from ..fork_types import ( + ExecutionGas, + StateGas, + StateGasPerByte, + VersionedHash, +) from ..transactions import ( TX_MAX_GAS_LIMIT, BlobTransaction, @@ -71,34 +76,35 @@ class GasCosts: """ # Tiers - BASE: Final[Uint] = Uint(2) - VERY_LOW: Final[Uint] = Uint(3) - LOW: Final[Uint] = Uint(5) - MID: Final[Uint] = Uint(8) - HIGH: Final[Uint] = Uint(10) + BASE: Final[ExecutionGas] = ExecutionGas(Uint(2)) + VERY_LOW: Final[ExecutionGas] = ExecutionGas(Uint(3)) + LOW: Final[ExecutionGas] = ExecutionGas(Uint(5)) + MID: Final[ExecutionGas] = ExecutionGas(Uint(8)) + HIGH: Final[ExecutionGas] = ExecutionGas(Uint(10)) # Access - WARM_ACCESS: Final[Uint] = Uint(100) - COLD_ACCOUNT_ACCESS: Final[Uint] = Uint(3000) - COLD_STORAGE_ACCESS: Final[Uint] = Uint(2100) + WARM_ACCESS: Final[ExecutionGas] = ExecutionGas(Uint(100)) + COLD_ACCOUNT_ACCESS: Final[ExecutionGas] = ExecutionGas(Uint(3000)) + COLD_STORAGE_ACCESS: Final[ExecutionGas] = ExecutionGas(Uint(2100)) # Storage - STORAGE_WRITE: Final[Uint] = Uint(10000) + STORAGE_WRITE: Final[ExecutionGas] = ExecutionGas(Uint(10000)) # Call - CALL_VALUE: Final[Uint] = Uint(11300) # ACCOUNT_WRITE + CALL_STIPEND - CALL_STIPEND: Final[Uint] = Uint(2300) - ACCOUNT_WRITE: Final[Uint] = Uint(9000) + # ACCOUNT_WRITE + CALL_STIPEND + CALL_VALUE: Final[ExecutionGas] = ExecutionGas(Uint(11300)) + CALL_STIPEND: Final[ExecutionGas] = ExecutionGas(Uint(2300)) + ACCOUNT_WRITE: Final[ExecutionGas] = ExecutionGas(Uint(9000)) # Contract Creation - CODE_DEPOSIT_PER_BYTE: Final[Uint] = Uint(200) - CODE_INIT_PER_WORD: Final[Uint] = Uint(2) - CREATE_ACCESS: Final[Uint] = ACCOUNT_WRITE + COLD_ACCOUNT_ACCESS + CODE_DEPOSIT_PER_BYTE: Final[ExecutionGas] = ExecutionGas(Uint(200)) + CODE_INIT_PER_WORD: Final[ExecutionGas] = ExecutionGas(Uint(2)) + CREATE_ACCESS: Final[ExecutionGas] = ACCOUNT_WRITE + COLD_ACCOUNT_ACCESS # Utility - ZERO: Final[Uint] = Uint(0) - MEMORY_PER_WORD: Final[Uint] = Uint(3) - FAST_STEP: Final[Uint] = Uint(5) + ZERO: Final[ExecutionGas] = ExecutionGas(Uint(0)) + MEMORY_PER_WORD: Final[ExecutionGas] = ExecutionGas(Uint(3)) + FAST_STEP: Final[ExecutionGas] = ExecutionGas(Uint(5)) # Refunds REFUND_STORAGE_CLEAR: Final[int] = int( @@ -106,26 +112,32 @@ class GasCosts: ) # Precompiles - PRECOMPILE_ECRECOVER: Final[Uint] = Uint(3000) - PRECOMPILE_P256VERIFY: Final[Uint] = Uint(6900) - PRECOMPILE_SHA256_BASE: Final[Uint] = Uint(60) - PRECOMPILE_SHA256_PER_WORD: Final[Uint] = Uint(12) - PRECOMPILE_RIPEMD160_BASE: Final[Uint] = Uint(600) - PRECOMPILE_RIPEMD160_PER_WORD: Final[Uint] = Uint(120) - PRECOMPILE_IDENTITY_BASE: Final[Uint] = Uint(15) - PRECOMPILE_IDENTITY_PER_WORD: Final[Uint] = Uint(3) - PRECOMPILE_BLAKE2F_PER_ROUND: Final[Uint] = Uint(1) - PRECOMPILE_POINT_EVALUATION: Final[Uint] = Uint(50000) - PRECOMPILE_BLS_G1ADD: Final[Uint] = Uint(375) - PRECOMPILE_BLS_G1MUL: Final[Uint] = Uint(12000) - PRECOMPILE_BLS_G1MAP: Final[Uint] = Uint(5500) - PRECOMPILE_BLS_G2ADD: Final[Uint] = Uint(600) - PRECOMPILE_BLS_G2MUL: Final[Uint] = Uint(22500) - PRECOMPILE_BLS_G2MAP: Final[Uint] = Uint(23800) - PRECOMPILE_ECADD: Final[Uint] = Uint(150) - PRECOMPILE_ECMUL: Final[Uint] = Uint(6000) - PRECOMPILE_ECPAIRING_BASE: Final[Uint] = Uint(45000) - PRECOMPILE_ECPAIRING_PER_POINT: Final[Uint] = Uint(34000) + PRECOMPILE_ECRECOVER: Final[ExecutionGas] = ExecutionGas(Uint(3000)) + PRECOMPILE_P256VERIFY: Final[ExecutionGas] = ExecutionGas(Uint(6900)) + PRECOMPILE_SHA256_BASE: Final[ExecutionGas] = ExecutionGas(Uint(60)) + PRECOMPILE_SHA256_PER_WORD: Final[ExecutionGas] = ExecutionGas(Uint(12)) + PRECOMPILE_RIPEMD160_BASE: Final[ExecutionGas] = ExecutionGas(Uint(600)) + PRECOMPILE_RIPEMD160_PER_WORD: Final[ExecutionGas] = ExecutionGas( + Uint(120) + ) + PRECOMPILE_IDENTITY_BASE: Final[ExecutionGas] = ExecutionGas(Uint(15)) + PRECOMPILE_IDENTITY_PER_WORD: Final[ExecutionGas] = ExecutionGas(Uint(3)) + PRECOMPILE_BLAKE2F_PER_ROUND: Final[ExecutionGas] = ExecutionGas(Uint(1)) + PRECOMPILE_POINT_EVALUATION: Final[ExecutionGas] = ExecutionGas( + Uint(50000) + ) + PRECOMPILE_BLS_G1ADD: Final[ExecutionGas] = ExecutionGas(Uint(375)) + PRECOMPILE_BLS_G1MUL: Final[ExecutionGas] = ExecutionGas(Uint(12000)) + PRECOMPILE_BLS_G1MAP: Final[ExecutionGas] = ExecutionGas(Uint(5500)) + PRECOMPILE_BLS_G2ADD: Final[ExecutionGas] = ExecutionGas(Uint(600)) + PRECOMPILE_BLS_G2MUL: Final[ExecutionGas] = ExecutionGas(Uint(22500)) + PRECOMPILE_BLS_G2MAP: Final[ExecutionGas] = ExecutionGas(Uint(23800)) + PRECOMPILE_ECADD: Final[ExecutionGas] = ExecutionGas(Uint(150)) + PRECOMPILE_ECMUL: Final[ExecutionGas] = ExecutionGas(Uint(6000)) + PRECOMPILE_ECPAIRING_BASE: Final[ExecutionGas] = ExecutionGas(Uint(45000)) + PRECOMPILE_ECPAIRING_PER_POINT: Final[ExecutionGas] = ExecutionGas( + Uint(34000) + ) # Blobs PER_BLOB: Final[U64] = U64(2**17) @@ -137,20 +149,24 @@ class GasCosts: BLOB_BASE_FEE_UPDATE_FRACTION: Final[Uint] = Uint(11684671) # Block Access Lists - BLOCK_ACCESS_LIST_ITEM: Final[Uint] = Uint(2000) + BLOCK_ACCESS_LIST_ITEM: Final[ExecutionGas] = ExecutionGas(Uint(2000)) # Transactions - TX_BASE: Final[Uint] = Uint(12000) - TX_CREATE: Final[Uint] = Uint(32000) - TX_VALUE_COST: Final[Uint] = Uint(6000) - TX_DATA_TOKEN_STANDARD: Final[Uint] = Uint(4) - TX_DATA_TOKEN_FLOOR: Final[Uint] = Uint(16) - TX_ACCESS_LIST_ADDRESS: Final[Uint] = COLD_ACCOUNT_ACCESS - WARM_ACCESS - TX_ACCESS_LIST_STORAGE_KEY: Final[Uint] = COLD_STORAGE_ACCESS - WARM_ACCESS + TX_BASE: Final[ExecutionGas] = ExecutionGas(Uint(12000)) + TX_CREATE: Final[ExecutionGas] = ExecutionGas(Uint(32000)) + TX_VALUE_COST: Final[ExecutionGas] = ExecutionGas(Uint(6000)) + TX_DATA_TOKEN_STANDARD: Final[ExecutionGas] = ExecutionGas(Uint(4)) + TX_DATA_TOKEN_FLOOR: Final[ExecutionGas] = ExecutionGas(Uint(16)) + TX_ACCESS_LIST_ADDRESS: Final[ExecutionGas] = ( + COLD_ACCOUNT_ACCESS - WARM_ACCESS + ) + TX_ACCESS_LIST_STORAGE_KEY: Final[ExecutionGas] = ( + COLD_STORAGE_ACCESS - WARM_ACCESS + ) # Authorization AUTH_TUPLE_BYTES: Final[Uint] = Uint(101) - EXECUTION_PER_AUTH_BASE_COST: Final[Uint] = ( + EXECUTION_PER_AUTH_BASE_COST: Final[ExecutionGas] = ExecutionGas( AUTH_TUPLE_BYTES * TX_DATA_TOKEN_FLOOR + PRECOMPILE_ECRECOVER + COLD_ACCOUNT_ACCESS @@ -162,86 +178,86 @@ class GasCosts: LIMIT_MINIMUM: Final[Uint] = Uint(5000) # Static Opcodes - OPCODE_ADD: Final[Uint] = VERY_LOW - OPCODE_SUB: Final[Uint] = VERY_LOW - OPCODE_MUL: Final[Uint] = LOW - OPCODE_DIV: Final[Uint] = LOW - OPCODE_SDIV: Final[Uint] = LOW - OPCODE_MOD: Final[Uint] = LOW - OPCODE_SMOD: Final[Uint] = LOW - OPCODE_ADDMOD: Final[Uint] = MID - OPCODE_MULMOD: Final[Uint] = MID - OPCODE_SIGNEXTEND: Final[Uint] = LOW - OPCODE_LT: Final[Uint] = VERY_LOW - OPCODE_GT: Final[Uint] = VERY_LOW - OPCODE_SLT: Final[Uint] = VERY_LOW - OPCODE_SGT: Final[Uint] = VERY_LOW - OPCODE_EQ: Final[Uint] = VERY_LOW - OPCODE_ISZERO: Final[Uint] = VERY_LOW - OPCODE_AND: Final[Uint] = VERY_LOW - OPCODE_OR: Final[Uint] = VERY_LOW - OPCODE_XOR: Final[Uint] = VERY_LOW - OPCODE_NOT: Final[Uint] = VERY_LOW - OPCODE_BYTE: Final[Uint] = VERY_LOW - OPCODE_SHL: Final[Uint] = VERY_LOW - OPCODE_SHR: Final[Uint] = VERY_LOW - OPCODE_SAR: Final[Uint] = VERY_LOW - OPCODE_CLZ: Final[Uint] = LOW - OPCODE_JUMP: Final[Uint] = MID - OPCODE_JUMPI: Final[Uint] = HIGH - OPCODE_JUMPDEST: Final[Uint] = Uint(1) - OPCODE_CALLDATALOAD: Final[Uint] = VERY_LOW - OPCODE_BLOCKHASH: Final[Uint] = Uint(20) - OPCODE_COINBASE: Final[Uint] = BASE - OPCODE_POP: Final[Uint] = BASE - OPCODE_MSIZE: Final[Uint] = BASE - OPCODE_PC: Final[Uint] = BASE - OPCODE_GAS: Final[Uint] = BASE - OPCODE_ADDRESS: Final[Uint] = BASE - OPCODE_ORIGIN: Final[Uint] = BASE - OPCODE_CALLER: Final[Uint] = BASE - OPCODE_CALLVALUE: Final[Uint] = BASE - OPCODE_CALLDATASIZE: Final[Uint] = BASE - OPCODE_CODESIZE: Final[Uint] = BASE - OPCODE_GASPRICE: Final[Uint] = BASE - OPCODE_TIMESTAMP: Final[Uint] = BASE - OPCODE_NUMBER: Final[Uint] = BASE - OPCODE_GASLIMIT: Final[Uint] = BASE - OPCODE_PREVRANDAO: Final[Uint] = BASE - OPCODE_RETURNDATASIZE: Final[Uint] = BASE - OPCODE_CHAINID: Final[Uint] = BASE - OPCODE_BASEFEE: Final[Uint] = BASE - OPCODE_BLOBBASEFEE: Final[Uint] = BASE - OPCODE_SLOTNUM: Final[Uint] = BASE - OPCODE_BLOBHASH: Final[Uint] = Uint(3) - OPCODE_PUSH: Final[Uint] = VERY_LOW - OPCODE_PUSH0: Final[Uint] = BASE - OPCODE_DUP: Final[Uint] = VERY_LOW - OPCODE_SWAP: Final[Uint] = VERY_LOW - OPCODE_DUPN: Final[Uint] = VERY_LOW - OPCODE_SWAPN: Final[Uint] = VERY_LOW - OPCODE_EXCHANGE: Final[Uint] = VERY_LOW - OPCODE_TLOAD: Final[Uint] = Uint(100) - OPCODE_TSTORE: Final[Uint] = Uint(100) + OPCODE_ADD: Final[ExecutionGas] = VERY_LOW + OPCODE_SUB: Final[ExecutionGas] = VERY_LOW + OPCODE_MUL: Final[ExecutionGas] = LOW + OPCODE_DIV: Final[ExecutionGas] = LOW + OPCODE_SDIV: Final[ExecutionGas] = LOW + OPCODE_MOD: Final[ExecutionGas] = LOW + OPCODE_SMOD: Final[ExecutionGas] = LOW + OPCODE_ADDMOD: Final[ExecutionGas] = MID + OPCODE_MULMOD: Final[ExecutionGas] = MID + OPCODE_SIGNEXTEND: Final[ExecutionGas] = LOW + OPCODE_LT: Final[ExecutionGas] = VERY_LOW + OPCODE_GT: Final[ExecutionGas] = VERY_LOW + OPCODE_SLT: Final[ExecutionGas] = VERY_LOW + OPCODE_SGT: Final[ExecutionGas] = VERY_LOW + OPCODE_EQ: Final[ExecutionGas] = VERY_LOW + OPCODE_ISZERO: Final[ExecutionGas] = VERY_LOW + OPCODE_AND: Final[ExecutionGas] = VERY_LOW + OPCODE_OR: Final[ExecutionGas] = VERY_LOW + OPCODE_XOR: Final[ExecutionGas] = VERY_LOW + OPCODE_NOT: Final[ExecutionGas] = VERY_LOW + OPCODE_BYTE: Final[ExecutionGas] = VERY_LOW + OPCODE_SHL: Final[ExecutionGas] = VERY_LOW + OPCODE_SHR: Final[ExecutionGas] = VERY_LOW + OPCODE_SAR: Final[ExecutionGas] = VERY_LOW + OPCODE_CLZ: Final[ExecutionGas] = LOW + OPCODE_JUMP: Final[ExecutionGas] = MID + OPCODE_JUMPI: Final[ExecutionGas] = HIGH + OPCODE_JUMPDEST: Final[ExecutionGas] = ExecutionGas(Uint(1)) + OPCODE_CALLDATALOAD: Final[ExecutionGas] = VERY_LOW + OPCODE_BLOCKHASH: Final[ExecutionGas] = ExecutionGas(Uint(20)) + OPCODE_COINBASE: Final[ExecutionGas] = BASE + OPCODE_POP: Final[ExecutionGas] = BASE + OPCODE_MSIZE: Final[ExecutionGas] = BASE + OPCODE_PC: Final[ExecutionGas] = BASE + OPCODE_GAS: Final[ExecutionGas] = BASE + OPCODE_ADDRESS: Final[ExecutionGas] = BASE + OPCODE_ORIGIN: Final[ExecutionGas] = BASE + OPCODE_CALLER: Final[ExecutionGas] = BASE + OPCODE_CALLVALUE: Final[ExecutionGas] = BASE + OPCODE_CALLDATASIZE: Final[ExecutionGas] = BASE + OPCODE_CODESIZE: Final[ExecutionGas] = BASE + OPCODE_GASPRICE: Final[ExecutionGas] = BASE + OPCODE_TIMESTAMP: Final[ExecutionGas] = BASE + OPCODE_NUMBER: Final[ExecutionGas] = BASE + OPCODE_GASLIMIT: Final[ExecutionGas] = BASE + OPCODE_PREVRANDAO: Final[ExecutionGas] = BASE + OPCODE_RETURNDATASIZE: Final[ExecutionGas] = BASE + OPCODE_CHAINID: Final[ExecutionGas] = BASE + OPCODE_BASEFEE: Final[ExecutionGas] = BASE + OPCODE_BLOBBASEFEE: Final[ExecutionGas] = BASE + OPCODE_SLOTNUM: Final[ExecutionGas] = BASE + OPCODE_BLOBHASH: Final[ExecutionGas] = ExecutionGas(Uint(3)) + OPCODE_PUSH: Final[ExecutionGas] = VERY_LOW + OPCODE_PUSH0: Final[ExecutionGas] = BASE + OPCODE_DUP: Final[ExecutionGas] = VERY_LOW + OPCODE_SWAP: Final[ExecutionGas] = VERY_LOW + OPCODE_DUPN: Final[ExecutionGas] = VERY_LOW + OPCODE_SWAPN: Final[ExecutionGas] = VERY_LOW + OPCODE_EXCHANGE: Final[ExecutionGas] = VERY_LOW + OPCODE_TLOAD: Final[ExecutionGas] = ExecutionGas(Uint(100)) + OPCODE_TSTORE: Final[ExecutionGas] = ExecutionGas(Uint(100)) # Dynamic Opcode Components - OPCODE_RETURNDATACOPY_BASE: Final[Uint] = VERY_LOW - OPCODE_RETURNDATACOPY_PER_WORD: Final[Uint] = Uint(3) - OPCODE_CALLDATACOPY_BASE: Final[Uint] = VERY_LOW - OPCODE_CODECOPY_BASE: Final[Uint] = VERY_LOW - OPCODE_MCOPY_BASE: Final[Uint] = VERY_LOW - OPCODE_MLOAD_BASE: Final[Uint] = VERY_LOW - OPCODE_MSTORE_BASE: Final[Uint] = VERY_LOW - OPCODE_MSTORE8_BASE: Final[Uint] = VERY_LOW - OPCODE_COPY_PER_WORD: Final[Uint] = Uint(3) - OPCODE_EXP_BASE: Final[Uint] = Uint(10) - OPCODE_EXP_PER_BYTE: Final[Uint] = Uint(50) - OPCODE_KECCAK256_BASE: Final[Uint] = Uint(30) - OPCODE_KECCAK256_PER_WORD: Final[Uint] = Uint(6) - OPCODE_LOG_BASE: Final[Uint] = Uint(375) - OPCODE_LOG_DATA_PER_BYTE: Final[Uint] = Uint(8) - OPCODE_LOG_TOPIC: Final[Uint] = Uint(375) - OPCODE_SELFDESTRUCT_BASE: Final[Uint] = Uint(5000) + OPCODE_RETURNDATACOPY_BASE: Final[ExecutionGas] = VERY_LOW + OPCODE_RETURNDATACOPY_PER_WORD: Final[ExecutionGas] = ExecutionGas(Uint(3)) + OPCODE_CALLDATACOPY_BASE: Final[ExecutionGas] = VERY_LOW + OPCODE_CODECOPY_BASE: Final[ExecutionGas] = VERY_LOW + OPCODE_MCOPY_BASE: Final[ExecutionGas] = VERY_LOW + OPCODE_MLOAD_BASE: Final[ExecutionGas] = VERY_LOW + OPCODE_MSTORE_BASE: Final[ExecutionGas] = VERY_LOW + OPCODE_MSTORE8_BASE: Final[ExecutionGas] = VERY_LOW + OPCODE_COPY_PER_WORD: Final[ExecutionGas] = ExecutionGas(Uint(3)) + OPCODE_EXP_BASE: Final[ExecutionGas] = ExecutionGas(Uint(10)) + OPCODE_EXP_PER_BYTE: Final[ExecutionGas] = ExecutionGas(Uint(50)) + OPCODE_KECCAK256_BASE: Final[ExecutionGas] = ExecutionGas(Uint(30)) + OPCODE_KECCAK256_PER_WORD: Final[ExecutionGas] = ExecutionGas(Uint(6)) + OPCODE_LOG_BASE: Final[ExecutionGas] = ExecutionGas(Uint(375)) + OPCODE_LOG_DATA_PER_BYTE: Final[ExecutionGas] = ExecutionGas(Uint(8)) + OPCODE_LOG_TOPIC: Final[ExecutionGas] = ExecutionGas(Uint(375)) + OPCODE_SELFDESTRUCT_BASE: Final[ExecutionGas] = ExecutionGas(Uint(5000)) MAX_BLOB_GAS_PER_BLOCK: Final[U64] = ( @@ -262,7 +278,7 @@ class GasMeter: [`Evm`]: ref:ethereum.forks.amsterdam.vm.Evm """ - gas_left: Uint + gas_left: ExecutionGas """ Gas still available from the frame's execution-gas grant. Pays execution-gas charges, and state charges as [spill] once the @@ -271,13 +287,13 @@ class GasMeter: [spill]: ref:ethereum.forks.amsterdam.vm.gas.GasMeter.state_gas_spilled """ - state_gas_left: Uint + state_gas_left: StateGas """ State gas still available in the frame's reservoir. Charges draw from here first and spill into `gas_left` once it is empty. """ - state_gas_baseline: Uint + state_gas_baseline: StateGas """ Reservoir level a rollback refills to: the frame's grant at entry, moved down by [`commit_state_gas`][commit] when charges become @@ -289,7 +305,7 @@ class GasMeter: refund_counter: int = 0 """Gas eligible for refund at the end of the transaction.""" - state_gas_spilled: Uint = Uint(0) + state_gas_spilled: StateGas = StateGas(Uint(0)) """ Execution gas spent covering state charges after the reservoir emptied. Credited back to `gas_left` first, in LIFO order, on a @@ -299,7 +315,7 @@ class GasMeter: [EIP-8037]: https://eips.ethereum.org/EIPS/eip-8037 """ - state_gas_committed_spill: Uint = Uint(0) + state_gas_committed_spill: StateGas = StateGas(Uint(0)) """ [Spill] that [`commit_state_gas`][commit] marked non-refillable. It outlives the rollbacks [`restore_state_gas`][restore] performs; @@ -321,13 +337,13 @@ class ExtendMemory: """ Define the parameters for memory extension in opcodes. - `cost`: `ethereum.base_types.Uint` + `cost`: `ExecutionGas` The gas required to perform the extension `expand_by`: `ethereum.base_types.Uint` The size by which the memory will be extended """ - cost: Uint + cost: ExecutionGas expand_by: Uint @@ -338,19 +354,19 @@ class MessageCallGas: Define the gas cost and gas given to the sub-call for executing the call opcodes. - `cost`: `ethereum.base_types.Uint` + `cost`: `ExecutionGas` The gas required to execute the call opcode, excludes memory expansion costs. - `sub_call`: `ethereum.base_types.Uint` + `sub_call`: `ExecutionGas` The portion of gas available to sub-calls that is refundable if not consumed. """ - cost: Uint - sub_call: Uint + cost: ExecutionGas + sub_call: ExecutionGas -def check_gas(evm: "Evm", amount: Uint) -> None: +def check_gas(evm: "Evm", amount: ExecutionGas) -> None: """ Checks if `amount` gas is available without charging it. Raises OutOfGasError if insufficient gas. @@ -360,14 +376,14 @@ def check_gas(evm: "Evm", amount: Uint) -> None: evm : The current EVM. amount : - The amount of gas to check. + The amount of execution gas to check. """ if evm.gas_meter.gas_left < amount: raise OutOfGasError -def charge_gas_from_meter(gas_meter: GasMeter, amount: Uint) -> None: +def charge_gas_from_meter(gas_meter: GasMeter, amount: ExecutionGas) -> None: """ Subtracts `amount` from `gas_left` (execution gas). @@ -384,7 +400,7 @@ def charge_gas_from_meter(gas_meter: GasMeter, amount: Uint) -> None: gas_meter.gas_left -= amount -def charge_gas(evm: "Evm", amount: Uint) -> None: +def charge_gas(evm: "Evm", amount: ExecutionGas) -> None: """ Subtracts `amount` from `gas_left` (execution gas). @@ -418,10 +434,10 @@ def charge_state_gas_from_meter(gas_meter: GasMeter, amount: StateGas) -> None: """ if gas_meter.state_gas_left >= amount: gas_meter.state_gas_left -= amount - elif gas_meter.state_gas_left + gas_meter.gas_left >= amount: + elif Uint(gas_meter.state_gas_left) + Uint(gas_meter.gas_left) >= amount: remainder = amount - gas_meter.state_gas_left - gas_meter.state_gas_left = Uint(0) - gas_meter.gas_left -= remainder + gas_meter.state_gas_left = StateGas(Uint(0)) + gas_meter.gas_left = ExecutionGas(gas_meter.gas_left - Uint(remainder)) gas_meter.state_gas_spilled += remainder else: raise OutOfGasError @@ -478,7 +494,7 @@ def commit_state_gas(gas_meter: GasMeter) -> None: assert gas_meter.state_gas_left <= gas_meter.state_gas_baseline gas_meter.state_gas_committed_spill += gas_meter.state_gas_spilled gas_meter.state_gas_baseline = gas_meter.state_gas_left - gas_meter.state_gas_spilled = Uint(0) + gas_meter.state_gas_spilled = StateGas(Uint(0)) def restore_state_gas(gas_meter: GasMeter) -> None: @@ -500,14 +516,16 @@ def restore_state_gas(gas_meter: GasMeter) -> None: [spill]: ref:ethereum.forks.amsterdam.vm.gas.GasMeter.state_gas_spilled """ # noqa: E501 - gas_meter.gas_left += gas_meter.state_gas_spilled - gas_meter.state_gas_spilled = Uint(0) + gas_meter.gas_left = ExecutionGas( + gas_meter.gas_left + Uint(gas_meter.state_gas_spilled) + ) + gas_meter.state_gas_spilled = StateGas(Uint(0)) gas_meter.state_gas_left = gas_meter.state_gas_baseline gas_meter.refund_counter = 0 def restore_state_gas_to_entry( - gas_meter: GasMeter, state_gas_reservoir: Uint + gas_meter: GasMeter, state_gas_reservoir: StateGas ) -> None: """ Roll the frame's state gas back to frame entry, undoing any commit. @@ -533,16 +551,20 @@ def restore_state_gas_to_entry( # Only pre-dispatch failures roll back to entry, and no refund # accrues before dispatch. assert gas_meter.refund_counter == 0 - gas_meter.gas_left += ( - gas_meter.state_gas_spilled + gas_meter.state_gas_committed_spill + gas_meter.gas_left = ExecutionGas( + gas_meter.gas_left + + Uint(gas_meter.state_gas_spilled) + + Uint(gas_meter.state_gas_committed_spill) ) - gas_meter.state_gas_spilled = Uint(0) - gas_meter.state_gas_committed_spill = Uint(0) + gas_meter.state_gas_spilled = StateGas(Uint(0)) + gas_meter.state_gas_committed_spill = StateGas(Uint(0)) gas_meter.state_gas_left = state_gas_reservoir gas_meter.state_gas_baseline = state_gas_reservoir -def tx_state_gas_used(gas_meter: GasMeter, state_gas_reservoir: Uint) -> int: +def tx_state_gas_used( + gas_meter: GasMeter, state_gas_reservoir: StateGas +) -> int: """ Return the net state gas a transaction's execution consumed. @@ -596,7 +618,7 @@ def credit_state_gas_refund(gas_meter: GasMeter, amount: StateGas) -> None: """ from_gas_left = min(amount, gas_meter.state_gas_spilled) - gas_meter.gas_left += from_gas_left + gas_meter.gas_left = ExecutionGas(gas_meter.gas_left + Uint(from_gas_left)) gas_meter.state_gas_spilled -= from_gas_left gas_meter.state_gas_left += amount - from_gas_left @@ -614,10 +636,10 @@ def forfeit_remaining_gas(gas_meter: GasMeter) -> None: # A rollback owes any outstanding spill back to `gas_left`; it # must be restored before the remainder burns. assert gas_meter.state_gas_spilled == Uint(0) - gas_meter.gas_left = Uint(0) + gas_meter.gas_left = ExecutionGas(Uint(0)) -def withhold_create_gas(gas_meter: GasMeter) -> Uint: +def withhold_create_gas(gas_meter: GasMeter) -> ExecutionGas: """ Withhold and return the gas made available to a `CREATE*` child. @@ -631,7 +653,7 @@ def withhold_create_gas(gas_meter: GasMeter) -> Uint: Returns ------- - child_gas : `ethereum.base_types.Uint` + child_gas : `ExecutionGas` The execution gas granted to the child frame. """ @@ -640,7 +662,7 @@ def withhold_create_gas(gas_meter: GasMeter) -> Uint: return child_gas -def drain_state_gas_reservoir(gas_meter: GasMeter) -> Uint: +def drain_state_gas_reservoir(gas_meter: GasMeter) -> StateGas: """ Empty the frame's state gas reservoir for a child frame. @@ -655,17 +677,17 @@ def drain_state_gas_reservoir(gas_meter: GasMeter) -> Uint: Returns ------- - reservoir : `ethereum.base_types.Uint` + reservoir : `StateGas` The state gas granted to the child frame. """ reservoir = gas_meter.state_gas_left - gas_meter.state_gas_left = Uint(0) + gas_meter.state_gas_left = StateGas(Uint(0)) return reservoir def restore_child_gas( - gas_meter: GasMeter, gas: Uint, state_gas_reservoir: Uint + gas_meter: GasMeter, gas: ExecutionGas, state_gas_reservoir: StateGas ) -> None: """ Return a child frame's unused gas grant to the parent. @@ -688,7 +710,7 @@ def restore_child_gas( gas_meter.state_gas_left += state_gas_reservoir -def calculate_memory_gas_cost(size_in_bytes: Uint) -> Uint: +def calculate_memory_gas_cost(size_in_bytes: Uint) -> ExecutionGas: """ Calculates the gas cost for allocating memory to the smallest multiple of 32 bytes, @@ -701,7 +723,7 @@ def calculate_memory_gas_cost(size_in_bytes: Uint) -> Uint: Returns ------- - total_gas_cost : `ethereum.base_types.Uint` + total_gas_cost : `ExecutionGas` The gas cost for storing data in memory. """ @@ -710,7 +732,7 @@ def calculate_memory_gas_cost(size_in_bytes: Uint) -> Uint: quadratic_cost = size_in_words ** Uint(2) // Uint(512) total_gas_cost = linear_cost + quadratic_cost try: - return total_gas_cost + return ExecutionGas(total_gas_cost) except ValueError as e: raise OutOfGasError from e @@ -735,7 +757,7 @@ def calculate_gas_extend_memory( """ size_to_extend = Uint(0) - to_be_paid = Uint(0) + to_be_paid = GasCosts.ZERO current_size = ulen(memory) for start_position, size in extensions: if size == 0: @@ -757,11 +779,11 @@ def calculate_gas_extend_memory( def calculate_message_call_gas( value: U256, - gas: Uint, - gas_left: Uint, - memory_cost: Uint, - extra_gas: Uint, - call_stipend: Uint = GasCosts.CALL_STIPEND, + gas: ExecutionGas, + gas_left: ExecutionGas, + memory_cost: ExecutionGas, + extra_gas: ExecutionGas, + call_stipend: ExecutionGas = GasCosts.CALL_STIPEND, ) -> MessageCallGas: """ Calculates the MessageCallGas (cost and gas made available to the sub-call) @@ -789,7 +811,7 @@ def calculate_message_call_gas( message_call_gas: `MessageCallGas` """ - call_stipend = Uint(0) if value == 0 else call_stipend + call_stipend = GasCosts.ZERO if value == 0 else call_stipend if gas_left < extra_gas + memory_cost: return MessageCallGas(gas + extra_gas, gas + call_stipend) @@ -798,7 +820,7 @@ def calculate_message_call_gas( return MessageCallGas(gas + extra_gas, gas + call_stipend) -def max_message_call_gas(gas: Uint) -> Uint: +def max_message_call_gas(gas: ExecutionGas) -> ExecutionGas: """ Calculates the maximum gas that is allowed for making a message call. @@ -809,14 +831,14 @@ def max_message_call_gas(gas: Uint) -> Uint: Returns ------- - max_allowed_message_call_gas: `ethereum.base_types.Uint` + max_allowed_message_call_gas: `ExecutionGas` The maximum gas allowed for making the message-call. """ - return gas - (gas // Uint(64)) + return ExecutionGas(gas - (gas // Uint(64))) -def init_code_cost(init_code_length: Uint) -> Uint: +def init_code_cost(init_code_length: Uint) -> ExecutionGas: """ Calculates the gas to be charged for the init code in CREATE* opcodes as well as create transactions. @@ -829,11 +851,13 @@ def init_code_cost(init_code_length: Uint) -> Uint: Returns ------- - init_code_gas: `ethereum.base_types.Uint` + init_code_gas: `ExecutionGas` The gas to be charged for the init code. """ - return GasCosts.CODE_INIT_PER_WORD * ceil32(init_code_length) // Uint(32) + return ExecutionGas( + GasCosts.CODE_INIT_PER_WORD * ceil32(init_code_length) // Uint(32) + ) def calculate_excess_blob_gas( @@ -1047,10 +1071,10 @@ class EvmGasAllocation: Split of a transaction's EVM gas across the two dimensions. """ - execution_gas: Uint + execution_gas: ExecutionGas """Execution gas granted to the top frame, capped by the budget.""" - state_gas_reservoir: Uint + state_gas_reservoir: StateGas """State gas set aside for the top frame's reservoir.""" @@ -1084,8 +1108,8 @@ def allocate_evm_gas( """ evm_gas = tx_gas - Uint(intrinsic.execution) execution_gas_budget = TX_MAX_GAS_LIMIT - intrinsic.execution - execution_gas = min(execution_gas_budget, evm_gas) - state_gas_reservoir = Uint(evm_gas - execution_gas) + execution_gas = ExecutionGas(min(execution_gas_budget, evm_gas)) + state_gas_reservoir = StateGas(evm_gas - execution_gas) return EvmGasAllocation(execution_gas, state_gas_reservoir) @@ -1105,18 +1129,18 @@ class TransactionGasSettlement: gas_left: Uint """Gas returned to the sender, priced at the effective gas price.""" - execution_gas_used: Uint + execution_gas_used: ExecutionGas """Execution gas the transaction contributes to the block total.""" - state_gas_used: Uint + state_gas_used: StateGas """State gas the transaction contributes to the block total.""" def settle_transaction_gas( tx_gas: Uint, calldata_floor: Uint, - gas_left: Uint, - state_gas_left: Uint, + gas_left: ExecutionGas, + state_gas_left: StateGas, refund_counter: U256, state_gas_used: int, ) -> TransactionGasSettlement: @@ -1165,10 +1189,12 @@ def settle_transaction_gas( gas_used_after_refund = gas_used_before_refund - gas_refund gas_used = max(gas_used_after_refund, calldata_floor) - settled_state_gas_used = Uint(max(0, state_gas_used)) - execution_gas_used = max( - gas_used_before_refund - settled_state_gas_used, - calldata_floor, + settled_state_gas_used = StateGas(Uint(max(0, state_gas_used))) + execution_gas_used = ExecutionGas( + max( + gas_used_before_refund - settled_state_gas_used, + calldata_floor, + ) ) return TransactionGasSettlement( gas_used=gas_used, diff --git a/src/ethereum/forks/amsterdam/vm/instructions/arithmetic.py b/src/ethereum/forks/amsterdam/vm/instructions/arithmetic.py index 4c7423cba8e..62d825e3740 100644 --- a/src/ethereum/forks/amsterdam/vm/instructions/arithmetic.py +++ b/src/ethereum/forks/amsterdam/vm/instructions/arithmetic.py @@ -16,6 +16,7 @@ from ethereum.utils.numeric import get_sign +from ...fork_types import ExecutionGas from .. import Evm from ..gas import ( GasCosts, @@ -315,8 +316,10 @@ def exp(evm: Evm) -> None: exponent_bytes = (exponent_bits + Uint(7)) // Uint(8) charge_gas( evm, - GasCosts.OPCODE_EXP_BASE - + GasCosts.OPCODE_EXP_PER_BYTE * exponent_bytes, + ExecutionGas( + GasCosts.OPCODE_EXP_BASE + + GasCosts.OPCODE_EXP_PER_BYTE * exponent_bytes + ), ) # OPERATION diff --git a/src/ethereum/forks/amsterdam/vm/instructions/environment.py b/src/ethereum/forks/amsterdam/vm/instructions/environment.py index 443554dbc8f..582c36c1c58 100644 --- a/src/ethereum/forks/amsterdam/vm/instructions/environment.py +++ b/src/ethereum/forks/amsterdam/vm/instructions/environment.py @@ -17,6 +17,7 @@ from ethereum.state import EMPTY_ACCOUNT from ethereum.utils.numeric import ceil32 +from ...fork_types import ExecutionGas from ...state_tracker import get_account, get_code from ...utils.address import to_address_masked from ...vm.memory import buffer_read, memory_write @@ -224,7 +225,7 @@ def calldatacopy(evm: Evm) -> None: # GAS words = ceil32(Uint(size)) // Uint(32) - copy_gas_cost = GasCosts.OPCODE_COPY_PER_WORD * words + copy_gas_cost = ExecutionGas(GasCosts.OPCODE_COPY_PER_WORD * words) extend_memory = calculate_gas_extend_memory( evm.memory, [(memory_start_index, size)] ) @@ -285,7 +286,7 @@ def codecopy(evm: Evm) -> None: # GAS words = ceil32(Uint(size)) // Uint(32) - copy_gas_cost = GasCosts.OPCODE_COPY_PER_WORD * words + copy_gas_cost = ExecutionGas(GasCosts.OPCODE_COPY_PER_WORD * words) extend_memory = calculate_gas_extend_memory( evm.memory, [(memory_start_index, size)] ) @@ -378,7 +379,7 @@ def extcodecopy(evm: Evm) -> None: # GAS words = ceil32(Uint(size)) // Uint(32) - copy_gas_cost = GasCosts.OPCODE_COPY_PER_WORD * words + copy_gas_cost = ExecutionGas(GasCosts.OPCODE_COPY_PER_WORD * words) extend_memory = calculate_gas_extend_memory( evm.memory, [(memory_start_index, size)] ) @@ -447,7 +448,9 @@ def returndatacopy(evm: Evm) -> None: # GAS words = ceil32(Uint(size)) // Uint(32) - copy_gas_cost = GasCosts.OPCODE_RETURNDATACOPY_PER_WORD * words + copy_gas_cost = ExecutionGas( + GasCosts.OPCODE_RETURNDATACOPY_PER_WORD * words + ) extend_memory = calculate_gas_extend_memory( evm.memory, [(memory_start_index, size)] ) diff --git a/src/ethereum/forks/amsterdam/vm/instructions/keccak.py b/src/ethereum/forks/amsterdam/vm/instructions/keccak.py index 0d3e17cf08e..4b033f71610 100644 --- a/src/ethereum/forks/amsterdam/vm/instructions/keccak.py +++ b/src/ethereum/forks/amsterdam/vm/instructions/keccak.py @@ -16,6 +16,7 @@ from ethereum.crypto.hash import keccak256 from ethereum.utils.numeric import ceil32 +from ...fork_types import ExecutionGas from .. import Evm from ..gas import ( GasCosts, @@ -45,7 +46,7 @@ def keccak(evm: Evm) -> None: # GAS words = ceil32(Uint(size)) // Uint(32) - word_gas_cost = GasCosts.OPCODE_KECCAK256_PER_WORD * words + word_gas_cost = ExecutionGas(GasCosts.OPCODE_KECCAK256_PER_WORD * words) extend_memory = calculate_gas_extend_memory( evm.memory, [(memory_start_index, size)] ) diff --git a/src/ethereum/forks/amsterdam/vm/instructions/log.py b/src/ethereum/forks/amsterdam/vm/instructions/log.py index d5f016817e8..2a5fea8c930 100644 --- a/src/ethereum/forks/amsterdam/vm/instructions/log.py +++ b/src/ethereum/forks/amsterdam/vm/instructions/log.py @@ -17,6 +17,7 @@ from ethereum_types.numeric import Uint from ...blocks import Log +from ...fork_types import ExecutionGas from .. import Evm from ..exceptions import WriteInStaticContext from ..gas import ( @@ -58,10 +59,12 @@ def log_n(evm: Evm, num_topics: int) -> None: ) charge_gas( evm, - GasCosts.OPCODE_LOG_BASE - + GasCosts.OPCODE_LOG_DATA_PER_BYTE * Uint(size) - + GasCosts.OPCODE_LOG_TOPIC * Uint(num_topics) - + extend_memory.cost, + ExecutionGas( + GasCosts.OPCODE_LOG_BASE + + GasCosts.OPCODE_LOG_DATA_PER_BYTE * Uint(size) + + GasCosts.OPCODE_LOG_TOPIC * Uint(num_topics) + + extend_memory.cost + ), ) # OPERATION diff --git a/src/ethereum/forks/amsterdam/vm/instructions/memory.py b/src/ethereum/forks/amsterdam/vm/instructions/memory.py index bba3ddf19d5..8b5b5c3f5b2 100644 --- a/src/ethereum/forks/amsterdam/vm/instructions/memory.py +++ b/src/ethereum/forks/amsterdam/vm/instructions/memory.py @@ -16,6 +16,7 @@ from ethereum.utils.numeric import ceil32 +from ...fork_types import ExecutionGas from .. import Evm from ..gas import ( GasCosts, @@ -159,7 +160,7 @@ def mcopy(evm: Evm) -> None: # GAS words = ceil32(Uint(length)) // Uint(32) - copy_gas_cost = GasCosts.OPCODE_COPY_PER_WORD * words + copy_gas_cost = ExecutionGas(GasCosts.OPCODE_COPY_PER_WORD * words) extend_memory = calculate_gas_extend_memory( evm.memory, [(source, length), (destination, length)] diff --git a/src/ethereum/forks/amsterdam/vm/instructions/storage.py b/src/ethereum/forks/amsterdam/vm/instructions/storage.py index cebee0a7da7..432a3500baa 100644 --- a/src/ethereum/forks/amsterdam/vm/instructions/storage.py +++ b/src/ethereum/forks/amsterdam/vm/instructions/storage.py @@ -13,7 +13,7 @@ from ethereum_types.numeric import Uint -from ...fork_types import StateGas +from ...fork_types import ExecutionGas, StateGas from ...state_tracker import ( get_storage, get_storage_original, @@ -85,7 +85,7 @@ def sstore(evm: Evm) -> None: # GAS (STATE-INDEPENDENT) # Price what is computable without touching state, and check it is # affordable before any state access is performed. - gas_cost = Uint(0) + gas_cost = GasCosts.ZERO # Access cost: cold or warm, always charged. is_cold_access = ( @@ -101,7 +101,9 @@ def sstore(evm: Evm) -> None: # records the slot read in the Block Access List. Post-repricing the # access cost can exceed the stipend, so the EIP-2200 stipend sentry # (`gas_left > CALL_STIPEND`) is no longer sufficient on its own. - check_gas(evm, max(gas_cost, GasCosts.CALL_STIPEND + Uint(1))) + check_gas( + evm, max(gas_cost, ExecutionGas(GasCosts.CALL_STIPEND + Uint(1))) + ) # STATE ACCESS (STATE-DEPENDENT GAS) # Perform the access and complete the state-dependent pricing from diff --git a/src/ethereum/forks/amsterdam/vm/instructions/system.py b/src/ethereum/forks/amsterdam/vm/instructions/system.py index 082fe35d417..168cfb69240 100644 --- a/src/ethereum/forks/amsterdam/vm/instructions/system.py +++ b/src/ethereum/forks/amsterdam/vm/instructions/system.py @@ -20,7 +20,7 @@ from ethereum.state import Address from ethereum.utils.numeric import ceil32 -from ...fork_types import StateGas +from ...fork_types import ExecutionGas, StateGas from ...state_tracker import ( account_deployable, get_account, @@ -282,10 +282,12 @@ def create2(evm: Evm) -> None: init_code_gas = init_code_cost(Uint(memory_size)) charge_gas( evm, - GasCosts.CREATE_ACCESS - + GasCosts.OPCODE_KECCAK256_PER_WORD * call_data_words - + extend_memory.cost - + init_code_gas, + ExecutionGas( + GasCosts.CREATE_ACCESS + + GasCosts.OPCODE_KECCAK256_PER_WORD * call_data_words + + extend_memory.cost + + init_code_gas + ), ) if memory_size > U256(MAX_INIT_CODE_SIZE): @@ -351,8 +353,8 @@ class GenericCall: Parameters for the core logic of the `CALL*` family of opcodes. """ - gas: Uint - state_gas_reservoir: Uint + gas: ExecutionGas + state_gas_reservoir: StateGas value: U256 caller: Address to: Address @@ -480,7 +482,7 @@ def call(evm: Evm) -> None: """ # STACK - gas = Uint(pop(evm.stack)) + gas = ExecutionGas(Uint(pop(evm.stack))) to = to_address_masked(pop(evm.stack)) value = pop(evm.stack) memory_input_start_position = pop(evm.stack) @@ -508,7 +510,7 @@ def call(evm: Evm) -> None: else: access_gas_cost = GasCosts.WARM_ACCESS - transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE + transfer_gas_cost = GasCosts.ZERO if value == 0 else GasCosts.CALL_VALUE check_gas( evm, @@ -558,9 +560,9 @@ def call(evm: Evm) -> None: message_call_gas = calculate_message_call_gas( value, gas, - Uint(evm.gas_meter.gas_left), - memory_cost=Uint(0), - extra_gas=Uint(0), + evm.gas_meter.gas_left, + memory_cost=GasCosts.ZERO, + extra_gas=GasCosts.ZERO, ) charge_gas(evm, message_call_gas.cost) call_state_gas_reservoir = drain_state_gas_reservoir(evm.gas_meter) @@ -607,7 +609,7 @@ def callcode(evm: Evm) -> None: """ # STACK - gas = Uint(pop(evm.stack)) + gas = ExecutionGas(Uint(pop(evm.stack))) code_address = to_address_masked(pop(evm.stack)) value = pop(evm.stack) memory_input_start_position = pop(evm.stack) @@ -634,7 +636,7 @@ def callcode(evm: Evm) -> None: else: access_gas_cost = GasCosts.WARM_ACCESS - transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE + transfer_gas_cost = GasCosts.ZERO if value == 0 else GasCosts.CALL_VALUE check_gas( evm, @@ -673,7 +675,7 @@ def callcode(evm: Evm) -> None: message_call_gas = calculate_message_call_gas( value, gas, - Uint(evm.gas_meter.gas_left), + evm.gas_meter.gas_left, extend_memory.cost, extra_gas, ) @@ -749,7 +751,7 @@ def selfdestruct(evm: Evm) -> None: # and the creation, charged by the frame whose opcode causes it; # it refills only through the frame's own rollback. state_gas = StateGas(Uint(0)) - account_write_gas = Uint(0) + account_write_gas = GasCosts.ZERO if ( not is_account_alive(tx_state, beneficiary) and get_account(tx_state, evm.current_target).balance != 0 @@ -796,7 +798,7 @@ def delegatecall(evm: Evm) -> None: """ # STACK - gas = Uint(pop(evm.stack)) + gas = ExecutionGas(Uint(pop(evm.stack))) code_address = to_address_masked(pop(evm.stack)) memory_input_start_position = pop(evm.stack) memory_input_size = pop(evm.stack) @@ -854,7 +856,7 @@ def delegatecall(evm: Evm) -> None: message_call_gas = calculate_message_call_gas( U256(0), gas, - Uint(evm.gas_meter.gas_left), + evm.gas_meter.gas_left, extend_memory.cost, extra_gas, ) @@ -899,7 +901,7 @@ def staticcall(evm: Evm) -> None: """ # STACK - gas = Uint(pop(evm.stack)) + gas = ExecutionGas(Uint(pop(evm.stack))) to = to_address_masked(pop(evm.stack)) memory_input_start_position = pop(evm.stack) memory_input_size = pop(evm.stack) @@ -957,7 +959,7 @@ def staticcall(evm: Evm) -> None: message_call_gas = calculate_message_call_gas( U256(0), gas, - Uint(evm.gas_meter.gas_left), + evm.gas_meter.gas_left, extend_memory.cost, extra_gas, ) diff --git a/src/ethereum/forks/amsterdam/vm/interpreter.py b/src/ethereum/forks/amsterdam/vm/interpreter.py index 4275887632b..656cbc6b323 100644 --- a/src/ethereum/forks/amsterdam/vm/interpreter.py +++ b/src/ethereum/forks/amsterdam/vm/interpreter.py @@ -32,6 +32,7 @@ from ethereum.utils.numeric import ceil32 from ..blocks import Log +from ..fork_types import ExecutionGas, StateGas from ..state_tracker import ( TransactionState, account_deployable, @@ -95,7 +96,7 @@ class TransactionOutput: frame itself never leaves the interpreter. """ - gas_left: Uint + gas_left: ExecutionGas """Execution gas remaining after execution.""" refund_counter: U256 @@ -113,7 +114,7 @@ class TransactionOutput: return_data: Bytes """The output of the execution.""" - state_gas_left: Uint + state_gas_left: StateGas """State gas remaining in the reservoir after execution.""" state_gas_used: int @@ -371,7 +372,7 @@ def process_create(evm: Evm) -> Evm: if len(contract_code) > MAX_CODE_SIZE: raise OutOfGasError # Hash cost for computing keccak256 of deployed bytecode - code_hash_gas = ( + code_hash_gas = ExecutionGas( GasCosts.OPCODE_KECCAK256_PER_WORD * ceil32(ulen(contract_code)) // Uint(32) diff --git a/src/ethereum/forks/amsterdam/vm/precompiled_contracts/alt_bn128.py b/src/ethereum/forks/amsterdam/vm/precompiled_contracts/alt_bn128.py index e76bbcee100..a568edbbb45 100644 --- a/src/ethereum/forks/amsterdam/vm/precompiled_contracts/alt_bn128.py +++ b/src/ethereum/forks/amsterdam/vm/precompiled_contracts/alt_bn128.py @@ -30,6 +30,7 @@ from py_ecc.optimized_bn128.optimized_pairing import pairing from py_ecc.typing import Optimized_Point3D as Point3D +from ...fork_types import ExecutionGas from ...vm import Evm from ...vm.gas import GasCosts, charge_gas from ...vm.memory import buffer_read @@ -207,8 +208,10 @@ def alt_bn128_pairing_check(evm: Evm) -> None: # GAS charge_gas( evm, - GasCosts.PRECOMPILE_ECPAIRING_PER_POINT * (ulen(data) // Uint(192)) - + GasCosts.PRECOMPILE_ECPAIRING_BASE, + ExecutionGas( + GasCosts.PRECOMPILE_ECPAIRING_PER_POINT * (ulen(data) // Uint(192)) + + GasCosts.PRECOMPILE_ECPAIRING_BASE + ), ) # OPERATION diff --git a/src/ethereum/forks/amsterdam/vm/precompiled_contracts/bls12_381/bls12_381_g1.py b/src/ethereum/forks/amsterdam/vm/precompiled_contracts/bls12_381/bls12_381_g1.py index cb453f19ee0..1dc334a68cb 100644 --- a/src/ethereum/forks/amsterdam/vm/precompiled_contracts/bls12_381/bls12_381_g1.py +++ b/src/ethereum/forks/amsterdam/vm/precompiled_contracts/bls12_381/bls12_381_g1.py @@ -19,6 +19,7 @@ multiply as bls12_multiply, ) +from ....fork_types import ExecutionGas from ....vm import Evm from ....vm.gas import ( GasCosts, @@ -99,7 +100,9 @@ def bls12_g1_msm(evm: Evm) -> None: else: discount = Uint(G1_MAX_DISCOUNT) - gas_cost = Uint(k) * GasCosts.PRECOMPILE_BLS_G1MUL * discount // MULTIPLIER + gas_cost = ExecutionGas( + Uint(k) * GasCosts.PRECOMPILE_BLS_G1MUL * discount // MULTIPLIER + ) charge_gas(evm, gas_cost) # OPERATION diff --git a/src/ethereum/forks/amsterdam/vm/precompiled_contracts/bls12_381/bls12_381_g2.py b/src/ethereum/forks/amsterdam/vm/precompiled_contracts/bls12_381/bls12_381_g2.py index 7be6695fc2a..c9494da137f 100644 --- a/src/ethereum/forks/amsterdam/vm/precompiled_contracts/bls12_381/bls12_381_g2.py +++ b/src/ethereum/forks/amsterdam/vm/precompiled_contracts/bls12_381/bls12_381_g2.py @@ -19,6 +19,7 @@ multiply as bls12_multiply, ) +from ....fork_types import ExecutionGas from ....vm import Evm from ....vm.gas import ( GasCosts, @@ -100,7 +101,9 @@ def bls12_g2_msm(evm: Evm) -> None: else: discount = Uint(G2_MAX_DISCOUNT) - gas_cost = Uint(k) * GasCosts.PRECOMPILE_BLS_G2MUL * discount // MULTIPLIER + gas_cost = ExecutionGas( + Uint(k) * GasCosts.PRECOMPILE_BLS_G2MUL * discount // MULTIPLIER + ) charge_gas(evm, gas_cost) # OPERATION diff --git a/src/ethereum/forks/amsterdam/vm/precompiled_contracts/bls12_381/bls12_381_pairing.py b/src/ethereum/forks/amsterdam/vm/precompiled_contracts/bls12_381/bls12_381_pairing.py index 2723e11854e..1c29e7a8317 100644 --- a/src/ethereum/forks/amsterdam/vm/precompiled_contracts/bls12_381/bls12_381_pairing.py +++ b/src/ethereum/forks/amsterdam/vm/precompiled_contracts/bls12_381/bls12_381_pairing.py @@ -15,6 +15,7 @@ from py_ecc.optimized_bls12_381 import FQ12, curve_order, is_inf, pairing from py_ecc.optimized_bls12_381 import multiply as bls12_multiply +from ....fork_types import ExecutionGas from ....vm import Evm from ....vm.gas import charge_gas from ...exceptions import InvalidParameter @@ -42,7 +43,7 @@ def bls12_pairing(evm: Evm) -> None: # GAS k = len(data) // 384 - gas_cost = Uint(32600 * k + 37700) + gas_cost = ExecutionGas(Uint(32600 * k + 37700)) charge_gas(evm, gas_cost) # OPERATION diff --git a/src/ethereum/forks/amsterdam/vm/precompiled_contracts/identity.py b/src/ethereum/forks/amsterdam/vm/precompiled_contracts/identity.py index 448aa8e25a2..7934b90a597 100644 --- a/src/ethereum/forks/amsterdam/vm/precompiled_contracts/identity.py +++ b/src/ethereum/forks/amsterdam/vm/precompiled_contracts/identity.py @@ -15,6 +15,7 @@ from ethereum.utils.numeric import ceil32 +from ...fork_types import ExecutionGas from ...vm import Evm from ...vm.gas import ( GasCosts, @@ -38,8 +39,10 @@ def identity(evm: Evm) -> None: word_count = ceil32(ulen(data)) // Uint(32) charge_gas( evm, - GasCosts.PRECOMPILE_IDENTITY_BASE - + GasCosts.PRECOMPILE_IDENTITY_PER_WORD * word_count, + ExecutionGas( + GasCosts.PRECOMPILE_IDENTITY_BASE + + GasCosts.PRECOMPILE_IDENTITY_PER_WORD * word_count + ), ) # OPERATION diff --git a/src/ethereum/forks/amsterdam/vm/precompiled_contracts/modexp.py b/src/ethereum/forks/amsterdam/vm/precompiled_contracts/modexp.py index 51b2f886cae..f7fb0396369 100644 --- a/src/ethereum/forks/amsterdam/vm/precompiled_contracts/modexp.py +++ b/src/ethereum/forks/amsterdam/vm/precompiled_contracts/modexp.py @@ -14,6 +14,7 @@ from ethereum_types.bytes import Bytes from ethereum_types.numeric import U256, Uint +from ...fork_types import ExecutionGas from ...vm import Evm from ...vm.exceptions import ExceptionalHalt from ...vm.gas import charge_gas @@ -144,7 +145,7 @@ def gas_cost( modulus_length: U256, exponent_length: U256, exponent_head: U256, -) -> Uint: +) -> ExecutionGas: """ Calculate the gas cost of performing a modular exponentiation. @@ -165,11 +166,11 @@ def gas_cost( Returns ------- - gas_cost : `Uint` + gas_cost : `ExecutionGas` Gas required for performing the operation. """ multiplication_complexity = complexity(base_length, modulus_length) iteration_count = iterations(exponent_length, exponent_head) cost = multiplication_complexity * iteration_count - return max(Uint(500), cost) + return ExecutionGas(max(Uint(500), cost)) diff --git a/src/ethereum/forks/amsterdam/vm/precompiled_contracts/ripemd160.py b/src/ethereum/forks/amsterdam/vm/precompiled_contracts/ripemd160.py index 57afeff0a72..3d970b0d093 100644 --- a/src/ethereum/forks/amsterdam/vm/precompiled_contracts/ripemd160.py +++ b/src/ethereum/forks/amsterdam/vm/precompiled_contracts/ripemd160.py @@ -18,6 +18,7 @@ from ethereum.utils.byte import left_pad_zero_bytes from ethereum.utils.numeric import ceil32 +from ...fork_types import ExecutionGas from ...vm import Evm from ...vm.gas import ( GasCosts, @@ -41,8 +42,10 @@ def ripemd160(evm: Evm) -> None: word_count = ceil32(ulen(data)) // Uint(32) charge_gas( evm, - GasCosts.PRECOMPILE_RIPEMD160_BASE - + GasCosts.PRECOMPILE_RIPEMD160_PER_WORD * word_count, + ExecutionGas( + GasCosts.PRECOMPILE_RIPEMD160_BASE + + GasCosts.PRECOMPILE_RIPEMD160_PER_WORD * word_count + ), ) # OPERATION diff --git a/src/ethereum/forks/amsterdam/vm/precompiled_contracts/sha256.py b/src/ethereum/forks/amsterdam/vm/precompiled_contracts/sha256.py index 6db9ec970fb..640ec1e3dcd 100644 --- a/src/ethereum/forks/amsterdam/vm/precompiled_contracts/sha256.py +++ b/src/ethereum/forks/amsterdam/vm/precompiled_contracts/sha256.py @@ -17,6 +17,7 @@ from ethereum.utils.numeric import ceil32 +from ...fork_types import ExecutionGas from ...vm import Evm from ...vm.gas import ( GasCosts, @@ -40,8 +41,10 @@ def sha256(evm: Evm) -> None: word_count = ceil32(ulen(data)) // Uint(32) charge_gas( evm, - GasCosts.PRECOMPILE_SHA256_BASE - + GasCosts.PRECOMPILE_SHA256_PER_WORD * word_count, + ExecutionGas( + GasCosts.PRECOMPILE_SHA256_BASE + + GasCosts.PRECOMPILE_SHA256_PER_WORD * word_count + ), ) # OPERATION From 987b865311b1efe9d781f08c6ef4f7adebb4f305 Mon Sep 17 00:00:00 2001 From: danceratopz <danceratopz@gmail.com> Date: Wed, 5 Aug 2026 14:45:45 +0200 Subject: [PATCH 204/233] chore(tooling): add runner-selection guidance to the edit-workflow skill (#3313) --- .claude/commands/edit-workflow.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.claude/commands/edit-workflow.md b/.claude/commands/edit-workflow.md index 9c906e5ebfa..507e4efdfc2 100644 --- a/.claude/commands/edit-workflow.md +++ b/.claude/commands/edit-workflow.md @@ -13,6 +13,23 @@ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - Never use version tags alone (`@v4` is wrong) - Local actions (`./.github/actions/*`) are exempt from pinning +## Runner Selection + +Self-hosted runners (`[self-hosted-ghr, size-*-x64]`) are a shared EF devops +pool — reserve them for jobs that need the capacity. Non-critical or low-load +jobs belong on GitHub-hosted runners (`ubuntu-latest`), which also avoids a +provisioning wait (~2.5 min) if the self-hosted warm pool is exhausted. + +- **Pattern**: lightweight job (short runtime, no heavy parallelism) → + `ubuntu-latest` (e.g. `spec-tools` in #3177). +- **Anti-pattern**: defaulting a quick gate, lint, or cache-restore job to + `size-xl-x64` "to be safe". +- **Exception**: a job may need self-hosted for reasons other than load, e.g. + Docker Hub pulls from GHR egress IPs to dodge per-IP rate limits (#3185 + keeps push runs self-hosted while PR runs use `ubuntu-latest`). + +If unsure which runner a job needs, flag it and ask instead of guessing. + ## Validation Run `just lint-actions` before committing to validate YAML syntax and structure. From 07424a89ba0453709576830f6be33442ed8d102d Mon Sep 17 00:00:00 2001 From: Jochem Brouwer <jochembrouwer96@gmail.com> Date: Thu, 6 Aug 2026 00:45:51 +0200 Subject: [PATCH 205/233] feat(tests): add eth_getTransactionReceipt batching (#3298) --- .../client_clis/client_backend.py | 17 +++++++-- .../testing/src/execution_testing/rpc/rpc.py | 37 +++++++++++++++++++ 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/packages/testing/src/execution_testing/client_clis/client_backend.py b/packages/testing/src/execution_testing/client_clis/client_backend.py index ff1c5c4d003..f9bb2507b6d 100644 --- a/packages/testing/src/execution_testing/client_clis/client_backend.py +++ b/packages/testing/src/execution_testing/client_clis/client_backend.py @@ -436,10 +436,21 @@ def _finalize( def _fetch_receipts( self, txs: List[Transaction] ) -> List[TransactionReceipt]: - """Fetch receipts for each transaction. TODO: batch via JSON-RPC.""" + """ + Fetch receipts for every transaction in the block, batched. + + One request per transaction makes fill time latency-bound: a block + of 5,000 transactions costs 5,000 sequential round trips, which + against a non-local client dominates everything else the fill does + (the client executes such a block in ~150ms). + """ + if not txs: + return [] + receipt_data_list = self.eth_rpc.get_transaction_receipts( + [tx.hash for tx in txs] + ) receipts: List[TransactionReceipt] = [] - for tx in txs: - receipt_data = self.eth_rpc.get_transaction_receipt(tx.hash) + for tx, receipt_data in zip(txs, receipt_data_list, strict=True): if receipt_data is None: raise RuntimeError( f"No receipt found for transaction {tx.hash}" diff --git a/packages/testing/src/execution_testing/rpc/rpc.py b/packages/testing/src/execution_testing/rpc/rpc.py index 739c2c26962..72e49c43a7a 100644 --- a/packages/testing/src/execution_testing/rpc/rpc.py +++ b/packages/testing/src/execution_testing/rpc/rpc.py @@ -837,6 +837,43 @@ def get_transaction_receipt( ) ).result_or_raise() + def get_transaction_receipts( + self, + transaction_hashes: Sequence[Hash], + *, + chunk_size: int = 500, + ) -> List[dict[str, Any] | None]: + """ + `eth_getTransactionReceipt` batch: receipts for many transactions. + + Returns one entry per input hash, in the same order (see + `post_batch_request`, which maps responses back by request id). + + Requests are chunked because clients cap batch size -- geth's + `--rpc.batchrequestlimit` defaults to 1000 -- and because a single + response carrying thousands of receipts is several megabytes. + """ + if not transaction_hashes: + return [] + logger.info( + f"Batch requesting {len(transaction_hashes)} tx receipts " + f"in chunks of {chunk_size}" + ) + receipts: List[dict[str, Any] | None] = [] + for start in range(0, len(transaction_hashes), chunk_size): + chunk = transaction_hashes[start : start + chunk_size] + responses = self.post_batch_request( + calls=[ + RPCCall( + method="getTransactionReceipt", + params=[f"{tx_hash}"], + ) + for tx_hash in chunk + ] + ) + receipts.extend(r.result_or_raise() for r in responses) + return receipts + def get_storage_at( self, address: Address, From 1afd925ff8d2d080e0d979acde72bd2848d6291a Mon Sep 17 00:00:00 2001 From: spencer <spencer.taylor-brown@ethereum.org> Date: Thu, 6 Aug 2026 14:47:52 +0200 Subject: [PATCH 206/233] feat(tests): add EIP-2780 coverage gap tests (#3318) --- .../test_calldata_floor.py | 106 +++++++++++++++++- .../test_fork_transition.py | 99 ++++++++++++++++ .../test_intrinsic_gas_boundary.py | 60 ++++++++++ 3 files changed, 264 insertions(+), 1 deletion(-) diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_calldata_floor.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_calldata_floor.py index c1a13931d54..fd750ca4e05 100644 --- a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_calldata_floor.py +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_calldata_floor.py @@ -7,6 +7,7 @@ Alloc, Bytes, Fork, + Op, RecipientType, StateTestFiller, Transaction, @@ -18,7 +19,11 @@ from ...prague.eip7623_increase_calldata_cost.helpers import ( find_floor_cost_threshold, ) -from .helpers import EOA_INITIAL_BALANCE +from .helpers import ( + EOA_INITIAL_BALANCE, + AuthorizationAction, + build_authorization, +) from .spec import ref_spec_2780 REFERENCE_SPEC_GIT_PATH = ref_spec_2780.git_path @@ -323,3 +328,102 @@ def test_calldata_floor_contract_creation( } state_test(pre=pre, tx=tx, post=post) + + +@pytest.mark.parametrize( + "outcome", + [ + pytest.param("floor_binds", id="floor_binds"), + pytest.param( + "below_floor", + id="below_floor_rejected", + marks=pytest.mark.exception_test, + ), + ], +) +def test_calldata_floor_with_authorizations( + fork: Fork, + pre: Alloc, + state_test: StateTestFiller, + outcome: str, +) -> None: + """ + A data-heavy type-4 transaction whose calldata floor exceeds the + full authorization total: the intrinsic plus the authorization's + top-frame execution and state charges. + """ + sender = pre.fund_eoa() + recipient = pre.deploy_contract(code=Op.STOP) + scenario = build_authorization( + pre, AuthorizationAction.SETS_NEW_DELEGATION + ) + authorization_list = [scenario.authorization] + + intrinsic_calc = fork.transaction_intrinsic_cost_calculator() + floor_calc = fork.transaction_data_floor_cost_calculator() + top_frame_gas = fork.transaction_top_frame_gas_calculator()( + recipient_type=RecipientType.CONTRACT, + authorizations=authorization_list, + ) + top_frame_state_gas = fork.transaction_top_frame_state_gas( + recipient_type=RecipientType.CONTRACT, + authorizations=authorization_list, + ) + + def total(byte_count: int) -> int: + return ( + intrinsic_calc( + calldata=b"\x00" * byte_count, + recipient_type=RecipientType.CONTRACT, + authorization_list_or_count=authorization_list, + return_cost_deducted_prior_execution=True, + ) + + top_frame_gas + + top_frame_state_gas + ) + + def floor(byte_count: int) -> int: + return floor_calc( + data=b"\x00" * byte_count, + recipient_type=RecipientType.CONTRACT, + ) + + threshold = find_floor_cost_threshold( + floor_data_gas_cost_calculator=floor, + intrinsic_gas_cost_calculator=total, + ) + byte_count = threshold + 1 + calldata = Bytes(b"\x00" * byte_count) + calldata_floor = floor(byte_count) + assert calldata_floor > total(byte_count), ( + "the calldata floor must dominate the full authorization total" + ) + + post: dict[Address, Account | None] + if outcome == "below_floor": + tx = Transaction( + sender=sender, + to=recipient, + data=calldata, + authorization_list=authorization_list, + gas_limit=calldata_floor - 1, + error=TransactionException.INTRINSIC_GAS_BELOW_FLOOR_GAS_COST, + ) + post = {scenario.authority: scenario.original_account} + else: + tx = Transaction( + sender=sender, + to=recipient, + data=calldata, + authorization_list=authorization_list, + gas_limit=calldata_floor, + expected_receipt=TransactionReceipt( + cumulative_gas_used=calldata_floor, + ), + ) + post = { + sender: Account(nonce=1), + scenario.authority: scenario.applied_account, + } + + state_test(pre=pre, tx=tx, post=post) diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_fork_transition.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_fork_transition.py index 18885366818..8c8a2b221aa 100644 --- a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_fork_transition.py +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_fork_transition.py @@ -27,15 +27,18 @@ Account, Address, Alloc, + AuthorizationTuple, Block, BlockchainTestFiller, Op, RecipientType, Transaction, + TransactionReceipt, TransitionFork, compute_create_address, ) +from ...prague.eip7702_set_code_tx.spec import Spec as Spec7702 from .helpers import EOA_INITIAL_BALANCE from .spec import ref_spec_2780 @@ -269,3 +272,99 @@ def test_creation_tx_intrinsic_across_amsterdam_transition( post[created] = Account(nonce=1, balance=value, code=b"") blockchain_test(pre=pre, blocks=blocks, post=post) + + +def test_setcode_tx_across_amsterdam_transition( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: TransitionFork, +) -> None: + """ + Pin the EIP-2780 authorization repricing across the Amsterdam + boundary. + """ + gas_price = 1_000_000_000 + + pre_costs = fork.fork_at(timestamp=PRE_FORK_TIMESTAMP).gas_costs() + post_costs = fork.fork_at(timestamp=POST_FORK_TIMESTAMP).gas_costs() + + # Pre-fork: EIP-7702 charges the full per-authorization cost in the + # intrinsic; an empty authority earns no existing-authority refund. + expected_pre = pre_costs.TX_BASE + pre_costs.AUTH_PER_EMPTY_ACCOUNT + # Post-fork: EIP-2780 decomposition across the three charge layers. + expected_post = ( + post_costs.TX_BASE + + post_costs.COLD_ACCOUNT_ACCESS + + post_costs.EXECUTION_PER_AUTH_BASE_COST + + post_costs.ACCOUNT_WRITE + + post_costs.NEW_ACCOUNT + + post_costs.AUTH_BASE + ) + + timestamps = [PRE_FORK_TIMESTAMP, POST_FORK_TIMESTAMP] + expected_totals = [expected_pre, expected_post] + blocks = [] + post: dict[Address, Account] = {} + + for timestamp, expected_total in zip( + timestamps, expected_totals, strict=True + ): + sub_fork = fork.fork_at(timestamp=timestamp) + recipient = pre.deploy_contract(code=Op.STOP) + delegate_to = pre.deploy_contract(code=Op.STOP) + authority = pre.fund_eoa(amount=0) + authorization = AuthorizationTuple( + address=delegate_to, + nonce=0, + signer=authority, + creates_account=True, + ) + + intrinsic_gas = sub_fork.transaction_intrinsic_cost_calculator()( + recipient_type=RecipientType.CONTRACT, + authorization_list_or_count=[authorization], + return_cost_deducted_prior_execution=True, + ) + top_frame_gas = sub_fork.transaction_top_frame_gas_calculator()( + recipient_type=RecipientType.CONTRACT, + authorizations=[authorization], + ) + top_frame_state_gas = sub_fork.transaction_top_frame_state_gas( + recipient_type=RecipientType.CONTRACT, + authorizations=[authorization], + ) + total_gas = intrinsic_gas + top_frame_gas + top_frame_state_gas + assert total_gas == expected_total, ( + f"set-code total at timestamp {timestamp} ({sub_fork}) is " + f"{total_gas}, expected {expected_total}" + ) + + sender_initial_balance = 10**18 + sender = pre.fund_eoa(sender_initial_balance) + + # Both recipient and delegate run ``STOP`` (no execution gas), + # so the receipt pins the intrinsic and top-frame layers alone. + tx = Transaction( + sender=sender, + to=recipient, + authorization_list=[authorization], + gas_limit=total_gas, + max_fee_per_gas=gas_price, + max_priority_fee_per_gas=gas_price, + expected_receipt=TransactionReceipt( + cumulative_gas_used=total_gas, + ), + ) + blocks.append(Block(timestamp=timestamp, txs=[tx])) + + post[sender] = Account( + nonce=1, + balance=sender_initial_balance - total_gas * gas_price, + ) + post[authority] = Account( + nonce=1, + balance=0, + code=Spec7702.delegation_designation(delegate_to), + ) + + blockchain_test(pre=pre, blocks=blocks, post=post) diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_intrinsic_gas_boundary.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_intrinsic_gas_boundary.py index d8bad2b8ea1..e2c6fbc2be3 100644 --- a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_intrinsic_gas_boundary.py +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_intrinsic_gas_boundary.py @@ -12,16 +12,21 @@ Alloc, AuthorizationTuple, Fork, + Hash, Op, RecipientType, StateTestFiller, Transaction, TransactionException, + add_kzg_version, ) +from ...cancun.eip4844_blobs.spec import Spec as EIP4844_Spec from .helpers import ( EOA_INITIAL_BALANCE, RECIPIENT_TYPES_NON_CREATE, + AuthorizationAction, + build_authorization, setup_target, ) from .spec import ref_spec_2780 @@ -177,3 +182,58 @@ def test_intrinsic_gas_floor_boundary_with_authorizations( ) state_test(pre=pre, tx=tx, post=pre) + + +@pytest.mark.exception_test +@pytest.mark.with_all_tx_types +def test_intrinsic_gas_floor_boundary_all_tx_types( + fork: Fork, + pre: Alloc, + state_test: StateTestFiller, + tx_type: int, +) -> None: + """ + Reject every transaction type when ``gas_limit = intrinsic_gas - 1``. + + Each type layers its own intrinsic components on the shared + value-transfer shape -- ``EXECUTION_PER_AUTH_BASE_COST`` for type 4 -- + and the pre-execution check must reject one gas below the per-type + total. Blob gas is priced in its own dimension, so a type-3 + transaction's execution-gas boundary is identical to type 2. + """ + value = 1 + sender = pre.fund_eoa() + target = pre.fund_eoa(amount=EOA_INITIAL_BALANCE) + + scenario = ( + build_authorization(pre, AuthorizationAction.SETS_NEW_DELEGATION) + if tx_type == 4 + else None + ) + authorizations = [scenario.authorization] if scenario else [] + + blob_versioned_hashes = ( + add_kzg_version([Hash(1)], EIP4844_Spec.BLOB_COMMITMENT_VERSION_KZG) + if tx_type == 3 + else None + ) + + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()( + sends_value=True, + recipient_type=RecipientType.EOA, + authorization_list_or_count=authorizations, + return_cost_deducted_prior_execution=True, + ) + + tx = Transaction( + ty=tx_type, + sender=sender, + to=target, + value=value, + authorization_list=authorizations or None, + blob_versioned_hashes=blob_versioned_hashes, + gas_limit=intrinsic_gas - 1, + error=TransactionException.INTRINSIC_GAS_TOO_LOW, + ) + + state_test(pre=pre, tx=tx, post=pre) From 847f8857f843d95760882fd8d637d77a259636f2 Mon Sep 17 00:00:00 2001 From: spencer <spencer.taylor-brown@ethereum.org> Date: Thu, 6 Aug 2026 14:52:14 +0200 Subject: [PATCH 207/233] feat(test-tests): add engine payload attribute and genesis parity tests (#3309) --- .../cli/tests/test_execute_genesis.py | 48 ++++++++ .../execution_testing/fixtures/blockchain.py | 8 +- .../rpc/tests/test_payload_attributes.py | 115 ++++++++++++++++++ 3 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 packages/testing/src/execution_testing/cli/tests/test_execute_genesis.py create mode 100644 packages/testing/src/execution_testing/rpc/tests/test_payload_attributes.py diff --git a/packages/testing/src/execution_testing/cli/tests/test_execute_genesis.py b/packages/testing/src/execution_testing/cli/tests/test_execute_genesis.py new file mode 100644 index 00000000000..82057883753 --- /dev/null +++ b/packages/testing/src/execution_testing/cli/tests/test_execute_genesis.py @@ -0,0 +1,48 @@ +""" +Test the execute genesis header against the fill genesis header. + +``execute``/``fill-stateful`` build the client genesis with +``build_genesis_header`` while ``fill`` uses ``FixtureHeader.genesis``; +both hand-populate fork-conditional header fields, so a fork that +extends the header must extend both. Feeding them equivalent inputs and +comparing the result catches one-sided drift. +""" + +from typing import Any, Dict, List + +import pytest + +from execution_testing.base_types import to_json +from execution_testing.cli.pytest_commands.plugins.execute.rpc.hive import ( + build_genesis_header, +) +from execution_testing.fixtures.blockchain import FixtureHeader +from execution_testing.forks import ( + Fork, + get_deployed_forks, + get_development_forks, +) +from execution_testing.specs.blockchain import GENESIS_ENVIRONMENT_DEFAULTS +from execution_testing.test_types import Environment + +# ``build_genesis_header`` pins these two values instead of taking them +# from the environment; mirror them so both builders receive equivalent +# inputs and any difference is a structural one. +EXECUTE_GENESIS_ENVIRONMENT: Dict[str, Any] = GENESIS_ENVIRONMENT_DEFAULTS | { + "timestamp": 1, + "difficulty": 0x20000, +} + +FORKS: List[Fork] = get_deployed_forks() + get_development_forks() + + +@pytest.mark.parametrize("fork", FORKS, ids=lambda fork: fork.name()) +def test_execute_genesis_matches_fill_genesis(fork: Fork) -> None: + """The two genesis builders must produce identical headers.""" + pre_alloc, execute_genesis = build_genesis_header(fork) + env = Environment(**EXECUTE_GENESIS_ENVIRONMENT).set_fork_requirements( + fork + ) + fill_genesis = FixtureHeader.genesis(fork, env, pre_alloc.state_root()) + assert to_json(execute_genesis) == to_json(fill_genesis) + assert execute_genesis.block_hash == fill_genesis.block_hash diff --git a/packages/testing/src/execution_testing/fixtures/blockchain.py b/packages/testing/src/execution_testing/fixtures/blockchain.py index 5ad3d932fa5..a7331378786 100644 --- a/packages/testing/src/execution_testing/fixtures/blockchain.py +++ b/packages/testing/src/execution_testing/fixtures/blockchain.py @@ -561,7 +561,13 @@ def get_payload_attributes(self) -> "PayloadAttributes": withdrawals=execution_payload.withdrawals, parent_beacon_block_root=parent_beacon_block_root, slot_number=execution_payload.slot_number, - target_gas_limit=execution_payload.gas_limit, + # targetGasLimit exists from V4 onwards; earlier versions must + # not carry the field even though every payload has a gas limit. + target_gas_limit=( + execution_payload.gas_limit + if self.forkchoice_updated_version >= 4 + else None + ), ) @staticmethod diff --git a/packages/testing/src/execution_testing/rpc/tests/test_payload_attributes.py b/packages/testing/src/execution_testing/rpc/tests/test_payload_attributes.py new file mode 100644 index 00000000000..a9ce47d4e4a --- /dev/null +++ b/packages/testing/src/execution_testing/rpc/tests/test_payload_attributes.py @@ -0,0 +1,115 @@ +""" +Test fork-aware construction of engine API payload attributes. + +Every ``engine_payload_attribute_*`` fork predicate must be honored by +both ``PayloadAttributes`` producers: ``PayloadAttributes.for_fork`` +(used by ``execute`` and ``fill-stateful`` to build blocks live) and +``FixtureEngineNewPayload.get_payload_attributes`` (used by ``consume`` +to have a client build fixture blocks). A fork that adds a payload +attribute fails these tests until both producers populate it. +""" + +from typing import List + +import pytest + +from execution_testing.base_types import Hash +from execution_testing.fixtures.blockchain import ( + FixtureEngineNewPayload, + FixtureHeader, +) +from execution_testing.forks import ( + Fork, + get_deployed_forks, + get_development_forks, +) +from execution_testing.rpc.rpc_types import PayloadAttributes +from execution_testing.specs.blockchain import GENESIS_ENVIRONMENT_DEFAULTS +from execution_testing.test_types import BlockAccessList, Environment + +ENGINE_PAYLOAD_ATTRIBUTE_PREFIX = "engine_payload_attribute_" + +ALL_FORKS: List[Fork] = get_deployed_forks() + get_development_forks() +ENGINE_FORKS: List[Fork] = [ + fork + for fork in ALL_FORKS + if fork.engine_forkchoice_updated_version() is not None +] + + +def engine_payload_attribute_predicates(fork: Fork) -> List[str]: + """Return the fork's engine payload attribute predicate names.""" + return [ + name + for name in dir(fork) + if name.startswith(ENGINE_PAYLOAD_ATTRIBUTE_PREFIX) + ] + + +def assert_attributes_cover_fork( + attributes: PayloadAttributes, fork: Fork +) -> None: + """ + Assert every predicate-gated payload attribute is populated exactly + when the fork requires it. + """ + for predicate_name in engine_payload_attribute_predicates(fork): + field = predicate_name.removeprefix(ENGINE_PAYLOAD_ATTRIBUTE_PREFIX) + assert field in PayloadAttributes.model_fields, ( + f"{predicate_name} has no matching `{field}` field on " + "PayloadAttributes" + ) + required = getattr(fork, predicate_name)() + populated = getattr(attributes, field) is not None + assert populated == required, ( + f"`{field}` must be {'set' if required else 'unset'} for {fork}" + ) + + +def test_predicate_discovery() -> None: + """The predicate discovery must find the known predicate family.""" + names = engine_payload_attribute_predicates(ALL_FORKS[0]) + assert f"{ENGINE_PAYLOAD_ATTRIBUTE_PREFIX}slot_number" in names + + +@pytest.mark.parametrize("fork", ALL_FORKS, ids=lambda fork: fork.name()) +def test_for_fork_covers_every_engine_payload_attribute(fork: Fork) -> None: + """``for_fork`` must populate every attribute the fork requires.""" + attributes = PayloadAttributes.for_fork( + fork, + timestamp=2, + target_gas_limit=30_000_000, + slot_number=7, + ) + assert_attributes_cover_fork(attributes, fork) + if fork.engine_payload_attribute_slot_number(): + assert attributes.slot_number == 7 + if fork.engine_payload_attribute_target_gas_limit(): + assert attributes.target_gas_limit == 30_000_000 + + +@pytest.mark.parametrize("fork", ENGINE_FORKS, ids=lambda fork: fork.name()) +def test_fixture_payload_covers_every_engine_payload_attribute( + fork: Fork, +) -> None: + """ + Attributes rebuilt from a fixture payload must populate every + attribute the fork requires. + """ + env = Environment(**GENESIS_ENVIRONMENT_DEFAULTS).set_fork_requirements( + fork + ) + header = FixtureHeader.genesis(fork, env, Hash(0)) + payload = FixtureEngineNewPayload.from_fixture_header( + fork=fork, + header=header, + transactions=[], + withdrawals=[] if fork.header_withdrawals_required() else None, + requests=[] if fork.engine_new_payload_requests() else None, + block_access_list=( + BlockAccessList().rlp + if fork.engine_execution_payload_block_access_list() + else None + ), + ) + assert_attributes_cover_fork(payload.get_payload_attributes(), fork) From b802df1fccd94e9c7054d61ea925503dddf7c855 Mon Sep 17 00:00:00 2001 From: danceratopz <danceratopz@gmail.com> Date: Thu, 6 Aug 2026 14:56:14 +0200 Subject: [PATCH 208/233] fix(test-execute): register per-test hive test cases with the shared client (#3317) --- .../plugins/execute/rpc/hive.py | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/rpc/hive.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/rpc/hive.py index cc06ac10e3c..9498482afd3 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/rpc/hive.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/rpc/hive.py @@ -295,19 +295,20 @@ def test_case_description(request: pytest.FixtureRequest) -> str: @pytest.fixture(autouse=True) -def per_test_hive_test(hive_test: HiveTest) -> None: +def per_test_hive_test(client: Client, hive_test: HiveTest) -> None: """ Report each pytest test as an individual hive test case. - The client runs under the session-scoped base hive test; this - per-test entry only propagates the individual test result to hive. + The client runs under the session-scoped base hive test; register + it with each per-test entry so hive attaches the client and its + log segment to the individual test case and marks the base test + as the multi-test lifecycle owner. """ - del hive_test + hive_test.register_multi_test_client(client) @pytest.fixture(autouse=True, scope="session") def base_hive_test( - request: pytest.FixtureRequest, test_suite: HiveTestSuite, session_temp_folder: Path, ) -> Generator[HiveTest, None, None]: @@ -347,11 +348,17 @@ def base_hive_test( yield test - test_pass = True - test_details = "All tests have completed" - if request.session.testsfailed > 0: + # Individual results are reported by the per-test hive test cases, + # and hive marks this test as the multi-test lifecycle owner, so it + # always passes unless the client failed to start (the client + # fixture leaves its error file behind on startup failure). + client_error_file = session_temp_folder / "hive_client.err" + if client_error_file.exists(): test_pass = False - test_details = "One or more tests have failed" + test_details = "Failed to start the client." + else: + test_pass = True + test_details = "Multi-test client context completed." with FileLock(users_lock_file): with open(users_file, "r") as f: @@ -431,6 +438,11 @@ def client( with open(users_file, "w") as f: json.dump(users, f) + # Set on every worker (the client object is shared across xdist + # workers via JSON serialization) so that per-test hive test cases + # can register with the client. + client.multi_test = True + yield client with FileLock(users_lock_file): From a7103a7e531eee773cdaec3dc8b2be2913de4f2b Mon Sep 17 00:00:00 2001 From: milen <94537774+taratorio@users.noreply.github.com> Date: Thu, 6 Aug 2026 17:54:14 +0300 Subject: [PATCH 209/233] fix(tests): accept gas allowance error at EIP-7778 admission gate (#3330) --- .../test_gas_accounting.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/amsterdam/eip7778_block_gas_accounting_without_refunds/test_gas_accounting.py b/tests/amsterdam/eip7778_block_gas_accounting_without_refunds/test_gas_accounting.py index 38bf9296958..45b145c5212 100644 --- a/tests/amsterdam/eip7778_block_gas_accounting_without_refunds/test_gas_accounting.py +++ b/tests/amsterdam/eip7778_block_gas_accounting_without_refunds/test_gas_accounting.py @@ -614,7 +614,10 @@ def test_extra_tx_admission_uses_pre_refund_gas( blocks=[ Block( txs=[refund_tx, extra_tx], - exception=BlockException.GAS_USED_OVERFLOW, + exception=[ + BlockException.GAS_USED_OVERFLOW, + TransactionException.GAS_ALLOWANCE_EXCEEDED, + ], gas_limit=environment_gas_limit, ) ], From f6cf6a926c8272d963771d3f19a2b12f97867928 Mon Sep 17 00:00:00 2001 From: spencer <spencer.taylor-brown@ethereum.org> Date: Thu, 6 Aug 2026 18:57:02 +0200 Subject: [PATCH 210/233] chore(tooling): add test-docstring guidance to the write-test skill (#3328) --- .claude/commands/write-test.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.claude/commands/write-test.md b/.claude/commands/write-test.md index 1cda7e433ec..786fadae24c 100644 --- a/.claude/commands/write-test.md +++ b/.claude/commands/write-test.md @@ -75,6 +75,13 @@ Never hand-reconstruct a gas amount by summing `fork.gas_costs()` constants (`NE - Each EIP directory has `spec.py` with `ReferenceSpec(git_path=..., version=...)` and test files declaring `REFERENCE_SPEC_GIT_PATH` / `REFERENCE_SPEC_VERSION` - Use `conftest.py` for shared fixtures within an EIP directory +## Test Docstrings + +- Keep the docstring to a short summary of the scenario and the rule it pins — a sentence or two. +- Do not narrate the implementation: parametrized cases, gas decompositions, and case-by-case outcome walkthroughs are already expressed by the code. Prose restating them goes stale when the test changes and adds review burden. +- State only what the code cannot show (e.g. why a boundary value is chosen). Prefer a short inline comment at the relevant line over growing the docstring. +- Never hardcode numeric gas values in docstrings; name the constants instead. + ## Parametrization - `@pytest.mark.parametrize("name", [pytest.param(val, id="label"), ...])` with descriptive `id=` strings From 4f17c37ad75b4224c4a0e80f665b65d02d4ccc9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=94=A1=E4=BD=B3=E8=AA=A0=20Louis=20Tsai?= <72684086+LouisTsai-Csie@users.noreply.github.com> Date: Fri, 7 Aug 2026 17:22:11 +0800 Subject: [PATCH 211/233] refactor(test-rpc): unify batch request chunking (#3327) * refactor: align batch request interface * test: batch request chunking * Update packages/testing/src/execution_testing/rpc/rpc.py Co-authored-by: spencer <spencer.tb@ethereum.org> --------- Co-authored-by: spencer <spencer.tb@ethereum.org> --- docs/running_tests/execute/index.md | 3 +- .../plugins/execute/execute.py | 2 +- .../execute/rpc/chain_builder_eth_rpc.py | 4 +- .../plugins/execute/rpc/hive.py | 4 +- .../plugins/execute/rpc/remote.py | 6 +- .../execute/tests/test_execute_remote.py | 2 +- .../plugins/fill_stateful/fill_stateful.py | 2 +- .../plugins/shared/live_client_flags.py | 6 +- .../testing/src/execution_testing/rpc/rpc.py | 122 +++++++++--------- .../rpc/tests/test_batch_requests.py | 77 +++++++++++ 10 files changed, 153 insertions(+), 75 deletions(-) create mode 100644 packages/testing/src/execution_testing/rpc/tests/test_batch_requests.py diff --git a/docs/running_tests/execute/index.md b/docs/running_tests/execute/index.md index 6c13b2ad389..8fbaae1347b 100644 --- a/docs/running_tests/execute/index.md +++ b/docs/running_tests/execute/index.md @@ -58,6 +58,7 @@ When executing tests with many transactions (e.g., benchmark tests), the `execut - Transactions are sent in batches of up to 750 transactions by default - Each batch is sent and confirmed before the next batch begins - Progress logging shows batch number and transaction ranges +- The same limit caps every other batched JSON-RPC request (receipts, balances, code, account state) **CLI Configuration:** @@ -73,7 +74,7 @@ execute --max-tx-per-batch 1000 tests/ **Safety Threshold:** -A warning is logged when `max_transactions_per_batch` exceeds 1000, as this may cause RPC service instability or failures depending on the RPC endpoint's capacity. +A warning is logged when `max_batch_size` exceeds 1000, as this may cause RPC service instability or failures depending on the RPC endpoint's capacity. **Use Cases:** diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/execute.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/execute.py index b5a8830e4fd..e01fe7498df 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/execute.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/execute.py @@ -188,7 +188,7 @@ def pytest_html_report_title(report: Any) -> None: # NOTE: ``transactions_per_block``, ``default_gas_price``, ``dry_run``, -# ``max_transactions_per_batch``, ``use_testing_build_block``, +# ``max_batch_size``, ``use_testing_build_block``, # ``default_max_fee_per_gas``, ``default_max_priority_fee_per_gas``, # ``default_max_fee_per_blob_gas``, ``max_priority_fee_per_gas``, # ``max_fee_per_gas``, ``max_fee_per_blob_gas``, ``gas_price``, and diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/rpc/chain_builder_eth_rpc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/rpc/chain_builder_eth_rpc.py index 9097a3857fa..1577b4069b4 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/rpc/chain_builder_eth_rpc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/rpc/chain_builder_eth_rpc.py @@ -129,7 +129,7 @@ def __init__( get_payload_wait_time: float, initial_forkchoice_update_retries: int = 5, transaction_wait_timeout: int = 60, - max_transactions_per_batch: int | None = None, + max_batch_size: int | None = None, request_timeout: TimeoutType = DEFAULT_REQUEST_TIMEOUT, testing_rpc: TestingRPC | None = None, expected_genesis_header: FixtureHeader | None = None, @@ -138,7 +138,7 @@ def __init__( super().__init__( rpc_endpoint, transaction_wait_timeout=transaction_wait_timeout, - max_transactions_per_batch=max_transactions_per_batch, + max_batch_size=max_batch_size, request_timeout=request_timeout, ) self.fork = fork diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/rpc/hive.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/rpc/hive.py index 9498482afd3..58ce18f480f 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/rpc/hive.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/rpc/hive.py @@ -470,7 +470,7 @@ def eth_rpc( engine_rpc: EngineRPC, session_fork: Fork | TransitionFork, session_temp_folder: Path, - max_transactions_per_batch: int | None, + max_batch_size: int | None, use_testing_build_block: bool, base_pre_genesis: Tuple[Alloc, FixtureHeader], ) -> EthRPC: @@ -487,7 +487,7 @@ def eth_rpc( session_temp_folder=session_temp_folder, get_payload_wait_time=get_payload_wait_time, transaction_wait_timeout=tx_wait_timeout, - max_transactions_per_batch=max_transactions_per_batch, + max_batch_size=max_batch_size, testing_rpc=testing_rpc, expected_genesis_header=base_pre_genesis[1], ) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/rpc/remote.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/rpc/remote.py index fec6fe67e52..f28d90db83c 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/rpc/remote.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/rpc/remote.py @@ -175,7 +175,7 @@ def eth_rpc( engine_rpc: EngineRPC | None, session_fork: Fork | TransitionFork, session_temp_folder: Path, - max_transactions_per_batch: int | None, + max_batch_size: int | None, use_testing_build_block: bool, ) -> EthRPC: """Initialize ethereum RPC client for the execution client under test.""" @@ -189,7 +189,7 @@ def eth_rpc( return EthRPC( rpc_endpoint, transaction_wait_timeout=tx_wait_timeout, - max_transactions_per_batch=max_transactions_per_batch, + max_batch_size=max_batch_size, ) get_payload_wait_time = request.config.getoption("get_payload_wait_time") testing_rpc = None @@ -205,6 +205,6 @@ def eth_rpc( else session_temp_folder, get_payload_wait_time=get_payload_wait_time, transaction_wait_timeout=tx_wait_timeout, - max_transactions_per_batch=max_transactions_per_batch, + max_batch_size=max_batch_size, testing_rpc=testing_rpc, ) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/tests/test_execute_remote.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/tests/test_execute_remote.py index 471ad8244af..96b1b19e046 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/tests/test_execute_remote.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/tests/test_execute_remote.py @@ -307,7 +307,7 @@ def chain_builder_eth_rpc( session_temp_folder=session_temp_folder, get_payload_wait_time=1, transaction_wait_timeout=20, - max_transactions_per_batch=10, + max_batch_size=10, testing_rpc=TestingRPC(rpc_endpoint), ) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/fill_stateful/fill_stateful.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/fill_stateful/fill_stateful.py index e98f15a6573..743769544d9 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/fill_stateful/fill_stateful.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/fill_stateful/fill_stateful.py @@ -484,7 +484,7 @@ def max_gas_limit_per_test( return fork_at_genesis.transaction_gas_limit_cap() -# Other live-client fixtures (``max_transactions_per_batch``, +# Other live-client fixtures (``max_batch_size``, # ``default_*``, fee fields, ``dry_run``, ...) come from # ``shared.live_client_flags``. ``skip_cleanup`` from ``execute.pre_alloc``; # we force it on in ``pytest_configure``. diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/live_client_flags.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/live_client_flags.py index bd89330f736..bcb4da01fae 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/live_client_flags.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/live_client_flags.py @@ -144,7 +144,7 @@ def pytest_addoption(parser: pytest.Parser) -> None: type=int, default=None, help=( - "Maximum number of transactions to send in a single batch to " + "Maximum number of calls to send in a single batch request to " "the RPC. Default=750. Higher values may cause RPC instability." ), ) @@ -200,8 +200,8 @@ def dry_run(request: pytest.FixtureRequest) -> bool: @pytest.fixture(scope="session") -def max_transactions_per_batch(request: pytest.FixtureRequest) -> int | None: - """Return max transactions per batch, or None for default.""" +def max_batch_size(request: pytest.FixtureRequest) -> int | None: + """Return max calls per batch request, or None for default.""" return request.config.getoption("max_tx_per_batch") diff --git a/packages/testing/src/execution_testing/rpc/rpc.py b/packages/testing/src/execution_testing/rpc/rpc.py index 72e49c43a7a..f8018d39069 100644 --- a/packages/testing/src/execution_testing/rpc/rpc.py +++ b/packages/testing/src/execution_testing/rpc/rpc.py @@ -213,9 +213,13 @@ class BaseRPC: simulators. """ + OVERLOAD_THRESHOLD: int = 1000 + DEFAULT_MAX_BATCH_SIZE: int = 750 + namespace: ClassVar[str] response_validation_context: Any | None request_timeout: TimeoutType + max_batch_size: int def __init__( self, @@ -223,17 +227,29 @@ def __init__( *, response_validation_context: Any | None = None, request_timeout: TimeoutType = DEFAULT_REQUEST_TIMEOUT, + max_batch_size: int | None = None, ): """ Initialize BaseRPC class with the given url. - `request_timeout` bounds every request made through this client; + - `request_timeout` bounds every request made through this client; `None` disables the bound. + - `max_batch_size` caps how many calls in a single batch request. """ self.url = url self.request_id_counter = count(1) self.response_validation_context = response_validation_context self.request_timeout = request_timeout + if max_batch_size is not None and max_batch_size < 1: + raise ValueError( + f"max_batch_size must be >= 1, got {max_batch_size}" + ) + self.max_batch_size = max_batch_size or self.DEFAULT_MAX_BATCH_SIZE + if self.max_batch_size > self.OVERLOAD_THRESHOLD: + logger.warning( + f"max_batch_size ({max_batch_size}) exceeds safe threshold " + f"({self.OVERLOAD_THRESHOLD}) and may cause RPC instability." + ) self.session = requests.Session() def close(self) -> None: @@ -366,22 +382,13 @@ def post_request( return JSONRPCResponse.model_validate(response.json()) - def post_batch_request( + def _post_single_batch( self, - *, calls: Sequence[RPCCall], - extra_headers: Dict[str, str] | None = None, - timeout: TimeoutType = None, + extra_headers: Dict[str, str], + timeout: TimeoutType, ) -> List[JSONRPCResponse]: - """ - Send a JSON-RPC batch POST request to the client RPC server at port - defined in the url. - - A `timeout` of `None` applies the client's `request_timeout`. - """ - if extra_headers is None: - extra_headers = {} - + """Send one batch POST and return responses in request order.""" json_rpc_requests = [ self._build_json_rpc_request(call) for call in calls ] @@ -418,9 +425,36 @@ def post_batch_request( ) results.append(response_map[json_rpc_request.id]) - logger.info(f"Batch RPC: {len(results)} responses received") return results + def post_batch_request( + self, + *, + calls: Sequence[RPCCall], + extra_headers: Dict[str, str] | None = None, + timeout: TimeoutType = None, + ) -> List[JSONRPCResponse]: + """ + Send JSON-RPC batch POST requests to the client RPC server at port + defined in the url. + + Responses are returned in the same order as `calls`. + """ + if not calls: + return [] + if extra_headers is None: + extra_headers = {} + + responses: List[JSONRPCResponse] = [] + for start in range(0, len(calls), self.max_batch_size): + chunk = calls[start : start + self.max_batch_size] + responses.extend( + self._post_single_batch(chunk, extra_headers, timeout) + ) + + logger.info(f"Batch RPC: {len(responses)} responses received") + return responses + class BaseJwtRPC(BaseRPC): """ @@ -462,12 +496,8 @@ class EthRPC(BaseRPC): within EEST based hive simulators. """ - OVERLOAD_THRESHOLD: int = 1000 - DEFAULT_MAX_TRANSACTIONS_PER_BATCH: int = 750 - transaction_wait_timeout: int = 60 poll_interval: float = 1.0 # how often to poll for tx inclusion - max_transactions_per_batch: int = DEFAULT_MAX_TRANSACTIONS_PER_BATCH gas_information_stale_seconds: int @@ -482,7 +512,6 @@ def __init__( transaction_wait_timeout: int = 60, poll_interval: float | None = None, gas_information_stale_seconds: int = 12, - max_transactions_per_batch: int | None = None, **kwargs: Any, ) -> None: """Initialize JWT-authenticated RPC class with the given JWT secret.""" @@ -517,19 +546,6 @@ def __init__( "blobBaseFee": 0.0, } - # Transaction batching configuration - if max_transactions_per_batch is None: - max_transactions_per_batch = ( - self.DEFAULT_MAX_TRANSACTIONS_PER_BATCH - ) - self.max_transactions_per_batch = max_transactions_per_batch - if max_transactions_per_batch > self.OVERLOAD_THRESHOLD: - logger.warning( - f"max_transactions_per_batch ({max_transactions_per_batch}) " - f"exceeds the safe threshold ({self.OVERLOAD_THRESHOLD}). " - "This may cause RPC service instability or failures." - ) - def config(self, timeout: int | None = None) -> EthConfigResponse | None: """ `eth_config`: Returns information about a fork configuration of the @@ -838,41 +854,25 @@ def get_transaction_receipt( ).result_or_raise() def get_transaction_receipts( - self, - transaction_hashes: Sequence[Hash], - *, - chunk_size: int = 500, + self, transaction_hashes: Sequence[Hash] ) -> List[dict[str, Any] | None]: """ `eth_getTransactionReceipt` batch: receipts for many transactions. - Returns one entry per input hash, in the same order (see - `post_batch_request`, which maps responses back by request id). - - Requests are chunked because clients cap batch size -- geth's - `--rpc.batchrequestlimit` defaults to 1000 -- and because a single - response carrying thousands of receipts is several megabytes. + Returns one entry per input hash, in the same order. """ if not transaction_hashes: return [] - logger.info( - f"Batch requesting {len(transaction_hashes)} tx receipts " - f"in chunks of {chunk_size}" - ) - receipts: List[dict[str, Any] | None] = [] - for start in range(0, len(transaction_hashes), chunk_size): - chunk = transaction_hashes[start : start + chunk_size] - responses = self.post_batch_request( - calls=[ - RPCCall( - method="getTransactionReceipt", - params=[f"{tx_hash}"], - ) - for tx_hash in chunk - ] + logger.info(f"Batch requesting {len(transaction_hashes)} tx receipts") + calls = [ + RPCCall( + method="getTransactionReceipt", + params=[f"{tx_hash}"], ) - receipts.extend(r.result_or_raise() for r in responses) - return receipts + for tx_hash in transaction_hashes + ] + responses = self.post_batch_request(calls=calls) + return [r.result_or_raise() for r in responses] def get_storage_at( self, @@ -1297,7 +1297,7 @@ def send_wait_transactions( block. Transactions are sent in batches to avoid RPC overload. """ results: List[Any] = [] - batch_size = self.max_transactions_per_batch + batch_size = self.max_batch_size total_txs = len(transactions) for i in range(0, total_txs, batch_size): diff --git a/packages/testing/src/execution_testing/rpc/tests/test_batch_requests.py b/packages/testing/src/execution_testing/rpc/tests/test_batch_requests.py new file mode 100644 index 00000000000..91759728a48 --- /dev/null +++ b/packages/testing/src/execution_testing/rpc/tests/test_batch_requests.py @@ -0,0 +1,77 @@ +"""Test the batch request chunking of `BaseRPC` clients.""" + +from typing import Any, Iterator +from unittest.mock import MagicMock, patch + +import pytest + +from execution_testing.base_types import Hash +from execution_testing.rpc import EthRPC, RPCCall + + +def echo_batch_response(*_args: Any, **kwargs: Any) -> MagicMock: + """Return a mock batch response echoing each request id as its result.""" + response = MagicMock() + response.json.return_value = [ + {"jsonrpc": "2.0", "id": request["id"], "result": hex(request["id"])} + for request in kwargs["json"] + ] + return response + + +@pytest.fixture +def max_batch_size() -> int | None: + """Batch size cap of the client under test; `None` uses the default.""" + return None + + +@pytest.fixture +def rpc(max_batch_size: int | None) -> EthRPC: + """Return an `eth` RPC client pointed at a local endpoint.""" + return EthRPC("http://localhost:8545", max_batch_size=max_batch_size) + + +@pytest.fixture +def post(rpc: EthRPC) -> Iterator[MagicMock]: + """Patch the client's HTTP POST to echo back every batched call.""" + with patch.object( + rpc.session, "post", side_effect=echo_batch_response + ) as post_mock: + yield post_mock + + +@pytest.mark.parametrize("max_batch_size", [2]) +def test_batch_request_is_chunked(rpc: EthRPC, post: MagicMock) -> None: + """Calls beyond `max_batch_size` are split over several requests.""" + calls = [RPCCall(method="blockNumber") for _ in range(5)] + responses = rpc.post_batch_request(calls=calls) + chunk_sizes = [len(c.kwargs["json"]) for c in post.call_args_list] + assert chunk_sizes == [2, 2, 1] + assert [r.result for r in responses] == [hex(i) for i in range(1, 6)] + + +@pytest.mark.parametrize("max_batch_size", [5]) +def test_batch_request_within_limit_is_a_single_request( + rpc: EthRPC, post: MagicMock +) -> None: + """A call list at the limit is sent as one request.""" + calls = [RPCCall(method="blockNumber") for _ in range(5)] + rpc.post_batch_request(calls=calls) + assert post.call_count == 1 + + +def test_empty_batch_request_is_not_sent(rpc: EthRPC, post: MagicMock) -> None: + """An empty call list short-circuits without an HTTP request.""" + assert rpc.post_batch_request(calls=[]) == [] + post.assert_not_called() + + +@pytest.mark.parametrize("max_batch_size", [2]) +def test_chunked_receipts_keep_request_order( + rpc: EthRPC, post: MagicMock +) -> None: + """Chunked receipts are returned in the order of the input hashes.""" + hashes = [Hash(i) for i in range(1, 6)] + receipts = rpc.get_transaction_receipts(hashes) + assert post.call_count == 3 + assert receipts == [hex(i) for i in range(1, 6)] From 343274cc0f43962ff39a648e2b2614bf227f8d1f Mon Sep 17 00:00:00 2001 From: spencer <spencer.taylor-brown@ethereum.org> Date: Sat, 8 Aug 2026 02:05:39 +0200 Subject: [PATCH 212/233] fix(tests): enhance & un-skip Amsterdam ported static depth-recursion tests (Pt. 2a) (#3264) * fix(tests): enhance & un-skip Amsterdam ported static depth-recursion tests (Pt. 2a) * fix(test-forks): Fix contract creating tx gas cost bug * refactor(tests): Minor fixes --------- Co-authored-by: marioevz <marioevz@gmail.com> --- .claude/commands/enhance-ported-test.md | 124 +++++- .../forks/forks/eips/shanghai/eip_3860.py | 48 ++- .../execution_testing/forks/forks/forks.py | 7 +- .../forks/tests/test_forks.py | 4 +- tests/ported_static/amsterdam_skip_list.txt | 39 +- ...llcode_in_initcode_to_existing_contract.py | 232 ++++------- ...o_existing_contract_with_value_transfer.py | 93 ----- .../test_call1024_oog.py | 362 ++++++++++++------ .../test_callcode1024_oog.py | 131 ------- ..._ask_more_gas_then_transaction_provided.py | 202 +++++----- .../test_call1024_oog.py | 130 ------- .../test_delegatecall1024_oog.py | 84 ---- ...tecall_in_initcode_to_existing_contract.py | 141 +++---- ...more_gas_on_depth2_then_transaction_has.py | 134 +++---- .../test_transaction64_rule.py | 111 ++++++ .../test_transaction64_rule_d64e0.py | 84 ---- .../test_transaction64_rule_d64m1.py | 84 ---- .../test_transaction64_rule_d64p1.py | 84 ---- ...ransaction_has_with_mem_expanding_calls.py | 156 +++++--- .../stSystemOperationsTest/test_ab_acalls0.py | 259 +++++++++---- .../stSystemOperationsTest/test_ab_acalls3.py | 236 +++++++++--- .../test_call_recursive_bomb3.py | 232 ++++++++--- 22 files changed, 1495 insertions(+), 1482 deletions(-) delete mode 100644 tests/ported_static/stCallCodes/test_callcode_in_initcode_to_existing_contract_with_value_transfer.py delete mode 100644 tests/ported_static/stCallCreateCallCodeTest/test_callcode1024_oog.py delete mode 100644 tests/ported_static/stDelegatecallTestHomestead/test_call1024_oog.py delete mode 100644 tests/ported_static/stDelegatecallTestHomestead/test_delegatecall1024_oog.py create mode 100644 tests/ported_static/stEIP150Specific/test_transaction64_rule.py delete mode 100644 tests/ported_static/stEIP150Specific/test_transaction64_rule_d64e0.py delete mode 100644 tests/ported_static/stEIP150Specific/test_transaction64_rule_d64m1.py delete mode 100644 tests/ported_static/stEIP150Specific/test_transaction64_rule_d64p1.py diff --git a/.claude/commands/enhance-ported-test.md b/.claude/commands/enhance-ported-test.md index f6d3f6852e5..0d0404b2a04 100644 --- a/.claude/commands/enhance-ported-test.md +++ b/.claude/commands/enhance-ported-test.md @@ -42,11 +42,12 @@ so a failure is attributable. - **Checkpoint / done:** fill the whole `valid_from` range (omit `--fork`) so all deployed forks are exercised. - **Probe the future fork:** explicitly `--fork Amsterdam` (or the latest fork - that enables new EIPs). A ported test listed in `amsterdam_skip_list.txt` will - always show `sss` there — to see its *real* behavior, temporarily remove its - entry from that file, fill, then restore (or, once fixed, remove it for good — - see Finishing). A gas/state-cost change there is the most likely future - breakage. + that enables new EIPs). A gas/state-cost change there is the most likely + future breakage. (Historical note: broken tests used to be parked in a + `tests/ported_static/amsterdam_skip_list.txt` consumed by a local conftest; + the list was emptied and both were removed. If a future fork's repricing + breaks tests en masse, the same parking pattern — a substring-matched skip + list plus a `pytest_collection_modifyitems` hook — is in git history.) - **`fill` output:** writes to `./fixtures` (`--clean` resets it), or pass `--output <dir>` for a scratch location. Do **not** use `-o` — that is pytest's `--override-ini`, not the output dir. @@ -77,8 +78,8 @@ gas the tx receives, so the body executes fully. See `write-test.md` "Transactio - **Gas-snapshot tests are gas-sensitive.** If the post asserts a stored `GAS` reading or a `SUB(@gas_before, GAS)` delta (legacy slots `0` / `0x64`), the test *measures gas* — handle it under step 10 (preserve via `CodeGasMeasure`), - do not just strip `gas_limit`. This is the dominant `amsterdam_skip_list.txt` - shape: the stored gas value is exactly what EIP-8037 re-prices and breaks. + do not just strip `gas_limit`. This was the dominant skip-list shape: + the stored gas value is exactly what EIP-8037 re-prices and breaks. - **EIP-8037 caveat:** when you omit `gas_limit` on a test that *measures* an operation incurring **state gas** (account creation, storage writes), add `state_gas_reservoir=0` to the tx, or that state gas is silently dropped from @@ -294,8 +295,8 @@ ties a `CREATE`'s `size` operand to the memory/gas math that depends on it. on `test_make_money`. ### 10. (Gas-subject / gas-snapshot tests) Replace hardcoded gas with dynamic calculation -Covers both tests that *assert* a gas amount and the dominant -`amsterdam_skip_list.txt` shape: a legacy `GAS` snapshot / `SUB(@gas_before, +Covers both tests that *assert* a gas amount and the dominant broken-port +shape: a legacy `GAS` snapshot / `SUB(@gas_before, GAS)` delta stored to slot `0`/`0x64`. That stored value is *why* EIP-8037 breaks the test, but it is real coverage — **preserve and fork-robustify it, do not drop it.** @@ -386,6 +387,20 @@ previous_bytes=)`; EIP-3860 init-code words → `fork.gas_costs().CODE_INIT_PER_ * ceil(size/32)`. You can also call `.gas_cost` / `.regular_cost` / `.state_cost` on exactly the measured bytecode. +**Reservoir-less sub-calls pay state gas from their regular grant.** With +the tx reservoir at 0, a sub-frame's state charges spill from its own +`gas_left` — a delegate that does one first-set SSTORE needs its *whole* +~111k inside the forwarded grant on Amsterdam, not just the ~13k regular +part. Size derived sub-call budgets from the callee composite's full +`gas_cost(fork)`. Corollaries: (a) a *failed* sub-frame contributes its +entire forfeited grant to the parent's measured window, not its "cost"; +(b) `SSTORE(flag, <call>)` silently degrades to a ~3k no-op store when +the call fails — the flag reads 0 and no state gas is charged, which can +mask a broken callee behind a plausible-looking measurement. Validated on +`test_new_gas_price_for_codes` (delegate budget derived; failed value +calls return their stipends: subtract one `CALL_STIPEND` per failed +value-bearing call from window measurements). + **Nested / callee-side measurements.** When the measured op is a `CALL` whose callee does real work, the measured cost = `call_code.gas_cost(fork) + callee_code.gas_cost(fork)` (the CALL's own cost plus what the callee consumed). @@ -402,6 +417,88 @@ transition, not the magnitude — which also breaks the `forward_gas`/`new_value circularity.) Set `state_gas_reservoir=0` so the state gas is captured. Validated on `test_raw_call_gas`. +**An expensive store after a callee that eats all forwarded gas — pre-write +the slot.** When a frame must SSTORE a result *after* a subcall that +deliberately consumes its whole 63/64 grant (an OOG-probe callee), the frame +retains only 1/64 — under EIP-8037 that cannot afford a cold zero→nonzero +store (~111k), and pre-8037 it often couldn't afford the cold 2.2k either +(making the ported `{slot: 0}` expectation vacuous: caller-OOG and +callee-failure were indistinguishable). Fix: write a sentinel to the slot +*before* the call (paying cold + state with the full budget), then store +`BASE + result` after it — now a dirty-warm write (100 gas) the retention +always covers, and the three outcomes (success `BASE+1`, failure `BASE`, +caller OOG `sentinel`) are all distinct. Validated on +`test_static_execute_call_that_ask_fore_gas_then_trabsaction_has`. +**Caveat — EIP-2200's stipend rule caps this trick.** Any SSTORE (even a +100-gas dirty-warm one) exceptionally halts unless `gas_left > 2300` +(Istanbul+), so the 1/64 retention must exceed ~2400, i.e. the pre-call +budget must exceed ~154k. When the scenario *requires* a smaller budget +(e.g. a starved arm whose forwarded gas must undercut the callee's cost), +no post-call SSTORE is possible at all: write the sentinel *before* the +call and put nothing but a `POP` after it — frame completion (the account +persists with the sentinel) plus the callee-side observable already +separate the outcomes. Validated on +`test_contract_creation_make_call_that_ask_more_gas_then_transaction_provided`. + +**Refund-cap derivations need the EIP-7623 kwarg.** The EIP-3529 cap's +base is the gas deducted before execution, which excludes the calldata +floor: pass `return_cost_deducted_prior_execution=True` to the intrinsic +calculator whenever the tx has calldata, or the derived `executed` (and +the cap) overstate. Validated on `test_refund_suicide50procent_cap`. + +**A CREATE address collision burns the child's gas allowance** (the +EIP-684 path): the withheld child grant is consumed, nothing is created, +and under EIP-8037 the new-account state charge is refunded. Useful to +build always-failing creator frames with predictable consumption. +Validated on `test_revert_depth_create_address_collision`. + +**Loop-to-depth-1024 cannot replace loop-to-OOG.** With 63/64 +attenuation, reaching depth 1024 needs ~e^16 × the terminal gas — no +legal budget gets there. For call-loop depth tests the honest shape is a +fixed named budget with per-gas-schedule-era pinned depth counts, each +shift explained (±1 frame ≈ 64·ln(cost ratio)). Validated on +`test_loop_calls_depth_then_revert`. + +**Framework wart: the SSTORE dirty-rewrite composite prices 100 on every +fork**, but Constantinople/Petersburg charge 5,000 for a dirty re-store — +a derived budget that must survive pre-Istanbul forks needs an explicit +headroom constant for it (named, commented). Observed on +`test_revert_depth_create_address_collision`'s ConstantinopleFix sweep. + +**EIP-8037 repriced the code deposit's regular part — boundaries beware.** +On 8037 forks the deposit charges only the keccak word cost +(`OPCODE_KECCAK256_PER_WORD * ceil32(len)/32`, ~6 gas) as regular gas plus +`len * 1530` state; `fork.gas_costs().CODE_DEPOSIT_PER_BYTE` (200) is the +*pre-8037* constant. Using 200/byte in a *sufficiency* budget merely +overshoots (safe); using it in a one-gas-short *boundary* silently funds +the deposit on Amsterdam. Branch on `fork.is_eip_enabled(8037)` for exact +deposit boundaries. Validated on +`test_create_oo_gafter_init_code_returndata_size`. + +**Match the intrinsic calculator's kwargs to the transaction's shape.** +`fork.transaction_intrinsic_cost_calculator()()` defaults to +`sends_value=False`; under EIP-2780 a value-bearing transaction's intrinsic +includes the folded value-transfer cost (~5.9k), so a derived budget or +GAS-observation formula silently skews by that amount on Amsterdam only. +Pass `sends_value=True` when the tx carries value — or drop an incidental +tx `value` entirely (step 5) so the default holds. Validated on +`test_store_gas_on_create`. + +**A creation transaction's top frame pays new-account state gas +(EIP-8037) — but only for a fresh target.** When deriving a create-tx +budget, the intrinsic calculator does not include the created account's +state gas — add +`fork.transaction_top_frame_state_gas(contract_creation=True)` (183,600 on +Amsterdam, 0 before) or the whole creation silently OOGs only on the +future fork. Exception: `prepare_dispatch` charges it only when the +target's *pre-state* account is `EMPTY_ACCOUNT` — a prefunded create +address pays nothing (validated on +`test_out_of_gas_prefunded_contract_creation`, whose budgets omit the +term). A nested CREATE's new-account state is charged to the parent +before the 63/64 withhold and refunded if the child fails, so a derived +budget must cover its *peak* (use the composite `gas_cost(fork)`), even +on paths where the net is zero. + **Measuring forwarded gas / the EIP-150 63/64 rule (the `*_gas_ask` shape).** Ported fillers probe "how much gas does a subcall receive when it asks for more than is available" by pinning an absolute forwarded amount — fork-fragile, @@ -499,11 +596,10 @@ Not yet covered by a validated walkthrough; figure out and append when hit: ## Finishing -**Remove the skip-list entry.** Once the test passes on the future fork, delete -its line from `tests/ported_static/amsterdam_skip_list.txt` and decrement both -its per-directory count header (`# stXxx (N)`) and the `# Total entries:` count. -Confirm with a full-range fill (`--fork` omitted) with the entry gone — that is -the definition of done. +**Confirm with a full-range fill** (`--fork` omitted) — every deployed fork +green is the definition of done. (If a skip list is ever reintroduced for a +future fork, also delete the test's entry and keep its count headers +accurate.) **Final sweep checklist** — each of these has been missed in practice; check them one by one before calling the test done: diff --git a/packages/testing/src/execution_testing/forks/forks/eips/shanghai/eip_3860.py b/packages/testing/src/execution_testing/forks/forks/eips/shanghai/eip_3860.py index 128c162cc9d..01a5c2d6eda 100644 --- a/packages/testing/src/execution_testing/forks/forks/eips/shanghai/eip_3860.py +++ b/packages/testing/src/execution_testing/forks/forks/eips/shanghai/eip_3860.py @@ -7,10 +7,16 @@ https://eips.ethereum.org/EIPS/eip-3860 """ +from typing import List, Sized + +from execution_testing.base_types import AccessList, Bytes +from execution_testing.base_types.conversions import BytesConvertible from execution_testing.vm import OpcodeBase -from ....base_fork import BaseFork +from .....recipient_type import RecipientType +from ....base_fork import BaseFork, TransactionIntrinsicCostCalculator from ....gas_costs import GasCosts +from ...helpers import ceiling_division class EIP3860(BaseFork): @@ -21,6 +27,46 @@ def max_initcode_size(cls) -> int: """Initcode size is limited.""" return 0xC000 + @classmethod + def transaction_intrinsic_cost_calculator( + cls, + ) -> TransactionIntrinsicCostCalculator: + """ + The intrinsic cost of a creation transaction meters its init code. + """ + super_fn = super(EIP3860, cls).transaction_intrinsic_cost_calculator() + gas_costs = cls.gas_costs() + + def fn( + *, + calldata: BytesConvertible = b"", + contract_creation: bool = False, + access_list: List[AccessList] | None = None, + authorization_list_or_count: Sized | int | None = None, + return_cost_deducted_prior_execution: bool = False, + sends_value: bool = False, + recipient_type: RecipientType = RecipientType.CONTRACT, + ) -> int: + intrinsic_cost: int = super_fn( + calldata=calldata, + contract_creation=contract_creation, + access_list=access_list, + authorization_list_or_count=authorization_list_or_count, + return_cost_deducted_prior_execution=( + return_cost_deducted_prior_execution + ), + sends_value=sends_value, + recipient_type=recipient_type, + ) + if contract_creation: + intrinsic_cost += ( + gas_costs.CODE_INIT_PER_WORD + * ceiling_division(len(Bytes(calldata)), 32) + ) + return intrinsic_cost + + return fn + @classmethod def _calculate_create_gas( cls, opcode: OpcodeBase, gas_costs: GasCosts diff --git a/packages/testing/src/execution_testing/forks/forks/forks.py b/packages/testing/src/execution_testing/forks/forks/forks.py index b4f8492993d..d30f8fd376f 100644 --- a/packages/testing/src/execution_testing/forks/forks/forks.py +++ b/packages/testing/src/execution_testing/forks/forks/forks.py @@ -872,6 +872,7 @@ def fn( ) -> int: del return_cost_deducted_prior_execution del sends_value, recipient_type + del contract_creation assert access_list is None, ( f"Access list is not supported in {cls.name()}" @@ -882,12 +883,6 @@ def fn( intrinsic_cost: int = gas_costs.TX_BASE - if contract_creation: - intrinsic_cost += ( - gas_costs.CODE_INIT_PER_WORD - * ceiling_division(len(Bytes(calldata)), 32) - ) - return intrinsic_cost + calldata_gas_calculator(data=calldata) return fn diff --git a/packages/testing/src/execution_testing/forks/tests/test_forks.py b/packages/testing/src/execution_testing/forks/tests/test_forks.py index c9c06f0a7b4..0a3864a177c 100644 --- a/packages/testing/src/execution_testing/forks/tests/test_forks.py +++ b/packages/testing/src/execution_testing/forks/tests/test_forks.py @@ -402,6 +402,7 @@ def test_tx_types() -> None: # noqa: D103 @pytest.mark.parametrize( "fork", [ + pytest.param(Shanghai, id="Shanghai"), pytest.param(Berlin, id="Berlin"), pytest.param(Istanbul, id="Istanbul"), pytest.param(Homestead, id="Homestead"), @@ -434,7 +435,8 @@ def test_tx_intrinsic_gas_functions( # noqa: D103 if create_tx: if fork >= Homestead: intrinsic_gas += 32000 - intrinsic_gas += 2 + if fork >= Shanghai: + intrinsic_gas += 2 assert ( fork.transaction_intrinsic_cost_calculator()( calldata=calldata, diff --git a/tests/ported_static/amsterdam_skip_list.txt b/tests/ported_static/amsterdam_skip_list.txt index 7aba6f38bb0..92fb465eda3 100644 --- a/tests/ported_static/amsterdam_skip_list.txt +++ b/tests/ported_static/amsterdam_skip_list.txt @@ -8,7 +8,7 @@ # Entries are substring-matched against each pytest nodeid (after # stripping the fixture-format suffix in conftest.py). # -# Total entries: 153 +# Total entries: 130 # stAttackTest (1) stAttackTest/test_crashing_transaction.py::test_crashing_transaction[fork_Amsterdam] @@ -19,20 +19,9 @@ stBadOpcode/test_measure_gas.py::test_measure_gas[fork_Amsterdam-CREATE] stBadOpcode/test_operation_diff_gas.py::test_operation_diff_gas[fork_Amsterdam-CREATE2] stBadOpcode/test_operation_diff_gas.py::test_operation_diff_gas[fork_Amsterdam-CREATE] -# stCallCodes (3) -stCallCodes/test_callcode_in_initcode_to_existing_contract.py::test_callcode_in_initcode_to_existing_contract[fork_Amsterdam-d0] -stCallCodes/test_callcode_in_initcode_to_existing_contract.py::test_callcode_in_initcode_to_existing_contract[fork_Amsterdam-d1] -stCallCodes/test_callcode_in_initcode_to_existing_contract_with_value_transfer.py::test_callcode_in_initcode_to_existing_contract_with_value_transfer[fork_Amsterdam] - -# stCallCreateCallCodeTest (11) -stCallCreateCallCodeTest/test_call1024_oog.py::test_call1024_oog[fork_Amsterdam--g0] -stCallCreateCallCodeTest/test_call1024_oog.py::test_call1024_oog[fork_Amsterdam--g1] -stCallCreateCallCodeTest/test_call1024_oog.py::test_call1024_oog[fork_Amsterdam--g2] -stCallCreateCallCodeTest/test_call1024_oog.py::test_call1024_oog[fork_Amsterdam--g3] -stCallCreateCallCodeTest/test_callcode1024_oog.py::test_callcode1024_oog[fork_Amsterdam--g0] -stCallCreateCallCodeTest/test_callcode1024_oog.py::test_callcode1024_oog[fork_Amsterdam--g1] -stCallCreateCallCodeTest/test_contract_creation_make_call_that_ask_more_gas_then_transaction_provided.py::test_contract_creation_make_call_that_ask_more_gas_then_transaction_provided[fork_Amsterdam--g0] -stCallCreateCallCodeTest/test_contract_creation_make_call_that_ask_more_gas_then_transaction_provided.py::test_contract_creation_make_call_that_ask_more_gas_then_transaction_provided[fork_Amsterdam--g1] +# stCallCodes (0) + +# stCallCreateCallCodeTest (3) stCallCreateCallCodeTest/test_create_name_registrator_per_txs_not_enough_gas.py::test_create_name_registrator_per_txs_not_enough_gas[fork_Amsterdam--g0] stCallCreateCallCodeTest/test_create_name_registrator_per_txs_not_enough_gas.py::test_create_name_registrator_per_txs_not_enough_gas[fork_Amsterdam--g1] stCallCreateCallCodeTest/test_create_name_registrator_pre_store1_not_enough_gas.py::test_create_name_registrator_pre_store1_not_enough_gas[fork_Amsterdam] @@ -111,20 +100,12 @@ stCreateTest/test_transaction_collision_to_empty_but_code.py::test_transaction_c stCreateTest/test_transaction_collision_to_empty_but_nonce.py::test_transaction_collision_to_empty_but_nonce[fork_Amsterdam--g1-v0] stCreateTest/test_transaction_collision_to_empty_but_nonce.py::test_transaction_collision_to_empty_but_nonce[fork_Amsterdam--g1-v1] -# stDelegatecallTestHomestead (4) -stDelegatecallTestHomestead/test_call1024_oog.py::test_call1024_oog[fork_Amsterdam--g0] -stDelegatecallTestHomestead/test_call1024_oog.py::test_call1024_oog[fork_Amsterdam--g1] -stDelegatecallTestHomestead/test_delegatecall1024_oog.py::test_delegatecall1024_oog[fork_Amsterdam] -stDelegatecallTestHomestead/test_delegatecall_in_initcode_to_existing_contract.py::test_delegatecall_in_initcode_to_existing_contract[fork_Amsterdam] +# stDelegatecallTestHomestead (0) -# stEIP150Specific (7) -stEIP150Specific/test_call_ask_more_gas_on_depth2_then_transaction_has.py::test_call_ask_more_gas_on_depth2_then_transaction_has[fork_Amsterdam] +# stEIP150Specific (3) stEIP150Specific/test_create_and_gas_inside_create.py::test_create_and_gas_inside_create[fork_Amsterdam] stEIP150Specific/test_delegate_call_on_eip.py::test_delegate_call_on_eip[fork_Amsterdam] stEIP150Specific/test_new_gas_price_for_codes.py::test_new_gas_price_for_codes[fork_Amsterdam] -stEIP150Specific/test_transaction64_rule_d64e0.py::test_transaction64_rule_d64e0[fork_Amsterdam] -stEIP150Specific/test_transaction64_rule_d64m1.py::test_transaction64_rule_d64m1[fork_Amsterdam] -stEIP150Specific/test_transaction64_rule_d64p1.py::test_transaction64_rule_d64p1[fork_Amsterdam] # stEIP150singleCodeGasPrices (2) stEIP150singleCodeGasPrices/test_gas_cost.py::test_gas_cost[fork_Amsterdam-d40] @@ -145,8 +126,7 @@ stInitCodeTest/test_out_of_gas_prefunded_contract_creation.py::test_out_of_gas_p stInitCodeTest/test_out_of_gas_prefunded_contract_creation.py::test_out_of_gas_prefunded_contract_creation[fork_Amsterdam--g1] stInitCodeTest/test_out_of_gas_prefunded_contract_creation.py::test_out_of_gas_prefunded_contract_creation[fork_Amsterdam--g2] -# stMemExpandingEIP150Calls (4) -stMemExpandingEIP150Calls/test_call_ask_more_gas_on_depth2_then_transaction_has_with_mem_expanding_calls.py::test_call_ask_more_gas_on_depth2_then_transaction_has_with_mem_expanding_calls[fork_Amsterdam] +# stMemExpandingEIP150Calls (3) stMemExpandingEIP150Calls/test_call_goes_oog_on_second_level_with_mem_expanding_calls.py::test_call_goes_oog_on_second_level_with_mem_expanding_calls[fork_Amsterdam] stMemExpandingEIP150Calls/test_create_and_gas_inside_create_with_mem_expanding_calls.py::test_create_and_gas_inside_create_with_mem_expanding_calls[fork_Amsterdam] stMemExpandingEIP150Calls/test_new_gas_price_for_codes_with_mem_expanding_calls.py::test_new_gas_price_for_codes_with_mem_expanding_calls[fork_Amsterdam] @@ -194,10 +174,7 @@ stStaticCall/test_static_create_empty_contract_and_call_it_0wei.py::test_static_ stStaticCall/test_static_create_empty_contract_with_storage_and_call_it_0wei.py::test_static_create_empty_contract_with_storage_and_call_it_0wei[fork_Amsterdam] stStaticCall/test_static_execute_call_that_ask_fore_gas_then_trabsaction_has.py::test_static_execute_call_that_ask_fore_gas_then_trabsaction_has[fork_Amsterdam-d0] -# stSystemOperationsTest (5) -stSystemOperationsTest/test_ab_acalls0.py::test_ab_acalls0[fork_Amsterdam] -stSystemOperationsTest/test_ab_acalls3.py::test_ab_acalls3[fork_Amsterdam] -stSystemOperationsTest/test_call_recursive_bomb3.py::test_call_recursive_bomb3[fork_Amsterdam] +# stSystemOperationsTest (2) stSystemOperationsTest/test_double_selfdestruct_touch_paris.py::test_double_selfdestruct_touch_paris[fork_Amsterdam--v1] stSystemOperationsTest/test_double_selfdestruct_touch_paris.py::test_double_selfdestruct_touch_paris[fork_Amsterdam--v2] diff --git a/tests/ported_static/stCallCodes/test_callcode_in_initcode_to_existing_contract.py b/tests/ported_static/stCallCodes/test_callcode_in_initcode_to_existing_contract.py index 900c2e78bda..e8abac3f1f2 100644 --- a/tests/ported_static/stCallCodes/test_callcode_in_initcode_to_existing_contract.py +++ b/tests/ported_static/stCallCodes/test_callcode_in_initcode_to_existing_contract.py @@ -1,199 +1,121 @@ """ -Callcode inside create/create2 contract init to existing contract. +Verify a CALLCODE made from inside init code to an existing contract. + +The existing contract's code runs in the created account's context: its +storage write lands there, while the existing contract keeps its own +storage and receives no value. Parametrized over the endowment and the +CALLCODE value, including an endowment too small for the transfer, so +the CALLCODE fails. Ported from: state_tests/stCallCodes/callcodeInInitcodeToExistingContractFiller.json + +@manually-enhanced: Do not overwrite. The calldata-dispatch entry +contract is collapsed into a direct transaction to the create-runner, +sub-calls forward all gas (EIP-8037-proof), the post pins both the +created and the existing account, and the value cases are parametrized. +Widened down to TangerineWhistle, the EIP-150 floor for forwarding all +gas; CREATE2 rejoins at Constantinople. """ import pytest from execution_testing import ( - EOA, Account, - Address, Alloc, - Environment, - Hash, + Fork, + Macros, + Op, + Opcodes, StateTestFiller, Transaction, compute_create_address, ) -from execution_testing.forks import Fork -from execution_testing.vm import Op - -from tests.ported_static.post_state_resolution import ( - resolve_expect_post, -) REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +SUCCESS_FLAG_SLOT = 1 +DELEGATE_SLOT = 2 + @pytest.mark.ported_from( [ "state_tests/stCallCodes/callcodeInInitcodeToExistingContractFiller.json" # noqa: E501 ], ) -@pytest.mark.valid_from("Cancun") +@pytest.mark.valid_from("TangerineWhistle") @pytest.mark.parametrize( - "d, g, v", + "create_endowment,callcode_value", [ - pytest.param( - 0, - 0, - 0, - id="d0", - ), - pytest.param( - 1, - 0, - 0, - id="d1", - ), + pytest.param(1, 1, id="1_wei_value"), + pytest.param(0, 0, id="zero_value"), + pytest.param(0, 1, id="1_wei_callcode_value_with_zero_balance"), ], ) -@pytest.mark.pre_alloc_mutable +@pytest.mark.with_all_create_opcodes def test_callcode_in_initcode_to_existing_contract( state_test: StateTestFiller, pre: Alloc, fork: Fork, - d: int, - g: int, - v: int, + create_opcode: Opcodes, + create_endowment: int, + callcode_value: int, ) -> None: - """Callcode inside create/create2 contract init to existing contract.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0x1100000000000000000000000000000000000000) - contract_1 = Address(0x1000000000000000000000000000000000000000) - contract_2 = Address(0x2000000000000000000000000000000000000000) - contract_3 = Address(0x1000000000000000000000000000000000000001) - sender = EOA( - key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8 - ) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=1000000, + """Verify a CALLCODE in init code runs in the created account.""" + existing = pre.deploy_contract( + code=Op.SSTORE(key=DELEGATE_SLOT, value=1) + Op.STOP, ) - pre[sender] = Account(balance=0x2386F26FC10000) - # Source: lll - # { (CALL 300000 (CALLDATALOAD 0) 0 0 0 0 0) } - contract_0 = pre.deploy_contract( # noqa: F841 - code=Op.CALL( - gas=0x493E0, - address=Op.CALLDATALOAD(offset=0x0), - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ) - + Op.STOP, - nonce=0, - address=Address(0x1100000000000000000000000000000000000000), # noqa: E501 - ) - # Source: lll - # { (SSTORE 2 1) } - contract_3 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x2, value=0x1) + Op.STOP, - nonce=0, - address=Address(0x1000000000000000000000000000000000000001), # noqa: E501 - ) - # Source: lll - # {(seq (CREATE2 1 0 (lll (seq [[1]] (CALLCODE 50000 0x1000000000000000000000000000000000000001 1 0 0 0 0)) 0) 0) )} # noqa: E501 - contract_2 = pre.deploy_contract( # noqa: F841 - code=Op.PUSH1[0x0] - + Op.PUSH1[0x27] - + Op.CODECOPY(dest_offset=0x0, offset=0x11, size=Op.DUP1) - + Op.PUSH1[0x0] - + Op.PUSH1[0x1] - + Op.CREATE2 - + Op.STOP - + Op.INVALID - + Op.SSTORE( - key=0x1, - value=Op.CALLCODE( - gas=0xC350, - address=0x1000000000000000000000000000000000000001, - value=0x1, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), + initcode = ( + Op.SSTORE( + key=SUCCESS_FLAG_SLOT, + value=Op.CALLCODE(address=existing, value=callcode_value), ) - + Op.STOP, - balance=10000, - nonce=0, - address=Address(0x2000000000000000000000000000000000000000), # noqa: E501 - ) - # Source: lll - # {(seq (CREATE 1 0 (lll (seq [[1]] (CALLCODE 50000 0x1000000000000000000000000000000000000001 1 0 0 0 0)) 0) ) )} # noqa: E501 - contract_1 = pre.deploy_contract( # noqa: F841 - code=Op.PUSH1[0x27] - + Op.CODECOPY(dest_offset=0x0, offset=0xF, size=Op.DUP1) - + Op.PUSH1[0x0] - + Op.PUSH1[0x1] - + Op.CREATE + Op.STOP - + Op.INVALID - + Op.SSTORE( - key=0x1, - value=Op.CALLCODE( - gas=0xC350, - address=0x1000000000000000000000000000000000000001, - value=0x1, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.STOP, - balance=10000, - nonce=0, - address=Address(0x1000000000000000000000000000000000000000), # noqa: E501 ) + initcode_bytes = bytes(initcode) - expect_entries_: list[dict] = [ - { - "indexes": {"data": 0, "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": { - compute_create_address(address=contract_1, nonce=0): Account( - storage={1: 1, 2: 1}, balance=1 - ), - }, - }, - { - "indexes": {"data": 1, "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": { - Address(0x11B62573BE8F72B4085BAFE5B675B3E7F08ED522): Account( - storage={1: 1, 2: 1}, balance=1 - ), - }, - }, - ] - - post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork) + create_call = create_opcode( + value=create_endowment, + offset=0, + size=len(initcode_bytes), + ) + runner_balance = max(create_endowment, callcode_value) + 1 + runner = pre.deploy_contract( + code=Macros.MSTORE(initcode_bytes) + create_call + Op.STOP, + balance=runner_balance, + ) - tx_data = [ - Hash(contract_1, left_padding=True), - Hash(contract_2, left_padding=True), - ] - tx_gas = [1000000] + created = compute_create_address( + address=runner, + nonce=1, + initcode=initcode, + opcode=create_opcode, + ) tx = Transaction( - sender=sender, - to=contract_0, - data=tx_data[d], - gas_limit=tx_gas[g], - error=_exc, + sender=pre.fund_eoa(), + to=runner, + protected=fork.supports_protected_txs(), ) - state_test(env=env, pre=pre, post=post, tx=tx) + created_nonce = 1 if fork.is_eip_enabled(161) else 0 + callcode_success = create_endowment >= callcode_value + post = { + created: Account( + code=b"", + nonce=created_nonce, + balance=create_endowment, + storage={SUCCESS_FLAG_SLOT: 1, DELEGATE_SLOT: 1} + if callcode_success + else {SUCCESS_FLAG_SLOT: 0, DELEGATE_SLOT: 0}, + ), + runner: Account( + nonce=2, + balance=runner_balance - create_endowment, + storage={}, + ), + existing: Account(balance=0, storage={}), + } + + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stCallCodes/test_callcode_in_initcode_to_existing_contract_with_value_transfer.py b/tests/ported_static/stCallCodes/test_callcode_in_initcode_to_existing_contract_with_value_transfer.py deleted file mode 100644 index 4822486a1ea..00000000000 --- a/tests/ported_static/stCallCodes/test_callcode_in_initcode_to_existing_contract_with_value_transfer.py +++ /dev/null @@ -1,93 +0,0 @@ -""" -Callcode inside create/create2 contract init to existing contract. - -Ported from: -state_tests/stCallCodes/callcodeInInitcodeToExistingContractWithValueTransferFiller.json -""" - -import pytest -from execution_testing import ( - EOA, - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, - compute_create_address, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - [ - "state_tests/stCallCodes/callcodeInInitcodeToExistingContractWithValueTransferFiller.json" # noqa: E501 - ], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_callcode_in_initcode_to_existing_contract_with_value_transfer( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Callcode inside create/create2 contract init to existing contract.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0x1000000000000000000000000000000000000000) - contract_1 = Address(0x945304EB96065B2A98B57A48A06AE28D285A71B5) - sender = EOA( - key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8 - ) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=1000000, - ) - - pre[sender] = Account(balance=0x2386F26FC10000) - # Source: lll - # { (MSTORE 0 0x6040600060406000600573945304eb96065b2a98b57a48a06ae28d285a71b562) (MSTORE 32 0x0186a0f260005500000000000000000000000000000000000000000000000000) (CREATE 5 0 64) } # noqa: E501 - contract_0 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE( - offset=0x0, - value=0x6040600060406000600573945304EB96065B2A98B57A48A06AE28D285A71B562, # noqa: E501 - ) - + Op.MSTORE( - offset=0x20, - value=0x186A0F260005500000000000000000000000000000000000000000000000000, # noqa: E501 - ) - + Op.CREATE(value=0x5, offset=0x0, size=0x40) - + Op.STOP, - balance=10000, - nonce=0, - address=Address(0x1000000000000000000000000000000000000000), # noqa: E501 - ) - # Source: lll - # { (SSTORE 2 1) } - contract_1 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x2, value=0x1) + Op.STOP, - nonce=0, - address=Address(0x945304EB96065B2A98B57A48A06AE28D285A71B5), # noqa: E501 - ) - - tx = Transaction( - sender=sender, - to=contract_0, - data=Bytes(""), - gas_limit=453081, - ) - - post = { - compute_create_address(address=contract_0, nonce=0): Account( - storage={0: 1, 2: 1}, balance=5 - ), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stCallCreateCallCodeTest/test_call1024_oog.py b/tests/ported_static/stCallCreateCallCodeTest/test_call1024_oog.py index 16729e411a8..eb43f04e24a 100644 --- a/tests/ported_static/stCallCreateCallCodeTest/test_call1024_oog.py +++ b/tests/ported_static/stCallCreateCallCodeTest/test_call1024_oog.py @@ -1,153 +1,277 @@ """ -Calldepth with oog. +Verify self-recursive CALL, CALLCODE and DELEGATECALL chains that +terminate by out-of-gas. + +Each level bumps a shared depth counter, forwards almost all its gas to +a self-call (keeping a 10,000 reserve for its post-call stores), then +records the call's success flag and a depth marker. Levels too deep to +afford their stores halt and roll back, so the surviving storage pins +the exact depth the budget reaches under the EIP-150 63/64 rule. Ported from: state_tests/stCallCreateCallCodeTest/Call1024OOGFiller.json +state_tests/stCallCreateCallCodeTest/Callcode1024OOGFiller.json +state_tests/stDelegatecallTestHomestead/Call1024OOGFiller.json +state_tests/stDelegatecallTestHomestead/Delegatecall1024OOGFiller.json + +@manually-enhanced: Do not overwrite. The post state is predicted by an +exact fork-derived replay of the recursion's gas flow (EIP-150 grants, +warm/cold and SSTORE pricing via opcode metadata, EIP-8037 state-gas +spill), validated against the ported Cancun depths; the hardcoded +self-address is replaced by ADDRESS. Four fillers from two legacy +suites are joined into one opcode parametrization, every budget run +against every opcode, so the whole scope is visible in one file. """ import pytest from execution_testing import ( Account, - Address, Alloc, - Bytes, - Environment, + Bytecode, + Fork, StateTestFiller, Transaction, ) -from execution_testing.forks import Fork -from execution_testing.vm import Op - -from tests.ported_static.post_state_resolution import ( - resolve_expect_post, -) +from execution_testing.vm import Op, Opcode REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +COUNTER_SLOT = 0 +RESULT_SLOT = 1 +MARKER_SLOT = 2 +# Gas each level keeps back for its post-call stores. +GAS_RESERVE = 10_000 +# The ask factor zeroes out at the call-depth limit (never reached here; +# the recursion always dies of out-of-gas first). +DEPTH_CUTOFF = 1025 +# The marker store writes 1 + DEPTH_MARKER * depth. +DEPTH_MARKER = 1000 + + +def recursion_code(call_opcode: Opcode) -> Bytecode: + """Build the self-recursive body for the given call opcode.""" + return ( + Op.SSTORE( + key=COUNTER_SLOT, + value=Op.ADD(Op.SLOAD(key=COUNTER_SLOT), 1), + ) + + Op.SSTORE( + key=RESULT_SLOT, + value=call_opcode( + gas=Op.MUL( + Op.SUB(Op.GAS, GAS_RESERVE), + Op.SUB( + 1, Op.DIV(Op.SLOAD(key=COUNTER_SLOT), DEPTH_CUTOFF) + ), + ), + address=Op.ADDRESS, + ), + ) + + Op.SSTORE( + key=MARKER_SLOT, + value=Op.ADD(1, Op.MUL(Op.SLOAD(key=COUNTER_SLOT), DEPTH_MARKER)), + ) + + Op.STOP + ) + + +def predict_recursion_storage( + fork: Fork, call_opcode: Opcode, tx_gas_limit: int +) -> dict[int, int]: + """ + Replay the recursion's gas flow and return the surviving storage. + + Descend the self-call chain computing each level's EIP-150 grant, + then unwind: a level that cannot afford its post-call stores halts + and forfeits its entire grant to its parent, so the deepest level + that completes fixes the surviving depth counter (deeper levels' + writes and warmth all revert). Every cost is derived from the fork + via opcode metadata, including EIP-8037 state gas: with a sub-cap + gas limit the state reservoir is zero, so state charges spill from + the charging frame's own gas. + """ + push_cost = Op.PUSH1[0].gas_cost(fork) + # The SUB and MUL of the ask expression run after GAS reads gas_left. + post_gas_read = Op.SUB.gas_cost(fork) + Op.MUL.gas_cost(fork) + # EIP-2200: any SSTORE with gas_left <= stipend halts exceptionally. + stipend = fork.gas_costs().CALL_STIPEND + + def raw_store_cost(key_warm: bool, current: int, new: int) -> int: + """Cost of a bare SSTORE; original value is always zero here.""" + return Op.SSTORE( + key_warm=key_warm, + original_value=0, + current_value=current, + new_value=new, + ).gas_cost(fork) + + sstore_warm_set = raw_store_cost(True, 0, 1) + sstore_warm_dirty = raw_store_cost(True, 1, 2) + sstore_warm_noop = raw_store_cost(True, 1, 1) + sstore_cold_noop = raw_store_cost(False, 0, 0) + sstore_cold_set = raw_store_cost(False, 0, 1) + + def bump_statics(key_warm: bool) -> int: + """Counter-bump costs before its SSTORE (value expr plus key).""" + return ( + Op.ADD(Op.SLOAD(key=COUNTER_SLOT, key_warm=key_warm), 1).gas_cost( + fork + ) + + push_cost + ) + + bump_statics_cold = bump_statics(False) + bump_statics_warm = bump_statics(True) + + ask_expr = Op.MUL( + Op.SUB(Op.GAS, GAS_RESERVE), + Op.SUB( + 1, + Op.DIV(Op.SLOAD(key=COUNTER_SLOT, key_warm=True), DEPTH_CUTOFF), + ), + ) + call_upfront = call_opcode(address_warm=True).gas_cost(fork) + # Everything charged before GAS reads gas_left: the call's argument + # pushes, ADDRESS, and the ask expression through the GAS opcode. + pre_gas_read = ( + call_opcode( + gas=ask_expr, address=Op.ADDRESS, address_warm=True + ).gas_cost(fork) + - call_upfront + - post_gas_read + ) + + marker_statics = ( + Op.ADD( + 1, + Op.MUL(Op.SLOAD(key=COUNTER_SLOT, key_warm=True), DEPTH_MARKER), + ).gas_cost(fork) + + push_cost + ) + + # Descend: compute each level's grant until a level dies mid-frame. + gas = ( + tx_gas_limit + - fork.transaction_intrinsic_cost_calculator()() + - fork.transaction_top_frame_state_gas() + ) + levels: list[tuple[int, int]] = [] + level = 0 + while True: + level += 1 + first = level == 1 + gas -= bump_statics_cold if first else bump_statics_warm + if gas < 0 or gas <= stipend: + break + gas -= sstore_warm_set if first else sstore_warm_dirty + if gas < 0: + break + gas -= pre_gas_read + if gas < 0: + break + gas_read = gas + gas -= post_gas_read + call_upfront + if gas < 0: + break + assert level < DEPTH_CUTOFF, "recursion must die of gas, not depth" + # A reserve underflow wraps mod 2**256: an effectively infinite + # ask, clamped to the 63/64 forwardable maximum. + ask = gas_read - GAS_RESERVE if gas_read >= GAS_RESERVE else 1 << 256 + forwarded = min(ask, gas - gas // 64) + levels.append((gas, forwarded)) + gas = forwarded + + # Unwind: a failed level forfeits its whole grant to its parent. + child_ok = False + result_below = 0 + leftover = 0 + survivor = 0 + for lvl in range(len(levels), 0, -1): + available, forwarded = levels[lvl - 1] + gas = available - forwarded + (leftover if child_ok else 0) + # Result store: push the slot key, then store the success flag. + # Below the deepest completing level everything reverts, so its + # own stores find cold slots and zero current values. + gas -= push_cost + ok = gas >= 0 and gas > stipend + if ok: + if not child_ok: + result_store = sstore_cold_noop + elif result_below == 0: + result_store = sstore_warm_set + else: + result_store = sstore_warm_noop + gas -= result_store + ok = gas >= 0 + # Marker store: parents rewrite the same surviving marker value. + if ok: + gas -= marker_statics + ok = gas >= 0 and gas > stipend + if ok: + gas -= sstore_warm_noop if child_ok else sstore_cold_set + ok = gas >= 0 + if ok: + if not child_ok: + survivor = lvl + result_below = 1 if child_ok else 0 + leftover = gas + child_ok = True + else: + child_ok = False + result_below = 0 + leftover = 0 + survivor = 0 + assert child_ok and survivor > 0, "the top level must complete" + return { + COUNTER_SLOT: survivor, + RESULT_SLOT: result_below, + MARKER_SLOT: 1 + DEPTH_MARKER * survivor, + } + @pytest.mark.ported_from( - ["state_tests/stCallCreateCallCodeTest/Call1024OOGFiller.json"], + [ + "state_tests/stCallCreateCallCodeTest/Call1024OOGFiller.json", + "state_tests/stCallCreateCallCodeTest/Callcode1024OOGFiller.json", + "state_tests/stDelegatecallTestHomestead/Call1024OOGFiller.json", + "state_tests/stDelegatecallTestHomestead/Delegatecall1024OOGFiller.json", # noqa: E501 + ], ) -@pytest.mark.valid_from("Cancun") +@pytest.mark.valid_from("Berlin") @pytest.mark.parametrize( - "d, g, v", + "call_opcode", [ - pytest.param( - 0, - 0, - 0, - id="-g0", - ), - pytest.param( - 0, - 1, - 0, - id="-g1", - ), - pytest.param( - 0, - 2, - 0, - id="-g2", - ), - pytest.param( - 0, - 3, - 0, - id="-g3", - ), + pytest.param(Op.CALL, id="call"), + pytest.param(Op.CALLCODE, id="callcode"), + pytest.param(Op.DELEGATECALL, id="delegatecall"), ], ) -@pytest.mark.pre_alloc_mutable +@pytest.mark.parametrize( + # Ported budgets; each pins a distinct OOG-terminated depth. + "tx_gas_limit", + [13_120_826, 9_320_826, 15_720_826, 11_220_826], +) def test_call1024_oog( state_test: StateTestFiller, pre: Alloc, fork: Fork, - d: int, - g: int, - v: int, + call_opcode: Opcode, + tx_gas_limit: int, ) -> None: - """Calldepth with oog.""" - coinbase = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B) - sender = pre.fund_eoa(amount=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=9223372036854775807, - ) - - addr = pre.fund_eoa(amount=7000) # noqa: F841 - # Source: lll - # { [[ 0 ]] (ADD @@0 1) [[ 1 ]] (CALL (MUL (SUB (GAS) 10000) (SUB 1 (DIV @@0 1025))) <contract:target:0xbbbf5374fce5edbc8e2a8697c15331677e6ebf0b> 0 0 0 0 0) [[ 2 ]] (ADD 1(MUL @@0 1000)) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=Op.ADD(Op.SLOAD(key=0x0), 0x1)) - + Op.SSTORE( - key=0x1, - value=Op.CALL( - gas=Op.MUL( - Op.SUB(Op.GAS, 0x2710), - Op.SUB(0x1, Op.DIV(Op.SLOAD(key=0x0), 0x401)), - ), - address=0x878BC1C3D660907B056E31C854A309F7EF1B4C4, - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.SSTORE( - key=0x2, value=Op.ADD(0x1, Op.MUL(Op.SLOAD(key=0x0), 0x3E8)) - ) - + Op.STOP, - balance=1024, - nonce=0, - address=Address(0x0878BC1C3D660907B056E31C854A309F7EF1B4C4), # noqa: E501 - ) - - expect_entries_: list[dict] = [ - { - "indexes": {"data": -1, "gas": 0, "value": -1}, - "network": [">=Cancun"], - "result": {target: Account(storage={0: 134, 1: 1, 2: 0x20B71})}, - }, - { - "indexes": {"data": -1, "gas": 1, "value": -1}, - "network": [">=Cancun"], - "result": {target: Account(storage={0: 113, 1: 1, 2: 0x1B969})}, - }, - { - "indexes": {"data": -1, "gas": 2, "value": -1}, - "network": [">=Cancun"], - "result": {target: Account(storage={0: 146, 1: 1, 2: 0x23A51})}, - }, - { - "indexes": {"data": -1, "gas": 3, "value": -1}, - "network": [">=Cancun"], - "result": {target: Account(storage={0: 124, 1: 1, 2: 0x1E461})}, - }, - ] - - post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork) - - tx_data = [ - Bytes(""), - ] - tx_gas = [13120826, 9320826, 15720826, 11220826] - tx_value = [10] + """Pin the depth an OOG-terminated self-recursion reaches.""" + target = pre.deploy_contract(code=recursion_code(call_opcode)) tx = Transaction( - sender=sender, + sender=pre.fund_eoa(), to=target, - data=tx_data[d], - gas_limit=tx_gas[g], - value=tx_value[v], - error=_exc, + gas_limit=tx_gas_limit, ) - state_test(env=env, pre=pre, post=post, tx=tx) + post = { + target: Account( + storage=predict_recursion_storage(fork, call_opcode, tx_gas_limit) + ), + } + + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stCallCreateCallCodeTest/test_callcode1024_oog.py b/tests/ported_static/stCallCreateCallCodeTest/test_callcode1024_oog.py deleted file mode 100644 index c67dfb77762..00000000000 --- a/tests/ported_static/stCallCreateCallCodeTest/test_callcode1024_oog.py +++ /dev/null @@ -1,131 +0,0 @@ -""" -Calldepth and oog. - -Ported from: -state_tests/stCallCreateCallCodeTest/Callcode1024OOGFiller.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, -) -from execution_testing.forks import Fork -from execution_testing.vm import Op - -from tests.ported_static.post_state_resolution import ( - resolve_expect_post, -) - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - ["state_tests/stCallCreateCallCodeTest/Callcode1024OOGFiller.json"], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.parametrize( - "d, g, v", - [ - pytest.param( - 0, - 0, - 0, - id="-g0", - ), - pytest.param( - 0, - 1, - 0, - id="-g1", - ), - ], -) -@pytest.mark.pre_alloc_mutable -def test_callcode1024_oog( - state_test: StateTestFiller, - pre: Alloc, - fork: Fork, - d: int, - g: int, - v: int, -) -> None: - """Calldepth and oog.""" - coinbase = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B) - sender = pre.fund_eoa(amount=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=9223372036854775807, - ) - - addr = pre.fund_eoa(amount=7000) # noqa: F841 - # Source: lll - # { [[ 0 ]] (ADD @@0 1) [[ 1 ]] (CALLCODE (MUL (SUB (GAS) 10000) (SUB 1 (DIV @@0 1025))) <contract:target:0xbbbf5374fce5edbc8e2a8697c15331677e6ebf0b> 0 0 0 0 0) [[ 2 ]] (ADD 1(MUL @@0 1000)) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=Op.ADD(Op.SLOAD(key=0x0), 0x1)) - + Op.SSTORE( - key=0x1, - value=Op.CALLCODE( - gas=Op.MUL( - Op.SUB(Op.GAS, 0x2710), - Op.SUB(0x1, Op.DIV(Op.SLOAD(key=0x0), 0x401)), - ), - address=0x1B803058288DC00000F98311B059597434253374, - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.SSTORE( - key=0x2, value=Op.ADD(0x1, Op.MUL(Op.SLOAD(key=0x0), 0x3E8)) - ) - + Op.STOP, - balance=1024, - nonce=0, - address=Address(0x1B803058288DC00000F98311B059597434253374), # noqa: E501 - ) - - expect_entries_: list[dict] = [ - { - "indexes": {"data": -1, "gas": 0, "value": -1}, - "network": [">=Cancun"], - "result": {target: Account(storage={0: 146, 1: 1, 2: 0x23A51})}, - }, - { - "indexes": {"data": -1, "gas": 1, "value": -1}, - "network": [">=Cancun"], - "result": {target: Account(storage={0: 134, 1: 1, 2: 0x20B71})}, - }, - ] - - post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork) - - tx_data = [ - Bytes(""), - ] - tx_gas = [15720826, 13120826] - tx_value = [10] - - tx = Transaction( - sender=sender, - to=target, - data=tx_data[d], - gas_limit=tx_gas[g], - value=tx_value[v], - error=_exc, - ) - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stCallCreateCallCodeTest/test_contract_creation_make_call_that_ask_more_gas_then_transaction_provided.py b/tests/ported_static/stCallCreateCallCodeTest/test_contract_creation_make_call_that_ask_more_gas_then_transaction_provided.py index 281a5b080b4..c1df2752c79 100644 --- a/tests/ported_static/stCallCreateCallCodeTest/test_contract_creation_make_call_that_ask_more_gas_then_transaction_provided.py +++ b/tests/ported_static/stCallCreateCallCodeTest/test_contract_creation_make_call_that_ask_more_gas_then_transaction_provided.py @@ -1,152 +1,138 @@ """ -Test_contract_creation_make_call_that_ask_more_gas_then_transaction_prov... +Verify a CALL made inside a contract-creation transaction's init code that +asks for more gas than the transaction provided: the EIP-150 clamp decides +what the callee receives, and the transaction budget decides whether that +grant covers the callee's work. Ported from: state_tests/stCallCreateCallCodeTest/contractCreationMakeCallThatAskMoreGasThenTransactionProvidedFiller.json + +@manually-enhanced: Do not overwrite. The ask is explicitly oversized (the +ported 50000 was schedule-sized); both transaction budgets are derived from +the fork so the clamped grant lands above/below the callee's cost on every +fork; the init code writes a canary before the call (nothing after it needs +more than a POP — the 1/64 retention cannot afford an SSTORE, whose +EIP-2200 stipend rule would kill the creation), so a failed call and a +failed creation stay distinguishable. """ import pytest from execution_testing import ( - EOA, Account, - Address, Alloc, - Environment, + Fork, StateTestFiller, Transaction, - compute_create_address, ) -from execution_testing.forks import Fork from execution_testing.vm import Op -from tests.ported_static.post_state_resolution import ( - resolve_expect_post, -) - REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +CANARY_SLOT = 0x2 +CANARY = 0xFF + +# Far larger than any gas the init frame can hold: the clamp always +# applies, which is the scenario the ported filler names. +OVERSIZED_GAS_ASK = 2**61 + @pytest.mark.ported_from( [ "state_tests/stCallCreateCallCodeTest/contractCreationMakeCallThatAskMoreGasThenTransactionProvidedFiller.json" # noqa: E501 ], ) -@pytest.mark.valid_from("Cancun") +@pytest.mark.valid_from("Berlin") @pytest.mark.parametrize( - "d, g, v", + "call_covered", [ - pytest.param( - 0, - 0, - 0, - id="-g0", - ), - pytest.param( - 0, - 1, - 0, - id="-g1", - ), + pytest.param(True, id="enough_gas"), + pytest.param(False, id="not_enough_gas"), ], ) -@pytest.mark.pre_alloc_mutable def test_contract_creation_make_call_that_ask_more_gas_then_transaction_provided( # noqa: E501 state_test: StateTestFiller, pre: Alloc, fork: Fork, - d: int, - g: int, - v: int, + call_covered: bool, ) -> None: - """Test_contract_creation_make_call_that_ask_more_gas_then_transaction...""" # noqa: E501 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B) - contract_1 = Address(0x1000000000000000000000000000000000000001) - sender = EOA( - key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8 + """An init-code CALL asking above the tx budget gets the 63/64 clamp.""" + # Success indicator: writes one cold fresh slot when called. + writer_store = Op.SSTORE( + key=0x1, + value=0x1, + key_warm=False, + original_value=0, + new_value=1, ) + writer = pre.deploy_contract(code=writer_store + Op.STOP) - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, + # The init code writes a completion canary before the call (a failed + # creation persists nothing, so the canary distinguishes it from a + # failed call), makes the oversized ask, and deposits no code. Only a + # POP runs after the call: the 1/64 retention on the starved arm is + # far below the EIP-2200 stipend an SSTORE would require. + initcode = ( + Op.SSTORE( + key=CANARY_SLOT, + value=CANARY, + key_warm=False, + original_value=0, + new_value=CANARY, + ) + + Op.CALL( + gas=OVERSIZED_GAS_ASK, + address=writer, + address_warm=False, + value_transfer=False, + account_new=False, + ) + + Op.STOP ) - pre[sender] = Account(balance=0x10C8E0) - # Source: lll - # {(SSTORE 1 1)} - contract_1 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x1, value=0x1) + Op.STOP, - balance=0x186A0, - nonce=0, - address=Address(0x1000000000000000000000000000000000000001), # noqa: E501 - ) - # Source: lll - # {(CALL 50000 0x1000000000000000000000000000000000000001 0 0 64 0 64)} - contract_0 = pre.deploy_contract( # noqa: F841 - code=Op.CALL( - gas=0xC350, - address=0x1000000000000000000000000000000000000001, - value=0x0, - args_offset=0x0, - args_size=0x40, - ret_offset=0x0, - ret_size=0x40, + # Derive the two budgets around the callee's fork-priced cost: the + # clamped grant (63/64 of the base left after the charges made before + # the forward point) lands above it on one arm and below it on the + # other. The post-call flag write runs on the 1/64 retention. + overhead = ( + fork.transaction_intrinsic_cost_calculator()( + calldata=initcode, + contract_creation=True, + return_cost_deducted_prior_execution=True, ) - + Op.STOP, - balance=0x186A0, - nonce=0, - address=Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B), # noqa: E501 + # EIP-8037 charges the created account's state gas to the + # creation transaction's top frame (zero before Amsterdam). + + fork.transaction_top_frame_state_gas(contract_creation=True) + + initcode.gas_cost(fork) ) - - expect_entries_: list[dict] = [ - { - "indexes": {"data": -1, "gas": [0], "value": -1}, - "network": [">=Cancun"], - "result": { - compute_create_address(address=sender, nonce=0): Account( - balance=0 - ), - contract_1: Account(storage={1: 1}), - }, - }, - { - "indexes": {"data": -1, "gas": [1], "value": -1}, - "network": [">=Cancun"], - "result": { - compute_create_address(address=sender, nonce=0): Account( - balance=0 - ), - contract_1: Account(storage={1: 0}), - }, - }, - ] - - post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork) - - tx_data = [ - Op.CALL( - gas=0xC350, - address=contract_1, - value=0x0, - args_offset=0x0, - args_size=0x40, - ret_offset=0x0, - ret_size=0x40, - ), - ] - tx_gas = [96000, 60000] - + callee_needed = writer_store.gas_cost(fork) + # The grant, base - base // 64, is a step function that repeats at + # every multiple of 64, so one gas less does not always forward less: + # step until the grant really crosses the callee's cost. + base = callee_needed * 64 // 63 + while base - base // 64 < callee_needed: + base += 1 + if not call_covered: + while base - base // 64 >= callee_needed: + base -= 1 + assert base < OVERSIZED_GAS_ASK, "the 63/64 clamp must apply" + gas_limit = overhead + base + sender = pre.fund_eoa() tx = Transaction( sender=sender, to=None, - data=tx_data[d], - gas_limit=tx_gas[g], - error=_exc, + data=initcode, + gas_limit=gas_limit, ) - state_test(env=env, pre=pre, post=post, tx=tx) + created = tx.created_contract + post = { + created: Account( + nonce=1, + code=b"", + storage={CANARY_SLOT: CANARY}, + ), + writer: Account(storage={1: 1 if call_covered else 0}), + } + + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stDelegatecallTestHomestead/test_call1024_oog.py b/tests/ported_static/stDelegatecallTestHomestead/test_call1024_oog.py deleted file mode 100644 index 0f6b6855f24..00000000000 --- a/tests/ported_static/stDelegatecallTestHomestead/test_call1024_oog.py +++ /dev/null @@ -1,130 +0,0 @@ -""" -Test_call1024_oog. - -Ported from: -state_tests/stDelegatecallTestHomestead/Call1024OOGFiller.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, -) -from execution_testing.forks import Fork -from execution_testing.vm import Op - -from tests.ported_static.post_state_resolution import ( - resolve_expect_post, -) - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - ["state_tests/stDelegatecallTestHomestead/Call1024OOGFiller.json"], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.parametrize( - "d, g, v", - [ - pytest.param( - 0, - 0, - 0, - id="-g0", - ), - pytest.param( - 0, - 1, - 0, - id="-g1", - ), - ], -) -@pytest.mark.pre_alloc_mutable -def test_call1024_oog( - state_test: StateTestFiller, - pre: Alloc, - fork: Fork, - d: int, - g: int, - v: int, -) -> None: - """Test_call1024_oog.""" - coinbase = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B) - sender = pre.fund_eoa(amount=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=9223372036854775807, - ) - - addr = pre.fund_eoa(amount=7000) # noqa: F841 - # Source: lll - # { [[ 0 ]] (ADD @@0 1) [[ 1 ]] (DELEGATECALL (MUL (SUB (GAS) 10000) (SUB 1 (DIV @@0 1025))) <contract:target:0xbbbf5374fce5edbc8e2a8697c15331677e6ebf0b> 0 0 0 0) [[ 2 ]] (ADD 1(MUL @@0 1000)) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=Op.ADD(Op.SLOAD(key=0x0), 0x1)) - + Op.SSTORE( - key=0x1, - value=Op.DELEGATECALL( - gas=Op.MUL( - Op.SUB(Op.GAS, 0x2710), - Op.SUB(0x1, Op.DIV(Op.SLOAD(key=0x0), 0x401)), - ), - address=0x62C5C9278DA01E6594D6FEDE061838CF5E597F2B, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.SSTORE( - key=0x2, value=Op.ADD(0x1, Op.MUL(Op.SLOAD(key=0x0), 0x3E8)) - ) - + Op.STOP, - balance=1024, - nonce=0, - address=Address(0x62C5C9278DA01E6594D6FEDE061838CF5E597F2B), # noqa: E501 - ) - - expect_entries_: list[dict] = [ - { - "indexes": {"data": -1, "gas": 0, "value": -1}, - "network": [">=Cancun"], - "result": {target: Account(storage={0: 134, 1: 1, 2: 0x20B71})}, - }, - { - "indexes": {"data": -1, "gas": 1, "value": -1}, - "network": [">=Cancun"], - "result": {target: Account(storage={0: 146, 1: 1, 2: 0x23A51})}, - }, - ] - - post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork) - - tx_data = [ - Bytes(""), - ] - tx_gas = [13120826, 15720826] - tx_value = [10] - - tx = Transaction( - sender=sender, - to=target, - data=tx_data[d], - gas_limit=tx_gas[g], - value=tx_value[v], - error=_exc, - ) - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stDelegatecallTestHomestead/test_delegatecall1024_oog.py b/tests/ported_static/stDelegatecallTestHomestead/test_delegatecall1024_oog.py deleted file mode 100644 index 42959c58111..00000000000 --- a/tests/ported_static/stDelegatecallTestHomestead/test_delegatecall1024_oog.py +++ /dev/null @@ -1,84 +0,0 @@ -""" -Test_delegatecall1024_oog. - -Ported from: -state_tests/stDelegatecallTestHomestead/Delegatecall1024OOGFiller.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - ["state_tests/stDelegatecallTestHomestead/Delegatecall1024OOGFiller.json"], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_delegatecall1024_oog( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_delegatecall1024_oog.""" - coinbase = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B) - sender = pre.fund_eoa(amount=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=9223372036854775807, - ) - - addr = pre.fund_eoa(amount=7000) # noqa: F841 - # Source: lll - # { [[ 0 ]] (ADD @@0 1) [[ 1 ]] (DELEGATECALL (MUL (SUB (GAS) 10000) (SUB 1 (DIV @@0 1025))) <contract:target:0xbbbf5374fce5edbc8e2a8697c15331677e6ebf0b> 0 0 0 0) [[ 2 ]] (ADD 1(MUL @@0 1000)) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=Op.ADD(Op.SLOAD(key=0x0), 0x1)) - + Op.SSTORE( - key=0x1, - value=Op.DELEGATECALL( - gas=Op.MUL( - Op.SUB(Op.GAS, 0x2710), - Op.SUB(0x1, Op.DIV(Op.SLOAD(key=0x0), 0x401)), - ), - address=0x62C5C9278DA01E6594D6FEDE061838CF5E597F2B, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.SSTORE( - key=0x2, value=Op.ADD(0x1, Op.MUL(Op.SLOAD(key=0x0), 0x3E8)) - ) - + Op.STOP, - balance=1024, - nonce=0, - address=Address(0x62C5C9278DA01E6594D6FEDE061838CF5E597F2B), # noqa: E501 - ) - - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=15720826, - value=10, - ) - - post = {target: Account(storage={0: 146, 1: 1, 2: 0x23A51})} - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stDelegatecallTestHomestead/test_delegatecall_in_initcode_to_existing_contract.py b/tests/ported_static/stDelegatecallTestHomestead/test_delegatecall_in_initcode_to_existing_contract.py index e20c162ae59..31e1720c2c8 100644 --- a/tests/ported_static/stDelegatecallTestHomestead/test_delegatecall_in_initcode_to_existing_contract.py +++ b/tests/ported_static/stDelegatecallTestHomestead/test_delegatecall_in_initcode_to_existing_contract.py @@ -1,107 +1,120 @@ """ -Test_delegatecall_in_initcode_to_existing_contract. +Verify a DELEGATECALL made from inside init code to an existing +contract. + +The created account's init code DELEGATECALLs an already-deployed +contract, so that contract's code runs in the freshly created account's +context with the init frame's caller preserved: both the delegate and +the init code itself observe the creating contract as CALLER, and every +storage write lands in the created account, never in the delegate. Ported from: state_tests/stDelegatecallTestHomestead/delegatecallInInitcodeToExistingContractFiller.json + +@manually-enhanced: Do not overwrite. The port's unused second creator +contract is deleted, the raw-word init code is composed, the delegate +call forwards all gas (EIP-8037-proof), the transaction budget is +maxed, and the post also pins the created account's code/nonce/balance +and that the delegate's own storage stays untouched. """ import pytest from execution_testing import ( - EOA, Account, - Address, Alloc, - Bytes, - Environment, + Macros, + Op, + Opcodes, StateTestFiller, Transaction, compute_create_address, ) -from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +CREATE_ENDOWMENT = 1 +RUNNER_BALANCE = 10_000 + +# Written by the init code with the DELEGATECALL's success flag. +DELEGATE_RESULT_SLOT = 0 +# Written by the init code with the CALLER it observes (the runner). +INITCODE_CALLER_SLOT = 1 +# Written by the delegate's code, in the created account's context. +DELEGATE_WRITE_SLOT = 2 +# Written by the delegate with the CALLER it observes (still the +# runner: DELEGATECALL preserves the init frame's caller). +DELEGATE_CALLER_SLOT = 0xB +DELEGATE_VALUE_SLOT = 0xC + @pytest.mark.ported_from( [ "state_tests/stDelegatecallTestHomestead/delegatecallInInitcodeToExistingContractFiller.json" # noqa: E501 ], ) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable +@pytest.mark.with_all_create_opcodes +@pytest.mark.valid_from("SpuriousDragon") def test_delegatecall_in_initcode_to_existing_contract( state_test: StateTestFiller, pre: Alloc, + create_opcode: Opcodes, ) -> None: - """Test_delegatecall_in_initcode_to_existing_contract.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0x1000000000000000000000000000000000000000) - contract_1 = Address(0x1000000000000000000000000000000000000001) - contract_2 = Address(0x945304EB96065B2A98B57A48A06AE28D285A71B5) - sender = EOA( - key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8 - ) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=1000000, + """A DELEGATECALL in init code runs in the created account.""" + existing = pre.deploy_contract( + code=Op.SSTORE(key=DELEGATE_WRITE_SLOT, value=1) + + Op.SSTORE(key=DELEGATE_CALLER_SLOT, value=Op.CALLER) + + Op.SSTORE(key=DELEGATE_VALUE_SLOT, value=Op.CALLVALUE) + + Op.STOP, ) - pre[sender] = Account(balance=0x2386F26FC10000) - # Source: lll - # { (MSTORE 0 0x604060006040600073945304eb96065b2a98b57a48a06ae28d285a71b5620186) (MSTORE 32 0xa0f4600055336001550000000000000000000000000000000000000000000000) (CREATE 1 0 64) } # noqa: E501 - contract_0 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE( - offset=0x0, - value=0x604060006040600073945304EB96065B2A98B57A48A06AE28D285A71B5620186, # noqa: E501 + initcode = ( + Op.SSTORE( + key=DELEGATE_RESULT_SLOT, + value=Op.DELEGATECALL(address=existing), ) - + Op.MSTORE( - offset=0x20, - value=0xA0F4600055336001550000000000000000000000000000000000000000000000, # noqa: E501 - ) - + Op.CREATE(value=0x1, offset=0x0, size=0x40) - + Op.STOP, - balance=10000, - nonce=0, - address=Address(0x1000000000000000000000000000000000000000), # noqa: E501 + + Op.SSTORE(key=INITCODE_CALLER_SLOT, value=Op.CALLER) + + Op.STOP ) - # Source: lll - # { (MSTORE 0 0x6001600055) (CREATE 1 27 5) } - contract_1 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=0x6001600055) - + Op.CREATE(value=0x1, offset=0x1B, size=0x5) + + runner = pre.deploy_contract( + code=Macros.MSTORE(initcode) + + create_opcode(value=CREATE_ENDOWMENT, offset=0, size=len(initcode)) + Op.STOP, - balance=1000, - nonce=0, - address=Address(0x1000000000000000000000000000000000000001), # noqa: E501 + balance=RUNNER_BALANCE, ) - # Source: lll - # { (SSTORE 2 1) [[ 11 ]] (CALLER) } - contract_2 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x2, value=0x1) - + Op.SSTORE(key=0xB, value=Op.CALLER) - + Op.STOP, - nonce=0, - address=Address(0x945304EB96065B2A98B57A48A06AE28D285A71B5), # noqa: E501 + + # Deployed contracts start at nonce 1. + created = compute_create_address( + address=runner, nonce=1, initcode=initcode, opcode=create_opcode ) tx = Transaction( - sender=sender, - to=contract_0, - data=Bytes(""), - gas_limit=453081, + sender=pre.fund_eoa(), + to=runner, ) post = { - compute_create_address(address=contract_0, nonce=0): Account( - storage={0: 1, 1: contract_0, 2: 1, 11: contract_0}, - balance=1, + created: Account( + # The init code deploys no code but writes its own storage. + code=b"", + nonce=1, + balance=CREATE_ENDOWMENT, + storage={ + DELEGATE_RESULT_SLOT: 1, + INITCODE_CALLER_SLOT: runner, + DELEGATE_WRITE_SLOT: 1, + DELEGATE_CALLER_SLOT: runner, + DELEGATE_VALUE_SLOT: CREATE_ENDOWMENT, + }, + ), + runner: Account( + nonce=2, + balance=RUNNER_BALANCE - CREATE_ENDOWMENT, + storage={}, ), + # The delegate's own storage must stay untouched. + existing: Account(storage={}), } - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP150Specific/test_call_ask_more_gas_on_depth2_then_transaction_has.py b/tests/ported_static/stEIP150Specific/test_call_ask_more_gas_on_depth2_then_transaction_has.py index 61fd1052dec..20371a9ce34 100644 --- a/tests/ported_static/stEIP150Specific/test_call_ask_more_gas_on_depth2_then_transaction_has.py +++ b/tests/ported_static/stEIP150Specific/test_call_ask_more_gas_on_depth2_then_transaction_has.py @@ -1,17 +1,23 @@ """ -Test_call_ask_more_gas_on_depth2_then_transaction_has. +Verify the EIP-150 63/64 clamp at call depth 2: a first-level call receives +its exact (affordable) ask, and its own oversized ask is clamped to 63/64 +of what remains in that frame. Ported from: state_tests/stEIP150Specific/CallAskMoreGasOnDepth2ThenTransactionHasFiller.json + +@manually-enhanced: Do not overwrite. The lower frames return their +observed GAS up the stack instead of SSTORE-ing it (the ported lower-frame +gas snapshots are EIP-8037 state-gas traps), and both expectations are +derived from the fork: the depth-1 frame sees exactly its asked budget, +the depth-2 frame sees `base - base // 64` of the depth-1 remainder. """ import pytest from execution_testing import ( Account, - Address, Alloc, - Bytes, - Environment, + Fork, StateTestFiller, Transaction, ) @@ -20,86 +26,86 @@ REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +FLAG_SLOT = 0x0 +DEPTH2_GAS_SLOT = 0x1 +DEPTH1_GAS_SLOT = 0x2 + +# The ported depth-1 budget: affordable, so it is forwarded exactly. +CALLER_GAS = 0x30D40 +# The ported depth-2 ask: above anything the depth-1 frame can hold, so +# the 63/64 clamp decides what the depth-2 frame receives. +ASK_GAS = 0x927C0 + @pytest.mark.ported_from( [ "state_tests/stEIP150Specific/CallAskMoreGasOnDepth2ThenTransactionHasFiller.json" # noqa: E501 ], ) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable +@pytest.mark.valid_from("Berlin") def test_call_ask_more_gas_on_depth2_then_transaction_has( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: - """Test_call_ask_more_gas_on_depth2_then_transaction_has.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, + """A depth-2 call asking above the frame budget gets 63/64 of it.""" + # Depth 2: returns the gas it observed on entry. + gas_return_contract = pre.deploy_contract( + code=Op.MSTORE(0, Op.GAS, new_memory_size=0x20) + Op.RETURN(0, 0x20), ) - # Source: lll - # { (SSTORE 8 (GAS))} - addr_2 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x8, value=Op.GAS) + Op.STOP, - nonce=0, + # Depth 1: records its own entry gas, then asks depth 2 for more gas + # than this frame holds; both observations return to the top frame. + entry_snapshot = Op.MSTORE(0x20, Op.GAS, new_memory_size=0x40) + depth2_call = Op.CALL( + gas=ASK_GAS, + address=gas_return_contract, + ret_size=0x20, + address_warm=False, + account_new=False, + new_memory_size=0x40, + old_memory_size=0x40, ) - # Source: lll - # { (SSTORE 8 (GAS)) (SSTORE 9 (CALL 600000 <contract:0x1000000000000000000000000000000000000108> 0 0 0 0 0)) } # noqa: E501 - addr = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x8, value=Op.GAS) - + Op.SSTORE( - key=0x9, - value=Op.CALL( - gas=0x927C0, - address=addr_2, - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.STOP, - nonce=0, + caller = pre.deploy_contract( + code=entry_snapshot + depth2_call + Op.RETURN(0, 0x40), ) - # Source: lll - # { (SSTORE 8 (GAS)) (SSTORE 9 (CALL 200000 <contract:0x1000000000000000000000000000000000000107> 0 0 0 0 0)) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x8, value=Op.GAS) - + Op.SSTORE( - key=0x9, - value=Op.CALL( - gas=0x30D40, - address=addr, - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), + + # Top frame: forwards the exact (affordable) depth-1 budget and stores + # the success flag plus both returned observations. + entry = pre.deploy_contract( + code=Op.SSTORE( + key=FLAG_SLOT, + value=Op.CALL(gas=CALLER_GAS, address=caller, ret_size=0x40), ) - + Op.STOP, - nonce=0, + + Op.SSTORE(key=DEPTH2_GAS_SLOT, value=Op.MLOAD(0)) + + Op.SSTORE(key=DEPTH1_GAS_SLOT, value=Op.MLOAD(0x20)), ) tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=600000, + sender=pre.fund_eoa(), + to=entry, + state_gas_reservoir=0, + ) + + # Depth 1 received exactly CALLER_GAS; its snapshot reads it minus the + # GAS opcode itself. The depth-2 base is what remains after the + # snapshot and the call's own costs, clamped by EIP-150. + depth1_observed = CALLER_GAS - Op.GAS.gas_cost(fork) + base = ( + CALLER_GAS - entry_snapshot.gas_cost(fork) - depth2_call.gas_cost(fork) ) + assert 0 < base < ASK_GAS, "the 63/64 clamp must apply at depth 2" + forwarded = base - base // 64 + depth2_observed = forwarded - Op.GAS.gas_cost(fork) post = { - addr: Account(storage={8: 0x30D3E, 9: 1}), - addr_2: Account(storage={8: 0x2A1F6}), + entry: Account( + storage={ + FLAG_SLOT: 1, + DEPTH2_GAS_SLOT: depth2_observed, + DEPTH1_GAS_SLOT: depth1_observed, + }, + ), } - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP150Specific/test_transaction64_rule.py b/tests/ported_static/stEIP150Specific/test_transaction64_rule.py new file mode 100644 index 00000000000..ed0feb3e4fe --- /dev/null +++ b/tests/ported_static/stEIP150Specific/test_transaction64_rule.py @@ -0,0 +1,111 @@ +""" +Verify the EIP-150 "all but one 64th" rounding at the transaction level: the +gas available when a subcall asks for more than the transaction provided is +floored as `base - base // 64`, probed with the base exactly divisible by +64 and one gas below/above it. + +Ported from: +state_tests/stEIP150Specific/Transaction64Rule_d64e0Filler.json +state_tests/stEIP150Specific/Transaction64Rule_d64m1Filler.json +state_tests/stEIP150Specific/Transaction64Rule_d64p1Filler.json + +@manually-enhanced: Do not overwrite. Three fillers folded into one +parametrize; the callee reports its observed GAS so the exact forwarded +amount is asserted (`base - base // 64` differs from `base * 63 // 64` by +one whenever the base is not a multiple of 64 — the ported posts could not +see that difference); the tx gas limit is derived from the fork so the +divisibility residue holds on every fork. +""" + +import pytest +from execution_testing import ( + Account, + Alloc, + Fork, + StateTestFiller, + Transaction, +) +from execution_testing.vm import Op + +REFERENCE_SPEC_GIT_PATH = "N/A" +REFERENCE_SPEC_VERSION = "N/A" + +GAS_SLOT = 0x1 +# Far larger than any gas the frame can hold: the clamp always applies. +OVERSIZED_GAS_ASK = 2**61 + + +@pytest.mark.ported_from( + [ + "state_tests/stEIP150Specific/Transaction64Rule_d64e0Filler.json", + "state_tests/stEIP150Specific/Transaction64Rule_d64m1Filler.json", + "state_tests/stEIP150Specific/Transaction64Rule_d64p1Filler.json", + ], +) +@pytest.mark.valid_from("Berlin") +@pytest.mark.parametrize( + "residue", + [ + pytest.param(0, id="d64e0"), + pytest.param(-1, id="d64m1"), + pytest.param(1, id="d64p1"), + ], +) +def test_transaction64_rule( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + residue: int, +) -> None: + """A subcall asking above the tx budget receives `base - base // 64`.""" + # Callee returns the gas it observed on entry back to the caller. + gas_return_contract = pre.deploy_contract( + code=Op.MSTORE(0, Op.GAS, new_memory_size=0x20) + Op.RETURN(0, 0x20), + ) + + call_code = Op.CALL( + gas=OVERSIZED_GAS_ASK, + address=gas_return_contract, + ret_size=0x20, + address_warm=False, + account_new=False, + new_memory_size=0x20, + ) + # The observed-gas store is the only op after the call; the callee's + # returned surplus always covers it. + store_code = Op.SSTORE( + key=GAS_SLOT, + value=Op.MLOAD(0), + key_warm=False, + original_value=0, + new_value=1, + ) + caller = pre.deploy_contract(code=call_code + store_code + Op.STOP) + + # Choose the 63/64 rounding base: large enough that the frame can + # afford the trailing store from what the callee hands back, shaped to + # the parametrized residue mod 64. The +1024 margin absorbs the ops + # around the store. + intrinsic = fork.transaction_intrinsic_cost_calculator()( + return_cost_deducted_prior_execution=True + ) + min_base = store_code.gas_cost(fork) + 1024 + base = -(-min_base // 64) * 64 + residue + assert base < OVERSIZED_GAS_ASK, "the 63/64 clamp must apply" + gas_limit = intrinsic + call_code.gas_cost(fork) + base + + tx = Transaction( + sender=pre.fund_eoa(), + to=caller, + gas_limit=gas_limit, + ) + + # The EVM floors the forwarded gas as `base - base // 64`; the callee + # observes it minus its own GAS opcode. An implementation using + # `base * 63 // 64` is exactly one gas short on the m1/p1 residues. + forwarded = base - base // 64 + expected_gas = forwarded - Op.GAS.gas_cost(fork) + + post = {caller: Account(storage={GAS_SLOT: expected_gas})} + + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP150Specific/test_transaction64_rule_d64e0.py b/tests/ported_static/stEIP150Specific/test_transaction64_rule_d64e0.py deleted file mode 100644 index 256cf7ea0bb..00000000000 --- a/tests/ported_static/stEIP150Specific/test_transaction64_rule_d64e0.py +++ /dev/null @@ -1,84 +0,0 @@ -""" -Test_transaction64_rule_d64e0. - -Ported from: -state_tests/stEIP150Specific/Transaction64Rule_d64e0Filler.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - ["state_tests/stEIP150Specific/Transaction64Rule_d64e0Filler.json"], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_transaction64_rule_d64e0( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_transaction64_rule_d64e0.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - # Source: lll - # { [[1]] 12 } - addr = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x1, value=0xC) + Op.STOP, - nonce=0, - ) - # Source: lll - # { [0] (GAS) (CALL 160000 <contract:0x1000000000000000000000000000000000000118> 0 0 0 0 0) [[2]] (SUB @0 (GAS)) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.POP( - Op.CALL( - gas=0x27100, - address=addr, - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ) - ) - + Op.SSTORE(key=0x2, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.STOP, - nonce=0, - ) - - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=160062, - ) - - post = { - addr: Account(storage={1: 12}), - target: Account(storage={2: 24740}), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP150Specific/test_transaction64_rule_d64m1.py b/tests/ported_static/stEIP150Specific/test_transaction64_rule_d64m1.py deleted file mode 100644 index dd89bd167ec..00000000000 --- a/tests/ported_static/stEIP150Specific/test_transaction64_rule_d64m1.py +++ /dev/null @@ -1,84 +0,0 @@ -""" -Test_transaction64_rule_d64m1. - -Ported from: -state_tests/stEIP150Specific/Transaction64Rule_d64m1Filler.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - ["state_tests/stEIP150Specific/Transaction64Rule_d64m1Filler.json"], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_transaction64_rule_d64m1( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_transaction64_rule_d64m1.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - # Source: lll - # { [[1]] 12 } - addr = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x1, value=0xC) + Op.STOP, - nonce=0, - ) - # Source: lll - # { [0] (GAS) (CALL 160000 <contract:0x1000000000000000000000000000000000000118> 0 0 0 0 0) [[2]] (SUB @0 (GAS)) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.POP( - Op.CALL( - gas=0x27100, - address=addr, - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ) - ) - + Op.SSTORE(key=0x2, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.STOP, - nonce=0, - ) - - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=160061, - ) - - post = { - addr: Account(storage={1: 12}), - target: Account(storage={2: 24740}), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP150Specific/test_transaction64_rule_d64p1.py b/tests/ported_static/stEIP150Specific/test_transaction64_rule_d64p1.py deleted file mode 100644 index 2dead5d9e1a..00000000000 --- a/tests/ported_static/stEIP150Specific/test_transaction64_rule_d64p1.py +++ /dev/null @@ -1,84 +0,0 @@ -""" -Test_transaction64_rule_d64p1. - -Ported from: -state_tests/stEIP150Specific/Transaction64Rule_d64p1Filler.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - ["state_tests/stEIP150Specific/Transaction64Rule_d64p1Filler.json"], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_transaction64_rule_d64p1( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_transaction64_rule_d64p1.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - # Source: lll - # { [[1]] 12 } - addr = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x1, value=0xC) + Op.STOP, - nonce=0, - ) - # Source: lll - # { [0] (GAS) (CALL 160000 <contract:0x1000000000000000000000000000000000000118> 0 0 0 0 0) [[2]] (SUB @0 (GAS)) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.GAS) - + Op.POP( - Op.CALL( - gas=0x27100, - address=addr, - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ) - ) - + Op.SSTORE(key=0x2, value=Op.SUB(Op.MLOAD(offset=0x0), Op.GAS)) - + Op.STOP, - nonce=0, - ) - - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=160063, - ) - - post = { - addr: Account(storage={1: 12}), - target: Account(storage={2: 24740}), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stMemExpandingEIP150Calls/test_call_ask_more_gas_on_depth2_then_transaction_has_with_mem_expanding_calls.py b/tests/ported_static/stMemExpandingEIP150Calls/test_call_ask_more_gas_on_depth2_then_transaction_has_with_mem_expanding_calls.py index e181c5c7f03..f080fc4a5db 100644 --- a/tests/ported_static/stMemExpandingEIP150Calls/test_call_ask_more_gas_on_depth2_then_transaction_has_with_mem_expanding_calls.py +++ b/tests/ported_static/stMemExpandingEIP150Calls/test_call_ask_more_gas_on_depth2_then_transaction_has_with_mem_expanding_calls.py @@ -1,17 +1,24 @@ """ -Test_call_ask_more_gas_on_depth2_then_transaction_has_with_mem_expanding... +Verify the EIP-150 63/64 clamp at call depth 2 when the calls also expand +memory: a first-level call receives its exact (affordable) ask, and its own +oversized ask is clamped to 63/64 of what remains after the memory +expansion. Ported from: state_tests/stMemExpandingEIP150Calls/CallAskMoreGasOnDepth2ThenTransactionHasWithMemExpandingCallsFiller.json + +@manually-enhanced: Do not overwrite. The lower frames return their +observed GAS up the stack instead of SSTORE-ing it (the ported lower-frame +gas snapshots are EIP-8037 state-gas traps); every expectation is derived +from the fork, including the top frame's entry snapshot, which pins the +transaction intrinsic cost. """ import pytest from execution_testing import ( Account, - Address, Alloc, - Bytes, - Environment, + Fork, StateTestFiller, Transaction, ) @@ -20,86 +27,111 @@ REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +FLAG_SLOT = 0x0 +DEPTH2_GAS_SLOT = 0x1 +DEPTH1_GAS_SLOT = 0x2 +ENTRY_GAS_SLOT = 0x3 + +# The ported depth-1 budget: affordable, so it is forwarded exactly. +CALLER_GAS = 0x30D40 +# The ported depth-2 ask: above anything the depth-1 frame can hold, so +# the 63/64 clamp decides what the depth-2 frame receives. +ASK_GAS = 0x927C0 +# The ported calls' argument window, driving the memory expansion. +MEM_OFFSET = 0xFF +MEM_SIZE = 0xFF + @pytest.mark.ported_from( [ "state_tests/stMemExpandingEIP150Calls/CallAskMoreGasOnDepth2ThenTransactionHasWithMemExpandingCallsFiller.json" # noqa: E501 ], ) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable +@pytest.mark.valid_from("Berlin") def test_call_ask_more_gas_on_depth2_then_transaction_has_with_mem_expanding_calls( # noqa: E501 state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: - """Test_call_ask_more_gas_on_depth2_then_transaction_has_with_mem_expa...""" # noqa: E501 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, + """A depth-2 memory-expanding call is clamped to 63/64 of its frame.""" + # Depth 2: returns the gas it observed on entry. + gas_return_contract = pre.deploy_contract( + code=Op.MSTORE(0, Op.GAS, new_memory_size=0x20) + Op.RETURN(0, 0x20), ) - # Source: hex - # 0x5a600855 - addr = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x8, value=Op.GAS), - nonce=0, + # Depth 1: records its own entry gas, then asks depth 2 for more gas + # than this frame holds, expanding memory through the args window; + # both observations return to the top frame. + entry_snapshot = Op.MSTORE(0x20, Op.GAS, new_memory_size=0x40) + depth2_call = Op.CALL( + gas=ASK_GAS, + address=gas_return_contract, + args_offset=MEM_OFFSET, + args_size=MEM_SIZE, + ret_size=0x20, + address_warm=False, + account_new=False, + new_memory_size=MEM_OFFSET + MEM_SIZE, + old_memory_size=0x40, ) - # Source: hex - # 0x5a60085560ff60ff60ff60ff600073<contract:0x1000000000000000000000000000000000000108>620927c0f1600955 # noqa: E501 - addr_2 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x8, value=Op.GAS) - + Op.SSTORE( - key=0x9, - value=Op.CALL( - gas=0x927C0, - address=addr, - value=0x0, - args_offset=0xFF, - args_size=0xFF, - ret_offset=0xFF, - ret_size=0xFF, - ), - ), - nonce=0, + caller = pre.deploy_contract( + code=entry_snapshot + depth2_call + Op.RETURN(0, 0x40), ) - # Source: hex - # 0x5a60085560ff60ff60ff60ff600073<contract:0x1000000000000000000000000000000000000107>62030d40f1600955 # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x8, value=Op.GAS) + + # Top frame: snapshots its entry gas (pinning the tx intrinsic), then + # forwards the exact depth-1 budget and stores the success flag plus + # both returned observations. + entry_code = ( + Op.SSTORE(key=ENTRY_GAS_SLOT, value=Op.GAS) + Op.SSTORE( - key=0x9, + key=FLAG_SLOT, value=Op.CALL( - gas=0x30D40, - address=addr_2, - value=0x0, - args_offset=0xFF, - args_size=0xFF, - ret_offset=0xFF, - ret_size=0xFF, + gas=CALLER_GAS, + address=caller, + ret_size=0x40, + address_warm=False, + account_new=False, + new_memory_size=0x40, ), - ), - nonce=0, + ) + + Op.SSTORE(key=DEPTH2_GAS_SLOT, value=Op.MLOAD(0)) + + Op.SSTORE(key=DEPTH1_GAS_SLOT, value=Op.MLOAD(0x20)) ) + entry = pre.deploy_contract(code=entry_code + Op.STOP) + + # Conservative fork-derived budget: the entry's own costs (incl. the + # trailing state-priced stores) plus the full depth-1 grant. + intrinsic = fork.transaction_intrinsic_cost_calculator()() + gas_limit = intrinsic + entry_code.gas_cost(fork) + CALLER_GAS tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=600000, + sender=pre.fund_eoa(), + to=entry, + gas_limit=gas_limit, ) + # The entry snapshot observes everything after the intrinsic; depth 1 + # received exactly CALLER_GAS; the depth-2 base is what remains after + # the snapshot and the call's own costs (incl. memory expansion), + # clamped by EIP-150. + entry_observed = gas_limit - intrinsic - Op.GAS.gas_cost(fork) + depth1_observed = CALLER_GAS - Op.GAS.gas_cost(fork) + base = ( + CALLER_GAS - entry_snapshot.gas_cost(fork) - depth2_call.gas_cost(fork) + ) + assert 0 < base < ASK_GAS, "the 63/64 clamp must apply at depth 2" + forwarded = base - base // 64 + depth2_observed = forwarded - Op.GAS.gas_cost(fork) + post = { - sender: Account(nonce=1), - target: Account(storage={8: 0x8D5B6, 9: 1}), - addr: Account(storage={8: 0x2A1C7}), - addr_2: Account(storage={8: 0x30D3E, 9: 1}), + entry: Account( + storage={ + ENTRY_GAS_SLOT: entry_observed, + FLAG_SLOT: 1, + DEPTH2_GAS_SLOT: depth2_observed, + DEPTH1_GAS_SLOT: depth1_observed, + }, + ), } - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stSystemOperationsTest/test_ab_acalls0.py b/tests/ported_static/stSystemOperationsTest/test_ab_acalls0.py index 863e937e7bb..c87ef000bf1 100644 --- a/tests/ported_static/stSystemOperationsTest/test_ab_acalls0.py +++ b/tests/ported_static/stSystemOperationsTest/test_ab_acalls0.py @@ -1,8 +1,24 @@ """ -Test_ab_acalls0. +Verify mutual A<->B recursion with value transfers and fixed gas asks. + +Contract A calls B forwarding a fixed 100,000-gas ask with 24 wei; B +calls its caller back with a 50,000 ask and 23 wei, storing one plus +the result. Both store into a PC-derived slot only after their call +returns, so every level's store competes with what the descent left +behind: levels too deep to afford it halt and forfeit, rolling back +their stores and transfers, and the surviving storage and balances pin +exactly how far the budget reaches. Ported from: state_tests/stSystemOperationsTest/ABAcalls0Filler.json + +@manually-enhanced: Do not overwrite. The post state (stores and +balances) is predicted by an exact fork-derived replay of the gas flow +(EIP-150 grants, stipend gifting and return, warm/cold and SSTORE +pricing via opcode metadata, EIP-8037 state-gas spill), validated +against the ported Cancun stores. B reaches A as its CALLER instead of +a hardcoded address, which shifts B's PC-derived slot; both slots are +computed from the assembled code. """ import pytest @@ -10,8 +26,7 @@ Account, Address, Alloc, - Bytes, - Environment, + Fork, StateTestFiller, Transaction, ) @@ -20,84 +35,198 @@ REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +A_CALL_GAS = 100_000 +A_CALL_VALUE = 0x18 +B_CALL_GAS = 50_000 +B_CALL_VALUE = 0x17 +# One transfer per level up to the call-depth limit can never run dry. +A_INITIAL_BALANCE = A_CALL_VALUE * 1024 +# Exactly one return payment before any income (the ported balance). +B_INITIAL_BALANCE = B_CALL_VALUE +# Ported budget; pins how deep the mutual recursion reaches. +TX_GAS_LIMIT = 1_000_000 + + +def predict_final_state( + fork: Fork, tx_gas_limit: int, b_address: Address +) -> tuple[int, int, int, int]: + """ + Replay the mutual recursion's gas flow. + + Return A's stored value, B's stored value, and the committed + balance deltas of A and B. Descend the alternating call chain + computing each level's EIP-150 grant (both asks are pushed + constants; a value-bearing call gifts the callee the stipend and + gets any unused part back), then unwind: a level that cannot afford + its post-call store (EIP-2200's stipend rule included) halts and + forfeits its grant, reverting its own store and the transfer that + funded it. Every cost is derived from the fork via opcode metadata, + including EIP-8037 state gas: with a sub-cap gas limit the state + reservoir is zero, so state charges spill from the charging frame's + own gas. + """ + stipend = fork.gas_costs().CALL_STIPEND + pc_cost = Op.PC.gas_cost(fork) + + def raw_store_cost(key_warm: bool, current: int, new: int) -> int: + """Cost of a bare SSTORE; original value is always zero here.""" + return Op.SSTORE( + key_warm=key_warm, + original_value=0, + current_value=current, + new_value=new, + ).gas_cost(fork) + + # A's charges before forwarding: argument pushes plus the call's + # upfront costs (B is cold only in the top level). The ask is a + # pushed constant, so the whole call expression charges up front. + def a_charges(b_warm: bool) -> int: + return Op.CALL( + gas=A_CALL_GAS, + address=b_address, + value=A_CALL_VALUE, + address_warm=b_warm, + value_transfer=True, + ).gas_cost(fork) + + b_value_expr = Op.ADD( + 1, + Op.CALL( + gas=B_CALL_GAS, + address=Op.CALLER, + value=B_CALL_VALUE, + # A is the transaction target: always warm. + address_warm=True, + value_transfer=True, + ), + ) + # B's ADD and its constant push run only after the call returns. + b_post_call = Op.PUSH1[0].gas_cost(fork) + Op.ADD.gas_cost(fork) + b_charges = b_value_expr.gas_cost(fork) - b_post_call + + # Descend: alternate A and B levels until one dies mid-charges. + gas = ( + tx_gas_limit + - fork.transaction_intrinsic_cost_calculator()() + - fork.transaction_top_frame_state_gas() + ) + levels: list[tuple[int, int]] = [] + level = 0 + balance = {"A": A_INITIAL_BALANCE, "B": B_INITIAL_BALANCE} + while True: + level += 1 + is_a = level % 2 == 1 + if is_a: + gas -= a_charges(b_warm=level > 1) + ask, value = A_CALL_GAS, A_CALL_VALUE + else: + gas -= b_charges + ask, value = B_CALL_GAS, B_CALL_VALUE + if gas < 0: + break + assert level < 1024, "recursion must die of gas, not depth" + payer = "A" if is_a else "B" + assert balance[payer] >= value, "value transfer must be funded" + balance[payer] -= value + balance["B" if is_a else "A"] += value + forwarded = min(ask, gas - gas // 64) + levels.append((gas, forwarded)) + gas = forwarded + stipend + + # Unwind: a failed level forfeits its grant and reverts the whole + # committed state below it (stores, warmth, and transfers). + child_ok = False + leftover = 0 + a_val, a_warm, b_val, b_warm = 0, False, 0, False + a_delta, b_delta = 0, 0 + for lvl in range(len(levels), 0, -1): + available, forwarded = levels[lvl - 1] + is_a = lvl % 2 == 1 + gas = available - forwarded + (leftover if child_ok else 0) + result = 1 if child_ok else 0 + if is_a: + gas -= pc_cost + store_value, current, warm = result, a_val, a_warm + else: + gas -= b_post_call + pc_cost + store_value, current, warm = 1 + result, b_val, b_warm + ok = gas >= 0 and gas > stipend + if ok: + gas -= raw_store_cost(warm, current, store_value) + ok = gas >= 0 + if ok: + # Commit this level: its store and the transfer into it. + if is_a: + a_val, a_warm = store_value, True + if lvl > 1: + a_delta += B_CALL_VALUE + b_delta -= B_CALL_VALUE + else: + b_val, b_warm = store_value, True + a_delta -= A_CALL_VALUE + b_delta += A_CALL_VALUE + leftover = gas + child_ok = True + else: + child_ok = False + leftover = 0 + a_val, a_warm, b_val, b_warm = 0, False, 0, False + a_delta, b_delta = 0, 0 + assert child_ok, "the top level must complete" + return a_val, b_val, a_delta, b_delta + @pytest.mark.ported_from( ["state_tests/stSystemOperationsTest/ABAcalls0Filler.json"], ) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable +@pytest.mark.valid_from("Berlin") def test_ab_acalls0( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: - """Test_ab_acalls0.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, + """Pin how deep a value-bearing A<->B recursion reaches.""" + # B calls whoever called it, so it needs no embedded address. + b_value_expr = Op.ADD( + 1, + Op.CALL(gas=B_CALL_GAS, address=Op.CALLER, value=B_CALL_VALUE), + ) + contract_b = pre.deploy_contract( + code=Op.SSTORE(key=Op.PC, value=b_value_expr) + Op.STOP, + balance=B_INITIAL_BALANCE, ) - # Source: lll - # { [[ (PC) ]] (CALL 100000 <contract:0x945304eb96065b2a98b57a48a06ae28d285a71b5> 24 0 0 0 0) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE( - key=Op.PC, - value=Op.CALL( - gas=0x186A0, - address=0x44EB1162303B6A60F2F8882D43D661787B3011E6, - value=0x18, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.STOP, - balance=0xDE0B6B3A7640000, - nonce=0, - address=Address(0xD6CD6EC9ADCA299F2BBFD754FF8BCF6A4B9AAE40), # noqa: E501 + a_value_expr = Op.CALL( + gas=A_CALL_GAS, address=contract_b, value=A_CALL_VALUE ) - # Source: lll - # { [[ (PC) ]] (ADD 1 (CALL 50000 <contract:target:0x095e7baea6a6c7c4c2dfeb977efac326af552d87> 23 0 0 0 0)) } # noqa: E501 - addr = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE( - key=Op.PC, - value=Op.ADD( - 0x1, - Op.CALL( - gas=0xC350, - address=0xD6CD6EC9ADCA299F2BBFD754FF8BCF6A4B9AAE40, - value=0x17, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ), - ) - + Op.STOP, - balance=23, - nonce=0, - address=Address(0x44EB1162303B6A60F2F8882D43D661787B3011E6), # noqa: E501 + contract_a = pre.deploy_contract( + code=Op.SSTORE(key=Op.PC, value=a_value_expr) + Op.STOP, + balance=A_INITIAL_BALANCE, ) + # PC keys: each store's key is the code offset of its PC opcode, + # which sits right after the assembled value expression. + a_key = len(bytes(a_value_expr)) + b_key = len(bytes(b_value_expr)) + tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=1000000, - value=0x186A0, + sender=pre.fund_eoa(), + to=contract_a, + gas_limit=TX_GAS_LIMIT, ) + a_val, b_val, a_delta, b_delta = predict_final_state( + fork, TX_GAS_LIMIT, contract_b + ) post = { - target: Account(storage={36: 1}), - addr: Account(storage={38: 1}), + contract_a: Account( + storage={a_key: a_val}, + balance=A_INITIAL_BALANCE + a_delta, + ), + contract_b: Account( + storage={b_key: b_val}, + balance=B_INITIAL_BALANCE + b_delta, + ), } - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stSystemOperationsTest/test_ab_acalls3.py b/tests/ported_static/stSystemOperationsTest/test_ab_acalls3.py index 8f2b77d0966..5647ebd2c95 100644 --- a/tests/ported_static/stSystemOperationsTest/test_ab_acalls3.py +++ b/tests/ported_static/stSystemOperationsTest/test_ab_acalls3.py @@ -1,8 +1,22 @@ """ -Test_ab_acalls3. +Verify mutual A<->B recursion where each side reserves 100,000 gas. + +Both contracts bump their own depth counter during descent, then call +the other side forwarding everything but a 100,000-gas reserve (A sends +one wei each level; B sends nothing back). Nothing runs after the call, +so only the single deepest level dies of gas and every completed +level's counter bump and transfer persist: the counters and balances +pin exactly how many rounds the budget sustains. Ported from: state_tests/stSystemOperationsTest/ABAcalls3Filler.json + +@manually-enhanced: Do not overwrite. The post state (counters and +balances) is predicted by an exact fork-derived replay of the gas flow +(EIP-150 grants, stipend gifting, warm/cold and SSTORE pricing via +opcode metadata, EIP-8037 state-gas spill), validated against the +ported Cancun counters. B reaches A as its CALLER instead of a +hardcoded address. """ import pytest @@ -10,8 +24,8 @@ Account, Address, Alloc, - Bytes, - Environment, + Bytecode, + Fork, StateTestFiller, Transaction, ) @@ -20,76 +34,184 @@ REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +COUNTER_SLOT = 0 +# Gas each level keeps back for itself before forwarding the rest. +GAS_RESERVE = 100_000 +A_CALL_VALUE = 1 +# One transfer per level up to the call-depth limit can never run dry. +A_INITIAL_BALANCE = A_CALL_VALUE * 1024 +# Ported budget; pins how many rounds the recursion sustains. +TX_GAS_LIMIT = 10_000_000 + + +def predict_depths( + fork: Fork, tx_gas_limit: int, b_address: Address +) -> tuple[int, int]: + """ + Replay the mutual recursion's gas flow. + + Return how many A and B levels complete. Descend the alternating + call chain: each level bumps its own counter (one cold set per + contract, then dirty rewrites), pays its call charges, and forwards + everything but the reserve under the EIP-150 63/64 rule; once the + reserve underflows, the wrapped ask forwards the 63/64 maximum. + Nothing runs after a call, so only the single deepest level dies + and its bump and incoming transfer revert. Every cost is derived + from the fork via opcode metadata, including EIP-8037 state gas: + with a sub-cap gas limit the state reservoir is zero, so state + charges spill from the charging frame's own gas. + """ + push_cost = Op.PUSH1[0].gas_cost(fork) + # The ask expression's SUB runs after GAS reads gas_left. + post_gas_read = Op.SUB.gas_cost(fork) + # EIP-2200: any SSTORE with gas_left <= stipend halts exceptionally. + stipend = fork.gas_costs().CALL_STIPEND + + def raw_store_cost(key_warm: bool, current: int, new: int) -> int: + """Cost of a bare SSTORE; original value is always zero here.""" + return Op.SSTORE( + key_warm=key_warm, + original_value=0, + current_value=current, + new_value=new, + ).gas_cost(fork) + + sstore_warm_set = raw_store_cost(True, 0, 1) + sstore_warm_dirty = raw_store_cost(True, 1, 2) + + def bump_statics(key_warm: bool) -> int: + """Counter-bump costs before its SSTORE (value expr plus key).""" + return ( + Op.ADD(Op.SLOAD(key=COUNTER_SLOT, key_warm=key_warm), 1).gas_cost( + fork + ) + + push_cost + ) + + def call_split( + address: Address | Op, warm: bool, value: int + ) -> tuple[int, int]: + """Pre-GAS-read and upfront charges of one side's call.""" + upfront = Op.CALL( + address_warm=warm, value_transfer=value > 0 + ).gas_cost(fork) + composite = Op.CALL( + gas=Op.SUB(Op.GAS, GAS_RESERVE), + address=address, + value=value, + address_warm=warm, + value_transfer=value > 0, + ).gas_cost(fork) + return composite - upfront - post_gas_read, upfront + + a_pre, a_upfront_cold = call_split(b_address, False, A_CALL_VALUE) + _, a_upfront_warm = call_split(b_address, True, A_CALL_VALUE) + # A is the transaction target: always warm for B's call back. + b_pre, b_upfront = call_split(Op.CALLER, True, 0) + + gas = ( + tx_gas_limit + - fork.transaction_intrinsic_cost_calculator()() + - fork.transaction_top_frame_state_gas() + ) + level = 0 + a_balance = A_INITIAL_BALANCE + while True: + level += 1 + is_a = level % 2 == 1 + # Each contract's first level pays the cold counter set. + first = level <= 2 + gas -= bump_statics(key_warm=not first) + if gas < 0 or gas <= stipend: + break + gas -= sstore_warm_set if first else sstore_warm_dirty + if gas < 0: + break + gas -= a_pre if is_a else b_pre + if gas < 0: + break + gas_read = gas + if is_a: + gas -= post_gas_read + ( + a_upfront_cold if level == 1 else a_upfront_warm + ) + else: + gas -= post_gas_read + b_upfront + if gas < 0: + break + assert level < 1024, "recursion must die of gas, not depth" + if is_a: + assert a_balance >= A_CALL_VALUE, "transfer must be funded" + a_balance -= A_CALL_VALUE + # A reserve underflow wraps mod 2**256: an effectively infinite + # ask, clamped to the 63/64 forwardable maximum. + ask = gas_read - GAS_RESERVE if gas_read >= GAS_RESERVE else 1 << 256 + forwarded = min(ask, gas - gas // 64) + gas = forwarded + (stipend if is_a else 0) + + completed = level - 1 + assert completed >= 2, "both sides must run at least once" + a_count = (completed + 1) // 2 + b_count = completed // 2 + return a_count, b_count + @pytest.mark.ported_from( ["state_tests/stSystemOperationsTest/ABAcalls3Filler.json"], ) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable +@pytest.mark.valid_from("Berlin") def test_ab_acalls3( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: - """Test_ab_acalls3.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=100000000, - ) + """Pin how many rounds a reserve-throttled A<->B recursion runs.""" - # Source: lll - # { [[ 0 ]] (ADD (SLOAD 0) 1) (CALL (- (GAS) 100000) <contract:0x945304eb96065b2a98b57a48a06ae28d285a71b5> 1 0 0 0 0) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=Op.ADD(Op.SLOAD(key=0x0), 0x1)) - + Op.CALL( - gas=Op.SUB(Op.GAS, 0x186A0), - address=0xA890CEB693666313E0A5A1BE4F59F06C1E33F5C9, - value=0x1, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, + def bounce_code(call: Bytecode) -> Bytecode: + """Bump the own-depth counter, then call the other side.""" + return ( + Op.SSTORE( + key=COUNTER_SLOT, + value=Op.ADD(Op.SLOAD(key=COUNTER_SLOT), 1), + ) + + call + + Op.STOP ) - + Op.STOP, - balance=0xFA3E8, - nonce=0, - address=Address(0x4776B53DEB22F16581088F679DBA75E205B65D34), # noqa: E501 + + # B calls whoever called it, so it needs no embedded address. + contract_b = pre.deploy_contract( + code=bounce_code( + Op.CALL(gas=Op.SUB(Op.GAS, GAS_RESERVE), address=Op.CALLER) + ), ) - # Source: lll - # { [[ 0 ]] (ADD (SLOAD 0) 1) (CALL (- (GAS) 100000) <contract:target:0x095e7baea6a6c7c4c2dfeb977efac326af552d87> 0 0 0 0 0) } # noqa: E501 - addr = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=Op.ADD(Op.SLOAD(key=0x0), 0x1)) - + Op.CALL( - gas=Op.SUB(Op.GAS, 0x186A0), - address=0x4776B53DEB22F16581088F679DBA75E205B65D34, - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ) - + Op.STOP, - nonce=0, - address=Address(0xA890CEB693666313E0A5A1BE4F59F06C1E33F5C9), # noqa: E501 + contract_a = pre.deploy_contract( + code=bounce_code( + Op.CALL( + gas=Op.SUB(Op.GAS, GAS_RESERVE), + address=contract_b, + value=A_CALL_VALUE, + ) + ), + balance=A_INITIAL_BALANCE, ) tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=10000000, - value=0x186A0, + sender=pre.fund_eoa(), + to=contract_a, + gas_limit=TX_GAS_LIMIT, ) + a_count, b_count = predict_depths(fork, TX_GAS_LIMIT, contract_b) + # Each completed B level keeps the wei its calling A level sent. post = { - target: Account(storage={0: 52}), - addr: Account(storage={0: 52}), + contract_a: Account( + storage={COUNTER_SLOT: a_count}, + balance=A_INITIAL_BALANCE - b_count * A_CALL_VALUE, + ), + contract_b: Account( + storage={COUNTER_SLOT: b_count}, + balance=b_count * A_CALL_VALUE, + ), } - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stSystemOperationsTest/test_call_recursive_bomb3.py b/tests/ported_static/stSystemOperationsTest/test_call_recursive_bomb3.py index ee666d8bf61..445e782f2d2 100644 --- a/tests/ported_static/stSystemOperationsTest/test_call_recursive_bomb3.py +++ b/tests/ported_static/stSystemOperationsTest/test_call_recursive_bomb3.py @@ -1,17 +1,32 @@ """ -Test_call_recursive_bomb3. +Verify a self-recursive CALL bomb that keeps only a 224-gas reserve. + +Each level bumps a shared depth counter and forwards everything but a +tiny reserve to a call to itself, so descent is throttled only by the +EIP-150 63/64 withhold. On the way back up a level must afford its +success-flag store from its 1/64 retention plus whatever its child +returned; levels that cannot (EIP-2200's stipend rule included) halt +and forfeit, so the surviving storage pins the exact depth the budget +sustains. Ported from: state_tests/stSystemOperationsTest/CallRecursiveBomb3Filler.json + +@manually-enhanced: Do not overwrite. The post state is predicted by an +exact fork-derived replay of the recursion's gas flow (EIP-150 grants, +returned-leftover propagation, warm/cold and SSTORE pricing via opcode +metadata, EIP-8037 state-gas spill), validated against the ported +Cancun depth. Under Amsterdam's revised storage-growth pricing even the +top level cannot afford its zero-to-one flag store at the ported +budget, so the whole transaction reverts and the post pins empty +storage. """ import pytest from execution_testing import ( Account, - Address, Alloc, - Bytes, - Environment, + Fork, StateTestFiller, Transaction, ) @@ -20,58 +35,185 @@ REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +COUNTER_SLOT = 0 +RESULT_SLOT = 1 +# Gas each level keeps back; far below a cold store, so completing the +# post-call flag store depends on the 1/64 retention and the child's +# returned leftover. +GAS_RESERVE = 224 +# Ported budget; pins the OOG-terminated depth. +TX_GAS_LIMIT = 1_000_000 + +RECURSION_CODE = ( + Op.SSTORE( + key=COUNTER_SLOT, + value=Op.ADD(Op.SLOAD(key=COUNTER_SLOT), 1), + ) + + Op.SSTORE( + key=RESULT_SLOT, + value=Op.CALL( + gas=Op.SUB(Op.GAS, GAS_RESERVE), + address=Op.ADDRESS, + ), + ) + + Op.STOP +) + + +def predict_recursion_storage(fork: Fork, tx_gas_limit: int) -> dict[int, int]: + """ + Replay the recursion's gas flow and return the surviving storage. + + Descend the self-call chain computing each level's EIP-150 grant, + then unwind: a level that cannot afford its flag store halts and + forfeits its entire grant to its parent, so the deepest level that + completes fixes the surviving depth counter (deeper levels' writes + and warmth all revert). The level above the deepest survivor funds + its more expensive zero-to-one flag set partly from the survivor's + returned leftover. Every cost is derived from the fork via opcode + metadata, including EIP-8037 state gas: with a sub-cap gas limit + the state reservoir is zero, so state charges spill from the + charging frame's own gas. + """ + push_cost = Op.PUSH1[0].gas_cost(fork) + # The ask expression's SUB runs after GAS reads gas_left. + post_gas_read = Op.SUB.gas_cost(fork) + # EIP-2200: any SSTORE with gas_left <= stipend halts exceptionally. + stipend = fork.gas_costs().CALL_STIPEND + + def raw_store_cost(key_warm: bool, current: int, new: int) -> int: + """Cost of a bare SSTORE; original value is always zero here.""" + return Op.SSTORE( + key_warm=key_warm, + original_value=0, + current_value=current, + new_value=new, + ).gas_cost(fork) + + sstore_warm_set = raw_store_cost(True, 0, 1) + sstore_warm_dirty = raw_store_cost(True, 1, 2) + sstore_warm_noop = raw_store_cost(True, 1, 1) + sstore_cold_noop = raw_store_cost(False, 0, 0) + + def bump_statics(key_warm: bool) -> int: + """Counter-bump costs before its SSTORE (value expr plus key).""" + return ( + Op.ADD(Op.SLOAD(key=COUNTER_SLOT, key_warm=key_warm), 1).gas_cost( + fork + ) + + push_cost + ) + + bump_statics_cold = bump_statics(False) + bump_statics_warm = bump_statics(True) + + ask_expr = Op.SUB(Op.GAS, GAS_RESERVE) + call_upfront = Op.CALL(address_warm=True).gas_cost(fork) + # Everything charged before GAS reads gas_left: the call's argument + # pushes, ADDRESS, and the reserve push plus the GAS opcode itself. + pre_gas_read = ( + Op.CALL(gas=ask_expr, address=Op.ADDRESS, address_warm=True).gas_cost( + fork + ) + - call_upfront + - post_gas_read + ) + + # Descend: compute each level's grant until a level dies mid-frame. + gas = ( + tx_gas_limit + - fork.transaction_intrinsic_cost_calculator()() + - fork.transaction_top_frame_state_gas() + ) + levels: list[tuple[int, int]] = [] + level = 0 + while True: + level += 1 + first = level == 1 + gas -= bump_statics_cold if first else bump_statics_warm + if gas < 0 or gas <= stipend: + break + gas -= sstore_warm_set if first else sstore_warm_dirty + if gas < 0: + break + gas -= pre_gas_read + if gas < 0: + break + gas_read = gas + gas -= post_gas_read + call_upfront + if gas < 0: + break + assert level < 1024, "recursion must die of gas, not depth" + # A reserve underflow wraps mod 2**256: an effectively infinite + # ask, clamped to the 63/64 forwardable maximum. + ask = gas_read - GAS_RESERVE if gas_read >= GAS_RESERVE else 1 << 256 + forwarded = min(ask, gas - gas // 64) + levels.append((gas, forwarded)) + gas = forwarded + + # Unwind: a failed level forfeits its whole grant to its parent. + child_ok = False + result_below = 0 + leftover = 0 + survivor = 0 + for lvl in range(len(levels), 0, -1): + available, forwarded = levels[lvl - 1] + gas = available - forwarded + (leftover if child_ok else 0) + # Flag store: push the slot key, then store the success flag. + # Below the deepest completing level everything reverts, so its + # own store finds a cold slot and a zero current value. + gas -= push_cost + ok = gas >= 0 and gas > stipend + if ok: + if not child_ok: + result_store = sstore_cold_noop + elif result_below == 0: + result_store = sstore_warm_set + else: + result_store = sstore_warm_noop + gas -= result_store + ok = gas >= 0 + if ok: + if not child_ok: + survivor = lvl + result_below = 1 if child_ok else 0 + leftover = gas + child_ok = True + else: + child_ok = False + result_below = 0 + leftover = 0 + survivor = 0 + if not child_ok: + # The top level itself cannot afford its flag store (its + # retention plus the child's leftover falls short of the + # storage-growth cost), so the transaction halts and every + # write reverts. + return {COUNTER_SLOT: 0, RESULT_SLOT: 0} + assert survivor > 0, "a completing top level must record a depth" + return {COUNTER_SLOT: survivor, RESULT_SLOT: result_below} + @pytest.mark.ported_from( ["state_tests/stSystemOperationsTest/CallRecursiveBomb3Filler.json"], ) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable +@pytest.mark.valid_from("Berlin") def test_call_recursive_bomb3( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: - """Test_call_recursive_bomb3.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - # Source: lll - # { [[ 0 ]] (+ (SLOAD 0) 1) [[ 1 ]] (CALL (- (GAS) 224) (ADDRESS) 0 0 0 0 0) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=Op.ADD(Op.SLOAD(key=0x0), 0x1)) - + Op.SSTORE( - key=0x1, - value=Op.CALL( - gas=Op.SUB(Op.GAS, 0xE0), - address=Op.ADDRESS, - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.STOP, - balance=0x1312D00, - nonce=0, - ) + """Pin the depth a thin-reserve CALL self-recursion sustains.""" + target = pre.deploy_contract(code=RECURSION_CODE) tx = Transaction( - sender=sender, + sender=pre.fund_eoa(), to=target, - data=Bytes(""), - gas_limit=1000000, - value=0x186A0, + gas_limit=TX_GAS_LIMIT, ) - post = {target: Account(storage={0: 18, 1: 1})} + post = { + target: Account(storage=predict_recursion_storage(fork, TX_GAS_LIMIT)), + } - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) From ccaaaba58c748c072ca0ef9a09e91f9e3dcd277a Mon Sep 17 00:00:00 2001 From: Aliaksei Osipau <me@flcl.me> Date: Mon, 10 Aug 2026 16:44:30 +0300 Subject: [PATCH 213/233] fix(tests): account for value in EIP-8070 transaction gas (#3344) * fix(tests): account for value in EIP-8070 transaction gas * refacotr: suggested changes --------- Co-authored-by: LouisTsai <q1030176@gmail.com> --- tests/amsterdam/eip8070_sparse_blobpool/conftest.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/amsterdam/eip8070_sparse_blobpool/conftest.py b/tests/amsterdam/eip8070_sparse_blobpool/conftest.py index 3952b6363cf..cb2a757494c 100644 --- a/tests/amsterdam/eip8070_sparse_blobpool/conftest.py +++ b/tests/amsterdam/eip8070_sparse_blobpool/conftest.py @@ -27,9 +27,11 @@ def tx_value() -> int: @pytest.fixture -def tx_gas(fork: Fork) -> int: +def tx_gas(fork: Fork, tx_value: int) -> int: """Gas allocated to transactions sent during test.""" - return fork.transaction_intrinsic_cost_calculator()() + return fork.transaction_intrinsic_cost_calculator()( + sends_value=tx_value > 0 + ) @pytest.fixture From 46ba06183131f26cdfa52bb763a203591ce6b5b1 Mon Sep 17 00:00:00 2001 From: spencer <spencer.taylor-brown@ethereum.org> Date: Tue, 11 Aug 2026 06:41:11 +0200 Subject: [PATCH 214/233] fix(test-client-clis): update geth BAL exception mappings (#3347) --- .../src/execution_testing/client_clis/clis/geth.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/testing/src/execution_testing/client_clis/clis/geth.py b/packages/testing/src/execution_testing/client_clis/clis/geth.py index e133c839455..57c4793d1d9 100644 --- a/packages/testing/src/execution_testing/client_clis/clis/geth.py +++ b/packages/testing/src/execution_testing/client_clis/clis/geth.py @@ -156,7 +156,10 @@ class GethExceptionMapper(ExceptionMapper): # EELS definition for `is_valid_deposit_event_data`: # https://github.com/ethereum/execution-specs/blob/5ddb904fa7ba27daeff423e78466744c51e8cb6a/src/ethereum/forks/prague/requests.py#L51 # BAL Exceptions - BlockException.INVALID_BAL_HASH: (r"invalid block access list:"), + BlockException.INVALID_BAL_HASH: ( + r"invalid block access list:|" + r"access list hash mismatch" + ), BlockException.INVALID_BLOCK_ACCESS_LIST: ( r"difference between computed state diff and " r"BAL entry for account|" @@ -165,11 +168,14 @@ class GethExceptionMapper(ExceptionMapper): r"which weren't reported in BAL|" r"BAL change not reported in computed|" r"additional mutations compared to BAL|" + r"access list hash mismatch|" + r"failed to decode BAL|" r"[bB][aA][lL] validation fail" ), BlockException.INCORRECT_BLOCK_FORMAT: (r"invalid block access list:"), BlockException.BLOCK_ACCESS_LIST_GAS_LIMIT_EXCEEDED: ( - r"block access list exceeds gas limit" + r"block access list exceeds gas limit|" + r"block access list exceeds size constraint" ), BlockException.GAS_USED_OVERFLOW: (r"gas limit reached"), TransactionException.INTRINSIC_GAS_TOO_LOW: ( From ae801932088ce87df0b37cb7237ca84c45b21ff8 Mon Sep 17 00:00:00 2001 From: spencer <spencer.taylor-brown@ethereum.org> Date: Tue, 11 Aug 2026 07:41:37 +0200 Subject: [PATCH 215/233] refactor(tests): derive blob tx gas from fork intrinsic cost (#3346) --- tests/osaka/eip7594_peerdas/test_get_blobs.py | 6 ++++-- .../eip7594_peerdas/test_max_blob_per_tx.py | 16 +++++++++++++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/tests/osaka/eip7594_peerdas/test_get_blobs.py b/tests/osaka/eip7594_peerdas/test_get_blobs.py index 0a6d2052ff9..b0e2d073856 100644 --- a/tests/osaka/eip7594_peerdas/test_get_blobs.py +++ b/tests/osaka/eip7594_peerdas/test_get_blobs.py @@ -49,9 +49,11 @@ def tx_value() -> int: @pytest.fixture -def tx_gas() -> int: +def tx_gas(fork: Fork, tx_value: int) -> int: """Gas allocated to transactions sent during test.""" - return 21_000 + return fork.transaction_intrinsic_cost_calculator()( + sends_value=tx_value > 0 + ) @pytest.fixture diff --git a/tests/osaka/eip7594_peerdas/test_max_blob_per_tx.py b/tests/osaka/eip7594_peerdas/test_max_blob_per_tx.py index 317aec9164a..693ee2a4d48 100644 --- a/tests/osaka/eip7594_peerdas/test_max_blob_per_tx.py +++ b/tests/osaka/eip7594_peerdas/test_max_blob_per_tx.py @@ -54,12 +54,26 @@ def blob_gas_price(fork: Fork | TransitionFork) -> int: ) +@pytest.fixture +def tx_gas(fork: Fork | TransitionFork) -> int: + """Intrinsic gas for the value-carrying blob transactions.""" + return max( + fork.transitions_from().transaction_intrinsic_cost_calculator()( + sends_value=True + ), + fork.transitions_to().transaction_intrinsic_cost_calculator()( + sends_value=True + ), + ) + + @pytest.fixture def tx( sender: Address, destination: Address, blob_gas_price: int, blob_count: int, + tx_gas: int, ) -> Transaction: """Blob transaction fixture.""" return Transaction( @@ -67,7 +81,7 @@ def tx( sender=sender, to=destination, value=1, - gas_limit=21_000, + gas_limit=tx_gas, max_fee_per_gas=10, max_priority_fee_per_gas=1, max_fee_per_blob_gas=blob_gas_price, From 5b2b22c75f69bda02615204396b70a91e00529e0 Mon Sep 17 00:00:00 2001 From: danceratopz <danceratopz@gmail.com> Date: Tue, 11 Aug 2026 07:44:24 +0200 Subject: [PATCH 216/233] feat(consume): add ms-precision lifecycle logging to consume enginex (#3306) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add millisecond precision to log timestamps (previously truncated to seconds) and emit one structured, grep-able INFO line per lifecycle phase transition in the enginex simulator: ⏱ phase=<name> group=<id> ms=<duration> Phases bracketed per pre-alloc group cycle: - group_start (with idle_ms: xdist dispatch wait since the previous test protocol ended on the worker) - fixture_load (test fixture JSON read+parse, cache misses only) - pre_alloc_load (pre-alloc group JSON read+parse, cache misses only) - genesis_prep (genesis/alloc to_json conversion, cache misses only) - genesis_serialize (genesis JSON dump before client start) - client_start (hive start-client API call: container create, client boot and check-live wait) - client_stop (hive stop-client API call) Together with the existing per-test START/END lines (now ms-precision) this makes the complete->next-start "teardown gap" between group cycles exactly decomposable from the per-worker log files. --- .../plugins/consume/simulators/base.py | 9 +++ .../consume/simulators/enginex/conftest.py | 61 +++++++++++++++++++ .../consume/simulators/multi_test_client.py | 17 +++++- .../src/execution_testing/logging/logger.py | 4 +- .../logging/tests/test_logging.py | 5 +- 5 files changed, 91 insertions(+), 5 deletions(-) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/base.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/base.py index 35edcedba81..23d6fbd5ad0 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/base.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/base.py @@ -1,5 +1,7 @@ """Common pytest fixtures for the Hive simulators.""" +import logging +import time from pathlib import Path from typing import Dict, Generator, Literal @@ -19,6 +21,8 @@ from ..consume import FixturesSource from .helpers.rejected_blocks import BlockRejectionTracker +logger = logging.getLogger(__name__) + @pytest.fixture(scope="function") def eth_rpc(client: Client) -> Generator[EthRPC, None, None]: @@ -87,7 +91,12 @@ def __getitem__(self, key: Path) -> Fixtures: """ assert key.is_file(), f"Expected a file path, got '{key}'" if key not in self._fixtures: + start = time.perf_counter() self._fixtures[key] = Fixtures.model_validate_json(key.read_text()) + logger.info( + f"⏱ phase=fixture_load file={key.name} " + f"ms={(time.perf_counter() - start) * 1000:.1f}" + ) return self._fixtures[key] diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/enginex/conftest.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/enginex/conftest.py index 78c57a4fb7e..f0275269e97 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/enginex/conftest.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/enginex/conftest.py @@ -10,6 +10,7 @@ import io import json import logging +import time from typing import TYPE_CHECKING, Generator, cast import pytest @@ -98,6 +99,53 @@ def sort_key(item: pytest.Item) -> tuple[int, str]: logger.info("Sorted tests by pre-alloc group (largest first)") +class _GroupDispatchTracker: + """ + Per-worker (per-process) tracker of the idle time between test protocols. + + The gap between the end of one test's run protocol and the start of the + next test's protocol is time the xdist worker spends waiting for the + controller (dispatch latency) at group boundaries. Small gaps may + instead be ordinary inter-protocol overhead (e.g. report submission): + the gap only equals dispatch latency when the worker's local item + queue is empty. + """ + + last_group: str | None = None + last_protocol_end: float | None = None + + +def _xdist_group_name(item: pytest.Item) -> str | None: + """Return the xdist_group marker name of an item, if any.""" + for marker in item.iter_markers("xdist_group"): + if "name" in marker.kwargs: + return marker.kwargs["name"] + return None + + +@pytest.hookimpl(hookwrapper=True, tryfirst=True) +def pytest_runtest_protocol( + item: pytest.Item, nextitem: pytest.Item | None +) -> Generator[None, None, None]: + """Log a group-start marker with dispatch idle time at group boundaries.""" + del nextitem + + group = _xdist_group_name(item) + if group is not None and group != _GroupDispatchTracker.last_group: + if _GroupDispatchTracker.last_protocol_end is not None: + idle_ms = ( + time.perf_counter() - _GroupDispatchTracker.last_protocol_end + ) * 1000 + logger.info( + f"⏱ phase=group_start group={group} idle_ms={idle_ms:.1f}" + ) + else: + logger.info(f"⏱ phase=group_start group={group}") + _GroupDispatchTracker.last_group = group + yield + _GroupDispatchTracker.last_protocol_end = time.perf_counter() + + @pytest.fixture(scope="session", autouse=True) def _configure_client_manager( multi_test_client_manager: "MultiTestClientManager", @@ -169,16 +217,22 @@ def client( logger.info(f"♻️ Reusing client for group {group_identifier}") else: # Start new client; calculate genesis + serialize_start = time.perf_counter() genesis_bytes = json.dumps(client_genesis).encode("utf-8") buffered_genesis = io.BufferedReader( cast(io.RawIOBase, io.BytesIO(genesis_bytes)) ) + logger.info( + f"⏱ phase=genesis_serialize group={group_identifier} " + f"ms={(time.perf_counter() - serialize_start) * 1000:.1f}" + ) logger.info( f"🚀 Starting client ({client_type.name}) " f"for group {group_identifier}" ) + start_requested = time.perf_counter() with total_timing_data.time("Start client"): resolved_client = multi_test_hive_test.start_client( client_type=client_type, @@ -192,6 +246,13 @@ def client( "information." ) + # The hive start-client API only returns once the client answers + # its liveness check, so this duration spans container creation, + # client boot and the check-live wait. + logger.info( + f"⏱ phase=client_start group={group_identifier} " + f"ms={(time.perf_counter() - start_requested) * 1000:.1f}" + ) logger.info( f"Client ({client_type.name}) ready for group {group_identifier}" ) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/multi_test_client.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/multi_test_client.py index 898c9273959..c28bb60e085 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/multi_test_client.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/multi_test_client.py @@ -1,6 +1,7 @@ """Pytest fixtures for multi-test client architecture.""" import logging +import time from typing import Generator import pytest @@ -89,7 +90,12 @@ def mark_test_completed(self, group_identifier: str, test_id: str) -> None: logger.info( f"🛑 Stopping client for group {group_identifier}" ) + start = time.perf_counter() client.stop() + logger.info( + f"⏱ phase=client_stop group={group_identifier} " + f"ms={(time.perf_counter() - start) * 1000:.1f}" + ) except Exception as e: logger.error( "Error stopping client for group " @@ -188,10 +194,14 @@ def pre_alloc_group( # Load and cache logger.debug(f"Loading pre-alloc group from {pre_alloc_path}") + start = time.perf_counter() pre_alloc_group_obj = PreAllocGroup.from_file(pre_alloc_path) pre_alloc_group_cache[pre_hash] = pre_alloc_group_obj - logger.info(f"Loaded pre-alloc group for {pre_hash}") + logger.info( + f"⏱ phase=pre_alloc_load group={pre_hash} " + f"ms={(time.perf_counter() - start) * 1000:.1f}" + ) return pre_alloc_group_obj @@ -214,12 +224,17 @@ def client_genesis( if pre_hash in client_genesis_cache: return client_genesis_cache[pre_hash] + start = time.perf_counter() genesis = to_json(pre_alloc_group.genesis) alloc = to_json(pre_alloc_group.pre) # NOTE: nethermind requires account keys without '0x' prefix genesis["alloc"] = {k.replace("0x", ""): v for k, v in alloc.items()} client_genesis_cache[pre_hash] = genesis + logger.info( + f"⏱ phase=genesis_prep group={pre_hash} " + f"ms={(time.perf_counter() - start) * 1000:.1f}" + ) return genesis diff --git a/packages/testing/src/execution_testing/logging/logger.py b/packages/testing/src/execution_testing/logging/logger.py index 61e745b166d..47f4ad715e2 100644 --- a/packages/testing/src/execution_testing/logging/logger.py +++ b/packages/testing/src/execution_testing/logging/logger.py @@ -86,7 +86,7 @@ def get_logger(name: str) -> EESTLogger: class UTCFormatter(logging.Formatter): """ - Log formatter that formats UTC timestamps without milliseconds. + Log formatter that formats UTC timestamps with millisecond precision. """ def formatTime(self, record: LogRecord, datefmt: str | None = None) -> str: # noqa: D102,N802 @@ -94,7 +94,7 @@ def formatTime(self, record: LogRecord, datefmt: str | None = None) -> str: # n del datefmt dt = datetime.fromtimestamp(record.created, tz=timezone.utc) - return dt.strftime("%Y-%m-%d %H:%M:%S") + return f"{dt.strftime('%Y-%m-%d %H:%M:%S')}.{int(record.msecs):03d}" def format(self, record: LogRecord) -> str: """Format with relative pathname from current working directory.""" diff --git a/packages/testing/src/execution_testing/logging/tests/test_logging.py b/packages/testing/src/execution_testing/logging/tests/test_logging.py index 87fe76ac89c..1b9be2f0bc7 100644 --- a/packages/testing/src/execution_testing/logging/tests/test_logging.py +++ b/packages/testing/src/execution_testing/logging/tests/test_logging.py @@ -98,14 +98,15 @@ def test_utc_formatter(self) -> None: { "msg": "Test message", "created": 1609459200.0, # 2021-01-01 00:00:00 UTC + "msecs": 123.0, } ) formatted = formatter.format(record) # logs contain - # timestamp - assert "2021-01-01 00:00:00" in formatted + # timestamp with millisecond precision + assert "2021-01-01 00:00:00.123" in formatted # message assert "Test message" in formatted From db94367fc93fe5da7f9d43584c531d8c72f034e2 Mon Sep 17 00:00:00 2001 From: spencer <spencer.taylor-brown@ethereum.org> Date: Wed, 12 Aug 2026 01:07:09 +0200 Subject: [PATCH 217/233] fix(tests): enhance & un-skip Amsterdam ported static create-OOG tests (Pt. 2b) (#3320) * fix(tests): enhance & un-skip Amsterdam ported static create-OOG tests (Pt. 2b) * refactor(tests): Refactor/update ported static tests * fix(claude): Enhance skill * fix(tests): Restore attributions from the original test comments --------- Co-authored-by: marioevz <marioevz@gmail.com> --- .claude/commands/enhance-ported-test.md | 192 ++- tests/ported_static/amsterdam_skip_list.txt | 62 +- .../stBadOpcode/test_measure_gas.py | 578 ++------ .../stBadOpcode/test_operation_diff_gas.py | 456 ------ ...name_registrator_per_txs_not_enough_gas.py | 172 +-- ...e_registrator_pre_store1_not_enough_gas.py | 161 ++- ...tract_create_ne_contract_in_init_oog_tr.py | 211 +-- ..._contract_then_call_to_non_existent_acc.py | 101 -- .../test_create_empty_contract_and_call_it.py | 111 -- .../test_create_empty_contract_then_call.py | 159 +++ ...test_create_empty_contract_with_storage.py | 220 ++- ..._contract_with_storage_and_call_it_0wei.py | 112 -- ..._contract_with_storage_and_call_it_1wei.py | 115 -- ...ate_oo_gafter_init_code_returndata_size.py | 77 - ...ate_oog_after_init_code_returndata_size.py | 157 ++ .../test_create_oog_from_call_refunds.py | 1265 +++-------------- .../stCreateTest/test_create_results.py | 909 ++++-------- .../test_transaction_collision.py | 122 ++ .../test_transaction_collision_to_empty2.py | 154 +- ...transaction_collision_to_empty_but_code.py | 149 -- ...ransaction_collision_to_empty_but_nonce.py | 116 -- ...more_gas_on_depth2_then_transaction_has.py | 111 -- .../test_call_asks_more_gas_than_available.py | 254 ++++ .../test_call_goes_oog_on_second_level.py | 209 +-- .../test_call_goes_oog_on_second_level2.py | 106 -- .../test_create_and_gas_inside_create.py | 146 +- .../test_delegate_call_on_eip.py | 113 +- ..._that_ask_fore_gas_then_trabsaction_has.py | 86 -- .../test_out_of_gas_contract_creation.py | 222 +-- ..._out_of_gas_prefunded_contract_creation.py | 216 +-- ...ransaction_has_with_mem_expanding_calls.py | 137 -- ..._second_level2_with_mem_expanding_calls.py | 107 -- ...n_second_level_with_mem_expanding_calls.py | 107 -- ..._inside_create_with_mem_expanding_calls.py | 82 -- ...ransaction_has_with_mem_expanding_calls.py | 89 -- ...more_gas_on_depth2_then_transaction_has.py | 223 --- ...st_static_call_goes_oog_on_second_level.py | 103 -- ...t_static_call_goes_oog_on_second_level2.py | 146 -- ..._create_empty_contract_and_call_it_0wei.py | 220 ++- ..._contract_with_storage_and_call_it_0wei.py | 124 -- ..._that_ask_fore_gas_then_trabsaction_has.py | 162 --- 41 files changed, 2692 insertions(+), 5870 deletions(-) delete mode 100644 tests/ported_static/stBadOpcode/test_operation_diff_gas.py delete mode 100644 tests/ported_static/stCreateTest/test_create_e_contract_then_call_to_non_existent_acc.py delete mode 100644 tests/ported_static/stCreateTest/test_create_empty_contract_and_call_it.py create mode 100644 tests/ported_static/stCreateTest/test_create_empty_contract_then_call.py delete mode 100644 tests/ported_static/stCreateTest/test_create_empty_contract_with_storage_and_call_it_0wei.py delete mode 100644 tests/ported_static/stCreateTest/test_create_empty_contract_with_storage_and_call_it_1wei.py delete mode 100644 tests/ported_static/stCreateTest/test_create_oo_gafter_init_code_returndata_size.py create mode 100644 tests/ported_static/stCreateTest/test_create_oog_after_init_code_returndata_size.py create mode 100644 tests/ported_static/stCreateTest/test_transaction_collision.py delete mode 100644 tests/ported_static/stCreateTest/test_transaction_collision_to_empty_but_code.py delete mode 100644 tests/ported_static/stCreateTest/test_transaction_collision_to_empty_but_nonce.py delete mode 100644 tests/ported_static/stEIP150Specific/test_call_ask_more_gas_on_depth2_then_transaction_has.py create mode 100644 tests/ported_static/stEIP150Specific/test_call_asks_more_gas_than_available.py delete mode 100644 tests/ported_static/stEIP150Specific/test_call_goes_oog_on_second_level2.py delete mode 100644 tests/ported_static/stEIP150Specific/test_execute_call_that_ask_fore_gas_then_trabsaction_has.py delete mode 100644 tests/ported_static/stMemExpandingEIP150Calls/test_call_ask_more_gas_on_depth2_then_transaction_has_with_mem_expanding_calls.py delete mode 100644 tests/ported_static/stMemExpandingEIP150Calls/test_call_goes_oog_on_second_level2_with_mem_expanding_calls.py delete mode 100644 tests/ported_static/stMemExpandingEIP150Calls/test_call_goes_oog_on_second_level_with_mem_expanding_calls.py delete mode 100644 tests/ported_static/stMemExpandingEIP150Calls/test_create_and_gas_inside_create_with_mem_expanding_calls.py delete mode 100644 tests/ported_static/stMemExpandingEIP150Calls/test_execute_call_that_ask_more_gas_then_transaction_has_with_mem_expanding_calls.py delete mode 100644 tests/ported_static/stStaticCall/test_static_call_ask_more_gas_on_depth2_then_transaction_has.py delete mode 100644 tests/ported_static/stStaticCall/test_static_call_goes_oog_on_second_level.py delete mode 100644 tests/ported_static/stStaticCall/test_static_call_goes_oog_on_second_level2.py delete mode 100644 tests/ported_static/stStaticCall/test_static_create_empty_contract_with_storage_and_call_it_0wei.py delete mode 100644 tests/ported_static/stStaticCall/test_static_execute_call_that_ask_fore_gas_then_trabsaction_has.py diff --git a/.claude/commands/enhance-ported-test.md b/.claude/commands/enhance-ported-test.md index 0d0404b2a04..67ec0bdfd2c 100644 --- a/.claude/commands/enhance-ported-test.md +++ b/.claude/commands/enhance-ported-test.md @@ -54,9 +54,65 @@ so a failure is attributable. ## Ordered steps -Do them roughly in this order. Earlier steps unblock later ones (notably: max -out gas *before* strengthening post-state, so added opcodes don't hit a gas -ceiling). +Do them roughly in this order. Earlier steps unblock later ones (notably: audit +the bytecode *before* touching gas, since restoring elided opcodes moves the +budget; and max out gas *before* strengthening post-state, so added opcodes +don't hit a gas ceiling). + +### 0. Audit the bytecode against its `# Source: yul` comment +The `# Source: yul` blocks are the *filler's* source; the bytecode beside them is +what **solc emitted**, and the optimizer is free to delete operations the test +depends on. A port that faithfully reproduces the compiled bytecode therefore +faithfully reproduces the *hole* the optimizer left. Do this **first** — +restoring elided operations changes gas, so it must precede any budget work +(steps 2 / 10). + +**The canonical fold: a self-cancelling `SSTORE` pair.** Refund tests set a slot +then clear it (`sstore(k, 1); sstore(k, 0)`) to earn a refund. In a fresh +`CREATE` frame slot `k` is already zero, so solc folds the pair down to +`sstore(k, 0)` — a no-op that generates **no refund at all**, leaving the test +vacuous while still passing. Validated on `test_create_oog_from_call_refunds`, +where 2 of 24 init codes had lost their `sstore(1, 1)`: the OoG arms assert the +sender's balance reaches exactly zero, which *is* the "refund earned inside a +reverted frame must be discarded" check — and it was asserting nothing. + +**How to check.** Disassemble every bytecode blob and diff it against the comment +above it. Comparing opcode *counts* per mnemonic (`sstore(` in the Yul vs. +`SSTORE` in the asm) catches the whole class in one pass. A throwaway script that +`ast`-parses the test, `eval`s each `Op...` assignment against a namespace of the +test's constants, and walks `bytes(...)` through a `PUSH*`-aware opcode table is +enough — there is no disassembler in `execution_testing`. + +**Tells in the ported source.** Dense `DUP`/`SWAP` juggling +(`Op.SSTORE(key=Op.DUP2, value=Op.DUP2)`, a bare `Op.PUSH1[0x1] + Op.PUSH1[0x0]` +prologue, a trailing argument-less `Op.RETURN`) is solc's stack reuse — the shape +most likely to hide a fold, and unreadable regardless. Rewrite those from the Yul +into explicit `Op.SSTORE(key=..., value=...)` / `Op.RETURN(offset=..., size=...)` +form: it restores the intent and makes the next audit trivial. + +**Benign deviations — do not "fix" them.** solc drops a `POP` before a terminator +(`pop(call(...)); return(0, 1)` compiles without the `POP`, as `RETURN` ignores +leftover stack) and encodes repeated literal zeros as `DUP1` chains +(`Op.CALL(..., args_offset=Op.DUP1, ...)`). Both are semantically identical to +the Yul. Only a **missing or added state-changing operation** is a real +deviation. + +**Expect to re-budget afterwards.** Restoring an elided op adds its cost — a +zero->non-zero `SSTORE` is ~22.1k pre-EIP-8037 and ~97.9k of *state* gas on +Amsterdam — so a test with a hardcoded `gas_limit` may now OOG. That is usually +not a regression you introduced: it reveals that the sibling cases which never +lost their op were *already* failing on the future fork for the same reason. +Establish this before re-budgeting by copying the pre-change file aside under a +different test name, filling both, and diffing the failure sets — in the +validated case that separated 12 pre-existing Amsterdam failures from the 3 the +fix added. + +**Verify the restoration is observable, not merely green.** Fill before and +after and reconcile the gas delta. Above, consumption moved 77731 -> 97857 (the +added cold `SSTORE`, minus the reset dropping to a warm 100) and the 19900 refund +was capped by EIP-3529 at `97857 // 5 = 19571`, giving the reported 78286 exactly. +A delta you cannot account for means the rewrite changed the program (see +"Re-pinning" below). ### 1. Remove `env` Delete the `Environment(...)` block, the `env=env` arg to `state_test`, and any @@ -265,7 +321,9 @@ verifies anything. Improve coupling and observability: note the restoration in the `@manually-enhanced` line. Validated on `test_deleagate_call_after_value_transfer` (DELEGATECALL preserves the enclosing frame's value). Read the test's *name and source comment* against - what it actually checks; the gap is the enhancement. + what it actually checks; the gap is the enhancement. The compiler-optimized + init code of step 0 is the same family, one level down: there the *bytecode* + stopped matching the scenario its own Yul comment describes. ### 9. Introduce variables that encode relationships Whenever a literal carries intent or two literals are logically linked, lift them @@ -293,6 +351,23 @@ ties a `CREATE`'s `size` operand to the memory/gas math that depends on it. call_value`, `callee: INITIAL + call_value`). Only reach for the "derive the fee formula" machinery above when the fee itself is the observable. Validated on `test_make_money`. +- **A budget bounded on *both* sides needs a guard, not just a comment.** When a + test's OOG mechanism is itself gas-priced — the classic being an oversized code + deposit, `return(0, 5000)` — raising `gas_limit` to fit the *successful* cases + can quietly fund the *failing* ones, flipping them to success. That direction + fails silently, because a passing test is the failure mode. Name the quantity + (`oversized_code_size = 5000`), feed it to both the `Op.RETURN` operands and a + guard: `assert tx_gas[g] < oversized_code_size * + fork.gas_costs().CODE_DEPOSIT_PER_BYTE`. That constant is the last-resort form + (see step 10's `code_deposit_size` note); it is legitimate *here* only because + it understates the deposit on 8037 forks, keeping an "is this unaffordable?" + guard conservative. Prefer `Op.RETURN`'s metadata whenever the comparison can + be expressed against the init code's own `gas_cost(fork)`. Couple the sender's + balance to the budget in + the same breath — `balance=tx_gas[g] * tx_gas_price` — whenever the post asserts + it reaches exactly zero; a hardcoded `0x3D0900` silently desyncs the moment the + budget moves, and "burned the whole allowance" stops meaning anything. Validated + on `test_create_oog_from_call_refunds`. ### 10. (Gas-subject / gas-snapshot tests) Replace hardcoded gas with dynamic calculation Covers both tests that *assert* a gas amount and the dominant broken-port @@ -379,6 +454,17 @@ to intrinsic) and assert `code.gas_cost(fork)`. A legacy `[[0]](GAS) … operation (e.g. a `CREATE`); collapse it to a single `CodeGasMeasure` around that op and drop both raw slots. Validated on the `CREATE_EmptyContract*` family. +**Look for opcode metadata before hand-rolling a gas formula.** Opcodes carry +kwargs that fold fork-dependent charges into `gas_cost(fork)` — `Op.RETURN`'s +`code_deposit_size`, `Op.CREATE`'s `init_code_size`/`new_memory_size`, +`Op.CALL`'s `address_warm`/`value_transfer`/`account_new`, `Op.SSTORE`'s +`key_warm`/`original_value`/`new_value`. A formula assembled out of +`fork.gas_costs()` constants has to be re-audited at every repricing; the +metadata tracks it for you. Check the opcode's `Metadata` docstring block in +`packages/testing/src/execution_testing/vm/opcodes.py` before reaching for +constants — a `fork.gas_costs()` reference in a derivation is a smell that the +metadata was missed. + **Decompose the constant empirically** when no single helper applies (throwaway script against the fork): pin each term to the known-good number, then assemble. Map terms to fork-derived helpers: opcode base+pushes → `bytecode.gas_cost(fork)`; @@ -440,11 +526,69 @@ persists with the sentinel) plus the callee-side observable already separate the outcomes. Validated on `test_contract_creation_make_call_that_ask_more_gas_then_transaction_provided`. -**Refund-cap derivations need the EIP-7623 kwarg.** The EIP-3529 cap's -base is the gas deducted before execution, which excludes the calldata -floor: pass `return_cost_deducted_prior_execution=True` to the intrinsic -calculator whenever the tx has calldata, or the derived `executed` (and -the cap) overstate. Validated on `test_refund_suicide50procent_cap`. +**Any *exact* budget off the intrinsic calculator needs the EIP-7623 +kwarg.** `transaction_intrinsic_cost_calculator()` returns `max(intrinsic, +calldata_floor)`, but the floor is only compared against *after* execution — +it is never deducted up front. So whenever the derived number stands for +"gas taken before the first opcode" — a one-gas OOG boundary, an exact +success budget, an EIP-3529 refund cap — pass +`return_cost_deducted_prior_execution=True` if the transaction carries +calldata. **The bug hides until an EIP-7981 fork:** the delta is 0 from +Berlin through Osaka and 238 on Amsterdam for a 5-byte creation payload, so +a boundary tuned without the kwarg passes on every fork that exists today +and silently goes slack on the future one — and the symptom is an *OOG arm +that stops OOG-ing*, i.e. a test that fails only once someone runs +`--fork Amsterdam`. +**Keep the default (no kwarg) when the number is a validity floor** rather +than an execution budget: a tx whose `gas_limit` falls below +`max(intrinsic, floor)` is rejected outright, which is a different outcome +from running out of gas. +Validated on `test_refund_suicide50procent_cap` and +`test_transaction_collision_to_empty2`, whose OOG arm had been loosened to +"half the store's cost" to work around this, with a comment misattributing +it to a pre-Shanghai init-code word cost. + +**Read the trace when a derived budget does not line up.** `fill --traces +--evm-dump-dir <dir>` writes, per transaction, `input/txs.json`, +`output/result.json` and a `trace-*.jsonl`. Subtracting the first trace +entry's `gas` from the tx `gas` gives the intrinsic the EVM *actually* +charged, which is what settles a disagreement with any calculator; on +EIP-8037 forks the per-step `gasCost`/`stateGasCost` split shows where a +composite's cost really lands. Far faster than bisecting `gas_limit` by +re-filling, and it produced the 238 above in one run. + +**Gates are not costs, and `gas_cost()` only knows costs.** Several EVM +rules are *preconditions on `gas_left`* rather than charges, so a budget +derived from `gas_cost(fork)` is exactly right and still too small. The +canonical one is EIP-2200 (Istanbul+): **any** `SSTORE` halts when it runs +with `CALL_STIPEND` (2300) gas or less still available — including a +100-gas dirty-warm rewrite. No amount of fixing `_calculate_sstore*` can +express that, because it is not part of the price. + +*Tell it apart in a trace:* a gated halt shows `gasCost: 0x0` next to an +`error`, while a genuine can't-afford shows the real cost. `SSTORE +gas_left=2300 cost=0 OutOfGasError` is the signature. + +*Derive the headroom instead of padding.* The **last** gated op is the one +running closest to empty, so it sets the requirement: +``` +last_charge = Op.SSTORE(key_warm=True, original_value=…, current_value=…, + new_value=…).gas_cost(fork) # bare `55`, no PUSHes +headroom = fork.gas_costs().CALL_STIPEND - last_charge + 1 +gas_limit = overhead + code.gas_cost(fork) + headroom +``` +A bare opcode carrying only metadata prices the charge alone (same trick as +`Op.RETURN(code_deposit_size=n)`). This is what turns "add 5,000 and hope" +into a real one-gas boundary — and because it targets only the *last* gated +op, it is invariant in how many precede it. Validated on +`test_out_of_gas_contract_creation`, whose arms now straddle +`gas_left = 2301` / `2300` exactly. + +*Same family, when an "impossible" budget is really a gate:* an +`SSTORE` in a `STATICCALL` subtree, `RETURNDATACOPY` reading past the +return buffer, stack under/overflow, an EIP-684 address collision. Each +aborts the frame outright rather than charging for it, so the fix is +always to model the gate, never to inflate the budget until it passes. **A CREATE address collision burns the child's gas allowance** (the EIP-684 path): the withheld child grant is consumed, nothing is created, @@ -465,15 +609,27 @@ a derived budget that must survive pre-Istanbul forks needs an explicit headroom constant for it (named, commented). Observed on `test_revert_depth_create_address_collision`'s ConstantinopleFix sweep. -**EIP-8037 repriced the code deposit's regular part — boundaries beware.** -On 8037 forks the deposit charges only the keccak word cost -(`OPCODE_KECCAK256_PER_WORD * ceil32(len)/32`, ~6 gas) as regular gas plus -`len * 1530` state; `fork.gas_costs().CODE_DEPOSIT_PER_BYTE` (200) is the -*pre-8037* constant. Using 200/byte in a *sufficiency* budget merely -overshoots (safe); using it in a one-gas-short *boundary* silently funds -the deposit on Amsterdam. Branch on `fork.is_eip_enabled(8037)` for exact -deposit boundaries. Validated on -`test_create_oo_gafter_init_code_returndata_size`. +**Code-deposit cost comes from `Op.RETURN`'s metadata — never a per-byte +formula.** Annotate the init code's terminator, +`Op.RETURN(offset=o, size=n, code_deposit_size=n)`, and `gas_cost(fork)` +includes the deposit charge, correct on both sides of the EIP-8037 boundary +(10 bytes costs 2,000 before it; 15,306 after, once the per-byte price became +state gas). The annotated and unannotated forms assemble to **identical +bytes**, so building both yields an exact two-sided boundary out of the fork's +own model, with no EIP branch: +``` +child = stage + Op.RETURN(offset=o, size=n) +child_with_deposit = stage + Op.RETURN(offset=o, size=n, code_deposit_size=n) +assert child.gas_cost(fork) <= granted < child_with_deposit.gas_cost(fork) +``` +`fork.gas_costs().CODE_DEPOSIT_PER_BYTE` (200) is a **last resort**: it is the +*pre-8037* constant and understates the real charge ~7.5x on 8037 forks. Use +it only where understating is the safe direction — a *sufficiency* budget +overshoots, an "is this unaffordable?" guard stays conservative — and never in +a one-gas-short *boundary*, which it silently funds on Amsterdam. Branching on +`fork.is_eip_enabled(8037)` to patch it up is the wrong fix; the metadata +removes the branch entirely. Validated on +`test_create_oog_after_init_code_returndata_size`. **Match the intrinsic calculator's kwargs to the transaction's shape.** `fork.transaction_intrinsic_cost_calculator()()` defaults to diff --git a/tests/ported_static/amsterdam_skip_list.txt b/tests/ported_static/amsterdam_skip_list.txt index 92fb465eda3..e4c6faa9429 100644 --- a/tests/ported_static/amsterdam_skip_list.txt +++ b/tests/ported_static/amsterdam_skip_list.txt @@ -8,23 +8,16 @@ # Entries are substring-matched against each pytest nodeid (after # stripping the fixture-format suffix in conftest.py). # -# Total entries: 130 +# Total entries: 86 # stAttackTest (1) stAttackTest/test_crashing_transaction.py::test_crashing_transaction[fork_Amsterdam] -# stBadOpcode (4) -stBadOpcode/test_measure_gas.py::test_measure_gas[fork_Amsterdam-CREATE2] -stBadOpcode/test_measure_gas.py::test_measure_gas[fork_Amsterdam-CREATE] -stBadOpcode/test_operation_diff_gas.py::test_operation_diff_gas[fork_Amsterdam-CREATE2] -stBadOpcode/test_operation_diff_gas.py::test_operation_diff_gas[fork_Amsterdam-CREATE] +# stBadOpcode (0) # stCallCodes (0) -# stCallCreateCallCodeTest (3) -stCallCreateCallCodeTest/test_create_name_registrator_per_txs_not_enough_gas.py::test_create_name_registrator_per_txs_not_enough_gas[fork_Amsterdam--g0] -stCallCreateCallCodeTest/test_create_name_registrator_per_txs_not_enough_gas.py::test_create_name_registrator_per_txs_not_enough_gas[fork_Amsterdam--g1] -stCallCreateCallCodeTest/test_create_name_registrator_pre_store1_not_enough_gas.py::test_create_name_registrator_pre_store1_not_enough_gas[fork_Amsterdam] +# stCallCreateCallCodeTest (0) # stCallDelegateCodesCallCodeHomestead (1) stCallDelegateCodesCallCodeHomestead/test_callcallcallcode_001_suicide_end.py::test_callcallcallcode_001_suicide_end[fork_Amsterdam] @@ -62,7 +55,7 @@ stCreate2/test_revert_depth_create_address_collision_berlin.py::test_revert_dept stCreate2/test_revert_depth_create_address_collision_berlin.py::test_revert_depth_create_address_collision_berlin[fork_Amsterdam-d1-g1-v0] stCreate2/test_revert_depth_create_address_collision_berlin.py::test_revert_depth_create_address_collision_berlin[fork_Amsterdam-d1-g1-v1] -# stCreateTest (36) +# stCreateTest (13) stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create-0xef-v1] stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create-contructor-revert-v1] stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create-high-nonce-v1] @@ -76,35 +69,10 @@ stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_af stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create2-ok-v1] stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create2-oog-constructor-v1] stCreateTest/test_create_address_warm_after_fail.py::test_create_address_warm_after_fail[fork_Amsterdam-create2-oog-post-constr-v1] -stCreateTest/test_create_e_contract_create_ne_contract_in_init_oog_tr.py::test_create_e_contract_create_ne_contract_in_init_oog_tr[fork_Amsterdam--g0] -stCreateTest/test_create_e_contract_create_ne_contract_in_init_oog_tr.py::test_create_e_contract_create_ne_contract_in_init_oog_tr[fork_Amsterdam--g1] -stCreateTest/test_create_e_contract_then_call_to_non_existent_acc.py::test_create_e_contract_then_call_to_non_existent_acc[fork_Amsterdam] -stCreateTest/test_create_empty_contract_with_storage.py::test_create_empty_contract_with_storage[fork_Amsterdam] -stCreateTest/test_create_empty_contract_with_storage_and_call_it_0wei.py::test_create_empty_contract_with_storage_and_call_it_0wei[fork_Amsterdam] -stCreateTest/test_create_empty_contract_with_storage_and_call_it_1wei.py::test_create_empty_contract_with_storage_and_call_it_1wei[fork_Amsterdam] -stCreateTest/test_create_oo_gafter_init_code_returndata_size.py::test_create_oo_gafter_init_code_returndata_size[fork_Amsterdam] -stCreateTest/test_create_oog_from_call_refunds.py::test_create_oog_from_call_refunds[fork_Amsterdam-SStore_Create2_Refund_NoOoG] -stCreateTest/test_create_oog_from_call_refunds.py::test_create_oog_from_call_refunds[fork_Amsterdam-SStore_Create_Refund_NoOoG] -stCreateTest/test_create_oog_from_call_refunds.py::test_create_oog_from_call_refunds[fork_Amsterdam-SStore_Refund_NoOoG2] -stCreateTest/test_create_oog_from_call_refunds.py::test_create_oog_from_call_refunds[fork_Amsterdam-SStore_Refund_NoOoG3] -stCreateTest/test_create_results.py::test_create_results[fork_Amsterdam-d0] -stCreateTest/test_create_results.py::test_create_results[fork_Amsterdam-d1] -stCreateTest/test_create_results.py::test_create_results[fork_Amsterdam-d2] -stCreateTest/test_create_results.py::test_create_results[fork_Amsterdam-d4] -stCreateTest/test_create_results.py::test_create_results[fork_Amsterdam-d5] -stCreateTest/test_create_results.py::test_create_results[fork_Amsterdam-d6] -stCreateTest/test_transaction_collision_to_empty2.py::test_transaction_collision_to_empty2[fork_Amsterdam--g1-v0] -stCreateTest/test_transaction_collision_to_empty2.py::test_transaction_collision_to_empty2[fork_Amsterdam--g1-v1] -stCreateTest/test_transaction_collision_to_empty_but_code.py::test_transaction_collision_to_empty_but_code[fork_Amsterdam--g1-v0] -stCreateTest/test_transaction_collision_to_empty_but_code.py::test_transaction_collision_to_empty_but_code[fork_Amsterdam--g1-v1] -stCreateTest/test_transaction_collision_to_empty_but_nonce.py::test_transaction_collision_to_empty_but_nonce[fork_Amsterdam--g1-v0] -stCreateTest/test_transaction_collision_to_empty_but_nonce.py::test_transaction_collision_to_empty_but_nonce[fork_Amsterdam--g1-v1] # stDelegatecallTestHomestead (0) -# stEIP150Specific (3) -stEIP150Specific/test_create_and_gas_inside_create.py::test_create_and_gas_inside_create[fork_Amsterdam] -stEIP150Specific/test_delegate_call_on_eip.py::test_delegate_call_on_eip[fork_Amsterdam] +# stEIP150Specific (1) stEIP150Specific/test_new_gas_price_for_codes.py::test_new_gas_price_for_codes[fork_Amsterdam] # stEIP150singleCodeGasPrices (2) @@ -117,18 +85,9 @@ stEIP158Specific/test_exp_empty.py::test_exp_empty[fork_Amsterdam] # stHomesteadSpecific (1) stHomesteadSpecific/test_contract_creation_oo_gdont_leave_empty_contract_via_transaction.py::test_contract_creation_oo_gdont_leave_empty_contract_via_transaction[fork_Amsterdam] -# stInitCodeTest (7) -stInitCodeTest/test_out_of_gas_contract_creation.py::test_out_of_gas_contract_creation[fork_Amsterdam-d0-g0] -stInitCodeTest/test_out_of_gas_contract_creation.py::test_out_of_gas_contract_creation[fork_Amsterdam-d0-g1] -stInitCodeTest/test_out_of_gas_contract_creation.py::test_out_of_gas_contract_creation[fork_Amsterdam-d1-g0] -stInitCodeTest/test_out_of_gas_contract_creation.py::test_out_of_gas_contract_creation[fork_Amsterdam-d1-g1] -stInitCodeTest/test_out_of_gas_prefunded_contract_creation.py::test_out_of_gas_prefunded_contract_creation[fork_Amsterdam--g0] -stInitCodeTest/test_out_of_gas_prefunded_contract_creation.py::test_out_of_gas_prefunded_contract_creation[fork_Amsterdam--g1] -stInitCodeTest/test_out_of_gas_prefunded_contract_creation.py::test_out_of_gas_prefunded_contract_creation[fork_Amsterdam--g2] - -# stMemExpandingEIP150Calls (3) -stMemExpandingEIP150Calls/test_call_goes_oog_on_second_level_with_mem_expanding_calls.py::test_call_goes_oog_on_second_level_with_mem_expanding_calls[fork_Amsterdam] -stMemExpandingEIP150Calls/test_create_and_gas_inside_create_with_mem_expanding_calls.py::test_create_and_gas_inside_create_with_mem_expanding_calls[fork_Amsterdam] +# stInitCodeTest (0) + +# stMemExpandingEIP150Calls (1) stMemExpandingEIP150Calls/test_new_gas_price_for_codes_with_mem_expanding_calls.py::test_new_gas_price_for_codes_with_mem_expanding_calls[fork_Amsterdam] # stMemoryTest (2) @@ -169,10 +128,7 @@ stSolidityTest/test_recursive_create_contracts.py::test_recursive_create_contrac stSolidityTest/test_test_contract_interaction.py::test_test_contract_interaction[fork_Amsterdam] stSolidityTest/test_test_contract_suicide.py::test_test_contract_suicide[fork_Amsterdam] -# stStaticCall (3) -stStaticCall/test_static_create_empty_contract_and_call_it_0wei.py::test_static_create_empty_contract_and_call_it_0wei[fork_Amsterdam] -stStaticCall/test_static_create_empty_contract_with_storage_and_call_it_0wei.py::test_static_create_empty_contract_with_storage_and_call_it_0wei[fork_Amsterdam] -stStaticCall/test_static_execute_call_that_ask_fore_gas_then_trabsaction_has.py::test_static_execute_call_that_ask_fore_gas_then_trabsaction_has[fork_Amsterdam-d0] +# stStaticCall (0) # stSystemOperationsTest (2) stSystemOperationsTest/test_double_selfdestruct_touch_paris.py::test_double_selfdestruct_touch_paris[fork_Amsterdam--v1] diff --git a/tests/ported_static/stBadOpcode/test_measure_gas.py b/tests/ported_static/stBadOpcode/test_measure_gas.py index 5f9d16570a6..36c2957530b 100644 --- a/tests/ported_static/stBadOpcode/test_measure_gas.py +++ b/tests/ported_static/stBadOpcode/test_measure_gas.py @@ -1,467 +1,183 @@ """ -Ori Pomerantz qbzzt1@gmail.com. +Verify the exact gas each opcode needs to succeed: given precisely that +much, the frame running it completes; given one gas less, it does not. Ported from: state_tests/stBadOpcode/measureGasFiller.yml +state_tests/stBadOpcode/opcodeDiffGasFiller.yml +Written by Ori Pomerantz (qbzzt1@gmail.com). -@manually-enhanced: Do not overwrite. A binary search measures the gas -an opcode needs to succeed. Only the EXTCODE case shifts: it runs a -warm `EXTCODESIZE` plus a warm `EXTCODECOPY` (the target is warmed by -earlier search iterations), and EIP-8038 adds a flat +100 to each warm -extcode access. The stored threshold therefore grows by the sum of the -two opcodes' warm `(Amsterdam - Cancun)` cost deltas, derived from the -fork's own gas model so it is exactly 0 before EIP-8038; do not -hardcode the Amsterdam number. +@manually-enhanced: Do not overwrite. The filler bisected the gas operand +of a CALL inside EVM bytecode to find the gas it consumed in runtime. """ import pytest from execution_testing import ( - EOA, Account, - Address, Alloc, - Bytes, - Environment, - Hash, + Bytecode, + Fork, + Op, + Opcodes, StateTestFiller, Transaction, ) -from execution_testing.forks import Fork -from execution_testing.vm import Op - -from tests.ported_static.post_state_resolution import ( - resolve_expect_post, -) REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +FLAG_SLOT = 0x0 +# Seeded so a frame that never ran stays distinct from one that ran and +# reported a failed call. +SENTINEL = 0x60A7 + +# The ported operands: an init code window, a call's argument/return +# window, a memory offset well past anything already allocated, and a +# hash over a large span. +INIT_CODE_SIZE = 0x200 +CALL_WINDOW = 0x100 +MEMORY_OFFSET = 0xB000 +HASH_SIZE = 0xBEEF + +OPCODES = [ + Op.CREATE, + Op.CREATE2, + Op.CALL, + Op.CALLCODE, + Op.DELEGATECALL, + Op.STATICCALL, + Op.MLOAD, + Op.MSTORE, + Op.MSTORE8, + Op.SHA3, + Op.EXTCODECOPY, +] + + +@pytest.fixture +def probe_code(opcode: Opcodes, pre: Alloc) -> Bytecode: + """ + Return a frame that performs `opcode` once and stops. + + Each opcode carries the metadata describing the access it makes, so + `gas_cost(fork)` is the exact budget the frame needs. + """ + body: Bytecode + if opcode in (Op.CREATE, Op.CREATE2): + # CREATE2's salt defaults to zero: one probe per case, so there is + # nothing for it to collide with. + body = Op.POP( + opcode( + value=0x0, + offset=0x0, + size=INIT_CODE_SIZE, + new_memory_size=INIT_CODE_SIZE, + init_code_size=INIT_CODE_SIZE, + ) + ) + elif opcode in (Op.CALL, Op.CALLCODE, Op.DELEGATECALL, Op.STATICCALL): + # The value-passing forms default to transferring nothing, which + # keeps all four variants at the same base cost. + callee = pre.deploy_contract(code=Op.STOP) + body = Op.POP( + opcode( + gas=Op.GAS, + address=callee, + args_offset=0x0, + args_size=CALL_WINDOW, + ret_offset=0x0, + ret_size=CALL_WINDOW, + address_warm=False, + new_memory_size=CALL_WINDOW, + ) + ) + elif opcode == Op.MLOAD: + body = Op.POP( + Op.MLOAD( + offset=MEMORY_OFFSET, new_memory_size=MEMORY_OFFSET + 0x20 + ) + ) + elif opcode in (Op.MSTORE, Op.MSTORE8): + written = 0x20 if opcode == Op.MSTORE else 0x1 + body = opcode( + offset=MEMORY_OFFSET, + value=0xFF, + new_memory_size=MEMORY_OFFSET + written, + ) + elif opcode == Op.SHA3: + body = Op.POP( + Op.SHA3( + offset=0x0, + size=HASH_SIZE, + new_memory_size=HASH_SIZE, + data_size=HASH_SIZE, + ) + ) + elif opcode == Op.EXTCODECOPY: + # The size operand warms the account, so the copy that follows is + # a warm access. + callee = pre.deploy_contract(code=Op.STOP) + body = Op.EXTCODECOPY( + address=callee, + dest_offset=0x0, + offset=0x0, + size=0x1, + address_warm=False, + data_size=0x1, + new_memory_size=0x1, + ) + elif opcode == Op.EXTCODESIZE: + callee = pre.deploy_contract(code=Op.STOP) + body = Op.EXTCODESIZE(address=callee, address_warm=False) + else: + raise ValueError(f"Opcode {opcode} not yet supported by test") + return body + Op.STOP + @pytest.mark.ported_from( - ["state_tests/stBadOpcode/measureGasFiller.yml"], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.parametrize( - "d, g, v", [ - pytest.param( - 0, - 0, - 0, - id="CREATE", - ), - pytest.param( - 1, - 0, - 0, - id="CREATE2", - ), - pytest.param( - 2, - 0, - 0, - id="CALL", - ), - pytest.param( - 3, - 0, - 0, - id="CALLCODE", - ), - pytest.param( - 4, - 0, - 0, - id="DELEGATECALL", - ), - pytest.param( - 5, - 0, - 0, - id="STATICCALL", - ), - pytest.param( - 6, - 0, - 0, - id="MLOAD", - ), - pytest.param( - 7, - 0, - 0, - id="MSTORE", - ), - pytest.param( - 8, - 0, - 0, - id="MSTORE8", - ), - pytest.param( - 9, - 0, - 0, - id="SHA3", - ), - pytest.param( - 10, - 0, - 0, - id="EXTCODE", - ), + "state_tests/stBadOpcode/measureGasFiller.yml", + "state_tests/stBadOpcode/opcodeDiffGasFiller.yml", ], ) -@pytest.mark.pre_alloc_mutable +@pytest.mark.valid_from("Berlin") +@pytest.mark.parametrize( + "sufficient", [True, False], ids=["sufficient", "insufficient"] +) +@pytest.mark.parametrize("opcode", OPCODES) def test_measure_gas( state_test: StateTestFiller, pre: Alloc, fork: Fork, - d: int, - g: int, - v: int, + probe_code: Bytecode, + sufficient: bool, ) -> None: - """Ori Pomerantz qbzzt1@gmail.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0x0000000000000000000000000000000000C0DEF0) - contract_1 = Address(0x0000000000000000000000000000000000C0DEF5) - contract_2 = Address(0x000000000000000000000000000000000000CA11) - contract_3 = Address(0x0000000000000000000000000000000000C0DEF1) - contract_4 = Address(0x0000000000000000000000000000000000C0DEF2) - contract_5 = Address(0x0000000000000000000000000000000000C0DEF4) - contract_6 = Address(0x0000000000000000000000000000000000C0DEFA) - contract_7 = Address(0x0000000000000000000000000000000000C0DE51) - contract_8 = Address(0x0000000000000000000000000000000000C0DE52) - contract_9 = Address(0x0000000000000000000000000000000000C0DE53) - contract_10 = Address(0x0000000000000000000000000000000000C0DE20) - contract_11 = Address(0x0000000000000000000000000000000000C0DE3B) - contract_12 = Address(0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC) - sender = EOA( - key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8 - ) + """One gas decides whether the opcode's frame completes.""" + threshold = probe_code.gas_cost(fork) + probe = pre.deploy_contract(code=probe_code) - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=100000000, - ) - - pre[sender] = Account(balance=0xBA1A9CE0BA1A9CE, nonce=1) - # Source: yul - # berlin { - # pop(create(0, 0, 0x200)) - # } - contract_0 = pre.deploy_contract( # noqa: F841 - code=Op.CREATE(value=Op.DUP1, offset=0x0, size=0x200) + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=1, - address=Address(0x0000000000000000000000000000000000C0DEF0), # noqa: E501 - ) - # Source: yul - # berlin { - # // SALT needs to be different each time - # pop(create2(0, 0, 0x200, add(0x5A17, gas()))) - # } - contract_1 = pre.deploy_contract( # noqa: F841 - code=Op.CREATE2( - value=Op.DUP1, offset=0x0, size=0x200, salt=Op.ADD(0x5A17, Op.GAS) + # Handing the probe exactly its own cost is what makes the boundary + # exact; the cold access to it is charged here, not there. + entry = pre.deploy_contract( + code=Op.SSTORE( + FLAG_SLOT, + Op.CALL( + gas=threshold if sufficient else threshold - 1, + address=probe, + ), ) + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=1, - address=Address(0x0000000000000000000000000000000000C0DEF5), # noqa: E501 - ) - # Source: yul - # berlin { - # stop() - # } - contract_2 = pre.deploy_contract( # noqa: F841 - code=Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=1, - address=Address(0x000000000000000000000000000000000000CA11), # noqa: E501 - ) - # Source: yul - # berlin { - # let useless := mload(0xB000) - # } - contract_7 = pre.deploy_contract( # noqa: F841 - code=Op.MLOAD(offset=0xB000) + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=1, - address=Address(0x0000000000000000000000000000000000C0DE51), # noqa: E501 - ) - # Source: yul - # berlin { - # mstore(0xB000, 0xFF) - # } - contract_8 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0xB000, value=0xFF) + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=1, - address=Address(0x0000000000000000000000000000000000C0DE52), # noqa: E501 - ) - # Source: yul - # berlin { - # mstore8(0xB000, 0xFF) - # } - contract_9 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE8(offset=0xB000, value=0xFF) + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=1, - address=Address(0x0000000000000000000000000000000000C0DE53), # noqa: E501 - ) - # Source: yul - # berlin { - # let useless := keccak256(0,0xBEEF) - # } - contract_10 = pre.deploy_contract( # noqa: F841 - code=Op.SHA3(offset=0x0, size=0xBEEF) + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=1, - address=Address(0x0000000000000000000000000000000000C0DE20), # noqa: E501 - ) - # Source: yul - # berlin { - # // Find the operation's cost in gas - # let min := 0 - # let max := 60000 - # let addr := add(0xC0DE00, calldataload(0x04)) - # - # for { } gt(sub(max,min), 1) { } { // Until we get the exact figure - # let middle := div(add(min,max),2) - # let result := call(middle, addr, 0, 0, 0, 0, 0) - # if eq(result, 0) { min := middle } - # if eq(result, 1) { max := middle } - # } - # sstore(0, max) - # } - contract_12 = pre.deploy_contract( # noqa: F841 - code=Op.PUSH2[0xEA60] - + Op.ADD(Op.CALLDATALOAD(offset=0x4), 0xC0DE00) - + Op.PUSH1[0x0] - + Op.JUMPDEST - + Op.JUMPI(pc=0x1C, condition=Op.GT(Op.SUB(Op.DUP5, Op.DUP2), 0x1)) - + Op.SSTORE(key=0x0, value=Op.DUP3) - + Op.STOP - + Op.JUMPDEST - + Op.DIV(Op.ADD(Op.DUP3, Op.DUP4), 0x2) - + Op.CALL( - gas=Op.DUP7, - address=Op.DUP8, - value=Op.DUP1, - args_offset=Op.DUP1, - args_size=Op.DUP1, - ret_offset=Op.DUP1, - ret_size=0x0, - ) - + Op.JUMPI(pc=0x44, condition=Op.ISZERO(Op.DUP1)) - + Op.JUMPDEST - + Op.PUSH1[0x1] - + Op.JUMPI(pc=0x3D, condition=Op.EQ) - + Op.JUMPDEST - + Op.POP - + Op.JUMP(pc=0xD) - + Op.JUMPDEST - + Op.SWAP3 - + Op.POP - + Op.CODESIZE - + Op.JUMP(pc=0x38) - + Op.JUMPDEST - + Op.SWAP1 - + Op.SWAP2 - + Op.POP - + Op.DUP2 - + Op.SWAP1 - + Op.JUMP(pc=0x31), - balance=0xBA1A9CE0BA1A9CE, - nonce=1, - address=Address(0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC), # noqa: E501 - ) - # Source: yul - # berlin { - # let retval := call(gas(), 0xCA11, 0, 0, 0x100, 0, 0x100) - # } - contract_3 = pre.deploy_contract( # noqa: F841 - code=Op.CALL( - gas=Op.GAS, - address=0xCA11, - value=Op.DUP1, - args_offset=Op.DUP2, - args_size=Op.DUP2, - ret_offset=0x0, - ret_size=0x100, - ) - + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=1, - address=Address(0x0000000000000000000000000000000000C0DEF1), # noqa: E501 - ) - # Source: yul - # berlin { - # let addr := 0xCA11 - # extcodecopy(addr, 0, 0, extcodesize(addr)) - # } - contract_11 = pre.deploy_contract( # noqa: F841 - code=Op.PUSH2[0xCA11] - + Op.PUSH1[0x0] - + Op.DUP1 - + Op.EXTCODESIZE(address=Op.DUP3) - + Op.SWAP3 - + Op.EXTCODECOPY - + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=1, - address=Address(0x0000000000000000000000000000000000C0DE3B), # noqa: E501 - ) - # Source: yul - # berlin { - # let retval := staticcall(gas(), 0xCA11, 0, 0x100, 0, 0x100) - # } - contract_6 = pre.deploy_contract( # noqa: F841 - code=Op.STATICCALL( - gas=Op.GAS, - address=0xCA11, - args_offset=Op.DUP2, - args_size=Op.DUP2, - ret_offset=0x0, - ret_size=0x100, - ) - + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=1, - address=Address(0x0000000000000000000000000000000000C0DEFA), # noqa: E501 - ) - # Source: yul - # berlin { - # let retval := delegatecall(gas(), 0xCA11, 0, 0x100, 0, 0x100) - # } - contract_5 = pre.deploy_contract( # noqa: F841 - code=Op.DELEGATECALL( - gas=Op.GAS, - address=0xCA11, - args_offset=Op.DUP2, - args_size=Op.DUP2, - ret_offset=0x0, - ret_size=0x100, - ) - + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=1, - address=Address(0x0000000000000000000000000000000000C0DEF4), # noqa: E501 - ) - # Source: yul - # berlin { - # let retval := callcode(gas(), 0xCA11, 0, 0, 0x100, 0, 0x100) - # } - contract_4 = pre.deploy_contract( # noqa: F841 - code=Op.CALLCODE( - gas=Op.GAS, - address=0xCA11, - value=Op.DUP1, - args_offset=Op.DUP2, - args_size=Op.DUP2, - ret_offset=0x0, - ret_size=0x100, - ) - + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=1, - address=Address(0x0000000000000000000000000000000000C0DEF2), # noqa: E501 + storage={FLAG_SLOT: SENTINEL}, ) - # The EXTCODE search measures a warm `EXTCODESIZE` plus a warm - # `EXTCODECOPY` (the target is warmed by earlier search iterations). - # EIP-8038 adds a flat surcharge to each warm extcode access, so the - # threshold grows by the two opcodes' combined warm cost delta versus - # Cancun. Derived from the fork gas model so it is 0 before EIP-8038. - # The EXTCODECOPY metadata mirrors the measured access: a 0x20-byte - # copy into already-expanded memory, so only the account-access - # component varies across forks. - warm_extcode_delta = ( - Op.EXTCODESIZE.with_metadata(address_warm=True).gas_cost(fork) - 100 - ) + ( - Op.EXTCODECOPY.with_metadata( - address_warm=True, - data_size=0x20, - new_memory_size=0x120, - old_memory_size=0x120, - ).gas_cost(fork) - - 103 - ) - - expect_entries_: list[dict] = [ - { - "indexes": {"data": [0], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {contract_12: Account(storage={0: 32089})}, - }, - { - "indexes": {"data": [1], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {contract_12: Account(storage={0: 32193})}, - }, - { - "indexes": {"data": [2, 3], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {contract_12: Account(storage={0: 144})}, - }, - { - "indexes": {"data": [4, 5], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {contract_12: Account(storage={0: 141})}, - }, - { - "indexes": {"data": [6], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {contract_12: Account(storage={0: 8110})}, - }, - { - "indexes": {"data": [8, 7], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {contract_12: Account(storage={0: 8113})}, - }, - { - "indexes": {"data": [10], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": { - contract_12: Account(storage={0: 221 + warm_extcode_delta}) - }, - }, - { - "indexes": {"data": [9], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {contract_12: Account(storage={0: 18348})}, - }, - ] - - post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork) - - tx_data = [ - Bytes("693c6139") + Hash(0xF0), - Bytes("693c6139") + Hash(0xF5), - Bytes("693c6139") + Hash(0xF1), - Bytes("693c6139") + Hash(0xF2), - Bytes("693c6139") + Hash(0xF4), - Bytes("693c6139") + Hash(0xFA), - Bytes("693c6139") + Hash(0x51), - Bytes("693c6139") + Hash(0x52), - Bytes("693c6139") + Hash(0x53), - Bytes("693c6139") + Hash(0x20), - Bytes("693c6139") + Hash(0x3B), - ] - tx_gas = [16777216] - + # Without an empty reservoir the creations draw their state gas from + # it rather than from the probe, and one gas short still succeeds. tx = Transaction( - sender=sender, - to=contract_12, - data=tx_data[d], - gas_limit=tx_gas[g], - nonce=1, - error=_exc, + sender=pre.fund_eoa(), + to=entry, + state_gas_reservoir=0, ) - state_test(env=env, pre=pre, post=post, tx=tx) + post = {entry: Account(storage={FLAG_SLOT: 1 if sufficient else 0})} + + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stBadOpcode/test_operation_diff_gas.py b/tests/ported_static/stBadOpcode/test_operation_diff_gas.py deleted file mode 100644 index bb80de6c8d9..00000000000 --- a/tests/ported_static/stBadOpcode/test_operation_diff_gas.py +++ /dev/null @@ -1,456 +0,0 @@ -""" -Ori Pomerantz qbzzt1@gmail.com. - -Ported from: -state_tests/stBadOpcode/operationDiffGasFiller.yml - -@manually-enhanced: Do not overwrite. A search measures the gas an -opcode needs to succeed. Two access classes shift under EIP-8038: the -CALL-family probes (`CALL`/`CALLCODE`/`DELEGATECALL`/`STATICCALL`) make -one cold account access to the callee, repricing by -`COLD_ACCOUNT_ACCESS - 2600`; the EXTCODE probe runs a cold -`EXTCODESIZE` plus a warm `EXTCODECOPY`, each carrying the extra -extcode surcharge. Every delta is derived from the fork's own gas -model, so it is exactly 0 before EIP-8038 and tracks future parameter -changes; do not hardcode the Amsterdam numbers. -""" - -import pytest -from execution_testing import ( - EOA, - Account, - Address, - Alloc, - Bytes, - Environment, - Hash, - StateTestFiller, - Transaction, -) -from execution_testing.forks import Fork -from execution_testing.vm import Op - -from tests.ported_static.post_state_resolution import ( - resolve_expect_post, -) - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - ["state_tests/stBadOpcode/operationDiffGasFiller.yml"], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.parametrize( - "d, g, v", - [ - pytest.param( - 0, - 0, - 0, - id="CREATE", - ), - pytest.param( - 1, - 0, - 0, - id="CREATE2", - ), - pytest.param( - 2, - 0, - 0, - id="CALL", - ), - pytest.param( - 3, - 0, - 0, - id="CALLCODE", - ), - pytest.param( - 4, - 0, - 0, - id="DELEGATECALL", - ), - pytest.param( - 5, - 0, - 0, - id="STATICCALL", - ), - pytest.param( - 6, - 0, - 0, - id="MLOAD", - ), - pytest.param( - 7, - 0, - 0, - id="MSTORE", - ), - pytest.param( - 8, - 0, - 0, - id="MSTORE8", - ), - pytest.param( - 9, - 0, - 0, - id="SHA3", - ), - pytest.param( - 10, - 0, - 0, - id="EXTCODE", - ), - ], -) -@pytest.mark.pre_alloc_mutable -def test_operation_diff_gas( - state_test: StateTestFiller, - pre: Alloc, - fork: Fork, - d: int, - g: int, - v: int, -) -> None: - """Ori Pomerantz qbzzt1@gmail.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0x0000000000000000000000000000000000C0DEF0) - contract_1 = Address(0x0000000000000000000000000000000000C0DEF5) - contract_2 = Address(0x0000000000000000000000000000000000C0DEF1) - contract_3 = Address(0x0000000000000000000000000000000000C0DEF2) - contract_4 = Address(0x0000000000000000000000000000000000C0DEF4) - contract_5 = Address(0x0000000000000000000000000000000000C0DEFA) - contract_6 = Address(0x000000000000000000000000000000000000CA11) - contract_7 = Address(0x0000000000000000000000000000000000C0DE51) - contract_8 = Address(0x0000000000000000000000000000000000C0DE52) - contract_9 = Address(0x0000000000000000000000000000000000C0DE53) - contract_10 = Address(0x0000000000000000000000000000000000C0DE20) - contract_11 = Address(0x0000000000000000000000000000000000C0DE3B) - contract_12 = Address(0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC) - sender = EOA( - key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8 - ) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=100000000, - ) - - pre[sender] = Account(balance=0xBA1A9CE0BA1A9CE, nonce=1) - # Source: yul - # berlin { - # sstore(0,create(0, 0, 0x200)) - # } - contract_0 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE( - key=0x0, value=Op.CREATE(value=Op.DUP1, offset=0x0, size=0x200) - ) - + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=1, - address=Address(0x0000000000000000000000000000000000C0DEF0), # noqa: E501 - ) - # Source: yul - # berlin { - # sstore(0,create2(0, 0, 0x200, 0x5A17)) - # } - contract_1 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE( - key=0x0, - value=Op.CREATE2( - value=Op.DUP1, offset=0x0, size=0x200, salt=0x5A17 - ), - ) - + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=1, - address=Address(0x0000000000000000000000000000000000C0DEF5), # noqa: E501 - ) - # Source: yul - # berlin { - # mstore(0, 0xDEADBEEF) - # return(0, 0x100) - # } - contract_6 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=0xDEADBEEF) - + Op.RETURN(offset=0x0, size=0x100), - balance=0xBA1A9CE0BA1A9CE, - nonce=1, - address=Address(0x000000000000000000000000000000000000CA11), # noqa: E501 - ) - # Source: yul - # berlin { - # let useless := mload(0xBEEF) - # } - contract_7 = pre.deploy_contract( # noqa: F841 - code=Op.MLOAD(offset=0xBEEF) + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=1, - address=Address(0x0000000000000000000000000000000000C0DE51), # noqa: E501 - ) - # Source: yul - # berlin { - # mstore(0xBEEF, 0xFF) - # } - contract_8 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0xBEEF, value=0xFF) + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=1, - address=Address(0x0000000000000000000000000000000000C0DE52), # noqa: E501 - ) - # Source: yul - # berlin { - # mstore8(0xBEEF, 0xFF) - # } - contract_9 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE8(offset=0xBEEF, value=0xFF) + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=1, - address=Address(0x0000000000000000000000000000000000C0DE53), # noqa: E501 - ) - # Source: yul - # berlin { - # let useless := keccak256(0,0xBEEF) - # } - contract_10 = pre.deploy_contract( # noqa: F841 - code=Op.SHA3(offset=0x0, size=0xBEEF) + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=1, - address=Address(0x0000000000000000000000000000000000C0DE20), # noqa: E501 - ) - # Source: yul - # berlin { - # // Run the operation with gasAmt, gasAmt+gasDiff, gasAmt+2*gasDiff, etc. # noqa: E501 - # let gasAmt := calldataload(0x24) - # let gasDiff := calldataload(0x44) - # let addr := add(0xC0DE00, calldataload(0x04)) - # let result := 0 - # - # for { } eq(result, 0) { } { // Until the operation is successful - # result := call(gasAmt, addr, 0, 0, 0, 0, 0) - # gasAmt := add(gasAmt, gasDiff) - # } - # sstore(0, sub(gasAmt, gasDiff)) - # } - contract_12 = pre.deploy_contract( # noqa: F841 - code=Op.CALLDATALOAD(offset=0x44) - + Op.CALLDATALOAD(offset=0x24) - + Op.ADD(Op.CALLDATALOAD(offset=0x4), 0xC0DE00) - + Op.PUSH1[0x0] - + Op.DUP1 - + Op.JUMPDEST - + Op.JUMPI(pc=0x1C, condition=Op.EQ) - + Op.POP - + Op.SSTORE(key=0x0, value=Op.SUB) - + Op.STOP - + Op.JUMPDEST - + Op.PUSH1[0x0] - + Op.DUP4 - + Op.CALL( - gas=Op.DUP10, - address=Op.DUP8, - value=Op.DUP1, - args_offset=Op.DUP1, - args_size=Op.DUP1, - ret_offset=Op.DUP1, - ret_size=Op.DUP2, - ) - + Op.SWAP4 - + Op.ADD - + Op.SWAP3 - + Op.JUMP(pc=0x11), - balance=0xBA1A9CE0BA1A9CE, - nonce=1, - address=Address(0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC), # noqa: E501 - ) - # Source: yul - # berlin { - # let retval := call(gas(), 0xCA11, 0, 0, 0x100, 0, 0x100) - # } - contract_2 = pre.deploy_contract( # noqa: F841 - code=Op.CALL( - gas=Op.GAS, - address=0xCA11, - value=Op.DUP1, - args_offset=Op.DUP2, - args_size=Op.DUP2, - ret_offset=0x0, - ret_size=0x100, - ) - + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=1, - address=Address(0x0000000000000000000000000000000000C0DEF1), # noqa: E501 - ) - # Source: yul - # berlin { - # let addr := 0xCA11 - # extcodecopy(addr, 0, 0, extcodesize(addr)) - # } - contract_11 = pre.deploy_contract( # noqa: F841 - code=Op.PUSH2[0xCA11] - + Op.PUSH1[0x0] - + Op.DUP1 - + Op.EXTCODESIZE(address=Op.DUP3) - + Op.SWAP3 - + Op.EXTCODECOPY - + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=1, - address=Address(0x0000000000000000000000000000000000C0DE3B), # noqa: E501 - ) - # Source: yul - # berlin { - # let retval := staticcall(gas(), 0xCA11, 0, 0x100, 0, 0x100) - # } - contract_5 = pre.deploy_contract( # noqa: F841 - code=Op.STATICCALL( - gas=Op.GAS, - address=0xCA11, - args_offset=Op.DUP2, - args_size=Op.DUP2, - ret_offset=0x0, - ret_size=0x100, - ) - + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=1, - address=Address(0x0000000000000000000000000000000000C0DEFA), # noqa: E501 - ) - # Source: yul - # berlin { - # let retval := delegatecall(gas(), 0xCA11, 0, 0x100, 0, 0x100) - # } - contract_4 = pre.deploy_contract( # noqa: F841 - code=Op.DELEGATECALL( - gas=Op.GAS, - address=0xCA11, - args_offset=Op.DUP2, - args_size=Op.DUP2, - ret_offset=0x0, - ret_size=0x100, - ) - + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=1, - address=Address(0x0000000000000000000000000000000000C0DEF4), # noqa: E501 - ) - # Source: yul - # berlin { - # let retval := callcode(gas(), 0xCA11, 0, 0, 0x100, 0, 0x100) - # } - contract_3 = pre.deploy_contract( # noqa: F841 - code=Op.CALLCODE( - gas=Op.GAS, - address=0xCA11, - value=Op.DUP1, - args_offset=Op.DUP2, - args_size=Op.DUP2, - ret_offset=0x0, - ret_size=0x100, - ) - + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=1, - address=Address(0x0000000000000000000000000000000000C0DEF2), # noqa: E501 - ) - - # The CALL-family probes make one cold account access to the callee; - # EIP-8038 reprices it by `COLD_ACCOUNT_ACCESS - 2600`. The EXTCODE - # probe runs a cold `EXTCODESIZE` plus a warm `EXTCODECOPY`, each - # carrying the extcode surcharge. Both deltas come from the fork gas - # model, so they are 0 before EIP-8038. The EXTCODECOPY metadata - # mirrors the measured access (a 0x20-byte copy into already-expanded - # memory) so only the account-access component varies across forks. - gas_costs = fork.gas_costs() - cold_account_delta = gas_costs.COLD_ACCOUNT_ACCESS - 2600 - extcode_probe_delta = ( - Op.EXTCODESIZE.with_metadata(address_warm=False).gas_cost(fork) - 2600 - ) + ( - Op.EXTCODECOPY.with_metadata( - address_warm=True, - data_size=0x20, - new_memory_size=0x120, - old_memory_size=0x120, - ).gas_cost(fork) - - 103 - ) - - expect_entries_: list[dict] = [ - { - "indexes": {"data": [0], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {contract_12: Account(storage={0: 54200})}, - }, - { - "indexes": {"data": [1], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {contract_12: Account(storage={0: 54300})}, - }, - { - "indexes": {"data": [2, 3, 4, 5], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": { - contract_12: Account(storage={0: 2700 + cold_account_delta}) - }, - }, - { - "indexes": {"data": [8, 6, 7], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {contract_12: Account(storage={0: 9200})}, - }, - { - "indexes": {"data": [10], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": { - contract_12: Account(storage={0: 2800 + extcode_probe_delta}) - }, - }, - { - "indexes": {"data": [9], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {contract_12: Account(storage={0: 18400})}, - }, - ] - - post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork) - - tx_data = [ - Bytes("048071d3") + Hash(0xF0) + Hash(0x0) + Hash(0x64), - Bytes("048071d3") + Hash(0xF5) + Hash(0x0) + Hash(0x64), - Bytes("048071d3") + Hash(0xF1) + Hash(0x0) + Hash(0x64), - Bytes("048071d3") + Hash(0xF2) + Hash(0x0) + Hash(0x64), - Bytes("048071d3") + Hash(0xF4) + Hash(0x0) + Hash(0x64), - Bytes("048071d3") + Hash(0xFA) + Hash(0x0) + Hash(0x64), - Bytes("048071d3") + Hash(0x51) + Hash(0x0) + Hash(0x64), - Bytes("048071d3") + Hash(0x52) + Hash(0x0) + Hash(0x64), - Bytes("048071d3") + Hash(0x53) + Hash(0x0) + Hash(0x64), - Bytes("048071d3") + Hash(0x20) + Hash(0x0) + Hash(0x64), - Bytes("048071d3") + Hash(0x3B) + Hash(0x0) + Hash(0x64), - ] - tx_gas = [16777216] - - tx = Transaction( - sender=sender, - to=contract_12, - data=tx_data[d], - gas_limit=tx_gas[g], - nonce=1, - error=_exc, - ) - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stCallCreateCallCodeTest/test_create_name_registrator_per_txs_not_enough_gas.py b/tests/ported_static/stCallCreateCallCodeTest/test_create_name_registrator_per_txs_not_enough_gas.py index 87fb08afa78..3615d1964a4 100644 --- a/tests/ported_static/stCallCreateCallCodeTest/test_create_name_registrator_per_txs_not_enough_gas.py +++ b/tests/ported_static/stCallCreateCallCodeTest/test_create_name_registrator_per_txs_not_enough_gas.py @@ -1,127 +1,139 @@ """ -Legacy Test from Christoph. J. +Verify a name-registrator contract creation succeeds or fails with the +transaction budget: the init code writes a storage slot and deposits the +registrar's runtime code. Ported from: state_tests/stCallCreateCallCodeTest/createNameRegistratorPerTxsNotEnoughGasFiller.json +Legacy Test from Christoph. J. + +@manually-enhanced: Do not overwrite. The budget is an exact off-by-one +boundary derived from the fork: the sufficient arm gets intrinsic + +top-frame state gas + init code execution, the insufficient arm one gas +less. The deposit cost rides on RETURN's `code_deposit_size` metadata +rather than a hand-rolled per-byte constant, so it stays correct once +EIP-8037 moves most of it to state gas. The runtime code is a separate +bytecode appended to the init code instead of a slice of it, so the +executed cost no longer counts the payload. The success arm also pins +the deposited code and transferred balance, which the ported post never +checked. """ import pytest from execution_testing import ( Account, - Address, Alloc, - Environment, + Fork, StateTestFiller, Transaction, compute_create_address, ) -from execution_testing.forks import Fork from execution_testing.vm import Op -from tests.ported_static.post_state_resolution import ( - resolve_expect_post, -) - REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +COPY_OFFSET = 18 + @pytest.mark.ported_from( [ "state_tests/stCallCreateCallCodeTest/createNameRegistratorPerTxsNotEnoughGasFiller.json" # noqa: E501 ], ) -@pytest.mark.valid_from("Cancun") +@pytest.mark.valid_from("Berlin") @pytest.mark.parametrize( - "d, g, v", + "enough_gas", [ - pytest.param( - 0, - 0, - 0, - id="-g0", - ), - pytest.param( - 0, - 1, - 0, - id="-g1", - ), + pytest.param(False, id="insufficient_gas"), + pytest.param(True, id="sufficient_gas"), + ], +) +@pytest.mark.parametrize( + "value", + [ + pytest.param(0, id="zero_value"), + pytest.param(1, id="non_zero_value"), ], ) def test_create_name_registrator_per_txs_not_enough_gas( state_test: StateTestFiller, pre: Alloc, fork: Fork, - d: int, - g: int, - v: int, + enough_gas: bool, + value: int, ) -> None: - """Legacy Test from Christoph.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000000, + """An under-budgeted registrar creation leaves no account behind.""" + # The ported init code: write slot 1, then copy the registrar runtime + # appended after it and return it for deposit. + store = Op.SSTORE( + key=0x1, value=0x1, key_warm=False, original_value=0, new_value=1 ) - - expect_entries_: list[dict] = [ - { - "indexes": {"data": -1, "gas": 0, "value": -1}, - "network": [">=Cancun"], - "result": { - sender: Account(nonce=1), - compute_create_address( - address=sender, nonce=0 - ): Account.NONEXISTENT, - }, - }, - { - "indexes": {"data": -1, "gas": 1, "value": -1}, - "network": [">=Cancun"], - "result": { - sender: Account(nonce=1), - compute_create_address(address=sender, nonce=0): Account( - storage={1: 1} - ), - }, - }, - ] - - post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork) - - tx_data = [ - Op.SSTORE(key=0x1, value=0x1) - + Op.PUSH1[0x10] - + Op.CODECOPY(dest_offset=0x0, offset=0xC, size=Op.DUP1) - + Op.PUSH1[0x0] - + Op.RETURN - + Op.STOP - + Op.JUMPI( + deposited = ( + Op.JUMPI( pc=0x9, condition=Op.ISZERO(Op.SLOAD(key=Op.CALLDATALOAD(offset=0x0))), ) + Op.STOP + Op.JUMPDEST + Op.SSTORE( - key=Op.CALLDATALOAD(offset=0x0), value=Op.CALLDATALOAD(offset=0x20) - ), - ] - tx_gas = [56157, 86157] - tx_value = [100000] + key=Op.CALLDATALOAD(offset=0x0), + value=Op.CALLDATALOAD(offset=0x20), + ) + ) + deposited_size = len(deposited) + initcode = ( + store + + Op.CODECOPY( + dest_offset=0x0, + offset=COPY_OFFSET, + size=deposited_size, + data_size=deposited_size, + new_memory_size=deposited_size, + ) + + Op.RETURN(0, deposited_size, code_deposit_size=deposited_size) + + Op.STOP + ) + assert len(initcode) == COPY_OFFSET + calldata = initcode + deposited + + # Fork-derived budget: exactly what the creation needs, so one gas + # less must fail. The deposit is already inside the init code's cost, + # via RETURN's `code_deposit_size`. + overhead = fork.transaction_intrinsic_cost_calculator()( + calldata=calldata, + contract_creation=True, + return_cost_deducted_prior_execution=True, + ) + fork.transaction_top_frame_state_gas( + contract_creation=True, sends_value=value > 0 + ) + execution_cost = initcode.gas_cost(fork) + gas_limit = overhead + execution_cost + if not enough_gas: + gas_limit -= 1 + sender = pre.fund_eoa() tx = Transaction( sender=sender, to=None, - data=tx_data[d], - gas_limit=tx_gas[g], - value=tx_value[v], - error=_exc, + data=calldata, + gas_limit=gas_limit, + value=value, ) - state_test(env=env, pre=pre, post=post, tx=tx) + created = compute_create_address(address=sender, nonce=0) + if enough_gas: + created_account: Account | None = Account( + nonce=1, + code=deposited, + balance=value, + storage={1: 1}, + ) + else: + created_account = Account.NONEXISTENT + post = { + sender: Account(nonce=1), + created: created_account, + } + + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stCallCreateCallCodeTest/test_create_name_registrator_pre_store1_not_enough_gas.py b/tests/ported_static/stCallCreateCallCodeTest/test_create_name_registrator_pre_store1_not_enough_gas.py index cc76b3587f5..847947879fb 100644 --- a/tests/ported_static/stCallCreateCallCodeTest/test_create_name_registrator_pre_store1_not_enough_gas.py +++ b/tests/ported_static/stCallCreateCallCodeTest/test_create_name_registrator_pre_store1_not_enough_gas.py @@ -1,80 +1,161 @@ """ -Legacy Test from Christoph. J. +Verify a nested CREATE/CREATE2 of the name registrar at the exact boundary +of its EIP-150 grant: one gas either side decides whether the child account +materializes, while the creating frame completes regardless. Ported from: state_tests/stCallCreateCallCodeTest/createNameRegistratorPreStore1NotEnoughGasFiller.json +Legacy Test from Christoph. J. + +@manually-enhanced: Do not overwrite. The registrar init code is composed +(not a hex blob), with the runtime a separate bytecode appended to it so +the executed cost never counts the payload, and the deposit riding on +RETURN's `code_deposit_size` metadata. The creator's frame gas is solved +for the exact 63/64 grant that covers the child, then stepped one grant +below it for the failing arm; the creator's balance is asserted (the +endowment returns on failure). Both create opcodes are covered, which the +boundary keeps honest: CREATE2 costs 15 gas more up front (hashing the +init code, plus the salt push) and the solved budget absorbs it. """ import pytest from execution_testing import ( Account, - Address, Alloc, - Bytes, - Environment, + Fork, StateTestFiller, Transaction, compute_create_address, ) -from execution_testing.vm import Op +from execution_testing.vm import Macros, Op, Opcodes REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +TX_VALUE = 0x186A0 +CREATE_VALUE = 0x17 +INITIAL_BALANCE = 10**15 +COPY_OFFSET = 18 + @pytest.mark.ported_from( [ "state_tests/stCallCreateCallCodeTest/createNameRegistratorPreStore1NotEnoughGasFiller.json" # noqa: E501 ], ) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable +@pytest.mark.valid_from("Berlin") +@pytest.mark.with_all_create_opcodes +@pytest.mark.parametrize( + "enough_gas", + [ + pytest.param(False, id="insufficient_gas"), + pytest.param(True, id="sufficient_gas"), + ], +) def test_create_name_registrator_pre_store1_not_enough_gas( state_test: StateTestFiller, pre: Alloc, + fork: Fork, + create_opcode: Opcodes, + enough_gas: bool, ) -> None: - """Legacy Test from Christoph.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87) - sender = pre.fund_eoa(amount=0xDE0B6B3A7640000) + """A nested registrar creation at the exact edge of its EIP-150 grant.""" + # The registrar runtime, deployed by the init code below. + deployed = ( + Op.JUMPI( + pc=0x9, + condition=Op.ISZERO(Op.SLOAD(key=Op.CALLDATALOAD(offset=0x0))), + ) + + Op.STOP + + Op.JUMPDEST + + Op.SSTORE( + key=Op.CALLDATALOAD(offset=0x0), + value=Op.CALLDATALOAD(offset=0x20), + ) + ) + deployed_size = len(deployed) + # The registrar init code (same as the per-txs sibling): write slot 1, + # then copy the runtime appended after it and return it for deposit. + initcode = ( + Op.SSTORE( + key=0x1, value=0x1, key_warm=False, original_value=0, new_value=1 + ) + + Op.CODECOPY( + dest_offset=0x0, + offset=COPY_OFFSET, + size=deployed_size, + data_size=deployed_size, + new_memory_size=deployed_size, + ) + + Op.RETURN(0, deployed_size, code_deposit_size=deployed_size) + + Op.STOP + ) + assert len(initcode) == COPY_OFFSET + child_code = bytes(initcode + deployed) - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=100000000, + setup = Macros.MSTORE(child_code) + create_code = create_opcode( + value=CREATE_VALUE, + offset=0x0, + size=len(child_code), + init_code_size=len(child_code), + ) + creator = pre.deploy_contract( + code=setup + Op.POP(create_code) + Op.STOP, + balance=INITIAL_BALANCE, ) - # Source: lll - # {(MSTORE 0 0x6001600155601080600c6000396000f3006000355415600957005b6020356000 ) (MSTORE8 32 0x35) (MSTORE8 33 0x55) (CREATE 23 0 34) } # noqa: E501 - contract_0 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE( - offset=0x0, - value=0x6001600155601080600C6000396000F3006000355415600957005B6020356000, # noqa: E501 + # Solve for the creator frame's gas at the CREATE so its 63/64 grant + # lands exactly on the child's cost; the failing arm steps down until + # the grant really drops below it (the grant repeats every 64 gas). + child_needed = initcode.gas_cost(fork) + frame_gas = child_needed * 64 // 63 + while frame_gas - frame_gas // 64 < child_needed: + frame_gas += 1 + if not enough_gas: + while frame_gas - frame_gas // 64 >= child_needed: + frame_gas -= 1 + + gas_limit = ( + fork.transaction_intrinsic_cost_calculator()( + sends_value=True, + return_cost_deducted_prior_execution=True, ) - + Op.MSTORE8(offset=0x20, value=0x35) - + Op.MSTORE8(offset=0x21, value=0x55) - + Op.CREATE(value=0x17, offset=0x0, size=0x22) - + Op.STOP, - balance=0xDE0B6B3A7640000, - nonce=0, + + setup.gas_cost(fork) + + create_code.gas_cost(fork) + + frame_gas ) tx = Transaction( - sender=sender, - to=contract_0, - data=Bytes(""), - gas_limit=73071, - value=0x186A0, + sender=pre.fund_eoa(), + to=creator, + gas_limit=gas_limit, + value=TX_VALUE, + ) + + created = compute_create_address( + address=creator, + nonce=1, + initcode=child_code, + opcode=create_opcode, ) + if enough_gas: + created_account: Account | None = Account( + nonce=1, + code=deployed, + balance=CREATE_VALUE, + storage={1: 1}, + ) + creator_balance = INITIAL_BALANCE + TX_VALUE - CREATE_VALUE + else: + # The endowment returns when the child fails. + created_account = Account.NONEXISTENT + creator_balance = INITIAL_BALANCE + TX_VALUE post = { - contract_0: Account(nonce=1), - compute_create_address( - address=contract_0, nonce=0 - ): Account.NONEXISTENT, + # The CREATE advanced the nonce whether or not its child completed. + creator: Account(nonce=2, balance=creator_balance), + created: created_account, } - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stCreateTest/test_create_e_contract_create_ne_contract_in_init_oog_tr.py b/tests/ported_static/stCreateTest/test_create_e_contract_create_ne_contract_in_init_oog_tr.py index 943824d3990..620bc75d82e 100644 --- a/tests/ported_static/stCreateTest/test_create_e_contract_create_ne_contract_in_init_oog_tr.py +++ b/tests/ported_static/stCreateTest/test_create_e_contract_create_ne_contract_in_init_oog_tr.py @@ -1,141 +1,152 @@ """ -Test_create_e_contract_create_ne_contract_in_init_oog_tr. +Verify a contract-creation transaction whose init code first calls an +existing contract and then CREATEs a child, at the exact boundary of the +nested CREATE's EIP-150 grant: the call and the creating frame complete +either way, and one gas decides only whether the child materializes (at +the creator's nonce-1 address, not nonce 0). Ported from: state_tests/stCreateTest/CREATE_EContractCreateNEContractInInitOOG_TrFiller.json + +@manually-enhanced: Do not overwrite. Budgets are derived from the fork +(intrinsic + EIP-8037 top-frame and nested-create state gas + composed +code costs, the child's deposit riding on its RETURN metadata), and the +frame's gas is solved for the smallest 63/64 grant that covers the child +rather than scaled by a guessed factor. The callee call forwards all gas +instead of a ported fixed budget, and the nested child is now asserted at +its real nonce-1 address (the port only checked the vacuous nonce-0 +address). """ import pytest from execution_testing import ( Account, - Address, Alloc, - Environment, + Fork, StateTestFiller, Transaction, compute_create_address, ) -from execution_testing.forks import Fork from execution_testing.vm import Op -from tests.ported_static.post_state_resolution import ( - resolve_expect_post, -) - REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +CALLEE_STORED = 0xC + @pytest.mark.ported_from( [ "state_tests/stCreateTest/CREATE_EContractCreateNEContractInInitOOG_TrFiller.json" # noqa: E501 ], ) -@pytest.mark.valid_from("Cancun") -@pytest.mark.parametrize( - "d, g, v", - [ - pytest.param( - 0, - 0, - 0, - id="-g0", - ), - pytest.param( - 0, - 1, - 0, - id="-g1", - ), - ], -) -@pytest.mark.pre_alloc_mutable +@pytest.mark.valid_from("Berlin") +@pytest.mark.parametrize("oog", [False, True], ids=["enough-gas", "oog"]) def test_create_e_contract_create_ne_contract_in_init_oog_tr( state_test: StateTestFiller, pre: Alloc, fork: Fork, - d: int, - g: int, - v: int, + oog: bool, ) -> None: - """Test_create_e_contract_create_ne_contract_in_init_oog_tr.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0xC94F5374FCE5EDBC8E2A8697C15331677E6EBF0B) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, + """Budget decides how far a creation's call-then-CREATE init gets.""" + sender = pre.fund_eoa() + + # Callee: one cold zero->non-zero store observed in the post. + callee_code = ( + Op.SSTORE( + key=0x1, + value=CALLEE_STORED, + key_warm=False, + original_value=0, + new_value=CALLEE_STORED, + ) + + Op.STOP ) + callee = pre.deploy_contract(code=callee_code) - # Source: lll - # {[[1]]12} - contract_0 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x1, value=0xC) + Op.STOP, - balance=0xE8D4A51000, - nonce=0, + # Child init code: return a small runtime code from memory. + child_runtime = Op.SSTORE(key=0x0, value=CALLEE_STORED) + child_initcode = Op.MSTORE( + offset=0x0, + value=int.from_bytes(child_runtime, "big"), + new_memory_size=0x20, + ) + Op.RETURN( + offset=32 - len(child_runtime), + size=len(child_runtime), + code_deposit_size=len(child_runtime), ) - expect_entries_: list[dict] = [ - { - "indexes": {"data": -1, "gas": 0, "value": -1}, - "network": [">=Cancun"], - "result": { - contract_0: Account(storage={1: 12}), - compute_create_address(address=sender, nonce=0): Account( - nonce=2 - ), - compute_create_address( - address=compute_create_address(address=sender, nonce=0), - nonce=0, - ): Account.NONEXISTENT, - }, - }, - { - "indexes": {"data": -1, "gas": 1, "value": -1}, - "network": [">=Cancun"], - "result": { - contract_0: Account(storage={1: 0}), - compute_create_address( - address=sender, nonce=0 - ): Account.NONEXISTENT, - compute_create_address( - address=compute_create_address(address=sender, nonce=0), - nonce=0, - ): Account.NONEXISTENT, - }, - }, - ] - - post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork) - - tx_data = [ - Op.POP( - Op.CALL( - gas=0xEA60, - address=contract_0, - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ) + # Transaction init code: call the callee (forwarding all gas), then + # CREATE the child from memory; deploys nothing itself. + call_code = Op.POP(Op.CALL(address=callee)) + stage_code = Op.MSTORE( + offset=0x0, + value=int.from_bytes(child_initcode, "big"), + new_memory_size=0x20, + ) + create_code = Op.CREATE( + value=0x0, + offset=32 - len(child_initcode), + size=len(child_initcode), + init_code_size=len(child_initcode), + ) + initcode = call_code + stage_code + create_code + + # The CREATE forwards only 63/64 of what the frame holds, so the + # frame must keep more than the child needs: solve for the smallest + # amount whose grant still covers it, then step below that grant for + # the starved arm (the grant repeats every 64 gas, so one gas less + # does not always forward less). The deposit rides on the child's + # RETURN metadata. + child_total = child_initcode.gas_cost(fork) + frame_gas = child_total * 64 // 63 + while frame_gas - frame_gas // 64 < child_total: + frame_gas += 1 + if oog: + while frame_gas - frame_gas // 64 >= child_total: + frame_gas -= 1 + + # Everything else must land: the fresh create target's top-frame + # state gas (EIP-8037), the callee, and the nested creation's peak + # charge. + gas_limit = ( + fork.transaction_intrinsic_cost_calculator()( + calldata=initcode, + contract_creation=True, + return_cost_deducted_prior_execution=True, ) - + Op.MSTORE(offset=0x0, value=0x64600C6000556000526005601BF3) - + Op.CREATE(value=0x0, offset=0x12, size=0xE), - ] - tx_gas = [160000, 60000] + + fork.transaction_top_frame_state_gas( + contract_creation=True, sends_value=False + ) + + callee_code.gas_cost(fork) + + initcode.gas_cost(fork) + + frame_gas + ) tx = Transaction( sender=sender, to=None, - data=tx_data[d], - gas_limit=tx_gas[g], - error=_exc, + data=initcode, + gas_limit=gas_limit, ) - state_test(env=env, pre=pre, post=post, tx=tx) + created = compute_create_address(address=sender, nonce=0) + # The nested CREATE runs while the creator's nonce is 1 (EIP-161), + # so the child lands at the nonce-1 address and the nonce-0 address + # must stay empty. + child = compute_create_address(address=created, nonce=1) + child_at_nonce0 = compute_create_address(address=created, nonce=0) + + post = { + sender: Account(nonce=1), + # Only the child's grant changes between the arms: the call and + # the creating frame complete either way. + callee: Account(storage={1: CALLEE_STORED}), + created: Account(nonce=2, code=b""), + child: Account(nonce=1, code=child_runtime, storage={}) + if not oog + else Account.NONEXISTENT, + child_at_nonce0: Account.NONEXISTENT, + } + + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stCreateTest/test_create_e_contract_then_call_to_non_existent_acc.py b/tests/ported_static/stCreateTest/test_create_e_contract_then_call_to_non_existent_acc.py deleted file mode 100644 index f752c8b1f6b..00000000000 --- a/tests/ported_static/stCreateTest/test_create_e_contract_then_call_to_non_existent_acc.py +++ /dev/null @@ -1,101 +0,0 @@ -""" -Test_create_e_contract_then_call_to_non_existent_acc. - -Ported from: -state_tests/stCreateTest/CREATE_EContract_ThenCALLToNonExistentAccFiller.json -""" - -import pytest -from execution_testing import ( - EOA, - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, - compute_create_address, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - [ - "state_tests/stCreateTest/CREATE_EContract_ThenCALLToNonExistentAccFiller.json" # noqa: E501 - ], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_create_e_contract_then_call_to_non_existent_acc( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_create_e_contract_then_call_to_non_existent_acc.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B) - sender = EOA( - key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8 - ) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - pre[sender] = Account(balance=0xE8D4A51000) - # Source: lll - # { [[0]](GAS) [[1]] (CREATE 0 0 32) [[2]](GAS) [[3]] (CALL 60000 0xe1ecf98489fa9ed60a664fc4998db699cfa39d40 0 0 0 0 0) [[100]] (GAS) } # noqa: E501 - contract_0 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=Op.GAS) - + Op.SSTORE(key=0x1, value=Op.CREATE(value=0x0, offset=0x0, size=0x20)) - + Op.SSTORE(key=0x2, value=Op.GAS) - + Op.SSTORE( - key=0x3, - value=Op.CALL( - gas=0xEA60, - address=0xE1ECF98489FA9ED60A664FC4998DB699CFA39D40, - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.SSTORE(key=0x64, value=Op.GAS) - + Op.STOP, - nonce=0, - address=Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B), # noqa: E501 - ) - - tx = Transaction( - sender=sender, - to=contract_0, - data=Bytes(""), - gas_limit=600000, - ) - - post = { - contract_0: Account( - storage={ - 0: 0x8D5B6, - 1: compute_create_address(address=contract_0, nonce=0), - 2: 0x7ABF8, - 3: 1, - 100: 0x6F50B, - }, - ), - compute_create_address(address=contract_0, nonce=0): Account(nonce=1), - Address( - 0xE1ECF98489FA9ED60A664FC4998DB699CFA39D40 - ): Account.NONEXISTENT, - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stCreateTest/test_create_empty_contract_and_call_it.py b/tests/ported_static/stCreateTest/test_create_empty_contract_and_call_it.py deleted file mode 100644 index cb9c8b21fb7..00000000000 --- a/tests/ported_static/stCreateTest/test_create_empty_contract_and_call_it.py +++ /dev/null @@ -1,111 +0,0 @@ -""" -Test CREATE of an empty contract followed by a CALL to it, measuring the -CALL gas cost. - -Ported from: -state_tests/stCreateTest/CREATE_EmptyContractAndCallIt_0weiFiller.json -state_tests/stCreateTest/CREATE_EmptyContractAndCallIt_1weiFiller.json - -@manually-enhanced: Do not overwrite. CALL gas via CodeGasMeasure; dynamic -address (runtime SLOAD); 0wei/1wei folded into one parametrize. -""" - -import pytest -from execution_testing import ( - Account, - Alloc, - CodeGasMeasure, - Fork, - StateTestFiller, - Transaction, - compute_create_address, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - -ADDRESS_SLOT = 0x1 -GAS_SLOT = 0x64 - -FORWARDED_GAS = 0xEA60 - - -@pytest.mark.ported_from( - [ - "state_tests/stCreateTest/CREATE_EmptyContractAndCallIt_0weiFiller.json", # noqa: E501 - "state_tests/stCreateTest/CREATE_EmptyContractAndCallIt_1weiFiller.json", # noqa: E501 - ], -) -@pytest.mark.valid_from("Berlin") -@pytest.mark.parametrize( - "call_value", - [ - pytest.param(0, id="0wei"), - pytest.param(1, id="1wei"), - ], -) -def test_create_empty_contract_and_call_it( - state_test: StateTestFiller, - pre: Alloc, - fork: Fork, - call_value: int, -) -> None: - """CREATE an empty contract, then CALL it and measure the CALL gas.""" - # CREATE over never-written memory deposits no code -> an empty account - # with nonce 1. Its address is stored so the CALL can target it at - # runtime (it is not known when the caller code is assembled). - create_code = Op.CREATE( - value=0x0, - offset=0x0, - size=0x20, - new_memory_size=0x20, - init_code_size=0x20, - ) - # The created account already exists (CREATE set its nonce) and is warm - # (CREATE accessed it), so the CALL is a warm call to an existing account. - call_code = Op.CALL( - gas=FORWARDED_GAS, - address=Op.SLOAD(key=ADDRESS_SLOT, key_warm=True), - value=call_value, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - address_warm=True, - value_transfer=call_value > 0, - account_new=False, - ) - contract = pre.deploy_contract( - code=Op.SSTORE(key=ADDRESS_SLOT, value=create_code) - + CodeGasMeasure( - code=call_code, - extra_stack_items=1, - sstore_key=GAS_SLOT, - ), - balance=call_value, - ) - - tx = Transaction( - sender=pre.fund_eoa(), - to=contract, - state_gas_reservoir=0, - ) - - # A value-bearing CALL whose empty callee consumes nothing measures - # gas_cost minus the stipend (forwarded then returned unused). - stipend = fork.gas_costs().CALL_STIPEND if call_value else 0 - created = compute_create_address(address=contract, nonce=1) - post = { - contract: Account( - storage={ - ADDRESS_SLOT: created, - GAS_SLOT: call_code.gas_cost(fork) - stipend, - }, - balance=0, - ), - # The transferred value on the 1wei case proves the CALL executed. - created: Account(nonce=1, balance=call_value), - } - - state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stCreateTest/test_create_empty_contract_then_call.py b/tests/ported_static/stCreateTest/test_create_empty_contract_then_call.py new file mode 100644 index 00000000000..fa7beef6384 --- /dev/null +++ b/tests/ported_static/stCreateTest/test_create_empty_contract_then_call.py @@ -0,0 +1,159 @@ +""" +Measure a CREATE of an empty contract followed by a CALL, targeting either +the contract just created or an account that does not exist. + +Ported from: +state_tests/stCreateTest/CREATE_EmptyContractAndCallIt_0weiFiller.json +state_tests/stCreateTest/CREATE_EmptyContractAndCallIt_1weiFiller.json +state_tests/stCreateTest/CREATE_EContract_ThenCALLToNonExistentAccFiller.json + +@manually-enhanced: Do not overwrite. The ported absolute GAS snapshots +(slots 0/2/100) are re-expressed as two CodeGasMeasure windows asserted via +the fork's gas model, with the created address and the call's success flag +kept observable inside them; the three fillers, which share one program and +differ only in the call target, are folded into one parametrize. +""" + +import pytest +from execution_testing import ( + Account, + Address, + Alloc, + Bytecode, + CodeGasMeasure, + Fork, + StateTestFiller, + Transaction, + compute_create_address, +) +from execution_testing.vm import Op + +REFERENCE_SPEC_GIT_PATH = "N/A" +REFERENCE_SPEC_VERSION = "N/A" + +CREATE_GAS_SLOT = 0x0 +ADDRESS_SLOT = 0x1 +CALL_GAS_SLOT = 0x2 +FLAG_SLOT = 0x3 + + +@pytest.mark.ported_from( + [ + "state_tests/stCreateTest/CREATE_EmptyContractAndCallIt_0weiFiller.json", # noqa: E501 + "state_tests/stCreateTest/CREATE_EmptyContractAndCallIt_1weiFiller.json", # noqa: E501 + "state_tests/stCreateTest/CREATE_EContract_ThenCALLToNonExistentAccFiller.json", # noqa: E501 + ], +) +@pytest.mark.valid_from("Berlin") +@pytest.mark.parametrize( + "call_target, call_value", + [ + pytest.param("created", 0, id="0wei"), + pytest.param("created", 1, id="1wei"), + # A value-bearing call is not exercised against the absent account: + # it would create it, which is a different behavior entirely. + pytest.param("nonexistent", 0, id="non_existent_acc"), + ], +) +def test_create_empty_contract_then_call( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + call_target: str, + call_value: int, +) -> None: + """ + Measure a CREATE of an empty contract and the CALL that follows it. + + The created account exists but is empty, so calling it and calling an + account that never existed both succeed and both leave the callee + codeless; only the cold-access surcharge separates the two costs. + """ + calls_created = call_target == "created" + + # CREATE over never-written memory: the all-STOP init code deposits + # nothing, leaving an empty account with nonce 1. Storing the address + # keeps it observable, lets the CALL target it at runtime, and folds the + # store into the measured window (the address is non-zero, so the + # placeholder new_value only sizes the zero->non-zero transition). + create_code = Op.CREATE( + value=0x0, + offset=0x0, + size=0x20, + new_memory_size=0x20, + init_code_size=0x20, + ) + store_create = Op.SSTORE( + ADDRESS_SLOT, + create_code, + key_warm=False, + original_value=0, + new_value=1, + ) + + absent = None if calls_created else pre.nonexistent_account() + # CREATE both set the created account's nonce and accessed it, so calling + # it is a warm call to an account that already exists. + target: Bytecode | Address = ( + Op.SLOAD(key=ADDRESS_SLOT, key_warm=True) if absent is None else absent + ) + + # Either way the callee runs no code and consumes nothing, so the window + # measures the CALL itself. Storing the success flag keeps it observable. + call_code = Op.CALL( + address=target, + value=call_value, + address_warm=calls_created, + value_transfer=call_value > 0, + account_new=False, + ) + store_flag = Op.SSTORE( + FLAG_SLOT, + call_code, + key_warm=False, + original_value=0, + new_value=1, + ) + + contract = pre.deploy_contract( + code=CodeGasMeasure( + code=store_create, + sstore_key=CREATE_GAS_SLOT, + ) + + CodeGasMeasure( + code=store_flag, + sstore_key=CALL_GAS_SLOT, + ), + balance=call_value, + ) + + tx = Transaction( + sender=pre.fund_eoa(), + to=contract, + state_gas_reservoir=0, + ) + + # A value-bearing CALL whose empty callee consumes nothing measures + # gas_cost minus the stipend (forwarded, then returned unused). + stipend = fork.gas_costs().CALL_STIPEND if call_value else 0 + created = compute_create_address(address=contract, nonce=1) + post: dict = { + contract: Account( + storage={ + CREATE_GAS_SLOT: store_create.gas_cost(fork), + ADDRESS_SLOT: created, + CALL_GAS_SLOT: store_flag.gas_cost(fork) - stipend, + FLAG_SLOT: 1, + }, + balance=0, + ), + # The transferred value on the 1wei case proves the CALL executed. + created: Account( + nonce=1, code=b"", balance=call_value if calls_created else 0 + ), + } + if absent is not None: + # A value-less call neither creates the account nor touches it. + post[absent] = Account.NONEXISTENT + + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stCreateTest/test_create_empty_contract_with_storage.py b/tests/ported_static/stCreateTest/test_create_empty_contract_with_storage.py index 9ca299282ef..1f406840b63 100644 --- a/tests/ported_static/stCreateTest/test_create_empty_contract_with_storage.py +++ b/tests/ported_static/stCreateTest/test_create_empty_contract_with_storage.py @@ -1,18 +1,29 @@ """ -Test_create_empty_contract_with_storage. +Measure CREATE of a codeless-but-storage-writing contract, and optionally +a following CALL to it, via CodeGasMeasure. + +The init code writes the created account's own storage and calls a +storage-writer contract, then deposits no code: the result is an "empty" +(codeless) account with storage and nonce 1. Ported from: state_tests/stCreateTest/CREATE_EmptyContractWithStorageFiller.json +state_tests/stCreateTest/CREATE_EmptyContractWithStorageAndCallIt_0weiFiller.json +state_tests/stCreateTest/CREATE_EmptyContractWithStorageAndCallIt_1weiFiller.json + +@manually-enhanced: Do not overwrite. Three fillers folded into one +parametrize; the init code is composed (not hex blobs) so the measured +CREATE/CALL expectations derive from the same bytecode; the init code's +inner CALL forwards all gas (the ported 0xEA60 budget OOGs under +EIP-8037); the CALL success flag stays inside the measured window. """ import pytest from execution_testing import ( - EOA, Account, - Address, Alloc, - Bytes, - Environment, + CodeGasMeasure, + Fork, StateTestFiller, Transaction, compute_create_address, @@ -22,78 +33,169 @@ REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +ADDRESS_SLOT = 0x1 +CREATE_GAS_SLOT = 0x2 +CALL_FLAG_SLOT = 0x3 +CALL_GAS_SLOT = 0x64 +STORED_VALUE = 0xC + +FORWARDED_GAS = 0xEA60 + @pytest.mark.ported_from( - ["state_tests/stCreateTest/CREATE_EmptyContractWithStorageFiller.json"], + [ + "state_tests/stCreateTest/CREATE_EmptyContractWithStorageFiller.json", + "state_tests/stCreateTest/CREATE_EmptyContractWithStorageAndCallIt_0weiFiller.json", # noqa: E501 + "state_tests/stCreateTest/CREATE_EmptyContractWithStorageAndCallIt_1weiFiller.json", # noqa: E501 + ], +) +@pytest.mark.valid_from("Berlin") +@pytest.mark.parametrize( + "call_created, call_value", + [ + pytest.param(False, 0, id="with_storage"), + pytest.param(True, 0, id="and_call_it_0wei"), + pytest.param(True, 1, id="and_call_it_1wei"), + ], ) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable def test_create_empty_contract_with_storage( state_test: StateTestFiller, pre: Alloc, + fork: Fork, + call_created: bool, + call_value: int, ) -> None: - """Test_create_empty_contract_with_storage.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B) - contract_1 = Address(0xC94F5374FCE5EDBC8E2A8697C15331677E6EBF0B) - sender = EOA( - key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8 + """Measure CREATE (and optionally CALL) gas for a storage-only account.""" + # Called by the init code below; writes one cold fresh slot. + writer_store = Op.SSTORE( + key=0x1, + value=STORED_VALUE, + key_warm=False, + original_value=0, + new_value=STORED_VALUE, ) + writer = pre.deploy_contract(code=writer_store + Op.STOP) - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, + # The init code writes the created account's own slot 0 and calls the + # writer, then runs off its end (STOP) so no code is deposited. The + # inner CALL forwards all remaining gas (default Op.GAS operand). + initcode = Op.SSTORE( + key=0x0, + value=STORED_VALUE, + key_warm=False, + original_value=0, + new_value=STORED_VALUE, + ) + Op.CALL( + address=writer, + address_warm=False, + value_transfer=False, + account_new=False, ) + initcode_bytes = bytes(initcode) + assert len(initcode_bytes) <= 0x40, "init code must fit two MSTORE words" - pre[sender] = Account(balance=0xE8D4A51000) - # Source: lll - # { [[0]](GAS) (MSTORE 0 0x600c6000556000600060006000600073c94f5374fce5edbc8e2a8697c1533167) (MSTORE 32 0x7e6ebf0b61ea60f1000000000000000000000000000000000000000000000000) [[1]] (CREATE 0 0 64) [[100]] (GAS) } # noqa: E501 - contract_0 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=Op.GAS) - + Op.MSTORE( - offset=0x0, - value=0x600C6000556000600060006000600073C94F5374FCE5EDBC8E2A8697C1533167, # noqa: E501 - ) - + Op.MSTORE( - offset=0x20, - value=0x7E6EBF0B61EA60F1000000000000000000000000000000000000000000000000, # noqa: E501 - ) - + Op.SSTORE(key=0x1, value=Op.CREATE(value=0x0, offset=0x0, size=0x40)) - + Op.SSTORE(key=0x64, value=Op.GAS) - + Op.STOP, - nonce=0, - address=Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B), # noqa: E501 + # Memory is populated (and expanded to 0x40) before the measured + # window, so the CREATE itself expands nothing. + setup = Op.MSTORE( + offset=0x0, + value=int.from_bytes( + initcode_bytes[:0x20].ljust(0x20, b"\x00"), "big" + ), + ) + Op.MSTORE( + offset=0x20, + value=int.from_bytes( + initcode_bytes[0x20:].ljust(0x20, b"\x00"), "big" + ), + ) + + create_code = Op.CREATE( + value=0x0, + offset=0x0, + size=len(initcode_bytes), + new_memory_size=0x40, + old_memory_size=0x40, + init_code_size=len(initcode_bytes), ) - # Source: lll - # {[[1]]12} - contract_1 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x1, value=0xC) + Op.STOP, - balance=0xE8D4A51000, - nonce=0, - address=Address(0xC94F5374FCE5EDBC8E2A8697C15331677E6EBF0B), # noqa: E501 + # The created address is stored inside the measured window (as in the + # ported filler) so the optional CALL can target it at runtime. + create_store = Op.SSTORE( + key=ADDRESS_SLOT, + value=create_code, + key_warm=False, + original_value=0, + new_value=1, ) + # The created account exists (nonce 1) and is warm (CREATE accessed + # it); the CALL success flag is stored inside the measured window — a + # wrongly failed call would otherwise be unobservable for the 0wei arm. + call_code = Op.CALL( + gas=FORWARDED_GAS, + address=Op.SLOAD(key=ADDRESS_SLOT, key_warm=True), + value=call_value, + address_warm=True, + value_transfer=call_value > 0, + account_new=False, + ) + call_store = Op.SSTORE( + key=CALL_FLAG_SLOT, + value=call_code, + key_warm=False, + original_value=0, + new_value=1, + ) + + code = setup + CodeGasMeasure( + code=create_store, + extra_stack_items=0, + sstore_key=CREATE_GAS_SLOT, + ) + if call_created: + code += CodeGasMeasure( + code=call_store, + extra_stack_items=0, + sstore_key=CALL_GAS_SLOT, + ) + contract = pre.deploy_contract(code=code, balance=call_value) + tx = Transaction( - sender=sender, - to=contract_0, - data=Bytes(""), - gas_limit=600000, + sender=pre.fund_eoa(), + to=contract, + state_gas_reservoir=0, + ) + + # The measured CREATE includes the child's work: the init code's own + # consumption plus the writer's store it calls. + measured_create = ( + create_store.gas_cost(fork) + + initcode.gas_cost(fork) + + writer_store.gas_cost(fork) ) + # A value-bearing CALL whose codeless callee consumes nothing measures + # gas_cost minus the stipend (forwarded then returned unused). + stipend = fork.gas_costs().CALL_STIPEND if call_value else 0 + measured_call = call_store.gas_cost(fork) - stipend + + created = compute_create_address(address=contract, nonce=1) + contract_storage: dict = { + ADDRESS_SLOT: created, + CREATE_GAS_SLOT: measured_create, + } + if call_created: + contract_storage[CALL_FLAG_SLOT] = 1 + contract_storage[CALL_GAS_SLOT] = measured_call post = { - contract_0: Account( - storage={ - 0: 0x8D5B6, - 1: compute_create_address(address=contract_0, nonce=0), - 100: 0x6F4F0, - }, + contract: Account(storage=contract_storage, balance=0), + # Codeless, but with storage and (for the 1wei arm) the value the + # measured CALL transferred — proving both the init code and the + # CALL executed. + created: Account( + nonce=1, + balance=call_value if call_created else 0, + storage={0: STORED_VALUE}, ), - compute_create_address(address=contract_0, nonce=0): Account(nonce=1), - contract_1: Account(storage={1: 12}), + writer: Account(storage={1: STORED_VALUE}), } - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stCreateTest/test_create_empty_contract_with_storage_and_call_it_0wei.py b/tests/ported_static/stCreateTest/test_create_empty_contract_with_storage_and_call_it_0wei.py deleted file mode 100644 index d7940716427..00000000000 --- a/tests/ported_static/stCreateTest/test_create_empty_contract_with_storage_and_call_it_0wei.py +++ /dev/null @@ -1,112 +0,0 @@ -""" -Test_create_empty_contract_with_storage_and_call_it_0wei. - -Ported from: -state_tests/stCreateTest/CREATE_EmptyContractWithStorageAndCallIt_0weiFiller.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, - compute_create_address, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - [ - "state_tests/stCreateTest/CREATE_EmptyContractWithStorageAndCallIt_0weiFiller.json" # noqa: E501 - ], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_create_empty_contract_with_storage_and_call_it_0wei( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_create_empty_contract_with_storage_and_call_it_0wei.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B) - contract_1 = Address(0xC94F5374FCE5EDBC8E2A8697C15331677E6EBF0B) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - # Source: lll - # { [[0]](GAS) (MSTORE 0 0x600c6000556000600060006000600073c94f5374fce5edbc8e2a8697c1533167) (MSTORE 32 0x7e6ebf0b61ea60f1000000000000000000000000000000000000000000000000) [[1]] (CREATE 0 0 64) [[2]] (GAS) [[3]] (CALL 60000 (SLOAD 1) 0 0 0 0 0) [[100]] (GAS) } # noqa: E501 - contract_0 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=Op.GAS) - + Op.MSTORE( - offset=0x0, - value=0x600C6000556000600060006000600073C94F5374FCE5EDBC8E2A8697C1533167, # noqa: E501 - ) - + Op.MSTORE( - offset=0x20, - value=0x7E6EBF0B61EA60F1000000000000000000000000000000000000000000000000, # noqa: E501 - ) - + Op.SSTORE(key=0x1, value=Op.CREATE(value=0x0, offset=0x0, size=0x40)) - + Op.SSTORE(key=0x2, value=Op.GAS) - + Op.SSTORE( - key=0x3, - value=Op.CALL( - gas=0xEA60, - address=Op.SLOAD(key=0x1), - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.SSTORE(key=0x64, value=Op.GAS) - + Op.STOP, - nonce=0, - address=Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B), # noqa: E501 - ) - # Source: lll - # {[[1]]12} - contract_1 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x1, value=0xC) + Op.STOP, - balance=0xE8D4A51000, - nonce=0, - address=Address(0xC94F5374FCE5EDBC8E2A8697C15331677E6EBF0B), # noqa: E501 - ) - - tx = Transaction( - sender=sender, - to=contract_0, - data=Bytes(""), - gas_limit=600000, - ) - - post = { - contract_0: Account( - storage={ - 0: 0x8D5B6, - 1: compute_create_address(address=contract_0, nonce=0), - 2: 0x6F4F0, - 3: 1, - 100: 0x64763, - }, - ), - compute_create_address(address=contract_0, nonce=0): Account(nonce=1), - contract_1: Account(storage={1: 12}), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stCreateTest/test_create_empty_contract_with_storage_and_call_it_1wei.py b/tests/ported_static/stCreateTest/test_create_empty_contract_with_storage_and_call_it_1wei.py deleted file mode 100644 index cbd15afeba3..00000000000 --- a/tests/ported_static/stCreateTest/test_create_empty_contract_with_storage_and_call_it_1wei.py +++ /dev/null @@ -1,115 +0,0 @@ -""" -Test_create_empty_contract_with_storage_and_call_it_1wei. - -Ported from: -state_tests/stCreateTest/CREATE_EmptyContractWithStorageAndCallIt_1weiFiller.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, - compute_create_address, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - [ - "state_tests/stCreateTest/CREATE_EmptyContractWithStorageAndCallIt_1weiFiller.json" # noqa: E501 - ], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_create_empty_contract_with_storage_and_call_it_1wei( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_create_empty_contract_with_storage_and_call_it_1wei.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B) - contract_1 = Address(0xC94F5374FCE5EDBC8E2A8697C15331677E6EBF0B) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - # Source: lll - # { [[0]](GAS) (MSTORE 0 0x600c6000556000600060006000600073c94f5374fce5edbc8e2a8697c1533167) (MSTORE 32 0x7e6ebf0b61ea60f1000000000000000000000000000000000000000000000000) [[1]] (CREATE 0 0 64) [[2]] (GAS) [[3]] (CALL 60000 (SLOAD 1) 1 0 0 0 0) [[100]] (GAS) } # noqa: E501 - contract_0 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=Op.GAS) - + Op.MSTORE( - offset=0x0, - value=0x600C6000556000600060006000600073C94F5374FCE5EDBC8E2A8697C1533167, # noqa: E501 - ) - + Op.MSTORE( - offset=0x20, - value=0x7E6EBF0B61EA60F1000000000000000000000000000000000000000000000000, # noqa: E501 - ) - + Op.SSTORE(key=0x1, value=Op.CREATE(value=0x0, offset=0x0, size=0x40)) - + Op.SSTORE(key=0x2, value=Op.GAS) - + Op.SSTORE( - key=0x3, - value=Op.CALL( - gas=0xEA60, - address=Op.SLOAD(key=0x1), - value=0x1, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.SSTORE(key=0x64, value=Op.GAS) - + Op.STOP, - balance=1, - nonce=0, - address=Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B), # noqa: E501 - ) - # Source: lll - # {[[1]]12} - contract_1 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x1, value=0xC) + Op.STOP, - balance=0xE8D4A51000, - nonce=0, - address=Address(0xC94F5374FCE5EDBC8E2A8697C15331677E6EBF0B), # noqa: E501 - ) - - tx = Transaction( - sender=sender, - to=contract_0, - data=Bytes(""), - gas_limit=600000, - ) - - post = { - contract_0: Account( - storage={ - 0: 0x8D5B6, - 1: compute_create_address(address=contract_0, nonce=0), - 2: 0x6F4F0, - 3: 1, - 100: 0x62D37, - }, - ), - compute_create_address(address=contract_0, nonce=0): Account( - storage={0: 12}, balance=1 - ), - contract_1: Account(storage={1: 12}), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stCreateTest/test_create_oo_gafter_init_code_returndata_size.py b/tests/ported_static/stCreateTest/test_create_oo_gafter_init_code_returndata_size.py deleted file mode 100644 index 6f458740e9d..00000000000 --- a/tests/ported_static/stCreateTest/test_create_oo_gafter_init_code_returndata_size.py +++ /dev/null @@ -1,77 +0,0 @@ -""" -Calls a contract that runs CREATE which deploy a code. then OOG happens... - -Ported from: -state_tests/stCreateTest/CreateOOGafterInitCodeReturndataSizeFiller.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, - compute_create_address, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - [ - "state_tests/stCreateTest/CreateOOGafterInitCodeReturndataSizeFiller.json" # noqa: E501 - ], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_create_oo_gafter_init_code_returndata_size( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Calls a contract that runs CREATE which deploy a code.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - # Source: lll - # { (MSTORE 0 0x6960016001556001600255600052600a6016f3) (CREATE 0 13 19) (EXP 2 (RETURNDATASIZE)) } # noqa: E501 - contract_0 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE( - offset=0x0, value=0x6960016001556001600255600052600A6016F3 - ) - + Op.POP(Op.CREATE(value=0x0, offset=0xD, size=0x13)) - + Op.EXP(0x2, Op.RETURNDATASIZE) - + Op.STOP, - nonce=0, - ) - - tx = Transaction( - sender=sender, - to=contract_0, - data=Bytes(""), - gas_limit=55054, - value=1, - ) - - post = { - contract_0: Account(balance=1), - compute_create_address( - address=contract_0, nonce=0 - ): Account.NONEXISTENT, - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stCreateTest/test_create_oog_after_init_code_returndata_size.py b/tests/ported_static/stCreateTest/test_create_oog_after_init_code_returndata_size.py new file mode 100644 index 00000000000..a7f6b825eb1 --- /dev/null +++ b/tests/ported_static/stCreateTest/test_create_oog_after_init_code_returndata_size.py @@ -0,0 +1,157 @@ +""" +Verify that a CREATE whose child runs its init code to completion but cannot +afford the code deposit leaves no return data behind: the creation fails, no +account is deployed, and RETURNDATASIZE in the parent frame reads zero. + +Ported from: +state_tests/stCreateTest/CreateOOGafterInitCodeReturndataSizeFiller.json + +@manually-enhanced: Do not overwrite. The filler probed RETURNDATASIZE +indirectly, through the extra gas an `EXP` with a non-zero exponent costs; +the parent now reports the value to a calling frame that stores it, so the +observation is asserted directly. The budget is derived from the fork, +working backwards from the report the parent must afford out of its 1/64. +""" + +import pytest +from execution_testing import ( + Account, + Alloc, + Fork, + StateTestFiller, + Transaction, + compute_create_address, +) +from execution_testing.vm import Op + +REFERENCE_SPEC_GIT_PATH = "N/A" +REFERENCE_SPEC_VERSION = "N/A" + +RETURN_DATA_SIZE_SLOT = 0x1 +CALL_STATUS_SLOT = 0x2 + + +@pytest.mark.ported_from( + [ + "state_tests/stCreateTest/CreateOOGafterInitCodeReturndataSizeFiller.json" # noqa: E501 + ], +) +@pytest.mark.valid_from("Berlin") +def test_create_oog_after_init_code_returndata_size( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, +) -> None: + """CREATE fails at the code deposit; no return data is produced.""" + # The child returns this many zero bytes out of memory it never wrote to. + # Only the size matters: depositing them is what it cannot afford, and + # `code_deposit_size` folds that charge into RETURN's own gas cost, so the + # boundary below comes from the fork's model with no EIP branch. + deployed_code_size = 10 + child_initcode = Op.RETURN( + offset=0x0, + size=deployed_code_size, + new_memory_size=deployed_code_size, + code_deposit_size=deployed_code_size, + ) + initcode_bytes = bytes(child_initcode) + + # Parent: stage the init code in memory, CREATE from it, then report + # RETURNDATASIZE to its caller. + stage_code = Op.MSTORE( + offset=0x0, + value=int.from_bytes(initcode_bytes, "big"), + new_memory_size=0x20, + ) + create_code = Op.CREATE( + value=0x0, + offset=32 - len(initcode_bytes), + size=len(initcode_bytes), + new_memory_size=0x20, + old_memory_size=0x20, + init_code_size=len(initcode_bytes), + ) + # Returning the observation rather than storing it is what makes this + # test possible at all: the parent runs this on the 1/64 CREATE leaves + # it, and no SSTORE fits there -- EIP-2200 halts any SSTORE outright + # below 2300 gas, which would force a budget far past the deposit + # boundary the test exists to probe. + report_code = ( + Op.POP + + Op.MSTORE( + offset=0x0, + value=Op.RETURNDATASIZE, + new_memory_size=0x20, + old_memory_size=0x20, + ) + + Op.RETURN( + offset=0x0, + size=0x20, + new_memory_size=0x20, + old_memory_size=0x20, + ) + ) + parent = pre.deploy_contract(code=stage_code + create_code + report_code) + + # Work backwards from the report, which the parent pays for out of the + # 1/64 it retains: that fixes the gas CREATE is reached with, and the + # 63/64 the child is granted follows. + retained = report_code.gas_cost(fork) + available = retained * 64 + granted = available - available // 64 + assert available // 64 >= retained, ( + "retention must cover the parent's report" + ) + # The grant has to run the init code to completion, then fall short of the + # deposit that its RETURN triggers. A bare RETURN carrying only the deposit + # metadata prices that charge on its own. + child_cost = child_initcode.gas_cost(fork) + deposit_cost = Op.RETURN(code_deposit_size=deployed_code_size).gas_cost( + fork + ) + assert child_cost - deposit_cost <= granted < child_cost, ( + "grant must cover the child's init execution but not the deposit" + ) + + parent_budget = ( + stage_code.gas_cost(fork) + create_code.gas_cost(fork) + available + ) + + # The caller has gas to spare, so it can afford to store what the parent + # reports. Offsetting by one keeps a zero reading distinguishable from a + # parent that never returned anything. + entry = pre.deploy_contract( + code=Op.SSTORE( + CALL_STATUS_SLOT, + Op.CALL( + gas=parent_budget, + address=parent, + ret_offset=0x0, + ret_size=0x20, + ), + ) + + Op.SSTORE(RETURN_DATA_SIZE_SLOT, Op.ADD(Op.MLOAD(0x0), 1)) + ) + + tx = Transaction( + sender=pre.fund_eoa(), + to=entry, + # Load-bearing under EIP-8037: with a reservoir the deposit's state + # gas is paid out of it rather than out of the child's grant, the + # deposit succeeds, and the contract deploys after all. + state_gas_reservoir=0, + ) + + post = { + entry: Account( + storage={ + # The parent frame ran to completion on its retention. + CALL_STATUS_SLOT: 1, + # A failed code deposit produces no return data. + RETURN_DATA_SIZE_SLOT: 1, + } + ), + compute_create_address(address=parent, nonce=1): Account.NONEXISTENT, + } + + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stCreateTest/test_create_oog_from_call_refunds.py b/tests/ported_static/stCreateTest/test_create_oog_from_call_refunds.py index 2f7088ae13d..cd7bec0fb9d 100644 --- a/tests/ported_static/stCreateTest/test_create_oog_from_call_refunds.py +++ b/tests/ported_static/stCreateTest/test_create_oog_from_call_refunds.py @@ -1,30 +1,28 @@ """ -Test_create_oog_from_call_refunds. +Verify that gas refunds earned inside a CREATE frame are discarded when the +deployment fails. Ported from: state_tests/stCreateTest/CreateOOGFromCallRefundsFiller.yml + +@manually-enhanced: Do not overwrite. Restored SSTORE pairs solc had +optimized away, and parametrized on refund source x deployment outcome. """ import pytest from execution_testing import ( - EOA, Account, - Address, Alloc, - Bytes, - Environment, + Bytecode, Hash, StateTestFiller, Transaction, + TransactionReceipt, compute_create_address, ) from execution_testing.forks import Fork from execution_testing.vm import Op -from tests.ported_static.post_state_resolution import ( - resolve_expect_post, -) - REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" @@ -34,1104 +32,227 @@ ) @pytest.mark.valid_from("Cancun") @pytest.mark.parametrize( - "d, g, v", + "deploy_outcome", ["created", "code_deposit_oog", "invalid_opcode"] +) +@pytest.mark.parametrize( + "refund_source", [ - pytest.param( - 0, - 0, - 0, - id="SStore_Refund_NoOoG", - ), - pytest.param( - 1, - 0, - 0, - id="SStore_Refund_OoG", - ), - pytest.param( - 2, - 0, - 0, - id="SStore_Refund_OoG", - ), - pytest.param( - 3, - 0, - 0, - id="SStore_Refund_NoOoG", - ), - pytest.param( - 4, - 0, - 0, - id="SStore_Refund_OoG", - ), - pytest.param( - 5, - 0, - 0, - id="SStore_Refund_OoG", - ), - pytest.param( - 6, - 0, - 0, - id="SStore_Refund_NoOoG", - ), - pytest.param( - 7, - 0, - 0, - id="SStore_Refund_OoG", - ), - pytest.param( - 8, - 0, - 0, - id="SStore_Refund_OoG", - ), - pytest.param( - 9, - 0, - 0, - id="SStore_Refund_NoOoG", - ), - pytest.param( - 10, - 0, - 0, - id="SStore_Refund_OoG", - ), - pytest.param( - 11, - 0, - 0, - id="SStore_Refund_OoG", - ), - pytest.param( - 12, - 0, - 0, - id="SelfDestruct_Refund_NoOoG", - ), - pytest.param( - 13, - 0, - 0, - id="SelfDestruct_Refund_OoG", - ), - pytest.param( - 14, - 0, - 0, - id="SelfDestruct_Refund_OoG", - ), - pytest.param( - 15, - 0, - 0, - id="LogOp_NoOoG", - ), - pytest.param( - 16, - 0, - 0, - id="LogOp_OoG", - ), - pytest.param( - 17, - 0, - 0, - id="LogOp_OoG", - ), - pytest.param( - 18, - 0, - 0, - id="SStore_Create_Refund_NoOoG", - ), - pytest.param( - 19, - 0, - 0, - id="SStore_Create_Refund_OoG", - ), - pytest.param( - 20, - 0, - 0, - id="SStore_Create_Refund_OoG", - ), - pytest.param( - 21, - 0, - 0, - id="SStore_Create2_Refund_NoOoG", - ), - pytest.param( - 22, - 0, - 0, - id="SStore_Create2_Refund_OoG", - ), - pytest.param( - 23, - 0, - 0, - id="SStore_Create2_Refund_OoG", - ), + "sstore", + "call", + "delegatecall", + "callcode", + "selfdestruct", + "log", + "create", + "create2", ], ) -@pytest.mark.pre_alloc_mutable def test_create_oog_from_call_refunds( state_test: StateTestFiller, pre: Alloc, fork: Fork, - d: int, - g: int, - v: int, + refund_source: str, + deploy_outcome: str, ) -> None: - """Test_create_oog_from_call_refunds.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA) - contract_1 = Address(0x000000000000000000000000000000000000001A) - contract_2 = Address(0x000000000000000000000000000000000000001B) - contract_3 = Address(0x000000000000000000000000000000000000001C) - contract_4 = Address(0x000000000000000000000000000000000000002A) - contract_5 = Address(0x000000000000000000000000000000000000002B) - contract_6 = Address(0x000000000000000000000000000000000000002C) - contract_7 = Address(0x000000000000000000000000000000000000003A) - contract_8 = Address(0x000000000000000000000000000000000000003B) - contract_9 = Address(0x000000000000000000000000000000000000003C) - contract_10 = Address(0x000000000000000000000000000000000000004A) - contract_11 = Address(0x000000000000000000000000000000000000004B) - contract_12 = Address(0x000000000000000000000000000000000000004C) - contract_13 = Address(0x000000000000000000000000000000000000005A) - contract_14 = Address(0x000000000000000000000000000000000000005B) - contract_15 = Address(0x000000000000000000000000000000000000005C) - contract_16 = Address(0x000000000000000000000000000000000000006A) - contract_17 = Address(0x000000000000000000000000000000000000006B) - contract_18 = Address(0x000000000000000000000000000000000000006C) - contract_19 = Address(0x000000000000000000000000000000000000007A) - contract_20 = Address(0x000000000000000000000000000000000000007B) - contract_21 = Address(0x000000000000000000000000000000000000007C) - contract_22 = Address(0x000000000000000000000000000000000000008A) - contract_23 = Address(0x000000000000000000000000000000000000008B) - contract_24 = Address(0x000000000000000000000000000000000000008C) - contract_25 = Address(0x00000000000000000000000000000000000C0DEA) - contract_26 = Address(0x00000000000000000000000000000000000C0DED) - contract_27 = Address(0x00000000000000000000000000000000000C0DE0) - contract_28 = Address(0x00000000000000000000000000000000000C0DE1) - sender = EOA( - key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8 - ) + """ + Verify that a gas refund earned inside a CREATE frame only survives if + the deployment does. - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - ) + `refund_source` picks how the init code earns the refund; `deploy_outcome` + picks whether that frame commits (`created`) or is discarded, by failing + the code deposit (`code_deposit_oog`) or hitting `INVALID` + (`invalid_opcode`). Both failures burn the whole gas allowance, so the + receipt must charge the full limit -- a refund that wrongly survived + would show up as a discount. + """ + # More code than the budget can afford to deposit. + oversized_code_size = 5000 + tx_gas_limit = 800_000 + # High enough for the successful cases (EIP-8037 charges heavy state gas + # per zero -> non-zero SSTORE and per created account), low enough that + # the oversized deposit still cannot be paid for. + assert ( + tx_gas_limit + < oversized_code_size * fork.gas_costs().CODE_DEPOSIT_PER_BYTE + ), "budget covers the oversized code deposit; OOG cases would succeed" - pre[sender] = Account(balance=0x3D0900, nonce=1) - # Source: yul - # berlin - # { - # let init_addr := calldataload(4) - # let init_length := extcodesize(init_addr) - # extcodecopy(init_addr, 0, 0, init_length) - # let created_addr := create(0, 0, init_length) - # if eq(created_addr, 0) { - # /* This invalid will deplete the remaining gas to make refund check deterministic */ # noqa: E501 - # invalid() - # } - # } - contract_0 = pre.deploy_contract( # noqa: F841 - code=Op.PUSH1[0x0] - + Op.DUP1 - + Op.CALLDATALOAD(offset=0x4) - + Op.DUP2 - + Op.EXTCODESIZE(address=Op.DUP2) - + Op.SWAP3 - + Op.DUP4 - + Op.SWAP3 - + Op.EXTCODECOPY - + Op.DUP2 - + Op.JUMPI(pc=0x15, condition=Op.EQ(Op.CREATE, Op.DUP1)) - + Op.STOP - + Op.JUMPDEST - + Op.INVALID, - nonce=1, - address=Address(0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA), # noqa: E501 - ) - # Source: yul - # berlin - # { - # sstore(0, 1) - # sstore(1, 1) - # sstore(1, 0) - # return(0, 1) - # } - contract_1 = pre.deploy_contract( # noqa: F841 - code=Op.PUSH1[0x1] - + Op.PUSH1[0x0] - + Op.SSTORE(key=Op.DUP2, value=Op.DUP2) - + Op.SSTORE(key=Op.DUP3, value=Op.DUP1) - + Op.RETURN, - nonce=0, - address=Address(0x000000000000000000000000000000000000001A), # noqa: E501 - ) - # Source: yul - # berlin - # { - # sstore(0, 1) - # sstore(1, 1) - # sstore(1, 0) - # return(0, 5000) - # } - contract_2 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.SSTORE(key=Op.DUP1, value=0x1) - + Op.SSTORE(key=0x1, value=0x0) - + Op.RETURN(offset=0x0, size=0x1388), - nonce=0, - address=Address(0x000000000000000000000000000000000000001B), # noqa: E501 - ) - # Source: yul - # berlin - # { - # sstore(0, 1) - # sstore(1, 1) - # sstore(1, 0) - # invalid() - # } - contract_3 = pre.deploy_contract( # noqa: F841 - code=Op.PUSH1[0x1] - + Op.PUSH1[0x0] - + Op.SSTORE(key=Op.DUP2, value=Op.DUP2) - + Op.SWAP1 - + Op.SSTORE - + Op.INVALID, - nonce=0, - address=Address(0x000000000000000000000000000000000000001C), # noqa: E501 - ) - # Source: yul - # berlin - # { - # sstore(0, 1) - # sstore(1, 1) - # sstore(1, 0) - # let initcodeaddr := 0x00000000000000000000000000000000000c0de1 - # //let initcodelength := extcodesize(initcodeaddr) - # //extcodecopy(initcodeaddr, 0, 0, initcodelength) - # - # // protection from solc version changing init code - # let initcodelength := 15 - # mstore(0, 0x6001600055600060005560016000f30000000000000000000000000000000000) # noqa: E501 - # - # pop(create2(0, 0, initcodelength, 0)) - # return(add(initcodelength, 1), 1) - # } - contract_22 = pre.deploy_contract( # noqa: F841 - code=Op.PUSH1[0x1] - + Op.PUSH1[0x0] - + Op.SSTORE(key=Op.DUP2, value=Op.DUP2) - + Op.SSTORE(key=Op.DUP3, value=Op.DUP1) - + Op.MSTORE( - offset=Op.DUP2, - value=0x6001600055600060005560016000F30000000000000000000000000000000000, # noqa: E501 + sender = pre.fund_eoa() + + # Deploys the calldata as init code, burning all remaining gas on a + # trailing INVALID if that fails, which pins the gas charged on failure + # to the whole limit and makes any surviving refund observable. + factory_contract = pre.deploy_contract( + code=( + Op.CALLDATACOPY(dest_offset=0, offset=0, size=Op.CALLDATASIZE) + + Op.JUMPI( + 19, + Op.EQ(0, Op.CREATE(value=0, offset=0, size=Op.CALLDATASIZE)), + ) + + Op.STOP + + Op.JUMPDEST + + Op.INVALID ) - + Op.DUP2 - + Op.SWAP1 - + Op.PUSH1[0xF] - + Op.SWAP1 - + Op.DUP2 * 2 - + Op.DUP1 - + Op.POP(Op.CREATE2) - + Op.ADD - + Op.RETURN, - nonce=0, - address=Address(0x000000000000000000000000000000000000008A), # noqa: E501 ) - # Source: yul - # berlin - # { - # // Simple SSTORE to zero to get a refund - # sstore(1, 0) - # } - contract_25 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x1, value=0x0) + Op.STOP, - storage={1: 1}, - nonce=1, - address=Address(0x00000000000000000000000000000000000C0DEA), # noqa: E501 + created_contract = compute_create_address( + address=factory_contract, nonce=1 ) - # Source: yul - # berlin - # { - # selfdestruct(origin()) - # } - contract_26 = pre.deploy_contract( # noqa: F841 - code=Op.SELFDESTRUCT(address=Op.ORIGIN), - storage={1: 1}, - nonce=1, - address=Address(0x00000000000000000000000000000000000C0DED), # noqa: E501 - ) - # Source: yul - # berlin - # { - # mstore(0, 0xff) - # log0(0, 32) - # log1(0, 32, 0xfa) - # log2(0, 32, 0xfa, 0xfb) - # log3(0, 32, 0xfa, 0xfb, 0xfc) - # log4(0, 32, 0xfa, 0xfb, 0xfc, 0xfd) - # } - contract_27 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=0xFF) - + Op.LOG0(offset=0x0, size=0x20) - + Op.LOG1(offset=0x0, size=0x20, topic_1=0xFA) - + Op.LOG2(offset=0x0, size=0x20, topic_1=0xFA, topic_2=0xFB) - + Op.LOG3( - offset=0x0, size=0x20, topic_1=0xFA, topic_2=0xFB, topic_3=0xFC + + # Every init code returns this one byte, read from untouched memory. + deployed_code = Op.STOP + deploy_succeeds = deploy_outcome == "created" + + # Clears an already-set slot to earn a refund; which account's slot it + # hits depends on the call opcode used below. + storage_clearing_code = Op.SSTORE(key=0x1, value=0x0) + Op.STOP + + # Where the returned zero byte comes from; only nested creates use memory. + return_offset = 0 + # Post-state entries specific to the refund source. + extra_post: dict = {} + refund_code: Bytecode + + if refund_source == "sstore": + # Both slot 1 writes matter: alone, the clear is a no-op on a fresh + # account and earns nothing. solc folded the pair away when ported. + refund_code = ( + Op.SSTORE(key=0, value=1) + + Op.SSTORE(key=1, value=1) + + Op.SSTORE(key=1, value=0) ) - + Op.LOG4( - offset=0x0, - size=0x20, - topic_1=0xFA, - topic_2=0xFB, - topic_3=0xFC, - topic_4=0xFD, + elif refund_source == "call": + # Refund earned one frame below, in the callee's own storage. + storage_clearing_contract = pre.deploy_contract( + code=storage_clearing_code, storage={1: 1} ) - + Op.STOP, - storage={1: 1}, - nonce=1, - address=Address(0x00000000000000000000000000000000000C0DE0), # noqa: E501 - ) - # Source: yul - # berlin - # { - # sstore(0, 1) - # sstore(0, 0) - # return(0, 1) - # } - contract_28 = pre.deploy_contract( # noqa: F841 - code=Op.PUSH1[0x0] - + Op.SSTORE(key=Op.DUP1, value=Op.DUP1) - + Op.PUSH1[0x1] - + Op.SWAP1 - + Op.RETURN, - nonce=1, - address=Address(0x00000000000000000000000000000000000C0DE1), # noqa: E501 - ) - # Source: yul - # berlin - # { - # sstore(0, 1) - # pop(call(gas(), 0x00000000000000000000000000000000000c0deA, 0, 0, 0, 0, 0)) # noqa: E501 - # return(0, 1) - # } - contract_4 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.CALL( - gas=Op.GAS, - address=contract_25, - value=Op.DUP1, - args_offset=Op.DUP1, - args_size=Op.DUP1, - ret_offset=Op.DUP1, - ret_size=0x0, + refund_code = Op.SSTORE(key=0, value=1) + Op.POP( + Op.CALL(address=storage_clearing_contract) ) - + Op.RETURN(offset=0x0, size=0x1), - nonce=0, - address=Address(0x000000000000000000000000000000000000002A), # noqa: E501 - ) - # Source: yul - # berlin - # { - # sstore(0, 1) - # pop(call(gas(), 0x00000000000000000000000000000000000c0deA, 0, 0, 0, 0, 0)) # noqa: E501 - # return(0, 5000) - # } - contract_5 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.CALL( - gas=Op.GAS, - address=contract_25, - value=Op.DUP1, - args_offset=Op.DUP1, - args_size=Op.DUP1, - ret_offset=Op.DUP1, - ret_size=0x0, + elif refund_source in ("delegatecall", "callcode"): + # Both run the callee against this frame's storage, so slot 1 must be + # set here for the callee's clear to earn anything. + storage_clearing_contract = pre.deploy_contract( + code=storage_clearing_code, storage={1: 1} ) - + Op.RETURN(offset=0x0, size=0x1388), - nonce=0, - address=Address(0x000000000000000000000000000000000000002B), # noqa: E501 - ) - # Source: yul - # berlin - # { - # sstore(0, 1) - # sstore(1, 1) - # pop(delegatecall(gas(), 0x00000000000000000000000000000000000c0deA, 0, 0, 0, 0)) # noqa: E501 - # invalid() - # } - contract_9 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.SSTORE(key=Op.DUP1, value=0x1) - + Op.POP( - Op.DELEGATECALL( - gas=Op.GAS, - address=contract_25, - args_offset=Op.DUP1, - args_size=Op.DUP1, - ret_offset=Op.DUP1, - ret_size=0x0, - ) + subcall = ( + Op.DELEGATECALL if refund_source == "delegatecall" else Op.CALLCODE ) - + Op.INVALID, - nonce=0, - address=Address(0x000000000000000000000000000000000000003C), # noqa: E501 - ) - # Source: yul - # berlin - # { - # sstore(0, 1) - # pop(call(gas(), 0x00000000000000000000000000000000000c0deA, 0, 0, 0, 0, 0)) # noqa: E501 - # invalid() - # } - contract_6 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.POP( - Op.CALL( - gas=Op.GAS, - address=contract_25, - value=Op.DUP1, - args_offset=Op.DUP1, - args_size=Op.DUP1, - ret_offset=Op.DUP1, - ret_size=0x0, - ) + refund_code = ( + Op.SSTORE(key=0, value=1) + + Op.SSTORE(key=1, value=1) + + Op.POP(subcall(address=storage_clearing_contract)) ) - + Op.INVALID, - nonce=0, - address=Address(0x000000000000000000000000000000000000002C), # noqa: E501 - ) - # Source: yul - # berlin - # { - # sstore(0, 1) - # sstore(1, 1) - # pop(delegatecall(gas(), 0x00000000000000000000000000000000000c0deA, 0, 0, 0, 0)) # noqa: E501 - # return(0, 1) - # } - contract_7 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.SSTORE(key=Op.DUP1, value=0x1) - + Op.DELEGATECALL( - gas=Op.GAS, - address=contract_25, - args_offset=Op.DUP1, - args_size=Op.DUP1, - ret_offset=Op.DUP1, - ret_size=0x0, + elif refund_source == "selfdestruct": + self_destructing_code = Op.SELFDESTRUCT(address=Op.ORIGIN) + self_destructing_contract = pre.deploy_contract( + code=self_destructing_code, storage={1: 1} ) - + Op.RETURN(offset=0x0, size=0x1), - nonce=0, - address=Address(0x000000000000000000000000000000000000003A), # noqa: E501 - ) - # Source: yul - # berlin - # { - # sstore(0, 1) - # sstore(1, 1) - # pop(callcode(gas(), 0x00000000000000000000000000000000000c0deA, 0, 0, 0, 0, 0)) # noqa: E501 - # return(0, 5000) - # } - contract_11 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.SSTORE(key=Op.DUP1, value=0x1) - + Op.CALLCODE( - gas=Op.GAS, - address=contract_25, - value=Op.DUP1, - args_offset=Op.DUP1, - args_size=Op.DUP1, - ret_offset=Op.DUP1, - ret_size=0x0, + refund_code = Op.SSTORE(key=0, value=1) + Op.POP( + Op.CALL(address=self_destructing_contract) ) - + Op.RETURN(offset=0x0, size=0x1388), - nonce=0, - address=Address(0x000000000000000000000000000000000000004B), # noqa: E501 - ) - # Source: yul - # berlin - # { - # sstore(0, 1) - # sstore(1, 1) - # pop(delegatecall(gas(), 0x00000000000000000000000000000000000c0deA, 0, 0, 0, 0)) # noqa: E501 - # return(0, 5000) - # } - contract_8 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.SSTORE(key=Op.DUP1, value=0x1) - + Op.DELEGATECALL( - gas=Op.GAS, - address=contract_25, - args_offset=Op.DUP1, - args_size=Op.DUP1, - ret_offset=Op.DUP1, - ret_size=0x0, + extra_post[self_destructing_contract] = ( + Account(balance=0, nonce=1) + if deploy_succeeds + else Account(storage={1: 1}, code=self_destructing_code, nonce=1) ) - + Op.RETURN(offset=0x0, size=0x1388), - nonce=0, - address=Address(0x000000000000000000000000000000000000003B), # noqa: E501 - ) - # Source: yul - # berlin - # { - # sstore(0, 1) - # sstore(1, 1) - # pop(callcode(gas(), 0x00000000000000000000000000000000000c0deA, 0, 0, 0, 0, 0)) # noqa: E501 - # return(0, 1) - # } - contract_10 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.SSTORE(key=Op.DUP1, value=0x1) - + Op.CALLCODE( - gas=Op.GAS, - address=contract_25, - value=Op.DUP1, - args_offset=Op.DUP1, - args_size=Op.DUP1, - ret_offset=Op.DUP1, - ret_size=0x0, - ) - + Op.RETURN(offset=0x0, size=0x1), - nonce=0, - address=Address(0x000000000000000000000000000000000000004A), # noqa: E501 - ) - # Source: yul - # berlin - # { - # sstore(0, 1) - # sstore(1, 1) - # pop(callcode(gas(), 0x00000000000000000000000000000000000c0deA, 0, 0, 0, 0, 0)) # noqa: E501 - # invalid() - # } - contract_12 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.SSTORE(key=Op.DUP1, value=0x1) - + Op.POP( - Op.CALLCODE( - gas=Op.GAS, - address=contract_25, - value=Op.DUP1, - args_offset=Op.DUP1, - args_size=Op.DUP1, - ret_offset=Op.DUP1, - ret_size=0x0, + elif refund_source == "log": + # Control case: logs only, no refund earned. + logging_contract = pre.deploy_contract( + code=Op.MSTORE(offset=0x0, value=0xFF) + + Op.LOG0(offset=0x0, size=0x20) + + Op.LOG1(offset=0x0, size=0x20, topic_1=0xFA) + + Op.LOG2(offset=0x0, size=0x20, topic_1=0xFA, topic_2=0xFB) + + Op.LOG3( + offset=0x0, + size=0x20, + topic_1=0xFA, + topic_2=0xFB, + topic_3=0xFC, ) - ) - + Op.INVALID, - nonce=0, - address=Address(0x000000000000000000000000000000000000004C), # noqa: E501 - ) - # Source: yul - # berlin - # { - # sstore(0, 1) - # pop(call(gas(), 0x00000000000000000000000000000000000c0deD, 0, 0, 0, 0, 0)) # noqa: E501 - # return(0, 5000) - # } - contract_14 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.CALL( - gas=Op.GAS, - address=contract_26, - value=Op.DUP1, - args_offset=Op.DUP1, - args_size=Op.DUP1, - ret_offset=Op.DUP1, - ret_size=0x0, - ) - + Op.RETURN(offset=0x0, size=0x1388), - nonce=0, - address=Address(0x000000000000000000000000000000000000005B), # noqa: E501 - ) - # Source: yul - # berlin - # { - # sstore(0, 1) - # pop(call(gas(), 0x00000000000000000000000000000000000c0deD, 0, 0, 0, 0, 0)) # noqa: E501 - # return(0, 1) - # } - contract_13 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.CALL( - gas=Op.GAS, - address=contract_26, - value=Op.DUP1, - args_offset=Op.DUP1, - args_size=Op.DUP1, - ret_offset=Op.DUP1, - ret_size=0x0, - ) - + Op.RETURN(offset=0x0, size=0x1), - nonce=0, - address=Address(0x000000000000000000000000000000000000005A), # noqa: E501 - ) - # Source: yul - # berlin - # { - # sstore(0, 1) - # pop(call(gas(), 0x00000000000000000000000000000000000c0deD, 0, 0, 0, 0, 0)) # noqa: E501 - # invalid() - # } - contract_15 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.POP( - Op.CALL( - gas=Op.GAS, - address=contract_26, - value=Op.DUP1, - args_offset=Op.DUP1, - args_size=Op.DUP1, - ret_offset=Op.DUP1, - ret_size=0x0, + + Op.LOG4( + offset=0x0, + size=0x20, + topic_1=0xFA, + topic_2=0xFB, + topic_3=0xFC, + topic_4=0xFD, ) + + Op.STOP, + storage={1: 1}, ) - + Op.INVALID, - nonce=0, - address=Address(0x000000000000000000000000000000000000005C), # noqa: E501 - ) - # Source: yul - # berlin - # { - # sstore(0, 1) - # pop(call(gas(), 0x00000000000000000000000000000000000c0de0, 0, 0, 0, 0, 0)) # noqa: E501 - # return(0, 1) - # } - contract_16 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.CALL( - gas=Op.GAS, - address=contract_27, - value=Op.DUP1, - args_offset=Op.DUP1, - args_size=Op.DUP1, - ret_offset=Op.DUP1, - ret_size=0x0, + refund_code = Op.SSTORE(key=0, value=1) + Op.POP( + Op.CALL(address=logging_contract) ) - + Op.RETURN(offset=0x0, size=0x1), - nonce=0, - address=Address(0x000000000000000000000000000000000000006A), # noqa: E501 - ) - # Source: yul - # berlin - # { - # sstore(0, 1) - # pop(call(gas(), 0x00000000000000000000000000000000000c0de0, 0, 0, 0, 0, 0)) # noqa: E501 - # return(0, 5000) - # } - contract_17 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.CALL( - gas=Op.GAS, - address=contract_27, - value=Op.DUP1, - args_offset=Op.DUP1, - args_size=Op.DUP1, - ret_offset=Op.DUP1, - ret_size=0x0, + else: + # Grandchild earns a refund of its own, so a failure has to discard + # two nested frames' worth at once. + child_init_code = ( + Op.SSTORE(key=0x0, value=0x1) + + Op.SSTORE(key=0x0, value=0x0) + + Op.RETURN(offset=0x0, size=0x1) ) - + Op.RETURN(offset=0x0, size=0x1388), - nonce=0, - address=Address(0x000000000000000000000000000000000000006B), # noqa: E501 - ) - # Source: yul - # berlin - # { - # sstore(0, 1) - # pop(call(gas(), 0x00000000000000000000000000000000000c0de0, 0, 0, 0, 0, 0)) # noqa: E501 - # invalid() - # } - contract_18 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.POP( - Op.CALL( - gas=Op.GAS, - address=contract_27, - value=Op.DUP1, - args_offset=Op.DUP1, - args_size=Op.DUP1, - ret_offset=Op.DUP1, - ret_size=0x0, + create_op = Op.CREATE if refund_source == "create" else Op.CREATE2 + refund_code = ( + Op.SSTORE(key=0, value=1) + + Op.SSTORE(key=1, value=1) + + Op.SSTORE(key=1, value=0) + + Op.MSTORE( + offset=0, value=Hash(child_init_code, right_padding=True) ) + + Op.POP(create_op(value=0, offset=0, size=len(child_init_code))) ) - + Op.INVALID, - nonce=0, - address=Address(0x000000000000000000000000000000000000006C), # noqa: E501 - ) - # Source: yul - # berlin - # { - # sstore(0, 1) - # sstore(1, 1) - # sstore(1, 0) - # let initcodeaddr := 0x00000000000000000000000000000000000c0de1 - # let initcodelength := extcodesize(initcodeaddr) - # extcodecopy(initcodeaddr, 0, 0, initcodelength) - # pop(create2(0, 0, initcodelength, 0)) - # return(add(initcodelength, 1), 5000) - # } - contract_23 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.SSTORE(key=Op.DUP1, value=0x1) - + Op.SSTORE(key=0x1, value=0x0) - + Op.PUSH2[0x1388] - + Op.PUSH1[0x1] - + Op.PUSH1[0x0] - + Op.PUSH3[0xC0DE1] - + Op.DUP2 - + Op.EXTCODESIZE(address=Op.DUP2) - + Op.SWAP3 - + Op.DUP4 - + Op.SWAP3 - + Op.EXTCODECOPY - + Op.POP( - Op.CREATE2(value=Op.DUP1, offset=Op.DUP2, size=Op.DUP2, salt=0x0) + # Init code fills the first memory word; read past its end. + return_offset = len(child_init_code) + child_contract = compute_create_address( + address=created_contract, + nonce=1, + salt=0, + initcode=child_init_code, + opcode=create_op, ) - + Op.ADD - + Op.RETURN, - nonce=0, - address=Address(0x000000000000000000000000000000000000008B), # noqa: E501 - ) - # Source: yul - # berlin - # { - # sstore(0, 1) - # sstore(1, 1) - # sstore(1, 0) - # let initcodeaddr := 0x00000000000000000000000000000000000c0de1 - # let initcodelength := extcodesize(initcodeaddr) - # extcodecopy(initcodeaddr, 0, 0, initcodelength) - # pop(create2(0, 0, initcodelength, 0)) - # invalid() - # } - contract_24 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.SSTORE(key=Op.DUP1, value=0x1) - + Op.SSTORE(key=0x1, value=0x0) - + Op.PUSH1[0x0] - + Op.DUP1 - + Op.PUSH3[0xC0DE1] - + Op.DUP2 - + Op.EXTCODESIZE(address=Op.DUP2) - + Op.SWAP3 - + Op.DUP4 - + Op.SWAP3 - + Op.EXTCODECOPY - + Op.DUP2 - + Op.DUP1 - + Op.POP(Op.CREATE2) - + Op.INVALID, - nonce=0, - address=Address(0x000000000000000000000000000000000000008C), # noqa: E501 - ) - # Source: yul - # berlin - # { - # sstore(0, 1) - # sstore(1, 1) - # sstore(1, 0) - # let initcodeaddr := 0x00000000000000000000000000000000000c0de1 - # let initcodelength := extcodesize(initcodeaddr) - # extcodecopy(initcodeaddr, 0, 0, initcodelength) - # pop(create(0, 0, initcodelength)) - # return(add(initcodelength, 1), 5000) - # } - contract_20 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.SSTORE(key=Op.DUP1, value=0x1) - + Op.SSTORE(key=0x1, value=0x0) - + Op.PUSH2[0x1388] - + Op.PUSH1[0x1] - + Op.PUSH1[0x0] - + Op.PUSH3[0xC0DE1] - + Op.DUP2 - + Op.EXTCODESIZE(address=Op.DUP2) - + Op.SWAP3 - + Op.DUP4 - + Op.SWAP3 - + Op.EXTCODECOPY - + Op.POP(Op.CREATE(value=Op.DUP1, offset=0x0, size=Op.DUP1)) - + Op.ADD - + Op.RETURN, - nonce=0, - address=Address(0x000000000000000000000000000000000000007B), # noqa: E501 - ) - # Source: yul - # berlin - # { - # sstore(0, 1) - # sstore(1, 1) - # sstore(1, 0) - # let initcodeaddr := 0x00000000000000000000000000000000000c0de1 - # let initcodelength := extcodesize(initcodeaddr) - # extcodecopy(initcodeaddr, 0, 0, initcodelength) - # pop(create(0, 0, initcodelength)) - # invalid() - # } - contract_21 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x1) - + Op.SSTORE(key=Op.DUP1, value=0x1) - + Op.SSTORE(key=0x1, value=0x0) - + Op.PUSH1[0x0] - + Op.PUSH3[0xC0DE1] - + Op.DUP2 - + Op.EXTCODESIZE(address=Op.DUP2) - + Op.SWAP3 - + Op.DUP4 - + Op.SWAP3 - + Op.EXTCODECOPY - + Op.PUSH1[0x0] - + Op.DUP1 - + Op.POP(Op.CREATE) - + Op.INVALID, - nonce=0, - address=Address(0x000000000000000000000000000000000000007C), # noqa: E501 - ) - # Source: yul - # berlin - # { - # sstore(0, 1) - # sstore(1, 1) - # sstore(1, 0) - # let initcodeaddr := 0x00000000000000000000000000000000000c0de1 - # let initcodelength := extcodesize(initcodeaddr) - # extcodecopy(initcodeaddr, 0, 0, initcodelength) - # pop(create(0, 0, initcodelength)) - # return(add(initcodelength, 1), 1) - # } - contract_19 = pre.deploy_contract( # noqa: F841 - code=Op.PUSH1[0x1] - + Op.PUSH1[0x0] - + Op.SSTORE(key=Op.DUP2, value=Op.DUP2) - + Op.SSTORE(key=Op.DUP3, value=Op.DUP1) - + Op.DUP2 - + Op.SWAP1 - + Op.PUSH3[0xC0DE1] - + Op.EXTCODESIZE(address=Op.DUP1) - + Op.SWAP2 - + Op.DUP3 - + Op.SWAP2 - + Op.DUP2 - + Op.SWAP1 - + Op.EXTCODECOPY - + Op.POP(Op.CREATE(value=Op.DUP1, offset=0x0, size=Op.DUP1)) - + Op.ADD - + Op.RETURN, - nonce=0, - address=Address(0x000000000000000000000000000000000000007A), # noqa: E501 - ) - - expect_entries_: list[dict] = [] - if fork.is_eip_enabled(8037): - expect_entries_.append( - { - "indexes": { - "data": [ - 1, - 2, - 4, - 5, - 7, - 8, - 10, - 11, - 13, - 14, - 16, - 17, - 19, - 20, - 22, - 23, - ], - "gas": -1, - "value": -1, - }, - "network": [">=Cancun"], - "result": {sender: Account(nonce=2)}, - } + extra_post[child_contract] = ( + Account(storage={}, code=deployed_code, nonce=1) + if deploy_succeeds + else Account.NONEXISTENT ) - expect_entries_ += [ - { - "indexes": {"data": [0, 9, 3, 6], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": { - sender: Account(nonce=2), - compute_create_address(address=contract_0, nonce=1): Account( - storage={0: 1}, code=bytes.fromhex("00"), nonce=1 - ), - }, - }, - { - "indexes": { - "data": [1, 2, 4, 5, 7, 8, 10, 11], - "gas": -1, - "value": -1, - }, - "network": [">=Cancun"], - "result": { - sender: Account(balance=0, nonce=2), - compute_create_address( - address=contract_0, nonce=1 - ): Account.NONEXISTENT, - }, - }, - { - "indexes": {"data": [12], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": { - sender: Account(nonce=2), - compute_create_address(address=contract_0, nonce=1): Account( - storage={0: 1}, code=bytes.fromhex("00"), nonce=1 - ), - contract_26: Account(balance=0, nonce=1), - }, - }, - { - "indexes": {"data": [13, 14], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": { - sender: Account(balance=0, nonce=2), - compute_create_address( - address=contract_0, nonce=1 - ): Account.NONEXISTENT, - contract_26: Account( - storage={1: 1}, code=bytes.fromhex("32ff"), nonce=1 - ), - }, - }, - { - "indexes": {"data": [15], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": { - sender: Account(nonce=2), - compute_create_address(address=contract_0, nonce=1): Account( - storage={0: 1}, code=bytes.fromhex("00"), nonce=1 - ), - }, - }, - { - "indexes": {"data": [16, 17], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": { - sender: Account(balance=0, nonce=2), - compute_create_address( - address=contract_0, nonce=1 - ): Account.NONEXISTENT, - }, - }, - { - "indexes": {"data": [18], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": { - sender: Account(nonce=2), - compute_create_address(address=contract_0, nonce=1): Account( - storage={0: 1}, code=bytes.fromhex("00"), nonce=2 - ), - compute_create_address( - address=compute_create_address( - address=contract_0, nonce=1 - ), - nonce=1, - ): Account(storage={}, code=bytes.fromhex("00"), nonce=1), - }, - }, - { - "indexes": {"data": [19, 20], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": { - sender: Account(balance=0, nonce=2), - compute_create_address( - address=contract_0, nonce=1 - ): Account.NONEXISTENT, - compute_create_address( - address=compute_create_address( - address=contract_0, nonce=1 - ), - nonce=1, - ): Account.NONEXISTENT, - }, - }, - { - "indexes": {"data": [21], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": { - sender: Account(nonce=2), - compute_create_address(address=contract_0, nonce=1): Account( - storage={0: 1}, code=bytes.fromhex("00"), nonce=2 - ), - Address(0x06019547B6E360ABDAFEADE158A9667CC6106C17): Account( - storage={}, code=bytes.fromhex("00"), nonce=1 - ), - }, - }, - { - "indexes": {"data": [22, 23], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": { - sender: Account(balance=0, nonce=2), - compute_create_address( - address=contract_0, nonce=1 - ): Account.NONEXISTENT, - Address( - 0x06019547B6E360ABDAFEADE158A9667CC6106C17 - ): Account.NONEXISTENT, - }, - }, - ] - post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork) + deploy_code: Bytecode + if deploy_outcome == "created": + deploy_code = Op.RETURN(offset=return_offset, size=len(deployed_code)) + elif deploy_outcome == "code_deposit_oog": + # More code than the deposit charge can be paid for. + deploy_code = Op.RETURN(offset=return_offset, size=oversized_code_size) + else: + deploy_code = Op.INVALID - tx_data = [ - Bytes("693c6139") + Hash(contract_1, left_padding=True), - Bytes("693c6139") + Hash(contract_2, left_padding=True), - Bytes("693c6139") + Hash(contract_3, left_padding=True), - Bytes("693c6139") + Hash(contract_4, left_padding=True), - Bytes("693c6139") + Hash(contract_5, left_padding=True), - Bytes("693c6139") + Hash(contract_6, left_padding=True), - Bytes("693c6139") + Hash(contract_7, left_padding=True), - Bytes("693c6139") + Hash(contract_8, left_padding=True), - Bytes("693c6139") + Hash(contract_9, left_padding=True), - Bytes("693c6139") + Hash(contract_10, left_padding=True), - Bytes("693c6139") + Hash(contract_11, left_padding=True), - Bytes("693c6139") + Hash(contract_12, left_padding=True), - Bytes("693c6139") + Hash(contract_13, left_padding=True), - Bytes("693c6139") + Hash(contract_14, left_padding=True), - Bytes("693c6139") + Hash(contract_15, left_padding=True), - Bytes("693c6139") + Hash(contract_16, left_padding=True), - Bytes("693c6139") + Hash(contract_17, left_padding=True), - Bytes("693c6139") + Hash(contract_18, left_padding=True), - Bytes("693c6139") + Hash(contract_19, left_padding=True), - Bytes("693c6139") + Hash(contract_20, left_padding=True), - Bytes("693c6139") + Hash(contract_21, left_padding=True), - Bytes("693c6139") + Hash(contract_22, left_padding=True), - Bytes("693c6139") + Hash(contract_23, left_padding=True), - Bytes("693c6139") + Hash(contract_24, left_padding=True), - ] - tx_gas = [400000] + post = { + sender: Account(nonce=1), + created_contract: ( + Account( + storage={0: 1}, + code=deployed_code, + # The nested creates bump this nonce again. + nonce=2 if refund_source in ("create", "create2") else 1, + ) + if deploy_succeeds + else Account.NONEXISTENT + ), + **extra_post, + } tx = Transaction( sender=sender, - to=contract_0, - data=tx_data[d], - gas_limit=tx_gas[g], - nonce=1, - error=_exc, + to=factory_contract, + data=refund_code + deploy_code, + gas_limit=tx_gas_limit, + # The factory's INVALID burns the frame, so the entire limit must be + # charged: any refund that survived the failure would show up as a + # discount here. Only `cumulative_gas_used` is actually verified, and + # it equals this transaction's gas used since it is alone in its block. + expected_receipt=( + None + if deploy_succeeds + else TransactionReceipt(cumulative_gas_used=tx_gas_limit) + ), ) - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stCreateTest/test_create_results.py b/tests/ported_static/stCreateTest/test_create_results.py index a18d11e9cc6..cc6a5c98ce5 100644 --- a/tests/ported_static/stCreateTest/test_create_results.py +++ b/tests/ported_static/stCreateTest/test_create_results.py @@ -1,714 +1,295 @@ """ -Ori Pomerantz qbzzt1@gmail.com. +Verify what CREATE/CREATE2 leave behind for each constructor outcome -- +success, out of gas, empty revert, revert with data, empty deploy, and an +in-init SELFDESTRUCT -- plus each call kind's result against the contract a +successful constructor deploys, and the frame-aborting RETURNDATACOPY past +an empty return buffer. + +Written by Ori Pomerantz (qbzzt1@gmail.com). Ported from: state_tests/stCreateTest/CreateResultsFiller.yml + +@manually-enhanced: Do not overwrite. The filler drove a single LLL +dispatcher that branched at run time on an ABI calldata triple, because a +static filler can only vary data, not code. Here the three axes are +parametrized, so the creator contract and the init code are generated per +case and the whole jump table, its code-copied constructor fragments, and +its hardcoded layout are gone. The created account is asserted per case, +which the filler never did. """ import pytest from execution_testing import ( - EOA, Account, - Address, Alloc, - Bytes, - Environment, - Hash, + Bytecode, + Initcode, + Opcodes, StateTestFiller, + Storage, Transaction, + compute_create_address, ) -from execution_testing.forks import Fork from execution_testing.vm import Op -from tests.ported_static.post_state_resolution import ( - resolve_expect_post, -) - REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +# Written last; proves the creator frame ran to completion. +COMPLETED = 0xC0DE + +# Scratch memory, clear of the init code copied in from the calldata. +ADDRESS_MEM = 0x100 +RETURN_DATA_MEM = 0x120 + +# Memory expansion value that guarantees an OOG +MEM_EXPANSION_OOG = 0x2FFFFFFF + +# Where the created contract records that it ran, and what it writes. CALL +# runs it in its own context while CALLCODE and DELEGATECALL run it in the +# creator's, so the slot is kept clear of the creator's own observations. +EXECUTED_SLOT = 0x64 +EXECUTED = 0xE0DE + +# Word the `revert_data` constructor reverts with. +REVERT_WORD = 0x60A7 + @pytest.mark.ported_from( ["state_tests/stCreateTest/CreateResultsFiller.yml"], ) @pytest.mark.valid_from("Cancun") -@pytest.mark.parametrize( - "d, g, v", - [ - pytest.param( - 0, - 0, - 0, - id="d0", - ), - pytest.param( - 1, - 0, - 0, - id="d1", - ), - pytest.param( - 2, - 0, - 0, - id="d2", - ), - pytest.param( - 3, - 0, - 0, - id="d3", - ), - pytest.param( - 4, - 0, - 0, - id="d4", - ), - pytest.param( - 5, - 0, - 0, - id="d5", - ), - pytest.param( - 6, - 0, - 0, - id="d6", - ), - pytest.param( - 7, - 0, - 0, - id="d7", - ), - pytest.param( - 8, - 0, - 0, - id="d8", - ), - pytest.param( - 9, - 0, - 0, - id="d9", - ), - pytest.param( - 10, - 0, - 0, - id="d10", - ), - pytest.param( - 11, - 0, - 0, - id="d11", - ), - pytest.param( - 12, - 0, - 0, - id="d12", - ), - pytest.param( - 13, - 0, - 0, - id="d13", - ), - pytest.param( - 14, - 0, - 0, - id="d14", - ), - pytest.param( - 15, - 0, - 0, - id="d15", - ), - pytest.param( - 16, - 0, - 0, - id="d16", - ), - pytest.param( - 17, - 0, - 0, - id="d17", - ), - pytest.param( - 18, - 0, - 0, - id="d18", - ), - pytest.param( - 19, - 0, - 0, - id="d19", - ), - pytest.param( - 20, - 0, - 0, - id="d20", - ), - pytest.param( - 21, - 0, - 0, - id="d21", - ), - pytest.param( - 22, - 0, - 0, - id="d22", - ), - pytest.param( - 23, - 0, - 0, - id="d23", - ), - pytest.param( - 24, - 0, - 0, - id="d24", - ), - pytest.param( - 25, - 0, - 0, - id="d25", - ), - ], -) -@pytest.mark.pre_alloc_mutable -def test_create_results( +@pytest.mark.with_all_create_opcodes +@pytest.mark.with_all_call_opcodes +def test_create_results_with_call( state_test: StateTestFiller, pre: Alloc, - fork: Fork, - d: int, - g: int, - v: int, + create_opcode: Opcodes, + call_opcode: Opcodes, ) -> None: - """Ori Pomerantz qbzzt1@gmail.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC) - contract_1 = Address(0x00000000000000000000000000000000000060A7) - sender = EOA( - key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8 - ) + """ + Verify each call kind's result against a successfully created contract. - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - ) + The creator invokes what it just deployed, and where that contract's + store lands is what separates the call kinds: its own storage for + CALL, the creator's for CALLCODE and DELEGATECALL, nowhere at all for + STATICCALL, which forbids the write and so fails the call outright. + """ + deployed_code = Op.SSTORE(EXECUTED_SLOT, EXECUTED) + Op.STOP + init_code = Initcode(deploy_code=deployed_code) - pre[sender] = Account(balance=0xBA1A9CE0BA1A9CE) - # Source: lll - # { - # ; Variables are 0x20 bytes (= 256 bits) apart, except for - # ; code buffers that get 0x100 (256 bytes) - # (def 'creation 0x100) - # (def 'callType 0x120) - # (def 'constructor 0x140) - # (def 'contractCode 0x200) - # (def 'constructorCode 0x300) - # (def 'extCode 0x400) - # (def 'contractLength 0x520) - # (def 'constructorLength 0x540) - # (def 'extLength 0x560) - # (def 'addr1 0x600) - # (def 'addr2 0x620) - # (def 'callRet 0x640) - # (def 'retData0 0x160) ; storage for returned data - # ; Other constants - # (def 'NOP 0) ; No OPeration - # ; Understand the input. - # [creation] $0x04 - # [callType] $0x24 - # [constructor] $0x44 - # ; The contract code - # (def 'contractMacro - # (lll - # (call 0xFFFF 0x60A7 0 0 0 0 0) - # contractCode - # ) ; inner lll - # ) - # ; I did not want to rely on knowing the address at which the contract - # ... (138 more lines) - contract_0 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x100, value=Op.CALLDATALOAD(offset=0x4)) - + Op.MSTORE(offset=0x120, value=Op.CALLDATALOAD(offset=0x24)) - + Op.MSTORE(offset=0x140, value=Op.CALLDATALOAD(offset=0x44)) - + Op.JUMPI( - pc=Op.PUSH2[0x2F], - condition=Op.OR( - Op.EQ(Op.MLOAD(offset=0x140), 0x0), - Op.EQ(Op.MLOAD(offset=0x140), 0x4), - ), - ) - + Op.POP(0x0) - + Op.JUMP(pc=Op.PUSH2[0x3E]) - + Op.JUMPDEST - + Op.PUSH1[0x21] - + Op.CODECOPY(dest_offset=0x300, offset=0x250, size=Op.DUP1) - + Op.PUSH2[0x540] - + Op.MSTORE - + Op.JUMPDEST - + Op.JUMPI( - pc=Op.PUSH2[0x51], condition=Op.EQ(Op.MLOAD(offset=0x140), 0x1) - ) - + Op.POP(0x0) - + Op.JUMP(pc=Op.PUSH2[0x60]) - + Op.JUMPDEST - + Op.PUSH1[0x29] - + Op.CODECOPY(dest_offset=0x300, offset=0x271, size=Op.DUP1) - + Op.PUSH2[0x540] - + Op.MSTORE - + Op.JUMPDEST - + Op.JUMPI( - pc=Op.PUSH2[0x73], condition=Op.EQ(Op.MLOAD(offset=0x140), 0x2) - ) - + Op.POP(0x0) - + Op.JUMP(pc=Op.PUSH2[0x82]) - + Op.JUMPDEST - + Op.PUSH1[0x26] - + Op.CODECOPY(dest_offset=0x300, offset=0x29A, size=Op.DUP1) - + Op.PUSH2[0x540] - + Op.MSTORE - + Op.JUMPDEST - + Op.JUMPI( - pc=Op.PUSH2[0x95], condition=Op.EQ(Op.MLOAD(offset=0x140), 0x3) - ) - + Op.POP(0x0) - + Op.JUMP(pc=Op.PUSH2[0xA4]) - + Op.JUMPDEST - + Op.PUSH1[0x2C] - + Op.CODECOPY(dest_offset=0x300, offset=0x2C0, size=Op.DUP1) - + Op.PUSH2[0x540] - + Op.MSTORE - + Op.JUMPDEST - + Op.JUMPI( - pc=Op.PUSH2[0xB7], condition=Op.EQ(Op.MLOAD(offset=0x140), 0x5) - ) - + Op.POP(0x0) - + Op.JUMP(pc=Op.PUSH2[0xC6]) - + Op.JUMPDEST - + Op.PUSH1[0x28] - + Op.CODECOPY(dest_offset=0x300, offset=0x2EC, size=Op.DUP1) - + Op.PUSH2[0x540] - + Op.MSTORE - + Op.JUMPDEST - + Op.JUMPI( - pc=Op.PUSH2[0xD9], condition=Op.EQ(Op.MLOAD(offset=0x140), 0x6) - ) - + Op.POP(0x0) - + Op.JUMP(pc=Op.PUSH2[0xE8]) - + Op.JUMPDEST - + Op.PUSH1[0x2A] - + Op.CODECOPY(dest_offset=0x300, offset=0x314, size=Op.DUP1) - + Op.PUSH2[0x540] - + Op.MSTORE - + Op.JUMPDEST - + Op.PUSH1[0x12] - + Op.CODECOPY(dest_offset=0x200, offset=0x33E, size=Op.DUP1) - + Op.PUSH2[0x520] - + Op.MSTORE - + Op.JUMPI(pc=0x117, condition=Op.EQ(Op.MLOAD(offset=0x100), 0x1)) - + Op.MSTORE( - offset=0x600, - value=Op.CREATE2( - value=0x0, - offset=0x300, - size=Op.MLOAD(offset=0x540), - salt=0x5A17, - ), - ) - + Op.JUMP(pc=0x126) - + Op.JUMPDEST - + Op.MSTORE( - offset=0x600, - value=Op.CREATE( - value=0x0, offset=0x300, size=Op.MLOAD(offset=0x540) - ), - ) - + Op.JUMPDEST - + Op.SSTORE(key=0x20, value=Op.PC) - + Op.SSTORE(key=0x10, value=Op.RETURNDATASIZE) - + Op.JUMPI( - pc=0x143, - condition=Op.OR( - Op.RETURNDATASIZE, Op.EQ(Op.MLOAD(offset=0x140), 0x4) - ), - ) - + Op.POP(0x0) - + Op.JUMP(pc=0x153) - + Op.JUMPDEST - + Op.RETURNDATACOPY(dest_offset=0x160, offset=0x0, size=0x20) - + Op.SSTORE(key=0x11, value=Op.MLOAD(offset=0x160)) - + Op.JUMPDEST + # STATICCALL forbids that store, so the call itself fails. The rest + # succeed and differ only in whose storage the store lands in. + call_succeeds = call_opcode != Op.STATICCALL + writes_to_creator = call_opcode in (Op.CALLCODE, Op.DELEGATECALL) + + st = Storage() + factory_code = ( + Op.CALLDATACOPY(dest_offset=0x0, offset=0x0, size=Op.CALLDATASIZE) + # The created address is only known at run time, so park it in memory. + Op.MSTORE( - offset=0x560, value=Op.EXTCODESIZE(address=Op.MLOAD(offset=0x600)) - ) - + Op.EXTCODECOPY( - address=Op.MLOAD(offset=0x600), - dest_offset=0x400, - offset=0x0, - size=Op.MLOAD(offset=0x560), + offset=ADDRESS_MEM, + value=create_opcode(value=0x0, offset=0x0, size=Op.CALLDATASIZE), ) + + Op.SSTORE(st.store_next(0), Op.RETURNDATASIZE) + Op.SSTORE( - key=0x12, - value=Op.SUB(Op.MLOAD(offset=0x520), Op.MLOAD(offset=0x560)), + st.store_next(len(bytes(deployed_code))), + Op.EXTCODESIZE(address=Op.MLOAD(offset=ADDRESS_MEM)), ) + Op.SSTORE( - key=0x13, - value=Op.SUB(Op.MLOAD(offset=0x200), Op.MLOAD(offset=0x400)), - ) - + Op.JUMPI(pc=0x195, condition=Op.EQ(Op.MLOAD(offset=0x120), 0x1)) - + Op.POP(0x0) - + Op.JUMP(pc=0x1AC) - + Op.JUMPDEST - + Op.MSTORE( - offset=0x640, - value=Op.CALL( - gas=0xFFFF, - address=Op.MLOAD(offset=0x600), - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.JUMPDEST - + Op.JUMPI(pc=0x1BF, condition=Op.EQ(Op.MLOAD(offset=0x120), 0x2)) - + Op.POP(0x0) - + Op.JUMP(pc=0x1D6) - + Op.JUMPDEST - + Op.MSTORE( - offset=0x640, - value=Op.CALLCODE( - gas=0xFFFF, - address=Op.MLOAD(offset=0x600), - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.JUMPDEST - + Op.JUMPI(pc=0x1E9, condition=Op.EQ(Op.MLOAD(offset=0x120), 0x3)) - + Op.POP(0x0) - + Op.JUMP(pc=0x1FE) - + Op.JUMPDEST - + Op.MSTORE( - offset=0x640, - value=Op.DELEGATECALL( - gas=0xFFFF, - address=Op.MLOAD(offset=0x600), - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.JUMPDEST - + Op.JUMPI(pc=0x211, condition=Op.EQ(Op.MLOAD(offset=0x120), 0x4)) - + Op.POP(0x0) - + Op.JUMP(pc=0x226) - + Op.JUMPDEST - + Op.MSTORE( - offset=0x640, - value=Op.STATICCALL( - gas=0xFFFF, - address=Op.MLOAD(offset=0x600), - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.JUMPDEST - + Op.SSTORE(key=0x21, value=Op.PC) - + Op.JUMPI( - pc=0x23E, condition=Op.ISZERO(Op.EQ(Op.MLOAD(offset=0x120), 0x0)) + st.store_next(1 if call_succeeds else 0), + call_opcode(address=Op.MLOAD(offset=ADDRESS_MEM)), ) - + Op.POP(0x0) - + Op.JUMP(pc=0x24D) - + Op.JUMPDEST - + Op.SSTORE(key=0x14, value=Op.SUB(Op.MLOAD(offset=0x640), 0x1)) - + Op.SSTORE(key=0x15, value=Op.RETURNDATASIZE) - + Op.JUMPDEST + + Op.SSTORE(st.store_next(0), Op.RETURNDATASIZE) + + Op.SSTORE(st.store_next(COMPLETED), COMPLETED) + Op.STOP - + Op.INVALID - + Op.PUSH1[0x12] - + Op.CODECOPY(dest_offset=0x200, offset=0xF, size=Op.DUP1) - + Op.PUSH2[0x200] - + Op.RETURN - + Op.STOP - + Op.INVALID - + Op.CALL( - gas=0xFFFF, - address=0x60A7, - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, + ) + creator = pre.deploy_contract(code=factory_code, storage=st.canary()) + + created = compute_create_address( + address=creator, nonce=1, initcode=init_code, opcode=create_opcode + ) + + if writes_to_creator: + st[EXECUTED_SLOT] = EXECUTED + post = { + creator: Account(storage=st), + created: Account( + code=deployed_code, + nonce=1, + storage={EXECUTED_SLOT: EXECUTED} + if call_opcode == Op.CALL + else {}, + ), + } + + tx = Transaction( + sender=pre.fund_eoa(), + to=creator, + data=init_code, + state_gas_reservoir=0, + ) + + state_test(pre=pre, post=post, tx=tx) + + +@pytest.mark.ported_from( + ["state_tests/stCreateTest/CreateResultsFiller.yml"], +) +@pytest.mark.valid_from("Cancun") +@pytest.mark.with_all_create_opcodes +def test_returndatacopy_after_successful_create_aborts( + state_test: StateTestFiller, + pre: Alloc, + create_opcode: Opcodes, +) -> None: + """ + Verify that reading past the empty buffer a successful CREATE leaves + halts the creating frame. + + A create that succeeds produces no return data, so copying a word out + of it is an out-of-bounds read: the frame halts, every canary it was + seeded with survives, its completion marker is never written, and the + contract it had just created is rolled back with it. + """ + deployed_code = Op.SSTORE(EXECUTED_SLOT, EXECUTED) + Op.STOP + init_code = Initcode(deploy_code=deployed_code) + + st = Storage() + factory_code = ( + Op.CALLDATACOPY(dest_offset=0x0, offset=0x0, size=Op.CALLDATASIZE) + + Op.MSTORE( + offset=ADDRESS_MEM, + value=create_opcode(value=0x0, offset=0x0, size=Op.CALLDATASIZE), ) + + Op.SSTORE(st.store_next(0), Op.RETURNDATASIZE) + # Nothing past this point ever runs. + + Op.RETURNDATACOPY(dest_offset=RETURN_DATA_MEM, offset=0x0, size=0x20) + + Op.SSTORE(st.store_next(0), Op.MLOAD(offset=RETURN_DATA_MEM)) + + Op.SSTORE(st.store_next(COMPLETED), COMPLETED) + Op.STOP - + Op.POP(Op.SHA3(offset=0x0, size=0x2FFFFF)) - + Op.PUSH1[0x12] - + Op.CODECOPY(dest_offset=0x200, offset=0x17, size=Op.DUP1) - + Op.PUSH2[0x200] - + Op.RETURN - + Op.STOP - + Op.INVALID - + Op.CALL( - gas=0xFFFF, - address=0x60A7, - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, + ) + creator = pre.deploy_contract(code=factory_code, storage=st.canary()) + + created = compute_create_address( + address=creator, nonce=1, initcode=init_code, opcode=create_opcode + ) + + tx = Transaction( + sender=pre.fund_eoa(), + to=creator, + data=init_code, + state_gas_reservoir=0, + ) + + post = { + creator: Account(storage=st.canary()), + created: Account.NONEXISTENT, + } + + state_test(pre=pre, post=post, tx=tx) + + +@pytest.mark.ported_from( + ["state_tests/stCreateTest/CreateResultsFiller.yml"], +) +@pytest.mark.valid_from("Cancun") +@pytest.mark.with_all_create_opcodes +@pytest.mark.parametrize( + "constructor", + [ + "oog", + "revert", + "revert_data", + "empty_deploy", + "selfdestruct", + ], +) +def test_create_results_without_call( + state_test: StateTestFiller, + pre: Alloc, + create_opcode: Opcodes, + constructor: str, +) -> None: + """ + Verify what CREATE returns and leaves behind when the constructor + deploys nothing callable. + + The creator makes no follow-up call: it only records the create's + return data and the code size of the address it was handed. + """ + # What the constructor would have deployed had it got that far. + deployed_code = Op.SSTORE(EXECUTED_SLOT, EXECUTED) + Op.STOP + + # Each outcome is a prologue in front of the code that would return + # `deployed_code`; all of them halt before reaching it. + prologue: Bytecode + if constructor == "oog": + prologue = Op.POP(Op.SHA3(offset=0x0, size=MEM_EXPANSION_OOG)) + elif constructor == "revert": + prologue = Op.REVERT(offset=0x0, size=0x0) + elif constructor == "revert_data": + prologue = Op.MSTORE(offset=0x0, value=REVERT_WORD) + Op.REVERT( + offset=0x0, size=0x20 ) - + Op.STOP - + Op.REVERT(offset=0x0, size=0x0) - + Op.PUSH1[0x12] - + Op.CODECOPY(dest_offset=0x200, offset=0x14, size=Op.DUP1) - + Op.PUSH2[0x200] - + Op.RETURN - + Op.STOP - + Op.INVALID - + Op.CALL( - gas=0xFFFF, - address=0x60A7, - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, + elif constructor == "empty_deploy": + prologue = Op.STOP + else: + prologue = Op.SELFDESTRUCT(address=0x0) + + init_code = Initcode(deploy_code=deployed_code, initcode_prefix=prologue) + + st = Storage() + # Only `revert_data` leaves a return buffer to read. + read_return_data: Bytecode = Bytecode() + if constructor == "revert_data": + read_return_data = Op.RETURNDATACOPY( + dest_offset=RETURN_DATA_MEM, offset=0x0, size=0x20 + ) + Op.SSTORE( + st.store_next(REVERT_WORD), Op.MLOAD(offset=RETURN_DATA_MEM) ) - + Op.STOP - + Op.MSTORE(offset=0x0, value=0x60A7) - + Op.REVERT(offset=0x0, size=0x20) - + Op.PUSH1[0x12] - + Op.CODECOPY(dest_offset=0x200, offset=0x1A, size=Op.DUP1) - + Op.PUSH2[0x200] - + Op.RETURN - + Op.STOP - + Op.INVALID - + Op.CALL( - gas=0xFFFF, - address=0x60A7, - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, + + factory_code = ( + Op.CALLDATACOPY(dest_offset=0x0, offset=0x0, size=Op.CALLDATASIZE) + # The created address is only known at run time, so park it in memory. + + Op.MSTORE( + offset=ADDRESS_MEM, + value=create_opcode(value=0x0, offset=0x0, size=Op.CALLDATASIZE), ) - + Op.STOP - + Op.MSTORE(offset=0x0, value=0x60A7) - + Op.STOP - + Op.PUSH1[0x12] - + Op.CODECOPY(dest_offset=0x200, offset=0x16, size=Op.DUP1) - + Op.PUSH2[0x200] - + Op.RETURN - + Op.STOP - + Op.INVALID - + Op.CALL( - gas=0xFFFF, - address=0x60A7, - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, + + Op.SSTORE( + st.store_next(0x20 if constructor == "revert_data" else 0), + Op.RETURNDATASIZE, ) - + Op.STOP - + Op.MSTORE(offset=0x0, value=0x60A7) - + Op.SELFDESTRUCT(address=0x0) - + Op.PUSH1[0x12] - + Op.CODECOPY(dest_offset=0x200, offset=0x18, size=Op.DUP1) - + Op.PUSH2[0x200] - + Op.RETURN - + Op.STOP - + Op.INVALID - + Op.CALL( - gas=0xFFFF, - address=0x60A7, - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, + + read_return_data + + Op.SSTORE( + st.store_next(0), + Op.EXTCODESIZE(address=Op.MLOAD(offset=ADDRESS_MEM)), ) + + Op.SSTORE(st.store_next(COMPLETED), COMPLETED) + Op.STOP - + Op.CALL( - gas=0xFFFF, - address=0x60A7, - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ) - + Op.STOP, - storage={ - 16: contract_1, - 18: contract_1, - 19: contract_1, - 20: contract_1, - 21: contract_1, - 32: contract_1, - 33: contract_1, - }, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC), # noqa: E501 ) - # Source: lll - # { - # [[0]] 0x60A7 - # } ; end of LLL code - contract_1 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x60A7) + Op.STOP, - balance=0xBA1A9CE0BA1A9CE, - nonce=0, - address=Address(0x00000000000000000000000000000000000060A7), # noqa: E501 + creator = pre.deploy_contract(code=factory_code, storage=st.canary()) + + created = compute_create_address( + address=creator, nonce=1, initcode=init_code, opcode=create_opcode ) - expect_entries_: list[dict] = [ - { - "indexes": {"data": [0, 1, 2, 4, 5, 6], "gas": 0, "value": 0}, - "network": [">=Cancun"], - "result": { - contract_0: Account(storage={32: 295, 33: 551}), - contract_1: Account(storage={0: contract_1}), - }, - }, - { - "indexes": {"data": [3, 7], "gas": 0, "value": 0}, - "network": [">=Cancun"], - "result": {contract_0: Account(storage={32: 295, 33: 551})}, - }, - { - "indexes": { - "data": [8, 9, 10, 11, 12, 13, 14, 15], - "gas": 0, - "value": 0, - }, - "network": [">=Cancun"], - "result": { - contract_0: Account( - storage={ - 18: 18, - 19: 0x600060006000600060006160A761FFFFF1000000000000000000000000000000, # noqa: E501 - 20: contract_1, - 21: contract_1, - 32: 295, - 33: 551, - }, - ), - }, - }, - { - "indexes": {"data": [16, 17], "gas": 0, "value": 0}, - "network": [">=Cancun"], - "result": { - contract_0: Account( - storage={ - 16: 32, - 17: contract_1, - 18: 18, - 19: 0x600060006000600060006160A761FFFFF1000000000000000000000000000000, # noqa: E501 - 20: contract_1, - 21: contract_1, - 32: 295, - 33: 551, - }, - ), - }, - }, - { - "indexes": { - "data": [18, 19, 20, 21, 22, 23, 24, 25], - "gas": 0, - "value": 0, - }, - "network": [">=Cancun"], - "result": { - contract_0: Account( - storage={ - 16: contract_1, - 17: 0, - 18: contract_1, - 19: contract_1, - 20: contract_1, - 21: contract_1, - 32: contract_1, - 33: contract_1, - }, - ), - }, - }, - ] - - post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork) - - tx_data = [ - Bytes("048071d3") + Hash(0x1) + Hash(0x1) + Hash(0x0), - Bytes("048071d3") + Hash(0x1) + Hash(0x2) + Hash(0x0), - Bytes("048071d3") + Hash(0x1) + Hash(0x3) + Hash(0x0), - Bytes("048071d3") + Hash(0x1) + Hash(0x4) + Hash(0x0), - Bytes("048071d3") + Hash(0x2) + Hash(0x1) + Hash(0x0), - Bytes("048071d3") + Hash(0x2) + Hash(0x2) + Hash(0x0), - Bytes("048071d3") + Hash(0x2) + Hash(0x3) + Hash(0x0), - Bytes("048071d3") + Hash(0x2) + Hash(0x4) + Hash(0x0), - Bytes("048071d3") + Hash(0x1) + Hash(0x0) + Hash(0x1), - Bytes("048071d3") + Hash(0x2) + Hash(0x0) + Hash(0x1), - Bytes("048071d3") + Hash(0x1) + Hash(0x0) + Hash(0x2), - Bytes("048071d3") + Hash(0x2) + Hash(0x0) + Hash(0x2), - Bytes("048071d3") + Hash(0x1) + Hash(0x0) + Hash(0x5), - Bytes("048071d3") + Hash(0x2) + Hash(0x0) + Hash(0x5), - Bytes("048071d3") + Hash(0x1) + Hash(0x0) + Hash(0x6), - Bytes("048071d3") + Hash(0x2) + Hash(0x0) + Hash(0x6), - Bytes("048071d3") + Hash(0x1) + Hash(0x0) + Hash(0x3), - Bytes("048071d3") + Hash(0x2) + Hash(0x0) + Hash(0x3), - Bytes("048071d3") + Hash(0x1) + Hash(0x1) + Hash(0x4), - Bytes("048071d3") + Hash(0x1) + Hash(0x2) + Hash(0x4), - Bytes("048071d3") + Hash(0x1) + Hash(0x3) + Hash(0x4), - Bytes("048071d3") + Hash(0x1) + Hash(0x4) + Hash(0x4), - Bytes("048071d3") + Hash(0x2) + Hash(0x1) + Hash(0x4), - Bytes("048071d3") + Hash(0x2) + Hash(0x2) + Hash(0x4), - Bytes("048071d3") + Hash(0x2) + Hash(0x3) + Hash(0x4), - Bytes("048071d3") + Hash(0x2) + Hash(0x4) + Hash(0x4), - ] - tx_gas = [9437184] + post = { + creator: Account(storage=st), + # An empty deploy leaves an account with no code; every other + # outcome leaves nothing at all, an in-init SELFDESTRUCT included + # (destroyed in its creation transaction per EIP-6780). + created: Account(code=b"", nonce=1, storage={}) + if constructor == "empty_deploy" + else Account.NONEXISTENT, + } tx = Transaction( - sender=sender, - to=contract_0, - data=tx_data[d], - gas_limit=tx_gas[g], - error=_exc, + sender=pre.fund_eoa(), + to=creator, + data=init_code, + state_gas_reservoir=0, ) - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stCreateTest/test_transaction_collision.py b/tests/ported_static/stCreateTest/test_transaction_collision.py new file mode 100644 index 00000000000..af914fbe36d --- /dev/null +++ b/tests/ported_static/stCreateTest/test_transaction_collision.py @@ -0,0 +1,122 @@ +""" +Verify a contract-creation transaction whose target address already holds +code or a non-zero nonce: the collision aborts the creation, consumes the +whole gas limit, transfers no value, and leaves the existing account +untouched. + +A target holding only a balance is not a collision -- creation proceeds -- +which is `test_transaction_collision_to_empty2`. + +Ported from: +state_tests/stCreateTest/TransactionCollisionToEmptyButCodeFiller.json +state_tests/stCreateTest/TransactionCollisionToEmptyButNonceFiller.json + +@manually-enhanced: Do not overwrite. Budgets are derived from the fork +(bare intrinsic and a fully-funded creation); the post asserts the +colliding account's code, nonce, and unchanged zero balance. The two +fillers, which differ only in which field the target already holds, are +folded into one parametrize. +""" + +import pytest +from execution_testing import ( + Account, + Alloc, + Fork, + Header, + StateTestFiller, + Transaction, + compute_create_address, +) +from execution_testing.vm import Op + +REFERENCE_SPEC_GIT_PATH = "N/A" +REFERENCE_SPEC_VERSION = "N/A" + +# Any non-empty code at the target address triggers the collision. +COLLIDING_CODE = bytes.fromhex("1122334455") + + +@pytest.mark.ported_from( + [ + "state_tests/stCreateTest/TransactionCollisionToEmptyButCodeFiller.json", # noqa: E501 + "state_tests/stCreateTest/TransactionCollisionToEmptyButNonceFiller.json", # noqa: E501 + ], +) +@pytest.mark.valid_from("Berlin") +@pytest.mark.parametrize("collision", ["code", "nonce"]) +@pytest.mark.parametrize( + "full_budget", [True, False], ids=["full-budget", "intrinsic-only"] +) +@pytest.mark.parametrize("tx_value", [0, 1], ids=["v0", "v1"]) +@pytest.mark.pre_alloc_mutable +def test_transaction_collision( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + collision: str, + full_budget: bool, + tx_value: int, +) -> None: + """Creation collision burns the whole gas limit whatever the budget.""" + # Init code that would store a flag if it ever ran. + initcode = Op.SSTORE( + key=0x1, + value=0x1, + key_warm=False, + original_value=0, + new_value=1, + ) + + # No `return_cost_deducted_prior_execution` here: this is the floor a + # transaction must clear to be valid at all, not a budget for execution. + intrinsic = fork.transaction_intrinsic_cost_calculator()( + calldata=initcode, + contract_creation=True, + sends_value=tx_value > 0, + ) + if full_budget: + # Enough to fund the whole creation (even at the fresh-target + # EIP-8037 price) — the collision must still consume all of it. + gas_limit = ( + intrinsic + + fork.transaction_top_frame_state_gas(contract_creation=True) + + initcode.gas_cost(fork) + ) + else: + gas_limit = intrinsic + + # The target is empty but for the one field that makes it collide. The + # same account is the expected post-state, which is the whole point: + # nothing about it changes. + colliding_account = Account( + code=COLLIDING_CODE if collision == "code" else b"", + nonce=1 if collision == "nonce" else 0, + balance=0, + storage={}, + ) + + sender = pre.fund_eoa() + created = compute_create_address(address=sender, nonce=0) + pre[created] = colliding_account + + tx = Transaction( + sender=sender, + to=None, + data=initcode, + gas_limit=gas_limit, + value=tx_value, + ) + + post = { + sender: Account(nonce=1), + # Untouched: the init code never ran and the value never arrived. + created: colliding_account, + } + + state_test( + pre=pre, + post=post, + tx=tx, + blockchain_test_header_verify=Header(gas_used=gas_limit), + ) diff --git a/tests/ported_static/stCreateTest/test_transaction_collision_to_empty2.py b/tests/ported_static/stCreateTest/test_transaction_collision_to_empty2.py index e49cd593e8f..000cd5f3c43 100644 --- a/tests/ported_static/stCreateTest/test_transaction_collision_to_empty2.py +++ b/tests/ported_static/stCreateTest/test_transaction_collision_to_empty2.py @@ -1,134 +1,92 @@ """ -Test_transaction_collision_to_empty2. +Verify a contract-creation transaction targeting an address that holds +only a balance: the prefund is not a collision, so creation proceeds and +the budget alone decides whether the init code completes. Ported from: state_tests/stCreateTest/TransactionCollisionToEmpty2Filler.json + +@manually-enhanced: Do not overwrite. Budgets are derived from the fork +(intrinsic + init code cost, success arm exact), pinning that a prefunded +create target incurs no EIP-8037 top-frame new-account state gas. """ import pytest from execution_testing import ( - EOA, Account, - Address, Alloc, - Environment, + Fork, StateTestFiller, Transaction, + compute_create_address, ) -from execution_testing.forks import Fork from execution_testing.vm import Op -from tests.ported_static.post_state_resolution import ( - resolve_expect_post, -) - REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +PREFUND = 10 + @pytest.mark.ported_from( ["state_tests/stCreateTest/TransactionCollisionToEmpty2Filler.json"], ) -@pytest.mark.valid_from("Cancun") -@pytest.mark.parametrize( - "d, g, v", - [ - pytest.param( - 0, - 0, - 0, - id="-g0-v0", - ), - pytest.param( - 0, - 0, - 1, - id="-g0-v1", - ), - pytest.param( - 0, - 1, - 0, - id="-g1-v0", - ), - pytest.param( - 0, - 1, - 1, - id="-g1-v1", - ), - ], -) -@pytest.mark.pre_alloc_mutable +@pytest.mark.valid_from("Berlin") +@pytest.mark.parametrize("oog", [False, True], ids=["enough-gas", "oog"]) +@pytest.mark.parametrize("tx_value", [0, 1], ids=["v0", "v1"]) def test_transaction_collision_to_empty2( state_test: StateTestFiller, pre: Alloc, fork: Fork, - d: int, - g: int, - v: int, + oog: bool, + tx_value: int, ) -> None: - """Test_transaction_collision_to_empty2.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0x6295EE1B4F6DD65047762F924ECD367C17EABF8F) - sender = EOA( - key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8 + """Prefunded create target is no collision; budget decides the rest.""" + # Init code: one cold zero->non-zero store, deploys nothing. + initcode = Op.SSTORE( + key=0x1, + value=0x1, + key_warm=False, + original_value=0, + new_value=1, ) - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) + success_gas = fork.transaction_intrinsic_cost_calculator()( + calldata=initcode, + contract_creation=True, + sends_value=tx_value > 0, + return_cost_deducted_prior_execution=True, + ) + initcode.gas_cost(fork) + gas_limit = success_gas + if oog: + # Exactly one gas short, so the store is what cannot be paid for. + gas_limit -= 1 - pre[sender] = Account(balance=0xE8D4A51000) - pre[contract_0] = Account(balance=10) - - expect_entries_: list[dict] = [ - { - "indexes": {"data": -1, "gas": 0, "value": 0}, - "network": [">=Cancun"], - "result": { - sender: Account(nonce=1), - contract_0: Account(storage={1: 1}, balance=10, nonce=1), - }, - }, - { - "indexes": {"data": -1, "gas": 0, "value": 1}, - "network": [">=Cancun"], - "result": { - sender: Account(nonce=1), - contract_0: Account(storage={1: 1}, balance=11, nonce=1), - }, - }, - { - "indexes": {"data": -1, "gas": 1, "value": -1}, - "network": [">=Cancun"], - "result": { - sender: Account(nonce=1), - contract_0: Account(storage={}, balance=10, nonce=0), - }, - }, - ] - - post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork) - - tx_data = [ - Op.SSTORE(key=0x1, value=0x1), - ] - tx_gas = [600000, 54000] - tx_value = [0, 1] + sender = pre.fund_eoa() + created = compute_create_address(address=sender, nonce=0) + pre.fund_address(created, PREFUND) tx = Transaction( sender=sender, to=None, - data=tx_data[d], - gas_limit=tx_gas[g], - value=tx_value[v], - error=_exc, + data=initcode, + gas_limit=gas_limit, + value=tx_value, ) - state_test(env=env, pre=pre, post=post, tx=tx) + if oog: + # Creation rolled back: prefund kept, no value, nonce untouched. + created_account = Account( + storage={}, code=b"", nonce=0, balance=PREFUND + ) + else: + created_account = Account( + storage={1: 1}, code=b"", nonce=1, balance=PREFUND + tx_value + ) + + post = { + sender: Account(nonce=1), + created: created_account, + } + + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stCreateTest/test_transaction_collision_to_empty_but_code.py b/tests/ported_static/stCreateTest/test_transaction_collision_to_empty_but_code.py deleted file mode 100644 index 468a026fc98..00000000000 --- a/tests/ported_static/stCreateTest/test_transaction_collision_to_empty_but_code.py +++ /dev/null @@ -1,149 +0,0 @@ -""" -Test_transaction_collision_to_empty_but_code. - -Ported from: -state_tests/stCreateTest/TransactionCollisionToEmptyButCodeFiller.json -""" - -import pytest -from execution_testing import ( - EOA, - Account, - Address, - Alloc, - Environment, - Header, - StateTestFiller, - Transaction, -) -from execution_testing.forks import Fork -from execution_testing.vm import Op - -from tests.ported_static.post_state_resolution import ( - resolve_expect_post, -) - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - ["state_tests/stCreateTest/TransactionCollisionToEmptyButCodeFiller.json"], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.parametrize( - "d, g, v", - [ - pytest.param( - 0, - 0, - 0, - id="-g0-v0", - ), - pytest.param( - 0, - 0, - 1, - id="-g0-v1", - ), - pytest.param( - 0, - 1, - 0, - id="-g1-v0", - ), - pytest.param( - 0, - 1, - 1, - id="-g1-v1", - ), - ], -) -@pytest.mark.pre_alloc_mutable -def test_transaction_collision_to_empty_but_code( - state_test: StateTestFiller, - pre: Alloc, - fork: Fork, - d: int, - g: int, - v: int, -) -> None: - """Test_transaction_collision_to_empty_but_code.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0x6295EE1B4F6DD65047762F924ECD367C17EABF8F) - sender = EOA( - key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8 - ) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - pre[sender] = Account(balance=0xE8D4A51000) - # Source: raw - # 0x1122334455 - contract_0 = pre.deploy_contract( # noqa: F841 - code=bytes.fromhex("1122334455"), - nonce=0, - address=Address(0x6295EE1B4F6DD65047762F924ECD367C17EABF8F), # noqa: E501 - ) - - expect_entries_: list[dict] = [ - { - "indexes": {"data": -1, "gas": 0, "value": -1}, - "network": [">=Cancun"], - "result": { - sender: Account(nonce=1), - contract_0: Account( - storage={1: 0}, - code=bytes.fromhex("1122334455"), - nonce=0, - ), - }, - }, - { - "indexes": {"data": -1, "gas": 1, "value": -1}, - "network": [">=Cancun"], - "result": { - sender: Account(nonce=1), - contract_0: Account( - storage={}, - code=bytes.fromhex("1122334455"), - nonce=0, - ), - }, - }, - ] - - post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork) - - tx_data = [ - Op.SSTORE(key=0x1, value=0x1), - ] - tx_gas = [600000, 54000] - tx_value = [0, 1] - - tx = Transaction( - sender=sender, - to=None, - data=tx_data[d], - gas_limit=tx_gas[g], - value=tx_value[v], - error=_exc, - ) - - state_test( - env=env, - pre=pre, - post=post, - tx=tx, - blockchain_test_header_verify=Header( - gas_used=tx_gas[g], - ), - ) diff --git a/tests/ported_static/stCreateTest/test_transaction_collision_to_empty_but_nonce.py b/tests/ported_static/stCreateTest/test_transaction_collision_to_empty_but_nonce.py deleted file mode 100644 index 14a3c066470..00000000000 --- a/tests/ported_static/stCreateTest/test_transaction_collision_to_empty_but_nonce.py +++ /dev/null @@ -1,116 +0,0 @@ -""" -Test_transaction_collision_to_empty_but_nonce. - -Ported from: -state_tests/stCreateTest/TransactionCollisionToEmptyButNonceFiller.json -""" - -import pytest -from execution_testing import ( - EOA, - Account, - Address, - Alloc, - Environment, - Header, - StateTestFiller, - Transaction, -) -from execution_testing.forks import Fork -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - [ - "state_tests/stCreateTest/TransactionCollisionToEmptyButNonceFiller.json" # noqa: E501 - ], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.parametrize( - "d, g, v", - [ - pytest.param( - 0, - 0, - 0, - id="-g0-v0", - ), - pytest.param( - 0, - 0, - 1, - id="-g0-v1", - ), - pytest.param( - 0, - 1, - 0, - id="-g1-v0", - ), - pytest.param( - 0, - 1, - 1, - id="-g1-v1", - ), - ], -) -@pytest.mark.pre_alloc_mutable -def test_transaction_collision_to_empty_but_nonce( - state_test: StateTestFiller, - pre: Alloc, - fork: Fork, - d: int, - g: int, - v: int, -) -> None: - """Test_transaction_collision_to_empty_but_nonce.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0x6295EE1B4F6DD65047762F924ECD367C17EABF8F) - sender = EOA( - key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8 - ) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - pre[sender] = Account(balance=0xE8D4A51000) - pre[contract_0] = Account(balance=0, nonce=1) - - tx_data = [ - Op.SSTORE(key=0x1, value=0x1), - ] - tx_gas = [600000, 54000] - tx_value = [0, 1] - - tx = Transaction( - sender=sender, - to=None, - data=tx_data[d], - gas_limit=tx_gas[g], - value=tx_value[v], - ) - - post = { - sender: Account(nonce=1), - contract_0: Account(storage={1: 0}, nonce=1), - } - - state_test( - env=env, - pre=pre, - post=post, - tx=tx, - blockchain_test_header_verify=Header( - gas_used=tx_gas[g], - ), - ) diff --git a/tests/ported_static/stEIP150Specific/test_call_ask_more_gas_on_depth2_then_transaction_has.py b/tests/ported_static/stEIP150Specific/test_call_ask_more_gas_on_depth2_then_transaction_has.py deleted file mode 100644 index 20371a9ce34..00000000000 --- a/tests/ported_static/stEIP150Specific/test_call_ask_more_gas_on_depth2_then_transaction_has.py +++ /dev/null @@ -1,111 +0,0 @@ -""" -Verify the EIP-150 63/64 clamp at call depth 2: a first-level call receives -its exact (affordable) ask, and its own oversized ask is clamped to 63/64 -of what remains in that frame. - -Ported from: -state_tests/stEIP150Specific/CallAskMoreGasOnDepth2ThenTransactionHasFiller.json - -@manually-enhanced: Do not overwrite. The lower frames return their -observed GAS up the stack instead of SSTORE-ing it (the ported lower-frame -gas snapshots are EIP-8037 state-gas traps), and both expectations are -derived from the fork: the depth-1 frame sees exactly its asked budget, -the depth-2 frame sees `base - base // 64` of the depth-1 remainder. -""" - -import pytest -from execution_testing import ( - Account, - Alloc, - Fork, - StateTestFiller, - Transaction, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - -FLAG_SLOT = 0x0 -DEPTH2_GAS_SLOT = 0x1 -DEPTH1_GAS_SLOT = 0x2 - -# The ported depth-1 budget: affordable, so it is forwarded exactly. -CALLER_GAS = 0x30D40 -# The ported depth-2 ask: above anything the depth-1 frame can hold, so -# the 63/64 clamp decides what the depth-2 frame receives. -ASK_GAS = 0x927C0 - - -@pytest.mark.ported_from( - [ - "state_tests/stEIP150Specific/CallAskMoreGasOnDepth2ThenTransactionHasFiller.json" # noqa: E501 - ], -) -@pytest.mark.valid_from("Berlin") -def test_call_ask_more_gas_on_depth2_then_transaction_has( - state_test: StateTestFiller, - pre: Alloc, - fork: Fork, -) -> None: - """A depth-2 call asking above the frame budget gets 63/64 of it.""" - # Depth 2: returns the gas it observed on entry. - gas_return_contract = pre.deploy_contract( - code=Op.MSTORE(0, Op.GAS, new_memory_size=0x20) + Op.RETURN(0, 0x20), - ) - - # Depth 1: records its own entry gas, then asks depth 2 for more gas - # than this frame holds; both observations return to the top frame. - entry_snapshot = Op.MSTORE(0x20, Op.GAS, new_memory_size=0x40) - depth2_call = Op.CALL( - gas=ASK_GAS, - address=gas_return_contract, - ret_size=0x20, - address_warm=False, - account_new=False, - new_memory_size=0x40, - old_memory_size=0x40, - ) - caller = pre.deploy_contract( - code=entry_snapshot + depth2_call + Op.RETURN(0, 0x40), - ) - - # Top frame: forwards the exact (affordable) depth-1 budget and stores - # the success flag plus both returned observations. - entry = pre.deploy_contract( - code=Op.SSTORE( - key=FLAG_SLOT, - value=Op.CALL(gas=CALLER_GAS, address=caller, ret_size=0x40), - ) - + Op.SSTORE(key=DEPTH2_GAS_SLOT, value=Op.MLOAD(0)) - + Op.SSTORE(key=DEPTH1_GAS_SLOT, value=Op.MLOAD(0x20)), - ) - - tx = Transaction( - sender=pre.fund_eoa(), - to=entry, - state_gas_reservoir=0, - ) - - # Depth 1 received exactly CALLER_GAS; its snapshot reads it minus the - # GAS opcode itself. The depth-2 base is what remains after the - # snapshot and the call's own costs, clamped by EIP-150. - depth1_observed = CALLER_GAS - Op.GAS.gas_cost(fork) - base = ( - CALLER_GAS - entry_snapshot.gas_cost(fork) - depth2_call.gas_cost(fork) - ) - assert 0 < base < ASK_GAS, "the 63/64 clamp must apply at depth 2" - forwarded = base - base // 64 - depth2_observed = forwarded - Op.GAS.gas_cost(fork) - - post = { - entry: Account( - storage={ - FLAG_SLOT: 1, - DEPTH2_GAS_SLOT: depth2_observed, - DEPTH1_GAS_SLOT: depth1_observed, - }, - ), - } - - state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP150Specific/test_call_asks_more_gas_than_available.py b/tests/ported_static/stEIP150Specific/test_call_asks_more_gas_than_available.py new file mode 100644 index 00000000000..af930ca74ee --- /dev/null +++ b/tests/ported_static/stEIP150Specific/test_call_asks_more_gas_than_available.py @@ -0,0 +1,254 @@ +""" +Verify EIP-150's clamp on a call's gas operand: a frame may hand down at +most all but one 64th of what it holds, so an ask above that is silently +reduced rather than honoured or rejected. + +The callee reports the gas it actually received, which pins the forwarded +amount exactly instead of inferring it from whether the callee survived. + +Ported from: +state_tests/stEIP150Specific/ExecuteCallThatAskForeGasThenTrabsactionHasFiller.json +state_tests/stEIP150Specific/CallAskMoreGasOnDepth2ThenTransactionHasFiller.json +state_tests/stMemExpandingEIP150Calls/ExecuteCallThatAskMoreGasThenTransactionHasWithMemExpandingCallsFiller.json +state_tests/stMemExpandingEIP150Calls/CallAskMoreGasOnDepth2ThenTransactionHasWithMemExpandingCallsFiller.json +state_tests/stStaticCall/static_ExecuteCallThatAskForeGasThenTrabsactionHasFiller.json +state_tests/stStaticCall/static_CallAskMoreGasOnDepth2ThenTransactionHasFiller.json + +@manually-enhanced: Do not overwrite. The fillers proved the clamp only +indirectly -- a callee looping 50,000 times ran out, so it cannot have +received the oversized ask. Reporting GAS upward asserts the forwarded +amount to the gas, which also catches the `available * 63 // 64` form and +a window whose expansion is charged after the split rather than before. +The containment property those loops relied on -- that a callee burning +its grant leaves its caller's retention intact -- is covered separately by +`test_call_goes_oog_on_second_level`. The two depths get separate +functions because what the ask is measured against differs: at the top +frame it is what the transaction granted, one level down it is what the +frame was handed. +""" + +import pytest +from execution_testing import ( + Account, + Address, + Alloc, + Bytecode, + Fork, + Opcodes, + StateTestFiller, + Transaction, +) +from execution_testing.vm import Op + +REFERENCE_SPEC_GIT_PATH = "N/A" +REFERENCE_SPEC_VERSION = "N/A" + +OBSERVED_GAS_SLOT = 0x1 + +# The ported argument/return window. Its expansion is part of the asking +# call's own cost, so it is charged before the 63/64 split and lands in +# every expectation below. +MEM_WINDOW = 0xFF + +# What the top frame is granted, and what the nested frame is handed. Both +# are explicit: at each depth the ask is measured against one of them. +TX_GAS_LIMIT = 1_000_000 +NESTED_FRAME_GAS = 500_000 + +ASK_KINDS = ["honoured", "over_frame", "over_transaction"] + + +def reporter_code() -> Bytecode: + """Return code that reports the gas its frame received.""" + return Op.MSTORE(offset=0x0, value=Op.GAS) + Op.RETURN( + offset=0x0, size=0x20 + ) + + +def asking_call( + call_opcode: Opcodes, callee: Address, ask: int, window: int +) -> Bytecode: + """Return the call that asks `ask` gas, with the ported window.""" + return call_opcode( + gas=ask, + address=callee, + args_offset=window, + args_size=window, + ret_offset=0x0, + ret_size=0x20, + new_memory_size=max(2 * window, 0x20), + ) + + +def record_reply(call: Bytecode) -> Bytecode: + """Return code that makes `call` and stores what the callee replied.""" + return Op.POP(call) + Op.SSTORE(OBSERVED_GAS_SLOT, Op.MLOAD(offset=0x0)) + + +def forwarded_from(frame_gas: int, call: Bytecode, fork: Fork) -> int: + """ + Return the most `call` can hand down out of `frame_gas`. + + Only the call's own cost is spent by the time it executes -- whatever + the frame does afterwards is paid for out of the retention. + """ + available = frame_gas - call.gas_cost(fork) + assert available > 0, "the frame must afford the call itself" + # The EVM withholds `available // 64`, which is not the same as handing + # down `available * 63 // 64`. + return available - available // 64 + + +@pytest.mark.ported_from( + [ + "state_tests/stEIP150Specific/ExecuteCallThatAskForeGasThenTrabsactionHasFiller.json", # noqa: E501 + "state_tests/stMemExpandingEIP150Calls/ExecuteCallThatAskMoreGasThenTransactionHasWithMemExpandingCallsFiller.json", # noqa: E501 + "state_tests/stStaticCall/static_ExecuteCallThatAskForeGasThenTrabsactionHasFiller.json", # noqa: E501 + ], +) +@pytest.mark.valid_from("Berlin") +@pytest.mark.parametrize( + "memory_expansion", [False, True], ids=["flat", "mem_expansion"] +) +@pytest.mark.parametrize("ask_kind", ASK_KINDS) +@pytest.mark.with_all_call_opcodes +def test_top_frame_asks_more_gas_than_available( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + call_opcode: Opcodes, + ask_kind: str, + memory_expansion: bool, +) -> None: + """ + Verify the clamp when the transaction's own callee makes the ask. + + Here the ceiling comes from the transaction's grant, so `over_transaction` + asks for one gas more than the whole transaction was given -- which is + still just an oversized ask, not an error. + """ + callee = pre.deploy_contract(code=reporter_code()) + window = MEM_WINDOW if memory_expansion else 0 + + # The frame holds the whole grant less the intrinsic. + frame_gas = TX_GAS_LIMIT - fork.transaction_intrinsic_cost_calculator()() + # Sizing uses a placeholder ask; every candidate below assembles to the + # same length, so the cost this measures is the one that applies. + ceiling = forwarded_from( + frame_gas, asking_call(call_opcode, callee, TX_GAS_LIMIT, window), fork + ) + ask = { + "honoured": ceiling // 2, + "over_frame": ceiling + 1, + "over_transaction": TX_GAS_LIMIT + 1, + }[ask_kind] + + call = asking_call(call_opcode, callee, ask, window) + assert forwarded_from(frame_gas, call, fork) == ceiling, ( + "the ask must not change the call's own cost" + ) + caller = pre.deploy_contract(code=record_reply(call) + Op.STOP) + + tx = Transaction( + sender=pre.fund_eoa(), + to=caller, + gas_limit=TX_GAS_LIMIT, + state_gas_reservoir=0, + ) + + forwarded = min(ask, ceiling) + post = { + caller: Account( + storage={OBSERVED_GAS_SLOT: forwarded - Op.GAS.gas_cost(fork)} + ) + } + + state_test(pre=pre, post=post, tx=tx) + + +@pytest.mark.ported_from( + [ + "state_tests/stEIP150Specific/CallAskMoreGasOnDepth2ThenTransactionHasFiller.json", # noqa: E501 + "state_tests/stMemExpandingEIP150Calls/CallAskMoreGasOnDepth2ThenTransactionHasWithMemExpandingCallsFiller.json", # noqa: E501 + "state_tests/stStaticCall/static_CallAskMoreGasOnDepth2ThenTransactionHasFiller.json", # noqa: E501 + ], +) +@pytest.mark.valid_from("Berlin") +@pytest.mark.parametrize( + "memory_expansion", [False, True], ids=["flat", "mem_expansion"] +) +@pytest.mark.parametrize("ask_kind", ASK_KINDS) +@pytest.mark.with_all_call_opcodes +def test_nested_frame_asks_more_gas_than_available( + state_test: StateTestFiller, + pre: Alloc, + fork: Fork, + call_opcode: Opcodes, + ask_kind: str, + memory_expansion: bool, +) -> None: + """ + Verify the clamp when a frame one level down makes the ask. + + The ceiling now comes from what that frame was handed, not from the + transaction, so `over_transaction` overshoots by far more than + `over_frame` and yet is clamped to exactly the same amount. + """ + callee = pre.deploy_contract(code=reporter_code()) + window = MEM_WINDOW if memory_expansion else 0 + + ceiling = forwarded_from( + NESTED_FRAME_GAS, + asking_call(call_opcode, callee, TX_GAS_LIMIT, window), + fork, + ) + ask = { + "honoured": ceiling // 2, + "over_frame": ceiling + 1, + "over_transaction": TX_GAS_LIMIT + 1, + }[ask_kind] + + call = asking_call(call_opcode, callee, ask, window) + assert forwarded_from(NESTED_FRAME_GAS, call, fork) == ceiling, ( + "the ask must not change the call's own cost" + ) + # The asking frame reports upward, so the entry can store what it saw + # without the asking frame needing storage of its own. + asking_frame = pre.deploy_contract( + code=record_reply(call) + + Op.MSTORE( + offset=0x0, value=Op.SLOAD(key=OBSERVED_GAS_SLOT, key_warm=True) + ) + + Op.RETURN(offset=0x0, size=0x20) + ) + + # The entry hands the asking frame a fixed budget, so the ceiling does + # not depend on the transaction's own grant. + entry = pre.deploy_contract( + code=Op.POP( + Op.CALL( + gas=NESTED_FRAME_GAS, + address=asking_frame, + ret_offset=0x0, + ret_size=0x20, + ) + ) + + Op.SSTORE(OBSERVED_GAS_SLOT, Op.MLOAD(offset=0x0)) + + Op.STOP, + ) + + tx = Transaction( + sender=pre.fund_eoa(), + to=entry, + gas_limit=TX_GAS_LIMIT, + state_gas_reservoir=0, + ) + + forwarded = min(ask, ceiling) + observed = forwarded - Op.GAS.gas_cost(fork) + post = { + entry: Account(storage={OBSERVED_GAS_SLOT: observed}), + asking_frame: Account(storage={OBSERVED_GAS_SLOT: observed}), + } + + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP150Specific/test_call_goes_oog_on_second_level.py b/tests/ported_static/stEIP150Specific/test_call_goes_oog_on_second_level.py index 954c5e29ae7..bb732c456a4 100644 --- a/tests/ported_static/stEIP150Specific/test_call_goes_oog_on_second_level.py +++ b/tests/ported_static/stEIP150Specific/test_call_goes_oog_on_second_level.py @@ -1,26 +1,32 @@ """ -Test_call_goes_oog_on_second_level. +Verify EIP-150's "all but one 64th" retention across a three-level call +chain: a frame whose callee consumes its entire grant is left with only +`floor(N / 64)`, and whether that retention covers the frame's remaining +work is what decides how far up the chain the failure propagates. Ported from: state_tests/stEIP150Specific/CallGoesOOGOnSecondLevelFiller.json +state_tests/stMemExpandingEIP150Calls/CallGoesOOGOnSecondLevelWithMemExpandingCallsFiller.json +state_tests/stStaticCall/static_CallGoesOOGOnSecondLevelFiller.json -@manually-enhanced: Do not overwrite. The `gas_limit` is derived from -the fork intrinsic calculator instead of the original hardcoded value. -The test fixes the post-intrinsic budget that the nested Op.GAS storage -assertions (8: 0x927BE, 8: 0x213FB6) depend on, so it shifts the base -2_200_000 budget by the intrinsic delta versus the pre-EIP-2780 Cancun -baseline of 21_000 (`intrinsic - 21_000`). This stays correct across -the EIP-2780 intrinsic decomposition. Do not hardcode the gas_limit. +@manually-enhanced: Do not overwrite. The three fillers ran one program at +three budgets, landing either side of a boundary none of them located. +Here the only tuned value is the gas operand of the entry's call, set one +gas either side of the derived boundary, so the transaction budget carries +no meaning. The second level reports upward with RETURN rather than +SSTORE -- the static filler had to swap to MSTORE for exactly this +reason -- so one program serves every call opcode. The `*OnSecondLevel2*` +filler of all three directories was dropped: each asserted empty storage +on all three accounts, which only says the transaction ran out before +doing anything. """ import pytest from execution_testing import ( Account, - Address, Alloc, - Bytes, - Environment, Fork, + Opcodes, StateTestFiller, Transaction, ) @@ -29,99 +35,134 @@ REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +FLAG_SLOT = 0x9 +# Seeded into the entry's flag slot, so "the entry never ran" stays +# distinct from "it ran and its callee never reported back". +SENTINEL = 0x60A7 + +# The ported argument/return window, used by the `mem_expansion` cases. +# It is not decoration: its expansion is part of each CALL's own cost, and +# so part of the boundary below -- mispricing it by a gas flips a case. +MEM_WINDOW = 0xFF + +# The ported memory bomb: the third level can never afford it, so it burns +# its whole grant and returns nothing. That is what leaves its caller on +# the bare 1/64 retention. +MEM_BOMB = 0x2FFFFF + @pytest.mark.ported_from( - ["state_tests/stEIP150Specific/CallGoesOOGOnSecondLevelFiller.json"], + [ + "state_tests/stEIP150Specific/CallGoesOOGOnSecondLevelFiller.json", + "state_tests/stMemExpandingEIP150Calls/CallGoesOOGOnSecondLevelWithMemExpandingCallsFiller.json", # noqa: E501 + "state_tests/stStaticCall/static_CallGoesOOGOnSecondLevelFiller.json", + ], ) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable +@pytest.mark.valid_from("Berlin") +@pytest.mark.parametrize( + "memory_expansion", [False, True], ids=["flat", "mem_expansion"] +) +@pytest.mark.parametrize("second_level", ["survives", "starved"]) +@pytest.mark.with_all_call_opcodes def test_call_goes_oog_on_second_level( state_test: StateTestFiller, pre: Alloc, fork: Fork, + call_opcode: Opcodes, + second_level: str, + memory_expansion: bool, ) -> None: - """Test_call_goes_oog_on_second_level.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, + """One gas of retention decides whether the second level survives.""" + # Third level: burns its entire grant on a memory bomb it can never + # afford, so it returns nothing to its caller. + third_level = pre.deploy_contract( + code=Op.POP(Op.SHA3(offset=0x0, size=MEM_BOMB)) + Op.STOP ) - # Source: lll - # { (SSTORE 8 (GAS)) (KECCAK256 0x00 0x2fffff) (SSTORE 9 (GAS)) (SSTORE 10 (GAS)) } # noqa: E501 - addr_2 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x8, value=Op.GAS) - + Op.POP(Op.SHA3(offset=0x0, size=0x2FFFFF)) - + Op.SSTORE(key=0x9, value=Op.GAS) - + Op.SSTORE(key=0xA, value=Op.GAS) - + Op.STOP, - nonce=0, + # At zero the window costs nothing; at MEM_WINDOW its expansion is + # what the `*WithMemExpandingCalls` fillers exist to exercise. + window = MEM_WINDOW if memory_expansion else 0 + + # Only this call's window matters: its expansion is charged before the + # 63/64 split and so lands in the boundary below. The filler asked a + # fixed 600,000 here, a budget sized for the old schedule that the + # boundary outgrows on EIP-8037 forks; asking for everything leaves the + # clamp -- the actual subject -- in charge on every fork. + second_call = Op.CALL( + gas=Op.GAS, + address=third_level, + args_offset=window, + args_size=window, + ret_offset=window, + ret_size=window, + address_warm=False, + account_new=False, + new_memory_size=2 * window, ) - # Source: lll - # { (SSTORE 8 (GAS)) (SSTORE 9 (CALL 300000 <contract:0x1000000000000000000000000000000000000111> 0 0 0 0 0)) [[12]] 1} # noqa: E501 - addr = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x8, value=Op.GAS) - + Op.SSTORE( - key=0x9, - value=Op.CALL( - gas=0x493E0, - address=addr_2, - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.SSTORE(key=0xC, value=0x1) - + Op.STOP, - nonce=0, + # The second level reports its result upward instead of storing it, so + # the same program runs even when the caller used STATICCALL. Offset by + # one, so a caller that receives nothing can tell that apart from a + # frame that ran and saw its own callee fail. + memory_after_call = 2 * window + report_memory = max(memory_after_call, 0x20) + report = Op.MSTORE( + offset=0x0, + value=Op.ADD(second_call, 1), + new_memory_size=report_memory, + old_memory_size=memory_after_call, + ) + Op.RETURN( + offset=0x0, + size=0x20, + new_memory_size=report_memory, + old_memory_size=report_memory, ) - # Source: lll - # { (SSTORE 8 (GAS)) (SSTORE 9 (CALL 600000 <contract:0x1000000000000000000000000000000000000110> 0 0 0 0 0)) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x8, value=Op.GAS) - + Op.SSTORE( - key=0x9, - value=Op.CALL( - gas=0x927C0, - address=addr, - value=0x0, - args_offset=0x0, - args_size=0x0, + second = pre.deploy_contract(code=report) + + # Everything the second level runs before its call: the pushed offset + # and the call itself. The rest is what it must still hold afterwards. + before_call = Op.PUSH1[0].gas_cost(fork) + second_call.gas_cost(fork) + retention_needed = report.gas_cost(fork) - before_call + # A frame has to cover everything it runs, plus the 63 parts the 63/64 + # split hands down for every 1 it keeps back. + boundary = report.gas_cost(fork) + 63 * retention_needed + + entry_call_gas = boundary if second_level == "survives" else boundary - 1 + # No window here: what the second level receives is this fixed operand, + # so the entry's own memory costs cannot move the boundary. + entry = pre.deploy_contract( + code=Op.POP( + call_opcode( + gas=entry_call_gas, + address=second, ret_offset=0x0, - ret_size=0x0, - ), + ret_size=0x20, + ) ) + + Op.SSTORE(FLAG_SLOT, Op.MLOAD(offset=0x0)) + Op.STOP, - nonce=0, + storage={FLAG_SLOT: SENTINEL}, ) - # The original test was built against Cancun's ``TX_BASE`` of - # 21_000. EIP-2780 lowers the intrinsic for non-self non-value - # txs, so shift ``gas_limit`` by the intrinsic delta to preserve - # the post-intrinsic execution budget the Op.GAS storage - # assertions depend on. - intrinsic = fork.transaction_intrinsic_cost_calculator()() - gas_limit = 2_200_000 + (intrinsic - 21_000) - + # The transaction budget is deliberately not a boundary: maxing it out + # leaves the entry's CALL operand as the only tuned value. The reservoir + # is pinned to zero so a frame's state gas is charged against its own + # gas_left, which is what the retention below is measured in. tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=gas_limit, + sender=pre.fund_eoa(), + to=entry, + state_gas_reservoir=0, ) post = { - addr: Account(storage={8: 0x927BE, 12: 1}), - addr_2: Account(storage={}), - target: Account(storage={8: 0x213FB6, 9: 1}), + # 1 == the second level reported back, 0 == it never got that far. + # The seed would survive only if the entry itself had not run. + entry: Account( + storage={FLAG_SLOT: 1 if second_level == "survives" else 0} + ), + # Neither lower frame writes storage, which is what lets this run + # under STATICCALL at all. + second: Account(storage={}), + third_level: Account(storage={}), } - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP150Specific/test_call_goes_oog_on_second_level2.py b/tests/ported_static/stEIP150Specific/test_call_goes_oog_on_second_level2.py deleted file mode 100644 index 186b2ad0636..00000000000 --- a/tests/ported_static/stEIP150Specific/test_call_goes_oog_on_second_level2.py +++ /dev/null @@ -1,106 +0,0 @@ -""" -Test_call_goes_oog_on_second_level2. - -Ported from: -state_tests/stEIP150Specific/CallGoesOOGOnSecondLevel2Filler.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - ["state_tests/stEIP150Specific/CallGoesOOGOnSecondLevel2Filler.json"], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_call_goes_oog_on_second_level2( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_call_goes_oog_on_second_level2.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - # Source: lll - # { (SSTORE 8 (GAS)) (KECCAK256 0x00 0x2fffff) } - addr_2 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x8, value=Op.GAS) - + Op.SHA3(offset=0x0, size=0x2FFFFF) - + Op.STOP, - nonce=0, - ) - # Source: lll - # { (SSTORE 8 (GAS)) (SSTORE 9 (CALL 600000 <contract:0x1000000000000000000000000000000000000114> 0 0 0 0 0)) } # noqa: E501 - addr = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x8, value=Op.GAS) - + Op.SSTORE( - key=0x9, - value=Op.CALL( - gas=0x927C0, - address=addr_2, - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.STOP, - nonce=0, - ) - # Source: lll - # { (SSTORE 8 (GAS)) (SSTORE 9 (CALL 600000 <contract:0x1000000000000000000000000000000000000113> 0 0 0 0 0)) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x8, value=Op.GAS) - + Op.SSTORE( - key=0x9, - value=Op.CALL( - gas=0x927C0, - address=addr, - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.STOP, - nonce=0, - ) - - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=160000, - ) - - post = { - addr: Account(storage={}), - addr_2: Account(storage={}), - target: Account(storage={}), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP150Specific/test_create_and_gas_inside_create.py b/tests/ported_static/stEIP150Specific/test_create_and_gas_inside_create.py index 0bcf52c7d85..6544855c54c 100644 --- a/tests/ported_static/stEIP150Specific/test_create_and_gas_inside_create.py +++ b/tests/ported_static/stEIP150Specific/test_create_and_gas_inside_create.py @@ -1,17 +1,27 @@ """ -Test_create_and_gas_inside_create. +Verify the gas a CREATE's init code observes: the child receives all but +one 64th of what remains in the creating frame, and the parent's CREATE +cost is measured alongside it. Ported from: state_tests/stEIP150Specific/CreateAndGasInsideCreateFiller.json +state_tests/stMemExpandingEIP150Calls/CreateAndGasInsideCreateWithMemExpandingCallsFiller.json + +@manually-enhanced: Do not overwrite. An outer call pins the creating +frame's budget so the child's stored GAS observation is fork-derived +(`63/64` of the derived base); the parent measures the CREATE with +CodeGasMeasure instead of raw snapshots, which subsumes the raw entry and +post-CREATE snapshots the second filler stored. Neither filler varied the +one thing its directory is named for -- whether the CREATE's own window +grows memory -- so that is the parametrized axis here. """ import pytest from execution_testing import ( Account, - Address, Alloc, - Bytes, - Environment, + CodeGasMeasure, + Fork, StateTestFiller, Transaction, compute_create_address, @@ -21,58 +31,122 @@ REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +ADDRESS_SLOT = 0xB +GAS_SLOT = 0x9 +CHILD_GAS_SLOT = 0xFD + +# The creating frame's pinned budget (the ported transaction's). +CALLER_GAS = 600_000 + +# How far the CREATE's window runs past the word the setup already paid +# for, in the expanding case. A CREATE always reads its init code from +# memory, so the axis cannot be "touches memory or not" -- it is whether +# the CREATE is itself charged the growth, which happens before the 63/64 +# withhold and so lands in what the child observes. +MEM_EXPANSION_BYTES = 0x20 + @pytest.mark.ported_from( - ["state_tests/stEIP150Specific/CreateAndGasInsideCreateFiller.json"], + [ + "state_tests/stEIP150Specific/CreateAndGasInsideCreateFiller.json", + "state_tests/stMemExpandingEIP150Calls/CreateAndGasInsideCreateWithMemExpandingCallsFiller.json", # noqa: E501 + ], +) +@pytest.mark.valid_from("Berlin") +@pytest.mark.parametrize( + "memory_expansion", [False, True], ids=["flat", "mem_expansion"] ) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable def test_create_and_gas_inside_create( state_test: StateTestFiller, pre: Alloc, + fork: Fork, + memory_expansion: bool, ) -> None: - """Test_create_and_gas_inside_create.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B) - sender = pre.fund_eoa(amount=0xE8D4A51000) + """A CREATE's init code observes 63/64 of the creating frame's gas.""" + # Child init code: stores the gas it observes into its own storage + # and deposits no code. + child_code = Op.SSTORE( + key=CHILD_GAS_SLOT, + value=Op.GAS, + key_warm=False, + original_value=0, + new_value=1, + ) - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, + # The child bytes sit right-aligned in the first memory word. + setup = Op.MSTORE( + offset=0x0, + value=int.from_bytes(child_code, "big"), + new_memory_size=0x20, + ) + # Padding the window with bytes the setup never wrote leaves the child + # unchanged -- they read as zero, so they are STOPs after its store -- + # while forcing the CREATE to pay for the growth. + padding = MEM_EXPANSION_BYTES if memory_expansion else 0 + create_code = Op.CREATE( + value=0x0, + offset=0x20 - len(child_code), + size=len(child_code) + padding, + new_memory_size=0x20 + padding, + old_memory_size=0x20, + init_code_size=len(child_code) + padding, + ) + create_store = Op.SSTORE( + key=ADDRESS_SLOT, + value=create_code, + key_warm=False, + original_value=0, + new_value=1, + ) + creator = pre.deploy_contract( + code=setup + + CodeGasMeasure( + code=create_store, + extra_stack_items=0, + sstore_key=GAS_SLOT, + ), ) - # Source: lll - # { [100] (GAS) (MSTORE 0 0x5a60fd55) (SSTORE 11 (CREATE 0 28 4)) (SSTORE 9 (SUB @100 (GAS))) } # noqa: E501 - contract_0 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x64, value=Op.GAS) - + Op.MSTORE(offset=0x0, value=0x5A60FD55) - + Op.SSTORE(key=0xB, value=Op.CREATE(value=0x0, offset=0x1C, size=0x4)) - + Op.SSTORE(key=0x9, value=Op.SUB(Op.MLOAD(offset=0x64), Op.GAS)) + # The outer call pins the creating frame's budget so the child's + # observation does not depend on the tx gas limit. + entry = pre.deploy_contract( + code=Op.SSTORE(key=0x0, value=Op.CALL(gas=CALLER_GAS, address=creator)) + Op.STOP, - nonce=0, ) tx = Transaction( - sender=sender, - to=contract_0, - data=Bytes(""), - gas_limit=600000, + sender=pre.fund_eoa(), + to=entry, + state_gas_reservoir=0, + ) + + # The child receives all but one 64th of what remains after the + # setup, the measuring GAS read, and the CREATE's own charges (its + # new-account state gas is taken before the withhold). + base = ( + CALLER_GAS + - setup.gas_cost(fork) + - Op.GAS.gas_cost(fork) + - create_code.gas_cost(fork) ) + assert base > 0, "CALLER_GAS must cover the CREATE's charges" + child_observed = (base - base // 64) - Op.GAS.gas_cost(fork) + measured_create = create_store.gas_cost(fork) + child_code.gas_cost(fork) + created = compute_create_address(address=creator, nonce=1) post = { - contract_0: Account( + entry: Account(storage={0: 1}), + creator: Account( storage={ - 9: 0x129DB, - 11: compute_create_address(address=contract_0, nonce=0), + ADDRESS_SLOT: created, + GAS_SLOT: measured_create, }, ), - compute_create_address(address=contract_0, nonce=0): Account( - storage={253: 0x83729} + created: Account( + nonce=1, + code=b"", + storage={CHILD_GAS_SLOT: child_observed}, ), } - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP150Specific/test_delegate_call_on_eip.py b/tests/ported_static/stEIP150Specific/test_delegate_call_on_eip.py index 5156516411b..89d33ae0248 100644 --- a/tests/ported_static/stEIP150Specific/test_delegate_call_on_eip.py +++ b/tests/ported_static/stEIP150Specific/test_delegate_call_on_eip.py @@ -1,17 +1,23 @@ """ -Test_delegate_call_on_eip. +Measure a DELEGATECALL that asks for more gas than its frame holds: the +EIP-150 clamp decides the grant, the delegate writes into the caller's +storage, and the measured cost is the call plus the delegate's work. Ported from: state_tests/stEIP150Specific/DelegateCallOnEIPFiller.json + +@manually-enhanced: Do not overwrite. An outer call pins the frame budget +so the oversized ask always clamps; the DELEGATECALL is measured with +CodeGasMeasure (success flag inside the window) and the expectation is the +composite plus the delegate's fork-priced store. """ import pytest from execution_testing import ( Account, - Address, Alloc, - Bytes, - Environment, + CodeGasMeasure, + Fork, StateTestFiller, Transaction, ) @@ -20,62 +26,79 @@ REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +DELEGATE_VALUE = 0x12 +FLAG_SLOT = 0x9 +GAS_SLOT = 0x8 + +# The ported ask (600000): above the pinned frame budget, so the EIP-150 +# clamp decides the grant on every fork. +ASK_GAS = 0x927C0 +CALLER_GAS = 400_000 + @pytest.mark.ported_from( ["state_tests/stEIP150Specific/DelegateCallOnEIPFiller.json"], ) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable +@pytest.mark.valid_from("Berlin") def test_delegate_call_on_eip( state_test: StateTestFiller, pre: Alloc, + fork: Fork, ) -> None: - """Test_delegate_call_on_eip.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, + """Measure a clamped DELEGATECALL running a store in the caller.""" + # Runs in the caller's storage context: one cold fresh store. + delegate_store = Op.SSTORE( + key=0x0, + value=DELEGATE_VALUE, + key_warm=False, + original_value=0, + new_value=DELEGATE_VALUE, ) + delegate = pre.deploy_contract(code=delegate_store + Op.STOP) - # Source: lll - # { (SSTORE 0 0x12) } - addr = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=0x12) + Op.STOP, - nonce=0, + delegatecall_code = Op.DELEGATECALL( + gas=ASK_GAS, + address=delegate, + address_warm=False, ) - # Source: lll - # { [8] (GAS) (SSTORE 9 (DELEGATECALL 600000 <contract:0x1000000000000000000000000000000000000105> 0 0 0 0)) [[8]] (SUB @8 (GAS)) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x8, value=Op.GAS) - + Op.SSTORE( - key=0x9, - value=Op.DELEGATECALL( - gas=0x927C0, - address=addr, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.SSTORE(key=0x8, value=Op.SUB(Op.MLOAD(offset=0x8), Op.GAS)) + flag_store = Op.SSTORE( + key=FLAG_SLOT, + value=delegatecall_code, + key_warm=False, + original_value=0, + new_value=1, + ) + target = pre.deploy_contract( + code=CodeGasMeasure( + code=flag_store, + extra_stack_items=0, + sstore_key=GAS_SLOT, + ), + ) + + assert CALLER_GAS < ASK_GAS, "the 63/64 clamp must apply" + entry = pre.deploy_contract( + code=Op.SSTORE(key=0x0, value=Op.CALL(gas=CALLER_GAS, address=target)) + Op.STOP, - nonce=0, ) tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=600000, + sender=pre.fund_eoa(), + to=entry, + state_gas_reservoir=0, ) - post = {target: Account(storage={0: 18, 8: 46841, 9: 1})} + measured = flag_store.gas_cost(fork) + delegate_store.gas_cost(fork) + + post = { + entry: Account(storage={0: 1}), + target: Account( + storage={ + 0: DELEGATE_VALUE, + GAS_SLOT: measured, + FLAG_SLOT: 1, + }, + ), + } - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stEIP150Specific/test_execute_call_that_ask_fore_gas_then_trabsaction_has.py b/tests/ported_static/stEIP150Specific/test_execute_call_that_ask_fore_gas_then_trabsaction_has.py deleted file mode 100644 index f85f2c52531..00000000000 --- a/tests/ported_static/stEIP150Specific/test_execute_call_that_ask_fore_gas_then_trabsaction_has.py +++ /dev/null @@ -1,86 +0,0 @@ -""" -Test_execute_call_that_ask_fore_gas_then_trabsaction_has. - -Ported from: -state_tests/stEIP150Specific/ExecuteCallThatAskForeGasThenTrabsactionHasFiller.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - Fork, - StateTestFiller, - Transaction, -) -from execution_testing.forks import Amsterdam -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - [ - "state_tests/stEIP150Specific/ExecuteCallThatAskForeGasThenTrabsactionHasFiller.json" # noqa: E501 - ], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_execute_call_that_ask_fore_gas_then_trabsaction_has( - state_test: StateTestFiller, - fork: Fork, - pre: Alloc, -) -> None: - """Test_execute_call_that_ask_fore_gas_then_trabsaction_has.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0x5F5E100) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - # Source: lll - # { [[1]] 12 } - addr = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x1, value=0xC) + Op.STOP, - balance=0x186A0, - nonce=0, - ) - # Source: lll - # { [[1]] (CALL 600000 <contract:0x1000000000000000000000000000000000000001> 0 0 0 0 0) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE( - key=0x1, - value=Op.CALL( - gas=0x927C0, - address=addr, - value=0x0, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.STOP, - nonce=0, - ) - - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=2100000 if fork >= Amsterdam else 100000, - ) - - post = {addr: Account(storage={1: 12})} - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stInitCodeTest/test_out_of_gas_contract_creation.py b/tests/ported_static/stInitCodeTest/test_out_of_gas_contract_creation.py index f346844b949..14564b722b7 100644 --- a/tests/ported_static/stInitCodeTest/test_out_of_gas_contract_creation.py +++ b/tests/ported_static/stInitCodeTest/test_out_of_gas_contract_creation.py @@ -1,150 +1,152 @@ """ -Test_out_of_gas_contract_creation. +Verify a contract-creation transaction whose init code runs out of gas (or +halts on invalid code) leaves no account behind, while a sufficient budget +creates it. Ported from: state_tests/stInitCodeTest/OutOfGasContractCreationFiller.json + +@manually-enhanced: Do not overwrite. The two budgets sit one gas apart on +a boundary derived entirely from the fork: the intrinsic actually deducted +before execution, the created account's top-frame state gas, the init +code's metadata-priced cost, and the headroom EIP-2200's minimum-gas gate +demands of the last store. The success post pins the final storage value, +not just the nonce. """ import pytest from execution_testing import ( Account, - Address, Alloc, - Environment, + Bytecode, + Fork, StateTestFiller, Transaction, compute_create_address, ) -from execution_testing.forks import Fork from execution_testing.vm import Op -from tests.ported_static.post_state_resolution import ( - resolve_expect_post, -) - REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +TX_VALUE = 1 +MAX_VALUE_SSTORED = 6 + + +def storage_writes_initcode() -> Bytecode: + """Return init code that sets one slot, then rewrites it while dirty.""" + code = Op.SSTORE( + key=0x1, value=0x1, key_warm=False, original_value=0, new_value=1 + ) + for value in range(2, MAX_VALUE_SSTORED + 1): + code += Op.SSTORE( + key=0x1, + value=value, + key_warm=True, + original_value=0, + current_value=value - 1, + new_value=value, + ) + return code + + +def stack_underflow_initcode() -> Bytecode: + """The ported junk init code: CALLCODE underflows the stack.""" + return ( + Op.PUSH1[0xA] + + Op.CODECOPY(dest_offset=0x0, offset=0xC, size=Op.DUP1) + + Op.PUSH1[0x0] + + Op.CALLCODE + + Op.STOP + + Op.PUSH1[0x1] + + Op.PUSH1[0x0] + + Op.BYTE(Op.DUP2, Op.CALLDATALOAD(offset=Op.DUP1)) + + Op.DUP2 + + Op.STOP + ) + @pytest.mark.ported_from( ["state_tests/stInitCodeTest/OutOfGasContractCreationFiller.json"], ) -@pytest.mark.valid_from("Cancun") +@pytest.mark.valid_from("Berlin") +@pytest.mark.parametrize( + "invalid_initcode", + [ + pytest.param(True, id="invalid_initcode"), + pytest.param(False, id="valid_initcode"), + ], +) @pytest.mark.parametrize( - "d, g, v", + "enough_gas", [ - pytest.param( - 0, - 0, - 0, - id="d0-g0", - ), - pytest.param( - 0, - 1, - 0, - id="d0-g1", - ), - pytest.param( - 1, - 0, - 0, - id="d1-g0", - ), - pytest.param( - 1, - 1, - 0, - id="d1-g1", - ), + pytest.param(False, id="insufficient_gas"), + pytest.param(True, id="sufficient_gas"), ], ) def test_out_of_gas_contract_creation( state_test: StateTestFiller, pre: Alloc, fork: Fork, - d: int, - g: int, - v: int, + invalid_initcode: bool, + enough_gas: bool, ) -> None: - """Test_out_of_gas_contract_creation.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa( - amount=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF # noqa: E501 - ) + """An under-budgeted or invalid init code creates no account.""" + if invalid_initcode: + initcode = stack_underflow_initcode() + else: + initcode = storage_writes_initcode() - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=100000000000000, + # EIP-8037 charges the created account's state gas to the creation + # transaction's top frame. The intrinsic is asked for what is deducted + # before execution, since the default folds in the calldata floor, which + # is only compared against once the transaction is already done. + overhead = fork.transaction_intrinsic_cost_calculator()( + calldata=initcode, + contract_creation=True, + sends_value=TX_VALUE > 0, + return_cost_deducted_prior_execution=True, + ) + fork.transaction_top_frame_state_gas( + contract_creation=True, sends_value=TX_VALUE > 0 ) + initcode_cost = storage_writes_initcode().gas_cost(fork) + # EIP-2200 halts any SSTORE that runs with `CALL_STIPEND` gas or less + # still available -- a gate, not a charge, so it is not part of + # `gas_cost`. The init code's last store is the one that runs closest to + # empty, so the budget has to hold more than the stipend at that point: + # its own charge plus one gas over. A bare SSTORE with no operands + # prices that charge on its own. + last_store_charge = Op.SSTORE( + key_warm=True, + original_value=0, + current_value=MAX_VALUE_SSTORED - 1, + new_value=MAX_VALUE_SSTORED, + ).gas_cost(fork) + stipend_headroom = fork.gas_costs().CALL_STIPEND - last_store_charge + 1 + gas_limit = overhead + initcode_cost + stipend_headroom + if not enough_gas: + gas_limit -= 1 - expect_entries_: list[dict] = [ - { - "indexes": {"data": 0, "gas": 1, "value": -1}, - "network": [">=Cancun"], - "result": { - sender: Account(nonce=1), - compute_create_address( - address=sender, nonce=0 - ): Account.NONEXISTENT, - }, - }, - { - "indexes": {"data": 1, "gas": 1, "value": -1}, - "network": [">=Cancun"], - "result": { - sender: Account(nonce=1), - compute_create_address(address=sender, nonce=0): Account( - nonce=1 - ), - }, - }, - { - "indexes": {"data": -1, "gas": 0, "value": -1}, - "network": [">=Cancun"], - "result": { - sender: Account(nonce=1), - compute_create_address( - address=sender, nonce=0 - ): Account.NONEXISTENT, - }, - }, - ] - - post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork) - - tx_data = [ - Op.PUSH1[0xA] - + Op.CODECOPY(dest_offset=0x0, offset=0xC, size=Op.DUP1) - + Op.PUSH1[0x0] - + Op.CALLCODE - + Op.STOP - + Op.PUSH1[0x1] - + Op.PUSH1[0x0] - + Op.BYTE(Op.DUP2, Op.CALLDATALOAD(offset=Op.DUP1)) - + Op.DUP2 - + Op.STOP, - Op.SSTORE(key=0x1, value=0x1) - + Op.SSTORE(key=0x1, value=0x2) - + Op.SSTORE(key=0x1, value=0x3) - + Op.SSTORE(key=0x1, value=0x4) - + Op.SSTORE(key=0x1, value=0x5) - + Op.SSTORE(key=0x1, value=0x6), - ] - tx_gas = [56000, 150000] - tx_value = [1] - + sender = pre.fund_eoa() tx = Transaction( sender=sender, to=None, - data=tx_data[d], - gas_limit=tx_gas[g], - value=tx_value[v], - error=_exc, + data=initcode, + gas_limit=gas_limit, + value=TX_VALUE, ) - state_test(env=env, pre=pre, post=post, tx=tx) + created = compute_create_address(address=sender, nonce=0) + if enough_gas and not invalid_initcode: + created_account: Account | None = Account( + nonce=1, code=b"", storage={1: MAX_VALUE_SSTORED}, balance=1 + ) + else: + # OOG / invalid init code: the creation is rolled back entirely. + created_account = Account.NONEXISTENT + post = { + sender: Account(nonce=1), + created: created_account, + } + + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stInitCodeTest/test_out_of_gas_prefunded_contract_creation.py b/tests/ported_static/stInitCodeTest/test_out_of_gas_prefunded_contract_creation.py index 93f7f151c9e..2b4691037b7 100644 --- a/tests/ported_static/stInitCodeTest/test_out_of_gas_prefunded_contract_creation.py +++ b/tests/ported_static/stInitCodeTest/test_out_of_gas_prefunded_contract_creation.py @@ -1,137 +1,165 @@ """ -Test_out_of_gas_prefunded_contract_creation. +Verify a contract-creation transaction targeting a prefunded address, whose +init code CREATEs a value-bearing child: the budget decides whether the +outer creation fails (prefund untouched), the child fails (value stays), +or the child succeeds (one wei moves into it). Ported from: state_tests/stInitCodeTest/OutOfGasPrefundedContractCreationFiller.json + +@manually-enhanced: Do not overwrite. The three budgets are fork-derived +and one gas apart at each boundary: the inner CREATE's own price, and the +63/64 grant the child's init code needs. Asserting the child account is +what tells the ported "balance 1" outcomes -- outer failure and child +success -- apart. """ import pytest from execution_testing import ( - EOA, Account, - Address, Alloc, - Environment, + Fork, + Hash, StateTestFiller, Transaction, + compute_create_address, ) -from execution_testing.forks import Fork from execution_testing.vm import Op -from tests.ported_static.post_state_resolution import ( - resolve_expect_post, -) - REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +PREFUND = 1 +TX_VALUE = 1 +CHILD_VALUE = 1 +CHILD_STORED = 0x112233 + + +def minimum_frame_gas(needed: int) -> int: + """ + Return the smallest frame budget whose 63/64 grant covers `needed`. + + The EVM withholds `available // 64`, which is not the same as granting + `available * 63 // 64`: the two differ by one whenever `available` is + not a multiple of 64, so the inverse is found by adjusting an estimate + rather than computed directly. + """ + available = -(-needed * 64 // 63) + while available - available // 64 < needed: + available += 1 + while (available - 1) - (available - 1) // 64 >= needed: + available -= 1 + return available + @pytest.mark.ported_from( [ "state_tests/stInitCodeTest/OutOfGasPrefundedContractCreationFiller.json" # noqa: E501 ], ) -@pytest.mark.valid_from("Cancun") +@pytest.mark.valid_from("Berlin") @pytest.mark.parametrize( - "d, g, v", - [ - pytest.param( - 0, - 0, - 0, - id="-g0", - ), - pytest.param( - 0, - 1, - 0, - id="-g1", - ), - pytest.param( - 0, - 2, - 0, - id="-g2", - ), - ], + "outcome", ["child_succeeds", "outer_oog", "child_oog"] ) -@pytest.mark.pre_alloc_mutable def test_out_of_gas_prefunded_contract_creation( state_test: StateTestFiller, pre: Alloc, fork: Fork, - d: int, - g: int, - v: int, + outcome: str, ) -> None: - """Test_out_of_gas_prefunded_contract_creation.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0x6295EE1B4F6DD65047762F924ECD367C17EABF8F) - sender = EOA( - key=0x45A915E4D060149EB4365960E6A7A45F334393093061116B197E3240065FF2D8 + """Budget decides how deep a prefunded creation's child CREATE gets.""" + # Child init code: one cold store, deposits nothing. + child_code = Op.SSTORE( + key=0x0, + value=CHILD_STORED, + key_warm=False, + original_value=0, + new_value=CHILD_STORED, ) - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=1000000000, + # Outer init code: stage the child init code -- it fits in one word -- + # then CREATE a value-bearing child from it; deposits nothing. + inner_create = Op.CREATE( + value=CHILD_VALUE, + offset=0x0, + size=len(child_code), + # Memory is already a word wide: `stage_child` paid that expansion. + new_memory_size=0x20, + old_memory_size=0x20, + init_code_size=len(child_code), ) + stage_child = Op.MSTORE( + offset=0x0, + value=Hash(child_code, right_padding=True), + new_memory_size=0x20, + ) + initcode = stage_child + Op.POP(inner_create) + Op.STOP - pre[sender] = Account(balance=0xF424000) - # Source: hex - # 0x - contract_0 = pre.deploy_contract( # noqa: F841 - code="", - balance=1, - nonce=0, - address=Address(0x6295EE1B4F6DD65047762F924ECD367C17EABF8F), # noqa: E501 + # No top-frame new-account state gas: the prefunded target is not + # EMPTY_ACCOUNT in the pre-state, which is the EIP-8037 behaviour these + # exact budgets pin. The inner CREATE's composite cost is its peak + # charge -- refunded if the child fails, but payable when charged. + overhead = ( + fork.transaction_intrinsic_cost_calculator()( + calldata=initcode, + contract_creation=True, + sends_value=TX_VALUE > 0, + return_cost_deducted_prior_execution=True, + ) + + stage_child.gas_cost(fork) + + inner_create.gas_cost(fork) ) + # Exactly enough for the child, so one gas either side of it decides + # whether the 63/64 grant covers the child's init code. + child_frame_gas = minimum_frame_gas(child_code.gas_cost(fork)) + if outcome == "outer_oog": + # One gas short of paying for the inner CREATE itself. + gas_limit = overhead - 1 + elif outcome == "child_oog": + # Outer completes; the child's grant undercuts its cost by one. + gas_limit = overhead + child_frame_gas - 1 + else: + # Child completes too and keeps the transferred wei. + gas_limit = overhead + child_frame_gas - expect_entries_: list[dict] = [ - { - "indexes": {"data": -1, "gas": [0, 1], "value": -1}, - "network": [">=Cancun"], - "result": { - sender: Account(nonce=1), - contract_0: Account(balance=1), - }, - }, - { - "indexes": {"data": -1, "gas": [2], "value": -1}, - "network": [">=Cancun"], - "result": { - sender: Account(nonce=1), - contract_0: Account(balance=2), - }, - }, - ] - - post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork) - - tx_data = [ - Op.PUSH1[0x9] - + Op.CODECOPY(dest_offset=0x0, offset=0x11, size=Op.DUP1) - + Op.PUSH1[0x0] - + Op.PUSH1[0x1] - + Op.POP(Op.CREATE) - + Op.STOP * 2 - + Op.INVALID - + Op.SSTORE(key=0x0, value=0x112233) - + Op.STOP * 2, - ] - tx_gas = [154000, 65000, 95000] - tx_value = [1] + sender = pre.fund_eoa() + created = compute_create_address(address=sender, nonce=0) + pre.fund_address(created, PREFUND) tx = Transaction( sender=sender, to=None, - data=tx_data[d], - gas_limit=tx_gas[g], - value=tx_value[v], - error=_exc, + data=initcode, + gas_limit=gas_limit, + value=TX_VALUE, ) - state_test(env=env, pre=pre, post=post, tx=tx) + child = compute_create_address(address=created, nonce=1) + if outcome == "outer_oog": + # Creation rolled back: only the prefund remains, nonce untouched. + created_account = Account(nonce=0, balance=PREFUND) + child_account: Account | None = Account.NONEXISTENT + elif outcome == "child_oog": + # The inner CREATE increments the creator's nonce even when the + # child fails. + created_account = Account( + nonce=2, code=b"", balance=PREFUND + TX_VALUE + ) + child_account = Account.NONEXISTENT + else: + created_account = Account( + nonce=2, code=b"", balance=PREFUND + TX_VALUE - CHILD_VALUE + ) + child_account = Account( + nonce=1, + balance=CHILD_VALUE, + storage={0: CHILD_STORED}, + ) + + post = { + sender: Account(nonce=1), + created: created_account, + child: child_account, + } + + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stMemExpandingEIP150Calls/test_call_ask_more_gas_on_depth2_then_transaction_has_with_mem_expanding_calls.py b/tests/ported_static/stMemExpandingEIP150Calls/test_call_ask_more_gas_on_depth2_then_transaction_has_with_mem_expanding_calls.py deleted file mode 100644 index f080fc4a5db..00000000000 --- a/tests/ported_static/stMemExpandingEIP150Calls/test_call_ask_more_gas_on_depth2_then_transaction_has_with_mem_expanding_calls.py +++ /dev/null @@ -1,137 +0,0 @@ -""" -Verify the EIP-150 63/64 clamp at call depth 2 when the calls also expand -memory: a first-level call receives its exact (affordable) ask, and its own -oversized ask is clamped to 63/64 of what remains after the memory -expansion. - -Ported from: -state_tests/stMemExpandingEIP150Calls/CallAskMoreGasOnDepth2ThenTransactionHasWithMemExpandingCallsFiller.json - -@manually-enhanced: Do not overwrite. The lower frames return their -observed GAS up the stack instead of SSTORE-ing it (the ported lower-frame -gas snapshots are EIP-8037 state-gas traps); every expectation is derived -from the fork, including the top frame's entry snapshot, which pins the -transaction intrinsic cost. -""" - -import pytest -from execution_testing import ( - Account, - Alloc, - Fork, - StateTestFiller, - Transaction, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - -FLAG_SLOT = 0x0 -DEPTH2_GAS_SLOT = 0x1 -DEPTH1_GAS_SLOT = 0x2 -ENTRY_GAS_SLOT = 0x3 - -# The ported depth-1 budget: affordable, so it is forwarded exactly. -CALLER_GAS = 0x30D40 -# The ported depth-2 ask: above anything the depth-1 frame can hold, so -# the 63/64 clamp decides what the depth-2 frame receives. -ASK_GAS = 0x927C0 -# The ported calls' argument window, driving the memory expansion. -MEM_OFFSET = 0xFF -MEM_SIZE = 0xFF - - -@pytest.mark.ported_from( - [ - "state_tests/stMemExpandingEIP150Calls/CallAskMoreGasOnDepth2ThenTransactionHasWithMemExpandingCallsFiller.json" # noqa: E501 - ], -) -@pytest.mark.valid_from("Berlin") -def test_call_ask_more_gas_on_depth2_then_transaction_has_with_mem_expanding_calls( # noqa: E501 - state_test: StateTestFiller, - pre: Alloc, - fork: Fork, -) -> None: - """A depth-2 memory-expanding call is clamped to 63/64 of its frame.""" - # Depth 2: returns the gas it observed on entry. - gas_return_contract = pre.deploy_contract( - code=Op.MSTORE(0, Op.GAS, new_memory_size=0x20) + Op.RETURN(0, 0x20), - ) - - # Depth 1: records its own entry gas, then asks depth 2 for more gas - # than this frame holds, expanding memory through the args window; - # both observations return to the top frame. - entry_snapshot = Op.MSTORE(0x20, Op.GAS, new_memory_size=0x40) - depth2_call = Op.CALL( - gas=ASK_GAS, - address=gas_return_contract, - args_offset=MEM_OFFSET, - args_size=MEM_SIZE, - ret_size=0x20, - address_warm=False, - account_new=False, - new_memory_size=MEM_OFFSET + MEM_SIZE, - old_memory_size=0x40, - ) - caller = pre.deploy_contract( - code=entry_snapshot + depth2_call + Op.RETURN(0, 0x40), - ) - - # Top frame: snapshots its entry gas (pinning the tx intrinsic), then - # forwards the exact depth-1 budget and stores the success flag plus - # both returned observations. - entry_code = ( - Op.SSTORE(key=ENTRY_GAS_SLOT, value=Op.GAS) - + Op.SSTORE( - key=FLAG_SLOT, - value=Op.CALL( - gas=CALLER_GAS, - address=caller, - ret_size=0x40, - address_warm=False, - account_new=False, - new_memory_size=0x40, - ), - ) - + Op.SSTORE(key=DEPTH2_GAS_SLOT, value=Op.MLOAD(0)) - + Op.SSTORE(key=DEPTH1_GAS_SLOT, value=Op.MLOAD(0x20)) - ) - entry = pre.deploy_contract(code=entry_code + Op.STOP) - - # Conservative fork-derived budget: the entry's own costs (incl. the - # trailing state-priced stores) plus the full depth-1 grant. - intrinsic = fork.transaction_intrinsic_cost_calculator()() - gas_limit = intrinsic + entry_code.gas_cost(fork) + CALLER_GAS - - tx = Transaction( - sender=pre.fund_eoa(), - to=entry, - gas_limit=gas_limit, - ) - - # The entry snapshot observes everything after the intrinsic; depth 1 - # received exactly CALLER_GAS; the depth-2 base is what remains after - # the snapshot and the call's own costs (incl. memory expansion), - # clamped by EIP-150. - entry_observed = gas_limit - intrinsic - Op.GAS.gas_cost(fork) - depth1_observed = CALLER_GAS - Op.GAS.gas_cost(fork) - base = ( - CALLER_GAS - entry_snapshot.gas_cost(fork) - depth2_call.gas_cost(fork) - ) - assert 0 < base < ASK_GAS, "the 63/64 clamp must apply at depth 2" - forwarded = base - base // 64 - depth2_observed = forwarded - Op.GAS.gas_cost(fork) - - post = { - entry: Account( - storage={ - ENTRY_GAS_SLOT: entry_observed, - FLAG_SLOT: 1, - DEPTH2_GAS_SLOT: depth2_observed, - DEPTH1_GAS_SLOT: depth1_observed, - }, - ), - } - - state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stMemExpandingEIP150Calls/test_call_goes_oog_on_second_level2_with_mem_expanding_calls.py b/tests/ported_static/stMemExpandingEIP150Calls/test_call_goes_oog_on_second_level2_with_mem_expanding_calls.py deleted file mode 100644 index 03fba477f8e..00000000000 --- a/tests/ported_static/stMemExpandingEIP150Calls/test_call_goes_oog_on_second_level2_with_mem_expanding_calls.py +++ /dev/null @@ -1,107 +0,0 @@ -""" -Test_call_goes_oog_on_second_level2_with_mem_expanding_calls. - -Ported from: -state_tests/stMemExpandingEIP150Calls/CallGoesOOGOnSecondLevel2WithMemExpandingCallsFiller.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - [ - "state_tests/stMemExpandingEIP150Calls/CallGoesOOGOnSecondLevel2WithMemExpandingCallsFiller.json" # noqa: E501 - ], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_call_goes_oog_on_second_level2_with_mem_expanding_calls( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_call_goes_oog_on_second_level2_with_mem_expanding_calls.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xE8D4A510000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=100000000, - ) - - # Source: hex - # 0x5a6008555a6009555a600a55 - addr = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x8, value=Op.GAS) - + Op.SSTORE(key=0x9, value=Op.GAS) - + Op.SSTORE(key=0xA, value=Op.GAS), - nonce=0, - ) - # Source: hex - # 0x5a60085560ff60ff60ff60ff600073<contract:0x1000000000000000000000000000000000000114>620927c0f1600955 # noqa: E501 - addr_2 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x8, value=Op.GAS) - + Op.SSTORE( - key=0x9, - value=Op.CALL( - gas=0x927C0, - address=addr, - value=0x0, - args_offset=0xFF, - args_size=0xFF, - ret_offset=0xFF, - ret_size=0xFF, - ), - ), - nonce=0, - ) - # Source: hex - # 0x5a60085560ff60ff60ff60ff600073<contract:0x1000000000000000000000000000000000000113>620927c0f1600955 # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x8, value=Op.GAS) - + Op.SSTORE( - key=0x9, - value=Op.CALL( - gas=0x927C0, - address=addr_2, - value=0x0, - args_offset=0xFF, - args_size=0xFF, - ret_offset=0xFF, - ret_size=0xFF, - ), - ), - nonce=0, - ) - - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=160000, - ) - - post = { - sender: Account(nonce=1), - target: Account(storage={}), - addr_2: Account(storage={}), - addr: Account(storage={}), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stMemExpandingEIP150Calls/test_call_goes_oog_on_second_level_with_mem_expanding_calls.py b/tests/ported_static/stMemExpandingEIP150Calls/test_call_goes_oog_on_second_level_with_mem_expanding_calls.py deleted file mode 100644 index 5f31ce42a8a..00000000000 --- a/tests/ported_static/stMemExpandingEIP150Calls/test_call_goes_oog_on_second_level_with_mem_expanding_calls.py +++ /dev/null @@ -1,107 +0,0 @@ -""" -Test_call_goes_oog_on_second_level_with_mem_expanding_calls. - -Ported from: -state_tests/stMemExpandingEIP150Calls/CallGoesOOGOnSecondLevelWithMemExpandingCallsFiller.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - [ - "state_tests/stMemExpandingEIP150Calls/CallGoesOOGOnSecondLevelWithMemExpandingCallsFiller.json" # noqa: E501 - ], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_call_goes_oog_on_second_level_with_mem_expanding_calls( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_call_goes_oog_on_second_level_with_mem_expanding_calls.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - ) - - # Source: hex - # 0x5a600855600060006000f050600060006000f0505a6009555a600a55 - addr = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x8, value=Op.GAS) - + Op.POP(Op.CREATE(value=0x0, offset=0x0, size=0x0)) * 2 - + Op.SSTORE(key=0x9, value=Op.GAS) - + Op.SSTORE(key=0xA, value=Op.GAS), - nonce=0, - ) - # Source: hex - # 0x5a60085560ff60ff60ff60ff600073<contract:0x1000000000000000000000000000000000000111>620927c0f1600955 # noqa: E501 - addr_2 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x8, value=Op.GAS) - + Op.SSTORE( - key=0x9, - value=Op.CALL( - gas=0x927C0, - address=addr, - value=0x0, - args_offset=0xFF, - args_size=0xFF, - ret_offset=0xFF, - ret_size=0xFF, - ), - ), - nonce=0, - ) - # Source: hex - # 0x5a60085560ff60ff60ff60ff600073<contract:0x1000000000000000000000000000000000000110>620927c0f1600955 # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x8, value=Op.GAS) - + Op.SSTORE( - key=0x9, - value=Op.CALL( - gas=0x927C0, - address=addr_2, - value=0x0, - args_offset=0xFF, - args_size=0xFF, - ret_offset=0xFF, - ret_size=0xFF, - ), - ), - nonce=0, - ) - - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=220000, - ) - - post = { - sender: Account(nonce=1), - target: Account(storage={8: 0x30956}), - addr_2: Account(storage={}), - addr: Account(storage={}), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stMemExpandingEIP150Calls/test_create_and_gas_inside_create_with_mem_expanding_calls.py b/tests/ported_static/stMemExpandingEIP150Calls/test_create_and_gas_inside_create_with_mem_expanding_calls.py deleted file mode 100644 index 705e9e760f9..00000000000 --- a/tests/ported_static/stMemExpandingEIP150Calls/test_create_and_gas_inside_create_with_mem_expanding_calls.py +++ /dev/null @@ -1,82 +0,0 @@ -""" -Test_create_and_gas_inside_create_with_mem_expanding_calls. - -Ported from: -state_tests/stMemExpandingEIP150Calls/CreateAndGasInsideCreateWithMemExpandingCallsFiller.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, - compute_create_address, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - [ - "state_tests/stMemExpandingEIP150Calls/CreateAndGasInsideCreateWithMemExpandingCallsFiller.json" # noqa: E501 - ], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_create_and_gas_inside_create_with_mem_expanding_calls( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_create_and_gas_inside_create_with_mem_expanding_calls.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - # Source: hex - # 0x5a600a55635a60fd556000526004601c6000f0600b555a600955 - contract_0 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0xA, value=Op.GAS) - + Op.MSTORE(offset=0x0, value=0x5A60FD55) - + Op.SSTORE(key=0xB, value=Op.CREATE(value=0x0, offset=0x1C, size=0x4)) - + Op.SSTORE(key=0x9, value=Op.GAS), - nonce=0, - ) - - tx = Transaction( - sender=sender, - to=contract_0, - data=Bytes(""), - gas_limit=600000, - ) - - post = { - sender: Account(nonce=1), - contract_0: Account( - storage={ - 9: 0x75596, - 10: 0x8D5B6, - 11: compute_create_address(address=contract_0, nonce=0), - }, - nonce=1, - ), - compute_create_address(address=contract_0, nonce=0): Account( - storage={253: 0x7E23D} - ), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stMemExpandingEIP150Calls/test_execute_call_that_ask_more_gas_then_transaction_has_with_mem_expanding_calls.py b/tests/ported_static/stMemExpandingEIP150Calls/test_execute_call_that_ask_more_gas_then_transaction_has_with_mem_expanding_calls.py deleted file mode 100644 index db38f621ed0..00000000000 --- a/tests/ported_static/stMemExpandingEIP150Calls/test_execute_call_that_ask_more_gas_then_transaction_has_with_mem_expanding_calls.py +++ /dev/null @@ -1,89 +0,0 @@ -""" -Test_execute_call_that_ask_more_gas_then_transaction_has_with_mem_expand... - -Ported from: -state_tests/stMemExpandingEIP150Calls/ExecuteCallThatAskMoreGasThenTransactionHasWithMemExpandingCallsFiller.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - Fork, - StateTestFiller, - Transaction, -) -from execution_testing.forks import Amsterdam -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - [ - "state_tests/stMemExpandingEIP150Calls/ExecuteCallThatAskMoreGasThenTransactionHasWithMemExpandingCallsFiller.json" # noqa: E501 - ], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.pre_alloc_mutable -def test_execute_call_that_ask_more_gas_then_transaction_has_with_mem_expanding_calls( # noqa: E501 - state_test: StateTestFiller, - fork: Fork, - pre: Alloc, -) -> None: - """Test_execute_call_that_ask_more_gas_then_transaction_has_with_mem_e...""" # noqa: E501 - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0x186A000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - # Source: hex - # 0x600c600155 - addr = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x1, value=0xC), - balance=0x186A0, - nonce=0, - ) - # Source: hex - # 0x60ff60ff60ff60ff600073<contract:0x1000000000000000000000000000000000000001>620927c0f1600155 # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE( - key=0x1, - value=Op.CALL( - gas=0x927C0, - address=addr, - value=0x0, - args_offset=0xFF, - args_size=0xFF, - ret_offset=0xFF, - ret_size=0xFF, - ), - ), - nonce=0, - ) - - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=2100000 if fork >= Amsterdam else 100000, - ) - - post = { - sender: Account(nonce=1), - target: Account(storage={1: 1}), - addr: Account(storage={1: 12}, balance=0x186A0), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stStaticCall/test_static_call_ask_more_gas_on_depth2_then_transaction_has.py b/tests/ported_static/stStaticCall/test_static_call_ask_more_gas_on_depth2_then_transaction_has.py deleted file mode 100644 index 081928e9e52..00000000000 --- a/tests/ported_static/stStaticCall/test_static_call_ask_more_gas_on_depth2_then_transaction_has.py +++ /dev/null @@ -1,223 +0,0 @@ -""" -Test_static_call_ask_more_gas_on_depth2_then_transaction_has. - -Ported from: -state_tests/stStaticCall/static_CallAskMoreGasOnDepth2ThenTransactionHasFiller.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Environment, - Hash, - StateTestFiller, - Transaction, -) -from execution_testing.forks import Fork -from execution_testing.vm import Op - -from tests.ported_static.post_state_resolution import ( - resolve_expect_post, -) - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - [ - "state_tests/stStaticCall/static_CallAskMoreGasOnDepth2ThenTransactionHasFiller.json" # noqa: E501 - ], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.slow -@pytest.mark.parametrize( - "d, g, v", - [ - pytest.param( - 0, - 0, - 0, - id="d0", - ), - pytest.param( - 1, - 0, - 0, - id="d1", - ), - ], -) -@pytest.mark.pre_alloc_mutable -def test_static_call_ask_more_gas_on_depth2_then_transaction_has( - state_test: StateTestFiller, - pre: Alloc, - fork: Fork, - d: int, - g: int, - v: int, -) -> None: - """Test_static_call_ask_more_gas_on_depth2_then_transaction_has.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - # Source: lll - # { [[ 0 ]] (CALL (GAS) (CALLDATALOAD 0) (CALLVALUE) 0 0 0 0) [[ 1 ]] 1 } - target = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE( - key=0x0, - value=Op.CALL( - gas=Op.GAS, - address=Op.CALLDATALOAD(offset=0x0), - value=Op.CALLVALUE, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.SSTORE(key=0x1, value=0x1) - + Op.STOP, - nonce=0, - address=Address(0xC0E4183389EB57F779A986D8C878F89B9401DC8E), # noqa: E501 - ) - # Source: lll - # { (SSTORE 8 1)} - addr_3 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x8, value=0x1) + Op.STOP, - nonce=0, - address=Address(0x5044BFB29664A79DE12215897C630DC8A11B0B97), # noqa: E501 - ) - # Source: lll - # { (MSTORE 8 (GAS))} - addr_6 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x8, value=Op.GAS) + Op.STOP, - nonce=0, - address=Address(0x91B291A3336BC1357388354DF18CA061B39E3745), # noqa: E501 - ) - # Source: lll - # { (MSTORE 8 (GAS)) (MSTORE 9 (STATICCALL 600000 <contract:0x1000000000000000000000000000000000000108> 0 0 0 0)) } # noqa: E501 - addr_2 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x8, value=Op.GAS) - + Op.MSTORE( - offset=0x9, - value=Op.STATICCALL( - gas=0x927C0, - address=0x5044BFB29664A79DE12215897C630DC8A11B0B97, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.STOP, - nonce=0, - address=Address(0xD9539C5A3DC4713D47A547BFC9A075BD97287080), # noqa: E501 - ) - # Source: lll - # { (MSTORE 8 (GAS)) (MSTORE 9 (STATICCALL 600000 <contract:0x2000000000000000000000000000000000000108> 0 0 0 0)) } # noqa: E501 - addr_5 = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x8, value=Op.GAS) - + Op.MSTORE( - offset=0x9, - value=Op.STATICCALL( - gas=0x927C0, - address=0x91B291A3336BC1357388354DF18CA061B39E3745, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.STOP, - nonce=0, - address=Address(0xE5A4D8074950EC8067D602848B666CA151B09C9F), # noqa: E501 - ) - # Source: lll - # { (SSTORE 8 1) (SSTORE 9 (STATICCALL 200000 <contract:0x1000000000000000000000000000000000000107> 0 0 0 0)) } # noqa: E501 - addr = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x8, value=0x1) - + Op.SSTORE( - key=0x9, - value=Op.STATICCALL( - gas=0x30D40, - address=0xD9539C5A3DC4713D47A547BFC9A075BD97287080, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.STOP, - nonce=0, - address=Address(0xEF69A9B2C20255FB7BD2B0AC7D45601A03D570B0), # noqa: E501 - ) - # Source: lll - # { (SSTORE 8 1) (SSTORE 9 (STATICCALL 200000 <contract:0x2000000000000000000000000000000000000107> 0 0 0 0)) } # noqa: E501 - addr_4 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x8, value=0x1) - + Op.SSTORE( - key=0x9, - value=Op.STATICCALL( - gas=0x30D40, - address=0xE5A4D8074950EC8067D602848B666CA151B09C9F, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.STOP, - nonce=0, - address=Address(0x8169DC735802BB5C18A777052CF4CE326B5FD725), # noqa: E501 - ) - - expect_entries_: list[dict] = [ - { - "indexes": {"data": 0, "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": { - addr: Account(storage={8: 1, 9: 1}), - addr_2: Account(storage={8: 0, 9: 0}), - addr_3: Account(storage={8: 0}), - target: Account(storage={0: 1, 1: 1}), - }, - }, - { - "indexes": {"data": 1, "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": { - addr_4: Account(storage={8: 1, 9: 1}), - addr_5: Account(storage={8: 0, 9: 0}), - addr_6: Account(storage={8: 0}), - target: Account(storage={0: 1, 1: 1}), - }, - }, - ] - - post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork) - - tx_data = [ - Hash(addr, left_padding=True), - Hash(addr_4, left_padding=True), - ] - - tx = Transaction( - sender=sender, - to=target, - data=tx_data[d], - error=_exc, - ) - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stStaticCall/test_static_call_goes_oog_on_second_level.py b/tests/ported_static/stStaticCall/test_static_call_goes_oog_on_second_level.py deleted file mode 100644 index c9bfd6ecd1b..00000000000 --- a/tests/ported_static/stStaticCall/test_static_call_goes_oog_on_second_level.py +++ /dev/null @@ -1,103 +0,0 @@ -""" -Test_static_call_goes_oog_on_second_level. - -Ported from: -state_tests/stStaticCall/static_CallGoesOOGOnSecondLevelFiller.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Transaction, -) -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - ["state_tests/stStaticCall/static_CallGoesOOGOnSecondLevelFiller.json"], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.slow -@pytest.mark.pre_alloc_mutable -def test_static_call_goes_oog_on_second_level( - state_test: StateTestFiller, - pre: Alloc, -) -> None: - """Test_static_call_goes_oog_on_second_level.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - # Source: lll - # { (KECCAK256 0x00 0x2fffff) } - addr_2 = pre.deploy_contract( # noqa: F841 - code=Op.SHA3(offset=0x0, size=0x2FFFFF) + Op.STOP, - nonce=0, - ) - # Source: lll - # { (MSTORE 8 (GAS)) (MSTORE 9 (STATICCALL 600000 <contract:0x1000000000000000000000000000000000000111> 0 0 0 0)) } # noqa: E501 - addr = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x8, value=Op.GAS) - + Op.MSTORE( - offset=0x9, - value=Op.STATICCALL( - gas=0x927C0, - address=addr_2, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.STOP, - nonce=0, - ) - # Source: lll - # { (SSTORE 9 (STATICCALL 600000 <contract:0x1000000000000000000000000000000000000110> 0 0 0 0)) [[ 10 ]] (GAS) } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE( - key=0x9, - value=Op.STATICCALL( - gas=0x927C0, - address=addr, - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.SSTORE(key=0xA, value=Op.GAS) - + Op.STOP, - nonce=0, - ) - - tx = Transaction( - sender=sender, - to=target, - data=Bytes(""), - gas_limit=220000, - ) - - post = { - addr: Account(storage={}), - addr_2: Account(storage={}), - target: Account(storage={}), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stStaticCall/test_static_call_goes_oog_on_second_level2.py b/tests/ported_static/stStaticCall/test_static_call_goes_oog_on_second_level2.py deleted file mode 100644 index 62ed98b8dc7..00000000000 --- a/tests/ported_static/stStaticCall/test_static_call_goes_oog_on_second_level2.py +++ /dev/null @@ -1,146 +0,0 @@ -""" -Test_static_call_goes_oog_on_second_level2. - -Ported from: -state_tests/stStaticCall/static_CallGoesOOGOnSecondLevel2Filler.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Environment, - Hash, - StateTestFiller, - Transaction, -) -from execution_testing.forks import Fork -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - ["state_tests/stStaticCall/static_CallGoesOOGOnSecondLevel2Filler.json"], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.slow -@pytest.mark.parametrize( - "d, g, v", - [ - pytest.param( - 0, - 0, - 0, - id="d0", - ), - pytest.param( - 1, - 0, - 0, - id="d1", - ), - ], -) -@pytest.mark.pre_alloc_mutable -def test_static_call_goes_oog_on_second_level2( - state_test: StateTestFiller, - pre: Alloc, - fork: Fork, - d: int, - g: int, - v: int, -) -> None: - """Test_static_call_goes_oog_on_second_level2.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - # Source: lll - # { (MSTORE 8 (GAS)) (MSTORE 9 (STATICCALL 600000 (CALLDATALOAD 0) 0 0 0 0)) } # noqa: E501 - addr = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x8, value=Op.GAS) - + Op.MSTORE( - offset=0x9, - value=Op.STATICCALL( - gas=0x927C0, - address=Op.CALLDATALOAD(offset=0x0), - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.STOP, - nonce=0, - address=Address(0x666EBB8AFC7A9BA4BEDB7D78F85184B65639531D), # noqa: E501 - ) - # Source: lll - # { (SSTORE 1 1) } - addr_2 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x1, value=0x1) + Op.STOP, - nonce=0, - address=Address(0xF2774CEE95A518A51CD32426D3CE8DB19F095B37), # noqa: E501 - ) - # Source: lll - # { (def 'i 0x80) (for {} (< @i 50000) [i](+ @i 1) (EXTCODESIZE 1)) } - addr_3 = pre.deploy_contract( # noqa: F841 - code=Op.JUMPDEST - + Op.JUMPI( - pc=0x1C, condition=Op.ISZERO(Op.LT(Op.MLOAD(offset=0x80), 0xC350)) - ) - + Op.POP(Op.EXTCODESIZE(address=0x1)) - + Op.MSTORE(offset=0x80, value=Op.ADD(Op.MLOAD(offset=0x80), 0x1)) - + Op.JUMP(pc=0x0) - + Op.JUMPDEST - + Op.STOP, - nonce=0, - address=Address(0x45E70D14D712A8898DCE133FE063F71179F04059), # noqa: E501 - ) - # Source: lll - # { (MSTORE 0 (CALLDATALOAD 0)) [[ 0 ]] (STATICCALL 600000 <contract:0x1000000000000000000000000000000000000113> 0 32 0 0) [[ 1 ]] 1 } # noqa: E501 - target = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x0, value=Op.CALLDATALOAD(offset=0x0)) - + Op.SSTORE( - key=0x0, - value=Op.STATICCALL( - gas=0x927C0, - address=0x666EBB8AFC7A9BA4BEDB7D78F85184B65639531D, - args_offset=0x0, - args_size=0x20, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.SSTORE(key=0x1, value=0x1) - + Op.STOP, - nonce=0, - address=Address(0xB9C1C6C39CB3E528B2EF06493C17D63B7827077B), # noqa: E501 - ) - - tx_data = [ - Hash(addr_2, left_padding=True), - Hash(addr_3, left_padding=True), - ] - tx_gas = [160000] - - tx = Transaction( - sender=sender, - to=target, - data=tx_data[d], - gas_limit=tx_gas[g], - ) - - post = {target: Account(storage={0: 0, 1: 0})} - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stStaticCall/test_static_create_empty_contract_and_call_it_0wei.py b/tests/ported_static/stStaticCall/test_static_create_empty_contract_and_call_it_0wei.py index d426d271f6e..41a218cacd0 100644 --- a/tests/ported_static/stStaticCall/test_static_create_empty_contract_and_call_it_0wei.py +++ b/tests/ported_static/stStaticCall/test_static_create_empty_contract_and_call_it_0wei.py @@ -1,106 +1,194 @@ """ -Test_static_create_empty_contract_and_call_it_0wei. +Measure CREATE of a codeless contract (optionally writing storage in its +init code) followed by a STATICCALL to it, via CodeGasMeasure. Ported from: state_tests/stStaticCall/static_CREATE_EmptyContractAndCallIt_0weiFiller.json +state_tests/stStaticCall/static_CREATE_EmptyContractWithStorageAndCallIt_0weiFiller.json + +@manually-enhanced: Do not overwrite. Two fillers folded into one +parametrize; the storage-writing init code is composed (not hex blobs) so +the measured CREATE/STATICCALL expectations derive from the same bytecode; +the init code's inner CALL forwards all gas (the ported 0xEA60 budget OOGs +under EIP-8037); the STATICCALL success flag stays inside the measured +window. Replaces the prior EIP-8037 expect-any band-aid with fork-derived +gas assertions. """ import pytest from execution_testing import ( Account, - Address, Alloc, - Bytes, - Environment, + Bytecode, + CodeGasMeasure, + Fork, StateTestFiller, - Storage, Transaction, compute_create_address, ) -from execution_testing.forks import Fork from execution_testing.vm import Op REFERENCE_SPEC_GIT_PATH = "N/A" REFERENCE_SPEC_VERSION = "N/A" +ADDRESS_SLOT = 0x1 +CREATE_GAS_SLOT = 0x2 +STATICCALL_FLAG_SLOT = 0x3 +STATICCALL_GAS_SLOT = 0x64 +STORED_VALUE = 0xC + +FORWARDED_GAS = 0xEA60 + @pytest.mark.ported_from( [ - "state_tests/stStaticCall/static_CREATE_EmptyContractAndCallIt_0weiFiller.json" # noqa: E501 + "state_tests/stStaticCall/static_CREATE_EmptyContractAndCallIt_0weiFiller.json", # noqa: E501 + "state_tests/stStaticCall/static_CREATE_EmptyContractWithStorageAndCallIt_0weiFiller.json", # noqa: E501 + ], +) +@pytest.mark.valid_from("Berlin") +@pytest.mark.parametrize( + "with_storage", + [ + pytest.param(False, id="empty_contract"), + pytest.param(True, id="with_storage"), ], ) -@pytest.mark.valid_from("Cancun") -@pytest.mark.slow -@pytest.mark.pre_alloc_mutable def test_static_create_empty_contract_and_call_it_0wei( state_test: StateTestFiller, pre: Alloc, fork: Fork, + with_storage: bool, ) -> None: - """Test_static_create_empty_contract_and_call_it_0wei.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, + """Measure CREATE and STATICCALL gas for a created codeless account.""" + if with_storage: + # Called by the init code below; writes one cold fresh slot. + writer_store = Op.SSTORE( + key=0x1, + value=STORED_VALUE, + key_warm=False, + original_value=0, + new_value=STORED_VALUE, + ) + writer = pre.deploy_contract(code=writer_store + Op.STOP) + + # The init code writes the created account's own slot 0 and calls + # the writer, then runs off its end (STOP) so no code is deposited. + # The inner CALL forwards all remaining gas (default Op.GAS). + initcode = Op.SSTORE( + key=0x0, + value=STORED_VALUE, + key_warm=False, + original_value=0, + new_value=STORED_VALUE, + ) + Op.CALL( + address=writer, + address_warm=False, + value_transfer=False, + account_new=False, + ) + initcode_bytes = bytes(initcode) + assert len(initcode) <= 0x40, "init code must fit two words" + + # Memory is populated (and expanded to 0x40) before the measured + # window, so the CREATE itself expands nothing. + setup = Op.MSTORE( + offset=0x0, + value=initcode_bytes[:0x20], + ) + Op.MSTORE( + offset=0x20, + value=initcode_bytes[0x20:].ljust(0x20, b"\x00"), + ) + create_code = Op.CREATE( + value=0x0, + offset=0x0, + size=len(initcode), + new_memory_size=0x40, + old_memory_size=0x40, + init_code_size=len(initcode), + ) + # The measured CREATE includes the child's work: the init code's + # own consumption plus the writer's store it calls. + child_cost = initcode.gas_cost(fork) + writer_store.gas_cost(fork) + else: + # CREATE over never-written memory runs 32 zero bytes as init code + # (STOP on the first byte), depositing no code and consuming + # nothing; the memory expansion happens inside the window. + setup = Bytecode() + create_code = Op.CREATE( + value=0x0, + offset=0x0, + size=0x20, + new_memory_size=0x20, + init_code_size=0x20, + ) + child_cost = 0 + + # The created address is stored inside the measured window (as in the + # ported filler) so the STATICCALL can target it at runtime. + create_store = Op.SSTORE( + key=ADDRESS_SLOT, + value=create_code, + key_warm=False, + original_value=0, + new_value=1, ) - # Source: lll - # { [[0]](GAS) [[1]] (CREATE 0 0 32) [[2]](GAS) [[3]] (STATICCALL 60000 (SLOAD 1) 0 0 0 0) [[100]] (GAS) } # noqa: E501 - contract_0 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=Op.GAS) - + Op.SSTORE(key=0x1, value=Op.CREATE(value=0x0, offset=0x0, size=0x20)) - + Op.SSTORE(key=0x2, value=Op.GAS) - + Op.SSTORE( - key=0x3, - value=Op.STATICCALL( - gas=0xEA60, - address=Op.SLOAD(key=0x1), - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), + # The created account exists (nonce 1) and is warm (CREATE accessed + # it); the success flag is stored inside the measured window — a + # wrongly failed STATICCALL would otherwise be unobservable. + staticcall_code = Op.STATICCALL( + gas=FORWARDED_GAS, + address=Op.SLOAD(key=ADDRESS_SLOT, key_warm=True), + address_warm=True, + ) + staticcall_store = Op.SSTORE( + key=STATICCALL_FLAG_SLOT, + value=staticcall_code, + key_warm=False, + original_value=0, + new_value=1, + ) + + contract = pre.deploy_contract( + code=setup + + CodeGasMeasure( + code=create_store, + extra_stack_items=0, + sstore_key=CREATE_GAS_SLOT, ) - + Op.SSTORE(key=0x64, value=Op.GAS) - + Op.STOP, - nonce=0, - address=Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B), # noqa: E501 + + CodeGasMeasure( + code=staticcall_store, + extra_stack_items=0, + sstore_key=STATICCALL_GAS_SLOT, + ), ) tx = Transaction( - sender=sender, - to=contract_0, - data=Bytes(""), - gas_limit=600000, + sender=pre.fund_eoa(), + to=contract, + state_gas_reservoir=0, ) - if fork.is_eip_enabled(8037): - contract_0_storage = Storage.model_validate( - {1: compute_create_address(address=contract_0, nonce=0), 3: 1} - ) - contract_0_storage.set_expect_any(0) - contract_0_storage.set_expect_any(2) - contract_0_storage.set_expect_any(100) - else: - contract_0_storage = Storage.model_validate( - { - 0: 0x8D5B6, - 1: compute_create_address(address=contract_0, nonce=0), - 2: 0x7ABF8, - 3: 1, - 100: 0x6FE6E, - } - ) + measured_create = create_store.gas_cost(fork) + child_cost + measured_staticcall = staticcall_store.gas_cost(fork) + + created = compute_create_address(address=contract, nonce=1) post = { - contract_0: Account(storage=contract_0_storage), - compute_create_address(address=contract_0, nonce=0): Account(nonce=1), + contract: Account( + storage={ + ADDRESS_SLOT: created, + CREATE_GAS_SLOT: measured_create, + STATICCALL_FLAG_SLOT: 1, + STATICCALL_GAS_SLOT: measured_staticcall, + }, + ), + created: Account( + nonce=1, + storage={0: STORED_VALUE} if with_storage else {}, + ), } + if with_storage: + post[writer] = Account(storage={1: STORED_VALUE}) - state_test(env=env, pre=pre, post=post, tx=tx) + state_test(pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stStaticCall/test_static_create_empty_contract_with_storage_and_call_it_0wei.py b/tests/ported_static/stStaticCall/test_static_create_empty_contract_with_storage_and_call_it_0wei.py deleted file mode 100644 index 91623e4efe9..00000000000 --- a/tests/ported_static/stStaticCall/test_static_create_empty_contract_with_storage_and_call_it_0wei.py +++ /dev/null @@ -1,124 +0,0 @@ -""" -Test_static_create_empty_contract_with_storage_and_call_it_0wei. - -Ported from: -state_tests/stStaticCall/static_CREATE_EmptyContractWithStorageAndCallIt_0weiFiller.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Bytes, - Environment, - StateTestFiller, - Storage, - Transaction, - compute_create_address, -) -from execution_testing.forks import Fork -from execution_testing.vm import Op - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - [ - "state_tests/stStaticCall/static_CREATE_EmptyContractWithStorageAndCallIt_0weiFiller.json" # noqa: E501 - ], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.slow -@pytest.mark.pre_alloc_mutable -def test_static_create_empty_contract_with_storage_and_call_it_0wei( - state_test: StateTestFiller, - pre: Alloc, - fork: Fork, -) -> None: - """Test_static_create_empty_contract_with_storage_and_call_it_0wei.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - contract_0 = Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B) - contract_1 = Address(0xC94F5374FCE5EDBC8E2A8697C15331677E6EBF0B) - sender = pre.fund_eoa(amount=0xE8D4A51000) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - # Source: lll - # { [[0]](GAS) (MSTORE 0 0x600c6000556000600060006000600073c94f5374fce5edbc8e2a8697c1533167) (MSTORE 32 0x7e6ebf0b61ea60f1000000000000000000000000000000000000000000000000) [[1]] (CREATE 0 0 64) [[2]] (GAS) [[3]] (STATICCALL 60000 (SLOAD 1) 0 0 0 0) [[100]] (GAS) } # noqa: E501 - contract_0 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x0, value=Op.GAS) - + Op.MSTORE( - offset=0x0, - value=0x600C6000556000600060006000600073C94F5374FCE5EDBC8E2A8697C1533167, # noqa: E501 - ) - + Op.MSTORE( - offset=0x20, - value=0x7E6EBF0B61EA60F1000000000000000000000000000000000000000000000000, # noqa: E501 - ) - + Op.SSTORE(key=0x1, value=Op.CREATE(value=0x0, offset=0x0, size=0x40)) - + Op.SSTORE(key=0x2, value=Op.GAS) - + Op.SSTORE( - key=0x3, - value=Op.STATICCALL( - gas=0xEA60, - address=Op.SLOAD(key=0x1), - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.SSTORE(key=0x64, value=Op.GAS) - + Op.STOP, - nonce=0, - address=Address(0xB94F5374FCE5EDBC8E2A8697C15331677E6EBF0B), # noqa: E501 - ) - # Source: lll - # {[[1]]12} - contract_1 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x1, value=0xC) + Op.STOP, - balance=0xE8D4A51000, - nonce=0, - address=Address(0xC94F5374FCE5EDBC8E2A8697C15331677E6EBF0B), # noqa: E501 - ) - - tx = Transaction( - sender=sender, - to=contract_0, - data=Bytes(""), - gas_limit=600000, - ) - - if fork.is_eip_enabled(8037): - contract_0_storage = Storage.model_validate( - {1: compute_create_address(address=contract_0, nonce=0), 3: 1} - ) - contract_0_storage.set_expect_any(0) - contract_0_storage.set_expect_any(2) - contract_0_storage.set_expect_any(100) - else: - contract_0_storage = Storage.model_validate( - { - 0: 0x8D5B6, - 1: compute_create_address(address=contract_0, nonce=0), - 2: 0x6F4F0, - 3: 1, - 100: 0x64766, - } - ) - post = { - contract_0: Account(storage=contract_0_storage), - compute_create_address(address=contract_0, nonce=0): Account(nonce=1), - contract_1: Account(storage={1: 12}), - } - - state_test(env=env, pre=pre, post=post, tx=tx) diff --git a/tests/ported_static/stStaticCall/test_static_execute_call_that_ask_fore_gas_then_trabsaction_has.py b/tests/ported_static/stStaticCall/test_static_execute_call_that_ask_fore_gas_then_trabsaction_has.py deleted file mode 100644 index b5965a5da21..00000000000 --- a/tests/ported_static/stStaticCall/test_static_execute_call_that_ask_fore_gas_then_trabsaction_has.py +++ /dev/null @@ -1,162 +0,0 @@ -""" -Test_static_execute_call_that_ask_fore_gas_then_trabsaction_has. - -Ported from: -state_tests/stStaticCall/static_ExecuteCallThatAskForeGasThenTrabsactionHasFiller.json -""" - -import pytest -from execution_testing import ( - Account, - Address, - Alloc, - Environment, - Hash, - StateTestFiller, - Transaction, -) -from execution_testing.forks import Fork -from execution_testing.vm import Op - -from tests.ported_static.post_state_resolution import ( - resolve_expect_post, -) - -REFERENCE_SPEC_GIT_PATH = "N/A" -REFERENCE_SPEC_VERSION = "N/A" - - -@pytest.mark.ported_from( - [ - "state_tests/stStaticCall/static_ExecuteCallThatAskForeGasThenTrabsactionHasFiller.json" # noqa: E501 - ], -) -@pytest.mark.valid_from("Cancun") -@pytest.mark.slow -@pytest.mark.parametrize( - "d, g, v", - [ - pytest.param( - 0, - 0, - 0, - id="d0", - ), - pytest.param( - 1, - 0, - 0, - id="d1", - ), - pytest.param( - 2, - 0, - 0, - id="d2", - ), - ], -) -@pytest.mark.pre_alloc_mutable -def test_static_execute_call_that_ask_fore_gas_then_trabsaction_has( - state_test: StateTestFiller, - pre: Alloc, - fork: Fork, - d: int, - g: int, - v: int, -) -> None: - """Test_static_execute_call_that_ask_fore_gas_then_trabsaction_has.""" - coinbase = Address(0x2ADC25665018AA1FE0E6BC666DAC8FC2697FF9BA) - sender = pre.fund_eoa(amount=0x989680) - - env = Environment( - fee_recipient=coinbase, - number=1, - timestamp=1000, - prev_randao=0x20000, - base_fee_per_gas=10, - gas_limit=10000000, - ) - - # Source: lll - # { [[1]] (STATICCALL 600000 (CALLDATALOAD 0) 0 0 0 0) } - target = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE( - key=0x1, - value=Op.STATICCALL( - gas=0x927C0, - address=Op.CALLDATALOAD(offset=0x0), - args_offset=0x0, - args_size=0x0, - ret_offset=0x0, - ret_size=0x0, - ), - ) - + Op.STOP, - nonce=0, - address=Address(0xA256EBCC5536CDA56E04C39FE9584ECC7594A438), # noqa: E501 - ) - # Source: lll - # { (MSTORE 1 1) } - addr = pre.deploy_contract( # noqa: F841 - code=Op.MSTORE(offset=0x1, value=0x1) + Op.STOP, - balance=0x186A0, - nonce=0, - address=Address(0x3DC16A13CF554533F380CC938A2C1AB04DAC534F), # noqa: E501 - ) - # Source: lll - # { (def 'i 0x80) (for {} (< @i 50000) [i](+ @i 1) (EXTCODESIZE 1)) } - addr_2 = pre.deploy_contract( # noqa: F841 - code=Op.JUMPDEST - + Op.JUMPI( - pc=0x1C, condition=Op.ISZERO(Op.LT(Op.MLOAD(offset=0x80), 0xC350)) - ) - + Op.POP(Op.EXTCODESIZE(address=0x1)) - + Op.MSTORE(offset=0x80, value=Op.ADD(Op.MLOAD(offset=0x80), 0x1)) - + Op.JUMP(pc=0x0) - + Op.JUMPDEST - + Op.STOP, - balance=0x186A0, - nonce=0, - address=Address(0x73EF1878A0F2C9629DEDC1B1E9BE8D77DCF93688), # noqa: E501 - ) - # Source: lll - # { (SSTORE 1 1) } - addr_3 = pre.deploy_contract( # noqa: F841 - code=Op.SSTORE(key=0x1, value=0x1) + Op.STOP, - balance=0x186A0, - nonce=0, - address=Address(0xCE4CCBFFAF450AE2126EB96DCD7C891F37764F20), # noqa: E501 - ) - - expect_entries_: list[dict] = [ - { - "indexes": {"data": [1, 2], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {target: Account(storage={1: 0})}, - }, - { - "indexes": {"data": [0], "gas": -1, "value": -1}, - "network": [">=Cancun"], - "result": {target: Account(storage={1: 1})}, - }, - ] - - post, _exc = resolve_expect_post(expect_entries_, d, g, v, fork) - - tx_data = [ - Hash(addr, left_padding=True), - Hash(addr_2, left_padding=True), - Hash(addr_3, left_padding=True), - ] - tx_gas = [100000] - - tx = Transaction( - sender=sender, - to=target, - data=tx_data[d], - gas_limit=tx_gas[g], - error=_exc, - ) - - state_test(env=env, pre=pre, post=post, tx=tx) From 9496fa8a498d9bd4ce924b6e9c9ac789577f121b Mon Sep 17 00:00:00 2001 From: spencer <spencer.taylor-brown@ethereum.org> Date: Wed, 12 Aug 2026 01:24:58 +0200 Subject: [PATCH 218/233] chore(tests): mark memory heavy tests as bigmem (#3360) * chore(tests): mark memory heavy tests as bigmem Co-authored-by: jsign <6136245+jsign@users.noreply.github.com> * fix(test-plugins): register the bigmem marker --------- Co-authored-by: jsign <6136245+jsign@users.noreply.github.com> --- .../cli/pytest_commands/plugins/shared/execute_fill.py | 4 ++++ .../eip7825_transaction_gas_limit_cap/test_tx_gas_limit.py | 1 + .../eip7934_block_rlp_limit/test_max_block_rlp_size.py | 2 +- tests/prague/eip7702_set_code_tx/test_set_code_txs.py | 7 +++++++ tests/prague/eip7702_set_code_tx/test_set_code_txs_2.py | 3 +++ tests/shanghai/eip3855_push0/test_push0.py | 2 ++ tests/shanghai/eip3860_initcode/test_initcode.py | 2 ++ 7 files changed, 20 insertions(+), 1 deletion(-) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/execute_fill.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/execute_fill.py index 4c82a3adbca..6dc6788c51f 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/execute_fill.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/execute_fill.py @@ -180,6 +180,10 @@ def pytest_configure(config: pytest.Config) -> None: "exception_test: Negative tests that include an invalid block or " "transaction.", ) + config.addinivalue_line( + "markers", + "bigmem: Tests that consume a large amount of memory.", + ) config.addinivalue_line( "markers", "eip_checklist(item_id, eip=None): Mark a test as implementing a " diff --git a/tests/osaka/eip7825_transaction_gas_limit_cap/test_tx_gas_limit.py b/tests/osaka/eip7825_transaction_gas_limit_cap/test_tx_gas_limit.py index 4fb5bed7cef..c1f06d0f08d 100644 --- a/tests/osaka/eip7825_transaction_gas_limit_cap/test_tx_gas_limit.py +++ b/tests/osaka/eip7825_transaction_gas_limit_cap/test_tx_gas_limit.py @@ -334,6 +334,7 @@ def total_cost_floor_per_token(fork: Fork) -> int: return gas_costs.TX_DATA_TOKEN_FLOOR +@pytest.mark.bigmem @pytest.mark.xdist_group(name="bigmem") @pytest.mark.parametrize( "exceed_tx_gas_limit,correct_intrinsic_cost_in_transaction_gas_limit", diff --git a/tests/osaka/eip7934_block_rlp_limit/test_max_block_rlp_size.py b/tests/osaka/eip7934_block_rlp_limit/test_max_block_rlp_size.py index 21120f10cff..238960f3d30 100644 --- a/tests/osaka/eip7934_block_rlp_limit/test_max_block_rlp_size.py +++ b/tests/osaka/eip7934_block_rlp_limit/test_max_block_rlp_size.py @@ -36,7 +36,7 @@ REFERENCE_SPEC_GIT_PATH = ref_spec_7934.git_path REFERENCE_SPEC_VERSION = ref_spec_7934.version -pytestmark = pytest.mark.xdist_group(name="bigmem") +pytestmark = [pytest.mark.bigmem, pytest.mark.xdist_group(name="bigmem")] HEADER_TIMESTAMP = 123456789 diff --git a/tests/prague/eip7702_set_code_tx/test_set_code_txs.py b/tests/prague/eip7702_set_code_tx/test_set_code_txs.py index bd2bc30a90a..36ae71616ad 100644 --- a/tests/prague/eip7702_set_code_tx/test_set_code_txs.py +++ b/tests/prague/eip7702_set_code_tx/test_set_code_txs.py @@ -2066,6 +2066,7 @@ def test_set_code_to_self_destructing_account_deployed_in_same_tx( ) +@pytest.mark.bigmem @pytest.mark.xdist_group(name="bigmem") def test_set_code_multiple_first_valid_authorization_tuples_same_signer( state_test: StateTestFiller, @@ -2116,6 +2117,7 @@ def test_set_code_multiple_first_valid_authorization_tuples_same_signer( ) +@pytest.mark.bigmem @pytest.mark.xdist_group(name="bigmem") def test_set_code_multiple_valid_authorization_tuples_same_signer_increasing_nonce( # noqa: E501 state_test: StateTestFiller, @@ -2167,6 +2169,7 @@ def test_set_code_multiple_valid_authorization_tuples_same_signer_increasing_non ) +@pytest.mark.bigmem @pytest.mark.xdist_group(name="bigmem") def test_set_code_multiple_valid_authorization_tuples_same_signer_increasing_nonce_self_sponsored( # noqa: E501 state_test: StateTestFiller, @@ -2268,6 +2271,7 @@ def test_set_code_multiple_valid_authorization_tuples_first_invalid_same_signer( ) +@pytest.mark.bigmem @pytest.mark.xdist_group(name="bigmem") def test_set_code_all_invalid_authorization_tuples( state_test: StateTestFiller, @@ -2310,6 +2314,7 @@ def test_set_code_all_invalid_authorization_tuples( ) +@pytest.mark.bigmem @pytest.mark.xdist_group(name="bigmem") def test_set_code_using_chain_specific_id( state_test: StateTestFiller, @@ -2361,6 +2366,7 @@ def test_set_code_using_chain_specific_id( SECP256K1N_OVER_2 = SECP256K1N // 2 +@pytest.mark.bigmem @pytest.mark.xdist_group(name="bigmem") @pytest.mark.parametrize( "v,r,s", @@ -2428,6 +2434,7 @@ def test_set_code_using_valid_synthetic_signatures( ) +@pytest.mark.bigmem @pytest.mark.xdist_group(name="bigmem") @pytest.mark.parametrize( "v,r,s", diff --git a/tests/prague/eip7702_set_code_tx/test_set_code_txs_2.py b/tests/prague/eip7702_set_code_tx/test_set_code_txs_2.py index 995a6b9dadc..75ebba85a78 100644 --- a/tests/prague/eip7702_set_code_tx/test_set_code_txs_2.py +++ b/tests/prague/eip7702_set_code_tx/test_set_code_txs_2.py @@ -1751,6 +1751,7 @@ class DelegationTo(Enum): RESET = 3 +@pytest.mark.bigmem @pytest.mark.xdist_group(name="bigmem") @pytest.mark.valid_from("Prague") @pytest.mark.parametrize( @@ -1848,6 +1849,7 @@ def test_double_auth( ) +@pytest.mark.bigmem @pytest.mark.xdist_group(name="bigmem") @pytest.mark.valid_from("Prague") @pytest.mark.eels_base_coverage @@ -2069,6 +2071,7 @@ def test_set_code_type_tx_pre_fork( @pytest.mark.valid_from("Prague") +@pytest.mark.bigmem @pytest.mark.xdist_group(name="bigmem") def test_delegation_replacement_call_previous_contract( state_test: StateTestFiller, diff --git a/tests/shanghai/eip3855_push0/test_push0.py b/tests/shanghai/eip3855_push0/test_push0.py index 919952673f4..f4ab6f44e0a 100644 --- a/tests/shanghai/eip3855_push0/test_push0.py +++ b/tests/shanghai/eip3855_push0/test_push0.py @@ -27,6 +27,7 @@ pytestmark = pytest.mark.valid_from("Shanghai") +@pytest.mark.bigmem @pytest.mark.xdist_group(name="bigmem") @pytest.mark.parametrize( "contract_code,expected_storage", @@ -134,6 +135,7 @@ def push0_contract_caller( ) return pre.deploy_contract(call_code) + @pytest.mark.bigmem @pytest.mark.xdist_group(name="bigmem") @pytest.mark.parametrize( "call_opcode", diff --git a/tests/shanghai/eip3860_initcode/test_initcode.py b/tests/shanghai/eip3860_initcode/test_initcode.py index 9037d46c7ca..244cc802ed7 100644 --- a/tests/shanghai/eip3860_initcode/test_initcode.py +++ b/tests/shanghai/eip3860_initcode/test_initcode.py @@ -111,6 +111,7 @@ def initcode(fork: Fork, initcode_name: str) -> Initcode: """Test cases using a contract creating transaction""" +@pytest.mark.bigmem @pytest.mark.xdist_group(name="bigmem") @pytest.mark.parametrize( "initcode_name", @@ -505,6 +506,7 @@ def tx( sender=sender, ) + @pytest.mark.bigmem @pytest.mark.xdist_group(name="bigmem") @pytest.mark.slow() def test_create_opcode_initcode( From 6e4808927cb7140f05c43890b48630afcc368d91 Mon Sep 17 00:00:00 2001 From: spencer <spencer.taylor-brown@ethereum.org> Date: Wed, 12 Aug 2026 01:27:17 +0200 Subject: [PATCH 219/233] refactor(testing): capitalize SSZ in class names (#3354) --- .../src/execution_testing/base_types/ssz.py | 288 +++++++++--------- .../base_types/tests/test_ssz.py | 92 +++--- .../execution_testing/tools/ssz_vectors.py | 66 ++-- .../tools/tests/test_ssz_vectors.py | 18 +- 4 files changed, 232 insertions(+), 232 deletions(-) diff --git a/packages/testing/src/execution_testing/base_types/ssz.py b/packages/testing/src/execution_testing/base_types/ssz.py index 4605aaf3147..2759a3371a2 100644 --- a/packages/testing/src/execution_testing/base_types/ssz.py +++ b/packages/testing/src/execution_testing/base_types/ssz.py @@ -1,7 +1,7 @@ """ Native SSZ serialization for base_types models. -Declare a container once as a pydantic SszModel, in the ordinary base types, +Declare a container once as a pydantic SSZModel, in the ordinary base types, and get SSZ encoding, hash_tree_root, and defaults for them. Each field's SSZ type is derived from its Python type, so the model stays the @@ -10,20 +10,20 @@ * fixed byte types self-describe by byte_length (Hash -> ByteVector[32], Address -> ByteVector[20]); * the width ints defined here carry it (Uint64 -> uint64); -* bool -> boolean; a nested SszModel -> Container; +* bool -> boolean; a nested SSZModel -> Container; * the only facts a Python type cannot express -- list / vector / bytelist / bit caps -- ride as Annotated markers (ssz_list(N), ssz_vector(N), byte_list(N), bitvector(N), bitlist(N)). Element types are derived from the annotation, so a marker carries only the cap/length, never a duplicated element spec. -Each field's SSZ type is described by an SszType value (SszUint, SszByteList, -SszList, SszContainer, ...). The engine turns that into a remerkleable type +Each field's SSZ type is described by an SSZType value (SSZUint, SSZByteList, +SSZList, SSZContainer, ...). The engine turns that into a remerkleable type on demand (build_ssz_type) and delegates the actual encoding, merkleization, and default (zero) values to it. Fork-scoped models: one model can serve every fork. Future-fork fields are declared T | None (None == absent in older forks, omitted from JSON), and a -__ssz_schema__ = SszForkSchema(...) table beside the fields says which fork +__ssz_schema__ = SSZForkSchema(...) table beside the fields says which fork introduces what, in canonical SSZ order (the class body's order stays free for JSON). Such models require fork= on encode / hash_tree_root / decode / ssz_default / describe_schema / build_ssz_type @@ -82,97 +82,97 @@ } -class SszType: +class SSZType: """A description of a field's SSZ type.""" @dataclass(frozen=True) -class SszUint(SszType): +class SSZUint(SSZType): """An unsigned integer of bits width (8/16/32/64/128/256).""" bits: int @dataclass(frozen=True) -class SszByteVector(SszType): +class SSZByteVector(SSZType): """A fixed-length byte vector of length bytes.""" length: int @dataclass(frozen=True) -class SszByteList(SszType): +class SSZByteList(SSZType): """A variable byte list capped at limit bytes.""" limit: int @dataclass(frozen=True) -class SszList(SszType): +class SSZList(SSZType): """A list of element capped at limit items.""" - element: SszType + element: SSZType limit: int @dataclass(frozen=True) -class SszVector(SszType): +class SSZVector(SSZType): """A fixed-length vector of exactly length element items.""" - element: SszType + element: SSZType length: int @dataclass(frozen=True) -class SszBitvector(SszType): +class SSZBitvector(SSZType): """A fixed-length bit vector of length bits.""" length: int @dataclass(frozen=True) -class SszBitlist(SszType): +class SSZBitlist(SSZType): """A variable bit list capped at limit bits.""" limit: int @dataclass(frozen=True) -class SszBool(SszType): +class SSZBool(SSZType): """The SSZ boolean type.""" @dataclass(frozen=True) -class SszContainer(SszType): +class SSZContainer(SSZType): """A nested container backed by pydantic model.""" - model: Type["SszModel"] + model: Type["SSZModel"] @dataclass(frozen=True) -class SszProgressiveList(SszType): +class SSZProgressiveList(SSZType): """An uncapped progressive list of element (EIP-7916).""" - element: SszType + element: SSZType @dataclass(frozen=True) -class SszProgressiveBitlist(SszType): +class SSZProgressiveBitlist(SSZType): """An uncapped progressive bit list.""" @dataclass(frozen=True) -class SszProgressiveContainer(SszType): +class SSZProgressiveContainer(SSZType): """A forward-compatible progressive container backed by model.""" - model: Type["SszModel"] + model: Type["SSZModel"] -_M = TypeVar("_M", bound="SszModel") +_M = TypeVar("_M", bound="SSZModel") @dataclass(frozen=True, eq=False) -class SszForkSchema: +class SSZForkSchema: """ Fork-scoped field sets for a fork-evolving container. @@ -225,18 +225,18 @@ def _unwrap_optional(annotation: Any) -> Tuple[Any, bool]: return annotation, False -def _is_fork_optional(model_cls: Type["SszModel"], name: str) -> bool: +def _is_fork_optional(model_cls: Type["SSZModel"], name: str) -> bool: ann = model_cls.model_fields[name].annotation return _unwrap_optional(ann)[1] -def _is_ssz_excluded(model_cls: Type["SszModel"], name: str) -> bool: +def _is_ssz_excluded(model_cls: Type["SSZModel"], name: str) -> bool: """Whether name carries the ssz_exclude() marker (JSON-only).""" metadata = model_cls.model_fields[name].metadata - return any(isinstance(m, _SszExclude) for m in metadata) + return any(isinstance(m, _SSZExclude) for m in metadata) -def _included_fields(model_cls: Type["SszModel"]) -> Tuple[str, ...]: +def _included_fields(model_cls: Type["SSZModel"]) -> Tuple[str, ...]: """Every SSZ-participating field, in declaration order.""" return tuple( name @@ -245,7 +245,7 @@ def _included_fields(model_cls: Type["SszModel"]) -> Tuple[str, ...]: ) -def _check_fork_schema(model_cls: Type["SszModel"]) -> None: +def _check_fork_schema(model_cls: Type["SSZModel"]) -> None: """ Validate a model's __ssz_schema__ against its fields, at class definition. @@ -337,13 +337,13 @@ class _ProgressiveListMark(_Marker): @dataclass(frozen=True) -class _SszExclude(_Marker): +class _SSZExclude(_Marker): pass -def byte_list(limit: int) -> SszByteList: +def byte_list(limit: int) -> SSZByteList: """Annotate a Bytes field as a capped SSZ byte list.""" - return SszByteList(limit) + return SSZByteList(limit) def ssz_list(limit: int) -> _ListCap: @@ -356,14 +356,14 @@ def ssz_vector(length: int) -> _VectorLen: return _VectorLen(length) -def bitvector(length: int) -> SszBitvector: +def bitvector(length: int) -> SSZBitvector: """Annotate a list[bool] field as a fixed SSZ bit vector.""" - return SszBitvector(length) + return SSZBitvector(length) -def bitlist(limit: int) -> SszBitlist: +def bitlist(limit: int) -> SSZBitlist: """Annotate a list[bool] field as a capped SSZ bit list.""" - return SszBitlist(limit) + return SSZBitlist(limit) def progressive_list() -> _ProgressiveListMark: @@ -371,31 +371,31 @@ def progressive_list() -> _ProgressiveListMark: return _ProgressiveListMark() -def progressive_bitlist() -> SszProgressiveBitlist: +def progressive_bitlist() -> SSZProgressiveBitlist: """Annotate a list[bool] field as an uncapped progressive bit list.""" - return SszProgressiveBitlist() + return SSZProgressiveBitlist() -def ssz_exclude() -> _SszExclude: +def ssz_exclude() -> _SSZExclude: """ Annotate a field as JSON-only: SSZ ignores it entirely. Such a field must carry a default: decode never sees it on the wire and so cannot reconstruct it. """ - return _SszExclude() + return _SSZExclude() -class SszModel(CamelModel): +class SSZModel(CamelModel): """ A pydantic model whose fields carry SSZ types. - Every field must resolve to an SszType, or be excluded from SSZ with + Every field must resolve to an SSZType, or be excluded from SSZ with an ssz_exclude() marker (JSON-only fields); each Annotated marker must be consistent with the field's Python type. """ - __ssz_schema__: ClassVar[Optional[SszForkSchema]] = None + __ssz_schema__: ClassVar[Optional[SSZForkSchema]] = None @classmethod def __pydantic_init_subclass__(cls, **kwargs: Any) -> None: @@ -413,7 +413,7 @@ def __pydantic_init_subclass__(cls, **kwargs: Any) -> None: _check_fork_schema(cls) -class ProgressiveModel(SszModel): +class ProgressiveModel(SSZModel): """ A forward-compatible progressive container. @@ -442,28 +442,28 @@ def __pydantic_init_subclass__(cls, **kwargs: Any) -> None: def _marker_in(metadata: Any) -> Any: """The first SSZ marker in metadata.""" return next( - (m for m in metadata if isinstance(m, (SszType, _Marker))), None + (m for m in metadata if isinstance(m, (SSZType, _Marker))), None ) -def _spec_for_type_bare(annotation: Any) -> SszType: +def _spec_for_type_bare(annotation: Any) -> SSZType: """Derive the SSZ type of a plain Python type.""" ssz = getattr(annotation, "__ssz__", None) - if isinstance(ssz, SszType): + if isinstance(ssz, SSZType): return ssz if isinstance(annotation, type): if issubclass(annotation, FixedSizeBytes): - return SszByteVector(annotation.byte_length) + return SSZByteVector(annotation.byte_length) if issubclass(annotation, ProgressiveModel): - return SszProgressiveContainer(annotation) - if issubclass(annotation, SszModel): - return SszContainer(annotation) + return SSZProgressiveContainer(annotation) + if issubclass(annotation, SSZModel): + return SSZContainer(annotation) if annotation is bool: - return SszBool() + return SSZBool() raise TypeError(f"no SSZ type for {annotation!r}") -def _spec_for_type(annotation: Any) -> SszType: +def _spec_for_type(annotation: Any) -> SSZType: """Resolve an SSZ type, honoring an inner Annotated marker if present.""" meta = getattr(annotation, "__metadata__", None) if meta is not None: @@ -471,7 +471,7 @@ def _spec_for_type(annotation: Any) -> SszType: return _spec_for_type_bare(annotation) -def _element_of(annotation: Any, ctx: str) -> SszType: +def _element_of(annotation: Any, ctx: str) -> SSZType: """Resolve the element SSZ type of a list[...] annotation.""" if get_origin(annotation) not in (list, List): raise TypeError(f"{ctx} requires a list[...] field: {annotation!r}") @@ -481,7 +481,7 @@ def _element_of(annotation: Any, ctx: str) -> SszType: return _spec_for_type(args[0]) -def _resolve(marker: Any, annotation: Any) -> SszType: +def _resolve(marker: Any, annotation: Any) -> SSZType: """ Resolve a field/element into an SSZ type. @@ -493,12 +493,12 @@ def _resolve(marker: Any, annotation: Any) -> SszType: if marker is None: return _spec_for_type(annotation) if isinstance(marker, _ListCap): - return SszList(_element_of(annotation, "ssz_list"), marker.limit) + return SSZList(_element_of(annotation, "ssz_list"), marker.limit) if isinstance(marker, _VectorLen): - return SszVector(_element_of(annotation, "ssz_vector"), marker.length) + return SSZVector(_element_of(annotation, "ssz_vector"), marker.length) if isinstance(marker, _ProgressiveListMark): - return SszProgressiveList(_element_of(annotation, "progressive_list")) - if isinstance(marker, SszByteList): + return SSZProgressiveList(_element_of(annotation, "progressive_list")) + if isinstance(marker, SSZByteList): is_bytes = isinstance(annotation, type) and issubclass( annotation, Bytes ) @@ -507,17 +507,17 @@ def _resolve(marker: Any, annotation: Any) -> SszType: f"byte_list requires a Bytes field/element: {annotation!r}" ) return marker - if isinstance(marker, (SszBitvector, SszBitlist, SszProgressiveBitlist)): - if not isinstance(_element_of(annotation, "bit markers"), SszBool): + if isinstance(marker, (SSZBitvector, SSZBitlist, SSZProgressiveBitlist)): + if not isinstance(_element_of(annotation, "bit markers"), SSZBool): raise TypeError( f"bit markers require a list[bool] field: {annotation!r}" ) return marker - if isinstance(marker, _SszExclude): + if isinstance(marker, _SSZExclude): raise TypeError( f"field is ssz_exclude()d; it has no SSZ type: {annotation!r}" ) - # Raw SszType instances (SszUint, SszContainer, ...) as markers would + # Raw SSZType instances (SSZUint, SSZContainer, ...) as markers would # bypass the consistency checks above; only the marker helpers are # supported. raise TypeError( @@ -527,14 +527,14 @@ def _resolve(marker: Any, annotation: Any) -> SszType: @lru_cache(maxsize=None) -def spec_of(model_cls: Type["SszModel"], name: str) -> SszType: +def spec_of(model_cls: Type["SSZModel"], name: str) -> SSZType: """ The resolved SSZ type of a field. An Annotated marker takes precedence over the bare type; cap-only markers derive their element from the annotation, and every marker is checked for consistency with it. A T | None union resolves to T's SSZ type -- the - None arm means "absent in older forks" (see SszForkSchema), which is a + None arm means "absent in older forks" (see SSZForkSchema), which is a schema fact, not an SSZ type. Cached per (model_cls, name). """ field = model_cls.model_fields[name] @@ -547,40 +547,40 @@ def spec_of(model_cls: Type["SszModel"], name: str) -> SszType: return _resolve(_marker_in(field.metadata), annotation) -def _rmk_type(spec: SszType, fork: Optional[str] = None) -> Type[View]: - if isinstance(spec, SszUint): +def _rmk_type(spec: SSZType, fork: Optional[str] = None) -> Type[View]: + if isinstance(spec, SSZUint): return _UINTS[spec.bits] - if isinstance(spec, SszByteVector): + if isinstance(spec, SSZByteVector): return ByteVector[spec.length] - if isinstance(spec, SszByteList): + if isinstance(spec, SSZByteList): return ByteList[spec.limit] - if isinstance(spec, SszList): + if isinstance(spec, SSZList): return RmkList[_rmk_type(spec.element, fork), spec.limit] - if isinstance(spec, SszVector): + if isinstance(spec, SSZVector): return RmkVector[_rmk_type(spec.element, fork), spec.length] - if isinstance(spec, SszBitvector): + if isinstance(spec, SSZBitvector): return RmkBitvector[spec.length] - if isinstance(spec, SszBitlist): + if isinstance(spec, SSZBitlist): return RmkBitlist[spec.limit] - if isinstance(spec, SszProgressiveList): + if isinstance(spec, SSZProgressiveList): return RmkProgressiveList[_rmk_type(spec.element, fork)] - if isinstance(spec, SszProgressiveBitlist): + if isinstance(spec, SSZProgressiveBitlist): return RmkProgressiveBitlist - if isinstance(spec, (SszContainer, SszProgressiveContainer)): + if isinstance(spec, (SSZContainer, SSZProgressiveContainer)): return build_ssz_type(spec.model, _nested_fork(spec.model, fork)) - if isinstance(spec, SszBool): + if isinstance(spec, SSZBool): return boolean raise TypeError(f"unhandled SSZ type {spec!r}") -def _active_fields(model_cls: Type["SszModel"]) -> Sequence[int]: +def _active_fields(model_cls: Type["SSZModel"]) -> Sequence[int]: """The active-field bitvector, defaulting to every SSZ field active.""" declared = getattr(model_cls, "__active_fields__", ()) return declared if declared else [1] * len(_included_fields(model_cls)) def _nested_fork( - model_cls: Type["SszModel"], fork: Optional[str] + model_cls: Type["SSZModel"], fork: Optional[str] ) -> Optional[str]: """ The fork a nested container is projected at. @@ -594,7 +594,7 @@ def _nested_fork( def _schema_fields( - model_cls: Type["SszModel"], fork: Optional[str] + model_cls: Type["SSZModel"], fork: Optional[str] ) -> Tuple[str, ...]: """ The SSZ field names of model_cls, in canonical order. @@ -619,7 +619,7 @@ def _schema_fields( def ssz_fields( - model_cls: Type["SszModel"], fork: Optional[str] = None + model_cls: Type["SSZModel"], fork: Optional[str] = None ) -> Tuple[str, ...]: """ The SSZ field names of model_cls, in canonical (wire) order. @@ -632,7 +632,7 @@ def ssz_fields( def _check_populated( - model: "SszModel", names: Tuple[str, ...], fork: str + model: "SSZModel", names: Tuple[str, ...], fork: str ) -> None: """Raise unless the populated fields exactly match the fork schema.""" missing = [n for n in names if getattr(model, n) is None] @@ -650,7 +650,7 @@ def _check_populated( def build_ssz_type( - model_cls: Type["SszModel"], fork: Optional[str] = None + model_cls: Type["SSZModel"], fork: Optional[str] = None ) -> Type[Container]: """ Build the remerkleable container type mirroring model_cls. @@ -664,7 +664,7 @@ def build_ssz_type( @lru_cache(maxsize=None) def _build_ssz_type( - model_cls: Type["SszModel"], fork: Optional[str] + model_cls: Type["SSZModel"], fork: Optional[str] ) -> Type[Container]: names = _schema_fields(model_cls, fork) anns = {name: _rmk_type(spec_of(model_cls, name), fork) for name in names} @@ -678,18 +678,18 @@ def _build_ssz_type( return type(cls_name, (base,), {"__annotations__": anns}) -def _to_rmk(spec: SszType, value: Any, fork: Optional[str] = None) -> Any: - if isinstance(spec, (SszContainer, SszProgressiveContainer)): +def _to_rmk(spec: SSZType, value: Any, fork: Optional[str] = None) -> Any: + if isinstance(spec, (SSZContainer, SSZProgressiveContainer)): return _rmk_instance(value, _nested_fork(spec.model, fork)) - if isinstance(spec, (SszList, SszVector, SszProgressiveList)): + if isinstance(spec, (SSZList, SSZVector, SSZProgressiveList)): return [_to_rmk(spec.element, v, fork) for v in value] - if isinstance(spec, (SszBitvector, SszBitlist, SszProgressiveBitlist)): + if isinstance(spec, (SSZBitvector, SSZBitlist, SSZProgressiveBitlist)): return list(value) return value # scalar / byte-vector / byte-list: remerkleable coerces -def _rmk_instance(model: "SszModel", fork: Optional[str] = None) -> Container: - model_cls: Type[SszModel] = type(model) +def _rmk_instance(model: "SSZModel", fork: Optional[str] = None) -> Container: + model_cls: Type[SSZModel] = type(model) names = _schema_fields(model_cls, fork) if fork is not None: _check_populated(model, names, fork) @@ -701,21 +701,21 @@ def _rmk_instance(model: "SszModel", fork: Optional[str] = None) -> Container: return container(**values) -def _to_py(spec: SszType, value: Any, fork: Optional[str] = None) -> Any: - if isinstance(spec, (SszContainer, SszProgressiveContainer)): +def _to_py(spec: SSZType, value: Any, fork: Optional[str] = None) -> Any: + if isinstance(spec, (SSZContainer, SSZProgressiveContainer)): nested = _nested_fork(spec.model, fork) return _view_to_model( spec.model, value, _schema_fields(spec.model, nested), nested ) - if isinstance(spec, (SszList, SszVector, SszProgressiveList)): + if isinstance(spec, (SSZList, SSZVector, SSZProgressiveList)): return [_to_py(spec.element, v, fork) for v in value] - if isinstance(spec, (SszBitvector, SszBitlist, SszProgressiveBitlist)): + if isinstance(spec, (SSZBitvector, SSZBitlist, SSZProgressiveBitlist)): return [bool(b) for b in value] - if isinstance(spec, (SszByteVector, SszByteList)): + if isinstance(spec, (SSZByteVector, SSZByteList)): return bytes(value) - if isinstance(spec, SszUint): + if isinstance(spec, SSZUint): return int(value) - if isinstance(spec, SszBool): + if isinstance(spec, SSZBool): return bool(value) raise TypeError(f"unhandled SSZ type {spec!r}") @@ -737,28 +737,28 @@ def _view_to_model( ) -def default_value(spec: SszType, fork: Optional[str] = None) -> Any: +def default_value(spec: SSZType, fork: Optional[str] = None) -> Any: """Return the SSZ default (zero) value for spec as a pydantic value.""" - if isinstance(spec, SszUint): + if isinstance(spec, SSZUint): return 0 - if isinstance(spec, SszByteVector): + if isinstance(spec, SSZByteVector): return b"\x00" * spec.length if isinstance( spec, - (SszByteList, SszList, SszBitlist, SszProgressiveList), + (SSZByteList, SSZList, SSZBitlist, SSZProgressiveList), ): return [] - if isinstance(spec, SszProgressiveBitlist): + if isinstance(spec, SSZProgressiveBitlist): return [] - if isinstance(spec, SszVector): + if isinstance(spec, SSZVector): # A fresh value per slot: container defaults are mutable, so a shared # [x] * n would alias one instance across every position. return [default_value(spec.element, fork) for _ in range(spec.length)] - if isinstance(spec, SszBitvector): + if isinstance(spec, SSZBitvector): return [False] * spec.length - if isinstance(spec, (SszContainer, SszProgressiveContainer)): + if isinstance(spec, (SSZContainer, SSZProgressiveContainer)): return ssz_default(spec.model, _nested_fork(spec.model, fork)) - if isinstance(spec, SszBool): + if isinstance(spec, SSZBool): return False raise TypeError(f"no default for SSZ type {spec!r}") @@ -777,37 +777,37 @@ def ssz_default(model_cls: Type[_M], fork: Optional[str] = None) -> _M: ) -def describe_type(spec: SszType) -> str: +def describe_type(spec: SSZType) -> str: """Render an SSZ type as text (uint64, List[T, N], ...).""" - if isinstance(spec, SszUint): + if isinstance(spec, SSZUint): return f"uint{spec.bits}" - if isinstance(spec, SszByteVector): + if isinstance(spec, SSZByteVector): return f"ByteVector[{spec.length}]" - if isinstance(spec, SszByteList): + if isinstance(spec, SSZByteList): return f"ByteList[{spec.limit}]" - if isinstance(spec, SszList): + if isinstance(spec, SSZList): return f"List[{describe_type(spec.element)}, {spec.limit}]" - if isinstance(spec, SszVector): + if isinstance(spec, SSZVector): return f"Vector[{describe_type(spec.element)}, {spec.length}]" - if isinstance(spec, SszBitvector): + if isinstance(spec, SSZBitvector): return f"Bitvector[{spec.length}]" - if isinstance(spec, SszBitlist): + if isinstance(spec, SSZBitlist): return f"Bitlist[{spec.limit}]" - if isinstance(spec, SszProgressiveList): + if isinstance(spec, SSZProgressiveList): return f"ProgressiveList[{describe_type(spec.element)}]" - if isinstance(spec, SszProgressiveBitlist): + if isinstance(spec, SSZProgressiveBitlist): return "ProgressiveBitlist" - if isinstance(spec, SszContainer): + if isinstance(spec, SSZContainer): return spec.model.__name__ - if isinstance(spec, SszProgressiveContainer): + if isinstance(spec, SSZProgressiveContainer): return f"Progressive[{spec.model.__name__}]" - if isinstance(spec, SszBool): + if isinstance(spec, SSZBool): return "boolean" raise TypeError(f"unhandled SSZ type {spec!r}") def describe_schema( - model_cls: Type["SszModel"], fork: Optional[str] = None + model_cls: Type["SSZModel"], fork: Optional[str] = None ) -> str: """ Render the resolved SSZ layout, one 'field: type' line per field. @@ -821,7 +821,7 @@ def describe_schema( return "\n".join(lines) -def encode(model: "SszModel", fork: Optional[str] = None) -> bytes: +def encode(model: "SSZModel", fork: Optional[str] = None) -> bytes: """ Return the SSZ wire bytes of model. @@ -831,7 +831,7 @@ def encode(model: "SszModel", fork: Optional[str] = None) -> bytes: return _rmk_instance(model, fork).encode_bytes() -def hash_tree_root(model: "SszModel", fork: Optional[str] = None) -> bytes: +def hash_tree_root(model: "SSZModel", fork: Optional[str] = None) -> bytes: """ Return the 32-byte SSZ hash_tree_root of model. @@ -873,61 +873,61 @@ class Uint8(_SizedUint): """An 8-bit unsigned integer.""" __bits__: ClassVar[int] = 8 - __ssz__: ClassVar[SszType] = SszUint(8) + __ssz__: ClassVar[SSZType] = SSZUint(8) class Uint16(_SizedUint): """A 16-bit unsigned integer.""" __bits__: ClassVar[int] = 16 - __ssz__: ClassVar[SszType] = SszUint(16) + __ssz__: ClassVar[SSZType] = SSZUint(16) class Uint32(_SizedUint): """A 32-bit unsigned integer.""" __bits__: ClassVar[int] = 32 - __ssz__: ClassVar[SszType] = SszUint(32) + __ssz__: ClassVar[SSZType] = SSZUint(32) class Uint64(_SizedUint): """A 64-bit unsigned integer.""" __bits__: ClassVar[int] = 64 - __ssz__: ClassVar[SszType] = SszUint(64) + __ssz__: ClassVar[SSZType] = SSZUint(64) class Uint128(_SizedUint): """A 128-bit unsigned integer.""" __bits__: ClassVar[int] = 128 - __ssz__: ClassVar[SszType] = SszUint(128) + __ssz__: ClassVar[SSZType] = SSZUint(128) class Uint256(_SizedUint): """A 256-bit unsigned integer.""" __bits__: ClassVar[int] = 256 - __ssz__: ClassVar[SszType] = SszUint(256) + __ssz__: ClassVar[SSZType] = SSZUint(256) __all__ = [ "ProgressiveModel", - "SszBitlist", - "SszBitvector", - "SszBool", - "SszByteList", - "SszByteVector", - "SszContainer", - "SszForkSchema", - "SszList", - "SszModel", - "SszProgressiveBitlist", - "SszProgressiveContainer", - "SszProgressiveList", - "SszType", - "SszUint", - "SszVector", + "SSZBitlist", + "SSZBitvector", + "SSZBool", + "SSZByteList", + "SSZByteVector", + "SSZContainer", + "SSZForkSchema", + "SSZList", + "SSZModel", + "SSZProgressiveBitlist", + "SSZProgressiveContainer", + "SSZProgressiveList", + "SSZType", + "SSZUint", + "SSZVector", "Uint128", "Uint16", "Uint256", diff --git a/packages/testing/src/execution_testing/base_types/tests/test_ssz.py b/packages/testing/src/execution_testing/base_types/tests/test_ssz.py index 7b1b8ee649f..ba6e1ed9bdc 100644 --- a/packages/testing/src/execution_testing/base_types/tests/test_ssz.py +++ b/packages/testing/src/execution_testing/base_types/tests/test_ssz.py @@ -22,9 +22,9 @@ from execution_testing.base_types import Address, Bloom, Bytes, Hash from execution_testing.base_types.ssz import ( ProgressiveModel, - SszForkSchema, - SszModel, - SszUint, + SSZForkSchema, + SSZModel, + SSZUint, Uint8, Uint16, Uint32, @@ -57,7 +57,7 @@ BITS = [i % 3 == 0 for i in range(CELLS)] -class Withdrawal(SszModel): +class Withdrawal(SSZModel): """A pydantic model declared to check the SSZ machinery.""" index: Uint64 @@ -66,7 +66,7 @@ class Withdrawal(SszModel): amount: Uint64 -class ExecutionPayload(SszModel): +class ExecutionPayload(SSZModel): """An Amsterdam-shaped payload exercising every field kind.""" parent_hash: Hash @@ -83,21 +83,21 @@ class ExecutionPayload(SszModel): withdrawals: Annotated[List[Withdrawal], ssz_list(MAX_WITHDRAWALS)] -class Status(SszModel): +class Status(SSZModel): """A boolean and a fixed bit vector.""" ok: bool columns: Annotated[List[bool], bitvector(CELLS)] -class Committee(SszModel): +class Committee(SSZModel): """A fixed Vector[uint64, N] and a variable Bitlist[N].""" seats: Annotated[List[Uint64], ssz_vector(3)] flags: Annotated[List[bool], bitlist(8)] -class Ballot(SszModel): +class Ballot(SSZModel): """An uncapped progressive bit list.""" votes: Annotated[List[bool], progressive_bitlist()] @@ -128,7 +128,7 @@ class MixedProg(ProgressiveModel): c: Uint64 -class ForkedPayload(SszModel): +class ForkedPayload(SSZModel): """One model for every fork.""" parent_hash: Hash @@ -142,7 +142,7 @@ class ForkedPayload(SszModel): Annotated[List[Withdrawal], ssz_list(MAX_WITHDRAWALS)] | None ) = None - __ssz_schema__ = SszForkSchema( + __ssz_schema__ = SSZForkSchema( base_fork="Paris", base=("parent_hash", "block_number", "transactions"), appended={ @@ -152,7 +152,7 @@ class ForkedPayload(SszModel): ) -class Mixed(SszModel): +class Mixed(SSZModel): """An SSZ container carrying a JSON-only (excluded) field.""" a: Uint64 @@ -311,7 +311,7 @@ def _shanghai_payload() -> ForkedPayload: def assert_matches_reference( - model: SszModel, ref: Container, fork: Optional[str] = None + model: SSZModel, ref: Container, fork: Optional[str] = None ) -> None: """ Compare the engine against a hand-written remerkleable twin. @@ -337,7 +337,7 @@ def assert_matches_reference( TWIN_CASES: List[ Tuple[ str, - Callable[[], SszModel], + Callable[[], SSZModel], Callable[[], Container], Optional[str], ] @@ -418,7 +418,7 @@ def assert_matches_reference( [pytest.param(m, r, f, id=name) for name, m, r, f in TWIN_CASES], ) def test_matches_remerkleable_reference( - make_model: Callable[[], SszModel], + make_model: Callable[[], SSZModel], make_ref: Callable[[], Container], fork: Optional[str], ) -> None: @@ -471,10 +471,10 @@ def test_describe_schema_renders_every_field_kind() -> None: def test_default_vector_of_container_has_independent_slots() -> None: """A defaulted Vector-of-container has independent (non-aliased) slots.""" - class Inner(SszModel): + class Inner(SSZModel): x: Uint64 - class Outer(SszModel): + class Outer(SSZModel): items: Annotated[List[Inner], ssz_vector(3)] zero = ssz_default(Outer) @@ -514,7 +514,7 @@ def test_fork_scoped_nested_in_complete_model_raises() -> None: test_fork_propagates_to_nested_containers.) """ - class Wrapper(SszModel): + class Wrapper(SSZModel): payload: ForkedPayload wrapper = Wrapper(payload=_shanghai_payload()) @@ -531,11 +531,11 @@ def test_fork_propagates_to_nested_containers() -> None: the same chain fork. The outer fork= selects every nested projection. """ - class Envelope(SszModel): + class Envelope(SSZModel): payload: ForkedPayload blob_count: Uint64 | None = None # Shanghai-era envelope field - __ssz_schema__ = SszForkSchema( + __ssz_schema__ = SSZForkSchema( base_fork="Paris", base=("payload",), appended={"Shanghai": ("blob_count",)}, @@ -577,57 +577,57 @@ def test_forked_model_describe_schema_per_fork() -> None: def _bad_vector_marker_on_scalar() -> None: - class Bad(SszModel): + class Bad(SSZModel): seats: Annotated[Uint64, ssz_vector(3)] # not a list def _bad_byte_list_on_int() -> None: - class Bad(SszModel): + class Bad(SSZModel): data: Annotated[Uint64, byte_list(8)] # not Bytes def _bad_bit_marker_on_ints() -> None: - class Bad(SszModel): + class Bad(SSZModel): flags: Annotated[List[Uint64], bitlist(8)] # not list[bool] def _bad_raw_ssz_type_marker() -> None: - class Bad(SszModel): - x: Annotated[Uint64, SszUint(32)] # raw SszType, not a helper + class Bad(SSZModel): + x: Annotated[Uint64, SSZUint(32)] # raw SSZType, not a helper def _bad_unmapped_str() -> None: - class Bad(SszModel): + class Bad(SSZModel): s: str # no SSZ mapping and not excluded def _bad_bare_bytes() -> None: - class Bad(SszModel): + class Bad(SSZModel): data: Bytes # variable bytes need a byte_list cap def _bad_bare_list() -> None: - class Bad(SszModel): + class Bad(SSZModel): items: List[Uint64] # lists need a cap/length marker def _bad_multi_arm_union() -> None: - class Bad(SszModel): + class Bad(SSZModel): x: Uint64 | Uint8 | None = None # only T | None supported def _bad_optional_without_schema() -> None: - class Bad(SszModel): + class Bad(SSZModel): a: Uint64 b: Uint64 | None = None # optional but no schema def _bad_schema_field_typo() -> None: - class Bad(SszModel): + class Bad(SSZModel): a: Uint64 b: Uint64 | None = None - __ssz_schema__ = SszForkSchema( + __ssz_schema__ = SSZForkSchema( base_fork="Paris", base=("a",), appended={"Shanghai": ("typo",)}, @@ -635,11 +635,11 @@ class Bad(SszModel): def _bad_required_appended() -> None: - class Bad(SszModel): + class Bad(SSZModel): a: Uint64 b: Uint64 # appended but not optional - __ssz_schema__ = SszForkSchema( + __ssz_schema__ = SSZForkSchema( base_fork="Paris", base=("a",), appended={"Shanghai": ("b",)}, @@ -647,11 +647,11 @@ class Bad(SszModel): def _bad_optional_base() -> None: - class Bad(SszModel): + class Bad(SSZModel): a: Uint64 b: Uint64 | None = None # optional but declared in base - __ssz_schema__ = SszForkSchema( + __ssz_schema__ = SSZForkSchema( base_fork="Paris", base=("a", "b"), appended={}, @@ -659,11 +659,11 @@ class Bad(SszModel): def _bad_appended_no_default() -> None: - class Bad(SszModel): + class Bad(SSZModel): a: Uint64 b: Uint64 | None # optional type but NO None default - __ssz_schema__ = SszForkSchema( + __ssz_schema__ = SSZForkSchema( base_fork="Paris", base=("a",), appended={"Shanghai": ("b",)}, @@ -671,11 +671,11 @@ class Bad(SszModel): def _bad_duplicate_schema_names() -> None: - class Bad(SszModel): + class Bad(SSZModel): a: Uint64 b: Uint64 | None = None - __ssz_schema__ = SszForkSchema( + __ssz_schema__ = SSZForkSchema( base_fork="Paris", base=("a", "a"), appended={"Shanghai": ("b",)}, @@ -683,7 +683,7 @@ class Bad(SszModel): def _bad_required_excluded() -> None: - class Bad(SszModel): + class Bad(SSZModel): a: Uint64 note: Annotated[str, ssz_exclude()] # excluded but required @@ -692,7 +692,7 @@ def _bad_progressive_with_schema() -> None: class Bad(ProgressiveModel): a: Uint64 - __ssz_schema__ = SszForkSchema( + __ssz_schema__ = SSZForkSchema( base_fork="Paris", base=("a",), appended={} ) @@ -837,7 +837,7 @@ def test_build_ssz_type_cache_identity() -> None: ) def make_dup() -> type: - class Dup(SszModel): + class Dup(SSZModel): a: Uint64 return Dup @@ -895,12 +895,12 @@ def test_excluded_field_is_json_only() -> None: def test_excluded_field_on_fork_scoped_model() -> None: """Exclusion composes with __ssz_schema__ (schema skips the field).""" - class ForkedMixed(SszModel): + class ForkedMixed(SSZModel): a: Uint64 b: Uint64 | None = None note: Annotated[str, ssz_exclude()] = "aux" - __ssz_schema__ = SszForkSchema( + __ssz_schema__ = SSZForkSchema( base_fork="One", base=("a",), appended={"Two": ("b",)}, @@ -948,10 +948,10 @@ def test_spec_of_rejects_excluded_field() -> None: def test_single_fork_schema_works_end_to_end() -> None: """A schema with no appended forks is valid and encodable.""" - class OnlyFork(SszModel): + class OnlyFork(SSZModel): a: Uint64 - __ssz_schema__ = SszForkSchema( + __ssz_schema__ = SSZForkSchema( base_fork="Only", base=("a",), appended={} ) diff --git a/packages/testing/src/execution_testing/tools/ssz_vectors.py b/packages/testing/src/execution_testing/tools/ssz_vectors.py index 2bc26b7a9dc..9c6e637cf03 100644 --- a/packages/testing/src/execution_testing/tools/ssz_vectors.py +++ b/packages/testing/src/execution_testing/tools/ssz_vectors.py @@ -31,20 +31,20 @@ import yaml from execution_testing.base_types.ssz import ( - SszBitlist, - SszBitvector, - SszBool, - SszByteList, - SszByteVector, - SszContainer, - SszList, - SszModel, - SszProgressiveBitlist, - SszProgressiveContainer, - SszProgressiveList, - SszType, - SszUint, - SszVector, + SSZBitlist, + SSZBitvector, + SSZBool, + SSZByteList, + SSZByteVector, + SSZContainer, + SSZList, + SSZModel, + SSZProgressiveBitlist, + SSZProgressiveContainer, + SSZProgressiveList, + SSZType, + SSZUint, + SSZVector, encode, hash_tree_root, spec_of, @@ -56,7 +56,7 @@ RANDOM_CASE_COUNT = 30 -_M = TypeVar("_M", bound=SszModel) +_M = TypeVar("_M", bound=SSZModel) _MODE_NAMES = ( "random", @@ -147,7 +147,7 @@ def _bitlist_length( def random_value( rng: random.Random, - spec: SszType, + spec: SSZType, mode: RandomizationMode, *, max_bytes_length: int = MAX_BYTES_LENGTH, @@ -158,12 +158,12 @@ def random_value( Build a pydantic value of spec filled with random data per mode. A port of the consensus-specs get_random_ssz_object, branching on the - engine's SszType descriptors. With chaos, the mode is re-drawn at every + engine's SSZType descriptors. With chaos, the mode is re-drawn at every level of the value tree. """ if chaos: mode = rng.choice(list(RandomizationMode)) - if isinstance(spec, SszByteList): + if isinstance(spec, SSZByteList): if mode == RandomizationMode.mode_nil_count: return b"" if mode == RandomizationMode.mode_max_count: @@ -177,41 +177,41 @@ def random_value( return _random_bytes( rng, rng.randint(0, min(max_bytes_length, spec.limit)) ) - if isinstance(spec, SszByteVector): + if isinstance(spec, SSZByteVector): # Byte vectors are fixed length; no max-bytes cap applies. if mode == RandomizationMode.mode_zero: return b"\x00" * spec.length if mode == RandomizationMode.mode_max: return b"\xff" * spec.length return _random_bytes(rng, spec.length) - if isinstance(spec, SszUint): + if isinstance(spec, SSZUint): if mode == RandomizationMode.mode_zero: return 0 if mode == RandomizationMode.mode_max: return (1 << spec.bits) - 1 return rng.randint(0, (1 << spec.bits) - 1) - if isinstance(spec, SszBool): + if isinstance(spec, SSZBool): if mode == RandomizationMode.mode_zero: return False if mode == RandomizationMode.mode_max: return True return bool(rng.getrandbits(1)) - if isinstance(spec, SszBitvector): + if isinstance(spec, SSZBitvector): # Bit vectors are fixed length; no cap applies. return _bits(rng, spec.length, mode) - if isinstance(spec, SszBitlist): + if isinstance(spec, SSZBitlist): # Consensus caps bit lists by the LIST cap, not the byte cap. cap = min(max_list_length, spec.limit) length = _bitlist_length(rng, cap, mode) return _bits(rng, length, mode) - if isinstance(spec, SszProgressiveBitlist): + if isinstance(spec, SSZProgressiveBitlist): # Progressive bit lists are uncapped; the list cap bounds them. length = _bitlist_length(rng, max_list_length, mode) return _bits(rng, length, mode) - if isinstance(spec, (SszList, SszProgressiveList)): + if isinstance(spec, (SSZList, SSZProgressiveList)): # Progressive lists are uncapped; the list cap bounds them. limit = max_list_length - if isinstance(spec, SszList) and spec.limit < limit: + if isinstance(spec, SSZList) and spec.limit < limit: limit = spec.limit length = rng.randint(0, limit) if mode == RandomizationMode.mode_one_count: @@ -233,7 +233,7 @@ def random_value( ) for _ in range(length) ] - if isinstance(spec, SszVector): + if isinstance(spec, SSZVector): return [ random_value( rng, @@ -245,7 +245,7 @@ def random_value( ) for _ in range(spec.length) ] - if isinstance(spec, (SszContainer, SszProgressiveContainer)): + if isinstance(spec, (SSZContainer, SSZProgressiveContainer)): return random_model( rng, spec.model, @@ -288,7 +288,7 @@ def random_model( ) -def make_case(model: SszModel, fork: Optional[str] = None) -> VectorCase: +def make_case(model: SSZModel, fork: Optional[str] = None) -> VectorCase: """Turn a model instance into its ssz_static case triple.""" return VectorCase( value=model.model_dump(mode="json", exclude_none=True), @@ -331,17 +331,17 @@ def suite_plan( return plan -ModelSpec = Union[Type[SszModel], Tuple[Type[SszModel], str]] +ModelSpec = Union[Type[SSZModel], Tuple[Type[SSZModel], str]] def _normalize_models( models: Sequence[ModelSpec], -) -> List[Tuple[Type[SszModel], Optional[str]]]: +) -> List[Tuple[Type[SSZModel], Optional[str]]]: """Normalize entries to (model, fork) and reject output collisions.""" - entries: List[Tuple[Type[SszModel], Optional[str]]] = [ + entries: List[Tuple[Type[SSZModel], Optional[str]]] = [ m if isinstance(m, tuple) else (m, None) for m in models ] - seen: Dict[Tuple[str, Optional[str]], Type[SszModel]] = {} + seen: Dict[Tuple[str, Optional[str]], Type[SSZModel]] = {} for model_cls, fork in entries: key = (model_cls.__name__, fork) other = seen.setdefault(key, model_cls) diff --git a/packages/testing/src/execution_testing/tools/tests/test_ssz_vectors.py b/packages/testing/src/execution_testing/tools/tests/test_ssz_vectors.py index fa07717bede..3478d4e0c7c 100644 --- a/packages/testing/src/execution_testing/tools/tests/test_ssz_vectors.py +++ b/packages/testing/src/execution_testing/tools/tests/test_ssz_vectors.py @@ -16,8 +16,8 @@ from execution_testing.base_types import Address, Bytes, Hash from execution_testing.base_types.ssz import ( - SszForkSchema, - SszModel, + SSZForkSchema, + SSZModel, Uint64, Uint256, byte_list, @@ -44,7 +44,7 @@ MAX_WITHDRAWALS = 16 -class Withdrawal(SszModel): +class Withdrawal(SSZModel): """A withdrawal container.""" index: Uint64 @@ -53,7 +53,7 @@ class Withdrawal(SszModel): amount: Uint64 -class Payload(SszModel): +class Payload(SSZModel): """A container with a byte-list, a capped list, and a nested list.""" parent_hash: Hash @@ -66,7 +66,7 @@ class Payload(SszModel): withdrawals: Annotated[List[Withdrawal], ssz_list(MAX_WITHDRAWALS)] -class ForkedPayload(SszModel): +class ForkedPayload(SSZModel): """A fork-scoped model, for the generator's fork axis.""" parent_hash: Hash @@ -75,14 +75,14 @@ class ForkedPayload(SszModel): Annotated[List[Withdrawal], ssz_list(MAX_WITHDRAWALS)] | None ) = None - __ssz_schema__ = SszForkSchema( + __ssz_schema__ = SSZForkSchema( base_fork="Paris", base=("parent_hash", "block_number"), appended={"Shanghai": ("withdrawals",)}, ) -def assert_roundtrip(model: SszModel) -> None: +def assert_roundtrip(model: SSZModel) -> None: """Reusable harness: encode -> decode reconstructs the SSZ value.""" restored = decode(type(model), encode(model)) assert encode(restored) == encode(model) @@ -221,7 +221,7 @@ def test_chaos_redraws_modes() -> None: @pytest.mark.parametrize("name", list(Payload.model_fields)) def test_random_value_covers_field_spec(name: str) -> None: - """random_value handles every SszType the test containers use.""" + """random_value handles every SSZType the test containers use.""" rng = random.Random(1) value = random_value( rng, spec_of(Payload, name), RandomizationMode.mode_random @@ -287,7 +287,7 @@ def test_duplicate_vector_targets_rejected(tmp_path: Path) -> None: """Two distinct same-named models cannot share an output directory.""" def make_dup() -> type: - class Withdrawal(SszModel): # same __name__, different class + class Withdrawal(SSZModel): # same __name__, different class a: Uint64 return Withdrawal From 124f2181d7bd97a25e69a77dce020abf8cefa867 Mon Sep 17 00:00:00 2001 From: spencer <spencer.taylor-brown@ethereum.org> Date: Wed, 12 Aug 2026 11:38:27 +0200 Subject: [PATCH 220/233] refactor(spec-specs): split _prepare_data out of _prepare_trie (#3353) Co-authored-by: jsign <6136245+jsign@users.noreply.github.com> --- src/ethereum/merkle_patricia_trie.py | 39 +++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/src/ethereum/merkle_patricia_trie.py b/src/ethereum/merkle_patricia_trie.py index 76b26896fc5..0dbf455ad21 100644 --- a/src/ethereum/merkle_patricia_trie.py +++ b/src/ethereum/merkle_patricia_trie.py @@ -404,12 +404,13 @@ def bytes_to_nibble_list(bytes_: Bytes) -> Bytes: return Bytes(nibble_list) -def _prepare_trie( - trie: Trie[K, V], +def _prepare_data( + data: Mapping[K, V], + secured: bool, get_storage_root: Optional[Callable[[Address], Root]] = None, ) -> Mapping[Bytes, Bytes]: """ - Convert a [`Trie`] into the nibble-keyed mapping consumed by + Convert trie data into the nibble-keyed mapping consumed by [`patricialize`]. Each value is encoded with [`encode_node`]; if the value is an @@ -417,7 +418,6 @@ def _prepare_trie( root. Keys are hashed with [`keccak256`] when the trie is secured, then expanded into nibble form via [`bytes_to_nibble_list`][bnl]. - [`Trie`]: ref:ethereum.merkle_patricia_trie.Trie [`patricialize`]: ref:ethereum.merkle_patricia_trie.patricialize [`encode_node`]: ref:ethereum.merkle_patricia_trie.encode_node [`Account`]: ref:ethereum.state.Account @@ -426,7 +426,7 @@ def _prepare_trie( """ mapped: MutableMapping[Bytes, Bytes] = {} - for preimage, value in trie._data.items(): + for preimage, value in data.items(): if isinstance(value, Account): assert get_storage_root is not None address = Address(preimage) @@ -438,7 +438,7 @@ def _prepare_trie( if encoded_value == b"": raise AssertionError key: Bytes - if trie.secured: + if secured: # "secure" tries hash keys once before construction key = keccak256(preimage) else: @@ -448,6 +448,33 @@ def _prepare_trie( return mapped +def _prepare_trie( + trie: Trie[K, V], + get_storage_root: Optional[Callable[[Address], Root]] = None, +) -> Mapping[Bytes, Bytes]: + """ + Prepare the trie for root calculation. + + Remove values that are empty, hash the keys (if + ``secured == True``) and encode all the nodes. + + Parameters + ---------- + trie : + The ``Trie`` to prepare. + get_storage_root : + Function to get the storage root of an account. Needed + to encode ``Account`` objects. + + Returns + ------- + out : `Mapping[ethereum.base_types.Bytes, Node]` + Object with keys mapped to nibble-byte form. + + """ + return _prepare_data(trie._data, trie.secured, get_storage_root) + + def root( trie: Trie[K, V], get_storage_root: Optional[Callable[[Address], Root]] = None, From 2867859a3c19b925f7dc47dae648cca9758f4f80 Mon Sep 17 00:00:00 2001 From: spencer <spencer.taylor-brown@ethereum.org> Date: Wed, 12 Aug 2026 11:38:49 +0200 Subject: [PATCH 221/233] fix(spec-specs): carry excess blob gas across fork transitions (#3352) Co-authored-by: jsign <6136245+jsign@users.noreply.github.com> --- src/ethereum/forks/amsterdam/vm/gas.py | 7 ++++--- src/ethereum/forks/bpo1/vm/gas.py | 12 ++++++++---- src/ethereum/forks/bpo2/vm/gas.py | 12 ++++++++---- src/ethereum/forks/bpo3/vm/gas.py | 12 ++++++++---- src/ethereum/forks/bpo4/vm/gas.py | 12 ++++++++---- src/ethereum/forks/bpo5/vm/gas.py | 12 ++++++++---- src/ethereum/forks/osaka/vm/gas.py | 12 ++++++++---- src/ethereum/forks/prague/vm/gas.py | 12 ++++++++---- 8 files changed, 60 insertions(+), 31 deletions(-) diff --git a/src/ethereum/forks/amsterdam/vm/gas.py b/src/ethereum/forks/amsterdam/vm/gas.py index 25aac27d6b8..d599a1b654e 100644 --- a/src/ethereum/forks/amsterdam/vm/gas.py +++ b/src/ethereum/forks/amsterdam/vm/gas.py @@ -878,13 +878,14 @@ def calculate_excess_blob_gas( The excess blob gas for the current block. """ - # At the fork block, these are defined as zero. + # Defaults for a parent without blob gas fields. excess_blob_gas = U64(0) blob_gas_used = U64(0) base_fee_per_gas = Uint(0) - if isinstance(parent_header, Header): - # After the fork block, read them from the parent header. + if isinstance(parent_header, (Header, PreviousHeader)): + # Read them from any parent that carries the fields, so + # accumulated excess blob gas survives a fork transition. excess_blob_gas = parent_header.excess_blob_gas blob_gas_used = parent_header.blob_gas_used base_fee_per_gas = parent_header.base_fee_per_gas diff --git a/src/ethereum/forks/bpo1/vm/gas.py b/src/ethereum/forks/bpo1/vm/gas.py index 4d7fe3f4c32..c774efe9e3f 100644 --- a/src/ethereum/forks/bpo1/vm/gas.py +++ b/src/ethereum/forks/bpo1/vm/gas.py @@ -16,6 +16,7 @@ from ethereum_types.numeric import U64, U256, Uint, ulen +from ethereum.forks.osaka.blocks import Header as PreviousHeader from ethereum.trace import GasAndRefund, evm_trace from ethereum.utils.numeric import ceil32, taylor_exponential @@ -392,7 +393,9 @@ def init_code_cost(init_code_length: Uint) -> Uint: return GasCosts.CODE_INIT_PER_WORD * ceil32(init_code_length) // Uint(32) -def calculate_excess_blob_gas(parent_header: Header) -> U64: +def calculate_excess_blob_gas( + parent_header: Header | PreviousHeader, +) -> U64: """ Calculates the excess blob gas for the current block based on the gas used in the parent block. @@ -408,13 +411,14 @@ def calculate_excess_blob_gas(parent_header: Header) -> U64: The excess blob gas for the current block. """ - # At the fork block, these are defined as zero. + # Defaults for a parent without blob gas fields. excess_blob_gas = U64(0) blob_gas_used = U64(0) base_fee_per_gas = Uint(0) - if isinstance(parent_header, Header): - # After the fork block, read them from the parent header. + if isinstance(parent_header, (Header, PreviousHeader)): + # Read them from any parent that carries the fields, so + # accumulated excess blob gas survives a fork transition. excess_blob_gas = parent_header.excess_blob_gas blob_gas_used = parent_header.blob_gas_used base_fee_per_gas = parent_header.base_fee_per_gas diff --git a/src/ethereum/forks/bpo2/vm/gas.py b/src/ethereum/forks/bpo2/vm/gas.py index 653f6eb9ea1..7bac9ef1a15 100644 --- a/src/ethereum/forks/bpo2/vm/gas.py +++ b/src/ethereum/forks/bpo2/vm/gas.py @@ -16,6 +16,7 @@ from ethereum_types.numeric import U64, U256, Uint, ulen +from ethereum.forks.bpo1.blocks import Header as PreviousHeader from ethereum.trace import GasAndRefund, evm_trace from ethereum.utils.numeric import ceil32, taylor_exponential @@ -392,7 +393,9 @@ def init_code_cost(init_code_length: Uint) -> Uint: return GasCosts.CODE_INIT_PER_WORD * ceil32(init_code_length) // Uint(32) -def calculate_excess_blob_gas(parent_header: Header) -> U64: +def calculate_excess_blob_gas( + parent_header: Header | PreviousHeader, +) -> U64: """ Calculates the excess blob gas for the current block based on the gas used in the parent block. @@ -408,13 +411,14 @@ def calculate_excess_blob_gas(parent_header: Header) -> U64: The excess blob gas for the current block. """ - # At the fork block, these are defined as zero. + # Defaults for a parent without blob gas fields. excess_blob_gas = U64(0) blob_gas_used = U64(0) base_fee_per_gas = Uint(0) - if isinstance(parent_header, Header): - # After the fork block, read them from the parent header. + if isinstance(parent_header, (Header, PreviousHeader)): + # Read them from any parent that carries the fields, so + # accumulated excess blob gas survives a fork transition. excess_blob_gas = parent_header.excess_blob_gas blob_gas_used = parent_header.blob_gas_used base_fee_per_gas = parent_header.base_fee_per_gas diff --git a/src/ethereum/forks/bpo3/vm/gas.py b/src/ethereum/forks/bpo3/vm/gas.py index 653f6eb9ea1..1021be96e13 100644 --- a/src/ethereum/forks/bpo3/vm/gas.py +++ b/src/ethereum/forks/bpo3/vm/gas.py @@ -16,6 +16,7 @@ from ethereum_types.numeric import U64, U256, Uint, ulen +from ethereum.forks.bpo2.blocks import Header as PreviousHeader from ethereum.trace import GasAndRefund, evm_trace from ethereum.utils.numeric import ceil32, taylor_exponential @@ -392,7 +393,9 @@ def init_code_cost(init_code_length: Uint) -> Uint: return GasCosts.CODE_INIT_PER_WORD * ceil32(init_code_length) // Uint(32) -def calculate_excess_blob_gas(parent_header: Header) -> U64: +def calculate_excess_blob_gas( + parent_header: Header | PreviousHeader, +) -> U64: """ Calculates the excess blob gas for the current block based on the gas used in the parent block. @@ -408,13 +411,14 @@ def calculate_excess_blob_gas(parent_header: Header) -> U64: The excess blob gas for the current block. """ - # At the fork block, these are defined as zero. + # Defaults for a parent without blob gas fields. excess_blob_gas = U64(0) blob_gas_used = U64(0) base_fee_per_gas = Uint(0) - if isinstance(parent_header, Header): - # After the fork block, read them from the parent header. + if isinstance(parent_header, (Header, PreviousHeader)): + # Read them from any parent that carries the fields, so + # accumulated excess blob gas survives a fork transition. excess_blob_gas = parent_header.excess_blob_gas blob_gas_used = parent_header.blob_gas_used base_fee_per_gas = parent_header.base_fee_per_gas diff --git a/src/ethereum/forks/bpo4/vm/gas.py b/src/ethereum/forks/bpo4/vm/gas.py index 653f6eb9ea1..df6f77d64bd 100644 --- a/src/ethereum/forks/bpo4/vm/gas.py +++ b/src/ethereum/forks/bpo4/vm/gas.py @@ -16,6 +16,7 @@ from ethereum_types.numeric import U64, U256, Uint, ulen +from ethereum.forks.bpo3.blocks import Header as PreviousHeader from ethereum.trace import GasAndRefund, evm_trace from ethereum.utils.numeric import ceil32, taylor_exponential @@ -392,7 +393,9 @@ def init_code_cost(init_code_length: Uint) -> Uint: return GasCosts.CODE_INIT_PER_WORD * ceil32(init_code_length) // Uint(32) -def calculate_excess_blob_gas(parent_header: Header) -> U64: +def calculate_excess_blob_gas( + parent_header: Header | PreviousHeader, +) -> U64: """ Calculates the excess blob gas for the current block based on the gas used in the parent block. @@ -408,13 +411,14 @@ def calculate_excess_blob_gas(parent_header: Header) -> U64: The excess blob gas for the current block. """ - # At the fork block, these are defined as zero. + # Defaults for a parent without blob gas fields. excess_blob_gas = U64(0) blob_gas_used = U64(0) base_fee_per_gas = Uint(0) - if isinstance(parent_header, Header): - # After the fork block, read them from the parent header. + if isinstance(parent_header, (Header, PreviousHeader)): + # Read them from any parent that carries the fields, so + # accumulated excess blob gas survives a fork transition. excess_blob_gas = parent_header.excess_blob_gas blob_gas_used = parent_header.blob_gas_used base_fee_per_gas = parent_header.base_fee_per_gas diff --git a/src/ethereum/forks/bpo5/vm/gas.py b/src/ethereum/forks/bpo5/vm/gas.py index 653f6eb9ea1..42e4d66c858 100644 --- a/src/ethereum/forks/bpo5/vm/gas.py +++ b/src/ethereum/forks/bpo5/vm/gas.py @@ -16,6 +16,7 @@ from ethereum_types.numeric import U64, U256, Uint, ulen +from ethereum.forks.bpo4.blocks import Header as PreviousHeader from ethereum.trace import GasAndRefund, evm_trace from ethereum.utils.numeric import ceil32, taylor_exponential @@ -392,7 +393,9 @@ def init_code_cost(init_code_length: Uint) -> Uint: return GasCosts.CODE_INIT_PER_WORD * ceil32(init_code_length) // Uint(32) -def calculate_excess_blob_gas(parent_header: Header) -> U64: +def calculate_excess_blob_gas( + parent_header: Header | PreviousHeader, +) -> U64: """ Calculates the excess blob gas for the current block based on the gas used in the parent block. @@ -408,13 +411,14 @@ def calculate_excess_blob_gas(parent_header: Header) -> U64: The excess blob gas for the current block. """ - # At the fork block, these are defined as zero. + # Defaults for a parent without blob gas fields. excess_blob_gas = U64(0) blob_gas_used = U64(0) base_fee_per_gas = Uint(0) - if isinstance(parent_header, Header): - # After the fork block, read them from the parent header. + if isinstance(parent_header, (Header, PreviousHeader)): + # Read them from any parent that carries the fields, so + # accumulated excess blob gas survives a fork transition. excess_blob_gas = parent_header.excess_blob_gas blob_gas_used = parent_header.blob_gas_used base_fee_per_gas = parent_header.base_fee_per_gas diff --git a/src/ethereum/forks/osaka/vm/gas.py b/src/ethereum/forks/osaka/vm/gas.py index 575129dfb49..ed1fde8c127 100644 --- a/src/ethereum/forks/osaka/vm/gas.py +++ b/src/ethereum/forks/osaka/vm/gas.py @@ -16,6 +16,7 @@ from ethereum_types.numeric import U64, U256, Uint, ulen +from ethereum.forks.prague.blocks import Header as PreviousHeader from ethereum.trace import GasAndRefund, evm_trace from ethereum.utils.numeric import ceil32, taylor_exponential @@ -392,7 +393,9 @@ def init_code_cost(init_code_length: Uint) -> Uint: return GasCosts.CODE_INIT_PER_WORD * ceil32(init_code_length) // Uint(32) -def calculate_excess_blob_gas(parent_header: Header) -> U64: +def calculate_excess_blob_gas( + parent_header: Header | PreviousHeader, +) -> U64: """ Calculates the excess blob gas for the current block based on the gas used in the parent block. @@ -408,13 +411,14 @@ def calculate_excess_blob_gas(parent_header: Header) -> U64: The excess blob gas for the current block. """ - # At the fork block, these are defined as zero. + # Defaults for a parent without blob gas fields. excess_blob_gas = U64(0) blob_gas_used = U64(0) base_fee_per_gas = Uint(0) - if isinstance(parent_header, Header): - # After the fork block, read them from the parent header. + if isinstance(parent_header, (Header, PreviousHeader)): + # Read them from any parent that carries the fields, so + # accumulated excess blob gas survives a fork transition. excess_blob_gas = parent_header.excess_blob_gas blob_gas_used = parent_header.blob_gas_used base_fee_per_gas = parent_header.base_fee_per_gas diff --git a/src/ethereum/forks/prague/vm/gas.py b/src/ethereum/forks/prague/vm/gas.py index d1c7d94b649..0f546cfbfb1 100644 --- a/src/ethereum/forks/prague/vm/gas.py +++ b/src/ethereum/forks/prague/vm/gas.py @@ -16,6 +16,7 @@ from ethereum_types.numeric import U64, U256, Uint, ulen +from ethereum.forks.cancun.blocks import Header as PreviousHeader from ethereum.trace import GasAndRefund, evm_trace from ethereum.utils.numeric import ceil32, taylor_exponential @@ -387,7 +388,9 @@ def init_code_cost(init_code_length: Uint) -> Uint: return GasCosts.CODE_INIT_PER_WORD * ceil32(init_code_length) // Uint(32) -def calculate_excess_blob_gas(parent_header: Header) -> U64: +def calculate_excess_blob_gas( + parent_header: Header | PreviousHeader, +) -> U64: """ Calculates the excess blob gas for the current block based on the gas used in the parent block. @@ -403,12 +406,13 @@ def calculate_excess_blob_gas(parent_header: Header) -> U64: The excess blob gas for the current block. """ - # At the fork block, these are defined as zero. + # Defaults for a parent without blob gas fields. excess_blob_gas = U64(0) blob_gas_used = U64(0) - if isinstance(parent_header, Header): - # After the fork block, read them from the parent header. + if isinstance(parent_header, (Header, PreviousHeader)): + # Read them from any parent that carries the fields, so + # accumulated excess blob gas survives a fork transition. excess_blob_gas = parent_header.excess_blob_gas blob_gas_used = parent_header.blob_gas_used From a2936ed61f8e8424d91acef49dbf27fe8c0cd09c Mon Sep 17 00:00:00 2001 From: spencer <spencer.taylor-brown@ethereum.org> Date: Thu, 13 Aug 2026 00:48:02 +0200 Subject: [PATCH 222/233] fix(tests): use well-formed versioned hashes in getBlobs negative tests (#3359) --- .../amsterdam/eip8070_sparse_blobpool/spec.py | 5 +++- .../eip8070_sparse_blobpool/test_get_cells.py | 28 ++++++++++--------- tests/osaka/eip7594_peerdas/test_get_blobs.py | 27 +++++++++--------- 3 files changed, 33 insertions(+), 27 deletions(-) diff --git a/tests/amsterdam/eip8070_sparse_blobpool/spec.py b/tests/amsterdam/eip8070_sparse_blobpool/spec.py index d6ec4c58162..8b7ab0ca7ed 100644 --- a/tests/amsterdam/eip8070_sparse_blobpool/spec.py +++ b/tests/amsterdam/eip8070_sparse_blobpool/spec.py @@ -12,7 +12,7 @@ class ReferenceSpec: ref_spec_8070 = ReferenceSpec( - "EIPS/eip-8070.md", "64d1b463e1c75884c995f81d8ffab40401acbcaa" + "EIPS/eip-8070.md", "43c7af020f1641924bed60565fde86f6ad3469df" ) @@ -23,6 +23,9 @@ class Spec: https://eips.ethereum.org/EIPS/eip-8070. """ + BLOB_COMMITMENT_VERSION_KZG = 1 + """Version byte of a KZG blob versioned hash (EIP-4844).""" + CELLS_PER_EXT_BLOB = 128 """Number of cells an extended blob is split into for `getBlobsV4`.""" diff --git a/tests/amsterdam/eip8070_sparse_blobpool/test_get_cells.py b/tests/amsterdam/eip8070_sparse_blobpool/test_get_cells.py index 486fa333f50..b2e0408b02f 100644 --- a/tests/amsterdam/eip8070_sparse_blobpool/test_get_cells.py +++ b/tests/amsterdam/eip8070_sparse_blobpool/test_get_cells.py @@ -22,6 +22,7 @@ Hash, NetworkWrappedTransaction, Transaction, + add_kzg_version, ) from .spec import Spec, ref_spec_8070 @@ -109,6 +110,14 @@ def generate_blob_layouts(fork: Fork) -> List: ] +def generate_nonexisting_blob_hashes(count: int) -> List[Hash]: + """Return well-formed versioned hashes that match no pooled blob.""" + return add_kzg_version( + [sha256(str(i).encode()).digest() for i in range(count)], + Spec.BLOB_COMMITMENT_VERSION_KZG, + ) + + def generate_single_blob_layout(fork: Fork) -> List: """Return a single-blob transaction layout.""" return [ @@ -187,9 +196,7 @@ def test_get_cells_partial_and_missing( Test that `getBlobsV4` returns a partial response: existing blobs yield a cell matrix while non-existing versioned hashes yield `null` entries. """ - nonexisting_blob_hashes = [ - Hash(sha256(str(i).encode()).digest()) for i in range(5) - ] + nonexisting_blob_hashes = generate_nonexisting_blob_hashes(5) blobs_test( pre=pre, txs=txs, @@ -214,9 +221,7 @@ def test_get_cells_only_nonexisting( Test that `getBlobsV4` returns an array of `null` entries (one per requested hash) when all requested blobs are non-existing. """ - nonexisting_blob_hashes = [ - Hash(sha256(str(i).encode()).digest()) for i in range(5) - ] + nonexisting_blob_hashes = generate_nonexisting_blob_hashes(5) blobs_test( pre=pre, txs=[], @@ -245,10 +250,9 @@ def test_get_cells_min_request_size( The response must hold one entry per requested hash: a cell matrix for the existing blob and `null` for each non-existing hash. """ - nonexisting_blob_hashes = [ - Hash(sha256(str(i).encode()).digest()) - for i in range(Spec.MIN_SUPPORTED_REQUEST_SIZE - 1) - ] + nonexisting_blob_hashes = generate_nonexisting_blob_hashes( + Spec.MIN_SUPPORTED_REQUEST_SIZE - 1 + ) blobs_test( pre=pre, txs=txs, @@ -278,9 +282,7 @@ def test_get_cells_interleaved_missing( non-existing hashes are interleaved with existing ones (leading, middle, and trailing positions of the request). """ - nonexisting_blob_hashes = [ - Hash(sha256(str(i).encode()).digest()) for i in range(5) - ] + nonexisting_blob_hashes = generate_nonexisting_blob_hashes(5) blobs_test( pre=pre, txs=txs, diff --git a/tests/osaka/eip7594_peerdas/test_get_blobs.py b/tests/osaka/eip7594_peerdas/test_get_blobs.py index b0e2d073856..926e0e49ca4 100644 --- a/tests/osaka/eip7594_peerdas/test_get_blobs.py +++ b/tests/osaka/eip7594_peerdas/test_get_blobs.py @@ -19,12 +19,13 @@ NetworkWrappedTransaction, Transaction, TransactionException, + add_kzg_version, ) from execution_testing.logging import ( # noqa: E501 get_logger, ) -from .spec import ref_spec_7594 +from .spec import Spec, ref_spec_7594 REFERENCE_SPEC_GIT_PATH = ref_spec_7594.git_path REFERENCE_SPEC_VERSION = ref_spec_7594.version @@ -32,6 +33,14 @@ logger = get_logger(__name__) +def generate_nonexisting_blob_hashes(count: int) -> List[Hash]: + """Return well-formed versioned hashes that match no pooled blob.""" + return add_kzg_version( + [sha256(str(i).encode()).digest() for i in range(count)], + Spec.BLOB_COMMITMENT_VERSION_KZG, + ) + + @pytest.fixture def destination_account(pre: Alloc) -> Address: """Destination account for the blob transactions.""" @@ -385,9 +394,7 @@ def test_get_blobs_nonexisting_getblobsv1( Test that ensures clients respond with 'null' when at least one requested blob is not available (getBlobsV1 behavior: all-or-nothing response). """ - nonexisting_blob_hashes = [ - Hash(sha256(str(i).encode()).digest()) for i in range(5) - ] + nonexisting_blob_hashes = generate_nonexisting_blob_hashes(5) blobs_test( pre=pre, txs=txs, @@ -412,9 +419,7 @@ def test_get_blobs_nonexisting_getblobsv2( Test that ensures clients respond with 'null' when at least one requested blob is not available (getBlobsV2 behavior: all-or-nothing response). """ - nonexisting_blob_hashes = [ - Hash(sha256(str(i).encode()).digest()) for i in range(5) - ] + nonexisting_blob_hashes = generate_nonexisting_blob_hashes(5) print("Testing getBlobsV2 (all-or-nothing behavior)") for tx_idx, tx_hashes in enumerate(txs_versioned_hashes): for blob_idx, vh in enumerate(tx_hashes): @@ -447,9 +452,7 @@ def test_get_blobs_nonexisting_getblobsv3( Test that ensures clients respond with partial results when some requested blobs are not available (getBlobsV3 behavior: null only for missing blobs). """ - nonexisting_blob_hashes = [ - Hash(sha256(str(i).encode()).digest()) for i in range(5) - ] + nonexisting_blob_hashes = generate_nonexisting_blob_hashes(5) print("Testing getBlobsV3 (partial response behavior)") for tx_idx, tx_hashes in enumerate(txs_versioned_hashes): for blob_idx, vh in enumerate(tx_hashes): @@ -479,9 +482,7 @@ def test_get_blobs_only_nonexisting_getblobsv3( hash) when all requested blobs are non-existing, rather than returning a single null for the entire response. """ - nonexisting_blob_hashes = [ - Hash(sha256(str(i).encode()).digest()) for i in range(5) - ] + nonexisting_blob_hashes = generate_nonexisting_blob_hashes(5) print("Testing getBlobsV3 (only non-existing blobs)") for i, nh in enumerate(nonexisting_blob_hashes): print(f" non-existing {i}: {nh.hex()}") From af1d40a9aa551daccb72b6ac6f275e81cbed8a0b Mon Sep 17 00:00:00 2001 From: spencer <spencer.taylor-brown@ethereum.org> Date: Thu, 13 Aug 2026 14:41:30 +0200 Subject: [PATCH 223/233] fix(tests): accept gas-used divergence in BAL omitted-slot-change test (#3357) --- .../testing/src/execution_testing/client_clis/clis/geth.py | 3 +++ .../test_block_access_lists_invalid.py | 7 ++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/testing/src/execution_testing/client_clis/clis/geth.py b/packages/testing/src/execution_testing/client_clis/clis/geth.py index 57c4793d1d9..3184354bcd4 100644 --- a/packages/testing/src/execution_testing/client_clis/clis/geth.py +++ b/packages/testing/src/execution_testing/client_clis/clis/geth.py @@ -138,6 +138,9 @@ class GethExceptionMapper(ExceptionMapper): BlockException.INVALID_GAS_USED_ABOVE_LIMIT: ( r"invalid gasUsed: have \d+, gasLimit \d+" ), + BlockException.INVALID_GAS_USED: ( + r"invalid gas used \(remote: \d+ local: \d+\)" + ), BlockException.INVALID_DEPOSIT_EVENT_LAYOUT: ( r"invalid requests hash|failed to parse deposit logs" ), diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_invalid.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_invalid.py index abdd2a5bcc9..0199542e9ab 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_invalid.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_invalid.py @@ -2008,7 +2008,12 @@ def test_bal_invalid_omitted_slot_change_at_index( blocks=[ Block( txs=[tx1, tx2], - exception=BlockException.INVALID_BLOCK_ACCESS_LIST, + # Clients that execute against the declared BAL see gas + # diverge before the BAL comparison (e.g. geth, reth). + exception=[ + BlockException.INVALID_BLOCK_ACCESS_LIST, + BlockException.INVALID_GAS_USED, + ], expected_block_access_list=BlockAccessListExpectation( account_expectations={ alice: BalAccountExpectation( From d2e6bd12f33f8fc8246356d26e4c41c1917ec940 Mon Sep 17 00:00:00 2001 From: spencer <spencer.taylor-brown@ethereum.org> Date: Thu, 13 Aug 2026 16:04:56 +0200 Subject: [PATCH 224/233] fix(consume): emit genesis slotNumber as hex string like all other fields (#3369) --- .../plugins/consume/simulators/single_test_client.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/single_test_client.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/single_test_client.py index 67984186bd2..2d22fbb2e80 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/single_test_client.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/simulators/single_test_client.py @@ -34,9 +34,6 @@ def client_genesis(fixture: BlockchainFixtureCommon) -> dict: alloc = to_json(fixture.pre) # NOTE: nethermind requires account keys without '0x' prefix genesis["alloc"] = {k.replace("0x", ""): v for k, v in alloc.items()} - # NOTE: geth expects slotNumber as plain integer, not hex string - if "slotNumber" in genesis: - genesis["slotNumber"] = int(genesis["slotNumber"], 16) return genesis From d56b40ba7e3272d40d0bb521537fd2f9db7ee0c3 Mon Sep 17 00:00:00 2001 From: Mario Vega <marioevz@gmail.com> Date: Thu, 13 Aug 2026 09:42:07 -0600 Subject: [PATCH 225/233] feat(test-specs): Allow specs to fill same fixture format more than once, add `inclusion_test` marker (#3337) * chore(test-fill): Rename `not derived_test` to `primary_format` * refactor(test-fill): Move parametrization logic to `BaseTest` * refactor(test-fill): Retain label on parametrization * feat(test-fill): Allow a label to override the t8n cache key A fixture format's transition tool cache key is shared by every label of that format, and cache entries are looked up by call order, so two labels that ask the transition tool for different things would feed each other stale output. Let a label declare its own key, or an empty string to opt out of caching. * refactor(test-fill): Retain label in `discard_fixture_format_by_marks` The hook received the format with its label stripped, so a spec type that labels one format more than once could not discard a single label. Pass the labeled format through; comparisons against a plain format are unaffected since a labeled format compares equal to the format it wraps. * feat(tests): Add `inclusion_test` marker Marks tests whose purpose is to verify whether a transaction can be included in a block, where the transaction under test is the last one of the last block. Applied to the withdrawal funding, block gas limit and post-authorization nonce tests. * feat(tests): Apply `inclusion_test` marker to transaction validity tests Marks the hand-written tests that verify a transaction is rejected, where the transaction under test is the last one of the last block: 82 tests across 38 files, covering 27 distinct transaction exceptions. Excluded: `ported_static` tests, which resolve their expectation at runtime and repeat the same exceptions across thousands of cases, except one cherry-pick for an exception no hand-written test covers; `TransactionTest` based tests, which produce no block; and tests whose block is rejected for a block-level reason. * feat(tests): Mark transaction validity tests as inclusion tests A standalone invalid transaction can never be appended to a block, so these are the simplest inclusion tests there are: an empty block, the transaction offered to the client, and no way to include it. Adds `TYPE_4_INVALID_AUTHORITY_SIGNATURE`, `TYPE_4_INVALID_AUTHORITY_SIGNATURE_S_TOO_HIGH`, `TYPE_4_INVALID_AUTHORIZATION_FORMAT` and `NONCE_OVERFLOW` to the set of covered rejection reasons. * feat(tests): Mark transaction validity tests missed by the first pass The first pass looked for a transaction error in the test body, which misses the tests that take their transaction from a conftest fixture. The three `test_transaction_validity.py` files are marked whole, since every test in them asks whether a transaction is valid, and the ones that expect a valid transaction are the positive case. * feat(test-fill): Let a label veto itself by fork or marker `supports_fork` and `discard_fixture_format_by_marks` were called on the format with its label stripped, so a label could not exclude itself. Both are now methods on `LabeledFixtureFormat` that defer to the wrapped format by default, letting a subclass restrict a single label to the forks whose fixture it makes sense for, without affecting the sibling labels. * refactor(test-execute): Fix divergence between fill and execute * fix(tooling): Update stale `derived_test` selectors in `Justfile` to `primary_format` * chore(tests): Remove `inclusion_test` marker duplicated by the module `pytestmark` * feat(test-specs): Add unit test for the duplicate t8n cache key * fix(test-fill): Enforce inclusion_test template * fix(tests): Remove inclusion_test marker from mislabeled tests * fix(test-specs): Refactor to make re-labeling work * feat(tests): Add more markers * feat(docs): Add `inclusion_test` marker to docs * Review comment --------- Co-authored-by: danceratopz <danceratopz@gmail.com> --- .github/workflows/test.yaml | 2 +- Justfile | 8 +- docs/writing_tests/test_markers.md | 56 ++++ .../plugins/execute/execute.py | 20 +- .../plugins/filler/eip_checklist.py | 4 +- .../pytest_commands/plugins/filler/filler.py | 48 +--- .../plugins/filler/pre_alloc.py | 7 +- .../plugins/filler/tests/test_benchmarking.py | 4 +- .../tests/test_inclusion_test_marker.py | 185 ++++++++++++ ...arker.py => test_primary_format_marker.py} | 74 +++-- .../plugins/filler/tests/test_t8n_cache.py | 2 +- .../plugins/shared/execute_fill.py | 19 +- .../pytest_commands/plugins/shared/helpers.py | 67 ----- .../src/execution_testing/execution/base.py | 72 ++++- .../src/execution_testing/fixtures/base.py | 263 +++++++++++++++++- .../fixtures/tests/test_base.py | 239 +++++++++++++++- .../src/execution_testing/specs/base.py | 106 ++++++- .../src/execution_testing/specs/benchmark.py | 6 +- .../src/execution_testing/specs/blobs.py | 5 +- .../src/execution_testing/specs/blockchain.py | 30 +- .../src/execution_testing/specs/state.py | 24 +- .../specs/tests/test_specs.py | 179 ++++++++++++ .../execution_testing/specs/transaction.py | 4 +- .../test_authorization_oog.py | 1 + .../test_calldata_floor.py | 3 + .../test_intrinsic_gas_boundary.py | 4 + .../test_gas_accounting.py | 2 + .../test_block_access_lists.py | 1 + .../test_eip_mainnet.py | 1 + .../test_max_initcode_size.py | 2 + .../test_floor_boundary_exact_balance.py | 1 + .../test_fork_transition.py | 1 + .../test_transaction_validity.py | 5 +- .../test_floor_boundary_exact_balance.py | 1 + .../test_transaction_validity.py | 5 +- .../test_block_2d_gas_accounting.py | 4 + .../test_state_gas_calldata_floor.py | 1 + .../test_state_gas_create.py | 3 + .../test_state_gas_pricing.py | 3 + .../test_state_gas_reservoir.py | 5 + .../test_state_gas_set_code.py | 2 + .../test_create_gas.py | 1 + .../test_exact_balance_no_fallback.py | 3 + .../test_set_code_auth_gas.py | 1 + tests/berlin/eip2930_access_list/test_acl.py | 1 + .../test_tx_intrinsic_gas.py | 1 + .../eip2930_access_list/test_tx_type.py | 1 + tests/cancun/eip4844_blobs/test_blob_txs.py | 11 + tests/frontier/validation/test_transaction.py | 7 + .../eip1559_fee_market_change/test_tx_type.py | 2 + .../eip7594_peerdas/test_max_blob_per_tx.py | 1 + .../test_eip_mainnet.py | 1 + .../test_tx_gas_limit.py | 7 + .../test_tx_gas_limit_transition_fork.py | 1 + .../test_high_gas_price_paris.py | 1 + .../stTransactionTest/test_no_src_account.py | 1 + .../test_no_src_account1559.py | 1 + .../test_no_src_account_create.py | 1 + .../test_no_src_account_create1559.py | 1 + .../test_transaction_validity.py | 5 +- tests/prague/eip7702_set_code_tx/test_gas.py | 1 + .../eip7702_set_code_tx/test_invalid_tx.py | 6 +- .../eip7702_set_code_tx/test_set_code_txs.py | 5 + .../test_set_code_txs_2.py | 1 + .../eip3860_initcode/test_initcode.py | 2 + .../eip4895_withdrawals/test_withdrawals.py | 1 + 66 files changed, 1310 insertions(+), 223 deletions(-) create mode 100644 packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_inclusion_test_marker.py rename packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/{test_derived_test_marker.py => test_primary_format_marker.py} (58%) create mode 100644 packages/testing/src/execution_testing/specs/tests/test_specs.py diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index e26aeddaf57..bb787cd5d53 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -100,7 +100,7 @@ jobs: - name: Run fill (${{ matrix.label }}) run: > just fill --from ${{ matrix.from_fork }} --until ${{ matrix.until_fork }} - -m "not slow and not derived_test" + -m "not slow and primary_format" env: PYTEST_XDIST_AUTO_NUM_WORKERS: auto - name: Upload coverage reports to Codecov diff --git a/Justfile b/Justfile index 01467ef46fe..45af2a182ae 100644 --- a/Justfile +++ b/Justfile @@ -168,7 +168,7 @@ fill-pypy *args: (_tmp-logs "fill-pypy") -ra \ --show-capture=no \ --disable-warnings \ - -m "eels_base_coverage and not derived_test" \ + -m "eels_base_coverage and primary_format" \ -n auto --maxprocesses 7 \ --dist=loadgroup \ --basetemp="{{ output_dir }}/fill-pypy/tmp" \ @@ -183,7 +183,7 @@ fill-pypy *args: (_tmp-logs "fill-pypy") [group('integration tests')] json-loader *args: (_tmp "json-loader") uv run fill \ - -m "eels_base_coverage and not derived_test" \ + -m "eels_base_coverage and primary_format" \ --until "{{ latest_fork }}" \ -n {{ xdist_workers }} --dist=loadgroup \ --skip-index \ @@ -256,7 +256,7 @@ fill-benchmark *args: (_tmp-logs "fill-benchmark") uv run fill \ --gas-benchmark-values 1 \ --fork "{{ latest_fork }}" \ - -m "not slow and not derived_test" \ + -m "not slow and primary_format" \ -k "not test_return_revert" \ -n {{ xdist_workers }} --dist=loadgroup \ --skip-index \ @@ -290,7 +290,7 @@ bench-gas *args: (_tmp-logs "bench-gas") --evm-bin="{{ evm_bin }}" \ --gas-benchmark-values 1 \ --fork Amsterdam \ - -m "blockchain_test and (not derived_test) and (not slow)" \ + -m "blockchain_test and primary_format and (not slow)" \ -n auto --maxprocesses 10 --dist=loadgroup \ --durations=20 \ --output="{{ output_dir }}/bench-gas/fixtures" \ diff --git a/docs/writing_tests/test_markers.md b/docs/writing_tests/test_markers.md index 22c613ab498..71fc06e77e4 100644 --- a/docs/writing_tests/test_markers.md +++ b/docs/writing_tests/test_markers.md @@ -371,6 +371,62 @@ Examples of this include: - Contracts having zero-nonce - Deploying a contract to a hard-coded address +### `@pytest.mark.inclusion_test` + +This marker is used to mark tests that verify whether a transaction can be included in a block. The transaction under test must be the last transaction of the last block. + +Such a test can assert in either direction, and is most valuable when it covers both: + +- **negative** — the transaction is invalid, so it cannot be included. The block containing it is invalid too, so the test also requires `@pytest.mark.exception_test`. +- **positive** — the transaction is valid, so it must be included. + +A test that parametrizes a boundary usually covers both directions by crossing it. + +```python +import pytest + +from execution_testing import ( + Alloc, + Block, + BlockchainTestFiller, + Fork, + Transaction, + TransactionException, +) + +@pytest.mark.inclusion_test +@pytest.mark.exception_test +def test_something( + blockchain_test: BlockchainTestFiller, + pre: Alloc, + fork: Fork, +): + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() + blockchain_test( + pre=pre, + post={}, + blocks=[ + Block(txs=[Transaction(sender=pre.fund_eoa())]), + Block( + txs=[ + # The transaction under test is the last one of the + # last block. + Transaction( + sender=pre.fund_eoa(), + gas_limit=intrinsic_gas - 1, + error=TransactionException.INTRINSIC_GAS_TOO_LOW, + ), + ], + exception=TransactionException.INTRINSIC_GAS_TOO_LOW, + ), + ], + ) +``` + +Filling fails with a `test correctness` error if any other transaction in the chain is invalid, whether it sits in an earlier block or before the last transaction of the last block, since either would leave the subject of the test ambiguous. + +For that reason a fork transition test that rejects a transaction before the fork and accepts it after cannot use this marker: its rejected transaction is not in the last block. + ### `@pytest.mark.skip()` This marker can be used to skip a test. diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/execute.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/execute.py index e01fe7498df..de4eae5737f 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/execute.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/execute/execute.py @@ -11,7 +11,7 @@ from execution_testing.base_types import Account from execution_testing.base_types.base_types import HexNumber -from execution_testing.execution import BaseExecute +from execution_testing.execution import BaseExecute, LabeledExecuteFormat from execution_testing.forks import Fork, TransitionFork from execution_testing.logging import get_logger from execution_testing.rpc import EngineRPC, EthRPC @@ -26,7 +26,6 @@ from ..shared.helpers import ( get_spec_format_for_item, is_help_or_collectonly_mode, - labeled_format_parameter_set, option_was_explicitly_set, ) from ..spec_version_checker.spec_version_checker import EIPSpecTestItem @@ -318,6 +317,7 @@ def base_test_parametrizer_func( env_gas_limit: HexNumber, is_tx_gas_heavy_test: bool, is_exception_test: bool, + is_inclusion_test: bool, ) -> Type[BaseTest]: """ Fixture used to instantiate an auto-fillable BaseTest object from @@ -333,7 +333,9 @@ def base_test_parametrizer_func( del fixed_opcode_count execute_format = request.param assert execute_format in BaseExecute.formats.values() - assert issubclass(execute_format, BaseExecute) + assert isinstance(execute_format, LabeledExecuteFormat) or issubclass( + execute_format, BaseExecute + ) if execute_format.requires_engine_rpc: assert engine_rpc is not None, ( @@ -356,6 +358,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: kwargs["operation_mode"] = request.config.op_mode kwargs["is_tx_gas_heavy_test"] = is_tx_gas_heavy_test kwargs["is_exception_test"] = is_exception_test + kwargs["is_inclusion_test"] = is_inclusion_test kwargs |= { p: request.getfixturevalue(p) for p in cls_fixture_parameters @@ -483,13 +486,11 @@ def pytest_generate_tests(metafunc: pytest.Metafunc) -> None: if test_type.pytest_parameter_name() in metafunc.fixturenames: parameter_set = [] for ( - format_with_or_without_label - ) in test_type.supported_execute_formats: - param = labeled_format_parameter_set( - format_with_or_without_label - ) + execute_format, + param, + ) in test_type.execute_format_parameters(): if ( - format_with_or_without_label.requires_engine_rpc + execute_format.requires_engine_rpc and not engine_rpc_supported ): param.marks.append( # type: ignore @@ -526,7 +527,6 @@ def pytest_collection_modifyitems( continue fork: Fork | TransitionFork = params["fork"] spec_type, execute_format = get_spec_format_for_item(params) - assert issubclass(execute_format, BaseExecute) markers = list(item.iter_markers()) if spec_type.discard_execute_format_by_marks( execute_format, fork, markers diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/eip_checklist.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/eip_checklist.py index 11129083b82..d93edf0c486 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/eip_checklist.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/eip_checklist.py @@ -506,8 +506,8 @@ def pytest_collection_modifyitems( """Collect checklist markers during test collection.""" for item in items: eip = self.get_eip_from_item(item) - if item.get_closest_marker( - "derived_test" + if not item.get_closest_marker( + "primary_format" ) or item.get_closest_marker("skip"): continue self.collect_from_item(item, eip) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py index 7db3702718c..9fa7ab22324 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py @@ -26,7 +26,6 @@ import pytest import xdist from _pytest.compat import NotSetType -from _pytest.mark.structures import ParameterSet from _pytest.terminal import TerminalReporter from filelock import FileLock from pytest_metadata.plugin import metadata_key @@ -97,7 +96,6 @@ from ..shared.helpers import ( get_spec_format_for_item, is_help_or_collectonly_mode, - labeled_format_parameter_set, option_was_explicitly_set, ) from ..spec_version_checker.spec_version_checker import ( @@ -1584,6 +1582,7 @@ def base_test_parametrizer_func( fixed_opcode_count: int | None, is_tx_gas_heavy_test: bool, is_exception_test: bool, + is_inclusion_test: bool, ) -> Any: """ Fixture used to instantiate an auto-fillable BaseTest object from @@ -1601,7 +1600,9 @@ def base_test_parametrizer_func( fixture_format = request.node.fixture_format else: fixture_format = request.param - assert issubclass(fixture_format, BaseFixture) + assert isinstance(fixture_format, LabeledFixtureFormat) or issubclass( + fixture_format, BaseFixture + ) class BaseTestWrapper(cls): # type: ignore __is_base_test_wrapper__ = True @@ -1616,6 +1617,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: kwargs["operation_mode"] = op_mode kwargs["is_tx_gas_heavy_test"] = is_tx_gas_heavy_test kwargs["is_exception_test"] = is_exception_test + kwargs["is_inclusion_test"] = is_inclusion_test if ( op_mode == OpMode.OPTIMIZE_GAS or op_mode == OpMode.OPTIMIZE_GAS_POST_PROCESSING @@ -1833,38 +1835,15 @@ def pytest_generate_tests(metafunc: pytest.Metafunc) -> None: markers = list(metafunc.definition.iter_markers()) for test_type in BaseTest.spec_types.values(): if test_type.pytest_parameter_name() in metafunc.fixturenames: - parameters: List[ParameterSet] = [] - for ( - format_with_or_without_label - ) in test_type.supported_fixture_formats: - if not session.should_generate_format( - format_with_or_without_label - ): - continue - fixture_format = ( - format_with_or_without_label.format - if isinstance( - format_with_or_without_label, LabeledFixtureFormat - ) - else format_with_or_without_label - ) - if test_type.discard_fixture_format_by_marks( - fixture_format, markers - ): - continue - parameter = labeled_format_parameter_set( - format_with_or_without_label - ) - # The first surviving format is the test's primary; the - # rest are derived from it (e.g. a BlockchainTest derived - # from a StateTest) and can be deselected with - # `-m "not derived_test"`. - if parameters: - parameter.marks.append(pytest.mark.derived_test) # type: ignore - parameters.append(parameter) metafunc.parametrize( [test_type.pytest_parameter_name()], - parameters, + [ + parameter + for fixture_format, parameter in ( + test_type.fixture_format_parameters(markers=markers) + ) + if session.should_generate_format(fixture_format) + ], scope="function", indirect=True, ) @@ -1912,7 +1891,8 @@ def pytest_collection_modifyitems( ) specs_without_fixture_formats[spec_name].add(test_file) continue - assert issubclass(fixture_format, BaseFixture) + # The format keeps its label throughout, so a label can veto itself + # without affecting the other labels of the same format. if not fixture_format.supports_fork(fork): items_for_removal.append(i) continue diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py index 7385b825c8b..9b7fc22f338 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/pre_alloc.py @@ -24,7 +24,6 @@ BytesConvertible, NumberConvertible, ) -from execution_testing.fixtures import LabeledFixtureFormat from execution_testing.forks import Fork, TransitionFork from execution_testing.specs import BaseTest from execution_testing.test_types import ( @@ -420,11 +419,7 @@ def sha256_from_string(s: str) -> int: for spec in BaseTest.spec_types.values(): for labeled_fixture_format in spec.supported_fixture_formats: - name = ( - labeled_fixture_format.label - if isinstance(labeled_fixture_format, LabeledFixtureFormat) - else labeled_fixture_format.format_name.lower() - ) + name = labeled_fixture_format.format_id() if name not in ALL_FIXTURE_FORMAT_NAMES: ALL_FIXTURE_FORMAT_NAMES.append(name) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_benchmarking.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_benchmarking.py index 0d8bacfd5bd..68a844a3bb1 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_benchmarking.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_benchmarking.py @@ -246,7 +246,7 @@ def test_dummy_benchmark_test( "--gas-benchmark-values", "1,2", "-m", - "blockchain_test and not derived_test", + "blockchain_test and primary_format", "--no-html", "--skip-index", f"--output={output_dir}", @@ -327,7 +327,7 @@ def test_fixed_opcode_count_split_into_subdirs( "Prague", "--fixed-opcode-count=1,2", "-m", - "blockchain_test and not derived_test", + "blockchain_test and primary_format", "--no-html", "--skip-index", f"--output={output_dir}", diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_inclusion_test_marker.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_inclusion_test_marker.py new file mode 100644 index 00000000000..e568280629b --- /dev/null +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_inclusion_test_marker.py @@ -0,0 +1,185 @@ +""" +Test the static check that the `inclusion_test` marker applies to a +`BlockchainTest`. + +An inclusion test asserts whether one specific transaction can be included in +a block, and by convention that transaction is the last one of the last block. +Any other invalid transaction in the chain would leave the subject of the test +ambiguous, so `BlockchainTest` rejects such a layout on instantiation, before +any block is filled. + +Tests without the marker are unaffected: an invalid transaction may sit at the +end of any block. +""" + +import textwrap + +import pytest + +# Pinned so the fill runs against a single, stable fork: the invalid +# transaction is derived from the fork's intrinsic gas cost, which later forks +# reprice. +FORK = "Prague" + +TEST_MODULE_DIR = "tests/prague/dummy_test_module" + +MODULE_TEMPLATE = textwrap.dedent( + """\ + import pytest + + from execution_testing import ( + Alloc, + Block, + BlockchainTestFiller, + Fork, + Transaction, + TransactionException, + ) + + INVALID = TransactionException.INTRINSIC_GAS_TOO_LOW + + {markers} + @pytest.mark.valid_at("{fork}") + def test_case( + blockchain_test: BlockchainTestFiller, pre: Alloc, fork: Fork + ) -> None: + intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() + + def valid_tx() -> Transaction: + return Transaction(to=0, sender=pre.fund_eoa(), + gas_limit=intrinsic_gas) + + def invalid_tx() -> Transaction: + return Transaction(to=0, sender=pre.fund_eoa(), + gas_limit=intrinsic_gas - 1, error=INVALID) + + blockchain_test(pre=pre, post={{}}, blocks={blocks}) + """ +) + +INCLUSION_TEST_MARKERS = ( + "@pytest.mark.inclusion_test\n@pytest.mark.exception_test" +) +EXCEPTION_TEST_MARKER = "@pytest.mark.exception_test" + +# Invalid transaction in a block other than the last one. +INVALID_TX_IN_EARLIER_BLOCK = ( + "[Block(txs=[invalid_tx()], exception=INVALID), Block(txs=[valid_tx()])]" +) +# Invalid transaction in the last block, but not as its last transaction. +INVALID_TX_BEFORE_LAST_IN_LAST_BLOCK = ( + "[Block(txs=[valid_tx()]), " + "Block(txs=[invalid_tx(), valid_tx()], exception=INVALID)]" +) +# The only layout an inclusion test is allowed to use. +INVALID_TX_LAST_IN_LAST_BLOCK = ( + "[Block(txs=[valid_tx()]), " + "Block(txs=[valid_tx(), invalid_tx()], exception=INVALID)]" +) + + +def write_test_module( + pytester: pytest.Pytester, markers: str, blocks: str +) -> str: + """ + Write a single-test module using the given markers and block layout, and + return its path relative to the pytester directory. + """ + module_dir = pytester.path / TEST_MODULE_DIR + module_dir.mkdir(parents=True) + module = module_dir / "test_dummy.py" + module.write_text( + MODULE_TEMPLATE.format(markers=markers, fork=FORK, blocks=blocks) + ) + pytester.copy_example( + name="src/execution_testing/cli/pytest_commands/pytest_ini_files/pytest-fill.ini" + ) + return str(module.relative_to(pytester.path)) + + +def run_fill(pytester: pytest.Pytester, module_path: str) -> pytest.RunResult: + """ + Fill the given module, generating only the blockchain test fixture format. + """ + return pytester.runpytest( + "-c", + "pytest-fill.ini", + "--fork", + FORK, + "-m", + "blockchain_test", + "--no-html", + "--output", + "fixtures", + module_path, + ) + + +@pytest.mark.parametrize( + "blocks,expected_block_number", + [ + pytest.param( + INVALID_TX_IN_EARLIER_BLOCK, 0, id="invalid_tx_in_earlier_block" + ), + pytest.param( + INVALID_TX_BEFORE_LAST_IN_LAST_BLOCK, + 1, + id="invalid_tx_before_last_in_last_block", + ), + ], +) +def test_misplaced_invalid_tx_is_rejected( + pytester: pytest.Pytester, + blocks: str, + expected_block_number: int, +) -> None: + """ + Fill an inclusion test whose invalid transaction is not the last one of the + last block, and assert it fails pointing at the offending block. + """ + module_path = write_test_module(pytester, INCLUSION_TEST_MARKERS, blocks) + + result = run_fill(pytester, module_path) + + result.assert_outcomes(passed=0, failed=1) + output = "\n".join(result.outlines + result.errlines) + assert "in an inclusion test the only transaction allowed" in output, ( + f"Inclusion test check did not report the failure:\n{output}" + ) + assert f"but block {expected_block_number} contains" in output, ( + f"Expected block {expected_block_number} to be reported:\n{output}" + ) + + +@pytest.mark.parametrize( + "markers,blocks", + [ + pytest.param( + INCLUSION_TEST_MARKERS, + INVALID_TX_LAST_IN_LAST_BLOCK, + id="inclusion_test_with_invalid_tx_last_in_last_block", + ), + pytest.param( + EXCEPTION_TEST_MARKER, + INVALID_TX_IN_EARLIER_BLOCK, + id="unmarked_test_with_invalid_tx_in_earlier_block", + ), + ], +) +def test_allowed_invalid_tx_placement_fills( + pytester: pytest.Pytester, + markers: str, + blocks: str, +) -> None: + """ + Fill a test whose invalid transaction placement is allowed and assert the + check does not reject it. + + The first case is the layout an inclusion test is meant to have; the second + is the same layout the check rejects, minus the marker that enables it. + """ + module_path = write_test_module(pytester, markers, blocks) + + result = run_fill(pytester, module_path) + + result.assert_outcomes(passed=1, failed=0) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_derived_test_marker.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_primary_format_marker.py similarity index 58% rename from packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_derived_test_marker.py rename to packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_primary_format_marker.py index a7891c0db86..9ff3211a8f2 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_derived_test_marker.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_primary_format_marker.py @@ -1,19 +1,17 @@ """ -Test that the `derived_test` marker tracks the first fixture format that is -actually generated for a test, not merely the first entry in +Test that the `primary_format` marker tracks the first fixture format a test +actually generates, not merely the first entry in ``supported_fixture_formats``. -Two ways the positional-first format can drop out, leaving a fixture that -used to be tagged ``derived_test`` purely because of its list position: +A single-format marker such as ``blockchain_test_engine_only`` discards the +test's default (primary) format, so a later format becomes the effective +primary and must carry the mark to stay selectable via ``-m primary_format``. -1. A single-format marker such as ``blockchain_test_engine_only`` discards - the test's default (primary) format. -2. A session-level format filter (e.g. a ``--generate-pre-alloc-groups`` - session, which only generates EngineX fixtures) excludes the primary - format from parametrization entirely. - -In both cases the surviving format is the test's effective primary, so it -must stay unmarked and remain selectable via ``-m "not derived_test"``. +The session-level format filter (``should_generate_format``) can drop the +default format too, but only in sessions that generate a single format per +test (``--generate-pre-alloc-groups``, ``fill --stateful``). There is nothing +to deduplicate there, so the marker is not a useful selector; the last test +pins that one-format-per-test property instead. """ import textwrap @@ -95,7 +93,7 @@ def write_test_module(pytester: pytest.Pytester, module_source: str) -> None: NORMAL_BLOCKCHAIN_MODULE, "-blockchain_test]", "-blockchain_test_engine]", - id="normal_test_still_marks_derived", + id="normal_test_primary_is_default_format", ), pytest.param( STATE_ONLY_MODULE, @@ -105,16 +103,15 @@ def write_test_module(pytester: pytest.Pytester, module_source: str) -> None: ), ], ) -def test_not_derived_test_selects_primary_survivor( +def test_primary_format_selects_first_survivor( pytester: pytest.Pytester, module_source: str, present: str, absent: str, ) -> None: """ - Collect with ``-m "not derived_test"`` and assert the test's primary - (first surviving) fixture format is selected while its derived formats are - not. + Collect with ``-m primary_format`` and assert the test's primary (first + surviving) fixture format is selected while its other formats are not. """ write_test_module(pytester, module_source) @@ -124,34 +121,30 @@ def test_not_derived_test_selects_primary_survivor( "--fork", FORK, "-m", - "not derived_test", + "primary_format", TEST_MODULE_DIR, "--collect-only", "-q", ) assert result.ret == 0, f"Collection failed:\n{result.outlines}" - assert any(present in line for line in result.outlines), ( - f"Expected {present!r} to be collected:\n{result.outlines}" - ) - assert not any(absent in line for line in result.outlines), ( - f"Expected {absent!r} to be absent under `not derived_test`:\n" - f"{result.outlines}" - ) + result.stdout.fnmatch_lines([f"*{present}"]) + result.stdout.no_fnmatch_line(f"*{absent}") -def test_not_derived_test_selects_session_filter_survivor( +def test_pre_alloc_group_session_generates_one_format_per_test( pytester: pytest.Pytester, ) -> None: """ - Collect a plain state test in a ``--generate-pre-alloc-groups`` session - with ``-m "not derived_test"`` and assert that the only format generated - in this session (EngineX) is selected as the test's primary. - - The session-level format filter (``should_generate_format``) excludes all - other formats before parametrization, so the EngineX fixture must not - inherit a ``derived_test`` mark from its position in - ``supported_fixture_formats``. + Collect a plain state test in a ``--generate-pre-alloc-groups`` session and + assert it yields a single fixture format. + + The session-level format filter narrows every spec type to EngineX in this + phase, so each test already generates exactly one fixture and there is + nothing for ``primary_format`` to deduplicate. The mark that + ``fixture_format_parameters`` put on the default format is filtered out + along with it, which is why ``-m primary_format`` is not a useful selector + in this session. """ write_test_module(pytester, NORMAL_STATE_MODULE) @@ -161,19 +154,16 @@ def test_not_derived_test_selects_session_filter_survivor( "--fork", FORK, "--generate-pre-alloc-groups", - "-m", - "not derived_test", TEST_MODULE_DIR, "--collect-only", "-q", ) - engine_x = "-blockchain_test_engine_x_from_state_test]" assert result.ret == 0, f"Collection failed:\n{result.outlines}" - assert any(engine_x in line for line in result.outlines), ( - f"Expected {engine_x!r} to be collected:\n{result.outlines}" + collected = [line for line in result.outlines if "::test_case[" in line] + assert len(collected) == 1, ( + f"Expected a single fixture format:\n{result.outlines}" ) - assert not any("-state_test]" in line for line in result.outlines), ( - "Expected the state test format to be excluded by the session " - f"format filter:\n{result.outlines}" + result.stdout.fnmatch_lines( + ["*-blockchain_test_engine_x_from_state_test]"] ) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_t8n_cache.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_t8n_cache.py index 912c3a1d983..ebf27fc1a42 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_t8n_cache.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_t8n_cache.py @@ -15,8 +15,8 @@ StateFixture, strip_fixture_format_from_node, ) +from execution_testing.specs.base import labeled_format_parameter_set -from ...shared.helpers import labeled_format_parameter_set from ..filler import _strip_xdist_group_suffix diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/execute_fill.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/execute_fill.py index 6dc6788c51f..548ef53c6db 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/execute_fill.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/execute_fill.py @@ -193,8 +193,14 @@ def pytest_configure(config: pytest.Config) -> None: ) config.addinivalue_line( "markers", - "derived_test: Mark a test as a derived test (E.g. a BlockchainTest " - "that is derived from a StateTest).", + "primary_format: Mark the first fixture format generated for a test. " + "Select with `-m primary_format` to fill every test exactly once.", + ) + config.addinivalue_line( + "markers", + "inclusion_test: Mark a test that verifies whether a transaction can " + "be included in a block. The transaction under test must be the last " + "one of the last block.", ) config.addinivalue_line( "markers", @@ -393,3 +399,12 @@ def is_exception_test(request: pytest.FixtureRequest) -> bool: test (invalid block, invalid transaction). """ return request.node.get_closest_marker("exception_test") is not None + + +@pytest.fixture(scope="function") +def is_inclusion_test(request: pytest.FixtureRequest) -> bool: + """ + Check, given the test node properties, whether the test is an inclusion + test. + """ + return request.node.get_closest_marker("inclusion_test") is not None diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/helpers.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/helpers.py index 42febb7918f..515569a3ea5 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/helpers.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/helpers.py @@ -4,16 +4,7 @@ from typing import Any, Dict, Tuple, Type import pytest -from _pytest.mark.structures import ParameterSet -from execution_testing.execution import ( - ExecuteFormat, - LabeledExecuteFormat, -) -from execution_testing.fixtures import ( - FixtureFormat, - LabeledFixtureFormat, -) from execution_testing.specs import BaseTest @@ -70,64 +61,6 @@ def get_rpc_endpoint(config: pytest.Config) -> str | None: ) -def labeled_format_parameter_set( - format_with_or_without_label: LabeledExecuteFormat - | LabeledFixtureFormat - | ExecuteFormat - | FixtureFormat, -) -> ParameterSet: - """ - Return a parameter set from a fixture/execute format and parse a label if - there's any. - - The label will be used in the test id and also will be added as a marker to - the generated test case when filling/executing the test. - """ - transition_tool_cache_key = getattr( - format_with_or_without_label, "transition_tool_cache_key", "" - ) - if transition_tool_cache_key: - marks = [ - pytest.mark.transition_tool_cache_key(transition_tool_cache_key), - ] - else: - marks = [] - if isinstance( - format_with_or_without_label, LabeledExecuteFormat - ) or isinstance(format_with_or_without_label, LabeledFixtureFormat): - parameter_id = format_with_or_without_label.label - return pytest.param( - format_with_or_without_label.format, - id=parameter_id, - marks=[ - getattr( - pytest.mark, - format_with_or_without_label.format_name.lower(), - ), - getattr( - pytest.mark, - parameter_id.lower(), - ), - pytest.mark.fixture_format_id(parameter_id), - ] - + marks, - ) - else: - parameter_id = format_with_or_without_label.format_name.lower() - return pytest.param( - format_with_or_without_label, - id=parameter_id, - marks=[ - getattr( - pytest.mark, - parameter_id, - ), - pytest.mark.fixture_format_id(parameter_id), - ] - + marks, - ) - - def get_spec_format_for_item( params: Dict[str, Any], ) -> Tuple[Type[BaseTest], Any]: diff --git a/packages/testing/src/execution_testing/execution/base.py b/packages/testing/src/execution_testing/execution/base.py index 159d66e9fe1..19f4a369deb 100644 --- a/packages/testing/src/execution_testing/execution/base.py +++ b/packages/testing/src/execution_testing/execution/base.py @@ -1,8 +1,9 @@ """Ethereum test execution base types.""" from abc import abstractmethod -from typing import Annotated, Any, ClassVar, Dict, Type +from typing import Annotated, Any, ClassVar, Dict, List, Type +import pytest from pydantic import PlainSerializer, PlainValidator from pytest import FixtureRequest @@ -43,6 +44,29 @@ def __pydantic_init_subclass__(cls, **kwargs: Any) -> None: # Register the new execute format BaseExecute.formats[cls.format_name] = cls + @classmethod + def format_class(cls) -> "Type[BaseExecute]": + """Get the execute format.""" + return cls + + @classmethod + def format_id(cls) -> str: + """Get string used as identifier for this format.""" + return cls.format_name.lower() + + @classmethod + def marks(cls) -> List[pytest.MarkDecorator | pytest.Mark]: + """ + Get list of pytest marks that need to be added to a test produced + with this execute format. + """ + return [ + getattr( + pytest.mark, + cls.format_name.lower(), + ), + ] + def prepare_transactions( self, *, @@ -107,11 +131,7 @@ def __init__( description: str, ): """Initialize the execute format with a custom label.""" - self.format = ( - execute_format.format - if isinstance(execute_format, LabeledExecuteFormat) - else execute_format - ) + self.format = execute_format.format_class() self.label = label self.description = description if label not in LabeledExecuteFormat.registered_labels: @@ -122,24 +142,60 @@ def format_name(self) -> str: """Get the execute format name.""" return self.format.format_name + def format_class(self) -> Type[BaseExecute]: + """Get the format without label.""" + return self.format + @property def requires_engine_rpc(self) -> bool: - """Get the requires engine RPC flag.""" + """Get the requires-engine-RPC flag.""" return self.format.requires_engine_rpc + def format_id(self) -> str: + """Get string used as identifier for this format.""" + return self.label + + def marks(self) -> List[pytest.MarkDecorator | pytest.Mark]: + """ + Get list of pytest marks that need to be added to a test produced + with this execute format. + """ + marks: List[pytest.MarkDecorator | pytest.Mark] = self.format.marks() + if self.label.lower() != self.format.format_name.lower(): + marks.append( + getattr( + pytest.mark, + self.label.lower(), + ) + ) + return marks + def __eq__(self, other: Any) -> bool: """ Check if two labeled execute formats are equal. + Two labeled formats are equal only when they share both format and + label, so one format can carry more than one label. + If the other object is a ExecuteFormat type, the format of the labeled execute format will be compared with the format of the other object. """ if isinstance(other, LabeledExecuteFormat): - return self.format == other.format + return self.format == other.format and self.label == other.label if isinstance(other, type) and issubclass(other, BaseExecute): return self.format == other return False + def __hash__(self) -> int: + """ + Return the hash of the wrapped format. + + A labeled format compares equal to the plain format it wraps, so both + must hash alike. Two labels of one format collide, which is allowed + since they no longer compare equal. + """ + return hash(self.format) + # Type alias for a base execute class ExecuteFormat = Annotated[ diff --git a/packages/testing/src/execution_testing/fixtures/base.py b/packages/testing/src/execution_testing/fixtures/base.py index 9b2fb475669..46dd2be4cfd 100644 --- a/packages/testing/src/execution_testing/fixtures/base.py +++ b/packages/testing/src/execution_testing/fixtures/base.py @@ -195,6 +195,88 @@ def get_fork(self) -> Fork | TransitionFork | None: """Return fork of the fixture as a string.""" raise NotImplementedError + @classmethod + def format_class(cls) -> "Type[BaseFixture]": + """Get the fixture format.""" + return cls + + @classmethod + def format_id(cls) -> str: + """Get string used as identifier for this format.""" + return cls.format_name.lower() + + @classmethod + def is_variant(cls, variant: str) -> bool: + """ + Return whether this format is the named variant. + + A plain format never is: only a label can name a variant. + """ + del variant + return False + + @classmethod + def with_label_suffix( + cls, + suffix: str, + description: str | None = None, + *, + transition_tool_cache_key: str | None = None, + variant: str | None = None, + ) -> "LabeledFixtureFormat": + """ + Return this format labeled `<format_id>_<suffix>`. + + Use this instead of building a `LabeledFixtureFormat` by hand when a + spec type re-labels the formats of another one: the label is derived + from `format_id()`, so a format that already carries a label derives a + distinct label per label instead of collapsing them all onto its + format name. + + `transition_tool_cache_key` defaults to this format's key, which is + what a label that only renames the same fixture wants. A suffix that + asks the transition tool for something different must pass its own + key, or an empty string to opt out of caching. + + `variant` names what the label asks its spec type to fill differently, + for that spec type to query with `is_variant()` rather than comparing + formats. + """ + return LabeledFixtureFormat( + cls, + f"{cls.format_id()}_{suffix}", + description + if description is not None + else f"A {cls.format_id()} {suffix.replace('_', ' ')}", + transition_tool_cache_key=transition_tool_cache_key, + variant=variant, + ) + + @classmethod + def marks( + cls, *, transition_tool_cache_key: str | None = None + ) -> List[pytest.MarkDecorator | pytest.Mark]: + """ + Get list of pytest marks that need to be added to a test produced + with this fixture format. + + `transition_tool_cache_key` overrides the format's own key. + """ + cache_key = ( + cls.transition_tool_cache_key + if transition_tool_cache_key is None + else transition_tool_cache_key + ) + marks: List[pytest.MarkDecorator | pytest.Mark] = [ + getattr( + pytest.mark, + cls.format_name.lower(), + ), + ] + if cache_key: + marks.append(pytest.mark.transition_tool_cache_key(cache_key)) + return marks + @classmethod def supports_fork(cls, fork: Fork | TransitionFork) -> bool: """ @@ -230,6 +312,9 @@ class LabeledFixtureFormat: format: Type[BaseFixture] label: str description: str + base: "LabeledFixtureFormat | None" + _transition_tool_cache_key: str | None + _variant: str | None registered_labels: ClassVar[Dict[str, "LabeledFixtureFormat"]] = {} @@ -238,15 +323,36 @@ def __init__( fixture_format: "Type[BaseFixture] | LabeledFixtureFormat", label: str, description: str, + *, + transition_tool_cache_key: str | None = None, + variant: str | None = None, ): - """Initialize the fixture format with a custom label.""" - self.format = ( - fixture_format.format + """ + Initialize the fixture format with a custom label. + + `transition_tool_cache_key` defaults to the wrapped format's key. A + label that asks the transition tool for something different must set + its own key, or an empty string to opt out of caching, since labels + sharing a key also share cached output. + + `variant` names what this label asks its spec type to fill + differently, and defaults to the wrapped label's variant. + + Wrapping a label rather than a plain format keeps what that inner + label decided: its variant, its cache key and its fork/marker vetoes + still apply, so re-labeling never silently reverts them to the plain + format's. + """ + self.format = fixture_format.format_class() + self.base = ( + fixture_format if isinstance(fixture_format, LabeledFixtureFormat) - else fixture_format + else None ) self.label = label self.description = description + self._transition_tool_cache_key = transition_tool_cache_key + self._variant = variant if label not in LabeledFixtureFormat.registered_labels: LabeledFixtureFormat.registered_labels[label] = self @@ -260,24 +366,169 @@ def format_phases(self) -> Set[FixtureFillingPhase]: """Get the filling format phases where it should be included.""" return self.format.format_phases + def format_class(self) -> Type[BaseFixture]: + """Get the format without label.""" + return self.format + + def supports_fork(self, fork: Fork | TransitionFork) -> bool: + """ + Return whether this label can be filled for the given fork. + + Defers to the label this one was derived from, or to the wrapped + format. A label whose fixture only makes sense for some forks + overrides this. + """ + if self.base is not None: + return self.base.supports_fork(fork) + return self.format.supports_fork(fork) + + def discard_fixture_format_by_marks( + self, + fork: Fork | TransitionFork, + markers: List[pytest.Mark], + ) -> bool: + """ + Discard this label from filling if the appropriate marker is used. + + Defers to the label this one was derived from, or to the wrapped + format, so a label can veto itself without affecting the other labels + of the same format. + """ + if self.base is not None: + return self.base.discard_fixture_format_by_marks(fork, markers) + return self.format.discard_fixture_format_by_marks(fork, markers) + + def format_id(self) -> str: + """Get string used as identifier for this format.""" + return self.label + + @property + def variant(self) -> str | None: + """ + Get what this label asks its spec type to fill differently. + + Falls back to the variant of the label this one was derived from, so a + variant survives being re-labeled. + """ + if self._variant is not None: + return self._variant + if self.base is not None: + return self.base.variant + return None + + def is_variant(self, variant: str) -> bool: + """ + Return whether this label is the named variant. + + A spec type asks this instead of comparing the format it was handed + against the label it declared: comparing cannot tell a variant from + the plain format it wraps, and it stops matching as soon as another + spec type re-labels the variant. + """ + return self.variant == variant + + def labels(self) -> List[str]: + """ + Get this label and every label it was derived from, outermost last. + """ + labels = self.base.labels() if self.base is not None else [] + labels.append(self.label) + return labels + + def with_label_suffix( + self, + suffix: str, + description: str | None = None, + *, + transition_tool_cache_key: str | None = None, + variant: str | None = None, + ) -> "LabeledFixtureFormat": + """ + Return this label re-labeled as `<label>_<suffix>`. + + The derived label keeps this label's fork/marker vetoes, so every label + of one format derives its own distinct label rather than all of them + collapsing onto the format name. + + `transition_tool_cache_key` defaults to this label's key, so a label + that opted out of caching stays opted out. A suffix that asks the + transition tool for something different must pass its own key. + + `variant` defaults to this label's variant, so re-labeling a variant + keeps filling that variant. + """ + return LabeledFixtureFormat( + self, + f"{self.format_id()}_{suffix}", + description + if description is not None + else f"A {self.format_id()} {suffix.replace('_', ' ')}", + transition_tool_cache_key=transition_tool_cache_key, + variant=variant, + ) + + def marks(self) -> List[pytest.MarkDecorator | pytest.Mark]: + """ + Get list of pytest marks that need to be added to a test produced + with this fixture format. + + Every label this one was derived from is marked too, so selecting a + label also selects the labels another spec type derived from it. + """ + marks: List[pytest.MarkDecorator | pytest.Mark] = self.format.marks( + transition_tool_cache_key=self.transition_tool_cache_key + ) + for label in self.labels(): + if label.lower() != self.format.format_name.lower(): + marks.append( + getattr( + pytest.mark, + label.lower(), + ), + ) + return marks + @property def transition_tool_cache_key(self) -> str: - """Get the transition tool cache key.""" + """ + Get the transition tool cache key. + + Falls back to the key of the label this one was derived from, and then + to the wrapped format's, so a label that opted out of caching does not + opt back in when it is re-labeled. + """ + if self._transition_tool_cache_key is not None: + return self._transition_tool_cache_key + if self.base is not None: + return self.base.transition_tool_cache_key return self.format.transition_tool_cache_key def __eq__(self, other: Any) -> bool: """ Check if two labeled fixture formats are equal. + Two labeled formats are equal only when they share both format and + label, so one format can carry more than one label. + If the other object is a FixtureFormat type, the format of the labeled fixture format will be compared with the format of the other object. """ if isinstance(other, LabeledFixtureFormat): - return self.format == other.format + return self.format == other.format and self.label == other.label if isinstance(other, type) and issubclass(other, BaseFixture): return self.format == other return False + def __hash__(self) -> int: + """ + Return the hash of the wrapped format. + + A labeled format compares equal to the plain format it wraps, so both + must hash alike. Two labels of one format collide, which is allowed + since they no longer compare equal. + """ + return hash(self.format) + # Annotated type alias for a base fixture class FixtureFormat = Annotated[ diff --git a/packages/testing/src/execution_testing/fixtures/tests/test_base.py b/packages/testing/src/execution_testing/fixtures/tests/test_base.py index a05dd2210d2..f22ef4cc73d 100644 --- a/packages/testing/src/execution_testing/fixtures/tests/test_base.py +++ b/packages/testing/src/execution_testing/fixtures/tests/test_base.py @@ -1,5 +1,7 @@ """Test cases for the execution_testing.fixtures.base module.""" +from typing import List + import pytest from execution_testing.base_types import ( @@ -9,12 +11,14 @@ Hash, HeaderNonce, ) -from execution_testing.forks import Prague +from execution_testing.forks import Fork, Prague, TransitionFork from execution_testing.test_types import Transaction -from ..base import BaseFixture +from ..base import BaseFixture, LabeledFixtureFormat from ..blockchain import ( + BlockchainEngineFixture, BlockchainEngineStatefulFixture, + BlockchainFixture, FixtureConfig, FixtureEngineNewPayload, FixtureHeader, @@ -159,3 +163,234 @@ def test_base_fixtures_parsing(fixture: BaseFixture) -> None: json_dump = fixture.json_dict_with_info() assert json_dump is not None Fixtures.model_validate({"fixture": json_dump}) + + +class VetoingLabel(LabeledFixtureFormat): + """A label that vetoes itself for every fork and marker set.""" + + def supports_fork(self, fork: Fork | TransitionFork) -> bool: + """Refuse every fork.""" + del fork + return False + + def discard_fixture_format_by_marks( + self, + fork: Fork | TransitionFork, + markers: List[pytest.Mark], + ) -> bool: + """Discard for every marker set.""" + del fork, markers + return True + + +def test_with_label_suffix_on_plain_format() -> None: + """Test that a plain format derives its label from `format_id()`.""" + derived = BlockchainFixture.with_label_suffix("from_state_test") + + assert derived.format_id() == "blockchain_test_from_state_test" + assert derived.format_class() is BlockchainFixture + assert derived.base is None + assert ( + derived.transition_tool_cache_key + == BlockchainFixture.transition_tool_cache_key + ) + + +def test_with_label_suffix_keeps_labels_distinct() -> None: + """ + Test that every label of one format derives its own distinct label. + + Deriving from `format_name` instead of `format_id()` would collapse both + onto the format name, and the two derived labels would then compare equal + and register only once. + """ + one = LabeledFixtureFormat( + BlockchainFixture, "alt_one", "d", transition_tool_cache_key="" + ) + two = LabeledFixtureFormat( + BlockchainFixture, "alt_two", "d", transition_tool_cache_key="other" + ) + + derived_one = one.with_label_suffix("from_state_test") + derived_two = two.with_label_suffix("from_state_test") + + assert derived_one.format_id() == "alt_one_from_state_test" + assert derived_two.format_id() == "alt_two_from_state_test" + assert derived_one != derived_two + for derived in (derived_one, derived_two): + assert LabeledFixtureFormat.registered_labels[derived.label] is derived + + +def test_with_label_suffix_keeps_transition_tool_cache_key() -> None: + """ + Test that a derived label keeps the cache key of the label it came from. + + Reverting to the wrapped format's key would make a label that opted out of + caching share cached transition tool output once it is re-labeled. + """ + opted_out = LabeledFixtureFormat( + BlockchainFixture, "opted_out", "d", transition_tool_cache_key="" + ) + own_key = LabeledFixtureFormat( + BlockchainFixture, "own_key", "d", transition_tool_cache_key="own" + ) + + assert ( + opted_out.with_label_suffix( + "from_state_test" + ).transition_tool_cache_key + == "" + ) + assert ( + own_key.with_label_suffix("from_state_test").transition_tool_cache_key + == "own" + ) + + +def test_with_label_suffix_own_transition_tool_cache_key() -> None: + """ + Test that a suffix can set its own cache key, overriding what it derives + from. + + A variant whose fixture asks the transition tool for something different + needs its own key so it does not share cached output with the format or + label it was derived from. + """ + variant = BlockchainEngineFixture.with_label_suffix( + "inclusion_list", + transition_tool_cache_key="blockchain_test_inclusion_list", + ) + + assert variant.format_id() == "blockchain_test_engine_inclusion_list" + assert variant.format_class() is BlockchainEngineFixture + assert ( + variant.transition_tool_cache_key == "blockchain_test_inclusion_list" + ) + assert ( + variant.transition_tool_cache_key + != BlockchainEngineFixture.transition_tool_cache_key + ) + + # The key survives a further re-label, and can be overridden again. + derived = variant.with_label_suffix("from_state_test") + assert ( + derived.transition_tool_cache_key == "blockchain_test_inclusion_list" + ) + assert ( + variant.with_label_suffix( + "from_state_test", transition_tool_cache_key="" + ).transition_tool_cache_key + == "" + ) + + +def test_with_label_suffix_keeps_vetoes() -> None: + """ + Test that a derived label keeps the fork and marker vetoes of its base. + + A re-labeled format that deferred to the plain format instead would fill + for forks and markers the inner label had already refused. + """ + veto = VetoingLabel( + BlockchainFixture, "veto", "d", transition_tool_cache_key="" + ) + + derived = veto.with_label_suffix("from_state_test") + + assert derived.base is veto + assert not derived.supports_fork(Prague) + assert derived.discard_fixture_format_by_marks(Prague, []) + + +def mark_names( + fixture_format: LabeledFixtureFormat | type[BaseFixture], +) -> List[str]: + """Return the names of the marks a fixture format asks for.""" + return [mark.name for mark in fixture_format.marks()] + + +def test_is_variant_on_plain_format() -> None: + """Test that a plain format is never a variant.""" + assert not BlockchainEngineFixture.is_variant("inclusion_list") + assert not BlockchainEngineFixture.with_label_suffix( + "from_state_test" + ).is_variant("inclusion_list") + + +def test_variant_survives_re_labeling() -> None: + """ + Test that a variant label stays that variant once re-labeled. + + A spec type queries `is_variant()` rather than comparing formats, which + cannot tell a variant from the plain format it wraps. + """ + variant = BlockchainEngineFixture.with_label_suffix( + "inclusion_list", + transition_tool_cache_key="blockchain_test_inclusion_list", + variant="inclusion_list", + ) + derived = variant.with_label_suffix("from_state_test") + + assert variant.is_variant("inclusion_list") + assert derived.is_variant("inclusion_list") + assert derived.variant == "inclusion_list" + assert not derived.is_variant("something_else") + # The plain format it wraps is not the variant. + assert not derived.format_class().is_variant("inclusion_list") + + +def test_with_label_suffix_overrides_variant() -> None: + """Test that a passed variant replaces the one it derives from.""" + variant = BlockchainEngineFixture.with_label_suffix( + "inclusion_list", variant="inclusion_list" + ) + + overridden = variant.with_label_suffix( + "narrowed", variant="narrowed_inclusion_list" + ) + + assert overridden.variant == "narrowed_inclusion_list" + assert not overridden.is_variant("inclusion_list") + + +def test_marks_include_every_derived_label() -> None: + """ + Test that a label is marked with every label it was derived from. + + Selecting the variant's own label must also select the labels other spec + types derived from it, so `-m <variant>` does not silently miss them. + """ + variant = BlockchainEngineFixture.with_label_suffix( + "inclusion_list", + transition_tool_cache_key="blockchain_test_inclusion_list", + variant="inclusion_list", + ) + derived = variant.with_label_suffix("from_state_test") + + assert mark_names(variant) == [ + "blockchain_test_engine", + "transition_tool_cache_key", + "blockchain_test_engine_inclusion_list", + ] + assert mark_names(derived) == [ + "blockchain_test_engine", + "transition_tool_cache_key", + "blockchain_test_engine_inclusion_list", + "blockchain_test_engine_inclusion_list_from_state_test", + ] + assert derived.labels() == [ + "blockchain_test_engine_inclusion_list", + "blockchain_test_engine_inclusion_list_from_state_test", + ] + + +def test_marks_of_label_over_plain_format() -> None: + """Test that a label over a plain format marks only its own label.""" + derived = BlockchainFixture.with_label_suffix("from_state_test") + + assert derived.labels() == ["blockchain_test_from_state_test"] + assert mark_names(derived) == [ + "blockchain_test", + "transition_tool_cache_key", + "blockchain_test_from_state_test", + ] diff --git a/packages/testing/src/execution_testing/specs/base.py b/packages/testing/src/execution_testing/specs/base.py index bf0585396f8..3cf8e60f6b9 100644 --- a/packages/testing/src/execution_testing/specs/base.py +++ b/packages/testing/src/execution_testing/specs/base.py @@ -13,10 +13,12 @@ Generator, List, Sequence, + Tuple, Type, ) import pytest +from _pytest.mark.structures import ParameterSet from pydantic import BaseModel, ConfigDict, Field from typing_extensions import Self @@ -100,6 +102,37 @@ class FillResult(BaseModel): metadata: Dict[str, Any] = Field(default_factory=dict) +def labeled_format_parameter_set( + format_with_or_without_label: LabeledExecuteFormat + | LabeledFixtureFormat + | ExecuteFormat + | FixtureFormat, + primary_format: bool = False, +) -> ParameterSet: + """ + Return a parameter set from a fixture/execute format and parse a label if + there's any. + + The label will be used in the test id and also will be added as a marker to + the generated test case when filling/executing the test. + + The format keeps its label, so a spec type that labels one format more + than once can tell which of them it is filling. Call `format_class()` to + strip the label. + """ + return pytest.param( + format_with_or_without_label, + id=format_with_or_without_label.format_id(), + marks=format_with_or_without_label.marks() + + [ + pytest.mark.fixture_format_id( + format_with_or_without_label.format_id() + ) + ] + + ([pytest.mark.primary_format] if primary_format else []), + ) + + class BaseTest(BaseModel): """ Represents a base Ethereum test which must return a single test fixture. @@ -119,6 +152,7 @@ class BaseTest(BaseModel): expected_receipt_status: int | None = None is_tx_gas_heavy_test: bool = False is_exception_test: bool = False + is_inclusion_test: bool = False # Class variables, to be set by subclasses spec_types: ClassVar[Dict[str, Type["BaseTest"]]] = {} @@ -142,16 +176,80 @@ def model_post_init(self, __context: Any, /) -> None: @classmethod def discard_fixture_format_by_marks( cls, - fixture_format: FixtureFormat, + fixture_format: FixtureFormat | LabeledFixtureFormat, markers: List[pytest.Mark], ) -> bool: """ Discard a fixture format from filling if the appropriate marker is used. + + The format keeps its label: comparing it against a plain format + matches every label of it, `format_id()` identifies a single one. """ del fixture_format, markers return False + @classmethod + def fixture_format_parameters( + cls, + *, + markers: List[pytest.Mark], + ) -> List[Tuple[FixtureFormat | LabeledFixtureFormat, ParameterSet]]: + """ + Return one pytest parameter per fixture format this spec type fills, + paired with the format it fills, so the caller can narrow the formats + down to the ones its session generates. + + The first format not vetoed by `discard_fixture_format_by_marks` is + the primary and is marked `primary_format`, so `-m primary_format` + fills each test once. Only sessions that generate a single format per + test can filter that primary out, and there the marker is moot. + + An override that parametrizes further must leave exactly one parameter + marked `primary_format`. + """ + parameters: List[ + Tuple[FixtureFormat | LabeledFixtureFormat, ParameterSet] + ] = [] + for format_with_or_without_label in cls.supported_fixture_formats: + if cls.discard_fixture_format_by_marks( + format_with_or_without_label, + markers, + ): + continue + parameter = labeled_format_parameter_set( + format_with_or_without_label, + primary_format=not parameters, + ) + parameters.append((format_with_or_without_label, parameter)) + return parameters + + @classmethod + def execute_format_parameters( + cls, + ) -> List[Tuple[LabeledExecuteFormat, ParameterSet]]: + """ + Return one pytest parameter per execute format this spec type runs. + + Each element pairs the labeled format with the parameter that executes + it, so the caller can inspect what the format requires of the session + (e.g. an Engine RPC) and skip or select it accordingly. + + Formats are not vetoed here the way `fixture_format_parameters` vetoes + them: `discard_execute_format_by_marks` needs the fork, which is only + parametrized later, so the caller applies it during collection. + + Subclasses may override this to parametrize further, e.g. to execute a + single execute format more than once. + """ + return [ + ( + labeled_execute_format, + labeled_format_parameter_set(labeled_execute_format), + ) + for labeled_execute_format in cls.supported_execute_formats + ] + @classmethod def __pydantic_init_subclass__(cls, **kwargs: Any) -> None: """ @@ -184,7 +282,7 @@ def from_test( @classmethod def discard_execute_format_by_marks( cls, - execute_format: ExecuteFormat, + execute_format: ExecuteFormat | LabeledExecuteFormat, fork: Fork | TransitionFork, markers: List[pytest.Mark], ) -> bool: @@ -200,7 +298,7 @@ def generate( self, *, t8n: TransitionTool, - fixture_format: FixtureFormat, + fixture_format: FixtureFormat | LabeledFixtureFormat, ) -> FillResult: """Generate the test fixture using the given fixture format.""" pass @@ -208,7 +306,7 @@ def generate( def execute( self, *, - execute_format: ExecuteFormat, + execute_format: ExecuteFormat | LabeledExecuteFormat, ) -> BaseExecute: """Generate the list of test fixtures.""" raise Exception(f"Unsupported execute format: {execute_format}") diff --git a/packages/testing/src/execution_testing/specs/benchmark.py b/packages/testing/src/execution_testing/specs/benchmark.py index c1c2d5358e6..77b43a34eaa 100644 --- a/packages/testing/src/execution_testing/specs/benchmark.py +++ b/packages/testing/src/execution_testing/specs/benchmark.py @@ -448,7 +448,7 @@ def pytest_parameter_name(cls) -> str: @classmethod def discard_fixture_format_by_marks( cls, - fixture_format: FixtureFormat, + fixture_format: FixtureFormat | LabeledFixtureFormat, markers: List[pytest.Mark], ) -> bool: """ @@ -575,7 +575,7 @@ def _verify_target_opcode_count( def generate( self, t8n: TransitionTool, - fixture_format: FixtureFormat, + fixture_format: FixtureFormat | LabeledFixtureFormat, ) -> FillResult: """Generate the blockchain test fixture.""" self.check_exception_test( @@ -627,7 +627,7 @@ def generate( def execute( self, *, - execute_format: ExecuteFormat, + execute_format: ExecuteFormat | LabeledExecuteFormat, ) -> BaseExecute: """Execute the benchmark test by sending it to the live network.""" if execute_format == TransactionPost: diff --git a/packages/testing/src/execution_testing/specs/blobs.py b/packages/testing/src/execution_testing/specs/blobs.py index 630f6765dc6..9b2565b520f 100644 --- a/packages/testing/src/execution_testing/specs/blobs.py +++ b/packages/testing/src/execution_testing/specs/blobs.py @@ -8,6 +8,7 @@ from execution_testing.execution import BaseExecute, BlobTransaction from execution_testing.fixtures import ( FixtureFormat, + LabeledFixtureFormat, ) from execution_testing.test_types import ( NetworkWrappedTransaction, @@ -40,7 +41,7 @@ def generate( self, *, t8n: TransitionTool, - fixture_format: FixtureFormat, + fixture_format: FixtureFormat | LabeledFixtureFormat, ) -> FillResult: """Generate the list of test fixtures.""" del t8n @@ -49,7 +50,7 @@ def generate( def execute( self, *, - execute_format: ExecuteFormat, + execute_format: ExecuteFormat | LabeledExecuteFormat, ) -> BaseExecute: """Generate the list of test fixtures.""" if execute_format == BlobTransaction: diff --git a/packages/testing/src/execution_testing/specs/blockchain.py b/packages/testing/src/execution_testing/specs/blockchain.py index ba0f1f191fd..225f96a6012 100644 --- a/packages/testing/src/execution_testing/specs/blockchain.py +++ b/packages/testing/src/execution_testing/specs/blockchain.py @@ -769,7 +769,7 @@ class BlockchainTest(BaseTest): @classmethod def discard_fixture_format_by_marks( cls, - fixture_format: FixtureFormat, + fixture_format: FixtureFormat | LabeledFixtureFormat, markers: List[pytest.Mark], ) -> bool: """ @@ -790,6 +790,27 @@ def discard_fixture_format_by_marks( return True return False + def model_post_init(self, __context: Any, /) -> None: + """ + Model post-init to assert static (pre-fill/execute) checks. + """ + super().model_post_init(__context) + if self.is_inclusion_test: + # Verify that the blockchain contains at most one invalid + # transaction which must be located at the end of the last block. + for i, block in enumerate(self.blocks): + if i != (len(self.blocks) - 1): + valid_tx_range = block.txs[:] + else: + valid_tx_range = block.txs[:-1] + if any(tx.error is not None for tx in valid_tx_range): + raise Exception( + "test correctness: in an inclusion test the only " + "transaction allowed to produce an exception is the " + "last transaction of the last block, but block " + f"{i} contains an invalid transaction elsewhere" + ) + def get_genesis_environment(self) -> Environment: """Get the genesis environment for pre-allocation groups.""" modified_values = self.genesis_environment.set_fork_requirements( @@ -1236,7 +1257,8 @@ def make_fixture( def make_hive_fixture( self, t8n: FillerBackend, - fixture_format: FixtureFormat = BlockchainEngineFixture, + fixture_format: FixtureFormat + | LabeledFixtureFormat = BlockchainEngineFixture, ) -> FillResult: """Create a hive fixture from the blocktest definition.""" fixture_payloads: List[FixtureEngineNewPayload] = [] @@ -1614,7 +1636,7 @@ def make_stateful_fixture( def generate( self, t8n: FillerBackend, - fixture_format: FixtureFormat, + fixture_format: FixtureFormat | LabeledFixtureFormat, ) -> FillResult: """Generate the BlockchainTest fixture.""" if fixture_format == BlockchainEngineStatefulFixture: @@ -1633,7 +1655,7 @@ def generate( def execute( self, *, - execute_format: ExecuteFormat, + execute_format: ExecuteFormat | LabeledExecuteFormat, ) -> BaseExecute: """Generate the list of test fixtures.""" if execute_format == TransactionPost: diff --git a/packages/testing/src/execution_testing/specs/state.py b/packages/testing/src/execution_testing/specs/state.py index f5c0cea7a19..16323dfad68 100644 --- a/packages/testing/src/execution_testing/specs/state.py +++ b/packages/testing/src/execution_testing/specs/state.py @@ -91,24 +91,14 @@ class StateTest(BaseTest): ] = [ StateFixture, ] + [ - LabeledFixtureFormat( - fixture_format, - f"{fixture_format.format_name}_from_state_test", - f"A {fixture_format.format_name} generated from a state_test", + fixture_format.with_label_suffix( + "from_state_test", + f"A {fixture_format.format_id()} generated from a state_test", ) for fixture_format in BlockchainTest.supported_fixture_formats # Exclude sync fixtures from state tests - they don't make sense for # state tests - if not ( - ( - hasattr(fixture_format, "__name__") - and "Sync" in fixture_format.__name__ - ) - or ( - hasattr(fixture_format, "format") - and "Sync" in fixture_format.format.__name__ - ) - ) + if "Sync" not in fixture_format.format_class().__name__ ] supported_execute_formats: ClassVar[Sequence[LabeledExecuteFormat]] = [ LabeledExecuteFormat( @@ -232,7 +222,7 @@ def verify_modified_gas_limit( @classmethod def discard_fixture_format_by_marks( cls, - fixture_format: FixtureFormat, + fixture_format: FixtureFormat | LabeledFixtureFormat, markers: List[pytest.Mark], ) -> bool: """ @@ -526,7 +516,7 @@ def get_genesis_environment(self) -> Environment: def generate( self, t8n: TransitionTool, - fixture_format: FixtureFormat, + fixture_format: FixtureFormat | LabeledFixtureFormat, ) -> FillResult: """Generate the BlockchainTest fixture.""" self.check_exception_test(exception=self.tx.error is not None) @@ -542,7 +532,7 @@ def generate( def execute( self, *, - execute_format: ExecuteFormat, + execute_format: ExecuteFormat | LabeledExecuteFormat, ) -> BaseExecute: """Generate the list of test fixtures.""" if execute_format == TransactionPost: diff --git a/packages/testing/src/execution_testing/specs/tests/test_specs.py b/packages/testing/src/execution_testing/specs/tests/test_specs.py new file mode 100644 index 00000000000..9204f1330ca --- /dev/null +++ b/packages/testing/src/execution_testing/specs/tests/test_specs.py @@ -0,0 +1,179 @@ +"""Test specs from execution_testing.specs.""" + +from typing import ClassVar, Dict, Protocol, Sequence, Tuple, Type + +import pytest + +from execution_testing.fixtures import ( + BaseFixture, + BlockchainFixture, + FixtureFormat, + LabeledFixtureFormat, + StateFixture, +) + +from ..base import BaseTest +from ..blockchain import BlockchainTest +from ..state import StateTest + + +def test_spec_types() -> None: + """Test basic spec types are visible.""" + assert len(BaseTest.spec_types.items()) > 0 + assert "state_test" in BaseTest.spec_types + assert "blockchain_test" in BaseTest.spec_types + + +class SpecType(Protocol): + """Any class that declares supported fixture formats.""" + + supported_fixture_formats: ClassVar[ + Sequence[FixtureFormat | LabeledFixtureFormat] + ] + + +class DuplicateTransitionToolCacheKeyError(Exception): + """ + Exception used to indicate that a single spec uses the same + fixture format twice without making a clear distinction of their + transition tool cache keys. + """ + + def __init__( + self, + *, + spec: Type[SpecType], + key: Tuple[Type[BaseFixture], str], + format_id_1: str, + format_id_2: str, + ): + super().__init__( + f"Duplicate Transition Tool Cache Key: " + f'key "{key}" is used by two different fixture formats in spec "' + f'f"type {spec}: "{format_id_1}", "{format_id_2}"' + ) + + +def spec_supported_fixture_formats_verifier(spec: Type[SpecType]) -> None: + """ + Verify that the provided spec does not break the + format-class+transition_tool_cache_key rule. + """ + keys: Dict[Tuple[Type[BaseFixture], str], str] = dict() + for fixture_format in spec.supported_fixture_formats: + key = ( + fixture_format.format_class(), + fixture_format.transition_tool_cache_key, + ) + if key in keys: + raise DuplicateTransitionToolCacheKeyError( + spec=spec, + key=key, + format_id_1=fixture_format.format_id(), + format_id_2=keys[key], + ) + keys[key] = fixture_format.format_id() + + +class DummyIncorrectSpec1: + """Spec type that duplicates fixture formats.""" + + supported_fixture_formats: ClassVar[ + Sequence[FixtureFormat | LabeledFixtureFormat] + ] = [ + BlockchainFixture, + BlockchainFixture, + ] + + +class DummyIncorrectSpec2: + """Spec type that duplicates fixture formats.""" + + supported_fixture_formats: ClassVar[ + Sequence[FixtureFormat | LabeledFixtureFormat] + ] = [ + BlockchainFixture, + LabeledFixtureFormat( + fixture_format=BlockchainFixture, + label="alt_blockchain_fixture", + description="alternative blockchain fixture", + ), + ] + + +class DummyCorrectSpec: + """Spec type that duplicates fixture formats.""" + + supported_fixture_formats: ClassVar[ + Sequence[FixtureFormat | LabeledFixtureFormat] + ] = [ + BlockchainFixture, + LabeledFixtureFormat( + fixture_format=BlockchainFixture, + label="alt_blockchain_fixture", + description="alternative blockchain fixture", + transition_tool_cache_key="", + ), + ] + + +@pytest.mark.parametrize( + "spec,correct", + [ + pytest.param(DummyIncorrectSpec1, False), + pytest.param(DummyIncorrectSpec2, False), + pytest.param(DummyCorrectSpec, True), + ], +) +def test_spec_supported_fixture_formats_verifier( + spec: Type[SpecType], + correct: bool, +) -> None: + """Unit test for `spec_supported_fixture_formats_verifier`.""" + if correct: + spec_supported_fixture_formats_verifier(spec) + else: + with pytest.raises(DuplicateTransitionToolCacheKeyError): + spec_supported_fixture_formats_verifier(spec) + + +@pytest.mark.parametrize("spec", BaseTest.spec_types) +def test_spec_types_fixture_formats(spec: str) -> None: + """ + Verify that none of the declared spec types contain fixture formats + that break the format-class+transition_tool_cache_key rule. + """ + spec_supported_fixture_formats_verifier(BaseTest.spec_types[spec]) + + +def test_state_test_labels_every_blockchain_test_format() -> None: + """ + Verify `StateTest` derives one label per format `BlockchainTest` fills, + minus the sync formats, each keeping its format class and cache key. + + Deriving the label from `format_name` rather than `format_id()` would + collapse two labels of one format onto the same derived label, and the + duplicate would be dropped silently by `registered_labels`. + """ + derived = { + fixture_format.format_id(): fixture_format + for fixture_format in StateTest.supported_fixture_formats + if fixture_format.format_class() is not StateFixture + } + expected = [ + fixture_format + for fixture_format in BlockchainTest.supported_fixture_formats + if "Sync" not in fixture_format.format_class().__name__ + ] + + assert len(derived) == len(expected), ( + f"Expected one label per blockchain test format: {sorted(derived)}" + ) + for fixture_format in expected: + label = f"{fixture_format.format_id()}_from_state_test" + assert label in derived, f"Missing label {label}: {sorted(derived)}" + assert derived[label].format_class() is fixture_format.format_class() + assert ( + derived[label].transition_tool_cache_key + == fixture_format.transition_tool_cache_key + ) diff --git a/packages/testing/src/execution_testing/specs/transaction.py b/packages/testing/src/execution_testing/specs/transaction.py index f3f032151f7..0bc821e9ff3 100644 --- a/packages/testing/src/execution_testing/specs/transaction.py +++ b/packages/testing/src/execution_testing/specs/transaction.py @@ -86,7 +86,7 @@ def make_transaction_test_fixture( def generate( self, t8n: TransitionTool, - fixture_format: FixtureFormat, + fixture_format: FixtureFormat | LabeledFixtureFormat, ) -> FillResult: """Generate the TransactionTest fixture.""" del t8n @@ -100,7 +100,7 @@ def generate( def execute( self, *, - execute_format: ExecuteFormat, + execute_format: ExecuteFormat | LabeledExecuteFormat, ) -> BaseExecute: """Execute the transaction test by sending it to the live network.""" if execute_format == TransactionPost: diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_authorization_oog.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_authorization_oog.py index eb9b707b739..0488a0d8c29 100644 --- a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_authorization_oog.py +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_authorization_oog.py @@ -1290,6 +1290,7 @@ def test_auth_state_gas_in_header_on_dispatch_revert( ) +@pytest.mark.inclusion_test @pytest.mark.parametrize( "delta", [ diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_calldata_floor.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_calldata_floor.py index fd750ca4e05..df47732d57a 100644 --- a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_calldata_floor.py +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_calldata_floor.py @@ -69,6 +69,7 @@ def floor(byte_count: int) -> int: return Bytes(b"\x00" * byte_count) +@pytest.mark.inclusion_test @pytest.mark.parametrize( "outcome", [ @@ -231,6 +232,7 @@ def floor(byte_count: int) -> int: return Bytes(b"\x00" * byte_count) +@pytest.mark.inclusion_test @pytest.mark.parametrize( "outcome", [ @@ -330,6 +332,7 @@ def test_calldata_floor_contract_creation( state_test(pre=pre, tx=tx, post=post) +@pytest.mark.inclusion_test @pytest.mark.parametrize( "outcome", [ diff --git a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_intrinsic_gas_boundary.py b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_intrinsic_gas_boundary.py index e2c6fbc2be3..3f0d3ea7677 100644 --- a/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_intrinsic_gas_boundary.py +++ b/tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_intrinsic_gas_boundary.py @@ -37,6 +37,7 @@ pytestmark = pytest.mark.valid_from("Amsterdam") +@pytest.mark.inclusion_test @pytest.mark.exception_test @pytest.mark.parametrize("recipient_type", RECIPIENT_TYPES_NON_CREATE) @pytest.mark.parametrize( @@ -80,6 +81,7 @@ def test_intrinsic_gas_floor_boundary( state_test(pre=pre, tx=tx, post={}) +@pytest.mark.inclusion_test @pytest.mark.exception_test @pytest.mark.parametrize( "value", @@ -125,6 +127,7 @@ def test_intrinsic_gas_floor_boundary_contract_creation( state_test(pre=pre, tx=tx, post={}) +@pytest.mark.inclusion_test @pytest.mark.exception_test @pytest.mark.parametrize( "authorization_count", @@ -184,6 +187,7 @@ def test_intrinsic_gas_floor_boundary_with_authorizations( state_test(pre=pre, tx=tx, post=pre) +@pytest.mark.inclusion_test @pytest.mark.exception_test @pytest.mark.with_all_tx_types def test_intrinsic_gas_floor_boundary_all_tx_types( diff --git a/tests/amsterdam/eip7778_block_gas_accounting_without_refunds/test_gas_accounting.py b/tests/amsterdam/eip7778_block_gas_accounting_without_refunds/test_gas_accounting.py index 45b145c5212..426d1a165c4 100644 --- a/tests/amsterdam/eip7778_block_gas_accounting_without_refunds/test_gas_accounting.py +++ b/tests/amsterdam/eip7778_block_gas_accounting_without_refunds/test_gas_accounting.py @@ -234,6 +234,7 @@ def test_simple_gas_accounting( ) +@pytest.mark.inclusion_test @pytest.mark.parametrize( "refund_tx_reverts", [ @@ -530,6 +531,7 @@ def test_varying_calldata_costs( ) +@pytest.mark.inclusion_test @pytest.mark.parametrize( "refund_tx_reverts", [ diff --git a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py index d9b50654535..eb728087828 100644 --- a/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py +++ b/tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py @@ -3006,6 +3006,7 @@ def test_bal_cross_tx_balance_dependency( ) +@pytest.mark.inclusion_test @pytest.mark.parametrize( "eunice_outcome", [ 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 7d0ff0e0a36..adcbdedadcc 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 @@ -53,6 +53,7 @@ def test_over_max_code_size_mainnet( state_test(pre=pre, tx=tx, post=post) +@pytest.mark.inclusion_test @pytest.mark.exception_test def test_over_max_initcode_size_mainnet( state_test: StateTestFiller, 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 f3a469c43a5..bd83b30c467 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 @@ -43,6 +43,7 @@ ] +@pytest.mark.inclusion_test @pytest.mark.parametrize("initcode_size", TX_INITCODE_SIZE_PARAMS) def test_max_initcode_size( state_test: StateTestFiller, @@ -141,6 +142,7 @@ def test_max_initcode_size_via_create( state_test(pre=pre, tx=tx, post=post) +@pytest.mark.inclusion_test @pytest.mark.parametrize( "gas_shortfall", [ diff --git a/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_floor_boundary_exact_balance.py b/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_floor_boundary_exact_balance.py index 8b2be28ff78..2088fb77a7b 100644 --- a/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_floor_boundary_exact_balance.py +++ b/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_floor_boundary_exact_balance.py @@ -22,6 +22,7 @@ pytestmark = pytest.mark.valid_at("EIP7976") +@pytest.mark.inclusion_test @pytest.mark.exception_test @pytest.mark.parametrize( "zero_bytes", diff --git a/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_fork_transition.py b/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_fork_transition.py index b78f996801b..5afbb85de60 100644 --- a/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_fork_transition.py +++ b/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_fork_transition.py @@ -167,6 +167,7 @@ def test_floor_cost_across_amsterdam_transition( blockchain_test(pre=pre, blocks=blocks, post=post) +@pytest.mark.inclusion_test @EIPChecklist.ModifiedTransactionValidityConstraint.Test.ForkTransition.AcceptedBeforeFork() @EIPChecklist.ModifiedTransactionValidityConstraint.Test.ForkTransition.RejectedBeforeFork() @EIPChecklist.ModifiedTransactionValidityConstraint.Test.ForkTransition.AcceptedAfterFork() diff --git a/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_transaction_validity.py b/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_transaction_validity.py index f12c8f11444..b6205314d05 100644 --- a/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_transaction_validity.py +++ b/tests/amsterdam/eip7976_increase_calldata_floor_cost/test_transaction_validity.py @@ -21,7 +21,10 @@ REFERENCE_SPEC_GIT_PATH = ref_spec_7976.git_path REFERENCE_SPEC_VERSION = ref_spec_7976.version -pytestmark = [pytest.mark.valid_from("EIP7976")] +pytestmark = [ + pytest.mark.valid_from("EIP7976"), + pytest.mark.inclusion_test, +] # All tests in this file are parametrized with the following parameters: diff --git a/tests/amsterdam/eip7981_increase_access_list_cost/test_floor_boundary_exact_balance.py b/tests/amsterdam/eip7981_increase_access_list_cost/test_floor_boundary_exact_balance.py index 60eafcd0efb..0f6d6a51231 100644 --- a/tests/amsterdam/eip7981_increase_access_list_cost/test_floor_boundary_exact_balance.py +++ b/tests/amsterdam/eip7981_increase_access_list_cost/test_floor_boundary_exact_balance.py @@ -25,6 +25,7 @@ pytestmark = pytest.mark.valid_at("EIP7981") +@pytest.mark.inclusion_test @EIPChecklist.GasCostChanges.Test.OutOfGas() @pytest.mark.exception_test @pytest.mark.parametrize( diff --git a/tests/amsterdam/eip7981_increase_access_list_cost/test_transaction_validity.py b/tests/amsterdam/eip7981_increase_access_list_cost/test_transaction_validity.py index dbeed9ded11..440e844c868 100644 --- a/tests/amsterdam/eip7981_increase_access_list_cost/test_transaction_validity.py +++ b/tests/amsterdam/eip7981_increase_access_list_cost/test_transaction_validity.py @@ -23,7 +23,10 @@ REFERENCE_SPEC_GIT_PATH = ref_spec_7981.git_path REFERENCE_SPEC_VERSION = ref_spec_7981.version -pytestmark = pytest.mark.valid_at("EIP7981") +pytestmark = [ + pytest.mark.valid_at("EIP7981"), + pytest.mark.inclusion_test, +] @EIPChecklist.GasCostChanges.Test.OutOfGas() diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py index 5726cd0d58c..84cbb484e4e 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_block_2d_gas_accounting.py @@ -489,6 +489,7 @@ def test_multi_block_dimension_flip( ) +@pytest.mark.inclusion_test @pytest.mark.parametrize( "tx_gas_delta, expected_exception", [ @@ -604,6 +605,7 @@ def test_tx_gas_limit_block_boundary( ) +@pytest.mark.inclusion_test @pytest.mark.parametrize( "delta", [ @@ -936,6 +938,7 @@ def test_base_fee_per_gas_follows_dominant_dimension( ) +@pytest.mark.inclusion_test @pytest.mark.parametrize( "delta", [ @@ -1011,6 +1014,7 @@ def test_cumulative_block_state_gas_boundary( ) +@pytest.mark.inclusion_test @pytest.mark.parametrize( "over_by", [ diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_calldata_floor.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_calldata_floor.py index 08ab7d6cd23..c79f3b05873 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_calldata_floor.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_calldata_floor.py @@ -126,6 +126,7 @@ def test_calldata_floor_higher_than_execution_with_state_ops( state_test(pre=pre, post=post, tx=tx) +@pytest.mark.inclusion_test @pytest.mark.parametrize( "exceeds_cap", [ diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py index d403d5f8b4b..cb47916b61d 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_create.py @@ -542,6 +542,7 @@ def test_create2_address_collision( state_test(pre=pre, post=post, tx=tx) +@pytest.mark.inclusion_test @pytest.mark.parametrize( "gas_delta", [ @@ -586,6 +587,7 @@ def test_create_tx_intrinsic_gas_boundary( state_test(pre=pre, post={}, tx=tx) +@pytest.mark.inclusion_test @pytest.mark.exception_test @pytest.mark.parametrize( "initcode", @@ -2349,6 +2351,7 @@ def test_create_onto_alive_refunds_to_gas_left( state_test(pre=pre, post=post, tx=tx) +@pytest.mark.inclusion_test @pytest.mark.parametrize( "initcode_size_delta", [ diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_pricing.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_pricing.py index df91061a30d..e05dc3d9c20 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_pricing.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_pricing.py @@ -302,6 +302,7 @@ def _access_list_over_execution_cap( ] +@pytest.mark.inclusion_test @pytest.mark.exception_test @pytest.mark.valid_from("EIP8037") def test_intrinsic_execution_gas_exceeds_cap( @@ -349,6 +350,7 @@ def test_intrinsic_execution_gas_exceeds_cap( state_test(pre=pre, post={}, tx=tx) +@pytest.mark.inclusion_test @pytest.mark.exception_test @pytest.mark.valid_from("EIP8037") def test_intrinsic_execution_gas_exceeds_cap_with_floor_below_cap( @@ -446,6 +448,7 @@ def test_intrinsic_within_cap_gas_limit_above_cap( state_test(pre=pre, post={contract: Account(storage=storage)}, tx=tx) +@pytest.mark.inclusion_test @pytest.mark.parametrize( "above_floor", [ diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_reservoir.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_reservoir.py index a75ac6136e5..c771cf73d28 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_reservoir.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_reservoir.py @@ -194,6 +194,7 @@ def test_insufficient_gas_for_sstore_state_cost( state_test(pre=pre, post=post, tx=tx) +@pytest.mark.inclusion_test @pytest.mark.parametrize( "exceed_block_gas_limit", [ @@ -247,6 +248,7 @@ def test_block_execution_gas_limit( blockchain_test(pre=pre, post={}, blocks=[block]) +@pytest.mark.inclusion_test @pytest.mark.parametrize( "delta", [ @@ -332,6 +334,7 @@ def test_block_state_gas_limit_boundary( ) +@pytest.mark.inclusion_test @pytest.mark.exception_test @pytest.mark.valid_from("EIP8037") def test_creation_tx_execution_check_uses_full_tx_gas( @@ -414,6 +417,7 @@ def test_creation_tx_execution_check_uses_full_tx_gas( ) +@pytest.mark.inclusion_test @pytest.mark.exception_test @pytest.mark.valid_from("EIP8037") def test_single_tx_state_check_exceeds_block_limit( @@ -456,6 +460,7 @@ def test_single_tx_state_check_exceeds_block_limit( ) +@pytest.mark.inclusion_test @pytest.mark.exception_test @pytest.mark.valid_from("EIP8037") def test_creation_tx_state_check_exceeded( diff --git a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py index 0d846bbfb90..b8b6a568e50 100644 --- a/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py +++ b/tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_set_code.py @@ -178,6 +178,7 @@ def test_authorization_state_gas_scaling( ) +@pytest.mark.inclusion_test @pytest.mark.exception_test @pytest.mark.parametrize( "num_auths", @@ -1131,6 +1132,7 @@ def test_auth_with_multiple_sstores( ) +@pytest.mark.inclusion_test @pytest.mark.parametrize( "gas_delta", [ diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_create_gas.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_create_gas.py index f03aae62c12..313413d7964 100644 --- a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_create_gas.py +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_create_gas.py @@ -279,6 +279,7 @@ def exact_execution_gas( execution += initcode.deployment_gas(fork) return execution + @pytest.mark.inclusion_test @pytest.mark.parametrize( "gas_test_case", [ diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_exact_balance_no_fallback.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_exact_balance_no_fallback.py index dec4044daa8..98263945731 100644 --- a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_exact_balance_no_fallback.py +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_exact_balance_no_fallback.py @@ -61,6 +61,7 @@ def gas_costs_before_increase( return ancestor.gas_costs() +@pytest.mark.inclusion_test @EIPChecklist.GasCostChanges.Test.OutOfGas() @pytest.mark.exception_test @pytest.mark.parametrize( @@ -136,6 +137,7 @@ def test_access_list_no_fallback( state_test(pre=pre, post={}, tx=tx) +@pytest.mark.inclusion_test @EIPChecklist.GasCostChanges.Test.OutOfGas() @pytest.mark.exception_test @pytest.mark.parametrize( @@ -205,6 +207,7 @@ def test_authorization_no_fallback( state_test(pre=pre, post={}, tx=tx) +@pytest.mark.inclusion_test @EIPChecklist.GasCostChanges.Test.OutOfGas() @pytest.mark.exception_test def test_cold_account_access_no_fallback( diff --git a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_set_code_auth_gas.py b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_set_code_auth_gas.py index fc7b3a6ab9f..b68a9f117b0 100644 --- a/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_set_code_auth_gas.py +++ b/tests/amsterdam/eip8038_state_access_gas_cost_increase/test_set_code_auth_gas.py @@ -173,6 +173,7 @@ def test_auth_execution_intrinsic_magnitude( state_test(env=env, pre=pre, post=post, tx=tx) +@pytest.mark.inclusion_test @EIPChecklist.GasCostChanges.Test.OutOfGas() @pytest.mark.exception_test @pytest.mark.parametrize("n", [1, 3]) diff --git a/tests/berlin/eip2930_access_list/test_acl.py b/tests/berlin/eip2930_access_list/test_acl.py index 9e7299994e6..30c1e78bd71 100644 --- a/tests/berlin/eip2930_access_list/test_acl.py +++ b/tests/berlin/eip2930_access_list/test_acl.py @@ -104,6 +104,7 @@ def test_account_storage_warm_cold_state( state_test(env=env, pre=pre, post=post, tx=tx) +@pytest.mark.inclusion_test @pytest.mark.parametrize( "access_lists", [ diff --git a/tests/berlin/eip2930_access_list/test_tx_intrinsic_gas.py b/tests/berlin/eip2930_access_list/test_tx_intrinsic_gas.py index 0fbfa171961..bcdaf00f1cc 100644 --- a/tests/berlin/eip2930_access_list/test_tx_intrinsic_gas.py +++ b/tests/berlin/eip2930_access_list/test_tx_intrinsic_gas.py @@ -143,6 +143,7 @@ ] +@pytest.mark.inclusion_test @pytest.mark.ported_from( [ "https://github.com/ethereum/tests/blob/v13.3/src/GeneralStateTestsFiller/stEIP1559/intrinsicGen.js", diff --git a/tests/berlin/eip2930_access_list/test_tx_type.py b/tests/berlin/eip2930_access_list/test_tx_type.py index 4eb7434abec..ae7f59ad883 100644 --- a/tests/berlin/eip2930_access_list/test_tx_type.py +++ b/tests/berlin/eip2930_access_list/test_tx_type.py @@ -34,6 +34,7 @@ def tx_validity(fork: Fork) -> Generator[ParameterSet, None, None]: ) +@pytest.mark.inclusion_test @pytest.mark.ported_from( [ "https://github.com/ethereum/legacytests/blob/master/src/LegacyTests/Cancun/GeneralStateTestsFiller/stExample/accessListExampleFiller.yml" diff --git a/tests/cancun/eip4844_blobs/test_blob_txs.py b/tests/cancun/eip4844_blobs/test_blob_txs.py index 9b05cf607ef..11215f65b2c 100644 --- a/tests/cancun/eip4844_blobs/test_blob_txs.py +++ b/tests/cancun/eip4844_blobs/test_blob_txs.py @@ -569,6 +569,7 @@ def generate_invalid_tx_max_fee_per_blob_gas_tests(fork: Fork) -> List: return tests +@pytest.mark.inclusion_test @pytest.mark.parametrize_by_fork( "parent_excess_blobs,parent_blobs,tx_max_fee_per_blob_gas,tx_error", generate_invalid_tx_max_fee_per_blob_gas_tests, @@ -604,6 +605,7 @@ def test_invalid_tx_max_fee_per_blob_gas( ) +@pytest.mark.inclusion_test @pytest.mark.parametrize_by_fork( "parent_excess_blobs,parent_blobs,tx_max_fee_per_blob_gas,tx_error", generate_invalid_tx_max_fee_per_blob_gas_tests, @@ -631,6 +633,7 @@ def test_invalid_tx_max_fee_per_blob_gas_state( ) +@pytest.mark.inclusion_test @pytest.mark.parametrize( "tx_max_fee_per_gas,tx_error", [ @@ -669,6 +672,7 @@ def test_invalid_normal_gas( ) +@pytest.mark.inclusion_test @pytest.mark.parametrize_by_fork( "blobs_per_tx", SpecHelpers.invalid_blob_combinations, @@ -708,6 +712,7 @@ def test_invalid_block_blob_count( ) +@pytest.mark.inclusion_test @pytest.mark.parametrize( "tx_access_list", [[], [AccessList(address=100, storage_keys=[100, 200])]], @@ -969,6 +974,7 @@ def test_blob_gas_subtraction_tx( ) +@pytest.mark.inclusion_test @pytest.mark.parametrize_by_fork( "blobs_per_tx", SpecHelpers.all_valid_blob_combinations, @@ -1026,6 +1032,7 @@ def generate_invalid_tx_blob_count_tests( ] +@pytest.mark.inclusion_test @pytest.mark.parametrize_by_fork( "blobs_per_tx,tx_error", generate_invalid_tx_blob_count_tests, @@ -1058,6 +1065,7 @@ def test_invalid_tx_blob_count( ) +@pytest.mark.inclusion_test @pytest.mark.parametrize( "blob_hashes_per_tx", [ @@ -1112,6 +1120,7 @@ def test_invalid_blob_hash_versioning_single_tx( ) +@pytest.mark.inclusion_test @pytest.mark.parametrize( "blob_hashes_per_tx", [ @@ -1172,6 +1181,7 @@ def test_invalid_blob_hash_versioning_multiple_txs( ) +@pytest.mark.inclusion_test @pytest.mark.parametrize( "tx_gas", [500_000], ids=[""] ) # Increase gas to account for contract creation @@ -1521,6 +1531,7 @@ def test_blob_tx_attribute_gasprice_opcode( ) +@pytest.mark.inclusion_test @pytest.mark.parametrize( [ "blobs_per_tx", diff --git a/tests/frontier/validation/test_transaction.py b/tests/frontier/validation/test_transaction.py index 8284fc31614..0be8dfd5a86 100644 --- a/tests/frontier/validation/test_transaction.py +++ b/tests/frontier/validation/test_transaction.py @@ -26,6 +26,7 @@ from execution_testing.test_types.transaction_types import TransactionDefaults +@pytest.mark.inclusion_test @pytest.mark.exception_test @pytest.mark.eels_base_coverage def test_tx_gas_limit( @@ -61,6 +62,7 @@ def test_tx_gas_limit( blockchain_test(pre=pre, post={}, blocks=[block], genesis_environment=env) +@pytest.mark.inclusion_test @pytest.mark.parametrize( "nonce_diff, expected_exception", [ @@ -102,6 +104,7 @@ def test_tx_nonce( state_test(pre=pre, post={}, tx=tx) +@pytest.mark.inclusion_test @pytest.mark.pre_alloc_mutable @pytest.mark.exception_test @pytest.mark.eels_base_coverage @@ -129,6 +132,7 @@ def test_tx_max_nonce(state_test: StateTestFiller, pre: Alloc) -> None: state_test(pre=pre, post={sender: Account(nonce=max_nonce)}, tx=tx) +@pytest.mark.inclusion_test @pytest.mark.exception_test def test_tx_nonce_overflow( transaction_test: TransactionTestFiller, @@ -151,6 +155,7 @@ def test_tx_nonce_overflow( transaction_test(pre=pre, tx=tx) +@pytest.mark.inclusion_test @pytest.mark.parametrize( "balance_diff, expected_exception", [ @@ -205,6 +210,7 @@ def test_sender_balance( blockchain_test(pre=pre, post={}, blocks=[block], genesis_environment=env) +@pytest.mark.inclusion_test @pytest.mark.valid_from("Frontier") @pytest.mark.state_test_only @pytest.mark.exception_test @@ -249,6 +255,7 @@ def test_sender_balance_insufficient_state_test( SECP256K1N = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 +@pytest.mark.inclusion_test @pytest.mark.valid_from("Frontier") @pytest.mark.exception_test @pytest.mark.eels_base_coverage diff --git a/tests/london/eip1559_fee_market_change/test_tx_type.py b/tests/london/eip1559_fee_market_change/test_tx_type.py index a9dae2eb0bc..39c342fc78a 100644 --- a/tests/london/eip1559_fee_market_change/test_tx_type.py +++ b/tests/london/eip1559_fee_market_change/test_tx_type.py @@ -36,6 +36,7 @@ def tx_validity(fork: Fork) -> Generator[ParameterSet, None, None]: ) +@pytest.mark.inclusion_test @pytest.mark.ported_from( [ "https://github.com/ethereum/legacytests/blob/master/Cancun/GeneralStateTests/stEIP1559/typeTwoBerlin.json" @@ -74,6 +75,7 @@ def test_eip1559_tx_validity( state_test(pre=pre, post=post, tx=tx) +@pytest.mark.inclusion_test @pytest.mark.valid_from("SpuriousDragon") @pytest.mark.exception_test @pytest.mark.with_all_tx_types diff --git a/tests/osaka/eip7594_peerdas/test_max_blob_per_tx.py b/tests/osaka/eip7594_peerdas/test_max_blob_per_tx.py index 693ee2a4d48..7082562c263 100644 --- a/tests/osaka/eip7594_peerdas/test_max_blob_per_tx.py +++ b/tests/osaka/eip7594_peerdas/test_max_blob_per_tx.py @@ -117,6 +117,7 @@ def test_valid_max_blobs_per_tx( ) +@pytest.mark.inclusion_test @pytest.mark.parametrize_by_fork( "blob_count", lambda fork: [ diff --git a/tests/osaka/eip7825_transaction_gas_limit_cap/test_eip_mainnet.py b/tests/osaka/eip7825_transaction_gas_limit_cap/test_eip_mainnet.py index 967db849397..34e3f939bc8 100644 --- a/tests/osaka/eip7825_transaction_gas_limit_cap/test_eip_mainnet.py +++ b/tests/osaka/eip7825_transaction_gas_limit_cap/test_eip_mainnet.py @@ -46,6 +46,7 @@ def test_tx_gas_limit_cap_at_maximum( state_test(pre=pre, post=post, tx=tx) +@pytest.mark.inclusion_test @pytest.mark.exception_test def test_tx_gas_limit_cap_exceeded( state_test: StateTestFiller, diff --git a/tests/osaka/eip7825_transaction_gas_limit_cap/test_tx_gas_limit.py b/tests/osaka/eip7825_transaction_gas_limit_cap/test_tx_gas_limit.py index c1f06d0f08d..b47d5c042f1 100644 --- a/tests/osaka/eip7825_transaction_gas_limit_cap/test_tx_gas_limit.py +++ b/tests/osaka/eip7825_transaction_gas_limit_cap/test_tx_gas_limit.py @@ -87,6 +87,7 @@ def tx_gas_limit_cap_tests(fork: Fork) -> List[ParameterSet]: ] +@pytest.mark.inclusion_test @pytest.mark.parametrize_by_fork("tx_gas_limit,error", tx_gas_limit_cap_tests) @pytest.mark.with_all_tx_types @pytest.mark.valid_from("Prague") @@ -206,6 +207,7 @@ def test_tx_gas_limit_cap_subcall_context( state_test(env=env, pre=pre, post=post, tx=tx) +@pytest.mark.inclusion_test @pytest.mark.parametrize( "exceed_block_gas_limit", [ @@ -334,6 +336,7 @@ def total_cost_floor_per_token(fork: Fork) -> int: return gas_costs.TX_DATA_TOKEN_FLOOR +@pytest.mark.inclusion_test @pytest.mark.bigmem @pytest.mark.xdist_group(name="bigmem") @pytest.mark.parametrize( @@ -408,6 +411,7 @@ def test_tx_gas_limit_cap_full_calldata( ) +@pytest.mark.inclusion_test @pytest.mark.parametrize( "exceed_tx_gas_limit", [ @@ -472,6 +476,7 @@ def test_tx_gas_limit_cap_contract_creation( ) +@pytest.mark.inclusion_test @pytest.mark.parametrize( "exceed_tx_gas_limit,correct_intrinsic_cost_in_transaction_gas_limit", [ @@ -559,6 +564,7 @@ def intrinsic_cost_for_num_storage_keys(storage_key_count: int) -> int: ) +@pytest.mark.inclusion_test @pytest.mark.parametrize( "exceed_tx_gas_limit,correct_intrinsic_cost_in_transaction_gas_limit", [ @@ -640,6 +646,7 @@ def intrinsic_cost_for_num_accounts(account_count: int) -> int: ) +@pytest.mark.inclusion_test @pytest.mark.parametrize( "exceed_tx_gas_limit,correct_intrinsic_cost_in_transaction_gas_limit", [ diff --git a/tests/osaka/eip7825_transaction_gas_limit_cap/test_tx_gas_limit_transition_fork.py b/tests/osaka/eip7825_transaction_gas_limit_cap/test_tx_gas_limit_transition_fork.py index 091adf0acb2..c3e7c83a437 100644 --- a/tests/osaka/eip7825_transaction_gas_limit_cap/test_tx_gas_limit_transition_fork.py +++ b/tests/osaka/eip7825_transaction_gas_limit_cap/test_tx_gas_limit_transition_fork.py @@ -24,6 +24,7 @@ REFERENCE_SPEC_VERSION = ref_spec_7825.version +@pytest.mark.inclusion_test @EIPChecklist.ModifiedTransactionValidityConstraint.Test.ForkTransition.AcceptedBeforeFork() @EIPChecklist.ModifiedTransactionValidityConstraint.Test.ForkTransition.RejectedBeforeFork() @EIPChecklist.ModifiedTransactionValidityConstraint.Test.ForkTransition.AcceptedAfterFork() diff --git a/tests/ported_static/stTransactionTest/test_high_gas_price_paris.py b/tests/ported_static/stTransactionTest/test_high_gas_price_paris.py index 8c6398c3164..f1903b43fd3 100644 --- a/tests/ported_static/stTransactionTest/test_high_gas_price_paris.py +++ b/tests/ported_static/stTransactionTest/test_high_gas_price_paris.py @@ -21,6 +21,7 @@ REFERENCE_SPEC_VERSION = "N/A" +@pytest.mark.inclusion_test @pytest.mark.ported_from( ["state_tests/stTransactionTest/HighGasPriceParisFiller.yml"], ) diff --git a/tests/ported_static/stTransactionTest/test_no_src_account.py b/tests/ported_static/stTransactionTest/test_no_src_account.py index 63e91f3eeda..4a716af0487 100644 --- a/tests/ported_static/stTransactionTest/test_no_src_account.py +++ b/tests/ported_static/stTransactionTest/test_no_src_account.py @@ -28,6 +28,7 @@ REFERENCE_SPEC_VERSION = "N/A" +@pytest.mark.inclusion_test @pytest.mark.ported_from( ["state_tests/stTransactionTest/NoSrcAccountFiller.yml"], ) diff --git a/tests/ported_static/stTransactionTest/test_no_src_account1559.py b/tests/ported_static/stTransactionTest/test_no_src_account1559.py index 3388b348377..00d0153876e 100644 --- a/tests/ported_static/stTransactionTest/test_no_src_account1559.py +++ b/tests/ported_static/stTransactionTest/test_no_src_account1559.py @@ -28,6 +28,7 @@ REFERENCE_SPEC_VERSION = "N/A" +@pytest.mark.inclusion_test @pytest.mark.ported_from( ["state_tests/stTransactionTest/NoSrcAccount1559Filler.yml"], ) diff --git a/tests/ported_static/stTransactionTest/test_no_src_account_create.py b/tests/ported_static/stTransactionTest/test_no_src_account_create.py index 43ff298fbf6..01b891bdea0 100644 --- a/tests/ported_static/stTransactionTest/test_no_src_account_create.py +++ b/tests/ported_static/stTransactionTest/test_no_src_account_create.py @@ -28,6 +28,7 @@ REFERENCE_SPEC_VERSION = "N/A" +@pytest.mark.inclusion_test @pytest.mark.ported_from( ["state_tests/stTransactionTest/NoSrcAccountCreateFiller.yml"], ) diff --git a/tests/ported_static/stTransactionTest/test_no_src_account_create1559.py b/tests/ported_static/stTransactionTest/test_no_src_account_create1559.py index a3a9bf5c9c7..c7a3a513ef6 100644 --- a/tests/ported_static/stTransactionTest/test_no_src_account_create1559.py +++ b/tests/ported_static/stTransactionTest/test_no_src_account_create1559.py @@ -27,6 +27,7 @@ REFERENCE_SPEC_VERSION = "N/A" +@pytest.mark.inclusion_test @pytest.mark.ported_from( ["state_tests/stTransactionTest/NoSrcAccountCreate1559Filler.yml"], ) diff --git a/tests/prague/eip7623_increase_calldata_cost/test_transaction_validity.py b/tests/prague/eip7623_increase_calldata_cost/test_transaction_validity.py index d42d8c4f86e..92030a570df 100644 --- a/tests/prague/eip7623_increase_calldata_cost/test_transaction_validity.py +++ b/tests/prague/eip7623_increase_calldata_cost/test_transaction_validity.py @@ -23,7 +23,10 @@ REFERENCE_SPEC_VERSION = ref_spec_7623.version ENABLE_FORK = Prague -pytestmark = [pytest.mark.valid_from(str(ENABLE_FORK))] +pytestmark = [ + pytest.mark.valid_from(str(ENABLE_FORK)), + pytest.mark.inclusion_test, +] # All tests in this file are parametrized with the following parameters: diff --git a/tests/prague/eip7702_set_code_tx/test_gas.py b/tests/prague/eip7702_set_code_tx/test_gas.py index 4acb1f4f083..7cf6f8f63e1 100644 --- a/tests/prague/eip7702_set_code_tx/test_gas.py +++ b/tests/prague/eip7702_set_code_tx/test_gas.py @@ -1132,6 +1132,7 @@ def test_account_warming( ) +@pytest.mark.inclusion_test @pytest.mark.parametrize( **gas_test_parameter_args(include_pre_authorized=False) ) diff --git a/tests/prague/eip7702_set_code_tx/test_invalid_tx.py b/tests/prague/eip7702_set_code_tx/test_invalid_tx.py index e5100127416..d91ff1f3329 100644 --- a/tests/prague/eip7702_set_code_tx/test_invalid_tx.py +++ b/tests/prague/eip7702_set_code_tx/test_invalid_tx.py @@ -29,7 +29,11 @@ REFERENCE_SPEC_GIT_PATH = ref_spec_7702.git_path REFERENCE_SPEC_VERSION = ref_spec_7702.version -pytestmark = [pytest.mark.valid_from("Prague"), pytest.mark.exception_test] +pytestmark = [ + pytest.mark.valid_from("Prague"), + pytest.mark.exception_test, + pytest.mark.inclusion_test, +] auth_account_start_balance = 0 diff --git a/tests/prague/eip7702_set_code_tx/test_set_code_txs.py b/tests/prague/eip7702_set_code_tx/test_set_code_txs.py index 36ae71616ad..1649b3ee1ba 100644 --- a/tests/prague/eip7702_set_code_tx/test_set_code_txs.py +++ b/tests/prague/eip7702_set_code_tx/test_set_code_txs.py @@ -3533,6 +3533,7 @@ def test_reset_code( ) +@pytest.mark.inclusion_test @pytest.mark.exception_test @pytest.mark.eels_base_coverage def test_contract_create( @@ -3561,6 +3562,7 @@ def test_contract_create( ) +@pytest.mark.inclusion_test @pytest.mark.exception_test @pytest.mark.eels_base_coverage def test_empty_authorization_list( @@ -4062,6 +4064,7 @@ def test_many_delegations( ) +@pytest.mark.inclusion_test @pytest.mark.exception_test def test_invalid_transaction_after_authorization( blockchain_test: BlockchainTestFiller, @@ -4180,6 +4183,7 @@ def test_authorization_reusing_nonce( ) +@pytest.mark.inclusion_test @pytest.mark.parametrize( "set_code_type", list(AddressType), @@ -4256,6 +4260,7 @@ def test_set_code_from_account_with_non_delegating_code( ) +@pytest.mark.inclusion_test @pytest.mark.parametrize( "max_fee_per_gas, max_priority_fee_per_gas, expected_error", [ diff --git a/tests/prague/eip7702_set_code_tx/test_set_code_txs_2.py b/tests/prague/eip7702_set_code_tx/test_set_code_txs_2.py index 75ebba85a78..11720ceed64 100644 --- a/tests/prague/eip7702_set_code_tx/test_set_code_txs_2.py +++ b/tests/prague/eip7702_set_code_tx/test_set_code_txs_2.py @@ -2010,6 +2010,7 @@ def test_pointer_resets_an_empty_code_account_with_storage( ) +@pytest.mark.inclusion_test @pytest.mark.parametrize( "tx_value", [0, 1], diff --git a/tests/shanghai/eip3860_initcode/test_initcode.py b/tests/shanghai/eip3860_initcode/test_initcode.py index 244cc802ed7..19c7068185f 100644 --- a/tests/shanghai/eip3860_initcode/test_initcode.py +++ b/tests/shanghai/eip3860_initcode/test_initcode.py @@ -111,6 +111,7 @@ def initcode(fork: Fork, initcode_name: str) -> Initcode: """Test cases using a contract creating transaction""" +@pytest.mark.inclusion_test @pytest.mark.bigmem @pytest.mark.xdist_group(name="bigmem") @pytest.mark.parametrize( @@ -354,6 +355,7 @@ def post( # `test_create_tx_intrinsic_gas_boundary` and # `test_max_initcode_size_gas_metering_via_create` in # `eip8037_state_creation_gas_cost_increase/test_state_gas_create.py`. + @pytest.mark.inclusion_test @pytest.mark.valid_before("EIP8037") @pytest.mark.slow() def test_gas_usage( diff --git a/tests/shanghai/eip4895_withdrawals/test_withdrawals.py b/tests/shanghai/eip4895_withdrawals/test_withdrawals.py index 9d9a3074888..76ecad0b672 100644 --- a/tests/shanghai/eip4895_withdrawals/test_withdrawals.py +++ b/tests/shanghai/eip4895_withdrawals/test_withdrawals.py @@ -35,6 +35,7 @@ ONE_GWEI = 10**9 +@pytest.mark.inclusion_test @pytest.mark.parametrize( "test_case", [ From 56e8617b619c0ab22284b140b49cc5501e5e6227 Mon Sep 17 00:00:00 2001 From: Guruprasad Kamath <48196632+gurukamath@users.noreply.github.com> Date: Thu, 13 Aug 2026 20:39:10 +0200 Subject: [PATCH 226/233] fix(spec-tools,testing): move evm_tools into the testing package (#3307) * feat(ci): smoke-test the built wheel in a clean environment * refactor(spec-tools,testing): move evm_tools into the testing package * docs(spec-tools,testing): document the new home of ethereum-spec-evm * post review updates * post review updates: round 2 - add `just build-wheels`/`test-packaging`; the packaging CI job now delegates to it and the import walk lives in `.github/scripts/` - add `just check-testing-imports`: grep `src/` for execution_testing references, catching the function-scoped imports that a module-level import walk cannot see - move the t8n_build fixtures into the testing package's tests/fixtures - rename `tests/evm_tools` to `tests/spec_tools` - repoint the stale import-cycle comment pointers in `t8n/cli.py` at the surviving note in `result.py` - drop the broken `whitelist` entry point; `just whitelist` now runs the module via `python -m` - extend `just deadcode` to the moved evm_tools code and refresh the vulture whitelist accordingly - SHA-pin the `ethereum-spec-evm` docs link; drop the archived-EEST reference from the testing README - exclude `packages/testing/build/` from mypy: local wheel builds leave a setuptools tree there that shadows `execution_testing` * fix(test-evm-tools): Fix test_count_opcodes.py --------- Co-authored-by: marioevz <marioevz@gmail.com> --- .github/scripts/import_check.py | 24 ++++++ .github/workflows/test.yaml | 10 +++ Justfile | 81 ++++++++++++++++++- README.md | 4 + docs/dev/deps_and_packaging.md | 4 +- docs/filling_tests/transition_tool_support.md | 2 +- docs/getting_started/repository_overview.md | 2 +- docs/library/execution_testing_evm_tools.md | 3 + docs/library/index.md | 1 + docs/navigation.md | 1 + packages/testing/README.md | 37 +++++++++ packages/testing/pyproject.toml | 2 + .../cli/pytest_commands/fill.py | 2 +- .../client_clis/clis/execution_specs.py | 14 ++-- .../execution_testing}/evm_tools/__init__.py | 2 +- .../execution_testing}/evm_tools/__main__.py | 0 .../evm_tools/b11r/__init__.py | 5 +- .../evm_tools/b11r/b11r_types.py | 8 +- .../execution_testing}/evm_tools/daemon.py | 0 .../evm_tools/statetest/__init__.py | 2 +- .../evm_tools/t8n/__init__.py | 18 +++-- .../evm_tools/t8n/block_environment.py | 7 +- .../execution_testing}/evm_tools/t8n/cli.py | 15 ++-- .../evm_tools/t8n/evm_trace/__init__.py | 5 ++ .../evm_tools/t8n/evm_trace/count.py | 0 .../evm_tools/t8n/evm_trace/eip3155.py | 0 .../evm_tools/t8n/evm_trace/group.py | 3 +- .../evm_tools/t8n/evm_trace/protocols.py | 11 ++- .../evm_tools/t8n/result.py | 9 +-- .../tests/fixtures/count_opcodes/alloc.json | 14 ++++ .../tests/fixtures/count_opcodes/env.json | 7 ++ .../tests/fixtures/count_opcodes/txs.json | 14 ++++ .../tests/fixtures/t8n_build/alloc.json | 1 + .../tests/fixtures/t8n_build/env.json | 7 ++ .../tests/fixtures/t8n_build/txs.json | 1 + .../evm_tools/tests}/test_count_opcodes.py | 26 +++--- .../evm_tools/tests}/test_daemon.py | 4 +- .../evm_tools/tests}/test_fork_cache.py | 8 +- pyproject.toml | 19 ++--- .../evm_tools/t8n/evm_trace/__init__.py | 5 -- .../{evm_tools => }/loaders/__init__.py | 0 .../{evm_tools => }/loaders/fixture_loader.py | 0 .../{evm_tools => }/loaders/fork_loader.py | 0 .../loaders/transaction_loader.py | 2 +- .../{evm_tools => }/utils.py | 0 tests/json_loader/conftest.py | 8 +- .../helpers/load_blockchain_tests.py | 2 +- tests/json_loader/helpers/load_state_tests.py | 8 +- tests/json_loader/helpers/select_tests.py | 10 ++- tests/json_loader/stash_keys.py | 3 +- .../test_docc_shards.py | 0 tests/{evm_tools => spec_tools}/test_lint.py | 0 .../test_new_fork.py | 0 vulture_whitelist.py | 70 +++++++--------- 54 files changed, 328 insertions(+), 153 deletions(-) create mode 100644 .github/scripts/import_check.py create mode 100644 docs/library/execution_testing_evm_tools.md create mode 100644 packages/testing/README.md rename {src/ethereum_spec_tools => packages/testing/src/execution_testing}/evm_tools/__init__.py (98%) rename {src/ethereum_spec_tools => packages/testing/src/execution_testing}/evm_tools/__main__.py (100%) rename {src/ethereum_spec_tools => packages/testing/src/execution_testing}/evm_tools/b11r/__init__.py (98%) rename {src/ethereum_spec_tools => packages/testing/src/execution_testing}/evm_tools/b11r/b11r_types.py (99%) rename {src/ethereum_spec_tools => packages/testing/src/execution_testing}/evm_tools/daemon.py (100%) rename {src/ethereum_spec_tools => packages/testing/src/execution_testing}/evm_tools/statetest/__init__.py (99%) rename {src/ethereum_spec_tools => packages/testing/src/execution_testing}/evm_tools/t8n/__init__.py (98%) rename {src/ethereum_spec_tools => packages/testing/src/execution_testing}/evm_tools/t8n/block_environment.py (99%) rename {src/ethereum_spec_tools => packages/testing/src/execution_testing}/evm_tools/t8n/cli.py (96%) create mode 100644 packages/testing/src/execution_testing/evm_tools/t8n/evm_trace/__init__.py rename {src/ethereum_spec_tools => packages/testing/src/execution_testing}/evm_tools/t8n/evm_trace/count.py (100%) rename {src/ethereum_spec_tools => packages/testing/src/execution_testing}/evm_tools/t8n/evm_trace/eip3155.py (100%) rename {src/ethereum_spec_tools => packages/testing/src/execution_testing}/evm_tools/t8n/evm_trace/group.py (99%) rename {src/ethereum_spec_tools => packages/testing/src/execution_testing}/evm_tools/t8n/evm_trace/protocols.py (88%) rename {src/ethereum_spec_tools => packages/testing/src/execution_testing}/evm_tools/t8n/result.py (96%) create mode 100644 packages/testing/src/execution_testing/evm_tools/tests/fixtures/count_opcodes/alloc.json create mode 100644 packages/testing/src/execution_testing/evm_tools/tests/fixtures/count_opcodes/env.json create mode 100644 packages/testing/src/execution_testing/evm_tools/tests/fixtures/count_opcodes/txs.json create mode 100644 packages/testing/src/execution_testing/evm_tools/tests/fixtures/t8n_build/alloc.json create mode 100644 packages/testing/src/execution_testing/evm_tools/tests/fixtures/t8n_build/env.json create mode 100644 packages/testing/src/execution_testing/evm_tools/tests/fixtures/t8n_build/txs.json rename {tests/evm_tools => packages/testing/src/execution_testing/evm_tools/tests}/test_count_opcodes.py (56%) rename {tests/evm_tools => packages/testing/src/execution_testing/evm_tools/tests}/test_daemon.py (81%) rename {tests/evm_tools => packages/testing/src/execution_testing/evm_tools/tests}/test_fork_cache.py (99%) delete mode 100644 src/ethereum_spec_tools/evm_tools/t8n/evm_trace/__init__.py rename src/ethereum_spec_tools/{evm_tools => }/loaders/__init__.py (100%) rename src/ethereum_spec_tools/{evm_tools => }/loaders/fixture_loader.py (100%) rename src/ethereum_spec_tools/{evm_tools => }/loaders/fork_loader.py (100%) rename src/ethereum_spec_tools/{evm_tools => }/loaders/transaction_loader.py (99%) rename src/ethereum_spec_tools/{evm_tools => }/utils.py (100%) rename tests/{evm_tools => spec_tools}/test_docc_shards.py (100%) rename tests/{evm_tools => spec_tools}/test_lint.py (100%) rename tests/{evm_tools => spec_tools}/test_new_fork.py (100%) diff --git a/.github/scripts/import_check.py b/.github/scripts/import_check.py new file mode 100644 index 00000000000..0fb5c3deda1 --- /dev/null +++ b/.github/scripts/import_check.py @@ -0,0 +1,24 @@ +""" +Import every module shipped in the spec wheel. + +Run with an interpreter that has only the `ethereum-execution` wheel +installed: importing every module catches undeclared dependencies and +modules missing from the packages list, which only fail outside the +uv workspace. +""" + +import importlib +import pkgutil + +import ethereum +import ethereum_spec_tools + +# `docc` plugins only load once the `doc` group installs docc, and +# importing a `__main__` would run it. +SKIP = {"ethereum_spec_tools.docc"} + +for pkg in (ethereum, ethereum_spec_tools): + for mod in pkgutil.walk_packages(pkg.__path__, f"{pkg.__name__}."): + if mod.name in SKIP or mod.name.endswith(".__main__"): + continue + importlib.import_module(mod.name) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index bb787cd5d53..05374695995 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -69,6 +69,16 @@ jobs: EOF uvx --from actionlint-py actionlint + packaging: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: ./.github/actions/setup-uv + with: + python-version: "3.14" + - name: Test packaging + run: just test-packaging + fill: name: fill (${{ matrix.label }}) runs-on: [self-hosted-ghr, size-xl-x64] diff --git a/Justfile b/Justfile index 45af2a182ae..cc915648d71 100644 --- a/Justfile +++ b/Justfile @@ -45,6 +45,20 @@ fix: [group('static analysis'), parallel] static: typecheck lint-spec spellcheck deadcode lint-actions lock-check format-check lint +# Ensure the spec package never imports the testing package +[group('static analysis')] +check-testing-imports: + #!/usr/bin/env bash + # A module-level import walk cannot catch function-scoped imports, + # so reject any reference, wherever it appears. `-I` skips binary + # files, such as stale bytecode caches. + if grep -rn -I --exclude-dir=__pycache__ "execution_testing" src/; then + echo "" + echo "src/ must not reference the execution_testing package." + echo "The spec wheel must install and run without it." + exit 1 + fi + # Check spelling [group('static analysis')] spellcheck: @@ -64,7 +78,7 @@ spellcheck: # Add a word to the spellcheck whitelist [group('static analysis')] whitelist *words: - uv run whitelist "$@" + uv run python -m ethereum_spec_tools.whitelist "$@" # Lint with ruff [group('static analysis')] @@ -74,7 +88,10 @@ lint *args: # Check for dead code with vulture [group('static analysis')] deadcode: - uv run vulture src/ vulture_whitelist.py + uv run vulture \ + src/ \ + packages/testing/src/execution_testing/evm_tools/ \ + vulture_whitelist.py # Check formatting with ruff [group('static analysis')] @@ -215,9 +232,8 @@ spec-tools *args: (_tmp "spec-tools") uv run pytest \ -n {{ xdist_workers }} \ --basetemp="{{ output_dir }}/spec-tools/tmp" \ - --ignore=tests/evm_tools/test_count_opcodes.py \ "$@" \ - tests/evm_tools + tests/spec_tools # --- Unit Tests --- @@ -245,6 +261,63 @@ test-tests-pypy *args: (_tmp "test-tests-pypy") test-ci-scripts *args: uv run pytest "$@" .github/scripts/tests/ +# --- Packaging --- + +# Build every workspace wheel into .just/dist +[group('packaging')] +build-wheels: + #!/usr/bin/env bash + set -euo pipefail + # Build every workspace member, so a dependency on a sibling + # package resolves against the wheel built here rather than + # against an index. Start from an empty directory: stale wheels + # from an earlier version would also match the install globs. + rm -rf "{{ output_dir }}/dist" + uv build --wheel --all-packages --out-dir "{{ output_dir }}/dist" + +# Smoke-test the built wheels: clean-venv install, real t8n run, spec-wheel-alone import check +[group('packaging')] +test-packaging: check-testing-imports build-wheels + #!/usr/bin/env bash + set -euo pipefail + dist="{{ output_dir }}/dist" + work="{{ output_dir }}/test-packaging" + fixtures="packages/testing/src/execution_testing/evm_tools/tests/fixtures/t8n_build" + rm -rf "$work" + + # Install into a bare venv, deliberately outside the uv workspace. + # Both wheels are passed by explicit path: resolving either + # through an index could silently substitute a published PyPI + # version for the branch's own build. + echo "--> Installing the wheels into a clean environment" + uv venv "$work/wheel-venv" + uv pip install --python "$work/wheel-venv/bin/python" \ + "$dist"/ethereum_execution_testing-*.whl \ + "$dist"/ethereum_execution-*.whl + + # Run a real transition rather than `--help`, which returns inside + # argparse without ever reaching the imports that t8n needs. The + # output basedir is emptied before the run, so keep it out of the + # source tree. + echo "--> Smoke-testing ethereum-spec-evm t8n" + mkdir -p "$work/t8n-out" + "$work/wheel-venv/bin/ethereum-spec-evm" t8n \ + --state.fork=Frontier \ + --input.alloc="$fixtures/alloc.json" \ + --input.env="$fixtures/env.json" \ + --input.txs="$fixtures/txs.json" \ + --output.basedir="$work/t8n-out" + test -s "$work/t8n-out/result.json" + + # Install the spec wheel on its own and import every shipped + # module: catches undeclared dependencies and modules missing + # from the packages list, which only fail outside the workspace. + echo "--> Import-checking the spec wheel alone" + uv venv "$work/spec-venv" + uv pip install --python "$work/spec-venv/bin/python" \ + "$dist"/ethereum_execution-*.whl + "$work/spec-venv/bin/python" .github/scripts/import_check.py + # --- Benchmarks --- # test_return_revert is excluded: its max-size INVALID-padded callees make diff --git a/README.md b/README.md index 06b61ff278f..f38aa34efb9 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,10 @@ just shell-completions Python 3.11–3.14 are supported; 3.12 tends to be the smoothest for local setup (pre-built wheels are available across the dependency set). For alternative `just` installation paths, macOS-specific installation notes, and troubleshooting, see [Installation](docs/getting_started/installation.md). +## Reference EVM CLI + +`ethereum-spec-evm` — a `t8n` transition tool, `b11r` block builder, and state-test runner that execute the spec directly — is provided by the `ethereum-execution-testing` workspace package rather than by `ethereum-execution`. Within a checkout it is available as `uv run ethereum-spec-evm`; for standalone installation (e.g. in client CI or fuzzing setups), see [packages/testing/README.md](packages/testing/README.md). + ## Documentation - **Repo documentation (default branch/fork)**: <https://steel.ethereum.foundation/docs/execution-specs/> diff --git a/docs/dev/deps_and_packaging.md b/docs/dev/deps_and_packaging.md index c04e4d01b4b..26bd435e9d5 100644 --- a/docs/dev/deps_and_packaging.md +++ b/docs/dev/deps_and_packaging.md @@ -8,8 +8,8 @@ The repo is a `uv` workspace with two members, each defined by its own `pyprojec | Package | `pyproject.toml` | Contents | | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | -| `ethereum-execution` | [`pyproject.toml`](https://github.com/ethereum/execution-specs/blob/a830dab6f130151ab9023a473b7543120aa21961/pyproject.toml) | The Python specs (`src/ethereum/`) and associated tools. | -| `ethereum-execution-testing` | [`packages/testing/pyproject.toml`](https://github.com/ethereum/execution-specs/blob/a830dab6f130151ab9023a473b7543120aa21961/packages/testing/pyproject.toml) | The EEST test framework under `packages/testing/`. | +| `ethereum-execution` | [`pyproject.toml`](https://github.com/ethereum/execution-specs/blob/a830dab6f130151ab9023a473b7543120aa21961/pyproject.toml) | The Python specs (`src/ethereum/`) and spec-maintenance tools (`src/ethereum_spec_tools/`). | +| `ethereum-execution-testing` | [`packages/testing/pyproject.toml`](https://github.com/ethereum/execution-specs/blob/a830dab6f130151ab9023a473b7543120aa21961/packages/testing/pyproject.toml) | The EEST test framework under `packages/testing/`, including the `ethereum-spec-evm` CLI (`t8n`, `b11r`, state-test runner). | A single [`uv.lock`](https://github.com/ethereum/execution-specs/blob/a830dab6f130151ab9023a473b7543120aa21961/uv.lock) at the repo root pins dependencies for both packages. diff --git a/docs/filling_tests/transition_tool_support.md b/docs/filling_tests/transition_tool_support.md index 5a404f5dd07..49acb07eb7f 100644 --- a/docs/filling_tests/transition_tool_support.md +++ b/docs/filling_tests/transition_tool_support.md @@ -5,7 +5,7 @@ The following transition tools are supported by the framework: | Client | `t8n` Tool | Tracing Support | | -------| ---------- | --------------- | | [ethereum/evmone](https://github.com/ethereum/evmone) | `evmone t8n` | Yes | -| [ethereum/execution-specs](https://github.com/ethereum/execution-specs) | [`ethereum-spec-evm t8n`](https://github.com/ethereum/execution-specs/tree/a48e0b381d5225a6c3de2d06cd9ee7ae0b6ca9bb/src/ethereum_spec_tools/evm_tools/t8n) | Yes | +| [ethereum/execution-specs](https://github.com/ethereum/execution-specs) | [`ethereum-spec-evm t8n`](https://github.com/ethereum/execution-specs/tree/e50432d044728f59a51ebf284f1fdf638b41aff4/packages/testing/src/execution_testing/evm_tools/t8n) | Yes | | [ethereumjs](https://github.com/ethereumjs/ethereumjs-monorepo) | [`ethereumjs-t8ntool.sh`](https://github.com/ethereumjs/ethereumjs-monorepo/tree/master/packages/vm/test/t8n) | No | | [ethereum/go-ethereum](https://github.com/ethereum/go-ethereum) | [`evm t8n`](https://github.com/ethereum/go-ethereum/tree/master/cmd/evm) | Yes | | [besu-eth/besu](https://github.com/besu-eth/besu/tree/main/ethereum/evmtool) | [`evmtool t8n-server`](https://github.com/besu-eth/besu/tree/main/ethereum/evmtool) | Yes | diff --git a/docs/getting_started/repository_overview.md b/docs/getting_started/repository_overview.md index 260052099a2..4a6c01c79ea 100644 --- a/docs/getting_started/repository_overview.md +++ b/docs/getting_started/repository_overview.md @@ -39,7 +39,7 @@ Contains the implementation of the Ethereum consensus tests available in this re #### `packages/execution_testing/` -Contains the `execution_testing` package which provides tools to define test cases and to interface with `t8n` command interfaces that are required to generate tests. Additionally, it contains packages that enable test case execution by customizing pytest which acts as the test framework. +Contains the `execution_testing` package which provides tools to define test cases and to interface with `t8n` command interfaces that are required to generate tests. Additionally, it contains packages that enable test case execution by customizing pytest which acts as the test framework. It also ships the reference EVM `t8n` implementation, which `fill` runs in-process to generate the fixtures in this repository, and which external consumers can drive through the `ethereum-spec-evm` CLI. #### `docs/` diff --git a/docs/library/execution_testing_evm_tools.md b/docs/library/execution_testing_evm_tools.md new file mode 100644 index 00000000000..c9d6d09bcb9 --- /dev/null +++ b/docs/library/execution_testing_evm_tools.md @@ -0,0 +1,3 @@ +# EVM Tools Package + +::: execution_testing.evm_tools diff --git a/docs/library/index.md b/docs/library/index.md index ebb258b04e9..34d7bffddff 100644 --- a/docs/library/index.md +++ b/docs/library/index.md @@ -11,4 +11,5 @@ Execution spec tests consists of several packages that implement helper classes - [`execution_testing.test_types`](./execution_testing_test_types.md) - provides Ethereum types built on top of the base types which are used to define test cases and interact with other libraries. - [`execution_testing.vm`](./execution_testing_vm.md) - provides definitions for the Ethereum Virtual Machine (EVM) as used to define bytecode in test cases. - [`execution_testing.client_clis`](./execution_testing_client_clis.md) - a wrapper for the transition (`t8n`) tool. +- [`execution_testing.evm_tools`](./execution_testing_evm_tools.md) - the `ethereum-spec-evm` CLI: `t8n`, `b11r`, and state-test tools that run the execution specs directly. - [`pytest_plugins`](./pytest_plugins/index.md) - contains pytest customizations that provide additional functionality for generating test fixtures. diff --git a/docs/navigation.md b/docs/navigation.md index db82611d077..aa61e2bd46e 100644 --- a/docs/navigation.md +++ b/docs/navigation.md @@ -98,6 +98,7 @@ * [Execution Testing Test Types Package](library/execution_testing_test_types.md) * [Execution Testing VM Package](library/execution_testing_vm.md) * [Execution Testing Client CLIs Package](library/execution_testing_client_clis.md) + * [Execution Testing EVM Tools Package](library/execution_testing_evm_tools.md) * [Pytest Plugins](library/pytest_plugins/index.md) * [Filler](library/pytest_plugins/filler.md) * [Forks](library/pytest_plugins/forks.md) diff --git a/packages/testing/README.md b/packages/testing/README.md new file mode 100644 index 00000000000..c8e90c5484d --- /dev/null +++ b/packages/testing/README.md @@ -0,0 +1,37 @@ +# The `ethereum-execution-testing` Package + +Test generation and execution framework for the [Ethereum Execution Layer Specifications (EELS)](https://github.com/ethereum/execution-specs). + +The package provides: + +- The `execution_testing` library: base types, fork definitions, and test-spec primitives used to write consensus test cases. +- The pytest-based commands that generate and run test fixtures against execution clients: `fill`, `execute`, `consume`, and friends. +- `ethereum-spec-evm` — the reference EVM CLI that executes the spec directly: a `t8n` transition tool (also available as a daemon), a `b11r` block builder, and a state-test runner. + +## Installing `ethereum-spec-evm` standalone + +This package depends on `ethereum-execution` (the spec itself), and the two are developed in lockstep: the spec releases published on PyPI only carry forks that are live on mainnet and generally cannot satisfy this package's dependency pins. Install both packages from the same clone. + +With `uv` (resolves the sibling spec package from the checkout automatically): + +```console +git clone https://github.com/ethereum/execution-specs +uv tool install ./execution-specs/packages/testing +``` + +With `pip`, in a virtual environment: + +```console +pip install ./execution-specs ./execution-specs/packages/testing +``` + +With `pipx`: + +```console +pipx install ./execution-specs +pipx inject --include-apps ethereum-execution ./execution-specs/packages/testing +``` + +## Documentation + +Repository documentation, including this framework's reference documentation: <https://steel.ethereum.foundation/docs/execution-specs/> diff --git a/packages/testing/pyproject.toml b/packages/testing/pyproject.toml index f8d980872ad..5ad9ec0491d 100644 --- a/packages/testing/pyproject.toml +++ b/packages/testing/pyproject.toml @@ -78,6 +78,7 @@ dev = [ ] [project.scripts] +ethereum-spec-evm = "execution_testing.evm_tools:main" fill = "execution_testing.cli.pytest_commands.fill:fill" phil = "execution_testing.cli.pytest_commands.fill:phil" execute = "execution_testing.cli.pytest_commands.execute:execute" @@ -141,6 +142,7 @@ markers = [ "some_mark: Test marker for parametrizer tests", "eip_checklist: Custom marker for EIP checklist tests", "slow: Marks tests as slow running", + "evm_tools: marks tests as evm_tools (deselect with '-m \"not evm_tools\"')", ] [tool.uv] diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/fill.py b/packages/testing/src/execution_testing/cli/pytest_commands/fill.py index fbd513b823d..285b4efb619 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/fill.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/fill.py @@ -140,7 +140,7 @@ def _add_default_ignores(self, args: List[str]) -> List[str]: """Add default ignore paths for directories not used by fill.""" # Directories to ignore by default default_ignores = [ - "tests/evm_tools", + "tests/spec_tools", "tests/json_loader", "tests/fixtures", ] diff --git a/packages/testing/src/execution_testing/client_clis/clis/execution_specs.py b/packages/testing/src/execution_testing/client_clis/clis/execution_specs.py index 4d5d7d81865..7d007cca02a 100644 --- a/packages/testing/src/execution_testing/client_clis/clis/execution_specs.py +++ b/packages/testing/src/execution_testing/client_clis/clis/execution_specs.py @@ -29,7 +29,7 @@ from execution_testing.forks import Fork if TYPE_CHECKING: - from ethereum_spec_tools.evm_tools.t8n import ForkCache + from execution_testing.evm_tools.t8n import ForkCache class ExecutionSpecsTransitionTool(TransitionTool): @@ -60,7 +60,7 @@ def __init__( def fork_cache(self) -> "ForkCache": """Lazily import and instantiate the EELS fork cache on first use.""" if self._fork_cache is None: - from ethereum_spec_tools.evm_tools.t8n import ForkCache + from execution_testing.evm_tools.t8n import ForkCache self._fork_cache = ForkCache() return self._fork_cache @@ -80,7 +80,7 @@ def version(self) -> str: def is_fork_supported(self, fork: Fork) -> bool: """Return True if the fork is supported by the tool.""" - from ethereum_spec_tools.evm_tools.utils import get_supported_forks + from ethereum_spec_tools.utils import get_supported_forks return fork.transition_tool_name() in get_supported_forks() @@ -100,14 +100,14 @@ def _evaluate( — and ``T8N.run()`` returns the ``TransitionToolOutput`` directly. """ - from ethereum_spec_tools.evm_tools.t8n import T8N - from ethereum_spec_tools.evm_tools.t8n.evm_trace.count import ( + from execution_testing.evm_tools.t8n import T8N + from execution_testing.evm_tools.t8n.evm_trace.count import ( CountTracer, ) - from ethereum_spec_tools.evm_tools.t8n.evm_trace.eip3155 import ( + from execution_testing.evm_tools.t8n.evm_trace.eip3155 import ( Eip3155Tracer, ) - from ethereum_spec_tools.evm_tools.t8n.evm_trace.group import ( + from execution_testing.evm_tools.t8n.evm_trace.group import ( GroupTracer, ) diff --git a/src/ethereum_spec_tools/evm_tools/__init__.py b/packages/testing/src/execution_testing/evm_tools/__init__.py similarity index 98% rename from src/ethereum_spec_tools/evm_tools/__init__.py rename to packages/testing/src/execution_testing/evm_tools/__init__.py index bf854c088cd..14165c170de 100644 --- a/src/ethereum_spec_tools/evm_tools/__init__.py +++ b/packages/testing/src/execution_testing/evm_tools/__init__.py @@ -10,13 +10,13 @@ from typing import Optional, Sequence, Text, TextIO from ethereum import __version__ +from ethereum_spec_tools.utils import get_supported_forks from .b11r import B11R, b11r_arguments from .daemon import Daemon, daemon_arguments from .statetest import StateTest, state_test_arguments from .t8n import ForkCache from .t8n.cli import run_t8n_cli, t8n_arguments -from .utils import get_supported_forks DESCRIPTION = """ This is the EVM tool for execution specs. The EVM tool diff --git a/src/ethereum_spec_tools/evm_tools/__main__.py b/packages/testing/src/execution_testing/evm_tools/__main__.py similarity index 100% rename from src/ethereum_spec_tools/evm_tools/__main__.py rename to packages/testing/src/execution_testing/evm_tools/__main__.py diff --git a/src/ethereum_spec_tools/evm_tools/b11r/__init__.py b/packages/testing/src/execution_testing/evm_tools/b11r/__init__.py similarity index 98% rename from src/ethereum_spec_tools/evm_tools/b11r/__init__.py rename to packages/testing/src/execution_testing/evm_tools/b11r/__init__.py index e106af5cd76..96af1938bf4 100644 --- a/src/ethereum_spec_tools/evm_tools/b11r/__init__.py +++ b/packages/testing/src/execution_testing/evm_tools/b11r/__init__.py @@ -6,12 +6,11 @@ import json from typing import Optional, TextIO +from ethereum.crypto.hash import keccak256 from ethereum_rlp import rlp +from ethereum_spec_tools.utils import get_stream_logger from ethereum_types.bytes import Bytes32 -from ethereum.crypto.hash import keccak256 - -from ..utils import get_stream_logger from .b11r_types import Body, Header diff --git a/src/ethereum_spec_tools/evm_tools/b11r/b11r_types.py b/packages/testing/src/execution_testing/evm_tools/b11r/b11r_types.py similarity index 99% rename from src/ethereum_spec_tools/evm_tools/b11r/b11r_types.py rename to packages/testing/src/execution_testing/evm_tools/b11r/b11r_types.py index 4cc7be9bdc4..f7a194ffebd 100644 --- a/src/ethereum_spec_tools/evm_tools/b11r/b11r_types.py +++ b/packages/testing/src/execution_testing/evm_tools/b11r/b11r_types.py @@ -5,15 +5,13 @@ import json from typing import Any, List, Optional, Tuple +from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.utils.hexadecimal import hex_to_bytes, hex_to_bytes8 from ethereum_rlp import rlp +from ethereum_spec_tools.utils import parse_hex_or_int from ethereum_types.bytes import Bytes, Bytes8, Bytes20, Bytes32, Bytes256 from ethereum_types.numeric import U64, U256, Uint -from ethereum.crypto.hash import Hash32, keccak256 -from ethereum.utils.hexadecimal import hex_to_bytes, hex_to_bytes8 - -from ..utils import parse_hex_or_int - DEFAULT_TRIE_ROOT = ( "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" ) diff --git a/src/ethereum_spec_tools/evm_tools/daemon.py b/packages/testing/src/execution_testing/evm_tools/daemon.py similarity index 100% rename from src/ethereum_spec_tools/evm_tools/daemon.py rename to packages/testing/src/execution_testing/evm_tools/daemon.py diff --git a/src/ethereum_spec_tools/evm_tools/statetest/__init__.py b/packages/testing/src/execution_testing/evm_tools/statetest/__init__.py similarity index 99% rename from src/ethereum_spec_tools/evm_tools/statetest/__init__.py rename to packages/testing/src/execution_testing/evm_tools/statetest/__init__.py index e9b74e62e65..51c6192a90e 100644 --- a/src/ethereum_spec_tools/evm_tools/statetest/__init__.py +++ b/packages/testing/src/execution_testing/evm_tools/statetest/__init__.py @@ -21,10 +21,10 @@ ) from ethereum.utils.hexadecimal import hex_to_bytes +from ethereum_spec_tools.utils import get_supported_forks from ..t8n import ForkCache from ..t8n.cli import build_t8n_from_cli_options -from ..utils import get_supported_forks if TYPE_CHECKING: from execution_testing.client_clis.cli_types import ( diff --git a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py b/packages/testing/src/execution_testing/evm_tools/t8n/__init__.py similarity index 98% rename from src/ethereum_spec_tools/evm_tools/t8n/__init__.py rename to packages/testing/src/execution_testing/evm_tools/t8n/__init__.py index bbf59167d72..850fed9d05b 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/__init__.py +++ b/packages/testing/src/execution_testing/evm_tools/t8n/__init__.py @@ -18,23 +18,25 @@ TypeVar, ) -from ethereum_rlp import rlp -from ethereum_types.bytes import Bytes -from ethereum_types.numeric import U64, U256, Uint -from typing_extensions import override - from ethereum import trace from ethereum.exceptions import EthereumException, InvalidBlock from ethereum.fork_criteria import ByBlockNumber, ByTimestamp, Unscheduled +from ethereum_rlp import rlp from ethereum_spec_tools.forks import ( ForkOverrides, Hardfork, TemporaryHardfork, ) +from ethereum_spec_tools.loaders.fixture_loader import Load +from ethereum_spec_tools.loaders.transaction_loader import ( + TransactionLoad, + UnsupportedTxError, +) +from ethereum_spec_tools.utils import get_stream_logger, resolve_fork +from ethereum_types.bytes import Bytes +from ethereum_types.numeric import U64, U256, Uint +from typing_extensions import override -from ..loaders.fixture_loader import Load -from ..loaders.transaction_loader import TransactionLoad, UnsupportedTxError -from ..utils import get_stream_logger, resolve_fork from .block_environment import Ommer, build_block_environment from .evm_trace.group import GroupTracer from .result import build_result, record_rejected_tx diff --git a/src/ethereum_spec_tools/evm_tools/t8n/block_environment.py b/packages/testing/src/execution_testing/evm_tools/t8n/block_environment.py similarity index 99% rename from src/ethereum_spec_tools/evm_tools/t8n/block_environment.py rename to packages/testing/src/execution_testing/evm_tools/t8n/block_environment.py index aab52779c9d..3a602b9c6c4 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/block_environment.py +++ b/packages/testing/src/execution_testing/evm_tools/t8n/block_environment.py @@ -6,16 +6,15 @@ from dataclasses import dataclass from typing import TYPE_CHECKING, Any, List, Optional +from ethereum.crypto.hash import Hash32, keccak256 from ethereum_rlp import rlp from ethereum_types.bytes import Bytes8, Bytes20, Bytes32, Bytes256 from ethereum_types.numeric import U64, U256, Uint -from ethereum.crypto.hash import Hash32, keccak256 - if TYPE_CHECKING: - from execution_testing.test_types import Environment as TestingEnvironment + from ethereum_spec_tools.loaders.fork_loader import ForkLoad - from ..loaders.fork_loader import ForkLoad + from execution_testing.test_types import Environment as TestingEnvironment @dataclass diff --git a/src/ethereum_spec_tools/evm_tools/t8n/cli.py b/packages/testing/src/execution_testing/evm_tools/t8n/cli.py similarity index 96% rename from src/ethereum_spec_tools/evm_tools/t8n/cli.py rename to packages/testing/src/execution_testing/evm_tools/t8n/cli.py index 14fb0e737c3..7b438d83f65 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/cli.py +++ b/packages/testing/src/execution_testing/evm_tools/t8n/cli.py @@ -22,13 +22,12 @@ from typing import Any, Dict, List, Optional, TextIO, Tuple from ethereum_rlp import rlp +from ethereum_spec_tools.forks import Hardfork +from ethereum_spec_tools.loaders.fork_loader import ForkLoad +from ethereum_spec_tools.utils import FatalError, find_fork, parse_hex_or_int from ethereum_types.bytes import Bytes from ethereum_types.numeric import U64 -from ethereum_spec_tools.forks import Hardfork - -from ..loaders.fork_loader import ForkLoad -from ..utils import FatalError, find_fork, parse_hex_or_int from . import T8N, ForkCache from .block_environment import Ommer from .evm_trace.count import CountTracer @@ -210,7 +209,7 @@ def _parse_blob_params_from_options( Returns ``None`` when the flag is unset. Reads from ``stdin`` (``"blobParams"`` key) or a file path depending on the flag value. """ - # Function-scoped: see import-cycle note in ``build_t8n_from_cli_options``. + # Function-scoped: see import-cycle note in ``result.py``. from execution_testing.base_types.composite_types import ( ForkBlobSchedule, ) @@ -262,7 +261,7 @@ def _build_tracers_from_options( def _testing_fork_from_spec_hardfork(hardfork: Hardfork) -> Any: """Map a spec ``Hardfork`` to the matching testing ``Fork`` class.""" - # Function-scoped: see import-cycle note in ``build_t8n_from_cli_options``. + # Function-scoped: see import-cycle note in ``result.py``. from execution_testing.forks import get_fork_by_name name = hardfork.title_case_name.replace(" ", "") @@ -307,10 +306,6 @@ def build_t8n_from_cli_options( testing pydantic types, bundles them into a ``TransitionToolData``, builds the tracer group, and hands them to ``T8N``. """ - # Function-scoped imports: ``execution_testing/__init__`` eagerly - # imports ``.specs`` which transitively imports ``client_clis``, - # which imports ``ExecutionSpecsTransitionTool`` — top-level imports - # from ``execution_testing`` would cycle back into spec-tools. from execution_testing.base_types.composite_types import BlobSchedule from execution_testing.client_clis.transition_tool import TransitionTool from execution_testing.test_types import ( diff --git a/packages/testing/src/execution_testing/evm_tools/t8n/evm_trace/__init__.py b/packages/testing/src/execution_testing/evm_tools/t8n/evm_trace/__init__.py new file mode 100644 index 00000000000..62df52b8dc4 --- /dev/null +++ b/packages/testing/src/execution_testing/evm_tools/t8n/evm_trace/__init__.py @@ -0,0 +1,5 @@ +""" +EVM Trace Implementations. + +See the spec's `ethereum.trace` module for the trace event definitions. +""" diff --git a/src/ethereum_spec_tools/evm_tools/t8n/evm_trace/count.py b/packages/testing/src/execution_testing/evm_tools/t8n/evm_trace/count.py similarity index 100% rename from src/ethereum_spec_tools/evm_tools/t8n/evm_trace/count.py rename to packages/testing/src/execution_testing/evm_tools/t8n/evm_trace/count.py diff --git a/src/ethereum_spec_tools/evm_tools/t8n/evm_trace/eip3155.py b/packages/testing/src/execution_testing/evm_tools/t8n/evm_trace/eip3155.py similarity index 100% rename from src/ethereum_spec_tools/evm_tools/t8n/evm_trace/eip3155.py rename to packages/testing/src/execution_testing/evm_tools/t8n/evm_trace/eip3155.py diff --git a/src/ethereum_spec_tools/evm_tools/t8n/evm_trace/group.py b/packages/testing/src/execution_testing/evm_tools/t8n/evm_trace/group.py similarity index 99% rename from src/ethereum_spec_tools/evm_tools/t8n/evm_trace/group.py rename to packages/testing/src/execution_testing/evm_tools/t8n/evm_trace/group.py index 72e9858d83c..7a7d378b245 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/evm_trace/group.py +++ b/packages/testing/src/execution_testing/evm_tools/t8n/evm_trace/group.py @@ -4,9 +4,8 @@ from typing import Final -from typing_extensions import override - from ethereum.trace import EvmTracer, TraceEvent +from typing_extensions import override class GroupTracer(EvmTracer): diff --git a/src/ethereum_spec_tools/evm_tools/t8n/evm_trace/protocols.py b/packages/testing/src/execution_testing/evm_tools/t8n/evm_trace/protocols.py similarity index 88% rename from src/ethereum_spec_tools/evm_tools/t8n/evm_trace/protocols.py rename to packages/testing/src/execution_testing/evm_tools/t8n/evm_trace/protocols.py index 94a8a479dc4..3dc31758fd5 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/evm_trace/protocols.py +++ b/packages/testing/src/execution_testing/evm_tools/t8n/evm_trace/protocols.py @@ -35,12 +35,11 @@ class Evm(Protocol): The class describes the EVM interface common to every fork's trace. The message-scoped fields (`depth`, `tx_env`, `parent_evm`) are - described by [`Message`][msg]. Older forks carry them on - `evm.message`; forks that merge the message into the frame expose - them on `evm` itself, so `evm` satisfies both protocols. Tracers - resolve the carrier with `getattr(evm, "message", evm)`. - - [msg]: ref:ethereum_spec_tools.evm_tools.t8n.evm_trace.protocols.Message + described by the `Message` protocol in this module. Older forks + carry them on `evm.message`; forks that merge the message into the + frame expose them on `evm` itself, so `evm` satisfies both + protocols. Tracers resolve the carrier with + `getattr(evm, "message", evm)`. """ # TODO: Rethink the tracer interface so it does not probe diff --git a/src/ethereum_spec_tools/evm_tools/t8n/result.py b/packages/testing/src/execution_testing/evm_tools/t8n/result.py similarity index 96% rename from src/ethereum_spec_tools/evm_tools/t8n/result.py rename to packages/testing/src/execution_testing/evm_tools/t8n/result.py index 5bab2af3b75..0b07a60a20e 100644 --- a/src/ethereum_spec_tools/evm_tools/t8n/result.py +++ b/packages/testing/src/execution_testing/evm_tools/t8n/result.py @@ -8,10 +8,9 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional -from ethereum_rlp import rlp - from ethereum.crypto.hash import keccak256 from ethereum.merkle_patricia_trie import root, trie_get +from ethereum_rlp import rlp if TYPE_CHECKING: from execution_testing.client_clis.cli_types import ( @@ -24,9 +23,9 @@ def get_receipts_from_output(t8n: "T8N", block_output: Any) -> List[Any]: """Build testing-side `TransactionReceipt`s from the block output tries.""" # Function-scoped: ``execution_testing/__init__`` eagerly imports - # ``.specs`` which transitively imports ``client_clis``, which - # imports ``ExecutionSpecsTransitionTool`` — top-level import would - # cycle back into ``t8n``. + # ``.specs`` -> ``client_clis`` -> ``ExecutionSpecsTransitionTool``, + # which imports ``t8n`` to run it in-process. A top-level import here + # would run while ``client_clis`` is still mid-initialization. from execution_testing.test_types.receipt_types import ( TransactionLog, TransactionReceipt, diff --git a/packages/testing/src/execution_testing/evm_tools/tests/fixtures/count_opcodes/alloc.json b/packages/testing/src/execution_testing/evm_tools/tests/fixtures/count_opcodes/alloc.json new file mode 100644 index 00000000000..771c5ec7515 --- /dev/null +++ b/packages/testing/src/execution_testing/evm_tools/tests/fixtures/count_opcodes/alloc.json @@ -0,0 +1,14 @@ +{ + "0x095e7baea6a6c7c4c2dfeb977efac326af552d87": { + "balance": "0x0de0b6b3a7640000", + "code": "0x6001600053600160006001f0ff00", + "nonce": "0x00", + "storage": {} + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x0de0b6b3a7640000", + "code": "0x", + "nonce": "0x00", + "storage": {} + } +} diff --git a/packages/testing/src/execution_testing/evm_tools/tests/fixtures/count_opcodes/env.json b/packages/testing/src/execution_testing/evm_tools/tests/fixtures/count_opcodes/env.json new file mode 100644 index 00000000000..bd6e798e448 --- /dev/null +++ b/packages/testing/src/execution_testing/evm_tools/tests/fixtures/count_opcodes/env.json @@ -0,0 +1,7 @@ +{ + "currentCoinbase": "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "currentDifficulty": "0x020000", + "currentGasLimit": "0x3b9aca00", + "currentNumber": "0x01", + "currentTimestamp": "0x03e8" +} diff --git a/packages/testing/src/execution_testing/evm_tools/tests/fixtures/count_opcodes/txs.json b/packages/testing/src/execution_testing/evm_tools/tests/fixtures/count_opcodes/txs.json new file mode 100644 index 00000000000..aa11473474e --- /dev/null +++ b/packages/testing/src/execution_testing/evm_tools/tests/fixtures/count_opcodes/txs.json @@ -0,0 +1,14 @@ +[ + { + "input": "0x", + "gas": "0x5f5e100", + "gasPrice": "0x1", + "nonce": "0x0", + "to": "0x095e7baea6a6c7c4c2dfeb977efac326af552d87", + "value": "0x186a0", + "v": "0x1b", + "r": "0x88544c93a564b4c28d2ffac2074a0c55fdd4658fe0d215596ed2e32e3ef7f56b", + "s": "0x7fb4075d54190f825d7c47bb820284757b34fd6293904a93cddb1d3aa961ac28", + "hash": "0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81" + } +] diff --git a/packages/testing/src/execution_testing/evm_tools/tests/fixtures/t8n_build/alloc.json b/packages/testing/src/execution_testing/evm_tools/tests/fixtures/t8n_build/alloc.json new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/packages/testing/src/execution_testing/evm_tools/tests/fixtures/t8n_build/alloc.json @@ -0,0 +1 @@ +{} diff --git a/packages/testing/src/execution_testing/evm_tools/tests/fixtures/t8n_build/env.json b/packages/testing/src/execution_testing/evm_tools/tests/fixtures/t8n_build/env.json new file mode 100644 index 00000000000..468fd210bf1 --- /dev/null +++ b/packages/testing/src/execution_testing/evm_tools/tests/fixtures/t8n_build/env.json @@ -0,0 +1,7 @@ +{ + "currentCoinbase": "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "currentGasLimit": "0x016345785d8a0000", + "currentNumber": "0x01", + "currentTimestamp": "0x03e8", + "currentDifficulty": "0x020000" +} diff --git a/packages/testing/src/execution_testing/evm_tools/tests/fixtures/t8n_build/txs.json b/packages/testing/src/execution_testing/evm_tools/tests/fixtures/t8n_build/txs.json new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/packages/testing/src/execution_testing/evm_tools/tests/fixtures/t8n_build/txs.json @@ -0,0 +1 @@ +[] diff --git a/tests/evm_tools/test_count_opcodes.py b/packages/testing/src/execution_testing/evm_tools/tests/test_count_opcodes.py similarity index 56% rename from tests/evm_tools/test_count_opcodes.py rename to packages/testing/src/execution_testing/evm_tools/tests/test_count_opcodes.py index 0ced37e51f5..e4d83991f0d 100644 --- a/tests/evm_tools/test_count_opcodes.py +++ b/packages/testing/src/execution_testing/evm_tools/tests/test_count_opcodes.py @@ -6,34 +6,36 @@ import json from io import StringIO from pathlib import Path -from typing import Callable import pytest -from ethereum_spec_tools.evm_tools import create_parser -from ethereum_spec_tools.evm_tools.t8n import ForkCache -from ethereum_spec_tools.evm_tools.t8n.cli import run_t8n_cli +from execution_testing.evm_tools import create_parser +from execution_testing.evm_tools.t8n import ForkCache +from execution_testing.evm_tools.t8n.cli import run_t8n_cli parser = create_parser() +# Vendored from https://github.com/gurukamath/evm-tools-testdata at +# commit 792422d, `t8n/fixtures/testdata/2`. The retired +# `evm_tools_testdata` download step used to supply these inputs. +FIXTURE_DIR = Path(__file__).parent / "fixtures" / "count_opcodes" + @pytest.mark.evm_tools -def test_count_opcodes(root_relative: Callable[[str | Path], Path]) -> None: +def test_count_opcodes(tmp_path: Path) -> None: """Test counting opcodes in a transaction execution using the T8N tool.""" - base_path = root_relative( - "fixtures/evm_tools_testdata/t8n/fixtures/testdata/2" - ) - options = parser.parse_args( [ "t8n", - f"--input.env={base_path / 'env.json'}", - f"--input.alloc={base_path / 'alloc.json'}", - f"--input.txs={base_path / 'txs.json'}", + f"--input.env={FIXTURE_DIR / 'env.json'}", + f"--input.alloc={FIXTURE_DIR / 'alloc.json'}", + f"--input.txs={FIXTURE_DIR / 'txs.json'}", + f"--output.basedir={tmp_path}", "--output.result=stdout", "--output.body=stdout", "--output.alloc=stdout", "--opcode.count=stdout", + "--state.fork=Frontier", "--state-test", ] ) diff --git a/tests/evm_tools/test_daemon.py b/packages/testing/src/execution_testing/evm_tools/tests/test_daemon.py similarity index 81% rename from tests/evm_tools/test_daemon.py rename to packages/testing/src/execution_testing/evm_tools/tests/test_daemon.py index 6d4d08b74c3..caa6f2ec976 100644 --- a/tests/evm_tools/test_daemon.py +++ b/packages/testing/src/execution_testing/evm_tools/tests/test_daemon.py @@ -4,8 +4,8 @@ import pytest -from ethereum_spec_tools.evm_tools import daemon -from ethereum_spec_tools.evm_tools.daemon import Daemon +from execution_testing.evm_tools import daemon +from execution_testing.evm_tools.daemon import Daemon def test_daemon_run_rejects_windows( diff --git a/tests/evm_tools/test_fork_cache.py b/packages/testing/src/execution_testing/evm_tools/tests/test_fork_cache.py similarity index 99% rename from tests/evm_tools/test_fork_cache.py rename to packages/testing/src/execution_testing/evm_tools/tests/test_fork_cache.py index 516943aac5c..618de35f702 100644 --- a/tests/evm_tools/test_fork_cache.py +++ b/packages/testing/src/execution_testing/evm_tools/tests/test_fork_cache.py @@ -4,16 +4,16 @@ from typing import Any import pytest -from ethereum_types.numeric import U64, Uint -from typing_extensions import assert_never - from ethereum.fork_criteria import ( ByBlockNumber, ByTimestamp, Unscheduled, ) -from ethereum_spec_tools.evm_tools.t8n import ForkCache from ethereum_spec_tools.forks import ForkOverrides, Hardfork +from ethereum_types.numeric import U64, Uint +from typing_extensions import assert_never + +from execution_testing.evm_tools.t8n import ForkCache pytestmark = pytest.mark.evm_tools diff --git a/pyproject.toml b/pyproject.toml index 23a1eff6527..89dd04d9d85 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,12 +37,7 @@ dependencies = [ [tool.setuptools] packages = [ "ethereum_spec_tools", - "ethereum_spec_tools.evm_tools", - "ethereum_spec_tools.evm_tools.t8n", - "ethereum_spec_tools.evm_tools.t8n.evm_trace", - "ethereum_spec_tools.evm_tools.b11r", - "ethereum_spec_tools.evm_tools.statetest", - "ethereum_spec_tools.evm_tools.loaders", + "ethereum_spec_tools.loaders", "ethereum_spec_tools.lint", "ethereum_spec_tools.lint.lints", "ethereum_spec_tools.new_fork", @@ -272,8 +267,6 @@ ethereum-spec-lint = "ethereum_spec_tools.lint:main" ethereum-spec-sync = "ethereum_spec_tools.sync:main" ethereum-spec-new-fork = "ethereum_spec_tools.new_fork.cli:main" ethereum-spec-patch = "ethereum_spec_tools.patch_tool:main" -ethereum-spec-evm = "ethereum_spec_tools.evm_tools:main" -whitelist = "ethereum_spec_tools.whitelist:main" [project.entry-points."docc.plugins"] "ethereum_spec_tools.docc.listing" = "ethereum_spec_tools.docc:EthereumListingDiscover" @@ -293,7 +286,6 @@ whitelist = "ethereum_spec_tools.whitelist:main" markers = [ "slow: marks tests as slow (deselect with '-m \"not slow\"')", "bigmem: marks tests as big memory (deselect with '-m \"not bigmem\"')", - "evm_tools: marks tests as evm_tools (deselect with '-m \"not evm_tools\"')", "json_blockchain_tests: marks tests as json_blockchain_tests (deselect with '-m \"not json_blockchain_tests\"')", "json_state_tests: marks tests as json_state_tests (deselect with '-m \"not json_state_tests\"')", "vm_test: marks tests as vm_test (deselect with '-m \"not vm_test\"')", @@ -424,7 +416,7 @@ ignore = [ ] [tool.ruff.lint.per-file-ignores] -"src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py" = [ +"src/ethereum_spec_tools/loaders/fork_loader.py" = [ "N802" # Property names do not need to be lowercase ] "src/ethereum_spec_tools/lint/*" = [ @@ -434,13 +426,11 @@ ignore = [ "N806", # Special crypto code absolved of variable naming reqs "N802" # Special crypto code absolved of function naming reqs ] -"src/ethereum_spec_tools/evm_tools/t8n/evm_trace/eip3155.py" = [ - "N815" # The traces must use camel case in JSON property names -] -"src/ethereum_spec_tools/evm_tools/t8n/evm_trace.py" = [ +"packages/testing/src/execution_testing/evm_tools/t8n/evm_trace/eip3155.py" = [ "N815" # The traces must use camel case in JSON property names ] "tests/*" = ["ARG001"] +"packages/testing/src/execution_testing/evm_tools/tests/*" = ["ARG001"] "vulture_whitelist.py" = [ "B018", # Useless expression (intentional for Vulture whitelisting) "E402", # Module-level imports throughout file (needed for whitelisting) @@ -533,6 +523,7 @@ exclude = [ "^logs/", "^site/", "^tests/json_loader/fixtures/", + "^packages/testing/build/", ] plugins = ["pydantic.mypy"] diff --git a/src/ethereum_spec_tools/evm_tools/t8n/evm_trace/__init__.py b/src/ethereum_spec_tools/evm_tools/t8n/evm_trace/__init__.py deleted file mode 100644 index 4975e5529a3..00000000000 --- a/src/ethereum_spec_tools/evm_tools/t8n/evm_trace/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -""" -EVM Trace Implementations. - -See [`ethereum.trace`](ref:ethereum.trace). -""" diff --git a/src/ethereum_spec_tools/evm_tools/loaders/__init__.py b/src/ethereum_spec_tools/loaders/__init__.py similarity index 100% rename from src/ethereum_spec_tools/evm_tools/loaders/__init__.py rename to src/ethereum_spec_tools/loaders/__init__.py diff --git a/src/ethereum_spec_tools/evm_tools/loaders/fixture_loader.py b/src/ethereum_spec_tools/loaders/fixture_loader.py similarity index 100% rename from src/ethereum_spec_tools/evm_tools/loaders/fixture_loader.py rename to src/ethereum_spec_tools/loaders/fixture_loader.py diff --git a/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py b/src/ethereum_spec_tools/loaders/fork_loader.py similarity index 100% rename from src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py rename to src/ethereum_spec_tools/loaders/fork_loader.py diff --git a/src/ethereum_spec_tools/evm_tools/loaders/transaction_loader.py b/src/ethereum_spec_tools/loaders/transaction_loader.py similarity index 99% rename from src/ethereum_spec_tools/evm_tools/loaders/transaction_loader.py rename to src/ethereum_spec_tools/loaders/transaction_loader.py index da882c94d99..ae2cab3ba41 100644 --- a/src/ethereum_spec_tools/evm_tools/loaders/transaction_loader.py +++ b/src/ethereum_spec_tools/loaders/transaction_loader.py @@ -19,7 +19,7 @@ hex_to_u256, hex_to_uint, ) -from ethereum_spec_tools.evm_tools.utils import parse_hex_or_int +from ethereum_spec_tools.utils import parse_hex_or_int class UnsupportedTxError(Exception): diff --git a/src/ethereum_spec_tools/evm_tools/utils.py b/src/ethereum_spec_tools/utils.py similarity index 100% rename from src/ethereum_spec_tools/evm_tools/utils.py rename to src/ethereum_spec_tools/utils.py diff --git a/tests/json_loader/conftest.py b/tests/json_loader/conftest.py index e5faa6b1532..f2027c4faa3 100644 --- a/tests/json_loader/conftest.py +++ b/tests/json_loader/conftest.py @@ -5,10 +5,9 @@ from _pytest.config.argparsing import Parser from _pytest.nodes import Item +from execution_testing.evm_tools.t8n import ForkCache from pytest import Collector, Config, Session, fixture -from ethereum_spec_tools.evm_tools.t8n import ForkCache - from . import FORKS from .helpers import FixturesFile, FixtureTestItem from .helpers.select_tests import extract_affected_forks @@ -119,11 +118,12 @@ def pytest_configure(config: Config) -> None: ethereum_optimized.monkey_patch(None) if config.getoption("evm_trace"): - import ethereum.trace - from ethereum_spec_tools.evm_tools.t8n.evm_trace.eip3155 import ( + from execution_testing.evm_tools.t8n.evm_trace.eip3155 import ( Eip3155Tracer, ) + import ethereum.trace + # Replace the function in the module ethereum.trace.set_evm_trace(Eip3155Tracer()) diff --git a/tests/json_loader/helpers/load_blockchain_tests.py b/tests/json_loader/helpers/load_blockchain_tests.py index feec8e44b0f..0ff4b8c36a1 100644 --- a/tests/json_loader/helpers/load_blockchain_tests.py +++ b/tests/json_loader/helpers/load_blockchain_tests.py @@ -14,7 +14,7 @@ from ethereum.exceptions import EthereumException, StateWithEmptyAccount from ethereum.state_mpt import close_state from ethereum.utils.hexadecimal import hex_to_bytes -from ethereum_spec_tools.evm_tools.loaders.fixture_loader import Load +from ethereum_spec_tools.loaders.fixture_loader import Load from .. import FORKS from ..stash_keys import desired_forks_key diff --git a/tests/json_loader/helpers/load_state_tests.py b/tests/json_loader/helpers/load_state_tests.py index 52a4557ff53..b645be0be0b 100644 --- a/tests/json_loader/helpers/load_state_tests.py +++ b/tests/json_loader/helpers/load_state_tests.py @@ -7,14 +7,14 @@ import pytest from _pytest.config import Config from _pytest.nodes import Item +from execution_testing.evm_tools import create_parser +from execution_testing.evm_tools.statetest import read_test_case +from execution_testing.evm_tools.t8n import ForkCache +from execution_testing.evm_tools.t8n.cli import build_t8n_from_cli_options from pytest import Collector from ethereum.exceptions import StateWithEmptyAccount from ethereum.utils.hexadecimal import hex_to_bytes -from ethereum_spec_tools.evm_tools import create_parser -from ethereum_spec_tools.evm_tools.statetest import read_test_case -from ethereum_spec_tools.evm_tools.t8n import ForkCache -from ethereum_spec_tools.evm_tools.t8n.cli import build_t8n_from_cli_options from .. import FORKS from ..stash_keys import desired_forks_key, fork_cache_key diff --git a/tests/json_loader/helpers/select_tests.py b/tests/json_loader/helpers/select_tests.py index 452a782e6c0..ff77a3d6018 100644 --- a/tests/json_loader/helpers/select_tests.py +++ b/tests/json_loader/helpers/select_tests.py @@ -70,10 +70,18 @@ def extract_affected_forks( # Run all forks if something changes in the test # framework return all_forks - if file_path.is_relative_to("src/ethereum_spec_tools/evm_tools"): + if file_path.is_relative_to( + "packages/testing/src/execution_testing/evm_tools" + ): # Run all forks if something changes in the evm # tools return all_forks + if file_path.is_relative_to( + "src/ethereum_spec_tools/loaders" + ) or file_path == Path("src/ethereum_spec_tools/utils.py"): + # Run all forks if something changes in the fixture/fork + # loading or shared helpers the evm tools depend on + return all_forks if optimized and file_path.is_relative_to("src/ethereum_optimized"): # Run all forks if something changes in the optimized tools and # while running optimized environment. diff --git a/tests/json_loader/stash_keys.py b/tests/json_loader/stash_keys.py index 63002ce3ab0..ae242c9a3d2 100644 --- a/tests/json_loader/stash_keys.py +++ b/tests/json_loader/stash_keys.py @@ -1,8 +1,7 @@ """Shared StashKey definitions for json_loader tests.""" +from execution_testing.evm_tools.t8n import ForkCache from pytest import StashKey -from ethereum_spec_tools.evm_tools.t8n import ForkCache - desired_forks_key = StashKey[list[str]]() fork_cache_key = StashKey[ForkCache]() diff --git a/tests/evm_tools/test_docc_shards.py b/tests/spec_tools/test_docc_shards.py similarity index 100% rename from tests/evm_tools/test_docc_shards.py rename to tests/spec_tools/test_docc_shards.py diff --git a/tests/evm_tools/test_lint.py b/tests/spec_tools/test_lint.py similarity index 100% rename from tests/evm_tools/test_lint.py rename to tests/spec_tools/test_lint.py diff --git a/tests/evm_tools/test_new_fork.py b/tests/spec_tools/test_new_fork.py similarity index 100% rename from tests/evm_tools/test_new_fork.py rename to tests/spec_tools/test_new_fork.py diff --git a/vulture_whitelist.py b/vulture_whitelist.py index b68fbccedab..ebdffee5ead 100644 --- a/vulture_whitelist.py +++ b/vulture_whitelist.py @@ -7,7 +7,6 @@ """ from ethereum.cancun.blocks import Withdrawal -from ethereum_spec_tools.evm_tools.t8n.transition_tool import EELST8N from ethereum.ethash import * from ethereum.fork_criteria import Unscheduled @@ -15,15 +14,6 @@ from ethereum.utils.hexadecimal import hex_to_bytes256 from ethereum_optimized.state_db import State from ethereum_spec_tools.docc import * -from ethereum_spec_tools.evm_tools.daemon import _EvmToolHandler -from ethereum_spec_tools.evm_tools.loaders.transaction_loader import ( - TransactionLoad, -) -from ethereum_spec_tools.evm_tools.t8n.block_environment import Ommer -from ethereum_spec_tools.evm_tools.t8n.evm_trace.eip3155 import ( - FinalTrace, - Trace, -) from ethereum_spec_tools.lint.lints.final_decorator import ( FinalDecoratorHygiene, ) @@ -32,6 +22,9 @@ ) from ethereum_spec_tools.lint.lints.import_hygiene import ImportHygiene from ethereum_spec_tools.lint.lints.uint_len import UintLenHygiene +from ethereum_spec_tools.loaders.transaction_loader import ( + TransactionLoad, +) from ethereum_spec_tools.new_fork.codemod.comment import CommentReplaceCommand from ethereum_spec_tools.new_fork.codemod.constant import SetConstantCommand from ethereum_spec_tools.new_fork.codemod.string_replace import ( @@ -91,17 +84,6 @@ docc.render_before_after docc._EthereumListingSource.listing_order_key -# src/ethereum_spec_tools/evm_tools/daemon.py -_EvmToolHandler.do_POST -_EvmToolHandler.log_request - -# src/ethereum_spec_tools/evm_tools/transition_tool.py -EELST8N -EELST8N._info_metadata -EELST8N.version -EELST8N.is_fork_supported -EELST8N.evaluate - # src/ethereum_spec_tools/loaders/transaction_loader.py TransactionLoad.json_to_authorizations TransactionLoad.json_to_chain_id @@ -121,25 +103,6 @@ TransactionLoad.json_to_r TransactionLoad.json_to_s -# src/ethereum_spec_tools/evm_tools/t8n/block_environment.py -Ommer.delta - -# src/ethereum_spec_tools/evm_tools/t8n/__init__.py -# `protected` is a field on the testing-package `Transaction` model; -# T8N flips it to False for pre-EIP-155 forks before calling `sign()`. -_unused_protected_marker = None -_unused_protected_marker.protected # type: ignore[attr-defined] - -# src/ethereum_spec_tools/evm_tools/t8n/evm_trace/eip3155.py -Trace.gasCost -Trace.memSize -Trace.returnData -Trace.refund -Trace.opName -Trace.stateGas -Trace.stateGasCost -FinalTrace.gasUsed - # src/ethereum_spec_tools/lint/lints/final_decorator.py FinalDecoratorHygiene @@ -173,8 +136,8 @@ _children # unused attribute (src/ethereum_spec_tools/docc.py:751) -# evm_tools/loaders/fixture_loader.py - abstract methods -from ethereum_spec_tools.evm_tools.loaders.fixture_loader import BaseLoad +# loaders/fixture_loader.py - abstract methods +from ethereum_spec_tools.loaders.fixture_loader import BaseLoad BaseLoad.json_to_header BaseLoad.json_to_state @@ -203,3 +166,26 @@ _configure_client_manager # autouse fixture test_suite_name # hive test suite name fixture genesis_header # genesis header fixture + +# packages/testing/src/execution_testing/evm_tools/t8n/evm_trace/ +# eip3155.py - EIP-3155 trace output field names, serialized to JSON +gasCost +gasUsed +memSize +opName +refund +returnData +stateGas +stateGasCost + +# packages/testing/src/execution_testing/evm_tools/daemon.py - +# overrides `BaseHTTPRequestHandler.log_request` +log_request + +# packages/testing/src/execution_testing/evm_tools/t8n/cli.py - field +# on the testing `Transaction` model, read by `Transaction.sign` +protected + +# packages/testing/src/execution_testing/evm_tools/tests/ - pytest +# marker magic variable +pytestmark From 292fa9c1b88eac6fc587235e8f25600cc9a78186 Mon Sep 17 00:00:00 2001 From: spencer <spencer.taylor-brown@ethereum.org> Date: Fri, 14 Aug 2026 07:30:19 +0200 Subject: [PATCH 227/233] fix(consume): map reth BAL account-miss and item-cost rejection messages (#3371) * fix(consume): map reth BAL account-miss and item-cost rejection messages * map reth SYSTEM_CONTRACT_EMPTY message --- .../src/execution_testing/client_clis/clis/reth.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/testing/src/execution_testing/client_clis/clis/reth.py b/packages/testing/src/execution_testing/client_clis/clis/reth.py index 9cf169b15da..0b62494fc30 100644 --- a/packages/testing/src/execution_testing/client_clis/clis/reth.py +++ b/packages/testing/src/execution_testing/client_clis/clis/reth.py @@ -119,7 +119,14 @@ class RethExceptionMapper(ExceptionMapper): BlockException.INVALID_BAL_HASH: (r"block access list hash mismatch"), BlockException.INVALID_BLOCK_ACCESS_LIST: ( r"block access list hash mismatch|" - r"BAL rejection: FinalHashMismatch" + r"BAL rejection: FinalHashMismatch|" + r"Bal error: Account .* not found in BAL" + ), + BlockException.BLOCK_ACCESS_LIST_GAS_LIMIT_EXCEEDED: ( + r"block access list item cost exceeds gas limit" + ), + BlockException.SYSTEM_CONTRACT_EMPTY: ( + r"system contract .* has no code" ), BlockException.INCORRECT_BLOCK_FORMAT: ( r"block access list hash mismatch|" From 253a46337b1c56736d1b1d46552220236d14e489 Mon Sep 17 00:00:00 2001 From: Jochem Brouwer <jochembrouwer96@gmail.com> Date: Fri, 14 Aug 2026 10:16:15 +0200 Subject: [PATCH 228/233] perf(test-rpc, test-fill): fetch a block's receipts with single `eth_getBlockReceipts` (#3345) * perf(fill): fetch a block's receipts with one eth_getBlockReceipts * refactor: block receipt fetching logic --------- Co-authored-by: LouisTsai <q1030176@gmail.com> --- .../client_clis/client_backend.py | 63 +++++++++++++------ .../testing/src/execution_testing/rpc/rpc.py | 11 ++++ 2 files changed, 56 insertions(+), 18 deletions(-) diff --git a/packages/testing/src/execution_testing/client_clis/client_backend.py b/packages/testing/src/execution_testing/client_clis/client_backend.py index f9bb2507b6d..0e2544c233d 100644 --- a/packages/testing/src/execution_testing/client_clis/client_backend.py +++ b/packages/testing/src/execution_testing/client_clis/client_backend.py @@ -292,7 +292,9 @@ def evaluate( assert np_version is not None assert fcu_version is not None - receipts = self._fetch_receipts(txs) + receipts = self._fetch_receipts( + txs, get_payload_response.execution_payload.block_hash + ) result = self._build_result( built_payload=get_payload_response.execution_payload, execution_requests=get_payload_response.execution_requests, @@ -434,29 +436,54 @@ def _finalize( ) def _fetch_receipts( - self, txs: List[Transaction] + self, + txs: List[Transaction], + block_hash: Hash, ) -> List[TransactionReceipt]: """ - Fetch receipts for every transaction in the block, batched. + Fetch all transaction receipts in order. - One request per transaction makes fill time latency-bound: a block - of 5,000 transactions costs 5,000 sequential round trips, which - against a non-local client dominates everything else the fill does - (the client executes such a block in ~150ms). + Use `eth_getBlockReceipts` for the block, then fetch any receipt it + did not return with a batched `eth_getTransactionReceipt` fallback. + Raise an error if any receipt remains missing. """ if not txs: return [] - receipt_data_list = self.eth_rpc.get_transaction_receipts( - [tx.hash for tx in txs] - ) - receipts: List[TransactionReceipt] = [] - for tx, receipt_data in zip(txs, receipt_data_list, strict=True): - if receipt_data is None: - raise RuntimeError( - f"No receipt found for transaction {tx.hash}" - ) - receipts.append(TransactionReceipt.model_validate(receipt_data)) - return receipts + block_receipts: List[dict[str, Any]] = [] + try: + block_receipts = self.eth_rpc.get_block_receipts(block_hash) or [] + except JSONRPCError as error: + logger.warning( + f"eth_getBlockReceipts failed for block {block_hash}: {error}" + ) + + receipt_data_by_hash: Dict[Hash, dict[str, Any]] = {} + for receipt_data in block_receipts: + tx_hash = receipt_data.get("transactionHash") + if tx_hash is not None: + receipt_data_by_hash[Hash(tx_hash)] = receipt_data + + missing = [ + tx.hash for tx in txs if tx.hash not in receipt_data_by_hash + ] + if missing: + logger.warning( + f"Block receipts covered {len(txs) - len(missing)} of " + f"{len(txs)} transactions; fetching the remaining " + f"{len(missing)} in a batched fallback" + ) + fetched = self.eth_rpc.get_transaction_receipts(missing) + for tx_hash, fetched_data in zip(missing, fetched, strict=True): + if fetched_data is None: + raise RuntimeError( + f"No receipt found for transaction {tx_hash}" + ) + receipt_data_by_hash[tx_hash] = fetched_data + + return [ + TransactionReceipt.model_validate(receipt_data_by_hash[tx.hash]) + for tx in txs + ] def _build_result( self, diff --git a/packages/testing/src/execution_testing/rpc/rpc.py b/packages/testing/src/execution_testing/rpc/rpc.py index f8018d39069..58cfb14035d 100644 --- a/packages/testing/src/execution_testing/rpc/rpc.py +++ b/packages/testing/src/execution_testing/rpc/rpc.py @@ -597,6 +597,17 @@ def get_block_by_number( request=RPCCall(method="getBlockByNumber", params=params) ).result_or_raise() + def get_block_receipts( + self, block_hash: Hash + ) -> List[dict[str, Any]] | None: + """`eth_getBlockReceipts`: Returns every receipt in a block.""" + logger.info(f"Requesting all receipts for block {block_hash}..") + return self.post_request( + request=RPCCall( + method="getBlockReceipts", params=[f"{block_hash}"] + ) + ).result_or_raise() + def get_block_by_hash( self, block_hash: Hash, full_txs: bool = True ) -> Any | None: From 1162c61f17cab7fe134a4e74dff69530d01fa1ba Mon Sep 17 00:00:00 2001 From: Jochem Brouwer <jochembrouwer96@gmail.com> Date: Fri, 14 Aug 2026 10:24:20 +0200 Subject: [PATCH 229/233] perf(test-forks): memoize per-fork gas costs (#3303) * perf(test-forks): memoize per-fork gas costs * chore: deduce docstring --------- Co-authored-by: LouisTsai <q1030176@gmail.com> --- .../src/execution_testing/forks/base_fork.py | 38 +++++++ .../forks/tests/test_forks.py | 99 ++++++++++++++++++- 2 files changed, 136 insertions(+), 1 deletion(-) diff --git a/packages/testing/src/execution_testing/forks/base_fork.py b/packages/testing/src/execution_testing/forks/base_fork.py index b1a5026acfb..30fe2fee5b3 100644 --- a/packages/testing/src/execution_testing/forks/base_fork.py +++ b/packages/testing/src/execution_testing/forks/base_fork.py @@ -3,8 +3,10 @@ import re from abc import ABCMeta, abstractmethod from enum import Enum, auto +from functools import lru_cache from typing import ( TYPE_CHECKING, + Any, Callable, ClassVar, Dict, @@ -15,7 +17,9 @@ Sequence, Set, Sized, + Tuple, Type, + cast, ) if TYPE_CHECKING: @@ -268,6 +272,40 @@ class RefundTypes(Enum): class BaseForkMeta(ABCMeta): """Metaclass for BaseFork.""" + MEMOIZED_FORK_METHODS = ("gas_costs",) + """fork ``classmethod``s that are memoized per fork.""" + + def __new__( + mcs, + name: str, + bases: Tuple[type, ...], + namespace: Dict[str, Any], + **kwargs: Any, + ) -> "BaseForkMeta": + """ + Create the fork class, memoizing `MEMOIZED_FORK_METHODS`. + + Wrapping every override here, rather than at each definition site, + means the most-derived one caches, keyed on the fork it was called + with, so the ``super()`` chain runs once per fork. + """ + for method_name in mcs.MEMOIZED_FORK_METHODS: + method = namespace.get(method_name) + if not isinstance(method, classmethod): + continue + function = method.__func__ + if getattr(function, "__isabstractmethod__", False): + # Leave `BaseFork`'s declarations visible to `abc`. + continue + # typeshed models `lru_cache` as returning an + # `_lru_cache_wrapper`, not a plain function, so `classmethod` + # cannot infer the descriptor signature from it. + cached = cast( + Callable[..., Any], lru_cache(maxsize=None)(function) + ) + namespace[method_name] = classmethod(cached) + return super().__new__(mcs, name, bases, namespace, **kwargs) + @abstractmethod def name(cls) -> str: """ diff --git a/packages/testing/src/execution_testing/forks/tests/test_forks.py b/packages/testing/src/execution_testing/forks/tests/test_forks.py index 0a3864a177c..98bf05b2ee4 100644 --- a/packages/testing/src/execution_testing/forks/tests/test_forks.py +++ b/packages/testing/src/execution_testing/forks/tests/test_forks.py @@ -1,6 +1,7 @@ """Test fork utilities.""" -from typing import Dict +import dataclasses +from typing import Any, Dict, Iterator, List, Tuple, Type import pytest from pydantic import BaseModel @@ -8,6 +9,7 @@ from execution_testing.base_types import BlobSchedule from execution_testing.vm import Opcodes +from ..base_fork import BaseFork, BaseForkMeta from ..forks.eips.paris.eip_3675 import EIP3675 from ..forks.forks import ( BPO1, @@ -828,3 +830,98 @@ def test_oog_budget_lift() -> None: ) == 3 * sstore + 2 * create + code_64 ) + + +@pytest.fixture(scope="module") +def all_fork_classes() -> List[Type[BaseFork]]: + """Return every concrete fork class, transition forks excluded.""" + return sorted(get_forks(), key=str) + + +def _memoized_caches( + fork_classes: List[Type[BaseFork]], +) -> Iterator[Tuple[Type[Any], str, Any]]: + """Yield ``(owner, method_name, cache)`` for every memoized override.""" + owners: set = set() + for fork in fork_classes: + owners.update(fork.__mro__) + for owner in owners: + for method_name in BaseForkMeta.MEMOIZED_FORK_METHODS: + member = owner.__dict__.get(method_name) + if not isinstance(member, classmethod): + continue + function = member.__func__ + if hasattr(function, "cache_clear"): + yield owner, method_name, function + + +def test_memoized_fork_methods_are_installed( + all_fork_classes: List[Type[BaseFork]], +) -> None: + """Every fork must resolve each memoized name to a cached override.""" + for fork in all_fork_classes: + for method_name in BaseForkMeta.MEMOIZED_FORK_METHODS: + resolved = getattr(fork, method_name) + assert hasattr(resolved.__func__, "cache_info"), ( + f"{fork}.{method_name} resolves to an uncached override" + ) + + +def test_memoized_fork_methods_are_computed_once_per_fork( + all_fork_classes: List[Type[BaseFork]], +) -> None: + """The first call per fork computes, and every later one is a hit.""" + for method_name in BaseForkMeta.MEMOIZED_FORK_METHODS: + for _, _, function in _memoized_caches(all_fork_classes): + function.cache_clear() + for fork in all_fork_classes: + cache = getattr(fork, method_name).__func__ + before = cache.cache_info() + getattr(fork, method_name)() + getattr(fork, method_name)() + after = cache.cache_info() + assert after.misses == before.misses + 1, ( + f"{fork}.{method_name} recomputed on a repeat call" + ) + assert after.hits == before.hits + 1, ( + f"{fork}.{method_name} was not served from its cache" + ) + + +def test_memoized_fork_methods_are_not_shared_between_forks( + all_fork_classes: List[Type[BaseFork]], +) -> None: + """A cache is keyed on the fork, so no fork may serve another's value.""" + for method_name in BaseForkMeta.MEMOIZED_FORK_METHODS: + warm = { + str(fork): getattr(fork, method_name)() + for fork in all_fork_classes + } + for _, _, function in _memoized_caches(all_fork_classes): + function.cache_clear() + for fork in reversed(all_fork_classes): + assert getattr(fork, method_name)() == warm[str(fork)], ( + f"{fork}.{method_name} changed when recomputed in a " + "different order" + ) + + assert Amsterdam.gas_costs() is not Cancun.gas_costs() + assert Amsterdam.gas_costs() != Cancun.gas_costs() + + +def test_memoized_fork_methods_return_immutable_values() -> None: + """Callers share one object, so a mutable value could be corrupted.""" + for method_name in BaseForkMeta.MEMOIZED_FORK_METHODS: + value = getattr(Amsterdam, method_name)() + assert dataclasses.is_dataclass(value) + field_name = next(iter(dataclasses.fields(value))).name + with pytest.raises(dataclasses.FrozenInstanceError): + setattr(value, field_name, 0) + + +def test_abstract_memoized_declarations_are_left_alone() -> None: + """`abc` must still see `BaseFork`'s declarations as unimplemented.""" + for method_name in BaseForkMeta.MEMOIZED_FORK_METHODS: + declaration = BaseFork.__dict__[method_name] + assert getattr(declaration, "__isabstractmethod__", False) + assert not hasattr(declaration.__func__, "cache_info") From 26332146a768f7491287a45254d7bb05f78b4611 Mon Sep 17 00:00:00 2001 From: Rafael Matias <rafael@skyle.net> Date: Fri, 14 Aug 2026 11:47:51 +0200 Subject: [PATCH 230/233] refactor(client-clis): bound the opcode-count trace time explicitly (#3367) * fix(client-clis): bound the opcode-count trace explicitly `extract_block_opcode_count` calls `debug_traceBlockByHash` without a `timeout`, so the client applies its own default -- 5s per transaction on geth. That is far below what a benchmark block needs. * refactor: request timeout logic --------- Co-authored-by: LouisTsai <q1030176@gmail.com> --- .../plugins/fill_stateful/fill_stateful.py | 24 ++++++ .../client_clis/client_backend.py | 7 +- .../tests/test_opcode_count_trace.py | 86 +++++++++++++++++++ 3 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 packages/testing/src/execution_testing/client_clis/tests/test_opcode_count_trace.py diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/fill_stateful/fill_stateful.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/fill_stateful/fill_stateful.py index 743769544d9..bf427899202 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/fill_stateful/fill_stateful.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/fill_stateful/fill_stateful.py @@ -30,6 +30,9 @@ ) from execution_testing.client_clis import ClientBackend from execution_testing.client_clis.cli_types import EnginePayloadMetadata +from execution_testing.client_clis.client_backend import ( + DEFAULT_OPCODE_COUNT_TRACE_TIMEOUT, +) from execution_testing.fixtures import FixtureFillingPhase from execution_testing.fixtures.blockchain import ( StatefulPreRunFixture, @@ -147,6 +150,19 @@ def pytest_addoption(parser: pytest.Parser) -> None: "opt-in." ), ) + group.addoption( + "--opcode-count-trace-timeout", + action="store", + dest="opcode_count_trace_timeout", + default=DEFAULT_OPCODE_COUNT_TRACE_TIMEOUT, + type=str, + help=( + "Per-transaction bound for the --extract-opcode-count trace, as " + "a Go duration (e.g. 30s, 5m, 1h). Without it the client applies " + "its own (5s on geth), which a benchmark block's trace outruns; " + "the abandoned transaction is then tallied as zero." + ), + ) group.addoption( "--verify-full-accounts", action="store_true", @@ -507,11 +523,18 @@ def extract_opcode_count(request: pytest.FixtureRequest) -> bool: return request.config.getoption("extract_opcode_count") +@pytest.fixture(scope="session") +def opcode_count_trace_timeout(request: pytest.FixtureRequest) -> str: + """The --opcode-count-trace-timeout bound sent with each trace.""" + return request.config.getoption("opcode_count_trace_timeout") + + @pytest.fixture(scope="session") def client_backend( eth_rpc: ChainBuilderEthRPC, debug_rpc: DebugRPC, extract_opcode_count: bool, + opcode_count_trace_timeout: str, session_fork: Fork | TransitionFork, default_gas_price: int | None, default_max_fee_per_gas: int | None, @@ -537,6 +560,7 @@ def client_backend( fork=session_fork, debug_rpc=debug_rpc, extract_opcode_count=extract_opcode_count, + opcode_count_trace_timeout=opcode_count_trace_timeout, ) priority_fee = default_max_priority_fee_per_gas diff --git a/packages/testing/src/execution_testing/client_clis/client_backend.py b/packages/testing/src/execution_testing/client_clis/client_backend.py index 0e2544c233d..0b42b3d216d 100644 --- a/packages/testing/src/execution_testing/client_clis/client_backend.py +++ b/packages/testing/src/execution_testing/client_clis/client_backend.py @@ -75,6 +75,8 @@ "disableStorage": True, } +DEFAULT_OPCODE_COUNT_TRACE_TIMEOUT = "1h" + def _normalize_opcode_name(name: str) -> str | None: """ @@ -190,6 +192,7 @@ def __init__( fork: Fork | TransitionFork, debug_rpc: DebugRPC | None = None, extract_opcode_count: bool = False, + opcode_count_trace_timeout: str = DEFAULT_OPCODE_COUNT_TRACE_TIMEOUT, ) -> None: """Initialize with the RPC clients and the session fork.""" self.testing_rpc = testing_rpc @@ -198,6 +201,7 @@ def __init__( self.fork = fork self.debug_rpc = debug_rpc self.extract_opcode_count = extract_opcode_count + self.opcode_count_trace_timeout = opcode_count_trace_timeout # Sticky fallback to struct logs (besu has no JS tracer). self._js_tracer_unsupported = False self.exception_mapper = ClientBackendExceptionMapper() @@ -358,7 +362,8 @@ def _trace_block( """Raw ``debug_traceBlockByHash`` call; exceptions propagate.""" assert self.debug_rpc is not None return self.debug_rpc.trace_block_by_hash( - str(block_hash), tracer_config + str(block_hash), + {"timeout": self.opcode_count_trace_timeout, **tracer_config}, ) def _payload_attributes( diff --git a/packages/testing/src/execution_testing/client_clis/tests/test_opcode_count_trace.py b/packages/testing/src/execution_testing/client_clis/tests/test_opcode_count_trace.py new file mode 100644 index 00000000000..566758d1c06 --- /dev/null +++ b/packages/testing/src/execution_testing/client_clis/tests/test_opcode_count_trace.py @@ -0,0 +1,86 @@ +"""Test suite for the opcode-count trace request in ``ClientBackend``.""" + +from typing import Any, Dict, List + +import pytest + +from execution_testing.base_types import Hash +from execution_testing.client_clis import ClientBackend +from execution_testing.client_clis.client_backend import ( + DEFAULT_OPCODE_COUNT_TRACE_TIMEOUT, + OPCODE_COUNT_TRACER_JS, + STRUCT_LOG_TRACER_CONFIG, +) +from execution_testing.rpc.rpc_types import JSONRPCError + +BLOCK_HASH = Hash(0) +JS_TRACER_REJECTED = JSONRPCError(code=-32601, message="method not found") +JS_TRACE = [{"result": {"PUSH0": 3}}] +STRUCT_LOG_TRACE = [{"result": {"structLogs": [{"op": "PUSH0"}]}}] + + +class StubDebugRPC: + """Record each trace request's config and replay a canned response.""" + + def __init__(self, responses: List[Any]) -> None: + self.responses = responses + self.configs: List[Dict[str, Any]] = [] + + def trace_block_by_hash( + self, _block_hash: str, tracer_config: Dict[str, Any] + ) -> Any: + """Record the request config, then return or raise the response.""" + self.configs.append(tracer_config) + response = self.responses[len(self.configs) - 1] + if isinstance(response, Exception): + raise response + return response + + +def _trace_configs( + responses: List[Any], + timeout: str = DEFAULT_OPCODE_COUNT_TRACE_TIMEOUT, +) -> List[Dict[str, Any]]: + """Return the configs ``extract_block_opcode_count`` sends per call.""" + # ``__new__`` skips ``__init__``: the trace path needs no live client. + backend = ClientBackend.__new__(ClientBackend) + backend.extract_opcode_count = True + backend.opcode_count_trace_timeout = timeout + backend._js_tracer_unsupported = False + debug_rpc = StubDebugRPC(responses) + backend.debug_rpc = debug_rpc # type: ignore[assignment] + backend.extract_block_opcode_count(BLOCK_HASH) + return debug_rpc.configs + + +@pytest.mark.parametrize( + "responses,traced_call,tracer_config", + [ + pytest.param( + [JS_TRACE], + 0, + {"tracer": OPCODE_COUNT_TRACER_JS}, + id="js_tracer", + ), + pytest.param( + [JS_TRACER_REJECTED, STRUCT_LOG_TRACE], + 1, + STRUCT_LOG_TRACER_CONFIG, + id="struct_log_fallback", + ), + ], +) +def test_trace_request_carries_the_timeout( + responses: List[Any], + traced_call: int, + tracer_config: Dict[str, Any], +) -> None: + """ + Both tracer paths bound the trace and keep their own config intact. + """ + configs = _trace_configs(responses) + + config = configs[traced_call] + assert config["timeout"] == DEFAULT_OPCODE_COUNT_TRACE_TIMEOUT + for key, value in tracer_config.items(): + assert config[key] == value From 43e3cd1ec03dd550a2bd43746564b339e85fcd1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=94=A1=E4=BD=B3=E8=AA=A0=20Louis=20Tsai?= <72684086+LouisTsai-Csie@users.noreply.github.com> Date: Fri, 14 Aug 2026 22:48:50 +0800 Subject: [PATCH 231/233] refactor(test-benchmark): remove potential duplicated cases (#3374) --- .../stateful/bloatnet/test_account_query.py | 290 ------------- .../benchmark/stateful/bloatnet/test_call.py | 95 ----- .../benchmark/stateful/bloatnet/test_erc20.py | 398 ------------------ 3 files changed, 783 deletions(-) delete mode 100644 tests/benchmark/stateful/bloatnet/test_erc20.py diff --git a/tests/benchmark/stateful/bloatnet/test_account_query.py b/tests/benchmark/stateful/bloatnet/test_account_query.py index bec412631d8..1b520c58bf6 100644 --- a/tests/benchmark/stateful/bloatnet/test_account_query.py +++ b/tests/benchmark/stateful/bloatnet/test_account_query.py @@ -5,20 +5,14 @@ import pytest from execution_testing import ( Account, - Address, Alloc, BenchmarkTestFiller, - Block, - BlockchainTestFiller, Bytecode, - Conditional, - Create2PreimageLayout, Fork, Hash, IteratingBytecode, JumpLoopGenerator, Op, - Storage, TestPhaseManager, Transaction, While, @@ -29,9 +23,7 @@ AccountMode, ) from tests.benchmark.helper.enums import CacheStrategy -from tests.benchmark.helper.loops import DECREMENT_COUNTER_CONDITION from tests.benchmark.helper.transactions import ( - build_benchmark_txs, build_cache_strategy_blocks, ) @@ -348,285 +340,3 @@ def calldata(iteration_count: int, start_iteration: int) -> bytes: skip_gas_used_validation=True, expected_receipt_status=1, ) - - -@pytest.mark.stub_parametrize("factory_stub", "bloatnet_factory_") -@pytest.mark.parametrize( - "second_opcode", - [Op.EXTCODESIZE, Op.EXTCODECOPY, Op.EXTCODEHASH, Op.STATICCALL, Op.CALL], -) -@pytest.mark.parametrize( - "balance_first", - [True, False], - ids=["balance_first", "opcode_first"], -) -def test_balance_query( - benchmark_test: BenchmarkTestFiller, - pre: Alloc, - fork: Fork, - gas_benchmark_value: int, - tx_gas_limit: int, - balance_first: bool, - second_opcode: Op, - factory_stub: str, -) -> None: - """Benchmark BALANCE paired with a second opcode on factory contracts.""" - factory_address = pre.deploy_contract( - code=Bytecode(), - stub=factory_stub, - ) - - # Contract Construction - setup = Bytecode() - - setup += Conditional( - condition=Op.STATICCALL( - gas=Op.GAS, - address=factory_address, - args_offset=0, - args_size=0, - ret_offset=96, - ret_size=64, - # gas accounting - address_warm=False, - old_memory_size=0, - new_memory_size=160, - ), - if_false=Op.INVALID, - ) - - create2_preimage = Create2PreimageLayout( - factory_address=factory_address, - salt=Op.CALLDATALOAD(32), - init_code_hash=Op.MLOAD(128), - old_memory_size=160, - ) - - setup += create2_preimage - setup += Op.CALLDATALOAD(0) # [num_contract] - - # Build the second opcode's bytecode - balance_op = Op.POP(Op.BALANCE) - - if second_opcode == Op.EXTCODESIZE: - other_op = Op.POP(Op.EXTCODESIZE) - elif second_opcode == Op.EXTCODECOPY: - max_contract_size = fork.max_code_size() - other_op = Op.POP( - Op.EXTCODECOPY( - address=Op.DUP4, - dest_offset=Op.ADD(Op.MLOAD(32), 96), - offset=max_contract_size - 1, - size=1, - data_size=1, - ) - ) - elif second_opcode == Op.EXTCODEHASH: - other_op = Op.POP(Op.EXTCODEHASH) - elif second_opcode == Op.STATICCALL: - # gas=1: forces account/code loading, then fails - other_op = ( - Op.POP( - Op.STATICCALL( - gas=1, - address=Op.DUP5, - args_offset=0, - args_size=0, - ret_offset=0, - ret_size=0, - ) - ) - + Op.POP - ) - elif second_opcode == Op.CALL: - # gas=1: forces account/code loading, then fails - other_op = ( - Op.POP( - Op.CALL( - gas=1, - address=Op.DUP6, - value=0, - args_offset=0, - args_size=0, - ret_offset=0, - ret_size=0, - ) - ) - + Op.POP - ) - else: - raise ValueError(f"Unsupported opcode: {second_opcode}") - - benchmark_ops = ( - (balance_op + other_op) if balance_first else (other_op + balance_op) - ) - - loop = While( - body=( - create2_preimage.address_op() - + Op.DUP1 - + benchmark_ops - + create2_preimage.increment_salt_op() - ), - condition=DECREMENT_COUNTER_CONDITION, - ) - - # Contract Deployment - code = setup + loop - attack_contract_address = pre.deploy_contract(code=code) - - # Gas Accounting - txs, total_gas_consumed = build_benchmark_txs( - pre=pre, - fork=fork, - gas_benchmark_value=gas_benchmark_value, - tx_gas_limit=tx_gas_limit, - attack_contract_address=attack_contract_address, - setup_cost=setup.gas_cost(fork), - iteration_cost=loop.gas_cost(fork), - ) - - benchmark_test( - pre=pre, - blocks=[Block(txs=txs)], - expected_benchmark_gas_used=total_gas_consumed, - skip_gas_used_validation=True, - ) - - -def get_factory_stub_name(size_kb: float) -> str: - """Generate stub name for factory based on size.""" - if size_kb == 0.5: - return "bloatnet_factory_0_5kb" - elif size_kb == 1.0: - return "bloatnet_factory_1kb" - elif size_kb == 2.0: - return "bloatnet_factory_2kb" - elif size_kb == 5.0: - return "bloatnet_factory_5kb" - elif size_kb == 10.0: - return "bloatnet_factory_10kb" - elif size_kb == 24.0: - return "bloatnet_factory_24kb" - else: - raise ValueError(f"Unsupported size: {size_kb}KB") - - -def build_attack_contract(factory_address: Address) -> Bytecode: - """Build the EXTCODESIZE attack contract with a gas-based loop exit.""" - gas_reserve = 50_000 # Reserve for 2x SSTORE + cleanup - num_deployed_offset = 96 - init_code_hash_offset = num_deployed_offset + 32 - return_size = 64 - return ( - # Call factory.getConfig() -> (num_deployed, init_code_hash) - Conditional( - condition=Op.STATICCALL( - gas=Op.GAS, - address=factory_address, - args_offset=0, - args_size=0, - # MEM[num_deployed_offset]=num_deployed - # MEM[num_deployed_offset + 32]=init_code_hash - ret_offset=num_deployed_offset, - ret_size=return_size, - ), - if_false=Op.REVERT(0, 0), - ) - + ( - create2_preimage := Create2PreimageLayout( - factory_address=factory_address, - salt=Op.SLOAD(0), - init_code_hash=Op.MLOAD(init_code_hash_offset), - old_memory_size=num_deployed_offset + return_size, - ) - ) - + Op.MSTORE(160, 0) # Initialize last_size - + While( - body=( - Op.MSTORE(160, Op.EXTCODESIZE(create2_preimage.address_op())) - + create2_preimage.increment_salt_op() - ), - condition=( - Op.AND( - Op.GT(Op.GAS, gas_reserve), - # num_deployed > salt - Op.GT( - Op.MLOAD(num_deployed_offset), - Op.MLOAD(create2_preimage.salt_offset), - ), - ) - ), - ) - + Op.SSTORE(0, Op.MLOAD(32)) # Save final salt - + Op.SSTORE(1, Op.MLOAD(160)) # Save last result - + Op.STOP - ) - - -@pytest.mark.parametrize( - "bytecode_size_kb", - [0.5, 1.0, 2.0, 5.0, 10.0, 24.0], - ids=lambda size: f"{size}KB", -) -def test_extcodesize_bytecode_sizes( - blockchain_test: BlockchainTestFiller, - pre: Alloc, - bytecode_size_kb: float, - gas_benchmark_value: int, - tx_gas_limit: int, -) -> None: - """Execute EXTCODESIZE benchmark against pre-deployed contracts.""" - expected_size_bytes = int(bytecode_size_kb * 1024) - - # Get factory stub name for this size - factory_stub = get_factory_stub_name(bytecode_size_kb) - - # Deploy factory stub (address comes from stub file) - factory_address = pre.deploy_contract( - code=Bytecode(), # Empty bytecode - address from stub - stub=factory_stub, - ) - - # Build and deploy the attack contract - attack_code = build_attack_contract(factory_address) - attack_address = pre.deploy_contract(code=attack_code) - - # Calculate how many transactions we need to fill the block - num_attack_txs = gas_benchmark_value // tx_gas_limit - if num_attack_txs == 0: - num_attack_txs = 1 - - # Fund the sender - sender = pre.fund_eoa() - - # Build transactions - txs = [] - - # Attack transactions: all identical, no calldata needed - for _ in range(num_attack_txs): - attack_tx = Transaction( - gas_limit=tx_gas_limit, - to=attack_address, - sender=sender, - ) - txs.append(attack_tx) - - # Create block with all transactions - block = Block(txs=txs) - - # Post-state verification: - # Attack contract slot 1 = expected size (last EXTCODESIZE result) - # Slot 0 can be any value (final salt depends on gas used) - attack_storage = Storage({1: expected_size_bytes}) # type: ignore[dict-item] - attack_storage.set_expect_any(0) - - post = { - attack_address: Account(storage=attack_storage), - } - - blockchain_test( - pre=pre, - post=post, - blocks=[block], - ) diff --git a/tests/benchmark/stateful/bloatnet/test_call.py b/tests/benchmark/stateful/bloatnet/test_call.py index 32062de5319..80f5ad4e91d 100644 --- a/tests/benchmark/stateful/bloatnet/test_call.py +++ b/tests/benchmark/stateful/bloatnet/test_call.py @@ -1,15 +1,11 @@ """Benchmark call operations with value transfer on target accounts.""" -import pytest from execution_testing import ( Account, Address, Alloc, BenchmarkTestFiller, Block, - Bytecode, - Conditional, - Create2PreimageLayout, Fork, Hash, IteratingBytecode, @@ -19,97 +15,6 @@ ) from tests.benchmark.helper.loops import DECREMENT_COUNTER_CONDITION -from tests.benchmark.helper.transactions import build_benchmark_txs - - -@pytest.mark.stub_parametrize("factory_stub", "bloatnet_factory_") -def test_call_value_to_existing( - benchmark_test: BenchmarkTestFiller, - pre: Alloc, - fork: Fork, - gas_benchmark_value: int, - tx_gas_limit: int, - factory_stub: str, -) -> None: - """Benchmark CALL with value transfer to cold existing contracts.""" - factory_address = pre.deploy_contract( - code=Bytecode(), - stub=factory_stub, - ) - - # Contract Construction - setup = Bytecode() - - setup += Conditional( - condition=Op.STATICCALL( - gas=Op.GAS, - address=factory_address, - args_offset=0, - args_size=0, - ret_offset=96, - ret_size=64, - # gas accounting - address_warm=False, - old_memory_size=0, - new_memory_size=160, - ), - if_false=Op.INVALID, - ) - - create2_preimage = Create2PreimageLayout( - factory_address=factory_address, - salt=Op.CALLDATALOAD(32), - init_code_hash=Op.MLOAD(128), - old_memory_size=160, - ) - - setup += create2_preimage - setup += Op.CALLDATALOAD(0) # [num_contract] - - # CALL with value=1 to factory contracts. - # The address is computed inline via SHA3, avoiding DUP depth issues. - # gas=1: subcall gets 1 + 2300 stipend, still not enough for 24KB - # bytecode → subcall fails, but cold + value gas costs are charged. - call_value_op = Op.POP( - Op.CALL( - gas=1, - address=create2_preimage.address_op(), - value=1, - args_offset=0, - args_size=0, - ret_offset=0, - ret_size=0, - # gas accounting - value_transfer=True, - ) - ) - - loop = While( - body=(call_value_op + create2_preimage.increment_salt_op()), - condition=DECREMENT_COUNTER_CONDITION, - ) - - # Contract Deployment - code = setup + loop - attack_contract_address = pre.deploy_contract(code=code) - - # Gas Accounting - txs, total_gas_consumed = build_benchmark_txs( - pre=pre, - fork=fork, - gas_benchmark_value=gas_benchmark_value, - tx_gas_limit=tx_gas_limit, - attack_contract_address=attack_contract_address, - setup_cost=setup.gas_cost(fork), - iteration_cost=loop.gas_cost(fork), - ) - - benchmark_test( - pre=pre, - blocks=[Block(txs=txs)], - expected_benchmark_gas_used=total_gas_consumed, - skip_gas_used_validation=True, - ) def test_call_value_to_empty( diff --git a/tests/benchmark/stateful/bloatnet/test_erc20.py b/tests/benchmark/stateful/bloatnet/test_erc20.py deleted file mode 100644 index 5f1207fab93..00000000000 --- a/tests/benchmark/stateful/bloatnet/test_erc20.py +++ /dev/null @@ -1,398 +0,0 @@ -"""Benchmark storage operations through ERC20 calls.""" - -import pytest -from execution_testing import ( - AccessList, - Alloc, - BenchmarkTestFiller, - Block, - Bytecode, - Fork, - Op, - TestPhaseManager, - Transaction, - While, -) - -# ERC20 function selectors -BALANCEOF_SELECTOR = 0x70A08231 # balanceOf(address) -APPROVE_SELECTOR = 0x095EA7B3 # approve(address,uint256) - -# SLOAD BENCHMARK ARCHITECTURE: -# -# [Pre-deployed ERC20 Contract] ──── Storage slots for balances -# │ -# │ balanceOf(address) → SLOAD(keccak256(address || slot)) -# │ -# [Attack Contract] ──CALL──► ERC20.balanceOf(random_address) -# │ -# └─► Loop(i=0 to N): -# 1. Generate random address from counter -# 2. CALL balanceOf(random_address) → forces cold SLOAD -# 3. Most addresses have zero balance → empty storage slots -# -# WHY IT STRESSES CLIENTS: -# - Each balanceOf() call forces a cold SLOAD on a likely-empty slot -# - Storage slot = keccak256(address || balances_slot) -# - Random addresses ensure maximum cache misses -# - Tests client's sparse storage handling efficiency - - -@pytest.mark.stub_parametrize( - "erc20_stub", "test_sload_empty_erc20_balanceof_" -) -def test_sload_erc20_generic( - benchmark_test: BenchmarkTestFiller, - pre: Alloc, - fork: Fork, - gas_benchmark_value: int, - tx_gas_limit: int, - erc20_stub: str, -) -> None: - """Benchmark SLOAD using ERC20 balanceOf.""" - # Stub Account - erc20_address = pre.deploy_contract( - code=Bytecode(), - stub=erc20_stub, - ) - threshold = 100000 - - # MEM[0] = function selector - # MEM[32] = starting address offset - setup = Op.MSTORE( - 0, - BALANCEOF_SELECTOR, - # gas accounting - old_memory_size=0, - new_memory_size=32, - ) + Op.MSTORE( - 32, - Op.SLOAD(0), # Address Offset - # gas accounting - old_memory_size=32, - new_memory_size=64, - ) - - call_balance_of = Op.POP( - Op.CALL( - address=erc20_address, - args_offset=32 - 4, - args_size=32 + 4, - ) - ) - - loop = While( - body=call_balance_of + Op.MSTORE(32, Op.ADD(Op.MLOAD(32), 1)), - condition=Op.GT(Op.GAS, threshold), - ) - - teardown = Op.SSTORE(0, Op.MLOAD(32)) - - # Contract Deployment - code = setup + loop + teardown - attack_contract_address = pre.deploy_contract(code=code) - - intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() - - # Transaction Loops - txs = [] - gas_remaining = gas_benchmark_value - - sender = pre.fund_eoa() - - while gas_remaining > intrinsic_gas: - gas_available = min(gas_remaining, tx_gas_limit) - - if gas_available < intrinsic_gas: - break - - with TestPhaseManager.execution(): - txs.append( - Transaction( - gas_limit=gas_available, - to=attack_contract_address, - sender=sender, - ) - ) - - gas_remaining -= gas_available - - blocks = [Block(txs=txs)] - benchmark_test( - pre=pre, - blocks=blocks, - skip_gas_used_validation=True, - expected_receipt_status=True, - ) - - -# SSTORE BENCHMARK ARCHITECTURE: -# -# [Pre-deployed ERC20 Contract] ──── Storage slots for allowances -# │ -# │ approve(spender, amount) -# │ → SSTORE(keccak256(spender || slot), amount) -# │ -# [Attack Contract] -# ──CALL──► ERC20.approve(counter_as_spender, counter_as_amount) -# │ -# └─► Loop(i=0 to N): -# 1. Use counter as both spender address and amount -# 2. CALL approve(counter, counter) → forces cold SSTORE -# 3. Writes to new allowance slots in sparse storage -# -# WHY IT STRESSES CLIENTS: -# - Each approve() call forces an SSTORE to a new storage slot -# - Storage slot = keccak256( -# msg.sender || keccak256(spender || allowances_slot) -# ) -# - Sequential counter ensures unique storage locations -# - Tests client's ability to handle many storage writes -# - Simulates real-world contract state accumulation over time - - -@pytest.mark.stub_parametrize("erc20_stub", "test_sstore_erc20_approve_") -def test_sstore_erc20_generic( - benchmark_test: BenchmarkTestFiller, - pre: Alloc, - fork: Fork, - gas_benchmark_value: int, - tx_gas_limit: int, - erc20_stub: str, -) -> None: - """Benchmark SSTORE using ERC20 approve.""" - sender = pre.fund_eoa() - - threshold = 100_000 - - # Stub Account - erc20_address = pre.deploy_contract( - code=Bytecode(), - stub=erc20_stub, - ) - - # MEM[0] = function selector - # MEM[32] = starting address offset - setup = Op.MSTORE( - 0, - APPROVE_SELECTOR, - ) + Op.MSTORE( - 32, - Op.SLOAD(0), # Address Offset - ) - - call_approve = Op.MSTORE( - 64, - Op.ADD(1, Op.MLOAD(32)), - ) + Op.POP( - Op.CALL( - address=erc20_address, - args_offset=28, - args_size=68, - ) - ) - - loop = While( - body=call_approve + Op.MSTORE(32, Op.ADD(Op.MLOAD(32), 1)), - condition=Op.GT(Op.GAS, threshold), - ) - - teardown = Op.SSTORE(0, Op.MLOAD(32)) - - # Contract Deployment - code = setup + loop + teardown - attack_contract_address = pre.deploy_contract(code=code) - - intrinsic_gas = fork.transaction_intrinsic_cost_calculator()() - - # Transaction Loops - gas_remaining = gas_benchmark_value - - # Collect tx params first, then build Transaction objects - # so that nonces are allocated contiguously per block. - tx_gas: list[int] = [] - while gas_remaining > intrinsic_gas: - gas_available = min(gas_remaining, tx_gas_limit) - - if gas_available < intrinsic_gas: - break - - tx_gas.append(gas_available) - - gas_remaining -= gas_available - - txs = [] - with TestPhaseManager.execution(): - for gas_available in tx_gas: - txs.append( - Transaction( - gas_limit=gas_available, - to=attack_contract_address, - sender=sender, - ) - ) - - blocks = [Block(txs=txs)] - - benchmark_test( - pre=pre, - blocks=blocks, - skip_gas_used_validation=True, - expected_receipt_status=True, - ) - - -@pytest.mark.stub_parametrize("erc20_stub", "test_mixed_sload_sstore_") -@pytest.mark.parametrize( - "sload_percent,sstore_percent", - [ - pytest.param(10, 90, id="10-90"), - pytest.param(30, 70, id="30-70"), - pytest.param(50, 50, id="50-50"), - pytest.param(70, 30, id="70-30"), - pytest.param(90, 10, id="90-10"), - ], -) -def test_mixed_sload_sstore( - benchmark_test: BenchmarkTestFiller, - pre: Alloc, - fork: Fork, - gas_benchmark_value: int, - tx_gas_limit: int, - erc20_stub: str, - sload_percent: int, - sstore_percent: int, -) -> None: - """Benchmark mixed SLOAD/SSTORE ratios on ERC20 contracts.""" - # The gas threshold is the minimum gas reserved to exit the - # loops and execute cleanup (SSTORE to persist slot offset). - # 150_000 is conservative: cold approve ~25K + cleanup ~20K. - gas_threshold = 150_000 - slot_offset_key = 0 # storage slot for persistent offset - - # Stub Account - erc20_address = pre.deploy_contract( - code=Bytecode(), - stub=erc20_stub, - ) - - # Contract Construction - # MEM[0] = function selector - # MEM[32] = address/slot offset (incremented each iteration) - # MEM[64] = spender/amount for approve (copied from MEM[32]) - # MEM[96] = initial_gas snapshot - # MEM[128] = gas_floor for SLOAD phase - setup = ( - Op.MSTORE( - 0, - BALANCEOF_SELECTOR, - old_memory_size=0, - new_memory_size=32, - ) - + Op.MSTORE( - 32, - Op.SLOAD(slot_offset_key), - old_memory_size=32, - new_memory_size=64, - ) - + Op.MSTORE( - 96, - Op.GAS, - old_memory_size=64, - new_memory_size=128, - ) - # gas_floor = initial_gas * sstore_percent / 100 - # This is the gas level at which SLOADs stop and - # SSTOREs begin, leaving sstore_percent of the - # initial gas for the SSTORE phase. - + Op.MSTORE( - 128, - Op.DIV(Op.MUL(Op.MLOAD(96), sstore_percent), 100), - old_memory_size=128, - new_memory_size=160, - ) - ) - - # SLOAD loop — STATICCALL since balanceOf is a view function. - # Continues while both: gas is above the sload/sstore - # transition floor AND above the safety threshold. - sload_loop = While( - body=Op.POP( - Op.STATICCALL( - address=erc20_address, - args_offset=28, - args_size=36, - ret_offset=0, - ret_size=0, - address_warm=True, - ) - ) - + Op.MSTORE(32, Op.ADD(Op.MLOAD(32), 1)), - condition=Op.AND( - Op.GT(Op.GAS, Op.MLOAD(128)), - Op.GT(Op.GAS, gas_threshold), - ), - ) - - transition = Op.MSTORE(0, APPROVE_SELECTOR) - - # SSTORE loop — runs until gas drops below safety threshold. - sstore_loop = While( - body=( - Op.MSTORE(64, Op.MLOAD(32)) - + Op.POP( - Op.CALL( - address=erc20_address, - value=0, - args_offset=28, - args_size=68, - ret_offset=0, - ret_size=0, - address_warm=True, - ) - ) - + Op.MSTORE(32, Op.ADD(Op.MLOAD(32), 1)) - ), - condition=Op.GT(Op.GAS, gas_threshold), - ) - - # Persist the final slot offset so the next tx continues - # from where this one left off. - cleanup = Op.SSTORE(slot_offset_key, Op.MLOAD(32)) - - # Contract Deployment - code = setup + sload_loop + transition + sstore_loop + cleanup - attack_contract_address = pre.deploy_contract( - code=code, - storage={slot_offset_key: 0}, - ) - - # Transaction Construction — no iteration count math. - # Each tx gets up to tx_gas_limit gas; the contract - # self-regulates via the GAS opcode. - access_list = [AccessList(address=erc20_address, storage_keys=[])] - intrinsic_gas_cost = fork.transaction_intrinsic_cost_calculator()( - access_list=access_list, - ) - - gas_remaining = gas_benchmark_value - txs = [] - while gas_remaining >= intrinsic_gas_cost + gas_threshold: - gas_limit = min(gas_remaining, tx_gas_limit) - txs.append( - Transaction( - gas_limit=gas_limit, - to=attack_contract_address, - sender=pre.fund_eoa(), - access_list=access_list, - ) - ) - gas_remaining -= gas_limit - - assert txs, "Gas loop produced zero transactions" - benchmark_test( - pre=pre, - blocks=[Block(txs=txs)], - skip_gas_used_validation=True, - expected_receipt_status=True, - ) From 739963d2b3e3884afc819bb565e54f5e6fcef96b Mon Sep 17 00:00:00 2001 From: pdobacz <5735525+pdobacz@users.noreply.github.com> Date: Tue, 18 Aug 2026 12:11:53 +0000 Subject: [PATCH 232/233] refactor(ci): release monad fixtures via the upstream workflow Drop the tag-push workflow in favour of dispatching release_fixtures.yaml. Co-Authored-By: Claude <claude-opus-5[1m]> --- .../workflows/release_fixture_feature.yaml | 144 ------------------ .github/workflows/release_fixtures.yaml | 19 +-- 2 files changed, 5 insertions(+), 158 deletions(-) delete mode 100644 .github/workflows/release_fixture_feature.yaml diff --git a/.github/workflows/release_fixture_feature.yaml b/.github/workflows/release_fixture_feature.yaml deleted file mode 100644 index 252396d9e3e..00000000000 --- a/.github/workflows/release_fixture_feature.yaml +++ /dev/null @@ -1,144 +0,0 @@ -name: Create Fixture Release - -on: - push: - tags: - - "tests-*@v*" - workflow_dispatch: - -jobs: - setup: - runs-on: ubuntu-24.04 - outputs: - build_matrix: ${{ steps.matrix.outputs.build_matrix }} - feature_name: ${{ steps.matrix.outputs.feature_name }} - combine_labels: ${{ steps.matrix.outputs.combine_labels }} - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - submodules: false - - uses: ./.github/actions/setup-uv - - - name: Generate build matrix - id: matrix - shell: bash - run: | - FEATURE_PREFIX="${GITHUB_REF_NAME//@*/}" - FEATURE_NAME="${FEATURE_PREFIX#tests-}" - uv run -q .github/scripts/generate_build_matrix.py "$FEATURE_NAME" >> "$GITHUB_OUTPUT" - - build: - name: fill (${{ matrix.label || matrix.feature }}) - needs: setup - runs-on: ubuntu-24.04 - timeout-minutes: 1440 - strategy: - fail-fast: true - matrix: - include: ${{ fromJson(needs.setup.outputs.build_matrix) }} - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - submodules: true - - - uses: ./.github/actions/build-fixtures - with: - release_name: ${{ matrix.feature }} - from_fork: ${{ matrix.from_fork }} - until_fork: ${{ matrix.until_fork }} - split_label: ${{ matrix.label }} - - combine: - name: combine (${{ needs.setup.outputs.feature_name }}) - needs: [setup, build] - if: needs.setup.outputs.combine_labels != '' - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - submodules: false - - uses: ./.github/actions/setup-uv - - name: Install pigz - run: sudo apt-get install -y pigz - - name: Download and merge split artifacts - shell: bash - run: | - mkdir -p combined - for label in ${{ needs.setup.outputs.combine_labels }}; do - echo "Downloading: fixtures__${label}" - if gh run download ${{ github.run_id }} -n "fixtures__${label}" --dir "split_artifacts/fixtures__${label}"; then - cp -r "split_artifacts/fixtures__${label}"/* combined/ - else - echo "No artifact for ${label} (no tests collected, skipping)" - fi - done - echo "Combined directory contents:" - find combined -maxdepth 3 -type d | head -30 || true - env: - GH_TOKEN: ${{ github.token }} - - name: Merge split index files - shell: bash - run: | - SPLIT_DIRS=() - for label in ${{ needs.setup.outputs.combine_labels }}; do - dir="split_artifacts/fixtures__${label}" - if [ -d "$dir" ]; then - SPLIT_DIRS+=("$dir") - fi - done - if [ ${#SPLIT_DIRS[@]} -gt 0 ]; then - uv run python .github/scripts/merge_index_files.py combined/.meta/index.json "${SPLIT_DIRS[@]}" - fi - - name: Free disk space - run: rm -rf split_artifacts/ - - name: Create release tarball - shell: bash - run: | - uv run -q .github/scripts/create_release_tarball.py combined fixtures_${{ needs.setup.outputs.feature_name }}.tar.gz - - name: Upload combined fixture tarball - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 - with: - name: fixtures_${{ needs.setup.outputs.feature_name }} - path: fixtures_${{ needs.setup.outputs.feature_name }}.tar.gz - - release: - runs-on: ubuntu-24.04 - needs: [setup, build, combine] - if: always() && needs.build.result == 'success' && (needs.combine.result == 'success' || needs.combine.result == 'skipped') && startsWith(github.ref, 'refs/tags/tests-') - permissions: - contents: write - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - submodules: false - fetch-depth: 0 - - - name: Download release artifacts - shell: bash - run: | - gh run download ${{ github.run_id }} -p "fixtures_*" --dir ./artifacts - rm -rf ./artifacts/fixtures__*/ - env: - GH_TOKEN: ${{ github.token }} - - - name: Draft release on EELS (canonical) - run: | - FEATURE_PREFIX="${TAG_NAME%%@*}" - PREV_TAG=$( - git tag --list "${FEATURE_PREFIX}@v*" --sort=-v:refname \ - | grep -v "^${TAG_NAME}$" \ - | head -n 1 \ - || true - ) - RELEASE_ARGS=(--draft --generate-notes) - if [ "$FEATURE_NAME" != "mainnet" ]; then - RELEASE_ARGS+=(--prerelease) - fi - if [ -n "$PREV_TAG" ]; then - RELEASE_ARGS+=(--notes-start-tag "$PREV_TAG") - fi - gh release create "$TAG_NAME" "${RELEASE_ARGS[@]}" ./artifacts/**/*.tar.gz - env: - TAG_NAME: ${{ github.ref_name }} - FEATURE_NAME: ${{ needs.setup.outputs.feature_name }} - GH_TOKEN: ${{ github.token }} diff --git a/.github/workflows/release_fixtures.yaml b/.github/workflows/release_fixtures.yaml index d56a9dae918..4f3c9a0dcc8 100644 --- a/.github/workflows/release_fixtures.yaml +++ b/.github/workflows/release_fixtures.yaml @@ -2,20 +2,11 @@ name: Create Fixture Release run-name: ${{ github.event_name == 'schedule' && 'Nightly Fill' || format('Create Fixture Release {0}@{1}{2}', inputs.feature, inputs.version, (inputs.cached || inputs.commit != '') && ' (cached)' || '') }} -# Scheduled runs fill the mainnet `tests` feature (all tests, all fixture -# formats, up to the latest mainnet fork -- no dev forks) through the exact -# release pipeline, but skip the `release` job, so no tag or draft release -# is created: a rotating, always-available artifact of the mainnet -# fixtures. The cron fires at 02:00 UTC: the self-hosted runners are past -# the EU/US daytime peaks and results are ready before the EU morning. -# -# A manual `tests` release can reuse the newest of those artifacts -# instead of refilling via the `cached` checkbox: `build` and `combine` -# are skipped and the `release` job drafts from the nightly's tarball, -# tagged at the commit the nightly built. Runs in minutes. +# Upstream also runs this on a nightly schedule to rehearse a mainnet +# `tests` release and to feed the `cached` release path. This fork releases +# the `monad` and `monad_runloop` features only, so the schedule is dropped +# and the `cached` / `commit` inputs fail fast on their `tests`-only guard. on: - schedule: - - cron: "0 2 * * *" workflow_dispatch: inputs: feature: @@ -141,7 +132,7 @@ jobs: name: fill (${{ matrix.label || matrix.feature }}) needs: setup if: needs.setup.outputs.run == 'true' - runs-on: [self-hosted-ghr, size-gigachungus-x64] + runs-on: ubuntu-24.04 timeout-minutes: 1440 strategy: # A release must be complete, so abort on the first failed range; a From 17ea9d4d501ba31d0c677017dd5d87e502ac60fd Mon Sep 17 00:00:00 2001 From: pdobacz <5735525+pdobacz@users.noreply.github.com> Date: Tue, 18 Aug 2026 12:11:53 +0000 Subject: [PATCH 233/233] chore(ci): drop --suppress-no-test-exit-code from the monad features Phase 1 allows NO_TESTS_COLLECTED and build-fixtures already tolerates exit 5. Co-Authored-By: Claude <claude-opus-5[1m]> --- .github/configs/feature.yaml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/configs/feature.yaml b/.github/configs/feature.yaml index f91ac9bbabf..7fca18fd9ef 100644 --- a/.github/configs/feature.yaml +++ b/.github/configs/feature.yaml @@ -5,12 +5,9 @@ mainnet: monad: evm-type: eels - # --suppress-no-test-exit-code works around a problem where multi-phase fill - # (triggered by tarball output) fails to proceed on no tests processed - # in 1st phase (exit code 5) - fill-params: --suppress-no-test-exit-code -m blockchain_test --from=MONAD_EIGHT --until=MONAD_TEN --chain-id=143 -k "not invalid_header" + fill-params: -m blockchain_test --from=MONAD_EIGHT --until=MONAD_TEN --chain-id=143 -k "not invalid_header" monad_runloop: evm-type: eels # Like `monad`, but `--monad-runloop` and eestnet chain id `30143` - fill-params: --suppress-no-test-exit-code -m blockchain_test --from=MONAD_EIGHT --until=MONAD_TEN --chain-id=30143 --monad-runloop -k "not invalid_header" + fill-params: -m blockchain_test --from=MONAD_EIGHT --until=MONAD_TEN --chain-id=30143 --monad-runloop -k "not invalid_header"